@vera-ai/cli 2.0.1 → 2.1.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.
Files changed (3) hide show
  1. package/README.md +2 -2
  2. package/bin/vera.js +82 -27
  3. package/package.json +3 -4
package/README.md CHANGED
@@ -2,14 +2,14 @@
2
2
 
3
3
  Code search for AI agents. Vera indexes your codebase using tree-sitter parsing and hybrid search (BM25 + vector similarity + optional cross-encoder reranking), then returns ranked code snippets as Markdown codeblocks by default, or JSON with `--json`.
4
4
 
5
- This package downloads and wraps the native Vera binary for your platform. It downloads the exact package version, checks the release archive size and SHA-256, and stores a completed binary cache. Later runs use that cache without network access. Older caches are downloaded and verified once before they can be reused offline. On Linux without glibc (such as Alpine or NixOS), the wrapper selects the musl binary. Other native targets use their platform system libraries; optional ONNX backends have additional runtime requirements. Set `VERA_TARGET` to override target detection (e.g., `VERA_TARGET=x86_64-unknown-linux-musl npm install -g @vera-ai/cli`).
5
+ This package downloads and wraps the native Vera binary for your platform. It downloads the exact package version, checks the release archive size and SHA-256, and stores a completed binary cache. Later runs use that cache without network access. Older caches are downloaded and verified once before they can be reused offline. On Linux without glibc (such as Alpine or NixOS), the wrapper selects the musl binary. Other native targets use their platform system libraries; optional ONNX backends have additional runtime requirements. Set `VERA_TARGET` to override target detection (e.g., `VERA_TARGET=x86_64-unknown-linux-musl npx -y @vera-ai/cli install`).
6
6
 
7
7
  The default local embedding model, `minishlab/potion-code-16M-v2`, runs on CPU and works offline after its first download. API mode is also available; Qwen/OpenRouter is recommended among API presets (paid usage). The [benchmark report](https://github.com/VeraTools/Vera/blob/master/docs/benchmarks.md) separates the full 1,251-task suite from smaller screening subsets, and the [agent benchmark](https://github.com/VeraTools/Vera/blob/master/docs/benchmarks-history.md#agent-level-benchmark) records context measurements and their sample limits.
8
8
 
9
9
  ## Install
10
10
 
11
11
  ```bash
12
- npm install -g @vera-ai/cli
12
+ npx -y @vera-ai/cli install
13
13
  ```
14
14
 
15
15
  ## Quick Start
package/bin/vera.js CHANGED
@@ -84,7 +84,19 @@ function manifestUrl(version) {
84
84
  }
85
85
 
86
86
  function defaultVeraHome() {
87
- return process.env.VERA_HOME || path.join(os.homedir(), ".vera");
87
+ if (process.env.VERA_HOME && process.env.VERA_HOME.trim()) return path.resolve(process.env.VERA_HOME);
88
+ const home = os.homedir();
89
+ const legacy = path.join(home, ".vera");
90
+ try {
91
+ if (fs.readdirSync(legacy).some((name) => !name.startsWith(".") && name !== "update-check.json")) return legacy;
92
+ } catch (error) {
93
+ if (error.code !== "ENOENT") throw error;
94
+ }
95
+ const data = process.platform === "win32" ? process.env.APPDATA : process.platform === "darwin"
96
+ ? path.join(home, "Library", "Application Support")
97
+ : process.env.XDG_DATA_HOME && path.isAbsolute(process.env.XDG_DATA_HOME)
98
+ ? process.env.XDG_DATA_HOME : path.join(home, ".local", "share");
99
+ return data ? path.join(data, "vera") : legacy;
88
100
  }
89
101
 
90
102
  function installMetadataPath() {
@@ -367,21 +379,66 @@ async function extractArchive(archivePath, destination, target) {
367
379
  }
368
380
  }
369
381
 
382
+ function shimContents(binaryPath, platform) {
383
+ if (platform === "win32") {
384
+ return `@echo off\r\nsetlocal DisableDelayedExpansion\r\n"${binaryPath.replace(/%/g, "%%")}" %*\r\n`;
385
+ }
386
+ return `#!/bin/sh\nexec '${binaryPath.replace(/'/g, "'\"'\"'")}' "$@"\n`;
387
+ }
388
+
389
+ function shimTarget(text) {
390
+ if (text.startsWith("#!/bin/sh\nexec ") && text.endsWith(' "$@"\n')) {
391
+ const word = text.slice(15, -6);
392
+ if (word.startsWith("'") && word.endsWith("'")) {
393
+ const parts = word.slice(1, -1).split("'\"'\"'");
394
+ if (parts.every((part) => !part.includes("'"))) return parts.join("'") || null;
395
+ } else if (word.startsWith('"') && word.endsWith('"')) {
396
+ const target = word.slice(1, -1);
397
+ return target && !/[$`\\"]/.test(target) ? target : null;
398
+ } else if (/^[A-Za-z0-9@%+=:,./_-]+$/.test(word)) return word;
399
+ }
400
+ for (const setlocal of [true, false]) {
401
+ const head = '@echo off\r\n' + (setlocal ? 'setlocal DisableDelayedExpansion\r\n' : '') + '"';
402
+ if (text.startsWith(head) && text.endsWith('" %*\r\n')) {
403
+ const target = text.slice(head.length, -6);
404
+ // cmd expands a lone `%`; only the escaped form may carry a literal one.
405
+ if (target.includes('"') || (setlocal ? target.replace(/%%/g, "") : target).includes("%")) return null;
406
+ return (setlocal ? target.replace(/%%/g, "%") : target) || null;
407
+ }
408
+ }
409
+ return null;
410
+ }
411
+
370
412
  async function createShim(binaryPath) {
371
413
  const binDir = pickUserBinDir();
372
414
  await fsp.mkdir(binDir, { recursive: true });
373
415
  const shimPath = path.join(binDir, shimName());
374
416
  if (path.join(await fsp.realpath(binDir), shimName()) === await fsp.realpath(binaryPath)) return shimPath;
417
+ const contents = shimContents(binaryPath, process.platform);
418
+ try {
419
+ const stat = await fsp.lstat(shimPath);
420
+ const current = stat.isFile() ? await fsp.readFile(shimPath, "utf8").catch(() => "") : "";
421
+ const target = shimTarget(current);
422
+ const normalized = target && path.isAbsolute(target) ? path.resolve(target) : null;
423
+ // Releases live at <home>/bin/<version>/<target>/<binary>. The legacy home
424
+ // counts too: an older uninstall could leave its shim behind.
425
+ const owned = normalized && [defaultVeraHome(), path.join(os.homedir(), ".vera")].some((home) => {
426
+ const relative = path.relative(path.resolve(home, "bin"), normalized);
427
+ const parts = relative.split(path.sep);
428
+ return !path.isAbsolute(relative) && parts.length === 3 && parts[0] !== ".." && parts[2] === binaryName();
429
+ });
430
+ if (!owned) {
431
+ console.error(`Left ${shimPath} in place because Vera did not create it. Run ${binaryPath} directly, or remove that file and install again.`);
432
+ return null;
433
+ }
434
+ if (current === contents) return shimPath;
435
+ } catch (error) {
436
+ if (error.code !== "ENOENT") throw error;
437
+ }
375
438
  const staging = await fsp.mkdtemp(path.join(binDir, ".vera-shim-"));
376
439
  const stagedShim = path.join(staging, shimName());
377
440
  try {
378
- if (process.platform === "win32") {
379
- const contents = `@echo off\r\nsetlocal DisableDelayedExpansion\r\n"${binaryPath.replace(/%/g, "%%")}" %*\r\n`;
380
- await fsp.writeFile(stagedShim, contents, "utf8");
381
- } else {
382
- const quoted = "'" + binaryPath.replace(/'/g, "'\"'\"'") + "'";
383
- await fsp.writeFile(stagedShim, `#!/bin/sh\nexec ${quoted} "$@"\n`, { mode: 0o755 });
384
- }
441
+ await fsp.writeFile(stagedShim, contents, { mode: 0o755 });
385
442
  await fsp.rename(stagedShim, shimPath);
386
443
  } finally {
387
444
  await fsp.rm(staging, { recursive: true, force: true });
@@ -426,20 +483,23 @@ async function cachedBinary(target) {
426
483
  return null;
427
484
  }
428
485
 
429
- async function finishInstall(binaryPath, version, target, announce = false) {
430
- const shimPath = await createShim(binaryPath);
431
- if (announce && !isOnPath(path.dirname(shimPath))) {
432
- console.error(`Added Vera to ${path.dirname(shimPath)}. Add that directory to PATH to run \`vera\` directly.`);
486
+ async function finishInstall(binaryPath, version, target, install, downloaded = false) {
487
+ const writeLauncher = install || !fs.lstatSync(path.join(pickUserBinDir(), shimName()), { throwIfNoEntry: false });
488
+ if (writeLauncher) {
489
+ const shimPath = await createShim(binaryPath);
490
+ if (downloaded && shimPath && !isOnPath(path.dirname(shimPath))) {
491
+ console.error(`Added Vera to ${path.dirname(shimPath)}. Add that directory to PATH to run \`vera\` directly.`);
492
+ }
433
493
  }
434
- await writeInstallMetadata({ installMethod: currentInstallMethod(), version, binaryPath, target });
494
+ if (writeLauncher || downloaded) await writeInstallMetadata({ installMethod: currentInstallMethod(), version, binaryPath, target });
435
495
  return { binaryPath, version };
436
496
  }
437
497
 
438
- async function ensureBinaryInstalled() {
498
+ async function ensureBinaryInstalled(install = false) {
439
499
  const target = resolveTarget(process.platform, process.arch);
440
500
  if (!/^[A-Za-z0-9_-]+$/.test(target)) throw new Error("invalid release target");
441
501
  const cached = await cachedBinary(target);
442
- if (cached) return finishInstall(cached.binaryPath, cached.version, target);
502
+ if (cached) return finishInstall(cached.binaryPath, cached.version, target, install);
443
503
  const manifest = await loadManifest();
444
504
  const asset = manifest.assets && manifest.assets[target];
445
505
  if (!asset) throw new Error(`no release asset for target ${target}`);
@@ -470,7 +530,7 @@ async function ensureBinaryInstalled() {
470
530
  } finally {
471
531
  await fsp.rm(tempRoot, { recursive: true, force: true });
472
532
  }
473
- return finishInstall(binaryPath, version, target, true);
533
+ return finishInstall(binaryPath, version, target, install, true);
474
534
  }
475
535
 
476
536
  async function runBinary(binaryPath, args) {
@@ -494,19 +554,14 @@ async function runBinary(binaryPath, args) {
494
554
  async function main() {
495
555
  const { command, rest } = parseArgs(process.argv.slice(2));
496
556
 
497
- if (command === "_record-install-method") {
498
- await writeInstallMetadata({
499
- installMethod: "npm",
500
- version: packageVersion,
501
- binaryPath: null,
502
- });
503
- return;
504
- }
505
-
506
- const { binaryPath, version } = await ensureBinaryInstalled();
557
+ const { binaryPath, version } = await ensureBinaryInstalled(command === "install");
507
558
 
508
559
  if (command === "install") {
509
560
  console.error(`Vera ${version} installed.`);
561
+ if (rest.length === 0 && (!process.stdin.isTTY || !process.stderr.isTTY)) {
562
+ console.error("Run `vera agent install` in a terminal or `vera agent install --client all --scope global` to install agent skills.");
563
+ return;
564
+ }
510
565
  await runBinary(binaryPath, ["agent", "install", ...rest]);
511
566
  return;
512
567
  }
@@ -526,4 +581,4 @@ if (require.main === module) {
526
581
  });
527
582
  }
528
583
 
529
- module.exports = { detectMusl, fetchText, downloadFile, extractArchive, ensureBinaryInstalled, createShim, runBinary };
584
+ module.exports = { defaultVeraHome, shimContents, shimTarget, detectMusl, fetchText, downloadFile, extractArchive, ensureBinaryInstalled, createShim, runBinary };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vera-ai/cli",
3
- "version": "2.0.1",
3
+ "version": "2.1.0",
4
4
  "description": "Bootstrap installer and wrapper for the Vera CLI",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/VeraTools/Vera#readme",
@@ -12,9 +12,8 @@
12
12
  "bugs": {
13
13
  "url": "https://github.com/VeraTools/Vera/issues"
14
14
  },
15
- "bin": "./bin/vera.js",
16
- "scripts": {
17
- "postinstall": "node ./bin/vera.js _record-install-method"
15
+ "bin": {
16
+ "vera-ai": "./bin/vera.js"
18
17
  },
19
18
  "files": [
20
19
  "bin/vera.js",