diff --git a/apps/kimi-code/src/cli/sub/server/lifecycle.ts b/apps/kimi-code/src/cli/sub/server/lifecycle.ts index 27a847e99..eb1349c99 100644 --- a/apps/kimi-code/src/cli/sub/server/lifecycle.ts +++ b/apps/kimi-code/src/cli/sub/server/lifecycle.ts @@ -32,7 +32,6 @@ import { } from './shared'; export interface InstallCliOptions { - host?: string; port?: string; logLevel?: string; force?: boolean; @@ -63,7 +62,6 @@ export function addLifecycleCommands(parent: Command, deps: LifecycleCommandDeps parent .command('install') .description('Install the Kimi server as an OS-managed service (launchd/systemd/schtasks).') - .option('--host ', `Bind host (default ${DEFAULT_SERVER_HOST})`, DEFAULT_SERVER_HOST) .option('--port ', `Bind port (default ${DEFAULT_SERVER_PORT})`, String(DEFAULT_SERVER_PORT)) .option( '--log-level ', @@ -76,7 +74,7 @@ export function addLifecycleCommands(parent: Command, deps: LifecycleCommandDeps .action(async (opts: InstallCliOptions) => { await runLifecycle(deps, opts.json === true, async (mgr) => { const args: InstallArgs = { - host: opts.host ?? DEFAULT_SERVER_HOST, + host: DEFAULT_SERVER_HOST, port: parsePort(opts.port, '--port', DEFAULT_SERVER_PORT), logLevel: parseLogLevel(opts.logLevel), force: opts.force === true, diff --git a/apps/kimi-code/src/cli/sub/server/run.ts b/apps/kimi-code/src/cli/sub/server/run.ts index 6b58e996b..f46bc7a6a 100644 --- a/apps/kimi-code/src/cli/sub/server/run.ts +++ b/apps/kimi-code/src/cli/sub/server/run.ts @@ -9,7 +9,9 @@ * registered in `./web-alias.ts`. */ +import chalk from 'chalk'; import type { Command } from 'commander'; +import { truncateToWidth, visibleWidth } from '@earendil-works/pi-tui'; import { join } from 'node:path'; @@ -21,11 +23,12 @@ import { } from '@moonshot-ai/server'; import { getNativeWebAssetsDir } from '#/native/web-assets'; +import { darkColors } from '#/tui/theme/colors'; import { openUrl as defaultOpenUrl } from '#/utils/open-url'; import { createKimiCodeHostIdentity, getHostPackageRoot, getVersion } from '../../version'; import { - DEFAULT_LOG_LEVEL, + DEFAULT_FOREGROUND_LOG_LEVEL, DEFAULT_SERVER_HOST, DEFAULT_SERVER_PORT, parseServerOptions, @@ -36,6 +39,7 @@ import { } from './shared'; const WEB_ASSETS_DIR = 'dist-web'; +const READY_PANEL_WIDTH = 72; export interface RunCliOptions extends ServerCliOptions { open?: boolean; @@ -52,11 +56,6 @@ export interface RunCommandDeps { /** Build the `run` subcommand, mounted under a parent (`server` or top-level). */ export function buildRunCommand(cmd: Command, options: { defaultOpen: boolean }): Command { return cmd - .option( - '--host ', - `Bind host (default ${DEFAULT_SERVER_HOST})`, - DEFAULT_SERVER_HOST, - ) .option( '--port ', `Bind port (default ${DEFAULT_SERVER_PORT})`, @@ -64,8 +63,7 @@ export function buildRunCommand(cmd: Command, options: { defaultOpen: boolean }) ) .option( '--log-level ', - `Log level: ${VALID_LOG_LEVELS.join('|')} (default ${DEFAULT_LOG_LEVEL})`, - DEFAULT_LOG_LEVEL, + `Enable foreground logs at level: ${VALID_LOG_LEVELS.join('|')}. Omit to keep logs off.`, ) .option( '--debug-endpoints', @@ -100,6 +98,7 @@ export async function handleRunCommand( ): Promise { const parsed = parseServerOptions(opts); let outcome: { origin: string }; + const startedAt = Date.now(); try { outcome = await deps.startServerForeground(parsed); } catch (error) { @@ -112,7 +111,12 @@ export async function handleRunCommand( } throw error; } - deps.stdout.write(`Kimi server: ${outcome.origin}\n`); + const readyMs = Date.now() - startedAt; + deps.stdout.write( + parsed.logLevel === DEFAULT_FOREGROUND_LOG_LEVEL + ? formatForegroundReadyBanner(outcome.origin, readyMs) + : `Kimi server: ${outcome.origin}\n`, + ); if (opts.open === true) { deps.openUrl(outcome.origin); } @@ -183,7 +187,7 @@ function describeAlreadyRunning( mode, pid: existing.pid, url: serverOrigin(host === '0.0.0.0' ? DEFAULT_SERVER_HOST : host, status?.port ?? existing.port), - stopCommand: mode === 'background' ? 'kimi server stop' : foregroundStopCommand(existing.pid), + stopCommand: mode === 'background' ? 'kimi server stop' : formatForegroundStopCommand(existing.pid), }; } @@ -197,9 +201,12 @@ function isBackgroundServer( return status.installed; } -function foregroundStopCommand(pid: number): string { - if (process.platform === 'win32') return `taskkill /PID ${String(pid)} /T /F`; - return 'pkill -f "kimi server run"'; +export function formatForegroundStopCommand( + pid: number, + platform: NodeJS.Platform = process.platform, +): string { + if (platform === 'win32') return `taskkill /PID ${String(pid)} /T /F`; + return `kill -TERM ${String(pid)}`; } function formatAlreadyRunning(details: AlreadyRunningDetails): string { @@ -211,6 +218,61 @@ function formatAlreadyRunning(details: AlreadyRunningDetails): string { ].join('\n'); } +function formatForegroundReadyBanner(origin: string, readyMs: number): string { + const primary = (text: string): string => chalk.hex(darkColors.primary)(text); + const title = (text: string): string => chalk.bold.hex(darkColors.primary)(text); + const dim = (text: string): string => chalk.hex(darkColors.textDim)(text); + const muted = (text: string): string => chalk.hex(darkColors.textMuted)(text); + const label = (text: string): string => chalk.bold.hex(darkColors.textDim)(text); + const url = chalk.hex(darkColors.accent)(displayOrigin(origin)); + const width = READY_PANEL_WIDTH; + const innerWidth = width - 4; + const pad = ' '; + + const logo = ['▐█▛█▛█▌', '▐█████▌'] as const; + const logoWidth = Math.max(...logo.map((row) => visibleWidth(row))); + const gap = ' '; + const textWidth = innerWidth - logoWidth - gap.length; + const headerLines = [ + primary(logo[0].padEnd(logoWidth)) + + gap + + truncateToWidth(title('Kimi server ready'), textWidth, '…'), + primary(logo[1].padEnd(logoWidth)) + + gap + + truncateToWidth(dim('Local web UI is available from this machine.'), textWidth, '…'), + ]; + const infoLines = [ + label('URL: ') + url, + label('Network: ') + muted('local only'), + label('Logs: ') + muted('off') + dim(' use --log-level info to enable'), + label('Stop: ') + muted('Ctrl+C'), + label('Ready: ') + muted(`${String(Math.max(0, readyMs))} ms`), + label('Version: ') + muted(getVersion()), + ]; + const contentLines = [...headerLines, '', ...infoLines]; + + const lines = [ + '', + primary('╭' + '─'.repeat(width - 2) + '╮'), + primary('│') + ' '.repeat(width - 2) + primary('│'), + ]; + + for (const content of contentLines) { + const truncated = truncateToWidth(content, innerWidth, '…'); + const rightPad = Math.max(0, innerWidth - visibleWidth(truncated)); + lines.push(primary('│') + pad + truncated + ' '.repeat(rightPad) + primary('│')); + } + + lines.push(primary('│') + ' '.repeat(width - 2) + primary('│')); + lines.push(primary('╰' + '─'.repeat(width - 2) + '╯')); + lines.push(''); + return lines.join('\n'); +} + +function displayOrigin(origin: string): string { + return origin.endsWith('/') ? origin : `${origin}/`; +} + const DEFAULT_RUN_COMMAND_DEPS: RunCommandDeps = { startServerForeground, getServiceStatus: async () => { diff --git a/apps/kimi-code/src/cli/sub/server/shared.ts b/apps/kimi-code/src/cli/sub/server/shared.ts index 6304ba374..6127b98d7 100644 --- a/apps/kimi-code/src/cli/sub/server/shared.ts +++ b/apps/kimi-code/src/cli/sub/server/shared.ts @@ -12,6 +12,7 @@ export const DEFAULT_SERVER_PORT = 7878; export const DEFAULT_SERVER_ORIGIN = serverOrigin(DEFAULT_SERVER_HOST, DEFAULT_SERVER_PORT); export const DEFAULT_LOG_LEVEL: ServerLogLevel = 'info'; +export const DEFAULT_FOREGROUND_LOG_LEVEL: ServerLogLevel = 'silent'; export const VALID_LOG_LEVELS: readonly ServerLogLevel[] = [ 'fatal', @@ -43,7 +44,7 @@ export function parseServerOptions(opts: ServerCliOptions): ParsedServerOptions return { host: opts.host ?? DEFAULT_SERVER_HOST, port: parsePort(opts.port, '--port', DEFAULT_SERVER_PORT), - logLevel: parseLogLevel(opts.logLevel), + logLevel: parseLogLevel(opts.logLevel ?? DEFAULT_FOREGROUND_LOG_LEVEL), debugEndpoints: opts.debugEndpoints === true, swagger: opts.swagger === true, }; @@ -84,7 +85,9 @@ export function normalizeServerOrigin(value: string): string { /** Single probe of `/api/v1/healthz`. Returns true if the response envelope reports `code: 0`. */ export async function isServerHealthy(origin: string, timeoutMs: number): Promise { const controller = new AbortController(); - const timeout = setTimeout(() => controller.abort(), timeoutMs); + const timeout = setTimeout(() => { + controller.abort(); + }, timeoutMs); try { const response = await fetch(`${origin}/api/v1/healthz`, { signal: controller.signal, @@ -123,7 +126,9 @@ export async function waitForServerHealthy(origin: string, timeoutMs: number): P */ export async function ensureServerWebReady(origin: string): Promise { const controller = new AbortController(); - const timeout = setTimeout(() => controller.abort(), 3000); + const timeout = setTimeout(() => { + controller.abort(); + }, 3000); try { const response = await fetch(`${origin}/`, { headers: { accept: 'text/html' }, diff --git a/apps/kimi-code/test/cli/server/server.test.ts b/apps/kimi-code/test/cli/server/server.test.ts index dab744a2e..7820dbb7f 100644 --- a/apps/kimi-code/test/cli/server/server.test.ts +++ b/apps/kimi-code/test/cli/server/server.test.ts @@ -10,11 +10,17 @@ import { readFileSync } from 'node:fs'; +import chalk, { Chalk } from 'chalk'; import { Command } from 'commander'; import { describe, expect, it, vi } from 'vitest'; import { registerServerCommand } from '#/cli/sub/server'; import { addLifecycleCommands } from '#/cli/sub/server/lifecycle'; +import { darkColors } from '#/tui/theme/colors'; + +function stripAnsi(text: string): string { + return text.replaceAll(/\u001B\[[0-9;]*m/g, ''); +} function makeProgram(): Command { // `commander` exitOverride avoids killing the test runner when --help/error fires. @@ -40,14 +46,14 @@ describe('kimi server', () => { expect(subs).toEqual(['install', 'restart', 'run', 'start', 'status', 'stop', 'uninstall']); }); - it('`server run` exposes --host, --port, --log-level, --debug-endpoints, --swagger, --open', () => { + it('`server run` exposes local-only foreground options', () => { const program = makeProgram(); const run = program.commands .find((c) => c.name() === 'server') ?.commands.find((c) => c.name() === 'run'); expect(run).toBeDefined(); const longs = run!.options.map((o) => o.long).filter(Boolean); - expect(longs).toContain('--host'); + expect(longs).not.toContain('--host'); expect(longs).toContain('--port'); expect(longs).toContain('--log-level'); expect(longs).toContain('--debug-endpoints'); @@ -56,14 +62,14 @@ describe('kimi server', () => { expect(longs).toContain('--open'); }); - it('`server install` exposes --host, --port, --log-level, --force, --no-open, --json', () => { + it('`server install` exposes local-only service options', () => { const program = makeProgram(); const install = program.commands .find((c) => c.name() === 'server') ?.commands.find((c) => c.name() === 'install'); expect(install).toBeDefined(); const longs = install!.options.map((o) => o.long).filter(Boolean); - expect(longs).toContain('--host'); + expect(longs).not.toContain('--host'); expect(longs).toContain('--port'); expect(longs).toContain('--log-level'); expect(longs).toContain('--force'); @@ -78,7 +84,7 @@ describe('kimi server', () => { const longs = web!.options.map((o) => o.long).filter(Boolean); // web defaults to opening → the option is the negative form --no-open expect(longs).toContain('--no-open'); - expect(longs).toContain('--host'); + expect(longs).not.toContain('--host'); expect(longs).toContain('--port'); }); }); @@ -148,7 +154,7 @@ describe('`kimi server` lifecycle handles unavailable service managers', () => { ok: false, action: 'unavailable', platform: 'linux', - message: expect.stringContaining('server run --host 0.0.0.0'), + message: expect.stringContaining('server run --port '), }); }); }); @@ -268,7 +274,7 @@ describe('`kimi server` lifecycle output', () => { }); describe('`kimi server run` already-running handling', () => { - it('passes --swagger through to foreground startup options', async () => { + it('defaults foreground logs off and passes --swagger through to startup options', async () => { const { handleRunCommand } = await import('#/cli/sub/server/run'); let parsed: unknown; @@ -294,7 +300,116 @@ describe('`kimi server run` already-running handling', () => { }, ); - expect(parsed).toMatchObject({ swagger: true }); + expect(parsed).toMatchObject({ logLevel: 'silent', swagger: true }); + }); + + it('enables foreground logs only when --log-level is provided', async () => { + const { handleRunCommand } = await import('#/cli/sub/server/run'); + let parsed: unknown; + + await handleRunCommand( + { port: '7878', logLevel: 'debug' }, + { + startServerForeground: async (options) => { + parsed = options; + return { origin: 'http://127.0.0.1:7878' }; + }, + getServiceStatus: async () => undefined, + openUrl: vi.fn(), + stdout: { + write() { + return true; + }, + }, + stderr: { + write() { + return true; + }, + }, + }, + ); + + expect(parsed).toMatchObject({ logLevel: 'debug' }); + }); + + it('prints a TUI-style welcome panel when foreground logs are off', async () => { + const { handleRunCommand } = await import('#/cli/sub/server/run'); + let stdout = ''; + + await handleRunCommand( + { port: '7878' }, + { + startServerForeground: async () => ({ origin: 'http://127.0.0.1:7878' }), + getServiceStatus: async () => undefined, + openUrl: vi.fn(), + stdout: { + write(chunk: string | Uint8Array) { + stdout += String(chunk); + return true; + }, + }, + stderr: { + write() { + return true; + }, + }, + }, + ); + + const plain = stripAnsi(stdout); + expect(plain).toContain('╭'); + expect(plain).toContain('╰'); + expect(plain).toContain('▐█▛█▛█▌'); + expect(plain).toContain('▐█████▌'); + expect(plain).toContain('Kimi server ready'); + expect(plain).toContain('URL:'); + expect(plain).toContain('http://127.0.0.1:7878/'); + expect(plain).toContain('Network:'); + expect(plain).toContain('local only'); + expect(plain).toContain('Logs:'); + expect(plain).toContain('off'); + expect(plain).toContain('Stop:'); + expect(plain).toContain('Ctrl+C'); + expect(plain).not.toContain('➜'); + expect(plain).not.toContain('Kimi server:'); + }); + + it('uses the TUI dark palette for the foreground ready banner', async () => { + const { handleRunCommand } = await import('#/cli/sub/server/run'); + let stdout = ''; + const previousChalkLevel = chalk.level; + chalk.level = 3; + + try { + await handleRunCommand( + { port: '7878' }, + { + startServerForeground: async () => ({ origin: 'http://127.0.0.1:7878' }), + getServiceStatus: async () => undefined, + openUrl: vi.fn(), + stdout: { + write(chunk: string | Uint8Array) { + stdout += String(chunk); + return true; + }, + }, + stderr: { + write() { + return true; + }, + }, + }, + ); + } finally { + chalk.level = previousChalkLevel; + } + + const color = new Chalk({ level: 3 }); + expect(stdout).toContain(color.hex(darkColors.primary)('▐█▛█▛█▌')); + expect(stdout).toContain(color.bold.hex(darkColors.primary)('Kimi server ready')); + expect(stdout).toContain(color.hex(darkColors.accent)('http://127.0.0.1:7878/')); + expect(stdout).toContain(color.bold.hex(darkColors.textDim)('URL: ')); + expect(stdout).toContain(color.hex(darkColors.textMuted)('local only')); }); it('reports a background service conflict, suggests server stop, and opens the existing URL', async () => { @@ -344,7 +459,7 @@ describe('`kimi server run` already-running handling', () => { expect(openUrl).toHaveBeenCalledWith('http://127.0.0.1:9999'); }); - it('reports a foreground process conflict, suggests pkill, and opens the existing URL', async () => { + it('reports a foreground process conflict, suggests a pid-based stop command, and opens the existing URL', async () => { const { ServerLockedError } = await import('@moonshot-ai/server'); const { handleRunCommand } = await import('#/cli/sub/server/run'); let stdout = ''; @@ -382,10 +497,18 @@ describe('`kimi server run` already-running handling', () => { expect(stdout).toContain('already running in foreground'); expect(stdout).toContain('URL: http://127.0.0.1:10001'); - expect(stdout).toContain('Stop: pkill -f "kimi server run"'); + expect(stdout).toContain('Stop: kill -TERM 5678'); expect(stdout).not.toContain('kimi server stop'); expect(openUrl).toHaveBeenCalledWith('http://127.0.0.1:10001'); }); + + it('formats foreground stop commands by platform and pid', async () => { + const { formatForegroundStopCommand } = await import('#/cli/sub/server/run'); + + expect(formatForegroundStopCommand(1234, 'darwin')).toBe('kill -TERM 1234'); + expect(formatForegroundStopCommand(1234, 'linux')).toBe('kill -TERM 1234'); + expect(formatForegroundStopCommand(1234, 'win32')).toBe('taskkill /PID 1234 /T /F'); + }); }); describe('`kimi server` does not register a legacy `daemon` command', () => { diff --git a/docs/en/reference/kimi-command.md b/docs/en/reference/kimi-command.md index 9f1ec1e5a..4d57d46d9 100644 --- a/docs/en/reference/kimi-command.md +++ b/docs/en/reference/kimi-command.md @@ -148,7 +148,7 @@ Run, install, and manage the local Kimi server — a single process that exposes When the server is running, `GET /openapi.json` returns the REST OpenAPI document and `GET /asyncapi.json` returns the local WebSocket AsyncAPI document. The Swagger UI is separate and is mounted at `/documentation` only when the server is started with `--swagger`. ```sh -kimi server run # foreground (logs in the current terminal) +kimi server run # foreground (logs off unless --log-level is set) kimi server install # register with launchd / systemd / schtasks kimi server start # start the OS-managed service kimi server status # snapshot of installed/running state @@ -158,14 +158,13 @@ kimi server status # snapshot of installed/running state | Option | Description | | --- | --- | -| `--host ` | Bind host; defaults to `127.0.0.1` | | `--port ` | Bind port; defaults to `7878` | -| `--log-level ` | Log level; defaults to `info` | +| `--log-level ` | Enable foreground logs at the selected level; omitted by default | | `--debug-endpoints` | Mount `/api/v1/debug/*` routes (off by default) | | `--swagger` | Mount the Swagger UI at `/documentation` (off by default) | | `--open` | Open the web UI in the default browser once the server is healthy | -`kimi server run` does not return — it stays attached to the current terminal and shuts down cleanly on `SIGINT` / `SIGTERM`. For background operation, use the OS-service path below. +`kimi server run` binds to local loopback only and does not return — it stays attached to the current terminal and shuts down cleanly on `SIGINT` / `SIGTERM`. For background operation, use the OS-service path below. #### `kimi server install` @@ -177,13 +176,12 @@ Register the server as an OS-managed service so it starts at login and restarts | Option | Description | | --- | --- | -| `--host ` | Bind host the supervised server uses; defaults to `127.0.0.1` | | `--port ` | Bind port the supervised server uses; defaults to `7878` | | `--log-level ` | Log level recorded in the generated unit | | `--force` | Replace an existing install instead of failing | | `--json` | Output JSON instead of a human-readable line | -The chosen host / port / log-level are recorded to `~/.kimi-code/server/install.json` so `kimi server status` can report them even when the service is stopped. +The loopback host, chosen port, and log level are recorded to `~/.kimi-code/server/install.json` so `kimi server status` can report them even when the service is stopped. #### Lifecycle subcommands @@ -204,7 +202,7 @@ kimi web # foreground + open browser kimi web --no-open # equivalent to `kimi server run` ``` -The same `--host`, `--port`, `--log-level`, and `--debug-endpoints` flags work as on `kimi server run`. +The same `--port`, `--log-level`, `--debug-endpoints`, and `--swagger` flags work as on `kimi server run`. ### `kimi doctor` diff --git a/docs/zh/reference/kimi-command.md b/docs/zh/reference/kimi-command.md index 0366e46f8..cc596e00b 100644 --- a/docs/zh/reference/kimi-command.md +++ b/docs/zh/reference/kimi-command.md @@ -148,7 +148,7 @@ kimi acp 服务运行时,`GET /openapi.json` 会返回 REST OpenAPI 文档,`GET /asyncapi.json` 会返回本地 WebSocket 协议的 AsyncAPI 文档。Swagger UI 独立于这两个 JSON 接口,只有用 `--swagger` 启动时才会挂载到 `/documentation`。 ```sh -kimi server run # 前台运行(日志输出到当前终端) +kimi server run # 前台运行(除非设置 --log-level,否则不输出日志) kimi server install # 注册到 launchd / systemd / schtasks kimi server start # 启动 OS 管理的服务 kimi server status # 查看安装与运行状态 @@ -158,14 +158,13 @@ kimi server status # 查看安装与运行状态 | 选项 | 说明 | | --- | --- | -| `--host ` | 绑定地址;默认 `127.0.0.1` | | `--port ` | 绑定端口;默认 `7878` | -| `--log-level ` | 日志级别;默认 `info` | +| `--log-level ` | 按所选级别开启前台日志;默认不输出 | | `--debug-endpoints` | 挂载 `/api/v1/debug/*` 调试路由(默认关闭) | | `--swagger` | 在 `/documentation` 挂载 Swagger UI(默认关闭) | | `--open` | 服务健康后用默认浏览器打开 web UI | -`kimi server run` 不会返回——保持挂在当前终端,在 `SIGINT` / `SIGTERM` 时干净退出。后台运行请走下面的 OS 服务方式。 +`kimi server run` 只绑定本机 loopback 地址,不会返回——保持挂在当前终端,在 `SIGINT` / `SIGTERM` 时干净退出。后台运行请走下面的 OS 服务方式。 #### `kimi server install` @@ -177,13 +176,12 @@ kimi server status # 查看安装与运行状态 | 选项 | 说明 | | --- | --- | -| `--host ` | 被托管的服务绑定地址;默认 `127.0.0.1` | | `--port ` | 被托管的服务绑定端口;默认 `7878` | | `--log-level ` | 写入生成 unit 的日志级别 | | `--force` | 已安装时强制覆盖 | | `--json` | 用 JSON 替代人类可读输出 | -选定的 host / port / log-level 会写入 `~/.kimi-code/server/install.json`,即便服务停掉 `kimi server status` 也能读到。 +本机地址、选定的端口和日志级别会写入 `~/.kimi-code/server/install.json`,即便服务停掉 `kimi server status` 也能读到。 #### 生命周期子命令 @@ -204,7 +202,7 @@ kimi web # 前台 + 自动打开浏览器 kimi web --no-open # 等价于 `kimi server run` ``` -`--host`、`--port`、`--log-level`、`--debug-endpoints` 与 `kimi server run` 完全一致。 +`--port`、`--log-level`、`--debug-endpoints`、`--swagger` 与 `kimi server run` 完全一致。 ### `kimi doctor` diff --git a/packages/server/src/svc/install-plan.ts b/packages/server/src/svc/install-plan.ts index a182da07f..496135e2a 100644 --- a/packages/server/src/svc/install-plan.ts +++ b/packages/server/src/svc/install-plan.ts @@ -6,6 +6,8 @@ import { dirname } from 'node:path'; import { installPlanPath } from './paths'; import type { InstallArgs } from './types'; +const SUPERVISED_SERVER_HOST = '127.0.0.1'; + export interface InstallPlan { host: string; @@ -35,7 +37,7 @@ export interface BuildInstallPlanInput extends InstallArgs { export function buildInstallPlan(input: BuildInstallPlanInput): InstallPlan { return { - host: input.host, + host: SUPERVISED_SERVER_HOST, port: input.port, logLevel: input.logLevel, program: input.program, @@ -43,8 +45,6 @@ export function buildInstallPlan(input: BuildInstallPlanInput): InstallPlan { input.program, 'server', 'run', - '--host', - input.host, '--port', String(input.port), '--log-level', diff --git a/packages/server/src/svc/types.ts b/packages/server/src/svc/types.ts index 450684da0..95f970e74 100644 --- a/packages/server/src/svc/types.ts +++ b/packages/server/src/svc/types.ts @@ -90,7 +90,7 @@ export class ServiceUnavailableError extends Error { constructor(platform: string, reason: string) { super( - `${reason} Run \`kimi server run --host 0.0.0.0 --port \` directly when running inside Docker or another container supervisor.`, + `${reason} Run \`kimi server run --port \` directly when running inside Docker or another container supervisor.`, ); this.platform = platform; } diff --git a/packages/server/test/svc/launchd.test.ts b/packages/server/test/svc/launchd.test.ts index 4cdef878d..db658a90b 100644 --- a/packages/server/test/svc/launchd.test.ts +++ b/packages/server/test/svc/launchd.test.ts @@ -85,7 +85,7 @@ describe('buildLaunchAgentPlist', () => { it('renders a well-formed plist with label, ProgramArguments, and stdio paths', () => { const xml = buildLaunchAgentPlist({ label: KIMI_SERVER_LABEL, - programArguments: ['/usr/local/bin/kimi', 'server', 'run', '--host', '127.0.0.1', '--port', '7878'], + programArguments: ['/usr/local/bin/kimi', 'server', 'run', '--port', '7878'], stdoutPath: '/tmp/x.log', stderrPath: '/tmp/x.log', }); @@ -93,8 +93,7 @@ describe('buildLaunchAgentPlist', () => { expect(xml).toContain('/usr/local/bin/kimi'); expect(xml).toContain('server'); expect(xml).toContain('run'); - expect(xml).toContain('--host'); - expect(xml).toContain('127.0.0.1'); + expect(xml).not.toContain('--host'); expect(xml).toContain('--port'); expect(xml).toContain('7878'); expect(xml).toContain('StandardOutPath\n /tmp/x.log'); @@ -189,7 +188,7 @@ describe('launchd manager — install', () => { expect(calls[0]?.args[0]).toBe('bootout'); expect(calls[1]?.args[0]).toBe('bootstrap'); const xml = readFileSync(plistPath, 'utf8'); - expect(xml).toContain('0.0.0.0'); + expect(xml).not.toContain('0.0.0.0'); expect(xml).toContain('9999'); }); diff --git a/packages/server/test/svc/schtasks.test.ts b/packages/server/test/svc/schtasks.test.ts index 55a898f44..5a9a2165e 100644 --- a/packages/server/test/svc/schtasks.test.ts +++ b/packages/server/test/svc/schtasks.test.ts @@ -84,12 +84,12 @@ describe('buildScheduledTaskXml', () => { const xml = buildScheduledTaskXml({ description: 'test desc', command: 'C:\\bin\\kimi.exe', - arguments: 'server run --host 127.0.0.1 --port 7878', + arguments: 'server run --port 7878', }); expect(xml).toContain(''); expect(xml).toContain('test desc'); expect(xml).toContain('C:\\bin\\kimi.exe'); - expect(xml).toContain('server run --host 127.0.0.1 --port 7878'); + expect(xml).toContain('server run --port 7878'); expect(xml).toContain(''); expect(xml).toContain('LeastPrivilege'); }); @@ -167,7 +167,7 @@ describe('schtasks manager — install', () => { expect(result.taskName).toBe(KIMI_SERVER_TASK_NAME); expect(writtenXmls.length).toBe(1); expect(writtenXmls[0]).toContain(`Kimi Code local server`); - expect(writtenXmls[0]).toContain('--host 127.0.0.1'); + expect(writtenXmls[0]).not.toContain('--host'); expect(writtenXmls[0]).toContain('--port 7878'); expect(calls.length).toBe(2); diff --git a/packages/server/test/svc/systemd.test.ts b/packages/server/test/svc/systemd.test.ts index b5a56bc15..9ca127fa1 100644 --- a/packages/server/test/svc/systemd.test.ts +++ b/packages/server/test/svc/systemd.test.ts @@ -81,13 +81,13 @@ afterEach(() => { describe('buildSystemdUnit', () => { it('renders the standard [Unit]/[Service]/[Install] triple', () => { const unit = buildSystemdUnit({ - programArguments: ['/usr/local/bin/kimi', 'server', 'run', '--host', '127.0.0.1', '--port', '7878'], + programArguments: ['/usr/local/bin/kimi', 'server', 'run', '--port', '7878'], }); expect(unit).toContain('[Unit]'); expect(unit).toContain('[Service]'); expect(unit).toContain('[Install]'); expect(unit).toContain('Description=Kimi Code local server'); - expect(unit).toContain('ExecStart=/usr/local/bin/kimi server run --host 127.0.0.1 --port 7878'); + expect(unit).toContain('ExecStart=/usr/local/bin/kimi server run --port 7878'); expect(unit).toContain('Restart=always'); expect(unit).toContain('WantedBy=default.target'); }); @@ -149,7 +149,8 @@ describe('systemd manager — install', () => { expect(result.unitPath).toBe(unitPath); expect(existsSync(unitPath)).toBe(true); const text = readFileSync(unitPath, 'utf8'); - expect(text).toContain('ExecStart=/usr/local/bin/kimi server run --host 127.0.0.1 --port 7878 --log-level info'); + expect(text).toContain('ExecStart=/usr/local/bin/kimi server run --port 7878 --log-level info'); + expect(text).not.toContain('--host'); expect(calls.length).toBe(3); expect(calls[0]?.args).toEqual(['show-environment']); @@ -184,7 +185,8 @@ describe('systemd manager — install', () => { const result = await mgr.install({ host: '0.0.0.0', port: 9999, logLevel: 'debug', force: true }); expect(result.status).toBe('replaced'); const text = readFileSync(unitPath, 'utf8'); - expect(text).toContain('ExecStart=/usr/local/bin/kimi server run --host 0.0.0.0 --port 9999 --log-level debug'); + expect(text).toContain('ExecStart=/usr/local/bin/kimi server run --port 9999 --log-level debug'); + expect(text).not.toContain('0.0.0.0'); }); it('fails before writing files when user systemd is unavailable', async () => {