@mycadet/cli 0.12.7 → 0.12.8
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/README.md +2 -2
- package/knip.json +1 -1
- package/node_modules/@mycadet/assistant-api/events/cloud-disconnected.ts +21 -0
- package/node_modules/@mycadet/assistant-api/events/host-bash.ts +1 -1
- package/node_modules/@mycadet/assistant-api/events/host-cu.ts +1 -1
- package/node_modules/@mycadet/assistant-api/events/host-file.ts +1 -1
- package/node_modules/@mycadet/assistant-api/events/notification-intent.ts +1 -1
- package/node_modules/@mycadet/assistant-api/events/{show-platform-login.ts → show-cloud-login.ts} +3 -5
- package/node_modules/@mycadet/assistant-api/index.ts +11 -11
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/package.json +1 -1
- package/node_modules/@mycadet/{service-contracts/src/platform-credential.ts → assistant-api/node_modules/@mycadet/service-contracts/src/cloud-credential.ts} +9 -9
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
- package/node_modules/@mycadet/assistant-api/responses/home.ts +1 -1
- package/node_modules/@mycadet/assistant-api/surfaces.ts +4 -4
- package/node_modules/@mycadet/avatar-manifest/src/__tests__/notification-avatar.test.ts +1 -1
- package/node_modules/@mycadet/avatar-manifest/src/notification-avatar.ts +2 -2
- package/node_modules/@mycadet/environments/src/seeds.ts +6 -6
- package/node_modules/@mycadet/environments/src/shell.test.ts +1 -1
- package/node_modules/@mycadet/environments/src/types.ts +6 -6
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/avatar-manifest/src/__tests__/notification-avatar.test.ts +1 -1
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/avatar-manifest/src/notification-avatar.ts +2 -2
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/environments/src/seeds.ts +6 -6
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/environments/src/shell.test.ts +1 -1
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/environments/src/types.ts +6 -6
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/package.json +1 -1
- 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
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
- package/node_modules/@mycadet/local-mode/src/__tests__/pair.test.ts +5 -5
- package/node_modules/@mycadet/local-mode/src/__tests__/retire.test.ts +1 -1
- package/node_modules/@mycadet/local-mode/src/config.ts +2 -2
- package/node_modules/@mycadet/local-mode/src/index.ts +1 -1
- package/node_modules/@mycadet/local-mode/src/lockfile-contract.test.ts +3 -3
- package/node_modules/@mycadet/local-mode/src/lockfile-contract.ts +10 -5
- package/node_modules/@mycadet/local-mode/src/lockfile.test.ts +15 -15
- package/node_modules/@mycadet/local-mode/src/lockfile.ts +9 -9
- package/node_modules/@mycadet/local-mode/src/pair.ts +2 -2
- package/node_modules/@mycadet/local-mode/src/retire.ts +3 -3
- package/node_modules/@mycadet/local-mode/src/teleport-backup-policy.ts +2 -2
- package/node_modules/@mycadet/local-mode/src/unpair.ts +2 -2
- package/node_modules/@mycadet/service-contracts/package.json +1 -1
- package/node_modules/@mycadet/{local-mode/node_modules/@mycadet/service-contracts/src/platform-credential.ts → service-contracts/src/cloud-credential.ts} +9 -9
- package/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
- package/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
- package/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
- package/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
- package/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
- package/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
- package/package.json +3 -2
- package/src/__tests__/assistant-api-key-resolution.test.ts +6 -6
- package/src/__tests__/{assistant-client-platform-auth.test.ts → assistant-client-cloud-auth.test.ts} +10 -10
- package/src/__tests__/assistant-client-refresh.test.ts +2 -2
- package/src/__tests__/backup.test.ts +86 -91
- package/src/__tests__/client-token.test.ts +1 -1
- package/src/__tests__/client-tui-refresh.test.ts +1 -1
- package/src/__tests__/client-web-config.test.ts +2 -2
- package/src/__tests__/{platform-client.test.ts → cloud-client.test.ts} +59 -59
- package/src/__tests__/{platform-releases.test.ts → cloud-releases.test.ts} +5 -5
- package/src/__tests__/{cloudflare-tunnel.test.ts → cloudflare.test.ts} +1 -1
- package/src/__tests__/connect-import.test.ts +55 -39
- package/src/__tests__/debug.test.ts +38 -38
- package/src/__tests__/guardian-token.test.ts +149 -56
- package/src/__tests__/hatch-provider-secrets.test.ts +2 -2
- package/src/__tests__/helpers/guardian-token.ts +22 -0
- package/src/__tests__/helpers/http.ts +10 -0
- package/src/__tests__/nginx-ingress.test.ts +18 -18
- package/src/__tests__/orphan-detection.test.ts +1 -1
- package/src/__tests__/pair.test.ts +1 -8
- package/src/__tests__/{ps-platform-status.test.ts → ps-cloud-status.test.ts} +79 -26
- package/src/__tests__/{recover.test.ts → restore.test.ts} +29 -28
- package/src/__tests__/retire.test.ts +58 -199
- package/src/__tests__/root-process-lockfile.test.ts +99 -0
- package/src/__tests__/sleep.test.ts +19 -19
- package/src/__tests__/statefulset.test.ts +8 -8
- package/src/__tests__/{tailscale-tunnel.test.ts → tailscale.test.ts} +1 -1
- package/src/__tests__/teleport-backup.test.ts +3 -3
- package/src/__tests__/teleport.test.ts +180 -196
- package/src/__tests__/tunnel.test.ts +255 -227
- package/src/__tests__/uninstall.test.ts +13 -4
- package/src/__tests__/upgrade-local.test.ts +3 -3
- package/src/__tests__/upgrade-replay-env.test.ts +12 -12
- package/src/__tests__/wake.test.ts +258 -41
- package/src/__tests__/workos-pkce.test.ts +2 -2
- package/src/commands/backup.ts +22 -22
- package/src/commands/client.ts +76 -76
- package/src/commands/debug.ts +20 -20
- package/src/commands/exec.ts +5 -5
- package/src/commands/hatch.ts +22 -22
- package/src/commands/login.ts +22 -22
- package/src/commands/logs.ts +1 -1
- package/src/commands/ps.ts +188 -79
- package/src/commands/restart.ts +30 -0
- package/src/commands/restore.ts +138 -20
- package/src/commands/retire.ts +46 -148
- package/src/commands/rollback.ts +18 -24
- package/src/commands/setup.ts +2 -2
- package/src/commands/teleport.ts +93 -93
- package/src/commands/terminal.ts +157 -17
- package/src/commands/tunnel.ts +80 -122
- package/src/commands/uninstall.ts +55 -7
- package/src/commands/unpair.ts +3 -3
- package/src/commands/upgrade.ts +22 -46
- package/src/commands/wake.ts +162 -46
- package/src/index.ts +3 -9
- package/src/lib/__tests__/{platform-client-signed-url.test.ts → cloud-client-signed-url.test.ts} +16 -16
- package/src/lib/__tests__/job-polling.test.ts +1 -1
- package/src/lib/__tests__/local-runtime-client.test.ts +4 -4
- package/src/lib/__tests__/runtime-url.test.ts +1 -1
- package/src/lib/assistant-api-key-resolution.ts +5 -5
- package/src/lib/assistant-client.ts +8 -8
- package/src/lib/assistant-config.ts +58 -12
- package/src/lib/{platform-client.ts → cloud-client.ts} +107 -98
- package/src/lib/cloud-login.ts +24 -24
- package/src/lib/{platform-releases.ts → cloud-releases.ts} +5 -5
- package/src/lib/cloud-values.ts +3 -0
- package/src/lib/config-utils.ts +1 -1
- package/src/lib/detached-process.ts +1 -1
- package/src/lib/docker.ts +111 -10
- package/src/lib/environments/__tests__/paths.test.ts +2 -2
- package/src/lib/environments/__tests__/resolve.test.ts +26 -26
- package/src/lib/environments/resolve.ts +8 -8
- package/src/lib/guardian-token.ts +36 -4
- package/src/lib/hatch-local.ts +19 -23
- package/src/lib/health-check.ts +6 -6
- package/src/lib/ingress-config.ts +8 -35
- package/src/lib/job-polling.ts +1 -1
- package/src/lib/live-voice/connection.ts +12 -12
- package/src/lib/live-voice/tunnel.test.ts +11 -11
- package/src/lib/live-voice/tunnel.ts +15 -15
- package/src/lib/local-runtime-client.ts +20 -20
- package/src/lib/local.ts +9 -30
- package/src/lib/nginx-ingress.ts +41 -14
- package/src/lib/process.ts +1 -1
- package/src/lib/provider-secrets.ts +3 -3
- package/src/lib/retire-apple-container.ts +1 -1
- package/src/lib/runtime-url.ts +8 -8
- package/src/lib/statefulset.ts +2 -2
- package/src/lib/sync-cloud-assistants.ts +18 -18
- package/src/lib/teleport-backup.ts +4 -4
- package/src/lib/terminal-client.ts +17 -17
- package/src/lib/terminal-session.ts +24 -24
- package/src/lib/{tunnel-edge.ts → tunnel.ts} +199 -11
- package/src/lib/tunnels/cadet.ts +29 -0
- package/src/lib/{cloudflare-tunnel.ts → tunnels/cloudflare.ts} +15 -4
- package/src/lib/tunnels/gateway-notify.ts +61 -0
- package/src/lib/{ngrok.ts → tunnels/ngrok.ts} +201 -30
- package/src/lib/{tailscale-tunnel.ts → tunnels/tailscale.ts} +19 -5
- package/src/lib/upgrade-lifecycle.ts +3 -17
- package/src/lib/upgrade-preflight.ts +2 -2
- package/src/lib/workos-pkce.ts +5 -5
- package/node_modules/@mycadet/assistant-api/events/platform-disconnected.ts +0 -21
- package/src/commands/recover.ts +0 -131
- package/src/commands/roadmap.ts +0 -459
- package/src/commands/ssh.ts +0 -144
- package/src/commands/workflows.ts +0 -305
- package/src/lib/managed-cloud.ts +0 -3
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
-
type
|
|
2
|
+
type CloudCredentialVerificationStatus,
|
|
3
3
|
PlatformVerifyCredentialResponseSchema,
|
|
4
|
-
} from "@mycadet/service-contracts/
|
|
4
|
+
} from "@mycadet/service-contracts/cloud-credential";
|
|
5
5
|
import {
|
|
6
6
|
chmodSync,
|
|
7
7
|
readFileSync,
|
|
@@ -18,7 +18,7 @@ import { getCurrentEnvironment } from "./environments/resolve.js";
|
|
|
18
18
|
import { loopbackSafeFetch } from "./loopback-fetch.js";
|
|
19
19
|
import { z } from "zod";
|
|
20
20
|
|
|
21
|
-
const
|
|
21
|
+
const CloudErrorDetailSchema = z.object({
|
|
22
22
|
detail: z.string().optional(),
|
|
23
23
|
});
|
|
24
24
|
|
|
@@ -26,13 +26,18 @@ const InProgressResponseSchema = z.object({
|
|
|
26
26
|
in_progress: z.boolean().optional(),
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
-
function
|
|
29
|
+
function getCloudTokenPath(): string {
|
|
30
|
+
return join(getConfigDir(getCurrentEnvironment()), "cloud-token");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** The pre-rename token filename, read as a fallback for existing installs. */
|
|
34
|
+
function getLegacyCloudTokenPath(): string {
|
|
30
35
|
return join(getConfigDir(getCurrentEnvironment()), "platform-token");
|
|
31
36
|
}
|
|
32
37
|
|
|
33
38
|
/**
|
|
34
|
-
* Resolve the
|
|
35
|
-
* 1. `
|
|
39
|
+
* Resolve the cloud API base URL. Resolution order:
|
|
40
|
+
* 1. `cloudBaseUrl` persisted on the lockfile by
|
|
36
41
|
* {@link syncConfigToLockfile} when the active assistant was last
|
|
37
42
|
* hatched/waked. This is the source of truth for "what URL does the
|
|
38
43
|
* currently-active assistant target" — reading the workspace
|
|
@@ -40,17 +45,17 @@ function getPlatformTokenPath(): string {
|
|
|
40
45
|
* non-production XDG layouts because the CLI process has no way to
|
|
41
46
|
* know which instance to read from without first consulting the
|
|
42
47
|
* lockfile anyway.
|
|
43
|
-
* 2. `
|
|
48
|
+
* 2. `CADET_CLOUD_URL` env var (explicit override, e.g. in CI).
|
|
44
49
|
* 3. The current environment's seed URL (e.g. `https://dev-cloud.mycadet.ai`
|
|
45
50
|
* for `CADET_ENVIRONMENT=dev`, `https://cloud.mycadet.ai` for prod).
|
|
46
51
|
* This makes the CLI environment-aware when no lockfile entry exists yet.
|
|
47
52
|
*/
|
|
48
|
-
export function
|
|
53
|
+
export function getCloudUrl(): string {
|
|
49
54
|
const lockfileUrl = getLockfilePlatformBaseUrl();
|
|
50
55
|
return (
|
|
51
56
|
lockfileUrl ||
|
|
52
|
-
process.env.
|
|
53
|
-
getCurrentEnvironment().
|
|
57
|
+
process.env.CADET_CLOUD_URL?.trim() ||
|
|
58
|
+
getCurrentEnvironment().cloudUrl
|
|
54
59
|
);
|
|
55
60
|
}
|
|
56
61
|
|
|
@@ -67,19 +72,21 @@ export function getWebUrl(): string {
|
|
|
67
72
|
return process.env.CADET_WEB_URL?.trim() || getCurrentEnvironment().webUrl;
|
|
68
73
|
}
|
|
69
74
|
|
|
70
|
-
export function
|
|
75
|
+
export function readCloudToken(): string | null {
|
|
71
76
|
for (const dir of getConfigDirs(getCurrentEnvironment())) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
77
|
+
for (const name of ["cloud-token", "platform-token"]) {
|
|
78
|
+
try {
|
|
79
|
+
return readFileSync(join(dir, name), "utf-8").trim();
|
|
80
|
+
} catch {
|
|
81
|
+
// Try the next compatible location, then the legacy name.
|
|
82
|
+
}
|
|
76
83
|
}
|
|
77
84
|
}
|
|
78
85
|
return null;
|
|
79
86
|
}
|
|
80
87
|
|
|
81
|
-
export function
|
|
82
|
-
const tokenPath =
|
|
88
|
+
export function saveCloudToken(token: string): void {
|
|
89
|
+
const tokenPath = getCloudTokenPath();
|
|
83
90
|
const dir = dirname(tokenPath);
|
|
84
91
|
if (!existsSync(dir)) {
|
|
85
92
|
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
@@ -88,11 +95,13 @@ export function savePlatformToken(token: string): void {
|
|
|
88
95
|
chmodSync(tokenPath, 0o600);
|
|
89
96
|
}
|
|
90
97
|
|
|
91
|
-
export function
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
98
|
+
export function clearCloudToken(): void {
|
|
99
|
+
for (const path of [getCloudTokenPath(), getLegacyCloudTokenPath()]) {
|
|
100
|
+
try {
|
|
101
|
+
unlinkSync(path);
|
|
102
|
+
} catch {
|
|
103
|
+
// already doesn't exist
|
|
104
|
+
}
|
|
96
105
|
}
|
|
97
106
|
}
|
|
98
107
|
|
|
@@ -131,20 +140,20 @@ const orgIdCache = new Map<string, { orgId: string; expiresAt: number }>();
|
|
|
131
140
|
const ORG_ID_CACHE_TTL_MS = 60_000; // 60 seconds
|
|
132
141
|
|
|
133
142
|
/**
|
|
134
|
-
* Drop the cached org ID for a given (token,
|
|
143
|
+
* Drop the cached org ID for a given (token, cloudUrl) pair. Used by the
|
|
135
144
|
* one-shot 401-retry path: a 401 on a session-token request frequently means
|
|
136
145
|
* the cached `Cadet-Organization-Id` header is stale (e.g. user switched
|
|
137
146
|
* orgs in another tab). Clearing the entry forces the next `authHeaders`
|
|
138
|
-
* call to refetch the org ID from the
|
|
147
|
+
* call to refetch the org ID from the cloud.
|
|
139
148
|
*
|
|
140
149
|
* Exported so other modules (e.g. local-runtime-client) can implement the
|
|
141
150
|
* same retry pattern without needing direct access to the cache map.
|
|
142
151
|
*/
|
|
143
152
|
export function invalidateOrgIdCache(
|
|
144
153
|
token: string,
|
|
145
|
-
|
|
154
|
+
cloudUrl?: string,
|
|
146
155
|
): void {
|
|
147
|
-
orgIdCache.delete(`${token}::${
|
|
156
|
+
orgIdCache.delete(`${token}::${cloudUrl ?? ""}`);
|
|
148
157
|
}
|
|
149
158
|
|
|
150
159
|
/**
|
|
@@ -154,10 +163,10 @@ export function invalidateOrgIdCache(
|
|
|
154
163
|
* - `Content-Type: application/json`
|
|
155
164
|
* - The appropriate auth header (`Authorization: Bearer` for `vak_`
|
|
156
165
|
* API keys, `X-Session-Token` for session tokens).
|
|
157
|
-
* - `Cadet-Organization-Id` – fetched from the
|
|
166
|
+
* - `Cadet-Organization-Id` – fetched from the cloud. Only
|
|
158
167
|
* included for session-token callers; API keys are already org-scoped.
|
|
159
168
|
*
|
|
160
|
-
* The org ID is cached per (token,
|
|
169
|
+
* The org ID is cached per (token, cloudUrl) for 60 seconds to avoid
|
|
161
170
|
* redundant HTTP requests in tight polling loops.
|
|
162
171
|
*
|
|
163
172
|
* Auth errors (401 / 403) from the org-ID fetch are wrapped in a
|
|
@@ -169,7 +178,7 @@ export function invalidateOrgIdCache(
|
|
|
169
178
|
*/
|
|
170
179
|
export async function authHeaders(
|
|
171
180
|
token: string,
|
|
172
|
-
|
|
181
|
+
cloudUrl?: string,
|
|
173
182
|
): Promise<Record<string, string>> {
|
|
174
183
|
const base: Record<string, string> = {
|
|
175
184
|
"Content-Type": "application/json",
|
|
@@ -181,14 +190,14 @@ export async function authHeaders(
|
|
|
181
190
|
return base;
|
|
182
191
|
}
|
|
183
192
|
|
|
184
|
-
const cacheKey = `${token}::${
|
|
193
|
+
const cacheKey = `${token}::${cloudUrl ?? ""}`;
|
|
185
194
|
const cached = orgIdCache.get(cacheKey);
|
|
186
195
|
if (cached && Date.now() < cached.expiresAt) {
|
|
187
196
|
return { ...base, "Cadet-Organization-Id": cached.orgId };
|
|
188
197
|
}
|
|
189
198
|
|
|
190
199
|
try {
|
|
191
|
-
const orgId = await fetchOrganizationId(token,
|
|
200
|
+
const orgId = await fetchOrganizationId(token, cloudUrl);
|
|
192
201
|
orgIdCache.set(cacheKey, {
|
|
193
202
|
orgId,
|
|
194
203
|
expiresAt: Date.now() + ORG_ID_CACHE_TTL_MS,
|
|
@@ -300,7 +309,7 @@ const RawUnifiedJobStatusSchema = z.object({
|
|
|
300
309
|
|
|
301
310
|
export interface HatchAssistantResult {
|
|
302
311
|
assistant: HatchedAssistant;
|
|
303
|
-
/** true when the
|
|
312
|
+
/** true when the cloud returned an existing assistant (HTTP 200) */
|
|
304
313
|
reusedExisting: boolean;
|
|
305
314
|
}
|
|
306
315
|
|
|
@@ -320,7 +329,7 @@ export interface EnsureRegistrationResponse {
|
|
|
320
329
|
}
|
|
321
330
|
|
|
322
331
|
/**
|
|
323
|
-
* Register (or re-confirm) a self-hosted local assistant with the
|
|
332
|
+
* Register (or re-confirm) a self-hosted local assistant with the cloud.
|
|
324
333
|
*
|
|
325
334
|
* Calls `POST /v1/assistants/self-hosted-local/ensure-registration/`.
|
|
326
335
|
* The endpoint is idempotent: the first call provisions an API key;
|
|
@@ -333,10 +342,10 @@ export async function ensureSelfHostedLocalRegistration(
|
|
|
333
342
|
runtimeAssistantId: string,
|
|
334
343
|
clientPlatform: string,
|
|
335
344
|
assistantVersion?: string,
|
|
336
|
-
|
|
345
|
+
cloudUrl?: string,
|
|
337
346
|
publicBaseUrl?: string,
|
|
338
347
|
): Promise<EnsureRegistrationResponse> {
|
|
339
|
-
const resolvedUrl =
|
|
348
|
+
const resolvedUrl = cloudUrl || getCloudUrl();
|
|
340
349
|
const body: Record<string, string> = {
|
|
341
350
|
client_installation_id: clientInstallationId,
|
|
342
351
|
runtime_assistant_id: runtimeAssistantId,
|
|
@@ -391,7 +400,7 @@ export interface ReprovisionApiKeyResponse {
|
|
|
391
400
|
* Reprovision (rotate) the API key for a self-hosted local assistant.
|
|
392
401
|
*
|
|
393
402
|
* Calls `POST /v1/assistants/self-hosted-local/reprovision-api-key/`.
|
|
394
|
-
* Returns a fresh API key. The
|
|
403
|
+
* Returns a fresh API key. The cloud keeps the previous key valid for a
|
|
395
404
|
* grace period rather than revoking it on the spot, so a rotation whose
|
|
396
405
|
* replacement never gets stored leaves the assistant on a key that still
|
|
397
406
|
* works until that window closes.
|
|
@@ -403,9 +412,9 @@ export async function reprovisionAssistantApiKey(
|
|
|
403
412
|
runtimeAssistantId: string,
|
|
404
413
|
clientPlatform: string,
|
|
405
414
|
assistantVersion?: string,
|
|
406
|
-
|
|
415
|
+
cloudUrl?: string,
|
|
407
416
|
): Promise<ReprovisionApiKeyResponse> {
|
|
408
|
-
const resolvedUrl =
|
|
417
|
+
const resolvedUrl = cloudUrl || getCloudUrl();
|
|
409
418
|
const body: Record<string, string> = {
|
|
410
419
|
client_installation_id: clientInstallationId,
|
|
411
420
|
runtime_assistant_id: runtimeAssistantId,
|
|
@@ -455,20 +464,20 @@ export interface GatewayCredentialResult {
|
|
|
455
464
|
}
|
|
456
465
|
|
|
457
466
|
/**
|
|
458
|
-
* Ask a running assistant to check its stored
|
|
467
|
+
* Ask a running assistant to check its stored cloud credential against the
|
|
459
468
|
* platform, via the gateway-proxied `POST /v1/platform/verify-credential`.
|
|
460
469
|
*
|
|
461
|
-
* Returns `"rejected"` only when the
|
|
470
|
+
* Returns `"rejected"` only when the cloud settled that answer. Every other
|
|
462
471
|
* case, including a daemon too old to serve the route and any failure to reach
|
|
463
472
|
* one, returns null: the answer decides whether a key is rotated, so an absent
|
|
464
473
|
* answer must never read as a rejection.
|
|
465
474
|
*
|
|
466
475
|
* Never throws.
|
|
467
476
|
*/
|
|
468
|
-
export async function
|
|
477
|
+
export async function verifyGatewayCloudCredential(
|
|
469
478
|
gatewayUrl: string,
|
|
470
479
|
bearerToken?: string,
|
|
471
|
-
): Promise<
|
|
480
|
+
): Promise<CloudCredentialVerificationStatus | null> {
|
|
472
481
|
try {
|
|
473
482
|
const headers: Record<string, string> = { Accept: "application/json" };
|
|
474
483
|
if (bearerToken) {
|
|
@@ -506,7 +515,7 @@ export async function verifyGatewayManagedCredential(
|
|
|
506
515
|
* Returns a result distinguishing "key not found" (`value: null,
|
|
507
516
|
* unreachable: false`) from "gateway unreachable" (`value: null,
|
|
508
517
|
* unreachable: true`). Callers should only reprovision when the gateway
|
|
509
|
-
* is reachable: the
|
|
518
|
+
* is reachable: the cloud keeps the outgoing key valid for a grace
|
|
510
519
|
* period, so reprovisioning while the gateway is down does not break the
|
|
511
520
|
* assistant immediately, but it starts a clock on a key the caller cannot
|
|
512
521
|
* yet replace.
|
|
@@ -594,8 +603,8 @@ export interface CredentialInjectionParams {
|
|
|
594
603
|
gatewayUrl: string;
|
|
595
604
|
bearerToken?: string;
|
|
596
605
|
assistantApiKey?: string | null;
|
|
597
|
-
|
|
598
|
-
|
|
606
|
+
cloudAssistantId: string;
|
|
607
|
+
cloudBaseUrl: string;
|
|
599
608
|
organizationId: string;
|
|
600
609
|
userId?: string;
|
|
601
610
|
webhookSecret?: string | null;
|
|
@@ -622,7 +631,7 @@ export async function injectCredentialsIntoAssistant(
|
|
|
622
631
|
promises.push(inject("cadet:assistant_api_key", params.assistantApiKey));
|
|
623
632
|
}
|
|
624
633
|
|
|
625
|
-
promises.push(inject("cadet:platform_base_url", params.
|
|
634
|
+
promises.push(inject("cadet:platform_base_url", params.cloudBaseUrl));
|
|
626
635
|
|
|
627
636
|
if (params.webhookSecret) {
|
|
628
637
|
promises.push(inject("cadet:webhook_secret", params.webhookSecret));
|
|
@@ -634,14 +643,14 @@ export async function injectCredentialsIntoAssistant(
|
|
|
634
643
|
|
|
635
644
|
export async function hatchAssistant(
|
|
636
645
|
token: string,
|
|
637
|
-
|
|
646
|
+
cloudUrl?: string,
|
|
638
647
|
): Promise<HatchAssistantResult> {
|
|
639
|
-
const resolvedUrl =
|
|
648
|
+
const resolvedUrl = cloudUrl || getCloudUrl();
|
|
640
649
|
const url = `${resolvedUrl}/v1/assistants/hatch/`;
|
|
641
650
|
|
|
642
651
|
const response = await loopbackSafeFetch(url, {
|
|
643
652
|
method: "POST",
|
|
644
|
-
headers: await authHeaders(token,
|
|
653
|
+
headers: await authHeaders(token, cloudUrl),
|
|
645
654
|
body: JSON.stringify({}),
|
|
646
655
|
signal: AbortSignal.timeout(300_000),
|
|
647
656
|
});
|
|
@@ -652,7 +661,7 @@ export async function hatchAssistant(
|
|
|
652
661
|
}
|
|
653
662
|
|
|
654
663
|
if (response.status === 401 || response.status === 403) {
|
|
655
|
-
const detail =
|
|
664
|
+
const detail = CloudErrorDetailSchema
|
|
656
665
|
.catch({})
|
|
657
666
|
.parse(await response.json().catch(() => ({})));
|
|
658
667
|
throw new Error(
|
|
@@ -665,7 +674,7 @@ export async function hatchAssistant(
|
|
|
665
674
|
throw new Error("Insufficient balance to hatch a new assistant.");
|
|
666
675
|
}
|
|
667
676
|
|
|
668
|
-
const errorBody =
|
|
677
|
+
const errorBody = CloudErrorDetailSchema
|
|
669
678
|
.catch({})
|
|
670
679
|
.parse(await response.json().catch(() => ({})));
|
|
671
680
|
throw new Error(
|
|
@@ -677,18 +686,18 @@ export async function hatchAssistant(
|
|
|
677
686
|
const PLATFORM_FETCH_TIMEOUT_MS = 10_000;
|
|
678
687
|
|
|
679
688
|
/**
|
|
680
|
-
* Lightweight pre-check: returns the first active
|
|
689
|
+
* Lightweight pre-check: returns the first active cloud assistant for the
|
|
681
690
|
* authenticated user, or `null` if none exists. Calls `GET /v1/assistants/`
|
|
682
691
|
* and looks for any assistant with status "active".
|
|
683
692
|
*
|
|
684
693
|
* Used by the teleport flow to block BEFORE the expensive GCS upload when
|
|
685
|
-
* the user already has a
|
|
694
|
+
* the user already has a cloud assistant.
|
|
686
695
|
*/
|
|
687
|
-
export async function
|
|
696
|
+
export async function checkExistingCloudAssistant(
|
|
688
697
|
token: string,
|
|
689
|
-
|
|
698
|
+
cloudUrl?: string,
|
|
690
699
|
): Promise<HatchedAssistant | null> {
|
|
691
|
-
const resolvedUrl =
|
|
700
|
+
const resolvedUrl = cloudUrl || getCloudUrl();
|
|
692
701
|
const url = `${resolvedUrl}/v1/assistants/`;
|
|
693
702
|
|
|
694
703
|
const controller = new AbortController();
|
|
@@ -700,7 +709,7 @@ export async function checkExistingPlatformAssistant(
|
|
|
700
709
|
try {
|
|
701
710
|
const response = await loopbackSafeFetch(url, {
|
|
702
711
|
signal: controller.signal,
|
|
703
|
-
headers: await authHeaders(token,
|
|
712
|
+
headers: await authHeaders(token, cloudUrl),
|
|
704
713
|
});
|
|
705
714
|
|
|
706
715
|
if (!response.ok) {
|
|
@@ -717,14 +726,14 @@ export async function checkExistingPlatformAssistant(
|
|
|
717
726
|
}
|
|
718
727
|
|
|
719
728
|
/**
|
|
720
|
-
* Fetch all active assistants for the authenticated user from the
|
|
729
|
+
* Fetch all active assistants for the authenticated user from the cloud.
|
|
721
730
|
* Returns an empty array on failure (non-fatal).
|
|
722
731
|
*/
|
|
723
|
-
export async function
|
|
732
|
+
export async function fetchCloudAssistants(
|
|
724
733
|
token: string,
|
|
725
|
-
|
|
734
|
+
cloudUrl?: string,
|
|
726
735
|
): Promise<HatchedAssistant[]> {
|
|
727
|
-
const resolvedUrl =
|
|
736
|
+
const resolvedUrl = cloudUrl || getCloudUrl();
|
|
728
737
|
const url = `${resolvedUrl}/v1/assistants/`;
|
|
729
738
|
|
|
730
739
|
const controller = new AbortController();
|
|
@@ -736,7 +745,7 @@ export async function fetchPlatformAssistants(
|
|
|
736
745
|
try {
|
|
737
746
|
const response = await loopbackSafeFetch(url, {
|
|
738
747
|
signal: controller.signal,
|
|
739
|
-
headers: await authHeaders(token,
|
|
748
|
+
headers: await authHeaders(token, cloudUrl),
|
|
740
749
|
});
|
|
741
750
|
|
|
742
751
|
if (!response.ok) return [];
|
|
@@ -750,7 +759,7 @@ export async function fetchPlatformAssistants(
|
|
|
750
759
|
}
|
|
751
760
|
}
|
|
752
761
|
|
|
753
|
-
export interface
|
|
762
|
+
export interface CloudAssistantDetail {
|
|
754
763
|
currentReleaseVersion: string | null;
|
|
755
764
|
releaseChannel: "stable" | "preview";
|
|
756
765
|
}
|
|
@@ -763,15 +772,15 @@ export interface PlatformAssistantDetail {
|
|
|
763
772
|
export async function fetchAssistantDetail(
|
|
764
773
|
token: string,
|
|
765
774
|
assistantId: string,
|
|
766
|
-
|
|
767
|
-
): Promise<
|
|
768
|
-
const resolvedUrl =
|
|
775
|
+
cloudUrl?: string,
|
|
776
|
+
): Promise<CloudAssistantDetail | null> {
|
|
777
|
+
const resolvedUrl = cloudUrl || getCloudUrl();
|
|
769
778
|
const url = `${resolvedUrl}/v1/assistants/${encodeURIComponent(assistantId)}/`;
|
|
770
779
|
|
|
771
780
|
try {
|
|
772
781
|
const response = await loopbackSafeFetch(url, {
|
|
773
782
|
signal: AbortSignal.timeout(PLATFORM_FETCH_TIMEOUT_MS),
|
|
774
|
-
headers: await authHeaders(token,
|
|
783
|
+
headers: await authHeaders(token, cloudUrl),
|
|
775
784
|
});
|
|
776
785
|
|
|
777
786
|
if (!response.ok) return null;
|
|
@@ -790,7 +799,7 @@ export async function fetchAssistantDetail(
|
|
|
790
799
|
}
|
|
791
800
|
|
|
792
801
|
/**
|
|
793
|
-
* Check whether the
|
|
802
|
+
* Check whether the cloud reports an upgrade in progress for the
|
|
794
803
|
* assistant via `GET /v1/assistants/{id}/upgrade-status/`. Returns null
|
|
795
804
|
* when the endpoint is unavailable (404 on older platforms, network
|
|
796
805
|
* error) so callers can skip the check. Never throws.
|
|
@@ -798,15 +807,15 @@ export async function fetchAssistantDetail(
|
|
|
798
807
|
export async function fetchUpgradeInProgress(
|
|
799
808
|
token: string,
|
|
800
809
|
assistantId: string,
|
|
801
|
-
|
|
810
|
+
cloudUrl?: string,
|
|
802
811
|
): Promise<boolean | null> {
|
|
803
|
-
const resolvedUrl =
|
|
812
|
+
const resolvedUrl = cloudUrl || getCloudUrl();
|
|
804
813
|
const url = `${resolvedUrl}/v1/assistants/${encodeURIComponent(assistantId)}/upgrade-status/`;
|
|
805
814
|
|
|
806
815
|
try {
|
|
807
816
|
const response = await loopbackSafeFetch(url, {
|
|
808
817
|
signal: AbortSignal.timeout(PLATFORM_FETCH_TIMEOUT_MS),
|
|
809
|
-
headers: await authHeaders(token,
|
|
818
|
+
headers: await authHeaders(token, cloudUrl),
|
|
810
819
|
});
|
|
811
820
|
|
|
812
821
|
if (!response.ok) return null;
|
|
@@ -828,9 +837,9 @@ export interface PlatformUser {
|
|
|
828
837
|
|
|
829
838
|
export async function fetchOrganizationId(
|
|
830
839
|
token: string,
|
|
831
|
-
|
|
840
|
+
cloudUrl?: string,
|
|
832
841
|
): Promise<string> {
|
|
833
|
-
const resolvedUrl =
|
|
842
|
+
const resolvedUrl = cloudUrl || getCloudUrl();
|
|
834
843
|
const url = `${resolvedUrl}/v1/organizations/`;
|
|
835
844
|
|
|
836
845
|
const controller = new AbortController();
|
|
@@ -866,9 +875,9 @@ export async function fetchOrganizationId(
|
|
|
866
875
|
|
|
867
876
|
export async function fetchCurrentUser(
|
|
868
877
|
token: string,
|
|
869
|
-
|
|
878
|
+
cloudUrl?: string,
|
|
870
879
|
): Promise<PlatformUser> {
|
|
871
|
-
const resolvedUrl =
|
|
880
|
+
const resolvedUrl = cloudUrl || getCloudUrl();
|
|
872
881
|
const url = `${resolvedUrl}/_allauth/app/v1/auth/session`;
|
|
873
882
|
|
|
874
883
|
const controller = new AbortController();
|
|
@@ -907,18 +916,18 @@ export async function fetchCurrentUser(
|
|
|
907
916
|
// Rollback
|
|
908
917
|
// ---------------------------------------------------------------------------
|
|
909
918
|
|
|
910
|
-
export async function
|
|
919
|
+
export async function rollbackCloudAssistant(
|
|
911
920
|
token: string,
|
|
912
921
|
assistantId: string,
|
|
913
922
|
version?: string,
|
|
914
|
-
|
|
923
|
+
cloudUrl?: string,
|
|
915
924
|
): Promise<{ detail: string; version: string | null }> {
|
|
916
|
-
const resolvedUrl =
|
|
925
|
+
const resolvedUrl = cloudUrl || getCloudUrl();
|
|
917
926
|
const response = await loopbackSafeFetch(
|
|
918
927
|
`${resolvedUrl}/v1/assistants/${encodeURIComponent(assistantId)}/rollback/`,
|
|
919
928
|
{
|
|
920
929
|
method: "POST",
|
|
921
|
-
headers: await authHeaders(token,
|
|
930
|
+
headers: await authHeaders(token, cloudUrl),
|
|
922
931
|
body: JSON.stringify(version ? { version } : {}),
|
|
923
932
|
},
|
|
924
933
|
);
|
|
@@ -973,14 +982,14 @@ export async function platformUploadToSignedUrl(
|
|
|
973
982
|
export async function platformImportPreflightFromGcs(
|
|
974
983
|
bundleKey: string,
|
|
975
984
|
token: string,
|
|
976
|
-
|
|
985
|
+
cloudUrl?: string,
|
|
977
986
|
): Promise<{ statusCode: number; body: Record<string, unknown> }> {
|
|
978
|
-
const resolvedUrl =
|
|
987
|
+
const resolvedUrl = cloudUrl || getCloudUrl();
|
|
979
988
|
const response = await loopbackSafeFetch(
|
|
980
989
|
`${resolvedUrl}/v1/migrations/import-preflight-from-gcs/`,
|
|
981
990
|
{
|
|
982
991
|
method: "POST",
|
|
983
|
-
headers: await authHeaders(token,
|
|
992
|
+
headers: await authHeaders(token, cloudUrl),
|
|
984
993
|
signal: AbortSignal.timeout(120_000),
|
|
985
994
|
body: JSON.stringify({ bundle_key: bundleKey }),
|
|
986
995
|
},
|
|
@@ -995,14 +1004,14 @@ export async function platformImportPreflightFromGcs(
|
|
|
995
1004
|
export async function platformImportBundleFromGcs(
|
|
996
1005
|
bundleKey: string,
|
|
997
1006
|
token: string,
|
|
998
|
-
|
|
1007
|
+
cloudUrl?: string,
|
|
999
1008
|
): Promise<{ statusCode: number; body: Record<string, unknown> }> {
|
|
1000
|
-
const resolvedUrl =
|
|
1009
|
+
const resolvedUrl = cloudUrl || getCloudUrl();
|
|
1001
1010
|
const response = await loopbackSafeFetch(
|
|
1002
1011
|
`${resolvedUrl}/v1/migrations/import-from-gcs/`,
|
|
1003
1012
|
{
|
|
1004
1013
|
method: "POST",
|
|
1005
|
-
headers: await authHeaders(token,
|
|
1014
|
+
headers: await authHeaders(token, cloudUrl),
|
|
1006
1015
|
body: JSON.stringify({ bundle_key: bundleKey }),
|
|
1007
1016
|
signal: AbortSignal.timeout(60_000),
|
|
1008
1017
|
},
|
|
@@ -1024,7 +1033,7 @@ export async function platformImportBundleFromGcs(
|
|
|
1024
1033
|
|
|
1025
1034
|
/**
|
|
1026
1035
|
* Discriminated union representing the unified migration job status shape
|
|
1027
|
-
* returned by `GET /v1/migrations/jobs/{job_id}/` on both the
|
|
1036
|
+
* returned by `GET /v1/migrations/jobs/{job_id}/` on both the cloud and
|
|
1028
1037
|
* the local runtime.
|
|
1029
1038
|
*/
|
|
1030
1039
|
export type UnifiedJobStatus =
|
|
@@ -1090,7 +1099,7 @@ export interface BundleCompatibility {
|
|
|
1090
1099
|
}
|
|
1091
1100
|
|
|
1092
1101
|
/**
|
|
1093
|
-
* Thrown by
|
|
1102
|
+
* Thrown by cloudRequestSignedUrl when the cloud rejects a download
|
|
1094
1103
|
* signed-URL request because the target runtime version is outside the
|
|
1095
1104
|
* ExportJob's [min_runtime_version, max_runtime_version] band. Terminal
|
|
1096
1105
|
* — callers must NOT retry; surface to the user and abort the
|
|
@@ -1124,7 +1133,7 @@ export class VersionMismatchError extends Error {
|
|
|
1124
1133
|
}
|
|
1125
1134
|
|
|
1126
1135
|
/**
|
|
1127
|
-
* Request a signed URL from the
|
|
1136
|
+
* Request a signed URL from the cloud for either uploading a new bundle
|
|
1128
1137
|
* or downloading an existing one. Calls `POST /v1/migrations/signed-url/`.
|
|
1129
1138
|
*
|
|
1130
1139
|
* - `operation: "upload"` (optionally with `contentType` / `contentLength`)
|
|
@@ -1138,7 +1147,7 @@ export class VersionMismatchError extends Error {
|
|
|
1138
1147
|
* Throws {@link VersionMismatchError} on a 422 `version_mismatch` response,
|
|
1139
1148
|
* which is terminal — callers must NOT retry.
|
|
1140
1149
|
*/
|
|
1141
|
-
export async function
|
|
1150
|
+
export async function cloudRequestSignedUrl(
|
|
1142
1151
|
params: {
|
|
1143
1152
|
operation: "upload" | "download";
|
|
1144
1153
|
bundleKey?: string;
|
|
@@ -1150,21 +1159,21 @@ export async function platformRequestSignedUrl(
|
|
|
1150
1159
|
// Target-side, download only: runtime version that will import.
|
|
1151
1160
|
targetRuntimeVersion?: string;
|
|
1152
1161
|
// Upload only: who will PUT to the URL. "runtime" signs against the
|
|
1153
|
-
// runtime-reachable storage endpoint so a
|
|
1162
|
+
// runtime-reachable storage endpoint so a cloud assistant pod can
|
|
1154
1163
|
// upload its export bundle (platform→local teleport); defaults to
|
|
1155
1164
|
// "client" server-side. No effect in production, where both endpoints
|
|
1156
1165
|
// are the same.
|
|
1157
1166
|
consumer?: "client" | "runtime";
|
|
1158
1167
|
},
|
|
1159
1168
|
token: string,
|
|
1160
|
-
|
|
1169
|
+
cloudUrl?: string,
|
|
1161
1170
|
): Promise<{
|
|
1162
1171
|
url: string;
|
|
1163
1172
|
bundleKey: string;
|
|
1164
1173
|
expiresAt: string;
|
|
1165
1174
|
maxContentLength?: number;
|
|
1166
1175
|
}> {
|
|
1167
|
-
const resolvedUrl =
|
|
1176
|
+
const resolvedUrl = cloudUrl || getCloudUrl();
|
|
1168
1177
|
const body: Record<string, unknown> = { operation: params.operation };
|
|
1169
1178
|
if (params.bundleKey !== undefined) body.bundle_key = params.bundleKey;
|
|
1170
1179
|
if (params.contentType !== undefined) body.content_type = params.contentType;
|
|
@@ -1189,7 +1198,7 @@ export async function platformRequestSignedUrl(
|
|
|
1189
1198
|
const doRequest = async (): Promise<Response> =>
|
|
1190
1199
|
loopbackSafeFetch(`${resolvedUrl}/v1/migrations/signed-url/`, {
|
|
1191
1200
|
method: "POST",
|
|
1192
|
-
headers: await authHeaders(token,
|
|
1201
|
+
headers: await authHeaders(token, cloudUrl),
|
|
1193
1202
|
body: JSON.stringify(body),
|
|
1194
1203
|
});
|
|
1195
1204
|
|
|
@@ -1200,7 +1209,7 @@ export async function platformRequestSignedUrl(
|
|
|
1200
1209
|
// lookup. For session-token callers, a 401 frequently means the
|
|
1201
1210
|
// cached org ID is stale — calling doRequest() again without clearing
|
|
1202
1211
|
// the cache would just send the same stale header and fail again.
|
|
1203
|
-
invalidateOrgIdCache(token,
|
|
1212
|
+
invalidateOrgIdCache(token, cloudUrl);
|
|
1204
1213
|
response = await doRequest();
|
|
1205
1214
|
}
|
|
1206
1215
|
|
|
@@ -1240,19 +1249,19 @@ export async function platformRequestSignedUrl(
|
|
|
1240
1249
|
}
|
|
1241
1250
|
|
|
1242
1251
|
/**
|
|
1243
|
-
* Poll the unified job-status endpoint on the
|
|
1252
|
+
* Poll the unified job-status endpoint on the cloud. Calls
|
|
1244
1253
|
* `GET /v1/migrations/jobs/{jobId}/` and parses into {@link UnifiedJobStatus}.
|
|
1245
1254
|
*/
|
|
1246
1255
|
export async function platformPollJobStatus(
|
|
1247
1256
|
jobId: string,
|
|
1248
1257
|
token: string,
|
|
1249
|
-
|
|
1258
|
+
cloudUrl?: string,
|
|
1250
1259
|
): Promise<UnifiedJobStatus> {
|
|
1251
|
-
const resolvedUrl =
|
|
1260
|
+
const resolvedUrl = cloudUrl || getCloudUrl();
|
|
1252
1261
|
const response = await loopbackSafeFetch(
|
|
1253
1262
|
`${resolvedUrl}/v1/migrations/jobs/${jobId}/`,
|
|
1254
1263
|
{
|
|
1255
|
-
headers: await authHeaders(token,
|
|
1264
|
+
headers: await authHeaders(token, cloudUrl),
|
|
1256
1265
|
},
|
|
1257
1266
|
);
|
|
1258
1267
|
|