fix: keep ssh route operation boundaries

This commit is contained in:
Codex
2026-05-25 06:56:47 +00:00
parent 0d6bcd9f26
commit 2eaad15fb2
4 changed files with 20 additions and 11 deletions
+2 -1
View File
@@ -166,7 +166,8 @@ export function sshHelp(): unknown {
notes: [
"ssh --help and ssh <route> --help print this JSON help and never open an interactive session.",
"For non-interactive remote commands, prefer argv for a single process and script/stdin for shell logic.",
"Route syntax is provider:plane[:namespace:resource[:container]] and locates a distributed target only. For native k3s providers such as D601 and G14, <provider>:k3s locates the control plane, <provider>:k3s:<namespace>:<workload> locates a workload, and kubectl/script/logs/apply-patch/exec are operations placed after the route.",
"Route syntax is `{provider}:{plane}[:{scope...}] {operation} [operation-args...]`: the first argv token locates a distributed target only, and every following token belongs to the operation parser. For native k3s providers such as D601 and G14, <provider>:k3s locates the control plane, <provider>:k3s:<namespace>:<workload> locates a workload, and kubectl/script/logs/apply-patch/exec are operations placed after the route.",
"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.",
"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.",
"Use -- before a remote command that intentionally starts with a dash.",
+5 -7
View File
@@ -867,7 +867,8 @@ export function parseSshRoute(target: string): ParsedSshRoute {
}
if (plane !== "k3s") throw new Error(`unsupported ssh route plane: ${plane}`);
const [first, second, third, fourth] = rest;
if (first && legacyK3sOperationRouteSegments.has(first)) throw new Error(k3sOperationInRouteMessage(target, first, second, third));
const operationInRoute = [first, second, third].find((segment) => segment !== undefined && legacyK3sOperationRouteSegments.has(segment));
if (operationInRoute !== undefined) throw new Error(k3sOperationInRouteMessage(target, operationInRoute));
if (fourth !== undefined) throw new Error("ssh k3s target route supports at most provider:k3s:namespace:resource:container");
return {
providerId,
@@ -880,13 +881,10 @@ export function parseSshRoute(target: string): ParsedSshRoute {
};
}
function k3sOperationInRouteMessage(target: string, operation: string, namespace: string | undefined, resource: string | undefined): string {
function k3sOperationInRouteMessage(target: string, operation: string): string {
const providerId = target.split(":")[0] || "<provider>";
if (operation === "kubectl") return `ssh k3s route must locate a target only; use "ssh ${providerId}:k3s kubectl ..." instead of "${target}"`;
if (operation === "script" && namespace === undefined) return `ssh k3s route must locate a target only; use "ssh ${providerId}:k3s script <<'SCRIPT'" instead of "${target}"`;
if (operation === "guard") return `ssh k3s route must locate a target only; use "ssh ${providerId}:k3s guard" or "ssh ${providerId}:k3s" instead of "${target}"`;
if (namespace !== undefined && resource !== undefined) return `ssh k3s route must locate a target only; use "ssh ${providerId}:k3s:${namespace}:${resource} ${operation} ..." instead of "${target}"`;
return `ssh k3s route must locate a target only; put operation "${operation}" after the route, for example "ssh ${providerId}:k3s ${operation} ..."`;
const operationExample = operation === "guard" ? "guard" : `${operation} ...`;
return `ssh k3s route must locate a target only; put operation "${operation}" after the route, for example "ssh ${providerId}:k3s ${operationExample}" or "ssh ${providerId}:k3s:<namespace>:<workload> ${operationExample}" instead of "${target}"`;
}
function shellArgv(args: string[]): string {