@kimchi-dev/kimchictl 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +203 -0
- package/README.md +105 -0
- package/bin/kimchictl.mjs +17 -0
- package/dist/api/http.d.ts +7 -0
- package/dist/api/http.d.ts.map +1 -0
- package/dist/api/http.js +60 -0
- package/dist/api/http.js.map +1 -0
- package/dist/api/keys.d.ts +7 -0
- package/dist/api/keys.d.ts.map +1 -0
- package/dist/api/keys.js +28 -0
- package/dist/api/keys.js.map +1 -0
- package/dist/api/me.d.ts +12 -0
- package/dist/api/me.d.ts.map +1 -0
- package/dist/api/me.js +28 -0
- package/dist/api/me.js.map +1 -0
- package/dist/api/resolver.d.ts +26 -0
- package/dist/api/resolver.d.ts.map +1 -0
- package/dist/api/resolver.js +62 -0
- package/dist/api/resolver.js.map +1 -0
- package/dist/api/resources.d.ts +16 -0
- package/dist/api/resources.d.ts.map +1 -0
- package/dist/api/resources.js +96 -0
- package/dist/api/resources.js.map +1 -0
- package/dist/api/templates.d.ts +19 -0
- package/dist/api/templates.d.ts.map +1 -0
- package/dist/api/templates.js +25 -0
- package/dist/api/templates.js.map +1 -0
- package/dist/api/tokens.d.ts +23 -0
- package/dist/api/tokens.d.ts.map +1 -0
- package/dist/api/tokens.js +51 -0
- package/dist/api/tokens.js.map +1 -0
- package/dist/api/types.d.ts +34 -0
- package/dist/api/types.d.ts.map +1 -0
- package/dist/api/types.js +32 -0
- package/dist/api/types.js.map +1 -0
- package/dist/api/wait.d.ts +17 -0
- package/dist/api/wait.d.ts.map +1 -0
- package/dist/api/wait.js +37 -0
- package/dist/api/wait.js.map +1 -0
- package/dist/api/workspaces.d.ts +33 -0
- package/dist/api/workspaces.d.ts.map +1 -0
- package/dist/api/workspaces.js +159 -0
- package/dist/api/workspaces.js.map +1 -0
- package/dist/auth/auth-file.d.ts +37 -0
- package/dist/auth/auth-file.d.ts.map +1 -0
- package/dist/auth/auth-file.js +100 -0
- package/dist/auth/auth-file.js.map +1 -0
- package/dist/auth/browser-login.d.ts +45 -0
- package/dist/auth/browser-login.d.ts.map +1 -0
- package/dist/auth/browser-login.js +195 -0
- package/dist/auth/browser-login.js.map +1 -0
- package/dist/auth/config-file.d.ts +4 -0
- package/dist/auth/config-file.d.ts.map +1 -0
- package/dist/auth/config-file.js +52 -0
- package/dist/auth/config-file.js.map +1 -0
- package/dist/auth/models-providers.d.ts +17 -0
- package/dist/auth/models-providers.d.ts.map +1 -0
- package/dist/auth/models-providers.js +63 -0
- package/dist/auth/models-providers.js.map +1 -0
- package/dist/auth/paths.d.ts +15 -0
- package/dist/auth/paths.d.ts.map +1 -0
- package/dist/auth/paths.js +25 -0
- package/dist/auth/paths.js.map +1 -0
- package/dist/auth/provider-ids.d.ts +4 -0
- package/dist/auth/provider-ids.d.ts.map +1 -0
- package/dist/auth/provider-ids.js +12 -0
- package/dist/auth/provider-ids.js.map +1 -0
- package/dist/auth/resolve.d.ts +19 -0
- package/dist/auth/resolve.d.ts.map +1 -0
- package/dist/auth/resolve.js +37 -0
- package/dist/auth/resolve.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +15 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/completion.d.ts +2 -0
- package/dist/commands/completion.d.ts.map +1 -0
- package/dist/commands/completion.js +71 -0
- package/dist/commands/completion.js.map +1 -0
- package/dist/commands/flags.d.ts +16 -0
- package/dist/commands/flags.d.ts.map +1 -0
- package/dist/commands/flags.js +23 -0
- package/dist/commands/flags.js.map +1 -0
- package/dist/commands/guard.d.ts +10 -0
- package/dist/commands/guard.d.ts.map +1 -0
- package/dist/commands/guard.js +41 -0
- package/dist/commands/guard.js.map +1 -0
- package/dist/commands/help.d.ts +4 -0
- package/dist/commands/help.d.ts.map +1 -0
- package/dist/commands/help.js +19 -0
- package/dist/commands/help.js.map +1 -0
- package/dist/commands/login.d.ts +18 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +80 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logout.d.ts +14 -0
- package/dist/commands/logout.d.ts.map +1 -0
- package/dist/commands/logout.js +41 -0
- package/dist/commands/logout.js.map +1 -0
- package/dist/commands/prompt.d.ts +9 -0
- package/dist/commands/prompt.d.ts.map +1 -0
- package/dist/commands/prompt.js +21 -0
- package/dist/commands/prompt.js.map +1 -0
- package/dist/commands/registry.d.ts +16 -0
- package/dist/commands/registry.d.ts.map +1 -0
- package/dist/commands/registry.js +35 -0
- package/dist/commands/registry.js.map +1 -0
- package/dist/commands/ssh.d.ts +15 -0
- package/dist/commands/ssh.d.ts.map +1 -0
- package/dist/commands/ssh.js +90 -0
- package/dist/commands/ssh.js.map +1 -0
- package/dist/commands/table.d.ts +3 -0
- package/dist/commands/table.d.ts.map +1 -0
- package/dist/commands/table.js +10 -0
- package/dist/commands/table.js.map +1 -0
- package/dist/commands/update.d.ts +16 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +57 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/commands/version.d.ts +2 -0
- package/dist/commands/version.d.ts.map +1 -0
- package/dist/commands/version.js +6 -0
- package/dist/commands/version.js.map +1 -0
- package/dist/commands/whoami.d.ts +7 -0
- package/dist/commands/whoami.d.ts.map +1 -0
- package/dist/commands/whoami.js +30 -0
- package/dist/commands/whoami.js.map +1 -0
- package/dist/commands/workspace.d.ts +15 -0
- package/dist/commands/workspace.d.ts.map +1 -0
- package/dist/commands/workspace.js +206 -0
- package/dist/commands/workspace.js.map +1 -0
- package/dist/run.d.ts +10 -0
- package/dist/run.d.ts.map +1 -0
- package/dist/run.js +30 -0
- package/dist/run.js.map +1 -0
- package/dist/ssh/bridge.d.ts +25 -0
- package/dist/ssh/bridge.d.ts.map +1 -0
- package/dist/ssh/bridge.js +95 -0
- package/dist/ssh/bridge.js.map +1 -0
- package/dist/ssh/config.d.ts +65 -0
- package/dist/ssh/config.d.ts.map +1 -0
- package/dist/ssh/config.js +198 -0
- package/dist/ssh/config.js.map +1 -0
- package/dist/ssh/connect.d.ts +27 -0
- package/dist/ssh/connect.d.ts.map +1 -0
- package/dist/ssh/connect.js +67 -0
- package/dist/ssh/connect.js.map +1 -0
- package/dist/ssh/tunnel.d.ts +29 -0
- package/dist/ssh/tunnel.d.ts.map +1 -0
- package/dist/ssh/tunnel.js +36 -0
- package/dist/ssh/tunnel.js.map +1 -0
- package/dist/test-support.d.ts +30 -0
- package/dist/test-support.d.ts.map +1 -0
- package/dist/test-support.js +67 -0
- package/dist/test-support.js.map +1 -0
- package/dist/updates/check.d.ts +18 -0
- package/dist/updates/check.d.ts.map +1 -0
- package/dist/updates/check.js +90 -0
- package/dist/updates/check.js.map +1 -0
- package/dist/updates/self.d.ts +36 -0
- package/dist/updates/self.d.ts.map +1 -0
- package/dist/updates/self.js +103 -0
- package/dist/updates/self.js.map +1 -0
- package/dist/version.d.ts +7 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +7 -0
- package/dist/version.js.map +1 -0
- package/package.json +77 -0
- package/src/api/http.ts +70 -0
- package/src/api/keys.ts +40 -0
- package/src/api/me.ts +48 -0
- package/src/api/resolver.test.ts +109 -0
- package/src/api/resolver.ts +72 -0
- package/src/api/resources.test.ts +56 -0
- package/src/api/resources.ts +92 -0
- package/src/api/templates.ts +28 -0
- package/src/api/tokens.ts +80 -0
- package/src/api/types.ts +50 -0
- package/src/api/wait.ts +45 -0
- package/src/api/workspaces.ts +234 -0
- package/src/auth/auth-file.test.ts +176 -0
- package/src/auth/auth-file.ts +103 -0
- package/src/auth/browser-login.test.ts +93 -0
- package/src/auth/browser-login.ts +247 -0
- package/src/auth/config-file.test.ts +68 -0
- package/src/auth/config-file.ts +50 -0
- package/src/auth/models-providers.test.ts +63 -0
- package/src/auth/models-providers.ts +76 -0
- package/src/auth/paths.ts +28 -0
- package/src/auth/provider-ids.ts +15 -0
- package/src/auth/resolve.test.ts +72 -0
- package/src/auth/resolve.ts +46 -0
- package/src/cli.ts +16 -0
- package/src/commands/completion.test.ts +31 -0
- package/src/commands/completion.ts +77 -0
- package/src/commands/flags.ts +29 -0
- package/src/commands/guard.ts +44 -0
- package/src/commands/help.ts +20 -0
- package/src/commands/login.test.ts +159 -0
- package/src/commands/login.ts +94 -0
- package/src/commands/logout.test.ts +89 -0
- package/src/commands/logout.ts +53 -0
- package/src/commands/prompt.ts +21 -0
- package/src/commands/registry.ts +50 -0
- package/src/commands/ssh.test.ts +101 -0
- package/src/commands/ssh.ts +112 -0
- package/src/commands/table.test.ts +24 -0
- package/src/commands/table.ts +10 -0
- package/src/commands/update.test.ts +91 -0
- package/src/commands/update.ts +78 -0
- package/src/commands/version.ts +6 -0
- package/src/commands/whoami.test.ts +72 -0
- package/src/commands/whoami.ts +36 -0
- package/src/commands/workspace.test.ts +360 -0
- package/src/commands/workspace.ts +250 -0
- package/src/run.test.ts +53 -0
- package/src/run.ts +34 -0
- package/src/ssh/bridge.test.ts +70 -0
- package/src/ssh/bridge.ts +110 -0
- package/src/ssh/config.test.ts +213 -0
- package/src/ssh/config.ts +233 -0
- package/src/ssh/connect.test.ts +123 -0
- package/src/ssh/connect.ts +98 -0
- package/src/ssh/tunnel.test.ts +119 -0
- package/src/ssh/tunnel.ts +64 -0
- package/src/test-support.ts +77 -0
- package/src/updates/check.test.ts +99 -0
- package/src/updates/check.ts +117 -0
- package/src/updates/self.test.ts +117 -0
- package/src/updates/self.ts +131 -0
- package/src/version.ts +6 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { WebSocket } from "ws";
|
|
2
|
+
/**
|
|
3
|
+
* Binary WebSocket↔stdio bridge — the runtime behind the hidden
|
|
4
|
+
* `kimchictl ssh proxy` command that the generated ssh_config references via
|
|
5
|
+
* ProxyCommand. TS port of kap's internal/ssh/bridge.go.
|
|
6
|
+
*
|
|
7
|
+
* Exit semantics (mirroring kap): stdin EOF or a normal WS close end the
|
|
8
|
+
* bridge without error; any other failure rejects with a descriptive error.
|
|
9
|
+
* The proxy process is short-lived — it exits when the bridge resolves, so
|
|
10
|
+
* dangling listeners cannot outlive it.
|
|
11
|
+
*/
|
|
12
|
+
export class BridgeError extends Error {
|
|
13
|
+
constructor(message) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = "BridgeError";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export async function bridgeStdioToWebSocket(options) {
|
|
19
|
+
const WS = options._WebSocket ?? WebSocket;
|
|
20
|
+
const input = options.input ?? process.stdin;
|
|
21
|
+
const output = options.output ?? process.stdout;
|
|
22
|
+
let ws;
|
|
23
|
+
try {
|
|
24
|
+
ws = new WS(options.wsUrl, {
|
|
25
|
+
headers: { Authorization: `Bearer ${options.token}` },
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
throw new BridgeError(`websocket dial ${options.wsUrl}: ${err instanceof Error ? err.message : String(err)}`);
|
|
30
|
+
}
|
|
31
|
+
await new Promise((resolve, reject) => {
|
|
32
|
+
let settled = false;
|
|
33
|
+
const done = (err) => {
|
|
34
|
+
if (settled)
|
|
35
|
+
return;
|
|
36
|
+
settled = true;
|
|
37
|
+
if (err)
|
|
38
|
+
reject(err);
|
|
39
|
+
else
|
|
40
|
+
resolve();
|
|
41
|
+
};
|
|
42
|
+
ws.on("open", () => done());
|
|
43
|
+
ws.on("unexpected-response", (_req, res) => {
|
|
44
|
+
done(new BridgeError(`websocket dial ${options.wsUrl}: unexpected HTTP ${res.statusCode} ${res.statusMessage ?? ""}`.trim()));
|
|
45
|
+
});
|
|
46
|
+
ws.on("error", (err) => done(new BridgeError(`websocket dial ${options.wsUrl}: ${err.message}`)));
|
|
47
|
+
});
|
|
48
|
+
return new Promise((resolve, reject) => {
|
|
49
|
+
let settled = false;
|
|
50
|
+
const finish = (err) => {
|
|
51
|
+
if (settled)
|
|
52
|
+
return;
|
|
53
|
+
settled = true;
|
|
54
|
+
input.removeAllListeners();
|
|
55
|
+
// Output is process.stdout when bridging for real — never end() that;
|
|
56
|
+
// only close streams the caller explicitly passed in.
|
|
57
|
+
if (options.output)
|
|
58
|
+
output.end();
|
|
59
|
+
if (err)
|
|
60
|
+
reject(err);
|
|
61
|
+
else
|
|
62
|
+
resolve();
|
|
63
|
+
};
|
|
64
|
+
// WS → stdout
|
|
65
|
+
ws.on("message", (data, isBinary) => {
|
|
66
|
+
// The workspace bridge is binary-only; text frames would be a server bug,
|
|
67
|
+
// but writing them through is still friendlier than dropping bytes.
|
|
68
|
+
void isBinary;
|
|
69
|
+
const buffer = Buffer.isBuffer(data) ? data : Array.isArray(data) ? Buffer.concat(data) : Buffer.from(data);
|
|
70
|
+
if (!output.write(buffer)) {
|
|
71
|
+
// Keep memory bounded if the consumer stalls: pause the socket until drained.
|
|
72
|
+
ws.pause();
|
|
73
|
+
output.once("drain", () => ws.resume());
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
ws.on("close", () => finish());
|
|
77
|
+
ws.on("error", (err) => finish(err.code === 1000 || err.code === 1001
|
|
78
|
+
? undefined // normal / going-away closure
|
|
79
|
+
: new BridgeError(`websocket error: ${err.message}`)));
|
|
80
|
+
// stdin → WS
|
|
81
|
+
if (input.isPaused?.())
|
|
82
|
+
input.resume();
|
|
83
|
+
input.on("data", (chunk) => {
|
|
84
|
+
if (ws.readyState !== WebSocket.OPEN)
|
|
85
|
+
return;
|
|
86
|
+
ws.send(chunk, { binary: true });
|
|
87
|
+
});
|
|
88
|
+
input.on("end", () => {
|
|
89
|
+
if (ws.readyState === WebSocket.OPEN)
|
|
90
|
+
ws.close(1000);
|
|
91
|
+
});
|
|
92
|
+
input.on("error", (err) => finish(err));
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge.js","sourceRoot":"../src/","sources":["ssh/bridge.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native SSH integration (TS port of kap:internal/ssh/setup.go).
|
|
3
|
+
*
|
|
4
|
+
* Layout:
|
|
5
|
+
* ~/.config/kimchi/kimchictl/ssh_config — `Host *.<domain>` wildcard routing
|
|
6
|
+
* through `kimchictl ssh proxy %h` as ProxyCommand
|
|
7
|
+
* ~/.config/kimchi/kimchictl/known_hosts — dedicated known_hosts for workspace hosts
|
|
8
|
+
* ~/.ssh/config — gets a marker-wrapped `Include` block
|
|
9
|
+
*
|
|
10
|
+
* Behavior per the product decision "setup happens on install": writes are
|
|
11
|
+
* automatic (no y/N prompt) with a clear notice; `--uninstall` reverts.
|
|
12
|
+
*/
|
|
13
|
+
export declare const SSH_INCLUDE_BEGIN = "# >>> kimchictl managed include >>>";
|
|
14
|
+
export declare const SSH_INCLUDE_END = "# <<< kimchictl managed include <<<";
|
|
15
|
+
export interface SshPaths {
|
|
16
|
+
/** ~/.config/kimchi/kimchictl root (also used for the update-check cache). */
|
|
17
|
+
home: string;
|
|
18
|
+
sshConfig: string;
|
|
19
|
+
knownHosts: string;
|
|
20
|
+
userSshConfig: string;
|
|
21
|
+
}
|
|
22
|
+
export declare function resolveSshPaths(env?: NodeJS.ProcessEnv): SshPaths;
|
|
23
|
+
/**
|
|
24
|
+
* The wildcard domain workspaces are reached at. Default remote.kimchi.dev;
|
|
25
|
+
* derived from the API endpoint host for dev environments
|
|
26
|
+
* (app.X → remote.X), overridable via flag/env for exotic setups.
|
|
27
|
+
*/
|
|
28
|
+
export declare function resolveSshDomain(explicit?: string, env?: NodeJS.ProcessEnv): string;
|
|
29
|
+
/**
|
|
30
|
+
* The ProxyCommand must not rely on ssh's PATH lookup — point it at a
|
|
31
|
+
* fully-qualified command:
|
|
32
|
+
* - compiled binary: its own absolute path
|
|
33
|
+
* - node/bun runtime: the runtime plus the ABSOLUTE entry script
|
|
34
|
+
* (e.g. `/usr/bin/node /…/dist/cli.js`), so the config works from any cwd
|
|
35
|
+
* Bare `kimchictl` remains only as a last resort when no entry can be named.
|
|
36
|
+
*/
|
|
37
|
+
export declare function resolveProxyCommandTarget(execPath?: string, entryScript?: string): string;
|
|
38
|
+
export declare function sshConfigContent(domain: string, knownHostsPath: string, proxyTarget: string): string;
|
|
39
|
+
export interface SetupResult {
|
|
40
|
+
notes: string[];
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Write the kimchictl ssh_config and ensure ~/.ssh/config includes it.
|
|
44
|
+
* Idempotent; a stale managed block (different include path) is replaced in
|
|
45
|
+
* place. Returns human-readable notes of everything that changed.
|
|
46
|
+
*/
|
|
47
|
+
export declare function setupSshIntegration(options: {
|
|
48
|
+
paths: SshPaths;
|
|
49
|
+
domain: string;
|
|
50
|
+
proxyTarget?: string;
|
|
51
|
+
/** Detect and warn about a kap-managed block (same wildcard domain conflict). */
|
|
52
|
+
warnKapConflict?: (note: string) => void;
|
|
53
|
+
}): Promise<SetupResult>;
|
|
54
|
+
/** Remove the Include block and the generated ssh_config. Returns notes. */
|
|
55
|
+
export declare function uninstallSshIntegration(options: {
|
|
56
|
+
paths: SshPaths;
|
|
57
|
+
}): Promise<SetupResult>;
|
|
58
|
+
/**
|
|
59
|
+
* True when the generated ssh_config exists AND the user config includes it.
|
|
60
|
+
* Used by `ssh <name>` / `login` to auto-setup when missing.
|
|
61
|
+
*/
|
|
62
|
+
export declare function isSshIntegrationConfigured(paths: SshPaths, domain: string, proxyTarget?: string): Promise<boolean>;
|
|
63
|
+
/** Disabled-marker env var honored by every auto-setup caller. */
|
|
64
|
+
export declare function isSshSetupDisabled(env?: NodeJS.ProcessEnv): boolean;
|
|
65
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"../src/","sources":["ssh/config.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;GAWG;AAEH,eAAO,MAAM,iBAAiB,wCAAwC,CAAA;AACtE,eAAO,MAAM,eAAe,wCAAwC,CAAA;AAEpE,MAAM,WAAW,QAAQ;IACxB,8EAA8E;IAC9E,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,aAAa,EAAE,MAAM,CAAA;CACrB;AAED,wBAAgB,eAAe,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,QAAQ,CAU9E;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAchG;AAOD;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,GAAE,MAAyB,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAU3G;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAQpG;AAED,MAAM,WAAW,WAAW;IAC3B,KAAK,EAAE,MAAM,EAAE,CAAA;CACf;AAqBD;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,EAAE;IAClD,KAAK,EAAE,QAAQ,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,iFAAiF;IACjF,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CACxC,GAAG,OAAO,CAAC,WAAW,CAAC,CAwCvB;AAED,4EAA4E;AAC5E,wBAAsB,uBAAuB,CAAC,OAAO,EAAE;IAAE,KAAK,EAAE,QAAQ,CAAA;CAAE,GAAG,OAAO,CAAC,WAAW,CAAC,CA2BhG;AAED;;;GAGG;AACH,wBAAsB,0BAA0B,CAC/C,KAAK,EAAE,QAAQ,EACf,MAAM,EAAE,MAAM,EACd,WAAW,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,OAAO,CAAC,CAelB;AAED,kEAAkE;AAClE,wBAAgB,kBAAkB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAGhF"}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { chmod, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { dirname, join, resolve } from "node:path";
|
|
5
|
+
/**
|
|
6
|
+
* Native SSH integration (TS port of kap:internal/ssh/setup.go).
|
|
7
|
+
*
|
|
8
|
+
* Layout:
|
|
9
|
+
* ~/.config/kimchi/kimchictl/ssh_config — `Host *.<domain>` wildcard routing
|
|
10
|
+
* through `kimchictl ssh proxy %h` as ProxyCommand
|
|
11
|
+
* ~/.config/kimchi/kimchictl/known_hosts — dedicated known_hosts for workspace hosts
|
|
12
|
+
* ~/.ssh/config — gets a marker-wrapped `Include` block
|
|
13
|
+
*
|
|
14
|
+
* Behavior per the product decision "setup happens on install": writes are
|
|
15
|
+
* automatic (no y/N prompt) with a clear notice; `--uninstall` reverts.
|
|
16
|
+
*/
|
|
17
|
+
export const SSH_INCLUDE_BEGIN = "# >>> kimchictl managed include >>>";
|
|
18
|
+
export const SSH_INCLUDE_END = "# <<< kimchictl managed include <<<";
|
|
19
|
+
export function resolveSshPaths(env = process.env) {
|
|
20
|
+
// Lives with the rest of the kimchi config tree (harness/, desktop/, memory/…).
|
|
21
|
+
const home = env.KIMCHICTL_HOME ?? join(env.HOME ?? homedir(), ".config", "kimchi", "kimchictl");
|
|
22
|
+
const userSshConfig = join(env.HOME ?? homedir(), ".ssh", "config");
|
|
23
|
+
return {
|
|
24
|
+
home,
|
|
25
|
+
sshConfig: join(home, "ssh_config"),
|
|
26
|
+
knownHosts: join(home, "known_hosts"),
|
|
27
|
+
userSshConfig,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The wildcard domain workspaces are reached at. Default remote.kimchi.dev;
|
|
32
|
+
* derived from the API endpoint host for dev environments
|
|
33
|
+
* (app.X → remote.X), overridable via flag/env for exotic setups.
|
|
34
|
+
*/
|
|
35
|
+
export function resolveSshDomain(explicit, env = process.env) {
|
|
36
|
+
if (explicit)
|
|
37
|
+
return explicit;
|
|
38
|
+
if (env.KIMCHICTL_SSH_DOMAIN)
|
|
39
|
+
return env.KIMCHICTL_SSH_DOMAIN;
|
|
40
|
+
const endpoint = env.KIMCHI_REMOTE_ENDPOINT;
|
|
41
|
+
if (endpoint) {
|
|
42
|
+
try {
|
|
43
|
+
const host = new URL(endpoint).hostname;
|
|
44
|
+
const stripped = host.startsWith("app.") ? host.slice("app.".length) : host;
|
|
45
|
+
return `remote.${stripped}`;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// Fall through to the production default below.
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return "remote.kimchi.dev";
|
|
52
|
+
}
|
|
53
|
+
/** Shell-quote a single ProxyCommand token when it contains whitespace. */
|
|
54
|
+
function shellQuote(token) {
|
|
55
|
+
return /\s|"|'/.test(token) ? `"${token.replace(/"/g, '\\"')}"` : token;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The ProxyCommand must not rely on ssh's PATH lookup — point it at a
|
|
59
|
+
* fully-qualified command:
|
|
60
|
+
* - compiled binary: its own absolute path
|
|
61
|
+
* - node/bun runtime: the runtime plus the ABSOLUTE entry script
|
|
62
|
+
* (e.g. `/usr/bin/node /…/dist/cli.js`), so the config works from any cwd
|
|
63
|
+
* Bare `kimchictl` remains only as a last resort when no entry can be named.
|
|
64
|
+
*/
|
|
65
|
+
export function resolveProxyCommandTarget(execPath = process.execPath, entryScript) {
|
|
66
|
+
const base = execPath.split("/").pop() ?? "";
|
|
67
|
+
if (base === "kimchictl" || base.startsWith("kimchictl-")) {
|
|
68
|
+
return shellQuote(execPath);
|
|
69
|
+
}
|
|
70
|
+
const entry = entryScript ?? process.argv[1];
|
|
71
|
+
if (entry) {
|
|
72
|
+
return `${shellQuote(execPath)} ${shellQuote(resolve(entry))}`;
|
|
73
|
+
}
|
|
74
|
+
return "kimchictl";
|
|
75
|
+
}
|
|
76
|
+
export function sshConfigContent(domain, knownHostsPath, proxyTarget) {
|
|
77
|
+
return (`Host *.${domain}\n` +
|
|
78
|
+
` ProxyCommand ${proxyTarget} ssh proxy %h\n` +
|
|
79
|
+
` UserKnownHostsFile ${knownHostsPath}\n` +
|
|
80
|
+
` StrictHostKeyChecking accept-new\n` +
|
|
81
|
+
` User developer\n`);
|
|
82
|
+
}
|
|
83
|
+
async function writePrivateFile(path, content) {
|
|
84
|
+
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
85
|
+
await writeFile(path, content, { mode: 0o600 });
|
|
86
|
+
await chmod(path, 0o600);
|
|
87
|
+
}
|
|
88
|
+
function managedBlock(includePath) {
|
|
89
|
+
return `${SSH_INCLUDE_BEGIN}\nInclude ${includePath}\n${SSH_INCLUDE_END}`;
|
|
90
|
+
}
|
|
91
|
+
/** Extract an existing managed include block (any path), or undefined. */
|
|
92
|
+
function extractManagedBlock(content) {
|
|
93
|
+
const start = content.indexOf(SSH_INCLUDE_BEGIN);
|
|
94
|
+
if (start === -1)
|
|
95
|
+
return undefined;
|
|
96
|
+
const endRel = content.slice(start).indexOf(SSH_INCLUDE_END);
|
|
97
|
+
if (endRel === -1)
|
|
98
|
+
return undefined;
|
|
99
|
+
return content.slice(start, start + endRel + SSH_INCLUDE_END.length + 1);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Write the kimchictl ssh_config and ensure ~/.ssh/config includes it.
|
|
103
|
+
* Idempotent; a stale managed block (different include path) is replaced in
|
|
104
|
+
* place. Returns human-readable notes of everything that changed.
|
|
105
|
+
*/
|
|
106
|
+
export async function setupSshIntegration(options) {
|
|
107
|
+
const { paths, domain } = options;
|
|
108
|
+
const notes = [];
|
|
109
|
+
const content = sshConfigContent(domain, paths.knownHosts, options.proxyTarget ?? resolveProxyCommandTarget());
|
|
110
|
+
const previous = existsSync(paths.sshConfig) ? await readFile(paths.sshConfig, "utf-8") : undefined;
|
|
111
|
+
if (previous !== content) {
|
|
112
|
+
await writePrivateFile(paths.sshConfig, content);
|
|
113
|
+
notes.push(`wrote ${paths.sshConfig} (Host *.${domain})`);
|
|
114
|
+
}
|
|
115
|
+
let userConfig = "";
|
|
116
|
+
try {
|
|
117
|
+
userConfig = await readFile(paths.userSshConfig, "utf-8");
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
if (err.code !== "ENOENT")
|
|
121
|
+
throw err;
|
|
122
|
+
}
|
|
123
|
+
if (userConfig.includes("# >>> kap managed include >>>")) {
|
|
124
|
+
notes.push("⚠ found a kap-managed Include block in ~/.ssh/config — both wildcard configs match " +
|
|
125
|
+
"this domain; the first Include wins. Remove one if connections route to the wrong proxy.");
|
|
126
|
+
}
|
|
127
|
+
const block = managedBlock(paths.sshConfig);
|
|
128
|
+
const existing = extractManagedBlock(userConfig);
|
|
129
|
+
if (existing === undefined) {
|
|
130
|
+
// First match in ssh config wins for most options: the Include must come first.
|
|
131
|
+
const next = userConfig.length > 0 ? `${block}\n\n${userConfig}` : `${block}\n`;
|
|
132
|
+
await writePrivateFile(paths.userSshConfig, next);
|
|
133
|
+
notes.push(`added Include block to ${paths.userSshConfig}`);
|
|
134
|
+
}
|
|
135
|
+
else if (existing !== block && existing !== `${block}\n`) {
|
|
136
|
+
// Stale block (e.g. config dir moved) — replace in place.
|
|
137
|
+
const next = userConfig.replace(existing, `${block}\n`);
|
|
138
|
+
await writePrivateFile(paths.userSshConfig, next);
|
|
139
|
+
notes.push(`updated stale Include block in ${paths.userSshConfig}`);
|
|
140
|
+
}
|
|
141
|
+
return { notes };
|
|
142
|
+
}
|
|
143
|
+
/** Remove the Include block and the generated ssh_config. Returns notes. */
|
|
144
|
+
export async function uninstallSshIntegration(options) {
|
|
145
|
+
const { paths } = options;
|
|
146
|
+
const notes = [];
|
|
147
|
+
let userConfig = "";
|
|
148
|
+
try {
|
|
149
|
+
userConfig = await readFile(paths.userSshConfig, "utf-8");
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
if (err.code !== "ENOENT")
|
|
153
|
+
throw err;
|
|
154
|
+
}
|
|
155
|
+
const existing = extractManagedBlock(userConfig);
|
|
156
|
+
if (existing !== undefined) {
|
|
157
|
+
let next = userConfig.replace(existing, "");
|
|
158
|
+
// Tidy the blank line we added after the block when nothing precedes it.
|
|
159
|
+
next = next.replace(/^\n+/, "").replace(/\n{3,}/g, "\n\n");
|
|
160
|
+
await writePrivateFile(paths.userSshConfig, next);
|
|
161
|
+
notes.push(`removed Include block from ${paths.userSshConfig}`);
|
|
162
|
+
}
|
|
163
|
+
if (existsSync(paths.sshConfig)) {
|
|
164
|
+
// Remove rather than truncate: keep the dir (it also holds known_hosts/cache).
|
|
165
|
+
await rm(paths.sshConfig, { force: true });
|
|
166
|
+
notes.push(`removed ${paths.sshConfig}`);
|
|
167
|
+
}
|
|
168
|
+
return { notes };
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* True when the generated ssh_config exists AND the user config includes it.
|
|
172
|
+
* Used by `ssh <name>` / `login` to auto-setup when missing.
|
|
173
|
+
*/
|
|
174
|
+
export async function isSshIntegrationConfigured(paths, domain, proxyTarget) {
|
|
175
|
+
if (!existsSync(paths.sshConfig))
|
|
176
|
+
return false;
|
|
177
|
+
try {
|
|
178
|
+
const userConfig = await readFile(paths.userSshConfig, "utf-8");
|
|
179
|
+
const expected = managedBlock(paths.sshConfig);
|
|
180
|
+
const existing = extractManagedBlock(userConfig);
|
|
181
|
+
if (existing !== expected && existing !== `${expected}\n`)
|
|
182
|
+
return false;
|
|
183
|
+
const generated = await readFile(paths.sshConfig, "utf-8");
|
|
184
|
+
// Both domain drift (endpoint override changed) and proxy-target drift
|
|
185
|
+
// (the executable moved since setup) must re-trigger setup: compare the
|
|
186
|
+
// full expected content.
|
|
187
|
+
return generated === sshConfigContent(domain, paths.knownHosts, proxyTarget ?? resolveProxyCommandTarget());
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/** Disabled-marker env var honored by every auto-setup caller. */
|
|
194
|
+
export function isSshSetupDisabled(env = process.env) {
|
|
195
|
+
const value = env.KIMCHICTL_NO_SSH_SETUP;
|
|
196
|
+
return value !== undefined && value !== "" && value !== "0" && value !== "false";
|
|
197
|
+
}
|
|
198
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"../src/","sources":["ssh/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AACpC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACxE,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACjC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAElD;;;;;;;;;;;GAWG;AAEH,MAAM,CAAC,MAAM,iBAAiB,GAAG,qCAAqC,CAAA;AACtE,MAAM,CAAC,MAAM,eAAe,GAAG,qCAAqC,CAAA;AAUpE,MAAM,UAAU,eAAe,CAAC,MAAyB,OAAO,CAAC,GAAG;IACnE,gFAAgF;IAChF,MAAM,IAAI,GAAG,GAAG,CAAC,cAAc,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,OAAO,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAA;IAChG,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,OAAO,EAAE,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAA;IACnE,OAAO;QACN,IAAI;QACJ,SAAS,EAAE,IAAI,CAAC,IAAI,EAAE,YAAY,CAAC;QACnC,UAAU,EAAE,IAAI,CAAC,IAAI,EAAE,aAAa,CAAC;QACrC,aAAa;KACb,CAAA;AACF,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAiB,EAAE,MAAyB,OAAO,CAAC,GAAG;IACvF,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAA;IAC7B,IAAI,GAAG,CAAC,oBAAoB;QAAE,OAAO,GAAG,CAAC,oBAAoB,CAAA;IAC7D,MAAM,QAAQ,GAAG,GAAG,CAAC,sBAAsB,CAAA;IAC3C,IAAI,QAAQ,EAAE,CAAC;QACd,IAAI,CAAC;YACJ,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAA;YACvC,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;YAC3E,OAAO,UAAU,QAAQ,EAAE,CAAA;QAC5B,CAAC;QAAC,MAAM,CAAC;YACR,gDAAgD;QACjD,CAAC;IACF,CAAC;IACD,OAAO,mBAAmB,CAAA;AAC3B,CAAC;AAED,2EAA2E;AAC3E,SAAS,UAAU,CAAC,KAAa;IAChC,OAAO,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAA;AACxE,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,yBAAyB,CAAC,WAAmB,OAAO,CAAC,QAAQ,EAAE,WAAoB;IAClG,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,CAAA;IAC5C,IAAI,IAAI,KAAK,WAAW,IAAI,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QAC3D,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAA;IAC5B,CAAC;IACD,MAAM,KAAK,GAAG,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IAC5C,IAAI,KAAK,EAAE,CAAC;QACX,OAAO,GAAG,UAAU,CAAC,QAAQ,CAAC,IAAI,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAA;IAC/D,CAAC;IACD,OAAO,WAAW,CAAA;AACnB,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,MAAc,EAAE,cAAsB,EAAE,WAAmB;IAC3F,OAAO,CACN,UAAU,MAAM,IAAI;QACpB,oBAAoB,WAAW,iBAAiB;QAChD,0BAA0B,cAAc,IAAI;QAC5C,wCAAwC;QACxC,sBAAsB,CACtB,CAAA;AACF,CAAC;AAMD,KAAK,UAAU,gBAAgB,CAAC,IAAY,EAAE,OAAe;IAC5D,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IAC5D,MAAM,SAAS,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IAC/C,MAAM,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;AACzB,CAAC;AAED,SAAS,YAAY,CAAC,WAAmB;IACxC,OAAO,GAAG,iBAAiB,aAAa,WAAW,KAAK,eAAe,EAAE,CAAA;AAC1E,CAAC;AAED,0EAA0E;AAC1E,SAAS,mBAAmB,CAAC,OAAe;IAC3C,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAA;IAChD,IAAI,KAAK,KAAK,CAAC,CAAC;QAAE,OAAO,SAAS,CAAA;IAClC,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,eAAe,CAAC,CAAA;IAC5D,IAAI,MAAM,KAAK,CAAC,CAAC;QAAE,OAAO,SAAS,CAAA;IACnC,OAAO,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,GAAG,eAAe,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;AACzE,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,OAMzC;IACA,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAA;IACjC,MAAM,KAAK,GAAa,EAAE,CAAA;IAE1B,MAAM,OAAO,GAAG,gBAAgB,CAAC,MAAM,EAAE,KAAK,CAAC,UAAU,EAAE,OAAO,CAAC,WAAW,IAAI,yBAAyB,EAAE,CAAC,CAAA;IAC9G,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,MAAM,QAAQ,CAAC,KAAK,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IACnG,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QAC1B,MAAM,gBAAgB,CAAC,KAAK,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA;QAChD,KAAK,CAAC,IAAI,CAAC,SAAS,KAAK,CAAC,SAAS,YAAY,MAAM,GAAG,CAAC,CAAA;IAC1D,CAAC;IAED,IAAI,UAAU,GAAG,EAAE,CAAA;IACnB,IAAI,CAAC;QACJ,UAAU,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,aAAa,EAAE,OAAO,CAAC,CAAA;IAC1D,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;YAAE,MAAM,GAAG,CAAA;IAChE,CAAC;IAED,IAAI,UAAU,CAAC,QAAQ,CAAC,+BAA+B,CAAC,EAAE,CAAC;QAC1D,KAAK,CAAC,IAAI,CACT,qFAAqF;YACpF,0FAA0F,CAC3F,CAAA;IACF,CAAC;IAED,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;IAC3C,MAAM,QAAQ,GAAG,mBAAmB,CAAC,UAAU,CAAC,CAAA;IAChD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC5B,gFAAgF;QAChF,MAAM,IAAI,GAAG,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,OAAO,UAAU,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,IAAI,CAAA;QAC/E,MAAM,gBAAgB,CAAC,KAAK,CAAC,aAAa,EAAE,IAAI,CAAC,CAAA;QACjD,KAAK,CAAC,IAAI,CAAC,0BAA0B,KAAK,CAAC,aAAa,EAAE,CAAC,CAAA;IAC5D,CAAC;SAAM,IAAI,QAAQ,KAAK,KAAK,IAAI,QAAQ,KAAK,GAAG,KAAK,IAAI,EAAE,CAAC;QAC5D,0DAA0D;QAC1D,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,KAAK,IAAI,CAAC,CAAA;QACvD,MAAM,gBAAgB,CAAC,KAAK,CAAC,aAAa,EAAE,IAAI,CAAC,CAAA;QACjD,KAAK,CAAC,IAAI,CAAC,kCAAkC,KAAK,CAAC,aAAa,EAAE,CAAC,CAAA;IACpE,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,CAAA;AACjB,CAAC;AAED,4EAA4E;AAC5E,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,OAA4B;IACzE,MAAM,EAAE,KAAK,EAAE,GAAG,OAAO,CAAA;IACzB,MAAM,KAAK,GAAa,EAAE,CAAA;IAE1B,IAAI,UAAU,GAAG,EAAE,CAAA;IACnB,IAAI,CAAC;QACJ,UAAU,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,aAAa,EAAE,OAAO,CAAC,CAAA;IAC1D,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;YAAE,MAAM,GAAG,CAAA;IAChE,CAAC;IAED,MAAM,QAAQ,GAAG,mBAAmB,CAAC,UAAU,CAAC,CAAA;IAChD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC5B,IAAI,IAAI,GAAG,UAAU,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAA;QAC3C,yEAAyE;QACzE,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC,CAAA;QAC1D,MAAM,gBAAgB,CAAC,KAAK,CAAC,aAAa,EAAE,IAAI,CAAC,CAAA;QACjD,KAAK,CAAC,IAAI,CAAC,8BAA8B,KAAK,CAAC,aAAa,EAAE,CAAC,CAAA;IAChE,CAAC;IAED,IAAI,UAAU,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;QACjC,+EAA+E;QAC/E,MAAM,EAAE,CAAC,KAAK,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;QAC1C,KAAK,CAAC,IAAI,CAAC,WAAW,KAAK,CAAC,SAAS,EAAE,CAAC,CAAA;IACzC,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,CAAA;AACjB,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC/C,KAAe,EACf,MAAc,EACd,WAAoB;IAEpB,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,SAAS,CAAC;QAAE,OAAO,KAAK,CAAA;IAC9C,IAAI,CAAC;QACJ,MAAM,UAAU,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,aAAa,EAAE,OAAO,CAAC,CAAA;QAC/D,MAAM,QAAQ,GAAG,YAAY,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;QAC9C,MAAM,QAAQ,GAAG,mBAAmB,CAAC,UAAU,CAAC,CAAA;QAChD,IAAI,QAAQ,KAAK,QAAQ,IAAI,QAAQ,KAAK,GAAG,QAAQ,IAAI;YAAE,OAAO,KAAK,CAAA;QACvE,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA;QAC1D,uEAAuE;QACvE,wEAAwE;QACxE,yBAAyB;QACzB,OAAO,SAAS,KAAK,gBAAgB,CAAC,MAAM,EAAE,KAAK,CAAC,UAAU,EAAE,WAAW,IAAI,yBAAyB,EAAE,CAAC,CAAA;IAC5G,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,KAAK,CAAA;IACb,CAAC;AACF,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,kBAAkB,CAAC,MAAyB,OAAO,CAAC,GAAG;IACtE,MAAM,KAAK,GAAG,GAAG,CAAC,sBAAsB,CAAA;IACxC,OAAO,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE,IAAI,KAAK,KAAK,GAAG,IAAI,KAAK,KAAK,OAAO,CAAA;AACjF,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ApiOptions } from "../api/types.js";
|
|
2
|
+
import { isSshIntegrationConfigured, setupSshIntegration } from "./config.js";
|
|
3
|
+
/**
|
|
4
|
+
* Validate that native SSH integration is configured; set it up automatically
|
|
5
|
+
* when missing (the "setup on use" half of the setup-on-install decision).
|
|
6
|
+
* Returns false when auto-setup is disabled via KIMCHICTL_NO_SSH_SETUP.
|
|
7
|
+
*/
|
|
8
|
+
export declare function ensureSshIntegration(options: {
|
|
9
|
+
env?: NodeJS.ProcessEnv;
|
|
10
|
+
domain?: string;
|
|
11
|
+
/** Injection seams for tests. */
|
|
12
|
+
setup?: typeof setupSshIntegration;
|
|
13
|
+
isConfigured?: typeof isSshIntegrationConfigured;
|
|
14
|
+
}): Promise<boolean>;
|
|
15
|
+
export interface SshConnectOptions extends ApiOptions {
|
|
16
|
+
env?: NodeJS.ProcessEnv;
|
|
17
|
+
/** Exec seam (tests): run system ssh, return its exit status. */
|
|
18
|
+
exec?: (args: string[]) => number;
|
|
19
|
+
sleep?: (ms: number) => Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Connect to a workspace with system ssh: ensure integration → resume a
|
|
23
|
+
* hibernated workspace (and wait for it) → exec `ssh <alias>.<domain>` with
|
|
24
|
+
* inherited stdio. Extra args are passed to ssh as the remote command.
|
|
25
|
+
*/
|
|
26
|
+
export declare function connectToWorkspace(name: string, remoteCommand: string[], options?: SshConnectOptions): Promise<number>;
|
|
27
|
+
//# sourceMappingURL=connect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connect.d.ts","sourceRoot":"../src/","sources":["ssh/connect.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAGjD,OAAO,EACN,0BAA0B,EAI1B,mBAAmB,EACnB,MAAM,aAAa,CAAA;AAMpB;;;;GAIG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,EAAE;IACnD,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAA;IACvB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,iCAAiC;IACjC,KAAK,CAAC,EAAE,OAAO,mBAAmB,CAAA;IAClC,YAAY,CAAC,EAAE,OAAO,0BAA0B,CAAA;CAChD,GAAG,OAAO,CAAC,OAAO,CAAC,CAenB;AAED,MAAM,WAAW,iBAAkB,SAAQ,UAAU;IACpD,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAA;IACvB,iEAAiE;IACjE,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,MAAM,CAAA;IACjC,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CACrC;AAMD;;;;GAIG;AACH,wBAAsB,kBAAkB,CACvC,IAAI,EAAE,MAAM,EACZ,aAAa,EAAE,MAAM,EAAE,EACvB,OAAO,GAAE,iBAAsB,GAC7B,OAAO,CAAC,MAAM,CAAC,CAgCjB"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { resolveWorkspace } from "../api/resolver.js";
|
|
3
|
+
import { waitForWorkspaceActive } from "../api/wait.js";
|
|
4
|
+
import { requireApiKey } from "../auth/resolve.js";
|
|
5
|
+
import { isSshIntegrationConfigured, isSshSetupDisabled, resolveSshDomain, resolveSshPaths, setupSshIntegration, } from "./config.js";
|
|
6
|
+
import { resumeForConnect, workspaceIdFromRef } from "./tunnel.js";
|
|
7
|
+
/** Post-resume wait budget for a hibernated workspace to reach ACTIVE. */
|
|
8
|
+
const RESUME_WAIT_TIMEOUT_MS = 90_000;
|
|
9
|
+
/**
|
|
10
|
+
* Validate that native SSH integration is configured; set it up automatically
|
|
11
|
+
* when missing (the "setup on use" half of the setup-on-install decision).
|
|
12
|
+
* Returns false when auto-setup is disabled via KIMCHICTL_NO_SSH_SETUP.
|
|
13
|
+
*/
|
|
14
|
+
export async function ensureSshIntegration(options) {
|
|
15
|
+
const env = options.env ?? process.env;
|
|
16
|
+
if (isSshSetupDisabled(env))
|
|
17
|
+
return false;
|
|
18
|
+
const paths = resolveSshPaths(env);
|
|
19
|
+
const domain = resolveSshDomain(options.domain, env);
|
|
20
|
+
const isConfigured = options.isConfigured ?? isSshIntegrationConfigured;
|
|
21
|
+
if (await isConfigured(paths, domain))
|
|
22
|
+
return true;
|
|
23
|
+
const setup = options.setup ?? setupSshIntegration;
|
|
24
|
+
const { notes } = await setup({ paths, domain });
|
|
25
|
+
console.error("✓ SSH integration configured");
|
|
26
|
+
for (const note of notes)
|
|
27
|
+
console.error(` ${note}`);
|
|
28
|
+
console.error(" undo: kimchictl ssh setup --uninstall");
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
function defaultExec(args) {
|
|
32
|
+
return spawnSync(args[0] ?? "ssh", args.slice(1), { stdio: "inherit", shell: false }).status ?? 1;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Connect to a workspace with system ssh: ensure integration → resume a
|
|
36
|
+
* hibernated workspace (and wait for it) → exec `ssh <alias>.<domain>` with
|
|
37
|
+
* inherited stdio. Extra args are passed to ssh as the remote command.
|
|
38
|
+
*/
|
|
39
|
+
export async function connectToWorkspace(name, remoteCommand, options = {}) {
|
|
40
|
+
const env = options.env ?? process.env;
|
|
41
|
+
await ensureSshIntegration({ env });
|
|
42
|
+
const { key } = requireApiKey(env);
|
|
43
|
+
// Accepts "<alias>", "<alias>.<domain>", a UUID, or an alias prefix.
|
|
44
|
+
const ref = workspaceIdFromRef(name);
|
|
45
|
+
let workspace = await resolveWorkspace(key, ref, options);
|
|
46
|
+
if (workspace.status === "suspended" || workspace.status === "initializing") {
|
|
47
|
+
if (workspace.status === "suspended") {
|
|
48
|
+
await resumeForConnect(key, workspace.id, options);
|
|
49
|
+
}
|
|
50
|
+
process.stderr.write(" resuming…");
|
|
51
|
+
workspace = await waitForWorkspaceActive(key, workspace.id, {
|
|
52
|
+
...options,
|
|
53
|
+
timeoutMs: RESUME_WAIT_TIMEOUT_MS,
|
|
54
|
+
sleep: options.sleep,
|
|
55
|
+
onTick: (elapsedSeconds) => process.stderr.write(`\r resuming… ${elapsedSeconds}s`),
|
|
56
|
+
});
|
|
57
|
+
process.stderr.write("\n");
|
|
58
|
+
if (workspace.status !== "active") {
|
|
59
|
+
process.stderr.write(` ⚠ workspace is ${workspace.status} — connecting anyway; ` +
|
|
60
|
+
`if it fails, retry in a minute or check: kimchictl workspace get ${workspace.alias}\n`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const domain = resolveSshDomain(undefined, env);
|
|
64
|
+
const host = `${workspace.alias}.${domain}`;
|
|
65
|
+
return (options.exec ?? defaultExec)(["ssh", host, ...remoteCommand]);
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=connect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connect.js","sourceRoot":"../src/","sources":["ssh/connect.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ApiOptions } from "../api/types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Tunnel resolution for the SSH bridge (TS port of kap's
|
|
4
|
+
* internal/ssh/bridge.go ResolveTunnel — refs resolve via api/resolver.ts).
|
|
5
|
+
*/
|
|
6
|
+
export interface TunnelCreds {
|
|
7
|
+
/** wss://<workspace-host>/ssh */
|
|
8
|
+
wsUrl: string;
|
|
9
|
+
/** Short-lived workspace JWT for the Authorization header. */
|
|
10
|
+
token: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Accepts a bare workspace id or a full hostname (id.domain) — the domain
|
|
14
|
+
* suffix is stripped, which is what makes this usable as the ProxyCommand
|
|
15
|
+
* target (`%h` expands to the full hostname).
|
|
16
|
+
*/
|
|
17
|
+
export declare function workspaceIdFromRef(wsRef: string): string;
|
|
18
|
+
/** wss://{uri}/ssh — explicit port appended only when not 0/443 (kap parity). */
|
|
19
|
+
export declare function buildWsUrl(uri: string, port?: number): string;
|
|
20
|
+
export interface ResolveTunnelOptions extends ApiOptions {
|
|
21
|
+
/** WebSocket port override (dev; default 443). */
|
|
22
|
+
port?: number;
|
|
23
|
+
}
|
|
24
|
+
export declare function resolveTunnel(apiKey: string, wsRef: string, options?: ResolveTunnelOptions): Promise<TunnelCreds>;
|
|
25
|
+
/** Wake a hibernated workspace. Best-effort by contract: resume is idempotent server-side. */
|
|
26
|
+
export declare function resumeForConnect(apiKey: string, workspaceId: string, options?: ApiOptions & {
|
|
27
|
+
orgId?: string;
|
|
28
|
+
}): Promise<void>;
|
|
29
|
+
//# sourceMappingURL=tunnel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tunnel.d.ts","sourceRoot":"../src/","sources":["ssh/tunnel.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAGjD;;;GAGG;AAEH,MAAM,WAAW,WAAW;IAC3B,iCAAiC;IACjC,KAAK,EAAE,MAAM,CAAA;IACb,8DAA8D;IAC9D,KAAK,EAAE,MAAM,CAAA;CACb;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGxD;AAED,iFAAiF;AACjF,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,SAAM,GAAG,MAAM,CAK1D;AAED,MAAM,WAAW,oBAAqB,SAAQ,UAAU;IACvD,kDAAkD;IAClD,IAAI,CAAC,EAAE,MAAM,CAAA;CACb;AAED,wBAAsB,aAAa,CAClC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,oBAAoB,GAC5B,OAAO,CAAC,WAAW,CAAC,CAQtB;AAED,8FAA8F;AAC9F,wBAAsB,gBAAgB,CACrC,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,UAAU,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GACvC,OAAO,CAAC,IAAI,CAAC,CAGf"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { verifyApiKey } from "../api/keys.js";
|
|
2
|
+
import { resolveWorkspace } from "../api/resolver.js";
|
|
3
|
+
import { exchangeWorkspaceToken, resumeWorkspace } from "../api/tokens.js";
|
|
4
|
+
import { RemoteNetworkError } from "../api/types.js";
|
|
5
|
+
/**
|
|
6
|
+
* Accepts a bare workspace id or a full hostname (id.domain) — the domain
|
|
7
|
+
* suffix is stripped, which is what makes this usable as the ProxyCommand
|
|
8
|
+
* target (`%h` expands to the full hostname).
|
|
9
|
+
*/
|
|
10
|
+
export function workspaceIdFromRef(wsRef) {
|
|
11
|
+
const dot = wsRef.indexOf(".");
|
|
12
|
+
return dot > 0 ? wsRef.slice(0, dot) : wsRef;
|
|
13
|
+
}
|
|
14
|
+
/** wss://{uri}/ssh — explicit port appended only when not 0/443 (kap parity). */
|
|
15
|
+
export function buildWsUrl(uri, port = 443) {
|
|
16
|
+
// The API may return a bare host or a scheme-prefixed URI; normalize to host[:port].
|
|
17
|
+
const host = uri.includes("://") ? new URL(uri).host : uri;
|
|
18
|
+
if (!host)
|
|
19
|
+
throw new RemoteNetworkError(`Invalid workspace URI from server: ${uri}`);
|
|
20
|
+
return `wss://${host}${port === 0 || port === 443 ? "" : `:${port}`}/ssh`;
|
|
21
|
+
}
|
|
22
|
+
export async function resolveTunnel(apiKey, wsRef, options) {
|
|
23
|
+
const ref = workspaceIdFromRef(wsRef);
|
|
24
|
+
const workspace = await resolveWorkspace(apiKey, ref, options);
|
|
25
|
+
if (!workspace.uri) {
|
|
26
|
+
throw new RemoteNetworkError(`Workspace ${ref} has no connection URI yet — it may still be provisioning`);
|
|
27
|
+
}
|
|
28
|
+
const { token } = await exchangeWorkspaceToken(apiKey, workspace.id, options);
|
|
29
|
+
return { wsUrl: buildWsUrl(workspace.uri, options?.port), token };
|
|
30
|
+
}
|
|
31
|
+
/** Wake a hibernated workspace. Best-effort by contract: resume is idempotent server-side. */
|
|
32
|
+
export async function resumeForConnect(apiKey, workspaceId, options) {
|
|
33
|
+
const orgId = options?.orgId ?? (await verifyApiKey(apiKey, options));
|
|
34
|
+
await resumeWorkspace(apiKey, workspaceId, { ...options, orgId });
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=tunnel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tunnel.js","sourceRoot":"../src/","sources":["ssh/tunnel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAC7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AACrD,OAAO,EAAE,sBAAsB,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAE1E,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAcpD;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAa;IAC/C,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IAC9B,OAAO,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAA;AAC7C,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,UAAU,CAAC,GAAW,EAAE,IAAI,GAAG,GAAG;IACjD,qFAAqF;IACrF,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAA;IAC1D,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,kBAAkB,CAAC,sCAAsC,GAAG,EAAE,CAAC,CAAA;IACpF,OAAO,SAAS,IAAI,GAAG,IAAI,KAAK,CAAC,IAAI,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,MAAM,CAAA;AAC1E,CAAC;AAOD,MAAM,CAAC,KAAK,UAAU,aAAa,CAClC,MAAc,EACd,KAAa,EACb,OAA8B;IAE9B,MAAM,GAAG,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAA;IACrC,MAAM,SAAS,GAAG,MAAM,gBAAgB,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,CAAC,CAAA;IAC9D,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC;QACpB,MAAM,IAAI,kBAAkB,CAAC,aAAa,GAAG,2DAA2D,CAAC,CAAA;IAC1G,CAAC;IACD,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,sBAAsB,CAAC,MAAM,EAAE,SAAS,CAAC,EAAE,EAAE,OAAO,CAAC,CAAA;IAC7E,OAAO,EAAE,KAAK,EAAE,UAAU,CAAC,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,KAAK,EAAE,CAAA;AAClE,CAAC;AAED,8FAA8F;AAC9F,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACrC,MAAc,EACd,WAAmB,EACnB,OAAyC;IAEzC,MAAM,KAAK,GAAG,OAAO,EAAE,KAAK,IAAI,CAAC,MAAM,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;IACrE,MAAM,eAAe,CAAC,MAAM,EAAE,WAAW,EAAE,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,CAAC,CAAA;AAClE,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared test helpers — co-located *.test.ts files import from here instead of
|
|
3
|
+
* re-rolling temp-dir / fetch-stub / console-capture boilerplate per file.
|
|
4
|
+
* Excluded from the published build (tsconfig.build.json).
|
|
5
|
+
*/
|
|
6
|
+
export declare function makeTempDir(prefix?: string): string;
|
|
7
|
+
/** Call from afterEach — removes every dir created via makeTempDir. */
|
|
8
|
+
export declare function cleanupTempDirs(): void;
|
|
9
|
+
export type FetchImpl = typeof globalThis.fetch;
|
|
10
|
+
/**
|
|
11
|
+
* Build a fetch stub from a narrow implementation. The `as unknown` adapts the
|
|
12
|
+
* test's string-URL signature to fetch's wide `(string | URL | Request)` one —
|
|
13
|
+
* deliberate, one place, test-only.
|
|
14
|
+
*/
|
|
15
|
+
export declare function stubFetch(impl: (url: string, init?: RequestInit) => Promise<Response>): FetchImpl;
|
|
16
|
+
export declare function jsonResponse(body: unknown, status?: number): Response;
|
|
17
|
+
/** Point the shared-credential paths at an isolated HOME + agent dir and neutralize ambient key env vars. Returns the stubbed HOME — the shared config.json lives at `<home>/.config/kimchi/config.json` (sibling of the harness agent dir, matching kimchi-dev:src/config.ts). */
|
|
18
|
+
export declare function stubAgentDirEnv(dir: string): {
|
|
19
|
+
home: string;
|
|
20
|
+
};
|
|
21
|
+
/** Path of the harness's shared wizard config under a (stubbed) HOME. */
|
|
22
|
+
export declare function sharedConfigPath(home: string): string;
|
|
23
|
+
/** Seed the shared config.json under a stubbed HOME (creates parent dirs). */
|
|
24
|
+
export declare function writeSharedConfigJson(home: string, value: unknown): string;
|
|
25
|
+
/** Capture console.log / console.error; restore() in afterEach (or rely on vi.restoreAllMocks). */
|
|
26
|
+
export declare function captureConsole(): {
|
|
27
|
+
lines: string[];
|
|
28
|
+
errors: string[];
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=test-support.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-support.d.ts","sourceRoot":"../src/","sources":["test-support.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AASH,wBAAgB,WAAW,CAAC,MAAM,SAAoB,GAAG,MAAM,CAI9D;AAED,uEAAuE;AACvE,wBAAgB,eAAe,IAAI,IAAI,CAEtC;AAED,MAAM,MAAM,SAAS,GAAG,OAAO,UAAU,CAAC,KAAK,CAAA;AAE/C;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,GAAG,SAAS,CAEjG;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,SAAM,GAAG,QAAQ,CAKlE;AAED,mRAAmR;AACnR,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAO7D;AAED,yEAAyE;AACzE,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,8EAA8E;AAC9E,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAK1E;AAED,mGAAmG;AACnG,wBAAgB,cAAc,IAAI;IAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAUtE"}
|