@vellumai/cli 0.8.4 → 0.8.6

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.
Files changed (43) hide show
  1. package/AGENTS.md +17 -1
  2. package/knip.json +2 -1
  3. package/package.json +1 -1
  4. package/src/__tests__/api-key-check.test.ts +78 -0
  5. package/src/__tests__/backup.test.ts +38 -0
  6. package/src/__tests__/recover.test.ts +307 -0
  7. package/src/__tests__/retire.test.ts +241 -0
  8. package/src/__tests__/wake.test.ts +215 -0
  9. package/src/commands/backup.ts +2 -0
  10. package/src/commands/client.ts +62 -32
  11. package/src/commands/flags.ts +197 -0
  12. package/src/commands/gateway/token.ts +73 -0
  13. package/src/commands/gateway.ts +29 -0
  14. package/src/commands/logs.ts +6 -18
  15. package/src/commands/ps.ts +41 -41
  16. package/src/commands/recover.ts +47 -9
  17. package/src/commands/restore.ts +8 -1
  18. package/src/commands/retire.ts +145 -55
  19. package/src/commands/roadmap.ts +449 -0
  20. package/src/commands/rollback.ts +2 -14
  21. package/src/commands/ssh.ts +5 -24
  22. package/src/commands/teleport.ts +34 -26
  23. package/src/commands/upgrade.ts +8 -16
  24. package/src/commands/wake.ts +68 -45
  25. package/src/index.ts +9 -0
  26. package/src/lib/__tests__/port-allocator.test.ts +117 -0
  27. package/src/lib/__tests__/step-runner.test.ts +133 -0
  28. package/src/lib/api-key-check.ts +40 -0
  29. package/src/lib/assistant-config.ts +13 -0
  30. package/src/lib/config-utils.ts +24 -3
  31. package/src/lib/docker.ts +72 -8
  32. package/src/lib/hatch-local.ts +15 -2
  33. package/src/lib/http-client.ts +1 -3
  34. package/src/lib/local.ts +173 -292
  35. package/src/lib/orphan-detection.ts +9 -5
  36. package/src/lib/pgrep.ts +5 -1
  37. package/src/lib/platform-client.ts +97 -49
  38. package/src/lib/port-allocator.ts +93 -0
  39. package/src/lib/process.ts +109 -39
  40. package/src/lib/statefulset.ts +0 -10
  41. package/src/lib/step-runner.ts +102 -9
  42. package/src/lib/sync-cloud-assistants.ts +17 -0
  43. package/src/shared/provider-env-vars.ts +1 -0
@@ -0,0 +1,449 @@
1
+ import { readPlatformToken, getWebUrl } from "../lib/platform-client.js";
2
+
3
+ function printUsage(): void {
4
+ console.log("Usage: vellum roadmap <subcommand>");
5
+ console.log("");
6
+ console.log("Manage roadmap items.");
7
+ console.log("");
8
+ console.log("Subcommands:");
9
+ console.log(
10
+ " list [--query <q>] [--status <s>] [--tag <slug>] [--sort upvotes|created] [--limit <n>]",
11
+ );
12
+ console.log(" get <slug>");
13
+ console.log(
14
+ " create --title <title> [--description <desc>] [--tag <slug>...]",
15
+ );
16
+ console.log(
17
+ " update <slug> [--title <title>] [--description <desc>] [--status <s>] [--tag <slug>...]",
18
+ );
19
+ console.log(" delete <slug>");
20
+ console.log(" upvote <slug>");
21
+ console.log(" unvote <slug>");
22
+ console.log("");
23
+ console.log("Examples:");
24
+ console.log(' $ vellum roadmap list --query "dark mode"');
25
+ console.log(" $ vellum roadmap list --status planned --sort upvotes");
26
+ console.log(" $ vellum roadmap get my-feature-slug");
27
+ console.log(' $ vellum roadmap create --title "Add dark mode"');
28
+ console.log(
29
+ ' $ vellum roadmap update my-feature --status planned --tag integrations',
30
+ );
31
+ console.log(" $ vellum roadmap upvote my-feature-slug");
32
+ }
33
+
34
+ function consumeValue(args: string[], i: number, flag: string): string {
35
+ const next = args[i + 1];
36
+ if (next === undefined || next.startsWith("--")) {
37
+ console.error(`Error: ${flag} requires a value.`);
38
+ process.exit(1);
39
+ }
40
+ return next;
41
+ }
42
+
43
+ function requireAuth(): string {
44
+ const token = readPlatformToken();
45
+ if (!token) {
46
+ console.error("Not logged in. Run `vellum login` first.");
47
+ process.exit(1);
48
+ }
49
+ return token;
50
+ }
51
+
52
+ function requireSlug(args: string[], command: string): string {
53
+ const slug = args[0];
54
+ if (!slug || slug.startsWith("--")) {
55
+ console.error(`Usage: vellum roadmap ${command} <slug>`);
56
+ process.exit(1);
57
+ }
58
+ return slug;
59
+ }
60
+
61
+ // eslint-disable-next-line no-control-regex
62
+ const ANSI_RE = /[\x00-\x08\x0b-\x1f\x7f]|\x1b(?:\[[0-9;]*[A-Za-z]|\].*?(?:\x07|\x1b\\))/g;
63
+ function sanitize(text: string): string {
64
+ return text.replace(ANSI_RE, "");
65
+ }
66
+
67
+ function makeLink(url: string): string {
68
+ return `\x1b]8;;${url}\x1b\\${url}\x1b]8;;\x1b\\`;
69
+ }
70
+
71
+ async function apiFetch(
72
+ path: string,
73
+ options: {
74
+ method?: string;
75
+ token?: string;
76
+ body?: Record<string, unknown>;
77
+ params?: Record<string, string>;
78
+ } = {},
79
+ ): Promise<Response> {
80
+ const webUrl = getWebUrl();
81
+ let url = `${webUrl}/api/marketing${path}`;
82
+ if (options.params) {
83
+ const qs = new URLSearchParams(options.params).toString();
84
+ if (qs) url += `?${qs}`;
85
+ }
86
+
87
+ const headers: Record<string, string> = {};
88
+ if (options.token) headers["X-Session-Token"] = options.token;
89
+ if (options.body) headers["Content-Type"] = "application/json";
90
+
91
+ return fetch(url, {
92
+ method: options.method ?? "GET",
93
+ headers,
94
+ body: options.body ? JSON.stringify(options.body) : undefined,
95
+ });
96
+ }
97
+
98
+ async function handleError(
99
+ response: Response,
100
+ action: string,
101
+ ): Promise<never> {
102
+ const text = await response.text().catch(() => "");
103
+ console.error(`Failed to ${action} (${response.status}): ${text}`);
104
+ process.exit(1);
105
+ }
106
+
107
+ // ── list ──
108
+
109
+ interface ListItem {
110
+ slug: string;
111
+ title: string;
112
+ status: string;
113
+ upvote_count: number;
114
+ comment_count: number;
115
+ tags: { slug: string; name: string }[];
116
+ viewer_upvoted: boolean | null;
117
+ }
118
+
119
+ async function roadmapList(args: string[]): Promise<void> {
120
+ const params: Record<string, string> = {};
121
+ const token = readPlatformToken() ?? undefined;
122
+
123
+ for (let i = 0; i < args.length; i++) {
124
+ switch (args[i]) {
125
+ case "--query":
126
+ case "-q":
127
+ params.q = consumeValue(args, i, "--query");
128
+ i++;
129
+ break;
130
+ case "--status":
131
+ params.status = consumeValue(args, i, "--status");
132
+ i++;
133
+ break;
134
+ case "--tag":
135
+ params.tag = consumeValue(args, i, "--tag");
136
+ i++;
137
+ break;
138
+ case "--sort":
139
+ params.sort = consumeValue(args, i, "--sort");
140
+ i++;
141
+ break;
142
+ case "--limit":
143
+ params.limit = consumeValue(args, i, "--limit");
144
+ i++;
145
+ break;
146
+ case "--offset":
147
+ params.offset = consumeValue(args, i, "--offset");
148
+ i++;
149
+ break;
150
+ }
151
+ }
152
+
153
+ const response = await apiFetch("/v1/roadmap", { params, token });
154
+ if (!response.ok) return handleError(response, "list roadmap items");
155
+
156
+ const data = (await response.json()) as {
157
+ items: ListItem[];
158
+ total: number;
159
+ };
160
+
161
+ if (data.items.length === 0) {
162
+ console.log("No roadmap items found.");
163
+ return;
164
+ }
165
+
166
+ const webUrl = getWebUrl();
167
+ console.log(`Showing ${data.items.length} of ${data.total} items:\n`);
168
+
169
+ for (const item of data.items) {
170
+ const upvoted = item.viewer_upvoted ? " (upvoted)" : "";
171
+ const tags = item.tags.length > 0
172
+ ? ` [${item.tags.map((t) => sanitize(t.slug)).join(", ")}]`
173
+ : "";
174
+ console.log(
175
+ ` ${sanitize(item.title)} ▲${item.upvote_count}${upvoted} 💬${item.comment_count} ${item.status}${tags}`,
176
+ );
177
+ console.log(` ${makeLink(`${webUrl}/roadmap/${item.slug}`)}`);
178
+ }
179
+ }
180
+
181
+ // ── get ──
182
+
183
+ async function roadmapGet(args: string[]): Promise<void> {
184
+ const slug = requireSlug(args, "get");
185
+ const token = readPlatformToken() ?? undefined;
186
+ const response = await apiFetch(`/v1/roadmap/${slug}`, { token });
187
+ if (!response.ok) return handleError(response, "get roadmap item");
188
+
189
+ const item = (await response.json()) as {
190
+ slug: string;
191
+ title: string;
192
+ description: string;
193
+ status: string;
194
+ upvote_count: number;
195
+ comment_count: number;
196
+ tags: { slug: string; name: string }[];
197
+ viewer_upvoted: boolean | null;
198
+ creator_username: string;
199
+ created: string;
200
+ comments: {
201
+ id: string;
202
+ author_username: string;
203
+ author_is_staff: boolean;
204
+ body: string;
205
+ created: string;
206
+ }[];
207
+ };
208
+
209
+ const webUrl = getWebUrl();
210
+ const upvoted = item.viewer_upvoted ? " (upvoted)" : "";
211
+ const tags =
212
+ item.tags.length > 0
213
+ ? item.tags.map((t) => sanitize(t.slug)).join(", ")
214
+ : "none";
215
+
216
+ console.log(sanitize(item.title));
217
+ console.log(` slug: ${item.slug}`);
218
+ console.log(` status: ${item.status}`);
219
+ console.log(` upvotes: ${item.upvote_count}${upvoted}`);
220
+ console.log(` tags: ${tags}`);
221
+ console.log(` by: ${sanitize(item.creator_username)}`);
222
+ console.log(` created: ${item.created}`);
223
+ console.log(` url: ${makeLink(`${webUrl}/roadmap/${item.slug}`)}`);
224
+ if (item.description) {
225
+ console.log(`\n${sanitize(item.description)}`);
226
+ }
227
+
228
+ if (item.comments.length > 0) {
229
+ console.log(`\nComments (${item.comments.length}):`);
230
+ for (const c of item.comments) {
231
+ const staff = c.author_is_staff ? " [staff]" : "";
232
+ console.log(` ${sanitize(c.author_username)}${staff} (${c.created}):`);
233
+ console.log(` ${sanitize(c.body)}`);
234
+ }
235
+ }
236
+ }
237
+
238
+ // ── create ──
239
+
240
+ async function roadmapCreate(args: string[]): Promise<void> {
241
+ let title: string | undefined;
242
+ let description: string | undefined;
243
+ const tags: string[] = [];
244
+
245
+ for (let i = 0; i < args.length; i++) {
246
+ switch (args[i]) {
247
+ case "--title":
248
+ title = consumeValue(args, i, "--title");
249
+ i++;
250
+ break;
251
+ case "--description":
252
+ description = consumeValue(args, i, "--description");
253
+ i++;
254
+ break;
255
+ case "--tag":
256
+ tags.push(consumeValue(args, i, "--tag"));
257
+ i++;
258
+ break;
259
+ }
260
+ }
261
+
262
+ if (!title) {
263
+ console.error("Error: --title is required.");
264
+ console.error('Usage: vellum roadmap create --title "My feature request"');
265
+ process.exitCode = 1;
266
+ return;
267
+ }
268
+
269
+ const token = requireAuth();
270
+ const body: Record<string, unknown> = { title };
271
+ if (description) body.description = description;
272
+ if (tags.length > 0) body.tags = tags;
273
+
274
+ const response = await apiFetch("/v1/roadmap", {
275
+ method: "POST",
276
+ token,
277
+ body,
278
+ });
279
+ if (!response.ok) return handleError(response, "create roadmap item");
280
+
281
+ const item = (await response.json()) as {
282
+ slug: string;
283
+ title: string;
284
+ status: string;
285
+ };
286
+
287
+ const webUrl = getWebUrl();
288
+ console.log(`Created roadmap item: ${sanitize(item.title)}`);
289
+ console.log(` slug: ${item.slug}`);
290
+ console.log(` status: ${item.status}`);
291
+ console.log(` url: ${makeLink(`${webUrl}/roadmap/${item.slug}`)}`);
292
+ }
293
+
294
+ // ── update ──
295
+
296
+ async function roadmapUpdate(args: string[]): Promise<void> {
297
+ const slug = requireSlug(args, "update");
298
+
299
+ let title: string | undefined;
300
+ let description: string | undefined;
301
+ let status: string | undefined;
302
+ const tags: string[] = [];
303
+
304
+ for (let i = 1; i < args.length; i++) {
305
+ switch (args[i]) {
306
+ case "--title":
307
+ title = consumeValue(args, i, "--title");
308
+ i++;
309
+ break;
310
+ case "--description":
311
+ description = consumeValue(args, i, "--description");
312
+ i++;
313
+ break;
314
+ case "--status":
315
+ status = consumeValue(args, i, "--status");
316
+ i++;
317
+ break;
318
+ case "--tag":
319
+ tags.push(consumeValue(args, i, "--tag"));
320
+ i++;
321
+ break;
322
+ }
323
+ }
324
+
325
+ const body: Record<string, unknown> = {};
326
+ if (title !== undefined) body.title = title;
327
+ if (description !== undefined) body.description = description;
328
+ if (status !== undefined) body.status = status;
329
+ if (tags.length > 0) body.tags = tags;
330
+
331
+ if (Object.keys(body).length === 0) {
332
+ console.error("Error: at least one field to update is required.");
333
+ process.exitCode = 1;
334
+ return;
335
+ }
336
+
337
+ const token = requireAuth();
338
+ const response = await apiFetch(`/v1/roadmap/${slug}`, {
339
+ method: "PATCH",
340
+ token,
341
+ body,
342
+ });
343
+ if (!response.ok) return handleError(response, "update roadmap item");
344
+
345
+ const item = (await response.json()) as {
346
+ slug: string;
347
+ title: string;
348
+ status: string;
349
+ };
350
+
351
+ const webUrl = getWebUrl();
352
+ console.log(`Updated roadmap item: ${sanitize(item.title)}`);
353
+ console.log(` slug: ${item.slug}`);
354
+ console.log(` status: ${item.status}`);
355
+ console.log(` url: ${makeLink(`${webUrl}/roadmap/${item.slug}`)}`);
356
+ }
357
+
358
+ // ── delete ──
359
+
360
+ async function roadmapDelete(args: string[]): Promise<void> {
361
+ const slug = requireSlug(args, "delete");
362
+ const token = requireAuth();
363
+ const response = await apiFetch(`/v1/roadmap/${slug}`, {
364
+ method: "DELETE",
365
+ token,
366
+ });
367
+ if (!response.ok) return handleError(response, "delete roadmap item");
368
+
369
+ console.log(`Deleted roadmap item: ${slug}`);
370
+ }
371
+
372
+ // ── upvote / unvote ──
373
+
374
+ async function roadmapUpvote(args: string[]): Promise<void> {
375
+ const slug = requireSlug(args, "upvote");
376
+ const token = requireAuth();
377
+ const response = await apiFetch(`/v1/roadmap/${slug}/upvote`, {
378
+ method: "POST",
379
+ token,
380
+ });
381
+ if (!response.ok) return handleError(response, "upvote roadmap item");
382
+
383
+ const data = (await response.json()) as {
384
+ slug: string;
385
+ upvote_count: number;
386
+ };
387
+
388
+ console.log(`Upvoted: ${data.slug} (${data.upvote_count} total)`);
389
+ }
390
+
391
+ async function roadmapUnvote(args: string[]): Promise<void> {
392
+ const slug = requireSlug(args, "unvote");
393
+ const token = requireAuth();
394
+ const response = await apiFetch(`/v1/roadmap/${slug}/upvote`, {
395
+ method: "DELETE",
396
+ token,
397
+ });
398
+ if (!response.ok) return handleError(response, "remove upvote");
399
+
400
+ const data = (await response.json()) as {
401
+ slug: string;
402
+ upvote_count: number;
403
+ };
404
+
405
+ console.log(`Removed upvote: ${data.slug} (${data.upvote_count} total)`);
406
+ }
407
+
408
+ // ── main ──
409
+
410
+ export async function roadmap(): Promise<void> {
411
+ const args = process.argv.slice(3);
412
+ const sub = args[0];
413
+
414
+ if (!sub || sub === "--help" || sub === "-h") {
415
+ printUsage();
416
+ return;
417
+ }
418
+
419
+ switch (sub) {
420
+ case "list":
421
+ case "ls":
422
+ await roadmapList(args.slice(1));
423
+ break;
424
+ case "get":
425
+ case "show":
426
+ await roadmapGet(args.slice(1));
427
+ break;
428
+ case "create":
429
+ await roadmapCreate(args.slice(1));
430
+ break;
431
+ case "update":
432
+ await roadmapUpdate(args.slice(1));
433
+ break;
434
+ case "delete":
435
+ case "rm":
436
+ await roadmapDelete(args.slice(1));
437
+ break;
438
+ case "upvote":
439
+ await roadmapUpvote(args.slice(1));
440
+ break;
441
+ case "unvote":
442
+ await roadmapUnvote(args.slice(1));
443
+ break;
444
+ default:
445
+ console.error(`Unknown subcommand: ${sub}`);
446
+ printUsage();
447
+ process.exitCode = 1;
448
+ }
449
+ }
@@ -4,9 +4,10 @@ import {
4
4
  findAssistantByName,
5
5
  getActiveAssistant,
6
6
  loadAllAssistants,
7
+ resolveCloud,
7
8
  saveAssistantEntry,
9
+ type AssistantEntry,
8
10
  } from "../lib/assistant-config";
9
- import type { AssistantEntry } from "../lib/assistant-config";
10
11
  import {
11
12
  captureImageRefs,
12
13
  GATEWAY_INTERNAL_PORT,
@@ -90,19 +91,6 @@ function parseArgs(): { name: string | null; version: string | null } {
90
91
  return { name, version };
91
92
  }
92
93
 
93
- function resolveCloud(entry: AssistantEntry): string {
94
- if (entry.cloud) {
95
- return entry.cloud;
96
- }
97
- if (entry.project) {
98
- return "gcp";
99
- }
100
- if (entry.sshUser) {
101
- return "custom";
102
- }
103
- return "local";
104
- }
105
-
106
94
  /**
107
95
  * Resolve which assistant to target for the rollback command. Priority:
108
96
  * 1. Explicit name argument
@@ -1,7 +1,10 @@
1
1
  import { spawn } from "child_process";
2
2
 
3
- import { resolveAssistant } from "../lib/assistant-config";
4
- import type { AssistantEntry } from "../lib/assistant-config";
3
+ import {
4
+ extractHostFromUrl,
5
+ resolveAssistant,
6
+ resolveCloud,
7
+ } from "../lib/assistant-config";
5
8
  import { dockerResourceNames } from "../lib/docker";
6
9
  import { getPlatformUrl, readPlatformToken } from "../lib/platform-client";
7
10
  import { sshAppleContainer } from "../lib/ssh-apple-container";
@@ -18,28 +21,6 @@ const SSH_OPTS = [
18
21
  "LogLevel=ERROR",
19
22
  ];
20
23
 
21
- function resolveCloud(entry: AssistantEntry): string {
22
- if (entry.cloud) {
23
- return entry.cloud;
24
- }
25
- if (entry.project) {
26
- return "gcp";
27
- }
28
- if (entry.sshUser) {
29
- return "custom";
30
- }
31
- return "local";
32
- }
33
-
34
- function extractHostFromUrl(url: string): string {
35
- try {
36
- const parsed = new URL(url);
37
- return parsed.hostname;
38
- } catch {
39
- return url.replace(/^https?:\/\//, "").split(":")[0];
40
- }
41
- }
42
-
43
24
  export async function ssh(): Promise<void> {
44
25
  const args = process.argv.slice(3);
45
26
  if (args.includes("--help") || args.includes("-h")) {
@@ -3,10 +3,11 @@ import {
3
3
  loadAllAssistants,
4
4
  getDaemonPidPath,
5
5
  removeAssistantEntry,
6
+ resolveCloud,
6
7
  saveAssistantEntry,
7
8
  setActiveAssistant,
9
+ type AssistantEntry,
8
10
  } from "../lib/assistant-config.js";
9
- import type { AssistantEntry } from "../lib/assistant-config.js";
10
11
  import {
11
12
  loadGuardianToken,
12
13
  leaseGuardianToken,
@@ -214,12 +215,6 @@ export function parseArgs(argv: string[]): {
214
215
  return { from, to, targetEnv, targetName, keepSource, dryRun, help };
215
216
  }
216
217
 
217
- function resolveCloud(entry: AssistantEntry): string {
218
- return (
219
- entry.cloud || (entry.project ? "gcp" : entry.sshUser ? "custom" : "local")
220
- );
221
- }
222
-
223
218
  // ---------------------------------------------------------------------------
224
219
  // Auth helper — same pattern as restore.ts
225
220
  // ---------------------------------------------------------------------------
@@ -228,7 +223,7 @@ async function getAccessToken(
228
223
  runtimeUrl: string,
229
224
  assistantId: string,
230
225
  displayName: string,
231
- options?: { forceRefresh?: boolean },
226
+ options?: { forceRefresh?: boolean; bootstrapSecret?: string },
232
227
  ): Promise<string> {
233
228
  // When forceRefresh is set (e.g. after a runtime 401 on the cached token)
234
229
  // we skip the cache and lease a brand-new token from the gateway, so a
@@ -242,7 +237,11 @@ async function getAccessToken(
242
237
  }
243
238
 
244
239
  try {
245
- const freshToken = await leaseGuardianToken(runtimeUrl, assistantId);
240
+ const freshToken = await leaseGuardianToken(
241
+ runtimeUrl,
242
+ assistantId,
243
+ options?.bootstrapSecret,
244
+ );
246
245
  return freshToken.accessToken;
247
246
  } catch (err) {
248
247
  const msg = err instanceof Error ? err.message : String(err);
@@ -281,11 +280,15 @@ function isRuntime401(err: unknown): boolean {
281
280
  * — propagates to the caller.
282
281
  */
283
282
  async function callRuntimeWithAuthRetry<T>(
284
- runtimeUrl: string,
285
- assistantId: string,
283
+ entry: AssistantEntry,
286
284
  fn: (token: string) => Promise<T>,
287
285
  ): Promise<T> {
288
- const firstToken = await getAccessToken(runtimeUrl, assistantId, assistantId);
286
+ const firstToken = await getAccessToken(
287
+ entry.runtimeUrl,
288
+ entry.assistantId,
289
+ entry.assistantId,
290
+ { bootstrapSecret: entry.guardianBootstrapSecret },
291
+ );
289
292
  try {
290
293
  return await fn(firstToken);
291
294
  } catch (err) {
@@ -293,10 +296,13 @@ async function callRuntimeWithAuthRetry<T>(
293
296
  throw err;
294
297
  }
295
298
  const refreshedToken = await getAccessToken(
296
- runtimeUrl,
297
- assistantId,
298
- assistantId,
299
- { forceRefresh: true },
299
+ entry.runtimeUrl,
300
+ entry.assistantId,
301
+ entry.assistantId,
302
+ {
303
+ forceRefresh: true,
304
+ bootstrapSecret: entry.guardianBootstrapSecret,
305
+ },
300
306
  );
301
307
  return await fn(refreshedToken);
302
308
  }
@@ -386,8 +392,7 @@ async function exportFromAssistant(
386
392
  let sourceRuntimeVersion: string;
387
393
  try {
388
394
  const identity = await callRuntimeWithAuthRetry(
389
- entry.runtimeUrl,
390
- entry.assistantId,
395
+ entry,
391
396
  async (token) => localRuntimeIdentity(entry, token),
392
397
  );
393
398
  sourceRuntimeVersion = identity.version;
@@ -423,8 +428,7 @@ async function exportFromAssistant(
423
428
  let accessToken: string;
424
429
  try {
425
430
  const result = await callRuntimeWithAuthRetry(
426
- entry.runtimeUrl,
427
- entry.assistantId,
431
+ entry,
428
432
  async (token) => {
429
433
  const r = await localRuntimeExportToGcs(entry, token, {
430
434
  uploadUrl,
@@ -462,7 +466,10 @@ async function exportFromAssistant(
462
466
  entry.runtimeUrl,
463
467
  entry.assistantId,
464
468
  entry.assistantId,
465
- { forceRefresh: true },
469
+ {
470
+ forceRefresh: true,
471
+ bootstrapSecret: entry.guardianBootstrapSecret,
472
+ },
466
473
  );
467
474
  },
468
475
  });
@@ -728,8 +735,7 @@ async function importToAssistant(
728
735
  let targetRuntimeVersion: string;
729
736
  try {
730
737
  const identity = await callRuntimeWithAuthRetry(
731
- entry.runtimeUrl,
732
- entry.assistantId,
738
+ entry,
733
739
  (token) => localRuntimeIdentity(entry, token),
734
740
  );
735
741
  targetRuntimeVersion = identity.version;
@@ -774,8 +780,7 @@ async function importToAssistant(
774
780
  let accessToken: string;
775
781
  try {
776
782
  const result = await callRuntimeWithAuthRetry(
777
- entry.runtimeUrl,
778
- entry.assistantId,
783
+ entry,
779
784
  async (token) => {
780
785
  const r = await localRuntimeImportFromGcs(entry, token, {
781
786
  bundleUrl,
@@ -806,7 +811,10 @@ async function importToAssistant(
806
811
  entry.runtimeUrl,
807
812
  entry.assistantId,
808
813
  entry.assistantId,
809
- { forceRefresh: true },
814
+ {
815
+ forceRefresh: true,
816
+ bootstrapSecret: entry.guardianBootstrapSecret,
817
+ },
810
818
  );
811
819
  },
812
820
  });