@vellumai/cli 0.6.3 → 0.6.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 -2
- package/README.md +3 -3
- package/bun.lock +17 -17
- package/bunfig.toml +6 -0
- package/package.json +18 -18
- package/src/__tests__/assistant-config.test.ts +124 -0
- package/src/__tests__/env-drift.test.ts +87 -0
- package/src/__tests__/guardian-token.test.ts +225 -0
- package/src/__tests__/llm-provider-env-var-parity.test.ts +64 -0
- package/src/__tests__/multi-local.test.ts +90 -13
- package/src/__tests__/orphan-detection.test.ts +214 -0
- package/src/__tests__/platform-client.test.ts +204 -0
- package/src/__tests__/preload.ts +27 -0
- package/src/__tests__/ssh-user-guard.test.ts +28 -0
- package/src/__tests__/teleport.test.ts +1073 -56
- package/src/commands/backup.ts +8 -0
- package/src/commands/exec.ts +186 -0
- package/src/commands/hatch.ts +1 -1
- package/src/commands/login.ts +209 -9
- package/src/commands/logs.ts +652 -0
- package/src/commands/pair.ts +9 -1
- package/src/commands/ps.ts +37 -7
- package/src/commands/recover.ts +8 -4
- package/src/commands/restore.ts +8 -0
- package/src/commands/retire.ts +16 -9
- package/src/commands/rollback.ts +32 -33
- package/src/commands/ssh.ts +7 -0
- package/src/commands/teleport.ts +253 -1
- package/src/commands/upgrade.ts +43 -52
- package/src/commands/wake.ts +25 -10
- package/src/components/DefaultMainScreen.tsx +7 -1
- package/src/index.ts +6 -0
- package/src/lib/__tests__/docker.test.ts +168 -0
- package/src/lib/assistant-config.ts +82 -108
- package/src/lib/aws.ts +12 -1
- package/src/lib/config-utils.ts +4 -4
- package/src/lib/constants.ts +0 -10
- package/src/lib/docker.ts +158 -8
- package/src/lib/environments/__tests__/paths.test.ts +228 -0
- package/src/lib/environments/__tests__/resolve.test.ts +226 -0
- package/src/lib/environments/__tests__/seeds.test.ts +72 -0
- package/src/lib/environments/paths.ts +109 -0
- package/src/lib/environments/resolve.ts +96 -0
- package/src/lib/environments/seeds.ts +74 -0
- package/src/lib/environments/types.ts +60 -0
- package/src/lib/exec-apple-container.ts +122 -0
- package/src/lib/gcp.ts +12 -1
- package/src/lib/guardian-token.ts +71 -10
- package/src/lib/hatch-local.ts +44 -23
- package/src/lib/local.ts +47 -5
- package/src/lib/orphan-detection.ts +28 -12
- package/src/lib/platform-client.ts +354 -24
- package/src/lib/retire-apple-container.ts +102 -0
- package/src/lib/ssh-apple-container.ts +166 -0
- package/src/lib/upgrade-lifecycle.ts +101 -28
- package/src/shared/provider-env-vars.ts +30 -6
package/src/commands/backup.ts
CHANGED
|
@@ -64,6 +64,14 @@ export async function backup(): Promise<void> {
|
|
|
64
64
|
// Detect topology and route platform assistants through Django export
|
|
65
65
|
const cloud =
|
|
66
66
|
entry.cloud || (entry.project ? "gcp" : entry.sshUser ? "custom" : "local");
|
|
67
|
+
|
|
68
|
+
if (cloud === "apple-container") {
|
|
69
|
+
console.error(
|
|
70
|
+
`Error: '${name}' uses the Apple Containers runtime. Backup is not yet supported for this topology.`,
|
|
71
|
+
);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
|
|
67
75
|
if (cloud === "vellum") {
|
|
68
76
|
await backupPlatform(name, outputArg, entry.runtimeUrl);
|
|
69
77
|
return;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
findAssistantByName,
|
|
5
|
+
loadLatestAssistant,
|
|
6
|
+
resolveCloud,
|
|
7
|
+
} from "../lib/assistant-config";
|
|
8
|
+
import { dockerResourceNames } from "../lib/docker";
|
|
9
|
+
import type { ServiceName } from "../lib/docker";
|
|
10
|
+
import { execAppleContainer } from "../lib/exec-apple-container";
|
|
11
|
+
import { sshAppleContainer } from "../lib/ssh-apple-container";
|
|
12
|
+
|
|
13
|
+
const SERVICE_ALIASES: Record<string, ServiceName> = {
|
|
14
|
+
assistant: "assistant",
|
|
15
|
+
"vellum-assistant": "assistant",
|
|
16
|
+
gateway: "gateway",
|
|
17
|
+
"vellum-gateway": "gateway",
|
|
18
|
+
"credential-executor": "credential-executor",
|
|
19
|
+
"vellum-credential-executor": "credential-executor",
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function normalizeService(raw: string): ServiceName {
|
|
23
|
+
const normalized = SERVICE_ALIASES[raw];
|
|
24
|
+
if (!normalized) {
|
|
25
|
+
console.error(
|
|
26
|
+
`Unknown service '${raw}'. Valid services: assistant, gateway, credential-executor`,
|
|
27
|
+
);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
return normalized;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function resolveDockerContainer(
|
|
34
|
+
instanceName: string,
|
|
35
|
+
service: ServiceName,
|
|
36
|
+
): string {
|
|
37
|
+
const res = dockerResourceNames(instanceName);
|
|
38
|
+
switch (service) {
|
|
39
|
+
case "assistant":
|
|
40
|
+
return res.assistantContainer;
|
|
41
|
+
case "gateway":
|
|
42
|
+
return res.gatewayContainer;
|
|
43
|
+
case "credential-executor":
|
|
44
|
+
return res.cesContainer;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function exec(): Promise<void> {
|
|
49
|
+
const rawArgs = process.argv.slice(3);
|
|
50
|
+
|
|
51
|
+
// Only check for help flags before the -- separator so that
|
|
52
|
+
// `vellum exec -- curl --help` passes through correctly.
|
|
53
|
+
const dashDashIndex = rawArgs.indexOf("--");
|
|
54
|
+
const preArgs =
|
|
55
|
+
dashDashIndex === -1 ? rawArgs : rawArgs.slice(0, dashDashIndex);
|
|
56
|
+
|
|
57
|
+
if (
|
|
58
|
+
preArgs.includes("--help") ||
|
|
59
|
+
preArgs.includes("-h") ||
|
|
60
|
+
rawArgs.length === 0
|
|
61
|
+
) {
|
|
62
|
+
console.log(
|
|
63
|
+
"Usage: vellum exec [<name>] [--service <svc>] [-it] -- <command...>",
|
|
64
|
+
);
|
|
65
|
+
console.log("");
|
|
66
|
+
console.log("Execute a command inside an assistant's container.");
|
|
67
|
+
console.log("");
|
|
68
|
+
console.log("Arguments:");
|
|
69
|
+
console.log(
|
|
70
|
+
" <name> Name of the assistant (defaults to active)",
|
|
71
|
+
);
|
|
72
|
+
console.log(
|
|
73
|
+
" <command...> Command and arguments to run (after --)",
|
|
74
|
+
);
|
|
75
|
+
console.log("");
|
|
76
|
+
console.log("Options:");
|
|
77
|
+
console.log(
|
|
78
|
+
" --service <svc> Target service (default: assistant)",
|
|
79
|
+
);
|
|
80
|
+
console.log(
|
|
81
|
+
" -it Interactive mode with TTY (like docker exec -it)",
|
|
82
|
+
);
|
|
83
|
+
console.log("");
|
|
84
|
+
console.log("Services:");
|
|
85
|
+
console.log(" assistant (or vellum-assistant)");
|
|
86
|
+
console.log(" gateway (or vellum-gateway)");
|
|
87
|
+
console.log(" credential-executor (or vellum-credential-executor)");
|
|
88
|
+
console.log("");
|
|
89
|
+
console.log("Examples:");
|
|
90
|
+
console.log(" vellum exec -- ls -la /workspace");
|
|
91
|
+
console.log(" vellum exec -- cat /workspace/NOW.md");
|
|
92
|
+
console.log(" vellum exec -it -- /bin/bash");
|
|
93
|
+
console.log(
|
|
94
|
+
" vellum exec --service gateway -- cat /tmp/gateway.log",
|
|
95
|
+
);
|
|
96
|
+
process.exit(0);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (dashDashIndex === -1) {
|
|
100
|
+
console.error(
|
|
101
|
+
"Error: missing '--' separator before command.\n" +
|
|
102
|
+
"Usage: vellum exec [<name>] -- <command...>",
|
|
103
|
+
);
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const command = rawArgs.slice(dashDashIndex + 1);
|
|
108
|
+
|
|
109
|
+
if (command.length === 0) {
|
|
110
|
+
console.error("Error: no command specified after '--'.");
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let nameArg: string | undefined;
|
|
115
|
+
let serviceRaw = "assistant";
|
|
116
|
+
let interactive = false;
|
|
117
|
+
|
|
118
|
+
for (let i = 0; i < preArgs.length; i++) {
|
|
119
|
+
if (preArgs[i] === "--service" && preArgs[i + 1]) {
|
|
120
|
+
serviceRaw = preArgs[++i];
|
|
121
|
+
} else if (preArgs[i] === "-it" || preArgs[i] === "-ti") {
|
|
122
|
+
interactive = true;
|
|
123
|
+
} else if (!preArgs[i].startsWith("-")) {
|
|
124
|
+
nameArg = preArgs[i];
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const service = normalizeService(serviceRaw);
|
|
129
|
+
|
|
130
|
+
const entry = nameArg
|
|
131
|
+
? findAssistantByName(nameArg)
|
|
132
|
+
: loadLatestAssistant();
|
|
133
|
+
|
|
134
|
+
if (!entry) {
|
|
135
|
+
if (nameArg) {
|
|
136
|
+
console.error(`No assistant instance found with name '${nameArg}'.`);
|
|
137
|
+
} else {
|
|
138
|
+
console.error("No assistant instance found. Run `vellum hatch` first.");
|
|
139
|
+
}
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const cloud = resolveCloud(entry);
|
|
144
|
+
|
|
145
|
+
if (cloud === "local") {
|
|
146
|
+
console.error(
|
|
147
|
+
"Cannot exec into a local assistant — it runs directly on this machine.",
|
|
148
|
+
);
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (cloud === "apple-container") {
|
|
153
|
+
const fullServiceName = `vellum-${service}`;
|
|
154
|
+
if (interactive) {
|
|
155
|
+
await sshAppleContainer(entry, command, fullServiceName);
|
|
156
|
+
} else {
|
|
157
|
+
await execAppleContainer(entry, command, fullServiceName);
|
|
158
|
+
}
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (cloud === "docker") {
|
|
163
|
+
const container = resolveDockerContainer(entry.assistantId, service);
|
|
164
|
+
const dockerArgs = interactive
|
|
165
|
+
? ["exec", "-it", container, ...command]
|
|
166
|
+
: ["exec", container, ...command];
|
|
167
|
+
|
|
168
|
+
const child = spawn("docker", dockerArgs, { stdio: "inherit" });
|
|
169
|
+
await new Promise<void>((resolve, reject) => {
|
|
170
|
+
child.on("close", (code) => {
|
|
171
|
+
if (code === 0) resolve();
|
|
172
|
+
else {
|
|
173
|
+
process.exitCode = code ?? 1;
|
|
174
|
+
resolve();
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
child.on("error", reject);
|
|
178
|
+
});
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
console.error(
|
|
183
|
+
`Error: 'vellum exec' is not supported for ${cloud} instances.`,
|
|
184
|
+
);
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}
|
package/src/commands/hatch.ts
CHANGED
|
@@ -570,7 +570,7 @@ async function hatchVellumPlatform(): Promise<void> {
|
|
|
570
570
|
console.log(" Hatching assistant on Vellum platform...");
|
|
571
571
|
console.log("");
|
|
572
572
|
|
|
573
|
-
const result = await hatchAssistant(token);
|
|
573
|
+
const { assistant: result } = await hatchAssistant(token);
|
|
574
574
|
|
|
575
575
|
const platformUrl = getPlatformUrl();
|
|
576
576
|
|
package/src/commands/login.ts
CHANGED
|
@@ -1,20 +1,143 @@
|
|
|
1
|
+
import { createServer } from "http";
|
|
2
|
+
import { spawn } from "child_process";
|
|
3
|
+
import { randomBytes } from "crypto";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
findAssistantByName,
|
|
7
|
+
getActiveAssistant,
|
|
8
|
+
loadLatestAssistant,
|
|
9
|
+
} from "../lib/assistant-config";
|
|
10
|
+
import { computeDeviceId } from "../lib/guardian-token";
|
|
1
11
|
import {
|
|
2
12
|
clearPlatformToken,
|
|
13
|
+
ensureSelfHostedLocalRegistration,
|
|
3
14
|
fetchCurrentUser,
|
|
15
|
+
fetchOrganizationId,
|
|
16
|
+
getPlatformUrl,
|
|
17
|
+
injectCredentialsIntoAssistant,
|
|
18
|
+
readGatewayCredential,
|
|
4
19
|
readPlatformToken,
|
|
20
|
+
reprovisionAssistantApiKey,
|
|
5
21
|
savePlatformToken,
|
|
6
22
|
} from "../lib/platform-client";
|
|
7
23
|
|
|
24
|
+
const LOGIN_TIMEOUT_MS = 120_000; // 2 minutes
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Open a URL in the user's default browser.
|
|
28
|
+
*/
|
|
29
|
+
function openBrowser(url: string): void {
|
|
30
|
+
const platform = process.platform;
|
|
31
|
+
const cmd =
|
|
32
|
+
platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
|
|
33
|
+
const args =
|
|
34
|
+
platform === "win32"
|
|
35
|
+
? ["/c", "start", '""', url.replace(/&/g, "^&")]
|
|
36
|
+
: [url];
|
|
37
|
+
const child = spawn(cmd, args, { stdio: "ignore", detached: true });
|
|
38
|
+
child.on("error", () => {
|
|
39
|
+
// Silently ignore — the user can still copy the URL from the console
|
|
40
|
+
});
|
|
41
|
+
child.unref();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Start a local HTTP server, open the browser to the platform login page,
|
|
46
|
+
* and wait for the platform to redirect back with the session token.
|
|
47
|
+
*/
|
|
48
|
+
function browserLogin(platformUrl: string): Promise<string> {
|
|
49
|
+
return new Promise((resolve, reject) => {
|
|
50
|
+
const state = randomBytes(32).toString("hex");
|
|
51
|
+
|
|
52
|
+
const server = createServer((req, res) => {
|
|
53
|
+
const url = new URL(req.url ?? "/", `http://localhost`);
|
|
54
|
+
|
|
55
|
+
if (url.pathname !== "/callback") {
|
|
56
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
57
|
+
res.end("Not found");
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const receivedState = url.searchParams.get("state");
|
|
62
|
+
const sessionToken = url.searchParams.get("session_token");
|
|
63
|
+
|
|
64
|
+
if (receivedState !== state) {
|
|
65
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
66
|
+
res.end(
|
|
67
|
+
"<html><body><h2>Login failed</h2><p>State mismatch. Please try again.</p></body></html>",
|
|
68
|
+
);
|
|
69
|
+
cleanup("State mismatch — possible CSRF attack.");
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!sessionToken) {
|
|
74
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
75
|
+
res.end(
|
|
76
|
+
"<html><body><h2>Login failed</h2><p>No session token received. Please try again.</p></body></html>",
|
|
77
|
+
);
|
|
78
|
+
cleanup("No session token received from platform.");
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
83
|
+
res.end(
|
|
84
|
+
"<html><body><h2>Login successful!</h2><p>You can close this window and return to your terminal.</p></body></html>",
|
|
85
|
+
);
|
|
86
|
+
cleanup(null, sessionToken);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const timeout = setTimeout(() => {
|
|
90
|
+
cleanup("Login timed out. Please try again.");
|
|
91
|
+
}, LOGIN_TIMEOUT_MS);
|
|
92
|
+
|
|
93
|
+
function cleanup(error: string | null, token?: string): void {
|
|
94
|
+
clearTimeout(timeout);
|
|
95
|
+
server.close();
|
|
96
|
+
if (error) {
|
|
97
|
+
reject(new Error(error));
|
|
98
|
+
} else if (token) {
|
|
99
|
+
resolve(token);
|
|
100
|
+
} else {
|
|
101
|
+
reject(new Error("Unknown error during login."));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
server.on("error", (err) => cleanup(err.message));
|
|
106
|
+
server.listen(0, "127.0.0.1", () => {
|
|
107
|
+
const addr = server.address();
|
|
108
|
+
if (!addr || typeof addr === "string") {
|
|
109
|
+
cleanup("Failed to start local server.");
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const port = addr.port;
|
|
114
|
+
const returnTo = `/accounts/cli/callback?port=${port}&state=${state}`;
|
|
115
|
+
const loginUrl = `${platformUrl}/account/login?returnTo=${encodeURIComponent(returnTo)}`;
|
|
116
|
+
|
|
117
|
+
console.log("Opening browser for login...");
|
|
118
|
+
console.log(`If the browser doesn't open, visit: ${loginUrl}`);
|
|
119
|
+
openBrowser(loginUrl);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
8
124
|
export async function login(): Promise<void> {
|
|
9
125
|
const args = process.argv.slice(3);
|
|
10
126
|
|
|
11
127
|
if (args.includes("--help") || args.includes("-h")) {
|
|
12
|
-
console.log("Usage: vellum login --token <session-token>");
|
|
128
|
+
console.log("Usage: vellum login [--token <session-token>]");
|
|
13
129
|
console.log("");
|
|
14
130
|
console.log("Log in to the Vellum platform.");
|
|
15
131
|
console.log("");
|
|
132
|
+
console.log("By default, opens a browser window for authentication.");
|
|
133
|
+
console.log("Alternatively, pass a session token directly with --token.");
|
|
134
|
+
console.log("");
|
|
16
135
|
console.log("Options:");
|
|
17
136
|
console.log(" --token <token> Session token from the Vellum platform");
|
|
137
|
+
console.log("");
|
|
138
|
+
console.log("Examples:");
|
|
139
|
+
console.log(" vellum login");
|
|
140
|
+
console.log(" vellum login --token <session-token>");
|
|
18
141
|
process.exit(0);
|
|
19
142
|
}
|
|
20
143
|
|
|
@@ -31,14 +154,15 @@ export async function login(): Promise<void> {
|
|
|
31
154
|
}
|
|
32
155
|
}
|
|
33
156
|
|
|
157
|
+
// If no --token flag, use browser-based login
|
|
34
158
|
if (!token) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
159
|
+
const platformUrl = getPlatformUrl();
|
|
160
|
+
try {
|
|
161
|
+
token = await browserLogin(platformUrl);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
console.error(`❌ ${error instanceof Error ? error.message : error}`);
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
42
166
|
}
|
|
43
167
|
|
|
44
168
|
console.log("Validating token...");
|
|
@@ -47,6 +171,82 @@ export async function login(): Promise<void> {
|
|
|
47
171
|
const user = await fetchCurrentUser(token);
|
|
48
172
|
savePlatformToken(token);
|
|
49
173
|
console.log(`✅ Logged in as ${user.email}`);
|
|
174
|
+
|
|
175
|
+
// Register the local assistant with the platform (non-fatal).
|
|
176
|
+
// Mirrors the desktop app's LocalAssistantBootstrapService flow.
|
|
177
|
+
try {
|
|
178
|
+
const activeName = getActiveAssistant();
|
|
179
|
+
const entry = activeName
|
|
180
|
+
? findAssistantByName(activeName)
|
|
181
|
+
: loadLatestAssistant();
|
|
182
|
+
|
|
183
|
+
// Skip managed ("vellum") assistants — they are handled by the platform.
|
|
184
|
+
if (entry && entry.cloud !== "vellum") {
|
|
185
|
+
const orgId = await fetchOrganizationId(token);
|
|
186
|
+
const clientInstallationId = computeDeviceId();
|
|
187
|
+
const registration = await ensureSelfHostedLocalRegistration(
|
|
188
|
+
token,
|
|
189
|
+
orgId,
|
|
190
|
+
clientInstallationId,
|
|
191
|
+
entry.assistantId,
|
|
192
|
+
"cli",
|
|
193
|
+
);
|
|
194
|
+
console.log(
|
|
195
|
+
`Registered assistant: ${registration.assistant.name} (${registration.assistant.id})`,
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
// Resolve the API key to inject, mirroring the macOS app's
|
|
199
|
+
// LocalAssistantBootstrapService 3-step flow:
|
|
200
|
+
// 1. Use fresh key from registration (first-time only)
|
|
201
|
+
// 2. Use existing key from the daemon's credential store
|
|
202
|
+
// 3. Reprovision (rotate) as a last resort — this revokes the
|
|
203
|
+
// old key server-side, so we only do it when the gateway
|
|
204
|
+
// confirms no key exists (not when it's merely unreachable).
|
|
205
|
+
let assistantApiKey = registration.assistant_api_key;
|
|
206
|
+
if (!assistantApiKey) {
|
|
207
|
+
const cached = await readGatewayCredential(
|
|
208
|
+
entry.runtimeUrl,
|
|
209
|
+
"vellum:assistant_api_key",
|
|
210
|
+
entry.bearerToken,
|
|
211
|
+
);
|
|
212
|
+
if (cached.value) {
|
|
213
|
+
assistantApiKey = cached.value;
|
|
214
|
+
} else if (!cached.unreachable) {
|
|
215
|
+
console.log("No API key available locally — reprovisioning...");
|
|
216
|
+
const reprovision = await reprovisionAssistantApiKey(
|
|
217
|
+
token,
|
|
218
|
+
orgId,
|
|
219
|
+
clientInstallationId,
|
|
220
|
+
entry.assistantId,
|
|
221
|
+
"cli",
|
|
222
|
+
);
|
|
223
|
+
assistantApiKey = reprovision.provisioning.assistant_api_key;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Inject credentials into the running assistant via the gateway,
|
|
228
|
+
// mirroring the desktop app's LocalAssistantBootstrapService flow.
|
|
229
|
+
const allInjected = await injectCredentialsIntoAssistant({
|
|
230
|
+
gatewayUrl: entry.runtimeUrl,
|
|
231
|
+
bearerToken: entry.bearerToken,
|
|
232
|
+
assistantApiKey,
|
|
233
|
+
platformAssistantId: registration.assistant.id,
|
|
234
|
+
platformBaseUrl: getPlatformUrl(),
|
|
235
|
+
organizationId: orgId,
|
|
236
|
+
userId: user.id,
|
|
237
|
+
webhookSecret: registration.webhook_secret,
|
|
238
|
+
});
|
|
239
|
+
if (allInjected) {
|
|
240
|
+
console.log("Injected platform credentials into assistant.");
|
|
241
|
+
} else {
|
|
242
|
+
console.warn(
|
|
243
|
+
"Some credentials could not be injected into the assistant.",
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
} catch {
|
|
248
|
+
// Non-fatal — login succeeded even if registration fails
|
|
249
|
+
}
|
|
50
250
|
} catch (error) {
|
|
51
251
|
console.error(
|
|
52
252
|
`❌ Login failed: ${error instanceof Error ? error.message : error}`,
|
|
@@ -81,7 +281,7 @@ export async function whoami(): Promise<void> {
|
|
|
81
281
|
|
|
82
282
|
const token = readPlatformToken();
|
|
83
283
|
if (!token) {
|
|
84
|
-
console.error("Not logged in. Run `vellum login
|
|
284
|
+
console.error("Not logged in. Run `vellum login` first.");
|
|
85
285
|
process.exit(1);
|
|
86
286
|
}
|
|
87
287
|
|