@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.
- package/eslint.config.mjs +7 -0
- package/node_modules/@mycadet/local-mode/src/__tests__/environment.test.ts +1 -4
- package/node_modules/@mycadet/local-mode/src/config.ts +14 -19
- package/node_modules/@mycadet/local-mode/src/index.ts +5 -0
- package/node_modules/@mycadet/local-mode/src/local-signing-key.ts +73 -0
- package/node_modules/@mycadet/local-mode/src/lockfile.ts +18 -3
- package/package.json +4 -3
- package/src/__tests__/assistant-config.test.ts +30 -28
- package/src/__tests__/debug.test.ts +8 -8
- package/src/__tests__/default-main-screen-formatting.test.ts +5 -4
- package/src/__tests__/flags.test.ts +2 -2
- package/src/__tests__/hatch-provider-secrets.test.ts +3 -3
- package/src/__tests__/ingress-config.test.ts +2 -2
- package/src/__tests__/login-loopback.test.ts +1 -1
- package/src/__tests__/nginx-ingress.test.ts +68 -76
- package/src/__tests__/pair.test.ts +135 -121
- package/src/__tests__/platform-client.test.ts +2 -2
- package/src/__tests__/recover.test.ts +96 -3
- package/src/__tests__/retire.test.ts +5 -3
- package/src/__tests__/signing-key-continuity.test.ts +261 -0
- package/src/__tests__/sleep.test.ts +189 -6
- package/src/__tests__/tailscale-tunnel.test.ts +1 -1
- package/src/__tests__/uninstall.test.ts +171 -0
- package/src/__tests__/upgrade-local.test.ts +16 -3
- package/src/__tests__/use.test.ts +2 -2
- package/src/__tests__/wake-error.test.ts +76 -0
- package/src/__tests__/wake.test.ts +309 -36
- package/src/commands/backup.ts +6 -2
- package/src/commands/clean.ts +6 -2
- package/src/commands/client.ts +91 -41
- package/src/commands/confirm.ts +6 -2
- package/src/commands/connect.ts +6 -2
- package/src/commands/debug.ts +6 -2
- package/src/commands/devices.ts +6 -2
- package/src/commands/events.ts +6 -2
- package/src/commands/exec.ts +50 -12
- package/src/commands/flags.ts +6 -2
- package/src/commands/gateway.ts +6 -2
- package/src/commands/hatch.ts +6 -2
- package/src/commands/login.ts +26 -356
- package/src/commands/logs.ts +6 -2
- package/src/commands/message.ts +6 -2
- package/src/commands/pair.ts +6 -2
- package/src/commands/ps.ts +7 -2
- package/src/commands/recover.ts +21 -16
- package/src/commands/restore.ts +6 -2
- package/src/commands/retire.ts +6 -2
- package/src/commands/roadmap.ts +6 -2
- package/src/commands/rollback.ts +6 -2
- package/src/commands/setup.ts +76 -9
- package/src/commands/sleep.ts +131 -31
- package/src/commands/ssh.ts +6 -2
- package/src/commands/teleport.ts +6 -2
- package/src/commands/terminal.ts +6 -2
- package/src/commands/tunnel.ts +6 -2
- package/src/commands/uninstall.ts +228 -0
- package/src/commands/unpair.ts +6 -2
- package/src/commands/upgrade.ts +13 -8
- package/src/commands/use.ts +6 -2
- package/src/commands/voice.ts +6 -2
- package/src/commands/wake.ts +69 -37
- package/src/commands/workflows.ts +6 -2
- package/src/components/DefaultMainScreen.tsx +250 -36
- package/src/index.ts +23 -55
- package/src/lib/assistant-config.ts +27 -13
- package/src/lib/backup-ops.ts +3 -4
- package/src/lib/cli-error.ts +5 -0
- package/src/lib/client-identity.ts +8 -10
- package/src/lib/cloud-login.ts +422 -0
- package/src/lib/color.ts +16 -0
- package/src/lib/default-main-screen-formatting.ts +32 -8
- package/src/lib/environments/__tests__/paths.test.ts +9 -11
- package/src/lib/environments/paths.ts +2 -20
- package/src/lib/guardian-token.ts +72 -18
- package/src/lib/hatch-local.ts +52 -1
- package/src/lib/health-check.ts +24 -15
- package/src/lib/lifecycle-reporter.ts +9 -1
- package/src/lib/local-hatch-lock.ts +19 -27
- package/src/lib/local-runtime-client.ts +25 -14
- package/src/lib/local.ts +17 -43
- package/src/lib/ngrok.ts +48 -16
- package/src/lib/platform-client.ts +141 -72
- package/src/lib/retire-archive.ts +4 -4
- package/src/lib/retire-local.ts +2 -25
- package/src/lib/teleport-backup.ts +23 -9
- package/src/lib/terminal-client.ts +21 -9
- package/src/lib/tunnel-edge.ts +11 -13
- package/src/lib/upgrade-lifecycle.ts +16 -4
- package/src/lib/workos-pkce.ts +52 -21
package/src/commands/upgrade.ts
CHANGED
|
@@ -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
|
|
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
|
+
|
package/src/commands/use.ts
CHANGED
|
@@ -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
|
|
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
|
+
|
package/src/commands/voice.ts
CHANGED
|
@@ -66,7 +66,8 @@ EXAMPLES:
|
|
|
66
66
|
`);
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
export
|
|
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 {
|
package/src/commands/wake.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
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
|
|
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
|
|
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
|
|
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
|
+
|