@vellumai/cli 0.6.2 → 0.6.3
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/package.json +1 -1
- package/src/__tests__/teleport.test.ts +0 -1
- package/src/commands/hatch.ts +4 -27
- package/src/commands/restore.ts +116 -12
- package/src/commands/retire.ts +7 -0
- package/src/commands/sleep.ts +7 -0
- package/src/commands/teleport.ts +81 -2
- package/src/commands/wake.ts +7 -0
- package/src/lib/assistant-config.ts +6 -0
- package/src/lib/docker.ts +3 -0
- package/src/lib/hatch-local.ts +13 -23
- package/src/lib/platform-client.ts +41 -1
package/package.json
CHANGED
package/src/commands/hatch.ts
CHANGED
|
@@ -176,7 +176,6 @@ interface HatchArgs {
|
|
|
176
176
|
keepAlive: boolean;
|
|
177
177
|
name: string | null;
|
|
178
178
|
remote: RemoteHost;
|
|
179
|
-
restart: boolean;
|
|
180
179
|
watch: boolean;
|
|
181
180
|
configValues: Record<string, string>;
|
|
182
181
|
}
|
|
@@ -188,7 +187,6 @@ function parseArgs(): HatchArgs {
|
|
|
188
187
|
let keepAlive = false;
|
|
189
188
|
let name: string | null = null;
|
|
190
189
|
let remote: RemoteHost = DEFAULT_REMOTE;
|
|
191
|
-
let restart = false;
|
|
192
190
|
let watch = false;
|
|
193
191
|
const configValues: Record<string, string> = {};
|
|
194
192
|
|
|
@@ -209,9 +207,6 @@ function parseArgs(): HatchArgs {
|
|
|
209
207
|
console.log(
|
|
210
208
|
" --remote <host> Remote host (local, gcp, aws, docker, custom, vellum)",
|
|
211
209
|
);
|
|
212
|
-
console.log(
|
|
213
|
-
" --restart Restart processes without onboarding side effects",
|
|
214
|
-
);
|
|
215
210
|
console.log(
|
|
216
211
|
" --watch Run assistant and gateway in watch mode (hot reload on source changes)",
|
|
217
212
|
);
|
|
@@ -224,8 +219,6 @@ function parseArgs(): HatchArgs {
|
|
|
224
219
|
process.exit(0);
|
|
225
220
|
} else if (arg === "-d") {
|
|
226
221
|
detached = true;
|
|
227
|
-
} else if (arg === "--restart") {
|
|
228
|
-
restart = true;
|
|
229
222
|
} else if (arg === "--watch") {
|
|
230
223
|
watch = true;
|
|
231
224
|
} else if (arg === "--keep-alive") {
|
|
@@ -277,7 +270,7 @@ function parseArgs(): HatchArgs {
|
|
|
277
270
|
species = arg as Species;
|
|
278
271
|
} else {
|
|
279
272
|
console.error(
|
|
280
|
-
`Error: Unknown argument '${arg}'. Valid options: ${VALID_SPECIES.join(", ")}, -d, --
|
|
273
|
+
`Error: Unknown argument '${arg}'. Valid options: ${VALID_SPECIES.join(", ")}, -d, --watch, --keep-alive, --name <name>, --remote <${VALID_REMOTE_HOSTS.join("|")}>, --config <key=value>`,
|
|
281
274
|
);
|
|
282
275
|
process.exit(1);
|
|
283
276
|
}
|
|
@@ -289,7 +282,6 @@ function parseArgs(): HatchArgs {
|
|
|
289
282
|
keepAlive,
|
|
290
283
|
name,
|
|
291
284
|
remote,
|
|
292
|
-
restart,
|
|
293
285
|
watch,
|
|
294
286
|
configValues,
|
|
295
287
|
};
|
|
@@ -516,23 +508,8 @@ export async function hatch(): Promise<void> {
|
|
|
516
508
|
const cliVersion = getCliVersion();
|
|
517
509
|
console.log(`@vellumai/cli v${cliVersion}`);
|
|
518
510
|
|
|
519
|
-
const {
|
|
520
|
-
|
|
521
|
-
detached,
|
|
522
|
-
keepAlive,
|
|
523
|
-
name,
|
|
524
|
-
remote,
|
|
525
|
-
restart,
|
|
526
|
-
watch,
|
|
527
|
-
configValues,
|
|
528
|
-
} = parseArgs();
|
|
529
|
-
|
|
530
|
-
if (restart && remote !== "local") {
|
|
531
|
-
console.error(
|
|
532
|
-
"Error: --restart is only supported for local hatch targets.",
|
|
533
|
-
);
|
|
534
|
-
process.exit(1);
|
|
535
|
-
}
|
|
511
|
+
const { species, detached, keepAlive, name, remote, watch, configValues } =
|
|
512
|
+
parseArgs();
|
|
536
513
|
|
|
537
514
|
if (watch && remote !== "local" && remote !== "docker") {
|
|
538
515
|
console.error(
|
|
@@ -542,7 +519,7 @@ export async function hatch(): Promise<void> {
|
|
|
542
519
|
}
|
|
543
520
|
|
|
544
521
|
if (remote === "local") {
|
|
545
|
-
await hatchLocal(species, name,
|
|
522
|
+
await hatchLocal(species, name, watch, keepAlive, configValues);
|
|
546
523
|
return;
|
|
547
524
|
}
|
|
548
525
|
|
package/src/commands/restore.ts
CHANGED
|
@@ -11,6 +11,11 @@ import {
|
|
|
11
11
|
rollbackPlatformAssistant,
|
|
12
12
|
platformImportPreflight,
|
|
13
13
|
platformImportBundle,
|
|
14
|
+
platformRequestUploadUrl,
|
|
15
|
+
platformUploadToSignedUrl,
|
|
16
|
+
platformImportPreflightFromGcs,
|
|
17
|
+
platformImportBundleFromGcs,
|
|
18
|
+
platformPollImportStatus,
|
|
14
19
|
} from "../lib/platform-client.js";
|
|
15
20
|
import { performDockerRollback } from "../lib/upgrade-lifecycle.js";
|
|
16
21
|
|
|
@@ -176,6 +181,25 @@ async function restorePlatform(
|
|
|
176
181
|
process.exit(1);
|
|
177
182
|
}
|
|
178
183
|
|
|
184
|
+
// Step 1.5 — Upload to GCS via signed URL (with fallback to inline)
|
|
185
|
+
let bundleKey: string | null = null;
|
|
186
|
+
try {
|
|
187
|
+
const { uploadUrl, bundleKey: key } = await platformRequestUploadUrl(
|
|
188
|
+
token,
|
|
189
|
+
entry.runtimeUrl,
|
|
190
|
+
);
|
|
191
|
+
bundleKey = key;
|
|
192
|
+
console.log("Uploading bundle...");
|
|
193
|
+
await platformUploadToSignedUrl(uploadUrl, new Uint8Array(bundleData));
|
|
194
|
+
} catch (err) {
|
|
195
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
196
|
+
if (msg.includes("not available")) {
|
|
197
|
+
bundleKey = null;
|
|
198
|
+
} else {
|
|
199
|
+
throw err;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
179
203
|
// Step 2 — Dry-run path
|
|
180
204
|
if (opts.dryRun) {
|
|
181
205
|
if (opts.version) {
|
|
@@ -189,11 +213,17 @@ async function restorePlatform(
|
|
|
189
213
|
|
|
190
214
|
let preflightResult: { statusCode: number; body: Record<string, unknown> };
|
|
191
215
|
try {
|
|
192
|
-
preflightResult =
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
216
|
+
preflightResult = bundleKey
|
|
217
|
+
? await platformImportPreflightFromGcs(
|
|
218
|
+
bundleKey,
|
|
219
|
+
token,
|
|
220
|
+
entry.runtimeUrl,
|
|
221
|
+
)
|
|
222
|
+
: await platformImportPreflight(
|
|
223
|
+
new Uint8Array(bundleData),
|
|
224
|
+
token,
|
|
225
|
+
entry.runtimeUrl,
|
|
226
|
+
);
|
|
197
227
|
} catch (err) {
|
|
198
228
|
if (err instanceof Error && err.name === "TimeoutError") {
|
|
199
229
|
console.error("Error: Preflight request timed out after 2 minutes.");
|
|
@@ -323,14 +353,16 @@ async function restorePlatform(
|
|
|
323
353
|
|
|
324
354
|
let importResult: { statusCode: number; body: Record<string, unknown> };
|
|
325
355
|
try {
|
|
326
|
-
importResult =
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
356
|
+
importResult = bundleKey
|
|
357
|
+
? await platformImportBundleFromGcs(bundleKey, token, entry.runtimeUrl)
|
|
358
|
+
: await platformImportBundle(
|
|
359
|
+
new Uint8Array(bundleData),
|
|
360
|
+
token,
|
|
361
|
+
entry.runtimeUrl,
|
|
362
|
+
);
|
|
331
363
|
} catch (err) {
|
|
332
364
|
if (err instanceof Error && err.name === "TimeoutError") {
|
|
333
|
-
console.error("Error: Import request timed out after
|
|
365
|
+
console.error("Error: Import request timed out after 5 minutes.");
|
|
334
366
|
process.exit(1);
|
|
335
367
|
}
|
|
336
368
|
throw err;
|
|
@@ -364,11 +396,83 @@ async function restorePlatform(
|
|
|
364
396
|
process.exit(1);
|
|
365
397
|
}
|
|
366
398
|
|
|
367
|
-
if (
|
|
399
|
+
if (
|
|
400
|
+
importResult.statusCode !== 202 &&
|
|
401
|
+
(importResult.statusCode < 200 || importResult.statusCode >= 300)
|
|
402
|
+
) {
|
|
368
403
|
console.error(`Error: Import failed (${importResult.statusCode})`);
|
|
369
404
|
process.exit(1);
|
|
370
405
|
}
|
|
371
406
|
|
|
407
|
+
// Async import — poll until complete
|
|
408
|
+
if (importResult.statusCode === 202) {
|
|
409
|
+
const jobId = (importResult.body as { job_id?: string }).job_id;
|
|
410
|
+
if (!jobId) {
|
|
411
|
+
console.error("Error: Import accepted but no job ID returned.");
|
|
412
|
+
process.exit(1);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const POLL_INTERVAL_MS = 5_000;
|
|
416
|
+
const TIMEOUT_MS = 10 * 60 * 1_000; // 10 minutes
|
|
417
|
+
const startTime = Date.now();
|
|
418
|
+
const deadline = startTime + TIMEOUT_MS;
|
|
419
|
+
|
|
420
|
+
while (Date.now() < deadline) {
|
|
421
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
|
|
422
|
+
|
|
423
|
+
let status: {
|
|
424
|
+
status: string;
|
|
425
|
+
result?: Record<string, unknown>;
|
|
426
|
+
error?: string;
|
|
427
|
+
};
|
|
428
|
+
try {
|
|
429
|
+
status = await platformPollImportStatus(jobId, token, entry.runtimeUrl);
|
|
430
|
+
} catch (err) {
|
|
431
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
432
|
+
if (msg.includes("not found")) {
|
|
433
|
+
throw err;
|
|
434
|
+
}
|
|
435
|
+
// Fail fast on auth errors from authHeaders() which don't
|
|
436
|
+
// match the "status check failed: NNN" format
|
|
437
|
+
if (msg.includes("401") || msg.includes("403")) {
|
|
438
|
+
throw err;
|
|
439
|
+
}
|
|
440
|
+
// Re-throw permanent 4xx errors, retry transient 5xx
|
|
441
|
+
const statusMatch = msg.match(/status check failed: (\d+)/);
|
|
442
|
+
if (statusMatch) {
|
|
443
|
+
const statusCode = parseInt(statusMatch[1], 10);
|
|
444
|
+
if (statusCode >= 400 && statusCode < 500) {
|
|
445
|
+
throw err;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
// Transient error (5xx, network) — retry
|
|
449
|
+
console.warn(`Polling failed, retrying... (${msg})`);
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (status.status === "complete") {
|
|
454
|
+
importResult = { statusCode: 200, body: status.result ?? {} };
|
|
455
|
+
break;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (status.status === "failed") {
|
|
459
|
+
console.error(`Import failed: ${status.error ?? "unknown error"}`);
|
|
460
|
+
process.exit(1);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const elapsed = Math.round((Date.now() - startTime) / 1000);
|
|
464
|
+
process.stdout.write(`\rImporting... ${elapsed}s elapsed`);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Clear the progress line
|
|
468
|
+
process.stdout.write("\r" + " ".repeat(40) + "\r");
|
|
469
|
+
|
|
470
|
+
if (importResult.statusCode === 202) {
|
|
471
|
+
console.error("Import timed out after 10 minutes.");
|
|
472
|
+
process.exit(1);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
372
476
|
const result = importResult.body as unknown as ImportResponse;
|
|
373
477
|
|
|
374
478
|
if (!result.success) {
|
package/src/commands/retire.ts
CHANGED
|
@@ -200,6 +200,13 @@ async function retireInner(): Promise<void> {
|
|
|
200
200
|
const source = parseSource();
|
|
201
201
|
const cloud = resolveCloud(entry);
|
|
202
202
|
|
|
203
|
+
if (cloud === "apple-container") {
|
|
204
|
+
console.error(
|
|
205
|
+
`Error: '${name}' uses the Apple Containers runtime. Its lifecycle is managed by the macOS app — use the app to retire it.`,
|
|
206
|
+
);
|
|
207
|
+
process.exit(1);
|
|
208
|
+
}
|
|
209
|
+
|
|
203
210
|
if (cloud === "gcp") {
|
|
204
211
|
const project = entry.project;
|
|
205
212
|
const zone = entry.zone;
|
package/src/commands/sleep.ts
CHANGED
|
@@ -72,6 +72,13 @@ export async function sleep(): Promise<void> {
|
|
|
72
72
|
return;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
if (entry.cloud === "apple-container") {
|
|
76
|
+
console.error(
|
|
77
|
+
`Error: '${entry.assistantId}' uses the Apple Containers runtime. Its lifecycle is managed by the macOS app — use the app to stop it.`,
|
|
78
|
+
);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
|
|
75
82
|
if (entry.cloud && entry.cloud !== "local") {
|
|
76
83
|
console.error(
|
|
77
84
|
`Error: 'vellum sleep' only works with local and docker assistants. '${entry.assistantId}' is a ${entry.cloud} instance.`,
|
package/src/commands/teleport.ts
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
platformUploadToSignedUrl,
|
|
24
24
|
platformImportPreflightFromGcs,
|
|
25
25
|
platformImportBundleFromGcs,
|
|
26
|
+
platformPollImportStatus,
|
|
26
27
|
} from "../lib/platform-client.js";
|
|
27
28
|
import {
|
|
28
29
|
hatchDocker,
|
|
@@ -512,6 +513,16 @@ async function exportFromAssistant(
|
|
|
512
513
|
if (msg.includes("not found")) {
|
|
513
514
|
throw err;
|
|
514
515
|
}
|
|
516
|
+
// Re-throw permanent 4xx errors (auth, forbidden, etc.)
|
|
517
|
+
// but retry transient 5xx errors
|
|
518
|
+
const statusMatch = msg.match(/status check failed: (\d+)/);
|
|
519
|
+
if (statusMatch) {
|
|
520
|
+
const statusCode = parseInt(statusMatch[1], 10);
|
|
521
|
+
if (statusCode >= 400 && statusCode < 500) {
|
|
522
|
+
throw err;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
// Transient error — retry
|
|
515
526
|
console.warn(`Polling failed, retrying... (${msg})`);
|
|
516
527
|
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
|
|
517
528
|
continue;
|
|
@@ -706,7 +717,7 @@ async function importToAssistant(
|
|
|
706
717
|
: await platformImportBundle(bundleData, token, entry.runtimeUrl);
|
|
707
718
|
} catch (err) {
|
|
708
719
|
if (err instanceof Error && err.name === "TimeoutError") {
|
|
709
|
-
console.error("Error: Import request timed out
|
|
720
|
+
console.error("Error: Import request timed out.");
|
|
710
721
|
process.exit(1);
|
|
711
722
|
}
|
|
712
723
|
throw err;
|
|
@@ -714,6 +725,74 @@ async function importToAssistant(
|
|
|
714
725
|
|
|
715
726
|
handleImportStatusErrors(importResult.statusCode, entry.assistantId);
|
|
716
727
|
|
|
728
|
+
if (importResult.statusCode === 202) {
|
|
729
|
+
const jobId = (importResult.body as { job_id?: string }).job_id;
|
|
730
|
+
if (!jobId) {
|
|
731
|
+
console.error("Error: Import accepted but no job ID returned.");
|
|
732
|
+
process.exit(1);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
const POLL_INTERVAL_MS = 5_000;
|
|
736
|
+
const TIMEOUT_MS = 10 * 60 * 1_000; // 10 minutes (platform staleness is 930s)
|
|
737
|
+
const startTime = Date.now();
|
|
738
|
+
const deadline = startTime + TIMEOUT_MS;
|
|
739
|
+
|
|
740
|
+
while (Date.now() < deadline) {
|
|
741
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
|
|
742
|
+
|
|
743
|
+
let status: {
|
|
744
|
+
status: string;
|
|
745
|
+
result?: Record<string, unknown>;
|
|
746
|
+
error?: string;
|
|
747
|
+
};
|
|
748
|
+
try {
|
|
749
|
+
status = await platformPollImportStatus(
|
|
750
|
+
jobId,
|
|
751
|
+
token,
|
|
752
|
+
entry.runtimeUrl,
|
|
753
|
+
);
|
|
754
|
+
} catch (err) {
|
|
755
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
756
|
+
if (msg.includes("not found")) {
|
|
757
|
+
throw err;
|
|
758
|
+
}
|
|
759
|
+
// Re-throw permanent 4xx errors (auth, forbidden, etc.)
|
|
760
|
+
// but retry transient 5xx errors
|
|
761
|
+
const statusMatch = msg.match(/status check failed: (\d+)/);
|
|
762
|
+
if (statusMatch) {
|
|
763
|
+
const statusCode = parseInt(statusMatch[1], 10);
|
|
764
|
+
if (statusCode >= 400 && statusCode < 500) {
|
|
765
|
+
throw err;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
// Transient error — retry
|
|
769
|
+
console.warn(`Polling failed, retrying... (${msg})`);
|
|
770
|
+
continue;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
if (status.status === "complete") {
|
|
774
|
+
importResult = { statusCode: 200, body: status.result ?? {} };
|
|
775
|
+
break;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
if (status.status === "failed") {
|
|
779
|
+
console.error(`Import failed: ${status.error ?? "unknown error"}`);
|
|
780
|
+
process.exit(1);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
const elapsed = Math.round((Date.now() - startTime) / 1000);
|
|
784
|
+
process.stdout.write(`\rImporting... ${elapsed}s elapsed`);
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// Clear the progress line
|
|
788
|
+
process.stdout.write("\r" + " ".repeat(40) + "\r");
|
|
789
|
+
|
|
790
|
+
if (importResult.statusCode === 202) {
|
|
791
|
+
console.error("Import timed out after 10 minutes.");
|
|
792
|
+
process.exit(1);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
717
796
|
const result = importResult.body as unknown as ImportResponse;
|
|
718
797
|
printImportSummary(result);
|
|
719
798
|
return;
|
|
@@ -779,7 +858,7 @@ export async function resolveOrHatchTarget(
|
|
|
779
858
|
// Hatch a new assistant in the target environment
|
|
780
859
|
if (targetEnv === "local") {
|
|
781
860
|
const beforeIds = new Set(loadAllAssistants().map((e) => e.assistantId));
|
|
782
|
-
await hatchLocal("vellum", targetName ?? null, false, false,
|
|
861
|
+
await hatchLocal("vellum", targetName ?? null, false, false, {});
|
|
783
862
|
const entry = targetName
|
|
784
863
|
? findAssistantByName(targetName)
|
|
785
864
|
: (loadAllAssistants().find((e) => !beforeIds.has(e.assistantId)) ??
|
package/src/commands/wake.ts
CHANGED
|
@@ -59,6 +59,13 @@ export async function wake(): Promise<void> {
|
|
|
59
59
|
return;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
if (entry.cloud === "apple-container") {
|
|
63
|
+
console.error(
|
|
64
|
+
`Error: '${entry.assistantId}' uses the Apple Containers runtime. Its lifecycle is managed by the macOS app — use the app to start it.`,
|
|
65
|
+
);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
|
|
62
69
|
if (entry.cloud && entry.cloud !== "local") {
|
|
63
70
|
console.error(
|
|
64
71
|
`Error: 'vellum wake' only works with local and docker assistants. '${entry.assistantId}' is a ${entry.cloud} instance.`,
|
|
@@ -181,6 +181,12 @@ export function migrateLegacyEntry(raw: Record<string, unknown>): boolean {
|
|
|
181
181
|
return false;
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
+
// Apple-containers entries are fully managed by the macOS app.
|
|
185
|
+
// Skip legacy migration to avoid corrupting their fields.
|
|
186
|
+
if (raw.cloud === "apple-container") {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
|
|
184
190
|
let mutated = false;
|
|
185
191
|
|
|
186
192
|
// Migrate top-level `baseDataDir` → `resources.instanceDir`
|
package/src/lib/docker.ts
CHANGED
|
@@ -644,6 +644,9 @@ export function serviceDockerRunArgs(opts: {
|
|
|
644
644
|
...(opts.bootstrapSecret
|
|
645
645
|
? ["-e", `GUARDIAN_BOOTSTRAP_SECRET=${opts.bootstrapSecret}`]
|
|
646
646
|
: []),
|
|
647
|
+
...(process.env.VELLUM_PLATFORM_URL
|
|
648
|
+
? ["-e", `VELLUM_PLATFORM_URL=${process.env.VELLUM_PLATFORM_URL}`]
|
|
649
|
+
: []),
|
|
647
650
|
imageTags.gateway,
|
|
648
651
|
],
|
|
649
652
|
"credential-executor": () => [
|
package/src/lib/hatch-local.ts
CHANGED
|
@@ -144,18 +144,10 @@ function installCLISymlink(): void {
|
|
|
144
144
|
export async function hatchLocal(
|
|
145
145
|
species: Species,
|
|
146
146
|
name: string | null,
|
|
147
|
-
restart: boolean = false,
|
|
148
147
|
watch: boolean = false,
|
|
149
148
|
keepAlive: boolean = false,
|
|
150
149
|
configValues: Record<string, string> = {},
|
|
151
150
|
): Promise<void> {
|
|
152
|
-
if (restart && !name && !process.env.VELLUM_ASSISTANT_NAME) {
|
|
153
|
-
console.error(
|
|
154
|
-
"Error: Cannot restart without a known assistant ID. Provide --name or ensure VELLUM_ASSISTANT_NAME is set.",
|
|
155
|
-
);
|
|
156
|
-
process.exit(1);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
151
|
const instanceName = generateInstanceName(
|
|
160
152
|
species,
|
|
161
153
|
name ?? process.env.VELLUM_ASSISTANT_NAME,
|
|
@@ -345,24 +337,22 @@ export async function hatchLocal(
|
|
|
345
337
|
resources: { ...resources, signingKey },
|
|
346
338
|
};
|
|
347
339
|
emitProgress(7, 7, "Saving configuration...");
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
syncConfigToLockfile();
|
|
352
|
-
|
|
353
|
-
if (process.env.VELLUM_DESKTOP_APP) {
|
|
354
|
-
installCLISymlink();
|
|
355
|
-
}
|
|
340
|
+
saveAssistantEntry(localEntry);
|
|
341
|
+
setActiveAssistant(instanceName);
|
|
342
|
+
syncConfigToLockfile();
|
|
356
343
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
console.log("");
|
|
360
|
-
console.log("Instance details:");
|
|
361
|
-
console.log(` Name: ${instanceName}`);
|
|
362
|
-
console.log(` Runtime: ${runtimeUrl}`);
|
|
363
|
-
console.log("");
|
|
344
|
+
if (process.env.VELLUM_DESKTOP_APP) {
|
|
345
|
+
installCLISymlink();
|
|
364
346
|
}
|
|
365
347
|
|
|
348
|
+
console.log("");
|
|
349
|
+
console.log(`✅ Local assistant hatched!`);
|
|
350
|
+
console.log("");
|
|
351
|
+
console.log("Instance details:");
|
|
352
|
+
console.log(` Name: ${instanceName}`);
|
|
353
|
+
console.log(` Runtime: ${runtimeUrl}`);
|
|
354
|
+
console.log("");
|
|
355
|
+
|
|
366
356
|
if (keepAlive) {
|
|
367
357
|
const healthUrl = `http://127.0.0.1:${resources.gatewayPort}/healthz`;
|
|
368
358
|
const healthTarget = "Gateway";
|
|
@@ -529,7 +529,7 @@ export async function platformImportBundleFromGcs(
|
|
|
529
529
|
method: "POST",
|
|
530
530
|
headers: await authHeaders(token, platformUrl),
|
|
531
531
|
body: JSON.stringify({ bundle_key: bundleKey }),
|
|
532
|
-
signal: AbortSignal.timeout(
|
|
532
|
+
signal: AbortSignal.timeout(60_000),
|
|
533
533
|
},
|
|
534
534
|
);
|
|
535
535
|
|
|
@@ -543,3 +543,43 @@ export async function platformImportBundleFromGcs(
|
|
|
543
543
|
>;
|
|
544
544
|
return { statusCode: response.status, body };
|
|
545
545
|
}
|
|
546
|
+
|
|
547
|
+
export async function platformPollImportStatus(
|
|
548
|
+
jobId: string,
|
|
549
|
+
token: string,
|
|
550
|
+
platformUrl?: string,
|
|
551
|
+
): Promise<{
|
|
552
|
+
status: string;
|
|
553
|
+
result?: Record<string, unknown>;
|
|
554
|
+
error?: string;
|
|
555
|
+
}> {
|
|
556
|
+
const resolvedUrl = platformUrl || getPlatformUrl();
|
|
557
|
+
const response = await fetch(
|
|
558
|
+
`${resolvedUrl}/v1/migrations/import/${jobId}/status/`,
|
|
559
|
+
{
|
|
560
|
+
headers: await authHeaders(token, platformUrl),
|
|
561
|
+
},
|
|
562
|
+
);
|
|
563
|
+
|
|
564
|
+
if (response.status === 404) {
|
|
565
|
+
throw new Error("Import job not found");
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
if (!response.ok) {
|
|
569
|
+
throw new Error(
|
|
570
|
+
`Import status check failed: ${response.status} ${response.statusText}`,
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
const body = (await response.json()) as {
|
|
575
|
+
status: string;
|
|
576
|
+
job_id?: string;
|
|
577
|
+
result?: Record<string, unknown>;
|
|
578
|
+
error?: string;
|
|
579
|
+
};
|
|
580
|
+
return {
|
|
581
|
+
status: body.status,
|
|
582
|
+
result: body.result,
|
|
583
|
+
error: body.error,
|
|
584
|
+
};
|
|
585
|
+
}
|