@mycadet/cli 0.12.7 → 0.12.9-dev.202610131452.9acab64

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) 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 +6 -4
  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 +1 -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 +15 -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 +149 -85
  80. package/src/__tests__/{ps-platform-status.test.ts → ps-cloud-status.test.ts} +80 -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 +180 -211
  90. package/src/__tests__/tunnel.test.ts +490 -270
  91. package/src/__tests__/uninstall.test.ts +150 -9
  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 -78
  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 +27 -24
  104. package/src/commands/login.ts +22 -22
  105. package/src/commands/logs.ts +8 -8
  106. package/src/commands/pair.ts +140 -37
  107. package/src/commands/ps.ts +232 -86
  108. package/src/commands/restart.ts +30 -0
  109. package/src/commands/restore.ts +140 -22
  110. package/src/commands/retire.ts +48 -150
  111. package/src/commands/rollback.ts +20 -26
  112. package/src/commands/setup.ts +2 -2
  113. package/src/commands/sleep.ts +2 -2
  114. package/src/commands/teleport.ts +110 -129
  115. package/src/commands/terminal.ts +158 -18
  116. package/src/commands/tunnel.ts +156 -175
  117. package/src/commands/uninstall.ts +137 -54
  118. package/src/commands/unpair.ts +3 -3
  119. package/src/commands/upgrade.ts +30 -54
  120. package/src/commands/use.ts +1 -1
  121. package/src/commands/wake.ts +166 -50
  122. package/src/index.ts +18 -9
  123. package/src/lib/__tests__/{platform-client-signed-url.test.ts → cloud-client-signed-url.test.ts} +16 -16
  124. package/src/lib/__tests__/docker.test.ts +168 -20
  125. package/src/lib/__tests__/job-polling.test.ts +2 -2
  126. package/src/lib/__tests__/local-gateway-cli.test.ts +42 -0
  127. package/src/lib/__tests__/local-runtime-client.test.ts +4 -4
  128. package/src/lib/__tests__/runtime-url.test.ts +1 -1
  129. package/src/lib/assistant-api-key-resolution.ts +5 -5
  130. package/src/lib/assistant-client.ts +8 -8
  131. package/src/lib/assistant-config.ts +58 -12
  132. package/src/lib/{platform-client.ts → cloud-client.ts} +105 -99
  133. package/src/lib/cloud-login.ts +23 -24
  134. package/src/lib/{platform-releases.ts → cloud-releases.ts} +11 -9
  135. package/src/lib/cloud-values.ts +3 -0
  136. package/src/lib/color.ts +4 -0
  137. package/src/lib/config-utils.ts +1 -1
  138. package/src/lib/detached-process.ts +1 -1
  139. package/src/lib/docker.ts +300 -111
  140. package/src/lib/ensure-binary.ts +171 -0
  141. package/src/lib/environments/__tests__/paths.test.ts +29 -3
  142. package/src/lib/environments/__tests__/resolve.test.ts +26 -26
  143. package/src/lib/environments/paths.ts +16 -8
  144. package/src/lib/environments/resolve.ts +10 -12
  145. package/src/lib/flag-args.test.ts +1 -1
  146. package/src/lib/flag-args.ts +1 -1
  147. package/src/lib/guardian-token.ts +37 -5
  148. package/src/lib/hatch-local.ts +32 -29
  149. package/src/lib/hatch-next-steps.ts +5 -9
  150. package/src/lib/health-check.ts +6 -6
  151. package/src/lib/ingress-config.ts +8 -35
  152. package/src/lib/job-polling.ts +2 -2
  153. package/src/lib/live-voice/connection.ts +12 -12
  154. package/src/lib/live-voice/tunnel.test.ts +11 -11
  155. package/src/lib/live-voice/tunnel.ts +15 -15
  156. package/src/lib/local-runtime-client.ts +20 -20
  157. package/src/lib/local.ts +46 -34
  158. package/src/lib/nginx-ingress.ts +40 -15
  159. package/src/lib/pair.ts +1 -1
  160. package/src/lib/process.ts +1 -1
  161. package/src/lib/provider-secrets.ts +3 -3
  162. package/src/lib/retire-apple-container.ts +1 -1
  163. package/src/lib/runtime-url.ts +8 -8
  164. package/src/lib/statefulset.ts +2 -2
  165. package/src/lib/step-runner.ts +6 -1
  166. package/src/lib/sync-cloud-assistants.ts +19 -19
  167. package/src/lib/teleport-backup.ts +4 -4
  168. package/src/lib/terminal-client.ts +17 -17
  169. package/src/lib/terminal-session.ts +24 -24
  170. package/src/lib/{tunnel-edge.ts → tunnel.ts} +199 -11
  171. package/src/lib/tunnels/cadet.ts +29 -0
  172. package/src/lib/{cloudflare-tunnel.ts → tunnels/cloudflare.ts} +36 -15
  173. package/src/lib/tunnels/gateway-notify.ts +61 -0
  174. package/src/lib/{ngrok.ts → tunnels/ngrok.ts} +350 -89
  175. package/src/lib/{tailscale-tunnel.ts → tunnels/tailscale.ts} +53 -9
  176. package/src/lib/upgrade-lifecycle.ts +8 -21
  177. package/src/lib/upgrade-preflight.ts +2 -2
  178. package/src/lib/workos-pkce.ts +5 -5
  179. package/node_modules/@mycadet/assistant-api/events/platform-disconnected.ts +0 -21
  180. package/node_modules/@mycadet/assistant-api/events/show-platform-login.ts +0 -20
  181. package/src/commands/recover.ts +0 -131
  182. package/src/commands/roadmap.ts +0 -459
  183. package/src/commands/ssh.ts +0 -144
  184. package/src/commands/workflows.ts +0 -305
  185. 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,81 @@ 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) ──────────
618
+
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
+ }
546
661
 
547
- printTable(rows);
662
+ /**
663
+ * Fold the CLI-managed process rows into ONE table with the assistant rows.
664
+ * Tunnel agents and the nginx edge are root resources of the machine, not
665
+ * children of whichever assistant they front — especially as the web bundle
666
+ * splits into /app and /ui — so every CLI-managed row renders machine-level.
667
+ */
668
+ async function assistantRowsWithRootProcesses(
669
+ assistants: AssistantEntry[],
670
+ healths: AssistantHealth[],
671
+ activeAssistantId: string | null,
672
+ ): Promise<TableRow[]> {
673
+ const rootRows = (await getRootManagedRows()).map((managed) => managed.row);
674
+
675
+ const rows: TableRow[] = [];
676
+ assistants.forEach((entry, i) => {
677
+ rows.push(formatAssistantListRow(entry, activeAssistantId, healths[i]!));
678
+ });
679
+ rows.push(...rootRows);
680
+ return rows;
548
681
  }
549
682
 
550
683
  // ── List all assistants (no arg) ────────────────────────────────
@@ -590,7 +723,7 @@ async function getAssistantListHealth(
590
723
  : { status: "unknown", detail: "no runtime URL" };
591
724
  }
592
725
 
593
- if (isManagedCloud(entry.cloud)) {
726
+ if (isCloudHome(entry.cloud)) {
594
727
  return checkManagedHealth(entry.runtimeUrl, entry.assistantId);
595
728
  }
596
729
 
@@ -611,6 +744,7 @@ function formatAssistantListRow(
611
744
  const prefix = entry.assistantId === activeAssistantId ? "* " : " ";
612
745
  return {
613
746
  name: prefix + getAssistantDisplayName(entry),
747
+ type: ASSISTANT_TYPE,
614
748
  status: withStatusEmoji(health.status),
615
749
  info: infoParts.join(" | "),
616
750
  };
@@ -630,13 +764,13 @@ export async function listAllAssistants(verbose: boolean): Promise<void> {
630
764
 
631
765
  // Sync cloud assistants (when logged in) before listing. Login status has
632
766
  // its own command — `cadet whoami` — so ps stays silent about it.
633
- const platformToken = readPlatformToken();
634
- if (!platformToken) {
767
+ const cloudToken = readCloudToken();
768
+ if (!cloudToken) {
635
769
  log?.("No platform token found — skipping cloud sync");
636
770
  } else {
637
771
  // A failed sync (offline, expired token) just means cloud-managed
638
772
  // assistants may be stale — the local list still renders.
639
- await syncCloudAssistants(platformToken, { log });
773
+ await syncCloudAssistants(cloudToken, { log });
640
774
  }
641
775
  console.log("");
642
776
 
@@ -654,6 +788,7 @@ export async function listAllAssistants(verbose: boolean): Promise<void> {
654
788
  console.log("\nOrphaned processes detected:\n");
655
789
  const rows: TableRow[] = orphans.map((o) => ({
656
790
  name: o.name,
791
+ type: ASSISTANT_TYPE,
657
792
  status: withStatusEmoji("running"),
658
793
  info: `PID ${o.pid} (from ${o.source})`,
659
794
  }));
@@ -666,60 +801,71 @@ export async function listAllAssistants(verbose: boolean): Promise<void> {
666
801
  return;
667
802
  }
668
803
 
669
- const rows = await Promise.all(
670
- assistants.map(async (entry) =>
671
- formatAssistantListRow(
672
- entry,
673
- activeAssistantId,
674
- await getAssistantListHealth(entry),
675
- ),
676
- ),
804
+ const healths = await Promise.all(
805
+ assistants.map((entry) => getAssistantListHealth(entry)),
677
806
  );
678
807
 
679
- printTable(rows, { name: "NAME", status: "STATUS", info: "INFO" });
680
- console.log("");
808
+ printTable(
809
+ await assistantRowsWithRootProcesses(
810
+ assistants,
811
+ healths,
812
+ activeAssistantId,
813
+ ),
814
+ { name: "NAME", type: "TYPE", status: "STATUS", info: "INFO" },
815
+ );
681
816
  }
682
817
 
683
818
  // ── Entry point ─────────────────────────────────────────────────
684
819
 
685
820
  export const ps = Object.assign(
686
821
  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
- }
822
+ const args = process.argv.slice(3);
823
+ if (args.includes("--help") || args.includes("-h")) {
824
+ console.log("Usage: cadet ps [<name-or-id>] [--verbose]");
825
+ console.log("");
826
+ console.log(
827
+ "List all assistants, or show processes for a specific assistant.",
828
+ );
829
+ console.log("");
830
+ console.log("Arguments:");
831
+ console.log(
832
+ " <name-or-id> Show processes for the assistant display name or ID",
833
+ );
834
+ console.log("");
835
+ console.log("Options:");
836
+ console.log(
837
+ " --verbose Show diagnostic logs (platform sync, auth issues)",
838
+ );
839
+ process.exit(0);
840
+ }
706
841
 
707
- const verbose = args.includes("--verbose");
708
- const assistantIdentifier = parseAssistantTargetArg(args);
842
+ const verbose = args.includes("--verbose");
843
+ const assistantIdentifier = parseAssistantTargetArg(args);
709
844
 
710
- if (!assistantIdentifier) {
711
- await listAllAssistants(verbose);
712
- return;
713
- }
845
+ if (!assistantIdentifier) {
846
+ await listAllAssistants(verbose);
847
+ return;
848
+ }
714
849
 
715
- const result = lookupAssistantByIdentifier(assistantIdentifier);
716
- if (result.status !== "found") {
717
- console.error(formatAssistantLookupError(assistantIdentifier, result));
718
- process.exit(1);
719
- }
850
+ const result = lookupAssistantByIdentifier(assistantIdentifier);
851
+ if (result.status !== "found") {
852
+ console.error(formatAssistantLookupError(assistantIdentifier, result));
853
+ process.exit(1);
854
+ }
720
855
 
721
- await showAssistantProcesses(result.entry);
722
- },
723
- { description: "List assistants (or processes for a specific assistant)" },
856
+ const rows = await getAssistantProcessRows(result.entry);
857
+ // The CLI-managed processes (root lockfile) that front THIS assistant ride
858
+ // the same table; ones fronting another assistant stay in the list view.
859
+ for (const managed of await getRootManagedRows()) {
860
+ if (
861
+ managed.assistantId &&
862
+ managed.assistantId !== result.entry.assistantId
863
+ ) {
864
+ continue;
865
+ }
866
+ rows.push(managed.row);
867
+ }
868
+ printTable(rows);
869
+ },
870
+ { description: "List assistants" },
724
871
  );
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
+ );