@vellumai/cli 0.8.5 → 0.8.7-dev.202606052118.34cd356
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 +6 -0
- package/bun.lock +8 -0
- package/knip.json +6 -1
- package/node_modules/@vellumai/environments/bun.lock +24 -0
- package/node_modules/@vellumai/environments/package.json +18 -0
- package/node_modules/@vellumai/environments/src/__tests__/package-boundary.test.ts +95 -0
- package/node_modules/@vellumai/environments/src/index.ts +11 -0
- package/{src/lib/environments → node_modules/@vellumai/environments/src}/seeds.ts +5 -9
- package/node_modules/@vellumai/environments/tsconfig.json +20 -0
- package/node_modules/@vellumai/local-mode/bun.lock +29 -0
- package/node_modules/@vellumai/local-mode/package.json +22 -0
- package/node_modules/@vellumai/local-mode/src/__tests__/environment.test.ts +116 -0
- package/node_modules/@vellumai/local-mode/src/__tests__/gateway-proxy.test.ts +79 -0
- package/node_modules/@vellumai/local-mode/src/__tests__/hatch.test.ts +108 -0
- package/node_modules/@vellumai/local-mode/src/__tests__/package-boundary.test.ts +104 -0
- package/node_modules/@vellumai/local-mode/src/__tests__/wake.test.ts +66 -0
- package/node_modules/@vellumai/local-mode/src/config.ts +66 -0
- package/node_modules/@vellumai/local-mode/src/environment.ts +62 -0
- package/node_modules/@vellumai/local-mode/src/gateway-proxy.ts +109 -0
- package/node_modules/@vellumai/local-mode/src/guardian-token.ts +122 -0
- package/node_modules/@vellumai/local-mode/src/hatch.ts +92 -0
- package/node_modules/@vellumai/local-mode/src/index.ts +48 -0
- package/node_modules/@vellumai/local-mode/src/lockfile-contract.test.ts +173 -0
- package/node_modules/@vellumai/local-mode/src/lockfile-contract.ts +114 -0
- package/node_modules/@vellumai/local-mode/src/lockfile.test.ts +235 -0
- package/node_modules/@vellumai/local-mode/src/lockfile.ts +133 -0
- package/node_modules/@vellumai/local-mode/src/retire.ts +58 -0
- package/node_modules/@vellumai/local-mode/src/util.ts +102 -0
- package/node_modules/@vellumai/local-mode/src/wake.ts +78 -0
- package/node_modules/@vellumai/local-mode/tsconfig.json +16 -0
- package/package.json +12 -1
- package/src/__tests__/assistant-client-refresh.test.ts +182 -0
- package/src/__tests__/backup.test.ts +38 -0
- package/src/__tests__/clean.test.ts +179 -0
- package/src/__tests__/client-token.test.ts +87 -0
- package/src/__tests__/client-tui-refresh.test.ts +170 -0
- package/src/__tests__/cloudflare-tunnel.test.ts +137 -0
- package/src/__tests__/connect-import.test.ts +317 -0
- package/src/__tests__/devices.test.ts +272 -0
- package/src/__tests__/env-drift.test.ts +32 -44
- package/src/__tests__/flags.test.ts +248 -0
- package/src/__tests__/guardian-token.test.ts +126 -2
- package/src/__tests__/multi-local.test.ts +1 -1
- package/src/__tests__/orphan-detection.test.ts +8 -6
- package/src/__tests__/pair.test.ts +271 -0
- package/src/__tests__/paired-lifecycle.test.ts +116 -0
- package/src/__tests__/recover.test.ts +307 -0
- package/src/__tests__/segments-to-plain-text.test.ts +37 -0
- package/src/__tests__/tui-midsession-refresh.test.ts +166 -0
- package/src/__tests__/unpair.test.ts +163 -0
- package/src/__tests__/wake.test.ts +215 -0
- package/src/commands/backup.ts +2 -0
- package/src/commands/client.ts +569 -39
- package/src/commands/connect/import.ts +217 -0
- package/src/commands/connect.ts +31 -0
- package/src/commands/devices.ts +247 -0
- package/src/commands/env.ts +1 -1
- package/src/commands/flags.ts +269 -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/pair.ts +222 -0
- package/src/commands/ps.ts +57 -41
- package/src/commands/recover.ts +47 -9
- package/src/commands/restore.ts +8 -1
- package/src/commands/retire.ts +23 -70
- package/src/commands/rollback.ts +2 -14
- package/src/commands/sleep.ts +7 -0
- package/src/commands/ssh.ts +5 -24
- package/src/commands/teleport.ts +34 -26
- package/src/commands/tunnel.ts +46 -2
- package/src/commands/unpair.ts +118 -0
- package/src/commands/upgrade.ts +8 -16
- package/src/commands/wake.ts +75 -45
- package/src/components/DefaultMainScreen.tsx +100 -14
- package/src/index.ts +22 -0
- package/src/lib/__tests__/lifecycle-reporter.test.ts +59 -0
- package/src/lib/__tests__/step-runner.test.ts +49 -1
- package/src/lib/assistant-client.ts +58 -37
- package/src/lib/assistant-config.ts +28 -3
- package/src/lib/cloudflare-tunnel.ts +276 -0
- package/src/lib/config-utils.ts +24 -3
- package/src/lib/confirm-action.ts +57 -0
- package/src/lib/docker.ts +82 -8
- package/src/lib/environments/__tests__/paths.test.ts +2 -1
- package/src/lib/environments/__tests__/seeds.test.ts +2 -1
- package/src/lib/environments/paths.ts +1 -1
- package/src/lib/environments/resolve.ts +11 -35
- package/src/lib/guardian-token.ts +132 -9
- package/src/lib/hatch-local.ts +75 -33
- package/src/lib/http-client.ts +1 -3
- package/src/lib/lifecycle-reporter.ts +31 -0
- package/src/lib/local.ts +193 -298
- 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/process.ts +109 -39
- package/src/lib/retire-local.ts +28 -14
- package/src/lib/segments-to-plain-text.ts +35 -0
- package/src/lib/step-runner.ts +67 -7
- package/src/lib/sync-cloud-assistants.ts +17 -0
- /package/{src/lib/environments → node_modules/@vellumai/environments/src}/types.ts +0 -0
package/src/lib/hatch-local.ts
CHANGED
|
@@ -33,7 +33,10 @@ import {
|
|
|
33
33
|
import { generateInstanceName } from "./random-name.js";
|
|
34
34
|
import { leaseGuardianToken } from "./guardian-token.js";
|
|
35
35
|
import { archiveLogFile, resetLogFile } from "./xdg-log.js";
|
|
36
|
-
import {
|
|
36
|
+
import {
|
|
37
|
+
consoleLifecycleReporter,
|
|
38
|
+
type LifecycleReporter,
|
|
39
|
+
} from "./lifecycle-reporter.js";
|
|
37
40
|
import {
|
|
38
41
|
configureHatchProviderApiKey,
|
|
39
42
|
formatProviderName,
|
|
@@ -134,6 +137,25 @@ function installCLISymlink(): void {
|
|
|
134
137
|
|
|
135
138
|
export interface HatchLocalOptions {
|
|
136
139
|
setupProviderCredentials?: boolean;
|
|
140
|
+
/**
|
|
141
|
+
* Sink for progress and log output. Defaults to the console reporter so CLI
|
|
142
|
+
* callers keep their existing terminal output; in-process callers can inject
|
|
143
|
+
* their own reporter to consume progress without writing to stdout.
|
|
144
|
+
*/
|
|
145
|
+
reporter?: LifecycleReporter;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export interface HatchLocalResult {
|
|
149
|
+
assistantId: string;
|
|
150
|
+
runtimeUrl: string;
|
|
151
|
+
localUrl: string;
|
|
152
|
+
species: Species;
|
|
153
|
+
/**
|
|
154
|
+
* Guardian access token leased during hatch, when the lease succeeded. The
|
|
155
|
+
* full token pair is persisted to disk regardless; this is surfaced so an
|
|
156
|
+
* in-process caller can prime a connection without re-reading the file.
|
|
157
|
+
*/
|
|
158
|
+
guardianAccessToken?: string;
|
|
137
159
|
}
|
|
138
160
|
|
|
139
161
|
export async function hatchLocal(
|
|
@@ -143,7 +165,8 @@ export async function hatchLocal(
|
|
|
143
165
|
keepAlive: boolean = false,
|
|
144
166
|
configValues: Record<string, string> = {},
|
|
145
167
|
options: HatchLocalOptions = {},
|
|
146
|
-
): Promise<
|
|
168
|
+
): Promise<HatchLocalResult> {
|
|
169
|
+
const reporter = options.reporter ?? consoleLifecycleReporter;
|
|
147
170
|
const provider =
|
|
148
171
|
options.setupProviderCredentials === false
|
|
149
172
|
? undefined
|
|
@@ -153,7 +176,7 @@ export async function hatchLocal(
|
|
|
153
176
|
name ?? process.env.VELLUM_ASSISTANT_NAME,
|
|
154
177
|
);
|
|
155
178
|
|
|
156
|
-
|
|
179
|
+
reporter.progress(1, 6, "Allocating resources...");
|
|
157
180
|
|
|
158
181
|
const existing = findAssistantByName(instanceName);
|
|
159
182
|
if (existing && (!existing.cloud || existing.cloud === "local")) {
|
|
@@ -175,43 +198,47 @@ export async function hatchLocal(
|
|
|
175
198
|
archiveLogFile("hatch.log", logsDir);
|
|
176
199
|
resetLogFile("hatch.log");
|
|
177
200
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
201
|
+
reporter.log(`🥚 Hatching local assistant: ${instanceName}`);
|
|
202
|
+
reporter.log(` Species: ${species}`);
|
|
203
|
+
reporter.log("");
|
|
181
204
|
|
|
182
205
|
const apiKeyCheck = checkProviderApiKey();
|
|
183
206
|
if (!apiKeyCheck.hasKey) {
|
|
184
|
-
|
|
207
|
+
reporter.warn(
|
|
185
208
|
"Warning: No LLM provider API key is configured. The assistant will fail when you try to send a message.",
|
|
186
209
|
);
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
210
|
+
reporter.warn(" To fix, export your key before running vellum hatch:");
|
|
211
|
+
reporter.warn(" export ANTHROPIC_API_KEY=<your-key>");
|
|
212
|
+
reporter.warn("");
|
|
190
213
|
}
|
|
191
214
|
|
|
192
215
|
if (!process.env.APP_VERSION) {
|
|
193
216
|
process.env.APP_VERSION = cliPkg.version;
|
|
194
217
|
}
|
|
195
218
|
|
|
196
|
-
|
|
219
|
+
reporter.progress(2, 6, "Writing configuration...");
|
|
197
220
|
const hatchConfigValues = buildHatchConfigValues(configValues, provider);
|
|
198
221
|
const defaultWorkspaceConfigPath = writeInitialConfig(hatchConfigValues);
|
|
199
222
|
|
|
200
|
-
|
|
223
|
+
reporter.progress(3, 6, "Starting assistant...");
|
|
201
224
|
const signingKey = generateLocalSigningKey();
|
|
225
|
+
const bootstrapSecret = generateLocalSigningKey();
|
|
202
226
|
await startLocalDaemon(watch, resources, {
|
|
203
227
|
defaultWorkspaceConfigPath,
|
|
204
228
|
signingKey,
|
|
205
229
|
});
|
|
206
230
|
|
|
207
|
-
|
|
231
|
+
reporter.progress(4, 6, "Starting gateway...");
|
|
208
232
|
let runtimeUrl = `http://127.0.0.1:${resources.gatewayPort}`;
|
|
209
233
|
try {
|
|
210
|
-
runtimeUrl = await startGateway(watch, resources, {
|
|
234
|
+
runtimeUrl = await startGateway(watch, resources, {
|
|
235
|
+
signingKey,
|
|
236
|
+
bootstrapSecret,
|
|
237
|
+
});
|
|
211
238
|
} catch (error) {
|
|
212
239
|
// Gateway failed — stop the daemon we just started so we don't leave
|
|
213
240
|
// orphaned processes with no lock file entry.
|
|
214
|
-
|
|
241
|
+
reporter.error(
|
|
215
242
|
`\n❌ Gateway startup failed — stopping assistant to avoid orphaned processes.`,
|
|
216
243
|
);
|
|
217
244
|
await stopLocalProcesses(resources);
|
|
@@ -222,24 +249,28 @@ export async function hatchLocal(
|
|
|
222
249
|
// instead of hitting /v1/guardian/init itself. Use loopback to satisfy
|
|
223
250
|
// the daemon's local-only check — the mDNS runtimeUrl resolves to a LAN
|
|
224
251
|
// IP which the daemon rejects as non-loopback.
|
|
225
|
-
|
|
252
|
+
reporter.progress(5, 6, "Securing connection...");
|
|
226
253
|
const loopbackUrl = `http://127.0.0.1:${resources.gatewayPort}`;
|
|
227
254
|
const maxLeaseAttempts = 3;
|
|
228
255
|
let guardianAccessToken: string | undefined;
|
|
229
256
|
for (let attempt = 1; attempt <= maxLeaseAttempts; attempt++) {
|
|
230
257
|
try {
|
|
231
|
-
const tokenData = await leaseGuardianToken(
|
|
258
|
+
const tokenData = await leaseGuardianToken(
|
|
259
|
+
loopbackUrl,
|
|
260
|
+
instanceName,
|
|
261
|
+
bootstrapSecret,
|
|
262
|
+
);
|
|
232
263
|
guardianAccessToken = tokenData.accessToken;
|
|
233
264
|
break;
|
|
234
265
|
} catch (err) {
|
|
235
266
|
if (attempt < maxLeaseAttempts) {
|
|
236
267
|
const delayMs = 2000 * 2 ** (attempt - 1);
|
|
237
|
-
|
|
268
|
+
reporter.error(
|
|
238
269
|
`⚠️ Guardian token lease attempt ${attempt}/${maxLeaseAttempts} failed — retrying in ${delayMs / 1000}s: ${err}`,
|
|
239
270
|
);
|
|
240
271
|
await new Promise((r) => setTimeout(r, delayMs));
|
|
241
272
|
} else {
|
|
242
|
-
|
|
273
|
+
reporter.error(
|
|
243
274
|
`⚠️ Guardian token lease failed after ${maxLeaseAttempts} attempts: ${err}\n` +
|
|
244
275
|
` The assistant is running but guardian-token.json was not written.\n` +
|
|
245
276
|
` If the desktop app loses its stored credentials, re-hatch to recover.`,
|
|
@@ -257,9 +288,10 @@ export async function hatchLocal(
|
|
|
257
288
|
species,
|
|
258
289
|
hatchedAt: new Date().toISOString(),
|
|
259
290
|
resources: { ...resources, signingKey },
|
|
291
|
+
guardianBootstrapSecret: bootstrapSecret,
|
|
260
292
|
};
|
|
261
293
|
|
|
262
|
-
|
|
294
|
+
reporter.progress(6, 6, "Saving configuration...");
|
|
263
295
|
saveAssistantEntry(localEntry);
|
|
264
296
|
setActiveAssistant(instanceName);
|
|
265
297
|
|
|
@@ -268,13 +300,13 @@ export async function hatchLocal(
|
|
|
268
300
|
}
|
|
269
301
|
|
|
270
302
|
if (provider !== undefined && provider !== null && !guardianAccessToken) {
|
|
271
|
-
|
|
303
|
+
reporter.error(
|
|
272
304
|
`⚠️ Provider credential setup skipped because the guardian token was not leased.\n` +
|
|
273
305
|
` The assistant is still hatched. Run \`vellum setup --provider ${provider}\` after fixing the connection.`,
|
|
274
306
|
);
|
|
275
307
|
} else if (provider !== undefined) {
|
|
276
|
-
|
|
277
|
-
|
|
308
|
+
reporter.log("");
|
|
309
|
+
reporter.log(
|
|
278
310
|
provider === null
|
|
279
311
|
? "Checking provider credentials..."
|
|
280
312
|
: `Checking ${formatProviderName(provider)} credentials...`,
|
|
@@ -287,14 +319,22 @@ export async function hatchLocal(
|
|
|
287
319
|
});
|
|
288
320
|
}
|
|
289
321
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
logHatchNextSteps(
|
|
322
|
+
reporter.log("");
|
|
323
|
+
reporter.log(`✅ Local assistant hatched!`);
|
|
324
|
+
reporter.log("");
|
|
325
|
+
reporter.log("Instance details:");
|
|
326
|
+
reporter.log(` Name: ${instanceName}`);
|
|
327
|
+
reporter.log(` Runtime: ${runtimeUrl}`);
|
|
328
|
+
reporter.log("");
|
|
329
|
+
logHatchNextSteps((message) => reporter.log(message), instanceName);
|
|
330
|
+
|
|
331
|
+
const result: HatchLocalResult = {
|
|
332
|
+
assistantId: instanceName,
|
|
333
|
+
runtimeUrl,
|
|
334
|
+
localUrl: `http://127.0.0.1:${resources.gatewayPort}`,
|
|
335
|
+
species,
|
|
336
|
+
guardianAccessToken,
|
|
337
|
+
};
|
|
298
338
|
|
|
299
339
|
if (keepAlive) {
|
|
300
340
|
const healthUrl = `http://127.0.0.1:${resources.gatewayPort}/healthz`;
|
|
@@ -304,7 +344,7 @@ export async function hatchLocal(
|
|
|
304
344
|
let consecutiveFailures = 0;
|
|
305
345
|
|
|
306
346
|
const shutdown = async (): Promise<void> => {
|
|
307
|
-
|
|
347
|
+
reporter.log("\nShutting down local processes...");
|
|
308
348
|
await stopLocalProcesses(resources);
|
|
309
349
|
process.exit(0);
|
|
310
350
|
};
|
|
@@ -328,7 +368,7 @@ export async function hatchLocal(
|
|
|
328
368
|
consecutiveFailures++;
|
|
329
369
|
}
|
|
330
370
|
if (consecutiveFailures >= MAX_FAILURES) {
|
|
331
|
-
|
|
371
|
+
reporter.log(
|
|
332
372
|
`\n⚠️ ${healthTarget} stopped responding — shutting down.`,
|
|
333
373
|
);
|
|
334
374
|
await stopLocalProcesses(resources);
|
|
@@ -336,4 +376,6 @@ export async function hatchLocal(
|
|
|
336
376
|
}
|
|
337
377
|
}
|
|
338
378
|
}
|
|
379
|
+
|
|
380
|
+
return result;
|
|
339
381
|
}
|
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
|
*/
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { emitProgress } from "./desktop-progress.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Sink for the human-facing and structured output of long-running lifecycle
|
|
5
|
+
* operations (hatch, retire). Injecting it lets an in-process caller (e.g. a
|
|
6
|
+
* desktop main process embedding these functions) observe progress without the
|
|
7
|
+
* operation writing to the terminal, while the CLI keeps its existing stdout.
|
|
8
|
+
*/
|
|
9
|
+
export interface LifecycleReporter {
|
|
10
|
+
/**
|
|
11
|
+
* Coarse step progress. The CLI reporter mirrors this to the desktop
|
|
12
|
+
* `HATCH_PROGRESS:` stdout channel.
|
|
13
|
+
*/
|
|
14
|
+
progress(step: number, total: number, label: string): void;
|
|
15
|
+
log(message: string): void;
|
|
16
|
+
warn(message: string): void;
|
|
17
|
+
error(message: string): void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Reporter used by the CLI commands: human-readable lines to the console plus
|
|
22
|
+
* structured step events on the desktop progress channel. Reproduces the exact
|
|
23
|
+
* terminal output — and the `HATCH_PROGRESS:` lines under `VELLUM_DESKTOP_APP` —
|
|
24
|
+
* that existing subprocess consumers parse.
|
|
25
|
+
*/
|
|
26
|
+
export const consoleLifecycleReporter: LifecycleReporter = {
|
|
27
|
+
progress: (step, total, label) => emitProgress(step, total, label),
|
|
28
|
+
log: (message) => console.log(message),
|
|
29
|
+
warn: (message) => console.warn(message),
|
|
30
|
+
error: (message) => console.error(message),
|
|
31
|
+
};
|