@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,19 +1,46 @@
1
1
  import { existsSync, readFileSync, renameSync, rmSync, unlinkSync } from "fs";
2
- import { basename, dirname, join } from "path";
2
+ import { basename, dirname, join, sep } from "path";
3
3
  import { fileURLToPath } from "url";
4
4
 
5
- import { loadAllAssistants } from "../lib/assistant-config.js";
5
+ import {
6
+ getDaemonPidPath,
7
+ loadAllAssistants,
8
+ type AssistantEntry,
9
+ } from "../lib/assistant-config.js";
6
10
  import {
7
11
  canPromptForConfirmation,
8
12
  confirmAction,
9
13
  } from "../lib/confirm-action.js";
14
+ import { retireDocker } from "../lib/docker.js";
10
15
  import {
11
16
  getConfigDirs,
12
- getInputHistoryPath,
13
17
  getLogDir,
18
+ getSharedStateDir,
14
19
  getStateDir,
15
20
  } from "../lib/environments/paths.js";
16
21
  import { getCurrentEnvironment } from "../lib/environments/resolve.js";
22
+ import { stopProcessByPidFile } from "../lib/process";
23
+
24
+ /**
25
+ * The per-assistant teardown uninstall performs before deleting the data:
26
+ * docker assistants keep containers, volumes, and a network in docker that
27
+ * the filesystem sweep never reaches, and local assistants keep processes.
28
+ * Everything on-disk is deleted by the sweep regardless — this is the part
29
+ * uninstall could NOT do before it refused.
30
+ */
31
+ async function retireAssistantData(entry: AssistantEntry): Promise<void> {
32
+ if (entry.cloud === "docker") {
33
+ await retireDocker(entry.assistantId);
34
+ return;
35
+ }
36
+ const resources = entry.resources;
37
+ if (!resources) return;
38
+ await stopProcessByPidFile(
39
+ join(resources.instanceDir, "gateway.pid"),
40
+ "gateway",
41
+ );
42
+ await stopProcessByPidFile(getDaemonPidPath(resources), "assistant");
43
+ }
17
44
 
18
45
  /** A directory (or file) scheduled for deletion, with what it holds. */
19
46
  interface DeleteTarget {
@@ -71,62 +98,84 @@ function findInstalledPackageRoot(startDir: string): string | null {
71
98
  return null;
72
99
  }
73
100
 
101
+ /** The CLI's own install location, when it can be deleted. */
102
+ interface SelfDeleteTarget {
103
+ path: string;
104
+ description: string;
105
+ }
106
+
74
107
  /**
75
- * Remove the running CLI. Handles the two real install shapes:
76
- * - the compiled `cadet` binary (process.execPath IS the install)
77
- * - a global package install (bun install -g mycadet / npm install -g)
78
- * Under a source checkout or a bare runtime (bun/node), nothing is deleted —
79
- * the runtime binary must never be a deletion target.
108
+ * Where this CLI is running from, when that is an INSTALL we can remove:
109
+ * the compiled `cadet` binary (process.execPath IS the install) or a global
110
+ * package install (bun install -g mycadet / npm install -g mycadet). Under
111
+ * a source checkout or a bare runtime (bun/node), null — the runtime binary
112
+ * must never be a deletion target. The uninstall prompt lists the same
113
+ * target it later deletes, so both share this resolver.
80
114
  */
81
- function selfDelete(): void {
115
+ function resolveSelfDeleteTarget(): SelfDeleteTarget | null {
82
116
  const execPath = process.execPath;
83
117
  const execBase = basename(execPath);
84
118
  if (execBase === "cadet" || execBase === "cadet.exe") {
119
+ return { path: execPath, description: "the Cadet executable" };
120
+ }
121
+ const runningFile = fileURLToPath(import.meta.url);
122
+ const pkgRoot = findInstalledPackageRoot(dirname(runningFile));
123
+ if (pkgRoot) {
124
+ return { path: pkgRoot, description: "the installed Cadet CLI package" };
125
+ }
126
+ return null;
127
+ }
128
+
129
+ /**
130
+ * Delete the install {@link resolveSelfDeleteTarget} found. The two shapes
131
+ * delete differently — the running binary needs the win32 rename dance, a
132
+ * package dir is a plain recursive delete — and the bin symlinks the
133
+ * package manager planted (e.g. ~/.bun/bin/cadet, the npm global bin) are
134
+ * left dangling — say so.
135
+ */
136
+ function selfDelete(): void {
137
+ const target = resolveSelfDeleteTarget();
138
+ if (!target) {
139
+ console.log(
140
+ "Cadet is running from a source checkout — skipping self-removal.",
141
+ );
142
+ console.log(
143
+ "Remove the installed copy with `bun remove -g mycadet` or `npm uninstall -g mycadet`.",
144
+ );
145
+ return;
146
+ }
147
+ const execBase = basename(target.path);
148
+ const isCompiledBinary = execBase === "cadet" || execBase === "cadet.exe";
149
+ if (isCompiledBinary) {
85
150
  try {
86
151
  if (process.platform === "win32") {
87
152
  // Windows refuses to unlink a running executable — stage it under a
88
153
  // junk name and delete that.
89
- const stale = `${execPath}.uninstalled`;
90
- renameSync(execPath, stale);
154
+ const stale = `${target.path}.uninstalled`;
155
+ renameSync(target.path, stale);
91
156
  rmSync(stale, { force: true, recursive: true });
92
157
  } else {
93
- unlinkSync(execPath);
158
+ unlinkSync(target.path);
94
159
  }
95
- console.log(`Removed ${execPath}.`);
96
- return;
160
+ console.log(`Removed ${target.path}.`);
97
161
  } catch (err) {
98
162
  console.warn(
99
- `⚠️ Could not remove ${execPath}: ${
163
+ `⚠️ Could not remove ${target.path}: ${
100
164
  err instanceof Error ? err.message : String(err)
101
165
  } — remove it by hand.`,
102
166
  );
103
- return;
104
167
  }
105
- }
106
-
107
- // Package-manager install: remove the package directory we are running
108
- // from, best-effort. Bin symlinks the package manager planted (e.g.
109
- // ~/.bun/bin/cadet, the npm global bin) are left dangling — say so.
110
- const runningFile = fileURLToPath(import.meta.url);
111
- const pkgRoot = findInstalledPackageRoot(dirname(runningFile));
112
- if (!pkgRoot) {
113
- console.log(
114
- "Cadet is running from a source checkout — skipping self-removal.",
115
- );
116
- console.log(
117
- "Remove the installed copy with `bun remove -g mycadet` (or `npm uninstall -g mycadet`).",
118
- );
119
168
  return;
120
169
  }
121
170
  try {
122
- rmSync(pkgRoot, { recursive: true, force: true });
123
- console.log(`Removed ${pkgRoot}.`);
171
+ rmSync(target.path, { recursive: true, force: true });
172
+ console.log(`Removed ${target.path}.`);
124
173
  console.log(
125
174
  "The `cadet` command on your PATH is now stale — remove it if your package manager didn't (`bun remove -g mycadet` or `npm uninstall -g mycadet`).",
126
175
  );
127
176
  } catch (err) {
128
177
  console.warn(
129
- `⚠️ Could not remove ${pkgRoot}: ${
178
+ `⚠️ Could not remove ${target.path}: ${
130
179
  err instanceof Error ? err.message : String(err)
131
180
  } — remove it with \`bun remove -g mycadet\` (or \`npm uninstall -g mycadet\`).`,
132
181
  );
@@ -146,10 +195,24 @@ function collectDeleteTargets(env = getCurrentEnvironment()): DeleteTarget[] {
146
195
  { path: getLogDir(env), description: "logs" },
147
196
  // Not environment-scoped: deleting it affects every cadet CLI on the
148
197
  // machine — called out in the prompt, not hidden.
149
- { path: getInputHistoryPath(), description: "shared terminal input history" },
198
+ {
199
+ path: getSharedStateDir(),
200
+ description: "shared state, including the terminal input history",
201
+ },
150
202
  ];
151
203
  // Only targets that exist — the prompt lists what will actually go.
152
- return targets.filter((t) => existsSync(t.path));
204
+ const existing = targets.filter((t) => existsSync(t.path));
205
+ // The log dir lives INSIDE a listed target (the config dir on
206
+ // macOS/Linux, the state dir on Windows) and goes with its parent's
207
+ // recursive delete — never list a path twice.
208
+ const deduped: DeleteTarget[] = [];
209
+ for (const t of existing) {
210
+ const covered = deduped.some(
211
+ (k) => t.path === k.path || t.path.startsWith(k.path + sep),
212
+ );
213
+ if (!covered) deduped.push(t);
214
+ }
215
+ return deduped;
153
216
  }
154
217
 
155
218
  function rmrf(target: string): void {
@@ -163,42 +226,48 @@ export const uninstall = Object.assign(
163
226
  console.log("Usage: cadet uninstall [--yes]");
164
227
  console.log("");
165
228
  console.log(
166
- "Remove Cadet entirely: delete every location it uses for this environment (assistant state, retired archives, config, logs) and then delete the executable itself.",
229
+ "Remove Cadet entirely: retire any remaining assistants, delete every location it uses for this environment (assistant state, retired archives, config, logs), and remove the Cadet installation itself.",
167
230
  );
168
231
  process.exit(0);
169
232
  }
170
233
  const yes = args.includes("--yes");
171
234
 
172
- // Retire tears down an assistant AND archives its data. Uninstall assumes
173
- // an empty rack: retiring first is the way to keep an archive.
174
- if (loadAllAssistants().length > 0) {
175
- console.error(
176
- "Error: You still have assistants. Retire them first (`cadet retire <name>`) — retiring archives their data before removing it.",
177
- );
178
- process.exit(1);
179
- }
180
-
181
235
  const env = getCurrentEnvironment();
182
236
  const envLabel = env.name ?? "production";
237
+ const existing = loadAllAssistants();
183
238
  const targets = collectDeleteTargets(env);
184
- const execPath = process.execPath;
239
+ const selfTarget = resolveSelfDeleteTarget();
185
240
 
186
241
  console.log(
187
242
  `This removes Cadet entirely for the '${envLabel}' environment and cannot be undone.`,
188
243
  );
189
- if (targets.length === 0) {
244
+
245
+ // Assistants still on the rack are retired — with their data deleted —
246
+ // as part of the uninstall, and retiring happens only AFTER the
247
+ // confirmation below: it is part of what the prompt describes, never a
248
+ // side effect of running the command. Uninstall deletes every location
249
+ // cadet uses INCLUDING the retired archives, so retiring first (which
250
+ // archives) only matters for the teardown it performs: docker
251
+ // assistants keep containers, volumes, and a network in docker that
252
+ // the filesystem sweep below never reaches. A warning, not a refusal
253
+ // (v0.0.10 run feedback: the hard error just blocked the uninstall).
254
+ if (existing.length > 0) {
255
+ console.warn(
256
+ `\n⚠️ ${existing.length} ${existing.length === 1 ? "assistant still exists" : "assistants still exist"}: ${existing.map((a) => a.assistantId).join(", ")}`,
257
+ );
258
+ console.warn(
259
+ " They will be retired as part of this uninstall — their data (and any retired archives) will be permanently deleted.",
260
+ );
261
+ }
262
+
263
+ if (targets.length === 0 && selfTarget === null) {
190
264
  console.log("No Cadet data directories found to delete.");
191
265
  } else {
192
266
  console.log("It will permanently delete:");
193
- for (const t of targets) {
194
- console.log(` - ${t.path} (${t.description})`);
267
+ for (const t of selfTarget ? [...targets, selfTarget] : targets) {
268
+ console.log(` - ${t.path} — ${t.description}`);
195
269
  }
196
270
  }
197
- const execBase = basename(execPath);
198
- const isCompiledBinary = execBase === "cadet" || execBase === "cadet.exe";
199
- if (isCompiledBinary) {
200
- console.log(`It will then remove the Cadet executable at ${execPath}.`);
201
- }
202
271
 
203
272
  if (!yes) {
204
273
  if (!canPromptForConfirmation()) {
@@ -217,6 +286,20 @@ export const uninstall = Object.assign(
217
286
  }
218
287
  }
219
288
 
289
+ for (const entry of existing) {
290
+ if (entry.cloud === "apple-container" || entry.cloud === "paired") {
291
+ console.log(` Skipping '${entry.assistantId}' on ${entry.cloud} — its lifecycle is managed elsewhere.`);
292
+ continue;
293
+ }
294
+ try {
295
+ await retireAssistantData(entry);
296
+ } catch (err) {
297
+ console.error(
298
+ ` Warning: could not fully retire '${entry.assistantId}': ${err instanceof Error ? err.message : String(err)}`,
299
+ );
300
+ }
301
+ }
302
+
220
303
  for (const t of targets) {
221
304
  rmrf(t.path);
222
305
  console.log(`Deleted ${t.path}.`);
@@ -4,7 +4,7 @@
4
4
  * Forget a pairing imported from another machine via `cadet connect import`:
5
5
  * remove its lockfile entry and stored guardian token from THIS machine. Only
6
6
  * paired assistants (`cloud: "paired"`) can be unpaired — `cadet retire` owns
7
- * local and managed assistants.
7
+ * local and cloud assistants.
8
8
  *
9
9
  * This is client-side only: it forgets the connection here but does not revoke
10
10
  * the device on the host. (Host-side revocation is `cadet devices`.)
@@ -37,7 +37,7 @@ OPTIONS:
37
37
 
38
38
  Removes the local connection (lockfile entry + stored token). Only paired
39
39
  assistants (imported via 'cadet connect import') can be unpaired; use
40
- 'cadet retire' for local or managed assistants.
40
+ 'cadet retire' for local or cloud assistants.
41
41
 
42
42
  EXAMPLES:
43
43
  cadet unpair paired-desk
@@ -72,7 +72,7 @@ export const unpair = Object.assign(
72
72
 
73
73
  if (entry.cloud !== "paired") {
74
74
  console.error(
75
- `Error: '${name}' is not a paired assistant. Use \`cadet retire\` to remove a local or managed assistant.`,
75
+ `Error: '${name}' is not a paired assistant. Use \`cadet retire\` to remove a local or cloud assistant.`,
76
76
  );
77
77
  process.exit(1);
78
78
  }
@@ -1,7 +1,6 @@
1
1
  import { resolveExistingLocalSigningKey } from "@mycadet/local-mode";
2
- import { isManagedCloud } from "../lib/managed-cloud.js";
2
+ import { isCloudHome } from "../lib/cloud-values.js";
3
3
  import { spawnSync } from "child_process";
4
- import { writeFileSync } from "fs";
5
4
  import { join } from "path";
6
5
 
7
6
  import cliPkg from "../../package.json";
@@ -20,6 +19,7 @@ import {
20
19
  GATEWAY_INTERNAL_PORT,
21
20
  ASSISTANT_INTERNAL_PORT,
22
21
  dockerResourceNames,
22
+ pullImage,
23
23
  startContainers,
24
24
  stopContainers,
25
25
  } from "../lib/docker";
@@ -27,14 +27,14 @@ import {
27
27
  fetchLatestStableVersion,
28
28
  fetchReleases,
29
29
  resolveImageRefs,
30
- } from "../lib/platform-releases";
30
+ } from "../lib/cloud-releases";
31
31
  import {
32
32
  authHeaders,
33
33
  fetchAssistantDetail,
34
34
  fetchUpgradeInProgress,
35
- getPlatformUrl,
36
- readPlatformToken,
37
- } from "../lib/platform-client";
35
+ getCloudUrl,
36
+ readCloudToken,
37
+ } from "../lib/cloud-client";
38
38
  import { checkManagedHealth } from "../lib/health-check.js";
39
39
  import {
40
40
  evaluateUpgradePoll,
@@ -47,7 +47,6 @@ import {
47
47
  } from "../lib/backup-ops.js";
48
48
  import { emitCliError, categorizeUpgradeError } from "../lib/cli-error.js";
49
49
  import { parseAssistantTargetArg } from "../lib/assistant-target-args.js";
50
- import { exec } from "../lib/step-runner.js";
51
50
  import {
52
51
  broadcastUpgradeEvent,
53
52
  attemptFailedStateRestore,
@@ -77,7 +76,7 @@ import {
77
76
  startLocalDaemon,
78
77
  stopLocalProcesses,
79
78
  } from "../lib/local.js";
80
- import { restoreTunnelEdgeAndAutoTunnel } from "../lib/tunnel-edge.js";
79
+ import { restoreTunnelEdgeAndAutoTunnel } from "../lib/tunnel.js";
81
80
  import {
82
81
  leaseGuardianToken,
83
82
  resetGuardianBootstrap,
@@ -119,12 +118,12 @@ function parseArgs(): UpgradeArgs {
119
118
  console.log("");
120
119
  console.log("Arguments:");
121
120
  console.log(
122
- " <name> Name of the assistant to upgrade (default: active or only assistant)",
121
+ " <name> Name of the assistant to upgrade, defaults to active or only assistant",
123
122
  );
124
123
  console.log("");
125
124
  console.log("Options:");
126
125
  console.log(
127
- " --version <version> Target version to upgrade to (default: CLI version)",
126
+ " --version <version> Target version to upgrade to, defaults to the CLI version",
128
127
  );
129
128
  console.log(
130
129
  " --latest Upgrade to the latest stable release, updating the CLI first if needed",
@@ -317,7 +316,7 @@ async function upgradeDocker(
317
316
  );
318
317
  return;
319
318
  }
320
- console.log(`🔁 Reinstalling ${versionTag} (--force)...\n`);
319
+ console.log(`🔁 Reinstalling ${versionTag} with --force...\n`);
321
320
  }
322
321
 
323
322
  console.log("🔍 Resolving image references...");
@@ -407,7 +406,7 @@ async function upgradeDocker(
407
406
  try {
408
407
  for (const [service, image] of pullImages) {
409
408
  console.log(` Pulling ${service}: ${image}`);
410
- await exec("docker", ["pull", image]);
409
+ await pullImage(image);
411
410
  }
412
411
  } catch (pullErr) {
413
412
  const detail = pullErr instanceof Error ? pullErr.message : String(pullErr);
@@ -454,7 +453,7 @@ async function upgradeDocker(
454
453
  // Clean up old pre-upgrade backups, keep last 3
455
454
  pruneOldBackups(entry.assistantId, 3);
456
455
  } else {
457
- console.warn("⚠️ Pre-upgrade backup failed (continuing with upgrade)\n");
456
+ console.warn("⚠️ Pre-upgrade backup failed, continuing with upgrade\n");
458
457
  }
459
458
 
460
459
  // Persist the backup path so `cadet rollback` can restore the exact backup
@@ -502,17 +501,12 @@ async function upgradeDocker(
502
501
  const ready = await waitForReady(entry.runtimeUrl);
503
502
  if (ready) {
504
503
  // Update lockfile with new service group topology
505
- const newDigests = await captureImageRefs(res);
506
504
  const updatedEntry: AssistantEntry = {
507
505
  ...entry,
508
506
  containerInfo: {
509
507
  assistantImage: imageTags.assistant,
510
508
  gatewayImage: imageTags.gateway,
511
509
  cesImage: imageTags["credential-executor"],
512
- assistantDigest: newDigests?.assistant,
513
- gatewayDigest: newDigests?.gateway,
514
- cesDigest: newDigests?.["credential-executor"],
515
- networkName: res.network,
516
510
  assistantPort,
517
511
  },
518
512
  previousContainerInfo: entry.containerInfo,
@@ -668,13 +662,9 @@ async function upgradeDocker(
668
662
  );
669
663
  }
670
664
 
671
- // Capture fresh digests from the now-running rolled-back containers.
672
- const rollbackDigests = await captureImageRefs(res);
673
-
674
665
  // Restore previous container info in lockfile after rollback.
675
- // The *Image fields hold human-readable image:tag names from the
676
- // pre-upgrade containerInfo; *Digest fields get fresh values from
677
- // the running containers (or fall back to previousImageRefs).
666
+ // The recorded image refs are the rollback record now (digests are
667
+ // gone from the contract).
678
668
  const rolledBackEntry: AssistantEntry = {
679
669
  ...entry,
680
670
  containerInfo: {
@@ -686,14 +676,6 @@ async function upgradeDocker(
686
676
  cesImage:
687
677
  entry.containerInfo?.cesImage ??
688
678
  previousImageRefs["credential-executor"],
689
- assistantDigest:
690
- rollbackDigests?.assistant ?? previousImageRefs.assistant,
691
- gatewayDigest:
692
- rollbackDigests?.gateway ?? previousImageRefs.gateway,
693
- cesDigest:
694
- rollbackDigests?.["credential-executor"] ??
695
- previousImageRefs["credential-executor"],
696
- networkName: res.network,
697
679
  assistantPort,
698
680
  },
699
681
  previousContainerInfo: undefined,
@@ -909,7 +891,7 @@ async function upgradeLocal(
909
891
  );
910
892
  return;
911
893
  }
912
- console.log(`🔁 Reinstalling ${targetVersion} (--force)...\n`);
894
+ console.log(`🔁 Reinstalling ${targetVersion} with --force...\n`);
913
895
  }
914
896
 
915
897
  console.log(
@@ -951,7 +933,7 @@ async function upgradeLocal(
951
933
  console.log(` Backup saved: ${backupPath}\n`);
952
934
  pruneOldBackups(entry.assistantId, 3);
953
935
  } else {
954
- console.warn("⚠️ Pre-upgrade backup failed (continuing with upgrade)\n");
936
+ console.warn("⚠️ Pre-upgrade backup failed, continuing with upgrade\n");
955
937
  }
956
938
 
957
939
  let bootstrapSecret = entry.guardianBootstrapSecret;
@@ -1035,17 +1017,11 @@ async function upgradeLocal(
1035
1017
  console.log("✅ Local assistant processes started\n");
1036
1018
 
1037
1019
  const workspaceDir = join(entry.resources.instanceDir, "workspace");
1038
- const ngrokChild = await restoreTunnelEdgeAndAutoTunnel(
1020
+ await restoreTunnelEdgeAndAutoTunnel(
1039
1021
  entry.assistantId,
1040
1022
  entry.resources.gatewayPort,
1041
1023
  workspaceDir,
1042
1024
  );
1043
- if (ngrokChild?.pid) {
1044
- writeFileSync(
1045
- join(entry.resources.instanceDir, "ngrok.pid"),
1046
- String(ngrokChild.pid),
1047
- );
1048
- }
1049
1025
 
1050
1026
  console.log("Waiting for assistant to become ready...");
1051
1027
  const ready = await waitForReady(adminUrl);
@@ -1113,7 +1089,7 @@ function sleep(ms: number): Promise<void> {
1113
1089
  }
1114
1090
 
1115
1091
  /**
1116
- * Poll the platform until the upgrade completes, then confirm health.
1092
+ * Poll the cloud until the upgrade completes, then confirm health.
1117
1093
  * Mirrors the web UI's Software Updates flow (poll the DB-backed
1118
1094
  * `current_release_version` rather than the gateway, which bounces while
1119
1095
  * the service group restarts).
@@ -1163,7 +1139,7 @@ async function waitForPlatformUpgrade(
1163
1139
  const healthDeadline = Date.now() + PLATFORM_HEALTH_CONFIRM_TIMEOUT_MS;
1164
1140
  while (Date.now() < healthDeadline) {
1165
1141
  const health = await checkManagedHealth(
1166
- entry.runtimeUrl || getPlatformUrl(),
1142
+ entry.runtimeUrl || getCloudUrl(),
1167
1143
  entry.assistantId,
1168
1144
  );
1169
1145
  if (health.status === "healthy") {
@@ -1187,7 +1163,7 @@ async function waitForPlatformUpgrade(
1187
1163
 
1188
1164
  const timeoutMin = Math.round(timeoutMs / 60_000);
1189
1165
  console.warn(
1190
- `⚠️ Upgrade request was accepted but completion was not confirmed within ${timeoutMin} minutes. The platform may still be working — check \`cadet ps\` or the web settings page.`,
1166
+ `⚠️ Upgrade request was accepted but completion was not confirmed within ${timeoutMin} minutes. The cloud may still be working — check \`cadet ps\` or the web settings page.`,
1191
1167
  );
1192
1168
  }
1193
1169
 
@@ -1197,10 +1173,10 @@ async function upgradePlatform(
1197
1173
  wait: boolean,
1198
1174
  ): Promise<void> {
1199
1175
  console.log(
1200
- `🔄 Upgrading platform-hosted assistant '${entry.assistantId}'...\n`,
1176
+ `🔄 Upgrading cloud-hosted assistant '${entry.assistantId}'...\n`,
1201
1177
  );
1202
1178
 
1203
- const token = readPlatformToken();
1179
+ const token = readCloudToken();
1204
1180
  if (!token) {
1205
1181
  const msg =
1206
1182
  "Error: Not logged in. Run `cadet login --token <token>` first.";
@@ -1214,7 +1190,7 @@ async function upgradePlatform(
1214
1190
  // downgrades before POSTing, and bail if an upgrade is already running.
1215
1191
  const [assistantDetail, health] = await Promise.all([
1216
1192
  fetchAssistantDetail(token, entry.assistantId, entry.runtimeUrl),
1217
- checkManagedHealth(entry.runtimeUrl || getPlatformUrl(), entry.assistantId),
1193
+ checkManagedHealth(entry.runtimeUrl || getCloudUrl(), entry.assistantId),
1218
1194
  ]);
1219
1195
  // Health probe first, DB-backed field as the sleeping-assistant fallback.
1220
1196
  const currentVersion =
@@ -1225,7 +1201,7 @@ async function upgradePlatform(
1225
1201
  // platform may differ from the active lockfile default.
1226
1202
  const releases = await fetchReleases({
1227
1203
  channel: releaseChannel,
1228
- platformUrl: entry.runtimeUrl,
1204
+ cloudUrl: entry.runtimeUrl,
1229
1205
  });
1230
1206
 
1231
1207
  const resolution = resolveUpgradeTarget({
@@ -1276,7 +1252,7 @@ async function upgradePlatform(
1276
1252
 
1277
1253
  const headers = await authHeaders(token, entry.runtimeUrl);
1278
1254
 
1279
- const url = `${entry.runtimeUrl || getPlatformUrl()}/v1/assistants/${encodeURIComponent(entry.assistantId)}/upgrade/`;
1255
+ const url = `${entry.runtimeUrl || getCloudUrl()}/v1/assistants/${encodeURIComponent(entry.assistantId)}/upgrade/`;
1280
1256
  const body: { version?: string } = {};
1281
1257
  if (version) {
1282
1258
  body.version = version;
@@ -1345,11 +1321,11 @@ async function upgradePlatform(
1345
1321
  }
1346
1322
 
1347
1323
  // NOTE: We intentionally do NOT broadcast a "complete" event here.
1348
- // The platform API returning 200 only means "upgrade request accepted" —
1324
+ // The cloud API returning 200 only means "upgrade request accepted" —
1349
1325
  // the service group has not yet restarted with the new version. The
1350
1326
  // completion signal will come from the client's health-check
1351
1327
  // version-change detection (DaemonConnection.swift) once the new
1352
- // version actually appears after the platform restarts the service group.
1328
+ // version actually appears after the cloud restarts the service group.
1353
1329
 
1354
1330
  console.log(`✅ ${result.detail}`);
1355
1331
  if (result.version) {
@@ -1483,7 +1459,7 @@ async function resolveLatestStableTag(): Promise<string> {
1483
1459
  );
1484
1460
  emitCliError(
1485
1461
  "UNKNOWN",
1486
- "Could not determine the latest stable release from the platform API",
1462
+ "Could not determine the latest stable release from the cloud API",
1487
1463
  );
1488
1464
  process.exit(1);
1489
1465
  }
@@ -1560,7 +1536,7 @@ export const upgrade = Object.assign(
1560
1536
  return;
1561
1537
  }
1562
1538
 
1563
- // When --latest is passed, resolve the target from the platform API and
1539
+ // When --latest is passed, resolve the target from the cloud API and
1564
1540
  // self-update the CLI if it's behind. The resolved version is then used
1565
1541
  // as the explicit target for the rest of the upgrade flow.
1566
1542
  let effectiveVersion = version;
@@ -1586,7 +1562,7 @@ export const upgrade = Object.assign(
1586
1562
  return;
1587
1563
  }
1588
1564
 
1589
- if (isManagedCloud(cloud)) {
1565
+ if (isCloudHome(cloud)) {
1590
1566
  await upgradePlatform(entry, effectiveVersion, !noWait);
1591
1567
  return;
1592
1568
  }
@@ -38,7 +38,7 @@ export const use = Object.assign(
38
38
  `Active assistant: ${formatAssistantReference(result.entry)}`,
39
39
  );
40
40
  } else {
41
- console.log(`Active assistant: ${active} (not found in lockfile)`);
41
+ console.log(`Active assistant: ${active} — not found in lockfile`);
42
42
  }
43
43
  } else {
44
44
  console.log("No active assistant set.");