create-byan-agent 2.29.1 → 2.29.2
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
CHANGED
|
@@ -9,6 +9,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
9
9
|
|
|
10
10
|
## [Unreleased]
|
|
11
11
|
|
|
12
|
+
## [2.29.2] - 2026-06-23
|
|
13
|
+
|
|
14
|
+
### Fixed - RTK optional install: fail-proof, off-PATH, shell-aware
|
|
15
|
+
|
|
16
|
+
The 2.29.x RTK installer could hang silently and mis-report a successful build.
|
|
17
|
+
|
|
18
|
+
- **Bounded + visible.** The delegated install now runs with a per-strategy
|
|
19
|
+
timeout (brew/script 5min, cargo 20min; override via `BYAN_RTK_TIMEOUT_MS`,
|
|
20
|
+
positive integers only) and inherited stdio, so progress streams live instead
|
|
21
|
+
of a frozen line. Inherited stdio also sidesteps execSync's 1MB maxBuffer cap a
|
|
22
|
+
verbose build would blow.
|
|
23
|
+
- **Off-PATH resolution.** `cargo install` drops the binary in `~/.cargo/bin`,
|
|
24
|
+
which a Debian non-login PATH does not include — the install succeeded but
|
|
25
|
+
`rtk --version` reported "unverified". The installer now resolves rtk across
|
|
26
|
+
known dirs (PATH, `~/.cargo/bin`, `~/.local/bin`, `/usr/local/bin`, brew prefix,
|
|
27
|
+
`CARGO_HOME`), verifies + wires the hook by the resolved path, and prints a
|
|
28
|
+
shell-correct PATH hint (`fish_add_path` on fish, `export` on bash/zsh,
|
|
29
|
+
`$env:PATH` on Windows).
|
|
30
|
+
- **Prebuilt preferred.** Strategy order is now brew > script > cargo: the
|
|
31
|
+
prebuilt-binary script (pinned to the immutable tag ref, which SHA-256-verifies
|
|
32
|
+
the binary per refs/tags/v0.42.4/install.sh) is fast and PATH-stable; cargo
|
|
33
|
+
(from-source, off-PATH, slow) becomes the last-resort fallback.
|
|
34
|
+
- **Stays graceful.** Hardened the no-throw contract: a HOME-less environment
|
|
35
|
+
(`os.homedir()` throwing) no longer propagates out of `setup-rtk.js`.
|
|
36
|
+
|
|
37
|
+
Reviewed by an adversarial workflow (correctness + supply-chain + mantras) plus a
|
|
38
|
+
compliance pass; 2375/2375 tests green (49 dedicated to RTK).
|
|
39
|
+
|
|
12
40
|
## [2.29.1] - 2026-06-23
|
|
13
41
|
|
|
14
42
|
### Fixed - Republish (the 2.29.0 npm tarball was missing)
|
|
@@ -1383,11 +1383,14 @@ async function install(options = {}) {
|
|
|
1383
1383
|
},
|
|
1384
1384
|
]);
|
|
1385
1385
|
if (proceed) {
|
|
1386
|
+
console.log(chalk.gray(' Installing... progress streams below. The cargo fallback compiles from source (can take minutes); Ctrl+C is safe — BYAN is already installed.'));
|
|
1386
1387
|
const r = setupRtkIntegration({ log: (m) => console.log(chalk.gray(' ' + m)) });
|
|
1387
1388
|
if (r.synced) {
|
|
1388
1389
|
console.log(chalk.green(` ✓ rtk ready (${r.installedVia}, v${r.version || '?'}) — restart Claude Code to activate`));
|
|
1390
|
+
if (r.pathHint) console.log(chalk.yellow(` ⚠ rtk is not on your PATH — add it: ${r.pathHint}`));
|
|
1389
1391
|
} else {
|
|
1390
|
-
console.log(chalk.yellow(` ⚠ rtk not wired (${r.reason}) — BYAN unaffected`));
|
|
1392
|
+
console.log(chalk.yellow(` ⚠ rtk not wired (${r.reason}) — BYAN unaffected; re-run \`npm run setup-rtk\` anytime`));
|
|
1393
|
+
if (r.pathHint) console.log(chalk.yellow(` ⚠ rtk found off-PATH — add it then re-run: ${r.pathHint}`));
|
|
1391
1394
|
}
|
|
1392
1395
|
} else {
|
|
1393
1396
|
console.log(chalk.gray(' rtk skipped — run `npm run setup-rtk` anytime to enable.'));
|
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
const { execSync } = require('child_process');
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const os = require('os');
|
|
16
|
+
const path = require('path');
|
|
14
17
|
|
|
15
18
|
/**
|
|
16
19
|
* commandExists(cmd) -> boolean. Is an executable resolvable on PATH?
|
|
@@ -26,6 +29,70 @@ function commandExists(cmd, { run = execSync, platform = process.platform } = {}
|
|
|
26
29
|
}
|
|
27
30
|
}
|
|
28
31
|
|
|
32
|
+
/**
|
|
33
|
+
* safeHomedir() -> the home directory, or '' if it cannot be determined. WHY:
|
|
34
|
+
* os.homedir() THROWS on an env with no $HOME/$USERPROFILE AND no passwd entry
|
|
35
|
+
* for the effective uid (distroless / random-uid containers). An optional install
|
|
36
|
+
* helper must never throw on that — it degrades to "no home dir to scan".
|
|
37
|
+
*/
|
|
38
|
+
function safeHomedir() {
|
|
39
|
+
try {
|
|
40
|
+
return os.homedir();
|
|
41
|
+
} catch {
|
|
42
|
+
return '';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* knownBinDirs() -> ordered list of directories where installers commonly drop a
|
|
48
|
+
* binary that is NOT on a non-login shell's PATH. The canonical case: `cargo
|
|
49
|
+
* install` lands in ~/.cargo/bin, which Debian's apt-packaged cargo does NOT add
|
|
50
|
+
* to PATH — so a freshly built binary is present yet invisible to a bare probe.
|
|
51
|
+
* fs/env/home are injectable for tests. `home` defaults via safeHomedir so a
|
|
52
|
+
* HOME-less environment yields a shorter scan instead of a throw.
|
|
53
|
+
*/
|
|
54
|
+
function knownBinDirs({ env = process.env, home = safeHomedir(), platform = process.platform } = {}) {
|
|
55
|
+
const dirs = [];
|
|
56
|
+
if (env && env.CARGO_HOME) dirs.push(path.join(env.CARGO_HOME, 'bin'));
|
|
57
|
+
dirs.push(path.join(home, '.cargo', 'bin')); // cargo install
|
|
58
|
+
dirs.push(path.join(home, '.local', 'bin')); // pip/pipx + many curl scripts
|
|
59
|
+
if (platform === 'win32') {
|
|
60
|
+
if (env && env.LOCALAPPDATA) dirs.push(path.join(env.LOCALAPPDATA, 'Programs'));
|
|
61
|
+
} else {
|
|
62
|
+
dirs.push('/usr/local/bin'); // common script target + brew (intel mac)
|
|
63
|
+
dirs.push('/opt/homebrew/bin'); // brew (apple silicon)
|
|
64
|
+
dirs.push('/usr/bin');
|
|
65
|
+
}
|
|
66
|
+
return dirs;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* resolveBinary(name) -> the command to invoke the executable, or null if it
|
|
71
|
+
* cannot be located. Returns the BARE name when it is on PATH (let the shell
|
|
72
|
+
* resolve it), else an ABSOLUTE path found in a knownBinDir. WHY: an optional
|
|
73
|
+
* native install can succeed yet leave the binary off PATH; callers must verify
|
|
74
|
+
* and wire it by its real location, not declare failure. All I/O is injectable.
|
|
75
|
+
*/
|
|
76
|
+
function resolveBinary(name, {
|
|
77
|
+
has = commandExists,
|
|
78
|
+
existsSync = fs.existsSync,
|
|
79
|
+
env = process.env,
|
|
80
|
+
home = safeHomedir(),
|
|
81
|
+
platform = process.platform,
|
|
82
|
+
} = {}) {
|
|
83
|
+
if (has(name)) return name;
|
|
84
|
+
const exe = platform === 'win32' ? `${name}.exe` : name;
|
|
85
|
+
for (const dir of knownBinDirs({ env, home, platform })) {
|
|
86
|
+
const full = path.join(dir, exe);
|
|
87
|
+
try {
|
|
88
|
+
if (existsSync(full)) return full;
|
|
89
|
+
} catch {
|
|
90
|
+
// a stat error on one candidate must not abort the scan
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
29
96
|
/**
|
|
30
97
|
* detectPlatform() -> 'mac' | 'windows' | 'linux'. Coarse bucket for choosing an
|
|
31
98
|
* install strategy. Keeps the platform string in ONE place.
|
|
@@ -47,4 +114,4 @@ function firstAvailable(candidates, { has = commandExists } = {}) {
|
|
|
47
114
|
return candidates.find((c) => c && c.tool && has(c.tool)) || null;
|
|
48
115
|
}
|
|
49
116
|
|
|
50
|
-
module.exports = { commandExists, detectPlatform, firstAvailable };
|
|
117
|
+
module.exports = { commandExists, detectPlatform, firstAvailable, knownBinDirs, resolveBinary, safeHomedir };
|
|
@@ -9,18 +9,32 @@
|
|
|
9
9
|
*
|
|
10
10
|
* MAINTAINABILITY by design (the whole point of choosing this shape):
|
|
11
11
|
* - We do NOT reimplement per-OS download / checksum / version pinning. We
|
|
12
|
-
* DELEGATE the install to rtk's own canonical installer (brew /
|
|
13
|
-
*
|
|
14
|
-
* `rtk init -g`. BYAN maintains only "pick the available installer, run it
|
|
15
|
-
*
|
|
12
|
+
* DELEGATE the install to rtk's own canonical installer (brew / the official
|
|
13
|
+
* install.sh / cargo), and DELEGATE the Claude Code hook wiring to rtk's own
|
|
14
|
+
* `rtk init -g`. BYAN maintains only "pick the available installer, run it
|
|
15
|
+
* bounded + visible, locate the binary, ask rtk to wire its hook" — a tiny
|
|
16
|
+
* surface, bumped via one constant.
|
|
16
17
|
* - SUPPLY-CHAIN: we pin to a TAG, never a moving branch. cargo builds the
|
|
17
|
-
* tagged source (`--tag`), and install.sh is fetched from the IMMUTABLE tag ref
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* -
|
|
23
|
-
*
|
|
18
|
+
* tagged source (`--tag`), and install.sh is fetched from the IMMUTABLE tag ref.
|
|
19
|
+
* That script verifies a SHA-256 of the downloaded binary against a published
|
|
20
|
+
* checksums.txt and aborts on mismatch (source: refs/tags/v0.42.4/install.sh,
|
|
21
|
+
* fetched + verified 2026-06-23). So the fetched artifacts are reproducible and
|
|
22
|
+
* integrity-checked, not "whatever master is today".
|
|
23
|
+
* - FAIL-PROOF: the delegated install runs with a per-strategy TIMEOUT (a hung
|
|
24
|
+
* network/build is bounded, not infinite) and with INHERITED stdio (the user
|
|
25
|
+
* SEES the installer's progress instead of staring at a frozen line; this also
|
|
26
|
+
* sidesteps execSync's 1MB maxBuffer cap that a verbose build would blow). Any
|
|
27
|
+
* missing installer / failed / timed-out step is a logged no-op that NEVER
|
|
28
|
+
* breaks the BYAN install (returns { ok:true, synced:false, reason }).
|
|
29
|
+
* - OFF-PATH SAFE: a successful install can leave the binary off PATH (the
|
|
30
|
+
* classic: cargo drops it in ~/.cargo/bin, which Debian's apt-cargo does not
|
|
31
|
+
* add to PATH). We RESOLVE the binary across known install dirs before
|
|
32
|
+
* declaring "unverified", verify + wire it by its real path, and hand the user
|
|
33
|
+
* a one-line PATH hint. The user's own report (rtk installed at ~/.cargo/bin,
|
|
34
|
+
* invisible to a bare probe) is exactly this case.
|
|
35
|
+
* - The command runner + the PATH probe + the resolver are injectable, so the
|
|
36
|
+
* whole flow is unit-tested without shelling out (see
|
|
37
|
+
* install/__tests__/rtk-integration.test.js).
|
|
24
38
|
*
|
|
25
39
|
* Mirrors the ENTRY-POINT shape of byan-web-integration.js / byan-leantime-
|
|
26
40
|
* integration.js (one `setup*Integration`); the return contract is { ok, synced,
|
|
@@ -29,7 +43,8 @@
|
|
|
29
43
|
*/
|
|
30
44
|
|
|
31
45
|
const { execSync } = require('child_process');
|
|
32
|
-
const
|
|
46
|
+
const path = require('path');
|
|
47
|
+
const { commandExists, resolveBinary, firstAvailable } = require('./native-helper');
|
|
33
48
|
|
|
34
49
|
// Pinned install target. Bumping rtk = change these two constants only. We pin a
|
|
35
50
|
// TAG so the install is reproducible and supply-chain-bounded (see header).
|
|
@@ -38,20 +53,37 @@ const RTK_TAG = `v${RTK_VERSION}`;
|
|
|
38
53
|
const RTK_REPO = 'https://github.com/rtk-ai/rtk';
|
|
39
54
|
const RTK_INSTALL_SH = `https://raw.githubusercontent.com/rtk-ai/rtk/refs/tags/${RTK_TAG}/install.sh`;
|
|
40
55
|
|
|
56
|
+
// Per-strategy timeout (ms). A prebuilt-binary path (brew / script) should be
|
|
57
|
+
// quick; cargo COMPILES from source and is legitimately slow, so it gets a wide
|
|
58
|
+
// budget. The bound exists to cap a genuine HANG, not to race a normal build.
|
|
59
|
+
const MIN = 60 * 1000;
|
|
60
|
+
const RTK_TIMEOUT_MS = { brew: 5 * MIN, script: 5 * MIN, cargo: 20 * MIN };
|
|
61
|
+
const RTK_DEFAULT_TIMEOUT_MS = 10 * MIN;
|
|
62
|
+
|
|
41
63
|
/**
|
|
42
64
|
* Ordered install strategies — each DELEGATES to rtk's own canonical installer,
|
|
43
|
-
* pinned to RTK_TAG where the mechanism allows.
|
|
44
|
-
*
|
|
45
|
-
*
|
|
65
|
+
* pinned to RTK_TAG where the mechanism allows.
|
|
66
|
+
*
|
|
67
|
+
* Order = brew > script > cargo. WHY this order (not brew > cargo > script):
|
|
68
|
+
* - brew first: vetted formula, cleanest upgrades, prebuilt.
|
|
69
|
+
* - script second: the official install.sh downloads a PREBUILT binary to a
|
|
70
|
+
* PATH-stable location in seconds. It is pinned to the immutable tag ref and
|
|
71
|
+
* verifies the binary's SHA-256 before installing (see the cited check above)
|
|
72
|
+
* — a bounded supply-chain surface.
|
|
73
|
+
* - cargo LAST: it COMPILES from source (minutes) AND drops the binary in
|
|
74
|
+
* ~/.cargo/bin, frequently off a non-login PATH. It stays as a build-from-
|
|
75
|
+
* source fallback for machines without brew/curl, not the default path.
|
|
46
76
|
*/
|
|
47
77
|
function installStrategies() {
|
|
48
78
|
return [
|
|
49
|
-
{ id: 'brew', tool: 'brew', cmd: 'brew install rtk' },
|
|
50
|
-
{ id: 'cargo', tool: 'cargo', cmd: `cargo install --git ${RTK_REPO} --tag ${RTK_TAG}` },
|
|
79
|
+
{ id: 'brew', tool: 'brew', cmd: 'brew install rtk', timeoutMs: RTK_TIMEOUT_MS.brew },
|
|
51
80
|
// Pass RTK_VERSION into the script so it pins the BINARY too (the script
|
|
52
81
|
// builds releases/download/${VERSION}/...; without it, it resolves
|
|
53
82
|
// releases/latest). Pinning the URL alone is not enough — this pins both.
|
|
54
|
-
|
|
83
|
+
// The script verifies a SHA-256 of the binary (vs checksums.txt) before
|
|
84
|
+
// installing — verified at refs/tags/v0.42.4/install.sh, 2026-06-23.
|
|
85
|
+
{ id: 'script', tool: 'curl', cmd: `curl -fsSL ${RTK_INSTALL_SH} | RTK_VERSION=${RTK_TAG} sh`, timeoutMs: RTK_TIMEOUT_MS.script },
|
|
86
|
+
{ id: 'cargo', tool: 'cargo', cmd: `cargo install --git ${RTK_REPO} --tag ${RTK_TAG}`, timeoutMs: RTK_TIMEOUT_MS.cargo },
|
|
55
87
|
];
|
|
56
88
|
}
|
|
57
89
|
|
|
@@ -59,19 +91,78 @@ function oneLine(err) {
|
|
|
59
91
|
return String((err && err.message) || err).split('\n')[0];
|
|
60
92
|
}
|
|
61
93
|
|
|
94
|
+
// A bounded, env-overridable timeout for a strategy. BYAN_RTK_TIMEOUT_MS (ms)
|
|
95
|
+
// lets a user widen the bound (e.g. a slow machine compiling via cargo). Only a
|
|
96
|
+
// POSITIVE integer is honored: Node treats timeout:0 as "no timeout", so a 0
|
|
97
|
+
// override would silently re-introduce the unbounded hang the bound exists to
|
|
98
|
+
// prevent — it falls back to the strategy budget instead.
|
|
99
|
+
function timeoutFor(strat, { env = process.env } = {}) {
|
|
100
|
+
const raw = env && env.BYAN_RTK_TIMEOUT_MS;
|
|
101
|
+
if (raw != null && /^[0-9]+$/.test(String(raw))) {
|
|
102
|
+
const n = Number(raw);
|
|
103
|
+
if (n > 0) return n;
|
|
104
|
+
}
|
|
105
|
+
return (strat && strat.timeoutMs) || RTK_DEFAULT_TIMEOUT_MS;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Did execSync throw because WE killed it on timeout (vs the command failing on
|
|
109
|
+
// its own)? Node sets `killed=true` and code 'ETIMEDOUT' when it kills the child
|
|
110
|
+
// on timeout; a build error sets a non-zero exit status with killed=false. We do
|
|
111
|
+
// NOT match a bare 'SIGTERM' signal: a sub-process self-terminating with SIGTERM
|
|
112
|
+
// for an unrelated reason would otherwise be mislabelled a timeout.
|
|
113
|
+
function isTimeout(err) {
|
|
114
|
+
return Boolean(err && (err.killed === true || err.code === 'ETIMEDOUT'));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// A shell/platform-correct "add this dir to PATH" hint. The resolved binary can
|
|
118
|
+
// live off PATH (cargo -> ~/.cargo/bin); the user needs the line that actually
|
|
119
|
+
// works in THEIR shell — fish rejects POSIX `export`, Windows rejects both.
|
|
120
|
+
function pathHintFor(bin, { env = process.env, platform = process.platform } = {}) {
|
|
121
|
+
// Parse the dir with the TARGET platform's rules, not the host's — so a win32
|
|
122
|
+
// path yields the right dir even when this code runs on a POSIX CI host.
|
|
123
|
+
const dir = (platform === 'win32' ? path.win32 : path.posix).dirname(bin);
|
|
124
|
+
if (platform === 'win32') return `$env:PATH = "${dir};$env:PATH"`;
|
|
125
|
+
const shell = String((env && env.SHELL) || '');
|
|
126
|
+
if (/(^|\/)fish$/.test(shell)) return `fish_add_path ${dir}`;
|
|
127
|
+
return `export PATH="${dir}:$PATH"`;
|
|
128
|
+
}
|
|
129
|
+
|
|
62
130
|
/**
|
|
63
|
-
* rtkStatus() -> { installed, version }. Never throws.
|
|
64
|
-
* ("rtk 0.42.4"). An installed-but-
|
|
65
|
-
*
|
|
131
|
+
* rtkStatus() -> { installed, version, bin }. Never throws. Probes `<bin>
|
|
132
|
+
* --version` ("rtk 0.42.4"), default bin "rtk" (on PATH). An installed-but-
|
|
133
|
+
* unparseable build yields { installed:true, version:null }.
|
|
66
134
|
*/
|
|
67
|
-
function rtkStatus({ run = execSync } = {}) {
|
|
135
|
+
function rtkStatus({ run = execSync, bin = 'rtk' } = {}) {
|
|
68
136
|
try {
|
|
69
|
-
const out = String(run(
|
|
137
|
+
const out = String(run(`${bin} --version`, { stdio: 'pipe' }) || '');
|
|
70
138
|
const m = out.match(/rtk\s+v?([0-9]+\.[0-9]+\.[0-9]+)/i);
|
|
71
|
-
return { installed: true, version: m ? m[1] : null };
|
|
139
|
+
return { installed: true, version: m ? m[1] : null, bin };
|
|
72
140
|
} catch {
|
|
73
|
-
return { installed: false, version: null };
|
|
141
|
+
return { installed: false, version: null, bin: null };
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* locateRtk() -> a verified { installed, version, bin } by trying the bare name
|
|
147
|
+
* first (on PATH), then resolving across known install dirs (off-PATH). Returns
|
|
148
|
+
* the off-PATH absolute path in `bin` when that is where it was found.
|
|
149
|
+
*/
|
|
150
|
+
function locateRtk({ run = execSync, has = commandExists, resolve = resolveBinary, env = process.env } = {}) {
|
|
151
|
+
const onPath = rtkStatus({ run, bin: 'rtk' });
|
|
152
|
+
if (onPath.installed) return onPath;
|
|
153
|
+
// The resolver must never break the never-throws contract; degrade to not-found
|
|
154
|
+
// if it (or a misbehaving injected resolver) throws.
|
|
155
|
+
let resolved = null;
|
|
156
|
+
try {
|
|
157
|
+
resolved = resolve('rtk', { has, env });
|
|
158
|
+
} catch {
|
|
159
|
+
resolved = null;
|
|
160
|
+
}
|
|
161
|
+
if (resolved && resolved !== 'rtk') {
|
|
162
|
+
const s = rtkStatus({ run, bin: resolved });
|
|
163
|
+
if (s.installed) return s;
|
|
74
164
|
}
|
|
165
|
+
return { installed: false, version: null, bin: null };
|
|
75
166
|
}
|
|
76
167
|
|
|
77
168
|
/**
|
|
@@ -82,16 +173,18 @@ function pickStrategy({ has = commandExists } = {}) {
|
|
|
82
173
|
}
|
|
83
174
|
|
|
84
175
|
/**
|
|
85
|
-
* doctor() -> a status report for diagnostics / a `byan` doctor surface.
|
|
86
|
-
*
|
|
87
|
-
*
|
|
176
|
+
* doctor() -> a status report for diagnostics / a `byan` doctor surface. Resolves
|
|
177
|
+
* the binary across known dirs so an off-PATH install is reported as installed
|
|
178
|
+
* (with its real path), not as missing. `pinned` is the version BYAN targets —
|
|
179
|
+
* NOT an enforced floor on a pre-existing rtk.
|
|
88
180
|
*/
|
|
89
|
-
function doctor({ run = execSync } = {}) {
|
|
90
|
-
const s =
|
|
181
|
+
function doctor({ run = execSync, has = commandExists, resolve = resolveBinary, env = process.env } = {}) {
|
|
182
|
+
const s = locateRtk({ run, has, resolve, env });
|
|
91
183
|
return {
|
|
92
184
|
component: 'rtk',
|
|
93
185
|
installed: s.installed,
|
|
94
186
|
version: s.version,
|
|
187
|
+
bin: s.bin,
|
|
95
188
|
pinned: RTK_VERSION,
|
|
96
189
|
repo: RTK_REPO,
|
|
97
190
|
hookCommand: 'rtk init -g',
|
|
@@ -99,61 +192,88 @@ function doctor({ run = execSync } = {}) {
|
|
|
99
192
|
}
|
|
100
193
|
|
|
101
194
|
// Delegate the Claude Code hook wiring to rtk's OWN command (idempotent). This is
|
|
102
|
-
// the maintainability win: no manual settings.json merge to keep in sync.
|
|
103
|
-
|
|
195
|
+
// the maintainability win: no manual settings.json merge to keep in sync. Wires
|
|
196
|
+
// via the resolved `bin` so an off-PATH install still gets its hook.
|
|
197
|
+
function wireHook({ run, log, installedVia, version, bin = 'rtk', env = process.env, platform = process.platform }) {
|
|
198
|
+
const offPath = bin !== 'rtk';
|
|
104
199
|
try {
|
|
105
|
-
run(
|
|
106
|
-
log(`rtk: ready (${installedVia}, v${version || '?'}) — hook wired via '
|
|
107
|
-
|
|
200
|
+
run(`${bin} init -g`, { stdio: 'pipe' });
|
|
201
|
+
log(`rtk: ready (${installedVia}, v${version || '?'}) — hook wired via '${bin} init -g'. Restart Claude Code to activate.`);
|
|
202
|
+
const r = { ok: true, synced: true, reason: 'wired', installed: true, installedVia, version, hook: true, bin };
|
|
203
|
+
if (offPath) {
|
|
204
|
+
r.pathHint = pathHintFor(bin, { env, platform });
|
|
205
|
+
log(`rtk: note — installed at ${bin}, not on your PATH. Add it: ${r.pathHint}`);
|
|
206
|
+
}
|
|
207
|
+
return r;
|
|
108
208
|
} catch (err) {
|
|
109
|
-
log(`rtk: installed (${installedVia}) but '
|
|
110
|
-
|
|
209
|
+
log(`rtk: installed (${installedVia}) but '${bin} init -g' failed (${oneLine(err)}) — run it manually, BYAN unaffected.`);
|
|
210
|
+
const r = { ok: true, synced: false, reason: 'hook-failed', installed: true, installedVia, version, hook: false, bin };
|
|
211
|
+
if (offPath) r.pathHint = pathHintFor(bin, { env, platform });
|
|
212
|
+
return r;
|
|
111
213
|
}
|
|
112
214
|
}
|
|
113
215
|
|
|
114
216
|
/**
|
|
115
217
|
* setupRtkIntegration(options) -> { ok, synced, reason, ... }. The single entry.
|
|
116
218
|
*
|
|
117
|
-
* Flow: if rtk is already present
|
|
118
|
-
* pick an available installer, delegate
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
219
|
+
* Flow: if rtk is already present (on PATH or off-PATH in a known dir), just
|
|
220
|
+
* (re)wire the hook (idempotent). Otherwise pick an available installer, delegate
|
|
221
|
+
* the install (BOUNDED by a timeout + VISIBLE via inherited stdio), LOCATE the
|
|
222
|
+
* binary, verify, then wire the hook. Any missing tool / failed / timed-out step
|
|
223
|
+
* degrades to a logged no-op — it NEVER throws and NEVER fails the surrounding
|
|
224
|
+
* BYAN install. `ok` is therefore always true; `synced` says whether rtk ended
|
|
225
|
+
* up wired. An off-PATH install carries a `pathHint`.
|
|
122
226
|
*
|
|
123
227
|
* @param {object} [o]
|
|
124
|
-
* @param {Function} [o.run]
|
|
125
|
-
* @param {Function} [o.has]
|
|
126
|
-
* @param {Function} [o.
|
|
228
|
+
* @param {Function} [o.run] command runner (default execSync) — injected in tests
|
|
229
|
+
* @param {Function} [o.has] PATH probe (default commandExists) — injected in tests
|
|
230
|
+
* @param {Function} [o.resolve] binary resolver (default resolveBinary) — injected in tests
|
|
231
|
+
* @param {Function} [o.log] one-line breadcrumb sink (default no-op)
|
|
232
|
+
* @param {object} [o.env] environment (default process.env) — for the timeout override
|
|
127
233
|
*/
|
|
128
|
-
function setupRtkIntegration({
|
|
129
|
-
|
|
234
|
+
function setupRtkIntegration({
|
|
235
|
+
run = execSync,
|
|
236
|
+
has = commandExists,
|
|
237
|
+
resolve = resolveBinary,
|
|
238
|
+
log = () => {},
|
|
239
|
+
env = process.env,
|
|
240
|
+
platform = process.platform,
|
|
241
|
+
} = {}) {
|
|
242
|
+
const before = locateRtk({ run, has, resolve, env });
|
|
130
243
|
if (before.installed) {
|
|
131
|
-
return wireHook({ run, log, installedVia: 'already-present', version: before.version });
|
|
244
|
+
return wireHook({ run, log, installedVia: 'already-present', version: before.version, bin: before.bin, env, platform });
|
|
132
245
|
}
|
|
133
246
|
|
|
134
247
|
const strat = pickStrategy({ has });
|
|
135
248
|
if (!strat) {
|
|
136
|
-
log('rtk: no installer found (brew /
|
|
249
|
+
log('rtk: no installer found (brew / curl / cargo) — skipped. BYAN install unaffected.');
|
|
137
250
|
return { ok: true, synced: false, reason: 'no-installer', installed: false, hook: false };
|
|
138
251
|
}
|
|
139
252
|
|
|
140
|
-
// Breadcrumb BEFORE the
|
|
141
|
-
//
|
|
142
|
-
|
|
253
|
+
// Breadcrumb BEFORE the delegated install. stdio is INHERITED so the installer's
|
|
254
|
+
// own progress streams to the user (no frozen line), bounded by a timeout.
|
|
255
|
+
const timeoutMs = timeoutFor(strat, { env });
|
|
256
|
+
log(`rtk: installing via ${strat.id} (pinned ${RTK_TAG}; up to ${Math.round(timeoutMs / MIN)}min, Ctrl+C is safe)...`);
|
|
143
257
|
try {
|
|
144
|
-
run(strat.cmd, { stdio: '
|
|
258
|
+
run(strat.cmd, { stdio: 'inherit', timeout: timeoutMs });
|
|
145
259
|
} catch (err) {
|
|
260
|
+
if (isTimeout(err)) {
|
|
261
|
+
log(`rtk: install via ${strat.id} exceeded ${Math.round(timeoutMs / MIN)}min — stopped (a background build may still continue). Retry with 'npm run setup-rtk' (or widen BYAN_RTK_TIMEOUT_MS). BYAN unaffected.`);
|
|
262
|
+
return { ok: true, synced: false, reason: `install-timeout:${strat.id}`, installed: false, hook: false };
|
|
263
|
+
}
|
|
146
264
|
log(`rtk: install via ${strat.id} failed (${oneLine(err)}) — skipped gracefully.`);
|
|
147
265
|
return { ok: true, synced: false, reason: `install-failed:${strat.id}`, installed: false, hook: false };
|
|
148
266
|
}
|
|
149
267
|
|
|
150
|
-
|
|
268
|
+
// The binary may be installed but OFF PATH (cargo -> ~/.cargo/bin). Resolve it
|
|
269
|
+
// before declaring failure, and wire it by its real location.
|
|
270
|
+
const after = locateRtk({ run, has, resolve, env });
|
|
151
271
|
if (!after.installed) {
|
|
152
272
|
log(`rtk: install via ${strat.id} ran but 'rtk --version' did not confirm — skipped.`);
|
|
153
273
|
return { ok: true, synced: false, reason: 'install-unverified', installed: false, hook: false };
|
|
154
274
|
}
|
|
155
275
|
|
|
156
|
-
return wireHook({ run, log, installedVia: strat.id, version: after.version });
|
|
276
|
+
return wireHook({ run, log, installedVia: strat.id, version: after.version, bin: after.bin, env, platform });
|
|
157
277
|
}
|
|
158
278
|
|
|
159
279
|
/**
|
|
@@ -171,12 +291,18 @@ function shouldOfferRtk({ env = process.env, isTTY = !!(process.stdin && process
|
|
|
171
291
|
module.exports = {
|
|
172
292
|
setupRtkIntegration,
|
|
173
293
|
rtkStatus,
|
|
294
|
+
locateRtk,
|
|
174
295
|
pickStrategy,
|
|
175
296
|
installStrategies,
|
|
297
|
+
timeoutFor,
|
|
298
|
+
isTimeout,
|
|
299
|
+
pathHintFor,
|
|
176
300
|
doctor,
|
|
177
301
|
shouldOfferRtk,
|
|
178
302
|
RTK_VERSION,
|
|
179
303
|
RTK_TAG,
|
|
180
304
|
RTK_INSTALL_SH,
|
|
181
305
|
RTK_REPO,
|
|
306
|
+
RTK_TIMEOUT_MS,
|
|
307
|
+
RTK_DEFAULT_TIMEOUT_MS,
|
|
182
308
|
};
|
package/install/package.json
CHANGED
package/install/setup-rtk.js
CHANGED
|
@@ -16,14 +16,18 @@ const { setupRtkIntegration, doctor } = require('./lib/rtk-integration');
|
|
|
16
16
|
|
|
17
17
|
function main() {
|
|
18
18
|
console.log(chalk.cyan('\nRTK token optimizer (rtk-ai/rtk) — optional native component'));
|
|
19
|
-
console.log(chalk.gray(' Cuts dev-command tokens 60-90%; wires its own Claude Code hook
|
|
19
|
+
console.log(chalk.gray(' Cuts dev-command tokens 60-90%; wires its own Claude Code hook.'));
|
|
20
|
+
console.log(chalk.gray(' Progress streams below. The cargo fallback compiles from source (minutes); Ctrl+C is safe.\n'));
|
|
20
21
|
|
|
21
22
|
const result = setupRtkIntegration({ log: (m) => console.log(chalk.gray(' ' + m)) });
|
|
22
23
|
|
|
23
24
|
if (result.synced) {
|
|
24
25
|
console.log(chalk.green(`\n rtk ready (${result.installedVia}, v${result.version || '?'}). Restart Claude Code to activate.`));
|
|
26
|
+
if (result.pathHint) console.log(chalk.yellow(` rtk is not on your PATH — add it: ${result.pathHint}`));
|
|
25
27
|
} else if (result.reason === 'no-installer') {
|
|
26
|
-
console.log(chalk.yellow('\n No installer found (brew /
|
|
28
|
+
console.log(chalk.yellow('\n No installer found (brew / curl / cargo). Install one, then re-run `npm run setup-rtk`.'));
|
|
29
|
+
} else if (result.pathHint) {
|
|
30
|
+
console.log(chalk.yellow(`\n rtk found off-PATH (${result.reason}) — add it then re-run: ${result.pathHint}`));
|
|
27
31
|
} else {
|
|
28
32
|
console.log(chalk.yellow(`\n rtk not wired (${result.reason}). BYAN is unaffected; you can retry later.`));
|
|
29
33
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-byan-agent",
|
|
3
|
-
"version": "2.29.
|
|
3
|
+
"version": "2.29.2",
|
|
4
4
|
"description": "BYAN v2.8 - Intelligent AI agent creator with ELO trust system + scientific fact-check + Hermes universal dispatcher + native Claude Code integration (hooks, skills, MCP server). Multi-platform (Claude Code, Codex). Merise Agile + TDD + 71 Mantras. ~54% LLM cost savings.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|