@vellumai/cli 0.6.6 → 0.7.0
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 +8 -2
- package/package.json +1 -1
- package/src/__tests__/assistant-config.test.ts +1 -7
- package/src/__tests__/config-utils.test.ts +159 -0
- package/src/__tests__/env-drift.test.ts +10 -32
- package/src/__tests__/llm-provider-env-var-parity.test.ts +1 -21
- package/src/__tests__/multi-local.test.ts +0 -5
- package/src/__tests__/sleep.test.ts +1 -2
- package/src/__tests__/teleport.test.ts +919 -1255
- package/src/commands/env.ts +93 -0
- package/src/commands/events.ts +2 -0
- package/src/commands/exec.ts +40 -8
- package/src/commands/hatch.ts +6 -2
- package/src/commands/login.ts +89 -6
- package/src/commands/ps.ts +104 -20
- package/src/commands/sleep.ts +5 -2
- package/src/commands/ssh.ts +15 -2
- package/src/commands/teleport.ts +447 -583
- package/src/commands/terminal.ts +9 -221
- package/src/commands/wake.ts +2 -1
- package/src/components/DefaultMainScreen.tsx +304 -152
- package/src/index.ts +3 -0
- package/src/lib/__tests__/docker.test.ts +50 -74
- package/src/lib/__tests__/job-polling.test.ts +278 -0
- package/src/lib/__tests__/local-runtime-client.test.ts +383 -0
- package/src/lib/__tests__/platform-client-signed-url.test.ts +405 -0
- package/src/lib/assistant-config.ts +12 -8
- package/src/lib/client-identity.ts +67 -0
- package/src/lib/config-utils.ts +97 -1
- package/src/lib/docker.ts +73 -75
- package/src/lib/environments/__tests__/paths.test.ts +2 -0
- package/src/lib/environments/resolve.ts +89 -7
- package/src/lib/environments/seeds.ts +8 -5
- package/src/lib/environments/types.ts +10 -0
- package/src/lib/hatch-local.ts +15 -120
- package/src/lib/health-check.ts +98 -0
- package/src/lib/job-polling.ts +195 -0
- package/src/lib/local-runtime-client.ts +178 -0
- package/src/lib/local.ts +139 -15
- package/src/lib/orphan-detection.ts +2 -35
- package/src/lib/platform-client.ts +215 -0
- package/src/lib/retire-local.ts +6 -2
- package/src/lib/terminal-session.ts +457 -0
- package/src/shared/provider-env-vars.ts +2 -3
- package/src/__tests__/orphan-detection.test.ts +0 -214
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { SEEDS } from "../lib/environments/seeds.js";
|
|
2
|
+
import {
|
|
3
|
+
clearDefaultEnvironment,
|
|
4
|
+
readDefaultEnvironment,
|
|
5
|
+
resolveEnvironmentSource,
|
|
6
|
+
writeDefaultEnvironment,
|
|
7
|
+
} from "../lib/environments/resolve.js";
|
|
8
|
+
|
|
9
|
+
function printUsage(): void {
|
|
10
|
+
console.log("Usage: vellum env <subcommand>");
|
|
11
|
+
console.log("");
|
|
12
|
+
console.log("Manage the default CLI environment.");
|
|
13
|
+
console.log("");
|
|
14
|
+
console.log("Subcommands:");
|
|
15
|
+
console.log(" set <name> Set the default environment");
|
|
16
|
+
console.log(" get Show the current environment and its source");
|
|
17
|
+
console.log(" clear Remove the default, falling back to production");
|
|
18
|
+
console.log("");
|
|
19
|
+
console.log(`Known environments: ${Object.keys(SEEDS).join(", ")}`);
|
|
20
|
+
console.log("");
|
|
21
|
+
console.log("Examples:");
|
|
22
|
+
console.log(" $ vellum env set local # all commands default to local");
|
|
23
|
+
console.log(" $ vellum env get # show resolved environment");
|
|
24
|
+
console.log(" $ vellum env clear # revert to production default");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function envSet(name: string | undefined): void {
|
|
28
|
+
if (!name) {
|
|
29
|
+
console.error(
|
|
30
|
+
`Usage: vellum env set <name>\nKnown environments: ${Object.keys(SEEDS).join(", ")}`,
|
|
31
|
+
);
|
|
32
|
+
process.exitCode = 1;
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (!SEEDS[name]) {
|
|
36
|
+
console.error(
|
|
37
|
+
`Unknown environment "${name}". Known environments: ${Object.keys(SEEDS).join(", ")}`,
|
|
38
|
+
);
|
|
39
|
+
process.exitCode = 1;
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
writeDefaultEnvironment(name);
|
|
43
|
+
console.log(`Default environment set to "${name}".`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function envGet(): void {
|
|
47
|
+
const { name, source } = resolveEnvironmentSource();
|
|
48
|
+
const sourceLabels: Record<typeof source, string> = {
|
|
49
|
+
flag: "--environment flag",
|
|
50
|
+
env: "VELLUM_ENVIRONMENT env var",
|
|
51
|
+
config: "~/.config/vellum/environment",
|
|
52
|
+
default: "default",
|
|
53
|
+
};
|
|
54
|
+
console.log(`${name} (from ${sourceLabels[source]})`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function envClear(): void {
|
|
58
|
+
const current = readDefaultEnvironment();
|
|
59
|
+
if (!current) {
|
|
60
|
+
console.log("No default environment is set (already using production).");
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
clearDefaultEnvironment();
|
|
64
|
+
console.log(
|
|
65
|
+
`Cleared default environment "${current}". Falling back to production.`,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function env(): Promise<void> {
|
|
70
|
+
const args = process.argv.slice(3);
|
|
71
|
+
const sub = args[0];
|
|
72
|
+
|
|
73
|
+
if (!sub || sub === "--help" || sub === "-h") {
|
|
74
|
+
printUsage();
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
switch (sub) {
|
|
79
|
+
case "set":
|
|
80
|
+
envSet(args[1]);
|
|
81
|
+
break;
|
|
82
|
+
case "get":
|
|
83
|
+
envGet();
|
|
84
|
+
break;
|
|
85
|
+
case "clear":
|
|
86
|
+
envClear();
|
|
87
|
+
break;
|
|
88
|
+
default:
|
|
89
|
+
console.error(`Unknown subcommand: ${sub}`);
|
|
90
|
+
printUsage();
|
|
91
|
+
process.exitCode = 1;
|
|
92
|
+
}
|
|
93
|
+
}
|
package/src/commands/events.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { extractFlag } from "../lib/arg-utils.js";
|
|
11
11
|
import { AssistantClient } from "../lib/assistant-client.js";
|
|
12
|
+
import { getClientRegistrationHeaders } from "../lib/client-identity.js";
|
|
12
13
|
|
|
13
14
|
function printUsage(): void {
|
|
14
15
|
console.log(`vellum events - Stream events from a running assistant
|
|
@@ -136,6 +137,7 @@ export async function events(): Promise<void> {
|
|
|
136
137
|
for await (const event of client.stream<AssistantEvent>("/events", {
|
|
137
138
|
signal: controller.signal,
|
|
138
139
|
query,
|
|
140
|
+
headers: getClientRegistrationHeaders(),
|
|
139
141
|
})) {
|
|
140
142
|
if (jsonOutput) {
|
|
141
143
|
console.log(JSON.stringify(event));
|
package/src/commands/exec.ts
CHANGED
|
@@ -8,7 +8,13 @@ import {
|
|
|
8
8
|
import { dockerResourceNames } from "../lib/docker";
|
|
9
9
|
import type { ServiceName } from "../lib/docker";
|
|
10
10
|
import { execAppleContainer } from "../lib/exec-apple-container";
|
|
11
|
+
import { getPlatformUrl, readPlatformToken } from "../lib/platform-client";
|
|
11
12
|
import { sshAppleContainer } from "../lib/ssh-apple-container";
|
|
13
|
+
import {
|
|
14
|
+
interactiveSession,
|
|
15
|
+
nonInteractiveExec,
|
|
16
|
+
shellEscapeArgs,
|
|
17
|
+
} from "../lib/terminal-session";
|
|
12
18
|
|
|
13
19
|
const SERVICE_ALIASES: Record<string, ServiceName> = {
|
|
14
20
|
assistant: "assistant",
|
|
@@ -74,11 +80,12 @@ export async function exec(): Promise<void> {
|
|
|
74
80
|
);
|
|
75
81
|
console.log("");
|
|
76
82
|
console.log("Options:");
|
|
83
|
+
console.log(" --service <svc> Target service (default: assistant)");
|
|
77
84
|
console.log(
|
|
78
|
-
"
|
|
85
|
+
" -it Interactive mode with TTY (like docker exec -it)",
|
|
79
86
|
);
|
|
80
87
|
console.log(
|
|
81
|
-
"
|
|
88
|
+
" --verbose Show debug output (SSE events, sentinel parsing)",
|
|
82
89
|
);
|
|
83
90
|
console.log("");
|
|
84
91
|
console.log("Services:");
|
|
@@ -90,9 +97,7 @@ export async function exec(): Promise<void> {
|
|
|
90
97
|
console.log(" vellum exec -- ls -la /workspace");
|
|
91
98
|
console.log(" vellum exec -- cat /workspace/NOW.md");
|
|
92
99
|
console.log(" vellum exec -it -- /bin/bash");
|
|
93
|
-
console.log(
|
|
94
|
-
" vellum exec --service gateway -- cat /tmp/gateway.log",
|
|
95
|
-
);
|
|
100
|
+
console.log(" vellum exec --service gateway -- cat /tmp/gateway.log");
|
|
96
101
|
process.exit(0);
|
|
97
102
|
}
|
|
98
103
|
|
|
@@ -114,12 +119,15 @@ export async function exec(): Promise<void> {
|
|
|
114
119
|
let nameArg: string | undefined;
|
|
115
120
|
let serviceRaw = "assistant";
|
|
116
121
|
let interactive = false;
|
|
122
|
+
let verbose = false;
|
|
117
123
|
|
|
118
124
|
for (let i = 0; i < preArgs.length; i++) {
|
|
119
125
|
if (preArgs[i] === "--service" && preArgs[i + 1]) {
|
|
120
126
|
serviceRaw = preArgs[++i];
|
|
121
127
|
} else if (preArgs[i] === "-it" || preArgs[i] === "-ti") {
|
|
122
128
|
interactive = true;
|
|
129
|
+
} else if (preArgs[i] === "--verbose") {
|
|
130
|
+
verbose = true;
|
|
123
131
|
} else if (!preArgs[i].startsWith("-")) {
|
|
124
132
|
nameArg = preArgs[i];
|
|
125
133
|
}
|
|
@@ -127,9 +135,7 @@ export async function exec(): Promise<void> {
|
|
|
127
135
|
|
|
128
136
|
const service = normalizeService(serviceRaw);
|
|
129
137
|
|
|
130
|
-
const entry = nameArg
|
|
131
|
-
? findAssistantByName(nameArg)
|
|
132
|
-
: loadLatestAssistant();
|
|
138
|
+
const entry = nameArg ? findAssistantByName(nameArg) : loadLatestAssistant();
|
|
133
139
|
|
|
134
140
|
if (!entry) {
|
|
135
141
|
if (nameArg) {
|
|
@@ -179,6 +185,32 @@ export async function exec(): Promise<void> {
|
|
|
179
185
|
return;
|
|
180
186
|
}
|
|
181
187
|
|
|
188
|
+
if (cloud === "vellum") {
|
|
189
|
+
const token = readPlatformToken();
|
|
190
|
+
if (!token) {
|
|
191
|
+
console.error(
|
|
192
|
+
"Not logged in. Run `vellum login` first to authenticate with the platform.",
|
|
193
|
+
);
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const assistant = {
|
|
198
|
+
assistantId: entry.assistantId,
|
|
199
|
+
token,
|
|
200
|
+
platformUrl: getPlatformUrl(),
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
if (interactive) {
|
|
204
|
+
// Interactive mode: shell-escape argv and delegate to full terminal
|
|
205
|
+
await interactiveSession(assistant, shellEscapeArgs(command));
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Non-interactive: sentinel-based output capture with exit code
|
|
210
|
+
await nonInteractiveExec(assistant, command, { verbose });
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
182
214
|
console.error(
|
|
183
215
|
`Error: 'vellum exec' is not supported for ${cloud} instances.`,
|
|
184
216
|
);
|
package/src/commands/hatch.ts
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
VALID_SPECIES,
|
|
16
16
|
} from "../lib/constants";
|
|
17
17
|
import type { RemoteHost, Species } from "../lib/constants";
|
|
18
|
-
import {
|
|
18
|
+
import { buildInitialConfig } from "../lib/config-utils";
|
|
19
19
|
import { hatchDocker } from "../lib/docker";
|
|
20
20
|
import { hatchGcp } from "../lib/gcp";
|
|
21
21
|
import type { PollResult, WatchHatchingResult } from "../lib/gcp";
|
|
@@ -123,7 +123,11 @@ export async function buildStartupScript(
|
|
|
123
123
|
// and export the env var so the daemon reads it on first boot.
|
|
124
124
|
let configWriteBlock = "";
|
|
125
125
|
if (Object.keys(configValues).length > 0) {
|
|
126
|
-
const configJson = JSON.stringify(
|
|
126
|
+
const configJson = JSON.stringify(
|
|
127
|
+
buildInitialConfig(configValues),
|
|
128
|
+
null,
|
|
129
|
+
2,
|
|
130
|
+
);
|
|
127
131
|
configWriteBlock = `
|
|
128
132
|
echo "Writing default workspace config..."
|
|
129
133
|
VELLUM_DEFAULT_WORKSPACE_CONFIG_PATH="/tmp/vellum-initial-config-$$.json"
|
package/src/commands/login.ts
CHANGED
|
@@ -6,6 +6,10 @@ import {
|
|
|
6
6
|
findAssistantByName,
|
|
7
7
|
getActiveAssistant,
|
|
8
8
|
loadLatestAssistant,
|
|
9
|
+
loadAllAssistants,
|
|
10
|
+
removeAssistantEntry,
|
|
11
|
+
saveAssistantEntry,
|
|
12
|
+
setActiveAssistant,
|
|
9
13
|
} from "../lib/assistant-config";
|
|
10
14
|
import { computeDeviceId } from "../lib/guardian-token";
|
|
11
15
|
import {
|
|
@@ -13,7 +17,9 @@ import {
|
|
|
13
17
|
ensureSelfHostedLocalRegistration,
|
|
14
18
|
fetchCurrentUser,
|
|
15
19
|
fetchOrganizationId,
|
|
20
|
+
fetchPlatformAssistants,
|
|
16
21
|
getPlatformUrl,
|
|
22
|
+
getWebUrl,
|
|
17
23
|
injectCredentialsIntoAssistant,
|
|
18
24
|
readGatewayCredential,
|
|
19
25
|
readPlatformToken,
|
|
@@ -45,7 +51,7 @@ function openBrowser(url: string): void {
|
|
|
45
51
|
* Start a local HTTP server, open the browser to the platform login page,
|
|
46
52
|
* and wait for the platform to redirect back with the session token.
|
|
47
53
|
*/
|
|
48
|
-
function browserLogin(
|
|
54
|
+
function browserLogin(webUrl: string): Promise<string> {
|
|
49
55
|
return new Promise((resolve, reject) => {
|
|
50
56
|
const state = randomBytes(32).toString("hex");
|
|
51
57
|
|
|
@@ -112,7 +118,7 @@ function browserLogin(platformUrl: string): Promise<string> {
|
|
|
112
118
|
|
|
113
119
|
const port = addr.port;
|
|
114
120
|
const returnTo = `/accounts/cli/callback?port=${port}&state=${state}`;
|
|
115
|
-
const loginUrl = `${
|
|
121
|
+
const loginUrl = `${webUrl}/account/login?returnTo=${encodeURIComponent(returnTo)}`;
|
|
116
122
|
|
|
117
123
|
console.log("Opening browser for login...");
|
|
118
124
|
console.log(`If the browser doesn't open, visit: ${loginUrl}`);
|
|
@@ -125,22 +131,30 @@ export async function login(): Promise<void> {
|
|
|
125
131
|
const args = process.argv.slice(3);
|
|
126
132
|
|
|
127
133
|
if (args.includes("--help") || args.includes("-h")) {
|
|
128
|
-
console.log("Usage: vellum login [--token <session-token>]");
|
|
134
|
+
console.log("Usage: vellum login [--token <session-token>] [--force]");
|
|
129
135
|
console.log("");
|
|
130
136
|
console.log("Log in to the Vellum platform.");
|
|
131
137
|
console.log("");
|
|
132
138
|
console.log("By default, opens a browser window for authentication.");
|
|
133
139
|
console.log("Alternatively, pass a session token directly with --token.");
|
|
134
140
|
console.log("");
|
|
141
|
+
console.log("On success, syncs cloud-managed assistants to the local");
|
|
142
|
+
console.log("lockfile so they appear in `vellum ps`.");
|
|
143
|
+
console.log("");
|
|
135
144
|
console.log("Options:");
|
|
136
145
|
console.log(" --token <token> Session token from the Vellum platform");
|
|
146
|
+
console.log(
|
|
147
|
+
" --force, -f Re-authenticate even if already logged in",
|
|
148
|
+
);
|
|
137
149
|
console.log("");
|
|
138
150
|
console.log("Examples:");
|
|
139
151
|
console.log(" vellum login");
|
|
140
152
|
console.log(" vellum login --token <session-token>");
|
|
153
|
+
console.log(" vellum login --force");
|
|
141
154
|
process.exit(0);
|
|
142
155
|
}
|
|
143
156
|
|
|
157
|
+
const forceFlag = args.includes("--force") || args.includes("-f");
|
|
144
158
|
let token: string | null = null;
|
|
145
159
|
|
|
146
160
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -154,11 +168,27 @@ export async function login(): Promise<void> {
|
|
|
154
168
|
}
|
|
155
169
|
}
|
|
156
170
|
|
|
171
|
+
// Block if already authenticated (unless --force)
|
|
172
|
+
if (!forceFlag && !token) {
|
|
173
|
+
const existingToken = readPlatformToken();
|
|
174
|
+
if (existingToken) {
|
|
175
|
+
try {
|
|
176
|
+
const existingUser = await fetchCurrentUser(existingToken);
|
|
177
|
+
console.error(
|
|
178
|
+
`Already logged in as ${existingUser.email}. Run \`vellum logout\` first, or use \`vellum login --force\` to re-authenticate.`,
|
|
179
|
+
);
|
|
180
|
+
process.exit(1);
|
|
181
|
+
} catch {
|
|
182
|
+
// Token is stale/invalid — proceed with login
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
157
187
|
// If no --token flag, use browser-based login
|
|
158
188
|
if (!token) {
|
|
159
|
-
const
|
|
189
|
+
const webUrl = getWebUrl();
|
|
160
190
|
try {
|
|
161
|
-
token = await browserLogin(
|
|
191
|
+
token = await browserLogin(webUrl);
|
|
162
192
|
} catch (error) {
|
|
163
193
|
console.error(`❌ ${error instanceof Error ? error.message : error}`);
|
|
164
194
|
process.exit(1);
|
|
@@ -247,6 +277,45 @@ export async function login(): Promise<void> {
|
|
|
247
277
|
} catch {
|
|
248
278
|
// Non-fatal — login succeeded even if registration fails
|
|
249
279
|
}
|
|
280
|
+
|
|
281
|
+
// Sync cloud assistants from the platform into the local lockfile.
|
|
282
|
+
// This ensures `vellum ps` shows managed assistants immediately
|
|
283
|
+
// after login (e.g. after a retire-and-rehatch cycle).
|
|
284
|
+
try {
|
|
285
|
+
const platformAssistants = await fetchPlatformAssistants(token);
|
|
286
|
+
const existingIds = new Set(
|
|
287
|
+
loadAllAssistants()
|
|
288
|
+
.filter((a) => a.cloud === "vellum")
|
|
289
|
+
.map((a) => a.assistantId),
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
let synced = 0;
|
|
293
|
+
for (const pa of platformAssistants) {
|
|
294
|
+
if (!existingIds.has(pa.id)) {
|
|
295
|
+
saveAssistantEntry({
|
|
296
|
+
assistantId: pa.id,
|
|
297
|
+
runtimeUrl: getPlatformUrl(),
|
|
298
|
+
cloud: "vellum",
|
|
299
|
+
species: "vellum",
|
|
300
|
+
hatchedAt: new Date().toISOString(),
|
|
301
|
+
});
|
|
302
|
+
synced++;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (synced > 0) {
|
|
307
|
+
console.log(
|
|
308
|
+
`Synced ${synced} cloud assistant${synced > 1 ? "s" : ""} to local lockfile.`,
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// If no active assistant is set, activate the first cloud one.
|
|
313
|
+
if (!getActiveAssistant() && platformAssistants.length > 0) {
|
|
314
|
+
setActiveAssistant(platformAssistants[0].id);
|
|
315
|
+
}
|
|
316
|
+
} catch {
|
|
317
|
+
// Non-fatal — login succeeded even if sync fails
|
|
318
|
+
}
|
|
250
319
|
} catch (error) {
|
|
251
320
|
console.error(
|
|
252
321
|
`❌ Login failed: ${error instanceof Error ? error.message : error}`,
|
|
@@ -261,11 +330,25 @@ export async function logout(): Promise<void> {
|
|
|
261
330
|
console.log("Usage: vellum logout");
|
|
262
331
|
console.log("");
|
|
263
332
|
console.log(
|
|
264
|
-
"Log out of the Vellum platform
|
|
333
|
+
"Log out of the Vellum platform, remove the stored session token,",
|
|
265
334
|
);
|
|
335
|
+
console.log("and remove cloud-managed assistants from the local lockfile.");
|
|
266
336
|
process.exit(0);
|
|
267
337
|
}
|
|
268
338
|
|
|
339
|
+
// Remove cloud-managed assistants from the lockfile.
|
|
340
|
+
const cloudAssistants = loadAllAssistants().filter(
|
|
341
|
+
(a) => a.cloud === "vellum",
|
|
342
|
+
);
|
|
343
|
+
for (const a of cloudAssistants) {
|
|
344
|
+
removeAssistantEntry(a.assistantId);
|
|
345
|
+
}
|
|
346
|
+
if (cloudAssistants.length > 0) {
|
|
347
|
+
console.log(
|
|
348
|
+
`Removed ${cloudAssistants.length} cloud assistant${cloudAssistants.length > 1 ? "s" : ""} from local lockfile.`,
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
|
|
269
352
|
clearPlatformToken();
|
|
270
353
|
console.log("Logged out. Platform token removed.");
|
|
271
354
|
}
|
package/src/commands/ps.ts
CHANGED
|
@@ -3,11 +3,18 @@ import { join } from "path";
|
|
|
3
3
|
import {
|
|
4
4
|
findAssistantByName,
|
|
5
5
|
getActiveAssistant,
|
|
6
|
+
getDaemonPidPath,
|
|
6
7
|
loadAllAssistants,
|
|
7
8
|
type AssistantEntry,
|
|
8
9
|
} from "../lib/assistant-config";
|
|
10
|
+
import { resolveEnvironmentSource } from "../lib/environments/resolve";
|
|
9
11
|
import { loadGuardianToken } from "../lib/guardian-token";
|
|
10
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
checkHealth,
|
|
14
|
+
checkManagedHealth,
|
|
15
|
+
fetchManagedPs,
|
|
16
|
+
type ManagedProcessEntry,
|
|
17
|
+
} from "../lib/health-check";
|
|
11
18
|
import { dockerResourceNames } from "../lib/docker";
|
|
12
19
|
import { existsSync } from "fs";
|
|
13
20
|
import {
|
|
@@ -65,6 +72,49 @@ function printTable(rows: TableRow[]): void {
|
|
|
65
72
|
}
|
|
66
73
|
}
|
|
67
74
|
|
|
75
|
+
// ── Managed process tree rendering ──────────────────────────────
|
|
76
|
+
|
|
77
|
+
const STATUS_LABELS: Record<ManagedProcessEntry["status"], string> = {
|
|
78
|
+
running: "running",
|
|
79
|
+
not_running: "not running",
|
|
80
|
+
unreachable: "unreachable",
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
function flattenProcessTree(
|
|
84
|
+
entries: ManagedProcessEntry[],
|
|
85
|
+
depth = 0,
|
|
86
|
+
): TableRow[] {
|
|
87
|
+
const rows: TableRow[] = [];
|
|
88
|
+
for (const entry of entries) {
|
|
89
|
+
const children = entry.children ?? [];
|
|
90
|
+
|
|
91
|
+
rows.push({
|
|
92
|
+
name:
|
|
93
|
+
depth === 0 ? entry.name : `${" ".repeat(depth - 1)}├─ ${entry.name}`,
|
|
94
|
+
status: withStatusEmoji(STATUS_LABELS[entry.status]),
|
|
95
|
+
info: entry.info ?? "",
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
for (let j = 0; j < children.length; j++) {
|
|
99
|
+
const child = children[j];
|
|
100
|
+
const isLast = j === children.length - 1;
|
|
101
|
+
const prefix = `${" ".repeat(depth)}${isLast ? "└─" : "├─"} ${child.name}`;
|
|
102
|
+
rows.push({
|
|
103
|
+
name: prefix,
|
|
104
|
+
status: withStatusEmoji(STATUS_LABELS[child.status]),
|
|
105
|
+
info: child.info ?? "",
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Recurse into grandchildren
|
|
109
|
+
const grandchildren = child.children ?? [];
|
|
110
|
+
if (grandchildren.length > 0) {
|
|
111
|
+
rows.push(...flattenProcessTree(grandchildren, depth + 2));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return rows;
|
|
116
|
+
}
|
|
117
|
+
|
|
68
118
|
// ── Remote process listing via SSH ──────────────────────────────
|
|
69
119
|
|
|
70
120
|
const SSH_OPTS = [
|
|
@@ -215,37 +265,38 @@ async function getLocalProcesses(entry: AssistantEntry): Promise<TableRow[]> {
|
|
|
215
265
|
const resources = entry.resources;
|
|
216
266
|
const vellumDir = join(resources.instanceDir, ".vellum");
|
|
217
267
|
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
268
|
+
const assistantSpec: ProcessSpec = {
|
|
269
|
+
name: "assistant",
|
|
270
|
+
pgrepName: "vellum-daemon",
|
|
271
|
+
port: resources.daemonPort,
|
|
272
|
+
pidFile: getDaemonPidPath(resources),
|
|
273
|
+
};
|
|
274
|
+
const subSpecs: ProcessSpec[] = [
|
|
225
275
|
{
|
|
226
|
-
name: "qdrant",
|
|
276
|
+
name: "├─ qdrant",
|
|
227
277
|
pgrepName: "qdrant",
|
|
228
278
|
port: resources.qdrantPort,
|
|
229
279
|
pidFile: join(vellumDir, "workspace", "data", "qdrant", "qdrant.pid"),
|
|
230
280
|
},
|
|
231
281
|
{
|
|
232
|
-
name: "
|
|
233
|
-
pgrepName: "vellum-gateway",
|
|
234
|
-
port: resources.gatewayPort,
|
|
235
|
-
pidFile: join(vellumDir, "gateway.pid"),
|
|
236
|
-
},
|
|
237
|
-
{
|
|
238
|
-
name: "embed-worker",
|
|
282
|
+
name: "└─ embed-worker",
|
|
239
283
|
pgrepName: "embed-worker",
|
|
240
284
|
port: 0,
|
|
241
285
|
pidFile: join(vellumDir, "workspace", "embed-worker.pid"),
|
|
242
286
|
},
|
|
243
287
|
];
|
|
288
|
+
const gatewaySpec: ProcessSpec = {
|
|
289
|
+
name: "gateway",
|
|
290
|
+
pgrepName: "vellum-gateway",
|
|
291
|
+
port: resources.gatewayPort,
|
|
292
|
+
pidFile: join(vellumDir, "gateway.pid"),
|
|
293
|
+
};
|
|
244
294
|
|
|
245
|
-
const
|
|
295
|
+
const allSpecs = [assistantSpec, ...subSpecs, gatewaySpec];
|
|
296
|
+
const results = await Promise.all(allSpecs.map(detectProcess));
|
|
246
297
|
|
|
247
|
-
return results.map((proc) => ({
|
|
248
|
-
name:
|
|
298
|
+
return results.map((proc, i) => ({
|
|
299
|
+
name: allSpecs[i].name,
|
|
249
300
|
status: withStatusEmoji(proc.running ? "running" : "not running"),
|
|
250
301
|
info: proc.running ? formatDetectionInfo(proc) : "not detected",
|
|
251
302
|
}));
|
|
@@ -335,6 +386,28 @@ async function showAssistantProcesses(entry: AssistantEntry): Promise<void> {
|
|
|
335
386
|
return;
|
|
336
387
|
}
|
|
337
388
|
|
|
389
|
+
if (cloud === "vellum") {
|
|
390
|
+
console.log(` Platform ID: ${entry.assistantId}\n`);
|
|
391
|
+
|
|
392
|
+
const psData = await fetchManagedPs(entry.runtimeUrl, entry.assistantId);
|
|
393
|
+
|
|
394
|
+
if (!psData) {
|
|
395
|
+
const rows: TableRow[] = [
|
|
396
|
+
{
|
|
397
|
+
name: "assistant",
|
|
398
|
+
status: withStatusEmoji("unreachable"),
|
|
399
|
+
info: "could not reach platform API — run `vellum login`",
|
|
400
|
+
},
|
|
401
|
+
];
|
|
402
|
+
printTable(rows);
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const rows = flattenProcessTree(psData.processes);
|
|
407
|
+
printTable(rows);
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
|
|
338
411
|
if (cloud === "apple-container") {
|
|
339
412
|
const mgmtSocket = entry.mgmtSocket as string | undefined;
|
|
340
413
|
const socketAlive = mgmtSocket ? existsSync(mgmtSocket) : false;
|
|
@@ -396,6 +469,15 @@ async function showAssistantProcesses(entry: AssistantEntry): Promise<void> {
|
|
|
396
469
|
// ── List all assistants (no arg) ────────────────────────────────
|
|
397
470
|
|
|
398
471
|
async function listAllAssistants(): Promise<void> {
|
|
472
|
+
const { name: envName, source: envSource } = resolveEnvironmentSource();
|
|
473
|
+
const sourceLabels: Record<typeof envSource, string> = {
|
|
474
|
+
flag: "--environment flag",
|
|
475
|
+
env: "VELLUM_ENVIRONMENT",
|
|
476
|
+
config: "~/.config/vellum/environment",
|
|
477
|
+
default: "default",
|
|
478
|
+
};
|
|
479
|
+
console.log(`Environment: ${envName} (${sourceLabels[envSource]})\n`);
|
|
480
|
+
|
|
399
481
|
const assistants = loadAllAssistants();
|
|
400
482
|
const activeId = getActiveAssistant();
|
|
401
483
|
|
|
@@ -454,7 +536,9 @@ async function listAllAssistants(): Promise<void> {
|
|
|
454
536
|
let health: { status: string; detail: string | null; version?: string };
|
|
455
537
|
const resources = a.resources;
|
|
456
538
|
if (a.cloud === "local" && resources) {
|
|
457
|
-
|
|
539
|
+
// TODO(ATL-306): Remove readPidFile/getDaemonPidPath in favor of
|
|
540
|
+
// fetching daemon PIDs via the health API (Gateway Security Migration).
|
|
541
|
+
const pid = readPidFile(getDaemonPidPath(resources));
|
|
458
542
|
const alive = pid !== null && isProcessAlive(pid);
|
|
459
543
|
if (!alive) {
|
|
460
544
|
health = { status: "sleeping", detail: null };
|
package/src/commands/sleep.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "fs";
|
|
2
2
|
import { join } from "path";
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
getDaemonPidPath,
|
|
6
|
+
resolveTargetAssistant,
|
|
7
|
+
} from "../lib/assistant-config.js";
|
|
5
8
|
import type { AssistantEntry } from "../lib/assistant-config.js";
|
|
6
9
|
import { dockerResourceNames, sleepContainers } from "../lib/docker.js";
|
|
7
10
|
import { isProcessAlive, stopProcessByPidFile } from "../lib/process";
|
|
@@ -93,7 +96,7 @@ export async function sleep(): Promise<void> {
|
|
|
93
96
|
process.exit(1);
|
|
94
97
|
}
|
|
95
98
|
const resources = entry.resources;
|
|
96
|
-
const assistantPidFile = resources
|
|
99
|
+
const assistantPidFile = getDaemonPidPath(resources);
|
|
97
100
|
const vellumDir = getAssistantRootDir(entry);
|
|
98
101
|
const gatewayPidFile = join(vellumDir, "gateway.pid");
|
|
99
102
|
|
package/src/commands/ssh.ts
CHANGED
|
@@ -6,7 +6,9 @@ import {
|
|
|
6
6
|
} from "../lib/assistant-config";
|
|
7
7
|
import type { AssistantEntry } from "../lib/assistant-config";
|
|
8
8
|
import { dockerResourceNames } from "../lib/docker";
|
|
9
|
+
import { getPlatformUrl, readPlatformToken } from "../lib/platform-client";
|
|
9
10
|
import { sshAppleContainer } from "../lib/ssh-apple-container";
|
|
11
|
+
import { interactiveSession } from "../lib/terminal-session";
|
|
10
12
|
|
|
11
13
|
const SSH_OPTS = [
|
|
12
14
|
"-o",
|
|
@@ -121,8 +123,19 @@ export async function ssh(): Promise<void> {
|
|
|
121
123
|
{ stdio: "inherit" },
|
|
122
124
|
);
|
|
123
125
|
} else if (cloud === "vellum") {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
+
const token = readPlatformToken();
|
|
127
|
+
if (!token) {
|
|
128
|
+
console.error(
|
|
129
|
+
"Not logged in. Run `vellum login` first to authenticate with the platform.",
|
|
130
|
+
);
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
await interactiveSession({
|
|
134
|
+
assistantId: entry.assistantId,
|
|
135
|
+
token,
|
|
136
|
+
platformUrl: getPlatformUrl(),
|
|
137
|
+
});
|
|
138
|
+
return;
|
|
126
139
|
} else if (cloud === "custom") {
|
|
127
140
|
const host = extractHostFromUrl(entry.runtimeUrl);
|
|
128
141
|
const sshUser = entry.sshUser ?? "root";
|