@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
|
@@ -24,6 +24,22 @@ import {
|
|
|
24
24
|
import type { AssistantEntry } from "./assistant-config.js";
|
|
25
25
|
import { loopbackSafeFetch } from "./loopback-fetch.js";
|
|
26
26
|
import { authHeaders, invalidateOrgIdCache } from "./platform-client.js";
|
|
27
|
+
import { z } from "zod";
|
|
28
|
+
|
|
29
|
+
const ManagedBackupEntrySchema = z.object({
|
|
30
|
+
snapshot_name: z.string().optional(),
|
|
31
|
+
created_at: z.string().optional(),
|
|
32
|
+
ready_to_use: z.boolean().optional(),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const ManagedBackupsResponseSchema = z.object({
|
|
36
|
+
backups: z.array(ManagedBackupEntrySchema).optional(),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const CreatedBackupResponseSchema = z.object({
|
|
40
|
+
snapshot_name: z.string().optional(),
|
|
41
|
+
ready_to_use: z.boolean().optional(),
|
|
42
|
+
});
|
|
27
43
|
|
|
28
44
|
type PlatformEntry = Pick<AssistantEntry, "runtimeUrl" | "assistantId">;
|
|
29
45
|
|
|
@@ -72,8 +88,8 @@ async function fetchPlatformBackups(
|
|
|
72
88
|
`Cadet Cloud backup list failed ${await errorSuffix(response)}`,
|
|
73
89
|
);
|
|
74
90
|
}
|
|
75
|
-
const
|
|
76
|
-
return
|
|
91
|
+
const parsed = ManagedBackupsResponseSchema.safeParse(await response.json());
|
|
92
|
+
return parsed.success ? (parsed.data.backups ?? []) : [];
|
|
77
93
|
}
|
|
78
94
|
|
|
79
95
|
/**
|
|
@@ -113,17 +129,15 @@ export async function createPlatformBackup(
|
|
|
113
129
|
`Cadet Cloud backup create failed ${await errorSuffix(response)}`,
|
|
114
130
|
);
|
|
115
131
|
}
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
} | null;
|
|
120
|
-
const snapshotName = created?.snapshot_name;
|
|
121
|
-
if (!snapshotName) {
|
|
132
|
+
const body: unknown = await response.json().catch(() => null);
|
|
133
|
+
const created = CreatedBackupResponseSchema.safeParse(body);
|
|
134
|
+
if (!created.success || !created.data.snapshot_name) {
|
|
122
135
|
throw new Error("Cadet Cloud backup create returned no snapshot name");
|
|
123
136
|
}
|
|
124
|
-
if (created
|
|
137
|
+
if (created.data.ready_to_use === true) {
|
|
125
138
|
return;
|
|
126
139
|
}
|
|
140
|
+
const snapshotName = created.data.snapshot_name;
|
|
127
141
|
|
|
128
142
|
const deadline = Date.now() + timeoutMs;
|
|
129
143
|
while (Date.now() < deadline) {
|
|
@@ -6,8 +6,19 @@
|
|
|
6
6
|
* the web UI's xterm.js terminal uses.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { authHeaders, getPlatformUrl } from "./platform-client.js";
|
|
10
9
|
import { loopbackSafeFetch } from "./loopback-fetch.js";
|
|
10
|
+
import { authHeaders, getPlatformUrl } from "./platform-client.js";
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
|
|
13
|
+
const TerminalSessionResponseSchema = z.object({ session_id: z.string() });
|
|
14
|
+
|
|
15
|
+
const TerminalOutputEventSchema = z.object({
|
|
16
|
+
seq: z.number(),
|
|
17
|
+
/** Base64-encoded PTY output bytes. */
|
|
18
|
+
data: z.string(),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export type TerminalOutputEvent = z.infer<typeof TerminalOutputEventSchema>;
|
|
11
22
|
|
|
12
23
|
// ---------------------------------------------------------------------------
|
|
13
24
|
// Create / Close
|
|
@@ -40,7 +51,11 @@ export async function createTerminalSession(
|
|
|
40
51
|
`Failed to create terminal session (${response.status}): ${detail || response.statusText}`,
|
|
41
52
|
);
|
|
42
53
|
}
|
|
43
|
-
|
|
54
|
+
const parsed = TerminalSessionResponseSchema.safeParse(await response.json());
|
|
55
|
+
if (!parsed.success) {
|
|
56
|
+
throw new Error("Terminal session response did not include a session_id.");
|
|
57
|
+
}
|
|
58
|
+
return parsed.data;
|
|
44
59
|
}
|
|
45
60
|
|
|
46
61
|
export async function closeTerminalSession(
|
|
@@ -120,12 +135,6 @@ export async function resizeTerminalSession(
|
|
|
120
135
|
// SSE event stream
|
|
121
136
|
// ---------------------------------------------------------------------------
|
|
122
137
|
|
|
123
|
-
export interface TerminalOutputEvent {
|
|
124
|
-
seq: number;
|
|
125
|
-
/** Base64-encoded PTY output bytes. */
|
|
126
|
-
data: string;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
138
|
/**
|
|
130
139
|
* Subscribe to the terminal output SSE stream. Yields parsed events as they
|
|
131
140
|
* arrive. The generator completes when the stream ends or is aborted.
|
|
@@ -170,7 +179,10 @@ export async function* subscribeTerminalEvents(
|
|
|
170
179
|
const trimmed = line.trimEnd();
|
|
171
180
|
if (trimmed.startsWith("data: ")) {
|
|
172
181
|
try {
|
|
173
|
-
|
|
182
|
+
const frame = TerminalOutputEventSchema.safeParse(
|
|
183
|
+
JSON.parse(trimmed.slice(6)),
|
|
184
|
+
);
|
|
185
|
+
if (frame.success) yield frame.data;
|
|
174
186
|
} catch {
|
|
175
187
|
// Skip malformed SSE frames
|
|
176
188
|
}
|
package/src/lib/tunnel-edge.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
loadRawConfig,
|
|
15
15
|
parseGatewayPortFromEntryUrls,
|
|
16
16
|
} from "./ingress-config.js";
|
|
17
|
+
import { IngressConfigSchema } from "./ngrok.js";
|
|
17
18
|
import {
|
|
18
19
|
ensureTunnelEdge,
|
|
19
20
|
formatEdgeMode,
|
|
@@ -33,13 +34,11 @@ export const DOCKER_GATEWAY_READY_TIMEOUT_MS = 5 * 60_000;
|
|
|
33
34
|
* enabled with a saved public URL.
|
|
34
35
|
*/
|
|
35
36
|
function wantsWebIngress(config: Record<string, unknown>): boolean {
|
|
36
|
-
const
|
|
37
|
-
| { enabled?: unknown; publicBaseUrl?: unknown }
|
|
38
|
-
| undefined;
|
|
37
|
+
const parsed = IngressConfigSchema.safeParse(config.ingress);
|
|
39
38
|
return (
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
parsed.success &&
|
|
40
|
+
parsed.data.enabled === true &&
|
|
41
|
+
(parsed.data.publicBaseUrl?.trim() ?? "") !== ""
|
|
43
42
|
);
|
|
44
43
|
}
|
|
45
44
|
|
|
@@ -189,17 +188,16 @@ function ownsSharedIngress(
|
|
|
189
188
|
) {
|
|
190
189
|
return false;
|
|
191
190
|
}
|
|
192
|
-
let publicBaseUrl:
|
|
191
|
+
let publicBaseUrl: string | undefined;
|
|
193
192
|
try {
|
|
194
|
-
|
|
195
|
-
loadRawConfig(workspaceDir).ingress
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
)?.publicBaseUrl;
|
|
193
|
+
const parsed = IngressConfigSchema.safeParse(
|
|
194
|
+
loadRawConfig(workspaceDir).ingress,
|
|
195
|
+
);
|
|
196
|
+
publicBaseUrl = parsed.success ? parsed.data.publicBaseUrl : undefined;
|
|
199
197
|
} catch {
|
|
200
198
|
return true;
|
|
201
199
|
}
|
|
202
|
-
if (
|
|
200
|
+
if (!publicBaseUrl || !publicBaseUrl.trim()) {
|
|
203
201
|
return true;
|
|
204
202
|
}
|
|
205
203
|
if (entry.ingressUrl === publicBaseUrl) {
|
|
@@ -25,6 +25,16 @@ import { getCurrentEnvironment } from "./environments/resolve.js";
|
|
|
25
25
|
import { loadGuardianToken } from "./guardian-token.js";
|
|
26
26
|
import { classifyReadyzResponse } from "./http-client.js";
|
|
27
27
|
import { fetchReleases, resolveImageRefs } from "./platform-releases.js";
|
|
28
|
+
import { z } from "zod";
|
|
29
|
+
|
|
30
|
+
const GatewayHealthSchema = z.object({
|
|
31
|
+
version: z.string().optional(),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const MigrationGateBodySchema = z.object({
|
|
35
|
+
status: z.string().optional(),
|
|
36
|
+
reason: z.string().optional(),
|
|
37
|
+
});
|
|
28
38
|
import {
|
|
29
39
|
getBuilderManagedEnvKeys,
|
|
30
40
|
type DockerStatefulSetSpec,
|
|
@@ -289,8 +299,8 @@ export async function fetchCurrentVersion(
|
|
|
289
299
|
signal: AbortSignal.timeout(5000),
|
|
290
300
|
});
|
|
291
301
|
if (resp.ok) {
|
|
292
|
-
const
|
|
293
|
-
return
|
|
302
|
+
const parsed = GatewayHealthSchema.safeParse(await resp.json());
|
|
303
|
+
return parsed.success ? parsed.data.version : undefined;
|
|
294
304
|
}
|
|
295
305
|
} catch {
|
|
296
306
|
// Best-effort
|
|
@@ -532,9 +542,11 @@ export async function rollbackMigrations(
|
|
|
532
542
|
// The daemon's running-migrations 503 also says "starting" but
|
|
533
543
|
// carries a `reason` field — don't retry against that one (the
|
|
534
544
|
// rollback stays blocked until migrations leave the running state).
|
|
535
|
-
const parsed = JSON.parse(text)
|
|
545
|
+
const parsed = MigrationGateBodySchema.safeParse(JSON.parse(text));
|
|
536
546
|
gatewayStarting =
|
|
537
|
-
parsed.
|
|
547
|
+
parsed.success &&
|
|
548
|
+
parsed.data.status === "starting" &&
|
|
549
|
+
parsed.data.reason === undefined;
|
|
538
550
|
} catch {
|
|
539
551
|
// Non-JSON error body — not the starting gate.
|
|
540
552
|
}
|
package/src/lib/workos-pkce.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import crypto from "node:crypto";
|
|
6
6
|
|
|
7
7
|
import { loopbackSafeFetch } from "./loopback-fetch.js";
|
|
8
|
+
import { z } from "zod";
|
|
8
9
|
|
|
9
10
|
const WORKOS_API_BASE_URL = "https://api.workos.com";
|
|
10
11
|
const PROVIDER_ID = "workos";
|
|
@@ -50,13 +51,39 @@ export function buildAuthorizeUrl(options: AuthorizeUrlOptions): string {
|
|
|
50
51
|
return url.toString();
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
|
|
54
|
-
id: string
|
|
55
|
-
name
|
|
56
|
-
client_id
|
|
57
|
-
flows
|
|
58
|
-
openid_configuration_url
|
|
59
|
-
}
|
|
54
|
+
const HeadlessProviderEntrySchema = z.object({
|
|
55
|
+
id: z.string().optional(),
|
|
56
|
+
name: z.string().optional(),
|
|
57
|
+
client_id: z.string().optional(),
|
|
58
|
+
flows: z.array(z.string()).optional(),
|
|
59
|
+
openid_configuration_url: z.string().optional(),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
type HeadlessProviderEntry = z.infer<typeof HeadlessProviderEntrySchema>;
|
|
63
|
+
|
|
64
|
+
const WorkosAuthConfigSchema = z.object({
|
|
65
|
+
data: z
|
|
66
|
+
.object({
|
|
67
|
+
socialaccount: z
|
|
68
|
+
.object({
|
|
69
|
+
providers: z.array(HeadlessProviderEntrySchema).optional(),
|
|
70
|
+
})
|
|
71
|
+
.optional(),
|
|
72
|
+
})
|
|
73
|
+
.optional(),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const WorkosAccessTokenResponseSchema = z.object({
|
|
77
|
+
access_token: z.string(),
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const WorkosSessionTokenResponseSchema = z.object({
|
|
81
|
+
meta: z
|
|
82
|
+
.object({
|
|
83
|
+
session_token: z.string(),
|
|
84
|
+
})
|
|
85
|
+
.optional(),
|
|
86
|
+
});
|
|
60
87
|
|
|
61
88
|
/**
|
|
62
89
|
* Pick the OAuth2 WorkOS provider from the headless config. During the
|
|
@@ -83,12 +110,11 @@ export async function fetchWorkosClientId(
|
|
|
83
110
|
if (!response.ok) {
|
|
84
111
|
throw new Error(`Failed to fetch auth config (${response.status})`);
|
|
85
112
|
}
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
);
|
|
113
|
+
const parsed = WorkosAuthConfigSchema.safeParse(await response.json());
|
|
114
|
+
const providers = parsed.success
|
|
115
|
+
? (parsed.data.data?.socialaccount?.providers ?? [])
|
|
116
|
+
: [];
|
|
117
|
+
const clientId = selectWorkosClientId(providers);
|
|
92
118
|
if (!clientId) {
|
|
93
119
|
throw new Error(
|
|
94
120
|
"Cadet Cloud does not advertise a token-auth WorkOS provider; cannot start PKCE login.",
|
|
@@ -122,11 +148,13 @@ export async function exchangeCodeWithWorkos(options: {
|
|
|
122
148
|
`WorkOS code exchange failed (${response.status}): ${body}`,
|
|
123
149
|
);
|
|
124
150
|
}
|
|
125
|
-
const
|
|
126
|
-
|
|
151
|
+
const parsed = WorkosAccessTokenResponseSchema.safeParse(
|
|
152
|
+
await response.json(),
|
|
153
|
+
);
|
|
154
|
+
if (!parsed.success) {
|
|
127
155
|
throw new Error("WorkOS code exchange returned no access token.");
|
|
128
156
|
}
|
|
129
|
-
return data.access_token;
|
|
157
|
+
return parsed.data.access_token;
|
|
130
158
|
}
|
|
131
159
|
|
|
132
160
|
/** Exchange the WorkOS access token for a platform session token. */
|
|
@@ -149,11 +177,14 @@ export async function exchangeAccessTokenForSession(
|
|
|
149
177
|
const body = await response.text();
|
|
150
178
|
throw new Error(`Session exchange failed (${response.status}): ${body}`);
|
|
151
179
|
}
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
180
|
+
const parsed = WorkosSessionTokenResponseSchema.safeParse(
|
|
181
|
+
await response.json(),
|
|
182
|
+
);
|
|
183
|
+
const sessionToken = parsed.success
|
|
184
|
+
? parsed.data.meta?.session_token
|
|
185
|
+
: undefined;
|
|
186
|
+
if (!sessionToken) {
|
|
156
187
|
throw new Error("Session exchange returned no session token.");
|
|
157
188
|
}
|
|
158
|
-
return
|
|
189
|
+
return sessionToken;
|
|
159
190
|
}
|