Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 123 additions & 8 deletions docs/schemas/hub-control.md

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -757,7 +757,8 @@
"needB": "Pass --b to specify the source B (new render) directory: that semantic choice belongs to the caller, this tool does not guess",
"noDeck": "No deck directory found under {root}; create one first with tts import / tts deck",
"multipleDecks": "Multiple decks under decks/ ({names}); pass --deck to choose the one to review",
"invalidPort": "Invalid port: {value} (must be an integer in 1..65535)"
"invalidPort": "Invalid port: {value} (must be an integer in 1..65535)",
"invalidOption": "Invalid option value: {value} (allowed: {allowed})"
},
"status": {
"needApprovalRoot": "Pass --approval-root pointing at the approval tool repository (contains agent.py), or use --server for HTTP"
Expand Down Expand Up @@ -785,7 +786,8 @@
"editorPort": "TTS editor port (default 39998; TTS connects inbound)",
"tcpPort": "TCP fanout port (default 39997)",
"wsPort": "WebSocket fanout port (default 39996)",
"logFile": "Log file path (appended; by default logs go to stdout only)"
"logFile": "Log file path (appended; by default logs go to stdout only)",
"appMode": "App mode (standalone=standalone; app=launched as sidecar by desktop UI, errors carry userAction key)"
},
"started": "Hub started: control channel http://127.0.0.1:{controlPort}/v1; editor port {editorPort}; TCP fanout {tcpPort}; WS fanout {wsPort} (Ctrl+C to stop)",
"stopped": "Hub stopped"
Expand Down
6 changes: 4 additions & 2 deletions locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -757,7 +757,8 @@
"needB": "请用 --b 指定源B(新版渲染)目录:它是使用方的语义决定,本工具不猜",
"noDeck": "在 {root} 下没有找到任何 deck 目录,请先用 tts import / tts deck 建卡堆",
"multipleDecks": "decks/ 下有多个卡堆({names}),请用 --deck 指定要审批的那个",
"invalidPort": "端口无效:{value}(需为 1..65535 的整数)"
"invalidPort": "端口无效:{value}(需为 1..65535 的整数)",
"invalidOption": "选项值无效:{value}(允许的取值:{allowed})"
},
"status": {
"needApprovalRoot": "请用 --approval-root 指定审批工具仓库根(含 agent.py),或改用 --server 走 HTTP"
Expand Down Expand Up @@ -785,7 +786,8 @@
"editorPort": "TTS 编辑器端口(缺省 39998,由 TTS 主动连入)",
"tcpPort": "TCP 扇出端口(缺省 39997)",
"wsPort": "WebSocket 扇出端口(缺省 39996)",
"logFile": "日志文件路径(追加写入;缺省日志只写 stdout)"
"logFile": "日志文件路径(追加写入;缺省日志只写 stdout)",
"appMode": "应用模式(standalone=独立运行;app=由桌面 UI 作 sidecar 启动,错误体含 userAction 提示键)"
},
"started": "hub 已启动:控制通道 http://127.0.0.1:{controlPort}/v1;编辑器端口 {editorPort};TCP 扇出 {tcpPort};WS 扇出 {wsPort}(Ctrl+C 停止)",
"stopped": "hub 已停止"
Expand Down
54 changes: 50 additions & 4 deletions src/cli/commands/hub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,14 @@
* 本模块使用的 i18n 键(locales/*.json 双语镜像;缺键时 t() 原样输出键名):
* - 静态:`cli.command.hub.description`、`cli.hub.option.port` /
* `cli.hub.option.editorPort` / `cli.hub.option.tcpPort` /
* `cli.hub.option.wsPort` / `cli.hub.option.logFile`、`cli.hub.stopped`;
* `cli.hub.option.wsPort` / `cli.hub.option.logFile` /
* `cli.hub.option.appMode`、`cli.hub.stopped`;
* - 插值:`cli.hub.started` {controlPort} {editorPort} {tcpPort} {wsPort};
* - 复用既有键:`cli.review.prepare.invalidPort` {value}(端口非法提示,文案
* 通用:端口无效:{value}(需为 1..65535 的整数))、`error.generic` {message}
* (非端口占用的启动失败统一出口)。
* 通用:端口无效:{value}(需为 1..65535 的整数))、
* `cli.review.prepare.invalidOption` {value} {allowed}(选项值不在白名单的
* 提示,--app-mode 校验用)、`error.generic` {message}(非端口占用的启动
* 失败统一出口)。
*/

import { Command } from "commander";
Expand All @@ -53,12 +56,22 @@ interface HubCliOptions {
wsPort?: string;
/** 日志文件路径(追加写;缺省只写 stdout) */
logFile?: string;
/** 应用模式(字符串形式,action 内校验白名单 ["standalone", "app"]) */
appMode?: string;
}

// ---------------------------------------------------------------------------
// 内部工具
// ---------------------------------------------------------------------------

/**
* `--app-mode` 的合法取值白名单(v0.8.0):
* - "standalone":独立运行(CLI 手动启动,缺省);
* - "app":由桌面 UI 作 sidecar 启动(控制通道错误体 details 会附 userAction
* 提示键,供 UI 直接渲染,见 docs/schemas/hub-control.md §3.2)。
*/
const APP_MODE_WHITELIST: readonly string[] = ["standalone", "app"];

/**
* 解析端口选项:字符串 → 1..65535 的整数;未提供选项时返回 undefined
* 表示"用缺省端口"(控制通道 39995 / 编辑器 39998 / TCP 39997 / WS 39996)。
Expand All @@ -79,16 +92,46 @@ function parsePortOption(value: string | undefined): number | undefined {
return port;
}

/**
* 解析 --app-mode 选项:须命中白名单 ["standalone", "app"];未提供选项时返回
* undefined 表示"用 lifecycle 的解析链"(参数 > 环境变量 TTS_HUB_APP_MODE >
* 缺省 "standalone",见 src/hub/lifecycle.ts)。
*
* @param value 命令行原始值;undefined 表示未提供该选项
* @returns 合法的应用模式;未提供选项时 undefined
* @returns 非法值时向 stderr 输出本地化提示并 process.exit(1)(不返回)
*/
function parseAppModeOption(value: string | undefined): string | undefined {
if (value === undefined) {
return undefined;
}
if (!APP_MODE_WHITELIST.includes(value)) {
console.error(
t("cli.review.prepare.invalidOption", {
value,
allowed: APP_MODE_WHITELIST.join(" / "),
}),
);
process.exit(1);
}
return value;
}

// ---------------------------------------------------------------------------
// 子命令
// ---------------------------------------------------------------------------

/**
* `tts hub [--port <n>] [--editor-port <n>] [--tcp-port <n>] [--ws-port <n>]
* [--log-file <路径>]`:前台常驻的 hub 进程(控制通道 + 协议扇出)。
* [--log-file <路径>] [--app-mode <standalone|app>]`:前台常驻的 hub 进程
* (控制通道 + 协议扇出)。
*
* 启动成功打印一行 started(含实际端口),随后常驻;SIGINT / SIGTERM /
* /v1/hub/shutdown 触发优雅停止后打印 stopped 并以 0 退出。
*
* --app-mode(v0.8.0):standalone=独立运行(缺省);app=由桌面 UI 作 sidecar
* 启动(控制通道错误体 details 附 userAction 提示键)。`tts-hub` bin 保持零
* 旗标,同语义经环境变量 TTS_HUB_APP_MODE 透传。
*/
export const hubCommand: Command = new Command("hub")
.description(t("cli.command.hub.description"))
Expand All @@ -97,18 +140,21 @@ export const hubCommand: Command = new Command("hub")
.option("--tcp-port <number>", t("cli.hub.option.tcpPort"))
.option("--ws-port <number>", t("cli.hub.option.wsPort"))
.option("--log-file <path>", t("cli.hub.option.logFile"))
.option("--app-mode <mode>", t("cli.hub.option.appMode"))
.action(async (opts: HubCliOptions) => {
const controlPort = parsePortOption(opts.port);
const editorPort = parsePortOption(opts.editorPort) ?? EDITOR_PORT;
const tcpPort = parsePortOption(opts.tcpPort) ?? HUB_TCP_PORT;
const wsPort = parsePortOption(opts.wsPort) ?? HUB_WS_PORT;
const appMode = parseAppModeOption(opts.appMode);

const hub = runHubProcess({
...(controlPort !== undefined ? { controlPort } : {}),
editorPort,
tcpPort,
wsPort,
...(opts.logFile !== undefined ? { logFile: opts.logFile } : {}),
...(appMode !== undefined ? { appMode } : {}),
});

try {
Expand Down
Loading
Loading