@mycadet/cli 0.12.7 → 0.12.9
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/README.md +2 -2
- package/knip.json +1 -1
- package/node_modules/@mycadet/assistant-api/events/cloud-disconnected.ts +21 -0
- package/node_modules/@mycadet/assistant-api/events/host-bash.ts +1 -1
- package/node_modules/@mycadet/assistant-api/events/host-cu.ts +1 -1
- package/node_modules/@mycadet/assistant-api/events/host-file.ts +1 -1
- package/node_modules/@mycadet/assistant-api/events/notification-intent.ts +1 -1
- package/node_modules/@mycadet/assistant-api/events/show-cloud-login.ts +18 -0
- package/node_modules/@mycadet/assistant-api/index.ts +11 -11
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/package.json +2 -2
- package/node_modules/@mycadet/{service-contracts/src/platform-credential.ts → assistant-api/node_modules/@mycadet/service-contracts/src/cloud-credential.ts} +9 -9
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
- package/node_modules/@mycadet/assistant-api/responses/home.ts +1 -1
- package/node_modules/@mycadet/assistant-api/surfaces.ts +4 -4
- package/node_modules/@mycadet/avatar-manifest/src/__tests__/notification-avatar.test.ts +1 -1
- package/node_modules/@mycadet/avatar-manifest/src/notification-avatar.ts +2 -2
- package/node_modules/@mycadet/environments/src/seeds.ts +6 -6
- package/node_modules/@mycadet/environments/src/shell.test.ts +1 -1
- package/node_modules/@mycadet/environments/src/types.ts +6 -6
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/avatar-manifest/src/__tests__/notification-avatar.test.ts +1 -1
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/avatar-manifest/src/notification-avatar.ts +2 -2
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/environments/src/seeds.ts +6 -6
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/environments/src/shell.test.ts +1 -1
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/environments/src/types.ts +6 -6
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/package.json +2 -2
- package/node_modules/@mycadet/{assistant-api/node_modules/@mycadet/service-contracts/src/platform-credential.ts → local-mode/node_modules/@mycadet/service-contracts/src/cloud-credential.ts} +9 -9
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
- package/node_modules/@mycadet/local-mode/src/__tests__/pair.test.ts +5 -5
- package/node_modules/@mycadet/local-mode/src/__tests__/retire.test.ts +1 -1
- package/node_modules/@mycadet/local-mode/src/config.ts +2 -2
- package/node_modules/@mycadet/local-mode/src/index.ts +1 -1
- package/node_modules/@mycadet/local-mode/src/lockfile-contract.test.ts +3 -3
- package/node_modules/@mycadet/local-mode/src/lockfile-contract.ts +10 -5
- package/node_modules/@mycadet/local-mode/src/lockfile.test.ts +15 -15
- package/node_modules/@mycadet/local-mode/src/lockfile.ts +9 -9
- package/node_modules/@mycadet/local-mode/src/pair.ts +2 -2
- package/node_modules/@mycadet/local-mode/src/retire.ts +3 -3
- package/node_modules/@mycadet/local-mode/src/teleport-backup-policy.ts +2 -2
- package/node_modules/@mycadet/local-mode/src/unpair.ts +2 -2
- package/node_modules/@mycadet/service-contracts/package.json +2 -2
- package/node_modules/@mycadet/{local-mode/node_modules/@mycadet/service-contracts/src/platform-credential.ts → service-contracts/src/cloud-credential.ts} +9 -9
- package/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
- package/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
- package/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
- package/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
- package/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
- package/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
- package/package.json +3 -2
- package/src/__tests__/assistant-api-key-resolution.test.ts +6 -6
- package/src/__tests__/{assistant-client-platform-auth.test.ts → assistant-client-cloud-auth.test.ts} +10 -10
- package/src/__tests__/assistant-client-refresh.test.ts +2 -2
- package/src/__tests__/backup.test.ts +86 -91
- package/src/__tests__/client-token.test.ts +1 -1
- package/src/__tests__/client-tui-refresh.test.ts +1 -1
- package/src/__tests__/client-web-config.test.ts +2 -2
- package/src/__tests__/{platform-client.test.ts → cloud-client.test.ts} +59 -59
- package/src/__tests__/{platform-releases.test.ts → cloud-releases.test.ts} +28 -18
- package/src/__tests__/{cloudflare-tunnel.test.ts → cloudflare.test.ts} +1 -1
- package/src/__tests__/connect-import.test.ts +55 -39
- package/src/__tests__/debug.test.ts +38 -38
- package/src/__tests__/ensure-binary.test.ts +182 -0
- package/src/__tests__/guardian-token.test.ts +149 -56
- package/src/__tests__/hatch-provider-secrets.test.ts +2 -2
- package/src/__tests__/helpers/guardian-token.ts +22 -0
- package/src/__tests__/helpers/http.ts +10 -0
- package/src/__tests__/nginx-ingress.test.ts +21 -20
- package/src/__tests__/ngrok-spawn.test.ts +49 -0
- package/src/__tests__/orphan-detection.test.ts +1 -1
- package/src/__tests__/pair.test.ts +1 -8
- package/src/__tests__/{ps-platform-status.test.ts → ps-cloud-status.test.ts} +79 -26
- package/src/__tests__/ps-table.test.ts +79 -0
- package/src/__tests__/{recover.test.ts → restore.test.ts} +29 -28
- package/src/__tests__/retire.test.ts +58 -199
- package/src/__tests__/root-process-lockfile.test.ts +99 -0
- package/src/__tests__/sleep.test.ts +19 -19
- package/src/__tests__/statefulset.test.ts +8 -8
- package/src/__tests__/{tailscale-tunnel.test.ts → tailscale.test.ts} +2 -2
- package/src/__tests__/teleport-backup.test.ts +3 -3
- package/src/__tests__/teleport.test.ts +181 -197
- package/src/__tests__/tunnel.test.ts +257 -222
- package/src/__tests__/uninstall.test.ts +13 -4
- package/src/__tests__/upgrade-local.test.ts +3 -3
- package/src/__tests__/upgrade-replay-env.test.ts +12 -12
- package/src/__tests__/wake.test.ts +260 -43
- package/src/__tests__/workos-pkce.test.ts +2 -2
- package/src/commands/backup.ts +24 -24
- package/src/commands/client.ts +76 -76
- package/src/commands/completions.test.ts +104 -0
- package/src/commands/completions.ts +87 -0
- package/src/commands/debug.ts +20 -20
- package/src/commands/exec.ts +12 -12
- package/src/commands/flags.ts +2 -2
- package/src/commands/hatch.ts +23 -23
- package/src/commands/login.ts +22 -22
- package/src/commands/logs.ts +8 -8
- package/src/commands/ps.ts +244 -86
- package/src/commands/restart.ts +30 -0
- package/src/commands/restore.ts +140 -22
- package/src/commands/retire.ts +48 -150
- package/src/commands/rollback.ts +20 -26
- package/src/commands/setup.ts +2 -2
- package/src/commands/sleep.ts +2 -2
- package/src/commands/teleport.ts +109 -109
- package/src/commands/terminal.ts +158 -18
- package/src/commands/tunnel.ts +138 -157
- package/src/commands/uninstall.ts +57 -9
- package/src/commands/unpair.ts +3 -3
- package/src/commands/upgrade.ts +30 -54
- package/src/commands/use.ts +1 -1
- package/src/commands/wake.ts +166 -50
- package/src/index.ts +18 -9
- package/src/lib/__tests__/{platform-client-signed-url.test.ts → cloud-client-signed-url.test.ts} +16 -16
- package/src/lib/__tests__/docker.test.ts +168 -20
- package/src/lib/__tests__/job-polling.test.ts +2 -2
- package/src/lib/__tests__/local-runtime-client.test.ts +4 -4
- package/src/lib/__tests__/runtime-url.test.ts +1 -1
- package/src/lib/assistant-api-key-resolution.ts +5 -5
- package/src/lib/assistant-client.ts +8 -8
- package/src/lib/assistant-config.ts +58 -12
- package/src/lib/{platform-client.ts → cloud-client.ts} +107 -98
- package/src/lib/cloud-login.ts +24 -24
- package/src/lib/{platform-releases.ts → cloud-releases.ts} +11 -9
- package/src/lib/cloud-values.ts +3 -0
- package/src/lib/config-utils.ts +1 -1
- package/src/lib/detached-process.ts +1 -1
- package/src/lib/docker.ts +260 -53
- package/src/lib/ensure-binary.ts +171 -0
- package/src/lib/environments/__tests__/paths.test.ts +2 -2
- package/src/lib/environments/__tests__/resolve.test.ts +26 -26
- package/src/lib/environments/resolve.ts +8 -8
- package/src/lib/flag-args.test.ts +1 -1
- package/src/lib/flag-args.ts +1 -1
- package/src/lib/guardian-token.ts +37 -5
- package/src/lib/hatch-local.ts +19 -23
- package/src/lib/health-check.ts +6 -6
- package/src/lib/ingress-config.ts +8 -35
- package/src/lib/job-polling.ts +2 -2
- package/src/lib/live-voice/connection.ts +12 -12
- package/src/lib/live-voice/tunnel.test.ts +11 -11
- package/src/lib/live-voice/tunnel.ts +15 -15
- package/src/lib/local-runtime-client.ts +20 -20
- package/src/lib/local.ts +10 -31
- package/src/lib/nginx-ingress.ts +42 -15
- package/src/lib/pair.ts +1 -1
- package/src/lib/process.ts +1 -1
- package/src/lib/provider-secrets.ts +3 -3
- package/src/lib/retire-apple-container.ts +1 -1
- package/src/lib/runtime-url.ts +8 -8
- package/src/lib/statefulset.ts +2 -2
- package/src/lib/sync-cloud-assistants.ts +19 -19
- package/src/lib/teleport-backup.ts +4 -4
- package/src/lib/terminal-client.ts +17 -17
- package/src/lib/terminal-session.ts +24 -24
- package/src/lib/{tunnel-edge.ts → tunnel.ts} +199 -11
- package/src/lib/tunnels/cadet.ts +29 -0
- package/src/lib/{cloudflare-tunnel.ts → tunnels/cloudflare.ts} +36 -15
- package/src/lib/tunnels/gateway-notify.ts +61 -0
- package/src/lib/{ngrok.ts → tunnels/ngrok.ts} +350 -89
- package/src/lib/{tailscale-tunnel.ts → tunnels/tailscale.ts} +53 -9
- package/src/lib/upgrade-lifecycle.ts +8 -21
- package/src/lib/upgrade-preflight.ts +2 -2
- package/src/lib/workos-pkce.ts +5 -5
- package/node_modules/@mycadet/assistant-api/events/platform-disconnected.ts +0 -21
- package/node_modules/@mycadet/assistant-api/events/show-platform-login.ts +0 -20
- package/src/commands/recover.ts +0 -131
- package/src/commands/roadmap.ts +0 -459
- package/src/commands/ssh.ts +0 -144
- package/src/commands/workflows.ts +0 -305
- package/src/lib/managed-cloud.ts +0 -3
package/src/commands/restore.ts
CHANGED
|
@@ -1,7 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { isCloudHome } from "../lib/cloud-values.js";
|
|
2
|
+
import {
|
|
3
|
+
existsSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
renameSync,
|
|
7
|
+
unlinkSync,
|
|
8
|
+
} from "fs";
|
|
9
|
+
import { basename, dirname, join } from "path";
|
|
10
|
+
|
|
11
|
+
import { resolveExistingLocalSigningKey } from "@mycadet/local-mode";
|
|
12
|
+
import {
|
|
13
|
+
findAssistantByName,
|
|
14
|
+
saveAssistantEntry,
|
|
15
|
+
} from "../lib/assistant-config.js";
|
|
5
16
|
import type { AssistantEntry } from "../lib/assistant-config.js";
|
|
6
17
|
import {
|
|
7
18
|
bundleFileSizeBytes,
|
|
@@ -16,23 +27,37 @@ import {
|
|
|
16
27
|
leaseGuardianToken,
|
|
17
28
|
} from "../lib/guardian-token.js";
|
|
18
29
|
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
30
|
+
generateLocalSigningKey,
|
|
31
|
+
startCes,
|
|
32
|
+
startLocalDaemon,
|
|
33
|
+
startGateway,
|
|
34
|
+
} from "../lib/local.js";
|
|
35
|
+
import { getArchivePath, getMetadataPath } from "../lib/retire-archive.js";
|
|
36
|
+
import { exec } from "../lib/step-runner.js";
|
|
37
|
+
import {
|
|
38
|
+
readCloudToken,
|
|
39
|
+
rollbackCloudAssistant,
|
|
40
|
+
cloudRequestSignedUrl,
|
|
22
41
|
platformUploadToSignedUrl,
|
|
23
42
|
platformImportPreflightFromGcs,
|
|
24
43
|
platformImportBundleFromGcs,
|
|
25
44
|
platformPollJobStatus,
|
|
26
|
-
} from "../lib/
|
|
45
|
+
} from "../lib/cloud-client.js";
|
|
27
46
|
import { performDockerRollback } from "../lib/upgrade-lifecycle.js";
|
|
28
47
|
import { loopbackSafeFetch } from "../lib/loopback-fetch.js";
|
|
29
48
|
|
|
30
49
|
function printUsage(): void {
|
|
31
50
|
console.log(
|
|
32
|
-
"Usage: cadet restore <name> --from <path> [--version <version>] [--dry-run]",
|
|
51
|
+
"Usage: cadet restore <name> [--from <path>] [--version <version>] [--dry-run]",
|
|
33
52
|
);
|
|
34
53
|
console.log("");
|
|
35
54
|
console.log("Restore data from a .vbundle backup into an assistant.");
|
|
55
|
+
console.log(
|
|
56
|
+
"Without --from, recovers a retired assistant from its archive in",
|
|
57
|
+
);
|
|
58
|
+
console.log(
|
|
59
|
+
"$XDG_DATA_HOME/cadet/retired/ and starts it again.",
|
|
60
|
+
);
|
|
36
61
|
console.log(
|
|
37
62
|
"With --version, also rolls back to the specified version first.",
|
|
38
63
|
);
|
|
@@ -50,6 +75,7 @@ function printUsage(): void {
|
|
|
50
75
|
);
|
|
51
76
|
console.log("");
|
|
52
77
|
console.log("Examples:");
|
|
78
|
+
console.log(" cadet restore my-retired-assistant");
|
|
53
79
|
console.log(" cadet restore my-assistant --from backup.vbundle");
|
|
54
80
|
console.log(
|
|
55
81
|
" cadet restore my-assistant --from backup.vbundle --version v1.2.3",
|
|
@@ -188,7 +214,7 @@ async function restorePlatform(
|
|
|
188
214
|
opts: { version?: string; dryRun: boolean },
|
|
189
215
|
): Promise<void> {
|
|
190
216
|
// Step 1 — Authenticate
|
|
191
|
-
const token =
|
|
217
|
+
const token = readCloudToken();
|
|
192
218
|
if (!token) {
|
|
193
219
|
console.error("Not logged in. Run 'cadet login' first.");
|
|
194
220
|
process.exit(1);
|
|
@@ -198,10 +224,10 @@ async function restorePlatform(
|
|
|
198
224
|
// We deliberately omit min/max runtime version here: restore uploads an
|
|
199
225
|
// arbitrary .vbundle from disk (often produced by a different runtime
|
|
200
226
|
// than the one we'd query right now), and the bundle's own manifest is
|
|
201
|
-
// the authority on its compatibility band. The
|
|
227
|
+
// the authority on its compatibility band. The cloud skips the
|
|
202
228
|
// version gate when these fields are absent and re-derives compat from
|
|
203
229
|
// the manifest when it processes the import.
|
|
204
|
-
const { url: uploadUrl, bundleKey } = await
|
|
230
|
+
const { url: uploadUrl, bundleKey } = await cloudRequestSignedUrl(
|
|
205
231
|
{ operation: "upload" },
|
|
206
232
|
token,
|
|
207
233
|
entry.runtimeUrl,
|
|
@@ -245,7 +271,7 @@ async function restorePlatform(
|
|
|
245
271
|
|
|
246
272
|
if (preflightResult.statusCode === 404) {
|
|
247
273
|
console.error(
|
|
248
|
-
"No
|
|
274
|
+
"No cloud assistant found. Ensure your assistant is running.",
|
|
249
275
|
);
|
|
250
276
|
process.exit(1);
|
|
251
277
|
}
|
|
@@ -335,7 +361,7 @@ async function restorePlatform(
|
|
|
335
361
|
);
|
|
336
362
|
|
|
337
363
|
try {
|
|
338
|
-
await
|
|
364
|
+
await rollbackCloudAssistant(
|
|
339
365
|
token,
|
|
340
366
|
entry.assistantId,
|
|
341
367
|
opts.version,
|
|
@@ -381,7 +407,7 @@ async function restorePlatform(
|
|
|
381
407
|
|
|
382
408
|
if (importResult.statusCode === 404) {
|
|
383
409
|
console.error(
|
|
384
|
-
"No
|
|
410
|
+
"No cloud assistant found. Ensure your assistant is running.",
|
|
385
411
|
);
|
|
386
412
|
process.exit(1);
|
|
387
413
|
}
|
|
@@ -448,7 +474,7 @@ async function restorePlatform(
|
|
|
448
474
|
}
|
|
449
475
|
}
|
|
450
476
|
// Transient error (5xx, network) — retry
|
|
451
|
-
console.warn(`Polling failed
|
|
477
|
+
console.warn(`Polling failed: ${msg}. Retrying...`);
|
|
452
478
|
continue;
|
|
453
479
|
}
|
|
454
480
|
|
|
@@ -515,6 +541,86 @@ async function restorePlatform(
|
|
|
515
541
|
}
|
|
516
542
|
}
|
|
517
543
|
|
|
544
|
+
/**
|
|
545
|
+
* Recover a previously retired local assistant from its archive: extract the
|
|
546
|
+
* archived workspace data back to its original location, restore the lockfile
|
|
547
|
+
* entry, and start the assistant and gateway. Archives live in
|
|
548
|
+
* $XDG_DATA_HOME/cadet/retired/ (default: ~/.local/share/cadet/retired/).
|
|
549
|
+
*/
|
|
550
|
+
async function recoverFromArchive(name: string): Promise<void> {
|
|
551
|
+
const archivePath = getArchivePath(name);
|
|
552
|
+
const metadataPath = getMetadataPath(name);
|
|
553
|
+
|
|
554
|
+
// Verify the archive exists
|
|
555
|
+
if (!existsSync(archivePath) || !existsSync(metadataPath)) {
|
|
556
|
+
console.error(`No retired archive found for '${name}'.`);
|
|
557
|
+
process.exit(1);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// Read and validate metadata before any side effects
|
|
561
|
+
const entry: AssistantEntry = JSON.parse(readFileSync(metadataPath, "utf-8"));
|
|
562
|
+
if (!entry.resources) {
|
|
563
|
+
throw new Error(
|
|
564
|
+
`Retired assistant '${name}' is missing resource configuration. ` +
|
|
565
|
+
`Fix the archive metadata at ${metadataPath} and retry, ` +
|
|
566
|
+
`or run 'cadet hatch' to re-provision with proper resource allocation.`,
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// Check that the recovering entry's own target directory is free.
|
|
571
|
+
const target = entry.resources.instanceDir;
|
|
572
|
+
if (existsSync(target)) {
|
|
573
|
+
console.error(
|
|
574
|
+
`Error: ${target} already exists (owned by ${entry.assistantId}). ` +
|
|
575
|
+
`Retire the current assistant first.`,
|
|
576
|
+
);
|
|
577
|
+
process.exit(1);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// Extract and rename.
|
|
581
|
+
//
|
|
582
|
+
// retireLocal archives the full instanceDir. The directory is staged under
|
|
583
|
+
// `<archive>.staging` inside the retired dir before being packed with
|
|
584
|
+
// `tar -C <retiredDir> <stagingBasename>`, so the top-level entry inside
|
|
585
|
+
// the tarball is always `<name>.tar.gz.staging`.
|
|
586
|
+
//
|
|
587
|
+
// Correct restoration: extract to retiredDir, then rename the staging entry
|
|
588
|
+
// back to the original target path. Using homedir() as the -C target was
|
|
589
|
+
// wrong for any instance stored outside the home directory.
|
|
590
|
+
const targetDir = entry.resources.instanceDir;
|
|
591
|
+
const retiredDir = dirname(archivePath);
|
|
592
|
+
const extractedPath = join(retiredDir, basename(archivePath) + ".staging");
|
|
593
|
+
|
|
594
|
+
await exec("tar", ["xzf", archivePath, "-C", retiredDir]);
|
|
595
|
+
// Validate the staged identity before occupying the destination or updating
|
|
596
|
+
// the registry. A failed validation leaves the archive available for retry.
|
|
597
|
+
const signingKey = resolveExistingLocalSigningKey(
|
|
598
|
+
entry.resources,
|
|
599
|
+
extractedPath,
|
|
600
|
+
);
|
|
601
|
+
const bootstrapSecret =
|
|
602
|
+
entry.guardianBootstrapSecret ?? generateLocalSigningKey();
|
|
603
|
+
entry.resources = { ...entry.resources, signingKey };
|
|
604
|
+
entry.guardianBootstrapSecret = bootstrapSecret;
|
|
605
|
+
mkdirSync(dirname(targetDir), { recursive: true });
|
|
606
|
+
renameSync(extractedPath, targetDir);
|
|
607
|
+
saveAssistantEntry(entry);
|
|
608
|
+
|
|
609
|
+
// Start CES sibling + daemon + gateway in parallel, the way the
|
|
610
|
+
// Docker topology brings its sibling processes up together. startCes
|
|
611
|
+
// always launches the CES sibling.
|
|
612
|
+
await Promise.all([
|
|
613
|
+
startCes(false, entry.resources),
|
|
614
|
+
startLocalDaemon(false, entry.resources, { signingKey }),
|
|
615
|
+
startGateway(false, entry.resources, { signingKey, bootstrapSecret }),
|
|
616
|
+
]);
|
|
617
|
+
|
|
618
|
+
unlinkSync(archivePath);
|
|
619
|
+
unlinkSync(metadataPath);
|
|
620
|
+
|
|
621
|
+
console.log(`✅ Recovered assistant '${name}'.`);
|
|
622
|
+
}
|
|
623
|
+
|
|
518
624
|
export const restore = Object.assign(
|
|
519
625
|
async function restore(): Promise<void> {
|
|
520
626
|
const { name, fromPath, version, dryRun, help } = parseArgs(process.argv);
|
|
@@ -540,13 +646,25 @@ export const restore = Object.assign(
|
|
|
540
646
|
process.exit(1);
|
|
541
647
|
}
|
|
542
648
|
|
|
543
|
-
if (!name
|
|
544
|
-
console.error("Error:
|
|
649
|
+
if (!name) {
|
|
650
|
+
console.error("Error: <name> is required.");
|
|
545
651
|
console.error("");
|
|
546
652
|
printUsage();
|
|
547
653
|
process.exit(1);
|
|
548
654
|
}
|
|
549
655
|
|
|
656
|
+
// Without --from: recover a retired assistant from its retire archive.
|
|
657
|
+
if (!fromPath) {
|
|
658
|
+
if (dryRun) {
|
|
659
|
+
console.error(
|
|
660
|
+
"Dry-run is only supported with --from. Recovery replays the retired assistant's archive directly.",
|
|
661
|
+
);
|
|
662
|
+
process.exit(1);
|
|
663
|
+
}
|
|
664
|
+
await recoverFromArchive(name);
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
|
|
550
668
|
// Look up the instance
|
|
551
669
|
const entry = findAssistantByName(name);
|
|
552
670
|
if (!entry) {
|
|
@@ -562,7 +680,7 @@ export const restore = Object.assign(
|
|
|
562
680
|
}
|
|
563
681
|
|
|
564
682
|
const sizeMB = formatBundleSizeMb(bundleFileSizeBytes(fromPath));
|
|
565
|
-
console.log(`Reading ${fromPath}
|
|
683
|
+
console.log(`Reading ${fromPath} — ${sizeMB} MB...`);
|
|
566
684
|
|
|
567
685
|
// Detect topology and route platform assistants through Django import
|
|
568
686
|
const cloud =
|
|
@@ -575,7 +693,7 @@ export const restore = Object.assign(
|
|
|
575
693
|
process.exit(1);
|
|
576
694
|
}
|
|
577
695
|
|
|
578
|
-
if (
|
|
696
|
+
if (isCloudHome(cloud)) {
|
|
579
697
|
const bundleData = readFileSync(fromPath);
|
|
580
698
|
await restorePlatform(entry, name, bundleData, { version, dryRun });
|
|
581
699
|
return;
|
|
@@ -583,7 +701,7 @@ export const restore = Object.assign(
|
|
|
583
701
|
|
|
584
702
|
if (version && cloud !== "docker") {
|
|
585
703
|
console.error(
|
|
586
|
-
"Restore with --version is only supported for Docker and
|
|
704
|
+
"Restore with --version is only supported for Docker and cloud assistants.",
|
|
587
705
|
);
|
|
588
706
|
process.exit(1);
|
|
589
707
|
}
|
|
@@ -625,7 +743,7 @@ export const restore = Object.assign(
|
|
|
625
743
|
}
|
|
626
744
|
}
|
|
627
745
|
},
|
|
628
|
-
{ description: "Restore
|
|
746
|
+
{ description: "Restore a .vbundle backup, or recover a retired assistant" },
|
|
629
747
|
);
|
|
630
748
|
|
|
631
749
|
|
package/src/commands/retire.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isCloudHome } from "../lib/cloud-values.js";
|
|
2
2
|
import { existsSync, unlinkSync } from "fs";
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
|
|
@@ -24,10 +24,9 @@ import { printInstanceDetails } from "../lib/instance-details.js";
|
|
|
24
24
|
import {
|
|
25
25
|
authHeaders,
|
|
26
26
|
authHeadersForKnownOrganization,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
} from "../lib/platform-client.js";
|
|
27
|
+
getCloudUrl,
|
|
28
|
+
readCloudToken,
|
|
29
|
+
} from "../lib/cloud-client.js";
|
|
31
30
|
import { retireDocker, dockerResourceNames } from "../lib/docker.js";
|
|
32
31
|
import { retireInstance as retireAwsInstance } from "../lib/aws.js";
|
|
33
32
|
import { retireInstance as retireGcpInstance } from "../lib/gcp.js";
|
|
@@ -51,44 +50,15 @@ interface RetireArgs {
|
|
|
51
50
|
yes: boolean;
|
|
52
51
|
}
|
|
53
52
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
platformBaseUrl: string | null;
|
|
57
|
-
organizationId: string | null;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const PLATFORM_TOKEN_ENV = "CADET_PLATFORM_TOKEN";
|
|
53
|
+
const CLOUD_TOKEN_ENV = "CADET_CLOUD_TOKEN";
|
|
54
|
+
const LEGACY_CLOUD_TOKEN_ENV = "CADET_PLATFORM_TOKEN";
|
|
61
55
|
|
|
62
56
|
function readPlatformRetireToken(): string | null {
|
|
63
|
-
const envToken =
|
|
57
|
+
const envToken = (
|
|
58
|
+
process.env[CLOUD_TOKEN_ENV] ?? process.env[LEGACY_CLOUD_TOKEN_ENV]
|
|
59
|
+
)?.trim();
|
|
64
60
|
if (envToken) return envToken;
|
|
65
|
-
return
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function platformOrigin(platformUrl: string): string | null {
|
|
69
|
-
try {
|
|
70
|
-
return new URL(platformUrl).origin;
|
|
71
|
-
} catch {
|
|
72
|
-
return null;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function resolveTrustedSelfHostedPlatformUrl(
|
|
77
|
-
storedPlatformUrl: string | null,
|
|
78
|
-
): string | null {
|
|
79
|
-
const configuredPlatformUrl = getPlatformUrl();
|
|
80
|
-
if (!storedPlatformUrl) return configuredPlatformUrl;
|
|
81
|
-
|
|
82
|
-
const storedOrigin = platformOrigin(storedPlatformUrl);
|
|
83
|
-
const configuredOrigin = platformOrigin(configuredPlatformUrl);
|
|
84
|
-
if (storedOrigin && configuredOrigin && storedOrigin === configuredOrigin) {
|
|
85
|
-
return configuredPlatformUrl;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
console.warn(
|
|
89
|
-
"⚠️ Stored platform URL does not match the configured platform URL; skipping platform unregister.",
|
|
90
|
-
);
|
|
91
|
-
return null;
|
|
61
|
+
return readCloudToken();
|
|
92
62
|
}
|
|
93
63
|
|
|
94
64
|
function nonEmptyString(value: string | null | undefined): string | null {
|
|
@@ -96,35 +66,22 @@ function nonEmptyString(value: string | null | undefined): string | null {
|
|
|
96
66
|
return trimmed ? trimmed : null;
|
|
97
67
|
}
|
|
98
68
|
|
|
99
|
-
function
|
|
100
|
-
entry: AssistantEntry,
|
|
101
|
-
): SelfHostedPlatformRegistration | null {
|
|
102
|
-
const platformAssistantId = nonEmptyString(entry.platformAssistantId);
|
|
103
|
-
if (!platformAssistantId) return null;
|
|
104
|
-
|
|
105
|
-
return {
|
|
106
|
-
platformAssistantId,
|
|
107
|
-
platformBaseUrl: nonEmptyString(entry.platformBaseUrl),
|
|
108
|
-
organizationId: nonEmptyString(entry.platformOrganizationId),
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
async function deletePlatformAssistant(
|
|
69
|
+
async function deleteCloudAssistant(
|
|
113
70
|
assistantId: string,
|
|
114
71
|
options: {
|
|
115
72
|
token: string;
|
|
116
|
-
|
|
73
|
+
cloudUrl?: string;
|
|
117
74
|
organizationId?: string;
|
|
118
75
|
label: string;
|
|
119
76
|
successMessage: string;
|
|
120
77
|
alreadyGoneMessage: string;
|
|
121
78
|
},
|
|
122
79
|
): Promise<void> {
|
|
123
|
-
const
|
|
124
|
-
const url = `${
|
|
80
|
+
const cloudUrl = options.cloudUrl || getCloudUrl();
|
|
81
|
+
const url = `${cloudUrl}/v1/assistants/${encodeURIComponent(assistantId)}/retire/`;
|
|
125
82
|
const headers = options.organizationId
|
|
126
83
|
? authHeadersForKnownOrganization(options.token, options.organizationId)
|
|
127
|
-
: await authHeaders(options.token,
|
|
84
|
+
: await authHeaders(options.token, cloudUrl);
|
|
128
85
|
|
|
129
86
|
const response = await loopbackSafeFetch(url, {
|
|
130
87
|
method: "DELETE",
|
|
@@ -143,85 +100,23 @@ async function deletePlatformAssistant(
|
|
|
143
100
|
}
|
|
144
101
|
}
|
|
145
102
|
|
|
146
|
-
async function readSelfHostedPlatformRegistration(
|
|
147
|
-
entry: AssistantEntry,
|
|
148
|
-
): Promise<SelfHostedPlatformRegistration | null> {
|
|
149
|
-
const fallback = lockfileSelfHostedPlatformRegistration(entry);
|
|
150
|
-
const gatewayUrl = entry.localUrl ?? entry.runtimeUrl;
|
|
151
|
-
const platformAssistant = await readGatewayCredential(
|
|
152
|
-
gatewayUrl,
|
|
153
|
-
"cadet:platform_assistant_id",
|
|
154
|
-
entry.bearerToken,
|
|
155
|
-
);
|
|
156
|
-
if (platformAssistant.unreachable) {
|
|
157
|
-
if (fallback) {
|
|
158
|
-
console.warn(
|
|
159
|
-
"\u26a0\ufe0f Could not read platform registration from the local assistant; using saved platform registration.",
|
|
160
|
-
);
|
|
161
|
-
return fallback;
|
|
162
|
-
}
|
|
163
|
-
console.warn(
|
|
164
|
-
"\u26a0\ufe0f Could not read platform registration from the local assistant and no saved platform registration is available; skipping platform unregister.",
|
|
165
|
-
);
|
|
166
|
-
return null;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const platformAssistantId =
|
|
170
|
-
nonEmptyString(platformAssistant.value) ?? fallback?.platformAssistantId;
|
|
171
|
-
if (!platformAssistantId) return null;
|
|
172
|
-
|
|
173
|
-
const [platformBaseUrl, organizationId] = await Promise.all([
|
|
174
|
-
readGatewayCredential(
|
|
175
|
-
gatewayUrl,
|
|
176
|
-
"cadet:platform_base_url",
|
|
177
|
-
entry.bearerToken,
|
|
178
|
-
),
|
|
179
|
-
readGatewayCredential(
|
|
180
|
-
gatewayUrl,
|
|
181
|
-
"cadet:platform_organization_id",
|
|
182
|
-
entry.bearerToken,
|
|
183
|
-
),
|
|
184
|
-
]);
|
|
185
|
-
|
|
186
|
-
return {
|
|
187
|
-
platformAssistantId,
|
|
188
|
-
platformBaseUrl:
|
|
189
|
-
nonEmptyString(platformBaseUrl.value) ??
|
|
190
|
-
fallback?.platformBaseUrl ??
|
|
191
|
-
null,
|
|
192
|
-
organizationId:
|
|
193
|
-
nonEmptyString(organizationId.value) ?? fallback?.organizationId ?? null,
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
|
|
197
103
|
async function unregisterSelfHostedLocalPlatformRecord(
|
|
198
104
|
entry: AssistantEntry,
|
|
199
105
|
): Promise<void> {
|
|
200
106
|
const token = readPlatformRetireToken();
|
|
201
107
|
if (!token) return;
|
|
202
108
|
|
|
203
|
-
const
|
|
204
|
-
if (!
|
|
205
|
-
|
|
206
|
-
const platformUrl = resolveTrustedSelfHostedPlatformUrl(
|
|
207
|
-
registration.platformBaseUrl,
|
|
208
|
-
);
|
|
209
|
-
if (!platformUrl) return;
|
|
210
|
-
if (!registration.organizationId) {
|
|
211
|
-
console.warn(
|
|
212
|
-
"⚠️ Could not read platform organization ID from the local assistant; skipping platform unregister.",
|
|
213
|
-
);
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
109
|
+
const cloudAssistantId = nonEmptyString(entry.cloudAssistantId);
|
|
110
|
+
if (!cloudAssistantId) return;
|
|
216
111
|
|
|
217
|
-
await
|
|
112
|
+
await deleteCloudAssistant(cloudAssistantId, {
|
|
218
113
|
token,
|
|
219
|
-
|
|
220
|
-
organizationId:
|
|
114
|
+
cloudUrl: getCloudUrl(),
|
|
115
|
+
organizationId: nonEmptyString(entry.cloudOrganizationId) ?? undefined,
|
|
221
116
|
label: "Platform self-hosted unregister",
|
|
222
|
-
successMessage: "
|
|
117
|
+
successMessage: "✅ Platform self-hosted registration unregistered.",
|
|
223
118
|
alreadyGoneMessage:
|
|
224
|
-
"
|
|
119
|
+
"✅ Platform self-hosted registration already unregistered (404).",
|
|
225
120
|
});
|
|
226
121
|
}
|
|
227
122
|
|
|
@@ -275,9 +170,9 @@ async function retireCadet(
|
|
|
275
170
|
}
|
|
276
171
|
|
|
277
172
|
try {
|
|
278
|
-
await
|
|
173
|
+
await deleteCloudAssistant(assistantId, {
|
|
279
174
|
token,
|
|
280
|
-
|
|
175
|
+
cloudUrl: runtimeUrl,
|
|
281
176
|
label: "Platform retire",
|
|
282
177
|
successMessage: "\u2705 Platform-hosted instance retired.",
|
|
283
178
|
alreadyGoneMessage:
|
|
@@ -324,8 +219,8 @@ function printRetireTarget(entry: AssistantEntry, cloud: string): void {
|
|
|
324
219
|
: {}),
|
|
325
220
|
...(entry.cloud === "docker"
|
|
326
221
|
? {
|
|
327
|
-
container:
|
|
328
|
-
|
|
222
|
+
container: dockerResourceNames(entry.assistantId)
|
|
223
|
+
.assistantContainer,
|
|
329
224
|
}
|
|
330
225
|
: {}),
|
|
331
226
|
},
|
|
@@ -366,37 +261,38 @@ function teeConsoleToLogFile(fd: number | "ignore"): void {
|
|
|
366
261
|
|
|
367
262
|
export const retire = Object.assign(
|
|
368
263
|
async function retire(): Promise<void> {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
264
|
+
if (process.env.CADET_DESKTOP_APP) {
|
|
265
|
+
resetLogFile("retire.log");
|
|
266
|
+
}
|
|
267
|
+
const logFd = process.env.CADET_DESKTOP_APP
|
|
268
|
+
? openLogFile("retire.log")
|
|
269
|
+
: "ignore";
|
|
270
|
+
teeConsoleToLogFile(logFd);
|
|
376
271
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
},
|
|
272
|
+
try {
|
|
273
|
+
await retireInner();
|
|
274
|
+
} finally {
|
|
275
|
+
closeLogFile(logFd);
|
|
276
|
+
}
|
|
277
|
+
},
|
|
383
278
|
{ description: "Delete an assistant instance" },
|
|
384
279
|
);
|
|
385
280
|
|
|
386
|
-
|
|
387
281
|
async function retireInner(): Promise<void> {
|
|
388
282
|
const args = process.argv.slice(3);
|
|
389
283
|
if (args.includes("--help") || args.includes("-h")) {
|
|
390
|
-
console.log(
|
|
284
|
+
console.log(
|
|
285
|
+
"Usage: cadet retire [<name-or-id>] [--source <source>] [--yes]",
|
|
286
|
+
);
|
|
391
287
|
console.log("");
|
|
392
288
|
console.log("Delete an assistant instance and archive its data.");
|
|
393
289
|
console.log(
|
|
394
|
-
"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.",
|
|
395
291
|
);
|
|
396
292
|
console.log("");
|
|
397
293
|
console.log("Arguments:");
|
|
398
294
|
console.log(
|
|
399
|
-
" <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",
|
|
400
296
|
);
|
|
401
297
|
console.log("");
|
|
402
298
|
console.log("Options:");
|
|
@@ -415,7 +311,9 @@ async function retireInner(): Promise<void> {
|
|
|
415
311
|
console.error(
|
|
416
312
|
"Error: No assistant specified and no active assistant. Pass a name or ID, or pick one with `cadet use <name>`.",
|
|
417
313
|
);
|
|
418
|
-
console.error(
|
|
314
|
+
console.error(
|
|
315
|
+
"Usage: cadet retire <name-or-id> [--source <source>] [--yes]",
|
|
316
|
+
);
|
|
419
317
|
process.exit(1);
|
|
420
318
|
}
|
|
421
319
|
|
|
@@ -498,7 +396,7 @@ async function retireInner(): Promise<void> {
|
|
|
498
396
|
await retireLocal(assistantId, entry);
|
|
499
397
|
} else if (cloud === "custom") {
|
|
500
398
|
await retireCustom(entry);
|
|
501
|
-
} else if (
|
|
399
|
+
} else if (isCloudHome(cloud)) {
|
|
502
400
|
await retireCadet(assistantId, entry.runtimeUrl);
|
|
503
401
|
} else {
|
|
504
402
|
console.error(`Error: Unknown cloud type '${cloud}'.`);
|