@vellumai/cli 0.4.48 → 0.4.49
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 +7 -9
- package/package.json +1 -1
- package/src/adapters/install.sh +6 -6
- package/src/commands/clean.ts +11 -6
- package/src/commands/client.ts +5 -13
- package/src/commands/hatch.ts +8 -20
- package/src/commands/ps.ts +21 -3
- package/src/commands/recover.ts +1 -1
- package/src/commands/retire.ts +2 -5
- package/src/commands/setup.ts +172 -0
- package/src/commands/sleep.ts +1 -1
- package/src/commands/wake.ts +2 -2
- package/src/components/DefaultMainScreen.tsx +160 -19
- package/src/components/TextInput.tsx +159 -12
- package/src/index.ts +3 -0
- package/src/lib/assistant-config.ts +1 -6
- package/src/lib/constants.ts +7 -1
- package/src/lib/docker.ts +54 -6
- package/src/lib/doctor-client.ts +1 -1
- package/src/lib/gcp.ts +1 -1
- package/src/lib/http-client.ts +1 -2
- package/src/lib/input-history.ts +44 -0
- package/src/lib/local.ts +160 -161
- package/src/lib/orphan-detection.ts +13 -3
- package/src/lib/process.ts +3 -1
- package/src/lib/terminal-capabilities.ts +124 -0
- package/src/lib/xdg-log.ts +2 -2
package/README.md
CHANGED
|
@@ -62,21 +62,19 @@ vellum hatch [species] [options]
|
|
|
62
62
|
|
|
63
63
|
#### Remote Targets
|
|
64
64
|
|
|
65
|
-
- **`local`** -- Starts the local assistant and local gateway. Gateway source resolution order is:
|
|
65
|
+
- **`local`** -- Starts the local assistant and local gateway. Gateway source resolution order is: repo source tree, then installed `@vellumai/vellum-gateway` package.
|
|
66
66
|
- **`gcp`** -- Creates a GCP Compute Engine VM (`e2-standard-4`: 4 vCPUs, 16 GB) with a startup script that bootstraps the assistant. Requires `gcloud` authentication and `GCP_PROJECT` / `GCP_DEFAULT_ZONE` environment variables.
|
|
67
67
|
- **`aws`** -- Provisions an AWS instance.
|
|
68
68
|
- **`custom`** -- Provisions on an arbitrary SSH host. Set `VELLUM_CUSTOM_HOST` (e.g. `user@hostname`) to specify the target.
|
|
69
69
|
|
|
70
70
|
#### Environment Variables
|
|
71
71
|
|
|
72
|
-
| Variable
|
|
73
|
-
|
|
|
74
|
-
| `ANTHROPIC_API_KEY`
|
|
75
|
-
| `GCP_PROJECT`
|
|
76
|
-
| `GCP_DEFAULT_ZONE`
|
|
77
|
-
| `VELLUM_CUSTOM_HOST`
|
|
78
|
-
| `VELLUM_GATEWAY_DIR` | `local` | Optional absolute path to a local gateway source directory to run instead of the packaged gateway. |
|
|
79
|
-
| `INGRESS_PUBLIC_BASE_URL` | `local` | Optional fallback public ingress URL when `ingress.publicBaseUrl` is not set in workspace config. |
|
|
72
|
+
| Variable | Required For | Description |
|
|
73
|
+
| -------------------- | ------------ | ---------------------------------------------------------- |
|
|
74
|
+
| `ANTHROPIC_API_KEY` | All | Anthropic API key passed to the assistant runtime. |
|
|
75
|
+
| `GCP_PROJECT` | `gcp` | GCP project ID. Falls back to the active `gcloud` project. |
|
|
76
|
+
| `GCP_DEFAULT_ZONE` | `gcp` | GCP zone for the compute instance. |
|
|
77
|
+
| `VELLUM_CUSTOM_HOST` | `custom` | SSH host in `user@hostname` format. |
|
|
80
78
|
|
|
81
79
|
#### Examples
|
|
82
80
|
|
package/package.json
CHANGED
package/src/adapters/install.sh
CHANGED
|
@@ -229,14 +229,14 @@ symlink_vellum() {
|
|
|
229
229
|
symlink_cli "assistant"
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
-
#
|
|
233
|
-
#
|
|
232
|
+
# Append PATH setup to ~/.config/vellum/env so callers can pick up PATH
|
|
233
|
+
# changes without restarting their shell:
|
|
234
234
|
# curl -fsSL https://assistant.vellum.ai/install.sh | bash && . ~/.config/vellum/env
|
|
235
235
|
write_env_file() {
|
|
236
236
|
local env_dir="${XDG_CONFIG_HOME:-$HOME/.config}/vellum"
|
|
237
237
|
local env_file="$env_dir/env"
|
|
238
238
|
mkdir -p "$env_dir"
|
|
239
|
-
cat
|
|
239
|
+
cat >> "$env_file" <<'ENVEOF'
|
|
240
240
|
export BUN_INSTALL="$HOME/.bun"
|
|
241
241
|
case ":$PATH:" in
|
|
242
242
|
*":$BUN_INSTALL/bin:"*) ;;
|
|
@@ -278,8 +278,8 @@ main() {
|
|
|
278
278
|
info "Note: 'assistant' command may require opening a new terminal session"
|
|
279
279
|
fi
|
|
280
280
|
|
|
281
|
-
#
|
|
282
|
-
# PATH changes in the caller's shell:
|
|
281
|
+
# Append PATH config to the env file so the quickstart one-liner can
|
|
282
|
+
# pick up PATH changes in the caller's shell:
|
|
283
283
|
# curl ... | bash && . ~/.config/vellum/env
|
|
284
284
|
write_env_file
|
|
285
285
|
|
|
@@ -292,7 +292,7 @@ main() {
|
|
|
292
292
|
info "Running vellum hatch..."
|
|
293
293
|
printf "\n"
|
|
294
294
|
if [ -n "${VELLUM_SSH_USER:-}" ] && [ "$(id -u)" = "0" ]; then
|
|
295
|
-
su - "$VELLUM_SSH_USER" -c "set -a; [ -f \"\$HOME/.vellum
|
|
295
|
+
su - "$VELLUM_SSH_USER" -c "set -a; [ -f \"\$HOME/.config/vellum/env\" ] && . \"\$HOME/.config/vellum/env\"; set +a; export PATH=\"$HOME/.bun/bin:\$PATH\"; vellum hatch"
|
|
296
296
|
else
|
|
297
297
|
vellum hatch
|
|
298
298
|
fi
|
package/src/commands/clean.ts
CHANGED
|
@@ -6,7 +6,9 @@ export async function clean(): Promise<void> {
|
|
|
6
6
|
if (args.includes("--help") || args.includes("-h")) {
|
|
7
7
|
console.log("Usage: vellum clean");
|
|
8
8
|
console.log("");
|
|
9
|
-
console.log(
|
|
9
|
+
console.log(
|
|
10
|
+
"Kill all orphaned vellum processes that are not tracked by any assistant.",
|
|
11
|
+
);
|
|
10
12
|
process.exit(0);
|
|
11
13
|
}
|
|
12
14
|
|
|
@@ -17,18 +19,21 @@ export async function clean(): Promise<void> {
|
|
|
17
19
|
return;
|
|
18
20
|
}
|
|
19
21
|
|
|
20
|
-
console.log(
|
|
22
|
+
console.log(
|
|
23
|
+
`Found ${orphans.length} orphaned process${orphans.length === 1 ? "" : "es"}.\n`,
|
|
24
|
+
);
|
|
21
25
|
|
|
22
26
|
let killed = 0;
|
|
23
27
|
for (const orphan of orphans) {
|
|
24
28
|
const pid = parseInt(orphan.pid, 10);
|
|
25
|
-
const stopped = await stopProcess(
|
|
29
|
+
const stopped = await stopProcess(
|
|
30
|
+
pid,
|
|
31
|
+
`${orphan.name} (PID ${orphan.pid})`,
|
|
32
|
+
);
|
|
26
33
|
if (stopped) {
|
|
27
34
|
killed++;
|
|
28
35
|
}
|
|
29
36
|
}
|
|
30
37
|
|
|
31
|
-
console.log(
|
|
32
|
-
`\nCleaned up ${killed} process${killed === 1 ? "" : "es"}.`,
|
|
33
|
-
);
|
|
38
|
+
console.log(`\nCleaned up ${killed} process${killed === 1 ? "" : "es"}.`);
|
|
34
39
|
}
|
package/src/commands/client.ts
CHANGED
|
@@ -60,9 +60,7 @@ function parseArgs(): ParsedArgs {
|
|
|
60
60
|
}
|
|
61
61
|
} else {
|
|
62
62
|
const hasExplicitUrl =
|
|
63
|
-
|
|
64
|
-
flagArgs.includes("--url") ||
|
|
65
|
-
flagArgs.includes("-u");
|
|
63
|
+
flagArgs.includes("--url") || flagArgs.includes("-u");
|
|
66
64
|
const active = getActiveAssistant();
|
|
67
65
|
if (active) {
|
|
68
66
|
entry = findAssistantByName(active);
|
|
@@ -84,15 +82,9 @@ function parseArgs(): ParsedArgs {
|
|
|
84
82
|
}
|
|
85
83
|
}
|
|
86
84
|
|
|
87
|
-
let runtimeUrl =
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
entry?.runtimeUrl ||
|
|
91
|
-
FALLBACK_RUNTIME_URL;
|
|
92
|
-
let assistantId =
|
|
93
|
-
process.env.ASSISTANT_ID || entry?.assistantId || FALLBACK_ASSISTANT_ID;
|
|
94
|
-
const bearerToken =
|
|
95
|
-
process.env.RUNTIME_PROXY_BEARER_TOKEN || entry?.bearerToken || undefined;
|
|
85
|
+
let runtimeUrl = entry?.localUrl || entry?.runtimeUrl || FALLBACK_RUNTIME_URL;
|
|
86
|
+
let assistantId = entry?.assistantId || FALLBACK_ASSISTANT_ID;
|
|
87
|
+
const bearerToken = entry?.bearerToken || undefined;
|
|
96
88
|
const species: Species = (entry?.species as Species) ?? "vellum";
|
|
97
89
|
|
|
98
90
|
for (let i = 0; i < flagArgs.length; i++) {
|
|
@@ -177,7 +169,7 @@ ${ANSI.bold}OPTIONS:${ANSI.reset}
|
|
|
177
169
|
|
|
178
170
|
${ANSI.bold}DEFAULTS:${ANSI.reset}
|
|
179
171
|
Reads from ~/.vellum.lock.json (created by vellum hatch).
|
|
180
|
-
Override with flags above
|
|
172
|
+
Override with flags above.
|
|
181
173
|
|
|
182
174
|
${ANSI.bold}EXAMPLES:${ANSI.reset}
|
|
183
175
|
vellum client
|
package/src/commands/hatch.ts
CHANGED
|
@@ -103,7 +103,7 @@ export async function buildStartupScript(
|
|
|
103
103
|
cloud: RemoteHost,
|
|
104
104
|
): Promise<string> {
|
|
105
105
|
const platformUrl =
|
|
106
|
-
process.env.
|
|
106
|
+
process.env.VELLUM_PLATFORM_URL ?? "https://assistant.vellum.ai";
|
|
107
107
|
const logPath =
|
|
108
108
|
cloud === "custom"
|
|
109
109
|
? "/tmp/vellum-startup.log"
|
|
@@ -133,28 +133,13 @@ ${timestampRedirect}
|
|
|
133
133
|
trap 'EXIT_CODE=\$?; if [ \$EXIT_CODE -ne 0 ]; then echo "Startup script failed with exit code \$EXIT_CODE at line \$LINENO" > ${errorPath}; echo "Last 20 log lines:" >> ${errorPath}; tail -20 ${logPath} >> ${errorPath} 2>/dev/null || true; fi' EXIT
|
|
134
134
|
${userSetup}
|
|
135
135
|
ANTHROPIC_API_KEY=${anthropicApiKey}
|
|
136
|
-
GATEWAY_RUNTIME_PROXY_ENABLED=true
|
|
137
|
-
RUNTIME_PROXY_BEARER_TOKEN=${bearerToken}
|
|
138
136
|
VELLUM_ASSISTANT_NAME=${instanceName}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
cat > "\$HOME/.vellum/.env" << DOTENV_EOF
|
|
137
|
+
mkdir -p "\$HOME/.config/vellum"
|
|
138
|
+
cat > "\$HOME/.config/vellum/env" << DOTENV_EOF
|
|
142
139
|
ANTHROPIC_API_KEY=\$ANTHROPIC_API_KEY
|
|
143
|
-
GATEWAY_RUNTIME_PROXY_ENABLED=\$GATEWAY_RUNTIME_PROXY_ENABLED
|
|
144
|
-
RUNTIME_PROXY_BEARER_TOKEN=\$RUNTIME_PROXY_BEARER_TOKEN
|
|
145
140
|
RUNTIME_HTTP_PORT=7821
|
|
146
|
-
VELLUM_CLOUD=\$VELLUM_CLOUD
|
|
147
141
|
DOTENV_EOF
|
|
148
142
|
|
|
149
|
-
mkdir -p "\$HOME/.vellum/workspace"
|
|
150
|
-
cat > "\$HOME/.vellum/workspace/config.json" << CONFIG_EOF
|
|
151
|
-
{
|
|
152
|
-
"logFile": {
|
|
153
|
-
"dir": "\$HOME/.vellum/workspace/data/logs"
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
CONFIG_EOF
|
|
157
|
-
|
|
158
143
|
${ownershipFixup}
|
|
159
144
|
|
|
160
145
|
export VELLUM_SSH_USER="\$SSH_USER"
|
|
@@ -743,7 +728,10 @@ async function hatchLocal(
|
|
|
743
728
|
`🧹 Found ${orphans.length} orphaned process${orphans.length === 1 ? "" : "es"} — cleaning up...`,
|
|
744
729
|
);
|
|
745
730
|
for (const orphan of orphans) {
|
|
746
|
-
await stopProcess(
|
|
731
|
+
await stopProcess(
|
|
732
|
+
parseInt(orphan.pid, 10),
|
|
733
|
+
`${orphan.name} (PID ${orphan.pid})`,
|
|
734
|
+
);
|
|
747
735
|
}
|
|
748
736
|
}
|
|
749
737
|
}
|
|
@@ -776,7 +764,7 @@ async function hatchLocal(
|
|
|
776
764
|
|
|
777
765
|
let runtimeUrl: string;
|
|
778
766
|
try {
|
|
779
|
-
runtimeUrl = await startGateway(
|
|
767
|
+
runtimeUrl = await startGateway(watch, resources);
|
|
780
768
|
} catch (error) {
|
|
781
769
|
// Gateway failed — stop the daemon we just started so we don't leave
|
|
782
770
|
// orphaned processes with no lock file entry.
|
package/src/commands/ps.ts
CHANGED
|
@@ -150,7 +150,13 @@ async function detectProcess(spec: ProcessSpec): Promise<DetectedProcess> {
|
|
|
150
150
|
const pids = await pgrepExact(spec.pgrepName);
|
|
151
151
|
if (pids.length > 0) {
|
|
152
152
|
const watch = await isWatchMode(pids[0]);
|
|
153
|
-
return {
|
|
153
|
+
return {
|
|
154
|
+
name: spec.name,
|
|
155
|
+
pid: pids[0],
|
|
156
|
+
port: spec.port,
|
|
157
|
+
running: true,
|
|
158
|
+
watch,
|
|
159
|
+
};
|
|
154
160
|
}
|
|
155
161
|
|
|
156
162
|
// Tier 2: TCP port probe (skip for processes without a port)
|
|
@@ -171,10 +177,22 @@ async function detectProcess(spec: ProcessSpec): Promise<DetectedProcess> {
|
|
|
171
177
|
const filePid = readPidFile(spec.pidFile);
|
|
172
178
|
if (filePid && isProcessAlive(filePid)) {
|
|
173
179
|
const watch = await isWatchMode(filePid);
|
|
174
|
-
return {
|
|
180
|
+
return {
|
|
181
|
+
name: spec.name,
|
|
182
|
+
pid: filePid,
|
|
183
|
+
port: spec.port,
|
|
184
|
+
running: true,
|
|
185
|
+
watch,
|
|
186
|
+
};
|
|
175
187
|
}
|
|
176
188
|
|
|
177
|
-
return {
|
|
189
|
+
return {
|
|
190
|
+
name: spec.name,
|
|
191
|
+
pid: null,
|
|
192
|
+
port: spec.port,
|
|
193
|
+
running: false,
|
|
194
|
+
watch: false,
|
|
195
|
+
};
|
|
178
196
|
}
|
|
179
197
|
|
|
180
198
|
function formatDetectionInfo(proc: DetectedProcess): string {
|
package/src/commands/recover.ts
CHANGED
|
@@ -69,7 +69,7 @@ export async function recover(): Promise<void> {
|
|
|
69
69
|
// 7. Start daemon + gateway (same as wake)
|
|
70
70
|
await startLocalDaemon(false, entry.resources);
|
|
71
71
|
if (!process.env.VELLUM_DESKTOP_APP) {
|
|
72
|
-
await startGateway(
|
|
72
|
+
await startGateway(false, entry.resources);
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
console.log(`✅ Recovered assistant '${name}'.`);
|
package/src/commands/retire.ts
CHANGED
|
@@ -9,10 +9,7 @@ import {
|
|
|
9
9
|
removeAssistantEntry,
|
|
10
10
|
} from "../lib/assistant-config";
|
|
11
11
|
import type { AssistantEntry } from "../lib/assistant-config";
|
|
12
|
-
import {
|
|
13
|
-
getPlatformUrl,
|
|
14
|
-
readPlatformToken,
|
|
15
|
-
} from "../lib/platform-client";
|
|
12
|
+
import { getPlatformUrl, readPlatformToken } from "../lib/platform-client";
|
|
16
13
|
import { retireInstance as retireAwsInstance } from "../lib/aws";
|
|
17
14
|
import { retireDocker } from "../lib/docker";
|
|
18
15
|
import { retireInstance as retireGcpInstance } from "../lib/gcp";
|
|
@@ -83,7 +80,7 @@ async function retireLocal(name: string, entry: AssistantEntry): Promise<void> {
|
|
|
83
80
|
const daemonStopped = await stopProcessByPidFile(daemonPidFile, "daemon");
|
|
84
81
|
|
|
85
82
|
// Stop gateway via PID file — use a longer timeout because the gateway has a
|
|
86
|
-
//
|
|
83
|
+
// drain window (5s) before it exits.
|
|
87
84
|
const gatewayPidFile = join(vellumDir, "gateway.pid");
|
|
88
85
|
await stopProcessByPidFile(gatewayPidFile, "gateway", undefined, 7000);
|
|
89
86
|
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { createInterface } from "readline";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
3
|
+
import { homedir } from "os";
|
|
4
|
+
import { dirname, join } from "path";
|
|
5
|
+
|
|
6
|
+
function getVellumDir(): string {
|
|
7
|
+
const base = process.env.BASE_DATA_DIR?.trim() || homedir();
|
|
8
|
+
return join(base, ".vellum");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function getEnvFilePath(): string {
|
|
12
|
+
return join(getVellumDir(), ".env");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function readEnvFile(): Record<string, string> {
|
|
16
|
+
const envPath = getEnvFilePath();
|
|
17
|
+
const vars: Record<string, string> = {};
|
|
18
|
+
if (!existsSync(envPath)) return vars;
|
|
19
|
+
|
|
20
|
+
const content = readFileSync(envPath, "utf-8");
|
|
21
|
+
for (const line of content.split("\n")) {
|
|
22
|
+
const trimmed = line.trim();
|
|
23
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
24
|
+
const eqIdx = trimmed.indexOf("=");
|
|
25
|
+
if (eqIdx === -1) continue;
|
|
26
|
+
const key = trimmed.slice(0, eqIdx).trim();
|
|
27
|
+
const value = trimmed.slice(eqIdx + 1).trim();
|
|
28
|
+
vars[key] = value;
|
|
29
|
+
}
|
|
30
|
+
return vars;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function writeEnvFile(vars: Record<string, string>): void {
|
|
34
|
+
const envPath = getEnvFilePath();
|
|
35
|
+
const dir = dirname(envPath);
|
|
36
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
37
|
+
|
|
38
|
+
const lines = Object.entries(vars).map(([k, v]) => `${k}=${v}`);
|
|
39
|
+
writeFileSync(envPath, lines.join("\n") + "\n", { mode: 0o600 });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function promptMasked(prompt: string): Promise<string> {
|
|
43
|
+
return new Promise((resolve) => {
|
|
44
|
+
const rl = createInterface({
|
|
45
|
+
input: process.stdin,
|
|
46
|
+
output: process.stdout,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Disable echoing by writing the prompt manually and intercepting keystrokes
|
|
50
|
+
process.stdout.write(prompt);
|
|
51
|
+
|
|
52
|
+
const stdin = process.stdin;
|
|
53
|
+
const wasRaw = stdin.isRaw;
|
|
54
|
+
if (stdin.isTTY) {
|
|
55
|
+
stdin.setRawMode(true);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let input = "";
|
|
59
|
+
const onData = (key: Buffer): void => {
|
|
60
|
+
const char = key.toString("utf-8");
|
|
61
|
+
|
|
62
|
+
if (char === "\r" || char === "\n") {
|
|
63
|
+
// Enter pressed
|
|
64
|
+
stdin.removeListener("data", onData);
|
|
65
|
+
if (stdin.isTTY) {
|
|
66
|
+
stdin.setRawMode(wasRaw ?? false);
|
|
67
|
+
}
|
|
68
|
+
process.stdout.write("\n");
|
|
69
|
+
rl.close();
|
|
70
|
+
resolve(input);
|
|
71
|
+
} else if (char === "\u0003") {
|
|
72
|
+
// Ctrl+C
|
|
73
|
+
process.stdout.write("\n");
|
|
74
|
+
process.exit(1);
|
|
75
|
+
} else if (char === "\u007F" || char === "\b") {
|
|
76
|
+
// Backspace
|
|
77
|
+
if (input.length > 0) {
|
|
78
|
+
input = input.slice(0, -1);
|
|
79
|
+
process.stdout.write("\b \b");
|
|
80
|
+
}
|
|
81
|
+
} else if (char.length === 1 && char >= " ") {
|
|
82
|
+
input += char;
|
|
83
|
+
process.stdout.write("*");
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
stdin.on("data", onData);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function validateAnthropicKey(apiKey: string): Promise<boolean> {
|
|
92
|
+
try {
|
|
93
|
+
const resp = await fetch("https://api.anthropic.com/v1/models", {
|
|
94
|
+
headers: {
|
|
95
|
+
"x-api-key": apiKey,
|
|
96
|
+
"anthropic-version": "2023-06-01",
|
|
97
|
+
},
|
|
98
|
+
signal: AbortSignal.timeout(10_000),
|
|
99
|
+
});
|
|
100
|
+
return resp.ok;
|
|
101
|
+
} catch {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export async function setup(): Promise<void> {
|
|
107
|
+
const args = process.argv.slice(3);
|
|
108
|
+
|
|
109
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
110
|
+
console.log("Usage: vellum setup");
|
|
111
|
+
console.log("");
|
|
112
|
+
console.log("Interactive wizard to configure API keys.");
|
|
113
|
+
console.log(
|
|
114
|
+
"Keys are validated against their APIs and saved to <BASE_DATA_DIR>/.vellum/.env.",
|
|
115
|
+
);
|
|
116
|
+
process.exit(0);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
console.log("Vellum Setup");
|
|
120
|
+
console.log("============\n");
|
|
121
|
+
|
|
122
|
+
const existingVars = readEnvFile();
|
|
123
|
+
const hasExistingKey = !!existingVars.ANTHROPIC_API_KEY;
|
|
124
|
+
|
|
125
|
+
if (hasExistingKey) {
|
|
126
|
+
const masked =
|
|
127
|
+
existingVars.ANTHROPIC_API_KEY.slice(0, 7) +
|
|
128
|
+
"..." +
|
|
129
|
+
existingVars.ANTHROPIC_API_KEY.slice(-4);
|
|
130
|
+
console.log(`Anthropic API key is already configured (${masked}).`);
|
|
131
|
+
|
|
132
|
+
const rl = createInterface({
|
|
133
|
+
input: process.stdin,
|
|
134
|
+
output: process.stdout,
|
|
135
|
+
});
|
|
136
|
+
const answer = await new Promise<string>((resolve) => {
|
|
137
|
+
rl.question("Overwrite? [y/N] ", resolve);
|
|
138
|
+
});
|
|
139
|
+
rl.close();
|
|
140
|
+
|
|
141
|
+
if (answer.trim().toLowerCase() !== "y") {
|
|
142
|
+
console.log("\nSetup complete. No changes made.");
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
console.log("");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const apiKey = await promptMasked(
|
|
149
|
+
"Enter your Anthropic API key (sk-ant-...): ",
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
if (!apiKey.trim()) {
|
|
153
|
+
console.error("Error: API key cannot be empty.");
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
console.log("Validating key...");
|
|
158
|
+
const valid = await validateAnthropicKey(apiKey.trim());
|
|
159
|
+
|
|
160
|
+
if (!valid) {
|
|
161
|
+
console.error(
|
|
162
|
+
"Error: Invalid API key. Could not authenticate with the Anthropic API.",
|
|
163
|
+
);
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
existingVars.ANTHROPIC_API_KEY = apiKey.trim();
|
|
168
|
+
writeEnvFile(existingVars);
|
|
169
|
+
|
|
170
|
+
console.log(`\nAPI key saved to ${getEnvFilePath()}`);
|
|
171
|
+
console.log("Setup complete.");
|
|
172
|
+
}
|
package/src/commands/sleep.ts
CHANGED
|
@@ -120,7 +120,7 @@ export async function sleep(): Promise<void> {
|
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
// Stop gateway — use a longer timeout because the gateway has a configurable
|
|
123
|
-
// drain window (
|
|
123
|
+
// drain window (5s) before it exits.
|
|
124
124
|
const gatewayStopped = await stopProcessByPidFile(
|
|
125
125
|
gatewayPidFile,
|
|
126
126
|
"gateway",
|
package/src/commands/wake.ts
CHANGED
|
@@ -87,12 +87,12 @@ export async function wake(): Promise<void> {
|
|
|
87
87
|
`Gateway running (pid ${pid}) — restarting in watch mode...`,
|
|
88
88
|
);
|
|
89
89
|
await stopProcessByPidFile(gatewayPidFile, "gateway");
|
|
90
|
-
await startGateway(
|
|
90
|
+
await startGateway(watch, resources);
|
|
91
91
|
} else {
|
|
92
92
|
console.log(`Gateway already running (pid ${pid}).`);
|
|
93
93
|
}
|
|
94
94
|
} else {
|
|
95
|
-
await startGateway(
|
|
95
|
+
await startGateway(watch, resources);
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
|