@vellumai/cli 0.4.0 → 0.4.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/cli",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "CLI tools for vellum-assistant",
5
5
  "type": "module",
6
6
  "exports": {
@@ -79,6 +79,82 @@ ensure_bun() {
79
79
  success "bun installed ($(bun --version))"
80
80
  }
81
81
 
82
+ # Ensure ~/.bun/bin is in the user's shell profile so bun and vellum are
83
+ # available in new terminal sessions. The bun installer sometimes skips
84
+ # this (e.g. when stdin is piped via curl | bash).
85
+ configure_shell_profile() {
86
+ local bun_line='export BUN_INSTALL="$HOME/.bun"'
87
+ local path_line='export PATH="$BUN_INSTALL/bin:$PATH"'
88
+ local snippet
89
+ snippet=$(printf '\n# bun\n%s\n%s\n' "$bun_line" "$path_line")
90
+
91
+ local profiles=()
92
+ local shell_name="${SHELL:-}"
93
+
94
+ if [[ "$shell_name" == */zsh ]]; then
95
+ profiles+=("$HOME/.zshrc")
96
+ elif [[ "$shell_name" == */bash ]]; then
97
+ # Write to both .bashrc (non-login shells, e.g. new terminal on Linux)
98
+ # and .bash_profile (login shells, e.g. macOS Terminal.app)
99
+ profiles+=("$HOME/.bashrc")
100
+ [ -f "$HOME/.bash_profile" ] && profiles+=("$HOME/.bash_profile")
101
+ else
102
+ # Unknown shell — try both
103
+ profiles+=("$HOME/.bashrc")
104
+ [ -f "$HOME/.zshrc" ] && profiles+=("$HOME/.zshrc")
105
+ fi
106
+
107
+ for profile in "${profiles[@]}"; do
108
+ if [ -f "$profile" ] && grep -q 'BUN_INSTALL' "$profile" 2>/dev/null; then
109
+ continue
110
+ fi
111
+ printf '%s\n' "$snippet" >> "$profile"
112
+ success "Added bun to PATH in $profile"
113
+ done
114
+ }
115
+
116
+ # Create a symlink so `vellum` is available without ~/.bun/bin in PATH.
117
+ # Tries /usr/local/bin first (works on most systems), falls back to
118
+ # ~/.local/bin (user-writable, no sudo needed).
119
+ # This is best-effort — failure must not abort the install script.
120
+ symlink_vellum() {
121
+ local vellum_bin="$HOME/.bun/bin/vellum"
122
+ if [ ! -f "$vellum_bin" ]; then
123
+ return 0
124
+ fi
125
+
126
+ # Skip if vellum is already resolvable outside of ~/.bun/bin
127
+ local resolved
128
+ resolved=$(command -v vellum 2>/dev/null || true)
129
+ if [ -n "$resolved" ] && [ "$resolved" != "$vellum_bin" ]; then
130
+ return 0
131
+ fi
132
+
133
+ # Try /usr/local/bin (may need sudo on some systems)
134
+ if [ -d "/usr/local/bin" ] && [ -w "/usr/local/bin" ]; then
135
+ if ln -sf "$vellum_bin" /usr/local/bin/vellum 2>/dev/null; then
136
+ success "Symlinked /usr/local/bin/vellum → $vellum_bin"
137
+ return 0
138
+ fi
139
+ fi
140
+
141
+ # Fallback: ~/.local/bin
142
+ local local_bin="$HOME/.local/bin"
143
+ mkdir -p "$local_bin" 2>/dev/null || true
144
+ if ln -sf "$vellum_bin" "$local_bin/vellum" 2>/dev/null; then
145
+ success "Symlinked $local_bin/vellum → $vellum_bin"
146
+ # Ensure ~/.local/bin is in PATH in shell profile
147
+ for profile in "$HOME/.zshrc" "$HOME/.bashrc" "$HOME/.bash_profile"; do
148
+ if [ -f "$profile" ] && ! grep -q "$local_bin" "$profile" 2>/dev/null; then
149
+ printf '\nexport PATH="%s:$PATH"\n' "$local_bin" >> "$profile"
150
+ fi
151
+ done
152
+ return 0
153
+ fi
154
+
155
+ return 0
156
+ }
157
+
82
158
  install_vellum() {
83
159
  if command -v vellum >/dev/null 2>&1; then
84
160
  info "Updating vellum to latest..."
@@ -103,7 +179,15 @@ main() {
103
179
 
104
180
  ensure_git
105
181
  ensure_bun
182
+ configure_shell_profile
106
183
  install_vellum
184
+ symlink_vellum
185
+
186
+ # Source the shell profile so vellum hatch runs with the correct PATH
187
+ # in this session (the profile changes only take effect in new shells
188
+ # otherwise).
189
+ export BUN_INSTALL="$HOME/.bun"
190
+ export PATH="$BUN_INSTALL/bin:$PATH"
107
191
 
108
192
  info "Running vellum hatch..."
109
193
  printf "\n"
@@ -13,6 +13,7 @@ import { loadAllAssistants, saveAssistantEntry, syncConfigToLockfile } from "../
13
13
  import type { AssistantEntry } from "../lib/assistant-config";
14
14
  import { hatchAws } from "../lib/aws";
15
15
  import {
16
+ GATEWAY_PORT,
16
17
  SPECIES_CONFIG,
17
18
  VALID_REMOTE_HOSTS,
18
19
  VALID_SPECIES,
@@ -21,6 +22,7 @@ import type { RemoteHost, Species } from "../lib/constants";
21
22
  import { hatchGcp } from "../lib/gcp";
22
23
  import type { PollResult, WatchHatchingResult } from "../lib/gcp";
23
24
  import { startLocalDaemon, startGateway, stopLocalProcesses } from "../lib/local";
25
+ import { probePort } from "../lib/port-probe";
24
26
  import { isProcessAlive } from "../lib/process";
25
27
  import { generateRandomSuffix } from "../lib/random-name";
26
28
  import { validateAssistantName } from "../lib/retire-archive";
@@ -155,7 +157,22 @@ function parseArgs(): HatchArgs {
155
157
 
156
158
  for (let i = 0; i < args.length; i++) {
157
159
  const arg = args[i];
158
- if (arg === "-d") {
160
+ if (arg === "--help" || arg === "-h") {
161
+ console.log("Usage: vellum hatch [species] [options]");
162
+ console.log("");
163
+ console.log("Create a new assistant instance.");
164
+ console.log("");
165
+ console.log("Species:");
166
+ console.log(" vellum Default assistant (default)");
167
+ console.log(" openclaw OpenClaw adapter");
168
+ console.log("");
169
+ console.log("Options:");
170
+ console.log(" -d Run in detached mode");
171
+ console.log(" --name <name> Custom instance name");
172
+ console.log(" --remote <host> Remote host (local, gcp, aws, custom)");
173
+ console.log(" --daemon-only Start daemon only, skip gateway");
174
+ process.exit(0);
175
+ } else if (arg === "-d") {
159
176
  detached = true;
160
177
  } else if (arg === "--daemon-only") {
161
178
  daemonOnly = true;
@@ -496,6 +513,8 @@ async function displayPairingQRCode(runtimeUrl: string, bearerToken: string | un
496
513
  });
497
514
 
498
515
  if (!registerRes.ok) {
516
+ const body = await registerRes.text().catch(() => "");
517
+ console.warn(`⚠ Could not register pairing request: ${registerRes.status} ${registerRes.statusText}${body ? ` — ${body}` : ""}. Run \`vellum pair\` to try again.\n`);
499
518
  return;
500
519
  }
501
520
 
@@ -519,8 +538,10 @@ async function displayPairingQRCode(runtimeUrl: string, bearerToken: string | un
519
538
  console.log(qrString);
520
539
  console.log("This pairing request expires in 5 minutes.");
521
540
  console.log("Run `vellum pair` to generate a new one.\n");
522
- } catch {
541
+ } catch (err) {
523
542
  // Non-fatal — pairing is optional
543
+ const reason = err instanceof Error ? err.message : String(err);
544
+ console.warn(`⚠ Could not generate pairing QR code: ${reason}. Run \`vellum pair\` to try again.\n`);
524
545
  }
525
546
  }
526
547
 
@@ -540,6 +561,32 @@ async function hatchLocal(species: Species, name: string | null, daemonOnly: boo
540
561
  console.log("🧹 Cleaning up stale local processes (no lock file entry)...\n");
541
562
  await stopLocalProcesses();
542
563
  }
564
+
565
+ // Verify required ports are available before starting any services.
566
+ // Only check when no local assistants exist — if there are existing local
567
+ // assistants, their daemon/gateway/qdrant legitimately own these ports.
568
+ const RUNTIME_HTTP_PORT = Number(process.env.RUNTIME_HTTP_PORT) || 7821;
569
+ const QDRANT_PORT = 6333;
570
+ const requiredPorts = [
571
+ { name: "daemon", port: RUNTIME_HTTP_PORT },
572
+ { name: "gateway", port: GATEWAY_PORT },
573
+ { name: "qdrant", port: QDRANT_PORT },
574
+ ];
575
+ const conflicts: string[] = [];
576
+ await Promise.all(
577
+ requiredPorts.map(async ({ name, port }) => {
578
+ if (await probePort(port)) {
579
+ conflicts.push(` - Port ${port} (${name}) is already in use`);
580
+ }
581
+ }),
582
+ );
583
+ if (conflicts.length > 0) {
584
+ throw new Error(
585
+ `Cannot hatch — required ports are already in use:\n${conflicts.join("\n")}\n\n` +
586
+ "Stop the conflicting processes or use environment variables to configure alternative ports " +
587
+ "(RUNTIME_HTTP_PORT, GATEWAY_PORT).",
588
+ );
589
+ }
543
590
  }
544
591
 
545
592
  const baseDataDir = join(process.env.BASE_DATA_DIR?.trim() || (process.env.HOME ?? userInfo().homedir), ".vellum");
@@ -7,6 +7,17 @@ import {
7
7
 
8
8
  export async function login(): Promise<void> {
9
9
  const args = process.argv.slice(3);
10
+
11
+ if (args.includes("--help") || args.includes("-h")) {
12
+ console.log("Usage: vellum login --token <session-token>");
13
+ console.log("");
14
+ console.log("Log in to the Vellum platform.");
15
+ console.log("");
16
+ console.log("Options:");
17
+ console.log(" --token <token> Session token from the Vellum platform");
18
+ process.exit(0);
19
+ }
20
+
10
21
  let token: string | null = null;
11
22
 
12
23
  for (let i = 0; i < args.length; i++) {
@@ -43,11 +54,27 @@ export async function login(): Promise<void> {
43
54
  }
44
55
 
45
56
  export async function logout(): Promise<void> {
57
+ const args = process.argv.slice(3);
58
+ if (args.includes("--help") || args.includes("-h")) {
59
+ console.log("Usage: vellum logout");
60
+ console.log("");
61
+ console.log("Log out of the Vellum platform and remove the stored session token.");
62
+ process.exit(0);
63
+ }
64
+
46
65
  clearPlatformToken();
47
66
  console.log("Logged out. Platform token removed.");
48
67
  }
49
68
 
50
69
  export async function whoami(): Promise<void> {
70
+ const args = process.argv.slice(3);
71
+ if (args.includes("--help") || args.includes("-h")) {
72
+ console.log("Usage: vellum whoami");
73
+ console.log("");
74
+ console.log("Show the currently logged-in Vellum platform user.");
75
+ process.exit(0);
76
+ }
77
+
51
78
  const token = readPlatformToken();
52
79
  if (!token) {
53
80
  console.error("Not logged in. Run `vellum login --token <token>` first.");
@@ -72,6 +72,14 @@ async function pollForApproval(
72
72
 
73
73
  export async function pair(): Promise<void> {
74
74
  const args = process.argv.slice(3);
75
+
76
+ if (args.includes("--help") || args.includes("-h")) {
77
+ console.log("Usage: vellum pair <path-to-qrcode.png>");
78
+ console.log("");
79
+ console.log("Pair with a remote assistant by scanning the QR code PNG generated during setup.");
80
+ process.exit(0);
81
+ }
82
+
75
83
  const qrCodePath = args[0] || process.env.VELLUM_CUSTOM_QR_CODE_PATH;
76
84
 
77
85
  if (!qrCodePath) {
@@ -409,6 +409,17 @@ async function listAllAssistants(): Promise<void> {
409
409
  // ── Entry point ─────────────────────────────────────────────────
410
410
 
411
411
  export async function ps(): Promise<void> {
412
+ const args = process.argv.slice(3);
413
+ if (args.includes("--help") || args.includes("-h")) {
414
+ console.log("Usage: vellum ps [<name>]");
415
+ console.log("");
416
+ console.log("List all assistants, or show processes for a specific assistant.");
417
+ console.log("");
418
+ console.log("Arguments:");
419
+ console.log(" <name> Show processes for the named assistant");
420
+ process.exit(0);
421
+ }
422
+
412
423
  const assistantId = process.argv[3];
413
424
 
414
425
  if (!assistantId) {
@@ -9,6 +9,17 @@ import { getArchivePath, getMetadataPath } from "../lib/retire-archive";
9
9
  import { exec } from "../lib/step-runner";
10
10
 
11
11
  export async function recover(): Promise<void> {
12
+ const args = process.argv.slice(3);
13
+ if (args.includes("--help") || args.includes("-h")) {
14
+ console.log("Usage: vellum recover <name>");
15
+ console.log("");
16
+ console.log("Restore a previously retired local assistant from its archive.");
17
+ console.log("");
18
+ console.log("Arguments:");
19
+ console.log(" <name> Name of the retired assistant to recover");
20
+ process.exit(0);
21
+ }
22
+
12
23
  const name = process.argv[3];
13
24
  if (!name) {
14
25
  console.error("Usage: vellum recover <name>");
@@ -121,6 +121,20 @@ function parseSource(): string | undefined {
121
121
  }
122
122
 
123
123
  export async function retire(): Promise<void> {
124
+ const args = process.argv.slice(3);
125
+ if (args.includes("--help") || args.includes("-h")) {
126
+ console.log("Usage: vellum retire <name> [--source <source>]");
127
+ console.log("");
128
+ console.log("Delete an assistant instance and archive its data.");
129
+ console.log("");
130
+ console.log("Arguments:");
131
+ console.log(" <name> Name of the assistant to retire");
132
+ console.log("");
133
+ console.log("Options:");
134
+ console.log(" --source <source> Source identifier for the retirement");
135
+ process.exit(0);
136
+ }
137
+
124
138
  const name = process.argv[3];
125
139
 
126
140
  if (!name) {
@@ -5,6 +5,14 @@ import { loadAllAssistants } from "../lib/assistant-config";
5
5
  import { stopProcessByPidFile } from "../lib/process";
6
6
 
7
7
  export async function sleep(): Promise<void> {
8
+ const args = process.argv.slice(3);
9
+ if (args.includes("--help") || args.includes("-h")) {
10
+ console.log("Usage: vellum sleep");
11
+ console.log("");
12
+ console.log("Stop the daemon and gateway processes.");
13
+ process.exit(0);
14
+ }
15
+
8
16
  const assistants = loadAllAssistants();
9
17
  const hasLocal = assistants.some((a) => a.cloud === "local");
10
18
  if (!hasLocal) {
@@ -33,6 +33,17 @@ function extractHostFromUrl(url: string): string {
33
33
  }
34
34
 
35
35
  export async function ssh(): Promise<void> {
36
+ const args = process.argv.slice(3);
37
+ if (args.includes("--help") || args.includes("-h")) {
38
+ console.log("Usage: vellum ssh [<name>]");
39
+ console.log("");
40
+ console.log("SSH into a remote assistant instance.");
41
+ console.log("");
42
+ console.log("Arguments:");
43
+ console.log(" <name> Name of the assistant to connect to (defaults to latest)");
44
+ process.exit(0);
45
+ }
46
+
36
47
  const name = process.argv[3];
37
48
  const entry = name ? findAssistantByName(name) : loadLatestAssistant();
38
49
 
@@ -7,6 +7,14 @@ import { isProcessAlive } from "../lib/process";
7
7
  import { startLocalDaemon, startGateway } from "../lib/local";
8
8
 
9
9
  export async function wake(): Promise<void> {
10
+ const args = process.argv.slice(3);
11
+ if (args.includes("--help") || args.includes("-h")) {
12
+ console.log("Usage: vellum wake");
13
+ console.log("");
14
+ console.log("Start the daemon and gateway processes.");
15
+ process.exit(0);
16
+ }
17
+
10
18
  const assistants = loadAllAssistants();
11
19
  const hasLocal = assistants.some((a) => a.cloud === "local");
12
20
  if (!hasLocal) {
package/src/index.ts CHANGED
@@ -31,8 +31,8 @@ const commands = {
31
31
  email,
32
32
  hatch,
33
33
  login,
34
- pair,
35
34
  logout,
35
+ pair,
36
36
  ps,
37
37
  recover,
38
38
  retire,
@@ -90,9 +90,9 @@ async function main() {
90
90
  console.log(" contacts Manage the contact graph");
91
91
  console.log(" email Email operations (status, create inbox)");
92
92
  console.log(" hatch Create a new assistant instance");
93
- console.log(" pair Pair with a remote assistant via QR code");
94
93
  console.log(" login Log in to the Vellum platform");
95
94
  console.log(" logout Log out of the Vellum platform");
95
+ console.log(" pair Pair with a remote assistant via QR code");
96
96
  console.log(" ps List assistants (or processes for a specific assistant)");
97
97
  console.log(" recover Restore a previously retired local assistant");
98
98
  console.log(" retire Delete an assistant instance");