@mycadet/cli 0.12.7-dev.202610111049.d2b52a8 → 0.12.8

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.
Files changed (30) hide show
  1. package/node_modules/@mycadet/assistant-api/events/cloud-disconnected.ts +2 -2
  2. package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/ingress.ts +10 -4
  3. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/ingress.ts +10 -4
  4. package/node_modules/@mycadet/service-contracts/src/ingress.ts +10 -4
  5. package/package.json +4 -3
  6. package/src/__tests__/{cloudflare-tunnel.test.ts → cloudflare.test.ts} +1 -1
  7. package/src/__tests__/ps-cloud-status.test.ts +53 -0
  8. package/src/__tests__/retire.test.ts +37 -178
  9. package/src/__tests__/root-process-lockfile.test.ts +99 -0
  10. package/src/__tests__/{tailscale-tunnel.test.ts → tailscale.test.ts} +1 -1
  11. package/src/__tests__/tunnel.test.ts +127 -80
  12. package/src/__tests__/upgrade-local.test.ts +3 -3
  13. package/src/__tests__/wake.test.ts +16 -9
  14. package/src/commands/client.ts +1 -1
  15. package/src/commands/ps.ts +181 -72
  16. package/src/commands/retire.ts +28 -137
  17. package/src/commands/tunnel.ts +76 -118
  18. package/src/commands/upgrade.ts +2 -9
  19. package/src/commands/wake.ts +5 -9
  20. package/src/lib/assistant-config.ts +53 -0
  21. package/src/lib/detached-process.ts +1 -1
  22. package/src/lib/ingress-config.ts +7 -34
  23. package/src/lib/local.ts +6 -27
  24. package/src/lib/nginx-ingress.ts +29 -8
  25. package/src/lib/{tunnel-edge.ts → tunnel.ts} +199 -11
  26. package/src/lib/tunnels/cadet.ts +29 -0
  27. package/src/lib/{cloudflare-tunnel.ts → tunnels/cloudflare.ts} +15 -4
  28. package/src/lib/tunnels/gateway-notify.ts +61 -0
  29. package/src/lib/{ngrok.ts → tunnels/ngrok.ts} +200 -29
  30. package/src/lib/{tailscale-tunnel.ts → tunnels/tailscale.ts} +19 -5
@@ -11,6 +11,7 @@ import {
11
11
  getDaemonPidPath,
12
12
  loadAllAssistants,
13
13
  lookupAssistantByIdentifier,
14
+ readRootProcesses,
14
15
  type AssistantEntry,
15
16
  } from "../lib/assistant-config";
16
17
  import { parseAssistantTargetArg } from "../lib/assistant-target-args.js";
@@ -44,14 +45,21 @@ import {
44
45
 
45
46
  // ── Table formatting helpers ────────────────────────────────────
46
47
 
48
+ /** What each row's `type` column says: the KIND of process the row describes. */
49
+ const ASSISTANT_TYPE = "🧑‍🚀 Assistant";
50
+ const TUNNEL_TYPE = "🚇 Tunnel";
51
+ const APP_TYPE = "🌐 App";
52
+
47
53
  interface TableRow {
48
54
  name: string;
55
+ type: string;
49
56
  status: string;
50
57
  info: string;
51
58
  }
52
59
 
53
60
  interface ColWidths {
54
61
  name: number;
62
+ type: number;
55
63
  status: number;
56
64
  info: number;
57
65
  }
@@ -62,27 +70,38 @@ function pad(s: string, w: number): string {
62
70
 
63
71
  function computeColWidths(
64
72
  rows: TableRow[],
65
- headers: TableRow = { name: "NAME", status: "STATUS", info: "INFO" },
73
+ headers: TableRow = {
74
+ name: "NAME",
75
+ type: "TYPE",
76
+ status: "STATUS",
77
+ info: "INFO",
78
+ },
66
79
  ): ColWidths {
67
80
  const all = [headers, ...rows];
68
81
  return {
69
82
  name: Math.max(...all.map((r) => r.name.length)),
83
+ type: Math.max(...all.map((r) => r.type.length)),
70
84
  status: Math.max(...all.map((r) => r.status.length), "checking...".length),
71
85
  info: Math.max(...all.map((r) => r.info.length)),
72
86
  };
73
87
  }
74
88
 
75
89
  function formatRow(r: TableRow, colWidths: ColWidths): string {
76
- return ` ${pad(r.name, colWidths.name)} ${pad(r.status, colWidths.status)} ${r.info}`;
90
+ return ` ${pad(r.name, colWidths.name)} ${pad(r.type, colWidths.type)} ${pad(r.status, colWidths.status)} ${r.info}`;
77
91
  }
78
92
 
79
93
  function printTable(
80
94
  rows: TableRow[],
81
- headers: TableRow = { name: "PROCESS", status: "STATUS", info: "INFO" },
95
+ headers: TableRow = {
96
+ name: "PROCESS",
97
+ type: "TYPE",
98
+ status: "STATUS",
99
+ info: "INFO",
100
+ },
82
101
  ): void {
83
102
  const colWidths = computeColWidths(rows, headers);
84
103
  console.log(formatRow(headers, colWidths));
85
- const sep = ` ${"-".repeat(colWidths.name)} ${"-".repeat(colWidths.status)} ${"-".repeat(colWidths.info)}`;
104
+ const sep = ` ${"-".repeat(colWidths.name)} ${"-".repeat(colWidths.type)} ${"-".repeat(colWidths.status)} ${"-".repeat(colWidths.info)}`;
86
105
  console.log(sep);
87
106
  for (const row of rows) {
88
107
  console.log(formatRow(row, colWidths));
@@ -108,6 +127,7 @@ function flattenProcessTree(
108
127
  rows.push({
109
128
  name:
110
129
  depth === 0 ? entry.name : `${" ".repeat(depth - 1)}├─ ${entry.name}`,
130
+ type: ASSISTANT_TYPE,
111
131
  status: withStatusEmoji(STATUS_LABELS[entry.status]),
112
132
  info: entry.info ?? "",
113
133
  });
@@ -118,6 +138,7 @@ function flattenProcessTree(
118
138
  const prefix = `${" ".repeat(depth)}${isLast ? "└─" : "├─"} ${child.name}`;
119
139
  rows.push({
120
140
  name: prefix,
141
+ type: ASSISTANT_TYPE,
121
142
  status: withStatusEmoji(STATUS_LABELS[child.status]),
122
143
  info: child.info ?? "",
123
144
  });
@@ -284,9 +305,7 @@ async function getLocalProcesses(entry: AssistantEntry): Promise<TableRow[]> {
284
305
  // tree (assistant + every child it owns, built from the OS process table)
285
306
  // with the gateway's own entry appended — no hard-coded subsystem list to
286
307
  // drift out of sync with what the assistant actually spawns.
287
- const tree = gatewayUrl
288
- ? await fetchGatewayPs(gatewayUrl, token)
289
- : null;
308
+ const tree = gatewayUrl ? await fetchGatewayPs(gatewayUrl, token) : null;
290
309
 
291
310
  const rows: TableRow[] =
292
311
  tree && tree.length > 0
@@ -303,6 +322,7 @@ async function getLocalProcesses(entry: AssistantEntry): Promise<TableRow[]> {
303
322
  });
304
323
  rows.push({
305
324
  name: "credential-executor",
325
+ type: ASSISTANT_TYPE,
306
326
  status: withStatusEmoji(ces.running ? "running" : "not running"),
307
327
  info: ces.running ? formatDetectionInfo(ces) : "not detected",
308
328
  });
@@ -349,6 +369,7 @@ async function detectLocalProcessesFallback(
349
369
 
350
370
  return results.map((proc, i) => ({
351
371
  name: allSpecs[i].name,
372
+ type: ASSISTANT_TYPE,
352
373
  status: withStatusEmoji(proc.running ? "running" : "not running"),
353
374
  info: proc.running ? formatDetectionInfo(proc) : "not detected",
354
375
  }));
@@ -393,12 +414,14 @@ async function getDockerProcesses(entry: AssistantEntry): Promise<TableRow[]> {
393
414
  if (!state) {
394
415
  return {
395
416
  name,
417
+ type: ASSISTANT_TYPE,
396
418
  status: withStatusEmoji("not found"),
397
419
  info: `container ${containerName}`,
398
420
  };
399
421
  }
400
422
  return {
401
423
  name,
424
+ type: ASSISTANT_TYPE,
402
425
  status: withStatusEmoji(state === "running" ? "running" : state),
403
426
  info: `container ${containerName}`,
404
427
  };
@@ -412,6 +435,7 @@ async function getDockerProcesses(entry: AssistantEntry): Promise<TableRow[]> {
412
435
  const alive = isLocalProcessAlive(watcherPid);
413
436
  results.push({
414
437
  name: "file-watcher",
438
+ type: ASSISTANT_TYPE,
415
439
  status: withStatusEmoji(alive ? "running" : "not running"),
416
440
  info: alive ? `PID ${watcherPid}` : "not detected",
417
441
  });
@@ -430,22 +454,20 @@ function locationFor(entry: AssistantEntry): string {
430
454
  return entry.runtimeUrl ?? "unknown — no runtime URL recorded";
431
455
  }
432
456
 
433
- async function showAssistantProcesses(entry: AssistantEntry): Promise<void> {
457
+ async function getAssistantProcessRows(
458
+ entry: AssistantEntry,
459
+ ): Promise<TableRow[]> {
434
460
  const cloud = entry.cloud;
435
461
 
436
462
  console.log(`Processes for ${formatAssistantReference(entry)} (${cloud}):`);
437
463
  console.log(`Location: ${locationFor(entry)}\n`);
438
464
 
439
465
  if (cloud === "local") {
440
- const rows = await getLocalProcesses(entry);
441
- printTable(rows);
442
- return;
466
+ return getLocalProcesses(entry);
443
467
  }
444
468
 
445
469
  if (cloud === "docker") {
446
- const rows = await getDockerProcesses(entry);
447
- printTable(rows);
448
- return;
470
+ return getDockerProcesses(entry);
449
471
  }
450
472
 
451
473
  if (isCloudHome(cloud)) {
@@ -454,20 +476,17 @@ async function showAssistantProcesses(entry: AssistantEntry): Promise<void> {
454
476
  const psData = await fetchManagedPs(entry.runtimeUrl, entry.assistantId);
455
477
 
456
478
  if (!psData) {
457
- const rows: TableRow[] = [
479
+ return [
458
480
  {
459
481
  name: "assistant",
482
+ type: ASSISTANT_TYPE,
460
483
  status: withStatusEmoji("unreachable"),
461
484
  info: "could not reach Cadet Cloud API — run `cadet login`",
462
485
  },
463
486
  ];
464
- printTable(rows);
465
- return;
466
487
  }
467
488
 
468
- const rows = flattenProcessTree(psData.processes);
469
- printTable(rows);
470
- return;
489
+ return flattenProcessTree(psData.processes);
471
490
  }
472
491
 
473
492
  if (cloud === "apple-container") {
@@ -476,6 +495,7 @@ async function showAssistantProcesses(entry: AssistantEntry): Promise<void> {
476
495
  const rows: TableRow[] = [
477
496
  {
478
497
  name: "container",
498
+ type: ASSISTANT_TYPE,
479
499
  status: withStatusEmoji(socketAlive ? "running" : "not running"),
480
500
  info: socketAlive
481
501
  ? `mgmt ${mgmtSocket}`
@@ -487,12 +507,12 @@ async function showAssistantProcesses(entry: AssistantEntry): Promise<void> {
487
507
  const health = await checkHealth(entry.runtimeUrl, token);
488
508
  rows.push({
489
509
  name: "gateway",
510
+ type: ASSISTANT_TYPE,
490
511
  status: withStatusEmoji(health.status),
491
512
  info: entry.runtimeUrl + (health.detail ? ` | ${health.detail}` : ""),
492
513
  });
493
514
  }
494
- printTable(rows);
495
- return;
515
+ return rows;
496
516
  }
497
517
 
498
518
  if (cloud === "paired") {
@@ -500,15 +520,14 @@ async function showAssistantProcesses(entry: AssistantEntry): Promise<void> {
500
520
  // list — probe the remote gateway's health over the bearer token instead.
501
521
  const token = loadGuardianToken(entry.assistantId)?.accessToken;
502
522
  const health = await checkHealth(entry.runtimeUrl, token);
503
- const rows: TableRow[] = [
523
+ return [
504
524
  {
505
525
  name: "gateway",
526
+ type: ASSISTANT_TYPE,
506
527
  status: withStatusEmoji(health.status),
507
528
  info: entry.runtimeUrl + (health.detail ? ` | ${health.detail}` : ""),
508
529
  },
509
530
  ];
510
- printTable(rows);
511
- return;
512
531
  }
513
532
 
514
533
  let output: string;
@@ -525,7 +544,7 @@ async function showAssistantProcesses(entry: AssistantEntry): Promise<void> {
525
544
  const msg = error instanceof Error ? error.message : String(error);
526
545
  if (msg.includes("timed out")) {
527
546
  console.warn(`Warning: remote process listing timed out — ${msg}`);
528
- return;
547
+ return [];
529
548
  }
530
549
  console.error(`Failed to list processes: ${msg}`);
531
550
  process.exit(1);
@@ -535,16 +554,93 @@ async function showAssistantProcesses(entry: AssistantEntry): Promise<void> {
535
554
 
536
555
  if (procs.length === 0) {
537
556
  console.log("No cadet processes found on the remote instance.");
538
- return;
557
+ return [];
539
558
  }
540
559
 
541
- const rows: TableRow[] = procs.map((p) => ({
560
+ return procs.map((p) => ({
542
561
  name: classifyProcess(p.command),
562
+ type: ASSISTANT_TYPE,
543
563
  status: withStatusEmoji("running"),
544
564
  info: `PID ${p.pid} | ${p.command.slice(0, 80)}`,
545
565
  }));
566
+ }
567
+
568
+ // ── Root-managed process rows (tunnel agent, app edge) ──────────
569
+
570
+ /** A CLI-managed process row, plus the assistant it fronts (when recorded). */
571
+ interface RootManagedRow {
572
+ row: TableRow;
573
+ assistantId?: string;
574
+ }
575
+
576
+ /**
577
+ * Rows for the CLI-managed processes recorded at the lockfile root: the
578
+ * `tunnel` agent and the `app` nginx edge. Pids are liveness-checked so a
579
+ * stale entry reads as not running rather than as a lie. Each row carries the
580
+ * assistant it fronts so the listing can nest it under that assistant.
581
+ */
582
+ async function getRootManagedRows(): Promise<RootManagedRow[]> {
583
+ const { tunnel, app } = readRootProcesses();
584
+ const rows: RootManagedRow[] = [];
585
+
586
+ if (tunnel) {
587
+ const alive = isLocalProcessAlive(tunnel.pid);
588
+ rows.push({
589
+ assistantId: tunnel.assistantId,
590
+ row: {
591
+ name: tunnel.provider ?? "unknown provider",
592
+ type: TUNNEL_TYPE,
593
+ status: withStatusEmoji(alive ? "running" : "not running"),
594
+ info: alive ? `PID ${tunnel.pid}` : `PID ${tunnel.pid} (exited)`,
595
+ },
596
+ });
597
+ }
598
+ if (app) {
599
+ const alive = isLocalProcessAlive(app.pid);
600
+ rows.push({
601
+ assistantId: app.assistantId,
602
+ row: {
603
+ name: "nginx edge",
604
+ type: APP_TYPE,
605
+ status: withStatusEmoji(alive ? "running" : "not running"),
606
+ info: alive ? `PID ${app.pid}` : `PID ${app.pid} (exited)`,
607
+ },
608
+ });
609
+ }
610
+ return rows;
611
+ }
612
+
613
+ /**
614
+ * Fold the CLI-managed process rows into ONE table with the assistant rows:
615
+ * a process fronts an assistant when it recorded that assistant's id, and
616
+ * those rows nest under it; anything unattributed (or fronting an assistant
617
+ * this environment no longer lists) renders at the bottom, machine-level.
618
+ */
619
+ async function assistantRowsWithRootProcesses(
620
+ assistants: AssistantEntry[],
621
+ healths: AssistantHealth[],
622
+ activeAssistantId: string | null,
623
+ ): Promise<TableRow[]> {
624
+ const nestedByAssistant = new Map<string, TableRow[]>();
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
+ }
546
636
 
547
- printTable(rows);
637
+ const rows: TableRow[] = [];
638
+ assistants.forEach((entry, i) => {
639
+ rows.push(formatAssistantListRow(entry, activeAssistantId, healths[i]!));
640
+ rows.push(...(nestedByAssistant.get(entry.assistantId) ?? []));
641
+ });
642
+ rows.push(...unattributed);
643
+ return rows;
548
644
  }
549
645
 
550
646
  // ── List all assistants (no arg) ────────────────────────────────
@@ -611,6 +707,7 @@ function formatAssistantListRow(
611
707
  const prefix = entry.assistantId === activeAssistantId ? "* " : " ";
612
708
  return {
613
709
  name: prefix + getAssistantDisplayName(entry),
710
+ type: ASSISTANT_TYPE,
614
711
  status: withStatusEmoji(health.status),
615
712
  info: infoParts.join(" | "),
616
713
  };
@@ -654,6 +751,7 @@ export async function listAllAssistants(verbose: boolean): Promise<void> {
654
751
  console.log("\nOrphaned processes detected:\n");
655
752
  const rows: TableRow[] = orphans.map((o) => ({
656
753
  name: o.name,
754
+ type: ASSISTANT_TYPE,
657
755
  status: withStatusEmoji("running"),
658
756
  info: `PID ${o.pid} (from ${o.source})`,
659
757
  }));
@@ -666,60 +764,71 @@ export async function listAllAssistants(verbose: boolean): Promise<void> {
666
764
  return;
667
765
  }
668
766
 
669
- const rows = await Promise.all(
670
- assistants.map(async (entry) =>
671
- formatAssistantListRow(
672
- entry,
673
- activeAssistantId,
674
- await getAssistantListHealth(entry),
675
- ),
676
- ),
767
+ const healths = await Promise.all(
768
+ assistants.map((entry) => getAssistantListHealth(entry)),
677
769
  );
678
770
 
679
- printTable(rows, { name: "NAME", status: "STATUS", info: "INFO" });
680
- console.log("");
771
+ printTable(
772
+ await assistantRowsWithRootProcesses(
773
+ assistants,
774
+ healths,
775
+ activeAssistantId,
776
+ ),
777
+ { name: "NAME", type: "TYPE", status: "STATUS", info: "INFO" },
778
+ );
681
779
  }
682
780
 
683
781
  // ── Entry point ─────────────────────────────────────────────────
684
782
 
685
783
  export const ps = Object.assign(
686
784
  async function ps(): Promise<void> {
687
- const args = process.argv.slice(3);
688
- if (args.includes("--help") || args.includes("-h")) {
689
- console.log("Usage: cadet ps [<name-or-id>] [--verbose]");
690
- console.log("");
691
- console.log(
692
- "List all assistants, or show processes for a specific assistant.",
693
- );
694
- console.log("");
695
- console.log("Arguments:");
696
- console.log(
697
- " <name-or-id> Show processes for the assistant display name or ID",
698
- );
699
- console.log("");
700
- console.log("Options:");
701
- console.log(
702
- " --verbose Show diagnostic logs (platform sync, auth issues)",
703
- );
704
- process.exit(0);
705
- }
785
+ const args = process.argv.slice(3);
786
+ if (args.includes("--help") || args.includes("-h")) {
787
+ console.log("Usage: cadet ps [<name-or-id>] [--verbose]");
788
+ console.log("");
789
+ console.log(
790
+ "List all assistants, or show processes for a specific assistant.",
791
+ );
792
+ console.log("");
793
+ console.log("Arguments:");
794
+ console.log(
795
+ " <name-or-id> Show processes for the assistant display name or ID",
796
+ );
797
+ console.log("");
798
+ console.log("Options:");
799
+ console.log(
800
+ " --verbose Show diagnostic logs (platform sync, auth issues)",
801
+ );
802
+ process.exit(0);
803
+ }
706
804
 
707
- const verbose = args.includes("--verbose");
708
- const assistantIdentifier = parseAssistantTargetArg(args);
805
+ const verbose = args.includes("--verbose");
806
+ const assistantIdentifier = parseAssistantTargetArg(args);
709
807
 
710
- if (!assistantIdentifier) {
711
- await listAllAssistants(verbose);
712
- return;
713
- }
808
+ if (!assistantIdentifier) {
809
+ await listAllAssistants(verbose);
810
+ return;
811
+ }
714
812
 
715
- const result = lookupAssistantByIdentifier(assistantIdentifier);
716
- if (result.status !== "found") {
717
- console.error(formatAssistantLookupError(assistantIdentifier, result));
718
- process.exit(1);
719
- }
813
+ const result = lookupAssistantByIdentifier(assistantIdentifier);
814
+ if (result.status !== "found") {
815
+ console.error(formatAssistantLookupError(assistantIdentifier, result));
816
+ process.exit(1);
817
+ }
720
818
 
721
- await showAssistantProcesses(result.entry);
722
- },
819
+ const rows = await getAssistantProcessRows(result.entry);
820
+ // The CLI-managed processes (root lockfile) that front THIS assistant ride
821
+ // the same table; ones fronting another assistant stay in the list view.
822
+ for (const managed of await getRootManagedRows()) {
823
+ if (
824
+ managed.assistantId &&
825
+ managed.assistantId !== result.entry.assistantId
826
+ ) {
827
+ continue;
828
+ }
829
+ rows.push(managed.row);
830
+ }
831
+ printTable(rows);
832
+ },
723
833
  { description: "List assistants (or processes for a specific assistant)" },
724
834
  );
725
-
@@ -25,7 +25,6 @@ import {
25
25
  authHeaders,
26
26
  authHeadersForKnownOrganization,
27
27
  getCloudUrl,
28
- readGatewayCredential,
29
28
  readCloudToken,
30
29
  } from "../lib/cloud-client.js";
31
30
  import { retireDocker, dockerResourceNames } from "../lib/docker.js";
@@ -51,12 +50,6 @@ interface RetireArgs {
51
50
  yes: boolean;
52
51
  }
53
52
 
54
- interface SelfHostedPlatformRegistration {
55
- cloudAssistantId: string;
56
- cloudBaseUrl: string | null;
57
- organizationId: string | null;
58
- }
59
-
60
53
  const CLOUD_TOKEN_ENV = "CADET_CLOUD_TOKEN";
61
54
  const LEGACY_CLOUD_TOKEN_ENV = "CADET_PLATFORM_TOKEN";
62
55
 
@@ -68,54 +61,11 @@ function readPlatformRetireToken(): string | null {
68
61
  return readCloudToken();
69
62
  }
70
63
 
71
- function platformOrigin(cloudUrl: string): string | null {
72
- try {
73
- return new URL(cloudUrl).origin;
74
- } catch {
75
- return null;
76
- }
77
- }
78
-
79
- function resolveTrustedSelfHostedCloudUrl(
80
- storedCloudUrl: string | null,
81
- ): string | null {
82
- const configuredCloudUrl = getCloudUrl();
83
- if (!storedCloudUrl) return configuredCloudUrl;
84
-
85
- const storedOrigin = platformOrigin(storedCloudUrl);
86
- const configuredOrigin = platformOrigin(configuredCloudUrl);
87
- if (storedOrigin && configuredOrigin && storedOrigin === configuredOrigin) {
88
- return configuredCloudUrl;
89
- }
90
-
91
- console.warn(
92
- "⚠️ Stored platform URL does not match the configured platform URL; skipping platform unregister.",
93
- );
94
- return null;
95
- }
96
-
97
64
  function nonEmptyString(value: string | null | undefined): string | null {
98
65
  const trimmed = value?.trim();
99
66
  return trimmed ? trimmed : null;
100
67
  }
101
68
 
102
- function lockfileSelfHostedPlatformRegistration(
103
- entry: AssistantEntry,
104
- ): SelfHostedPlatformRegistration | null {
105
- const cloudAssistantId = nonEmptyString(
106
- entry.cloudAssistantId ?? entry.cloudAssistantId,
107
- );
108
- if (!cloudAssistantId) return null;
109
-
110
- return {
111
- cloudAssistantId,
112
- cloudBaseUrl: nonEmptyString(entry.cloudBaseUrl ?? entry.platformBaseUrl),
113
- organizationId: nonEmptyString(
114
- entry.cloudOrganizationId ?? entry.platformOrganizationId,
115
- ),
116
- };
117
- }
118
-
119
69
  async function deleteCloudAssistant(
120
70
  assistantId: string,
121
71
  options: {
@@ -150,85 +100,23 @@ async function deleteCloudAssistant(
150
100
  }
151
101
  }
152
102
 
153
- async function readSelfHostedPlatformRegistration(
154
- entry: AssistantEntry,
155
- ): Promise<SelfHostedPlatformRegistration | null> {
156
- const fallback = lockfileSelfHostedPlatformRegistration(entry);
157
- const gatewayUrl = entry.localUrl ?? entry.runtimeUrl;
158
- const cloudRegistration = await readGatewayCredential(
159
- gatewayUrl,
160
- "cadet:platform_assistant_id",
161
- entry.bearerToken,
162
- );
163
- if (cloudRegistration.unreachable) {
164
- if (fallback) {
165
- console.warn(
166
- "\u26a0\ufe0f Could not read platform registration from the local assistant; using saved platform registration.",
167
- );
168
- return fallback;
169
- }
170
- console.warn(
171
- "\u26a0\ufe0f Could not read cloud registration from the local assistant and no saved cloud registration is available; skipping cloud unregister.",
172
- );
173
- return null;
174
- }
175
-
176
- const cloudAssistantId =
177
- nonEmptyString(cloudRegistration.value) ?? fallback?.cloudAssistantId;
178
- if (!cloudAssistantId) return null;
179
-
180
- const [cloudBaseUrl, organizationId] = await Promise.all([
181
- readGatewayCredential(
182
- gatewayUrl,
183
- "cadet:platform_base_url",
184
- entry.bearerToken,
185
- ),
186
- readGatewayCredential(
187
- gatewayUrl,
188
- "cadet:platform_organization_id",
189
- entry.bearerToken,
190
- ),
191
- ]);
192
-
193
- return {
194
- cloudAssistantId,
195
- cloudBaseUrl:
196
- nonEmptyString(cloudBaseUrl.value) ??
197
- fallback?.cloudBaseUrl ??
198
- null,
199
- organizationId:
200
- nonEmptyString(organizationId.value) ?? fallback?.organizationId ?? null,
201
- };
202
- }
203
-
204
103
  async function unregisterSelfHostedLocalPlatformRecord(
205
104
  entry: AssistantEntry,
206
105
  ): Promise<void> {
207
106
  const token = readPlatformRetireToken();
208
107
  if (!token) return;
209
108
 
210
- const registration = await readSelfHostedPlatformRegistration(entry);
211
- if (!registration) return;
212
-
213
- const cloudUrl = resolveTrustedSelfHostedCloudUrl(
214
- registration.cloudBaseUrl,
215
- );
216
- if (!cloudUrl) return;
217
- if (!registration.organizationId) {
218
- console.warn(
219
- "⚠️ Could not read platform organization ID from the local assistant; skipping platform unregister.",
220
- );
221
- return;
222
- }
109
+ const cloudAssistantId = nonEmptyString(entry.cloudAssistantId);
110
+ if (!cloudAssistantId) return;
223
111
 
224
- await deleteCloudAssistant(registration.cloudAssistantId, {
112
+ await deleteCloudAssistant(cloudAssistantId, {
225
113
  token,
226
- cloudUrl,
227
- organizationId: registration.organizationId,
114
+ cloudUrl: getCloudUrl(),
115
+ organizationId: nonEmptyString(entry.cloudOrganizationId) ?? undefined,
228
116
  label: "Platform self-hosted unregister",
229
- successMessage: "\u2705 Platform self-hosted registration unregistered.",
117
+ successMessage: "✅ Platform self-hosted registration unregistered.",
230
118
  alreadyGoneMessage:
231
- "\u2705 Platform self-hosted registration already unregistered (404).",
119
+ "✅ Platform self-hosted registration already unregistered (404).",
232
120
  });
233
121
  }
234
122
 
@@ -331,8 +219,8 @@ function printRetireTarget(entry: AssistantEntry, cloud: string): void {
331
219
  : {}),
332
220
  ...(entry.cloud === "docker"
333
221
  ? {
334
- container:
335
- dockerResourceNames(entry.assistantId).assistantContainer,
222
+ container: dockerResourceNames(entry.assistantId)
223
+ .assistantContainer,
336
224
  }
337
225
  : {}),
338
226
  },
@@ -373,28 +261,29 @@ function teeConsoleToLogFile(fd: number | "ignore"): void {
373
261
 
374
262
  export const retire = Object.assign(
375
263
  async function retire(): Promise<void> {
376
- if (process.env.CADET_DESKTOP_APP) {
377
- resetLogFile("retire.log");
378
- }
379
- const logFd = process.env.CADET_DESKTOP_APP
380
- ? openLogFile("retire.log")
381
- : "ignore";
382
- teeConsoleToLogFile(logFd);
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);
383
271
 
384
- try {
385
- await retireInner();
386
- } finally {
387
- closeLogFile(logFd);
388
- }
389
- },
272
+ try {
273
+ await retireInner();
274
+ } finally {
275
+ closeLogFile(logFd);
276
+ }
277
+ },
390
278
  { description: "Delete an assistant instance" },
391
279
  );
392
280
 
393
-
394
281
  async function retireInner(): Promise<void> {
395
282
  const args = process.argv.slice(3);
396
283
  if (args.includes("--help") || args.includes("-h")) {
397
- console.log("Usage: cadet retire [<name-or-id>] [--source <source>] [--yes]");
284
+ console.log(
285
+ "Usage: cadet retire [<name-or-id>] [--source <source>] [--yes]",
286
+ );
398
287
  console.log("");
399
288
  console.log("Delete an assistant instance and archive its data.");
400
289
  console.log(
@@ -422,7 +311,9 @@ async function retireInner(): Promise<void> {
422
311
  console.error(
423
312
  "Error: No assistant specified and no active assistant. Pass a name or ID, or pick one with `cadet use <name>`.",
424
313
  );
425
- console.error("Usage: cadet retire <name-or-id> [--source <source>] [--yes]");
314
+ console.error(
315
+ "Usage: cadet retire <name-or-id> [--source <source>] [--yes]",
316
+ );
426
317
  process.exit(1);
427
318
  }
428
319