@vellumai/cli 0.5.3 ā 0.5.5
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 +12 -0
- package/package.json +1 -1
- package/src/__tests__/assistant-config.test.ts +46 -0
- package/src/adapters/openclaw.ts +4 -2
- package/src/commands/backup.ts +151 -0
- package/src/commands/hatch.ts +14 -4
- package/src/commands/restore.ts +310 -0
- package/src/commands/sleep.ts +9 -1
- package/src/commands/ssh.ts +11 -1
- package/src/commands/upgrade.ts +51 -0
- package/src/commands/wake.ts +10 -1
- package/src/index.ts +6 -0
- package/src/lib/assistant-config.ts +29 -0
- package/src/lib/aws.ts +13 -5
- package/src/lib/constants.ts +12 -0
- package/src/lib/docker.ts +250 -11
- package/src/lib/gcp.ts +18 -6
package/src/commands/upgrade.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import { randomBytes } from "crypto";
|
|
2
|
+
|
|
1
3
|
import cliPkg from "../../package.json";
|
|
2
4
|
|
|
3
5
|
import {
|
|
4
6
|
findAssistantByName,
|
|
5
7
|
getActiveAssistant,
|
|
6
8
|
loadAllAssistants,
|
|
9
|
+
saveAssistantEntry,
|
|
7
10
|
} from "../lib/assistant-config";
|
|
8
11
|
import type { AssistantEntry } from "../lib/assistant-config";
|
|
9
12
|
import {
|
|
@@ -12,6 +15,8 @@ import {
|
|
|
12
15
|
DOCKER_READY_TIMEOUT_MS,
|
|
13
16
|
GATEWAY_INTERNAL_PORT,
|
|
14
17
|
dockerResourceNames,
|
|
18
|
+
migrateCesSecurityFiles,
|
|
19
|
+
migrateGatewaySecurityFiles,
|
|
15
20
|
startContainers,
|
|
16
21
|
stopContainers,
|
|
17
22
|
} from "../lib/docker";
|
|
@@ -257,10 +262,18 @@ async function upgradeDocker(
|
|
|
257
262
|
// use default
|
|
258
263
|
}
|
|
259
264
|
|
|
265
|
+
// Extract CES_SERVICE_TOKEN from the captured env so it can be passed via
|
|
266
|
+
// the dedicated cesServiceToken parameter (which propagates it to all three
|
|
267
|
+
// containers). If the old instance predates CES_SERVICE_TOKEN, generate a
|
|
268
|
+
// fresh one so gateway and CES can authenticate.
|
|
269
|
+
const cesServiceToken =
|
|
270
|
+
capturedEnv["CES_SERVICE_TOKEN"] || randomBytes(32).toString("hex");
|
|
271
|
+
|
|
260
272
|
// Build the set of extra env vars to replay on the new assistant container.
|
|
261
273
|
// Captured env vars serve as the base; keys already managed by
|
|
262
274
|
// serviceDockerRunArgs are excluded to avoid duplicates.
|
|
263
275
|
const envKeysSetByRunArgs = new Set([
|
|
276
|
+
"CES_SERVICE_TOKEN",
|
|
264
277
|
"VELLUM_ASSISTANT_NAME",
|
|
265
278
|
"RUNTIME_HTTP_HOST",
|
|
266
279
|
"PATH",
|
|
@@ -278,9 +291,16 @@ async function upgradeDocker(
|
|
|
278
291
|
}
|
|
279
292
|
}
|
|
280
293
|
|
|
294
|
+
console.log("š Migrating security files to gateway volume...");
|
|
295
|
+
await migrateGatewaySecurityFiles(res, (msg) => console.log(msg));
|
|
296
|
+
|
|
297
|
+
console.log("š Migrating credential files to CES security volume...");
|
|
298
|
+
await migrateCesSecurityFiles(res, (msg) => console.log(msg));
|
|
299
|
+
|
|
281
300
|
console.log("š Starting upgraded containers...");
|
|
282
301
|
await startContainers(
|
|
283
302
|
{
|
|
303
|
+
cesServiceToken,
|
|
284
304
|
extraAssistantEnv,
|
|
285
305
|
gatewayPort,
|
|
286
306
|
imageTags,
|
|
@@ -294,6 +314,23 @@ async function upgradeDocker(
|
|
|
294
314
|
console.log("Waiting for assistant to become ready...");
|
|
295
315
|
const ready = await waitForReady(entry.runtimeUrl);
|
|
296
316
|
if (ready) {
|
|
317
|
+
// Update lockfile with new service group topology
|
|
318
|
+
const newDigests = await captureImageRefs(res);
|
|
319
|
+
const updatedEntry: AssistantEntry = {
|
|
320
|
+
...entry,
|
|
321
|
+
serviceGroupVersion: versionTag,
|
|
322
|
+
containerInfo: {
|
|
323
|
+
assistantImage: imageTags.assistant,
|
|
324
|
+
gatewayImage: imageTags.gateway,
|
|
325
|
+
cesImage: imageTags["credential-executor"],
|
|
326
|
+
assistantDigest: newDigests?.assistant,
|
|
327
|
+
gatewayDigest: newDigests?.gateway,
|
|
328
|
+
cesDigest: newDigests?.["credential-executor"],
|
|
329
|
+
networkName: res.network,
|
|
330
|
+
},
|
|
331
|
+
};
|
|
332
|
+
saveAssistantEntry(updatedEntry);
|
|
333
|
+
|
|
297
334
|
console.log(
|
|
298
335
|
`\nā
Docker assistant '${instanceName}' upgraded to ${versionTag}.`,
|
|
299
336
|
);
|
|
@@ -307,6 +344,7 @@ async function upgradeDocker(
|
|
|
307
344
|
|
|
308
345
|
await startContainers(
|
|
309
346
|
{
|
|
347
|
+
cesServiceToken,
|
|
310
348
|
extraAssistantEnv,
|
|
311
349
|
gatewayPort,
|
|
312
350
|
imageTags: previousImageRefs,
|
|
@@ -318,6 +356,19 @@ async function upgradeDocker(
|
|
|
318
356
|
|
|
319
357
|
const rollbackReady = await waitForReady(entry.runtimeUrl);
|
|
320
358
|
if (rollbackReady) {
|
|
359
|
+
// Restore previous container info in lockfile after rollback
|
|
360
|
+
if (previousImageRefs) {
|
|
361
|
+
const rolledBackEntry: AssistantEntry = {
|
|
362
|
+
...entry,
|
|
363
|
+
containerInfo: {
|
|
364
|
+
assistantImage: previousImageRefs.assistant,
|
|
365
|
+
gatewayImage: previousImageRefs.gateway,
|
|
366
|
+
cesImage: previousImageRefs["credential-executor"],
|
|
367
|
+
networkName: res.network,
|
|
368
|
+
},
|
|
369
|
+
};
|
|
370
|
+
saveAssistantEntry(rolledBackEntry);
|
|
371
|
+
}
|
|
321
372
|
console.log(
|
|
322
373
|
`\nā ļø Rolled back to previous version. Upgrade to ${versionTag} failed.`,
|
|
323
374
|
);
|
package/src/commands/wake.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
|
2
2
|
import { join } from "path";
|
|
3
3
|
|
|
4
4
|
import { resolveTargetAssistant } from "../lib/assistant-config.js";
|
|
5
|
+
import { dockerResourceNames, wakeContainers } from "../lib/docker.js";
|
|
5
6
|
import { isProcessAlive, stopProcessByPidFile } from "../lib/process";
|
|
6
7
|
import {
|
|
7
8
|
isAssistantWatchModeAvailable,
|
|
@@ -38,9 +39,17 @@ export async function wake(): Promise<void> {
|
|
|
38
39
|
const nameArg = args.find((a) => !a.startsWith("-"));
|
|
39
40
|
const entry = resolveTargetAssistant(nameArg);
|
|
40
41
|
|
|
42
|
+
if (entry.cloud === "docker") {
|
|
43
|
+
const res = dockerResourceNames(entry.assistantId);
|
|
44
|
+
await wakeContainers(res);
|
|
45
|
+
console.log("Docker containers started.");
|
|
46
|
+
console.log("Wake complete.");
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
41
50
|
if (entry.cloud && entry.cloud !== "local") {
|
|
42
51
|
console.error(
|
|
43
|
-
`Error: 'vellum wake' only works with local assistants. '${entry.assistantId}' is a ${entry.cloud} instance.`,
|
|
52
|
+
`Error: 'vellum wake' only works with local and docker assistants. '${entry.assistantId}' is a ${entry.cloud} instance.`,
|
|
44
53
|
);
|
|
45
54
|
process.exit(1);
|
|
46
55
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
3
|
import cliPkg from "../package.json";
|
|
4
|
+
import { backup } from "./commands/backup";
|
|
4
5
|
import { clean } from "./commands/clean";
|
|
5
6
|
import { client } from "./commands/client";
|
|
6
7
|
import { hatch } from "./commands/hatch";
|
|
@@ -8,6 +9,7 @@ import { login, logout, whoami } from "./commands/login";
|
|
|
8
9
|
import { pair } from "./commands/pair";
|
|
9
10
|
import { ps } from "./commands/ps";
|
|
10
11
|
import { recover } from "./commands/recover";
|
|
12
|
+
import { restore } from "./commands/restore";
|
|
11
13
|
import { retire } from "./commands/retire";
|
|
12
14
|
import { setup } from "./commands/setup";
|
|
13
15
|
import { sleep } from "./commands/sleep";
|
|
@@ -26,6 +28,7 @@ import { loadGuardianToken } from "./lib/guardian-token";
|
|
|
26
28
|
import { checkHealth } from "./lib/health-check";
|
|
27
29
|
|
|
28
30
|
const commands = {
|
|
31
|
+
backup,
|
|
29
32
|
clean,
|
|
30
33
|
client,
|
|
31
34
|
hatch,
|
|
@@ -34,6 +37,7 @@ const commands = {
|
|
|
34
37
|
pair,
|
|
35
38
|
ps,
|
|
36
39
|
recover,
|
|
40
|
+
restore,
|
|
37
41
|
retire,
|
|
38
42
|
setup,
|
|
39
43
|
sleep,
|
|
@@ -51,6 +55,7 @@ function printHelp(): void {
|
|
|
51
55
|
console.log("Usage: vellum <command> [options]");
|
|
52
56
|
console.log("");
|
|
53
57
|
console.log("Commands:");
|
|
58
|
+
console.log(" backup Export a backup of a running assistant");
|
|
54
59
|
console.log(" clean Kill orphaned vellum processes");
|
|
55
60
|
console.log(" client Connect to a hatched assistant");
|
|
56
61
|
console.log(" hatch Create a new assistant instance");
|
|
@@ -61,6 +66,7 @@ function printHelp(): void {
|
|
|
61
66
|
" ps List assistants (or processes for a specific assistant)",
|
|
62
67
|
);
|
|
63
68
|
console.log(" recover Restore a previously retired local assistant");
|
|
69
|
+
console.log(" restore Restore a .vbundle backup into a running assistant");
|
|
64
70
|
console.log(" retire Delete an assistant instance");
|
|
65
71
|
console.log(" setup Configure API keys interactively");
|
|
66
72
|
console.log(" sleep Stop the assistant process");
|
|
@@ -4,6 +4,7 @@ import { join } from "path";
|
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
6
|
DAEMON_INTERNAL_ASSISTANT_ID,
|
|
7
|
+
DEFAULT_CES_PORT,
|
|
7
8
|
DEFAULT_DAEMON_PORT,
|
|
8
9
|
DEFAULT_GATEWAY_PORT,
|
|
9
10
|
DEFAULT_QDRANT_PORT,
|
|
@@ -29,11 +30,28 @@ export interface LocalInstanceResources {
|
|
|
29
30
|
gatewayPort: number;
|
|
30
31
|
/** HTTP port for the Qdrant vector store */
|
|
31
32
|
qdrantPort: number;
|
|
33
|
+
/** HTTP port for the CES (Claude Extension Server) */
|
|
34
|
+
cesPort: number;
|
|
32
35
|
/** Absolute path to the daemon PID file */
|
|
33
36
|
pidFile: string;
|
|
34
37
|
[key: string]: unknown;
|
|
35
38
|
}
|
|
36
39
|
|
|
40
|
+
/** Docker image metadata for the service group. Enables rollback to known-good digests. */
|
|
41
|
+
export interface ContainerInfo {
|
|
42
|
+
assistantImage: string;
|
|
43
|
+
gatewayImage: string;
|
|
44
|
+
cesImage: string;
|
|
45
|
+
/** sha256 digest of the assistant image at time of hatch/upgrade */
|
|
46
|
+
assistantDigest?: string;
|
|
47
|
+
/** sha256 digest of the gateway image at time of hatch/upgrade */
|
|
48
|
+
gatewayDigest?: string;
|
|
49
|
+
/** sha256 digest of the CES image at time of hatch/upgrade */
|
|
50
|
+
cesDigest?: string;
|
|
51
|
+
/** Docker network name for the service group */
|
|
52
|
+
networkName?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
37
55
|
export interface AssistantEntry {
|
|
38
56
|
assistantId: string;
|
|
39
57
|
runtimeUrl: string;
|
|
@@ -56,6 +74,10 @@ export interface AssistantEntry {
|
|
|
56
74
|
resources?: LocalInstanceResources;
|
|
57
75
|
/** PID of the file watcher process for docker instances hatched with --watch. */
|
|
58
76
|
watcherPid?: number;
|
|
77
|
+
/** Last-known version of the service group, populated at hatch and updated by health checks. */
|
|
78
|
+
serviceGroupVersion?: string;
|
|
79
|
+
/** Docker image metadata for rollback. Only present for docker topology entries. */
|
|
80
|
+
containerInfo?: ContainerInfo;
|
|
59
81
|
[key: string]: unknown;
|
|
60
82
|
}
|
|
61
83
|
|
|
@@ -166,6 +188,7 @@ export function migrateLegacyEntry(raw: Record<string, unknown>): boolean {
|
|
|
166
188
|
daemonPort: DEFAULT_DAEMON_PORT,
|
|
167
189
|
gatewayPort,
|
|
168
190
|
qdrantPort: DEFAULT_QDRANT_PORT,
|
|
191
|
+
cesPort: DEFAULT_CES_PORT,
|
|
169
192
|
pidFile: join(instanceDir, ".vellum", "vellum.pid"),
|
|
170
193
|
};
|
|
171
194
|
mutated = true;
|
|
@@ -198,6 +221,10 @@ export function migrateLegacyEntry(raw: Record<string, unknown>): boolean {
|
|
|
198
221
|
res.qdrantPort = DEFAULT_QDRANT_PORT;
|
|
199
222
|
mutated = true;
|
|
200
223
|
}
|
|
224
|
+
if (typeof res.cesPort !== "number") {
|
|
225
|
+
res.cesPort = DEFAULT_CES_PORT;
|
|
226
|
+
mutated = true;
|
|
227
|
+
}
|
|
201
228
|
if (typeof res.pidFile !== "string") {
|
|
202
229
|
res.pidFile = join(res.instanceDir as string, ".vellum", "vellum.pid");
|
|
203
230
|
mutated = true;
|
|
@@ -373,6 +400,7 @@ export async function allocateLocalResources(
|
|
|
373
400
|
daemonPort: DEFAULT_DAEMON_PORT,
|
|
374
401
|
gatewayPort: DEFAULT_GATEWAY_PORT,
|
|
375
402
|
qdrantPort: DEFAULT_QDRANT_PORT,
|
|
403
|
+
cesPort: DEFAULT_CES_PORT,
|
|
376
404
|
pidFile: join(vellumDir, "vellum.pid"),
|
|
377
405
|
};
|
|
378
406
|
}
|
|
@@ -423,6 +451,7 @@ export async function allocateLocalResources(
|
|
|
423
451
|
daemonPort,
|
|
424
452
|
gatewayPort,
|
|
425
453
|
qdrantPort,
|
|
454
|
+
cesPort: DEFAULT_CES_PORT,
|
|
426
455
|
pidFile: join(instanceDir, ".vellum", "vellum.pid"),
|
|
427
456
|
};
|
|
428
457
|
}
|
package/src/lib/aws.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { buildStartupScript, watchHatching } from "../commands/hatch";
|
|
|
6
6
|
import type { PollResult } from "../commands/hatch";
|
|
7
7
|
import { saveAssistantEntry, setActiveAssistant } from "./assistant-config";
|
|
8
8
|
import type { AssistantEntry } from "./assistant-config";
|
|
9
|
-
import { GATEWAY_PORT } from "./constants";
|
|
9
|
+
import { GATEWAY_PORT, PROVIDER_ENV_VAR_NAMES } from "./constants";
|
|
10
10
|
import type { Species } from "./constants";
|
|
11
11
|
import { leaseGuardianToken } from "./guardian-token";
|
|
12
12
|
import { generateInstanceName } from "./random-name";
|
|
@@ -410,10 +410,18 @@ export async function hatchAws(
|
|
|
410
410
|
|
|
411
411
|
const sshUser = userInfo().username;
|
|
412
412
|
const hatchedBy = process.env.VELLUM_HATCHED_BY;
|
|
413
|
-
const
|
|
414
|
-
|
|
413
|
+
const providerApiKeys: Record<string, string> = {};
|
|
414
|
+
for (const [, envVar] of Object.entries(PROVIDER_ENV_VAR_NAMES)) {
|
|
415
|
+
const value = process.env[envVar];
|
|
416
|
+
if (value) {
|
|
417
|
+
providerApiKeys[envVar] = value;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
if (Object.keys(providerApiKeys).length === 0) {
|
|
415
421
|
console.error(
|
|
416
|
-
"Error:
|
|
422
|
+
"Error: No provider API key environment variable is set. " +
|
|
423
|
+
"Set at least one of: " +
|
|
424
|
+
Object.values(PROVIDER_ENV_VAR_NAMES).join(", "),
|
|
417
425
|
);
|
|
418
426
|
process.exit(1);
|
|
419
427
|
}
|
|
@@ -437,7 +445,7 @@ export async function hatchAws(
|
|
|
437
445
|
const startupScript = await buildStartupScript(
|
|
438
446
|
species,
|
|
439
447
|
sshUser,
|
|
440
|
-
|
|
448
|
+
providerApiKeys,
|
|
441
449
|
instanceName,
|
|
442
450
|
"aws",
|
|
443
451
|
);
|
package/src/lib/constants.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import providerEnvVarsRegistry from "../../../meta/provider-env-vars.json";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Canonical internal assistant ID used as the default/fallback across the CLI
|
|
3
5
|
* and daemon. Mirrors `DAEMON_INTERNAL_ASSISTANT_ID` from
|
|
@@ -14,6 +16,16 @@ export const GATEWAY_PORT = process.env.GATEWAY_PORT
|
|
|
14
16
|
export const DEFAULT_DAEMON_PORT = 7821;
|
|
15
17
|
export const DEFAULT_GATEWAY_PORT = 7830;
|
|
16
18
|
export const DEFAULT_QDRANT_PORT = 6333;
|
|
19
|
+
export const DEFAULT_CES_PORT = 8090;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Environment variable names for provider API keys, keyed by provider ID.
|
|
23
|
+
* Loaded from the shared registry at `meta/provider-env-vars.json` ā the
|
|
24
|
+
* single source of truth also consumed by the assistant runtime and the
|
|
25
|
+
* macOS client.
|
|
26
|
+
*/
|
|
27
|
+
export const PROVIDER_ENV_VAR_NAMES: Record<string, string> =
|
|
28
|
+
providerEnvVarsRegistry.providers;
|
|
17
29
|
|
|
18
30
|
export const VALID_REMOTE_HOSTS = [
|
|
19
31
|
"local",
|