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. 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/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 0e71ef4eef..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, @@ -98,6 +99,9 @@ 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 { 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'; @@ -1586,6 +1590,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 +5531,32 @@ 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, + }); + 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}`); + } + } + 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/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`; +} 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',