@vellumai/cli 0.4.45 → 0.4.48

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.45",
3
+ "version": "0.4.48",
4
4
  "description": "CLI tools for vellum-assistant",
5
5
  "type": "module",
6
6
  "exports": {
@@ -11,7 +11,7 @@
11
11
  "./src/commands/*": "./src/commands/*.ts"
12
12
  },
13
13
  "bin": {
14
- "assistant": "./src/index.ts"
14
+ "vellum": "./src/index.ts"
15
15
  },
16
16
  "scripts": {
17
17
  "format": "prettier --write .",
@@ -179,27 +179,30 @@ configure_shell_profile() {
179
179
  done
180
180
  }
181
181
 
182
- # Create a symlink so `vellum` is available without ~/.bun/bin in PATH.
182
+ # Create a symlink so a CLI command is available without ~/.bun/bin in PATH.
183
183
  # Tries /usr/local/bin first (works on most systems), falls back to
184
184
  # ~/.local/bin (user-writable, no sudo needed).
185
185
  # This is best-effort — failure must not abort the install script.
186
- symlink_vellum() {
187
- local vellum_bin="$HOME/.bun/bin/vellum"
188
- if [ ! -f "$vellum_bin" ]; then
186
+ #
187
+ # Usage: symlink_cli <command_name>
188
+ symlink_cli() {
189
+ local cmd_name="$1"
190
+ local cmd_bin="$HOME/.bun/bin/$cmd_name"
191
+ if [ ! -f "$cmd_bin" ]; then
189
192
  return 0
190
193
  fi
191
194
 
192
- # Skip if vellum is already resolvable outside of ~/.bun/bin
195
+ # Skip if the command is already resolvable outside of ~/.bun/bin
193
196
  local resolved
194
- resolved=$(command -v vellum 2>/dev/null || true)
195
- if [ -n "$resolved" ] && [ "$resolved" != "$vellum_bin" ]; then
197
+ resolved=$(command -v "$cmd_name" 2>/dev/null || true)
198
+ if [ -n "$resolved" ] && [ "$resolved" != "$cmd_bin" ]; then
196
199
  return 0
197
200
  fi
198
201
 
199
202
  # Try /usr/local/bin (may need sudo on some systems)
200
203
  if [ -d "/usr/local/bin" ] && [ -w "/usr/local/bin" ]; then
201
- if ln -sf "$vellum_bin" /usr/local/bin/vellum 2>/dev/null; then
202
- success "Symlinked /usr/local/bin/vellum → $vellum_bin"
204
+ if ln -sf "$cmd_bin" "/usr/local/bin/$cmd_name" 2>/dev/null; then
205
+ success "Symlinked /usr/local/bin/$cmd_name → $cmd_bin"
203
206
  return 0
204
207
  fi
205
208
  fi
@@ -207,8 +210,8 @@ symlink_vellum() {
207
210
  # Fallback: ~/.local/bin
208
211
  local local_bin="$HOME/.local/bin"
209
212
  mkdir -p "$local_bin" 2>/dev/null || true
210
- if ln -sf "$vellum_bin" "$local_bin/vellum" 2>/dev/null; then
211
- success "Symlinked $local_bin/vellum → $vellum_bin"
213
+ if ln -sf "$cmd_bin" "$local_bin/$cmd_name" 2>/dev/null; then
214
+ success "Symlinked $local_bin/$cmd_name → $cmd_bin"
212
215
  # Ensure ~/.local/bin is in PATH in shell profile
213
216
  for profile in "$HOME/.zshrc" "$HOME/.bashrc" "$HOME/.bash_profile"; do
214
217
  if [ -f "$profile" ] && ! grep -q "$local_bin" "$profile" 2>/dev/null; then
@@ -221,6 +224,11 @@ symlink_vellum() {
221
224
  return 0
222
225
  }
223
226
 
227
+ symlink_vellum() {
228
+ symlink_cli "vellum"
229
+ symlink_cli "assistant"
230
+ }
231
+
224
232
  # Write a small sourceable env file to ~/.config/vellum/env so callers can
225
233
  # pick up PATH changes without restarting their shell:
226
234
  # curl -fsSL https://assistant.vellum.ai/install.sh | bash && . ~/.config/vellum/env
@@ -265,6 +273,11 @@ main() {
265
273
  install_vellum
266
274
  symlink_vellum
267
275
 
276
+ # Verify the assistant CLI is available
277
+ if ! command -v assistant >/dev/null 2>&1; then
278
+ info "Note: 'assistant' command may require opening a new terminal session"
279
+ fi
280
+
268
281
  # Write a sourceable env file so the quickstart one-liner can pick up
269
282
  # PATH changes in the caller's shell:
270
283
  # curl ... | bash && . ~/.config/vellum/env
@@ -48,7 +48,8 @@ import {
48
48
  stopLocalProcesses,
49
49
  } from "../lib/local";
50
50
  import { maybeStartNgrokTunnel } from "../lib/ngrok";
51
- import { isProcessAlive } from "../lib/process";
51
+ import { detectOrphanedProcesses } from "../lib/orphan-detection";
52
+ import { isProcessAlive, stopProcess } from "../lib/process";
52
53
  import { generateRandomSuffix } from "../lib/random-name";
53
54
  import { validateAssistantName } from "../lib/retire-archive";
54
55
  import { archiveLogFile, resetLogFile } from "../lib/xdg-log";
@@ -731,6 +732,22 @@ async function hatchLocal(
731
732
  }
732
733
  }
733
734
 
735
+ // On desktop, scan the process table for orphaned vellum processes that
736
+ // are not tracked by any PID file or lock file entry and kill them before
737
+ // starting new ones. This prevents resource leaks when the desktop app
738
+ // crashes or is force-quit without a clean shutdown.
739
+ if (IS_DESKTOP) {
740
+ const orphans = await detectOrphanedProcesses();
741
+ if (orphans.length > 0) {
742
+ desktopLog(
743
+ `🧹 Found ${orphans.length} orphaned process${orphans.length === 1 ? "" : "es"} — cleaning up...`,
744
+ );
745
+ for (const orphan of orphans) {
746
+ await stopProcess(parseInt(orphan.pid, 10), `${orphan.name} (PID ${orphan.pid})`);
747
+ }
748
+ }
749
+ }
750
+
734
751
  // Reuse existing resources if re-hatching with --name that matches a known
735
752
  // local assistant, otherwise allocate fresh per-instance ports and directories.
736
753
  let resources: LocalInstanceResources;
@@ -9,6 +9,10 @@ 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
16
  import { retireInstance as retireAwsInstance } from "../lib/aws";
13
17
  import { retireDocker } from "../lib/docker";
14
18
  import { retireInstance as retireGcpInstance } from "../lib/gcp";
@@ -18,7 +22,12 @@ import {
18
22
  } from "../lib/process";
19
23
  import { getArchivePath, getMetadataPath } from "../lib/retire-archive";
20
24
  import { exec } from "../lib/step-runner";
21
- import { openLogFile, closeLogFile, writeToLogFile } from "../lib/xdg-log";
25
+ import {
26
+ openLogFile,
27
+ closeLogFile,
28
+ resetLogFile,
29
+ writeToLogFile,
30
+ } from "../lib/xdg-log";
22
31
 
23
32
  function resolveCloud(entry: AssistantEntry): string {
24
33
  if (entry.cloud) {
@@ -172,6 +181,34 @@ async function retireCustom(entry: AssistantEntry): Promise<void> {
172
181
  console.log(`\u2705 Custom instance retired.`);
173
182
  }
174
183
 
184
+ async function retireVellum(assistantId: string): Promise<void> {
185
+ console.log("\u{1F5D1}\ufe0f Retiring platform-hosted instance...\n");
186
+
187
+ const token = readPlatformToken();
188
+ if (!token) {
189
+ console.error(
190
+ "Error: Not logged in. Run `vellum login --token <token>` first.",
191
+ );
192
+ process.exit(1);
193
+ }
194
+
195
+ const url = `${getPlatformUrl()}/v1/assistants/${encodeURIComponent(assistantId)}/retire/`;
196
+ const response = await fetch(url, {
197
+ method: "DELETE",
198
+ headers: { "X-Session-Token": token },
199
+ });
200
+
201
+ if (!response.ok) {
202
+ const body = await response.text();
203
+ console.error(
204
+ `Error: Platform retire failed (${response.status}): ${body}`,
205
+ );
206
+ process.exit(1);
207
+ }
208
+
209
+ console.log("\u2705 Platform-hosted instance retired.");
210
+ }
211
+
175
212
  function parseSource(): string | undefined {
176
213
  const args = process.argv.slice(4);
177
214
  for (let i = 0; i < args.length; i++) {
@@ -213,6 +250,9 @@ function teeConsoleToLogFile(fd: number | "ignore"): void {
213
250
  }
214
251
 
215
252
  export async function retire(): Promise<void> {
253
+ if (process.env.VELLUM_DESKTOP_APP) {
254
+ resetLogFile("retire.log");
255
+ }
216
256
  const logFd = process.env.VELLUM_DESKTOP_APP
217
257
  ? openLogFile("retire.log")
218
258
  : "ignore";
@@ -281,6 +321,8 @@ async function retireInner(): Promise<void> {
281
321
  await retireLocal(name, entry);
282
322
  } else if (cloud === "custom") {
283
323
  await retireCustom(entry);
324
+ } else if (cloud === "vellum") {
325
+ await retireVellum(entry.assistantId);
284
326
  } else {
285
327
  console.error(`Error: Unknown cloud type '${cloud}'.`);
286
328
  process.exit(1);
package/src/lib/docker.ts CHANGED
@@ -31,7 +31,7 @@ interface DockerRoot {
31
31
  * Dockerfiles live under `meta/`, but when installed as an npm package they
32
32
  * are at the package root.
33
33
  */
34
- function findDockerRoot(): DockerRoot {
34
+ function findDockerRoot(developmentMode: boolean = false): DockerRoot {
35
35
  // Source tree: cli/src/lib/ -> repo root (Dockerfiles in meta/)
36
36
  const sourceTreeRoot = join(import.meta.dir, "..", "..", "..");
37
37
  if (existsSync(join(sourceTreeRoot, "meta", "Dockerfile"))) {
@@ -55,6 +55,21 @@ function findDockerRoot(): DockerRoot {
55
55
  dir = parent;
56
56
  }
57
57
 
58
+ // In development mode, walk up from the executable path to find the repo
59
+ // root. This handles the macOS app bundle case where the binary lives inside
60
+ // the repo at e.g. clients/macos/dist/Vellum.app/Contents/MacOS/.
61
+ if (developmentMode) {
62
+ let execDir = dirname(process.execPath);
63
+ while (true) {
64
+ if (existsSync(join(execDir, "meta", "Dockerfile.development"))) {
65
+ return { root: execDir, dockerfileDir: "meta" };
66
+ }
67
+ const parent = dirname(execDir);
68
+ if (parent === execDir) break;
69
+ execDir = parent;
70
+ }
71
+ }
72
+
58
73
  // macOS app bundle: Contents/MacOS/vellum-cli -> Contents/Resources/Dockerfile
59
74
  const appResourcesDir = join(dirname(process.execPath), "..", "Resources");
60
75
  if (existsSync(join(appResourcesDir, "Dockerfile"))) {
@@ -168,7 +183,7 @@ export async function hatchDocker(
168
183
  let repoRoot: string;
169
184
  let dockerfileDir: string;
170
185
  try {
171
- ({ root: repoRoot, dockerfileDir } = findDockerRoot());
186
+ ({ root: repoRoot, dockerfileDir } = findDockerRoot(watch));
172
187
  } catch (err) {
173
188
  const message = err instanceof Error ? err.message : String(err);
174
189
  const logFd = openLogFile("hatch.log");