zelari-code 1.0.2 → 1.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.
@@ -0,0 +1,287 @@
1
+ /**
2
+ * doctor.ts — `zelari-code doctor` diagnostic.
3
+ *
4
+ * Run after a fresh install (or when something looks wrong) to check:
5
+ * 1. Bin shim health (does `zelari-code.cmd` / `zelari-code` exist
6
+ * in the npm global prefix, and does it reference the right
7
+ * package install?).
8
+ * 2. Node availability (`node` on PATH, version >= engines.node).
9
+ * 3. CLI bundle presence (did `npm run build:cli` produce
10
+ * `dist/cli/main.bundled.js`?).
11
+ * 4. Runtime deps (ink / react / zod / ink-text-input resolvable
12
+ * from the package root).
13
+ * 5. @zelari/core dependency (only checked if it appears in the
14
+ * package's deps or devDeps — it's a build-time dep for the
15
+ * workspace, and a runtime dep only for the source/tsx fallback
16
+ * path in bin/zelari-code.js).
17
+ * 6. PATH includes the npm global prefix.
18
+ *
19
+ * Output is human-readable, with one line per check + a trailing
20
+ * summary. Non-zero exit code if any critical check fails so this
21
+ * can be used in CI / install scripts.
22
+ *
23
+ * The function never throws: it catches all per-check errors and
24
+ * reports them as "FAIL" with the error message. The final return
25
+ * value tells the caller whether the install is healthy.
26
+ *
27
+ * @see scripts/postinstall.mjs — the install-time shim check
28
+ * @see src/cli/updater.ts — the self-update mechanism
29
+ */
30
+ import { execSync } from "node:child_process";
31
+ import { existsSync, readFileSync, readlinkSync, statSync } from "node:fs";
32
+ import { createRequire } from "node:module";
33
+ import { fileURLToPath } from "node:url";
34
+ import path from "node:path";
35
+ const require = createRequire(import.meta.url);
36
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
37
+ // src/cli/utils/doctor.ts → <pkg>/src/cli/utils/doctor.ts
38
+ // We need <pkg> root. In dev (tsx) this is src/cli/utils/, in prod (bundled) it's dist/cli/utils/.
39
+ // In both cases we want the package root: that's `../../` from src/cli/utils, and `../../../` from dist/cli/utils/...
40
+ // Simplest: walk up until we find package.json with our name.
41
+ const packageRoot = findPackageRoot(__dirname);
42
+ function findPackageRoot(start) {
43
+ let dir = start;
44
+ // Walk at most 6 levels up (src/cli/utils -> src/cli -> src -> root, dist/cli/utils -> dist/cli -> dist -> root).
45
+ for (let i = 0; i < 6; i += 1) {
46
+ const candidate = path.join(dir, "package.json");
47
+ if (existsSync(candidate)) {
48
+ try {
49
+ const pkg = JSON.parse(readFileSync(candidate, "utf8"));
50
+ if (pkg.name === "zelari-code")
51
+ return dir;
52
+ }
53
+ catch {
54
+ // ignore
55
+ }
56
+ }
57
+ const parent = path.dirname(dir);
58
+ if (parent === dir)
59
+ break;
60
+ dir = parent;
61
+ }
62
+ // Fallback: assume standard layout.
63
+ return path.resolve(__dirname, "..", "..", "..");
64
+ }
65
+ const OK = (message) => ({
66
+ ok: true,
67
+ message,
68
+ severity: "critical",
69
+ });
70
+ const FAIL = (message, severity = "critical") => ({
71
+ ok: false,
72
+ message,
73
+ severity,
74
+ });
75
+ const WARN = (message) => ({
76
+ ok: false,
77
+ message,
78
+ severity: "warn",
79
+ });
80
+ /** Run a shell command and return trimmed stdout. Empty string on failure. */
81
+ function tryExec(cmd) {
82
+ try {
83
+ return execSync(cmd, {
84
+ encoding: "utf8",
85
+ stdio: ["ignore", "pipe", "ignore"],
86
+ }).trim();
87
+ }
88
+ catch {
89
+ return "";
90
+ }
91
+ }
92
+ /** Read the installed package.json (for name + version + bin path). */
93
+ function readPackageJson() {
94
+ try {
95
+ const pkgPath = path.join(packageRoot, "package.json");
96
+ return JSON.parse(readFileSync(pkgPath, "utf8"));
97
+ }
98
+ catch {
99
+ return null;
100
+ }
101
+ }
102
+ /** Resolve the npm global prefix. Empty string on failure. */
103
+ function getGlobalPrefix() {
104
+ return ((process.env.npm_config_prefix ||
105
+ process.env.NPM_CONFIG_PREFIX ||
106
+ "").trim() || tryExec("npm prefix -g"));
107
+ }
108
+ /** Check the bin shim in the global prefix. */
109
+ function checkShim(pkgName) {
110
+ const prefix = getGlobalPrefix();
111
+ if (!prefix) {
112
+ return WARN("npm global prefix not detectable (run inside an npm context)");
113
+ }
114
+ const isWin = process.platform === "win32";
115
+ const shimName = isWin ? "zelari-code.cmd" : "zelari-code";
116
+ const shimPath = path.join(prefix, shimName);
117
+ if (!existsSync(shimPath)) {
118
+ return FAIL(`shim not found at ${shimPath}\n` +
119
+ ` fix: npm install -g ${pkgName}@latest --force`);
120
+ }
121
+ try {
122
+ const st = statSync(shimPath);
123
+ if (isWin) {
124
+ const content = readFileSync(shimPath, "utf8");
125
+ if (content.includes(`${pkgName}\\bin\\`) ||
126
+ content.includes(`${pkgName}/bin/`)) {
127
+ return OK(`shim OK at ${shimPath} (${st.size} bytes)`);
128
+ }
129
+ return FAIL(`shim at ${shimPath} does not reference ${pkgName}/bin/\n` +
130
+ ` fix: npm install -g ${pkgName}@latest --force`);
131
+ }
132
+ let target;
133
+ try {
134
+ target = readlinkSync(shimPath);
135
+ }
136
+ catch {
137
+ return FAIL(`shim at ${shimPath} is not a symlink (POSIX global installs should be symlinks)\n` +
138
+ ` fix: npm install -g ${pkgName}@latest --force`);
139
+ }
140
+ const resolved = path.resolve(path.dirname(shimPath), target);
141
+ const expected = path.join(prefix, "node_modules", pkgName, "bin", "zelari-code.js");
142
+ if (resolved === expected) {
143
+ return OK(`shim OK at ${shimPath} → ${resolved}`);
144
+ }
145
+ return FAIL(`shim at ${shimPath} points to ${resolved}\n` +
146
+ ` expected ${expected}\n` +
147
+ ` fix: npm install -g ${pkgName}@latest --force`);
148
+ }
149
+ catch (err) {
150
+ return FAIL(`could not inspect shim: ${err instanceof Error ? err.message : String(err)}`);
151
+ }
152
+ }
153
+ /** Check that node is on PATH and meets the engines requirement. */
154
+ function checkNode(pkg) {
155
+ const raw = tryExec("node --version");
156
+ if (!raw) {
157
+ return FAIL("`node` not found on PATH");
158
+ }
159
+ // raw is like "v20.11.1"
160
+ const m = raw.match(/^v?(\d+)\.(\d+)\.(\d+)/);
161
+ if (!m) {
162
+ return WARN(`could not parse node version: ${raw}`);
163
+ }
164
+ const major = Number(m[1]);
165
+ if (major < 20) {
166
+ return FAIL(`node ${raw} is older than the required engines.node (>= 20.0.0)`);
167
+ }
168
+ return OK(`node ${raw}`);
169
+ }
170
+ /** Check the CLI bundle exists. */
171
+ function checkBundle() {
172
+ const bundle = path.join(packageRoot, "dist", "cli", "main.bundled.js");
173
+ if (!existsSync(bundle)) {
174
+ return FAIL(`dist/cli/main.bundled.js missing at ${bundle}\n` +
175
+ ` fix: npm run build:cli (then reinstall or run via tsx)`);
176
+ }
177
+ try {
178
+ const st = statSync(bundle);
179
+ return OK(`bundle OK (${(st.size / 1024 / 1024).toFixed(2)} MB)`);
180
+ }
181
+ catch (err) {
182
+ return FAIL(`could not stat bundle: ${err instanceof Error ? err.message : String(err)}`);
183
+ }
184
+ }
185
+ /** Check each runtime dep is resolvable. */
186
+ function checkRuntimeDeps() {
187
+ const required = ["react", "react-dom", "ink", "ink-text-input", "zod"];
188
+ const missing = [];
189
+ for (const dep of required) {
190
+ try {
191
+ // createRequire from the package root — this is what `node bin/zelari-code.js` uses.
192
+ const localReq = createRequire(path.join(packageRoot, "package.json"));
193
+ localReq.resolve(dep);
194
+ }
195
+ catch {
196
+ missing.push(dep);
197
+ }
198
+ }
199
+ if (missing.length === 0) {
200
+ return OK("runtime deps resolvable (react, react-dom, ink, ink-text-input, zod)");
201
+ }
202
+ return FAIL(`missing runtime deps: ${missing.join(", ")}\n` +
203
+ ` fix: npm install -g ${"<pkg>"} --force (then reopen the terminal)`);
204
+ }
205
+ /** Check PATH includes the npm global prefix. */
206
+ function checkPath() {
207
+ const prefix = getGlobalPrefix();
208
+ if (!prefix)
209
+ return WARN("npm global prefix not detectable");
210
+ const pathSep = process.platform === "win32" ? ";" : ":";
211
+ const pathDirs = (process.env.PATH || "").split(pathSep);
212
+ if (pathDirs.includes(prefix)) {
213
+ return OK(`PATH includes npm prefix (${prefix})`);
214
+ }
215
+ return WARN(`PATH does not include npm prefix (${prefix})\n` +
216
+ ` symptom: "zelari-code: command not found" after install\n` +
217
+ ` fix (POSIX): export PATH="$(npm prefix -g)/bin:$PATH"\n` +
218
+ ` fix (Windows): $env:Path = "$(npm prefix -g);$env:Path"`);
219
+ }
220
+ /**
221
+ * Run all checks and print results. Returns true if install is healthy
222
+ * (no critical failures), false otherwise. Never throws.
223
+ */
224
+ export function runDoctor() {
225
+ const pkg = readPackageJson();
226
+ const pkgName = pkg?.name ?? "zelari-code";
227
+ const checks = [
228
+ { name: "node", run: () => checkNode(pkg) },
229
+ { name: "bin shim", run: () => checkShim(pkgName) },
230
+ { name: "cli bundle", run: () => checkBundle() },
231
+ { name: "runtime deps", run: () => checkRuntimeDeps() },
232
+ { name: "PATH", run: () => checkPath() },
233
+ ];
234
+ // eslint-disable-next-line no-console
235
+ console.log(`zelari-code doctor (v${pkg?.version ?? "unknown"})`);
236
+ // eslint-disable-next-line no-console
237
+ console.log("platform:", process.platform, process.arch);
238
+ // eslint-disable-next-line no-console
239
+ console.log("node: ", process.version);
240
+ // eslint-disable-next-line no-console
241
+ console.log("prefix: ", getGlobalPrefix() || "(unknown)");
242
+ // eslint-disable-next-line no-console
243
+ console.log("root: ", packageRoot);
244
+ // eslint-disable-next-line no-console
245
+ console.log("");
246
+ let criticalFails = 0;
247
+ let warns = 0;
248
+ for (const c of checks) {
249
+ let result;
250
+ try {
251
+ result = c.run();
252
+ }
253
+ catch (err) {
254
+ result = FAIL(`unexpected error: ${err instanceof Error ? err.message : String(err)}`);
255
+ }
256
+ const tag = result.ok
257
+ ? "OK "
258
+ : result.severity === "critical"
259
+ ? "FAIL"
260
+ : "WARN";
261
+ const color = result.ok
262
+ ? "\x1b[32m"
263
+ : result.severity === "critical"
264
+ ? "\x1b[31m"
265
+ : "\x1b[33m";
266
+ const reset = "\x1b[0m";
267
+ // eslint-disable-next-line no-console
268
+ console.log(` ${color}${tag}${reset} ${c.name.padEnd(14)}${result.message.replace(/\n/g, "\n ")}`);
269
+ if (!result.ok) {
270
+ if (result.severity === "critical")
271
+ criticalFails += 1;
272
+ else
273
+ warns += 1;
274
+ }
275
+ }
276
+ // eslint-disable-next-line no-console
277
+ console.log("");
278
+ if (criticalFails === 0 && warns === 0) {
279
+ // eslint-disable-next-line no-console
280
+ console.log("\x1b[32m✔ all checks passed\x1b[0m");
281
+ return true;
282
+ }
283
+ // eslint-disable-next-line no-console
284
+ console.log(`✗ ${criticalFails} critical failure(s), ${warns} warning(s)`);
285
+ return criticalFails === 0;
286
+ }
287
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../../src/cli/utils/doctor.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zelari-code",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "Zelari Code — AI Council coding agent CLI. Multi-agent orchestration (Caronte, Nettuno, Gerione, Plutone, Minosse, Lucifero) with slash commands, provider-agnostic LLM streaming, and self-update.",
5
5
  "author": "N-THEM Studio",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -15,6 +15,7 @@
15
15
  "files": [
16
16
  "bin",
17
17
  "dist",
18
+ "scripts/postinstall.mjs",
18
19
  "LICENSE",
19
20
  "README.md",
20
21
  "package.json"
@@ -32,6 +33,7 @@
32
33
  "test:watch": "vitest",
33
34
  "smoke": "node bin/zelari-code.js --version",
34
35
  "verify:council": "npm run build --workspace=@zelari/core && node scripts/verify-council.mjs",
36
+ "postinstall": "node scripts/postinstall.mjs",
35
37
  "prepublishOnly": "node scripts/prepublish.mjs"
36
38
  },
37
39
  "dependencies": {
@@ -42,7 +44,7 @@
42
44
  },
43
45
  "devDependencies": {
44
46
  "@testing-library/react": "^16.3.2",
45
- "@zelari/core": "1.0.1",
47
+ "@zelari/core": "1.1.0",
46
48
  "@types/node": "^25.3.0",
47
49
  "@types/react": "^19.0.10",
48
50
  "esbuild": "^0.25.0",
@@ -0,0 +1,317 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * postinstall.mjs — Shim health check for global installs.
4
+ *
5
+ * Why this exists:
6
+ * On Windows + npm 10/11, `npm install -g zelari-code@<x>` sometimes
7
+ * fails to (re)create the bin shim `zelari-code.cmd` after an upgrade
8
+ * (e.g. 0.7.x → 1.0.x). The package is correctly installed in
9
+ * `<prefix>/node_modules/zelari-code/`, but `<prefix>/zelari-code.cmd`
10
+ * is missing, so `zelari-code` returns "command not found" on the
11
+ * next shell open. This script runs as `postinstall` and:
12
+ *
13
+ * 1. Detects global installs only (skips local installs to avoid noise).
14
+ * 2. Verifies the expected shim path exists and is current.
15
+ * 3. If missing or stale, prints a clear, actionable warning with the
16
+ * exact fix command. It does NOT auto-repair the shim — auto-repair
17
+ * is risky (shim is a privileged file in the global prefix and a
18
+ * misdirected symlink can shadow other tools).
19
+ * 4. Also warns if the npm global prefix is not on the current PATH,
20
+ * which is a separate-but-related failure mode (npm install
21
+ * succeeds, the shim exists, but the user can't run the command
22
+ * because the prefix isn't reachable from their shell).
23
+ *
24
+ * All output goes to stderr so it doesn't pollute the install log on
25
+ * success. The script is fail-safe: any error here is swallowed and
26
+ * logged, never thrown — a broken postinstall must NEVER fail the
27
+ * install itself.
28
+ *
29
+ * @see scripts/diagnose-path.ps1 — interactive Windows diagnostic
30
+ * @see scripts/fix-path.ps1 — adds npm prefix to user PATH
31
+ */
32
+
33
+ import { execSync } from 'node:child_process';
34
+ import {
35
+ existsSync,
36
+ readFileSync,
37
+ readlinkSync,
38
+ statSync,
39
+ writeFileSync,
40
+ } from 'node:fs';
41
+ import { fileURLToPath } from 'node:url';
42
+ import path from 'node:path';
43
+
44
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
45
+ const pkgRoot = path.resolve(__dirname, '..');
46
+
47
+ /**
48
+ * Auto-repair a MISSING Windows bin shim.
49
+ *
50
+ * This is the single most common "zelari-code: command not found on some
51
+ * Windows PCs" failure: npm correctly unpacks the package into
52
+ * `<prefix>\node_modules\zelari-code\` but never (re)creates the three bin
53
+ * shims `<prefix>\zelari-code[.cmd|.ps1]`, so nothing on PATH resolves the
54
+ * command. Repairing this is safe — unlike overwriting a shim that points
55
+ * elsewhere (which could shadow another tool, hence why we never touch an
56
+ * existing shim), writing a shim under OUR own bin name, pointing at OUR
57
+ * own installed package, only fills a gap npm left behind.
58
+ *
59
+ * We write the exact same three files npm's own `cmd-shim` produces (cmd,
60
+ * PowerShell, and a POSIX sh wrapper for Git Bash/MSYS), each resolving to
61
+ * `%~dp0\node_modules\zelari-code\bin\zelari-code.js` relative to the shim
62
+ * dir — which is the global prefix, exactly where the package was unpacked.
63
+ *
64
+ * Opt out with `ZELARI_NO_SHIM_REPAIR=1`. Never throws; returns true only
65
+ * when at least the `.cmd` shim was written.
66
+ *
67
+ * @param {string} prefix npm global prefix (dir that holds the shims)
68
+ * @param {string} pkgName installed package name
69
+ * @returns {boolean} true if the .cmd shim was created
70
+ */
71
+ function repairWindowsShim(prefix, pkgName) {
72
+ if (process.env.ZELARI_NO_SHIM_REPAIR === '1') return false;
73
+ const rel = `node_modules\\${pkgName}\\bin\\zelari-code.js`;
74
+ const relPosix = `node_modules/${pkgName}/bin/zelari-code.js`;
75
+
76
+ const cmd = [
77
+ '@ECHO off',
78
+ 'GOTO start',
79
+ ':find_dp0',
80
+ 'SET dp0=%~dp0',
81
+ 'EXIT /b',
82
+ ':start',
83
+ 'SETLOCAL',
84
+ 'CALL :find_dp0',
85
+ '',
86
+ 'IF EXIST "%dp0%\\node.exe" (',
87
+ ' SET "_prog=%dp0%\\node.exe"',
88
+ ') ELSE (',
89
+ ' SET "_prog=node"',
90
+ ' SET PATHEXT=%PATHEXT:;.JS;=;%',
91
+ ')',
92
+ '',
93
+ `endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\\${rel}" %*`,
94
+ '',
95
+ ].join('\r\n');
96
+
97
+ const ps1 = [
98
+ '#!/usr/bin/env pwsh',
99
+ '$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent',
100
+ '',
101
+ '$exe=""',
102
+ 'if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {',
103
+ ' $exe=".exe"',
104
+ '}',
105
+ '$ret=0',
106
+ 'if (Test-Path "$basedir/node$exe") {',
107
+ ' if ($MyInvocation.ExpectingInput) {',
108
+ ` $input | & "$basedir/node$exe" "$basedir/${relPosix}" $args`,
109
+ ' } else {',
110
+ ` & "$basedir/node$exe" "$basedir/${relPosix}" $args`,
111
+ ' }',
112
+ ' $ret=$LASTEXITCODE',
113
+ '} else {',
114
+ ' if ($MyInvocation.ExpectingInput) {',
115
+ ` $input | & "node$exe" "$basedir/${relPosix}" $args`,
116
+ ' } else {',
117
+ ` & "node$exe" "$basedir/${relPosix}" $args`,
118
+ ' }',
119
+ ' $ret=$LASTEXITCODE',
120
+ '}',
121
+ 'exit $ret',
122
+ '',
123
+ ].join('\n');
124
+
125
+ const sh = [
126
+ '#!/bin/sh',
127
+ 'basedir=$(dirname "$(echo "$0" | sed -e \'s,\\\\,/,g\')")',
128
+ '',
129
+ 'case `uname` in',
130
+ ' *CYGWIN*|*MINGW*|*MSYS*)',
131
+ ' if command -v cygpath > /dev/null 2>&1; then',
132
+ ' basedir=`cygpath -w "$basedir"`',
133
+ ' fi',
134
+ ' ;;',
135
+ 'esac',
136
+ '',
137
+ 'if [ -x "$basedir/node" ]; then',
138
+ ` exec "$basedir/node" "$basedir/${relPosix}" "$@"`,
139
+ 'else',
140
+ ` exec node "$basedir/${relPosix}" "$@"`,
141
+ 'fi',
142
+ '',
143
+ ].join('\n');
144
+
145
+ const targets = [
146
+ ['zelari-code.cmd', cmd],
147
+ ['zelari-code.ps1', ps1],
148
+ ['zelari-code', sh],
149
+ ];
150
+ let cmdWritten = false;
151
+ for (const [name, content] of targets) {
152
+ const dest = path.join(prefix, name);
153
+ // Never clobber an existing shim — only fill the gap npm left.
154
+ if (existsSync(dest)) continue;
155
+ try {
156
+ writeFileSync(dest, content, 'utf8');
157
+ if (name === 'zelari-code.cmd') cmdWritten = true;
158
+ } catch {
159
+ // Permission denied (prefix owned by admin) or read-only FS — the
160
+ // warning below still tells the user how to fix it manually.
161
+ }
162
+ }
163
+ return cmdWritten;
164
+ }
165
+
166
+ const warn = (msg) => {
167
+ // eslint-disable-next-line no-console
168
+ console.warn(`[zelari-code postinstall] ${msg}`);
169
+ };
170
+
171
+ const note = (msg) => {
172
+ // eslint-disable-next-line no-console
173
+ console.warn(`[zelari-code postinstall] ${msg}`);
174
+ };
175
+
176
+ try {
177
+ // 0. Only run for global installs. Local installs (`npm install zelari-code`)
178
+ // create a `node_modules/.bin/zelari-code` symlink that npm handles
179
+ // correctly, and the user runs the command via `npx` or package.json
180
+ // scripts — so the global-prefix shim is irrelevant.
181
+ //
182
+ // npm sets `npm_config_global=true` for `npm install -g` and for
183
+ // `npm install -g <pkg>` invocations. Some npm versions also set
184
+ // `npm_install_global` — check both for safety.
185
+ const isGlobal =
186
+ process.env.npm_config_global === 'true' ||
187
+ process.env.npm_install_global === 'true' ||
188
+ process.env.NPM_CONFIG_GLOBAL === 'true';
189
+
190
+ if (!isGlobal) {
191
+ process.exit(0);
192
+ }
193
+
194
+ // 1. Resolve the global prefix. Prefer the env var (set by npm), fall
195
+ // back to `npm prefix -g`. If both fail, bail silently — we can't
196
+ // diagnose without knowing where to look.
197
+ let prefix = (process.env.npm_config_prefix || process.env.NPM_CONFIG_PREFIX || '').trim();
198
+ if (!prefix) {
199
+ try {
200
+ prefix = execSync('npm prefix -g', { encoding: 'utf8' }).trim();
201
+ } catch {
202
+ // npm not on PATH, or this is a weirdly-sandboxed install.
203
+ // Either way, we can't help further.
204
+ process.exit(0);
205
+ }
206
+ }
207
+
208
+ const pkgName = process.env.npm_package_name || 'zelari-code';
209
+ const pkgVersion = process.env.npm_package_version || 'unknown';
210
+ const expectedBinScript = path.join(pkgRoot, 'bin', 'zelari-code.js');
211
+ const isWin = process.platform === 'win32';
212
+ const shimName = isWin ? 'zelari-code.cmd' : 'zelari-code';
213
+ const shimPath = path.join(prefix, shimName);
214
+ const expectedNodeModulesShim = isWin
215
+ ? `%~dp0\\node_modules\\${pkgName}\\bin\\zelari-code.js`
216
+ : path.join(prefix, 'node_modules', pkgName, 'bin', 'zelari-code.js');
217
+
218
+ // 2. Check the shim.
219
+ let shimOk = false;
220
+ let shimReason = '';
221
+
222
+ if (!existsSync(shimPath)) {
223
+ shimReason = `shim file not found at ${shimPath}`;
224
+ // Auto-repair the most common Windows failure: the package unpacked but
225
+ // npm never created the bin shim. We only write shims that are MISSING,
226
+ // and only under our own bin name → no risk of shadowing another tool.
227
+ if (isWin) {
228
+ const repaired = repairWindowsShim(prefix, pkgName);
229
+ if (repaired && existsSync(shimPath)) {
230
+ note(`shim was missing — auto-created ${shimPath}`);
231
+ note('open a NEW terminal and run `zelari-code --version` to confirm.');
232
+ process.exit(0);
233
+ }
234
+ }
235
+ } else {
236
+ try {
237
+ const st = statSync(shimPath);
238
+ if (isWin) {
239
+ // Windows: .cmd shim. Validate the content references the current
240
+ // package name + bin path. The exact text varies slightly across
241
+ // npm versions, so we just check for the package name + the
242
+ // bin/zelari-code.js substring.
243
+ const content = readFileSync(shimPath, 'utf8');
244
+ if (content.includes(`${pkgName}\\bin\\zelari-code.js`) || content.includes(`${pkgName}/bin/zelari-code.js`)) {
245
+ shimOk = true;
246
+ } else {
247
+ shimReason = `shim at ${shimPath} does not reference ${pkgName}/bin/zelari-code.js — it may belong to a different install`;
248
+ }
249
+ } else {
250
+ // POSIX: symlink (or hardlink). readlinkSync throws for non-symlinks.
251
+ let target;
252
+ try {
253
+ target = readlinkSync(shimPath);
254
+ } catch {
255
+ // Not a symlink — could be a hardlink or a copied file. In
256
+ // either case, npm shouldn't have done that. Treat as broken.
257
+ shimReason = `shim at ${shimPath} is not a symlink (npm should create a symlink for global installs on POSIX)`;
258
+ shimOk = false;
259
+ }
260
+ if (target) {
261
+ const resolved = path.resolve(path.dirname(shimPath), target);
262
+ if (resolved === expectedNodeModulesShim) {
263
+ shimOk = true;
264
+ } else {
265
+ shimReason = `shim at ${shimPath} points to ${resolved}, expected ${expectedNodeModulesShim}`;
266
+ }
267
+ }
268
+ }
269
+ // If we reach here with shimOk still false and shimReason still empty,
270
+ // it's a non-symlink non-cmd file — leave shimReason empty so the
271
+ // generic message is shown.
272
+ if (!shimOk && !shimReason) {
273
+ shimReason = `shim at ${shimPath} is not a regular file or symlink (mode=${st.mode})`;
274
+ }
275
+ } catch (err) {
276
+ shimReason = `could not inspect shim at ${shimPath}: ${err.message}`;
277
+ }
278
+ }
279
+
280
+ if (shimOk) {
281
+ note(`shim OK: ${shimPath}`);
282
+ process.exit(0);
283
+ }
284
+
285
+ // 3. Shim is broken or missing. Emit a clear, actionable warning.
286
+ warn('==============================================================');
287
+ warn(' npm install completed, but the `zelari-code` shim is broken.');
288
+ warn('==============================================================');
289
+ warn(` Reason: ${shimReason}`);
290
+ warn('');
291
+ warn(' The package itself is correctly installed in:');
292
+ warn(` ${path.join(prefix, 'node_modules', pkgName)}`);
293
+ warn(' But the bin shim is not where npm should have created it:');
294
+ warn(` ${shimPath}`);
295
+ warn('');
296
+ warn(' Symptom: `zelari-code` returns "command not found" in your');
297
+ warn(' shell, even though `npm ls -g` lists the package.');
298
+ warn('');
299
+ warn(' Fix (try in order, run as Administrator on Windows if needed):');
300
+ warn('');
301
+ warn(` npm install -g ${pkgName}@${pkgVersion} --force`);
302
+ warn('');
303
+ warn(' If the command is still not found after that, your npm');
304
+ warn(' global prefix may not be on your shell PATH. Run:');
305
+ warn('');
306
+ warn(' npm bin -g # shows the expected shim dir');
307
+ warn(' echo "$PATH" # check if it is included');
308
+ warn('');
309
+ warn(' On Windows, scripts/diagnose-path.ps1 and scripts/fix-path.ps1');
310
+ warn(' in the repo can inspect and repair PATH for you.');
311
+ warn('==============================================================');
312
+ process.exit(0); // Never fail the install.
313
+ } catch (err) {
314
+ // Last-resort safety net: log and exit 0.
315
+ warn(`unexpected error (install is not affected): ${err.message}`);
316
+ process.exit(0);
317
+ }