@vellumai/cli 0.5.9 → 0.5.11

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/cli",
3
- "version": "0.5.9",
3
+ "version": "0.5.11",
4
4
  "description": "CLI tools for vellum-assistant",
5
5
  "type": "module",
6
6
  "exports": {
@@ -55,6 +55,7 @@ import { generateInstanceName } from "../lib/random-name";
55
55
  import { validateAssistantName } from "../lib/retire-archive";
56
56
  import { leaseGuardianToken } from "../lib/guardian-token";
57
57
  import { archiveLogFile, resetLogFile } from "../lib/xdg-log";
58
+ import { emitProgress } from "../lib/desktop-progress.js";
58
59
 
59
60
  export type { PollResult, WatchHatchingResult } from "../lib/gcp";
60
61
 
@@ -645,6 +646,8 @@ async function hatchLocal(
645
646
  name ?? process.env.VELLUM_ASSISTANT_NAME,
646
647
  );
647
648
 
649
+ emitProgress(1, 7, "Preparing workspace...");
650
+
648
651
  // Clean up stale local state: if daemon/gateway processes are running but
649
652
  // the lock file has no entries AND the daemon is not healthy, stop them
650
653
  // before starting fresh. A healthy daemon should be reused, not killed —
@@ -703,6 +706,8 @@ async function hatchLocal(
703
706
  }
704
707
  }
705
708
 
709
+ emitProgress(2, 7, "Allocating resources...");
710
+
706
711
  // Reuse existing resources if re-hatching with --name that matches a known
707
712
  // local assistant, otherwise allocate fresh per-instance ports and directories.
708
713
  let resources: LocalInstanceResources;
@@ -763,10 +768,13 @@ async function hatchLocal(
763
768
  process.env.APP_VERSION = cliPkg.version;
764
769
  }
765
770
 
771
+ emitProgress(3, 7, "Writing configuration...");
766
772
  const defaultWorkspaceConfigPath = writeInitialConfig(configValues);
767
773
 
774
+ emitProgress(4, 7, "Starting assistant...");
768
775
  await startLocalDaemon(watch, resources, { defaultWorkspaceConfigPath });
769
776
 
777
+ emitProgress(5, 7, "Starting gateway...");
770
778
  let runtimeUrl = `http://127.0.0.1:${resources.gatewayPort}`;
771
779
  try {
772
780
  runtimeUrl = await startGateway(watch, resources);
@@ -782,6 +790,7 @@ async function hatchLocal(
782
790
 
783
791
  // Lease a guardian token so the desktop app can import it on first launch
784
792
  // instead of hitting /v1/guardian/init itself.
793
+ emitProgress(6, 7, "Securing connection...");
785
794
  try {
786
795
  await leaseGuardianToken(runtimeUrl, instanceName);
787
796
  } catch (err) {
@@ -813,6 +822,7 @@ async function hatchLocal(
813
822
  serviceGroupVersion: cliPkg.version ? `v${cliPkg.version}` : undefined,
814
823
  resources,
815
824
  };
825
+ emitProgress(7, 7, "Saving configuration...");
816
826
  if (!restart) {
817
827
  saveAssistantEntry(localEntry);
818
828
  setActiveAssistant(instanceName);
@@ -438,7 +438,7 @@ export async function allocateLocalResources(
438
438
  ): Promise<LocalInstanceResources> {
439
439
  // First local assistant gets the home directory with default ports.
440
440
  // Respect BASE_DATA_DIR when set (e.g. in e2e tests) so the daemon,
441
- // gateway, and keychain broker all resolve paths under the same root.
441
+ // gateway, and credential store all resolve paths under the same root.
442
442
  const existingLocals = loadAllAssistants().filter((e) => e.cloud === "local");
443
443
  if (existingLocals.length === 0) {
444
444
  const baseDir = getBaseDir();
package/src/lib/aws.ts CHANGED
@@ -11,6 +11,7 @@ import type { Species } from "./constants";
11
11
  import { leaseGuardianToken } from "./guardian-token";
12
12
  import { generateInstanceName } from "./random-name";
13
13
  import { exec, execOutput } from "./step-runner";
14
+ import { emitProgress } from "./desktop-progress.js";
14
15
 
15
16
  const KEY_PAIR_NAME = "vellum-assistant";
16
17
  const DEFAULT_SSH_USER = "admin";
@@ -443,6 +444,7 @@ export async function hatchAws(
443
444
  console.log("\u{1F50D} Finding latest Debian AMI...");
444
445
  const amiId = await getLatestDebianAmi(region);
445
446
 
447
+ emitProgress(1, 5, "Preparing startup script...");
446
448
  const { script: startupScript, laptopBootstrapSecret } =
447
449
  await buildStartupScript(
448
450
  species,
@@ -455,6 +457,7 @@ export async function hatchAws(
455
457
  const startupScriptPath = join(tmpdir(), `${instanceName}-startup.sh`);
456
458
  writeFileSync(startupScriptPath, startupScript);
457
459
 
460
+ emitProgress(2, 5, "Launching instance...");
458
461
  console.log("\u{1F528} Launching instance...");
459
462
  let instanceId: string;
460
463
  try {
@@ -491,6 +494,7 @@ export async function hatchAws(
491
494
  const runtimeUrl = externalIp
492
495
  ? `http://${externalIp}:${GATEWAY_PORT}`
493
496
  : `http://${instanceName}:${GATEWAY_PORT}`;
497
+ emitProgress(3, 5, "Saving configuration...");
494
498
  const awsEntry: AssistantEntry = {
495
499
  assistantId: instanceName,
496
500
  runtimeUrl,
@@ -522,6 +526,7 @@ export async function hatchAws(
522
526
 
523
527
  if (externalIp) {
524
528
  const ip = externalIp;
529
+ emitProgress(4, 5, "Installing software...");
525
530
  const result = await watchHatching(
526
531
  () => pollAwsInstance(ip, keyPath),
527
532
  instanceName,
@@ -539,6 +544,7 @@ export async function hatchAws(
539
544
  process.exit(1);
540
545
  }
541
546
 
547
+ emitProgress(5, 5, "Finalizing...");
542
548
  try {
543
549
  await leaseGuardianToken(
544
550
  runtimeUrl,
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Lightweight progress reporting for the desktop app.
3
+ *
4
+ * Writes structured `HATCH_PROGRESS:{...}` lines to stdout so the Electron
5
+ * wrapper can parse them and render a progress bar during hatch.
6
+ *
7
+ * This module intentionally has ZERO internal imports to avoid circular
8
+ * dependency issues — it is a leaf module.
9
+ */
10
+
11
+ /**
12
+ * Emit a structured progress event to stdout.
13
+ *
14
+ * Only emits when `VELLUM_DESKTOP_APP` env var is set (desktop mode).
15
+ * The desktop app parses lines matching `HATCH_PROGRESS:{...}` to update
16
+ * its progress UI.
17
+ */
18
+ export function emitProgress(step: number, total: number, label: string): void {
19
+ if (!process.env.VELLUM_DESKTOP_APP) return;
20
+ const payload = JSON.stringify({ step, total, label });
21
+ process.stdout.write(`HATCH_PROGRESS:${payload}\n`);
22
+ }
package/src/lib/docker.ts CHANGED
@@ -26,6 +26,7 @@ import {
26
26
  resetLogFile,
27
27
  writeToLogFile,
28
28
  } from "./xdg-log";
29
+ import { emitProgress } from "./desktop-progress.js";
29
30
 
30
31
  export type ServiceName = "assistant" | "credential-executor" | "gateway";
31
32
 
@@ -831,10 +832,11 @@ export async function sleepContainers(
831
832
  }
832
833
  }
833
834
 
834
- /** Start existing stopped containers. */
835
+ /** Start existing stopped containers, starting Colima first if it isn't running. */
835
836
  export async function wakeContainers(
836
837
  res: ReturnType<typeof dockerResourceNames>,
837
838
  ): Promise<void> {
839
+ await ensureColimaRunning();
838
840
  for (const container of [
839
841
  res.assistantContainer,
840
842
  res.gatewayContainer,
@@ -844,6 +846,20 @@ export async function wakeContainers(
844
846
  }
845
847
  }
846
848
 
849
+ /**
850
+ * Checks whether Colima is running and starts it if not.
851
+ * Assumes the Docker/Colima toolchain is already installed (handled during hatch).
852
+ */
853
+ async function ensureColimaRunning(): Promise<void> {
854
+ ensureLocalBinOnPath();
855
+ try {
856
+ await exec("colima", ["status"]);
857
+ } catch {
858
+ console.log("🚀 Colima is not running. Starting Colima...");
859
+ await exec("colima", ["start"]);
860
+ }
861
+ }
862
+
847
863
  /**
848
864
  * Capture the current image references for running service containers.
849
865
  * Returns a complete record of service → immutable image ID (sha256 digest)
@@ -1061,6 +1077,7 @@ export async function hatchDocker(
1061
1077
  };
1062
1078
 
1063
1079
  try {
1080
+ emitProgress(1, 6, "Checking Docker...");
1064
1081
  await ensureDockerInstalled();
1065
1082
 
1066
1083
  const instanceName = generateInstanceName(species, name);
@@ -1075,6 +1092,7 @@ export async function hatchDocker(
1075
1092
  let repoRoot: string | undefined;
1076
1093
 
1077
1094
  if (watch) {
1095
+ emitProgress(2, 6, "Building images...");
1078
1096
  repoRoot = findRepoRoot();
1079
1097
  const localTag = `local-${instanceName}`;
1080
1098
  imageTags.assistant = `vellum-assistant:${localTag}`;
@@ -1095,6 +1113,7 @@ export async function hatchDocker(
1095
1113
  await buildAllImages(repoRoot, imageTags, log);
1096
1114
  log("✅ Docker images built");
1097
1115
  } else {
1116
+ emitProgress(2, 6, "Pulling images...");
1098
1117
  const version = cliPkg.version;
1099
1118
  const versionTag = version ? `v${version}` : "latest";
1100
1119
  log("🔍 Resolving image references...");
@@ -1121,6 +1140,7 @@ export async function hatchDocker(
1121
1140
 
1122
1141
  const res = dockerResourceNames(instanceName);
1123
1142
 
1143
+ emitProgress(3, 6, "Creating volumes...");
1124
1144
  log("📁 Creating network and volumes...");
1125
1145
  await exec("docker", ["network", "create", res.network]);
1126
1146
  await exec("docker", ["volume", "create", res.socketVolume]);
@@ -1158,6 +1178,7 @@ export async function hatchDocker(
1158
1178
  ? `${preExisting},${ownSecret}`
1159
1179
  : ownSecret;
1160
1180
 
1181
+ emitProgress(4, 6, "Starting containers...");
1161
1182
  await startContainers(
1162
1183
  {
1163
1184
  signingKey,
@@ -1193,9 +1214,11 @@ export async function hatchDocker(
1193
1214
  networkName: res.network,
1194
1215
  },
1195
1216
  };
1217
+ emitProgress(5, 6, "Saving configuration...");
1196
1218
  saveAssistantEntry(dockerEntry);
1197
1219
  setActiveAssistant(instanceName);
1198
1220
 
1221
+ emitProgress(6, 6, "Waiting for services...");
1199
1222
  const { ready } = await waitForGatewayAndLease({
1200
1223
  bootstrapSecret: ownSecret,
1201
1224
  containerName: res.assistantContainer,
package/src/lib/gcp.ts CHANGED
@@ -14,6 +14,7 @@ import { leaseGuardianToken } from "./guardian-token";
14
14
  import { getPlatformUrl } from "./platform-client";
15
15
  import { generateInstanceName } from "./random-name";
16
16
  import { exec, execOutput } from "./step-runner";
17
+ import { emitProgress } from "./desktop-progress.js";
17
18
 
18
19
  export async function getActiveProject(): Promise<string> {
19
20
  const output = await execOutput("gcloud", ["config", "get-value", "project"]);
@@ -522,6 +523,7 @@ export async function hatchGcp(
522
523
  );
523
524
  process.exit(1);
524
525
  }
526
+ emitProgress(1, 5, "Preparing startup script...");
525
527
  const { script: startupScript, laptopBootstrapSecret } =
526
528
  await buildStartupScript(
527
529
  species,
@@ -534,6 +536,7 @@ export async function hatchGcp(
534
536
  const startupScriptPath = join(tmpdir(), `${instanceName}-startup.sh`);
535
537
  writeFileSync(startupScriptPath, startupScript);
536
538
 
539
+ emitProgress(2, 5, "Launching instance...");
537
540
  console.log("\ud83d\udd28 Creating instance with startup script...");
538
541
  try {
539
542
  const createArgs = [
@@ -595,6 +598,7 @@ export async function hatchGcp(
595
598
  const runtimeUrl = externalIp
596
599
  ? `http://${externalIp}:${GATEWAY_PORT}`
597
600
  : `http://${instanceName}:${GATEWAY_PORT}`;
601
+ emitProgress(3, 5, "Saving configuration...");
598
602
  const gcpEntry: AssistantEntry = {
599
603
  assistantId: instanceName,
600
604
  runtimeUrl,
@@ -624,6 +628,7 @@ export async function hatchGcp(
624
628
  console.log(" Press Ctrl+C to detach (instance will keep running)");
625
629
  console.log("");
626
630
 
631
+ emitProgress(4, 5, "Installing software...");
627
632
  const result = await watchHatching(
628
633
  () => pollInstance(instanceName, project, zone, account),
629
634
  instanceName,
@@ -662,6 +667,7 @@ export async function hatchGcp(
662
667
  }
663
668
  }
664
669
 
670
+ emitProgress(5, 5, "Finalizing...");
665
671
  try {
666
672
  await leaseGuardianToken(
667
673
  runtimeUrl,
package/src/lib/local.ts CHANGED
@@ -266,6 +266,7 @@ async function startDaemonFromSource(
266
266
  ...process.env,
267
267
  RUNTIME_HTTP_PORT: process.env.RUNTIME_HTTP_PORT || "7821",
268
268
  VELLUM_CLOUD: "local",
269
+ VELLUM_DEV: "1",
269
270
  };
270
271
  if (resources) {
271
272
  env.BASE_DATA_DIR = resources.instanceDir;