docs: standardize trans passthrough alias

This commit is contained in:
Codex
2026-06-03 01:44:46 +00:00
parent f445f2abd8
commit faee528ed4
29 changed files with 261 additions and 248 deletions
@@ -58,10 +58,10 @@ export function runProviderRunnerTriageContract(): JsonRecord {
assertCondition(result.decision === "retryable-transient", "single path provider offline should be retryable transient", result);
assertCondition(result.retryable === true, "single path provider offline should be retryable", result);
assertCondition(result.contract.singlePathProviderOfflineIsGlobalBlocker === false, "triage contract should reject single-path global blocker", result);
assertCondition(result.recommendedCrossChecks.includes("bun scripts/cli.ts ssh D601 argv true"), "provider triage result must recommend argv Host SSH cross-check", result.recommendedCrossChecks);
assertCondition(result.recommendedCrossChecks.includes("trans D601 argv true"), "provider triage result must recommend argv Host SSH cross-check", result.recommendedCrossChecks);
const crossChecks = providerTriageRecommendedCrossChecks("D601");
assertCondition(crossChecks.includes("bun scripts/cli.ts ssh D601 argv true"), "provider triage recommendedCrossChecks must keep ssh argv true", crossChecks);
assertCondition(crossChecks.includes("trans D601 argv true"), "provider triage recommendedCrossChecks must keep ssh argv true", crossChecks);
assertCondition(crossChecks.includes("bun scripts/cli.ts debug dispatch D601 host.ssh --wait-ms 15000"), "provider triage recommendedCrossChecks must keep host.ssh dispatch probe", crossChecks);
const rateLimitTriage = buildProviderTriageResult("D601", [
+1 -1
View File
@@ -320,7 +320,7 @@ async function triggerCurrent(config: UniDeskConfig, options: TriggerOptions): P
created: compactCapture(created),
next: {
status: "bun scripts/cli.ts agentrun v01 control-plane status",
logs: `bun scripts/cli.ts ssh G14:k3s logs -n ${ciNamespace} -l tekton.dev/pipelineRun=${pipelineRun} --tail 120`,
logs: `trans G14:k3s logs -n ${ciNamespace} -l tekton.dev/pipelineRun=${pipelineRun} --tail 120`,
},
};
}
+3 -3
View File
@@ -104,11 +104,11 @@ export function isApplyPatchV2HelpArgs(args: string[] = []): boolean {
export function applyPatchV2HelpPayload() {
return {
ok: true,
command: "ssh <route> apply-patch < patch.diff",
command: "trans <route> apply-patch < patch.diff",
summary: "Apply a standard apply_patch patch to a remote route with the local v2 line-based engine.",
usage: [
"tran G14:/root/hwlab/.worktree/task apply-patch < patch.diff",
"bun scripts/cli.ts ssh D601:/tmp apply-patch < patch.diff"
"trans G14:/root/hwlab/.worktree/task apply-patch < patch.diff",
"trans D601:/tmp apply-patch < patch.diff"
],
input: {
required: true,
+52 -52
View File
@@ -22,19 +22,19 @@ export function rootHelp(): unknown {
{ command: "gc plan|run|db-trace|policy|remote [--confirm] [--logs-keep-days N] [--include-browser-cache]", description: "One-time main-server or remote provider disk relief and low-risk anti-bloat policy for logs, journald, allowlisted /tmp artifacts, scoped core dumps and explicit trace telemetry retention; plan is read-only and run requires --confirm." },
{ command: "server rebuild <backend-core|frontend|dev-frontend-proxy|provider-gateway|todo-note|code-queue-mgr|project-manager|baidu-netdisk|oa-event-flow>", description: "Maintenance-only local Compose rebuild for reviewed main-server services; frontend standard release must use CI artifact plus deploy apply dev/prod artifact consumers." },
{ command: "provider attach <providerId> [--master-server URL] [--up] [--force] | provider triage <providerId> [--observed-error text] [--observed-scope scope] [--microservice id ...] [--full|--raw]", description: "Generate the minimal external provider-gateway env/compose bundle or run the low-noise read-only provider health triage contract." },
{ command: "ssh <route> [operation args...]", description: "Open a Host SSH / WSL SSH maintenance session through the provider-gateway bridge; route syntax such as `G14:k3s` or `D601:win/c/test` only locates distributed targets." },
{ command: "ssh gh:/owner/repo[/pr|/issue][/number[/1]] ls|cat|rg|patch-apply", description: "Treat GitHub PRs/issues as virtual text directories; `ls --full` shows state/floors/body length, and `patch-apply` updates first-floor `body.md` through UniDesk gh plus apply-patch v2." },
{ command: "ssh <route> apply-patch < patch.diff", description: "Default remote text patch entry: apply a standard patch with the local TypeScript v2 engine while the remote route only reads and writes files." },
{ command: "ssh <route> upload <local-file> <remote-file> | ssh <route> download <remote-file> <local-file>", description: "Transfer whole files through SSH passthrough with remote temp files, automatic endpoint byte/SHA-256 verification, and client-side chunk fallback." },
{ command: "ssh <providerId> apply-patch-v1 [tool args...] < patch.diff", description: "Fallback to the injected legacy remote apply_patch helper directly over SSH passthrough and stream the patch from local stdin." },
{ command: "ssh <providerId> 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 <providerId> script [--shell sh|bash] [script-args...] <<'SCRIPT' ...", description: "Run a remote shell script from local stdin using shell -s; default sh inherits provider proxy env and gets the portable printf helper used by shell/script." },
{ command: "ssh <providerId> 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 <providerId> find <path...> [--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 <providerId> 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 <providerId>:/absolute/workspace <operation args...>", description: "Route directly into a host workspace while keeping the operation parser independent from the location." },
{ command: "ssh <providerId>:k3s[:namespace:workload[:container]] <kubectl|logs|exec|script|apply-patch|apply-patch-v1|command> ...", description: "Locate a native k3s control plane or workload with route syntax, then run a separate operation with KUBECONFIG fixed and argv assembled by the CLI." },
{ command: "ssh <providerId> argv <command> [args...]", description: "Run a non-interactive remote command with each argv token shell-quoted by UniDesk before SSH passthrough; use `ssh <providerId> script` when shell features are required." },
{ command: "trans <route> [operation args...] (alias of ssh <route> ...)", description: "Open a Host SSH / WSL SSH maintenance session through the provider-gateway bridge; `trans` is the short alias for this ssh passthrough path." },
{ command: "trans gh:/owner/repo[/pr|/issue][/number[/1]] ls|cat|rg|patch-apply", description: "Treat GitHub PRs/issues as virtual text directories; `ls --full` shows state/floors/body length, and `patch-apply` updates first-floor `body.md` through UniDesk gh plus apply-patch v2." },
{ command: "trans <route> apply-patch < patch.diff", description: "Default remote text patch entry: apply a standard patch with the local TypeScript v2 engine while the remote route only reads and writes files." },
{ command: "trans <route> upload <local-file> <remote-file> | trans <route> download <remote-file> <local-file>", description: "Transfer whole files through SSH passthrough with remote temp files, automatic endpoint byte/SHA-256 verification, and client-side chunk fallback." },
{ command: "trans <providerId> apply-patch-v1 [tool args...] < patch.diff", description: "Fallback to the injected legacy remote apply_patch helper directly over SSH passthrough and stream the patch from local stdin." },
{ command: "trans <providerId> 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: "trans <providerId> script [--shell sh|bash] [script-args...] <<'SCRIPT' ...", description: "Run a remote shell script from local stdin using shell -s; default sh inherits provider proxy env and gets the portable printf helper used by shell/script." },
{ command: "trans <providerId> skills [--scope all|wsl|windows] [--limit N]", description: "Discover WSL/Linux and, for WSL providers, Windows skill directories in one SSH passthrough call." },
{ command: "trans <providerId> find <path...> [--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: "trans <providerId> 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: "trans <providerId>:/absolute/workspace <operation args...>", description: "Route directly into a host workspace while keeping the operation parser independent from the location." },
{ command: "trans <providerId>:k3s[:namespace:workload[:container]] <kubectl|logs|exec|script|apply-patch|apply-patch-v1|command> ...", description: "Locate a native k3s control plane or workload with route syntax, then run a separate operation with KUBECONFIG fixed and argv assembled by the CLI." },
{ command: "trans <providerId> argv <command> [args...]", description: "Run a non-interactive remote command with each argv token shell-quoted by UniDesk before SSH passthrough; use `trans <providerId> script` when shell features are required." },
{ command: "microservice list", description: "List UniDesk-managed user services and their provider/runtime mapping." },
{ command: "microservice status <id>", description: "Show one user service config, repository reference, backend mapping, and runtime status." },
{ command: "microservice health <id> [--compact|--raw|--full]", description: "Probe one user service through backend-core -> provider-gateway HTTP proxy; default output is compact, and code-queue uses a commander-safe liveness summary unless raw/full is requested." },
@@ -155,44 +155,44 @@ export function sshHelp(): unknown {
output: "json",
description: "Open a Host SSH / WSL SSH maintenance session through the provider-gateway bridge.",
usage: [
"bun scripts/cli.ts ssh <route>",
"bun scripts/cli.ts ssh <providerId> argv <command> [args...]",
"bun scripts/cli.ts ssh <providerId>:/absolute/workspace apply-patch < patch.diff",
"bun scripts/cli.ts ssh <route> upload <local-file> <remote-file>",
"bun scripts/cli.ts ssh <route> download <remote-file> <local-file>",
"bun scripts/cli.ts ssh <providerId> apply-patch-v1 [--allow-loose] < patch.diff",
"bun scripts/cli.ts ssh <providerId> py [script-args...] < script.py",
"bun scripts/cli.ts ssh <providerId> script [--shell sh|bash] [script-args...] <<'SCRIPT'",
"bun scripts/cli.ts ssh <providerId> shell [--shell sh|bash] \"sed -n '1,20p' a && sed -n '1,20p' b\"",
"bun scripts/cli.ts ssh <providerId> skills [--scope all|wsl|windows] [--limit N]",
"bun scripts/cli.ts ssh <providerId> find <path...> [--contains TEXT] [--limit N]",
"bun scripts/cli.ts ssh <providerId> glob [--root DIR] [--pattern PATTERN]",
"bun scripts/cli.ts ssh D601:/home/ubuntu/workspace/hwlab-dev git status --short --branch",
"bun scripts/cli.ts ssh D601:win cmd ver",
"bun scripts/cli.ts ssh D601:win/c/test cmd cd",
"bun scripts/cli.ts ssh D601:win skills [--scope agents|codex|all] [--limit N]",
"bun scripts/cli.ts ssh D601:k3s",
"bun scripts/cli.ts ssh D601:k3s kubectl get pods -n hwlab-dev",
"bun scripts/cli.ts ssh G14:k3s",
"bun scripts/cli.ts ssh G14:k3s kubectl get pipelineruns -n hwlab-ci",
"bun scripts/cli.ts ssh D601:k3s script <<'SCRIPT'",
"bun scripts/cli.ts ssh D601:k3s:hwlab-dev:hwlab-cloud-api/app pwd",
"bun scripts/cli.ts ssh D601:k3s:hwlab-dev:hwlab-cloud-api/app apply-patch <<'PATCH'",
"bun scripts/cli.ts ssh D601:k3s:hwlab-dev:hwlab-cloud-api apply-patch-v1 <<'PATCH'",
"tar -C /path/to/files -cf - . | bun scripts/cli.ts ssh D601:k3s:unidesk:code-queue/root/unidesk exec --stdin -- tar -xf - -C /root/unidesk",
"bun scripts/cli.ts ssh D601:win/c/test apply-patch <<'PATCH'",
"bun scripts/cli.ts ssh D601:win upload ./tool.mjs F:\\Work\\hwlab\\.tmp\\tool.mjs",
"bun scripts/cli.ts ssh D601:win download F:\\Work\\hwlab\\.tmp\\tool.mjs ./tool.mjs",
"bun scripts/cli.ts ssh D601:k3s:hwlab-dev:hwlab-cloud-api node -e 'console.log(process.version)'",
"bun scripts/cli.ts ssh D601:k3s:hwlab-dev:hwlab-cloud-api script <<'SCRIPT'",
"bun scripts/cli.ts ssh D601:k3s:hwlab-dev:hwlab-cloud-api logs --tail 80",
"bun scripts/cli.ts ssh G14:k3s logs -n agentrun-ci -l tekton.dev/pipelineRun=<run> --tail 120",
"trans <route>",
"trans <providerId> argv <command> [args...]",
"trans <providerId>:/absolute/workspace apply-patch < patch.diff",
"trans <route> upload <local-file> <remote-file>",
"trans <route> download <remote-file> <local-file>",
"trans <providerId> apply-patch-v1 [--allow-loose] < patch.diff",
"trans <providerId> py [script-args...] < script.py",
"trans <providerId> script [--shell sh|bash] [script-args...] <<'SCRIPT'",
"trans <providerId> shell [--shell sh|bash] \"sed -n '1,20p' a && sed -n '1,20p' b\"",
"trans <providerId> skills [--scope all|wsl|windows] [--limit N]",
"trans <providerId> find <path...> [--contains TEXT] [--limit N]",
"trans <providerId> glob [--root DIR] [--pattern PATTERN]",
"trans D601:/home/ubuntu/workspace/hwlab-dev git status --short --branch",
"trans D601:win cmd ver",
"trans D601:win/c/test cmd cd",
"trans D601:win skills [--scope agents|codex|all] [--limit N]",
"trans D601:k3s",
"trans D601:k3s kubectl get pods -n hwlab-dev",
"trans G14:k3s",
"trans G14:k3s kubectl get pipelineruns -n hwlab-ci",
"trans D601:k3s script <<'SCRIPT'",
"trans D601:k3s:hwlab-dev:hwlab-cloud-api/app pwd",
"trans D601:k3s:hwlab-dev:hwlab-cloud-api/app apply-patch <<'PATCH'",
"trans D601:k3s:hwlab-dev:hwlab-cloud-api apply-patch-v1 <<'PATCH'",
"tar -C /path/to/files -cf - . | trans D601:k3s:unidesk:code-queue/root/unidesk exec --stdin -- tar -xf - -C /root/unidesk",
"trans D601:win/c/test apply-patch <<'PATCH'",
"trans D601:win upload ./tool.mjs F:\\Work\\hwlab\\.tmp\\tool.mjs",
"trans D601:win download F:\\Work\\hwlab\\.tmp\\tool.mjs ./tool.mjs",
"trans D601:k3s:hwlab-dev:hwlab-cloud-api node -e 'console.log(process.version)'",
"trans D601:k3s:hwlab-dev:hwlab-cloud-api script <<'SCRIPT'",
"trans D601:k3s:hwlab-dev:hwlab-cloud-api logs --tail 80",
"trans G14:k3s logs -n agentrun-ci -l tekton.dev/pipelineRun=<run> --tail 120",
],
notes: [
"ssh --help and ssh <route> --help print this JSON help and never open an interactive session.",
"trans --help and trans <route> --help print this JSON help and never open an interactive session; the underlying ssh subcommand keeps the same help behavior.",
"For non-interactive remote commands, prefer argv for a single process and script/stdin for shell logic.",
"For one-line remote shell logic without a heredoc, use `script -- '<command && command>'`; outer shell operators written outside tran, such as `tran G14:/repo sed ... && sed ...`, are parsed by the local shell before UniDesk starts and therefore cannot be redirected by the CLI. The explicit `shell '<command>'` operation remains available for the same sh -c path.",
"When a one-line shell command is easier to type through the script path, `script -- '<command && command>'` runs that single string through the remote shell without waiting for stdin. When `script --` is followed by multiple tokens, it stays a direct argv form for commands such as `tran D601:/work script -- sed -n '1,20p' file`.",
"For one-line remote shell logic without a heredoc, use `script -- '<command && command>'`; outer shell operators written outside trans, such as `trans G14:/repo sed ... && sed ...`, are parsed by the local shell before UniDesk starts and therefore cannot be redirected by the CLI. The explicit `shell '<command>'` operation remains available for the same sh -c path.",
"When a one-line shell command is easier to type through the script path, `script -- '<command && command>'` runs that single string through the remote shell without waiting for stdin. When `script --` is followed by multiple tokens, it stays a direct argv form for commands such as `trans D601:/work script -- sed -n '1,20p' file`.",
"script and shell helper modes inject a tiny POSIX-compatible printf wrapper before user shell text, so portable printf headings such as `printf \"--- section ---\\n\"` work consistently under dash/sh and bash. Direct argv commands are unchanged.",
"For arbitrary stdin streams into a workload command, use a workload route plus `exec --stdin -- <command> ...`; this keeps the route as location-only and avoids heredoc/base64/tar shell wrapping.",
"`apply-patch` is the default remote text patch entry and uses the v2 local line-based patch engine with remote read/write operations, including Windows routes such as `D601:win/c/test`, so long Unicode/Chinese lines and pure insertion hunks avoid the legacy remote shell hunk parser. Its stdout/stderr follows Codex apply_patch text output rather than UniDesk JSON output; on multi-file failure, stderr lists applied hunks before the first failed hunk and the failed hunk, then stops like Codex apply_patch.",
@@ -203,11 +203,11 @@ export function sshHelp(): unknown {
"Use `win`, not `win32`; the win route sets chcp 65001, PYTHONUTF8=1, and PYTHONIOENCODING=utf-8 before running the requested cmd command line.",
"`<provider>:win skills` discovers the current Windows user's `%USERPROFILE%\\.agents\\skills` by default; use `--scope all` to include `%USERPROFILE%\\.codex\\skills`.",
"Do not put operation names in any colon route segment, including nested k3s namespace/workload/container segments.",
"Do not use post-provider shorthand such as `ssh G14 k3s ...`; write `ssh G14:k3s ...` so location and operation stay separated.",
"Do not use post-provider shorthand such as `trans G14 k3s ...`; write `trans G14:k3s ...` so location and operation stay separated.",
"If an ssh-like remote command fails with timeout/kex/exit-255 friction, stderr includes one low-noise UNIDESK_SSH_HINT JSON line with the argv retry command.",
"Non-interactive ssh/tran operations have a hard top-level runtime timeout capped at 60s. Timeout writes UNIDESK_SSH_RUNTIME_TIMEOUT or UNIDESK_TRAN_TIMEOUT_HINT and disconnects the broker; long CI/CD, trace, logs, build, or hardware work must use submit-and-poll / short query loops instead of keeping tran open.",
"Only slow ssh/tran runtime writes UNIDESK_SSH_TIMING JSON to stderr; operations over 10s are marked level=warning even when they succeed, because slow successful calls are a distributed performance monitoring signal. Check provider latency, remote command cost, helper bootstrap, or remote patch optimization before repeating high-frequency work. Routine short calls do not emit timing noise.",
"The local tran wrapper must not add provider/plane directory locks; rely on k8s/Tekton/Argo/Lease or server-side TTL queues for coordination.",
"Non-interactive ssh/trans/tran operations have a hard top-level runtime timeout capped at 60s. Timeout writes UNIDESK_SSH_RUNTIME_TIMEOUT or UNIDESK_TRAN_TIMEOUT_HINT and disconnects the broker; long CI/CD, trace, logs, build, or hardware work must use submit-and-poll / short query loops instead of keeping trans open.",
"Only slow ssh/trans/tran runtime writes UNIDESK_SSH_TIMING JSON to stderr; operations over 10s are marked level=warning even when they succeed, because slow successful calls are a distributed performance monitoring signal. Check provider latency, remote command cost, helper bootstrap, or remote patch optimization before repeating high-frequency work. Routine short calls do not emit timing noise.",
"The local trans/tran wrapper must not add provider/plane directory locks; rely on k8s/Tekton/Argo/Lease or server-side TTL queues for coordination.",
"Use -- before a remote command that intentionally starts with a dash.",
],
};
+4 -4
View File
@@ -1709,8 +1709,8 @@ function runControlPlaneReleasedPvCleanup(options: G14ControlPlaneOptions): Reco
candidatesBefore: candidates,
deletion,
followUp: {
diskPressure: "bun scripts/cli.ts ssh G14:k3s kubectl get node ubuntu-rog-zephyrus-g14-ga401iv-ga401iv -o jsonpath='{range .status.conditions[*]}{.type}{\"=\"}{.status}{\" \"}{.reason}{\"\\n\"}{end}'",
storage: "bun scripts/cli.ts ssh G14 script -- 'df -h /; sudo du -xh -d 1 /var/lib/rancher/k3s/storage 2>/dev/null | sort -h | tail -20'",
diskPressure: "trans G14:k3s kubectl get node ubuntu-rog-zephyrus-g14-ga401iv-ga401iv -o jsonpath='{range .status.conditions[*]}{.type}{\"=\"}{.status}{\" \"}{.reason}{\"\\n\"}{end}'",
storage: "trans G14 script -- 'df -h /; sudo du -xh -d 1 /var/lib/rancher/k3s/storage 2>/dev/null | sort -h | tail -20'",
},
};
}
@@ -1803,7 +1803,7 @@ function runControlPlaneCleanup(options: G14ControlPlaneOptions): Record<string,
deletion,
followUp: {
status: "bun scripts/cli.ts hwlab g14 control-plane status --lane v02",
diskPressure: "bun scripts/cli.ts ssh G14:k3s kubectl get node ubuntu-rog-zephyrus-g14-ga401iv-ga401iv -o jsonpath='{.spec.taints}{\"\\n\"}{range .status.conditions[*]}{.type}{\"=\"}{.status}{\" \"}{.reason}{\"\\n\"}{end}'",
diskPressure: "trans G14:k3s kubectl get node ubuntu-rog-zephyrus-g14-ga401iv-ga401iv -o jsonpath='{.spec.taints}{\"\\n\"}{range .status.conditions[*]}{.type}{\"=\"}{.status}{\" \"}{.reason}{\"\\n\"}{end}'",
},
};
}
@@ -3660,7 +3660,7 @@ function newDailyBriefBody(date: string): string {
"## 常驻观察与长期建议",
"",
"- G14 HWLAB source truth 固定为 G14 `/root/hwlab` 与 `origin/G14`DEV rollout 只接受 G14 Tekton/GitOps/Argo 和公网 health 证据。",
"- GitHub issue/PR 写操作必须走 UniDesk `gh` 子命令;G14 k3s 操作必须走 UniDesk `ssh G14:k3s`。",
"- GitHub issue/PR 写操作必须走 UniDesk `gh` 子命令;G14 k3s 操作必须走 UniDesk `trans G14:k3s`。",
].join("\n");
}
+1 -1
View File
@@ -429,7 +429,7 @@ export function providerTriageRecommendedCrossChecks(providerId: string): string
`${commandPrefix} provider triage ${providerId}`,
`${commandPrefix} debug health`,
`${commandPrefix} debug dispatch ${providerId} host.ssh --wait-ms 15000`,
`${commandPrefix} ssh ${providerId} argv true`,
`trans ${providerId} argv true`,
`${commandPrefix} artifact-registry health --provider-id ${providerId}`,
`${commandPrefix} microservice health k3sctl-adapter`,
`${commandPrefix} microservice health code-queue`,
+1 -1
View File
@@ -145,7 +145,7 @@ function parseSshFileTransferCliOptions(args: string[]): SshFileTransferCliOptio
continue;
}
if (arg === "--help" || arg === "-h" || arg === "help") {
throw new Error(`ssh ${action} usage: ssh <route> ${action} ${action === "upload" ? "<local-file> <remote-file>" : "<remote-file> <local-file>"} [--chunk-bytes N]`);
throw new Error(`ssh ${action} usage: trans <route> ${action} ${action === "upload" ? "<local-file> <remote-file>" : "<remote-file> <local-file>"} [--chunk-bytes N]`);
}
if (arg.startsWith("-")) throw new Error(`unsupported ssh ${action} option: ${arg}`);
positionals.push(arg);
+5 -5
View File
@@ -879,7 +879,7 @@ export function parseSshArgs(args: string[]): ParsedSshArgs {
return { remoteCommand: shellArgv(["glob", ...args.slice(1)]), requiresStdin: false, invocationKind: "helper", requiredHelpers: ["glob"] };
}
if (subcommand === "k3s") {
throw new Error("ssh k3s shorthand is unsupported; put k3s in the route, for example: ssh D601:k3s kubectl get nodes");
throw new Error("ssh k3s shorthand is unsupported; put k3s in the route, for example: trans D601:k3s kubectl get nodes");
}
if (argvQuotedSshSubcommands.has(subcommand)) {
return { remoteCommand: shellArgv(args), requiresStdin: false, invocationKind: "argv" };
@@ -919,13 +919,13 @@ export function parseSshInvocation(target: string, args: string[]): ParsedSshInv
return { providerId: route.providerId, route, parsed: parseWinRouteArgs(route, args) };
}
if ((args[0] ?? "") === "k3s") {
throw new Error(`ssh k3s shorthand is unsupported; use route syntax instead: ssh ${route.providerId}:k3s ${args.slice(1).join(" ")}`.trim());
throw new Error(`ssh k3s shorthand is unsupported; use route syntax instead: trans ${route.providerId}:k3s ${args.slice(1).join(" ")}`.trim());
}
return { providerId: route.providerId, route, parsed: parseSshArgs(args) };
}
export function parseSshRoute(target: string): ParsedSshRoute {
if (!target) throw new Error("ssh requires provider id, for example: bun scripts/cli.ts ssh D601");
if (!target) throw new Error("ssh requires provider id, for example: trans D601");
const firstColon = target.indexOf(":");
if (firstColon < 0) {
return hostSshRoute(target, target, null);
@@ -946,7 +946,7 @@ export function parseSshRoute(target: string): ParsedSshRoute {
return winSshRoute(providerId, target, parseWinRouteWorkspace(providerId, tail));
}
if (tail.startsWith("win:")) {
throw new Error(`ssh win workspace route uses slash syntax, for example: ssh ${providerId}:win/c/test cmd cd`);
throw new Error(`ssh win workspace route uses slash syntax, for example: trans ${providerId}:win/c/test cmd cd`);
}
const [plane, ...rest] = tail.split(":");
if (plane === undefined || plane.length === 0 || plane === "host") {
@@ -2034,7 +2034,7 @@ export function sshFailureHint(providerId: string, parsed: ParsedSshArgs, exitCo
trigger,
exitCode,
message: "ssh-like remote command failed before proving Host SSH is globally unavailable; prefer structured argv or stdin script passthrough for non-interactive commands.",
try: `bun scripts/cli.ts ssh ${shownProviderId} script <<'SCRIPT'`,
try: `trans ${shownProviderId} script <<'SCRIPT'`,
triage: `bun scripts/cli.ts provider triage ${shownProviderId} --observed-scope ssh --observed-error '<ssh-like timeout or kex failure>'`,
note: "This hint intentionally does not echo the original remote command.",
};
+17 -17
View File
@@ -548,7 +548,7 @@ export async function runSshArgvGuidanceContract(): Promise<JsonRecord> {
assertThrows(
() => parseSshInvocation("G14", ["k3s", "kubectl", "get", "nodes"]),
/unsupported.*ssh G14:k3s/u,
/unsupported.*trans G14:k3s/u,
"k3s must be a route plane, not a post-provider shorthand",
);
@@ -1075,7 +1075,7 @@ export async function runSshArgvGuidanceContract(): Promise<JsonRecord> {
assertThrows(
() => parseSshInvocation("D601:k3s:kubectl", ["get", "pods"]),
/route must locate a target only.*ssh D601:k3s kubectl/u,
/route must locate a target only.*trans D601:k3s kubectl/u,
"operation names must not be accepted as k3s route segments",
);
assertThrows(
@@ -1123,7 +1123,7 @@ export async function runSshArgvGuidanceContract(): Promise<JsonRecord> {
const sshLike = parseSshArgs(["echo hello"]);
const hint = sshFailureHint("D601", sshLike, 255, "kex_exchange_identification: Connection closed by remote host");
assertCondition(hint !== null, "ssh-like kex failure must produce a hint", sshLike);
assertCondition(hint?.try === "bun scripts/cli.ts ssh D601 script <<'SCRIPT'", "hint must provide canonical stdin script retry", hint);
assertCondition(hint?.try === "trans D601 script <<'SCRIPT'", "hint must provide canonical stdin script retry", hint);
assertCondition(hint?.triage.includes("provider triage D601"), "hint must provide provider triage command", hint);
const formatted = formatSshFailureHint(hint!);
assertCondition(formatted.startsWith("UNIDESK_SSH_HINT "), "formatted hint must have structured prefix", formatted);
@@ -1172,30 +1172,30 @@ export async function runSshArgvGuidanceContract(): Promise<JsonRecord> {
const help = sshHelp() as { notes?: unknown };
const helpText = JSON.stringify(help);
const helpNotes = Array.isArray(help.notes) ? help.notes.map(String).join("\n") : "";
assertCondition(helpText.includes("ssh <providerId> script [--shell sh|bash] [script-args...] <<'SCRIPT'"), "ssh help must recommend stdin script passthrough for shell scripts", helpText);
assertCondition(helpText.includes("ssh <providerId> shell [--shell sh|bash]") && helpText.includes("outer shell operators written outside tran"), "ssh help must document one-line shell passthrough and the local operator boundary", helpText);
assertCondition(helpText.includes("trans <providerId> script [--shell sh|bash] [script-args...] <<'SCRIPT'"), "ssh help must recommend stdin script passthrough for shell scripts", helpText);
assertCondition(helpText.includes("trans <providerId> shell [--shell sh|bash]") && helpText.includes("outer shell operators written outside trans"), "ssh help must document one-line shell passthrough and the local operator boundary", helpText);
assertCondition(helpText.includes("inherits provider proxy variables"), "ssh help must state default script inherits provider proxy env", helpText);
assertCondition(helpText.includes("not as a proxy workaround"), "ssh help must reserve --shell bash for bash syntax instead of proxy workarounds", helpText);
assertCondition(helpNotes.includes("portable printf headings") && helpNotes.includes('printf "--- section ---\\n"'), "ssh help must document script/shell printf heading compatibility", helpNotes);
assertCondition(helpText.includes("ssh D601:/home/ubuntu/workspace/hwlab-dev git status --short --branch"), "ssh help must document host workspace routes", helpText);
assertCondition(helpText.includes("ssh D601:win cmd ver") && helpText.includes("ssh D601:win/c/test cmd cd") && helpText.includes("ssh D601:win skills"), "ssh help must document Windows cmd and skills win routes", helpText);
assertCondition(helpText.includes("trans D601:/home/ubuntu/workspace/hwlab-dev git status --short --branch"), "ssh help must document host workspace routes", helpText);
assertCondition(helpText.includes("trans D601:win cmd ver") && helpText.includes("trans D601:win/c/test cmd cd") && helpText.includes("trans D601:win skills"), "ssh help must document Windows cmd and skills win routes", helpText);
assertCondition(helpText.includes("Use `win`, not `win32`") && helpText.includes("chcp 65001") && helpText.includes("PYTHONIOENCODING=utf-8"), "ssh help must document win route UTF-8 defaults and naming", helpText);
assertCondition(helpText.includes("ssh D601:k3s kubectl get pods -n hwlab-dev"), "ssh help must document k3s kubectl operation", helpText);
assertCondition(helpText.includes("ssh G14:k3s kubectl get pipelineruns -n hwlab-ci"), "ssh help must document G14 k3s route operation", helpText);
assertCondition(helpText.includes("ssh D601:k3s:hwlab-dev:hwlab-cloud-api/app pwd"), "ssh help must document k3s pod workspace route", helpText);
assertCondition(helpText.includes("ssh D601:k3s script <<'SCRIPT'"), "ssh help must document k3s control-plane script operation", helpText);
assertCondition(helpText.includes("ssh D601:k3s:hwlab-dev:hwlab-cloud-api/app apply-patch <<'PATCH'"), "ssh help must document k3s pod apply-patch operation", helpText);
assertCondition(helpText.includes("ssh <route> upload <local-file> <remote-file>") && helpText.includes("ssh <route> download <remote-file> <local-file>"), "ssh help must document verified file transfer operations", helpText);
assertCondition(helpText.includes("ssh D601:k3s:unidesk:code-queue/root/unidesk exec --stdin -- tar -xf - -C /root/unidesk"), "ssh help must document one-step stdin file streaming into pod exec", helpText);
assertCondition(helpText.includes("trans D601:k3s kubectl get pods -n hwlab-dev"), "ssh help must document k3s kubectl operation", helpText);
assertCondition(helpText.includes("trans G14:k3s kubectl get pipelineruns -n hwlab-ci"), "ssh help must document G14 k3s route operation", helpText);
assertCondition(helpText.includes("trans D601:k3s:hwlab-dev:hwlab-cloud-api/app pwd"), "ssh help must document k3s pod workspace route", helpText);
assertCondition(helpText.includes("trans D601:k3s script <<'SCRIPT'"), "ssh help must document k3s control-plane script operation", helpText);
assertCondition(helpText.includes("trans D601:k3s:hwlab-dev:hwlab-cloud-api/app apply-patch <<'PATCH'"), "ssh help must document k3s pod apply-patch operation", helpText);
assertCondition(helpText.includes("trans <route> upload <local-file> <remote-file>") && helpText.includes("trans <route> download <remote-file> <local-file>"), "ssh help must document verified file transfer operations", helpText);
assertCondition(helpText.includes("trans D601:k3s:unidesk:code-queue/root/unidesk exec --stdin -- tar -xf - -C /root/unidesk"), "ssh help must document one-step stdin file streaming into pod exec", helpText);
assertCondition(helpText.includes("apply-patch-v1 [--allow-loose]") && helpText.includes("low-context update hunks"), "ssh help must document apply-patch-v1 loose-context guard", helpText);
assertCondition(helpText.includes("ssh D601:k3s:hwlab-dev:hwlab-cloud-api script <<'SCRIPT'"), "ssh help must document k3s script operation", helpText);
assertCondition(helpText.includes("trans D601:k3s:hwlab-dev:hwlab-cloud-api script <<'SCRIPT'"), "ssh help must document k3s script operation", helpText);
assertCondition(helpText.includes("UNIDESK_SSH_HINT"), "ssh help must document structured failure hint", helpText);
assertCondition(helpText.includes("UNIDESK_SSH_RUNTIME_TIMEOUT") && helpText.includes("UNIDESK_TRAN_TIMEOUT_HINT") && helpText.includes("60s") && helpText.includes("submit-and-poll"), "ssh help must document top-level runtime timeout and short polling discipline", helpText);
assertCondition(helpText.includes("UNIDESK_SSH_TIMING") && helpText.includes("10s") && helpText.includes("slow successful calls are a distributed performance monitoring signal") && helpText.includes("Routine short calls do not emit timing noise"), "ssh help must document slow-only runtime timing hints", helpText);
assertCondition(helpText.includes("must not add provider/plane directory locks") && helpText.includes("k8s/Tekton/Argo/Lease"), "ssh help must document tran's no-local-lock boundary", helpText);
const crossChecks = providerTriageRecommendedCrossChecks("D601");
assertCondition(crossChecks.includes("bun scripts/cli.ts ssh D601 argv true"), "provider triage cross-checks must keep argv true", crossChecks);
assertCondition(crossChecks.includes("trans D601 argv true"), "provider triage cross-checks must keep argv true", crossChecks);
const frontendRemoteK3sPlan = remoteSshFrontendPlanForTest("D601:k3s", ["kubectl", "get", "nodes", "-o", "name"]);
assertCondition(frontendRemoteK3sPlan.transport === "frontend-websocket", "remote frontend ssh must use the streaming websocket bridge", frontendRemoteK3sPlan);
@@ -1280,7 +1280,7 @@ export async function runSshArgvGuidanceContract(): Promise<JsonRecord> {
"ssh-like timeout/kex failures emit one structured argv retry hint",
"ssh runtime emits structured timing for slow operations over 10 seconds, including successful slow calls",
"help text documents stdin script passthrough and UNIDESK_SSH_HINT",
"provider triage recommendedCrossChecks keeps ssh D601 argv true",
"provider triage recommendedCrossChecks keeps trans D601 argv true",
"remote frontend ssh uses the same structured route parser for host, k3s and pod argv routes",
"ssh helper bootstrap is lazy so plain argv/script commands do not transfer helper sources",
"host apply-patch-v1 bootstraps only the apply_patch helper and uses a Perl fast path for large files",
Executable
+10
View File
@@ -0,0 +1,10 @@
#!/bin/sh
set -eu
repo=${UNIDESK_TRANS_REPO_ROOT:-/root/unidesk}
if [ ! -f "$repo/scripts/cli.ts" ]; then
self_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
repo=$(CDPATH= cd -- "$self_dir/.." && pwd)
fi
exec bun "$repo/scripts/cli.ts" ssh "$@"