@vellumai/cli 0.5.6 → 0.5.8

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/knip.json CHANGED
@@ -3,7 +3,9 @@
3
3
  "src/**/*.test.ts",
4
4
  "src/**/__tests__/**/*.ts",
5
5
  "src/adapters/openclaw-http-server.ts",
6
- "src/lib/version-compat.ts"
6
+ "src/lib/version-compat.ts",
7
+ "src/lib/platform-releases.ts",
8
+ "src/lib/cli-error.ts"
7
9
  ],
8
10
  "project": ["src/**/*.ts", "src/**/*.tsx"]
9
11
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/cli",
3
- "version": "0.5.6",
3
+ "version": "0.5.8",
4
4
  "description": "CLI tools for vellum-assistant",
5
5
  "type": "module",
6
6
  "exports": {
@@ -1,21 +1,16 @@
1
1
  import { mkdirSync, writeFileSync } from "fs";
2
- import { homedir } from "os";
3
2
  import { dirname, join } from "path";
4
3
 
5
4
  import { findAssistantByName } from "../lib/assistant-config";
5
+ import { getBackupsDir, formatSize } from "../lib/backup-ops.js";
6
6
  import { loadGuardianToken, leaseGuardianToken } from "../lib/guardian-token";
7
-
8
- function getBackupsDir(): string {
9
- const dataHome =
10
- process.env.XDG_DATA_HOME?.trim() || join(homedir(), ".local", "share");
11
- return join(dataHome, "vellum", "backups");
12
- }
13
-
14
- function formatSize(bytes: number): string {
15
- if (bytes < 1024) return `${bytes} B`;
16
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
17
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
18
- }
7
+ import {
8
+ readPlatformToken,
9
+ fetchOrganizationId,
10
+ platformInitiateExport,
11
+ platformPollExportStatus,
12
+ platformDownloadExport,
13
+ } from "../lib/platform-client.js";
19
14
 
20
15
  export async function backup(): Promise<void> {
21
16
  const args = process.argv.slice(3);
@@ -67,6 +62,14 @@ export async function backup(): Promise<void> {
67
62
  process.exit(1);
68
63
  }
69
64
 
65
+ // Detect topology and route platform assistants through Django export
66
+ const cloud =
67
+ entry.cloud || (entry.project ? "gcp" : entry.sshUser ? "custom" : "local");
68
+ if (cloud === "vellum") {
69
+ await backupPlatform(name, outputArg);
70
+ return;
71
+ }
72
+
70
73
  // Obtain an auth token
71
74
  let accessToken: string;
72
75
  const tokenData = loadGuardianToken(entry.assistantId);
@@ -104,6 +107,33 @@ export async function backup(): Promise<void> {
104
107
  body: JSON.stringify({ description: "CLI backup" }),
105
108
  signal: AbortSignal.timeout(120_000),
106
109
  });
110
+
111
+ // Retry once with a fresh token on 401 — the cached token may be stale
112
+ // after a container restart that generated a new gateway signing key.
113
+ if (response.status === 401) {
114
+ let refreshedToken: string | null = null;
115
+ try {
116
+ const freshToken = await leaseGuardianToken(
117
+ entry.runtimeUrl,
118
+ entry.assistantId,
119
+ );
120
+ refreshedToken = freshToken.accessToken;
121
+ } catch {
122
+ // If token refresh fails, fall through to the !response.ok handler below
123
+ }
124
+ if (refreshedToken) {
125
+ accessToken = refreshedToken;
126
+ response = await fetch(`${entry.runtimeUrl}/v1/migrations/export`, {
127
+ method: "POST",
128
+ headers: {
129
+ Authorization: `Bearer ${accessToken}`,
130
+ "Content-Type": "application/json",
131
+ },
132
+ body: JSON.stringify({ description: "CLI backup" }),
133
+ signal: AbortSignal.timeout(120_000),
134
+ });
135
+ }
136
+ }
107
137
  } catch (err) {
108
138
  if (err instanceof Error && err.name === "TimeoutError") {
109
139
  console.error("Error: Export request timed out after 2 minutes.");
@@ -149,3 +179,112 @@ export async function backup(): Promise<void> {
149
179
  console.log(`Manifest SHA-256: ${manifestSha}`);
150
180
  }
151
181
  }
182
+
183
+ // ---------------------------------------------------------------------------
184
+ // Platform (Vellum-hosted) backup via Django async migration export
185
+ // ---------------------------------------------------------------------------
186
+
187
+ async function backupPlatform(name: string, outputArg?: string): Promise<void> {
188
+ // Step 1 — Authenticate
189
+ const token = readPlatformToken();
190
+ if (!token) {
191
+ console.error("Not logged in. Run 'vellum login' first.");
192
+ process.exit(1);
193
+ }
194
+
195
+ let orgId: string;
196
+ try {
197
+ orgId = await fetchOrganizationId(token);
198
+ } catch (err) {
199
+ const msg = err instanceof Error ? err.message : String(err);
200
+ if (msg.includes("401") || msg.includes("403")) {
201
+ console.error("Authentication failed. Run 'vellum login' to refresh.");
202
+ process.exit(1);
203
+ }
204
+ throw err;
205
+ }
206
+
207
+ // Step 2 — Initiate export job
208
+ let jobId: string;
209
+ try {
210
+ const result = await platformInitiateExport(token, orgId, "CLI backup");
211
+ jobId = result.jobId;
212
+ } catch (err) {
213
+ const msg = err instanceof Error ? err.message : String(err);
214
+ if (msg.includes("401") || msg.includes("403")) {
215
+ console.error("Authentication failed. Run 'vellum login' to refresh.");
216
+ process.exit(1);
217
+ }
218
+ if (msg.includes("429")) {
219
+ console.error(
220
+ "Too many export requests. Please wait before trying again.",
221
+ );
222
+ process.exit(1);
223
+ }
224
+ throw err;
225
+ }
226
+
227
+ console.log(`Export started (job ${jobId})...`);
228
+
229
+ // Step 3 — Poll for completion
230
+ const POLL_INTERVAL_MS = 2_000;
231
+ const TIMEOUT_MS = 5 * 60 * 1_000; // 5 minutes
232
+ const deadline = Date.now() + TIMEOUT_MS;
233
+ let downloadUrl: string | undefined;
234
+
235
+ while (Date.now() < deadline) {
236
+ let status: { status: string; downloadUrl?: string; error?: string };
237
+ try {
238
+ status = await platformPollExportStatus(jobId, token, orgId);
239
+ } catch (err) {
240
+ const msg = err instanceof Error ? err.message : String(err);
241
+ // Let non-transient errors (e.g. 404 "job not found") propagate immediately
242
+ if (msg.includes("not found")) {
243
+ throw err;
244
+ }
245
+ console.warn(`Polling failed, retrying... (${msg})`);
246
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
247
+ continue;
248
+ }
249
+
250
+ if (status.status === "complete") {
251
+ downloadUrl = status.downloadUrl;
252
+ break;
253
+ }
254
+
255
+ if (status.status === "failed") {
256
+ console.error(`Export failed: ${status.error ?? "unknown error"}`);
257
+ process.exit(1);
258
+ }
259
+
260
+ // Still in progress — wait and retry
261
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
262
+ }
263
+
264
+ if (!downloadUrl) {
265
+ console.error("Export timed out after 5 minutes.");
266
+ process.exit(1);
267
+ }
268
+
269
+ // Step 4 — Download bundle
270
+ const isoTimestamp = new Date().toISOString().replace(/[:.]/g, "-");
271
+ const outputPath =
272
+ outputArg || join(getBackupsDir(), `${name}-${isoTimestamp}.vbundle`);
273
+
274
+ mkdirSync(dirname(outputPath), { recursive: true });
275
+
276
+ const response = await platformDownloadExport(downloadUrl);
277
+ const arrayBuffer = await response.arrayBuffer();
278
+ const data = new Uint8Array(arrayBuffer);
279
+
280
+ writeFileSync(outputPath, data);
281
+
282
+ // Step 5 — Print success
283
+ console.log(`Backup saved to ${outputPath}`);
284
+ console.log(`Size: ${formatSize(data.byteLength)}`);
285
+
286
+ const manifestSha = response.headers.get("X-Vbundle-Manifest-Sha256");
287
+ if (manifestSha) {
288
+ console.log(`Manifest SHA-256: ${manifestSha}`);
289
+ }
290
+ }
@@ -1,3 +1,4 @@
1
+ import { randomBytes } from "crypto";
1
2
  import {
2
3
  appendFileSync,
3
4
  existsSync,
@@ -39,12 +40,14 @@ import type { RemoteHost, Species } from "../lib/constants";
39
40
  import { hatchDocker } from "../lib/docker";
40
41
  import { hatchGcp } from "../lib/gcp";
41
42
  import type { PollResult, WatchHatchingResult } from "../lib/gcp";
43
+ import { buildNestedConfig, writeInitialConfig } from "../lib/config-utils";
42
44
  import {
43
45
  startLocalDaemon,
44
46
  startGateway,
45
47
  stopLocalProcesses,
46
48
  } from "../lib/local";
47
49
  import { maybeStartNgrokTunnel } from "../lib/ngrok";
50
+ import { getPlatformUrl } from "../lib/platform-client";
48
51
  import { httpHealthCheck } from "../lib/http-client";
49
52
  import { detectOrphanedProcesses } from "../lib/orphan-detection";
50
53
  import { isProcessAlive, stopProcess } from "../lib/process";
@@ -99,8 +102,9 @@ export async function buildStartupScript(
99
102
  providerApiKeys: Record<string, string>,
100
103
  instanceName: string,
101
104
  cloud: RemoteHost,
102
- ): Promise<string> {
103
- const platformUrl = process.env.VELLUM_PLATFORM_URL ?? "https://vellum.ai";
105
+ configValues: Record<string, string> = {},
106
+ ): Promise<{ script: string; laptopBootstrapSecret: string }> {
107
+ const platformUrl = getPlatformUrl();
104
108
  const logPath =
105
109
  cloud === "custom"
106
110
  ? "/tmp/vellum-startup.log"
@@ -112,25 +116,57 @@ export async function buildStartupScript(
112
116
  const ownershipFixup = buildOwnershipFixup();
113
117
 
114
118
  if (species === "openclaw") {
115
- return await buildOpenclawStartupScript(
119
+ const script = await buildOpenclawStartupScript(
116
120
  sshUser,
117
121
  providerApiKeys,
118
122
  timestampRedirect,
119
123
  userSetup,
120
124
  ownershipFixup,
121
125
  );
126
+ return { script, laptopBootstrapSecret: "" };
122
127
  }
123
128
 
129
+ // Generate a bootstrap secret for the laptop that initiated this remote
130
+ // hatch. The startup script exports it as GUARDIAN_BOOTSTRAP_SECRET so
131
+ // that when `vellum hatch --remote docker` runs on the VM, the docker
132
+ // hatch detects the pre-set env var and appends its own secret.
133
+ const laptopBootstrapSecret = randomBytes(32).toString("hex");
134
+
124
135
  // Build bash lines that set each provider API key as a shell variable
125
136
  // and corresponding dotenv lines for the env file.
126
- const envSetLines = Object.entries(providerApiKeys)
137
+ // Include the laptop bootstrap secret so that when the remote runs
138
+ // `vellum hatch --remote docker`, the docker hatch detects the pre-set
139
+ // env var and appends its own secret for multi-secret guardian init.
140
+ const allEnvEntries: Record<string, string> = {
141
+ ...providerApiKeys,
142
+ GUARDIAN_BOOTSTRAP_SECRET: laptopBootstrapSecret,
143
+ };
144
+ const envSetLines = Object.entries(allEnvEntries)
127
145
  .map(([envVar, value]) => `${envVar}=${value}`)
128
146
  .join("\n");
129
147
  const dotenvLines = Object.keys(providerApiKeys)
130
148
  .map((envVar) => `${envVar}=\$${envVar}`)
131
149
  .join("\n");
132
150
 
133
- return `#!/bin/bash
151
+ // Write --config key=value pairs to a temp JSON file on the remote host
152
+ // and export the env var so the daemon reads it on first boot.
153
+ let configWriteBlock = "";
154
+ if (Object.keys(configValues).length > 0) {
155
+ const configJson = JSON.stringify(buildNestedConfig(configValues), null, 2);
156
+ configWriteBlock = `
157
+ echo "Writing default workspace config..."
158
+ VELLUM_DEFAULT_WORKSPACE_CONFIG_PATH="/tmp/vellum-initial-config-$$.json"
159
+ cat > "\$VELLUM_DEFAULT_WORKSPACE_CONFIG_PATH" << 'VELLUM_CONFIG_EOF'
160
+ ${configJson}
161
+ VELLUM_CONFIG_EOF
162
+ export VELLUM_DEFAULT_WORKSPACE_CONFIG_PATH
163
+ echo "Default workspace config written to \$VELLUM_DEFAULT_WORKSPACE_CONFIG_PATH"
164
+ `;
165
+ }
166
+
167
+ return {
168
+ laptopBootstrapSecret,
169
+ script: `#!/bin/bash
134
170
  set -e
135
171
 
136
172
  ${timestampRedirect}
@@ -146,16 +182,19 @@ RUNTIME_HTTP_PORT=7821
146
182
  DOTENV_EOF
147
183
 
148
184
  ${ownershipFixup}
149
-
185
+ ${configWriteBlock}
186
+ export GUARDIAN_BOOTSTRAP_SECRET
150
187
  export VELLUM_SSH_USER="\$SSH_USER"
151
188
  export VELLUM_ASSISTANT_NAME="\$VELLUM_ASSISTANT_NAME"
189
+ export VELLUM_CLOUD="${cloud}"
152
190
  echo "Downloading install script from ${platformUrl}/install.sh..."
153
191
  curl -fsSL ${platformUrl}/install.sh -o ${INSTALL_SCRIPT_REMOTE_PATH}
154
192
  echo "Install script downloaded (\$(wc -c < ${INSTALL_SCRIPT_REMOTE_PATH}) bytes)"
155
193
  chmod +x ${INSTALL_SCRIPT_REMOTE_PATH}
156
194
  echo "Running install script..."
157
195
  source ${INSTALL_SCRIPT_REMOTE_PATH}
158
- `;
196
+ `,
197
+ };
159
198
  }
160
199
 
161
200
  const DEFAULT_REMOTE: RemoteHost = "local";
@@ -166,9 +205,9 @@ interface HatchArgs {
166
205
  keepAlive: boolean;
167
206
  name: string | null;
168
207
  remote: RemoteHost;
169
- daemonOnly: boolean;
170
208
  restart: boolean;
171
209
  watch: boolean;
210
+ configValues: Record<string, string>;
172
211
  }
173
212
 
174
213
  function parseArgs(): HatchArgs {
@@ -178,9 +217,9 @@ function parseArgs(): HatchArgs {
178
217
  let keepAlive = false;
179
218
  let name: string | null = null;
180
219
  let remote: RemoteHost = DEFAULT_REMOTE;
181
- let daemonOnly = false;
182
220
  let restart = false;
183
221
  let watch = false;
222
+ const configValues: Record<string, string> = {};
184
223
 
185
224
  for (let i = 0; i < args.length; i++) {
186
225
  const arg = args[i];
@@ -199,9 +238,6 @@ function parseArgs(): HatchArgs {
199
238
  console.log(
200
239
  " --remote <host> Remote host (local, gcp, aws, docker, custom)",
201
240
  );
202
- console.log(
203
- " --daemon-only Start assistant only, skip gateway",
204
- );
205
241
  console.log(
206
242
  " --restart Restart processes without onboarding side effects",
207
243
  );
@@ -211,11 +247,12 @@ function parseArgs(): HatchArgs {
211
247
  console.log(
212
248
  " --keep-alive Stay alive after hatch, exit when gateway stops",
213
249
  );
250
+ console.log(
251
+ " --config <key=value> Set a workspace config value (repeatable)",
252
+ );
214
253
  process.exit(0);
215
254
  } else if (arg === "-d") {
216
255
  detached = true;
217
- } else if (arg === "--daemon-only") {
218
- daemonOnly = true;
219
256
  } else if (arg === "--restart") {
220
257
  restart = true;
221
258
  } else if (arg === "--watch") {
@@ -248,11 +285,28 @@ function parseArgs(): HatchArgs {
248
285
  }
249
286
  remote = next as RemoteHost;
250
287
  i++;
288
+ } else if (arg === "--config") {
289
+ const next = args[i + 1];
290
+ if (!next || next.startsWith("-")) {
291
+ console.error("Error: --config requires a key=value argument");
292
+ process.exit(1);
293
+ }
294
+ const eqIndex = next.indexOf("=");
295
+ if (eqIndex <= 0) {
296
+ console.error(
297
+ `Error: --config value must be in key=value format, got '${next}'`,
298
+ );
299
+ process.exit(1);
300
+ }
301
+ const key = next.slice(0, eqIndex);
302
+ const value = next.slice(eqIndex + 1);
303
+ configValues[key] = value;
304
+ i++;
251
305
  } else if (VALID_SPECIES.includes(arg as Species)) {
252
306
  species = arg as Species;
253
307
  } else {
254
308
  console.error(
255
- `Error: Unknown argument '${arg}'. Valid options: ${VALID_SPECIES.join(", ")}, -d, --daemon-only, --restart, --watch, --keep-alive, --name <name>, --remote <${VALID_REMOTE_HOSTS.join("|")}>`,
309
+ `Error: Unknown argument '${arg}'. Valid options: ${VALID_SPECIES.join(", ")}, -d, --restart, --watch, --keep-alive, --name <name>, --remote <${VALID_REMOTE_HOSTS.join("|")}>, --config <key=value>`,
256
310
  );
257
311
  process.exit(1);
258
312
  }
@@ -264,9 +318,9 @@ function parseArgs(): HatchArgs {
264
318
  keepAlive,
265
319
  name,
266
320
  remote,
267
- daemonOnly,
268
321
  restart,
269
322
  watch,
323
+ configValues,
270
324
  };
271
325
  }
272
326
 
@@ -574,10 +628,10 @@ function installCLISymlink(): void {
574
628
  async function hatchLocal(
575
629
  species: Species,
576
630
  name: string | null,
577
- daemonOnly: boolean = false,
578
631
  restart: boolean = false,
579
632
  watch: boolean = false,
580
633
  keepAlive: boolean = false,
634
+ configValues: Record<string, string> = {},
581
635
  ): Promise<void> {
582
636
  if (restart && !name && !process.env.VELLUM_ASSISTANT_NAME) {
583
637
  console.error(
@@ -709,46 +763,44 @@ async function hatchLocal(
709
763
  process.env.APP_VERSION = cliPkg.version;
710
764
  }
711
765
 
712
- await startLocalDaemon(watch, resources);
766
+ const defaultWorkspaceConfigPath = writeInitialConfig(configValues);
767
+
768
+ await startLocalDaemon(watch, resources, { defaultWorkspaceConfigPath });
713
769
 
714
- // When daemonOnly is set, skip gateway and ngrok — the caller only wants
715
- // the daemon restarted (e.g. macOS app bootstrap retry).
716
770
  let runtimeUrl = `http://127.0.0.1:${resources.gatewayPort}`;
717
- if (!daemonOnly) {
718
- try {
719
- runtimeUrl = await startGateway(watch, resources);
720
- } catch (error) {
721
- // Gateway failed stop the daemon we just started so we don't leave
722
- // orphaned processes with no lock file entry.
723
- console.error(
724
- `\n❌ Gateway startup failed — stopping assistant to avoid orphaned processes.`,
725
- );
726
- await stopLocalProcesses(resources);
727
- throw error;
728
- }
771
+ try {
772
+ runtimeUrl = await startGateway(watch, resources);
773
+ } catch (error) {
774
+ // Gateway failed — stop the daemon we just started so we don't leave
775
+ // orphaned processes with no lock file entry.
776
+ console.error(
777
+ `\n❌ Gateway startup failed — stopping assistant to avoid orphaned processes.`,
778
+ );
779
+ await stopLocalProcesses(resources);
780
+ throw error;
781
+ }
729
782
 
730
- // Lease a guardian token so the desktop app can import it on first launch
731
- // instead of hitting /v1/guardian/init itself.
732
- try {
733
- await leaseGuardianToken(runtimeUrl, instanceName);
734
- } catch (err) {
735
- console.error(`⚠️ Guardian token lease failed: ${err}`);
736
- }
783
+ // Lease a guardian token so the desktop app can import it on first launch
784
+ // instead of hitting /v1/guardian/init itself.
785
+ try {
786
+ await leaseGuardianToken(runtimeUrl, instanceName);
787
+ } catch (err) {
788
+ console.error(`⚠️ Guardian token lease failed: ${err}`);
789
+ }
737
790
 
738
- // Auto-start ngrok if webhook integrations (e.g. Telegram, Twilio) are configured.
739
- // Set BASE_DATA_DIR so ngrok reads the correct instance config.
740
- const prevBaseDataDir = process.env.BASE_DATA_DIR;
741
- process.env.BASE_DATA_DIR = resources.instanceDir;
742
- const ngrokChild = await maybeStartNgrokTunnel(resources.gatewayPort);
743
- if (ngrokChild?.pid) {
744
- const ngrokPidFile = join(resources.instanceDir, ".vellum", "ngrok.pid");
745
- writeFileSync(ngrokPidFile, String(ngrokChild.pid));
746
- }
747
- if (prevBaseDataDir !== undefined) {
748
- process.env.BASE_DATA_DIR = prevBaseDataDir;
749
- } else {
750
- delete process.env.BASE_DATA_DIR;
751
- }
791
+ // Auto-start ngrok if webhook integrations (e.g. Telegram, Twilio) are configured.
792
+ // Set BASE_DATA_DIR so ngrok reads the correct instance config.
793
+ const prevBaseDataDir = process.env.BASE_DATA_DIR;
794
+ process.env.BASE_DATA_DIR = resources.instanceDir;
795
+ const ngrokChild = await maybeStartNgrokTunnel(resources.gatewayPort);
796
+ if (ngrokChild?.pid) {
797
+ const ngrokPidFile = join(resources.instanceDir, ".vellum", "ngrok.pid");
798
+ writeFileSync(ngrokPidFile, String(ngrokChild.pid));
799
+ }
800
+ if (prevBaseDataDir !== undefined) {
801
+ process.env.BASE_DATA_DIR = prevBaseDataDir;
802
+ } else {
803
+ delete process.env.BASE_DATA_DIR;
752
804
  }
753
805
 
754
806
  const localEntry: AssistantEntry = {
@@ -761,7 +813,7 @@ async function hatchLocal(
761
813
  serviceGroupVersion: cliPkg.version ? `v${cliPkg.version}` : undefined,
762
814
  resources,
763
815
  };
764
- if (!daemonOnly && !restart) {
816
+ if (!restart) {
765
817
  saveAssistantEntry(localEntry);
766
818
  setActiveAssistant(instanceName);
767
819
  syncConfigToLockfile();
@@ -780,12 +832,8 @@ async function hatchLocal(
780
832
  }
781
833
 
782
834
  if (keepAlive) {
783
- // When --daemon-only is set, no gateway is running — poll the daemon
784
- // health endpoint instead of the gateway to avoid self-termination.
785
- const healthUrl = daemonOnly
786
- ? `http://127.0.0.1:${resources.daemonPort}/healthz`
787
- : `http://127.0.0.1:${resources.gatewayPort}/healthz`;
788
- const healthTarget = daemonOnly ? "Assistant" : "Gateway";
835
+ const healthUrl = `http://127.0.0.1:${resources.gatewayPort}/healthz`;
836
+ const healthTarget = "Gateway";
789
837
  const POLL_INTERVAL_MS = 5000;
790
838
  const MAX_FAILURES = 3;
791
839
  let consecutiveFailures = 0;
@@ -839,9 +887,9 @@ export async function hatch(): Promise<void> {
839
887
  keepAlive,
840
888
  name,
841
889
  remote,
842
- daemonOnly,
843
890
  restart,
844
891
  watch,
892
+ configValues,
845
893
  } = parseArgs();
846
894
 
847
895
  if (restart && remote !== "local") {
@@ -859,22 +907,29 @@ export async function hatch(): Promise<void> {
859
907
  }
860
908
 
861
909
  if (remote === "local") {
862
- await hatchLocal(species, name, daemonOnly, restart, watch, keepAlive);
910
+ await hatchLocal(species, name, restart, watch, keepAlive, configValues);
863
911
  return;
864
912
  }
865
913
 
866
914
  if (remote === "gcp") {
867
- await hatchGcp(species, detached, name, buildStartupScript, watchHatching);
915
+ await hatchGcp(
916
+ species,
917
+ detached,
918
+ name,
919
+ buildStartupScript,
920
+ watchHatching,
921
+ configValues,
922
+ );
868
923
  return;
869
924
  }
870
925
 
871
926
  if (remote === "aws") {
872
- await hatchAws(species, detached, name);
927
+ await hatchAws(species, detached, name, configValues);
873
928
  return;
874
929
  }
875
930
 
876
931
  if (remote === "docker") {
877
- await hatchDocker(species, detached, name, watch);
932
+ await hatchDocker(species, detached, name, watch, configValues);
878
933
  return;
879
934
  }
880
935