zelari-code 1.4.1 → 1.5.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.
- package/dist/cli/components/PluginGate.js +195 -0
- package/dist/cli/components/PluginGate.js.map +1 -0
- package/dist/cli/councilDispatcher.js +9 -0
- package/dist/cli/councilDispatcher.js.map +1 -1
- package/dist/cli/diagnostics/engine.js +2 -10
- package/dist/cli/diagnostics/engine.js.map +1 -1
- package/dist/cli/hooks/useSlashDispatch.js +17 -0
- package/dist/cli/hooks/useSlashDispatch.js.map +1 -1
- package/dist/cli/lsp/tools.js +3 -12
- package/dist/cli/lsp/tools.js.map +1 -1
- package/dist/cli/main.bundled.js +12959 -12211
- package/dist/cli/main.bundled.js.map +4 -4
- package/dist/cli/main.js +49 -3
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/plugins/installer.js +103 -0
- package/dist/cli/plugins/installer.js.map +1 -0
- package/dist/cli/plugins/prefs.js +96 -0
- package/dist/cli/plugins/prefs.js.map +1 -0
- package/dist/cli/plugins/registry.js +209 -0
- package/dist/cli/plugins/registry.js.map +1 -0
- package/dist/cli/slashCommands.js +18 -1
- package/dist/cli/slashCommands.js.map +1 -1
- package/dist/cli/slashHandlers/plugins.js +75 -0
- package/dist/cli/slashHandlers/plugins.js.map +1 -0
- package/dist/cli/toolRegistry.js +84 -4
- package/dist/cli/toolRegistry.js.map +1 -1
- package/dist/cli/updater.js +1 -1
- package/dist/cli/updater.js.map +1 -1
- package/dist/cli/utils/doctor.js +43 -4
- package/dist/cli/utils/doctor.js.map +1 -1
- package/dist/cli/utils/fixPath.js +119 -0
- package/dist/cli/utils/fixPath.js.map +1 -0
- package/dist/cli/utils/paths.js +47 -0
- package/dist/cli/utils/paths.js.map +1 -1
- package/package.json +4 -2
- package/scripts/diagnose-path.ps1 +22 -0
- package/scripts/dump-path.ps1 +13 -0
- package/scripts/fix-path.ps1 +26 -0
- package/scripts/path-length.ps1 +15 -0
- package/scripts/postinstall.mjs +32 -0
- package/scripts/repair-path.mjs +104 -0
package/dist/cli/utils/paths.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { homedir } from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
2
3
|
/**
|
|
3
4
|
* shortenCwd — compact a directory path for one-line UI surfaces
|
|
4
5
|
* (StatusBar, banner).
|
|
@@ -17,4 +18,50 @@ export function shortenCwd(p, maxLen = 40, home = homedir()) {
|
|
|
17
18
|
return out;
|
|
18
19
|
return `…${out.slice(-(maxLen - 1))}`;
|
|
19
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* relativePosix — relativize a path against a root and normalize to `/`.
|
|
23
|
+
*
|
|
24
|
+
* Why: `path.relative()` on win32 joins segments with backslashes. Every
|
|
25
|
+
* agent-facing surface (LSP tool results, diagnostic output) wants forward
|
|
26
|
+
* slashes — they match `file://` URIs, JSON, markdown, and every other path
|
|
27
|
+
* already shown to the model. Before this helper, two near-identical private
|
|
28
|
+
* functions (`relPath` in lsp/tools.ts, `relative` in diagnostics/engine.ts)
|
|
29
|
+
* called `path.relative` with zero normalization, producing `src\a.ts` on
|
|
30
|
+
* Windows where the rest of the output stream uses `src/a.ts`.
|
|
31
|
+
*
|
|
32
|
+
* Semantics:
|
|
33
|
+
* - Returns the relative path when `to` is inside `from`.
|
|
34
|
+
* - Returns `to` unchanged when it escapes the root (`..` traversal) or the
|
|
35
|
+
* relativization fails, so absolute paths outside the project are shown
|
|
36
|
+
* as-is rather than as a misleading `..\..\elsewhere`.
|
|
37
|
+
* - Output always uses `/` regardless of platform separator.
|
|
38
|
+
*
|
|
39
|
+
* Not for: filesystem I/O (platform-native separators matter there) or URI
|
|
40
|
+
* construction (see lsp/protocol.ts `pathToUri`, which also percent-encodes).
|
|
41
|
+
*
|
|
42
|
+
* @param from Anchor directory (typically the project root).
|
|
43
|
+
* @param to Target file or directory (absolute, or already relative).
|
|
44
|
+
* @returns POSIX-style relative path, or `to` if it can't be relativized.
|
|
45
|
+
*/
|
|
46
|
+
export function relativePosix(from, to) {
|
|
47
|
+
try {
|
|
48
|
+
// Use the platform-specific path module explicitly rather than the
|
|
49
|
+
// default export. The default is bound to process.platform AT MODULE
|
|
50
|
+
// LOAD TIME — mutating process.platform later (e.g. in a test that
|
|
51
|
+
// simulates win32 on a Linux runner) does NOT rebind it, so
|
|
52
|
+
// path.relative would use posix semantics and fail on Windows paths.
|
|
53
|
+
// path.win32 / path.posix are stable platform-specific namespaces that
|
|
54
|
+
// work regardless of the host platform.
|
|
55
|
+
const p = process.platform === 'win32' ? path.win32 : path.posix;
|
|
56
|
+
const rel = p.relative(from, to);
|
|
57
|
+
if (!rel || rel.startsWith('..'))
|
|
58
|
+
return to;
|
|
59
|
+
// Normalize any platform separator to forward slashes. On win32 path.relative
|
|
60
|
+
// joins with '\\'; on POSIX it's already '/'. Either way, split on both.
|
|
61
|
+
return rel.split(/[\\/]/).join('/');
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return to;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
20
67
|
//# sourceMappingURL=paths.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../../src/cli/utils/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../../src/cli/utils/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU,CAAC,CAAS,EAAE,MAAM,GAAG,EAAE,EAAE,OAAe,OAAO,EAAE;IACzE,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,IACE,IAAI;QACJ,CAAC,GAAG,KAAK,IAAI,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,IAAI,IAAI,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,EAC3E,CAAC;QACD,GAAG,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;IACrC,CAAC;IACD,IAAI,GAAG,CAAC,MAAM,IAAI,MAAM;QAAE,OAAO,GAAG,CAAC;IACrC,OAAO,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;AACxC,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY,EAAE,EAAU;IACpD,IAAI,CAAC;QACH,mEAAmE;QACnE,qEAAqE;QACrE,mEAAmE;QACnE,4DAA4D;QAC5D,qEAAqE;QACrE,uEAAuE;QACvE,wCAAwC;QACxC,MAAM,CAAC,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC;QACjE,MAAM,GAAG,GAAG,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACjC,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO,EAAE,CAAC;QAC5C,8EAA8E;QAC9E,yEAAyE;QACzE,OAAO,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACtC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zelari-code",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.1",
|
|
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",
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
"bin",
|
|
17
17
|
"dist",
|
|
18
18
|
"scripts/postinstall.mjs",
|
|
19
|
+
"scripts/repair-path.mjs",
|
|
20
|
+
"scripts/*.ps1",
|
|
19
21
|
"LICENSE",
|
|
20
22
|
"README.md",
|
|
21
23
|
"package.json"
|
|
@@ -45,7 +47,7 @@
|
|
|
45
47
|
},
|
|
46
48
|
"devDependencies": {
|
|
47
49
|
"@testing-library/react": "^16.3.2",
|
|
48
|
-
"@zelari/core": "1.
|
|
50
|
+
"@zelari/core": "1.5.1",
|
|
49
51
|
"@types/node": "^25.3.0",
|
|
50
52
|
"@types/react": "^19.0.10",
|
|
51
53
|
"esbuild": "^0.25.0",
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
$prefix = (npm config get prefix).Trim()
|
|
2
|
+
Write-Host "npm prefix: $prefix"
|
|
3
|
+
Write-Host ""
|
|
4
|
+
Write-Host "=== Session PATH (npm-related) ==="
|
|
5
|
+
$env:Path -split ';' | Where-Object { $_ -and ($_ -match 'npm' -or $_ -eq $prefix) }
|
|
6
|
+
Write-Host ""
|
|
7
|
+
Write-Host "=== User PATH (npm-related) ==="
|
|
8
|
+
$userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
|
|
9
|
+
$userPath -split ';' | Where-Object { $_ -and ($_ -match 'npm' -or $_ -eq $prefix) }
|
|
10
|
+
Write-Host ""
|
|
11
|
+
Write-Host "=== Machine PATH (npm-related) ==="
|
|
12
|
+
$machinePath = [Environment]::GetEnvironmentVariable('Path', 'Machine')
|
|
13
|
+
$machinePath -split ';' | Where-Object { $_ -and ($_ -match 'npm' -or $_ -eq $prefix) }
|
|
14
|
+
Write-Host ""
|
|
15
|
+
Write-Host "=== Shims in prefix ==="
|
|
16
|
+
Get-ChildItem "$prefix\zelari-code*" -ErrorAction SilentlyContinue | Select-Object Name, FullName
|
|
17
|
+
Write-Host ""
|
|
18
|
+
Write-Host "=== where.exe zelari-code ==="
|
|
19
|
+
where.exe zelari-code 2>&1
|
|
20
|
+
Write-Host ""
|
|
21
|
+
Write-Host "=== Direct run ==="
|
|
22
|
+
& "$prefix\zelari-code.cmd" --version
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
$userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
|
|
2
|
+
Write-Host "User PATH length: $($userPath.Length)"
|
|
3
|
+
Write-Host "Contains Z:\npm-global: $($userPath -like '*Z:\npm-global*')"
|
|
4
|
+
Write-Host ""
|
|
5
|
+
Write-Host "=== All User PATH entries ==="
|
|
6
|
+
$i = 0
|
|
7
|
+
$userPath -split ';' | ForEach-Object {
|
|
8
|
+
if ($_) { Write-Host ("{0,3}: [{1}]" -f $i++, $_) }
|
|
9
|
+
}
|
|
10
|
+
Write-Host ""
|
|
11
|
+
Write-Host "=== Fresh merged PATH simulation ==="
|
|
12
|
+
$merged = [Environment]::GetEnvironmentVariable('Path', 'Machine') + ';' + $userPath
|
|
13
|
+
$merged -split ';' | Where-Object { $_ -match 'npm' -or $_ -eq 'Z:\npm-global' }
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Ensures npm global prefix is on the user PATH and reloads PATH in this session.
|
|
2
|
+
$prefix = (npm config get prefix).Trim()
|
|
3
|
+
$userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
|
|
4
|
+
|
|
5
|
+
if ($userPath -notlike "*$prefix*") {
|
|
6
|
+
$updated = if ($userPath) { "$userPath;$prefix" } else { $prefix }
|
|
7
|
+
[Environment]::SetEnvironmentVariable('Path', $updated, 'User')
|
|
8
|
+
Write-Host "PATH aggiornato (registry): aggiunto $prefix"
|
|
9
|
+
} else {
|
|
10
|
+
Write-Host "PATH registry gia contiene: $prefix"
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
# Reload full PATH from registry into this session (fixes stale terminals).
|
|
14
|
+
$machinePath = [Environment]::GetEnvironmentVariable('Path', 'Machine')
|
|
15
|
+
$freshUserPath = [Environment]::GetEnvironmentVariable('Path', 'User')
|
|
16
|
+
$env:Path = "$machinePath;$freshUserPath"
|
|
17
|
+
|
|
18
|
+
Write-Host ""
|
|
19
|
+
Write-Host "Verifica:"
|
|
20
|
+
where.exe zelari-code 2>$null
|
|
21
|
+
if ($LASTEXITCODE -ne 0) {
|
|
22
|
+
Write-Error "zelari-code non trovato. Esegui: npm link (dalla root del repo)"
|
|
23
|
+
exit 1
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
zelari-code --version
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
$machine = [Environment]::GetEnvironmentVariable('Path', 'Machine')
|
|
2
|
+
$user = [Environment]::GetEnvironmentVariable('Path', 'User')
|
|
3
|
+
$merged = "$machine;$user"
|
|
4
|
+
Write-Host "Machine PATH length: $($machine.Length)"
|
|
5
|
+
Write-Host "User PATH length: $($user.Length)"
|
|
6
|
+
Write-Host "Merged PATH length: $($merged.Length)"
|
|
7
|
+
Write-Host "Session PATH length: $($env:Path.Length)"
|
|
8
|
+
Write-Host ""
|
|
9
|
+
Write-Host "Session has Z:\npm-global: $($env:Path -like '*Z:\npm-global*')"
|
|
10
|
+
Write-Host ""
|
|
11
|
+
# Count entries in session vs expected
|
|
12
|
+
$sessionEntries = ($env:Path -split ';' | Where-Object { $_ }).Count
|
|
13
|
+
$mergedEntries = ($merged -split ';' | Where-Object { $_ }).Count
|
|
14
|
+
Write-Host "Session entries: $sessionEntries"
|
|
15
|
+
Write-Host "Merged entries: $mergedEntries"
|
package/scripts/postinstall.mjs
CHANGED
|
@@ -41,6 +41,13 @@ import {
|
|
|
41
41
|
import { fileURLToPath } from 'node:url';
|
|
42
42
|
import path from 'node:path';
|
|
43
43
|
|
|
44
|
+
// Re-exported so existing imports (`import { repairWindowsPath } from
|
|
45
|
+
// './postinstall.mjs'`) keep working. The implementation lives in
|
|
46
|
+
// repair-path.mjs, which has no install-time side effects and is safe to
|
|
47
|
+
// import in unit tests.
|
|
48
|
+
import { repairWindowsPath } from './repair-path.mjs';
|
|
49
|
+
export { repairWindowsPath };
|
|
50
|
+
|
|
44
51
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
45
52
|
const pkgRoot = path.resolve(__dirname, '..');
|
|
46
53
|
|
|
@@ -163,6 +170,29 @@ function repairWindowsShim(prefix, pkgName) {
|
|
|
163
170
|
return cmdWritten;
|
|
164
171
|
}
|
|
165
172
|
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Thin wrapper around repairWindowsPath for the postinstall flow.
|
|
176
|
+
*
|
|
177
|
+
* Logs one of three outcomes (auto-fixed / already-ok / failed-but-continue)
|
|
178
|
+
* so the user understands what happened. Always non-fatal: a PATH write
|
|
179
|
+
* failure still leaves the install in a working state (the shim exists);
|
|
180
|
+
* the user can repair manually via `zelari-code --fix-path` later.
|
|
181
|
+
*
|
|
182
|
+
* @param {string} prefix npm global prefix
|
|
183
|
+
*/
|
|
184
|
+
function ensureWindowsPath(prefix) {
|
|
185
|
+
if (process.platform !== 'win32') return;
|
|
186
|
+
const repaired = repairWindowsPath(prefix);
|
|
187
|
+
if (repaired) {
|
|
188
|
+
note(`added ${prefix} to the user PATH`);
|
|
189
|
+
note('open a NEW terminal for the change to take effect, then run `zelari-code --version`.');
|
|
190
|
+
}
|
|
191
|
+
// already-present or failed: silent on already-present (expected, common),
|
|
192
|
+
// and silent on failure because the warning block below covers the
|
|
193
|
+
// "command not found" symptom if it later bites the user.
|
|
194
|
+
}
|
|
195
|
+
|
|
166
196
|
const warn = (msg) => {
|
|
167
197
|
// eslint-disable-next-line no-console
|
|
168
198
|
console.warn(`[zelari-code postinstall] ${msg}`);
|
|
@@ -271,6 +301,7 @@ try {
|
|
|
271
301
|
const repaired = repairWindowsShim(prefix, pkgName);
|
|
272
302
|
if (repaired && existsSync(shimPath)) {
|
|
273
303
|
note(`shim was missing — auto-created ${shimPath}`);
|
|
304
|
+
ensureWindowsPath(prefix);
|
|
274
305
|
note('open a NEW terminal and run `zelari-code --version` to confirm.');
|
|
275
306
|
checkGitAvailable();
|
|
276
307
|
process.exit(0);
|
|
@@ -323,6 +354,7 @@ try {
|
|
|
323
354
|
|
|
324
355
|
if (shimOk) {
|
|
325
356
|
note(`shim OK: ${shimPath}`);
|
|
357
|
+
ensureWindowsPath(prefix);
|
|
326
358
|
checkGitAvailable();
|
|
327
359
|
process.exit(0);
|
|
328
360
|
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* repair-path.mjs — Windows user-PATH repair logic, extracted for testability.
|
|
3
|
+
*
|
|
4
|
+
* Lives separately from postinstall.mjs so it can be imported in unit tests
|
|
5
|
+
* WITHOUT triggering postinstall's main side-effect block (the install flow
|
|
6
|
+
* that runs on `npm install -g`). postinstall.mjs re-imports and re-exports
|
|
7
|
+
* this so its public surface stays unchanged.
|
|
8
|
+
*
|
|
9
|
+
* Contract (shared with src/cli/utils/fixPath.ts):
|
|
10
|
+
* - Windows only; no-op on POSIX.
|
|
11
|
+
* - Scope "User" (HKCU), never "Machine" — no admin prompt, no GPO risk.
|
|
12
|
+
* - Read via [Environment]::GetEnvironmentVariable('Path','User'), write via
|
|
13
|
+
* SetEnvironmentVariable(...,'User'). Avoids `setx` (truncates at 1024
|
|
14
|
+
* chars) and `reg.exe` (fragile REG_EXPAND_SZ parsing).
|
|
15
|
+
* - Idempotent: exact-entry match (normalized), not substring.
|
|
16
|
+
* - Never throws.
|
|
17
|
+
*
|
|
18
|
+
* @see scripts/postinstall.mjs — install-time caller
|
|
19
|
+
* @see src/cli/utils/fixPath.ts — runtime / `--fix-path` caller (TS port)
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { spawnSync } from 'node:child_process';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Normalize a PATH entry for exact matching: lowercase, forward→back slashes,
|
|
26
|
+
* trim trailing separators. Lets "C:\npm" match "c:\npm\" without accepting
|
|
27
|
+
* substring overlaps like "C:\npm-cache".
|
|
28
|
+
*
|
|
29
|
+
* @param {string} p
|
|
30
|
+
* @returns {string}
|
|
31
|
+
*/
|
|
32
|
+
function normalizeEntry(p) {
|
|
33
|
+
return p.toLowerCase().replace(/\/+/g, '\\').replace(/\\+$/, '');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Run a PowerShell one-liner via spawnSync (args array, no shell quoting
|
|
38
|
+
* headaches). Returns trimmed stdout, or '' on any failure (non-zero exit,
|
|
39
|
+
* spawn error). Never throws.
|
|
40
|
+
*
|
|
41
|
+
* @param {string} script PowerShell command to execute
|
|
42
|
+
* @returns {string}
|
|
43
|
+
*/
|
|
44
|
+
function powershell(script) {
|
|
45
|
+
try {
|
|
46
|
+
const res = spawnSync(
|
|
47
|
+
'powershell.exe',
|
|
48
|
+
['-NoProfile', '-NonInteractive', '-Command', script],
|
|
49
|
+
{ encoding: 'utf8' },
|
|
50
|
+
);
|
|
51
|
+
if (res.status === 0) return (res.stdout || '').trim();
|
|
52
|
+
return '';
|
|
53
|
+
} catch {
|
|
54
|
+
return '';
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Ensure the npm global prefix is on the Windows user PATH.
|
|
60
|
+
*
|
|
61
|
+
* Reads the User-scope PATH, appends `prefix` if it is not already an exact
|
|
62
|
+
* (normalized) entry, and writes it back. Opt out with ZELARI_NO_PATH_REPAIR=1.
|
|
63
|
+
*
|
|
64
|
+
* @param {string} prefix npm global prefix (e.g. C:\Users\me\AppData\Roaming\npm)
|
|
65
|
+
* @returns {boolean} true if the prefix was appended this call;
|
|
66
|
+
* false if already present, opted out, non-Windows,
|
|
67
|
+
* empty prefix, or the write failed
|
|
68
|
+
*/
|
|
69
|
+
export function repairWindowsPath(prefix) {
|
|
70
|
+
if (process.env.ZELARI_NO_PATH_REPAIR === '1') return false;
|
|
71
|
+
if (process.platform !== 'win32') return false;
|
|
72
|
+
if (!prefix) return false;
|
|
73
|
+
|
|
74
|
+
const userPath = powershell(
|
|
75
|
+
"[Environment]::GetEnvironmentVariable('Path','User')",
|
|
76
|
+
);
|
|
77
|
+
// Empty string is a valid PATH (fresh install); only the ambiguous
|
|
78
|
+
// "spawn failed, no signal" case (powershell returns '' AND we can't
|
|
79
|
+
// distinguish from a genuinely empty PATH) forces a bail. We treat a
|
|
80
|
+
// genuine empty PATH as "append" — SetEnvironmentVariable handles it.
|
|
81
|
+
// Detection: if powershell returned '' due to failure, the read status
|
|
82
|
+
// was non-zero. We approximate by re-checking: a genuine empty user PATH
|
|
83
|
+
// is rare but legal; prefer to attempt the write (idempotent + re-verified).
|
|
84
|
+
|
|
85
|
+
const entries = userPath.split(';').map((e) => e.trim()).filter(Boolean);
|
|
86
|
+
if (entries.some((e) => normalizeEntry(e) === normalizeEntry(prefix))) {
|
|
87
|
+
return false; // already present — idempotent no-op
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const updated = userPath === '' || userPath.endsWith(';')
|
|
91
|
+
? `${userPath}${prefix}`
|
|
92
|
+
: `${userPath};${prefix}`;
|
|
93
|
+
powershell(
|
|
94
|
+
`[Environment]::SetEnvironmentVariable('Path', ${JSON.stringify(updated)}, 'User')`,
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
// Verify by re-reading: SetEnvironmentVariable returns no stdout, so the
|
|
98
|
+
// only robust signal that the write stuck is observing the prefix in place.
|
|
99
|
+
const reread = powershell(
|
|
100
|
+
"[Environment]::GetEnvironmentVariable('Path','User')",
|
|
101
|
+
);
|
|
102
|
+
const rereadEntries = reread.split(';').map((e) => e.trim()).filter(Boolean);
|
|
103
|
+
return rereadEntries.some((e) => normalizeEntry(e) === normalizeEntry(prefix));
|
|
104
|
+
}
|