@vellumai/cli 0.8.5 → 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.
@@ -1,6 +1,12 @@
1
- import { existsSync, readFileSync, unlinkSync } from "fs";
1
+ import {
2
+ existsSync,
3
+ mkdirSync,
4
+ readFileSync,
5
+ renameSync,
6
+ unlinkSync,
7
+ } from "fs";
2
8
  import { homedir } from "os";
3
- import { join } from "path";
9
+ import { basename, dirname, join } from "path";
4
10
 
5
11
  import { saveAssistantEntry } from "../lib/assistant-config";
6
12
  import type { AssistantEntry } from "../lib/assistant-config";
@@ -21,8 +27,22 @@ export async function recover(): Promise<void> {
21
27
  "Restore a previously retired local assistant from its archive.",
22
28
  );
23
29
  console.log("");
30
+ console.log(
31
+ "Extracts the archived workspace data back to its original location,",
32
+ );
33
+ console.log(
34
+ "restores the lockfile entry, and starts the assistant and gateway.",
35
+ );
36
+ console.log(
37
+ "Archives are stored in $XDG_DATA_HOME/vellum/retired/ (default: ~/.local/share/vellum/retired/).",
38
+ );
39
+ console.log("");
24
40
  console.log("Arguments:");
25
41
  console.log(" <name> Name of the retired assistant to recover");
42
+ console.log("");
43
+ console.log("Examples:");
44
+ console.log(" $ vellum recover my-assistant");
45
+ console.log(" $ vellum recover aria-7f3a");
26
46
  process.exit(0);
27
47
  }
28
48
 
@@ -61,11 +81,27 @@ export async function recover(): Promise<void> {
61
81
  process.exit(1);
62
82
  }
63
83
 
64
- // 4. Extract archive
65
- // TODO: extraction target is hardcoded to homedir(); multi-instance entries
66
- // whose instanceDir differs from homedir will extract to the wrong
67
- // location. Tracked separately from the collision-check regression.
68
- await exec("tar", ["xzf", archivePath, "-C", homedir()]);
84
+ // 4. Determine the original target directory, then extract and rename.
85
+ //
86
+ // retireLocal archives either the full instanceDir (named instances) or just
87
+ // the .vellum/ subdirectory (default instance whose instanceDir === homedir()).
88
+ // The directory is staged under `<archive>.staging` inside the retired dir
89
+ // before being packed with `tar -C <retiredDir> <stagingBasename>`, so the
90
+ // top-level entry inside the tarball is always `<name>.tar.gz.staging`.
91
+ //
92
+ // Correct restoration: extract to retiredDir, then rename the staging entry
93
+ // back to the original target path. Using homedir() as the -C target was
94
+ // wrong for any instance stored outside the home directory.
95
+ const isNamedInstance = entry.resources.instanceDir !== homedir();
96
+ const targetDir = isNamedInstance
97
+ ? entry.resources.instanceDir
98
+ : join(entry.resources.instanceDir, ".vellum");
99
+ const retiredDir = dirname(archivePath);
100
+ const extractedPath = join(retiredDir, basename(archivePath) + ".staging");
101
+
102
+ await exec("tar", ["xzf", archivePath, "-C", retiredDir]);
103
+ mkdirSync(dirname(targetDir), { recursive: true });
104
+ renameSync(extractedPath, targetDir);
69
105
 
70
106
  // 5. Restore lockfile entry
71
107
  saveAssistantEntry(entry);
@@ -74,14 +110,16 @@ export async function recover(): Promise<void> {
74
110
  unlinkSync(archivePath);
75
111
  unlinkSync(metadataPath);
76
112
 
77
- // 7. Persist signing key so it survives daemon/gateway restarts (same as wake)
113
+ // 7. Persist signing key and bootstrap secret so they survive daemon/gateway restarts
78
114
  const signingKey = generateLocalSigningKey();
115
+ const bootstrapSecret = generateLocalSigningKey();
79
116
  entry.resources = { ...entry.resources, signingKey };
117
+ entry.guardianBootstrapSecret = bootstrapSecret;
80
118
  saveAssistantEntry(entry);
81
119
 
82
120
  // 8. Start daemon + gateway
83
121
  await startLocalDaemon(false, entry.resources, { signingKey });
84
- await startGateway(false, entry.resources, { signingKey });
122
+ await startGateway(false, entry.resources, { signingKey, bootstrapSecret });
85
123
 
86
124
  console.log(`✅ Recovered assistant '${name}'.`);
87
125
  }
@@ -97,6 +97,7 @@ async function getAccessToken(
97
97
  runtimeUrl: string,
98
98
  assistantId: string,
99
99
  displayName: string,
100
+ bootstrapSecret?: string,
100
101
  ): Promise<string> {
101
102
  const tokenData = loadGuardianToken(assistantId);
102
103
 
@@ -105,7 +106,11 @@ async function getAccessToken(
105
106
  }
106
107
 
107
108
  try {
108
- const freshToken = await leaseGuardianToken(runtimeUrl, assistantId);
109
+ const freshToken = await leaseGuardianToken(
110
+ runtimeUrl,
111
+ assistantId,
112
+ bootstrapSecret,
113
+ );
109
114
  return freshToken.accessToken;
110
115
  } catch (err) {
111
116
  const msg = err instanceof Error ? err.message : String(err);
@@ -574,6 +579,7 @@ export async function restore(): Promise<void> {
574
579
  entry.runtimeUrl,
575
580
  entry.assistantId,
576
581
  name,
582
+ entry.guardianBootstrapSecret,
577
583
  );
578
584
 
579
585
  if (dryRun) {
@@ -679,6 +685,7 @@ export async function restore(): Promise<void> {
679
685
  entry.runtimeUrl,
680
686
  entry.assistantId,
681
687
  name,
688
+ entry.guardianBootstrapSecret,
682
689
  );
683
690
  }
684
691
 
@@ -2,14 +2,16 @@ import { existsSync, unlinkSync } from "fs";
2
2
  import { join } from "path";
3
3
 
4
4
  import {
5
+ extractHostFromUrl,
5
6
  formatAssistantLookupError,
6
7
  formatAssistantReference,
7
8
  getAssistantDisplayName,
8
9
  loadAllAssistants,
9
10
  lookupAssistantByIdentifier,
10
11
  removeAssistantEntry,
12
+ resolveCloud,
13
+ type AssistantEntry,
11
14
  } from "../lib/assistant-config.js";
12
- import type { AssistantEntry } from "../lib/assistant-config.js";
13
15
  import { parseAssistantTargetArg } from "../lib/assistant-target-args.js";
14
16
  import { getConfigDir } from "../lib/environments/paths.js";
15
17
  import { getCurrentEnvironment } from "../lib/environments/resolve.js";
@@ -31,28 +33,6 @@ import {
31
33
  writeToLogFile,
32
34
  } from "../lib/xdg-log.js";
33
35
 
34
- function resolveCloud(entry: AssistantEntry): string {
35
- if (entry.cloud) {
36
- return entry.cloud;
37
- }
38
- if (entry.project) {
39
- return "gcp";
40
- }
41
- if (entry.sshUser) {
42
- return "custom";
43
- }
44
- return "local";
45
- }
46
-
47
- function extractHostFromUrl(url: string): string {
48
- try {
49
- const parsed = new URL(url);
50
- return parsed.hostname;
51
- } catch {
52
- return url.replace(/^https?:\/\//, "").split(":")[0];
53
- }
54
- }
55
-
56
36
  export { retireLocal };
57
37
 
58
38
  interface RetireArgs {
@@ -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
  });
@@ -7,9 +7,10 @@ import {
7
7
  findAssistantByName,
8
8
  getActiveAssistant,
9
9
  loadAllAssistants,
10
+ resolveCloud,
10
11
  saveAssistantEntry,
12
+ type AssistantEntry,
11
13
  } from "../lib/assistant-config";
12
- import type { AssistantEntry } from "../lib/assistant-config";
13
14
  import {
14
15
  captureImageRefs,
15
16
  GATEWAY_INTERNAL_PORT,
@@ -145,19 +146,6 @@ function parseArgs(): UpgradeArgs {
145
146
  return { name, version, latest, prepare, finalize };
146
147
  }
147
148
 
148
- function resolveCloud(entry: AssistantEntry): string {
149
- if (entry.cloud) {
150
- return entry.cloud;
151
- }
152
- if (entry.project) {
153
- return "gcp";
154
- }
155
- if (entry.sshUser) {
156
- return "custom";
157
- }
158
- return "local";
159
- }
160
-
161
149
  /**
162
150
  * Resolve which assistant to target for the upgrade command. Priority:
163
151
  * 1. Explicit name argument
@@ -512,7 +500,10 @@ async function upgradeDocker(
512
500
  } else {
513
501
  console.error(`\n❌ Containers failed to become ready within the timeout.`);
514
502
 
515
- const logDir = await captureUpgradeFailureLogs(res, `${instanceName}-upgrade-failure`);
503
+ const logDir = await captureUpgradeFailureLogs(
504
+ res,
505
+ `${instanceName}-upgrade-failure`,
506
+ );
516
507
  if (logDir) {
517
508
  console.log(`📋 Container logs saved to: ${logDir}`);
518
509
  }
@@ -938,7 +929,8 @@ async function resolveLatestAndMaybeSelfUpdate(
938
929
  );
939
930
  if (installResult.error || installResult.status !== 0) {
940
931
  const detail =
941
- installResult.error?.message ?? `exited with code ${installResult.status}`;
932
+ installResult.error?.message ??
933
+ `exited with code ${installResult.status}`;
942
934
  console.error(`\n❌ CLI self-update failed: ${detail}`);
943
935
  emitCliError("CLI_UPDATE_FAILED", "CLI self-update failed", detail);
944
936
  process.exit(1);
@@ -8,7 +8,7 @@ import {
8
8
  } from "../lib/assistant-config.js";
9
9
  import { dockerResourceNames, wakeContainers } from "../lib/docker.js";
10
10
  import { seedGuardianTokenFromSiblingEnv } from "../lib/guardian-token.js";
11
- import { isProcessAlive, stopProcessByPidFile } from "../lib/process";
11
+ import { resolveProcessState, stopProcessByPidFile } from "../lib/process";
12
12
  import {
13
13
  generateLocalSigningKey,
14
14
  isAssistantWatchModeAvailable,
@@ -85,36 +85,26 @@ export async function wake(): Promise<void> {
85
85
 
86
86
  const pidFile = getDaemonPidPath(resources);
87
87
 
88
- // Check if daemon is already running
89
88
  let daemonRunning = false;
90
- if (existsSync(pidFile)) {
91
- const pidStr = readFileSync(pidFile, "utf-8").trim();
92
- const pid = parseInt(pidStr, 10);
93
- if (!isNaN(pid)) {
94
- try {
95
- process.kill(pid, 0);
96
- daemonRunning = true;
97
- if (watch) {
98
- // Restart in watch mode but only if source files are available.
99
- // Watch mode requires bun --watch with .ts sources; packaged desktop
100
- // builds only have a compiled binary. Stopping the daemon without a
101
- // viable watch-mode path would leave the user with no running assistant.
102
- if (!isAssistantWatchModeAvailable()) {
103
- console.log(
104
- `Assistant running (pid ${pid}) — watch mode not available (no source files). Keeping existing process.`,
105
- );
106
- } else {
107
- console.log(
108
- `Assistant running (pid ${pid}) — restarting in watch mode...`,
109
- );
110
- await stopProcessByPidFile(pidFile, "assistant");
111
- daemonRunning = false;
112
- }
113
- } else {
114
- console.log(`Assistant already running (pid ${pid}).`);
115
- }
116
- } catch {
117
- // Process not alive, will start below
89
+ const daemonState = await resolveProcessState(
90
+ pidFile,
91
+ resources.daemonPort,
92
+ "Assistant",
93
+ );
94
+ if (daemonState.status === "healthy") {
95
+ if (watch && isAssistantWatchModeAvailable()) {
96
+ console.log(
97
+ `Assistant running (pid ${daemonState.pid})restarting in watch mode...`,
98
+ );
99
+ await stopProcessByPidFile(pidFile, "assistant");
100
+ } else {
101
+ daemonRunning = true;
102
+ if (watch) {
103
+ console.log(
104
+ `Assistant running (pid ${daemonState.pid}) — watch mode not available (no source files). Keeping existing process.`,
105
+ );
106
+ } else {
107
+ console.log(`Assistant already running (pid ${daemonState.pid}).`);
118
108
  }
119
109
  }
120
110
  }
@@ -153,6 +143,15 @@ export async function wake(): Promise<void> {
153
143
  saveAssistantEntry(entry);
154
144
  }
155
145
 
146
+ let bootstrapSecret = entry.guardianBootstrapSecret;
147
+ let bootstrapSecretBackfilled = false;
148
+ if (!bootstrapSecret) {
149
+ bootstrapSecret = generateLocalSigningKey();
150
+ entry.guardianBootstrapSecret = bootstrapSecret;
151
+ saveAssistantEntry(entry);
152
+ bootstrapSecretBackfilled = true;
153
+ }
154
+
156
155
  if (!daemonRunning) {
157
156
  await startLocalDaemon(watch, resources, { foreground, signingKey });
158
157
  }
@@ -161,26 +160,47 @@ export async function wake(): Promise<void> {
161
160
  {
162
161
  const vellumDir = join(resources.instanceDir, ".vellum");
163
162
  const gatewayPidFile = join(vellumDir, "gateway.pid");
164
- const { alive, pid } = isProcessAlive(gatewayPidFile);
165
- if (alive) {
166
- if (watch) {
167
- // Guard gateway restart separately: check gateway source availability.
168
- if (!isGatewayWatchModeAvailable()) {
163
+ const gatewayState = await resolveProcessState(
164
+ gatewayPidFile,
165
+ resources.gatewayPort,
166
+ "Gateway",
167
+ );
168
+ const gatewayAlive = gatewayState.status === "healthy";
169
+ const needsRestart = bootstrapSecretBackfilled && gatewayAlive;
170
+ if (needsRestart) {
171
+ const restartWithWatch = watch && isGatewayWatchModeAvailable();
172
+ if (restartWithWatch) {
173
+ console.log(
174
+ `Gateway running (pid ${gatewayState.pid}) — restarting to apply bootstrap secret...`,
175
+ );
176
+ } else {
177
+ console.log(
178
+ `Gateway running (pid ${gatewayState.pid}) — restarting without watch mode to apply bootstrap secret...`,
179
+ );
180
+ }
181
+ await stopProcessByPidFile(gatewayPidFile, "gateway");
182
+ await startGateway(restartWithWatch, resources, {
183
+ signingKey,
184
+ bootstrapSecret,
185
+ });
186
+ } else if (gatewayAlive) {
187
+ if (watch && isGatewayWatchModeAvailable()) {
188
+ console.log(
189
+ `Gateway running (pid ${gatewayState.pid}) — restarting in watch mode...`,
190
+ );
191
+ await stopProcessByPidFile(gatewayPidFile, "gateway");
192
+ await startGateway(watch, resources, { signingKey, bootstrapSecret });
193
+ } else {
194
+ if (watch) {
169
195
  console.log(
170
- `Gateway running (pid ${pid}) — watch mode not available (no source files). Keeping existing process.`,
196
+ `Gateway running (pid ${gatewayState.pid}) — watch mode not available (no source files). Keeping existing process.`,
171
197
  );
172
198
  } else {
173
- console.log(
174
- `Gateway running (pid ${pid}) — restarting in watch mode...`,
175
- );
176
- await stopProcessByPidFile(gatewayPidFile, "gateway");
177
- await startGateway(watch, resources, { signingKey });
199
+ console.log(`Gateway already running (pid ${gatewayState.pid}).`);
178
200
  }
179
- } else {
180
- console.log(`Gateway already running (pid ${pid}).`);
181
201
  }
182
202
  } else {
183
- await startGateway(watch, resources, { signingKey });
203
+ await startGateway(watch, resources, { signingKey, bootstrapSecret });
184
204
  }
185
205
  }
186
206
 
@@ -196,7 +216,10 @@ export async function wake(): Promise<void> {
196
216
 
197
217
  // Auto-start ngrok if webhook integrations (e.g. Telegram) are configured.
198
218
  const workspaceDir = join(resources.instanceDir, ".vellum", "workspace");
199
- const ngrokChild = await maybeStartNgrokTunnel(resources.gatewayPort, workspaceDir);
219
+ const ngrokChild = await maybeStartNgrokTunnel(
220
+ resources.gatewayPort,
221
+ workspaceDir,
222
+ );
200
223
  if (ngrokChild?.pid) {
201
224
  const ngrokPidFile = join(resources.instanceDir, ".vellum", "ngrok.pid");
202
225
  writeFileSync(ngrokPidFile, String(ngrokChild.pid));
package/src/index.ts CHANGED
@@ -7,6 +7,8 @@ import { client } from "./commands/client";
7
7
  import { env } from "./commands/env";
8
8
  import { events } from "./commands/events";
9
9
  import { exec } from "./commands/exec";
10
+ import { flags } from "./commands/flags";
11
+ import { gateway } from "./commands/gateway";
10
12
  import { hatch } from "./commands/hatch";
11
13
  import { login, logout, whoami } from "./commands/login";
12
14
  import { logs } from "./commands/logs";
@@ -37,6 +39,8 @@ const commands = {
37
39
  env,
38
40
  events,
39
41
  exec,
42
+ flags,
43
+ gateway,
40
44
  hatch,
41
45
  login,
42
46
  logout,
@@ -72,6 +76,8 @@ function printHelp(): void {
72
76
  console.log(" env Manage the default CLI environment");
73
77
  console.log(" events Stream events from a running assistant");
74
78
  console.log(" exec Execute a command inside an assistant's container");
79
+ console.log(" flags Show and toggle feature flags");
80
+ console.log(" gateway Gateway management commands");
75
81
  console.log(" hatch Create a new assistant instance");
76
82
  console.log(" logs View logs from an assistant instance");
77
83
  console.log(" login Log in to the Vellum platform");