wkappbot-code 0.3.17 → 0.3.19
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/bin/wkappbot-code.js +4 -4
- package/lib/ai-clis.js +18 -7
- package/lib/prereq.js +5 -2
- package/lib/setup-run.js +15 -3
- package/package.json +1 -1
package/bin/wkappbot-code.js
CHANGED
|
@@ -200,8 +200,8 @@ function installBin(o) {
|
|
|
200
200
|
log("bin: bash " + script + " acquire --dest " + target);
|
|
201
201
|
const r = spawnSync("bash", [shPath(script), "acquire", "--dest", shPath(target)], { encoding: "utf8", env: shEnv() });
|
|
202
202
|
for (const line of ((r.stdout || "") + (r.stderr || "")).split("\n").filter(Boolean)) log(" " + line);
|
|
203
|
-
// rc 3 = the release host could not be reached (a package-registry-only VM): no core
|
|
204
|
-
//
|
|
203
|
+
// rc 3 = the release host could not be reached (a package-registry-only VM): no core. The hooks and the lite harness
|
|
204
|
+
// need no core and still refuse the risky classes, but the install reports the missing core as a FAILED part (0.3.13).
|
|
205
205
|
if (r.status === 3) return { ok: false, degraded: true, dest, note: "core not fetched (release host unreachable); hooks + lite harness run without it -- rerun wkappbot-code install once github.com is reachable" };
|
|
206
206
|
if (r.status !== 0) return { ok: false, dest, note: "wk-bin-acquire rc " + r.status };
|
|
207
207
|
// Put `wkappbot` on PATH beside wkappbot-code itself, unless that name is already taken by someone else.
|
|
@@ -441,7 +441,7 @@ function install(o) {
|
|
|
441
441
|
}
|
|
442
442
|
}
|
|
443
443
|
// 0.3.13 (owner 2026-11-08): the famous AI CLIs come along, so a user who knows nothing about LLMs still has them.
|
|
444
|
-
st.aiClis = (o.npm && o.aiClis) ? installAiClis(log, process.env, undefined, (prev.aiClis && prev.aiClis.installed) || [])
|
|
444
|
+
st.aiClis = (o.npm && o.aiClis) ? installAiClis(log, process.env, undefined, (prev.aiClis && prev.aiClis.installed) || [], (prev.aiClis && prev.aiClis.prefix) || "")
|
|
445
445
|
: ((prev.aiClis && (prev.aiClis.installed || []).length) ? prev.aiClis : { skipped: true }); // a skip still owns what an earlier install added
|
|
446
446
|
const dest = binDest(o);
|
|
447
447
|
st.bin = o.bin ? installBin(o) : { skipped: true, dest };
|
|
@@ -569,7 +569,7 @@ function uninstall(o) {
|
|
|
569
569
|
else log("shim: kept " + cmdShim + " (it no longer calls " + core + ")");
|
|
570
570
|
}
|
|
571
571
|
// 3d. the AI CLIs this install added (lib/ai-clis.js), never one the user had before
|
|
572
|
-
removeAiClis((st.aiClis && st.aiClis.installed) || [], log);
|
|
572
|
+
removeAiClis((st.aiClis && st.aiClis.installed) || [], log, process.env, undefined, (st.aiClis && st.aiClis.prefix) || "");
|
|
573
573
|
// 4. the harness dirs we created, the plugin entry and our config folder
|
|
574
574
|
// Windows frees a closed handle late (ENOTEMPTY): lib/rmtree.js retries ~6 s, then a per-entry pass names the blocker; a failure goes on.
|
|
575
575
|
const rmTree = (p, what) => {
|
package/lib/ai-clis.js
CHANGED
|
@@ -23,6 +23,14 @@ export function cliVersion(name, env = process.env) {
|
|
|
23
23
|
return m ? m[0] : line.slice(0, 40);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
// The npm global prefix an install uses RIGHT NOW (npm_config_prefix included: npm exports it to a postinstall worker, so a
|
|
27
|
+
// worker lays the CLIs into the prefix of the `npm install -g wkappbot-code` that started it). Recorded in the state so an
|
|
28
|
+
// uninstall run from another shell takes the CLIs back from THAT prefix, not from whatever its own default prefix is.
|
|
29
|
+
export function npmPrefix(env = process.env) {
|
|
30
|
+
const r = spawnSync("npm", ["prefix", "-g"], { encoding: "utf8", timeout: 30000, shell: IS_WIN, env });
|
|
31
|
+
return r.status === 0 ? String(r.stdout || "").trim().split("\n").pop().trim() : "";
|
|
32
|
+
}
|
|
33
|
+
|
|
26
34
|
function npmGlobal(pkgs, env) {
|
|
27
35
|
const r = spawnSync("npm", ["install", "-g", ...pkgs, "--no-audit", "--no-fund"], { stdio: "inherit", shell: IS_WIN, env });
|
|
28
36
|
return r.status === 0;
|
|
@@ -30,13 +38,14 @@ function npmGlobal(pkgs, env) {
|
|
|
30
38
|
|
|
31
39
|
// Installs every CLI of AI_CLIS that is not on PATH yet. Returns { installed: [...], present: [...], failed: [...] }.
|
|
32
40
|
// prevInstalled: the names an earlier install added; a rerun keeps owning them, so uninstall still takes them back.
|
|
33
|
-
export function installAiClis(log, env = process.env, list = AI_CLIS, prevInstalled = []) {
|
|
41
|
+
export function installAiClis(log, env = process.env, list = AI_CLIS, prevInstalled = [], prevPrefix = "") {
|
|
34
42
|
const present = [], missing = [];
|
|
35
43
|
for (const c of list) (cliVersion(c.name, env) ? present : missing).push(c);
|
|
36
44
|
if (present.length) log("ai-clis: already installed: " + present.map((c) => c.name).join(", "));
|
|
37
|
-
const res = { installed: present.map((c) => c.name).filter((n) => prevInstalled.includes(n)), present: present.map((c) => c.name), failed: [] };
|
|
45
|
+
const res = { installed: present.map((c) => c.name).filter((n) => prevInstalled.includes(n)), present: present.map((c) => c.name), failed: [], prefix: prevPrefix };
|
|
38
46
|
if (!missing.length) return res;
|
|
39
|
-
|
|
47
|
+
res.prefix = npmPrefix(env) || prevPrefix;
|
|
48
|
+
log("ai-clis: npm install -g " + missing.map((c) => c.pkg).join(" ") + (res.prefix ? " (prefix " + res.prefix + ")" : ""));
|
|
40
49
|
if (npmGlobal(missing.map((c) => c.pkg), env)) res.installed.push(...missing.map((c) => c.name));
|
|
41
50
|
else {
|
|
42
51
|
log("ai-clis: the one-pass install failed, retrying each CLI alone");
|
|
@@ -49,12 +58,14 @@ export function installAiClis(log, env = process.env, list = AI_CLIS, prevInstal
|
|
|
49
58
|
// uninstall takes back what install added (SDK os-qa 2026-11-09, run 42552244938: the npm prefix bin kept claude, codex,
|
|
50
59
|
// gemini and qwen after uninstall on every leg). Only the names install recorded go; a CLI the user had stays. A failed
|
|
51
60
|
// npm uninstall is a WARN with the command to finish it by hand.
|
|
52
|
-
|
|
61
|
+
// prefix: the npm global prefix install recorded (SDK os-qa u1-uninstall, 2026-11-09: the worker laid the CLIs into the
|
|
62
|
+
// prefix of the npm that started it, and an uninstall from a shell with another default prefix left them behind).
|
|
63
|
+
export function removeAiClis(names, log, env = process.env, list = AI_CLIS, prefix = "") {
|
|
53
64
|
const pkgs = list.filter((c) => (names || []).includes(c.name)).map((c) => c.pkg);
|
|
54
65
|
if (!pkgs.length) return true;
|
|
55
|
-
log("ai-clis: npm uninstall -g " + pkgs.join(" "));
|
|
56
|
-
const r = spawnSync("npm", ["uninstall", "-g", ...pkgs, "--no-audit", "--no-fund"], { stdio: "inherit", shell: IS_WIN, env });
|
|
57
|
-
if (r.status !== 0) log("ai-clis: WARN npm uninstall rc " + r.status + " -- run: npm uninstall -g " + pkgs.join(" "));
|
|
66
|
+
log("ai-clis: npm uninstall -g " + pkgs.join(" ") + (prefix ? " (prefix " + prefix + ")" : ""));
|
|
67
|
+
const r = spawnSync("npm", ["uninstall", "-g", ...pkgs, ...(prefix ? ["--prefix", prefix] : []), "--no-audit", "--no-fund"], { stdio: "inherit", shell: IS_WIN, env });
|
|
68
|
+
if (r.status !== 0) log("ai-clis: WARN npm uninstall rc " + r.status + " -- run: npm uninstall -g " + pkgs.join(" ") + (prefix ? " --prefix " + prefix : ""));
|
|
58
69
|
return r.status === 0;
|
|
59
70
|
}
|
|
60
71
|
|
package/lib/prereq.js
CHANGED
|
@@ -24,7 +24,7 @@ const realRun = (cmd, args, env) => spawnSync(cmd, args, { stdio: "inherit", env
|
|
|
24
24
|
export function ensurePrereqs(o = {}) {
|
|
25
25
|
const platform = o.platform || process.platform, env = o.env || process.env, log = o.log || (() => {});
|
|
26
26
|
const which = o.which || realWhich, exists = o.exists || existsSync, run = o.run || realRun;
|
|
27
|
-
const probe = () => { const found = {}, missing = []; for (const t of TOOLS) { const p = which(t, env); if (p) found[t] = p; else missing.push(t); } return { found, missing }; };
|
|
27
|
+
let gitDir = ""; const inGit = (t) => { if (!gitDir) return ""; for (const sub of ["bin", join("usr", "bin"), "cmd", join("ucrt64", "bin"), join("mingw64", "bin"), join("clangarm64", "bin")]) { const f = join(gitDir, sub, t + ".exe"); if (exists(f)) return f; } return ""; }; const probe = () => { const found = {}, missing = []; for (const t of TOOLS) { const p = which(t, env) || inGit(t); if (p) found[t] = p; else missing.push(t); } return { found, missing }; };
|
|
28
28
|
let p = probe(), via = "path";
|
|
29
29
|
if (!p.missing.length) return { ok: true, missing: [], found: p.found, via, note: "" };
|
|
30
30
|
if (platform !== "win32") {
|
|
@@ -36,17 +36,20 @@ export function ensurePrereqs(o = {}) {
|
|
|
36
36
|
for (const d of GIT_DIRS(env)) {
|
|
37
37
|
if (!exists(join(d, "bin", "bash.exe"))) continue;
|
|
38
38
|
const key = Object.keys(env).find((k) => k.toUpperCase() === "PATH") || "PATH";
|
|
39
|
-
|
|
39
|
+
const dirs = [join(d, "bin"), join(d, "usr", "bin"), join(d, "cmd"), ...[join(d, "ucrt64", "bin"), join(d, "mingw64", "bin"), join(d, "clangarm64", "bin")].filter((x) => exists(x))];
|
|
40
|
+
env[key] = dirs.join(delimiter) + delimiter + (env[key] || "");
|
|
40
41
|
return d;
|
|
41
42
|
}
|
|
42
43
|
return "";
|
|
43
44
|
};
|
|
44
45
|
let dir = addGit();
|
|
46
|
+
gitDir = dir;
|
|
45
47
|
if (dir) { p = probe(); via = "git-dir"; log("prereq: Git Bash found at " + dir + " (not on PATH) -- used for this install; add " + join(dir, "bin") + " to your PATH"); }
|
|
46
48
|
if (p.missing.length && !o.noInstall && which("winget", env)) {
|
|
47
49
|
log("prereq: Git for Windows (Git Bash) is missing -- installing it with winget");
|
|
48
50
|
const rc = run("winget", ["install", "--id", "Git.Git", "-e", "--source", "winget", "--silent", "--accept-package-agreements", "--accept-source-agreements"], env);
|
|
49
51
|
dir = addGit();
|
|
52
|
+
gitDir = dir;
|
|
50
53
|
if (dir) { p = probe(); via = "winget"; log("prereq: winget installed Git for Windows at " + dir + " (rc " + rc + ")"); }
|
|
51
54
|
else log("prereq: winget rc " + rc + " but no Git Bash was found afterwards");
|
|
52
55
|
}
|
package/lib/setup-run.js
CHANGED
|
@@ -133,13 +133,25 @@ export function printBanner({ home = homedir(), out = console.log, now = Date.no
|
|
|
133
133
|
// cannot be started (not on PATH): the caller then prints the usage as before.
|
|
134
134
|
// 0.3.12: `opencode` is looked up on PATH first, then beside this CLI (argv1 = the npm bin dir where `npm i -g opencode-ai`
|
|
135
135
|
// linked it), so a global prefix that is not on the user PATH still opens it.
|
|
136
|
+
// 0.3.19: the binary is resolved BY FILE before spawning. On Windows a shell spawn of a missing name returns status 1
|
|
137
|
+
// with no r.error ("'opencode' is not recognized"), so the old error-only fallback never reached the sibling .cmd shim.
|
|
136
138
|
export function launchOpenCode(env = process.env, argv1 = process.argv[1] || "") {
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
139
|
+
const bin = findOpenCode(env, argv1 ? dirname(argv1) : "");
|
|
140
|
+
if (!bin) return null;
|
|
141
|
+
const shell = IS_WIN && /\.(?:cmd|bat)$/i.test(bin);
|
|
142
|
+
const r = spawnSync(shell ? '"' + bin + '"' : bin, [], { stdio: "inherit", shell, env: withPathFirst({ ...env }, dirname(bin)) });
|
|
140
143
|
if (r.error) return null;
|
|
141
144
|
return r.status ?? 1;
|
|
142
145
|
}
|
|
146
|
+
// The opencode file on PATH, else beside this CLI (the npm bin dir); "" when there is none anywhere.
|
|
147
|
+
export function findOpenCode(env = process.env, besideDir = "") {
|
|
148
|
+
const names = IS_WIN ? ["opencode.cmd", "opencode.exe", "opencode"] : ["opencode"];
|
|
149
|
+
const k = Object.keys(env).find((x) => x.toLowerCase() === "path") || "PATH";
|
|
150
|
+
const dirs = String(env[k] || "").split(delimiter).filter(Boolean);
|
|
151
|
+
if (besideDir) dirs.push(besideDir);
|
|
152
|
+
for (const d of dirs) for (const n of names) { const p = join(d, n); if (existsSync(p)) return p; }
|
|
153
|
+
return "";
|
|
154
|
+
}
|
|
143
155
|
|
|
144
156
|
// The npm global bin dir (where `npm i -g` links binaries): prefix/bin, or the prefix itself on Windows. "" when unknown.
|
|
145
157
|
export function globalBinDir(env = process.env) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wkappbot-code",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.19",
|
|
4
4
|
"description": "wkappbot-code -- Wisdom Kernel: the pinned OpenCode bridge plus the reflex harness plugin, the wkappbot core, the web harness and the latest public skills in one install",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|