From 56f2892c020d062ca6bb50502dd4add039a5b4a5 Mon Sep 17 00:00:00 2001 From: Codex Date: Mon, 18 May 2026 11:10:42 +0000 Subject: [PATCH] fix: allow target-side k3sctl deploy --- docs/reference/cli.md | 1 + docs/reference/deploy.md | 2 + scripts/cli.ts | 143 +++++---------------- scripts/src/check.ts | 1 + scripts/src/ci.ts | 4 +- scripts/src/deploy.ts | 12 +- scripts/src/help.ts | 262 +++++++++++++++++++++++++++++++++++++++ 7 files changed, 304 insertions(+), 121 deletions(-) create mode 100644 scripts/src/help.ts diff --git a/docs/reference/cli.md b/docs/reference/cli.md index cc7aafdc..68e24d9f 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -5,6 +5,7 @@ UniDesk 的统一 CLI 入口是根目录 `scripts/cli.ts`,运行方式固定 ## Command Model - `help` 输出命令索引,适合作为交互式入口。 +- 每个 CLI 命名空间必须支持 `help`、`--help` 或 `-h` 并返回 JSON,不得为了打印帮助而访问 runtime 服务、拉起交互会话或执行长时任务。 - `--main-server-ip ` 默认通过公网 frontend 登录态调用主 server 的同源 API 代理,不要求计算节点持有主 server SSH key;显式提供 `--main-server-key` 或 `--main-server-transport ssh` 时才使用旧 SSH 传输。 - `config show` 读取并校验根目录 `config.json`,不从环境变量、默认值或隐藏文件静默补配置。 - `check` 默认只执行轻量配置校验、Bun 版本检查和 Bun Transpiler 语法解析(覆盖 CLI 入口、主要 `scripts/` 模块和核心组件入口,不做类型推导);关键文件存在性、`scripts/` TypeScript 类型检查、`src/components/` TypeScript 类型检查、Docker Compose config 和日志轮转策略扫描默认不启用,分别通过 `--files`、`--scripts-typecheck`、`--components`、`--compose`、`--logs` 开启,或用 `--full` 一次性开启。`--rust` 只允许在 D601 CI/dev execution 中配合 `UNIDESK_D601_RUST_CHECK=1` 使用,长期规则见 `docs/reference/dev-environment.md`。 diff --git a/docs/reference/deploy.md b/docs/reference/deploy.md index 293a9675..de6ac40c 100644 --- a/docs/reference/deploy.md +++ b/docs/reference/deploy.md @@ -42,6 +42,8 @@ The optional non-service execution declaration under `environments.dev` is inten Environment mode never reads the local dirty working tree manifest. `deploy check --env ...`, `deploy plan --env ...` and `deploy apply --env ...` fetch `origin/master`, read `origin/master:deploy.json`, select `environments.`, and report the manifest commit/blob, service commit IDs, target namespace, database fingerprint and Provider identity. `deploy apply --env dev` is currently enabled only for persistent D601 dev `backend-core` and `frontend`; all other D601 services remain rejected before runtime mutation. `deploy apply --env prod` remains disabled until the production environment executor and authorization policy are explicitly added. +The only D601 direct-service exception in local manifest mode is `k3sctl-adapter`, because it is the UniDesk-managed control bridge outside the k3s fault domain and owns the Kubernetes service catalog used by the dev public frontend path. Updating it must still use the normal target-side deploy reconciler from a pushed commit. D601 Code Queue, Decision Center, MDTODO, ClaudeQQ and future k3s-managed workloads remain blocked from maintenance-channel direct deploy. + `config.json.microservices[].repository.commitId` is retained for catalog compatibility, but `deploy.json` is the deployment version authority for the reconciler. ## Dev CI Runner diff --git a/scripts/cli.ts b/scripts/cli.ts index 8e3cb5f5..a45a84dd 100644 --- a/scripts/cli.ts +++ b/scripts/cli.ts @@ -10,128 +10,19 @@ import { extractRemoteCliOptions, runRemoteCli } from "./src/remote"; import { runMicroserviceCommand } from "./src/microservices"; import { runCodeQueueCommand } from "./src/code-queue"; import { runDecisionCenterCommand } from "./src/decision-center"; -import { runCodeQueueDeployCompatCommand, runDeployCommand } from "./src/deploy"; +import { deployHelp, runCodeQueueDeployCompatCommand, runDeployCommand } from "./src/deploy"; import { runProviderCommand } from "./src/provider-attach"; import { runScheduleCommand } from "./src/schedules"; import { parseNetworkPerfOptions, runNetworkPerf } from "./src/network-perf"; -import { runCiCommand } from "./src/ci"; +import { ciHelp, runCiCommand } from "./src/ci"; import { runSwapCommand } from "./src/swap"; import { runDevEnvCommand } from "./src/dev-env"; +import { isHelpToken, rootHelp, serverHelp, sshHelp, staticNamespaceHelp } from "./src/help"; const remoteOptions = extractRemoteCliOptions(process.argv.slice(2)); const args = remoteOptions.args; const commandName = args.join(" ") || "help"; -function help(): unknown { - return { - entry: "bun scripts/cli.ts", - output: "json", - commands: [ - { command: "help", description: "List supported commands." }, - { command: "--main-server-ip ", description: "Run selected commands through the public frontend API; use --main-server-key only for legacy SSH transport." }, - { command: "config show", description: "Validate and print config.json as the single source of truth." }, - { command: "check [--full|--files|--scripts-typecheck|--components|--compose|--logs|--rust]", description: "Run the lightweight default syntax/config gate; Rust is opt-in and only allowed from D601 CI/dev execution." }, - { command: "server start", description: "Fire-and-forget build/start for database, backend-core, frontend, provider gateway, and managed main-server user services." }, - { command: "server stop", description: "Fire-and-forget docker-compose down for the fixed UniDesk stack." }, - { command: "server status", description: "Show fixed ports, containers, service health, and public URLs." }, - { command: "server swap status|ensure [--path /swapfile] [--size 2GiB] [--dry-run]", description: "Inspect or idempotently create host swap for low-memory main-server operation." }, - { command: "server logs [--tail-bytes N]", description: "Return bounded tails from file logs and docker logs." }, - { command: "server rebuild ", description: "Build first, then serialize, force-recreate, and validate one Compose service." }, - { command: "provider attach [--master-server URL] [--up] [--force]", description: "Generate the minimal external provider-gateway env/compose bundle; only master server URL and provider id are required." }, - { command: "ssh [ssh-like args...]", description: "Open a Host SSH / WSL SSH maintenance session through the provider-gateway bridge with built-in remote helper tools in PATH." }, - { command: "ssh apply-patch [tool args...] < patch.diff", description: "Invoke the injected remote apply_patch helper directly over SSH passthrough and stream the patch from local stdin." }, - { command: "ssh py [script-args...] < script.py", description: "Run remote Python from local stdin through SSH passthrough without nested shell quoting; extra args become script argv." }, - { command: "ssh skills [--scope all|wsl|windows] [--limit N]", description: "Discover WSL/Linux and, for WSL providers, Windows skill directories in one SSH passthrough call." }, - { command: "ssh find [--max-depth N] [--type d|f|l] [--contains TEXT] [--iname PATTERN] [--limit N] [--sort]", description: "Run a structured remote find command without nested shell quoting or parentheses." }, - { command: "ssh glob [--root DIR] [--pattern PATTERN] [--contains TEXT] [--type any|f|d] [--limit N] [--sort]", description: "Run remote glob matching through the injected helper without shell glob expansion." }, - { command: "ssh argv [args...]", description: "Run a remote command with each argv token shell-quoted by UniDesk before SSH passthrough." }, - { command: "microservice list", description: "List UniDesk-managed user services and their provider/runtime mapping." }, - { command: "microservice status ", description: "Show one user service config, repository reference, backend mapping, and runtime status." }, - { command: "microservice health ", description: "Probe one user service through backend-core -> provider-gateway HTTP proxy." }, - { command: "microservice proxy [--method GET|POST|PUT|PATCH|DELETE] [--body-json JSON|--body-file path|--body-stdin] [--raw] [--max-body-bytes N]", description: "Access a private user-service backend path through the same frontend-only proxy used by WebUI; JSON request bodies are supported for controlled write/debug endpoints." }, - { command: "microservice diagnostics ", description: "Split k3sctl-managed proxy health into provider-gateway, HTTP tunnel, adapter, Kubernetes API service proxy, and target Service checks." }, - { command: "microservice tunnel-self-test ", description: "Trigger an expected provider HTTP tunnel failure and verify requestId/stage diagnostics are returned." }, - { command: "decision upload [--title text] [--type meeting|decision] [--level G0|G1|G2|G3|P0|P1|P2|P3|none] [--status active|blocked|parked|done] [--linked-goal-id id] [--evidence url]", description: "Upload a meeting note or decision record through backend-core -> decision-center user-service proxy." }, - { command: "decision diary import [--source-file path] [--tag tag] [--include-entries]", description: "Import a dated work log Markdown into PostgreSQL diary entries split as YYYY-MM/YYYY-MM-DD.md." }, - { command: "decision diary list [--month YYYY-MM] [--from YYYY-MM-DD] [--to YYYY-MM-DD] [--limit N] [--include-body]", description: "List daily Markdown diary entries stored by Decision Center." }, - { command: "decision diary months", description: "List available Decision Center diary months with day counts." }, - { command: "decision diary show ", description: "Show one daily diary Markdown entry." }, - { command: "decision list [--type ...] [--status ...] [--level ...] [--linked-goal-id id] [--limit N]", description: "List Decision Center records through the user-service proxy." }, - { command: "decision show ", description: "Show one Decision Center record." }, - { command: "deploy check|plan|apply [--file deploy.json|--env dev|prod] [--service id] [--dry-run] [--force]", description: "Reconcile services from a repo+commit manifest; --env reads origin/master:deploy.json environments and can apply supported dev services." }, - { command: "dev-env validate|prewarm-images", description: "Validate D601 unidesk-dev guardrails or prewarm dev foundation images into native k3s containerd through a bounded async job." }, - { command: "schedule list|get|runs|run|delete", description: "Manage backend-core scheduled tasks and run history; schedule run supports --wait-ms N." }, - { command: "schedule upsert-pgdata-backup [--time HH:MM] [--remote-base /SERVER_DATA/UNIDESK_PG_DATA]", description: "Create or update the daily PGDATA physical backup task that uploads monthly rotated archives to Baidu Netdisk." }, - { command: "codex deploy [--provider-id D601] [--timeout-ms N]", description: "Compatibility wrapper for deploy apply --service code-queue with a temporary repo+commit manifest." }, - { command: "codex submit [prompt] [--prompt-file path|--prompt-stdin] [--queue queueId] [--provider-id id] [--cwd path] [--model model] [--execution-mode mode] [--max-attempts N] [--reference-task-id id] [--dry-run]", description: "Submit a Code Queue task through backend-core -> code-queue proxy; --dry-run shows the structured request without enqueueing." }, - { command: "codex task [--trace --tail|--from-start|--after-seq N|--before-seq N --limit N] [--full]", description: "Fetch a compact Code Queue task summary; trace rows are opt-in and paged with next/previous commands to avoid output explosion." }, - { command: "codex output [--tail|--from-start|--after-seq N|--before-seq N --limit N] [--full-text]", description: "Fetch paged raw Code Queue output records by seq when a trace row has omitted command/output text." }, - { command: "codex judge --attempt N [--dry-run] [--include-prompt]", description: "Replay one stored Code Queue attempt through the same judge context builder and MiniMax judge call path used by the live queue worker." }, - { command: "codex interrupt|cancel ", description: "Request interrupt for a running Code Queue task, or cancel a queued/retry_wait task, through the same private proxy." }, - { command: "codex (queues | queue create | queue merge --into | move --queue )", description: "List/create/merge Code Queue lanes and move a queued task; merge preserves task queue time order and deletes the source queue record." }, - { command: "job list [--limit N] [--include-command]", description: "List async jobs from .state/jobs with a bounded default page." }, - { command: "job status [--tail-bytes N]", description: "Show job state with bounded stdout/stderr tails." }, - { command: "debug health", description: "Probe internal core, nodes, system/Docker status, frontend, provider ingress, and public boundary." }, - { command: "debug dispatch [providerId] [docker.ps|provider.upgrade|host.ssh|microservice.http|echo] [--wait-ms N]", description: "Submit a real internal-core dispatch request for CLI debugging." }, - { command: "debug task ", description: "Read a dispatched task record from internal core for CLI debugging." }, - { command: "network perf [--service code-queue --path /api/tasks/overview?limit=30 --count N --concurrency N --label before|after]", description: "Benchmark frontend -> backend-core -> provider/adapter user-service networking and report latency/proxy-mode distributions." }, - { command: "ci install|status|run|run-dev-e2e|logs", description: "Manage D601 k3s Tekton CI only; run-dev-e2e manually validates master deploy.json dev state in an isolated temporary namespace." }, - { command: "e2e run [--only pattern[,pattern...]] [--skip pattern[,pattern...]]", description: "Run selected public/internal/Playwright E2E checks; use --only for focused iteration and rerun without filters for final regression." }, - ], - }; -} - -function isHelpToken(value: string | undefined): boolean { - return value === "help" || value === "--help" || value === "-h"; -} - -function serverHelp(action: string | undefined = undefined): unknown { - return { - command: action === undefined || isHelpToken(action) ? "server start|stop|status|swap|logs|rebuild" : `server ${action}`, - output: "json", - description: "Manage the fixed main-server Docker Compose stack without exposing backend-core REST publicly.", - usage: { - start: "bun scripts/cli.ts server start", - stop: "bun scripts/cli.ts server stop", - status: "bun scripts/cli.ts server status", - swap: "bun scripts/cli.ts server swap status|ensure [--path /swapfile] [--size 2GiB] [--dry-run]", - logs: "bun scripts/cli.ts server logs [--tail-bytes N]", - rebuild: "bun scripts/cli.ts server rebuild ", - }, - publicEntrypoints: { - frontend: "prod UniDesk frontend", - devFrontend: "dev UniDesk frontend proxy to D601 unidesk-dev/frontend-dev", - providerIngress: "provider-gateway WebSocket ingress", - }, - rustBoundary: { - masterServer: "do not use server rebuild backend-core for Rust iteration; it would build locally", - d601: "use deploy apply --env dev --service backend-core and CI for Rust build/check", - }, - }; -} - -function sshHelp(): unknown { - return { - command: "ssh", - output: "json", - description: "Open a Host SSH / WSL SSH maintenance session through the provider-gateway bridge.", - usage: [ - "bun scripts/cli.ts ssh ", - "bun scripts/cli.ts ssh argv [args...]", - "bun scripts/cli.ts ssh apply-patch < patch.diff", - "bun scripts/cli.ts ssh py [script-args...] < script.py", - "bun scripts/cli.ts ssh skills [--scope all|wsl|windows] [--limit N]", - "bun scripts/cli.ts ssh find [--contains TEXT] [--limit N]", - "bun scripts/cli.ts ssh glob [--root DIR] [--pattern PATTERN]", - ], - notes: [ - "ssh --help and ssh --help print this JSON help and never open an interactive session.", - "Use argv when nested shell quoting would be fragile.", - "Use -- before a remote command that intentionally starts with a dash.", - ], - }; -} - function numberOption(name: string, defaultValue: number): number { const index = args.indexOf(name); if (index === -1) return defaultValue; @@ -194,7 +85,7 @@ async function main(): Promise { const [top, sub, third, fourth] = args; if (top === undefined || top === "help" || top === "--help" || top === "-h") { - emitJson(commandName, help()); + emitJson(commandName, rootHelp()); return; } @@ -203,6 +94,32 @@ async function main(): Promise { return; } + if (top === "check" && isHelpToken(sub)) { + emitJson(commandName, checkHelp()); + return; + } + + if (top === "server" && (isHelpToken(sub) || args.slice(2).some(isHelpToken))) { + emitJson(commandName, serverHelp(isHelpToken(sub) ? undefined : sub)); + return; + } + + if (top === "deploy" && args.slice(1).some(isHelpToken)) { + emitJson(commandName, deployHelp(isHelpToken(sub) ? undefined : sub)); + return; + } + + if (top === "ci" && (isHelpToken(sub) || args.slice(1).some(isHelpToken))) { + emitJson(commandName, ciHelp()); + return; + } + + const namespaceHelp = staticNamespaceHelp(args); + if (namespaceHelp !== null) { + emitJson(commandName, namespaceHelp); + return; + } + if (top === "internal" && sub === "run-job") { if (!third) throw new Error("internal run-job requires job id"); emitJson(commandName, await runJob(third)); diff --git a/scripts/src/check.ts b/scripts/src/check.ts index 23aa883d..3644495b 100644 --- a/scripts/src/check.ts +++ b/scripts/src/check.ts @@ -20,6 +20,7 @@ const syntaxFiles = [ "scripts/src/deploy.ts", "scripts/src/docker.ts", "scripts/src/e2e.ts", + "scripts/src/help.ts", "scripts/src/remote.ts", "src/components/frontend/src/index.ts", "src/components/frontend/src/app.tsx", diff --git a/scripts/src/ci.ts b/scripts/src/ci.ts index 6f25c9b3..97ba543b 100644 --- a/scripts/src/ci.ts +++ b/scripts/src/ci.ts @@ -779,7 +779,7 @@ async function logs(name: string): Promise> { }; } -function help(): Record { +export function ciHelp(): Record { return { command: "ci install|status|run|run-dev-e2e|logs", description: "Manage the D601 k3s Tekton CI gate. This intentionally does not deploy CD.", @@ -815,7 +815,7 @@ function requireRunId(value: string): string { export async function runCiCommand(_config: UniDeskConfig, args: string[]): Promise> { const [action = "status", nameArg] = args; - if (isHelpArg(action) || args.slice(1).some(isHelpArg)) return help(); + if (isHelpArg(action) || args.slice(1).some(isHelpArg)) return ciHelp(); if (action === "install") return install(); if (action === "status") return status(); if (action === "run") { diff --git a/scripts/src/deploy.ts b/scripts/src/deploy.ts index 18ec86b5..1ccb19e1 100644 --- a/scripts/src/deploy.ts +++ b/scripts/src/deploy.ts @@ -131,7 +131,7 @@ const nativeK3sInstallVersion = "v1.34.1+k3s1"; const nativeK3sImage = "rancher/k3s:v1.34.1-k3s1"; const nativeK3sCtrAddress = "/run/k3s/containerd/containerd.sock"; const unideskRepoUrl = "https://github.com/pikasTech/unidesk"; -const d601MaintenanceDeployAllowedServiceIds = new Set(["backend-core", "frontend"]); +const d601MaintenanceDeployAllowedServiceIds = new Set(["backend-core", "frontend", "k3sctl-adapter"]); const devApplySupportedServiceIds = new Set(["backend-core", "frontend"]); const deployEnvironmentTargets: Record = { dev: { @@ -178,7 +178,7 @@ function isHelpArg(value: string | undefined): boolean { return value === "help" || value === "--help" || value === "-h"; } -function deployHelp(action: string | undefined = undefined): Record { +export function deployHelp(action: string | undefined = undefined): Record { const command = action === undefined || isHelpArg(action) ? "deploy check|plan|apply" : `deploy ${action}`; return { ok: true, @@ -194,8 +194,8 @@ function deployHelp(action: string | undefined = undefined): Record", default: defaultDeployFile, description: "Desired-state manifest path relative to the repo root. JSON and ESM JS manifests are supported, for example deploy.json or develop.js." }, - { name: "--env ", description: "Read the named environment from origin/master:deploy.json. Dev apply is enabled only for backend-core and frontend in D601 unidesk-dev." }, + { name: "--file ", default: defaultDeployFile, description: "Desired-state manifest path relative to the repo root. JSON and ESM JS manifests are supported, for example deploy.json or develop.js. Local manifest apply has one D601 direct-service exception: k3sctl-adapter." }, + { name: "--env ", description: "Read the named environment from origin/master:deploy.json. Dev apply is enabled only for backend-core and frontend in D601 unidesk-dev; prod apply is disabled." }, { name: "--service ", description: "Limit reconcile to one service from the manifest." }, { name: "--dry-run", description: "Prepare and validate without mutating the target service." }, { name: "--force", description: "Redeploy even when the live commit appears up to date." }, @@ -2170,7 +2170,7 @@ async function applyOneService(config: UniDeskConfig, service: UniDeskMicroservi ok: false, serviceId: service.id, skipped: true, - reason: `D601 dev deployment is allowed only for backend-core and frontend through the controlled deploy --env dev path; ${service.id} is not enabled. Use ci run-dev-e2e for smoke verification.`, + reason: `D601 target-side deployment is allowed only for k3sctl-adapter and dev backend-core/frontend; ${service.id} is not enabled. Use ci run-dev-e2e for smoke verification.`, steps, }; } @@ -2345,7 +2345,7 @@ function blockedD601MaintenanceDeployServices(config: UniDeskConfig, manifest: D } function d601MaintenanceDeployBlockMessage(blocked: string[]): string { - return `D601 dev deployment is enabled only for backend-core and frontend through deploy --env dev; blocked services: ${blocked.join(", ")}. Use ci run-dev-e2e for dev smoke verification.`; + return `D601 target-side deployment is enabled only for k3sctl-adapter and dev backend-core/frontend; blocked services: ${blocked.join(", ")}. Use ci run-dev-e2e for dev smoke verification.`; } async function runApplyNow(config: UniDeskConfig, manifest: DeployManifest, options: DeployOptions): Promise> { diff --git a/scripts/src/help.ts b/scripts/src/help.ts new file mode 100644 index 00000000..dd036be3 --- /dev/null +++ b/scripts/src/help.ts @@ -0,0 +1,262 @@ +export function rootHelp(): unknown { + return { + entry: "bun scripts/cli.ts", + output: "json", + commands: [ + { command: "help", description: "List supported commands." }, + { command: "--main-server-ip ", description: "Run selected commands through the public frontend API; use --main-server-key only for legacy SSH transport." }, + { command: "config show", description: "Validate and print config.json as the single source of truth." }, + { command: "check [--full|--files|--scripts-typecheck|--components|--compose|--logs|--rust]", description: "Run the lightweight default syntax/config gate; Rust is opt-in and only allowed from D601 CI/dev execution." }, + { command: "server start", description: "Fire-and-forget build/start for database, backend-core, frontend, provider gateway, and managed main-server user services." }, + { command: "server stop", description: "Fire-and-forget docker-compose down for the fixed UniDesk stack." }, + { command: "server status", description: "Show fixed ports, containers, service health, and public URLs." }, + { command: "server swap status|ensure [--path /swapfile] [--size 2GiB] [--dry-run]", description: "Inspect or idempotently create host swap for low-memory main-server operation." }, + { command: "server logs [--tail-bytes N]", description: "Return bounded tails from file logs and docker logs." }, + { command: "server rebuild ", description: "Build first, then serialize, force-recreate, and validate one Compose service." }, + { command: "provider attach [--master-server URL] [--up] [--force]", description: "Generate the minimal external provider-gateway env/compose bundle; only master server URL and provider id are required." }, + { command: "ssh [ssh-like args...]", description: "Open a Host SSH / WSL SSH maintenance session through the provider-gateway bridge with built-in remote helper tools in PATH." }, + { command: "ssh apply-patch [tool args...] < patch.diff", description: "Invoke the injected remote apply_patch helper directly over SSH passthrough and stream the patch from local stdin." }, + { command: "ssh py [script-args...] < script.py", description: "Run remote Python from local stdin through SSH passthrough without nested shell quoting; extra args become script argv." }, + { command: "ssh skills [--scope all|wsl|windows] [--limit N]", description: "Discover WSL/Linux and, for WSL providers, Windows skill directories in one SSH passthrough call." }, + { command: "ssh find [--max-depth N] [--type d|f|l] [--contains TEXT] [--iname PATTERN] [--limit N] [--sort]", description: "Run a structured remote find command without nested shell quoting or parentheses." }, + { command: "ssh glob [--root DIR] [--pattern PATTERN] [--contains TEXT] [--type any|f|d] [--limit N] [--sort]", description: "Run remote glob matching through the injected helper without shell glob expansion." }, + { command: "ssh argv [args...]", description: "Run a remote command with each argv token shell-quoted by UniDesk before SSH passthrough." }, + { command: "microservice list", description: "List UniDesk-managed user services and their provider/runtime mapping." }, + { command: "microservice status ", description: "Show one user service config, repository reference, backend mapping, and runtime status." }, + { command: "microservice health ", description: "Probe one user service through backend-core -> provider-gateway HTTP proxy." }, + { command: "microservice proxy [--method GET|POST|PUT|PATCH|DELETE] [--body-json JSON|--body-file path|--body-stdin] [--raw] [--max-body-bytes N]", description: "Access a private user-service backend path through the same frontend-only proxy used by WebUI; JSON request bodies are supported for controlled write/debug endpoints." }, + { command: "microservice diagnostics ", description: "Split k3sctl-managed proxy health into provider-gateway, HTTP tunnel, adapter, Kubernetes API service proxy, and target Service checks." }, + { command: "microservice tunnel-self-test ", description: "Trigger an expected provider HTTP tunnel failure and verify requestId/stage diagnostics are returned." }, + { command: "decision upload [--title text] [--type meeting|decision] [--level G0|G1|G2|G3|P0|P1|P2|P3|none] [--status active|blocked|parked|done] [--linked-goal-id id] [--evidence url]", description: "Upload a meeting note or decision record through backend-core -> decision-center user-service proxy." }, + { command: "decision diary import [--source-file path] [--tag tag] [--include-entries]", description: "Import a dated work log Markdown into PostgreSQL diary entries split as YYYY-MM/YYYY-MM-DD.md." }, + { command: "decision diary list [--month YYYY-MM] [--from YYYY-MM-DD] [--to YYYY-MM-DD] [--limit N] [--include-body]", description: "List daily Markdown diary entries stored by Decision Center." }, + { command: "decision diary months", description: "List available Decision Center diary months with day counts." }, + { command: "decision diary show ", description: "Show one daily diary Markdown entry." }, + { command: "decision list [--type ...] [--status ...] [--level ...] [--linked-goal-id id] [--limit N]", description: "List Decision Center records through the user-service proxy." }, + { command: "decision show ", description: "Show one Decision Center record." }, + { command: "deploy check|plan|apply [--file deploy.json|--env dev|prod] [--service id] [--dry-run] [--force]", description: "Reconcile services from a repo+commit manifest; --env reads origin/master:deploy.json environments and can apply supported dev services." }, + { command: "dev-env validate|prewarm-images", description: "Validate D601 unidesk-dev guardrails or prewarm dev foundation images into native k3s containerd through a bounded async job." }, + { command: "schedule list|get|runs|run|delete", description: "Manage backend-core scheduled tasks and run history; schedule run supports --wait-ms N." }, + { command: "schedule upsert-pgdata-backup [--time HH:MM] [--remote-base /SERVER_DATA/UNIDESK_PG_DATA]", description: "Create or update the daily PGDATA physical backup task that uploads monthly rotated archives to Baidu Netdisk." }, + { command: "codex deploy [--provider-id D601] [--timeout-ms N]", description: "Compatibility wrapper for deploy apply --service code-queue with a temporary repo+commit manifest." }, + { command: "codex submit [prompt] [--prompt-file path|--prompt-stdin] [--queue queueId] [--provider-id id] [--cwd path] [--model model] [--execution-mode mode] [--max-attempts N] [--reference-task-id id] [--dry-run]", description: "Submit a Code Queue task through backend-core -> code-queue proxy; --dry-run shows the structured request without enqueueing." }, + { command: "codex task [--trace --tail|--from-start|--after-seq N|--before-seq N --limit N] [--full]", description: "Fetch a compact Code Queue task summary; trace rows are opt-in and paged with next/previous commands to avoid output explosion." }, + { command: "codex output [--tail|--from-start|--after-seq N|--before-seq N --limit N] [--full-text]", description: "Fetch paged raw Code Queue output records by seq when a trace row has omitted command/output text." }, + { command: "codex judge --attempt N [--dry-run] [--include-prompt]", description: "Replay one stored Code Queue attempt through the same judge context builder and MiniMax judge call path used by the live queue worker." }, + { command: "codex interrupt|cancel ", description: "Request interrupt for a running Code Queue task, or cancel a queued/retry_wait task, through the same private proxy." }, + { command: "codex (queues | queue create | queue merge --into | move --queue )", description: "List/create/merge Code Queue lanes and move a queued task; merge preserves task queue time order and deletes the source queue record." }, + { command: "job list [--limit N] [--include-command]", description: "List async jobs from .state/jobs with a bounded default page." }, + { command: "job status [--tail-bytes N]", description: "Show job state with bounded stdout/stderr tails." }, + { command: "debug health", description: "Probe internal core, nodes, system/Docker status, frontend, provider ingress, and public boundary." }, + { command: "debug dispatch [providerId] [docker.ps|provider.upgrade|host.ssh|microservice.http|echo] [--wait-ms N]", description: "Submit a real internal-core dispatch request for CLI debugging." }, + { command: "debug task ", description: "Read a dispatched task record from internal core for CLI debugging." }, + { command: "network perf [--service code-queue --path /api/tasks/overview?limit=30 --count N --concurrency N --label before|after]", description: "Benchmark frontend -> backend-core -> provider/adapter user-service networking and report latency/proxy-mode distributions." }, + { command: "ci install|status|run|run-dev-e2e|logs", description: "Manage D601 k3s Tekton CI only; run-dev-e2e manually validates master deploy.json dev state in an isolated temporary namespace." }, + { command: "e2e run [--only pattern[,pattern...]] [--skip pattern[,pattern...]]", description: "Run selected public/internal/Playwright E2E checks; use --only for focused iteration and rerun without filters for final regression." }, + ], + }; +} + +export function isHelpToken(value: string | undefined): boolean { + return value === "help" || value === "--help" || value === "-h"; +} + +export function serverHelp(action: string | undefined = undefined): unknown { + return { + command: action === undefined || isHelpToken(action) ? "server start|stop|status|swap|logs|rebuild" : `server ${action}`, + output: "json", + description: "Manage the fixed main-server Docker Compose stack without exposing backend-core REST publicly.", + usage: { + start: "bun scripts/cli.ts server start", + stop: "bun scripts/cli.ts server stop", + status: "bun scripts/cli.ts server status", + swap: "bun scripts/cli.ts server swap status|ensure [--path /swapfile] [--size 2GiB] [--dry-run]", + logs: "bun scripts/cli.ts server logs [--tail-bytes N]", + rebuild: "bun scripts/cli.ts server rebuild ", + }, + publicEntrypoints: { + frontend: "prod UniDesk frontend", + devFrontend: "dev UniDesk frontend proxy to D601 unidesk-dev/frontend-dev", + providerIngress: "provider-gateway WebSocket ingress", + }, + rustBoundary: { + masterServer: "do not use server rebuild backend-core for Rust iteration; it would build locally", + d601: "use deploy apply --env dev --service backend-core and CI for Rust build/check", + }, + }; +} + +export function sshHelp(): unknown { + return { + command: "ssh", + output: "json", + description: "Open a Host SSH / WSL SSH maintenance session through the provider-gateway bridge.", + usage: [ + "bun scripts/cli.ts ssh ", + "bun scripts/cli.ts ssh argv [args...]", + "bun scripts/cli.ts ssh apply-patch < patch.diff", + "bun scripts/cli.ts ssh py [script-args...] < script.py", + "bun scripts/cli.ts ssh skills [--scope all|wsl|windows] [--limit N]", + "bun scripts/cli.ts ssh find [--contains TEXT] [--limit N]", + "bun scripts/cli.ts ssh glob [--root DIR] [--pattern PATTERN]", + ], + notes: [ + "ssh --help and ssh --help print this JSON help and never open an interactive session.", + "Use argv when nested shell quoting would be fragile.", + "Use -- before a remote command that intentionally starts with a dash.", + ], + }; +} + +function configHelp(): unknown { + return { + command: "config show", + output: "json", + usage: "bun scripts/cli.ts config show", + description: "Validate and print the root config.json single source of truth.", + }; +} + +function microserviceHelp(): unknown { + return { + command: "microservice list|status|health|diagnostics|tunnel-self-test|proxy", + output: "json", + usage: [ + "bun scripts/cli.ts microservice list", + "bun scripts/cli.ts microservice status ", + "bun scripts/cli.ts microservice health ", + "bun scripts/cli.ts microservice diagnostics ", + "bun scripts/cli.ts microservice tunnel-self-test ", + "bun scripts/cli.ts microservice proxy [--method GET|POST|PUT|PATCH|DELETE] [--body-json JSON|--body-file path|--body-stdin] [--raw] [--full] [--max-body-bytes N]", + ], + description: "Access UniDesk-managed user services through the same backend-core/provider proxy path used by the WebUI.", + }; +} + +function decisionHelp(): unknown { + return { + command: "decision upload|list|show|health|diary", + output: "json", + usage: [ + "bun scripts/cli.ts decision upload [--title text] [--type meeting|decision]", + "bun scripts/cli.ts decision list [--type ...] [--status ...] [--level ...] [--limit N]", + "bun scripts/cli.ts decision show ", + "bun scripts/cli.ts decision health", + "bun scripts/cli.ts decision diary import|list|months|show ...", + ], + description: "Operate Decision Center through the registered user-service proxy.", + }; +} + +function providerHelp(): unknown { + return { + command: "provider attach", + output: "json", + usage: "bun scripts/cli.ts provider attach [--master-server URL] [--up] [--force]", + description: "Generate the minimal provider-gateway attach env/compose bundle for a new compute node.", + }; +} + +function scheduleHelp(): unknown { + return { + command: "schedule list|get|runs|run|delete|upsert-pgdata-backup", + output: "json", + usage: [ + "bun scripts/cli.ts schedule list", + "bun scripts/cli.ts schedule get ", + "bun scripts/cli.ts schedule runs [id] [--limit N]", + "bun scripts/cli.ts schedule run [--wait-ms N]", + "bun scripts/cli.ts schedule delete ", + "bun scripts/cli.ts schedule upsert-pgdata-backup [--time HH:MM] [--remote-base path]", + ], + description: "Manage backend-core scheduled tasks and run history through the private core API.", + }; +} + +function codexHelp(): unknown { + return { + command: "codex deploy|submit|task|output|judge|interrupt|cancel|queues|queue|move", + output: "json", + usage: [ + "bun scripts/cli.ts codex submit [prompt] [--prompt-file path|--prompt-stdin] [--queue id] [--dry-run]", + "bun scripts/cli.ts codex task [--trace --tail|--from-start|--after-seq N|--before-seq N --limit N] [--full]", + "bun scripts/cli.ts codex output [--tail|--from-start|--after-seq N|--before-seq N --limit N] [--full-text]", + "bun scripts/cli.ts codex judge --attempt N [--dry-run] [--include-prompt]", + "bun scripts/cli.ts codex interrupt|cancel ", + "bun scripts/cli.ts codex queues | queue create | queue merge --into | move --queue ", + ], + description: "Operate Code Queue through the stable backend-core private proxy path.", + }; +} + +function jobHelp(): unknown { + return { + command: "job list|status", + output: "json", + usage: [ + "bun scripts/cli.ts job list [--limit N] [--include-command]", + "bun scripts/cli.ts job status [--tail-bytes N]", + ], + description: "Inspect fire-and-forget job state from .state/jobs without streaming unbounded logs.", + }; +} + +function debugHelp(): unknown { + return { + command: "debug health|dispatch|task", + output: "json", + usage: [ + "bun scripts/cli.ts debug health", + "bun scripts/cli.ts debug dispatch [providerId] [docker.ps|provider.upgrade|host.ssh|microservice.http|echo] [--wait-ms N]", + "bun scripts/cli.ts debug task ", + ], + description: "Debug the real core/provider/dispatch paths; do not use these as formal TEST.md acceptance steps.", + }; +} + +function networkHelp(): unknown { + return { + command: "network perf", + output: "json", + usage: "bun scripts/cli.ts network perf [--service id --path /api/path --count N --concurrency N --label text]", + description: "Benchmark frontend/backend/provider user-service networking with bounded JSON latency summaries.", + }; +} + +function e2eHelp(): unknown { + return { + command: "e2e run", + output: "json", + usage: "bun scripts/cli.ts e2e run [--only pattern[,pattern...]] [--skip pattern[,pattern...]]", + description: "Run selected public/internal/Playwright E2E checks; use filters for focused iteration and full run for final regression.", + }; +} + +function devEnvHelp(): unknown { + return { + command: "dev-env validate|prewarm-images", + output: "json", + usage: [ + "bun scripts/cli.ts dev-env validate [--manifest path] [--kubectl-dry-run]", + "bun scripts/cli.ts dev-env prewarm-images [--image image] [--provider-id D601] [--no-pull] [--dry-run]", + ], + description: "Validate D601 unidesk-dev guardrails or prewarm foundation images into native k3s containerd.", + }; +} + +export function staticNamespaceHelp(args: string[]): unknown | null { + const [top] = args; + if (!args.slice(1).some(isHelpToken)) return null; + if (top === "config") return configHelp(); + if (top === "microservice") return microserviceHelp(); + if (top === "decision" || top === "decision-center") return decisionHelp(); + if (top === "provider") return providerHelp(); + if (top === "schedule") return scheduleHelp(); + if (top === "codex") return codexHelp(); + if (top === "job") return jobHelp(); + if (top === "debug") return debugHelp(); + if (top === "network") return networkHelp(); + if (top === "e2e") return e2eHelp(); + if (top === "dev-env") return devEnvHelp(); + return null; +}