@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.
Files changed (179) hide show
  1. package/README.md +2 -2
  2. package/knip.json +1 -1
  3. package/node_modules/@mycadet/assistant-api/events/cloud-disconnected.ts +21 -0
  4. package/node_modules/@mycadet/assistant-api/events/host-bash.ts +1 -1
  5. package/node_modules/@mycadet/assistant-api/events/host-cu.ts +1 -1
  6. package/node_modules/@mycadet/assistant-api/events/host-file.ts +1 -1
  7. package/node_modules/@mycadet/assistant-api/events/notification-intent.ts +1 -1
  8. package/node_modules/@mycadet/assistant-api/events/show-cloud-login.ts +18 -0
  9. package/node_modules/@mycadet/assistant-api/index.ts +11 -11
  10. package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/package.json +2 -2
  11. package/node_modules/@mycadet/{service-contracts/src/platform-credential.ts → assistant-api/node_modules/@mycadet/service-contracts/src/cloud-credential.ts} +9 -9
  12. package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
  13. package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
  14. package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
  15. package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
  16. package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
  17. package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
  18. package/node_modules/@mycadet/assistant-api/responses/home.ts +1 -1
  19. package/node_modules/@mycadet/assistant-api/surfaces.ts +4 -4
  20. package/node_modules/@mycadet/avatar-manifest/src/__tests__/notification-avatar.test.ts +1 -1
  21. package/node_modules/@mycadet/avatar-manifest/src/notification-avatar.ts +2 -2
  22. package/node_modules/@mycadet/environments/src/seeds.ts +6 -6
  23. package/node_modules/@mycadet/environments/src/shell.test.ts +1 -1
  24. package/node_modules/@mycadet/environments/src/types.ts +6 -6
  25. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/avatar-manifest/src/__tests__/notification-avatar.test.ts +1 -1
  26. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/avatar-manifest/src/notification-avatar.ts +2 -2
  27. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/environments/src/seeds.ts +6 -6
  28. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/environments/src/shell.test.ts +1 -1
  29. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/environments/src/types.ts +6 -6
  30. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/package.json +2 -2
  31. 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
  32. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
  33. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
  34. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
  35. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
  36. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
  37. package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
  38. package/node_modules/@mycadet/local-mode/src/__tests__/pair.test.ts +5 -5
  39. package/node_modules/@mycadet/local-mode/src/__tests__/retire.test.ts +1 -1
  40. package/node_modules/@mycadet/local-mode/src/config.ts +2 -2
  41. package/node_modules/@mycadet/local-mode/src/index.ts +1 -1
  42. package/node_modules/@mycadet/local-mode/src/lockfile-contract.test.ts +3 -3
  43. package/node_modules/@mycadet/local-mode/src/lockfile-contract.ts +10 -5
  44. package/node_modules/@mycadet/local-mode/src/lockfile.test.ts +15 -15
  45. package/node_modules/@mycadet/local-mode/src/lockfile.ts +9 -9
  46. package/node_modules/@mycadet/local-mode/src/pair.ts +2 -2
  47. package/node_modules/@mycadet/local-mode/src/retire.ts +3 -3
  48. package/node_modules/@mycadet/local-mode/src/teleport-backup-policy.ts +2 -2
  49. package/node_modules/@mycadet/local-mode/src/unpair.ts +2 -2
  50. package/node_modules/@mycadet/service-contracts/package.json +2 -2
  51. package/node_modules/@mycadet/{local-mode/node_modules/@mycadet/service-contracts/src/platform-credential.ts → service-contracts/src/cloud-credential.ts} +9 -9
  52. package/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
  53. package/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
  54. package/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
  55. package/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
  56. package/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
  57. package/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
  58. package/package.json +3 -2
  59. package/src/__tests__/assistant-api-key-resolution.test.ts +6 -6
  60. package/src/__tests__/{assistant-client-platform-auth.test.ts → assistant-client-cloud-auth.test.ts} +10 -10
  61. package/src/__tests__/assistant-client-refresh.test.ts +2 -2
  62. package/src/__tests__/backup.test.ts +86 -91
  63. package/src/__tests__/client-token.test.ts +1 -1
  64. package/src/__tests__/client-tui-refresh.test.ts +1 -1
  65. package/src/__tests__/client-web-config.test.ts +2 -2
  66. package/src/__tests__/{platform-client.test.ts → cloud-client.test.ts} +59 -59
  67. package/src/__tests__/{platform-releases.test.ts → cloud-releases.test.ts} +28 -18
  68. package/src/__tests__/{cloudflare-tunnel.test.ts → cloudflare.test.ts} +1 -1
  69. package/src/__tests__/connect-import.test.ts +55 -39
  70. package/src/__tests__/debug.test.ts +38 -38
  71. package/src/__tests__/ensure-binary.test.ts +182 -0
  72. package/src/__tests__/guardian-token.test.ts +149 -56
  73. package/src/__tests__/hatch-provider-secrets.test.ts +2 -2
  74. package/src/__tests__/helpers/guardian-token.ts +22 -0
  75. package/src/__tests__/helpers/http.ts +10 -0
  76. package/src/__tests__/nginx-ingress.test.ts +21 -20
  77. package/src/__tests__/ngrok-spawn.test.ts +49 -0
  78. package/src/__tests__/orphan-detection.test.ts +1 -1
  79. package/src/__tests__/pair.test.ts +1 -8
  80. package/src/__tests__/{ps-platform-status.test.ts → ps-cloud-status.test.ts} +79 -26
  81. package/src/__tests__/ps-table.test.ts +79 -0
  82. package/src/__tests__/{recover.test.ts → restore.test.ts} +29 -28
  83. package/src/__tests__/retire.test.ts +58 -199
  84. package/src/__tests__/root-process-lockfile.test.ts +99 -0
  85. package/src/__tests__/sleep.test.ts +19 -19
  86. package/src/__tests__/statefulset.test.ts +8 -8
  87. package/src/__tests__/{tailscale-tunnel.test.ts → tailscale.test.ts} +2 -2
  88. package/src/__tests__/teleport-backup.test.ts +3 -3
  89. package/src/__tests__/teleport.test.ts +181 -197
  90. package/src/__tests__/tunnel.test.ts +257 -222
  91. package/src/__tests__/uninstall.test.ts +13 -4
  92. package/src/__tests__/upgrade-local.test.ts +3 -3
  93. package/src/__tests__/upgrade-replay-env.test.ts +12 -12
  94. package/src/__tests__/wake.test.ts +260 -43
  95. package/src/__tests__/workos-pkce.test.ts +2 -2
  96. package/src/commands/backup.ts +24 -24
  97. package/src/commands/client.ts +76 -76
  98. package/src/commands/completions.test.ts +104 -0
  99. package/src/commands/completions.ts +87 -0
  100. package/src/commands/debug.ts +20 -20
  101. package/src/commands/exec.ts +12 -12
  102. package/src/commands/flags.ts +2 -2
  103. package/src/commands/hatch.ts +23 -23
  104. package/src/commands/login.ts +22 -22
  105. package/src/commands/logs.ts +8 -8
  106. package/src/commands/ps.ts +244 -86
  107. package/src/commands/restart.ts +30 -0
  108. package/src/commands/restore.ts +140 -22
  109. package/src/commands/retire.ts +48 -150
  110. package/src/commands/rollback.ts +20 -26
  111. package/src/commands/setup.ts +2 -2
  112. package/src/commands/sleep.ts +2 -2
  113. package/src/commands/teleport.ts +109 -109
  114. package/src/commands/terminal.ts +158 -18
  115. package/src/commands/tunnel.ts +138 -157
  116. package/src/commands/uninstall.ts +57 -9
  117. package/src/commands/unpair.ts +3 -3
  118. package/src/commands/upgrade.ts +30 -54
  119. package/src/commands/use.ts +1 -1
  120. package/src/commands/wake.ts +166 -50
  121. package/src/index.ts +18 -9
  122. package/src/lib/__tests__/{platform-client-signed-url.test.ts → cloud-client-signed-url.test.ts} +16 -16
  123. package/src/lib/__tests__/docker.test.ts +168 -20
  124. package/src/lib/__tests__/job-polling.test.ts +2 -2
  125. package/src/lib/__tests__/local-runtime-client.test.ts +4 -4
  126. package/src/lib/__tests__/runtime-url.test.ts +1 -1
  127. package/src/lib/assistant-api-key-resolution.ts +5 -5
  128. package/src/lib/assistant-client.ts +8 -8
  129. package/src/lib/assistant-config.ts +58 -12
  130. package/src/lib/{platform-client.ts → cloud-client.ts} +107 -98
  131. package/src/lib/cloud-login.ts +24 -24
  132. package/src/lib/{platform-releases.ts → cloud-releases.ts} +11 -9
  133. package/src/lib/cloud-values.ts +3 -0
  134. package/src/lib/config-utils.ts +1 -1
  135. package/src/lib/detached-process.ts +1 -1
  136. package/src/lib/docker.ts +260 -53
  137. package/src/lib/ensure-binary.ts +171 -0
  138. package/src/lib/environments/__tests__/paths.test.ts +2 -2
  139. package/src/lib/environments/__tests__/resolve.test.ts +26 -26
  140. package/src/lib/environments/resolve.ts +8 -8
  141. package/src/lib/flag-args.test.ts +1 -1
  142. package/src/lib/flag-args.ts +1 -1
  143. package/src/lib/guardian-token.ts +37 -5
  144. package/src/lib/hatch-local.ts +19 -23
  145. package/src/lib/health-check.ts +6 -6
  146. package/src/lib/ingress-config.ts +8 -35
  147. package/src/lib/job-polling.ts +2 -2
  148. package/src/lib/live-voice/connection.ts +12 -12
  149. package/src/lib/live-voice/tunnel.test.ts +11 -11
  150. package/src/lib/live-voice/tunnel.ts +15 -15
  151. package/src/lib/local-runtime-client.ts +20 -20
  152. package/src/lib/local.ts +10 -31
  153. package/src/lib/nginx-ingress.ts +42 -15
  154. package/src/lib/pair.ts +1 -1
  155. package/src/lib/process.ts +1 -1
  156. package/src/lib/provider-secrets.ts +3 -3
  157. package/src/lib/retire-apple-container.ts +1 -1
  158. package/src/lib/runtime-url.ts +8 -8
  159. package/src/lib/statefulset.ts +2 -2
  160. package/src/lib/sync-cloud-assistants.ts +19 -19
  161. package/src/lib/teleport-backup.ts +4 -4
  162. package/src/lib/terminal-client.ts +17 -17
  163. package/src/lib/terminal-session.ts +24 -24
  164. package/src/lib/{tunnel-edge.ts → tunnel.ts} +199 -11
  165. package/src/lib/tunnels/cadet.ts +29 -0
  166. package/src/lib/{cloudflare-tunnel.ts → tunnels/cloudflare.ts} +36 -15
  167. package/src/lib/tunnels/gateway-notify.ts +61 -0
  168. package/src/lib/{ngrok.ts → tunnels/ngrok.ts} +350 -89
  169. package/src/lib/{tailscale-tunnel.ts → tunnels/tailscale.ts} +53 -9
  170. package/src/lib/upgrade-lifecycle.ts +8 -21
  171. package/src/lib/upgrade-preflight.ts +2 -2
  172. package/src/lib/workos-pkce.ts +5 -5
  173. package/node_modules/@mycadet/assistant-api/events/platform-disconnected.ts +0 -21
  174. package/node_modules/@mycadet/assistant-api/events/show-platform-login.ts +0 -20
  175. package/src/commands/recover.ts +0 -131
  176. package/src/commands/roadmap.ts +0 -459
  177. package/src/commands/ssh.ts +0 -144
  178. package/src/commands/workflows.ts +0 -305
  179. package/src/lib/managed-cloud.ts +0 -3
@@ -1,4 +1,4 @@
1
- import { isManagedCloud } from "../lib/managed-cloud.js";
1
+ import { isCloudHome } from "../lib/cloud-values.js";
2
2
  import { join } from "path";
3
3
 
4
4
  import {
@@ -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";
@@ -23,7 +24,7 @@ import {
23
24
  fetchManagedPs,
24
25
  type ManagedProcessEntry,
25
26
  } from "../lib/health-check";
26
- import { readPlatformToken } from "../lib/platform-client";
27
+ import { readCloudToken } from "../lib/cloud-client";
27
28
  import { dockerResourceNames } from "../lib/docker";
28
29
  import { existsSync } from "fs";
29
30
  import {
@@ -44,45 +45,112 @@ 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
  }
58
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
+
59
113
  function pad(s: string, w: number): string {
60
- return s + " ".repeat(Math.max(0, w - s.length));
114
+ return s + " ".repeat(Math.max(0, w - displayWidth(s)));
61
115
  }
62
116
 
63
117
  function computeColWidths(
64
118
  rows: TableRow[],
65
- headers: TableRow = { name: "NAME", status: "STATUS", info: "INFO" },
119
+ headers: TableRow = {
120
+ name: "NAME",
121
+ type: "TYPE",
122
+ status: "STATUS",
123
+ info: "INFO",
124
+ },
66
125
  ): ColWidths {
67
126
  const all = [headers, ...rows];
68
127
  return {
69
- name: Math.max(...all.map((r) => r.name.length)),
70
- status: Math.max(...all.map((r) => r.status.length), "checking...".length),
71
- info: Math.max(...all.map((r) => r.info.length)),
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))),
72
135
  };
73
136
  }
74
137
 
75
- function formatRow(r: TableRow, colWidths: ColWidths): string {
76
- return ` ${pad(r.name, colWidths.name)} ${pad(r.status, colWidths.status)} ${r.info}`;
138
+ export function formatRow(r: TableRow, colWidths: ColWidths): string {
139
+ return ` ${pad(r.name, colWidths.name)} ${pad(r.type, colWidths.type)} ${pad(r.status, colWidths.status)} ${r.info}`;
77
140
  }
78
141
 
79
142
  function printTable(
80
143
  rows: TableRow[],
81
- headers: TableRow = { name: "PROCESS", status: "STATUS", info: "INFO" },
144
+ headers: TableRow = {
145
+ name: "PROCESS",
146
+ type: "TYPE",
147
+ status: "STATUS",
148
+ info: "INFO",
149
+ },
82
150
  ): void {
83
151
  const colWidths = computeColWidths(rows, headers);
84
152
  console.log(formatRow(headers, colWidths));
85
- const sep = ` ${"-".repeat(colWidths.name)} ${"-".repeat(colWidths.status)} ${"-".repeat(colWidths.info)}`;
153
+ const sep = ` ${"-".repeat(colWidths.name)} ${"-".repeat(colWidths.type)} ${"-".repeat(colWidths.status)} ${"-".repeat(colWidths.info)}`;
86
154
  console.log(sep);
87
155
  for (const row of rows) {
88
156
  console.log(formatRow(row, colWidths));
@@ -108,6 +176,7 @@ function flattenProcessTree(
108
176
  rows.push({
109
177
  name:
110
178
  depth === 0 ? entry.name : `${" ".repeat(depth - 1)}├─ ${entry.name}`,
179
+ type: ASSISTANT_TYPE,
111
180
  status: withStatusEmoji(STATUS_LABELS[entry.status]),
112
181
  info: entry.info ?? "",
113
182
  });
@@ -118,6 +187,7 @@ function flattenProcessTree(
118
187
  const prefix = `${" ".repeat(depth)}${isLast ? "└─" : "├─"} ${child.name}`;
119
188
  rows.push({
120
189
  name: prefix,
190
+ type: ASSISTANT_TYPE,
121
191
  status: withStatusEmoji(STATUS_LABELS[child.status]),
122
192
  info: child.info ?? "",
123
193
  });
@@ -284,9 +354,7 @@ async function getLocalProcesses(entry: AssistantEntry): Promise<TableRow[]> {
284
354
  // tree (assistant + every child it owns, built from the OS process table)
285
355
  // with the gateway's own entry appended — no hard-coded subsystem list to
286
356
  // drift out of sync with what the assistant actually spawns.
287
- const tree = gatewayUrl
288
- ? await fetchGatewayPs(gatewayUrl, token)
289
- : null;
357
+ const tree = gatewayUrl ? await fetchGatewayPs(gatewayUrl, token) : null;
290
358
 
291
359
  const rows: TableRow[] =
292
360
  tree && tree.length > 0
@@ -303,6 +371,7 @@ async function getLocalProcesses(entry: AssistantEntry): Promise<TableRow[]> {
303
371
  });
304
372
  rows.push({
305
373
  name: "credential-executor",
374
+ type: ASSISTANT_TYPE,
306
375
  status: withStatusEmoji(ces.running ? "running" : "not running"),
307
376
  info: ces.running ? formatDetectionInfo(ces) : "not detected",
308
377
  });
@@ -349,6 +418,7 @@ async function detectLocalProcessesFallback(
349
418
 
350
419
  return results.map((proc, i) => ({
351
420
  name: allSpecs[i].name,
421
+ type: ASSISTANT_TYPE,
352
422
  status: withStatusEmoji(proc.running ? "running" : "not running"),
353
423
  info: proc.running ? formatDetectionInfo(proc) : "not detected",
354
424
  }));
@@ -393,12 +463,14 @@ async function getDockerProcesses(entry: AssistantEntry): Promise<TableRow[]> {
393
463
  if (!state) {
394
464
  return {
395
465
  name,
466
+ type: ASSISTANT_TYPE,
396
467
  status: withStatusEmoji("not found"),
397
468
  info: `container ${containerName}`,
398
469
  };
399
470
  }
400
471
  return {
401
472
  name,
473
+ type: ASSISTANT_TYPE,
402
474
  status: withStatusEmoji(state === "running" ? "running" : state),
403
475
  info: `container ${containerName}`,
404
476
  };
@@ -412,6 +484,7 @@ async function getDockerProcesses(entry: AssistantEntry): Promise<TableRow[]> {
412
484
  const alive = isLocalProcessAlive(watcherPid);
413
485
  results.push({
414
486
  name: "file-watcher",
487
+ type: ASSISTANT_TYPE,
415
488
  status: withStatusEmoji(alive ? "running" : "not running"),
416
489
  info: alive ? `PID ${watcherPid}` : "not detected",
417
490
  });
@@ -430,44 +503,39 @@ function locationFor(entry: AssistantEntry): string {
430
503
  return entry.runtimeUrl ?? "unknown — no runtime URL recorded";
431
504
  }
432
505
 
433
- async function showAssistantProcesses(entry: AssistantEntry): Promise<void> {
506
+ async function getAssistantProcessRows(
507
+ entry: AssistantEntry,
508
+ ): Promise<TableRow[]> {
434
509
  const cloud = entry.cloud;
435
510
 
436
- console.log(`Processes for ${formatAssistantReference(entry)} (${cloud}):`);
511
+ console.log(`Processes for ${formatAssistantReference(entry)} — ${cloud}:`);
437
512
  console.log(`Location: ${locationFor(entry)}\n`);
438
513
 
439
514
  if (cloud === "local") {
440
- const rows = await getLocalProcesses(entry);
441
- printTable(rows);
442
- return;
515
+ return getLocalProcesses(entry);
443
516
  }
444
517
 
445
518
  if (cloud === "docker") {
446
- const rows = await getDockerProcesses(entry);
447
- printTable(rows);
448
- return;
519
+ return getDockerProcesses(entry);
449
520
  }
450
521
 
451
- if (isManagedCloud(cloud)) {
522
+ if (isCloudHome(cloud)) {
452
523
  console.log(` Cadet Cloud ID: ${entry.assistantId}\n`);
453
524
 
454
525
  const psData = await fetchManagedPs(entry.runtimeUrl, entry.assistantId);
455
526
 
456
527
  if (!psData) {
457
- const rows: TableRow[] = [
528
+ return [
458
529
  {
459
530
  name: "assistant",
531
+ type: ASSISTANT_TYPE,
460
532
  status: withStatusEmoji("unreachable"),
461
533
  info: "could not reach Cadet Cloud API — run `cadet login`",
462
534
  },
463
535
  ];
464
- printTable(rows);
465
- return;
466
536
  }
467
537
 
468
- const rows = flattenProcessTree(psData.processes);
469
- printTable(rows);
470
- return;
538
+ return flattenProcessTree(psData.processes);
471
539
  }
472
540
 
473
541
  if (cloud === "apple-container") {
@@ -476,6 +544,7 @@ async function showAssistantProcesses(entry: AssistantEntry): Promise<void> {
476
544
  const rows: TableRow[] = [
477
545
  {
478
546
  name: "container",
547
+ type: ASSISTANT_TYPE,
479
548
  status: withStatusEmoji(socketAlive ? "running" : "not running"),
480
549
  info: socketAlive
481
550
  ? `mgmt ${mgmtSocket}`
@@ -487,12 +556,12 @@ async function showAssistantProcesses(entry: AssistantEntry): Promise<void> {
487
556
  const health = await checkHealth(entry.runtimeUrl, token);
488
557
  rows.push({
489
558
  name: "gateway",
559
+ type: ASSISTANT_TYPE,
490
560
  status: withStatusEmoji(health.status),
491
561
  info: entry.runtimeUrl + (health.detail ? ` | ${health.detail}` : ""),
492
562
  });
493
563
  }
494
- printTable(rows);
495
- return;
564
+ return rows;
496
565
  }
497
566
 
498
567
  if (cloud === "paired") {
@@ -500,15 +569,14 @@ async function showAssistantProcesses(entry: AssistantEntry): Promise<void> {
500
569
  // list — probe the remote gateway's health over the bearer token instead.
501
570
  const token = loadGuardianToken(entry.assistantId)?.accessToken;
502
571
  const health = await checkHealth(entry.runtimeUrl, token);
503
- const rows: TableRow[] = [
572
+ return [
504
573
  {
505
574
  name: "gateway",
575
+ type: ASSISTANT_TYPE,
506
576
  status: withStatusEmoji(health.status),
507
577
  info: entry.runtimeUrl + (health.detail ? ` | ${health.detail}` : ""),
508
578
  },
509
579
  ];
510
- printTable(rows);
511
- return;
512
580
  }
513
581
 
514
582
  let output: string;
@@ -525,7 +593,7 @@ async function showAssistantProcesses(entry: AssistantEntry): Promise<void> {
525
593
  const msg = error instanceof Error ? error.message : String(error);
526
594
  if (msg.includes("timed out")) {
527
595
  console.warn(`Warning: remote process listing timed out — ${msg}`);
528
- return;
596
+ return [];
529
597
  }
530
598
  console.error(`Failed to list processes: ${msg}`);
531
599
  process.exit(1);
@@ -535,16 +603,93 @@ async function showAssistantProcesses(entry: AssistantEntry): Promise<void> {
535
603
 
536
604
  if (procs.length === 0) {
537
605
  console.log("No cadet processes found on the remote instance.");
538
- return;
606
+ return [];
539
607
  }
540
608
 
541
- const rows: TableRow[] = procs.map((p) => ({
609
+ return procs.map((p) => ({
542
610
  name: classifyProcess(p.command),
611
+ type: ASSISTANT_TYPE,
543
612
  status: withStatusEmoji("running"),
544
613
  info: `PID ${p.pid} | ${p.command.slice(0, 80)}`,
545
614
  }));
615
+ }
616
+
617
+ // ── Root-managed process rows (tunnel agent, app edge) ──────────
546
618
 
547
- printTable(rows);
619
+ /** A CLI-managed process row, plus the assistant it fronts (when recorded). */
620
+ interface RootManagedRow {
621
+ row: TableRow;
622
+ assistantId?: string;
623
+ }
624
+
625
+ /**
626
+ * Rows for the CLI-managed processes recorded at the lockfile root: the
627
+ * `tunnel` agent and the `app` nginx edge. Pids are liveness-checked so a
628
+ * stale entry reads as not running rather than as a lie. Each row carries the
629
+ * assistant it fronts so the listing can nest it under that assistant.
630
+ */
631
+ async function getRootManagedRows(): Promise<RootManagedRow[]> {
632
+ const { tunnel, app } = readRootProcesses();
633
+ const rows: RootManagedRow[] = [];
634
+
635
+ if (tunnel) {
636
+ const alive = isLocalProcessAlive(tunnel.pid);
637
+ rows.push({
638
+ assistantId: tunnel.assistantId,
639
+ row: {
640
+ name: tunnel.provider ?? "unknown provider",
641
+ type: TUNNEL_TYPE,
642
+ status: withStatusEmoji(alive ? "running" : "not running"),
643
+ info: alive ? `PID ${tunnel.pid}` : `PID ${tunnel.pid} (exited)`,
644
+ },
645
+ });
646
+ }
647
+ if (app) {
648
+ const alive = isLocalProcessAlive(app.pid);
649
+ rows.push({
650
+ assistantId: app.assistantId,
651
+ row: {
652
+ name: "nginx edge",
653
+ type: APP_TYPE,
654
+ status: withStatusEmoji(alive ? "running" : "not running"),
655
+ info: alive ? `PID ${app.pid}` : `PID ${app.pid} (exited)`,
656
+ },
657
+ });
658
+ }
659
+ return rows;
660
+ }
661
+
662
+ /**
663
+ * Fold the CLI-managed process rows into ONE table with the assistant rows:
664
+ * a process fronts an assistant when it recorded that assistant's id, and
665
+ * those rows nest under it; anything unattributed (or fronting an assistant
666
+ * this environment no longer lists) renders at the bottom, machine-level.
667
+ */
668
+ async function assistantRowsWithRootProcesses(
669
+ assistants: AssistantEntry[],
670
+ healths: AssistantHealth[],
671
+ activeAssistantId: string | null,
672
+ ): Promise<TableRow[]> {
673
+ const nestedByAssistant = new Map<string, TableRow[]>();
674
+ const unattributed: TableRow[] = [];
675
+ const knownIds = new Set(assistants.map((entry) => entry.assistantId));
676
+ for (const managed of await getRootManagedRows()) {
677
+ if (managed.assistantId && knownIds.has(managed.assistantId)) {
678
+ const nested = nestedByAssistant.get(managed.assistantId) ?? [];
679
+ nested.push({ ...managed.row, name: ` └ ${managed.row.name}` });
680
+ nestedByAssistant.set(managed.assistantId, nested);
681
+ } else {
682
+ unattributed.push(managed.row);
683
+ }
684
+ }
685
+
686
+ const rows: TableRow[] = [];
687
+ assistants.forEach((entry, i) => {
688
+ rows.push(formatAssistantListRow(entry, activeAssistantId, healths[i]!));
689
+ rows.push(...(nestedByAssistant.get(entry.assistantId) ?? []));
690
+ });
691
+ rows.push(...unattributed);
692
+ return rows;
548
693
  }
549
694
 
550
695
  // ── List all assistants (no arg) ────────────────────────────────
@@ -590,7 +735,7 @@ async function getAssistantListHealth(
590
735
  : { status: "unknown", detail: "no runtime URL" };
591
736
  }
592
737
 
593
- if (isManagedCloud(entry.cloud)) {
738
+ if (isCloudHome(entry.cloud)) {
594
739
  return checkManagedHealth(entry.runtimeUrl, entry.assistantId);
595
740
  }
596
741
 
@@ -611,6 +756,7 @@ function formatAssistantListRow(
611
756
  const prefix = entry.assistantId === activeAssistantId ? "* " : " ";
612
757
  return {
613
758
  name: prefix + getAssistantDisplayName(entry),
759
+ type: ASSISTANT_TYPE,
614
760
  status: withStatusEmoji(health.status),
615
761
  info: infoParts.join(" | "),
616
762
  };
@@ -630,13 +776,13 @@ export async function listAllAssistants(verbose: boolean): Promise<void> {
630
776
 
631
777
  // Sync cloud assistants (when logged in) before listing. Login status has
632
778
  // its own command — `cadet whoami` — so ps stays silent about it.
633
- const platformToken = readPlatformToken();
634
- if (!platformToken) {
779
+ const cloudToken = readCloudToken();
780
+ if (!cloudToken) {
635
781
  log?.("No platform token found — skipping cloud sync");
636
782
  } else {
637
783
  // A failed sync (offline, expired token) just means cloud-managed
638
784
  // assistants may be stale — the local list still renders.
639
- await syncCloudAssistants(platformToken, { log });
785
+ await syncCloudAssistants(cloudToken, { log });
640
786
  }
641
787
  console.log("");
642
788
 
@@ -654,6 +800,7 @@ export async function listAllAssistants(verbose: boolean): Promise<void> {
654
800
  console.log("\nOrphaned processes detected:\n");
655
801
  const rows: TableRow[] = orphans.map((o) => ({
656
802
  name: o.name,
803
+ type: ASSISTANT_TYPE,
657
804
  status: withStatusEmoji("running"),
658
805
  info: `PID ${o.pid} (from ${o.source})`,
659
806
  }));
@@ -666,60 +813,71 @@ export async function listAllAssistants(verbose: boolean): Promise<void> {
666
813
  return;
667
814
  }
668
815
 
669
- const rows = await Promise.all(
670
- assistants.map(async (entry) =>
671
- formatAssistantListRow(
672
- entry,
673
- activeAssistantId,
674
- await getAssistantListHealth(entry),
675
- ),
676
- ),
816
+ const healths = await Promise.all(
817
+ assistants.map((entry) => getAssistantListHealth(entry)),
677
818
  );
678
819
 
679
- printTable(rows, { name: "NAME", status: "STATUS", info: "INFO" });
680
- console.log("");
820
+ printTable(
821
+ await assistantRowsWithRootProcesses(
822
+ assistants,
823
+ healths,
824
+ activeAssistantId,
825
+ ),
826
+ { name: "NAME", type: "TYPE", status: "STATUS", info: "INFO" },
827
+ );
681
828
  }
682
829
 
683
830
  // ── Entry point ─────────────────────────────────────────────────
684
831
 
685
832
  export const ps = Object.assign(
686
833
  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
- }
834
+ const args = process.argv.slice(3);
835
+ if (args.includes("--help") || args.includes("-h")) {
836
+ console.log("Usage: cadet ps [<name-or-id>] [--verbose]");
837
+ console.log("");
838
+ console.log(
839
+ "List all assistants, or show processes for a specific assistant.",
840
+ );
841
+ console.log("");
842
+ console.log("Arguments:");
843
+ console.log(
844
+ " <name-or-id> Show processes for the assistant display name or ID",
845
+ );
846
+ console.log("");
847
+ console.log("Options:");
848
+ console.log(
849
+ " --verbose Show diagnostic logs (platform sync, auth issues)",
850
+ );
851
+ process.exit(0);
852
+ }
706
853
 
707
- const verbose = args.includes("--verbose");
708
- const assistantIdentifier = parseAssistantTargetArg(args);
854
+ const verbose = args.includes("--verbose");
855
+ const assistantIdentifier = parseAssistantTargetArg(args);
709
856
 
710
- if (!assistantIdentifier) {
711
- await listAllAssistants(verbose);
712
- return;
713
- }
857
+ if (!assistantIdentifier) {
858
+ await listAllAssistants(verbose);
859
+ return;
860
+ }
714
861
 
715
- const result = lookupAssistantByIdentifier(assistantIdentifier);
716
- if (result.status !== "found") {
717
- console.error(formatAssistantLookupError(assistantIdentifier, result));
718
- process.exit(1);
719
- }
862
+ const result = lookupAssistantByIdentifier(assistantIdentifier);
863
+ if (result.status !== "found") {
864
+ console.error(formatAssistantLookupError(assistantIdentifier, result));
865
+ process.exit(1);
866
+ }
720
867
 
721
- await showAssistantProcesses(result.entry);
722
- },
723
- { description: "List assistants (or processes for a specific assistant)" },
868
+ const rows = await getAssistantProcessRows(result.entry);
869
+ // The CLI-managed processes (root lockfile) that front THIS assistant ride
870
+ // the same table; ones fronting another assistant stay in the list view.
871
+ for (const managed of await getRootManagedRows()) {
872
+ if (
873
+ managed.assistantId &&
874
+ managed.assistantId !== result.entry.assistantId
875
+ ) {
876
+ continue;
877
+ }
878
+ rows.push(managed.row);
879
+ }
880
+ printTable(rows);
881
+ },
882
+ { description: "List assistants" },
724
883
  );
725
-
@@ -0,0 +1,30 @@
1
+ import { sleep } from "./sleep.js";
2
+ import { wake } from "./wake.js";
3
+
4
+ export const restart = Object.assign(
5
+ async function restart(): Promise<void> {
6
+ const args = process.argv.slice(3);
7
+ if (args.includes("--help") || args.includes("-h")) {
8
+ console.log(
9
+ "Usage: cadet restart [<name> | --all] [--force] [--wait [<duration>]] [--repair-guardian]",
10
+ );
11
+ console.log("");
12
+ console.log("Sleep, then wake the assistant.");
13
+ console.log("");
14
+ console.log(
15
+ "Arguments and flags pass through to `cadet sleep` and `cadet wake`.",
16
+ );
17
+ console.log(
18
+ " --repair-guardian Re-provision the guardian token during the wake",
19
+ );
20
+ process.exit(0);
21
+ }
22
+ if (args.includes("--all") && args.includes("--repair-guardian")) {
23
+ console.error("Error: --all cannot be combined with --repair-guardian.");
24
+ process.exit(1);
25
+ }
26
+ await sleep();
27
+ await wake();
28
+ },
29
+ { description: "Sleep, then wake the assistant" },
30
+ );