chore: add KCD client for test (#1230)

* feat(kimi-desktop): add Electron desktop client wrapping kimi-web

New apps/kimi-desktop — a thin Electron shell + process manager around
the existing web UI. It reuses kimi-code's shared daemon: it runs the
bundled SEA's `server run` (the same ensureDaemon reuse-or-spawn flow as
`kimi web`), reads ~/.kimi-code/server/lock for the real origin, and
loads the SEA-served kimi-web same-origin. The daemon is left running on
quit so the CLI / browser / TUI keep sharing it.

- main process: ensure-server (run SEA, read lock, confirm healthz),
  sea-path (dev vs packaged), window + native menu + window-state +
  loading/error screens
- packaging: electron-builder config; before-pack stages the
  matching-platform SEA into <resources>/bin/<target>
- CI: desktop-build workflow builds unsigned mac/win/linux installers,
  each runner building its own SEA
- workspace wiring: register in flake.nix, allow electron postinstall
  (onlyBuiltDependencies), root dev:desktop + typecheck entries

v1 is unsigned, default icon, no auto-update.

* feat(kimi-desktop): sign + notarize macOS builds

Unsigned macOS builds are blocked by Gatekeeper ("app is damaged") once
transferred to another Mac. Add Developer ID signing + Apple notarization,
mirroring the TUI native build:

- build/entitlements.mac.plist: hardened-runtime entitlements (allow-jit,
  disable-library-validation for koffi/clipboard, etc.) applied to the app
  and — via entitlementsInherit — the nested SEA backend
- electron-builder.config.cjs (replaces .yml): hardenedRuntime + entitlements;
  signing and notarization are env-driven (CSC_* + KIMI_DESKTOP_NOTARIZE +
  APPLE_API_* ), so the same config builds unsigned locally or signed+notarized
- desktop-build CI: sign-macos input reuses the existing macos-keychain-setup
  action + APPLE_* secrets, notarizes via the notary API key
- README: document signing, the Developer-ID requirement, and the
  "don't rename the .app" gotcha

Verified locally that electron-builder signs both the app and the nested SEA
with hardened runtime + the entitlements, and the signed app still launches and
serves the web UI. Notarization itself needs a Developer ID cert (CI / a machine
that has one).

* feat(kimi-desktop): rename product to Kimi Code Desktop

productName / window title / menu label / error-screen text all use
"Kimi Code Desktop" so the bundle name matches its executable (a
mismatch from manual renaming is itself reported as "damaged").

* ci(kimi-desktop): build and attach desktop installers in the release pipeline

Make desktop-build.yml reusable (workflow_call) and invoke it from the
release workflow, mirroring the native-build pipeline, so each release
also attaches signed+notarized macOS, Windows and Linux desktop
installers to the GitHub Release.

* feat(kimi-desktop): brand the desktop as an internal testing build

- Add an inline 'internal testing build' tag next to the Kimi Code brand
  in the sidebar header, shown only inside the desktop app.
- Use a hidden native title bar on macOS with the traffic lights folded
  into the sidebar header, and pin the window title to the product name.
- Ship the Kimi app icon for macOS and Linux builds.

Desktop detection is runtime (a query hint from the Electron shell,
persisted in sessionStorage) so the branding appears even when the
window is served by an already-running shared daemon.

* docs(kimi-desktop): update v1 scope now that the app icon ships

* feat(kimi-desktop): add the Kimi app icon for Windows builds

* chore(nix): update pnpmDeps hash after lockfile refresh

* ci(kimi-desktop): build desktop on release but do not attach to GitHub Release

The desktop build is an internal-testing artifact (branded as such), so
keep it as a CI artifact for internal download instead of publishing it
to the public GitHub Release.

* chore(kimi-desktop): mark installers as internal pre-release builds

Rename the packaged artifacts to KCD-Internal-<version>-<arch>.<ext> and
bump the version to the 0.1.1-internal.0 pre-release, so a leaked or
forwarded installer file is not mistaken for an official public release.

* feat(kimi-desktop): strengthen the internal-build tag wording

Change the sidebar tag to 'Internal testing · do not distribute' /
'内部测试 · 禁止外传' so the no-distribution intent is explicit.

* feat(kimi-desktop): tweak internal-build tag to '仅供内部测试'

* fix(kimi-desktop): pass the server token to the web UI on launch

Read the daemon's persistent bearer token from <KIMI_CODE_HOME>/server.token
and carry it in the URL fragment (#token=), matching how 'kimi web' opens
the Web UI. Without this, a fresh launch (no saved credential) boots the
web UI without a token, hits 401, and falls into the manual token dialog
even though the desktop started the daemon itself.

Addresses review feedback on the desktop URL.
This commit is contained in:
qer 2026-06-30 21:46:51 +08:00 committed by GitHub
parent ceb27f5e44
commit 210cedb3bf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 3274 additions and 26 deletions

161
.github/workflows/desktop-build.yml vendored Normal file
View file

@ -0,0 +1,161 @@
name: desktop-build
# Builds the Kimi Desktop (Electron) installers for macOS, Windows and Linux.
# Each runner builds the matching-platform SEA backend first, then packages it
# with electron-builder.
#
# macOS is signed with a Developer ID certificate + notarized (so it opens on
# any Mac without the "app is damaged" Gatekeeper block) when `sign-macos` is
# true and the Apple secrets are configured. Windows/Linux ship unsigned in v1.
#
# Triggered two ways:
# - workflow_dispatch: manual ad-hoc builds from the Actions tab.
# - workflow_call: called by release.yml to attach installers to a release.
on:
workflow_dispatch:
inputs:
sign-macos:
description: 'Sign + notarize macOS (needs Apple secrets)'
required: false
type: boolean
default: true
retention-days:
description: 'Artifact retention in days'
required: false
type: number
default: 5
upload-artifact-prefix:
description: 'Prefix for uploaded artifact name'
required: false
type: string
default: 'kimi-desktop'
workflow_call:
inputs:
sign-macos:
description: 'Sign + notarize macOS (needs Apple secrets)'
required: false
type: boolean
default: false
retention-days:
description: 'Artifact retention in days'
required: false
type: number
default: 7
upload-artifact-prefix:
description: 'Prefix for uploaded artifact name'
required: false
type: string
default: 'kimi-desktop'
secrets:
APPLE_CERTIFICATE_P12:
required: false
APPLE_CERTIFICATE_PASSWORD:
required: false
APPLE_NOTARIZATION_KEY_P8:
required: false
APPLE_NOTARIZATION_KEY_ID:
required: false
APPLE_NOTARIZATION_ISSUER_ID:
required: false
permissions:
contents: read
jobs:
desktop:
name: Desktop installer (${{ matrix.target }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: macos-15
target: darwin-arm64
- os: macos-15-intel
target: darwin-x64
- os: windows-2025-vs2026
target: win32-x64
- os: ubuntu-24.04
target: linux-x64
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup pnpm
uses: pnpm/action-setup@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build Kimi web assets
# The SEA blob embeds apps/kimi-code/dist-web; build the web app and
# stage its assets before producing the native executable.
# KIMI_WEB_DESKTOP=1 bakes the internal-build banner into the web bundle
# (see apps/kimi-web/src/components/InternalBuildBanner.vue); only the
# desktop sets this flag, so the CLI `kimi web` stays banner-free.
env:
KIMI_WEB_DESKTOP: '1'
run: |
pnpm --filter @moonshot-ai/kimi-web run build
node apps/kimi-code/scripts/copy-web-assets.mjs
- name: Build native executable (local profile)
# The Electron app signs the SEA itself (electron-builder, inside-out),
# so the native build stays unsigned here.
run: pnpm --filter @moonshot-ai/kimi-code run build:native:sea
- name: Setup macOS keychain (Developer ID)
if: runner.os == 'macOS' && inputs.sign-macos
uses: ./.github/actions/macos-keychain-setup
with:
certificate-p12: ${{ secrets.APPLE_CERTIFICATE_P12 }}
certificate-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
- name: Prepare notarization API key (macOS)
if: runner.os == 'macOS' && inputs.sign-macos
shell: bash
env:
APPLE_NOTARIZATION_KEY_P8: ${{ secrets.APPLE_NOTARIZATION_KEY_P8 }}
run: |
set -euo pipefail
key_path="$RUNNER_TEMP/notary-AuthKey.p8"
printf '%s' "$APPLE_NOTARIZATION_KEY_P8" | base64 -d > "$key_path"
echo "APPLE_API_KEY=$key_path" >> "$GITHUB_ENV"
- name: Build & package desktop app
shell: bash
env:
# macOS signing is driven by env: when sign-macos, electron-builder
# signs with the keychain's Developer ID and notarizes via the notary
# API key; otherwise it builds unsigned.
CSC_IDENTITY_AUTO_DISCOVERY: ${{ (runner.os == 'macOS' && inputs.sign-macos) && 'true' || 'false' }}
CSC_KEYCHAIN: ${{ env.APPLE_KEYCHAIN_PATH }}
CSC_NAME: ${{ env.APPLE_SIGNING_IDENTITY }}
KIMI_DESKTOP_NOTARIZE: ${{ (runner.os == 'macOS' && inputs.sign-macos) && 'true' || 'false' }}
APPLE_API_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
APPLE_API_ISSUER: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
run: pnpm --filter @moonshot-ai/kimi-desktop run dist
- name: Cleanup macOS keychain
if: always() && runner.os == 'macOS' && inputs.sign-macos
uses: ./.github/actions/macos-keychain-cleanup
- name: Upload installers
uses: actions/upload-artifact@v7
with:
name: ${{ inputs.upload-artifact-prefix }}-${{ matrix.target }}
retention-days: ${{ inputs.retention-days }}
path: |
apps/kimi-desktop/dist-app/*.dmg
apps/kimi-desktop/dist-app/*.zip
apps/kimi-desktop/dist-app/*.exe
apps/kimi-desktop/dist-app/*.AppImage
apps/kimi-desktop/dist-app/*.deb
if-no-files-found: ignore

View file

@ -97,6 +97,22 @@ jobs:
APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
desktop-artifacts:
name: Desktop release artifact
needs: release
if: needs.release.outputs.kimi_native_release == 'true'
uses: ./.github/workflows/desktop-build.yml
with:
upload-artifact-prefix: kimi-desktop
retention-days: 7
sign-macos: true
secrets:
APPLE_CERTIFICATE_P12: ${{ secrets.APPLE_CERTIFICATE_P12 }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_NOTARIZATION_KEY_P8: ${{ secrets.APPLE_NOTARIZATION_KEY_P8 }}
APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
publish-native-assets:
name: Publish native release assets
needs: