@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/login.ts
CHANGED
|
@@ -1,303 +1,29 @@
|
|
|
1
1
|
import { isManagedCloud } from "../lib/managed-cloud.js";
|
|
2
|
-
import { randomBytes } from "crypto";
|
|
3
|
-
import { createServer } from "http";
|
|
4
|
-
import type { AddressInfo } from "net";
|
|
5
2
|
|
|
6
3
|
import {
|
|
7
4
|
getActiveAssistant,
|
|
8
5
|
loadAllAssistants,
|
|
9
6
|
removeAssistantEntry,
|
|
10
|
-
resolveAssistant,
|
|
11
|
-
saveAssistantEntry,
|
|
12
7
|
setActiveAssistant,
|
|
13
8
|
} from "../lib/assistant-config";
|
|
14
|
-
import { resolveAssistantApiKeyForInjection } from "../lib/assistant-api-key-resolution";
|
|
15
|
-
import { computeDeviceId } from "../lib/guardian-token";
|
|
16
|
-
import { openBrowser } from "../lib/open-browser";
|
|
17
9
|
import {
|
|
18
10
|
clearPlatformToken,
|
|
19
|
-
ensureSelfHostedLocalRegistration,
|
|
20
11
|
fetchCurrentUser,
|
|
21
|
-
fetchOrganizationId,
|
|
22
12
|
fetchPlatformAssistants,
|
|
23
|
-
getPlatformUrl,
|
|
24
|
-
injectCredentialsIntoAssistant,
|
|
25
13
|
readPlatformToken,
|
|
26
14
|
savePlatformToken,
|
|
27
15
|
} from "../lib/platform-client";
|
|
28
|
-
import { syncCloudAssistants } from "../lib/sync-cloud-assistants";
|
|
29
|
-
import {
|
|
30
|
-
fetchAssistantIngressUrl,
|
|
31
|
-
fetchCurrentVersion,
|
|
32
|
-
} from "../lib/upgrade-lifecycle.js";
|
|
33
16
|
import {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
} from "../lib/workos-pkce";
|
|
41
|
-
|
|
42
|
-
const LOGIN_TIMEOUT_MS = 120_000; // 2 minutes
|
|
43
|
-
|
|
44
|
-
function escapeHtml(s: string): string {
|
|
45
|
-
return s
|
|
46
|
-
.replace(/&/g, "&")
|
|
47
|
-
.replace(/</g, "<")
|
|
48
|
-
.replace(/>/g, ">")
|
|
49
|
-
.replace(/"/g, """)
|
|
50
|
-
.replace(/'/g, "'");
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function renderLoginPage(
|
|
54
|
-
title: string,
|
|
55
|
-
subtitle: string,
|
|
56
|
-
success: boolean,
|
|
57
|
-
): string {
|
|
58
|
-
const checkmarkSvg = `<svg class="icon" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
59
|
-
<circle cx="28" cy="28" r="28" fill="var(--positive-bg)"/>
|
|
60
|
-
<path class="check" d="M17 28.5L24.5 36L39 21" stroke="var(--positive-fg)" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
|
61
|
-
</svg>`;
|
|
62
|
-
|
|
63
|
-
const errorSvg = `<svg class="icon" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
64
|
-
<circle cx="28" cy="28" r="28" fill="var(--negative-bg)"/>
|
|
65
|
-
<path class="cross cross-1" d="M20 20L36 36" stroke="var(--negative-fg)" stroke-width="3.5" stroke-linecap="round" fill="none"/>
|
|
66
|
-
<path class="cross cross-2" d="M36 20L20 36" stroke="var(--negative-fg)" stroke-width="3.5" stroke-linecap="round" fill="none"/>
|
|
67
|
-
</svg>`;
|
|
68
|
-
|
|
69
|
-
return `<!DOCTYPE html>
|
|
70
|
-
<html lang="en">
|
|
71
|
-
<head>
|
|
72
|
-
<meta charset="utf-8">
|
|
73
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
74
|
-
<title>${escapeHtml(title)}</title>
|
|
75
|
-
<style>
|
|
76
|
-
:root {
|
|
77
|
-
--surface: #F5F3EB;
|
|
78
|
-
--surface-card: #FFFFFF;
|
|
79
|
-
--card-border: #E8E6DA;
|
|
80
|
-
--text-primary: #2A2A28;
|
|
81
|
-
--text-secondary: #4A4A46;
|
|
82
|
-
--positive-bg: #D4DFD0;
|
|
83
|
-
--positive-fg: #516748;
|
|
84
|
-
--negative-bg: #F7DAC9;
|
|
85
|
-
--negative-fg: #DA491A;
|
|
86
|
-
--shadow: 0 1px 3px rgba(0,0,0,0.04), 0 4px 12px rgba(0,0,0,0.06);
|
|
87
|
-
--font: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", sans-serif;
|
|
88
|
-
}
|
|
89
|
-
@media (prefers-color-scheme: dark) {
|
|
90
|
-
:root {
|
|
91
|
-
--surface: #1A1A18;
|
|
92
|
-
--surface-card: #2A2A28;
|
|
93
|
-
--card-border: #3A3A37;
|
|
94
|
-
--text-primary: #F5F3EB;
|
|
95
|
-
--text-secondary: #BDB9A9;
|
|
96
|
-
--positive-bg: #1A2316;
|
|
97
|
-
--positive-fg: #7A8B6F;
|
|
98
|
-
--negative-bg: #4E281D;
|
|
99
|
-
--negative-fg: #E86B40;
|
|
100
|
-
--shadow: 0 1px 3px rgba(0,0,0,0.2), 0 4px 12px rgba(0,0,0,0.3);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
104
|
-
body {
|
|
105
|
-
font-family: var(--font);
|
|
106
|
-
background: var(--surface);
|
|
107
|
-
color: var(--text-primary);
|
|
108
|
-
display: flex;
|
|
109
|
-
align-items: center;
|
|
110
|
-
justify-content: center;
|
|
111
|
-
min-height: 100vh;
|
|
112
|
-
-webkit-font-smoothing: antialiased;
|
|
113
|
-
}
|
|
114
|
-
.card {
|
|
115
|
-
text-align: center;
|
|
116
|
-
padding: 48px 40px 40px;
|
|
117
|
-
background: var(--surface-card);
|
|
118
|
-
border: 1px solid var(--card-border);
|
|
119
|
-
border-radius: 16px;
|
|
120
|
-
box-shadow: var(--shadow);
|
|
121
|
-
max-width: 380px;
|
|
122
|
-
width: 100%;
|
|
123
|
-
opacity: 0;
|
|
124
|
-
transform: translateY(8px) scale(0.98);
|
|
125
|
-
animation: cardIn 0.5s cubic-bezier(0.16, 1, 0.3, 1) 0.1s forwards;
|
|
126
|
-
}
|
|
127
|
-
@keyframes cardIn {
|
|
128
|
-
to { opacity: 1; transform: translateY(0) scale(1); }
|
|
129
|
-
}
|
|
130
|
-
.icon {
|
|
131
|
-
width: 56px;
|
|
132
|
-
height: 56px;
|
|
133
|
-
margin-bottom: 20px;
|
|
134
|
-
}
|
|
135
|
-
.check {
|
|
136
|
-
stroke-dasharray: 32;
|
|
137
|
-
stroke-dashoffset: 32;
|
|
138
|
-
animation: draw 0.4s ease-out 0.45s forwards;
|
|
139
|
-
}
|
|
140
|
-
.cross {
|
|
141
|
-
stroke-dasharray: 22;
|
|
142
|
-
stroke-dashoffset: 22;
|
|
143
|
-
}
|
|
144
|
-
.cross-1 { animation: draw 0.3s ease-out 0.45s forwards; }
|
|
145
|
-
.cross-2 { animation: draw 0.3s ease-out 0.55s forwards; }
|
|
146
|
-
@keyframes draw {
|
|
147
|
-
to { stroke-dashoffset: 0; }
|
|
148
|
-
}
|
|
149
|
-
h1 {
|
|
150
|
-
font-size: 18px;
|
|
151
|
-
font-weight: 600;
|
|
152
|
-
letter-spacing: -0.2px;
|
|
153
|
-
color: var(--text-primary);
|
|
154
|
-
margin-bottom: 6px;
|
|
155
|
-
}
|
|
156
|
-
p {
|
|
157
|
-
font-size: 13px;
|
|
158
|
-
line-height: 1.5;
|
|
159
|
-
color: var(--text-secondary);
|
|
160
|
-
}
|
|
161
|
-
</style>
|
|
162
|
-
</head>
|
|
163
|
-
<body>
|
|
164
|
-
<div class="card">
|
|
165
|
-
${success ? checkmarkSvg : errorSvg}
|
|
166
|
-
<h1>${escapeHtml(title)}</h1>
|
|
167
|
-
<p>${escapeHtml(subtitle)}</p>
|
|
168
|
-
</div>
|
|
169
|
-
</body>
|
|
170
|
-
</html>`;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
export interface LoopbackListener {
|
|
174
|
-
/** The full `http://127.0.0.1:<port>/auth/callback` redirect URI. */
|
|
175
|
-
redirectUri: string;
|
|
176
|
-
/** Resolves with the authorization code once the state-matched callback arrives. */
|
|
177
|
-
waitForCode: Promise<string>;
|
|
178
|
-
/** Tear down the server, rejecting any pending waiter with `reason`. */
|
|
179
|
-
close: (reason?: string) => void;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Bind an ephemeral 127.0.0.1 listener and wait for the OAuth redirect.
|
|
184
|
-
* Exported for tests; production callers go through `workosPkceLogin`.
|
|
185
|
-
*/
|
|
186
|
-
export function startLoopbackListener(
|
|
187
|
-
expectedState: string,
|
|
188
|
-
): Promise<LoopbackListener> {
|
|
189
|
-
return new Promise((resolveListener, rejectListener) => {
|
|
190
|
-
let settle: {
|
|
191
|
-
resolve: (code: string) => void;
|
|
192
|
-
reject: (err: Error) => void;
|
|
193
|
-
};
|
|
194
|
-
const waitForCode = new Promise<string>((resolve, reject) => {
|
|
195
|
-
settle = { resolve, reject };
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
const server = createServer((req, res) => {
|
|
199
|
-
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
200
|
-
if (
|
|
201
|
-
url.pathname !== CALLBACK_PATH ||
|
|
202
|
-
url.searchParams.get("state") !== expectedState
|
|
203
|
-
) {
|
|
204
|
-
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
205
|
-
res.end("Not found");
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const error = url.searchParams.get("error");
|
|
210
|
-
const code = url.searchParams.get("code");
|
|
211
|
-
if (error || !code) {
|
|
212
|
-
res.writeHead(400, { "Content-Type": "text/html" });
|
|
213
|
-
res.end(
|
|
214
|
-
renderLoginPage(
|
|
215
|
-
"Login Failed",
|
|
216
|
-
"Please try again from your terminal.",
|
|
217
|
-
false,
|
|
218
|
-
),
|
|
219
|
-
);
|
|
220
|
-
server.close();
|
|
221
|
-
settle.reject(
|
|
222
|
-
new Error(
|
|
223
|
-
`Authentication failed: ${error ?? "no authorization code received"}`,
|
|
224
|
-
),
|
|
225
|
-
);
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
res.writeHead(200, { "Content-Type": "text/html" });
|
|
230
|
-
res.end(
|
|
231
|
-
renderLoginPage(
|
|
232
|
-
"Login Successful",
|
|
233
|
-
"You can close this window and return to your terminal.",
|
|
234
|
-
true,
|
|
235
|
-
),
|
|
236
|
-
);
|
|
237
|
-
server.close();
|
|
238
|
-
settle.resolve(code);
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
server.on("error", rejectListener);
|
|
242
|
-
server.listen(0, "127.0.0.1", () => {
|
|
243
|
-
const addr = server.address();
|
|
244
|
-
if (!addr || typeof addr === "string") {
|
|
245
|
-
rejectListener(new Error("Failed to start local server."));
|
|
246
|
-
return;
|
|
247
|
-
}
|
|
248
|
-
const { port } = addr as AddressInfo;
|
|
249
|
-
resolveListener({
|
|
250
|
-
redirectUri: `http://127.0.0.1:${port}${CALLBACK_PATH}`,
|
|
251
|
-
waitForCode,
|
|
252
|
-
close: (reason?: string) => {
|
|
253
|
-
server.close();
|
|
254
|
-
settle.reject(new Error(reason ?? "Login cancelled."));
|
|
255
|
-
},
|
|
256
|
-
});
|
|
257
|
-
});
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/** App-held WorkOS PKCE login */
|
|
262
|
-
async function workosPkceLogin(platformUrl: string): Promise<string> {
|
|
263
|
-
const clientId = await fetchWorkosClientId(platformUrl);
|
|
264
|
-
const { verifier, challenge } = generatePkcePair();
|
|
265
|
-
const state = randomBytes(32).toString("hex");
|
|
266
|
-
|
|
267
|
-
const listener = await startLoopbackListener(state);
|
|
268
|
-
const timeout = setTimeout(() => {
|
|
269
|
-
listener.close("Login timed out. Please try again.");
|
|
270
|
-
}, LOGIN_TIMEOUT_MS);
|
|
271
|
-
|
|
272
|
-
try {
|
|
273
|
-
const authorizeUrl = buildAuthorizeUrl({
|
|
274
|
-
clientId,
|
|
275
|
-
redirectUri: listener.redirectUri,
|
|
276
|
-
challenge,
|
|
277
|
-
state,
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
console.log("Opening browser for login...");
|
|
281
|
-
console.log(`If the browser doesn't open, visit: ${authorizeUrl}`);
|
|
282
|
-
openBrowser(authorizeUrl);
|
|
17
|
+
describeLoginError,
|
|
18
|
+
registerLocalAssistantWithPlatform,
|
|
19
|
+
workosPkceLogin,
|
|
20
|
+
} from "../lib/cloud-login.js";
|
|
21
|
+
import { getPlatformUrl } from "../lib/platform-client";
|
|
22
|
+
import { syncCloudAssistants } from "../lib/sync-cloud-assistants";
|
|
283
23
|
|
|
284
|
-
const code = await listener.waitForCode;
|
|
285
|
-
const accessToken = await exchangeCodeWithWorkos({
|
|
286
|
-
clientId,
|
|
287
|
-
code,
|
|
288
|
-
verifier,
|
|
289
|
-
});
|
|
290
|
-
return await exchangeAccessTokenForSession(
|
|
291
|
-
platformUrl,
|
|
292
|
-
clientId,
|
|
293
|
-
accessToken,
|
|
294
|
-
);
|
|
295
|
-
} finally {
|
|
296
|
-
clearTimeout(timeout);
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
24
|
|
|
300
|
-
export
|
|
25
|
+
export const login = Object.assign(
|
|
26
|
+
async function login(): Promise<void> {
|
|
301
27
|
const args = process.argv.slice(3);
|
|
302
28
|
|
|
303
29
|
if (args.includes("--help") || args.includes("-h")) {
|
|
@@ -359,7 +85,7 @@ export async function login(): Promise<void> {
|
|
|
359
85
|
try {
|
|
360
86
|
token = await workosPkceLogin(getPlatformUrl());
|
|
361
87
|
} catch (error) {
|
|
362
|
-
console.error(`❌ ${error
|
|
88
|
+
console.error(`❌ ${describeLoginError(error)}`);
|
|
363
89
|
process.exit(1);
|
|
364
90
|
}
|
|
365
91
|
}
|
|
@@ -374,74 +100,7 @@ export async function login(): Promise<void> {
|
|
|
374
100
|
// Register the local assistant with the platform (non-fatal).
|
|
375
101
|
// Mirrors the desktop app's LocalAssistantBootstrapService flow.
|
|
376
102
|
try {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
// Skip managed ("cadet") assistants — they are handled by the platform.
|
|
380
|
-
if (entry && !isManagedCloud(entry.cloud)) {
|
|
381
|
-
const orgId = await fetchOrganizationId(token);
|
|
382
|
-
const clientInstallationId = computeDeviceId();
|
|
383
|
-
const [assistantVersion, ingressUrl] = await Promise.all([
|
|
384
|
-
fetchCurrentVersion(entry.runtimeUrl),
|
|
385
|
-
fetchAssistantIngressUrl(entry.runtimeUrl, entry.bearerToken),
|
|
386
|
-
]);
|
|
387
|
-
const platformUrl = getPlatformUrl();
|
|
388
|
-
const registration = await ensureSelfHostedLocalRegistration(
|
|
389
|
-
token,
|
|
390
|
-
orgId,
|
|
391
|
-
clientInstallationId,
|
|
392
|
-
entry.assistantId,
|
|
393
|
-
"cli",
|
|
394
|
-
assistantVersion,
|
|
395
|
-
platformUrl,
|
|
396
|
-
ingressUrl,
|
|
397
|
-
);
|
|
398
|
-
saveAssistantEntry({
|
|
399
|
-
...entry,
|
|
400
|
-
platformAssistantId: registration.assistant.id,
|
|
401
|
-
platformBaseUrl: platformUrl,
|
|
402
|
-
platformOrganizationId: orgId,
|
|
403
|
-
});
|
|
404
|
-
console.log(
|
|
405
|
-
`Registered assistant: ${registration.assistant.name} (${registration.assistant.id})`,
|
|
406
|
-
);
|
|
407
|
-
|
|
408
|
-
// Which key to hand the assistant, and why. Shared with teleport so
|
|
409
|
-
// the two injection paths cannot drift.
|
|
410
|
-
const resolved = await resolveAssistantApiKeyForInjection({
|
|
411
|
-
registrationApiKey: registration.assistant_api_key,
|
|
412
|
-
runtimeUrl: entry.runtimeUrl,
|
|
413
|
-
bearerToken: entry.bearerToken,
|
|
414
|
-
token,
|
|
415
|
-
organizationId: orgId,
|
|
416
|
-
clientInstallationId,
|
|
417
|
-
runtimeAssistantId: entry.assistantId,
|
|
418
|
-
clientPlatform: "cli",
|
|
419
|
-
});
|
|
420
|
-
const assistantApiKey = resolved.apiKey;
|
|
421
|
-
if (resolved.source === "reprovisioned") {
|
|
422
|
-
console.log("Provisioning a replacement API key...");
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
// Inject credentials into the running assistant via the gateway,
|
|
426
|
-
// mirroring the desktop app's LocalAssistantBootstrapService flow.
|
|
427
|
-
const allInjected = await injectCredentialsIntoAssistant({
|
|
428
|
-
gatewayUrl: entry.runtimeUrl,
|
|
429
|
-
bearerToken: entry.bearerToken,
|
|
430
|
-
assistantApiKey,
|
|
431
|
-
platformAssistantId: registration.assistant.id,
|
|
432
|
-
platformBaseUrl: platformUrl,
|
|
433
|
-
organizationId: orgId,
|
|
434
|
-
userId: user.id,
|
|
435
|
-
webhookSecret: registration.webhook_secret,
|
|
436
|
-
});
|
|
437
|
-
if (allInjected) {
|
|
438
|
-
console.log("Injected cloud credentials into assistant.");
|
|
439
|
-
} else {
|
|
440
|
-
console.warn(
|
|
441
|
-
"Some credentials could not be injected into the assistant.",
|
|
442
|
-
);
|
|
443
|
-
}
|
|
444
|
-
}
|
|
103
|
+
await registerLocalAssistantWithPlatform(token, user);
|
|
445
104
|
} catch {
|
|
446
105
|
// Non-fatal — login succeeded even if registration fails
|
|
447
106
|
}
|
|
@@ -477,9 +136,13 @@ export async function login(): Promise<void> {
|
|
|
477
136
|
);
|
|
478
137
|
process.exit(1);
|
|
479
138
|
}
|
|
480
|
-
}
|
|
139
|
+
},
|
|
140
|
+
{ description: "Log in to Cadet Cloud" },
|
|
141
|
+
);
|
|
481
142
|
|
|
482
|
-
|
|
143
|
+
|
|
144
|
+
export const logout = Object.assign(
|
|
145
|
+
async function logout(): Promise<void> {
|
|
483
146
|
const args = process.argv.slice(3);
|
|
484
147
|
if (args.includes("--help") || args.includes("-h")) {
|
|
485
148
|
console.log("Usage: cadet logout");
|
|
@@ -504,9 +167,13 @@ export async function logout(): Promise<void> {
|
|
|
504
167
|
|
|
505
168
|
clearPlatformToken();
|
|
506
169
|
console.log("Logged out. Cadet Cloud token removed.");
|
|
507
|
-
}
|
|
170
|
+
},
|
|
171
|
+
{ description: "Log out of Cadet Cloud" },
|
|
172
|
+
);
|
|
173
|
+
|
|
508
174
|
|
|
509
|
-
export
|
|
175
|
+
export const whoami = Object.assign(
|
|
176
|
+
async function whoami(): Promise<void> {
|
|
510
177
|
const args = process.argv.slice(3);
|
|
511
178
|
if (args.includes("--help") || args.includes("-h")) {
|
|
512
179
|
console.log("Usage: cadet whoami");
|
|
@@ -532,4 +199,7 @@ export async function whoami(): Promise<void> {
|
|
|
532
199
|
console.error(`Error: ${error instanceof Error ? error.message : error}`);
|
|
533
200
|
process.exit(1);
|
|
534
201
|
}
|
|
535
|
-
}
|
|
202
|
+
},
|
|
203
|
+
{ description: "Show current logged-in user" },
|
|
204
|
+
);
|
|
205
|
+
|
package/src/commands/logs.ts
CHANGED
|
@@ -583,7 +583,8 @@ async function showAwsLogs(
|
|
|
583
583
|
|
|
584
584
|
// ── Entry point ─────────────────────────────────────────────────
|
|
585
585
|
|
|
586
|
-
export
|
|
586
|
+
export const logs = Object.assign(
|
|
587
|
+
async function logs(): Promise<void> {
|
|
587
588
|
const opts = parseArgs();
|
|
588
589
|
|
|
589
590
|
const entry = resolveAssistant(opts.name);
|
|
@@ -640,4 +641,7 @@ export async function logs(): Promise<void> {
|
|
|
640
641
|
console.error(`Unsupported topology '${cloud}' for log viewing.`);
|
|
641
642
|
process.exit(1);
|
|
642
643
|
}
|
|
643
|
-
}
|
|
644
|
+
},
|
|
645
|
+
{ description: "View logs from an assistant instance" },
|
|
646
|
+
);
|
|
647
|
+
|
package/src/commands/message.ts
CHANGED
|
@@ -123,7 +123,8 @@ function exitWithUsage(error: string): never {
|
|
|
123
123
|
process.exit(1);
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
export
|
|
126
|
+
export const message = Object.assign(
|
|
127
|
+
async function message(): Promise<void> {
|
|
127
128
|
const rawArgs = process.argv.slice(3);
|
|
128
129
|
|
|
129
130
|
if (rawArgs.includes("--help") || rawArgs.includes("-h")) {
|
|
@@ -192,4 +193,7 @@ export async function message(): Promise<void> {
|
|
|
192
193
|
console.log(`Message rejected (id: ${result.messageId})`);
|
|
193
194
|
}
|
|
194
195
|
}
|
|
195
|
-
}
|
|
196
|
+
},
|
|
197
|
+
{ description: "Send a message to a running assistant" },
|
|
198
|
+
);
|
|
199
|
+
|
package/src/commands/pair.ts
CHANGED
|
@@ -258,7 +258,8 @@ async function approveRemoteWebPairing(
|
|
|
258
258
|
return (await response.json()) as RemoteWebPairingVerificationResponse;
|
|
259
259
|
}
|
|
260
260
|
|
|
261
|
-
export
|
|
261
|
+
export const pair = Object.assign(
|
|
262
|
+
async function pair(): Promise<void> {
|
|
262
263
|
const rawArgs = process.argv.slice(3);
|
|
263
264
|
|
|
264
265
|
if (rawArgs.includes("--help") || rawArgs.includes("-h")) {
|
|
@@ -497,4 +498,7 @@ export async function pair(): Promise<void> {
|
|
|
497
498
|
console.log(`On another computer, run: cadet connect import "${pairUrl}"`);
|
|
498
499
|
console.log("");
|
|
499
500
|
console.log(`Expires: ${challenge.expiresAt}`);
|
|
500
|
-
}
|
|
501
|
+
},
|
|
502
|
+
{ description: "Print a pairing link and QR code for another device [beta]" },
|
|
503
|
+
);
|
|
504
|
+
|
package/src/commands/ps.ts
CHANGED
|
@@ -677,11 +677,13 @@ export async function listAllAssistants(verbose: boolean): Promise<void> {
|
|
|
677
677
|
);
|
|
678
678
|
|
|
679
679
|
printTable(rows, { name: "NAME", status: "STATUS", info: "INFO" });
|
|
680
|
+
console.log("");
|
|
680
681
|
}
|
|
681
682
|
|
|
682
683
|
// ── Entry point ─────────────────────────────────────────────────
|
|
683
684
|
|
|
684
|
-
export
|
|
685
|
+
export const ps = Object.assign(
|
|
686
|
+
async function ps(): Promise<void> {
|
|
685
687
|
const args = process.argv.slice(3);
|
|
686
688
|
if (args.includes("--help") || args.includes("-h")) {
|
|
687
689
|
console.log("Usage: cadet ps [<name-or-id>] [--verbose]");
|
|
@@ -717,4 +719,7 @@ export async function ps(): Promise<void> {
|
|
|
717
719
|
}
|
|
718
720
|
|
|
719
721
|
await showAssistantProcesses(result.entry);
|
|
720
|
-
}
|
|
722
|
+
},
|
|
723
|
+
{ description: "List assistants (or processes for a specific assistant)" },
|
|
724
|
+
);
|
|
725
|
+
|
package/src/commands/recover.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveExistingLocalSigningKey } from "@mycadet/local-mode";
|
|
1
2
|
import {
|
|
2
3
|
existsSync,
|
|
3
4
|
mkdirSync,
|
|
@@ -18,7 +19,8 @@ import {
|
|
|
18
19
|
import { getArchivePath, getMetadataPath } from "../lib/retire-archive";
|
|
19
20
|
import { exec } from "../lib/step-runner";
|
|
20
21
|
|
|
21
|
-
export
|
|
22
|
+
export const recover = Object.assign(
|
|
23
|
+
async function recover(): Promise<void> {
|
|
22
24
|
const args = process.argv.slice(3);
|
|
23
25
|
if (args.includes("--help") || args.includes("-h")) {
|
|
24
26
|
console.log("Usage: cadet recover <name>");
|
|
@@ -96,24 +98,21 @@ export async function recover(): Promise<void> {
|
|
|
96
98
|
const extractedPath = join(retiredDir, basename(archivePath) + ".staging");
|
|
97
99
|
|
|
98
100
|
await exec("tar", ["xzf", archivePath, "-C", retiredDir]);
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
unlinkSync(metadataPath);
|
|
108
|
-
|
|
109
|
-
// 7. Persist signing key and bootstrap secret so they survive daemon/gateway restarts
|
|
110
|
-
const signingKey = generateLocalSigningKey();
|
|
111
|
-
const bootstrapSecret = generateLocalSigningKey();
|
|
101
|
+
// Validate the staged identity before occupying the destination or updating
|
|
102
|
+
// the registry. A failed validation leaves the archive available for retry.
|
|
103
|
+
const signingKey = resolveExistingLocalSigningKey(
|
|
104
|
+
entry.resources,
|
|
105
|
+
extractedPath,
|
|
106
|
+
);
|
|
107
|
+
const bootstrapSecret =
|
|
108
|
+
entry.guardianBootstrapSecret ?? generateLocalSigningKey();
|
|
112
109
|
entry.resources = { ...entry.resources, signingKey };
|
|
113
110
|
entry.guardianBootstrapSecret = bootstrapSecret;
|
|
111
|
+
mkdirSync(dirname(targetDir), { recursive: true });
|
|
112
|
+
renameSync(extractedPath, targetDir);
|
|
114
113
|
saveAssistantEntry(entry);
|
|
115
114
|
|
|
116
|
-
//
|
|
115
|
+
// 7. Start CES sibling + daemon + gateway in parallel, the way the
|
|
117
116
|
// Docker topology brings its sibling processes up together. startCes
|
|
118
117
|
// always launches the CES sibling.
|
|
119
118
|
await Promise.all([
|
|
@@ -122,5 +121,11 @@ export async function recover(): Promise<void> {
|
|
|
122
121
|
startGateway(false, entry.resources, { signingKey, bootstrapSecret }),
|
|
123
122
|
]);
|
|
124
123
|
|
|
124
|
+
unlinkSync(archivePath);
|
|
125
|
+
unlinkSync(metadataPath);
|
|
126
|
+
|
|
125
127
|
console.log(`✅ Recovered assistant '${name}'.`);
|
|
126
|
-
}
|
|
128
|
+
},
|
|
129
|
+
{ description: "Restore a previously retired local assistant" },
|
|
130
|
+
);
|
|
131
|
+
|
package/src/commands/restore.ts
CHANGED
|
@@ -515,7 +515,8 @@ async function restorePlatform(
|
|
|
515
515
|
}
|
|
516
516
|
}
|
|
517
517
|
|
|
518
|
-
export
|
|
518
|
+
export const restore = Object.assign(
|
|
519
|
+
async function restore(): Promise<void> {
|
|
519
520
|
const { name, fromPath, version, dryRun, help } = parseArgs(process.argv);
|
|
520
521
|
|
|
521
522
|
if (help) {
|
|
@@ -623,7 +624,10 @@ export async function restore(): Promise<void> {
|
|
|
623
624
|
await cleanupStaged();
|
|
624
625
|
}
|
|
625
626
|
}
|
|
626
|
-
}
|
|
627
|
+
},
|
|
628
|
+
{ description: "Restore data (and optionally version) from a .vbundle backup" },
|
|
629
|
+
);
|
|
630
|
+
|
|
627
631
|
|
|
628
632
|
async function runLocalRestore(opts: {
|
|
629
633
|
entry: AssistantEntry;
|
package/src/commands/retire.ts
CHANGED
|
@@ -364,7 +364,8 @@ function teeConsoleToLogFile(fd: number | "ignore"): void {
|
|
|
364
364
|
};
|
|
365
365
|
}
|
|
366
366
|
|
|
367
|
-
export
|
|
367
|
+
export const retire = Object.assign(
|
|
368
|
+
async function retire(): Promise<void> {
|
|
368
369
|
if (process.env.CADET_DESKTOP_APP) {
|
|
369
370
|
resetLogFile("retire.log");
|
|
370
371
|
}
|
|
@@ -378,7 +379,10 @@ export async function retire(): Promise<void> {
|
|
|
378
379
|
} finally {
|
|
379
380
|
closeLogFile(logFd);
|
|
380
381
|
}
|
|
381
|
-
}
|
|
382
|
+
},
|
|
383
|
+
{ description: "Delete an assistant instance" },
|
|
384
|
+
);
|
|
385
|
+
|
|
382
386
|
|
|
383
387
|
async function retireInner(): Promise<void> {
|
|
384
388
|
const args = process.argv.slice(3);
|
package/src/commands/roadmap.ts
CHANGED
|
@@ -413,7 +413,8 @@ async function roadmapUnvote(args: string[]): Promise<void> {
|
|
|
413
413
|
|
|
414
414
|
// ── main ──
|
|
415
415
|
|
|
416
|
-
export
|
|
416
|
+
export const roadmap = Object.assign(
|
|
417
|
+
async function roadmap(): Promise<void> {
|
|
417
418
|
const args = process.argv.slice(3);
|
|
418
419
|
const sub = args[0];
|
|
419
420
|
|
|
@@ -452,4 +453,7 @@ export async function roadmap(): Promise<void> {
|
|
|
452
453
|
printUsage();
|
|
453
454
|
process.exitCode = 1;
|
|
454
455
|
}
|
|
455
|
-
}
|
|
456
|
+
},
|
|
457
|
+
{ description: "Manage roadmap items" },
|
|
458
|
+
);
|
|
459
|
+
|
package/src/commands/rollback.ts
CHANGED
|
@@ -211,7 +211,8 @@ async function rollbackPlatformViaEndpoint(
|
|
|
211
211
|
}
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
-
export
|
|
214
|
+
export const rollback = Object.assign(
|
|
215
|
+
async function rollback(): Promise<void> {
|
|
215
216
|
const { name, version } = parseArgs();
|
|
216
217
|
const entry = resolveTargetAssistant(name);
|
|
217
218
|
const cloud = entry.cloud;
|
|
@@ -478,4 +479,7 @@ export async function rollback(): Promise<void> {
|
|
|
478
479
|
emitCliError(categorizeUpgradeError(err), "Rollback failed", detail);
|
|
479
480
|
process.exit(1);
|
|
480
481
|
}
|
|
481
|
-
}
|
|
482
|
+
},
|
|
483
|
+
{ description: "Roll back an assistant to a previous version" },
|
|
484
|
+
);
|
|
485
|
+
|