@aicraftalchemy/aca 0.0.33 → 0.0.34
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/cli-wrapper.cjs +64 -0
- package/install.cjs +6 -0
- package/package.json +1 -1
package/cli-wrapper.cjs
CHANGED
|
@@ -12,6 +12,11 @@ const fs = require('node:fs')
|
|
|
12
12
|
const binDir = path.join(__dirname, 'bin')
|
|
13
13
|
const isWin = process.platform === 'win32'
|
|
14
14
|
const bin = path.join(binDir, isWin ? 'aca.exe' : 'aca')
|
|
15
|
+
// Version of the binary currently in bin/ — written by install.cjs on install
|
|
16
|
+
// and kept current by applyStagedUpdate below. Lets the launcher detect a
|
|
17
|
+
// stale binary without paying a --version spawn on every start.
|
|
18
|
+
const stampFile = path.join(binDir, '.aca-bin-version')
|
|
19
|
+
const pkgVersion = require('./package.json').version
|
|
15
20
|
|
|
16
21
|
/**
|
|
17
22
|
* Apply a staged auto-update. The running binary downloads a new version to
|
|
@@ -25,6 +30,10 @@ const bin = path.join(binDir, isWin ? 'aca.exe' : 'aca')
|
|
|
25
30
|
function applyStagedUpdate() {
|
|
26
31
|
const marker = path.join(binDir, '.aca-pending-version')
|
|
27
32
|
if (!fs.existsSync(marker)) return
|
|
33
|
+
// The marker holds the staged version string (written by autoUpdater);
|
|
34
|
+
// capture it before the swap so the stamp can track what bin/ now contains.
|
|
35
|
+
let stagedVersion = null
|
|
36
|
+
try { stagedVersion = fs.readFileSync(marker, 'utf8').trim() } catch {}
|
|
28
37
|
const acaPending = path.join(binDir, isWin ? 'aca.exe.pending' : 'aca.pending')
|
|
29
38
|
if (!fs.existsSync(acaPending)) {
|
|
30
39
|
// Marker without a binary — stale/partial. Clear it and move on.
|
|
@@ -46,6 +55,9 @@ function applyStagedUpdate() {
|
|
|
46
55
|
swap(isWin ? 'aca.exe' : 'aca')
|
|
47
56
|
swap(isWin ? 'rg.exe' : 'rg')
|
|
48
57
|
try { fs.rmSync(marker, { force: true }) } catch {}
|
|
58
|
+
if (stagedVersion) {
|
|
59
|
+
try { fs.writeFileSync(stampFile, stagedVersion) } catch {}
|
|
60
|
+
}
|
|
49
61
|
} catch (err) {
|
|
50
62
|
// Swap failed (e.g. transient FS error) — leave the pending files in place
|
|
51
63
|
// so the next launch retries; fall through and run the current binary.
|
|
@@ -84,10 +96,62 @@ function selfHealBinary() {
|
|
|
84
96
|
}
|
|
85
97
|
}
|
|
86
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Refresh a STALE binary. On upgrade npm replaces the package directory, but
|
|
101
|
+
* when install scripts are blocked (allowScripts policy, `--ignore-scripts`)
|
|
102
|
+
* the postinstall never runs — and a surviving old bin/ means `aca` silently
|
|
103
|
+
* keeps launching the previous version forever. The stamp written by
|
|
104
|
+
* install.cjs (and kept current by applyStagedUpdate) says what bin/ holds;
|
|
105
|
+
* if it's OLDER than package.json, re-run the installer. Never downgrade: the
|
|
106
|
+
* auto-updater may legitimately have advanced the binary past the npm
|
|
107
|
+
* version. Best-effort — if the refresh fails (e.g. offline), warn and run
|
|
108
|
+
* the existing binary rather than dead-ending the user.
|
|
109
|
+
*/
|
|
110
|
+
function refreshStaleBinary() {
|
|
111
|
+
let stamped = null
|
|
112
|
+
try { stamped = fs.readFileSync(stampFile, 'utf8').trim() } catch {}
|
|
113
|
+
// A malformed stamp would compare as NaN (treated as equal) and block
|
|
114
|
+
// refresh forever — treat it as missing so the probe below recovers it.
|
|
115
|
+
if (stamped && !/^\d+\.\d+\.\d+$/.test(stamped)) stamped = null
|
|
116
|
+
if (!stamped) {
|
|
117
|
+
// Pre-stamp install: ask the binary itself once and record the answer.
|
|
118
|
+
const probe = spawnSync(bin, ['--version'], { encoding: 'utf8', timeout: 15000 })
|
|
119
|
+
const m = /(\d+\.\d+\.\d+)/.exec(probe.stdout || '')
|
|
120
|
+
if (m) {
|
|
121
|
+
stamped = m[1]
|
|
122
|
+
try { fs.writeFileSync(stampFile, stamped) } catch {}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const cmpVersions = (a, b) => {
|
|
126
|
+
const pa = a.split('.').map(Number)
|
|
127
|
+
const pb = b.split('.').map(Number)
|
|
128
|
+
for (let i = 0; i < 3; i++) {
|
|
129
|
+
const d = (pa[i] || 0) - (pb[i] || 0)
|
|
130
|
+
if (d) return d
|
|
131
|
+
}
|
|
132
|
+
return 0
|
|
133
|
+
}
|
|
134
|
+
if (stamped && cmpVersions(stamped, pkgVersion) >= 0) return
|
|
135
|
+
const installer = path.join(__dirname, 'install.cjs')
|
|
136
|
+
if (!fs.existsSync(installer)) return
|
|
137
|
+
console.error(
|
|
138
|
+
`ACA: refreshing binary ${stamped ? `v${stamped}` : '(unknown version)'} → v${pkgVersion} (install scripts were skipped)…`,
|
|
139
|
+
)
|
|
140
|
+
const r = spawnSync(process.execPath, [installer], { stdio: ['ignore', 2, 2] })
|
|
141
|
+
if (r.error || r.status !== 0) {
|
|
142
|
+
console.error(
|
|
143
|
+
'ACA: binary refresh failed; continuing with the existing binary.' +
|
|
144
|
+
'\nRetry with: npm install -g @aicraftalchemy/aca',
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
87
149
|
applyStagedUpdate()
|
|
88
150
|
|
|
89
151
|
if (!fs.existsSync(bin)) {
|
|
90
152
|
selfHealBinary()
|
|
153
|
+
} else {
|
|
154
|
+
refreshStaleBinary()
|
|
91
155
|
}
|
|
92
156
|
|
|
93
157
|
const result = spawnSync(bin, process.argv.slice(2), { stdio: 'inherit' })
|
package/install.cjs
CHANGED
|
@@ -100,6 +100,12 @@ async function main() {
|
|
|
100
100
|
writeReplacing(dest, bin)
|
|
101
101
|
if (process.platform !== 'win32') fs.chmodSync(dest, 0o755)
|
|
102
102
|
|
|
103
|
+
// Stamp the installed version. cli-wrapper compares this against its own
|
|
104
|
+
// package.json to detect a STALE binary — the case where npm upgraded the
|
|
105
|
+
// package but skipped this script (allowScripts policy / --ignore-scripts),
|
|
106
|
+
// leaving an old binary that would otherwise run forever.
|
|
107
|
+
fs.writeFileSync(path.join(binDir, '.aca-bin-version'), VERSION)
|
|
108
|
+
|
|
103
109
|
// Also fetch the sibling ripgrep binary. `bun build --compile` does not embed
|
|
104
110
|
// ripgrep, so ACA resolves this `rg` next to itself at runtime — it powers
|
|
105
111
|
// Grep, Glob, and @-mention file search. Best-effort: a failure here must not
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aicraftalchemy/aca",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.34",
|
|
4
4
|
"description": "ACA — agentic coding CLI. Any model, any provider (Claude, Gemini, OpenAI, NVIDIA, Groq, Cerebras, OpenRouter, Hugging Face). Developed by Aicraftalchemy.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"bin": {
|