gm-qwen 2.0.984 → 2.0.985

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.
Files changed (3) hide show
  1. package/bin/plugkit.js +82 -22
  2. package/gm.json +1 -1
  3. package/package.json +1 -1
package/bin/plugkit.js CHANGED
@@ -1,48 +1,108 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
- // Minimal exec wrapper. ZERO bootstrap, ZERO version-probe, ZERO async work.
4
- // Just shell out to ~/.claude/gm-tools/plugkit{.exe} with inherited stdio.
5
- // The Rust binary handles its own self-update at startup (detached); first-time
6
- // bootstrap is done by gm-cc postinstall.js. Hot path is one spawnSync, ~150ms
7
- // of node startup overhead.
3
+ // Hot path: spawnSync to ~/.claude/gm-tools/plugkit.exe with inherited stdio.
4
+ // Cold path (session-start / prompt-submit OR missing binary): synchronously
5
+ // ensure gm-tools/plugkit{.exe} matches the pinned version, then run hook.
6
+ // Cache-aware: when local matches the pin (sha-checked), zero network calls.
8
7
 
9
8
  const { spawnSync } = require('child_process');
10
9
  const path = require('path');
11
10
  const fs = require('fs');
12
11
  const os = require('os');
13
12
 
13
+ const wrapperDir = __dirname;
14
+
14
15
  function toolsBin() {
15
16
  const home = process.env.USERPROFILE || process.env.HOME || os.homedir();
16
17
  const exe = process.platform === 'win32' ? 'plugkit.exe' : 'plugkit';
17
18
  return path.join(home, '.claude', 'gm-tools', exe);
18
19
  }
19
20
 
20
- function legacyBesideWrapper() {
21
- const dir = __dirname;
22
- const p = os.platform();
23
- const a = os.arch();
24
- let candidates = [];
25
- if (p === 'win32') candidates = [path.join(dir, a === 'arm64' ? 'plugkit-win32-arm64.exe' : 'plugkit-win32-x64.exe')];
26
- else if (p === 'darwin') candidates = [path.join(dir, a === 'arm64' ? 'plugkit-darwin-arm64' : 'plugkit-darwin-x64')];
27
- else candidates = [path.join(dir, (a === 'arm64' || a === 'aarch64') ? 'plugkit-linux-arm64' : 'plugkit-linux-x64')];
28
- for (const c of candidates) if (fs.existsSync(c)) return c;
21
+ function sha256OfFileSync(filePath) {
22
+ try {
23
+ const crypto = require('crypto');
24
+ const h = crypto.createHash('sha256');
25
+ const fd = fs.openSync(filePath, 'r');
26
+ try {
27
+ const buf = Buffer.alloc(1 << 20);
28
+ let n;
29
+ while ((n = fs.readSync(fd, buf, 0, buf.length, null)) > 0) h.update(buf.subarray(0, n));
30
+ } finally { fs.closeSync(fd); }
31
+ return h.digest('hex');
32
+ } catch (_) { return null; }
33
+ }
34
+
35
+ function platformAsset() {
36
+ const p = process.platform;
37
+ const a = process.arch;
38
+ if (p === 'win32') return a === 'arm64' ? 'plugkit-win32-arm64.exe' : 'plugkit-win32-x64.exe';
39
+ if (p === 'darwin') return a === 'arm64' ? 'plugkit-darwin-arm64' : 'plugkit-darwin-x64';
40
+ return (a === 'arm64' || a === 'aarch64') ? 'plugkit-linux-arm64' : 'plugkit-linux-x64';
41
+ }
42
+
43
+ function readPinnedVersion() {
44
+ try { return fs.readFileSync(path.join(wrapperDir, 'plugkit.version'), 'utf8').trim(); } catch (_) { return null; }
45
+ }
46
+
47
+ function readExpectedSha() {
48
+ try {
49
+ const manifest = fs.readFileSync(path.join(wrapperDir, 'plugkit.sha256'), 'utf8');
50
+ const asset = platformAsset();
51
+ for (const line of manifest.split(/\r?\n/)) {
52
+ const parts = line.trim().split(/\s+/);
53
+ if (parts.length >= 2 && parts[parts.length - 1].replace(/^\*/, '') === asset) {
54
+ return parts[0].toLowerCase();
55
+ }
56
+ }
57
+ } catch (_) {}
29
58
  return null;
30
59
  }
31
60
 
61
+ // Returns true if gm-tools binary matches pinned version by sha. Fast: no network.
62
+ function isReady() {
63
+ const bin = toolsBin();
64
+ if (!fs.existsSync(bin)) return false;
65
+ const expected = readExpectedSha();
66
+ if (!expected) return true; // no manifest to compare against — trust existence
67
+ const actual = sha256OfFileSync(bin);
68
+ return actual && actual.toLowerCase() === expected;
69
+ }
70
+
71
+ // Synchronously run bootstrap.js in a child node. Blocks until install finishes
72
+ // (or fails). Bootstrap itself is cache-aware: re-download only when sha differs
73
+ // from manifest. Wraps stdio:inherit so the user sees progress.
74
+ function ensureReady(silent) {
75
+ if (isReady()) return true;
76
+ const bootstrap = path.join(wrapperDir, 'bootstrap.js');
77
+ const r = spawnSync(process.execPath, [bootstrap], {
78
+ stdio: silent ? ['ignore', 'pipe', 'pipe'] : ['ignore', 'inherit', 'inherit'],
79
+ windowsHide: true,
80
+ });
81
+ return r.status === 0 && isReady();
82
+ }
83
+
32
84
  function main() {
33
85
  const args = process.argv.slice(2);
34
86
  const isHook = args[0] === 'hook';
35
- let bin = toolsBin();
36
- if (!fs.existsSync(bin)) {
37
- bin = legacyBesideWrapper();
38
- if (!bin) {
39
- // Binary not yet installed. If this is a hook, exit cleanly so CC doesn't
40
- // see an error; postinstall will populate gm-tools on /plugin install.
41
- if (isHook) process.exit(0);
42
- process.stderr.write('[plugkit] binary not found at ~/.claude/gm-tools/plugkit — run postinstall\n');
87
+ const hookSubcmd = isHook ? (args[1] || '') : '';
88
+
89
+ // Synchronous readiness check on these hooks. Hot path: isReady() is sha-match
90
+ // against pinned manifest, returns true in <50ms with no network.
91
+ const blocksUntilReady = hookSubcmd === 'session-start' || hookSubcmd === 'prompt-submit';
92
+
93
+ if (blocksUntilReady) {
94
+ if (!ensureReady(false)) {
95
+ process.stderr.write('[plugkit] bootstrap failed; aborting hook\n');
43
96
  process.exit(1);
44
97
  }
98
+ } else if (!fs.existsSync(toolsBin())) {
99
+ // For non-blocking hooks (pre-tool-use, post-tool-use, stop, etc.): if the
100
+ // binary doesn't exist yet, exit cleanly — session-start will populate it.
101
+ if (isHook) process.exit(0);
102
+ process.exit(1);
45
103
  }
104
+
105
+ const bin = toolsBin();
46
106
  const r = spawnSync(bin, args, { stdio: 'inherit', windowsHide: true });
47
107
  process.exit(r.status ?? 1);
48
108
  }
package/gm.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm",
3
- "version": "2.0.984",
3
+ "version": "2.0.985",
4
4
  "description": "State machine agent with hooks, skills, and automated git enforcement",
5
5
  "author": "AnEntrypoint",
6
6
  "license": "MIT",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-qwen",
3
- "version": "2.0.984",
3
+ "version": "2.0.985",
4
4
  "description": "State machine agent with hooks, skills, and automated git enforcement",
5
5
  "author": "AnEntrypoint",
6
6
  "license": "MIT",