@hanzo/dev 2.1.1 → 3.0.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/bin/dev.js ADDED
@@ -0,0 +1,413 @@
1
+ #!/usr/bin/env node
2
+ // Unified entry point for the Hanzo Dev CLI.
3
+
4
+ import path from "path";
5
+ import { fileURLToPath } from "url";
6
+ import { platform as nodePlatform, arch as nodeArch } from "os";
7
+ import { execSync } from "child_process";
8
+ import { get as httpsGet } from "https";
9
+ import { runPostinstall } from "../postinstall.js";
10
+
11
+ // __dirname equivalent in ESM
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = path.dirname(__filename);
14
+
15
+ const { platform, arch } = process;
16
+
17
+ function isWSL() {
18
+ if (platform !== "linux") return false;
19
+ try {
20
+ const txt = readFileSync("/proc/version", "utf8").toLowerCase();
21
+ return txt.includes("microsoft");
22
+ } catch {
23
+ return false;
24
+ }
25
+ }
26
+
27
+ let targetTriple = null;
28
+ switch (platform) {
29
+ case "linux":
30
+ case "android":
31
+ switch (arch) {
32
+ case "x64":
33
+ targetTriple = "x86_64-unknown-linux-musl";
34
+ break;
35
+ case "arm64":
36
+ targetTriple = "aarch64-unknown-linux-musl";
37
+ break;
38
+ default:
39
+ break;
40
+ }
41
+ break;
42
+ case "darwin":
43
+ switch (arch) {
44
+ case "x64":
45
+ targetTriple = "x86_64-apple-darwin";
46
+ break;
47
+ case "arm64":
48
+ targetTriple = "aarch64-apple-darwin";
49
+ break;
50
+ default:
51
+ break;
52
+ }
53
+ break;
54
+ case "win32":
55
+ switch (arch) {
56
+ case "x64":
57
+ targetTriple = "x86_64-pc-windows-msvc.exe";
58
+ break;
59
+ case "arm64":
60
+ // We do not build this today, fall through...
61
+ default:
62
+ break;
63
+ }
64
+ break;
65
+ default:
66
+ break;
67
+ }
68
+
69
+ if (!targetTriple) {
70
+ throw new Error(`Unsupported platform: ${platform} (${arch})`);
71
+ }
72
+
73
+ // Binary names for Hanzo dev
74
+ let binaryPath = path.join(__dirname, "..", "bin", `dev-${targetTriple}`);
75
+ let legacyBinaryPath = path.join(__dirname, "..", "bin", `code-${targetTriple}`);
76
+
77
+ import { existsSync, chmodSync, statSync, openSync, readSync, closeSync, mkdirSync, copyFileSync, readFileSync, unlinkSync, createWriteStream } from "fs";
78
+
79
+ const validateBinary = (p) => {
80
+ try {
81
+ const st = statSync(p);
82
+ if (!st.isFile() || st.size === 0) {
83
+ return { ok: false, reason: "empty or not a regular file" };
84
+ }
85
+ const fd = openSync(p, "r");
86
+ try {
87
+ const buf = Buffer.alloc(4);
88
+ const n = readSync(fd, buf, 0, 4, 0);
89
+ if (n < 2) return { ok: false, reason: "too short" };
90
+ if (platform === "win32") {
91
+ if (!(buf[0] === 0x4d && buf[1] === 0x5a)) return { ok: false, reason: "invalid PE header (missing MZ)" };
92
+ } else if (platform === "linux" || platform === "android") {
93
+ if (!(buf[0] === 0x7f && buf[1] === 0x45 && buf[2] === 0x4c && buf[3] === 0x46)) return { ok: false, reason: "invalid ELF header" };
94
+ } else if (platform === "darwin") {
95
+ const isMachO = (buf[0] === 0xcf && buf[1] === 0xfa && buf[2] === 0xed && buf[3] === 0xfe) ||
96
+ (buf[0] === 0xca && buf[1] === 0xfe && buf[2] === 0xba && buf[3] === 0xbe);
97
+ if (!isMachO) return { ok: false, reason: "invalid Mach-O header" };
98
+ }
99
+ } finally {
100
+ closeSync(fd);
101
+ }
102
+ return { ok: true };
103
+ } catch (e) {
104
+ return { ok: false, reason: e.message };
105
+ }
106
+ };
107
+
108
+ const getCacheDir = (version) => {
109
+ const plt = nodePlatform();
110
+ const home = process.env.HOME || process.env.USERPROFILE || "";
111
+ let base = "";
112
+ if (plt === "win32") {
113
+ base = process.env.LOCALAPPDATA || path.join(home, "AppData", "Local");
114
+ } else if (plt === "darwin") {
115
+ base = path.join(home, "Library", "Caches");
116
+ } else {
117
+ base = process.env.XDG_CACHE_HOME || path.join(home, ".cache");
118
+ }
119
+ const dir = path.join(base, "hanzo", "dev", version);
120
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
121
+ return dir;
122
+ };
123
+
124
+ const getCachedBinaryPath = (version) => {
125
+ const cacheDir = getCacheDir(version);
126
+ return path.join(cacheDir, `dev-${targetTriple}`);
127
+ };
128
+
129
+ let lastBootstrapError = null;
130
+
131
+ const httpsDownload = (url, dest) => new Promise((resolve, reject) => {
132
+ const req = httpsGet(url, (res) => {
133
+ const status = res.statusCode || 0;
134
+ if (status >= 300 && status < 400 && res.headers.location) {
135
+ return resolve(httpsDownload(res.headers.location, dest));
136
+ }
137
+ if (status !== 200) {
138
+ return reject(new Error(`HTTP ${status}`));
139
+ }
140
+ const out = createWriteStream(dest);
141
+ res.pipe(out);
142
+ out.on("finish", () => out.close(resolve));
143
+ out.on("error", (e) => {
144
+ try { unlinkSync(dest); } catch {}
145
+ reject(e);
146
+ });
147
+ });
148
+ req.on("error", (e) => {
149
+ try { unlinkSync(dest); } catch {}
150
+ reject(e);
151
+ });
152
+ req.setTimeout(120000, () => {
153
+ req.destroy(new Error("download timed out"));
154
+ });
155
+ });
156
+
157
+ const tryBootstrapBinary = async () => {
158
+ try {
159
+ const pkg = JSON.parse(readFileSync(path.join(__dirname, "..", "package.json"), "utf8"));
160
+ const version = pkg.version;
161
+
162
+ const binDir = path.join(__dirname, "..", "bin");
163
+ if (!existsSync(binDir)) mkdirSync(binDir, { recursive: true });
164
+
165
+ // Fast path: user cache
166
+ const cachePath = getCachedBinaryPath(version);
167
+ if (existsSync(cachePath)) {
168
+ const v = validateBinary(cachePath);
169
+ if (v.ok) {
170
+ if (platform !== "win32") {
171
+ copyFileSync(cachePath, binaryPath);
172
+ try { chmodSync(binaryPath, 0o755); } catch {}
173
+ }
174
+ return true;
175
+ }
176
+ }
177
+
178
+ // Try platform package (if present)
179
+ try {
180
+ const req = (await import("module")).createRequire(import.meta.url);
181
+ const name = (() => {
182
+ if (platform === "win32") return "@hanzo/dev-win32-x64";
183
+ const plt = nodePlatform();
184
+ const cpu = nodeArch();
185
+ if (plt === "darwin" && cpu === "arm64") return "@hanzo/dev-darwin-arm64";
186
+ if (plt === "darwin" && cpu === "x64") return "@hanzo/dev-darwin-x64";
187
+ if (plt === "linux" && cpu === "x64") return "@hanzo/dev-linux-x64-musl";
188
+ if (plt === "linux" && cpu === "arm64") return "@hanzo/dev-linux-arm64-musl";
189
+ return null;
190
+ })();
191
+ if (name) {
192
+ try {
193
+ const pkgJson = req.resolve(`${name}/package.json`);
194
+ const pkgDir = path.dirname(pkgJson);
195
+ const src = path.join(pkgDir, "bin", `dev-${targetTriple}`);
196
+ if (existsSync(src)) {
197
+ copyFileSync(src, cachePath);
198
+ if (platform !== "win32") {
199
+ copyFileSync(cachePath, binaryPath);
200
+ try { chmodSync(binaryPath, 0o755); } catch {}
201
+ }
202
+ return true;
203
+ }
204
+ } catch { /* ignore and fall back */ }
205
+ }
206
+ } catch { /* ignore */ }
207
+
208
+ // Download from GitHub release
209
+ const isWin = platform === "win32";
210
+ // Use 'code-*' binary names since that's what the release produces
211
+ const binaryName = `code-${targetTriple}`;
212
+ const archiveName = isWin
213
+ ? `${binaryName}.zip`
214
+ : (() => { try { execSync("zstd --version", { stdio: "ignore", shell: true }); return `${binaryName}.zst`; } catch { return `${binaryName}.tar.gz`; } })();
215
+ const url = `https://github.com/hanzoai/dev/releases/download/v${version}/${archiveName}`;
216
+ const tmp = path.join(binDir, `.${archiveName}.part`);
217
+ return httpsDownload(url, tmp)
218
+ .then(() => {
219
+ if (isWin) {
220
+ try {
221
+ const sysRoot = process.env.SystemRoot || process.env.windir || 'C:\\Windows';
222
+ const psFull = path.join(sysRoot, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe');
223
+ const unzipDest = getCacheDir(version);
224
+ const psCmd = `Expand-Archive -Path '${tmp}' -DestinationPath '${unzipDest}' -Force`;
225
+ let ok = false;
226
+ try { execSync(`"${psFull}" -NoProfile -NonInteractive -Command "${psCmd}"`, { stdio: 'ignore' }); ok = true; } catch {}
227
+ if (!ok) { try { execSync(`powershell -NoProfile -NonInteractive -Command "${psCmd}"`, { stdio: 'ignore' }); ok = true; } catch {} }
228
+ if (!ok) { try { execSync(`pwsh -NoProfile -NonInteractive -Command "${psCmd}"`, { stdio: 'ignore' }); ok = true; } catch {} }
229
+ if (!ok) { execSync(`tar -xf "${tmp}" -C "${unzipDest}"`, { stdio: 'ignore', shell: true }); }
230
+ } catch (e) {
231
+ throw new Error(`failed to unzip: ${e.message}`);
232
+ } finally { try { unlinkSync(tmp); } catch {} }
233
+ } else {
234
+ if (archiveName.endsWith(".zst")) {
235
+ try { execSync(`zstd -d '${tmp}' -o '${binaryPath}'`, { stdio: 'ignore', shell: true }); }
236
+ catch (e) { try { unlinkSync(tmp); } catch {}; throw new Error(`failed to decompress zst: ${e.message}`); }
237
+ try { unlinkSync(tmp); } catch {}
238
+ } else {
239
+ try { execSync(`tar -xzf '${tmp}' -C '${binDir}'`, { stdio: 'ignore', shell: true }); }
240
+ catch (e) { try { unlinkSync(tmp); } catch {}; throw new Error(`failed to extract tar.gz: ${e.message}`); }
241
+ try { unlinkSync(tmp); } catch {}
242
+ }
243
+ }
244
+ if (platform !== "win32") {
245
+ try { copyFileSync(binaryPath, cachePath); } catch {}
246
+ }
247
+
248
+ const v = validateBinary(platform === "win32" ? cachePath : binaryPath);
249
+ if (!v.ok) throw new Error(`invalid binary (${v.reason})`);
250
+ if (platform !== "win32") try { chmodSync(binaryPath, 0o755); } catch {}
251
+ return true;
252
+ })
253
+ .catch((e) => { lastBootstrapError = e; return false; });
254
+ } catch {
255
+ return false;
256
+ }
257
+ };
258
+
259
+ // If missing, attempt to bootstrap into place
260
+ let binaryReady = existsSync(binaryPath) || existsSync(legacyBinaryPath);
261
+ if (!binaryReady) {
262
+ let runtimePostinstallError = null;
263
+ try {
264
+ await runPostinstall({ invokedByRuntime: true, skipGlobalAlias: true });
265
+ } catch (err) {
266
+ runtimePostinstallError = err;
267
+ }
268
+
269
+ binaryReady = existsSync(binaryPath) || existsSync(legacyBinaryPath);
270
+ if (!binaryReady) {
271
+ const ok = await tryBootstrapBinary();
272
+ if (!ok) {
273
+ if (runtimePostinstallError && !lastBootstrapError) {
274
+ lastBootstrapError = runtimePostinstallError;
275
+ }
276
+ if (existsSync(legacyBinaryPath) && !existsSync(binaryPath)) {
277
+ binaryPath = legacyBinaryPath;
278
+ }
279
+ }
280
+ }
281
+ }
282
+
283
+ // Prefer cached binary when available
284
+ try {
285
+ const pkg = JSON.parse(readFileSync(path.join(__dirname, "..", "package.json"), "utf8"));
286
+ const version = pkg.version;
287
+ const cached = getCachedBinaryPath(version);
288
+ const v = existsSync(cached) ? validateBinary(cached) : { ok: false };
289
+ if (v.ok) {
290
+ binaryPath = cached;
291
+ } else if (!existsSync(binaryPath) && existsSync(legacyBinaryPath)) {
292
+ binaryPath = legacyBinaryPath;
293
+ }
294
+ } catch {
295
+ // ignore
296
+ }
297
+
298
+ import { spawnSync } from "child_process";
299
+ if (existsSync(binaryPath)) {
300
+ try {
301
+ if (platform !== "win32") {
302
+ chmodSync(binaryPath, 0o755);
303
+ }
304
+ } catch (e) {
305
+ // Ignore permission errors
306
+ }
307
+ } else {
308
+ console.error(`Binary not found: ${binaryPath}`);
309
+ if (lastBootstrapError) {
310
+ const msg = (lastBootstrapError && (lastBootstrapError.message || String(lastBootstrapError))) || 'unknown bootstrap error';
311
+ console.error(`Bootstrap error: ${msg}`);
312
+ }
313
+ console.error(`Please try reinstalling the package:`);
314
+ console.error(` npm uninstall -g @hanzo/dev`);
315
+ console.error(` npm install -g @hanzo/dev`);
316
+ if (isWSL()) {
317
+ console.error("Detected WSL. Install inside WSL (Ubuntu) separately:");
318
+ console.error(" npx -y @hanzo/dev@latest (run inside WSL)");
319
+ console.error("If installed globally on Windows, those binaries are not usable from WSL.");
320
+ }
321
+ process.exit(1);
322
+ }
323
+
324
+ const validation = validateBinary(binaryPath);
325
+ if (!validation.ok) {
326
+ console.error(`The native binary at ${binaryPath} appears invalid: ${validation.reason}`);
327
+ console.error("This can happen if the download failed or was modified by antivirus/proxy.");
328
+ console.error("Please try reinstalling:");
329
+ console.error(" npm uninstall -g @hanzo/dev");
330
+ console.error(" npm install -g @hanzo/dev");
331
+ if (platform === "win32") {
332
+ console.error("If the issue persists, clear npm cache and disable antivirus temporarily:");
333
+ console.error(" npm cache clean --force");
334
+ }
335
+ if (isWSL()) {
336
+ console.error("Detected WSL. Ensure you install/run inside WSL, not Windows:");
337
+ console.error(" npx -y @hanzo/dev@latest (inside WSL)");
338
+ }
339
+ process.exit(1);
340
+ }
341
+
342
+ // If running under npx/npm, emit a concise notice
343
+ try {
344
+ const ua = process.env.npm_config_user_agent || "";
345
+ const isNpx = ua.includes("npx");
346
+ if (isNpx && process.stderr && process.stderr.isTTY) {
347
+ console.error(`@hanzo/dev: running bundled binary -> ${binaryPath}`);
348
+ }
349
+ } catch {}
350
+
351
+ const { spawn } = await import("child_process");
352
+
353
+ process.env.DEV_BINARY_PATH = binaryPath;
354
+
355
+ const child = spawn(binaryPath, process.argv.slice(2), {
356
+ stdio: "inherit",
357
+ env: { ...process.env, DEV_MANAGED_BY_NPM: "1", DEV_BINARY_PATH: binaryPath },
358
+ });
359
+
360
+ child.on("error", (err) => {
361
+ const code = err && err.code;
362
+ if (code === 'EACCES') {
363
+ console.error(`Permission denied: ${binaryPath}`);
364
+ console.error(`Try running: chmod +x "${binaryPath}"`);
365
+ console.error(`Or reinstall the package with: npm install -g @hanzo/dev`);
366
+ } else if (code === 'EFTYPE' || code === 'ENOEXEC') {
367
+ console.error(`Failed to execute native binary: ${binaryPath}`);
368
+ console.error("The file may be corrupt or of the wrong type. Reinstall usually fixes this:");
369
+ console.error(" npm uninstall -g @hanzo/dev && npm install -g @hanzo/dev");
370
+ if (platform === 'win32') {
371
+ console.error("On Windows, ensure the .exe downloaded correctly (proxy/AV can interfere).");
372
+ console.error("Try clearing cache: npm cache clean --force");
373
+ }
374
+ if (isWSL()) {
375
+ console.error("Detected WSL. Windows binaries cannot be executed from WSL.");
376
+ console.error("Install inside WSL and run there: npx -y @hanzo/dev@latest");
377
+ }
378
+ } else {
379
+ console.error(err);
380
+ }
381
+ process.exit(1);
382
+ });
383
+
384
+ const forwardSignal = (signal) => {
385
+ if (child.killed) {
386
+ return;
387
+ }
388
+ try {
389
+ child.kill(signal);
390
+ } catch {
391
+ /* ignore */
392
+ }
393
+ };
394
+
395
+ ["SIGINT", "SIGTERM", "SIGHUP"].forEach((sig) => {
396
+ process.on(sig, () => forwardSignal(sig));
397
+ });
398
+
399
+ const childResult = await new Promise((resolve) => {
400
+ child.on("exit", (code, signal) => {
401
+ if (signal) {
402
+ resolve({ type: "signal", signal });
403
+ } else {
404
+ resolve({ type: "code", exitCode: code ?? 1 });
405
+ }
406
+ });
407
+ });
408
+
409
+ if (childResult.type === "signal") {
410
+ process.kill(process.pid, childResult.signal);
411
+ } else {
412
+ process.exit(childResult.exitCode);
413
+ }
package/package.json CHANGED
@@ -1,73 +1,44 @@
1
1
  {
2
2
  "name": "@hanzo/dev",
3
- "version": "2.1.1",
4
- "description": "Hanzo Dev - Meta AI development CLI that manages and runs all LLMs and CLI tools",
5
- "main": "dist/index.js",
3
+ "version": "3.0.2",
4
+ "license": "Apache-2.0",
5
+ "description": "Hanzo AI coding assistant - intelligent CLI for developers",
6
6
  "bin": {
7
- "dev": "./dist/cli/dev.js"
7
+ "dev": "bin/dev.js",
8
+ "hanzo": "bin/dev.js"
8
9
  },
9
- "scripts": {
10
- "build": "esbuild src/cli/dev.ts --bundle --platform=node --target=node16 --outfile=dist/cli/dev.js --external:vscode --external:inquirer && chmod +x dist/cli/dev.js",
11
- "dev": "tsc --watch",
12
- "test": "vitest",
13
- "test:run": "vitest run",
14
- "test:ci": "vitest run --reporter=json --reporter=default",
15
- "test:watch": "vitest --watch",
16
- "test:ui": "vitest --ui",
17
- "test:coverage": "vitest --coverage",
18
- "test:swe-bench": "vitest run --testNamePattern=SWE-bench",
19
- "lint": "eslint src tests --ext .ts",
20
- "type-check": "tsc --noEmit",
21
- "prepublishOnly": "npm run build"
10
+ "type": "module",
11
+ "engines": {
12
+ "node": ">=16"
22
13
  },
23
- "keywords": [
24
- "ai",
25
- "llm",
26
- "cli",
27
- "claude",
28
- "openai",
29
- "gemini",
30
- "aider",
31
- "openhands",
32
- "development",
33
- "tools"
14
+ "files": [
15
+ "bin/dev.js",
16
+ "postinstall.js",
17
+ "scripts/preinstall.js",
18
+ "scripts/windows-cleanup.ps1",
19
+ "dist"
34
20
  ],
35
- "author": "Hanzo AI",
36
- "license": "MIT",
37
- "dependencies": {
38
- "@iarna/toml": "^2.2.5",
39
- "chalk": "^5.3.0",
40
- "commander": "^11.1.0",
41
- "glob": "^10.3.10",
42
- "inquirer": "^9.2.12",
43
- "ora": "^7.0.1",
44
- "uuid": "^9.0.1",
45
- "ws": "^8.16.0"
46
- },
47
- "devDependencies": {
48
- "@types/glob": "^8.1.0",
49
- "@types/inquirer": "^9.0.8",
50
- "@types/node": "^20.19.5",
51
- "@types/uuid": "^9.0.7",
52
- "@types/ws": "^8.5.10",
53
- "@typescript-eslint/eslint-plugin": "^6.19.0",
54
- "@typescript-eslint/parser": "^6.19.0",
55
- "@vitest/ui": "^3.2.4",
56
- "esbuild": "^0.25.6",
57
- "eslint": "^8.56.0",
58
- "typescript": "^5.3.3",
59
- "vitest": "^3.2.4"
60
- },
61
- "engines": {
62
- "node": ">=16.0.0"
21
+ "scripts": {
22
+ "preinstall": "node scripts/preinstall.js",
23
+ "postinstall": "node postinstall.js",
24
+ "prepublishOnly": "node -e \"const fs=require('fs'),path=require('path'); const repoGit=path.join(__dirname,'..','.git'); const inCi=process.env.GITHUB_ACTIONS==='true'||process.env.CI==='true'; if(fs.existsSync(repoGit) && !inCi){ console.error('Refusing to publish from dev-cli. Publishing happens via release.yml.'); process.exit(1);} else { console.log(inCi ? 'CI publish detected.' : 'Publishing staged package...'); }\""
63
25
  },
64
26
  "repository": {
65
27
  "type": "git",
66
- "url": "https://github.com/hanzoai/dev.git",
67
- "directory": "packages/dev"
28
+ "url": "git+https://github.com/hanzoai/dev.git"
29
+ },
30
+ "publishConfig": {
31
+ "access": "public"
32
+ },
33
+ "dependencies": {},
34
+ "devDependencies": {
35
+ "prettier": "^3.3.3"
68
36
  },
69
- "homepage": "https://hanzo.ai",
70
- "bugs": {
71
- "url": "https://github.com/hanzoai/dev/issues"
37
+ "optionalDependencies": {
38
+ "@hanzo/dev-darwin-arm64": "3.0.2",
39
+ "@hanzo/dev-darwin-x64": "3.0.2",
40
+ "@hanzo/dev-linux-x64-musl": "3.0.2",
41
+ "@hanzo/dev-linux-arm64-musl": "3.0.2",
42
+ "@hanzo/dev-win32-x64": "3.0.2"
72
43
  }
73
44
  }