@vellumai/cli 0.8.4 → 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.
- package/AGENTS.md +17 -1
- package/knip.json +2 -1
- package/package.json +1 -1
- package/src/__tests__/api-key-check.test.ts +78 -0
- package/src/__tests__/backup.test.ts +38 -0
- package/src/__tests__/recover.test.ts +307 -0
- package/src/__tests__/retire.test.ts +241 -0
- package/src/__tests__/wake.test.ts +215 -0
- package/src/commands/backup.ts +2 -0
- package/src/commands/client.ts +62 -32
- package/src/commands/flags.ts +197 -0
- package/src/commands/gateway/token.ts +73 -0
- package/src/commands/gateway.ts +29 -0
- package/src/commands/logs.ts +6 -18
- package/src/commands/ps.ts +41 -41
- package/src/commands/recover.ts +47 -9
- package/src/commands/restore.ts +8 -1
- package/src/commands/retire.ts +145 -55
- package/src/commands/roadmap.ts +449 -0
- package/src/commands/rollback.ts +2 -14
- package/src/commands/ssh.ts +5 -24
- package/src/commands/teleport.ts +34 -26
- package/src/commands/upgrade.ts +8 -16
- package/src/commands/wake.ts +68 -45
- package/src/index.ts +9 -0
- package/src/lib/__tests__/port-allocator.test.ts +117 -0
- package/src/lib/__tests__/step-runner.test.ts +133 -0
- package/src/lib/api-key-check.ts +40 -0
- package/src/lib/assistant-config.ts +13 -0
- package/src/lib/config-utils.ts +24 -3
- package/src/lib/docker.ts +72 -8
- package/src/lib/hatch-local.ts +15 -2
- package/src/lib/http-client.ts +1 -3
- package/src/lib/local.ts +173 -292
- package/src/lib/orphan-detection.ts +9 -5
- package/src/lib/pgrep.ts +5 -1
- package/src/lib/platform-client.ts +97 -49
- package/src/lib/port-allocator.ts +93 -0
- package/src/lib/process.ts +109 -39
- package/src/lib/statefulset.ts +0 -10
- package/src/lib/step-runner.ts +102 -9
- package/src/lib/sync-cloud-assistants.ts +17 -0
- package/src/shared/provider-env-vars.ts +1 -0
package/src/lib/docker.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { randomBytes } from "crypto";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
chmodSync,
|
|
4
|
+
existsSync,
|
|
5
|
+
mkdirSync,
|
|
6
|
+
readFileSync,
|
|
7
|
+
watch as fsWatch,
|
|
8
|
+
} from "fs";
|
|
3
9
|
import { arch, platform } from "os";
|
|
4
10
|
import { dirname, join, resolve } from "path";
|
|
5
11
|
|
|
@@ -35,7 +41,8 @@ import {
|
|
|
35
41
|
formatProviderName,
|
|
36
42
|
resolveHatchProvider,
|
|
37
43
|
} from "./provider-secrets.js";
|
|
38
|
-
import {
|
|
44
|
+
import { findOpenPort } from "./port-allocator.js";
|
|
45
|
+
import { exec, execOutput, execWithStdin } from "./step-runner";
|
|
39
46
|
import {
|
|
40
47
|
closeLogFile,
|
|
41
48
|
openLogFile,
|
|
@@ -645,7 +652,6 @@ export async function startContainers(
|
|
|
645
652
|
extraAssistantEnv?: Record<string, string>;
|
|
646
653
|
gatewayPort: number;
|
|
647
654
|
imageTags: Record<ServiceName, string>;
|
|
648
|
-
defaultWorkspaceConfigPath?: string;
|
|
649
655
|
instanceName: string;
|
|
650
656
|
res: ReturnType<typeof dockerResourceNames>;
|
|
651
657
|
},
|
|
@@ -981,7 +987,22 @@ export async function hatchDocker(
|
|
|
981
987
|
await ensureDockerInstalled();
|
|
982
988
|
|
|
983
989
|
const instanceName = generateInstanceName(species, name);
|
|
984
|
-
|
|
990
|
+
// Resolve the gateway's host port dynamically. The env-default
|
|
991
|
+
// (production 7830 / non-prod overrides) is just the *preferred*
|
|
992
|
+
// starting point — if it's taken by another local assistant, eval
|
|
993
|
+
// run, or unrelated process, we walk upward until we find a free
|
|
994
|
+
// port. This replaces the previous "first one in wins, everyone
|
|
995
|
+
// else gets a docker bind error" behavior and removes the need for
|
|
996
|
+
// an orphan-cleanup pre-flight in the evals harness.
|
|
997
|
+
const preferredGatewayPort = getDefaultPorts(
|
|
998
|
+
getCurrentEnvironment(),
|
|
999
|
+
).gateway;
|
|
1000
|
+
const gatewayPort = await findOpenPort(preferredGatewayPort);
|
|
1001
|
+
if (gatewayPort !== preferredGatewayPort) {
|
|
1002
|
+
log(
|
|
1003
|
+
`Preferred gateway port ${preferredGatewayPort} is in use; allocated ${gatewayPort} for this instance.`,
|
|
1004
|
+
);
|
|
1005
|
+
}
|
|
985
1006
|
|
|
986
1007
|
const imageTags: Record<ServiceName, string> = {
|
|
987
1008
|
assistant: "",
|
|
@@ -1150,10 +1171,53 @@ export async function hatchDocker(
|
|
|
1150
1171
|
"chown 1001:1001 /workspace /run/assistant-ipc /run/gateway-ipc",
|
|
1151
1172
|
]);
|
|
1152
1173
|
|
|
1153
|
-
//
|
|
1154
|
-
//
|
|
1174
|
+
// Stage the BYOK default-workspace-config overlay *inside* the workspace
|
|
1175
|
+
// volume so the daemon's startup loader can consume it. The loader
|
|
1176
|
+
// (`mergeDefaultWorkspaceConfig` in assistant/src/config/loader.ts) does:
|
|
1177
|
+
// 1. JSON.parse + deep-merge into the workspace's config.json.
|
|
1178
|
+
// 2. `renameSync(defaultConfigPath, <workspace>/default-config.json)`
|
|
1179
|
+
// to mark the overlay consumed so subsequent restarts skip it.
|
|
1180
|
+
//
|
|
1181
|
+
// The rename must be same-filesystem. Bind-mounting the host file into
|
|
1182
|
+
// `/tmp/...` (the pre-#32025 design) crossed filesystems and silently
|
|
1183
|
+
// failed with EXDEV, so every `docker start` re-applied the overlay.
|
|
1184
|
+
// Staging into the workspace volume keeps the rename in-place.
|
|
1185
|
+
//
|
|
1186
|
+
// Streaming the JSON via stdin (instead of bind-mounting the host file
|
|
1187
|
+
// into the staging container) sidesteps macOS Colima's virtiofs share,
|
|
1188
|
+
// which doesn't expose `/var/folders/...` (where `os.tmpdir()` resolves
|
|
1189
|
+
// on macOS) and would otherwise materialize an empty directory at the
|
|
1190
|
+
// bind-mount target.
|
|
1191
|
+
//
|
|
1192
|
+
// @deprecated stopgap. Replacement direction is one of:
|
|
1193
|
+
// 1. Post-hatch API calls (POST /v1/secrets + a small endpoint that
|
|
1194
|
+
// returns canonical inference-profile templates).
|
|
1195
|
+
// 2. Move inference-profile seeds out of workspace config and into
|
|
1196
|
+
// Assistant code, eliminating the overlay entirely.
|
|
1197
|
+
// See `cli/src/lib/config-utils.ts` JSDoc for context.
|
|
1155
1198
|
const hatchConfigValues = buildHatchConfigValues(configValues, provider);
|
|
1156
|
-
const
|
|
1199
|
+
const hostOverlayPath = writeInitialConfig(hatchConfigValues);
|
|
1200
|
+
const stagedOverlayInContainer = "/workspace/.default-config-overlay.json";
|
|
1201
|
+
const extraAssistantEnv: Record<string, string> = {};
|
|
1202
|
+
if (hostOverlayPath) {
|
|
1203
|
+
await execWithStdin(
|
|
1204
|
+
"docker",
|
|
1205
|
+
[
|
|
1206
|
+
"run",
|
|
1207
|
+
"--rm",
|
|
1208
|
+
"-i",
|
|
1209
|
+
"-v",
|
|
1210
|
+
`${res.workspaceVolume}:/workspace`,
|
|
1211
|
+
"busybox",
|
|
1212
|
+
"sh",
|
|
1213
|
+
"-c",
|
|
1214
|
+
`cat > ${stagedOverlayInContainer} && chown 1001:1001 ${stagedOverlayInContainer}`,
|
|
1215
|
+
],
|
|
1216
|
+
readFileSync(hostOverlayPath, "utf-8"),
|
|
1217
|
+
);
|
|
1218
|
+
extraAssistantEnv.VELLUM_DEFAULT_WORKSPACE_CONFIG_PATH =
|
|
1219
|
+
stagedOverlayInContainer;
|
|
1220
|
+
}
|
|
1157
1221
|
|
|
1158
1222
|
const cesServiceToken = randomBytes(32).toString("hex");
|
|
1159
1223
|
const signingKey = randomBytes(32).toString("hex");
|
|
@@ -1175,9 +1239,9 @@ export async function hatchDocker(
|
|
|
1175
1239
|
signingKey,
|
|
1176
1240
|
bootstrapSecret,
|
|
1177
1241
|
cesServiceToken,
|
|
1242
|
+
extraAssistantEnv,
|
|
1178
1243
|
gatewayPort,
|
|
1179
1244
|
imageTags,
|
|
1180
|
-
defaultWorkspaceConfigPath,
|
|
1181
1245
|
instanceName,
|
|
1182
1246
|
res,
|
|
1183
1247
|
},
|
package/src/lib/hatch-local.ts
CHANGED
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
resolveHatchProvider,
|
|
41
41
|
} from "./provider-secrets.js";
|
|
42
42
|
import { logHatchNextSteps } from "./hatch-next-steps.js";
|
|
43
|
+
import { checkProviderApiKey } from "./api-key-check.js";
|
|
43
44
|
|
|
44
45
|
/**
|
|
45
46
|
* Attempts to place a symlink at the given path pointing to cliBinary.
|
|
@@ -178,6 +179,16 @@ export async function hatchLocal(
|
|
|
178
179
|
console.log(` Species: ${species}`);
|
|
179
180
|
console.log("");
|
|
180
181
|
|
|
182
|
+
const apiKeyCheck = checkProviderApiKey();
|
|
183
|
+
if (!apiKeyCheck.hasKey) {
|
|
184
|
+
console.warn(
|
|
185
|
+
"Warning: No LLM provider API key is configured. The assistant will fail when you try to send a message.",
|
|
186
|
+
);
|
|
187
|
+
console.warn(" To fix, export your key before running vellum hatch:");
|
|
188
|
+
console.warn(" export ANTHROPIC_API_KEY=<your-key>");
|
|
189
|
+
console.warn("");
|
|
190
|
+
}
|
|
191
|
+
|
|
181
192
|
if (!process.env.APP_VERSION) {
|
|
182
193
|
process.env.APP_VERSION = cliPkg.version;
|
|
183
194
|
}
|
|
@@ -188,6 +199,7 @@ export async function hatchLocal(
|
|
|
188
199
|
|
|
189
200
|
emitProgress(3, 6, "Starting assistant...");
|
|
190
201
|
const signingKey = generateLocalSigningKey();
|
|
202
|
+
const bootstrapSecret = generateLocalSigningKey();
|
|
191
203
|
await startLocalDaemon(watch, resources, {
|
|
192
204
|
defaultWorkspaceConfigPath,
|
|
193
205
|
signingKey,
|
|
@@ -196,7 +208,7 @@ export async function hatchLocal(
|
|
|
196
208
|
emitProgress(4, 6, "Starting gateway...");
|
|
197
209
|
let runtimeUrl = `http://127.0.0.1:${resources.gatewayPort}`;
|
|
198
210
|
try {
|
|
199
|
-
runtimeUrl = await startGateway(watch, resources, { signingKey });
|
|
211
|
+
runtimeUrl = await startGateway(watch, resources, { signingKey, bootstrapSecret });
|
|
200
212
|
} catch (error) {
|
|
201
213
|
// Gateway failed — stop the daemon we just started so we don't leave
|
|
202
214
|
// orphaned processes with no lock file entry.
|
|
@@ -217,7 +229,7 @@ export async function hatchLocal(
|
|
|
217
229
|
let guardianAccessToken: string | undefined;
|
|
218
230
|
for (let attempt = 1; attempt <= maxLeaseAttempts; attempt++) {
|
|
219
231
|
try {
|
|
220
|
-
const tokenData = await leaseGuardianToken(loopbackUrl, instanceName);
|
|
232
|
+
const tokenData = await leaseGuardianToken(loopbackUrl, instanceName, bootstrapSecret);
|
|
221
233
|
guardianAccessToken = tokenData.accessToken;
|
|
222
234
|
break;
|
|
223
235
|
} catch (err) {
|
|
@@ -246,6 +258,7 @@ export async function hatchLocal(
|
|
|
246
258
|
species,
|
|
247
259
|
hatchedAt: new Date().toISOString(),
|
|
248
260
|
resources: { ...resources, signingKey },
|
|
261
|
+
guardianBootstrapSecret: bootstrapSecret,
|
|
249
262
|
};
|
|
250
263
|
|
|
251
264
|
emitProgress(6, 6, "Saving configuration...");
|
package/src/lib/http-client.ts
CHANGED
|
@@ -8,8 +8,6 @@ export function buildDaemonUrl(port: number): string {
|
|
|
8
8
|
/**
|
|
9
9
|
* Perform an HTTP health check against the daemon's `/healthz` endpoint.
|
|
10
10
|
* Returns true if the daemon responds with HTTP 200, false otherwise.
|
|
11
|
-
*
|
|
12
|
-
* This replaces the socket-based `isSocketResponsive()` check.
|
|
13
11
|
*/
|
|
14
12
|
export async function httpHealthCheck(
|
|
15
13
|
port: number,
|
|
@@ -28,7 +26,7 @@ export async function httpHealthCheck(
|
|
|
28
26
|
|
|
29
27
|
/**
|
|
30
28
|
* Poll the daemon's `/healthz` endpoint until it responds with 200 or the
|
|
31
|
-
* timeout is reached.
|
|
29
|
+
* timeout is reached.
|
|
32
30
|
*
|
|
33
31
|
* Returns true if the daemon became healthy within the timeout, false otherwise.
|
|
34
32
|
*/
|