kodelyth-ecc 2.22.0 → 2.23.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/CHANGELOG.md +108 -0
- package/README.md +16 -12
- package/VERSION +1 -1
- package/bin/kodelyth-ecc.js +43 -1
- package/package.json +12 -3
- package/scripts/codebase/index.js +26 -1
- package/scripts/doctor-health.js +7 -6
- package/scripts/lib/win-install.js +157 -0
- package/scripts/rtk/index.js +26 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,114 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to Kodelyth ECC are documented here.
|
|
4
4
|
|
|
5
|
+
## v2.23.1 — the shop window said the wrong numbers (October 2026)
|
|
6
|
+
|
|
7
|
+
No change to the toolkit. This exists because npm metadata only refreshes when
|
|
8
|
+
you publish, and three public-facing surfaces had gone stale.
|
|
9
|
+
|
|
10
|
+
Measured before changing anything: **9,233 npm downloads in the last 30 days
|
|
11
|
+
against 11 GitHub stars** — roughly 840:1, where a healthy project runs 50:1 to
|
|
12
|
+
200:1. Discovery is not the constraint; thousands install this and nothing
|
|
13
|
+
converts them.
|
|
14
|
+
|
|
15
|
+
### Corrected
|
|
16
|
+
|
|
17
|
+
| Surface | Said | Actual |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| `package.json` description | 194 skills, 97 commands | 196, 103 |
|
|
20
|
+
| GitHub repo description | 102 commands, 22+ hooks | 103, **44** |
|
|
21
|
+
| README badges | **373 tests**, Skills-194 | **647 tests**, 196 |
|
|
22
|
+
|
|
23
|
+
Wrong numbers in the first thing anyone sees read as abandoned. Both
|
|
24
|
+
descriptions now also name Windows, which became a real differentiator in 2.23.0
|
|
25
|
+
and went unmentioned.
|
|
26
|
+
|
|
27
|
+
### Asked, once
|
|
28
|
+
|
|
29
|
+
The end of a successful install is the highest-intent moment there is, and an
|
|
30
|
+
all-green `doctor` is the other. Both were silent. Both now print a single line.
|
|
31
|
+
|
|
32
|
+
It is gated hard, because an ask that repeats is an annoyance:
|
|
33
|
+
|
|
34
|
+
- shown **once per machine**, via a marker beside the other ECC state
|
|
35
|
+
- never after a failed install
|
|
36
|
+
- never when `doctor` reports a warning or a failure
|
|
37
|
+
- wrapped so it can never break an install
|
|
38
|
+
|
|
39
|
+
Someone wiring up four IDEs is asked once, not four times.
|
|
40
|
+
|
|
41
|
+
### Keywords
|
|
42
|
+
|
|
43
|
+
Strong on the long tail (`devil-mode`, `bm25`, `intent-routing`), missing the
|
|
44
|
+
broad terms people actually type. Added `claude`, `agent`, `agents`, `ai`,
|
|
45
|
+
`subagents`, `prompt-engineering`, `ai-assistant`, `code-review`, `windows`.
|
|
46
|
+
41 to 50.
|
|
47
|
+
|
|
48
|
+
**647 tests passing.**
|
|
49
|
+
|
|
50
|
+
## v2.23.0 — Windows has no manual step left (October 2026)
|
|
51
|
+
|
|
52
|
+
2.22.0 made the Windows install work but left two components needing a manual
|
|
53
|
+
download: RTK and the codebase graph. That turned out to be a gap in ECC, not a
|
|
54
|
+
limitation upstream — **both projects publish Windows builds** and ECC simply
|
|
55
|
+
never fetched them:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
rtk-x86_64-pc-windows-msvc.zip
|
|
59
|
+
codebase-memory-mcp-windows-amd64.zip / -arm64.zip
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Both now install automatically. Windows is at parity with macOS and Linux.
|
|
63
|
+
|
|
64
|
+
### How it works
|
|
65
|
+
|
|
66
|
+
A shared `scripts/lib/win-install.js`, used by both. It pulls from GitHub's
|
|
67
|
+
`releases/latest/download/<asset>` URL, which always redirects to the newest
|
|
68
|
+
release — no API call, so no token and no rate limit.
|
|
69
|
+
|
|
70
|
+
Download and extraction use `curl.exe` and `tar.exe`. Both ship with Windows 10
|
|
71
|
+
1803 and later, and `tar` there is bsdtar, which handles zip. That avoids
|
|
72
|
+
PowerShell quoting entirely. Both are probed before use, so an older machine
|
|
73
|
+
gets a clear message instead of a spawn error.
|
|
74
|
+
|
|
75
|
+
Architecture comes from `process.arch`: the codebase graph has a native arm64
|
|
76
|
+
build, while RTK publishes x86_64 only — fine on arm64 Windows, which runs x64
|
|
77
|
+
under emulation.
|
|
78
|
+
|
|
79
|
+
### PATH is yours, not ours
|
|
80
|
+
|
|
81
|
+
Binaries land in `%LOCALAPPDATA%\Kodelyth\bin`. ECC does **not** edit your PATH.
|
|
82
|
+
It prints the one command that does:
|
|
83
|
+
|
|
84
|
+
```powershell
|
|
85
|
+
setx PATH "%PATH%;%LOCALAPPDATA%\Kodelyth\bin"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
This mirrors the POSIX path, which reports `~/.local/bin` rather than writing to
|
|
89
|
+
a shell rc. Whether the directory is already persistent is read from
|
|
90
|
+
`HKCU\Environment`, not from `process.env` — the latter would always say yes
|
|
91
|
+
right after the installer prepends it for its own process, and would wrongly
|
|
92
|
+
tell you there is nothing left to do.
|
|
93
|
+
|
|
94
|
+
### Verified, not asserted
|
|
95
|
+
|
|
96
|
+
The Windows CI job now installs for real and asserts both binaries exist and
|
|
97
|
+
run:
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
install dir: C:\Users\runneradmin\AppData\Local\Kodelyth\bin
|
|
101
|
+
rtk.exe 10,285,056 bytes
|
|
102
|
+
rtk 0.51.0
|
|
103
|
+
codebase-memory-mcp 0.11.0
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
`doctor`'s Windows hints changed too. They previously said "no Windows installer
|
|
107
|
+
yet" and pointed at a release page; the install command is now correct on every
|
|
108
|
+
platform, and the hint names the PATH step rather than implying the command
|
|
109
|
+
alone finishes the job.
|
|
110
|
+
|
|
111
|
+
**647 tests passing.**
|
|
112
|
+
|
|
5
113
|
## v2.22.0 — Windows actually works now (September 2026)
|
|
6
114
|
|
|
7
115
|
The Windows install was broken in three separate ways. Windows CI had been green
|
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
[](https://www.npmjs.com/package/kodelyth-ecc)
|
|
14
14
|
[](https://github.com/sifxprime/kodelyth-ecc/stargazers)
|
|
15
15
|
[](LICENSE)
|
|
16
|
-

|
|
17
17
|

|
|
18
18
|

|
|
19
19
|

|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|

|
|
22
22
|

|
|
23
23
|

|
|
24
|
-

|
|
25
25
|

|
|
26
26
|

|
|
27
27
|

|
|
@@ -205,18 +205,22 @@ PowerShell 5.1 that ships with the OS. The installer tries `pwsh` first and fall
|
|
|
205
205
|
back automatically, and it passes `-ExecutionPolicy Bypass` for its own process
|
|
206
206
|
only, so you do not need to change your machine's execution policy.
|
|
207
207
|
|
|
208
|
-
**
|
|
209
|
-
15 rules, all 44 hook entries registered into
|
|
210
|
-
(16 tools),
|
|
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**.
|
|
211
212
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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:
|
|
215
217
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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.
|
|
220
224
|
|
|
221
225
|
**Verify:**
|
|
222
226
|
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.
|
|
1
|
+
2.23.1
|
package/bin/kodelyth-ecc.js
CHANGED
|
@@ -724,7 +724,13 @@ if (args[0] === 'doctor') {
|
|
|
724
724
|
w('─'.repeat(60));
|
|
725
725
|
const s = report.summary;
|
|
726
726
|
w(`${s.pass} pass · ${s.warn} warn · ${s.fail} fail (of ${s.total} checks)`);
|
|
727
|
-
if (s.fail === 0 && s.warn === 0)
|
|
727
|
+
if (s.fail === 0 && s.warn === 0) {
|
|
728
|
+
w('\x1b[32mEverything is wired and working.\x1b[0m');
|
|
729
|
+
// An all-green doctor is the other honest moment to ask. Shares the install
|
|
730
|
+
// marker, so nobody is asked twice on the same machine, and it stays silent
|
|
731
|
+
// when anything is warning or failing — that is not the time.
|
|
732
|
+
maybeAskForAStar(0);
|
|
733
|
+
}
|
|
728
734
|
else if (s.fail === 0) w('\x1b[33mWorking, with optional features not yet enabled (warnings above).\x1b[0m');
|
|
729
735
|
else w('\x1b[31mSome subsystems are broken — see fixes above.\x1b[0m');
|
|
730
736
|
w('');
|
|
@@ -1817,6 +1823,33 @@ if (args.includes('--version') || args.includes('-v')) {
|
|
|
1817
1823
|
// Every block below already guards itself and reports a reason when it cannot
|
|
1818
1824
|
// proceed, which is what makes sharing it safe: on Windows the parts that work
|
|
1819
1825
|
// now run, and the parts that cannot say so instead of vanishing.
|
|
1826
|
+
/**
|
|
1827
|
+
* One quiet line at the end of a successful install.
|
|
1828
|
+
*
|
|
1829
|
+
* This is the highest-intent moment there is — someone has just watched the
|
|
1830
|
+
* whole thing land and work — and it was going unused. It is deliberately shown
|
|
1831
|
+
* ONCE per machine, not on every target install: somebody wiring up four IDEs
|
|
1832
|
+
* should not be asked four times. The marker lives beside the other ECC state.
|
|
1833
|
+
*
|
|
1834
|
+
* Never shown on a failed install, and never blocks or prompts.
|
|
1835
|
+
*/
|
|
1836
|
+
function maybeAskForAStar(status) {
|
|
1837
|
+
if (status !== 0) return;
|
|
1838
|
+
try {
|
|
1839
|
+
const dir = path.join(os.homedir(), '.kodelythecc');
|
|
1840
|
+
const marker = path.join(dir, 'star-asked');
|
|
1841
|
+
if (fs.existsSync(marker)) return;
|
|
1842
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1843
|
+
fs.writeFileSync(marker, new Date().toISOString() + '\n');
|
|
1844
|
+
|
|
1845
|
+
process.stdout.write('\n');
|
|
1846
|
+
process.stdout.write(' If ECC saved you time today, a star helps others find it:\n');
|
|
1847
|
+
process.stdout.write(' https://github.com/sifxprime/kodelyth-ecc\n\n');
|
|
1848
|
+
} catch {
|
|
1849
|
+
// A star request is never worth failing an install over.
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1820
1853
|
function runPostInstall(status, args) {
|
|
1821
1854
|
// Post-install: auto-register ECC's own MCP server in Claude Code + Desktop.
|
|
1822
1855
|
if (status === 0 && !args.includes('--no-mcp-register')) {
|
|
@@ -1850,6 +1883,13 @@ function runPostInstall(status, args) {
|
|
|
1850
1883
|
const en = rtk.enableFor(target, { log: () => {} });
|
|
1851
1884
|
if (en.enabled) {
|
|
1852
1885
|
w(` ✓ RTK ${(rtk.getVersion() || '').replace(/^rtk /,'')} — wired for ${target}`);
|
|
1886
|
+
// On Windows the binary goes to %LOCALAPPDATA%\Kodelyth\bin. ECC does
|
|
1887
|
+
// not edit PATH for the user, so say plainly what is left to do —
|
|
1888
|
+
// otherwise rtk works during this install and vanishes next shell.
|
|
1889
|
+
if (inst.pathHint) {
|
|
1890
|
+
w(` ! ${inst.dir} is not on your PATH yet`);
|
|
1891
|
+
w(` run once, then reopen your terminal: ${inst.pathHint}`);
|
|
1892
|
+
}
|
|
1853
1893
|
w(` ✓ Restart your AI tool to activate. 60-90% token savings on shell commands.`);
|
|
1854
1894
|
} else {
|
|
1855
1895
|
w(` · skipped: ${en.reason}`);
|
|
@@ -1976,6 +2016,7 @@ if (isWin) {
|
|
|
1976
2016
|
process.exit(1);
|
|
1977
2017
|
}
|
|
1978
2018
|
runPostInstall(result.status, args);
|
|
2019
|
+
maybeAskForAStar(result.status);
|
|
1979
2020
|
process.exit(result.status ?? 1);
|
|
1980
2021
|
} else {
|
|
1981
2022
|
try { require(path.join(ROOT, 'scripts', 'migrate-legacy.js')).main(); } catch {}
|
|
@@ -1988,5 +2029,6 @@ if (isWin) {
|
|
|
1988
2029
|
const result = spawnSync('bash', [sh, ...args], { stdio: 'inherit', shell: false });
|
|
1989
2030
|
|
|
1990
2031
|
runPostInstall(result.status, args);
|
|
2032
|
+
maybeAskForAStar(result.status);
|
|
1991
2033
|
process.exit(result.status ?? 1);
|
|
1992
2034
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kodelyth-ecc",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Production-grade AI coding toolkit — 70 agents (incl. devil-mode adversarial crew),
|
|
3
|
+
"version": "2.23.1",
|
|
4
|
+
"description": "Production-grade AI coding toolkit — 70 agents (incl. devil-mode adversarial crew), 196 skills, 103 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 on macOS, Linux and Windows 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",
|
|
7
7
|
"repository": {
|
|
@@ -54,7 +54,16 @@
|
|
|
54
54
|
"local-memory",
|
|
55
55
|
"hooks",
|
|
56
56
|
"slash-commands",
|
|
57
|
-
"productivity"
|
|
57
|
+
"productivity",
|
|
58
|
+
"claude",
|
|
59
|
+
"agent",
|
|
60
|
+
"agents",
|
|
61
|
+
"ai",
|
|
62
|
+
"subagents",
|
|
63
|
+
"prompt-engineering",
|
|
64
|
+
"ai-assistant",
|
|
65
|
+
"code-review",
|
|
66
|
+
"windows"
|
|
58
67
|
],
|
|
59
68
|
"bin": {
|
|
60
69
|
"kodelyth-ecc": "bin/kodelyth-ecc.js",
|
|
@@ -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,10 +115,11 @@ function checkPromptInjectionGuard() {
|
|
|
115
115
|
return { status: PASS, detail: `prompt-injection guard active (mode=${mode})` };
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
//
|
|
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.
|
|
122
123
|
const IS_WIN = process.platform === 'win32';
|
|
123
124
|
|
|
124
125
|
function checkRtk() {
|
|
@@ -126,7 +127,7 @@ function checkRtk() {
|
|
|
126
127
|
status: WARN,
|
|
127
128
|
detail: 'RTK not installed (input savings inactive)',
|
|
128
129
|
fix: IS_WIN
|
|
129
|
-
? '
|
|
130
|
+
? 'kodelythecc rtk install (installs to %LOCALAPPDATA%\\Kodelyth\\bin — add it to PATH)'
|
|
130
131
|
: 'kodelythecc rtk install',
|
|
131
132
|
};
|
|
132
133
|
try {
|
|
@@ -146,7 +147,7 @@ function checkCodebaseGraph() {
|
|
|
146
147
|
status: WARN,
|
|
147
148
|
detail: 'codebase graph not installed (optional)',
|
|
148
149
|
fix: IS_WIN
|
|
149
|
-
? '
|
|
150
|
+
? 'kodelythecc codebase install (installs to %LOCALAPPDATA%\\Kodelyth\\bin — add it to PATH)'
|
|
150
151
|
: 'kodelythecc codebase install',
|
|
151
152
|
};
|
|
152
153
|
return { status: PASS, detail: 'codebase-memory-mcp installed' };
|
|
@@ -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.
|