@vera-ai/cli 0.1.1

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 (2) hide show
  1. package/bin/vera.js +327 -0
  2. package/package.json +28 -0
package/bin/vera.js ADDED
@@ -0,0 +1,327 @@
1
+ #!/usr/bin/env node
2
+
3
+ "use strict";
4
+
5
+ const crypto = require("node:crypto");
6
+ const fs = require("node:fs");
7
+ const fsp = require("node:fs/promises");
8
+ const http = require("node:http");
9
+ const https = require("node:https");
10
+ const os = require("node:os");
11
+ const path = require("node:path");
12
+ const { spawnSync } = require("node:child_process");
13
+
14
+ const { version: packageVersion } = require("../package.json");
15
+
16
+ const DEFAULT_REPO = "lemon07r/Vera";
17
+ const MAX_REDIRECTS = 5;
18
+
19
+ function parseArgs(argv) {
20
+ if (argv.length === 0) {
21
+ return { command: "help", rest: [] };
22
+ }
23
+
24
+ return { command: argv[0], rest: argv.slice(1) };
25
+ }
26
+
27
+ function resolveTarget(platform, arch) {
28
+ const key = `${platform}:${arch}`;
29
+ const targets = {
30
+ "linux:x64": "x86_64-unknown-linux-gnu",
31
+ "linux:arm64": "aarch64-unknown-linux-gnu",
32
+ "darwin:x64": "x86_64-apple-darwin",
33
+ "darwin:arm64": "aarch64-apple-darwin",
34
+ "win32:x64": "x86_64-pc-windows-msvc",
35
+ };
36
+
37
+ const target = targets[key];
38
+ if (!target) {
39
+ throw new Error(`unsupported platform: ${platform}/${arch}`);
40
+ }
41
+
42
+ return target;
43
+ }
44
+
45
+ function defaultReleaseBaseUrl() {
46
+ return process.env.VERA_RELEASE_BASE_URL || `https://github.com/${DEFAULT_REPO}`;
47
+ }
48
+
49
+ function manifestUrl(version) {
50
+ if (process.env.VERA_MANIFEST_URL) {
51
+ return process.env.VERA_MANIFEST_URL;
52
+ }
53
+
54
+ return `${defaultReleaseBaseUrl()}/releases/download/v${version}/release-manifest.json`;
55
+ }
56
+
57
+ function latestManifestUrl() {
58
+ return `${defaultReleaseBaseUrl()}/releases/latest/download/release-manifest.json`;
59
+ }
60
+
61
+ function defaultVeraHome() {
62
+ return process.env.VERA_HOME || path.join(os.homedir(), ".vera");
63
+ }
64
+
65
+ function preferredBinDirs() {
66
+ const home = os.homedir();
67
+ if (process.env.VERA_USER_BIN_DIR) {
68
+ return [process.env.VERA_USER_BIN_DIR];
69
+ }
70
+
71
+ if (process.platform === "win32") {
72
+ return [
73
+ path.join(home, "AppData", "Roaming", "npm"),
74
+ path.join(home, "AppData", "Local", "Programs", "Vera", "bin"),
75
+ ];
76
+ }
77
+
78
+ return [
79
+ path.join(home, ".local", "bin"),
80
+ path.join(home, ".cargo", "bin"),
81
+ path.join(home, "bin"),
82
+ ];
83
+ }
84
+
85
+ function pathEntries() {
86
+ return (process.env.PATH || "")
87
+ .split(path.delimiter)
88
+ .filter(Boolean)
89
+ .map((entry) => path.resolve(entry));
90
+ }
91
+
92
+ function pickUserBinDir() {
93
+ const entries = new Set(pathEntries());
94
+ const candidates = preferredBinDirs().map((entry) => path.resolve(entry));
95
+ return candidates.find((entry) => entries.has(entry)) || candidates[0];
96
+ }
97
+
98
+ function binaryName() {
99
+ return process.platform === "win32" ? "vera.exe" : "vera";
100
+ }
101
+
102
+ function shimName() {
103
+ return process.platform === "win32" ? "vera.cmd" : "vera";
104
+ }
105
+
106
+ async function fetchText(url, redirects = 0) {
107
+ const client = url.startsWith("https://") ? https : http;
108
+ return new Promise((resolve, reject) => {
109
+ const request = client.get(url, (response) => {
110
+ const status = response.statusCode || 0;
111
+ if ([301, 302, 303, 307, 308].includes(status) && response.headers.location) {
112
+ if (redirects >= MAX_REDIRECTS) {
113
+ reject(new Error(`too many redirects fetching ${url}`));
114
+ return;
115
+ }
116
+
117
+ const nextUrl = new URL(response.headers.location, url).toString();
118
+ resolve(fetchText(nextUrl, redirects + 1));
119
+ return;
120
+ }
121
+
122
+ if (status < 200 || status >= 300) {
123
+ reject(new Error(`request failed for ${url}: ${status}`));
124
+ return;
125
+ }
126
+
127
+ let body = "";
128
+ response.setEncoding("utf8");
129
+ response.on("data", (chunk) => {
130
+ body += chunk;
131
+ });
132
+ response.on("end", () => resolve(body));
133
+ });
134
+
135
+ request.on("error", reject);
136
+ });
137
+ }
138
+
139
+ async function downloadFile(url, destination, redirects = 0) {
140
+ const client = url.startsWith("https://") ? https : http;
141
+ await fsp.mkdir(path.dirname(destination), { recursive: true });
142
+
143
+ return new Promise((resolve, reject) => {
144
+ const request = client.get(url, (response) => {
145
+ const status = response.statusCode || 0;
146
+ if ([301, 302, 303, 307, 308].includes(status) && response.headers.location) {
147
+ if (redirects >= MAX_REDIRECTS) {
148
+ reject(new Error(`too many redirects downloading ${url}`));
149
+ return;
150
+ }
151
+
152
+ const nextUrl = new URL(response.headers.location, url).toString();
153
+ response.resume();
154
+ resolve(downloadFile(nextUrl, destination, redirects + 1));
155
+ return;
156
+ }
157
+
158
+ if (status < 200 || status >= 300) {
159
+ reject(new Error(`download failed for ${url}: ${status}`));
160
+ return;
161
+ }
162
+
163
+ const file = fs.createWriteStream(destination);
164
+ response.pipe(file);
165
+ file.on("finish", () => file.close(resolve));
166
+ file.on("error", reject);
167
+ });
168
+
169
+ request.on("error", reject);
170
+ });
171
+ }
172
+
173
+ async function sha256(filePath) {
174
+ const hash = crypto.createHash("sha256");
175
+ const stream = fs.createReadStream(filePath);
176
+
177
+ return new Promise((resolve, reject) => {
178
+ stream.on("data", (chunk) => hash.update(chunk));
179
+ stream.on("end", () => resolve(hash.digest("hex")));
180
+ stream.on("error", reject);
181
+ });
182
+ }
183
+
184
+ function runChecked(command, args) {
185
+ const result = spawnSync(command, args, { stdio: "inherit" });
186
+ if (result.error) {
187
+ throw result.error;
188
+ }
189
+
190
+ if (result.status !== 0) {
191
+ throw new Error(`${command} exited with status ${result.status}`);
192
+ }
193
+ }
194
+
195
+ async function extractArchive(archivePath, destination) {
196
+ await fsp.mkdir(destination, { recursive: true });
197
+ if (archivePath.endsWith(".zip")) {
198
+ runChecked("powershell", [
199
+ "-NoProfile",
200
+ "-Command",
201
+ "Expand-Archive",
202
+ "-LiteralPath",
203
+ archivePath,
204
+ "-DestinationPath",
205
+ destination,
206
+ "-Force",
207
+ ]);
208
+ return;
209
+ }
210
+
211
+ runChecked("tar", ["-xzf", archivePath, "-C", destination]);
212
+ }
213
+
214
+ async function createShim(binaryPath) {
215
+ const binDir = pickUserBinDir();
216
+ await fsp.mkdir(binDir, { recursive: true });
217
+ const shimPath = path.join(binDir, shimName());
218
+
219
+ if (process.platform === "win32") {
220
+ const contents = `@echo off\r\n"${binaryPath}" %*\r\n`;
221
+ await fsp.writeFile(shimPath, contents, "utf8");
222
+ } else {
223
+ const contents = `#!/bin/sh\nexec "${binaryPath}" "$@"\n`;
224
+ await fsp.writeFile(shimPath, contents, "utf8");
225
+ await fsp.chmod(shimPath, 0o755);
226
+ }
227
+
228
+ return shimPath;
229
+ }
230
+
231
+ function isOnPath(dirPath) {
232
+ return pathEntries().includes(path.resolve(dirPath));
233
+ }
234
+
235
+ async function loadManifest() {
236
+ const primaryUrl = manifestUrl(packageVersion);
237
+ try {
238
+ const manifestText = await fetchText(primaryUrl);
239
+ return JSON.parse(manifestText);
240
+ } catch (error) {
241
+ if (process.env.VERA_MANIFEST_URL) {
242
+ throw error;
243
+ }
244
+
245
+ const fallbackText = await fetchText(latestManifestUrl());
246
+ return JSON.parse(fallbackText);
247
+ }
248
+ }
249
+
250
+ async function ensureBinaryInstalled() {
251
+ const manifest = await loadManifest();
252
+ const target = resolveTarget(process.platform, process.arch);
253
+ const asset = manifest.assets && manifest.assets[target];
254
+ if (!asset) {
255
+ throw new Error(`no release asset for target ${target}`);
256
+ }
257
+
258
+ const version = manifest.version;
259
+ const installDir = path.join(defaultVeraHome(), "bin", version, target);
260
+ const binaryPath = path.join(installDir, binaryName());
261
+ if (fs.existsSync(binaryPath)) {
262
+ await createShim(binaryPath);
263
+ return { binaryPath, version };
264
+ }
265
+
266
+ const tempRoot = await fsp.mkdtemp(path.join(os.tmpdir(), "vera-install-"));
267
+ try {
268
+ const archivePath = path.join(tempRoot, asset.archive);
269
+ const extractDir = path.join(tempRoot, "extract");
270
+
271
+ console.error(`Downloading Vera ${version} for ${target}...`);
272
+ await downloadFile(asset.download_url, archivePath);
273
+
274
+ const actualSha = await sha256(archivePath);
275
+ if (actualSha !== asset.sha256) {
276
+ throw new Error(`checksum mismatch for ${asset.archive}`);
277
+ }
278
+
279
+ await extractArchive(archivePath, extractDir);
280
+ const extractedBinary = path.join(extractDir, `vera-${target}`, binaryName());
281
+ await fsp.mkdir(installDir, { recursive: true });
282
+ await fsp.copyFile(extractedBinary, binaryPath);
283
+ if (process.platform !== "win32") {
284
+ await fsp.chmod(binaryPath, 0o755);
285
+ }
286
+
287
+ const shimPath = await createShim(binaryPath);
288
+ if (!isOnPath(path.dirname(shimPath))) {
289
+ console.error(`Added Vera to ${path.dirname(shimPath)}. Add that directory to PATH to run \`vera\` directly.`);
290
+ }
291
+
292
+ return { binaryPath, version };
293
+ } finally {
294
+ await fsp.rm(tempRoot, { recursive: true, force: true });
295
+ }
296
+ }
297
+
298
+ function runBinary(binaryPath, args) {
299
+ const result = spawnSync(binaryPath, args, { stdio: "inherit" });
300
+ if (result.error) {
301
+ throw result.error;
302
+ }
303
+ process.exit(result.status ?? 0);
304
+ }
305
+
306
+ async function main() {
307
+ const { command, rest } = parseArgs(process.argv.slice(2));
308
+ const { binaryPath, version } = await ensureBinaryInstalled();
309
+
310
+ if (command === "install") {
311
+ console.error(`Vera ${version} installed.`);
312
+ runBinary(binaryPath, ["agent", "install", ...rest]);
313
+ return;
314
+ }
315
+
316
+ if (command === "help") {
317
+ runBinary(binaryPath, ["--help"]);
318
+ return;
319
+ }
320
+
321
+ runBinary(binaryPath, [command, ...rest]);
322
+ }
323
+
324
+ main().catch((error) => {
325
+ console.error(error.message);
326
+ process.exit(1);
327
+ });
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@vera-ai/cli",
3
+ "version": "0.1.1",
4
+ "description": "Bootstrap installer and wrapper for the Vera CLI",
5
+ "license": "MIT",
6
+ "homepage": "https://github.com/lemon07r/Vera#readme",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/lemon07r/Vera.git",
10
+ "directory": "packages/npm-cli"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/lemon07r/Vera/issues"
14
+ },
15
+ "bin": "./bin/vera.js",
16
+ "files": [
17
+ "bin/vera.js"
18
+ ],
19
+ "keywords": [
20
+ "vera",
21
+ "cli",
22
+ "code-search",
23
+ "agents"
24
+ ],
25
+ "engines": {
26
+ "node": ">=18"
27
+ }
28
+ }