@mycadet/cli 0.12.6-dev.202610082233.54244ee → 0.12.7

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 (89) hide show
  1. package/eslint.config.mjs +7 -0
  2. package/node_modules/@mycadet/local-mode/src/__tests__/environment.test.ts +1 -4
  3. package/node_modules/@mycadet/local-mode/src/config.ts +14 -19
  4. package/node_modules/@mycadet/local-mode/src/index.ts +5 -0
  5. package/node_modules/@mycadet/local-mode/src/local-signing-key.ts +73 -0
  6. package/node_modules/@mycadet/local-mode/src/lockfile.ts +18 -3
  7. package/package.json +4 -3
  8. package/src/__tests__/assistant-config.test.ts +30 -28
  9. package/src/__tests__/debug.test.ts +8 -8
  10. package/src/__tests__/default-main-screen-formatting.test.ts +5 -4
  11. package/src/__tests__/flags.test.ts +2 -2
  12. package/src/__tests__/hatch-provider-secrets.test.ts +3 -3
  13. package/src/__tests__/ingress-config.test.ts +2 -2
  14. package/src/__tests__/login-loopback.test.ts +1 -1
  15. package/src/__tests__/nginx-ingress.test.ts +68 -76
  16. package/src/__tests__/pair.test.ts +135 -121
  17. package/src/__tests__/platform-client.test.ts +2 -2
  18. package/src/__tests__/recover.test.ts +96 -3
  19. package/src/__tests__/retire.test.ts +5 -3
  20. package/src/__tests__/signing-key-continuity.test.ts +261 -0
  21. package/src/__tests__/sleep.test.ts +189 -6
  22. package/src/__tests__/tailscale-tunnel.test.ts +1 -1
  23. package/src/__tests__/uninstall.test.ts +171 -0
  24. package/src/__tests__/upgrade-local.test.ts +16 -3
  25. package/src/__tests__/use.test.ts +2 -2
  26. package/src/__tests__/wake-error.test.ts +76 -0
  27. package/src/__tests__/wake.test.ts +309 -36
  28. package/src/commands/backup.ts +6 -2
  29. package/src/commands/clean.ts +6 -2
  30. package/src/commands/client.ts +91 -41
  31. package/src/commands/confirm.ts +6 -2
  32. package/src/commands/connect.ts +6 -2
  33. package/src/commands/debug.ts +6 -2
  34. package/src/commands/devices.ts +6 -2
  35. package/src/commands/events.ts +6 -2
  36. package/src/commands/exec.ts +50 -12
  37. package/src/commands/flags.ts +6 -2
  38. package/src/commands/gateway.ts +6 -2
  39. package/src/commands/hatch.ts +6 -2
  40. package/src/commands/login.ts +26 -356
  41. package/src/commands/logs.ts +6 -2
  42. package/src/commands/message.ts +6 -2
  43. package/src/commands/pair.ts +6 -2
  44. package/src/commands/ps.ts +7 -2
  45. package/src/commands/recover.ts +21 -16
  46. package/src/commands/restore.ts +6 -2
  47. package/src/commands/retire.ts +6 -2
  48. package/src/commands/roadmap.ts +6 -2
  49. package/src/commands/rollback.ts +6 -2
  50. package/src/commands/setup.ts +76 -9
  51. package/src/commands/sleep.ts +131 -31
  52. package/src/commands/ssh.ts +6 -2
  53. package/src/commands/teleport.ts +6 -2
  54. package/src/commands/terminal.ts +6 -2
  55. package/src/commands/tunnel.ts +6 -2
  56. package/src/commands/uninstall.ts +228 -0
  57. package/src/commands/unpair.ts +6 -2
  58. package/src/commands/upgrade.ts +13 -8
  59. package/src/commands/use.ts +6 -2
  60. package/src/commands/voice.ts +6 -2
  61. package/src/commands/wake.ts +69 -37
  62. package/src/commands/workflows.ts +6 -2
  63. package/src/components/DefaultMainScreen.tsx +250 -36
  64. package/src/index.ts +23 -55
  65. package/src/lib/assistant-config.ts +27 -13
  66. package/src/lib/backup-ops.ts +3 -4
  67. package/src/lib/cli-error.ts +5 -0
  68. package/src/lib/client-identity.ts +8 -10
  69. package/src/lib/cloud-login.ts +422 -0
  70. package/src/lib/color.ts +16 -0
  71. package/src/lib/default-main-screen-formatting.ts +32 -8
  72. package/src/lib/environments/__tests__/paths.test.ts +9 -11
  73. package/src/lib/environments/paths.ts +2 -20
  74. package/src/lib/guardian-token.ts +72 -18
  75. package/src/lib/hatch-local.ts +52 -1
  76. package/src/lib/health-check.ts +24 -15
  77. package/src/lib/lifecycle-reporter.ts +9 -1
  78. package/src/lib/local-hatch-lock.ts +19 -27
  79. package/src/lib/local-runtime-client.ts +25 -14
  80. package/src/lib/local.ts +17 -43
  81. package/src/lib/ngrok.ts +48 -16
  82. package/src/lib/platform-client.ts +141 -72
  83. package/src/lib/retire-archive.ts +4 -4
  84. package/src/lib/retire-local.ts +2 -25
  85. package/src/lib/teleport-backup.ts +23 -9
  86. package/src/lib/terminal-client.ts +21 -9
  87. package/src/lib/tunnel-edge.ts +11 -13
  88. package/src/lib/upgrade-lifecycle.ts +16 -4
  89. package/src/lib/workos-pkce.ts +52 -21
@@ -1,3 +1,4 @@
1
+ import { resolveExistingLocalSigningKey } from "@mycadet/local-mode";
1
2
  import { isManagedCloud } from "../lib/managed-cloud.js";
2
3
  import { spawnSync } from "child_process";
3
4
  import { writeFileSync } from "fs";
@@ -873,6 +874,12 @@ async function upgradeLocal(
873
874
  process.exit(1);
874
875
  }
875
876
 
877
+ const signingKey = resolveExistingLocalSigningKey(entry.resources);
878
+ if (entry.resources.signingKey !== signingKey) {
879
+ entry.resources.signingKey = signingKey;
880
+ saveAssistantEntry(entry);
881
+ }
882
+
876
883
  const targetVersion = await targetVersionFromCli(version);
877
884
  const adminUrl = localAdminUrl(entry);
878
885
  const { currentVersion, preMigrationState } = await fetchLocalUpgradeState(
@@ -947,14 +954,8 @@ async function upgradeLocal(
947
954
  console.warn("⚠️ Pre-upgrade backup failed (continuing with upgrade)\n");
948
955
  }
949
956
 
950
- let signingKey = entry.resources.signingKey;
951
957
  let bootstrapSecret = entry.guardianBootstrapSecret;
952
958
  let entryChanged = false;
953
- if (!signingKey) {
954
- signingKey = generateLocalSigningKey();
955
- entry.resources = { ...entry.resources, signingKey };
956
- entryChanged = true;
957
- }
958
959
  if (!bootstrapSecret) {
959
960
  bootstrapSecret = generateLocalSigningKey();
960
961
  entry.guardianBootstrapSecret = bootstrapSecret;
@@ -1543,7 +1544,8 @@ async function resolveLatestAndMaybeSelfUpdate(
1543
1544
  return latestTag;
1544
1545
  }
1545
1546
 
1546
- export async function upgrade(): Promise<void> {
1547
+ export const upgrade = Object.assign(
1548
+ async function upgrade(): Promise<void> {
1547
1549
  const { name, version, latest, prepare, finalize, noWait, force } =
1548
1550
  parseArgs();
1549
1551
  const entry = resolveTargetAssistant(name);
@@ -1607,4 +1609,7 @@ export async function upgrade(): Promise<void> {
1607
1609
  console.error(msg);
1608
1610
  emitCliError("UNSUPPORTED_TOPOLOGY", msg);
1609
1611
  process.exit(1);
1610
- }
1612
+ },
1613
+ { description: "Upgrade an assistant to a newer version" },
1614
+ );
1615
+
@@ -7,7 +7,8 @@ import {
7
7
  } from "../lib/assistant-config.js";
8
8
  import { parseAssistantTargetArg } from "../lib/assistant-target-args.js";
9
9
 
10
- export async function use(): Promise<void> {
10
+ export const use = Object.assign(
11
+ async function use(): Promise<void> {
11
12
  const args = process.argv.slice(3);
12
13
 
13
14
  if (args.includes("--help") || args.includes("-h")) {
@@ -55,4 +56,7 @@ export async function use(): Promise<void> {
55
56
  console.log(
56
57
  `Active assistant set to ${formatAssistantReference(result.entry)}.`,
57
58
  );
58
- }
59
+ },
60
+ { description: "Set the active assistant for commands" },
61
+ );
62
+
@@ -66,7 +66,8 @@ EXAMPLES:
66
66
  `);
67
67
  }
68
68
 
69
- export async function voice(): Promise<void> {
69
+ export const voice = Object.assign(
70
+ async function voice(): Promise<void> {
70
71
  const args = process.argv.slice(3);
71
72
  if (args.includes("--help") || args.includes("-h")) {
72
73
  printUsage();
@@ -119,7 +120,10 @@ export async function voice(): Promise<void> {
119
120
  reference: connection.reference,
120
121
  ...(sayText !== undefined ? { sayText } : {}),
121
122
  });
122
- }
123
+ },
124
+ { description: "Type to an assistant and hear it answer out loud" },
125
+ );
126
+
123
127
 
124
128
  /** Exported for tests. Not part of the command's public surface. */
125
129
  export interface SessionDeps {
@@ -1,4 +1,8 @@
1
- import { existsSync, readFileSync, writeFileSync } from "fs";
1
+ import {
2
+ SigningKeyContinuityError,
3
+ resolveExistingLocalSigningKey,
4
+ } from "@mycadet/local-mode";
5
+ import { writeFileSync } from "fs";
2
6
  import { join } from "path";
3
7
 
4
8
  import {
@@ -16,6 +20,7 @@ import {
16
20
  probeDaemonReadinessWithRetry,
17
21
  waitForDaemonMigrationsReady,
18
22
  } from "../lib/http-client.js";
23
+ import { findOpenPort } from "../lib/port-allocator.js";
19
24
  import {
20
25
  DAEMON_STOP_TIMEOUT_MS,
21
26
  isProcessAlive,
@@ -36,7 +41,8 @@ import {
36
41
  restoreTunnelEdgeAndAutoTunnel,
37
42
  } from "../lib/tunnel-edge.js";
38
43
 
39
- export async function wake(): Promise<void> {
44
+ export const wake = Object.assign(
45
+ async function wake(): Promise<void> {
40
46
  const args = process.argv.slice(3);
41
47
  if (args.includes("--help") || args.includes("-h")) {
42
48
  console.log("Usage: cadet wake [<name>] [options]");
@@ -58,7 +64,8 @@ export async function wake(): Promise<void> {
58
64
  console.log(
59
65
  " --repair-guardian Force-re-provision the guardian token (resets the\n" +
60
66
  " gateway bootstrap and re-leases — REVOKES other device-bound\n" +
61
- " tokens, so only use deliberately, never from auto-repair)",
67
+ " tokens; replaces an unrecoverable signing key and restarts services only then.\n" +
68
+ " Clients may need to authenticate again. Use deliberately.)",
62
69
  );
63
70
  process.exit(0);
64
71
  }
@@ -120,6 +127,61 @@ export async function wake(): Promise<void> {
120
127
  process.exit(1);
121
128
  }
122
129
  const resources = entry.resources;
130
+ let signingKey: string;
131
+ let replacingSigningKey = false;
132
+ try {
133
+ signingKey = resolveExistingLocalSigningKey(resources);
134
+ } catch (error) {
135
+ if (!(error instanceof SigningKeyContinuityError)) {
136
+ throw error;
137
+ }
138
+ if (!repairGuardian) {
139
+ error.message += ` Run cadet wake ${JSON.stringify(entry.assistantId)} --repair-guardian to recover access. Repair may replace the signing key and require all clients to authenticate again.`;
140
+ throw error;
141
+ }
142
+ console.warn(
143
+ `Repairing authentication for ${entry.assistantId}: signing key ${error.code}. Replacing the signing key; all clients must authenticate again.`,
144
+ );
145
+ signingKey = generateLocalSigningKey();
146
+ replacingSigningKey = true;
147
+ }
148
+
149
+ // A replacement must reach both processes. Stop them before saving it so
150
+ // retries cannot attach to a surviving process that still uses the old key.
151
+ if (replacingSigningKey) {
152
+ for (const [file, label, port] of [
153
+ [
154
+ join(resources.instanceDir, "gateway.pid"),
155
+ "gateway",
156
+ resources.gatewayPort,
157
+ ],
158
+ [getDaemonPidPath(resources), "assistant", resources.daemonPort],
159
+ ] as const) {
160
+ const stopped = await stopProcessByPidFile(
161
+ file,
162
+ label,
163
+ undefined,
164
+ DAEMON_STOP_TIMEOUT_MS,
165
+ );
166
+ if (!stopped && isProcessAlive(file).alive) {
167
+ throw new Error(
168
+ `Cannot repair authentication: ${label} could not be stopped. Stop it and retry the same repair command; no replacement key was saved.`,
169
+ );
170
+ }
171
+ // A missing PID file does not prove the service has released its port.
172
+ try {
173
+ await findOpenPort(port, { maxAttempts: 1, host: "127.0.0.1" });
174
+ } catch {
175
+ throw new Error(
176
+ `Cannot repair authentication: could not confirm that ${label} port ${port} is free. Stop any process using it, then retry the same repair command; no replacement key was saved.`,
177
+ );
178
+ }
179
+ }
180
+ }
181
+ if (replacingSigningKey || signingKey !== resources.signingKey) {
182
+ resources.signingKey = signingKey;
183
+ saveAssistantEntry(entry);
184
+ }
123
185
 
124
186
  const pidFile = getDaemonPidPath(resources);
125
187
 
@@ -185,39 +247,6 @@ export async function wake(): Promise<void> {
185
247
  }
186
248
  }
187
249
 
188
- // Resolve the signing key. The gateway persists its own copy to disk at
189
- // <instanceDir>/.cadet/protected/actor-token-signing-key. That on-disk key
190
- // is the source of truth because it is what the gateway actually used to sign
191
- // existing actor tokens. Prefer it over the lockfile value so that tokens
192
- // survive upgrades and any scenario where the two diverge.
193
- //
194
- // NOTE: Removal of this legacy key path read is blocked on removing all use
195
- // of the signing key from the assistant daemon. Until then, the on-disk key
196
- // must remain the authoritative source.
197
- const legacyKeyPath = join(
198
- resources.instanceDir,
199
- "protected",
200
- "actor-token-signing-key",
201
- );
202
- let signingKey: string | undefined;
203
- if (existsSync(legacyKeyPath)) {
204
- try {
205
- const raw = readFileSync(legacyKeyPath);
206
- if (raw.length === 32) {
207
- signingKey = raw.toString("hex");
208
- }
209
- } catch {
210
- // Ignore — fall through to lockfile or generate.
211
- }
212
- }
213
- if (!signingKey) {
214
- signingKey = resources.signingKey ?? generateLocalSigningKey();
215
- }
216
- if (signingKey !== resources.signingKey) {
217
- entry.resources = { ...resources, signingKey };
218
- saveAssistantEntry(entry);
219
- }
220
-
221
250
  let bootstrapSecret = entry.guardianBootstrapSecret;
222
251
  let bootstrapSecretBackfilled = false;
223
252
  if (!bootstrapSecret) {
@@ -454,4 +483,7 @@ export async function wake(): Promise<void> {
454
483
  process.on("SIGTERM", () => resolve());
455
484
  });
456
485
  }
457
- }
486
+ },
487
+ { description: "Start the assistant and gateway" },
488
+ );
489
+
@@ -242,7 +242,8 @@ async function resumeRun(runId: string, assistantName?: string): Promise<void> {
242
242
  );
243
243
  }
244
244
 
245
- export async function workflows(): Promise<void> {
245
+ export const workflows = Object.assign(
246
+ async function workflows(): Promise<void> {
246
247
  const args = process.argv.slice(3);
247
248
 
248
249
  if (args.includes("--help") || args.includes("-h")) {
@@ -298,4 +299,7 @@ export async function workflows(): Promise<void> {
298
299
  console.error(`Unknown subcommand: ${subcommand}`);
299
300
  printHelp();
300
301
  process.exit(1);
301
- }
302
+ },
303
+ { description: "Inspect and control workflow runs" },
304
+ );
305
+