@mycadet/cli 0.12.8 → 0.12.9-dev.202610131452.9acab64
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/node_modules/@mycadet/assistant-api/events/show-cloud-login.ts +2 -2
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/package.json +1 -1
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/package.json +1 -1
- package/node_modules/@mycadet/service-contracts/package.json +1 -1
- package/package.json +4 -3
- package/src/__tests__/client-web-config.test.ts +0 -1
- package/src/__tests__/cloud-releases.test.ts +23 -13
- package/src/__tests__/ensure-binary.test.ts +182 -0
- package/src/__tests__/nginx-ingress.test.ts +6 -11
- package/src/__tests__/ngrok-spawn.test.ts +49 -0
- package/src/__tests__/pair.test.ts +149 -78
- package/src/__tests__/ps-cloud-status.test.ts +4 -3
- package/src/__tests__/ps-table.test.ts +79 -0
- package/src/__tests__/tailscale.test.ts +1 -1
- package/src/__tests__/teleport.test.ts +1 -16
- package/src/__tests__/tunnel.test.ts +274 -82
- package/src/__tests__/uninstall.test.ts +141 -9
- package/src/__tests__/wake.test.ts +2 -2
- package/src/commands/backup.ts +3 -3
- package/src/commands/client.ts +4 -6
- package/src/commands/completions.test.ts +104 -0
- package/src/commands/completions.ts +87 -0
- package/src/commands/exec.ts +7 -7
- package/src/commands/flags.ts +2 -2
- package/src/commands/hatch.ts +5 -2
- package/src/commands/logs.ts +7 -7
- package/src/commands/pair.ts +140 -37
- package/src/commands/ps.ts +63 -26
- package/src/commands/restore.ts +2 -2
- package/src/commands/retire.ts +2 -2
- package/src/commands/rollback.ts +2 -2
- package/src/commands/sleep.ts +2 -2
- package/src/commands/teleport.ts +18 -37
- package/src/commands/terminal.ts +1 -1
- package/src/commands/tunnel.ts +101 -78
- package/src/commands/uninstall.ts +108 -73
- package/src/commands/upgrade.ts +8 -8
- package/src/commands/use.ts +1 -1
- package/src/commands/wake.ts +6 -6
- package/src/index.ts +15 -0
- package/src/lib/__tests__/docker.test.ts +168 -20
- package/src/lib/__tests__/job-polling.test.ts +1 -1
- package/src/lib/__tests__/local-gateway-cli.test.ts +42 -0
- package/src/lib/cloud-client.ts +0 -3
- package/src/lib/cloud-login.ts +0 -1
- package/src/lib/cloud-releases.ts +6 -4
- package/src/lib/color.ts +4 -0
- package/src/lib/docker.ts +190 -102
- package/src/lib/ensure-binary.ts +171 -0
- package/src/lib/environments/__tests__/paths.test.ts +27 -1
- package/src/lib/environments/paths.ts +16 -8
- package/src/lib/environments/resolve.ts +2 -4
- package/src/lib/flag-args.test.ts +1 -1
- package/src/lib/flag-args.ts +1 -1
- package/src/lib/guardian-token.ts +1 -1
- package/src/lib/hatch-local.ts +15 -8
- package/src/lib/hatch-next-steps.ts +5 -9
- package/src/lib/job-polling.ts +1 -1
- package/src/lib/local.ts +37 -4
- package/src/lib/nginx-ingress.ts +3 -5
- package/src/lib/pair.ts +1 -1
- package/src/lib/step-runner.ts +6 -1
- package/src/lib/sync-cloud-assistants.ts +2 -2
- package/src/lib/tunnels/cloudflare.ts +21 -11
- package/src/lib/tunnels/ngrok.ts +198 -108
- package/src/lib/tunnels/tailscale.ts +34 -4
- package/src/lib/upgrade-lifecycle.ts +5 -4
package/src/commands/pair.ts
CHANGED
|
@@ -3,9 +3,12 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Print the pairing link for another device, and the same link rendered as a
|
|
5
5
|
* QR code. Runs on the machine hosting the assistant: it mints a remote-web
|
|
6
|
-
* pairing challenge
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* pairing challenge through the gateway CLI (`gateway pairing mint <url>` —
|
|
7
|
+
* locally, or docker-exec'd into the gateway container) which approves it
|
|
8
|
+
* immediately, so running this command IS the local-presence proof and one
|
|
9
|
+
* scan (or one paste into `cadet connect import`) completes the pairing.
|
|
10
|
+
* local and docker assistants mint without any HTTP; other topologies keep
|
|
11
|
+
* the loopback HTTP mint.
|
|
9
12
|
*
|
|
10
13
|
* `--web-approve` is the reverse direction: approve a code a device already
|
|
11
14
|
* shows, for pairings that device started on its own.
|
|
@@ -38,8 +41,11 @@ import {
|
|
|
38
41
|
import { GATEWAY_PORT } from "../lib/constants.js";
|
|
39
42
|
import { getCurrentEnvironment } from "../lib/environments/resolve.js";
|
|
40
43
|
import { isLoopbackUrl, loopbackSafeFetch } from "../lib/loopback-fetch.js";
|
|
44
|
+
import { dockerResourceNames } from "../lib/docker.js";
|
|
45
|
+
import { gatewayCliInvocation } from "../lib/local.js";
|
|
41
46
|
import { formatWebApproveFailure, parseGatewayErrorCode } from "../lib/pair.js";
|
|
42
47
|
import { STALE_CLI_UPDATE_HINT } from "../lib/stale-cli-hint.js";
|
|
48
|
+
import { execOutput } from "../lib/step-runner.js";
|
|
43
49
|
|
|
44
50
|
function assistantDisplayName(entry: AssistantEntry): string {
|
|
45
51
|
return entry.name || entry.assistantName || entry.assistantId;
|
|
@@ -89,9 +95,10 @@ function printUsage(): void {
|
|
|
89
95
|
USAGE:
|
|
90
96
|
cadet pair [assistant] [options]
|
|
91
97
|
|
|
92
|
-
Mints a pairing challenge on the assistant's gateway
|
|
93
|
-
|
|
94
|
-
|
|
98
|
+
Mints a pairing challenge on the assistant's gateway (via the gateway CLI
|
|
99
|
+
for local and docker assistants, over loopback HTTP otherwise) and approves
|
|
100
|
+
it on the spot (running this command on the host machine is the approval),
|
|
101
|
+
then prints the pairing link and the same link as a QR code.
|
|
95
102
|
Scanning the QR, opening the link, or passing it to 'cadet connect import'
|
|
96
103
|
on the other device completes the pairing in one step.
|
|
97
104
|
|
|
@@ -214,26 +221,122 @@ async function exitOnHttpError(response: Response): Promise<Response> {
|
|
|
214
221
|
return response;
|
|
215
222
|
}
|
|
216
223
|
|
|
217
|
-
/**
|
|
218
|
-
|
|
219
|
-
|
|
224
|
+
/**
|
|
225
|
+
* Mint (and approve) a remote-web pairing challenge. `local` and `docker`
|
|
226
|
+
* remotes mint through the gateway CLI — `gateway pairing mint <url>` run on
|
|
227
|
+
* the assistant's host (locally, or docker-exec'd into the gateway container)
|
|
228
|
+
* — replacing the old loopback HTTP mint whose loopback-peer gate
|
|
229
|
+
* containerized gateways could never satisfy (published-port NAT rewrites
|
|
230
|
+
* host-originated peers to the docker bridge address). The mint executes the
|
|
231
|
+
* pairing logic directly against the file-backed challenge store, so the
|
|
232
|
+
* running gateway's verification routes see it with no IPC round-trip.
|
|
233
|
+
* Process execution is the local-presence proof; the mint command approves
|
|
234
|
+
* on the spot, so the returned challenge is already paired-ready. Other
|
|
235
|
+
* topologies keep the loopback HTTP mint they used before.
|
|
236
|
+
*/
|
|
237
|
+
async function mintPairingChallengeOrExit(
|
|
238
|
+
entry: AssistantEntry,
|
|
239
|
+
publicBaseUrl: string,
|
|
240
|
+
): Promise<RemoteWebPairingChallengeResponse> {
|
|
241
|
+
if (entry.cloud !== "local" && entry.cloud !== "docker") {
|
|
242
|
+
return mintPairingChallengeOverLoopbackOrExit(entry, publicBaseUrl);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
let stdout: string;
|
|
246
|
+
try {
|
|
247
|
+
if (entry.cloud === "docker") {
|
|
248
|
+
stdout = await execOutput(
|
|
249
|
+
"docker",
|
|
250
|
+
[
|
|
251
|
+
"exec",
|
|
252
|
+
dockerResourceNames(entry.assistantId).gatewayContainer,
|
|
253
|
+
"bun",
|
|
254
|
+
"run",
|
|
255
|
+
"src/cli/index.ts",
|
|
256
|
+
"pairing",
|
|
257
|
+
"mint",
|
|
258
|
+
publicBaseUrl,
|
|
259
|
+
],
|
|
260
|
+
{ timeoutMs: 30_000 },
|
|
261
|
+
);
|
|
262
|
+
} else {
|
|
263
|
+
const invocation = gatewayCliInvocation(entry.resources);
|
|
264
|
+
stdout = await execOutput(
|
|
265
|
+
invocation.command,
|
|
266
|
+
[...invocation.args, "pairing", "mint", publicBaseUrl],
|
|
267
|
+
{
|
|
268
|
+
cwd: invocation.cwd,
|
|
269
|
+
env: invocation.env,
|
|
270
|
+
timeoutMs: 30_000,
|
|
271
|
+
},
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
} catch (err) {
|
|
275
|
+
console.error(
|
|
276
|
+
`Error: could not mint a pairing challenge on the gateway ` +
|
|
277
|
+
`(${err instanceof Error ? err.message : String(err)}).`,
|
|
278
|
+
);
|
|
279
|
+
console.error("Is the assistant running? Try `cadet wake`.");
|
|
280
|
+
process.exit(1);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const lastLine = stdout.split("\n").filter(Boolean).pop() ?? "";
|
|
284
|
+
let challenge: RemoteWebPairingChallengeResponse;
|
|
285
|
+
try {
|
|
286
|
+
challenge = JSON.parse(lastLine) as RemoteWebPairingChallengeResponse;
|
|
287
|
+
} catch {
|
|
288
|
+
console.error(
|
|
289
|
+
"Error: could not parse the gateway's pairing mint output.",
|
|
290
|
+
);
|
|
291
|
+
process.exit(1);
|
|
292
|
+
}
|
|
293
|
+
if (!challenge.deviceCode || !challenge.userCode) {
|
|
294
|
+
console.error(
|
|
295
|
+
"Error: the gateway's pairing mint output is missing the challenge codes.",
|
|
296
|
+
);
|
|
297
|
+
process.exit(1);
|
|
298
|
+
}
|
|
299
|
+
return challenge;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Mint a challenge over the gateway's loopback HTTP route — the path every
|
|
304
|
+
* topology took before the gateway-CLI mint existed. Kept for remotes that
|
|
305
|
+
* are neither `local` nor `docker` (apple containers, SSH-managed hosts,
|
|
306
|
+
* cloud-managed), where no gateway-CLI execution surface is defined.
|
|
307
|
+
*/
|
|
308
|
+
async function mintPairingChallengeOverLoopbackOrExit(
|
|
309
|
+
entry: AssistantEntry,
|
|
220
310
|
publicBaseUrl: string,
|
|
221
311
|
): Promise<RemoteWebPairingChallengeResponse> {
|
|
312
|
+
// Mint over loopback (localUrl avoids mDNS for same-machine calls), then
|
|
313
|
+
// approve immediately: running this CLI on the host IS the local-presence
|
|
314
|
+
// proof, so the other device completes pairing in one step from the link.
|
|
315
|
+
const mintUrl = (
|
|
316
|
+
entry.localUrl ||
|
|
317
|
+
entry.runtimeUrl ||
|
|
318
|
+
`http://127.0.0.1:${GATEWAY_PORT}`
|
|
319
|
+
).replace(/\/+$/, "");
|
|
222
320
|
const response = await gatewayPostOrExit(
|
|
223
|
-
|
|
321
|
+
mintUrl,
|
|
224
322
|
"/v1/remote-web/pairing-challenge",
|
|
225
323
|
{ publicBaseUrl } satisfies RemoteWebPairingChallengeRequest,
|
|
226
324
|
);
|
|
227
|
-
|
|
325
|
+
const challenge: RemoteWebPairingChallengeResponse =
|
|
326
|
+
await response.json();
|
|
327
|
+
await gatewayPostOrExit(mintUrl, "/v1/remote-web/pairing-verification", {
|
|
328
|
+
userCode: challenge.userCode,
|
|
329
|
+
} satisfies RemoteWebPairingVerificationRequest);
|
|
330
|
+
return challenge;
|
|
228
331
|
}
|
|
229
332
|
|
|
230
333
|
/**
|
|
231
334
|
* Approve a pending pairing challenge by its user code, the local-presence
|
|
232
335
|
* proof for the device-code flow. Single owner of the pairing-verification
|
|
233
|
-
* route and request body:
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
336
|
+
* route and request body: `--web-approve` calls this directly to inspect
|
|
337
|
+
* rejections and print mismatch diagnostics (see
|
|
338
|
+
* {@link formatWebApproveFailure}); the link flow needs no separate
|
|
339
|
+
* verification call — the gateway-CLI mint approves on the spot.
|
|
237
340
|
*/
|
|
238
341
|
async function postPairingVerification(
|
|
239
342
|
gatewayUrl: string,
|
|
@@ -244,20 +347,6 @@ async function postPairingVerification(
|
|
|
244
347
|
} satisfies RemoteWebPairingVerificationRequest);
|
|
245
348
|
}
|
|
246
349
|
|
|
247
|
-
/**
|
|
248
|
-
* Approve the challenge this command just minted so one scan completes
|
|
249
|
-
* pairing, exiting with a generic message on non-2xx.
|
|
250
|
-
*/
|
|
251
|
-
async function approveRemoteWebPairing(
|
|
252
|
-
gatewayUrl: string,
|
|
253
|
-
userCode: string,
|
|
254
|
-
): Promise<RemoteWebPairingVerificationResponse> {
|
|
255
|
-
const response = await exitOnHttpError(
|
|
256
|
-
await postPairingVerification(gatewayUrl, userCode),
|
|
257
|
-
);
|
|
258
|
-
return (await response.json()) as RemoteWebPairingVerificationResponse;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
350
|
export const pair = Object.assign(
|
|
262
351
|
async function pair(): Promise<void> {
|
|
263
352
|
const rawArgs = process.argv.slice(3);
|
|
@@ -355,6 +444,24 @@ export const pair = Object.assign(
|
|
|
355
444
|
}
|
|
356
445
|
}
|
|
357
446
|
|
|
447
|
+
// Minting requires host presence on this machine. Native and docker
|
|
448
|
+
// topologies qualify (the latter via docker exec); an assistant this
|
|
449
|
+
// machine only knows through `connect import` has no host presence here
|
|
450
|
+
// at all, and `cadet pair` never worked against it (its loopback gate
|
|
451
|
+
// denied remote mints). Refuse clearly instead of a confusing mint
|
|
452
|
+
// failure.
|
|
453
|
+
if (entry.cloud === "paired") {
|
|
454
|
+
console.error(
|
|
455
|
+
`Error: ${formatAssistantReference(entry)} was paired as a remote ` +
|
|
456
|
+
"assistant (cadet connect import), so this machine does not host it.",
|
|
457
|
+
);
|
|
458
|
+
console.error(
|
|
459
|
+
"Run `cadet pair` on the machine hosting the assistant, or approve " +
|
|
460
|
+
"a code it shows with `cadet pair --web-approve <code>`.",
|
|
461
|
+
);
|
|
462
|
+
process.exit(1);
|
|
463
|
+
}
|
|
464
|
+
|
|
358
465
|
// Mint over loopback (localUrl avoids mDNS for same-machine calls), but
|
|
359
466
|
// advertise a REACHABLE url in the link, since the loopback url would point
|
|
360
467
|
// the other device at its own localhost. Prefer an explicit --url, then the
|
|
@@ -437,14 +544,10 @@ export const pair = Object.assign(
|
|
|
437
544
|
}
|
|
438
545
|
const publicBaseUrl = resolved.url;
|
|
439
546
|
|
|
440
|
-
// Mint a challenge
|
|
441
|
-
// IS the local-presence proof, so the other
|
|
442
|
-
// step from the link alone.
|
|
443
|
-
const challenge = await
|
|
444
|
-
mintUrl,
|
|
445
|
-
publicBaseUrl,
|
|
446
|
-
);
|
|
447
|
-
await approveRemoteWebPairing(mintUrl, challenge.userCode);
|
|
547
|
+
// Mint a challenge (approved on the spot by the gateway-CLI invocation):
|
|
548
|
+
// running this CLI on the host IS the local-presence proof, so the other
|
|
549
|
+
// device completes pairing in one step from the link alone.
|
|
550
|
+
const challenge = await mintPairingChallengeOrExit(entry, publicBaseUrl);
|
|
448
551
|
const pairUrl = buildRemoteWebPairingUrl(challenge);
|
|
449
552
|
const displayName = assistantDisplayName(entry);
|
|
450
553
|
const appUrl = appVariant
|
package/src/commands/ps.ts
CHANGED
|
@@ -64,8 +64,54 @@ interface ColWidths {
|
|
|
64
64
|
info: number;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Terminal display width for table alignment: emoji render two cells wide
|
|
69
|
+
* (including ZWJ sequences like 🧑🚀 and VS16 pairs like ⚠️), while the ZWJ
|
|
70
|
+
* and variation selectors themselves render zero. `.length` counts UTF-16
|
|
71
|
+
* units and none of that, which is what broke the STATUS and INFO columns
|
|
72
|
+
* whenever an emoji appeared in an earlier one.
|
|
73
|
+
*/
|
|
74
|
+
export function displayWidth(s: string): number {
|
|
75
|
+
const chars = [...s];
|
|
76
|
+
let width = 0;
|
|
77
|
+
// A ZWJ joins the previous and next glyphs into ONE grapheme (🧑🚀 renders
|
|
78
|
+
// as a single 2-cell astronaut), so the glyph after a ZWJ adds no width.
|
|
79
|
+
let joinedByZwj = false;
|
|
80
|
+
for (let i = 0; i < chars.length; i++) {
|
|
81
|
+
const cp = chars[i]?.codePointAt(0) ?? 0;
|
|
82
|
+
// Zero-width: the joiner, the variation selectors, skin-tone modifiers.
|
|
83
|
+
if (cp === 0x200d) {
|
|
84
|
+
joinedByZwj = true;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (cp === 0xfe0e || cp === 0xfe0f) continue;
|
|
88
|
+
if (cp >= 0x1f3fb && cp <= 0x1f3ff) continue;
|
|
89
|
+
const next = chars[i + 1]?.codePointAt(0);
|
|
90
|
+
if (!joinedByZwj) {
|
|
91
|
+
// A VS16 after a narrow symbol forces emoji presentation — two cells.
|
|
92
|
+
width += isWideCodePoint(cp) || next === 0xfe0f ? 2 : 1;
|
|
93
|
+
}
|
|
94
|
+
joinedByZwj = false;
|
|
95
|
+
}
|
|
96
|
+
return width;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function isWideCodePoint(cp: number): boolean {
|
|
100
|
+
return (
|
|
101
|
+
(cp >= 0x1100 && cp <= 0x115f) || // Hangul Jamo
|
|
102
|
+
(cp >= 0x2e80 && cp <= 0x303e) || // CJK radicals and punctuation
|
|
103
|
+
(cp >= 0x3040 && cp <= 0x318f) || // Hiragana, Katakana, bopomofo
|
|
104
|
+
(cp >= 0xac00 && cp <= 0xd7a3) || // Hangul syllables
|
|
105
|
+
(cp >= 0xf900 && cp <= 0xfaff) || // CJK compatibility ideographs
|
|
106
|
+
(cp >= 0xfe30 && cp <= 0xfe6f) || // CJK compatibility forms
|
|
107
|
+
(cp >= 0xff00 && cp <= 0xff60) || // fullwidth forms
|
|
108
|
+
(cp >= 0xffe0 && cp <= 0xffe6) ||
|
|
109
|
+
cp >= 0x1f000 // emoji and symbol blocks (🟢, 🚀, 🧑, …)
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
67
113
|
function pad(s: string, w: number): string {
|
|
68
|
-
return s + " ".repeat(Math.max(0, w - s
|
|
114
|
+
return s + " ".repeat(Math.max(0, w - displayWidth(s)));
|
|
69
115
|
}
|
|
70
116
|
|
|
71
117
|
function computeColWidths(
|
|
@@ -79,14 +125,17 @@ function computeColWidths(
|
|
|
79
125
|
): ColWidths {
|
|
80
126
|
const all = [headers, ...rows];
|
|
81
127
|
return {
|
|
82
|
-
name: Math.max(...all.map((r) => r.name
|
|
83
|
-
type: Math.max(...all.map((r) => r.type
|
|
84
|
-
status: Math.max(
|
|
85
|
-
|
|
128
|
+
name: Math.max(...all.map((r) => displayWidth(r.name))),
|
|
129
|
+
type: Math.max(...all.map((r) => displayWidth(r.type))),
|
|
130
|
+
status: Math.max(
|
|
131
|
+
...all.map((r) => displayWidth(r.status)),
|
|
132
|
+
"checking...".length,
|
|
133
|
+
),
|
|
134
|
+
info: Math.max(...all.map((r) => displayWidth(r.info))),
|
|
86
135
|
};
|
|
87
136
|
}
|
|
88
137
|
|
|
89
|
-
function formatRow(r: TableRow, colWidths: ColWidths): string {
|
|
138
|
+
export function formatRow(r: TableRow, colWidths: ColWidths): string {
|
|
90
139
|
return ` ${pad(r.name, colWidths.name)} ${pad(r.type, colWidths.type)} ${pad(r.status, colWidths.status)} ${r.info}`;
|
|
91
140
|
}
|
|
92
141
|
|
|
@@ -459,7 +508,7 @@ async function getAssistantProcessRows(
|
|
|
459
508
|
): Promise<TableRow[]> {
|
|
460
509
|
const cloud = entry.cloud;
|
|
461
510
|
|
|
462
|
-
console.log(`Processes for ${formatAssistantReference(entry)}
|
|
511
|
+
console.log(`Processes for ${formatAssistantReference(entry)} — ${cloud}:`);
|
|
463
512
|
console.log(`Location: ${locationFor(entry)}\n`);
|
|
464
513
|
|
|
465
514
|
if (cloud === "local") {
|
|
@@ -611,35 +660,23 @@ async function getRootManagedRows(): Promise<RootManagedRow[]> {
|
|
|
611
660
|
}
|
|
612
661
|
|
|
613
662
|
/**
|
|
614
|
-
* Fold the CLI-managed process rows into ONE table with the assistant rows
|
|
615
|
-
*
|
|
616
|
-
*
|
|
617
|
-
*
|
|
663
|
+
* Fold the CLI-managed process rows into ONE table with the assistant rows.
|
|
664
|
+
* Tunnel agents and the nginx edge are root resources of the machine, not
|
|
665
|
+
* children of whichever assistant they front — especially as the web bundle
|
|
666
|
+
* splits into /app and /ui — so every CLI-managed row renders machine-level.
|
|
618
667
|
*/
|
|
619
668
|
async function assistantRowsWithRootProcesses(
|
|
620
669
|
assistants: AssistantEntry[],
|
|
621
670
|
healths: AssistantHealth[],
|
|
622
671
|
activeAssistantId: string | null,
|
|
623
672
|
): Promise<TableRow[]> {
|
|
624
|
-
const
|
|
625
|
-
const unattributed: TableRow[] = [];
|
|
626
|
-
const knownIds = new Set(assistants.map((entry) => entry.assistantId));
|
|
627
|
-
for (const managed of await getRootManagedRows()) {
|
|
628
|
-
if (managed.assistantId && knownIds.has(managed.assistantId)) {
|
|
629
|
-
const nested = nestedByAssistant.get(managed.assistantId) ?? [];
|
|
630
|
-
nested.push({ ...managed.row, name: ` └ ${managed.row.name}` });
|
|
631
|
-
nestedByAssistant.set(managed.assistantId, nested);
|
|
632
|
-
} else {
|
|
633
|
-
unattributed.push(managed.row);
|
|
634
|
-
}
|
|
635
|
-
}
|
|
673
|
+
const rootRows = (await getRootManagedRows()).map((managed) => managed.row);
|
|
636
674
|
|
|
637
675
|
const rows: TableRow[] = [];
|
|
638
676
|
assistants.forEach((entry, i) => {
|
|
639
677
|
rows.push(formatAssistantListRow(entry, activeAssistantId, healths[i]!));
|
|
640
|
-
rows.push(...(nestedByAssistant.get(entry.assistantId) ?? []));
|
|
641
678
|
});
|
|
642
|
-
rows.push(...
|
|
679
|
+
rows.push(...rootRows);
|
|
643
680
|
return rows;
|
|
644
681
|
}
|
|
645
682
|
|
|
@@ -830,5 +867,5 @@ export const ps = Object.assign(
|
|
|
830
867
|
}
|
|
831
868
|
printTable(rows);
|
|
832
869
|
},
|
|
833
|
-
{ description: "List assistants
|
|
870
|
+
{ description: "List assistants" },
|
|
834
871
|
);
|
package/src/commands/restore.ts
CHANGED
|
@@ -474,7 +474,7 @@ async function restorePlatform(
|
|
|
474
474
|
}
|
|
475
475
|
}
|
|
476
476
|
// Transient error (5xx, network) — retry
|
|
477
|
-
console.warn(`Polling failed
|
|
477
|
+
console.warn(`Polling failed: ${msg}. Retrying...`);
|
|
478
478
|
continue;
|
|
479
479
|
}
|
|
480
480
|
|
|
@@ -680,7 +680,7 @@ export const restore = Object.assign(
|
|
|
680
680
|
}
|
|
681
681
|
|
|
682
682
|
const sizeMB = formatBundleSizeMb(bundleFileSizeBytes(fromPath));
|
|
683
|
-
console.log(`Reading ${fromPath}
|
|
683
|
+
console.log(`Reading ${fromPath} — ${sizeMB} MB...`);
|
|
684
684
|
|
|
685
685
|
// Detect topology and route platform assistants through Django import
|
|
686
686
|
const cloud =
|
package/src/commands/retire.ts
CHANGED
|
@@ -287,12 +287,12 @@ async function retireInner(): Promise<void> {
|
|
|
287
287
|
console.log("");
|
|
288
288
|
console.log("Delete an assistant instance and archive its data.");
|
|
289
289
|
console.log(
|
|
290
|
-
"With no name or ID, retires the active assistant
|
|
290
|
+
"With no name or ID, retires the active assistant — see `cadet use`. By default, retire prints the assistant name, ID, cloud, and runtime before asking for confirmation.",
|
|
291
291
|
);
|
|
292
292
|
console.log("");
|
|
293
293
|
console.log("Arguments:");
|
|
294
294
|
console.log(
|
|
295
|
-
" <name-or-id> Assistant display name or ID to retire
|
|
295
|
+
" <name-or-id> Assistant display name or ID to retire, defaults to the active assistant",
|
|
296
296
|
);
|
|
297
297
|
console.log("");
|
|
298
298
|
console.log("Options:");
|
package/src/commands/rollback.ts
CHANGED
|
@@ -51,12 +51,12 @@ function parseArgs(): { name: string | null; version: string | null } {
|
|
|
51
51
|
console.log("");
|
|
52
52
|
console.log("Arguments:");
|
|
53
53
|
console.log(
|
|
54
|
-
" <name> Name of the assistant
|
|
54
|
+
" <name> Name of the assistant, defaults to active or only assistant",
|
|
55
55
|
);
|
|
56
56
|
console.log("");
|
|
57
57
|
console.log("Options:");
|
|
58
58
|
console.log(
|
|
59
|
-
" --version <version> Target version
|
|
59
|
+
" --version <version> Target version, optional for managed — omit to roll back to previous",
|
|
60
60
|
);
|
|
61
61
|
console.log("");
|
|
62
62
|
console.log("Examples:");
|
package/src/commands/sleep.ts
CHANGED
|
@@ -71,7 +71,7 @@ export const sleep = Object.assign(
|
|
|
71
71
|
console.log("");
|
|
72
72
|
console.log("Arguments:");
|
|
73
73
|
console.log(
|
|
74
|
-
" <name> Name of the assistant to stop
|
|
74
|
+
" <name> Name of the assistant to stop, defaults to active or only local",
|
|
75
75
|
);
|
|
76
76
|
console.log("");
|
|
77
77
|
console.log("Options:");
|
|
@@ -88,7 +88,7 @@ export const sleep = Object.assign(
|
|
|
88
88
|
" schedules) to finish before stopping. Waits as long as it",
|
|
89
89
|
);
|
|
90
90
|
console.log(
|
|
91
|
-
" takes by default; pass a duration
|
|
91
|
+
" takes by default; pass a duration, e.g. --wait 90s or --wait 10m",
|
|
92
92
|
);
|
|
93
93
|
console.log(
|
|
94
94
|
" to bound the wait. Ctrl-C cancels and leaves the assistant running.",
|
package/src/commands/teleport.ts
CHANGED
|
@@ -100,7 +100,7 @@ function printHelp(): void {
|
|
|
100
100
|
console.log(
|
|
101
101
|
"The source and target must be different environments. Same-environment",
|
|
102
102
|
);
|
|
103
|
-
console.log("transfers
|
|
103
|
+
console.log("transfers such as local to local are not supported.");
|
|
104
104
|
console.log("");
|
|
105
105
|
console.log(
|
|
106
106
|
"Before exporting, the source assistant is backed up. Platform sources",
|
|
@@ -159,7 +159,7 @@ function printHelp(): void {
|
|
|
159
159
|
" Import data from my-local into existing docker assistant my-docker",
|
|
160
160
|
);
|
|
161
161
|
console.log(
|
|
162
|
-
"
|
|
162
|
+
" or hatch a new docker assistant named my-docker if it doesn't exist",
|
|
163
163
|
);
|
|
164
164
|
console.log("");
|
|
165
165
|
console.log(" cadet teleport --from my-local --platform");
|
|
@@ -185,7 +185,6 @@ function printHelp(): void {
|
|
|
185
185
|
|
|
186
186
|
export function parseArgs(argv: string[]): {
|
|
187
187
|
from: string | undefined;
|
|
188
|
-
to: string | undefined;
|
|
189
188
|
targetEnv: "local" | "docker" | "platform" | undefined;
|
|
190
189
|
targetName: string | undefined;
|
|
191
190
|
keepSource: boolean;
|
|
@@ -193,7 +192,6 @@ export function parseArgs(argv: string[]): {
|
|
|
193
192
|
help: boolean;
|
|
194
193
|
} {
|
|
195
194
|
let from: string | undefined;
|
|
196
|
-
let to: string | undefined;
|
|
197
195
|
let targetEnv: "local" | "docker" | "platform" | undefined;
|
|
198
196
|
let targetName: string | undefined;
|
|
199
197
|
let keepSource = false;
|
|
@@ -209,11 +207,6 @@ export function parseArgs(argv: string[]): {
|
|
|
209
207
|
continue;
|
|
210
208
|
}
|
|
211
209
|
from = argv[++i];
|
|
212
|
-
} else if (arg === "--to" && i + 1 < argv.length) {
|
|
213
|
-
if (argv[i + 1].startsWith("--")) {
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
216
|
-
to = argv[++i];
|
|
217
210
|
} else if (
|
|
218
211
|
arg === "--local" ||
|
|
219
212
|
arg === "--docker" ||
|
|
@@ -242,7 +235,7 @@ export function parseArgs(argv: string[]): {
|
|
|
242
235
|
process.exit(1);
|
|
243
236
|
}
|
|
244
237
|
|
|
245
|
-
return { from,
|
|
238
|
+
return { from, targetEnv, targetName, keepSource, dryRun, help };
|
|
246
239
|
}
|
|
247
240
|
|
|
248
241
|
// ---------------------------------------------------------------------------
|
|
@@ -415,7 +408,7 @@ async function backupSourceBeforeTeleport(
|
|
|
415
408
|
cloud: string,
|
|
416
409
|
displayName: string,
|
|
417
410
|
): Promise<void> {
|
|
418
|
-
console.log(`Backing up ${displayName}
|
|
411
|
+
console.log(`Backing up ${displayName} on ${cloud}...`);
|
|
419
412
|
try {
|
|
420
413
|
if (isCloudHome(cloud)) {
|
|
421
414
|
const cloudToken = readCloudToken();
|
|
@@ -443,7 +436,7 @@ async function backupSourceBeforeTeleport(
|
|
|
443
436
|
timeoutMs: LOCAL_SOURCE_BACKUP_TIMEOUT_MS,
|
|
444
437
|
});
|
|
445
438
|
if (!backupPath) {
|
|
446
|
-
throw new Error("backup export failed
|
|
439
|
+
throw new Error("backup export failed — see the warning above");
|
|
447
440
|
}
|
|
448
441
|
pruneOldBackups(entry.assistantId, 3, "pre-teleport");
|
|
449
442
|
console.log(`Backup saved: ${backupPath}`);
|
|
@@ -470,7 +463,7 @@ async function exportFromAssistant(
|
|
|
470
463
|
const cloudToken = readCloudToken();
|
|
471
464
|
if (!cloudToken) {
|
|
472
465
|
console.error(
|
|
473
|
-
"Not logged in. Run 'cadet login' first
|
|
466
|
+
"Not logged in. Run 'cadet login' first.",
|
|
474
467
|
);
|
|
475
468
|
process.exit(1);
|
|
476
469
|
}
|
|
@@ -688,7 +681,7 @@ async function importToAssistant(
|
|
|
688
681
|
const cloudToken = readCloudToken();
|
|
689
682
|
if (!cloudToken) {
|
|
690
683
|
console.error(
|
|
691
|
-
"Not logged in. Run 'cadet login' first
|
|
684
|
+
"Not logged in. Run 'cadet login' first.",
|
|
692
685
|
);
|
|
693
686
|
process.exit(1);
|
|
694
687
|
}
|
|
@@ -990,7 +983,7 @@ export async function resolveOrHatchTarget(
|
|
|
990
983
|
);
|
|
991
984
|
process.exit(1);
|
|
992
985
|
}
|
|
993
|
-
console.log(`Target: ${targetName}
|
|
986
|
+
console.log(`Target: ${targetName} — ${targetEnv}`);
|
|
994
987
|
return existing;
|
|
995
988
|
}
|
|
996
989
|
|
|
@@ -1204,7 +1197,7 @@ async function tryInjectCloudCredentials(
|
|
|
1204
1197
|
): Promise<void> {
|
|
1205
1198
|
const token = readCloudToken();
|
|
1206
1199
|
if (!token) {
|
|
1207
|
-
console.log(" Skipped cloud credential injection
|
|
1200
|
+
console.log(" Skipped cloud credential injection — not logged in.");
|
|
1208
1201
|
return;
|
|
1209
1202
|
}
|
|
1210
1203
|
|
|
@@ -1253,7 +1246,6 @@ async function tryInjectCloudCredentials(
|
|
|
1253
1246
|
bearerToken: entry.bearerToken,
|
|
1254
1247
|
assistantApiKey,
|
|
1255
1248
|
cloudAssistantId: registration.assistant.id,
|
|
1256
|
-
cloudBaseUrl: cloudUrl,
|
|
1257
1249
|
organizationId: orgId,
|
|
1258
1250
|
userId: user.id,
|
|
1259
1251
|
webhookSecret: registration.webhook_secret,
|
|
@@ -1277,7 +1269,7 @@ async function tryInjectCloudCredentials(
|
|
|
1277
1269
|
export const teleport = Object.assign(
|
|
1278
1270
|
async function teleport(): Promise<void> {
|
|
1279
1271
|
const args = process.argv.slice(3);
|
|
1280
|
-
const { from,
|
|
1272
|
+
const { from, targetEnv, targetName, keepSource, dryRun, help } =
|
|
1281
1273
|
parseArgs(args);
|
|
1282
1274
|
|
|
1283
1275
|
if (help) {
|
|
@@ -1285,17 +1277,6 @@ export const teleport = Object.assign(
|
|
|
1285
1277
|
process.exit(0);
|
|
1286
1278
|
}
|
|
1287
1279
|
|
|
1288
|
-
// Legacy --to flag deprecation
|
|
1289
|
-
if (to) {
|
|
1290
|
-
console.error("Error: --to is deprecated. Use environment flags instead:");
|
|
1291
|
-
console.error(
|
|
1292
|
-
" cadet teleport --from <source> --local|--docker|--platform [name]",
|
|
1293
|
-
);
|
|
1294
|
-
console.error("");
|
|
1295
|
-
console.error("Run 'cadet teleport --help' for details.");
|
|
1296
|
-
process.exit(1);
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
1280
|
if (!from) {
|
|
1300
1281
|
printHelp();
|
|
1301
1282
|
process.exit(1);
|
|
@@ -1393,13 +1374,13 @@ export const teleport = Object.assign(
|
|
|
1393
1374
|
? fromEntry.runtimeUrl
|
|
1394
1375
|
: undefined;
|
|
1395
1376
|
|
|
1396
|
-
console.log(`Exporting from ${from}
|
|
1377
|
+
console.log(`Exporting from ${from} on ${fromCloud}...`);
|
|
1397
1378
|
const { bundleKey } = await exportFromAssistant(
|
|
1398
1379
|
fromEntry,
|
|
1399
1380
|
fromCloud,
|
|
1400
1381
|
bundleCloudUrl,
|
|
1401
1382
|
);
|
|
1402
|
-
console.log(`Importing to ${existingTarget.assistantId}
|
|
1383
|
+
console.log(`Importing to ${existingTarget.assistantId} on ${toCloud}...`);
|
|
1403
1384
|
await importToAssistant(
|
|
1404
1385
|
existingTarget,
|
|
1405
1386
|
toCloud,
|
|
@@ -1410,7 +1391,7 @@ export const teleport = Object.assign(
|
|
|
1410
1391
|
} else {
|
|
1411
1392
|
// No existing target — just describe what would happen
|
|
1412
1393
|
console.log("Dry run summary:");
|
|
1413
|
-
console.log(` Would export data from: ${from}
|
|
1394
|
+
console.log(` Would export data from: ${from} on ${fromCloud}`);
|
|
1414
1395
|
console.log(` Would upload bundle via signed URL`);
|
|
1415
1396
|
console.log(
|
|
1416
1397
|
` Would hatch a new ${targetEnv} assistant${targetName ? ` named '${targetName}'` : ""}`,
|
|
@@ -1482,7 +1463,7 @@ export const teleport = Object.assign(
|
|
|
1482
1463
|
// resolveOrHatchTarget writes to the new entry).
|
|
1483
1464
|
await backupSourceBeforeTeleport(fromEntry, fromCloud, from);
|
|
1484
1465
|
|
|
1485
|
-
console.log(`Exporting from ${from}
|
|
1466
|
+
console.log(`Exporting from ${from} on ${fromCloud}...`);
|
|
1486
1467
|
const bundleCloudUrl = targetCloudUrl ?? getCloudUrl();
|
|
1487
1468
|
const { bundleKey } = await exportFromAssistant(
|
|
1488
1469
|
fromEntry,
|
|
@@ -1495,7 +1476,7 @@ export const teleport = Object.assign(
|
|
|
1495
1476
|
const toCloud = toEntry.cloud;
|
|
1496
1477
|
|
|
1497
1478
|
// Import from GCS
|
|
1498
|
-
console.log(`Importing to ${toEntry.assistantId}
|
|
1479
|
+
console.log(`Importing to ${toEntry.assistantId} on ${toCloud}...`);
|
|
1499
1480
|
await importToAssistant(
|
|
1500
1481
|
toEntry,
|
|
1501
1482
|
toCloud,
|
|
@@ -1555,7 +1536,7 @@ export const teleport = Object.assign(
|
|
|
1555
1536
|
await backupSourceBeforeTeleport(fromEntry, fromCloud, from);
|
|
1556
1537
|
|
|
1557
1538
|
// Export from source (bundle lives in GCS after this returns).
|
|
1558
|
-
console.log(`Exporting from ${from}
|
|
1539
|
+
console.log(`Exporting from ${from} on ${fromCloud}...`);
|
|
1559
1540
|
const { bundleKey } = await exportFromAssistant(
|
|
1560
1541
|
fromEntry,
|
|
1561
1542
|
fromCloud,
|
|
@@ -1632,7 +1613,7 @@ export const teleport = Object.assign(
|
|
|
1632
1613
|
}
|
|
1633
1614
|
|
|
1634
1615
|
// Import to target (also GCS-driven)
|
|
1635
|
-
console.log(`Importing to ${toEntry.assistantId}
|
|
1616
|
+
console.log(`Importing to ${toEntry.assistantId} on ${toCloud}...`);
|
|
1636
1617
|
await importToAssistant(
|
|
1637
1618
|
toEntry,
|
|
1638
1619
|
toCloud,
|
|
@@ -1660,7 +1641,7 @@ export const teleport = Object.assign(
|
|
|
1660
1641
|
removeAssistantEntry(fromEntry.assistantId);
|
|
1661
1642
|
console.log(`Source assistant '${from}' retired.`);
|
|
1662
1643
|
} else {
|
|
1663
|
-
console.log(`Source assistant '${from}' kept
|
|
1644
|
+
console.log(`Source assistant '${from}' kept via --keep-source.`);
|
|
1664
1645
|
}
|
|
1665
1646
|
}
|
|
1666
1647
|
|
package/src/commands/terminal.ts
CHANGED
|
@@ -71,7 +71,7 @@ function printHelp(): void {
|
|
|
71
71
|
console.log("");
|
|
72
72
|
console.log("Options:");
|
|
73
73
|
console.log(
|
|
74
|
-
" <name> Name of the assistant
|
|
74
|
+
" <name> Name of the assistant, defaults to active",
|
|
75
75
|
);
|
|
76
76
|
console.log(
|
|
77
77
|
" --assistant <name> Explicit assistant name (alternative to positional)",
|