@vellumai/cli 0.5.13 → 0.5.14

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.
@@ -0,0 +1,746 @@
1
+ import { findAssistantByName } from "../lib/assistant-config.js";
2
+ import type { AssistantEntry } from "../lib/assistant-config.js";
3
+ import {
4
+ loadGuardianToken,
5
+ leaseGuardianToken,
6
+ } from "../lib/guardian-token.js";
7
+ import {
8
+ readPlatformToken,
9
+ fetchOrganizationId,
10
+ platformInitiateExport,
11
+ platformPollExportStatus,
12
+ platformDownloadExport,
13
+ platformImportPreflight,
14
+ platformImportBundle,
15
+ } from "../lib/platform-client.js";
16
+
17
+ function printHelp(): void {
18
+ console.log(
19
+ "Usage: vellum teleport --from <assistant> --to <assistant> [options]",
20
+ );
21
+ console.log("");
22
+ console.log(
23
+ "Transfer assistant data between local and platform environments.",
24
+ );
25
+ console.log("");
26
+ console.log("Options:");
27
+ console.log(" --from <name> Source assistant to export data from");
28
+ console.log(" --to <name> Target assistant to import data into");
29
+ console.log(
30
+ " --dry-run Preview the transfer without applying changes",
31
+ );
32
+ console.log(" --help, -h Show this help");
33
+ console.log("");
34
+ console.log("Examples:");
35
+ console.log(" vellum teleport --from my-local --to my-cloud");
36
+ console.log(" vellum teleport --from my-cloud --to my-local --dry-run");
37
+ console.log(" vellum teleport --from staging --to production --dry-run");
38
+ }
39
+
40
+ function parseArgs(argv: string[]): {
41
+ from: string | undefined;
42
+ to: string | undefined;
43
+ dryRun: boolean;
44
+ help: boolean;
45
+ } {
46
+ let from: string | undefined;
47
+ let to: string | undefined;
48
+ let dryRun = false;
49
+ let help = false;
50
+
51
+ for (let i = 0; i < argv.length; i++) {
52
+ const arg = argv[i];
53
+ if (arg === "--from" && i + 1 < argv.length) {
54
+ if (argv[i + 1].startsWith("--")) {
55
+ continue;
56
+ }
57
+ from = argv[++i];
58
+ } else if (arg === "--to" && i + 1 < argv.length) {
59
+ if (argv[i + 1].startsWith("--")) {
60
+ continue;
61
+ }
62
+ to = argv[++i];
63
+ } else if (arg === "--dry-run") {
64
+ dryRun = true;
65
+ } else if (arg === "--help" || arg === "-h") {
66
+ help = true;
67
+ }
68
+ }
69
+
70
+ return { from, to, dryRun, help };
71
+ }
72
+
73
+ function resolveCloud(entry: AssistantEntry): string {
74
+ return (
75
+ entry.cloud || (entry.project ? "gcp" : entry.sshUser ? "custom" : "local")
76
+ );
77
+ }
78
+
79
+ // ---------------------------------------------------------------------------
80
+ // Auth helper — same pattern as restore.ts
81
+ // ---------------------------------------------------------------------------
82
+
83
+ async function getAccessToken(
84
+ runtimeUrl: string,
85
+ assistantId: string,
86
+ displayName: string,
87
+ ): Promise<string> {
88
+ const tokenData = loadGuardianToken(assistantId);
89
+
90
+ if (tokenData && new Date(tokenData.accessTokenExpiresAt) > new Date()) {
91
+ return tokenData.accessToken;
92
+ }
93
+
94
+ try {
95
+ const freshToken = await leaseGuardianToken(runtimeUrl, assistantId);
96
+ return freshToken.accessToken;
97
+ } catch (err) {
98
+ const msg = err instanceof Error ? err.message : String(err);
99
+ if (msg.includes("ECONNREFUSED") || msg.includes("fetch failed")) {
100
+ console.error(
101
+ `Error: Could not connect to assistant '${displayName}'. Is it running?`,
102
+ );
103
+ console.error(`Try: vellum wake ${displayName}`);
104
+ process.exit(1);
105
+ }
106
+ throw err;
107
+ }
108
+ }
109
+
110
+ // ---------------------------------------------------------------------------
111
+ // Export from source assistant
112
+ // ---------------------------------------------------------------------------
113
+
114
+ async function exportFromAssistant(
115
+ entry: AssistantEntry,
116
+ cloud: string,
117
+ ): Promise<Uint8Array<ArrayBuffer>> {
118
+ if (cloud === "vellum") {
119
+ // Platform source — use Django async export
120
+ const token = readPlatformToken();
121
+ if (!token) {
122
+ console.error("Not logged in. Run 'vellum login' first.");
123
+ process.exit(1);
124
+ }
125
+
126
+ let orgId: string;
127
+ try {
128
+ orgId = await fetchOrganizationId(token, entry.runtimeUrl);
129
+ } catch (err) {
130
+ const msg = err instanceof Error ? err.message : String(err);
131
+ if (msg.includes("401") || msg.includes("403")) {
132
+ console.error("Authentication failed. Run 'vellum login' to refresh.");
133
+ process.exit(1);
134
+ }
135
+ throw err;
136
+ }
137
+
138
+ // Initiate export job
139
+ let jobId: string;
140
+ try {
141
+ const result = await platformInitiateExport(
142
+ token,
143
+ orgId,
144
+ "teleport export",
145
+ entry.runtimeUrl,
146
+ );
147
+ jobId = result.jobId;
148
+ } catch (err) {
149
+ const msg = err instanceof Error ? err.message : String(err);
150
+ if (msg.includes("401") || msg.includes("403")) {
151
+ console.error("Authentication failed. Run 'vellum login' to refresh.");
152
+ process.exit(1);
153
+ }
154
+ throw err;
155
+ }
156
+
157
+ console.log(`Export started (job ${jobId})...`);
158
+
159
+ // Poll for completion
160
+ const POLL_INTERVAL_MS = 2_000;
161
+ const TIMEOUT_MS = 5 * 60 * 1_000;
162
+ const deadline = Date.now() + TIMEOUT_MS;
163
+ let downloadUrl: string | undefined;
164
+
165
+ while (Date.now() < deadline) {
166
+ let status: { status: string; downloadUrl?: string; error?: string };
167
+ try {
168
+ status = await platformPollExportStatus(
169
+ jobId,
170
+ token,
171
+ orgId,
172
+ entry.runtimeUrl,
173
+ );
174
+ } catch (err) {
175
+ const msg = err instanceof Error ? err.message : String(err);
176
+ if (msg.includes("not found")) {
177
+ throw err;
178
+ }
179
+ console.warn(`Polling failed, retrying... (${msg})`);
180
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
181
+ continue;
182
+ }
183
+
184
+ if (status.status === "complete") {
185
+ downloadUrl = status.downloadUrl;
186
+ break;
187
+ }
188
+
189
+ if (status.status === "failed") {
190
+ console.error(`Export failed: ${status.error ?? "unknown error"}`);
191
+ process.exit(1);
192
+ }
193
+
194
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
195
+ }
196
+
197
+ if (!downloadUrl) {
198
+ console.error("Export timed out after 5 minutes.");
199
+ process.exit(1);
200
+ }
201
+
202
+ // Download the bundle
203
+ const response = await platformDownloadExport(downloadUrl);
204
+ const arrayBuffer = await response.arrayBuffer();
205
+ return new Uint8Array(arrayBuffer);
206
+ }
207
+
208
+ if (cloud === "local") {
209
+ // Local source — direct export endpoint
210
+ let accessToken = await getAccessToken(
211
+ entry.runtimeUrl,
212
+ entry.assistantId,
213
+ entry.assistantId,
214
+ );
215
+
216
+ let response: Response;
217
+ try {
218
+ response = await fetch(`${entry.runtimeUrl}/v1/migrations/export`, {
219
+ method: "POST",
220
+ headers: {
221
+ Authorization: `Bearer ${accessToken}`,
222
+ "Content-Type": "application/json",
223
+ },
224
+ body: JSON.stringify({ description: "teleport export" }),
225
+ signal: AbortSignal.timeout(120_000),
226
+ });
227
+
228
+ // Retry once with a fresh token on 401
229
+ if (response.status === 401) {
230
+ let refreshedToken: string | null = null;
231
+ try {
232
+ const freshToken = await leaseGuardianToken(
233
+ entry.runtimeUrl,
234
+ entry.assistantId,
235
+ );
236
+ refreshedToken = freshToken.accessToken;
237
+ } catch {
238
+ // If token refresh fails, fall through to the error handler below
239
+ }
240
+ if (refreshedToken) {
241
+ accessToken = refreshedToken;
242
+ response = await fetch(`${entry.runtimeUrl}/v1/migrations/export`, {
243
+ method: "POST",
244
+ headers: {
245
+ Authorization: `Bearer ${accessToken}`,
246
+ "Content-Type": "application/json",
247
+ },
248
+ body: JSON.stringify({ description: "teleport export" }),
249
+ signal: AbortSignal.timeout(120_000),
250
+ });
251
+ }
252
+ }
253
+ } catch (err) {
254
+ if (err instanceof Error && err.name === "TimeoutError") {
255
+ console.error("Error: Export request timed out after 2 minutes.");
256
+ process.exit(1);
257
+ }
258
+ const msg = err instanceof Error ? err.message : String(err);
259
+ if (msg.includes("ECONNREFUSED") || msg.includes("fetch failed")) {
260
+ console.error(
261
+ `Error: Could not connect to assistant '${entry.assistantId}'. Is it running?`,
262
+ );
263
+ console.error(`Try: vellum wake ${entry.assistantId}`);
264
+ process.exit(1);
265
+ }
266
+ throw err;
267
+ }
268
+
269
+ if (response.status === 401 || response.status === 403) {
270
+ console.error("Authentication failed.");
271
+ process.exit(1);
272
+ }
273
+
274
+ if (response.status === 404) {
275
+ console.error("Assistant not found or not running.");
276
+ process.exit(1);
277
+ }
278
+
279
+ if (
280
+ response.status === 502 ||
281
+ response.status === 503 ||
282
+ response.status === 504
283
+ ) {
284
+ console.error(
285
+ `Assistant is unreachable. Try 'vellum wake ${entry.assistantId}'.`,
286
+ );
287
+ process.exit(1);
288
+ }
289
+
290
+ if (!response.ok) {
291
+ const body = await response.text();
292
+ console.error(`Error: Export failed (${response.status}): ${body}`);
293
+ process.exit(1);
294
+ }
295
+
296
+ const arrayBuffer = await response.arrayBuffer();
297
+ return new Uint8Array(arrayBuffer);
298
+ }
299
+
300
+ console.error(
301
+ "Teleport only supports local and platform assistants as source.",
302
+ );
303
+ process.exit(1);
304
+ }
305
+
306
+ // ---------------------------------------------------------------------------
307
+ // Import into target assistant
308
+ // ---------------------------------------------------------------------------
309
+
310
+ interface PreflightFileEntry {
311
+ path: string;
312
+ action: string;
313
+ }
314
+
315
+ interface StructuredError {
316
+ code: string;
317
+ message: string;
318
+ path?: string;
319
+ }
320
+
321
+ interface PreflightResponse {
322
+ can_import: boolean;
323
+ validation?: {
324
+ is_valid: false;
325
+ errors: StructuredError[];
326
+ };
327
+ files?: PreflightFileEntry[];
328
+ summary?: {
329
+ files_to_create: number;
330
+ files_to_overwrite: number;
331
+ files_unchanged: number;
332
+ total_files: number;
333
+ };
334
+ conflicts?: StructuredError[];
335
+ }
336
+
337
+ interface ImportResponse {
338
+ success: boolean;
339
+ reason?: string;
340
+ errors?: StructuredError[];
341
+ message?: string;
342
+ warnings?: string[];
343
+ summary?: {
344
+ total_files: number;
345
+ files_created: number;
346
+ files_overwritten: number;
347
+ files_skipped: number;
348
+ backups_created: number;
349
+ };
350
+ }
351
+
352
+ async function importToAssistant(
353
+ entry: AssistantEntry,
354
+ cloud: string,
355
+ bundleData: Uint8Array<ArrayBuffer>,
356
+ dryRun: boolean,
357
+ ): Promise<void> {
358
+ if (cloud === "vellum") {
359
+ // Platform target
360
+ const token = readPlatformToken();
361
+ if (!token) {
362
+ console.error("Not logged in. Run 'vellum login' first.");
363
+ process.exit(1);
364
+ }
365
+
366
+ let orgId: string;
367
+ try {
368
+ orgId = await fetchOrganizationId(token, entry.runtimeUrl);
369
+ } catch (err) {
370
+ const msg = err instanceof Error ? err.message : String(err);
371
+ if (msg.includes("401") || msg.includes("403")) {
372
+ console.error("Authentication failed. Run 'vellum login' to refresh.");
373
+ process.exit(1);
374
+ }
375
+ throw err;
376
+ }
377
+
378
+ if (dryRun) {
379
+ console.log("Running preflight analysis...\n");
380
+
381
+ let preflightResult: {
382
+ statusCode: number;
383
+ body: Record<string, unknown>;
384
+ };
385
+ try {
386
+ preflightResult = await platformImportPreflight(
387
+ bundleData,
388
+ token,
389
+ orgId,
390
+ entry.runtimeUrl,
391
+ );
392
+ } catch (err) {
393
+ if (err instanceof Error && err.name === "TimeoutError") {
394
+ console.error("Error: Preflight request timed out after 2 minutes.");
395
+ process.exit(1);
396
+ }
397
+ throw err;
398
+ }
399
+
400
+ if (
401
+ preflightResult.statusCode === 401 ||
402
+ preflightResult.statusCode === 403
403
+ ) {
404
+ console.error("Authentication failed. Run 'vellum login' to refresh.");
405
+ process.exit(1);
406
+ }
407
+
408
+ if (preflightResult.statusCode === 404) {
409
+ console.error("Assistant not found or not running.");
410
+ process.exit(1);
411
+ }
412
+
413
+ if (
414
+ preflightResult.statusCode === 502 ||
415
+ preflightResult.statusCode === 503 ||
416
+ preflightResult.statusCode === 504
417
+ ) {
418
+ console.error(
419
+ `Assistant is unreachable. Try 'vellum wake ${entry.assistantId}'.`,
420
+ );
421
+ process.exit(1);
422
+ }
423
+
424
+ if (preflightResult.statusCode !== 200) {
425
+ console.error(
426
+ `Error: Preflight check failed (${preflightResult.statusCode}): ${JSON.stringify(preflightResult.body)}`,
427
+ );
428
+ process.exit(1);
429
+ }
430
+
431
+ const result = preflightResult.body as unknown as PreflightResponse;
432
+ printPreflightSummary(result);
433
+ return;
434
+ }
435
+
436
+ // Actual import
437
+ console.log("Importing data...");
438
+
439
+ let importResult: { statusCode: number; body: Record<string, unknown> };
440
+ try {
441
+ importResult = await platformImportBundle(
442
+ bundleData,
443
+ token,
444
+ orgId,
445
+ entry.runtimeUrl,
446
+ );
447
+ } catch (err) {
448
+ if (err instanceof Error && err.name === "TimeoutError") {
449
+ console.error("Error: Import request timed out after 2 minutes.");
450
+ process.exit(1);
451
+ }
452
+ throw err;
453
+ }
454
+
455
+ handleImportStatusErrors(importResult.statusCode, entry.assistantId);
456
+
457
+ const result = importResult.body as unknown as ImportResponse;
458
+ printImportSummary(result);
459
+ return;
460
+ }
461
+
462
+ if (cloud === "local") {
463
+ // Local target
464
+ const accessToken = await getAccessToken(
465
+ entry.runtimeUrl,
466
+ entry.assistantId,
467
+ entry.assistantId,
468
+ );
469
+
470
+ if (dryRun) {
471
+ console.log("Running preflight analysis...\n");
472
+
473
+ let response: Response;
474
+ try {
475
+ response = await fetch(
476
+ `${entry.runtimeUrl}/v1/migrations/import-preflight`,
477
+ {
478
+ method: "POST",
479
+ headers: {
480
+ Authorization: `Bearer ${accessToken}`,
481
+ "Content-Type": "application/octet-stream",
482
+ },
483
+ body: new Blob([bundleData]),
484
+ signal: AbortSignal.timeout(120_000),
485
+ },
486
+ );
487
+ } catch (err) {
488
+ if (err instanceof Error && err.name === "TimeoutError") {
489
+ console.error("Error: Preflight request timed out after 2 minutes.");
490
+ process.exit(1);
491
+ }
492
+ const msg = err instanceof Error ? err.message : String(err);
493
+ if (msg.includes("ECONNREFUSED") || msg.includes("fetch failed")) {
494
+ console.error(
495
+ `Error: Could not connect to assistant '${entry.assistantId}'. Is it running?`,
496
+ );
497
+ console.error(`Try: vellum wake ${entry.assistantId}`);
498
+ process.exit(1);
499
+ }
500
+ throw err;
501
+ }
502
+
503
+ handleLocalResponseErrors(response, entry.assistantId);
504
+
505
+ const result = (await response.json()) as PreflightResponse;
506
+ printPreflightSummary(result);
507
+ return;
508
+ }
509
+
510
+ // Actual import
511
+ console.log("Importing data...");
512
+
513
+ let response: Response;
514
+ try {
515
+ response = await fetch(`${entry.runtimeUrl}/v1/migrations/import`, {
516
+ method: "POST",
517
+ headers: {
518
+ Authorization: `Bearer ${accessToken}`,
519
+ "Content-Type": "application/octet-stream",
520
+ },
521
+ body: new Blob([bundleData]),
522
+ signal: AbortSignal.timeout(120_000),
523
+ });
524
+ } catch (err) {
525
+ if (err instanceof Error && err.name === "TimeoutError") {
526
+ console.error("Error: Import request timed out after 2 minutes.");
527
+ process.exit(1);
528
+ }
529
+ const msg = err instanceof Error ? err.message : String(err);
530
+ if (msg.includes("ECONNREFUSED") || msg.includes("fetch failed")) {
531
+ console.error(
532
+ `Error: Could not connect to assistant '${entry.assistantId}'. Is it running?`,
533
+ );
534
+ console.error(`Try: vellum wake ${entry.assistantId}`);
535
+ process.exit(1);
536
+ }
537
+ throw err;
538
+ }
539
+
540
+ handleLocalResponseErrors(response, entry.assistantId);
541
+
542
+ const result = (await response.json()) as ImportResponse;
543
+ printImportSummary(result);
544
+ return;
545
+ }
546
+
547
+ console.error(
548
+ "Teleport only supports local and platform assistants as target.",
549
+ );
550
+ process.exit(1);
551
+ }
552
+
553
+ // ---------------------------------------------------------------------------
554
+ // Error handling helpers
555
+ // ---------------------------------------------------------------------------
556
+
557
+ function handleLocalResponseErrors(
558
+ response: Response,
559
+ assistantName: string,
560
+ ): void {
561
+ if (response.status === 401 || response.status === 403) {
562
+ console.error("Authentication failed.");
563
+ process.exit(1);
564
+ }
565
+
566
+ if (response.status === 404) {
567
+ console.error("Assistant not found or not running.");
568
+ process.exit(1);
569
+ }
570
+
571
+ if (
572
+ response.status === 502 ||
573
+ response.status === 503 ||
574
+ response.status === 504
575
+ ) {
576
+ console.error(
577
+ `Assistant is unreachable. Try 'vellum wake ${assistantName}'.`,
578
+ );
579
+ process.exit(1);
580
+ }
581
+
582
+ if (!response.ok) {
583
+ console.error(`Error: Request failed (${response.status})`);
584
+ process.exit(1);
585
+ }
586
+ }
587
+
588
+ function handleImportStatusErrors(
589
+ statusCode: number,
590
+ assistantName: string,
591
+ ): void {
592
+ if (statusCode === 401 || statusCode === 403) {
593
+ console.error("Authentication failed. Run 'vellum login' to refresh.");
594
+ process.exit(1);
595
+ }
596
+
597
+ if (statusCode === 404) {
598
+ console.error("Assistant not found or not running.");
599
+ process.exit(1);
600
+ }
601
+
602
+ if (statusCode === 502 || statusCode === 503 || statusCode === 504) {
603
+ console.error(
604
+ `Assistant is unreachable. Try 'vellum wake ${assistantName}'.`,
605
+ );
606
+ process.exit(1);
607
+ }
608
+
609
+ if (statusCode < 200 || statusCode >= 300) {
610
+ console.error(`Error: Import failed (${statusCode})`);
611
+ process.exit(1);
612
+ }
613
+ }
614
+
615
+ // ---------------------------------------------------------------------------
616
+ // Summary printing — matches restore.ts format
617
+ // ---------------------------------------------------------------------------
618
+
619
+ function printPreflightSummary(result: PreflightResponse): void {
620
+ if (!result.can_import) {
621
+ if (result.validation?.errors?.length) {
622
+ console.error("Import blocked by validation errors:");
623
+ for (const err of result.validation.errors) {
624
+ console.error(` - ${err.message}${err.path ? ` (${err.path})` : ""}`);
625
+ }
626
+ }
627
+ if (result.conflicts?.length) {
628
+ console.error("Import blocked by conflicts:");
629
+ for (const conflict of result.conflicts) {
630
+ console.error(
631
+ ` - ${conflict.message}${conflict.path ? ` (${conflict.path})` : ""}`,
632
+ );
633
+ }
634
+ }
635
+ process.exit(1);
636
+ }
637
+
638
+ const summary = result.summary ?? {
639
+ files_to_create: 0,
640
+ files_to_overwrite: 0,
641
+ files_unchanged: 0,
642
+ total_files: 0,
643
+ };
644
+ console.log("Preflight analysis:");
645
+ console.log(` Files to create: ${summary.files_to_create}`);
646
+ console.log(` Files to overwrite: ${summary.files_to_overwrite}`);
647
+ console.log(` Files unchanged: ${summary.files_unchanged}`);
648
+ console.log(` Total: ${summary.total_files}`);
649
+ console.log("");
650
+
651
+ const conflicts = result.conflicts ?? [];
652
+ console.log(
653
+ `Conflicts: ${conflicts.length > 0 ? conflicts.map((c) => c.message).join(", ") : "none"}`,
654
+ );
655
+
656
+ if (result.files && result.files.length > 0) {
657
+ console.log("");
658
+ console.log("Files:");
659
+ for (const file of result.files) {
660
+ console.log(` [${file.action}] ${file.path}`);
661
+ }
662
+ }
663
+ }
664
+
665
+ function printImportSummary(result: ImportResponse): void {
666
+ if (!result.success) {
667
+ console.error(
668
+ `Error: Import failed — ${result.message ?? result.reason ?? "unknown reason"}`,
669
+ );
670
+ for (const err of result.errors ?? []) {
671
+ console.error(` - ${err.message}${err.path ? ` (${err.path})` : ""}`);
672
+ }
673
+ process.exit(1);
674
+ }
675
+
676
+ const summary = result.summary ?? {
677
+ total_files: 0,
678
+ files_created: 0,
679
+ files_overwritten: 0,
680
+ files_skipped: 0,
681
+ backups_created: 0,
682
+ };
683
+ console.log(` Files created: ${summary.files_created}`);
684
+ console.log(` Files overwritten: ${summary.files_overwritten}`);
685
+ console.log(` Files skipped: ${summary.files_skipped}`);
686
+ console.log(` Backups created: ${summary.backups_created}`);
687
+
688
+ const warnings = result.warnings ?? [];
689
+ if (warnings.length > 0) {
690
+ console.log("");
691
+ console.log("Warnings:");
692
+ for (const warning of warnings) {
693
+ console.log(` ${warning}`);
694
+ }
695
+ }
696
+ }
697
+
698
+ // ---------------------------------------------------------------------------
699
+ // Main entry point
700
+ // ---------------------------------------------------------------------------
701
+
702
+ export async function teleport(): Promise<void> {
703
+ const args = process.argv.slice(3);
704
+ const { from, to, dryRun, help } = parseArgs(args);
705
+
706
+ if (help) {
707
+ printHelp();
708
+ process.exit(0);
709
+ }
710
+
711
+ if (!from || !to) {
712
+ printHelp();
713
+ process.exit(1);
714
+ }
715
+
716
+ // Look up both assistants
717
+ const fromEntry = findAssistantByName(from);
718
+ if (!fromEntry) {
719
+ console.error(
720
+ `Assistant '${from}' not found in lockfile. Run \`vellum ps\` to see available assistants.`,
721
+ );
722
+ process.exit(1);
723
+ }
724
+
725
+ const toEntry = findAssistantByName(to);
726
+ if (!toEntry) {
727
+ console.error(
728
+ `Assistant '${to}' not found in lockfile. Run \`vellum ps\` to see available assistants.`,
729
+ );
730
+ process.exit(1);
731
+ }
732
+
733
+ const fromCloud = resolveCloud(fromEntry);
734
+ const toCloud = resolveCloud(toEntry);
735
+
736
+ // Export from source
737
+ console.log(`Exporting from ${from} (${fromCloud})...`);
738
+ const bundleData = await exportFromAssistant(fromEntry, fromCloud);
739
+
740
+ // Import to target
741
+ console.log(`Importing to ${to} (${toCloud})...`);
742
+ await importToAssistant(toEntry, toCloud, bundleData, dryRun);
743
+
744
+ // Success summary
745
+ console.log(`Teleport complete: ${from} → ${to}`);
746
+ }