gm-plugkit 2.0.1120 → 2.0.1121

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/bootstrap.js CHANGED
@@ -597,22 +597,31 @@ function copyWasmToGmTools(wasmPath, version) {
597
597
  const dst = gmToolsDir();
598
598
  fs.mkdirSync(dst, { recursive: true });
599
599
  const target = path.join(dst, 'plugkit.wasm');
600
+ const wrapperSrc = path.join(__dirname, 'plugkit-wasm-wrapper.js');
601
+ const wrapperDst = path.join(dst, 'plugkit-wasm-wrapper.js');
600
602
 
603
+ let wasmFresh = false;
601
604
  if (fs.existsSync(target)) {
602
- let needsRefresh = true;
603
605
  try {
604
606
  const cur = sha256OfFileSync(target);
605
607
  const src = sha256OfFileSync(wasmPath);
606
- if (cur === src) needsRefresh = false;
608
+ if (cur === src) wasmFresh = true;
607
609
  } catch (_) {}
608
- if (!needsRefresh) {
609
- try { fs.writeFileSync(path.join(dst, 'plugkit.version'), version); } catch (_) {}
610
- return;
611
- }
612
610
  }
613
-
614
- fs.copyFileSync(wasmPath, target);
611
+ if (!wasmFresh) fs.copyFileSync(wasmPath, target);
615
612
  fs.writeFileSync(path.join(dst, 'plugkit.version'), version);
613
+
614
+ if (fs.existsSync(wrapperSrc)) {
615
+ let wrapperFresh = false;
616
+ if (fs.existsSync(wrapperDst)) {
617
+ try {
618
+ const cur = sha256OfFileSync(wrapperDst);
619
+ const src = sha256OfFileSync(wrapperSrc);
620
+ if (cur === src) wrapperFresh = true;
621
+ } catch (_) {}
622
+ }
623
+ if (!wrapperFresh) fs.copyFileSync(wrapperSrc, wrapperDst);
624
+ }
616
625
  }
617
626
 
618
627
  function getWasmPath() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-plugkit",
3
- "version": "2.0.1120",
3
+ "version": "2.0.1121",
4
4
  "description": "Bootstrap and daemon-spawn tool for gm plugkit binary. Downloads the correct platform binary, verifies SHA256, and starts the spool watcher daemon. Includes plugkit-wasm-wrapper for WASM-based spool watching.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -6,6 +6,10 @@ import https from 'https';
6
6
  import { watch } from 'fs';
7
7
  import { spawn, spawnSync } from 'child_process';
8
8
  import net from 'net';
9
+ import { fileURLToPath } from 'url';
10
+
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = path.dirname(__filename);
9
13
 
10
14
  const KV_DIR = path.join(os.homedir(), '.claude', 'gm-tools', 'kv');
11
15
  fs.mkdirSync(KV_DIR, { recursive: true });
@@ -632,6 +636,24 @@ async function runSpoolWatcher(instance, spoolDir) {
632
636
  process.on('SIGTERM', () => { releaseLock(); process.exit(0); });
633
637
  process.on('exit', releaseLock);
634
638
 
639
+ try {
640
+ const wrapperDst = path.join(os.homedir(), '.claude', 'gm-tools', 'plugkit-wasm-wrapper.js');
641
+ if (path.resolve(__filename) !== path.resolve(wrapperDst)) {
642
+ let same = false;
643
+ if (fs.existsSync(wrapperDst)) {
644
+ try {
645
+ const a = fs.readFileSync(__filename);
646
+ const b = fs.readFileSync(wrapperDst);
647
+ if (a.length === b.length && crypto.createHash('sha256').update(a).digest('hex') === crypto.createHash('sha256').update(b).digest('hex')) same = true;
648
+ } catch (_) {}
649
+ }
650
+ if (!same) {
651
+ fs.copyFileSync(__filename, wrapperDst);
652
+ console.log(`[plugkit-wasm] installed wrapper at ${wrapperDst}`);
653
+ }
654
+ }
655
+ } catch (e) { console.error(`[plugkit-wasm] wrapper self-install failed: ${e.message}`); }
656
+
635
657
  console.log(`[plugkit-wasm] plugkit v${resolveVersion(instance)} (wasm)`);
636
658
  console.log(`[plugkit-wasm] watching ${inDir}`);
637
659
 
@@ -853,22 +875,116 @@ async function runSpoolWatcher(instance, spoolDir) {
853
875
  await new Promise(() => {});
854
876
  }
855
877
 
856
- (async () => {
878
+ async function selfHealFromGithubReleases() {
879
+ return new Promise((resolve, reject) => {
880
+ const fetchJson = (url) => new Promise((res, rej) => {
881
+ const req = https.get(url, { timeout: 5000, headers: { 'user-agent': 'plugkit-wasm-wrapper', 'accept': 'application/json' } }, (r) => {
882
+ if (r.statusCode >= 300 && r.statusCode < 400 && r.headers.location) {
883
+ r.resume(); fetchJson(r.headers.location).then(res, rej); return;
884
+ }
885
+ if (r.statusCode !== 200) { r.resume(); rej(new Error(`HTTP ${r.statusCode} ${url}`)); return; }
886
+ const chunks = []; r.on('data', c => chunks.push(c));
887
+ r.on('end', () => { try { res(JSON.parse(Buffer.concat(chunks).toString('utf-8'))); } catch (e) { rej(e); } });
888
+ r.on('error', rej);
889
+ });
890
+ req.on('timeout', () => req.destroy(new Error('timeout')));
891
+ req.on('error', rej);
892
+ });
893
+ const fetchBuf = (url) => new Promise((res, rej) => {
894
+ const req = https.get(url, { timeout: 30000, headers: { 'user-agent': 'plugkit-wasm-wrapper' } }, (r) => {
895
+ if (r.statusCode >= 300 && r.statusCode < 400 && r.headers.location) {
896
+ r.resume(); fetchBuf(r.headers.location).then(res, rej); return;
897
+ }
898
+ if (r.statusCode !== 200) { r.resume(); rej(new Error(`HTTP ${r.statusCode} ${url}`)); return; }
899
+ const chunks = []; r.on('data', c => chunks.push(c));
900
+ r.on('end', () => res(Buffer.concat(chunks)));
901
+ r.on('error', rej);
902
+ });
903
+ req.on('timeout', () => req.destroy(new Error('timeout')));
904
+ req.on('error', rej);
905
+ });
906
+ (async () => {
907
+ try {
908
+ const rel = await fetchJson('https://api.github.com/repos/AnEntrypoint/plugkit-bin/releases/latest');
909
+ const tag = rel.tag_name;
910
+ if (!tag) throw new Error('no tag_name from GH Releases');
911
+ const version = tag.replace(/^v/, '');
912
+ const base = `https://github.com/AnEntrypoint/plugkit-bin/releases/download/${tag}`;
913
+ const [wasm, sha] = await Promise.all([
914
+ fetchBuf(`${base}/plugkit.wasm`),
915
+ fetchBuf(`${base}/plugkit.wasm.sha256`).then(b => b.toString('utf-8').trim().split(/\s+/)[0]).catch(() => ''),
916
+ ]);
917
+ if (sha) {
918
+ const got = crypto.createHash('sha256').update(wasm).digest('hex');
919
+ if (got !== sha) throw new Error(`sha mismatch: got ${got}, expected ${sha}`);
920
+ }
921
+ const toolsDir = path.join(os.homedir(), '.claude', 'gm-tools');
922
+ fs.mkdirSync(toolsDir, { recursive: true });
923
+ fs.writeFileSync(path.join(toolsDir, 'plugkit.wasm'), wasm);
924
+ fs.writeFileSync(path.join(toolsDir, 'plugkit.version'), version);
925
+ const wrapperSrc = __filename;
926
+ const wrapperDst = path.join(toolsDir, 'plugkit-wasm-wrapper.js');
927
+ if (path.resolve(wrapperSrc) !== path.resolve(wrapperDst) && fs.existsSync(wrapperSrc)) {
928
+ try { fs.copyFileSync(wrapperSrc, wrapperDst); } catch (_) {}
929
+ }
930
+ resolve({ ok: true, version, sha });
931
+ } catch (e) { reject(e); }
932
+ })();
933
+ });
934
+ }
935
+
936
+ async function selfHeal(reason) {
937
+ console.error(`[plugkit-wasm] self-heal: ${reason}`);
857
938
  try {
858
- const wasmPath = path.join(os.homedir(), '.claude', 'gm-tools', 'plugkit.wasm');
859
- const wasmBuffer = fs.readFileSync(wasmPath);
860
- const wasmModule = new WebAssembly.Module(wasmBuffer);
939
+ const r = await selfHealFromGithubReleases();
940
+ console.error(`[plugkit-wasm] self-heal: installed v${r.version} from GH Releases`);
941
+ return true;
942
+ } catch (e) {
943
+ console.error(`[plugkit-wasm] self-heal GH fetch failed: ${e.message}`);
944
+ }
945
+ console.error('[plugkit-wasm] self-heal: run `bun x gm-plugkit@latest spool` to recover manually');
946
+ return false;
947
+ }
861
948
 
862
- const instanceRef = { value: null };
863
- const hostFunctions = makeHostFunctions(instanceRef);
949
+ async function tryInstantiate(wasmPath) {
950
+ const wasmBuffer = fs.readFileSync(wasmPath);
951
+ const wasmModule = new WebAssembly.Module(wasmBuffer);
952
+ const instanceRef = { value: null };
953
+ const hostFunctions = makeHostFunctions(instanceRef);
954
+ const importObject = {
955
+ env: hostFunctions,
956
+ wasi_snapshot_preview1: createWasiShim(instanceRef),
957
+ };
958
+ const instance = new WebAssembly.Instance(wasmModule, importObject);
959
+ instanceRef.value = instance;
960
+ return { instance, instanceRef };
961
+ }
864
962
 
865
- const importObject = {
866
- env: hostFunctions,
867
- wasi_snapshot_preview1: createWasiShim(instanceRef),
868
- };
963
+ (async () => {
964
+ try {
965
+ const wasmPath = path.join(os.homedir(), '.claude', 'gm-tools', 'plugkit.wasm');
869
966
 
870
- const instance = new WebAssembly.Instance(wasmModule, importObject);
871
- instanceRef.value = instance;
967
+ let instance, instanceRef;
968
+ if (!fs.existsSync(wasmPath)) {
969
+ const healed = await selfHeal('wasm not installed');
970
+ if (!healed) process.exit(1);
971
+ }
972
+ try {
973
+ ({ instance, instanceRef } = await tryInstantiate(wasmPath));
974
+ } catch (e) {
975
+ const isLink = e && (e.name === 'LinkError' || /Import/i.test(e.message || ''));
976
+ const isCompile = e && (e.name === 'CompileError' || /WebAssembly/i.test(e.message || ''));
977
+ if (isLink || isCompile) {
978
+ const healed = await selfHeal(`${e.name || 'instantiate'}: ${e.message}`);
979
+ if (!healed) {
980
+ console.error('[plugkit-wasm] wrapper/wasm version skew — run: bun x gm-plugkit@latest spool');
981
+ process.exit(1);
982
+ }
983
+ ({ instance, instanceRef } = await tryInstantiate(wasmPath));
984
+ } else {
985
+ throw e;
986
+ }
987
+ }
872
988
 
873
989
  const args = process.argv.slice(2);
874
990
  if (args.includes('--version')) {
@@ -876,6 +992,25 @@ async function runSpoolWatcher(instance, spoolDir) {
876
992
  process.exit(0);
877
993
  }
878
994
 
995
+ if (args[0] === 'bootstrap' || args.includes('--ensure-latest')) {
996
+ try {
997
+ const bootstrapPath = path.join(__dirname, 'bootstrap.js');
998
+ if (fs.existsSync(bootstrapPath)) {
999
+ const bootstrap = await import('file://' + bootstrapPath.replace(/\\/g, '/'));
1000
+ if (bootstrap && typeof bootstrap.ensureReady === 'function') {
1001
+ const r = await bootstrap.ensureReady({ forceLatest: true });
1002
+ console.log(JSON.stringify(r || { ok: true }));
1003
+ process.exit(0);
1004
+ }
1005
+ }
1006
+ console.error('bootstrap.js not callable');
1007
+ process.exit(1);
1008
+ } catch (e) {
1009
+ console.error('bootstrap error:', e.message);
1010
+ process.exit(1);
1011
+ }
1012
+ }
1013
+
879
1014
  if (args[0] === 'spool') {
880
1015
  const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
881
1016
  const spoolDir = path.join(projectDir, '.gm', 'exec-spool');