@mycadet/cli 0.12.7 → 0.12.9-dev.202610131452.9acab64
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-cloud-login.ts +18 -0
- package/node_modules/@mycadet/assistant-api/index.ts +11 -11
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/package.json +2 -2
- 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 +2 -2
- 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 +2 -2
- 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 +6 -4
- 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 +1 -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} +28 -18
- 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__/ensure-binary.test.ts +182 -0
- 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 +15 -20
- package/src/__tests__/ngrok-spawn.test.ts +49 -0
- package/src/__tests__/orphan-detection.test.ts +1 -1
- package/src/__tests__/pair.test.ts +149 -85
- package/src/__tests__/{ps-platform-status.test.ts → ps-cloud-status.test.ts} +80 -26
- package/src/__tests__/ps-table.test.ts +79 -0
- 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} +2 -2
- package/src/__tests__/teleport-backup.test.ts +3 -3
- package/src/__tests__/teleport.test.ts +180 -211
- package/src/__tests__/tunnel.test.ts +490 -270
- package/src/__tests__/uninstall.test.ts +150 -9
- 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 +260 -43
- package/src/__tests__/workos-pkce.test.ts +2 -2
- package/src/commands/backup.ts +24 -24
- package/src/commands/client.ts +76 -78
- package/src/commands/completions.test.ts +104 -0
- package/src/commands/completions.ts +87 -0
- package/src/commands/debug.ts +20 -20
- package/src/commands/exec.ts +12 -12
- package/src/commands/flags.ts +2 -2
- package/src/commands/hatch.ts +27 -24
- package/src/commands/login.ts +22 -22
- package/src/commands/logs.ts +8 -8
- package/src/commands/pair.ts +140 -37
- package/src/commands/ps.ts +232 -86
- package/src/commands/restart.ts +30 -0
- package/src/commands/restore.ts +140 -22
- package/src/commands/retire.ts +48 -150
- package/src/commands/rollback.ts +20 -26
- package/src/commands/setup.ts +2 -2
- package/src/commands/sleep.ts +2 -2
- package/src/commands/teleport.ts +110 -129
- package/src/commands/terminal.ts +158 -18
- package/src/commands/tunnel.ts +156 -175
- package/src/commands/uninstall.ts +137 -54
- package/src/commands/unpair.ts +3 -3
- package/src/commands/upgrade.ts +30 -54
- package/src/commands/use.ts +1 -1
- package/src/commands/wake.ts +166 -50
- package/src/index.ts +18 -9
- package/src/lib/__tests__/{platform-client-signed-url.test.ts → cloud-client-signed-url.test.ts} +16 -16
- package/src/lib/__tests__/docker.test.ts +168 -20
- package/src/lib/__tests__/job-polling.test.ts +2 -2
- package/src/lib/__tests__/local-gateway-cli.test.ts +42 -0
- 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} +105 -99
- package/src/lib/cloud-login.ts +23 -24
- package/src/lib/{platform-releases.ts → cloud-releases.ts} +11 -9
- package/src/lib/cloud-values.ts +3 -0
- package/src/lib/color.ts +4 -0
- package/src/lib/config-utils.ts +1 -1
- package/src/lib/detached-process.ts +1 -1
- package/src/lib/docker.ts +300 -111
- package/src/lib/ensure-binary.ts +171 -0
- package/src/lib/environments/__tests__/paths.test.ts +29 -3
- package/src/lib/environments/__tests__/resolve.test.ts +26 -26
- package/src/lib/environments/paths.ts +16 -8
- package/src/lib/environments/resolve.ts +10 -12
- package/src/lib/flag-args.test.ts +1 -1
- package/src/lib/flag-args.ts +1 -1
- package/src/lib/guardian-token.ts +37 -5
- package/src/lib/hatch-local.ts +32 -29
- package/src/lib/hatch-next-steps.ts +5 -9
- package/src/lib/health-check.ts +6 -6
- package/src/lib/ingress-config.ts +8 -35
- package/src/lib/job-polling.ts +2 -2
- 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 +46 -34
- package/src/lib/nginx-ingress.ts +40 -15
- package/src/lib/pair.ts +1 -1
- 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/step-runner.ts +6 -1
- package/src/lib/sync-cloud-assistants.ts +19 -19
- 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} +36 -15
- package/src/lib/tunnels/gateway-notify.ts +61 -0
- package/src/lib/{ngrok.ts → tunnels/ngrok.ts} +350 -89
- package/src/lib/{tailscale-tunnel.ts → tunnels/tailscale.ts} +53 -9
- package/src/lib/upgrade-lifecycle.ts +8 -21
- 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/node_modules/@mycadet/assistant-api/events/show-platform-login.ts +0 -20
- 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,7 @@ export interface CredentialInjectionParams {
|
|
|
594
603
|
gatewayUrl: string;
|
|
595
604
|
bearerToken?: string;
|
|
596
605
|
assistantApiKey?: string | null;
|
|
597
|
-
|
|
598
|
-
platformBaseUrl: string;
|
|
606
|
+
cloudAssistantId: string;
|
|
599
607
|
organizationId: string;
|
|
600
608
|
userId?: string;
|
|
601
609
|
webhookSecret?: string | null;
|
|
@@ -622,8 +630,6 @@ export async function injectCredentialsIntoAssistant(
|
|
|
622
630
|
promises.push(inject("cadet:assistant_api_key", params.assistantApiKey));
|
|
623
631
|
}
|
|
624
632
|
|
|
625
|
-
promises.push(inject("cadet:platform_base_url", params.platformBaseUrl));
|
|
626
|
-
|
|
627
633
|
if (params.webhookSecret) {
|
|
628
634
|
promises.push(inject("cadet:webhook_secret", params.webhookSecret));
|
|
629
635
|
}
|
|
@@ -634,14 +640,14 @@ export async function injectCredentialsIntoAssistant(
|
|
|
634
640
|
|
|
635
641
|
export async function hatchAssistant(
|
|
636
642
|
token: string,
|
|
637
|
-
|
|
643
|
+
cloudUrl?: string,
|
|
638
644
|
): Promise<HatchAssistantResult> {
|
|
639
|
-
const resolvedUrl =
|
|
645
|
+
const resolvedUrl = cloudUrl || getCloudUrl();
|
|
640
646
|
const url = `${resolvedUrl}/v1/assistants/hatch/`;
|
|
641
647
|
|
|
642
648
|
const response = await loopbackSafeFetch(url, {
|
|
643
649
|
method: "POST",
|
|
644
|
-
headers: await authHeaders(token,
|
|
650
|
+
headers: await authHeaders(token, cloudUrl),
|
|
645
651
|
body: JSON.stringify({}),
|
|
646
652
|
signal: AbortSignal.timeout(300_000),
|
|
647
653
|
});
|
|
@@ -652,7 +658,7 @@ export async function hatchAssistant(
|
|
|
652
658
|
}
|
|
653
659
|
|
|
654
660
|
if (response.status === 401 || response.status === 403) {
|
|
655
|
-
const detail =
|
|
661
|
+
const detail = CloudErrorDetailSchema
|
|
656
662
|
.catch({})
|
|
657
663
|
.parse(await response.json().catch(() => ({})));
|
|
658
664
|
throw new Error(
|
|
@@ -665,7 +671,7 @@ export async function hatchAssistant(
|
|
|
665
671
|
throw new Error("Insufficient balance to hatch a new assistant.");
|
|
666
672
|
}
|
|
667
673
|
|
|
668
|
-
const errorBody =
|
|
674
|
+
const errorBody = CloudErrorDetailSchema
|
|
669
675
|
.catch({})
|
|
670
676
|
.parse(await response.json().catch(() => ({})));
|
|
671
677
|
throw new Error(
|
|
@@ -677,18 +683,18 @@ export async function hatchAssistant(
|
|
|
677
683
|
const PLATFORM_FETCH_TIMEOUT_MS = 10_000;
|
|
678
684
|
|
|
679
685
|
/**
|
|
680
|
-
* Lightweight pre-check: returns the first active
|
|
686
|
+
* Lightweight pre-check: returns the first active cloud assistant for the
|
|
681
687
|
* authenticated user, or `null` if none exists. Calls `GET /v1/assistants/`
|
|
682
688
|
* and looks for any assistant with status "active".
|
|
683
689
|
*
|
|
684
690
|
* Used by the teleport flow to block BEFORE the expensive GCS upload when
|
|
685
|
-
* the user already has a
|
|
691
|
+
* the user already has a cloud assistant.
|
|
686
692
|
*/
|
|
687
|
-
export async function
|
|
693
|
+
export async function checkExistingCloudAssistant(
|
|
688
694
|
token: string,
|
|
689
|
-
|
|
695
|
+
cloudUrl?: string,
|
|
690
696
|
): Promise<HatchedAssistant | null> {
|
|
691
|
-
const resolvedUrl =
|
|
697
|
+
const resolvedUrl = cloudUrl || getCloudUrl();
|
|
692
698
|
const url = `${resolvedUrl}/v1/assistants/`;
|
|
693
699
|
|
|
694
700
|
const controller = new AbortController();
|
|
@@ -700,7 +706,7 @@ export async function checkExistingPlatformAssistant(
|
|
|
700
706
|
try {
|
|
701
707
|
const response = await loopbackSafeFetch(url, {
|
|
702
708
|
signal: controller.signal,
|
|
703
|
-
headers: await authHeaders(token,
|
|
709
|
+
headers: await authHeaders(token, cloudUrl),
|
|
704
710
|
});
|
|
705
711
|
|
|
706
712
|
if (!response.ok) {
|
|
@@ -717,14 +723,14 @@ export async function checkExistingPlatformAssistant(
|
|
|
717
723
|
}
|
|
718
724
|
|
|
719
725
|
/**
|
|
720
|
-
* Fetch all active assistants for the authenticated user from the
|
|
726
|
+
* Fetch all active assistants for the authenticated user from the cloud.
|
|
721
727
|
* Returns an empty array on failure (non-fatal).
|
|
722
728
|
*/
|
|
723
|
-
export async function
|
|
729
|
+
export async function fetchCloudAssistants(
|
|
724
730
|
token: string,
|
|
725
|
-
|
|
731
|
+
cloudUrl?: string,
|
|
726
732
|
): Promise<HatchedAssistant[]> {
|
|
727
|
-
const resolvedUrl =
|
|
733
|
+
const resolvedUrl = cloudUrl || getCloudUrl();
|
|
728
734
|
const url = `${resolvedUrl}/v1/assistants/`;
|
|
729
735
|
|
|
730
736
|
const controller = new AbortController();
|
|
@@ -736,7 +742,7 @@ export async function fetchPlatformAssistants(
|
|
|
736
742
|
try {
|
|
737
743
|
const response = await loopbackSafeFetch(url, {
|
|
738
744
|
signal: controller.signal,
|
|
739
|
-
headers: await authHeaders(token,
|
|
745
|
+
headers: await authHeaders(token, cloudUrl),
|
|
740
746
|
});
|
|
741
747
|
|
|
742
748
|
if (!response.ok) return [];
|
|
@@ -750,7 +756,7 @@ export async function fetchPlatformAssistants(
|
|
|
750
756
|
}
|
|
751
757
|
}
|
|
752
758
|
|
|
753
|
-
export interface
|
|
759
|
+
export interface CloudAssistantDetail {
|
|
754
760
|
currentReleaseVersion: string | null;
|
|
755
761
|
releaseChannel: "stable" | "preview";
|
|
756
762
|
}
|
|
@@ -763,15 +769,15 @@ export interface PlatformAssistantDetail {
|
|
|
763
769
|
export async function fetchAssistantDetail(
|
|
764
770
|
token: string,
|
|
765
771
|
assistantId: string,
|
|
766
|
-
|
|
767
|
-
): Promise<
|
|
768
|
-
const resolvedUrl =
|
|
772
|
+
cloudUrl?: string,
|
|
773
|
+
): Promise<CloudAssistantDetail | null> {
|
|
774
|
+
const resolvedUrl = cloudUrl || getCloudUrl();
|
|
769
775
|
const url = `${resolvedUrl}/v1/assistants/${encodeURIComponent(assistantId)}/`;
|
|
770
776
|
|
|
771
777
|
try {
|
|
772
778
|
const response = await loopbackSafeFetch(url, {
|
|
773
779
|
signal: AbortSignal.timeout(PLATFORM_FETCH_TIMEOUT_MS),
|
|
774
|
-
headers: await authHeaders(token,
|
|
780
|
+
headers: await authHeaders(token, cloudUrl),
|
|
775
781
|
});
|
|
776
782
|
|
|
777
783
|
if (!response.ok) return null;
|
|
@@ -790,7 +796,7 @@ export async function fetchAssistantDetail(
|
|
|
790
796
|
}
|
|
791
797
|
|
|
792
798
|
/**
|
|
793
|
-
* Check whether the
|
|
799
|
+
* Check whether the cloud reports an upgrade in progress for the
|
|
794
800
|
* assistant via `GET /v1/assistants/{id}/upgrade-status/`. Returns null
|
|
795
801
|
* when the endpoint is unavailable (404 on older platforms, network
|
|
796
802
|
* error) so callers can skip the check. Never throws.
|
|
@@ -798,15 +804,15 @@ export async function fetchAssistantDetail(
|
|
|
798
804
|
export async function fetchUpgradeInProgress(
|
|
799
805
|
token: string,
|
|
800
806
|
assistantId: string,
|
|
801
|
-
|
|
807
|
+
cloudUrl?: string,
|
|
802
808
|
): Promise<boolean | null> {
|
|
803
|
-
const resolvedUrl =
|
|
809
|
+
const resolvedUrl = cloudUrl || getCloudUrl();
|
|
804
810
|
const url = `${resolvedUrl}/v1/assistants/${encodeURIComponent(assistantId)}/upgrade-status/`;
|
|
805
811
|
|
|
806
812
|
try {
|
|
807
813
|
const response = await loopbackSafeFetch(url, {
|
|
808
814
|
signal: AbortSignal.timeout(PLATFORM_FETCH_TIMEOUT_MS),
|
|
809
|
-
headers: await authHeaders(token,
|
|
815
|
+
headers: await authHeaders(token, cloudUrl),
|
|
810
816
|
});
|
|
811
817
|
|
|
812
818
|
if (!response.ok) return null;
|
|
@@ -828,9 +834,9 @@ export interface PlatformUser {
|
|
|
828
834
|
|
|
829
835
|
export async function fetchOrganizationId(
|
|
830
836
|
token: string,
|
|
831
|
-
|
|
837
|
+
cloudUrl?: string,
|
|
832
838
|
): Promise<string> {
|
|
833
|
-
const resolvedUrl =
|
|
839
|
+
const resolvedUrl = cloudUrl || getCloudUrl();
|
|
834
840
|
const url = `${resolvedUrl}/v1/organizations/`;
|
|
835
841
|
|
|
836
842
|
const controller = new AbortController();
|
|
@@ -866,9 +872,9 @@ export async function fetchOrganizationId(
|
|
|
866
872
|
|
|
867
873
|
export async function fetchCurrentUser(
|
|
868
874
|
token: string,
|
|
869
|
-
|
|
875
|
+
cloudUrl?: string,
|
|
870
876
|
): Promise<PlatformUser> {
|
|
871
|
-
const resolvedUrl =
|
|
877
|
+
const resolvedUrl = cloudUrl || getCloudUrl();
|
|
872
878
|
const url = `${resolvedUrl}/_allauth/app/v1/auth/session`;
|
|
873
879
|
|
|
874
880
|
const controller = new AbortController();
|
|
@@ -907,18 +913,18 @@ export async function fetchCurrentUser(
|
|
|
907
913
|
// Rollback
|
|
908
914
|
// ---------------------------------------------------------------------------
|
|
909
915
|
|
|
910
|
-
export async function
|
|
916
|
+
export async function rollbackCloudAssistant(
|
|
911
917
|
token: string,
|
|
912
918
|
assistantId: string,
|
|
913
919
|
version?: string,
|
|
914
|
-
|
|
920
|
+
cloudUrl?: string,
|
|
915
921
|
): Promise<{ detail: string; version: string | null }> {
|
|
916
|
-
const resolvedUrl =
|
|
922
|
+
const resolvedUrl = cloudUrl || getCloudUrl();
|
|
917
923
|
const response = await loopbackSafeFetch(
|
|
918
924
|
`${resolvedUrl}/v1/assistants/${encodeURIComponent(assistantId)}/rollback/`,
|
|
919
925
|
{
|
|
920
926
|
method: "POST",
|
|
921
|
-
headers: await authHeaders(token,
|
|
927
|
+
headers: await authHeaders(token, cloudUrl),
|
|
922
928
|
body: JSON.stringify(version ? { version } : {}),
|
|
923
929
|
},
|
|
924
930
|
);
|
|
@@ -973,14 +979,14 @@ export async function platformUploadToSignedUrl(
|
|
|
973
979
|
export async function platformImportPreflightFromGcs(
|
|
974
980
|
bundleKey: string,
|
|
975
981
|
token: string,
|
|
976
|
-
|
|
982
|
+
cloudUrl?: string,
|
|
977
983
|
): Promise<{ statusCode: number; body: Record<string, unknown> }> {
|
|
978
|
-
const resolvedUrl =
|
|
984
|
+
const resolvedUrl = cloudUrl || getCloudUrl();
|
|
979
985
|
const response = await loopbackSafeFetch(
|
|
980
986
|
`${resolvedUrl}/v1/migrations/import-preflight-from-gcs/`,
|
|
981
987
|
{
|
|
982
988
|
method: "POST",
|
|
983
|
-
headers: await authHeaders(token,
|
|
989
|
+
headers: await authHeaders(token, cloudUrl),
|
|
984
990
|
signal: AbortSignal.timeout(120_000),
|
|
985
991
|
body: JSON.stringify({ bundle_key: bundleKey }),
|
|
986
992
|
},
|
|
@@ -995,14 +1001,14 @@ export async function platformImportPreflightFromGcs(
|
|
|
995
1001
|
export async function platformImportBundleFromGcs(
|
|
996
1002
|
bundleKey: string,
|
|
997
1003
|
token: string,
|
|
998
|
-
|
|
1004
|
+
cloudUrl?: string,
|
|
999
1005
|
): Promise<{ statusCode: number; body: Record<string, unknown> }> {
|
|
1000
|
-
const resolvedUrl =
|
|
1006
|
+
const resolvedUrl = cloudUrl || getCloudUrl();
|
|
1001
1007
|
const response = await loopbackSafeFetch(
|
|
1002
1008
|
`${resolvedUrl}/v1/migrations/import-from-gcs/`,
|
|
1003
1009
|
{
|
|
1004
1010
|
method: "POST",
|
|
1005
|
-
headers: await authHeaders(token,
|
|
1011
|
+
headers: await authHeaders(token, cloudUrl),
|
|
1006
1012
|
body: JSON.stringify({ bundle_key: bundleKey }),
|
|
1007
1013
|
signal: AbortSignal.timeout(60_000),
|
|
1008
1014
|
},
|
|
@@ -1024,7 +1030,7 @@ export async function platformImportBundleFromGcs(
|
|
|
1024
1030
|
|
|
1025
1031
|
/**
|
|
1026
1032
|
* Discriminated union representing the unified migration job status shape
|
|
1027
|
-
* returned by `GET /v1/migrations/jobs/{job_id}/` on both the
|
|
1033
|
+
* returned by `GET /v1/migrations/jobs/{job_id}/` on both the cloud and
|
|
1028
1034
|
* the local runtime.
|
|
1029
1035
|
*/
|
|
1030
1036
|
export type UnifiedJobStatus =
|
|
@@ -1090,7 +1096,7 @@ export interface BundleCompatibility {
|
|
|
1090
1096
|
}
|
|
1091
1097
|
|
|
1092
1098
|
/**
|
|
1093
|
-
* Thrown by
|
|
1099
|
+
* Thrown by cloudRequestSignedUrl when the cloud rejects a download
|
|
1094
1100
|
* signed-URL request because the target runtime version is outside the
|
|
1095
1101
|
* ExportJob's [min_runtime_version, max_runtime_version] band. Terminal
|
|
1096
1102
|
* — callers must NOT retry; surface to the user and abort the
|
|
@@ -1124,7 +1130,7 @@ export class VersionMismatchError extends Error {
|
|
|
1124
1130
|
}
|
|
1125
1131
|
|
|
1126
1132
|
/**
|
|
1127
|
-
* Request a signed URL from the
|
|
1133
|
+
* Request a signed URL from the cloud for either uploading a new bundle
|
|
1128
1134
|
* or downloading an existing one. Calls `POST /v1/migrations/signed-url/`.
|
|
1129
1135
|
*
|
|
1130
1136
|
* - `operation: "upload"` (optionally with `contentType` / `contentLength`)
|
|
@@ -1138,7 +1144,7 @@ export class VersionMismatchError extends Error {
|
|
|
1138
1144
|
* Throws {@link VersionMismatchError} on a 422 `version_mismatch` response,
|
|
1139
1145
|
* which is terminal — callers must NOT retry.
|
|
1140
1146
|
*/
|
|
1141
|
-
export async function
|
|
1147
|
+
export async function cloudRequestSignedUrl(
|
|
1142
1148
|
params: {
|
|
1143
1149
|
operation: "upload" | "download";
|
|
1144
1150
|
bundleKey?: string;
|
|
@@ -1150,21 +1156,21 @@ export async function platformRequestSignedUrl(
|
|
|
1150
1156
|
// Target-side, download only: runtime version that will import.
|
|
1151
1157
|
targetRuntimeVersion?: string;
|
|
1152
1158
|
// Upload only: who will PUT to the URL. "runtime" signs against the
|
|
1153
|
-
// runtime-reachable storage endpoint so a
|
|
1159
|
+
// runtime-reachable storage endpoint so a cloud assistant pod can
|
|
1154
1160
|
// upload its export bundle (platform→local teleport); defaults to
|
|
1155
1161
|
// "client" server-side. No effect in production, where both endpoints
|
|
1156
1162
|
// are the same.
|
|
1157
1163
|
consumer?: "client" | "runtime";
|
|
1158
1164
|
},
|
|
1159
1165
|
token: string,
|
|
1160
|
-
|
|
1166
|
+
cloudUrl?: string,
|
|
1161
1167
|
): Promise<{
|
|
1162
1168
|
url: string;
|
|
1163
1169
|
bundleKey: string;
|
|
1164
1170
|
expiresAt: string;
|
|
1165
1171
|
maxContentLength?: number;
|
|
1166
1172
|
}> {
|
|
1167
|
-
const resolvedUrl =
|
|
1173
|
+
const resolvedUrl = cloudUrl || getCloudUrl();
|
|
1168
1174
|
const body: Record<string, unknown> = { operation: params.operation };
|
|
1169
1175
|
if (params.bundleKey !== undefined) body.bundle_key = params.bundleKey;
|
|
1170
1176
|
if (params.contentType !== undefined) body.content_type = params.contentType;
|
|
@@ -1189,7 +1195,7 @@ export async function platformRequestSignedUrl(
|
|
|
1189
1195
|
const doRequest = async (): Promise<Response> =>
|
|
1190
1196
|
loopbackSafeFetch(`${resolvedUrl}/v1/migrations/signed-url/`, {
|
|
1191
1197
|
method: "POST",
|
|
1192
|
-
headers: await authHeaders(token,
|
|
1198
|
+
headers: await authHeaders(token, cloudUrl),
|
|
1193
1199
|
body: JSON.stringify(body),
|
|
1194
1200
|
});
|
|
1195
1201
|
|
|
@@ -1200,7 +1206,7 @@ export async function platformRequestSignedUrl(
|
|
|
1200
1206
|
// lookup. For session-token callers, a 401 frequently means the
|
|
1201
1207
|
// cached org ID is stale — calling doRequest() again without clearing
|
|
1202
1208
|
// the cache would just send the same stale header and fail again.
|
|
1203
|
-
invalidateOrgIdCache(token,
|
|
1209
|
+
invalidateOrgIdCache(token, cloudUrl);
|
|
1204
1210
|
response = await doRequest();
|
|
1205
1211
|
}
|
|
1206
1212
|
|
|
@@ -1240,19 +1246,19 @@ export async function platformRequestSignedUrl(
|
|
|
1240
1246
|
}
|
|
1241
1247
|
|
|
1242
1248
|
/**
|
|
1243
|
-
* Poll the unified job-status endpoint on the
|
|
1249
|
+
* Poll the unified job-status endpoint on the cloud. Calls
|
|
1244
1250
|
* `GET /v1/migrations/jobs/{jobId}/` and parses into {@link UnifiedJobStatus}.
|
|
1245
1251
|
*/
|
|
1246
1252
|
export async function platformPollJobStatus(
|
|
1247
1253
|
jobId: string,
|
|
1248
1254
|
token: string,
|
|
1249
|
-
|
|
1255
|
+
cloudUrl?: string,
|
|
1250
1256
|
): Promise<UnifiedJobStatus> {
|
|
1251
|
-
const resolvedUrl =
|
|
1257
|
+
const resolvedUrl = cloudUrl || getCloudUrl();
|
|
1252
1258
|
const response = await loopbackSafeFetch(
|
|
1253
1259
|
`${resolvedUrl}/v1/migrations/jobs/${jobId}/`,
|
|
1254
1260
|
{
|
|
1255
|
-
headers: await authHeaders(token,
|
|
1261
|
+
headers: await authHeaders(token, cloudUrl),
|
|
1256
1262
|
},
|
|
1257
1263
|
);
|
|
1258
1264
|
|