kodelyth-ecc 2.21.2 → 2.23.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.
- package/CHANGELOG.md +146 -0
- package/README.md +47 -3
- package/VERSION +1 -1
- package/bin/kodelyth-ecc.js +97 -74
- package/install.ps1 +57 -2
- package/package.json +1 -1
- package/scripts/codebase/index.js +26 -1
- package/scripts/doctor-health.js +21 -2
- package/scripts/lib/win-install.js +157 -0
- package/scripts/rtk/index.js +26 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,152 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to Kodelyth ECC are documented here.
|
|
4
4
|
|
|
5
|
+
## v2.23.0 — Windows has no manual step left (October 2026)
|
|
6
|
+
|
|
7
|
+
2.22.0 made the Windows install work but left two components needing a manual
|
|
8
|
+
download: RTK and the codebase graph. That turned out to be a gap in ECC, not a
|
|
9
|
+
limitation upstream — **both projects publish Windows builds** and ECC simply
|
|
10
|
+
never fetched them:
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
rtk-x86_64-pc-windows-msvc.zip
|
|
14
|
+
codebase-memory-mcp-windows-amd64.zip / -arm64.zip
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Both now install automatically. Windows is at parity with macOS and Linux.
|
|
18
|
+
|
|
19
|
+
### How it works
|
|
20
|
+
|
|
21
|
+
A shared `scripts/lib/win-install.js`, used by both. It pulls from GitHub's
|
|
22
|
+
`releases/latest/download/<asset>` URL, which always redirects to the newest
|
|
23
|
+
release — no API call, so no token and no rate limit.
|
|
24
|
+
|
|
25
|
+
Download and extraction use `curl.exe` and `tar.exe`. Both ship with Windows 10
|
|
26
|
+
1803 and later, and `tar` there is bsdtar, which handles zip. That avoids
|
|
27
|
+
PowerShell quoting entirely. Both are probed before use, so an older machine
|
|
28
|
+
gets a clear message instead of a spawn error.
|
|
29
|
+
|
|
30
|
+
Architecture comes from `process.arch`: the codebase graph has a native arm64
|
|
31
|
+
build, while RTK publishes x86_64 only — fine on arm64 Windows, which runs x64
|
|
32
|
+
under emulation.
|
|
33
|
+
|
|
34
|
+
### PATH is yours, not ours
|
|
35
|
+
|
|
36
|
+
Binaries land in `%LOCALAPPDATA%\Kodelyth\bin`. ECC does **not** edit your PATH.
|
|
37
|
+
It prints the one command that does:
|
|
38
|
+
|
|
39
|
+
```powershell
|
|
40
|
+
setx PATH "%PATH%;%LOCALAPPDATA%\Kodelyth\bin"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
This mirrors the POSIX path, which reports `~/.local/bin` rather than writing to
|
|
44
|
+
a shell rc. Whether the directory is already persistent is read from
|
|
45
|
+
`HKCU\Environment`, not from `process.env` — the latter would always say yes
|
|
46
|
+
right after the installer prepends it for its own process, and would wrongly
|
|
47
|
+
tell you there is nothing left to do.
|
|
48
|
+
|
|
49
|
+
### Verified, not asserted
|
|
50
|
+
|
|
51
|
+
The Windows CI job now installs for real and asserts both binaries exist and
|
|
52
|
+
run:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
install dir: C:\Users\runneradmin\AppData\Local\Kodelyth\bin
|
|
56
|
+
rtk.exe 10,285,056 bytes
|
|
57
|
+
rtk 0.51.0
|
|
58
|
+
codebase-memory-mcp 0.11.0
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`doctor`'s Windows hints changed too. They previously said "no Windows installer
|
|
62
|
+
yet" and pointed at a release page; the install command is now correct on every
|
|
63
|
+
platform, and the hint names the PATH step rather than implying the command
|
|
64
|
+
alone finishes the job.
|
|
65
|
+
|
|
66
|
+
**647 tests passing.**
|
|
67
|
+
|
|
68
|
+
## v2.22.0 — Windows actually works now (September 2026)
|
|
69
|
+
|
|
70
|
+
The Windows install was broken in three separate ways. Windows CI had been green
|
|
71
|
+
throughout, because the test suite only ever exercised library functions — it
|
|
72
|
+
never ran the installer, which takes a completely different path there
|
|
73
|
+
(`install.ps1` through PowerShell rather than `install.sh` through bash).
|
|
74
|
+
|
|
75
|
+
Every finding below was measured on a real `windows-latest` runner, not inferred
|
|
76
|
+
from reading the code.
|
|
77
|
+
|
|
78
|
+
### The documented command failed outright
|
|
79
|
+
|
|
80
|
+
`install.sh` matches `claude-home|claude-code` in every case arm, so both
|
|
81
|
+
spellings work on macOS and Linux. `install.ps1` had three separate
|
|
82
|
+
`switch ($Target)` blocks that each matched only `claude-home`, so the other
|
|
83
|
+
spelling hit the default arm:
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
Unknown target: claude-code
|
|
87
|
+
Valid targets: claude-home, windsurf-project, ...
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`claude-code` is what the README, the docs and the website install page all tell
|
|
91
|
+
people to use. **Every Windows user following the documented instructions got a
|
|
92
|
+
hard failure.** Aliases are now normalised once at the top of the file, which
|
|
93
|
+
covers all three switches and keeps the accepted set identical to `install.sh`
|
|
94
|
+
rather than merely similar.
|
|
95
|
+
|
|
96
|
+
### None of the post-install wiring ever ran
|
|
97
|
+
|
|
98
|
+
The Windows branch called `process.exit()` immediately after `install.ps1`, while
|
|
99
|
+
the POSIX branch continued into MCP registration, RTK, Terse mode and the
|
|
100
|
+
codebase graph. A Windows user got agents, skills and commands on disk and
|
|
101
|
+
silently none of those, with no message explaining why.
|
|
102
|
+
|
|
103
|
+
That block is now a shared `runPostInstall()` called by both branches.
|
|
104
|
+
|
|
105
|
+
### Hooks shipped without their scripts, and were never registered
|
|
106
|
+
|
|
107
|
+
`install.ps1` copied only `hooks/hooks.json` — the same defect `install.sh` had
|
|
108
|
+
before 2.21.0, where Windows was missed. So the manifest landed with none of the
|
|
109
|
+
scripts it points at, and `settings.json` was never written at all. Claude Code
|
|
110
|
+
reads hooks from `settings.json`, so every hook was inert.
|
|
111
|
+
|
|
112
|
+
### Before and after, measured
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
before after
|
|
116
|
+
--target claude-code hard fail works
|
|
117
|
+
hook files 1 12
|
|
118
|
+
settings.json ABSENT present, 8 events
|
|
119
|
+
Terse mode absent present
|
|
120
|
+
MCP registration absent present
|
|
121
|
+
total files installed — 502
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Guarded so it cannot regress
|
|
125
|
+
|
|
126
|
+
A `Windows install` workflow now runs on every push to main: it packs the
|
|
127
|
+
package as npm would, installs it globally on `windows-latest`, runs a real
|
|
128
|
+
install into a scratch HOME, and asserts on what lands — counts per module,
|
|
129
|
+
whether `settings.json` was written and with how many events, whether Terse and
|
|
130
|
+
MCP registration happened, then runs `doctor`. Green CI now means the Windows
|
|
131
|
+
install works, which it never did before.
|
|
132
|
+
|
|
133
|
+
### Also
|
|
134
|
+
|
|
135
|
+
`doctor` was telling Windows users to run `kodelythecc rtk install` and
|
|
136
|
+
`kodelythecc codebase install`. Neither has a Windows installer, so both
|
|
137
|
+
commands report that they cannot proceed and the user follows a dead end. Both
|
|
138
|
+
hints now point at the release page on Windows. Remediation that fails on the
|
|
139
|
+
reader's platform is worse than none.
|
|
140
|
+
|
|
141
|
+
RTK and the codebase graph remain manual on Windows — they are native binaries
|
|
142
|
+
without a Windows installer script. The difference is that ECC now says so
|
|
143
|
+
during install rather than the feature being silently absent.
|
|
144
|
+
|
|
145
|
+
README gains a Windows section covering what is automatic, what is not, and why
|
|
146
|
+
no execution-policy change is needed. Two stale claims corrected there: 373
|
|
147
|
+
tests became 647, and 22 hooks became 44 hook entries.
|
|
148
|
+
|
|
149
|
+
**647 tests passing.**
|
|
150
|
+
|
|
5
151
|
## v2.21.2 — same content, with a GitHub release attached (September 2026)
|
|
6
152
|
|
|
7
153
|
Identical to 2.21.1 in every shipped file. It exists because 2.21.1 could not be
|
package/README.md
CHANGED
|
@@ -96,7 +96,7 @@ You never typed `use debug-detective`. You didn't have to. The toolkit read the
|
|
|
96
96
|
| Quality hooks | ✅ 22+ | Some | ❌ | ❌ |
|
|
97
97
|
| IDE platforms | **11** (Claude Code, Windsurf, Cursor, Codex, Antigravity, OpenCode, Cline, Roo Code, Aider, Kimi, Gemini CLI) | 1-2 | 1 | Varies |
|
|
98
98
|
| Telemetry | ❌ none | Varies | ❌ | Varies |
|
|
99
|
-
| Test coverage | ✅
|
|
99
|
+
| Test coverage | ✅ 647 tests | ❌ | ❌ | ❌ |
|
|
100
100
|
| Distributed via | `npx`, curl, clone | Manual | Manual | Manual |
|
|
101
101
|
|
|
102
102
|
---
|
|
@@ -118,7 +118,7 @@ kodelythecc --target claude-code --codebase-graph
|
|
|
118
118
|
That's it. This single flow:
|
|
119
119
|
|
|
120
120
|
1. Installs both binaries (`kodelyth-ecc` and short-form `kodelythecc`) to your PATH
|
|
121
|
-
2. Copies 70 agents + 196 skills + 103 commands +
|
|
121
|
+
2. Copies 70 agents + 196 skills + 103 commands + 44 hook entries + 15 rules into your AI tool's config dir
|
|
122
122
|
3. Auto-installs **RTK** binary and wires its PreToolUse hook (input compression starts on next AI restart)
|
|
123
123
|
4. Installs **Terse mode** skill + `/terse` and `/terse-compress` slash commands (dormant — user types `/terse` to activate)
|
|
124
124
|
5. Auto-installs **codebase-memory-mcp** and registers its MCP entries in your AI tool (with `--codebase-graph`)
|
|
@@ -190,6 +190,48 @@ export KODELYTH_EXTRA_IDE_WATCH="$HOME/my-agent-logs,$HOME/other-tool/state"
|
|
|
190
190
|
npx kodelyth-ecc dashboard
|
|
191
191
|
```
|
|
192
192
|
|
|
193
|
+
### Windows
|
|
194
|
+
|
|
195
|
+
The same command works. `npx kodelyth-ecc` detects Windows and runs `install.ps1`
|
|
196
|
+
through PowerShell for you — there is nothing separate to download.
|
|
197
|
+
|
|
198
|
+
```powershell
|
|
199
|
+
npx kodelyth-ecc # Claude Code (default)
|
|
200
|
+
npx kodelyth-ecc --target cursor-project # any target works the same way
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
**Requirements.** Node.js 18+ and either PowerShell 7 (`pwsh`) or the Windows
|
|
204
|
+
PowerShell 5.1 that ships with the OS. The installer tries `pwsh` first and falls
|
|
205
|
+
back automatically, and it passes `-ExecutionPolicy Bypass` for its own process
|
|
206
|
+
only, so you do not need to change your machine's execution policy.
|
|
207
|
+
|
|
208
|
+
**Everything installs automatically**, same as macOS and Linux: the 70 agents,
|
|
209
|
+
196 skills, 103 commands, 15 rules, all 44 hook entries registered into
|
|
210
|
+
`settings.json`, the ECC MCP server (16 tools), Terse mode, **RTK**, and the
|
|
211
|
+
**codebase graph**.
|
|
212
|
+
|
|
213
|
+
The last two are native binaries. ECC downloads the published Windows builds
|
|
214
|
+
straight from GitHub releases and puts them in `%LOCALAPPDATA%\Kodelyth\bin`.
|
|
215
|
+
That directory is not on `PATH` by default, so the installer prints the one
|
|
216
|
+
command that fixes it:
|
|
217
|
+
|
|
218
|
+
```powershell
|
|
219
|
+
setx PATH "%PATH%;%LOCALAPPDATA%\Kodelyth\bin"
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Run it once, reopen your terminal, done. ECC does not edit `PATH` for you —
|
|
223
|
+
that is your environment to change.
|
|
224
|
+
|
|
225
|
+
**Verify:**
|
|
226
|
+
|
|
227
|
+
```powershell
|
|
228
|
+
kodelythecc doctor
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Every install is exercised on a real `windows-latest` runner on each push — it
|
|
232
|
+
packs the package, installs it globally, runs a real install, and asserts on
|
|
233
|
+
what lands. See the `Windows install` workflow.
|
|
234
|
+
|
|
193
235
|
### Option 2 — npx from GitHub (always latest commit)
|
|
194
236
|
|
|
195
237
|
```bash
|
|
@@ -973,7 +1015,9 @@ npx kodelyth-ecc typescript python golang rust java kotlin php swift cpp dart ru
|
|
|
973
1015
|
| Google Antigravity | Full | `antigravity` | Agents → skills, commands → workflows, rules |
|
|
974
1016
|
| OpenCode | Rules only | `opencode` | Rules (agents + skills not yet supported by OpenCode) |
|
|
975
1017
|
|
|
976
|
-
**OS support:** macOS, Linux
|
|
1018
|
+
**OS support:** macOS, Linux and Windows. `npx kodelyth-ecc` is the same command on all
|
|
1019
|
+
three — it dispatches to `install.sh` on macOS/Linux and `install.ps1` on Windows for you.
|
|
1020
|
+
See [Windows](#windows) for the two pieces that still need a manual step there.
|
|
977
1021
|
|
|
978
1022
|
---
|
|
979
1023
|
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.
|
|
1
|
+
2.23.0
|
package/bin/kodelyth-ecc.js
CHANGED
|
@@ -1806,79 +1806,20 @@ if (args.includes('--version') || args.includes('-v')) {
|
|
|
1806
1806
|
}
|
|
1807
1807
|
|
|
1808
1808
|
// ── Run installer ─────────────────────────────────────────────────────────────
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
const a = args[i];
|
|
1822
|
-
if ((a === '--target' || a === '-target') && args[i + 1]) {
|
|
1823
|
-
psArgs.push('-Target', args[++i]); continue;
|
|
1824
|
-
}
|
|
1825
|
-
if ((a === '--bundle' || a === '-bundle') && args[i + 1]) {
|
|
1826
|
-
psArgs.push('-Bundle', args[++i]); continue;
|
|
1827
|
-
}
|
|
1828
|
-
if ((a === '--profile' || a === '-profile') && args[i + 1]) {
|
|
1829
|
-
// profile maps to languages in ps1
|
|
1830
|
-
languages.push(args[++i]); continue;
|
|
1831
|
-
}
|
|
1832
|
-
if (!a.startsWith('-')) {
|
|
1833
|
-
// positional = language module
|
|
1834
|
-
languages.push(a); continue;
|
|
1835
|
-
}
|
|
1836
|
-
// pass through any other flags unmapped
|
|
1837
|
-
psArgs.push(a);
|
|
1838
|
-
}
|
|
1839
|
-
if (languages.length > 0) {
|
|
1840
|
-
psArgs.push('-Languages', languages.join(','));
|
|
1841
|
-
}
|
|
1842
|
-
|
|
1843
|
-
// Try pwsh (PowerShell 7+) first, then fall back to powershell (5.1)
|
|
1844
|
-
function trySpawn(bin) {
|
|
1845
|
-
return spawnSync(bin, ['-NoLogo', '-ExecutionPolicy', 'Bypass', '-File', ps1, ...psArgs], {
|
|
1846
|
-
stdio: 'inherit',
|
|
1847
|
-
shell: false,
|
|
1848
|
-
env: { ...process.env, KODELYTH_NONINTERACTIVE: '1' },
|
|
1849
|
-
});
|
|
1850
|
-
}
|
|
1851
|
-
|
|
1852
|
-
let result = trySpawn('pwsh');
|
|
1853
|
-
if (result.error && result.error.code === 'ENOENT') {
|
|
1854
|
-
// pwsh not found — try legacy powershell.exe
|
|
1855
|
-
result = trySpawn('powershell');
|
|
1856
|
-
}
|
|
1857
|
-
|
|
1858
|
-
if (result.error) {
|
|
1859
|
-
console.error(
|
|
1860
|
-
'\nError: Could not launch PowerShell to run the installer.\n' +
|
|
1861
|
-
'Make sure PowerShell is installed and accessible in your PATH.\n' +
|
|
1862
|
-
' • Try: pwsh --version\n' +
|
|
1863
|
-
' • Or: powershell -Version\n' +
|
|
1864
|
-
'\nAlternatively, run the installer manually:\n' +
|
|
1865
|
-
` powershell -ExecutionPolicy Bypass -File "${ps1}"\n`
|
|
1866
|
-
);
|
|
1867
|
-
process.exit(1);
|
|
1868
|
-
}
|
|
1869
|
-
process.exit(result.status ?? 1);
|
|
1870
|
-
} else {
|
|
1871
|
-
try { require(path.join(ROOT, 'scripts', 'migrate-legacy.js')).main(); } catch {}
|
|
1872
|
-
const sh = path.join(ROOT, 'install.sh');
|
|
1873
|
-
if (!fs.existsSync(sh)) {
|
|
1874
|
-
console.error('Error: install.sh not found in package root:', ROOT);
|
|
1875
|
-
process.exit(1);
|
|
1876
|
-
}
|
|
1877
|
-
fs.chmodSync(sh, 0o755);
|
|
1878
|
-
const result = spawnSync('bash', [sh, ...args], { stdio: 'inherit', shell: false });
|
|
1879
|
-
|
|
1809
|
+
// Post-install wiring: MCP registration, RTK, Terse mode, codebase graph.
|
|
1810
|
+
//
|
|
1811
|
+
// This lived inside the POSIX branch, and the Windows branch called
|
|
1812
|
+
// process.exit() straight after install.ps1 — so none of it ran on Windows. A
|
|
1813
|
+
// Windows user got agents, skills and commands on disk and silently none of:
|
|
1814
|
+
// the MCP server (16 tools), RTK token savings, /terse and /terse-compress, or
|
|
1815
|
+
// the codebase graph. Five advertised features, absent with no message.
|
|
1816
|
+
//
|
|
1817
|
+
// Every block below already guards itself and reports a reason when it cannot
|
|
1818
|
+
// proceed, which is what makes sharing it safe: on Windows the parts that work
|
|
1819
|
+
// now run, and the parts that cannot say so instead of vanishing.
|
|
1820
|
+
function runPostInstall(status, args) {
|
|
1880
1821
|
// Post-install: auto-register ECC's own MCP server in Claude Code + Desktop.
|
|
1881
|
-
if (
|
|
1822
|
+
if (status === 0 && !args.includes('--no-mcp-register')) {
|
|
1882
1823
|
try {
|
|
1883
1824
|
const reg = require(path.join(ROOT, 'scripts', 'mcp', 'register-self.js'));
|
|
1884
1825
|
process.stdout.write('\n━ ECC MCP server registration ' + '─'.repeat(31) + '\n');
|
|
@@ -1894,7 +1835,7 @@ if (isWin) {
|
|
|
1894
1835
|
}
|
|
1895
1836
|
|
|
1896
1837
|
// Post-install: auto-install + wire RTK for the target IDE (opt-out via --no-rtk).
|
|
1897
|
-
if (
|
|
1838
|
+
if (status === 0 && !args.includes('--no-rtk')) {
|
|
1898
1839
|
try {
|
|
1899
1840
|
const rtk = require(path.join(ROOT, 'scripts', 'rtk', 'index.js'));
|
|
1900
1841
|
const targetIdx = args.indexOf('--target');
|
|
@@ -1909,6 +1850,13 @@ if (isWin) {
|
|
|
1909
1850
|
const en = rtk.enableFor(target, { log: () => {} });
|
|
1910
1851
|
if (en.enabled) {
|
|
1911
1852
|
w(` ✓ RTK ${(rtk.getVersion() || '').replace(/^rtk /,'')} — wired for ${target}`);
|
|
1853
|
+
// On Windows the binary goes to %LOCALAPPDATA%\Kodelyth\bin. ECC does
|
|
1854
|
+
// not edit PATH for the user, so say plainly what is left to do —
|
|
1855
|
+
// otherwise rtk works during this install and vanishes next shell.
|
|
1856
|
+
if (inst.pathHint) {
|
|
1857
|
+
w(` ! ${inst.dir} is not on your PATH yet`);
|
|
1858
|
+
w(` run once, then reopen your terminal: ${inst.pathHint}`);
|
|
1859
|
+
}
|
|
1912
1860
|
w(` ✓ Restart your AI tool to activate. 60-90% token savings on shell commands.`);
|
|
1913
1861
|
} else {
|
|
1914
1862
|
w(` · skipped: ${en.reason}`);
|
|
@@ -1972,5 +1920,80 @@ if (isWin) {
|
|
|
1972
1920
|
}
|
|
1973
1921
|
}
|
|
1974
1922
|
|
|
1975
|
-
process.exit(result.status ?? 1);
|
|
1976
1923
|
}
|
|
1924
|
+
|
|
1925
|
+
if (isWin) {
|
|
1926
|
+
const ps1 = path.join(ROOT, 'install.ps1');
|
|
1927
|
+
if (!fs.existsSync(ps1)) {
|
|
1928
|
+
console.error('Error: install.ps1 not found. Please clone the repo and run install.ps1 manually.');
|
|
1929
|
+
process.exit(1);
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
// Map CLI args (--target X, --bundle X, --profile X) → PowerShell named params (-Target X, -Bundle X)
|
|
1933
|
+
// Positional language args (e.g. "typescript python") → collected for -Languages
|
|
1934
|
+
const psArgs = [];
|
|
1935
|
+
const languages = [];
|
|
1936
|
+
for (let i = 0; i < args.length; i++) {
|
|
1937
|
+
const a = args[i];
|
|
1938
|
+
if ((a === '--target' || a === '-target') && args[i + 1]) {
|
|
1939
|
+
psArgs.push('-Target', args[++i]); continue;
|
|
1940
|
+
}
|
|
1941
|
+
if ((a === '--bundle' || a === '-bundle') && args[i + 1]) {
|
|
1942
|
+
psArgs.push('-Bundle', args[++i]); continue;
|
|
1943
|
+
}
|
|
1944
|
+
if ((a === '--profile' || a === '-profile') && args[i + 1]) {
|
|
1945
|
+
// profile maps to languages in ps1
|
|
1946
|
+
languages.push(args[++i]); continue;
|
|
1947
|
+
}
|
|
1948
|
+
if (!a.startsWith('-')) {
|
|
1949
|
+
// positional = language module
|
|
1950
|
+
languages.push(a); continue;
|
|
1951
|
+
}
|
|
1952
|
+
// pass through any other flags unmapped
|
|
1953
|
+
psArgs.push(a);
|
|
1954
|
+
}
|
|
1955
|
+
if (languages.length > 0) {
|
|
1956
|
+
psArgs.push('-Languages', languages.join(','));
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
// Try pwsh (PowerShell 7+) first, then fall back to powershell (5.1)
|
|
1960
|
+
function trySpawn(bin) {
|
|
1961
|
+
return spawnSync(bin, ['-NoLogo', '-ExecutionPolicy', 'Bypass', '-File', ps1, ...psArgs], {
|
|
1962
|
+
stdio: 'inherit',
|
|
1963
|
+
shell: false,
|
|
1964
|
+
env: { ...process.env, KODELYTH_NONINTERACTIVE: '1' },
|
|
1965
|
+
});
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
let result = trySpawn('pwsh');
|
|
1969
|
+
if (result.error && result.error.code === 'ENOENT') {
|
|
1970
|
+
// pwsh not found — try legacy powershell.exe
|
|
1971
|
+
result = trySpawn('powershell');
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
if (result.error) {
|
|
1975
|
+
console.error(
|
|
1976
|
+
'\nError: Could not launch PowerShell to run the installer.\n' +
|
|
1977
|
+
'Make sure PowerShell is installed and accessible in your PATH.\n' +
|
|
1978
|
+
' • Try: pwsh --version\n' +
|
|
1979
|
+
' • Or: powershell -Version\n' +
|
|
1980
|
+
'\nAlternatively, run the installer manually:\n' +
|
|
1981
|
+
` powershell -ExecutionPolicy Bypass -File "${ps1}"\n`
|
|
1982
|
+
);
|
|
1983
|
+
process.exit(1);
|
|
1984
|
+
}
|
|
1985
|
+
runPostInstall(result.status, args);
|
|
1986
|
+
process.exit(result.status ?? 1);
|
|
1987
|
+
} else {
|
|
1988
|
+
try { require(path.join(ROOT, 'scripts', 'migrate-legacy.js')).main(); } catch {}
|
|
1989
|
+
const sh = path.join(ROOT, 'install.sh');
|
|
1990
|
+
if (!fs.existsSync(sh)) {
|
|
1991
|
+
console.error('Error: install.sh not found in package root:', ROOT);
|
|
1992
|
+
process.exit(1);
|
|
1993
|
+
}
|
|
1994
|
+
fs.chmodSync(sh, 0o755);
|
|
1995
|
+
const result = spawnSync('bash', [sh, ...args], { stdio: 'inherit', shell: false });
|
|
1996
|
+
|
|
1997
|
+
runPostInstall(result.status, args);
|
|
1998
|
+
process.exit(result.status ?? 1);
|
|
1999
|
+
}
|
package/install.ps1
CHANGED
|
@@ -32,6 +32,33 @@ $ErrorActionPreference = "Stop"
|
|
|
32
32
|
# Non-interactive mode: set by npx launcher (KODELYTH_NONINTERACTIVE=1) or --yes flag
|
|
33
33
|
$NonInteractive = ($env:KODELYTH_NONINTERACTIVE -eq '1')
|
|
34
34
|
|
|
35
|
+
# -- Target aliases -----------------------------------------------------------
|
|
36
|
+
# install.sh matches "claude-home|claude-code" in every one of its case arms, so
|
|
37
|
+
# both spellings work on macOS and Linux. This file had three separate
|
|
38
|
+
# switch ($Target) blocks that each matched only "claude-home", so
|
|
39
|
+
# "--target claude-code" fell through to the default arm and exited 1.
|
|
40
|
+
#
|
|
41
|
+
# That is the spelling the README, the docs and the website install page all
|
|
42
|
+
# tell people to use, so the documented primary command failed outright on
|
|
43
|
+
# Windows while working everywhere else. Measured on a windows-latest runner:
|
|
44
|
+
#
|
|
45
|
+
# Unknown target: claude-code
|
|
46
|
+
# Valid targets: claude-home, windsurf-project, ...
|
|
47
|
+
#
|
|
48
|
+
# Normalising once here fixes all three switches at the same time, and keeps the
|
|
49
|
+
# accepted set identical to install.sh rather than merely similar. Matching is
|
|
50
|
+
# case-insensitive because PowerShell users reasonably type -Target Claude-Code.
|
|
51
|
+
$TargetAliases = @{
|
|
52
|
+
'claude-code' = 'claude-home'
|
|
53
|
+
'claude' = 'claude-home'
|
|
54
|
+
}
|
|
55
|
+
$targetKey = $Target.ToLower()
|
|
56
|
+
if ($TargetAliases.ContainsKey($targetKey)) {
|
|
57
|
+
$Target = $TargetAliases[$targetKey]
|
|
58
|
+
} else {
|
|
59
|
+
$Target = $targetKey
|
|
60
|
+
}
|
|
61
|
+
|
|
35
62
|
# -- Bundle handling (audience-tailored cheat sheets) -------------------------
|
|
36
63
|
if ($Bundle) {
|
|
37
64
|
switch ($Bundle.ToLower()) {
|
|
@@ -233,10 +260,38 @@ switch ($Target) {
|
|
|
233
260
|
Install-Dir "$ScriptDir\commands" $CommandsDest "Commands (79)"
|
|
234
261
|
|
|
235
262
|
# Hooks
|
|
263
|
+
#
|
|
264
|
+
# Copy the whole tree, not just the manifest. Every command inside
|
|
265
|
+
# hooks.json points at ${CLAUDE_PLUGIN_ROOT}/hooks/{memory,safety}/*.js,
|
|
266
|
+
# so copying hooks.json alone registers hooks whose scripts are absent.
|
|
267
|
+
#
|
|
268
|
+
# Then register them into settings.json. Claude Code reads hooks from
|
|
269
|
+
# settings.json, NOT from hooks.json — a manifest sitting on disk does
|
|
270
|
+
# nothing. install.sh was fixed for this in 2.21.0; this file was missed,
|
|
271
|
+
# so Windows installs shipped 1 hook file and no settings.json at all
|
|
272
|
+
# (measured on a windows-latest runner).
|
|
236
273
|
if ($HooksDest) {
|
|
237
274
|
if (-not (Test-Path $HooksDest)) { New-Item -ItemType Directory -Path $HooksDest -Force | Out-Null }
|
|
238
|
-
Copy-Item "$ScriptDir\hooks
|
|
239
|
-
|
|
275
|
+
Copy-Item "$ScriptDir\hooks\*" $HooksDest -Recurse -Force
|
|
276
|
+
$hookScripts = (Get-ChildItem -Path $HooksDest -Recurse -Filter "*.js" -ErrorAction SilentlyContinue).Count
|
|
277
|
+
Write-Host " [OK] Hooks -> $HooksDest ($hookScripts scripts)" -ForegroundColor Green
|
|
278
|
+
|
|
279
|
+
$registrar = Join-Path $ScriptDir "scripts\install\register-hooks.js"
|
|
280
|
+
if (Test-Path $registrar) {
|
|
281
|
+
$nodeCmd = Get-Command node -ErrorAction SilentlyContinue
|
|
282
|
+
if ($nodeCmd) {
|
|
283
|
+
$summary = & node $registrar $Dest 2>&1
|
|
284
|
+
if ($LASTEXITCODE -eq 0) {
|
|
285
|
+
Write-Host " [OK] Hooks registered ($summary)" -ForegroundColor Green
|
|
286
|
+
} else {
|
|
287
|
+
Write-Host " [!] Hook registration failed: $summary" -ForegroundColor Yellow
|
|
288
|
+
Write-Host " Retry: node `"$registrar`" `"$Dest`"" -ForegroundColor Gray
|
|
289
|
+
}
|
|
290
|
+
} else {
|
|
291
|
+
Write-Host " [!] Hooks copied but not registered -- node not found" -ForegroundColor Yellow
|
|
292
|
+
Write-Host " Run after installing Node: node `"$registrar`" `"$Dest`"" -ForegroundColor Gray
|
|
293
|
+
}
|
|
294
|
+
}
|
|
240
295
|
}
|
|
241
296
|
|
|
242
297
|
# Rules
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kodelyth-ecc",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.23.0",
|
|
4
4
|
"description": "Production-grade AI coding toolkit — 70 agents (incl. devil-mode adversarial crew), 194 skills, 97 commands, parallel multi-agent commands, semantic intent routing, self-learning memory, and a built-in MCP server (16 tools / 6 prompts / 377 resources) that bridges to Claude Desktop, LangGraph, AutoGen, CrewAI, and OpenAI Agents SDK. Works with Claude Code, Windsurf, Cursor, Codex, Antigravity, OpenCode, Cline, RooCode, Aider, Kimi, and Gemini CLI.",
|
|
5
5
|
"author": "Kodelyth <github.com/sifxprime>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,7 +25,32 @@ function getVersion() {
|
|
|
25
25
|
function install({ log = () => {} } = {}) {
|
|
26
26
|
if (isInstalled()) return { installed: false, skipped: true, reason: 'already installed', version: getVersion() };
|
|
27
27
|
if (os.platform() === 'win32') {
|
|
28
|
-
|
|
28
|
+
// This project publishes Windows builds for both architectures; ECC just
|
|
29
|
+
// never downloaded them. Pick by process.arch so an arm64 machine gets the
|
|
30
|
+
// native build rather than running x64 under emulation.
|
|
31
|
+
const win = require('../lib/win-install.js');
|
|
32
|
+
const asset = process.arch === 'arm64'
|
|
33
|
+
? 'codebase-memory-mcp-windows-arm64.zip'
|
|
34
|
+
: 'codebase-memory-mcp-windows-amd64.zip';
|
|
35
|
+
const r = win.installFromRelease({
|
|
36
|
+
repo: 'DeusData/codebase-memory-mcp',
|
|
37
|
+
asset,
|
|
38
|
+
binName: `${BIN}.exe`,
|
|
39
|
+
log,
|
|
40
|
+
});
|
|
41
|
+
if (!r.installed) {
|
|
42
|
+
return { installed: false, skipped: true, reason: `${r.reason} — or install by hand: https://github.com/DeusData/codebase-memory-mcp/releases` };
|
|
43
|
+
}
|
|
44
|
+
if (!isInstalled()) {
|
|
45
|
+
return { installed: false, skipped: true, reason: `installed to ${r.dir} but not runnable from PATH` };
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
installed: true,
|
|
49
|
+
method: 'github-release',
|
|
50
|
+
version: getVersion(),
|
|
51
|
+
dir: r.dir,
|
|
52
|
+
pathHint: r.onPath ? null : win.setxHint(r.dir),
|
|
53
|
+
};
|
|
29
54
|
}
|
|
30
55
|
log('[codebase] installing codebase-memory-mcp via official curl script…');
|
|
31
56
|
const script = 'curl -fsSL https://raw.githubusercontent.com/DeusData/codebase-memory-mcp/main/install.sh | bash';
|
package/scripts/doctor-health.js
CHANGED
|
@@ -115,8 +115,21 @@ function checkPromptInjectionGuard() {
|
|
|
115
115
|
return { status: PASS, detail: `prompt-injection guard active (mode=${mode})` };
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
// Both tools DO publish Windows builds, and since 2.23.0 ECC downloads them, so
|
|
119
|
+
// the install command is now the right advice on every platform. What differs is
|
|
120
|
+
// what happens afterwards: on Windows the binary lands in
|
|
121
|
+
// %LOCALAPPDATA%\Kodelyth\bin, which is not on PATH by default, so the hint
|
|
122
|
+
// mentions that rather than implying the command alone finishes the job.
|
|
123
|
+
const IS_WIN = process.platform === 'win32';
|
|
124
|
+
|
|
118
125
|
function checkRtk() {
|
|
119
|
-
if (!onPath('rtk')) return {
|
|
126
|
+
if (!onPath('rtk')) return {
|
|
127
|
+
status: WARN,
|
|
128
|
+
detail: 'RTK not installed (input savings inactive)',
|
|
129
|
+
fix: IS_WIN
|
|
130
|
+
? 'kodelythecc rtk install (installs to %LOCALAPPDATA%\\Kodelyth\\bin — add it to PATH)'
|
|
131
|
+
: 'kodelythecc rtk install',
|
|
132
|
+
};
|
|
120
133
|
try {
|
|
121
134
|
const v = execFileSync('rtk', ['--version'], { encoding: 'utf8' }).trim();
|
|
122
135
|
return { status: PASS, detail: `${v} installed`, };
|
|
@@ -130,7 +143,13 @@ function checkTerse() {
|
|
|
130
143
|
}
|
|
131
144
|
|
|
132
145
|
function checkCodebaseGraph() {
|
|
133
|
-
if (!onPath('codebase-memory-mcp')) return {
|
|
146
|
+
if (!onPath('codebase-memory-mcp')) return {
|
|
147
|
+
status: WARN,
|
|
148
|
+
detail: 'codebase graph not installed (optional)',
|
|
149
|
+
fix: IS_WIN
|
|
150
|
+
? 'kodelythecc codebase install (installs to %LOCALAPPDATA%\\Kodelyth\\bin — add it to PATH)'
|
|
151
|
+
: 'kodelythecc codebase install',
|
|
152
|
+
};
|
|
134
153
|
return { status: PASS, detail: 'codebase-memory-mcp installed' };
|
|
135
154
|
}
|
|
136
155
|
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Install a released binary on Windows.
|
|
5
|
+
*
|
|
6
|
+
* Both RTK and codebase-memory-mcp publish Windows builds, but ECC never
|
|
7
|
+
* implemented the download path and reported "windows requires manual install"
|
|
8
|
+
* instead. That was a gap here, not a limitation upstream:
|
|
9
|
+
*
|
|
10
|
+
* rtk-x86_64-pc-windows-msvc.zip
|
|
11
|
+
* codebase-memory-mcp-windows-amd64.zip / -arm64.zip
|
|
12
|
+
*
|
|
13
|
+
* Uses GitHub's `releases/latest/download/<asset>` URL, which always redirects
|
|
14
|
+
* to the newest release's asset — no API call, so no token and no rate limit.
|
|
15
|
+
*
|
|
16
|
+
* curl.exe and tar.exe both ship with Windows 10 1803 and later, and tar there
|
|
17
|
+
* is bsdtar, which extracts zip. That avoids PowerShell quoting entirely. Both
|
|
18
|
+
* are probed before use so an older machine gets a clear message rather than a
|
|
19
|
+
* spawn error.
|
|
20
|
+
*
|
|
21
|
+
* PATH is deliberately NOT modified. The POSIX path installs to ~/.local/bin and
|
|
22
|
+
* tells the user to add it; this mirrors that, reporting the exact setx command
|
|
23
|
+
* rather than silently editing a user's environment.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const fs = require('fs');
|
|
27
|
+
const os = require('os');
|
|
28
|
+
const path = require('path');
|
|
29
|
+
const { execFileSync, spawnSync } = require('child_process');
|
|
30
|
+
|
|
31
|
+
/** Where ECC puts binaries it installs on Windows. User-writable, no admin. */
|
|
32
|
+
function installDir() {
|
|
33
|
+
const base = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
|
|
34
|
+
return path.join(base, 'Kodelyth', 'bin');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function have(bin) {
|
|
38
|
+
try {
|
|
39
|
+
execFileSync(bin, ['--version'], { stdio: 'ignore' });
|
|
40
|
+
return true;
|
|
41
|
+
} catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @param {object} opts
|
|
48
|
+
* @param {string} opts.repo "owner/name"
|
|
49
|
+
* @param {string} opts.asset asset filename in the latest release
|
|
50
|
+
* @param {string} opts.binName executable to find after extraction (with .exe)
|
|
51
|
+
* @param {function} [opts.log]
|
|
52
|
+
* @returns {{installed:boolean, reason?:string, dir?:string, binPath?:string, onPath?:boolean}}
|
|
53
|
+
*/
|
|
54
|
+
function installFromRelease({ repo, asset, binName, log = () => {} }) {
|
|
55
|
+
if (process.platform !== 'win32') {
|
|
56
|
+
return { installed: false, reason: 'installFromRelease is Windows-only' };
|
|
57
|
+
}
|
|
58
|
+
if (!have('curl')) {
|
|
59
|
+
return { installed: false, reason: 'curl.exe not found (needs Windows 10 1803 or later)' };
|
|
60
|
+
}
|
|
61
|
+
if (!have('tar')) {
|
|
62
|
+
return { installed: false, reason: 'tar.exe not found (needs Windows 10 1803 or later)' };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const dir = installDir();
|
|
66
|
+
const url = `https://github.com/${repo}/releases/latest/download/${asset}`;
|
|
67
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-win-'));
|
|
68
|
+
const zip = path.join(tmp, asset);
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
72
|
+
|
|
73
|
+
log(`[win] downloading ${asset}…`);
|
|
74
|
+
// -L to follow the release redirect, -f so a 404 is an error rather than
|
|
75
|
+
// an HTML error page written to disk and then "extracted".
|
|
76
|
+
const dl = spawnSync('curl', ['-fsSL', '-o', zip, url], { stdio: 'inherit' });
|
|
77
|
+
if (dl.status !== 0) {
|
|
78
|
+
return { installed: false, reason: `download failed (curl exit ${dl.status}) — ${url}` };
|
|
79
|
+
}
|
|
80
|
+
if (!fs.existsSync(zip) || fs.statSync(zip).size === 0) {
|
|
81
|
+
return { installed: false, reason: `downloaded file is empty — ${url}` };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
log('[win] extracting…');
|
|
85
|
+
const ex = spawnSync('tar', ['-xf', zip, '-C', dir], { stdio: 'inherit' });
|
|
86
|
+
if (ex.status !== 0) {
|
|
87
|
+
return { installed: false, reason: `extract failed (tar exit ${ex.status})` };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Archives vary: some put the binary at the root, some inside a folder.
|
|
91
|
+
const binPath = findBinary(dir, binName);
|
|
92
|
+
if (!binPath) {
|
|
93
|
+
return { installed: false, reason: `${binName} not found in ${asset} after extraction` };
|
|
94
|
+
}
|
|
95
|
+
// Flatten so the binary sits directly in dir and one PATH entry covers it.
|
|
96
|
+
const finalPath = path.join(dir, binName);
|
|
97
|
+
if (binPath !== finalPath) fs.copyFileSync(binPath, finalPath);
|
|
98
|
+
|
|
99
|
+
// Make it usable in THIS process so a follow-up isInstalled() check passes
|
|
100
|
+
// without the user restarting their shell.
|
|
101
|
+
const sep = path.delimiter;
|
|
102
|
+
if (!(process.env.PATH || '').split(sep).includes(dir)) {
|
|
103
|
+
process.env.PATH = `${dir}${sep}${process.env.PATH || ''}`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return { installed: true, dir, binPath: finalPath, onPath: isOnUserPath(dir) };
|
|
107
|
+
} finally {
|
|
108
|
+
try { fs.rmSync(tmp, { recursive: true, force: true }); } catch { /* temp dir */ }
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Depth-limited search — these archives are shallow; this avoids walking a tree. */
|
|
113
|
+
function findBinary(root, binName, depth = 0) {
|
|
114
|
+
if (depth > 3) return null;
|
|
115
|
+
let entries;
|
|
116
|
+
try {
|
|
117
|
+
entries = fs.readdirSync(root, { withFileTypes: true });
|
|
118
|
+
} catch {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
for (const e of entries) {
|
|
122
|
+
const p = path.join(root, e.name);
|
|
123
|
+
if (e.isFile() && e.name.toLowerCase() === binName.toLowerCase()) return p;
|
|
124
|
+
}
|
|
125
|
+
for (const e of entries) {
|
|
126
|
+
if (e.isDirectory()) {
|
|
127
|
+
const found = findBinary(path.join(root, e.name), binName, depth + 1);
|
|
128
|
+
if (found) return found;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Whether the directory is on the user's PERSISTENT PATH, not just this
|
|
136
|
+
* process's. Checking process.env would always say yes right after we prepend
|
|
137
|
+
* it, which would wrongly tell the user there is nothing left to do.
|
|
138
|
+
*/
|
|
139
|
+
function isOnUserPath(dir) {
|
|
140
|
+
try {
|
|
141
|
+
const out = execFileSync(
|
|
142
|
+
'reg',
|
|
143
|
+
['query', 'HKCU\\Environment', '/v', 'Path'],
|
|
144
|
+
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }
|
|
145
|
+
);
|
|
146
|
+
return out.toLowerCase().includes(dir.toLowerCase());
|
|
147
|
+
} catch {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** The exact command to persist the directory on PATH, for the user to run. */
|
|
153
|
+
function setxHint(dir) {
|
|
154
|
+
return `setx PATH "%PATH%;${dir}"`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
module.exports = { installFromRelease, installDir, isOnUserPath, setxHint, findBinary };
|
package/scripts/rtk/index.js
CHANGED
|
@@ -80,13 +80,37 @@ function getVersion() {
|
|
|
80
80
|
// ── Install RTK binary ───────────────────────────────────────────────────────
|
|
81
81
|
// Mac: prefer `brew install rtk` if brew is on PATH (fastest, cached).
|
|
82
82
|
// Otherwise: pipe the official install script through sh (installs to ~/.local/bin).
|
|
83
|
-
// Windows:
|
|
83
|
+
// Windows: downloads the published .zip from GitHub releases.
|
|
84
84
|
function install({ log = () => {} } = {}) {
|
|
85
85
|
if (isInstalled()) {
|
|
86
86
|
return { installed: false, skipped: true, reason: 'already installed', version: getVersion() };
|
|
87
87
|
}
|
|
88
88
|
if (os.platform() === 'win32') {
|
|
89
|
-
|
|
89
|
+
// RTK publishes a Windows build; ECC just never downloaded it and told the
|
|
90
|
+
// user to do it by hand. Only x86_64 is published, which is fine on arm64
|
|
91
|
+
// Windows because it runs x64 binaries under emulation.
|
|
92
|
+
const win = require('../lib/win-install.js');
|
|
93
|
+
const r = win.installFromRelease({
|
|
94
|
+
repo: 'rtk-ai/rtk',
|
|
95
|
+
asset: 'rtk-x86_64-pc-windows-msvc.zip',
|
|
96
|
+
binName: 'rtk.exe',
|
|
97
|
+
log,
|
|
98
|
+
});
|
|
99
|
+
if (!r.installed) {
|
|
100
|
+
return { installed: false, skipped: true, reason: `${r.reason} — or install by hand: https://github.com/rtk-ai/rtk/releases` };
|
|
101
|
+
}
|
|
102
|
+
if (!isInstalled()) {
|
|
103
|
+
return { installed: false, skipped: true, reason: `installed to ${r.dir} but not runnable from PATH` };
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
installed: true,
|
|
107
|
+
method: 'github-release',
|
|
108
|
+
version: getVersion(),
|
|
109
|
+
dir: r.dir,
|
|
110
|
+
// Reported so the caller can tell the user what is still needed. PATH is
|
|
111
|
+
// not edited for them — that is their environment to change.
|
|
112
|
+
pathHint: r.onPath ? null : win.setxHint(r.dir),
|
|
113
|
+
};
|
|
90
114
|
}
|
|
91
115
|
|
|
92
116
|
// Try Homebrew first on macOS.
|