@vellumai/cli 0.3.10 → 0.3.12
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/README.md +8 -8
- package/bun.lock +6 -0
- package/package.json +5 -2
- package/src/__tests__/assistant-config.test.ts +139 -0
- package/src/__tests__/constants.test.ts +48 -0
- package/src/__tests__/health-check.test.ts +64 -0
- package/src/__tests__/random-name.test.ts +19 -0
- package/src/__tests__/retire-archive.test.ts +30 -0
- package/src/__tests__/status-emoji.test.ts +44 -0
- package/src/commands/autonomy.ts +321 -0
- package/src/commands/client.ts +15 -8
- package/src/commands/contacts.ts +265 -0
- package/src/commands/hatch.ts +93 -37
- package/src/commands/login.ts +68 -0
- package/src/commands/ps.ts +7 -3
- package/src/commands/recover.ts +1 -1
- package/src/commands/retire.ts +2 -2
- package/src/commands/skills.ts +355 -0
- package/src/commands/ssh.ts +5 -2
- package/src/components/DefaultMainScreen.tsx +67 -3
- package/src/index.ts +23 -0
- package/src/lib/assistant-config.ts +1 -0
- package/src/lib/gcp.ts +9 -13
- package/src/lib/local.ts +23 -28
- package/src/lib/platform-client.ts +74 -0
- package/src/types/sh.d.ts +4 -0
package/src/lib/local.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { createRequire } from "module";
|
|
|
4
4
|
import { homedir } from "os";
|
|
5
5
|
import { dirname, join } from "path";
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { loadLatestAssistant } from "./assistant-config.js";
|
|
8
8
|
import { GATEWAY_PORT } from "./constants.js";
|
|
9
9
|
import { stopProcessByPidFile } from "./process.js";
|
|
10
10
|
|
|
@@ -288,7 +288,7 @@ export async function startLocalDaemon(): Promise<void> {
|
|
|
288
288
|
}
|
|
289
289
|
}
|
|
290
290
|
|
|
291
|
-
export async function startGateway(): Promise<string> {
|
|
291
|
+
export async function startGateway(assistantId?: string): Promise<string> {
|
|
292
292
|
const publicUrl = await discoverPublicUrl();
|
|
293
293
|
if (publicUrl) {
|
|
294
294
|
console.log(` Public URL: ${publicUrl}`);
|
|
@@ -296,12 +296,12 @@ export async function startGateway(): Promise<string> {
|
|
|
296
296
|
|
|
297
297
|
console.log("🌐 Starting gateway...");
|
|
298
298
|
|
|
299
|
-
//
|
|
300
|
-
//
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
299
|
+
// Resolve the default assistant ID for the gateway. Prefer the explicitly
|
|
300
|
+
// provided assistantId (from hatch), then env override, then lockfile.
|
|
301
|
+
const resolvedAssistantId =
|
|
302
|
+
assistantId
|
|
303
|
+
|| process.env.GATEWAY_DEFAULT_ASSISTANT_ID
|
|
304
|
+
|| loadLatestAssistant()?.assistantId;
|
|
305
305
|
|
|
306
306
|
// Read the bearer token so the gateway can authenticate proxied requests
|
|
307
307
|
// (e.g. from paired iOS devices). Respect VELLUM_HTTP_TOKEN_PATH and
|
|
@@ -318,12 +318,13 @@ export async function startGateway(): Promise<string> {
|
|
|
318
318
|
|
|
319
319
|
// If no token is available (first startup — daemon hasn't written it yet),
|
|
320
320
|
// poll for the file to appear. The daemon writes the token shortly after
|
|
321
|
-
// startup, so
|
|
322
|
-
//
|
|
323
|
-
// config is loaded once at startup and
|
|
321
|
+
// startup, so we wait generously — the daemon socket wait is 15s, and
|
|
322
|
+
// the token may be written after the socket. Starting the gateway without
|
|
323
|
+
// auth is a security risk since the config is loaded once at startup and
|
|
324
|
+
// never reloads, so we fail rather than silently disabling auth.
|
|
324
325
|
if (!runtimeProxyBearerToken) {
|
|
325
326
|
console.log(" Waiting for bearer token file...");
|
|
326
|
-
const maxWait =
|
|
327
|
+
const maxWait = 30000;
|
|
327
328
|
const pollInterval = 500;
|
|
328
329
|
const start = Date.now();
|
|
329
330
|
while (Date.now() - start < maxWait) {
|
|
@@ -340,36 +341,30 @@ export async function startGateway(): Promise<string> {
|
|
|
340
341
|
}
|
|
341
342
|
}
|
|
342
343
|
|
|
343
|
-
// If the token still isn't available after polling, fall back to starting
|
|
344
|
-
// the gateway without auth so it doesn't block forever. This is a degraded
|
|
345
|
-
// mode — the proxy will be broken because the runtime expects a token.
|
|
346
|
-
const proxyRequireAuth = runtimeProxyBearerToken ? "true" : "false";
|
|
347
344
|
if (!runtimeProxyBearerToken) {
|
|
348
|
-
|
|
345
|
+
throw new Error(
|
|
346
|
+
`Bearer token file not found at ${httpTokenPath} after 30s.\n` +
|
|
347
|
+
" The gateway cannot start without authentication — this would leave the proxy permanently unauthenticated.\n" +
|
|
348
|
+
" Ensure the daemon is running and has written the token file, or set VELLUM_HTTP_TOKEN_PATH to the correct path.",
|
|
349
|
+
);
|
|
349
350
|
}
|
|
350
351
|
|
|
351
352
|
const gatewayEnv: Record<string, string> = {
|
|
352
353
|
...process.env as Record<string, string>,
|
|
353
354
|
GATEWAY_RUNTIME_PROXY_ENABLED: "true",
|
|
354
|
-
GATEWAY_RUNTIME_PROXY_REQUIRE_AUTH:
|
|
355
|
+
GATEWAY_RUNTIME_PROXY_REQUIRE_AUTH: "true",
|
|
356
|
+
RUNTIME_PROXY_BEARER_TOKEN: runtimeProxyBearerToken,
|
|
355
357
|
RUNTIME_HTTP_PORT: process.env.RUNTIME_HTTP_PORT || "7821",
|
|
356
358
|
};
|
|
357
359
|
|
|
358
|
-
if (runtimeProxyBearerToken) {
|
|
359
|
-
gatewayEnv.RUNTIME_PROXY_BEARER_TOKEN = runtimeProxyBearerToken;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
360
|
if (process.env.GATEWAY_UNMAPPED_POLICY) {
|
|
363
361
|
gatewayEnv.GATEWAY_UNMAPPED_POLICY = process.env.GATEWAY_UNMAPPED_POLICY;
|
|
364
|
-
} else
|
|
362
|
+
} else {
|
|
365
363
|
gatewayEnv.GATEWAY_UNMAPPED_POLICY = "default";
|
|
366
364
|
}
|
|
367
365
|
|
|
368
|
-
if (
|
|
369
|
-
gatewayEnv.GATEWAY_DEFAULT_ASSISTANT_ID =
|
|
370
|
-
} else if (isSingleAssistant) {
|
|
371
|
-
gatewayEnv.GATEWAY_DEFAULT_ASSISTANT_ID =
|
|
372
|
-
assistants[0].assistantId || loadLatestAssistant()?.assistantId || "default";
|
|
366
|
+
if (resolvedAssistantId) {
|
|
367
|
+
gatewayEnv.GATEWAY_DEFAULT_ASSISTANT_ID = resolvedAssistantId;
|
|
373
368
|
}
|
|
374
369
|
const workspaceIngressPublicBaseUrl = readWorkspaceIngressPublicBaseUrl();
|
|
375
370
|
const ingressPublicBaseUrl =
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { chmodSync, readFileSync, writeFileSync, unlinkSync, existsSync, mkdirSync } from "fs";
|
|
2
|
+
import { homedir } from "os";
|
|
3
|
+
import { join, dirname } from "path";
|
|
4
|
+
|
|
5
|
+
const DEFAULT_PLATFORM_URL = "https://platform.vellum.ai";
|
|
6
|
+
|
|
7
|
+
function getPlatformTokenPath(): string {
|
|
8
|
+
const base = process.env.BASE_DATA_DIR || homedir();
|
|
9
|
+
return join(base, ".vellum", "platform-token");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function getPlatformUrl(): string {
|
|
13
|
+
return process.env.VELLUM_PLATFORM_URL ?? DEFAULT_PLATFORM_URL;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function readPlatformToken(): string | null {
|
|
17
|
+
try {
|
|
18
|
+
return readFileSync(getPlatformTokenPath(), "utf-8").trim();
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function savePlatformToken(token: string): void {
|
|
25
|
+
const tokenPath = getPlatformTokenPath();
|
|
26
|
+
const dir = dirname(tokenPath);
|
|
27
|
+
if (!existsSync(dir)) {
|
|
28
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
29
|
+
}
|
|
30
|
+
writeFileSync(tokenPath, token + "\n", { mode: 0o600 });
|
|
31
|
+
chmodSync(tokenPath, 0o600);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function clearPlatformToken(): void {
|
|
35
|
+
try {
|
|
36
|
+
unlinkSync(getPlatformTokenPath());
|
|
37
|
+
} catch {
|
|
38
|
+
// already doesn't exist
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface PlatformUser {
|
|
43
|
+
id: string;
|
|
44
|
+
email: string;
|
|
45
|
+
display: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface AllauthSessionResponse {
|
|
49
|
+
status: number;
|
|
50
|
+
data: {
|
|
51
|
+
user: {
|
|
52
|
+
id: string;
|
|
53
|
+
email: string;
|
|
54
|
+
display: string;
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function fetchCurrentUser(token: string): Promise<PlatformUser> {
|
|
60
|
+
const url = `${getPlatformUrl()}/_allauth/app/v1/auth/session`;
|
|
61
|
+
const response = await fetch(url, {
|
|
62
|
+
headers: { "X-Session-Token": token },
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
if (!response.ok) {
|
|
66
|
+
if (response.status === 401 || response.status === 403 || response.status === 410) {
|
|
67
|
+
throw new Error("Invalid or expired token. Please login again.");
|
|
68
|
+
}
|
|
69
|
+
throw new Error(`Platform API error: ${response.status} ${response.statusText}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const body = (await response.json()) as AllauthSessionResponse;
|
|
73
|
+
return body.data.user;
|
|
74
|
+
}
|