From 890689a718673fc6c71717d5ab19b848207526bf Mon Sep 17 00:00:00 2001 From: liruifengv Date: Wed, 27 May 2026 17:43:35 +0800 Subject: [PATCH 1/3] feat(tui): add /export-debug-zip slash command Add a new built-in slash command that exports the current session as a debug ZIP archive directly from the TUI, mirroring the existing `kimi export` CLI behavior. Extract `detectShellEnvironment` into a shared utility to eliminate duplication between the CLI export handler and the new TUI command. --- apps/kimi-code/src/cli/sub/export.ts | 18 +---------- apps/kimi-code/src/tui/commands/registry.ts | 6 ++++ apps/kimi-code/src/tui/kimi-tui.ts | 30 +++++++++++++++++++ apps/kimi-code/src/utils/process/shell-env.ts | 18 +++++++++++ .../test/tui/commands/registry.test.ts | 1 + 5 files changed, 56 insertions(+), 17 deletions(-) create mode 100644 apps/kimi-code/src/utils/process/shell-env.ts diff --git a/apps/kimi-code/src/cli/sub/export.ts b/apps/kimi-code/src/cli/sub/export.ts index 6c5caf10cf..f779cef583 100644 --- a/apps/kimi-code/src/cli/sub/export.ts +++ b/apps/kimi-code/src/cli/sub/export.ts @@ -27,6 +27,7 @@ import { CLI_SHUTDOWN_TIMEOUT_MS, CLI_UI_MODE } from '#/constant/app'; import { createCliTelemetryBootstrap, initializeCliTelemetry } from '#/cli/telemetry'; import { detectInstallSource } from '#/cli/update/source'; import { createKimiCodeHostIdentity } from '#/cli/version'; +import { detectShellEnvironment } from '#/utils/process/shell-env'; interface WritableLike { write(chunk: string): boolean; @@ -231,23 +232,6 @@ async function confirmPreviousSession(summary: PreviousSessionSummary): Promise< } } -function detectMultiplexer(): string | undefined { - if (process.env['TMUX']) return 'tmux'; - if (process.env['STY']) return 'screen'; - if (process.env['ZELLIJ']) return 'zellij'; - return undefined; -} - -function detectShellEnvironment(): ShellEnvironment { - return { - term: process.env['TERM'] || undefined, - termProgram: process.env['TERM_PROGRAM'] || undefined, - termProgramVersion: process.env['TERM_PROGRAM_VERSION'] || undefined, - multiplexer: detectMultiplexer(), - shell: process.env['SHELL'] || undefined, - }; -} - function errorMessage(error: unknown): string { return error instanceof Error ? error.message : String(error); } diff --git a/apps/kimi-code/src/tui/commands/registry.ts b/apps/kimi-code/src/tui/commands/registry.ts index 84e432b3ae..8184ea5216 100644 --- a/apps/kimi-code/src/tui/commands/registry.ts +++ b/apps/kimi-code/src/tui/commands/registry.ts @@ -145,6 +145,12 @@ export const BUILTIN_SLASH_COMMANDS = [ description: 'Connect a provider from a model catalog', priority: 40, }, + { + name: 'export-debug-zip', + aliases: [], + description: 'Export current session as a debug ZIP archive', + priority: 40, + }, { name: 'exit', aliases: ['quit', 'q'], diff --git a/apps/kimi-code/src/tui/kimi-tui.ts b/apps/kimi-code/src/tui/kimi-tui.ts index 0e71ef4eef..bda000f6d3 100644 --- a/apps/kimi-code/src/tui/kimi-tui.ts +++ b/apps/kimi-code/src/tui/kimi-tui.ts @@ -98,6 +98,8 @@ import type { import chalk from 'chalk'; import type { CLIOptions } from '#/cli/options'; +import { detectInstallSource } from '#/cli/update/source'; +import { detectShellEnvironment } from '#/utils/process/shell-env'; import { MigrationScreenComponent, type MigrationScreenResult } from '#/migration/index'; import { ClipboardMediaError, readClipboardMedia } from '#/utils/clipboard/clipboard-image'; import type { GitLsFilesCache } from '#/utils/git/git-ls-files'; @@ -1586,6 +1588,9 @@ export class KimiTUI { case 'fork': await this.handleForkCommand(args); return; + case 'export-debug-zip': + await this.handleExportDebugZipCommand(); + return; case 'login': await this.handleLoginCommand(); return; @@ -5524,6 +5529,31 @@ export class KimiTUI { } } + private async handleExportDebugZipCommand(): Promise { + const session = this.session; + if (session === undefined) { + this.showError(NO_ACTIVE_SESSION_MESSAGE); + return; + } + + this.showStatus('Exporting session…'); + try { + const installSource = await detectInstallSource(); + const shellEnv = detectShellEnvironment(); + const result = await this.harness.exportSession({ + id: session.id, + version: this.state.appState.version, + installSource, + shellEnv, + includeGlobalLog: true, + }); + this.showNotice('Export complete', result.zipPath); + } catch (error) { + const msg = formatErrorMessage(error); + this.showError(`Failed to export session: ${msg}`); + } + } + private forkSourceTitle(session: Session): string { const currentTitle = this.state.appState.sessionTitle?.trim(); if (currentTitle !== undefined && currentTitle.length > 0) return currentTitle; diff --git a/apps/kimi-code/src/utils/process/shell-env.ts b/apps/kimi-code/src/utils/process/shell-env.ts new file mode 100644 index 0000000000..3e63ac5607 --- /dev/null +++ b/apps/kimi-code/src/utils/process/shell-env.ts @@ -0,0 +1,18 @@ +import type { ShellEnvironment } from '@moonshot-ai/kimi-code-sdk'; + +function detectMultiplexer(): string | undefined { + if (process.env['TMUX']) return 'tmux'; + if (process.env['STY']) return 'screen'; + if (process.env['ZELLIJ']) return 'zellij'; + return undefined; +} + +export function detectShellEnvironment(): ShellEnvironment { + return { + term: process.env['TERM'] || undefined, + termProgram: process.env['TERM_PROGRAM'] || undefined, + termProgramVersion: process.env['TERM_PROGRAM_VERSION'] || undefined, + multiplexer: detectMultiplexer(), + shell: process.env['SHELL'] || undefined, + }; +} diff --git a/apps/kimi-code/test/tui/commands/registry.test.ts b/apps/kimi-code/test/tui/commands/registry.test.ts index 3685857b87..74737fb5d8 100644 --- a/apps/kimi-code/test/tui/commands/registry.test.ts +++ b/apps/kimi-code/test/tui/commands/registry.test.ts @@ -81,6 +81,7 @@ describe('built-in slash command registry', () => { 'compact', 'editor', 'exit', + 'export-debug-zip', 'fork', 'help', 'init', From ba234ef7962fe69f3c9f7ed412f506017bfbb49c Mon Sep 17 00:00:00 2001 From: liruifengv Date: Wed, 27 May 2026 17:44:33 +0800 Subject: [PATCH 2/3] chore: add changeset for /export-debug-zip --- .changeset/tui-export-debug-zip.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tui-export-debug-zip.md diff --git a/.changeset/tui-export-debug-zip.md b/.changeset/tui-export-debug-zip.md new file mode 100644 index 0000000000..925adb0152 --- /dev/null +++ b/.changeset/tui-export-debug-zip.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/kimi-code": minor +--- + +Add `/export-debug-zip` slash command to export the current session as a debug ZIP archive directly from the TUI. From 0c01d71bbfdd90a6bf7ba5c537c30740a689be4b Mon Sep 17 00:00:00 2001 From: liruifengv Date: Wed, 27 May 2026 17:51:33 +0800 Subject: [PATCH 3/3] refactor: extract toTerminalHyperlink into shared utility Make the exported ZIP path clickable in terminals that support OSC 8 hyperlinks (iTerm2, Terminal.app, VS Code, etc.). Also deduplicate the helper that was copy-pasted in plan-box.ts. --- apps/kimi-code/src/tui/components/messages/plan-box.ts | 6 ++---- apps/kimi-code/src/tui/kimi-tui.ts | 5 ++++- apps/kimi-code/src/utils/terminal-hyperlink.ts | 3 +++ 3 files changed, 9 insertions(+), 5 deletions(-) create mode 100644 apps/kimi-code/src/utils/terminal-hyperlink.ts diff --git a/apps/kimi-code/src/tui/components/messages/plan-box.ts b/apps/kimi-code/src/tui/components/messages/plan-box.ts index b79b7abb7d..c1cafd48da 100644 --- a/apps/kimi-code/src/tui/components/messages/plan-box.ts +++ b/apps/kimi-code/src/tui/components/messages/plan-box.ts @@ -11,6 +11,8 @@ import type { Component, MarkdownTheme } from '@earendil-works/pi-tui'; import { Markdown, visibleWidth } from '@earendil-works/pi-tui'; import chalk from 'chalk'; +import { toTerminalHyperlink } from '#/utils/terminal-hyperlink'; + const LEFT_MARGIN = 2; // two-space indent matching other tool call children const SIDE_PADDING = 1; // space between the │ and the content on each side const TITLE_PREFIX = ' plan: '; @@ -133,7 +135,3 @@ export class PlanBoxComponent implements Component { return ` · ${chalk.hex(status.colorHex)(status.label)}`; } } - -function toTerminalHyperlink(text: string, url: string): string { - return `\u001B]8;;${url}\u0007${text}\u001B]8;;\u0007`; -} diff --git a/apps/kimi-code/src/tui/kimi-tui.ts b/apps/kimi-code/src/tui/kimi-tui.ts index bda000f6d3..56bd5e2182 100644 --- a/apps/kimi-code/src/tui/kimi-tui.ts +++ b/apps/kimi-code/src/tui/kimi-tui.ts @@ -10,6 +10,7 @@ import { writeFileSync } from 'node:fs'; import { release as osRelease, type as osType } from 'node:os'; import { join } from 'node:path'; +import { pathToFileURL } from 'node:url'; import { Container, @@ -100,6 +101,7 @@ import chalk from 'chalk'; import type { CLIOptions } from '#/cli/options'; import { detectInstallSource } from '#/cli/update/source'; import { detectShellEnvironment } from '#/utils/process/shell-env'; +import { toTerminalHyperlink } from '#/utils/terminal-hyperlink'; import { MigrationScreenComponent, type MigrationScreenResult } from '#/migration/index'; import { ClipboardMediaError, readClipboardMedia } from '#/utils/clipboard/clipboard-image'; import type { GitLsFilesCache } from '#/utils/git/git-ls-files'; @@ -5547,7 +5549,8 @@ export class KimiTUI { shellEnv, includeGlobalLog: true, }); - this.showNotice('Export complete', result.zipPath); + const linked = toTerminalHyperlink(result.zipPath, pathToFileURL(result.zipPath).href); + this.showNotice('Export complete', linked); } catch (error) { const msg = formatErrorMessage(error); this.showError(`Failed to export session: ${msg}`); diff --git a/apps/kimi-code/src/utils/terminal-hyperlink.ts b/apps/kimi-code/src/utils/terminal-hyperlink.ts new file mode 100644 index 0000000000..43d27f0a32 --- /dev/null +++ b/apps/kimi-code/src/utils/terminal-hyperlink.ts @@ -0,0 +1,3 @@ +export function toTerminalHyperlink(text: string, url: string): string { + return `\u001B]8;;${url}\u0007${text}\u001B]8;;\u0007`; +}