wkappbot-code 0.3.7 → 0.3.9
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/prepack.js +55 -0
- package/bin/wkappbot-code.js +21 -6
- package/lib/wk-bin-acquire.sh +17 -7
- package/package.json +4 -2
package/bin/prepack.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// prepack -- bakes the minimal web harness INTO the npm package (harness/), so `npm install wkappbot-code` wires the
|
|
3
|
+
// Claude Code hooks from the package alone: no kih or WKAppBot checkout and no reach to the harness web page (a
|
|
4
|
+
// Default-environment cloud VM allows the package registries and little else; spokesman order 2026-11-06).
|
|
5
|
+
// harness/ holds the same four files the web page serves: install.sh, wk-harness-min.tar.gz, its .sha256, version.json.
|
|
6
|
+
// The tarball is the manifest bake (tools/wk-harness-min-copy.sh, scrub gate included) minus .git and bin/linux-x64, the
|
|
7
|
+
// core binary the installer fetches on its own (part: bin); 4 MB instead of 35 MB.
|
|
8
|
+
// Env: WKAPPBOT_CODE_HARNESS_STAGE = an already baked tree to pack (selftest.sh bakes once per run; 50 s per pack saved).
|
|
9
|
+
import { spawnSync } from "node:child_process";
|
|
10
|
+
import { createHash } from "node:crypto";
|
|
11
|
+
import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
12
|
+
import { tmpdir } from "node:os";
|
|
13
|
+
import { dirname, join, resolve } from "node:path";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
|
|
16
|
+
const PKG_DIR = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
17
|
+
const KIH = resolve(PKG_DIR, "..");
|
|
18
|
+
const OUT = join(PKG_DIR, "harness");
|
|
19
|
+
const PKG = JSON.parse(readFileSync(join(PKG_DIR, "package.json"), "utf8"));
|
|
20
|
+
const die = (m) => { console.error("[wkappbot-code prepack] " + m); process.exit(1); };
|
|
21
|
+
const sh = (p) => process.platform === "win32" ? p.replace(/\\/g, "/") : p;
|
|
22
|
+
|
|
23
|
+
let stage = process.env.WKAPPBOT_CODE_HARNESS_STAGE || "";
|
|
24
|
+
let tmp = "";
|
|
25
|
+
const copy = join(KIH, "tools", "wk-harness-min-copy.sh");
|
|
26
|
+
const install = join(KIH, "webvm", "x64-build", "harness-install.sh");
|
|
27
|
+
// A copy of the package packed OUTSIDE a kih checkout (the self-update selftest's tree) has nothing to bake from: it
|
|
28
|
+
// packs without harness/ and its install falls back to the web harness. The publish packs from the checkout, and
|
|
29
|
+
// selftest x16 refuses a tgz that carries no harness/.
|
|
30
|
+
if (!existsSync(install) || (!stage && !existsSync(copy))) {
|
|
31
|
+
console.error("[wkappbot-code prepack] no kih checkout at " + KIH + ": harness/ not baked (install falls back to the web harness)");
|
|
32
|
+
process.exit(0);
|
|
33
|
+
}
|
|
34
|
+
if (!stage) {
|
|
35
|
+
tmp = mkdtempSync(join(tmpdir(), "wkc-prepack-"));
|
|
36
|
+
stage = join(tmp, "h");
|
|
37
|
+
const r = spawnSync("bash", [sh(copy), sh(KIH), sh(stage), sh(join(KIH, "tools", "wk-harness-min-manifest.txt"))],
|
|
38
|
+
{ encoding: "utf8", env: { ...process.env, GIT_CONFIG_GLOBAL: "/dev/null" } });
|
|
39
|
+
if (r.status !== 0) die("harness bake rc " + r.status + "\n" + String(r.stderr).slice(-800));
|
|
40
|
+
}
|
|
41
|
+
if (!existsSync(join(stage, "tools", "wkharness-lite.sh"))) die("the baked tree " + stage + " has no tools/wkharness-lite.sh");
|
|
42
|
+
|
|
43
|
+
rmSync(OUT, { recursive: true, force: true });
|
|
44
|
+
mkdirSync(OUT, { recursive: true });
|
|
45
|
+
const tgz = join(OUT, "wk-harness-min.tar.gz");
|
|
46
|
+
const t = spawnSync("tar", ["--exclude=./.git", "--exclude=./bin/linux-x64", "-czf", sh(tgz), "-C", sh(stage), "."], { encoding: "utf8" });
|
|
47
|
+
if (t.status !== 0) die("tar rc " + t.status + " " + t.stderr);
|
|
48
|
+
const sha = createHash("sha256").update(readFileSync(tgz)).digest("hex");
|
|
49
|
+
writeFileSync(tgz + ".sha256", sha + " wk-harness-min.tar.gz\n");
|
|
50
|
+
const count = (d) => readdirSync(d, { withFileTypes: true }).reduce((n, e) => n + (e.isDirectory() ? (e.name === ".git" ? 0 : count(join(d, e.name))) : 1), 0);
|
|
51
|
+
writeFileSync(join(OUT, "version.json"), JSON.stringify({ version: "npm-" + PKG.version + "-" + sha.slice(0, 12), sha256: sha,
|
|
52
|
+
built: new Date().toISOString().replace(/\.\d+Z$/, "Z"), files: count(stage) }) + "\n");
|
|
53
|
+
copyFileSync(install, join(OUT, "install.sh"));
|
|
54
|
+
if (tmp) rmSync(tmp, { recursive: true, force: true });
|
|
55
|
+
console.error("[wkappbot-code prepack] harness/ baked: " + sha.slice(0, 12) + " (" + readFileSync(tgz).length + " bytes)");
|
package/bin/wkappbot-code.js
CHANGED
|
@@ -45,7 +45,11 @@ const PKG_DIR = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
|
45
45
|
const PKG = JSON.parse(readFileSync(join(PKG_DIR, "package.json"), "utf8"));
|
|
46
46
|
const OPENCODE_VERSION = PKG.wkAppbotCode.opencodeVersion;
|
|
47
47
|
const OWNER_LOGIN = "kiexpert";
|
|
48
|
-
|
|
48
|
+
// The harness this package carries (harness/, baked by bin/prepack.js at pack time) comes first: it needs no checkout
|
|
49
|
+
// and no network (spokesman 2026-11-06, a Default-environment VM). WK_HARNESS_BASE overrides; the web page serves an unbaked tree.
|
|
50
|
+
const BUNDLED_HARNESS = () => join(PKG_DIR, "harness");
|
|
51
|
+
const HARNESS_BASE = () => (process.env.WK_HARNESS_BASE || (existsSync(join(BUNDLED_HARNESS(), "install.sh")) ? pathToFileURL(BUNDLED_HARNESS()).href
|
|
52
|
+
: "https://kiexpert.github.io/wkappbot-kih/webvm/x64/harness")).replace(/\/+$/, "");
|
|
49
53
|
const HARNESS_DIR = () => process.env.WK_HARNESS_DIR || join(homedir(), ".wk-harness");
|
|
50
54
|
const HOOKS_DIR = () => join(homedir(), ".wk-harness-hooks"); // tools/wk-global-hooks.sh install default
|
|
51
55
|
const SETTINGS = () => process.env.WK_SETTINGS || join(homedir(), ".claude", "settings.json");
|
|
@@ -185,6 +189,9 @@ function installBin(o) {
|
|
|
185
189
|
log("bin: bash " + script + " acquire --dest " + dest);
|
|
186
190
|
const r = spawnSync("bash", [shPath(script), "acquire", "--dest", shPath(dest)], { encoding: "utf8", env: shEnv() });
|
|
187
191
|
for (const line of ((r.stdout || "") + (r.stderr || "")).split("\n").filter(Boolean)) log(" " + line);
|
|
192
|
+
// rc 3 = the release host could not be reached (a package-registry-only VM): no core, but NOT a failed install --
|
|
193
|
+
// the hooks and the lite harness need no core and still refuse the risky classes. A sha256 mismatch stays rc 1.
|
|
194
|
+
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" };
|
|
188
195
|
if (r.status !== 0) return { ok: false, dest, note: "wk-bin-acquire rc " + r.status };
|
|
189
196
|
// Put `wkappbot` on PATH beside wkappbot-code itself, unless that name is already taken by someone else.
|
|
190
197
|
let link = "";
|
|
@@ -394,7 +401,8 @@ function install(o) {
|
|
|
394
401
|
}
|
|
395
402
|
const dest = binDest(o);
|
|
396
403
|
st.bin = o.bin ? installBin(o) : { skipped: true, dest };
|
|
397
|
-
if (o.bin &&
|
|
404
|
+
if (o.bin && st.bin.degraded) log("bin: WARN -- " + st.bin.note);
|
|
405
|
+
else if (o.bin && !st.bin.ok) { bad++; log("bin: FAILED -- " + st.bin.note); }
|
|
398
406
|
st.harness = o.harness ? installHarness(o) : { skipped: true };
|
|
399
407
|
if (o.harness && !st.harness.ok) { bad++; log("harness: FAILED -- " + st.harness.note); }
|
|
400
408
|
// The plugin entry names the harness the install just chose (a forced web harness wins over a checkout).
|
|
@@ -485,8 +493,14 @@ function uninstall(o) {
|
|
|
485
493
|
if (isSymlink(p) || existsSync(p)) { rmSync(p, { force: true }); log("shim: removed " + p); }
|
|
486
494
|
}
|
|
487
495
|
// 4. the harness dirs we created, the plugin entry and our config folder
|
|
488
|
-
|
|
489
|
-
|
|
496
|
+
// Windows releases a just-closed handle late (EBUSY/EPERM/ENOTEMPTY), so every tree delete retries and, when it
|
|
497
|
+
// still fails, names the path and goes on with the rest instead of aborting the uninstall half way.
|
|
498
|
+
const rmTree = (p, what) => {
|
|
499
|
+
try { rmSync(p, { recursive: true, force: true, maxRetries: 10, retryDelay: 300 }); if (what) log(what + ": removed " + p); }
|
|
500
|
+
catch (e) { rc = 1; log((what || "remove") + ": could not remove " + p + " (" + (e.code || e.message) + ") -- delete it by hand"); }
|
|
501
|
+
};
|
|
502
|
+
if (st.harness && st.harness.mode === "web" && pr.harnessDirExisted !== true && existsSync(HARNESS_DIR())) rmTree(HARNESS_DIR(), "harness");
|
|
503
|
+
if (pr.hooksDirExisted !== true && existsSync(HOOKS_DIR())) rmTree(HOOKS_DIR(), "push-gate");
|
|
490
504
|
const cfgFile = join(configDir(o), "opencode.json");
|
|
491
505
|
try {
|
|
492
506
|
const c = JSON.parse(readFileSync(cfgFile, "utf8"));
|
|
@@ -498,8 +512,8 @@ function uninstall(o) {
|
|
|
498
512
|
log("config: plugin entry removed from " + cfgFile);
|
|
499
513
|
}
|
|
500
514
|
} catch {}
|
|
501
|
-
|
|
502
|
-
|
|
515
|
+
rmTree(STATE_HOME());
|
|
516
|
+
rmTree(join(configDir(o), "wkappbot-code"));
|
|
503
517
|
log("uninstall done. Kept: opencode-ai, the wkappbot core (" + ((st.bin && st.bin.dest) || binDest(o)) + ") and its skills, and this CLI (npm rm -g wkappbot-code)");
|
|
504
518
|
return rc;
|
|
505
519
|
}
|
|
@@ -539,6 +553,7 @@ function doctor(o) {
|
|
|
539
553
|
const dest = o.binDest ? resolve(o.binDest) : (st.bin && st.bin.dest) || binDest(o);
|
|
540
554
|
let core = "";
|
|
541
555
|
if (st.bin && st.bin.skipped) add("bin", false, "skipped (--no-bin)", false, true);
|
|
556
|
+
else if (st.bin && st.bin.degraded && !existsSync(dest)) add("bin", false, dest + " absent -- " + st.bin.note, false);
|
|
542
557
|
else {
|
|
543
558
|
const r = spawnSync(dest, ["--version"], { encoding: "utf8", timeout: 60000 });
|
|
544
559
|
core = r.status === 0 ? (r.stdout || "").trim().split("\n")[0] : "";
|
package/lib/wk-bin-acquire.sh
CHANGED
|
@@ -100,18 +100,20 @@ default_dest_win() {
|
|
|
100
100
|
fi
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
# sdk_fetch PLAT DIR -- prints the verified asset path
|
|
103
|
+
# sdk_fetch PLAT DIR -- prints the verified asset path; else prints nothing and returns
|
|
104
|
+
# 3 when a download failed (host unreachable, no such asset) or 4 when the asset is UNVERIFIED
|
|
105
|
+
# (no SHA256SUMS line for it, or a sha256 mismatch). Only 3 may degrade an install to hooks-only.
|
|
104
106
|
sdk_fetch() {
|
|
105
107
|
command -v wk_stopwatch_lap >/dev/null 2>&1 && wk_stopwatch_lap "sdk_fetch" || true
|
|
106
108
|
local plat="$1" dir="$2" asset base want got
|
|
107
109
|
asset="wkappbot-$plat"
|
|
108
110
|
base="${WK_BIN_BASE:-https://github.com/kiexpert/wkappbot-sdk/releases/latest/download}"
|
|
109
|
-
curl -fsSL -m 120 "$base/$asset" -o "$dir/$asset" || return
|
|
110
|
-
curl -fsSL -m 120 "$base/SHA256SUMS" -o "$dir/SHA256SUMS" || return
|
|
111
|
+
curl -fsSL -m "${WK_BIN_TIMEOUT:-120}" "$base/$asset" -o "$dir/$asset" || return 3
|
|
112
|
+
curl -fsSL -m "${WK_BIN_TIMEOUT:-120}" "$base/SHA256SUMS" -o "$dir/SHA256SUMS" || return 3
|
|
111
113
|
want="$(awk -v f="$asset" '{ n = $2; sub(/^\*/, "", n); if (n == f) { print $1; exit } }' "$dir/SHA256SUMS")"
|
|
112
|
-
[ -n "$want" ] || return
|
|
114
|
+
[ -n "$want" ] || return 4
|
|
113
115
|
got="$(sha_of "$dir/$asset" || true)"
|
|
114
|
-
[ "$got" = "$want" ] || return
|
|
116
|
+
[ "$got" = "$want" ] || return 4
|
|
115
117
|
echo "$dir/$asset"
|
|
116
118
|
}
|
|
117
119
|
|
|
@@ -147,12 +149,20 @@ install_file() {
|
|
|
147
149
|
|
|
148
150
|
acquire_unix() {
|
|
149
151
|
command -v wk_stopwatch_lap >/dev/null 2>&1 && wk_stopwatch_lap "acquire_unix" || true
|
|
150
|
-
local dest="$1" plat="$2" offline="$3" src label
|
|
152
|
+
local dest="$1" plat="$2" offline="$3" src="" label frc=3
|
|
151
153
|
[ -n "$dest" ] || dest="$(default_dest_unix)"
|
|
152
|
-
if [ "$offline" = 0 ]
|
|
154
|
+
if [ "$offline" = 0 ]; then src="$(sdk_fetch "$plat" "$WK_TMP")" && frc=0 || frc=$?; fi
|
|
155
|
+
if [ "$frc" = 0 ]; then
|
|
153
156
|
label="SDK release wkappbot-$plat"
|
|
154
157
|
else
|
|
155
158
|
if [ "$offline" = 0 ]; then note "SDK asset unavailable or unverified, using repo bin/$plat"; fi
|
|
159
|
+
# EXIT 3 = NOTHING TO FETCH FROM, never a security refusal: the release host is unreachable (a
|
|
160
|
+
# package-registry-only VM) and no checkout holds a repo copy. The caller may then install hooks
|
|
161
|
+
# and the lite harness without the core. An UNVERIFIED asset (frc 4) still dies rc 1 below.
|
|
162
|
+
if [ -z "$REPO" ] && [ "$frc" = 3 ]; then
|
|
163
|
+
note "UNREACHABLE: no wkappbot-$plat could be downloaded from ${WK_BIN_BASE:-github.com/kiexpert/wkappbot-sdk releases} and no kih checkout holds bin/$plat; nothing installed"
|
|
164
|
+
exit 3
|
|
165
|
+
fi
|
|
156
166
|
src="$(repo_file "$plat/wkappbot")"
|
|
157
167
|
label="repo bin/$plat/wkappbot"
|
|
158
168
|
fi
|
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wkappbot-code",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.9",
|
|
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": {
|
|
7
|
+
"prepack": "node bin/prepack.js",
|
|
7
8
|
"postinstall": "node bin/postinstall.js"
|
|
8
9
|
},
|
|
9
10
|
"bin": {
|
|
@@ -17,7 +18,8 @@
|
|
|
17
18
|
"plugin/*.js",
|
|
18
19
|
"opencode.json",
|
|
19
20
|
"install.sh",
|
|
20
|
-
"README.md"
|
|
21
|
+
"README.md",
|
|
22
|
+
"harness/*"
|
|
21
23
|
],
|
|
22
24
|
"wkAppbotCode": {
|
|
23
25
|
"opencodeVersion": "1.19.5"
|