gm-qwen 2.0.981 → 2.0.982
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/bootstrap.js +42 -0
- package/bin/plugkit.js +35 -144
- package/gm.json +1 -1
- package/package.json +1 -1
package/bin/bootstrap.js
CHANGED
|
@@ -99,6 +99,42 @@ function fallbackCacheRoot() {
|
|
|
99
99
|
return path.join(os.tmpdir(), 'plugkit-cache', 'bin');
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
function gmToolsDir() {
|
|
103
|
+
const home = process.env.USERPROFILE || process.env.HOME || os.homedir();
|
|
104
|
+
return path.join(home, '.claude', 'gm-tools');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Copy the freshly-resolved plugkit binary + its version+sha manifests to
|
|
108
|
+
// ~/.claude/gm-tools so hooks.json can invoke plugkit directly without going
|
|
109
|
+
// through node. Self-update inside the Rust binary keeps gm-tools fresh from
|
|
110
|
+
// here on. Skipped silently on any error — the next session-start hook will
|
|
111
|
+
// retry via ensure_tools_current.
|
|
112
|
+
function copyToGmTools(finalPath, wrapperDir, version) {
|
|
113
|
+
try {
|
|
114
|
+
const dst = gmToolsDir();
|
|
115
|
+
fs.mkdirSync(dst, { recursive: true });
|
|
116
|
+
const exeName = process.platform === 'win32' ? 'plugkit.exe' : 'plugkit';
|
|
117
|
+
const target = path.join(dst, exeName);
|
|
118
|
+
const targetTmp = target + '.new';
|
|
119
|
+
fs.copyFileSync(finalPath, targetTmp);
|
|
120
|
+
try { fs.renameSync(targetTmp, target); }
|
|
121
|
+
catch (err) {
|
|
122
|
+
if (err.code === 'EEXIST' || err.code === 'EPERM' || err.code === 'EBUSY') {
|
|
123
|
+
// target may be locked by a running plugkit; the .new file persists
|
|
124
|
+
// and the in-Rust self-update will eventually swap it. Leave it.
|
|
125
|
+
} else { throw err; }
|
|
126
|
+
}
|
|
127
|
+
if (process.platform !== 'win32') {
|
|
128
|
+
try { fs.chmodSync(target, 0o755); } catch (_) {}
|
|
129
|
+
}
|
|
130
|
+
fs.writeFileSync(path.join(dst, 'plugkit.version'), version);
|
|
131
|
+
try {
|
|
132
|
+
const srcSha = path.join(wrapperDir, 'plugkit.sha256');
|
|
133
|
+
if (fs.existsSync(srcSha)) fs.copyFileSync(srcSha, path.join(dst, 'plugkit.sha256'));
|
|
134
|
+
} catch (_) {}
|
|
135
|
+
} catch (_) {}
|
|
136
|
+
}
|
|
137
|
+
|
|
102
138
|
function ensureDir(dir) {
|
|
103
139
|
fs.mkdirSync(dir, { recursive: true });
|
|
104
140
|
}
|
|
@@ -349,6 +385,7 @@ async function bootstrap(opts) {
|
|
|
349
385
|
const actualSha = sha256OfFileSync(finalPath);
|
|
350
386
|
if (actualSha === expectedSha) {
|
|
351
387
|
if (!opts.silent) log(`decision: hit reason: sha-match v${version} (${finalPath})`);
|
|
388
|
+
copyToGmTools(finalPath, wrapperDir, version);
|
|
352
389
|
return finalPath;
|
|
353
390
|
}
|
|
354
391
|
log(`decision: fetch reason: cache-hit-sha-mismatch (dir=v${version} expected ${expectedSha.slice(0,12)}… got ${(actualSha||'').slice(0,12)}…)`);
|
|
@@ -362,6 +399,7 @@ async function bootstrap(opts) {
|
|
|
362
399
|
try { fs.unlinkSync(okSentinel); } catch (_) {}
|
|
363
400
|
} else {
|
|
364
401
|
if (!opts.silent) log(`decision: hit reason: sentinel+no-sha-manifest (${finalPath})`);
|
|
402
|
+
copyToGmTools(finalPath, wrapperDir, version);
|
|
365
403
|
return finalPath;
|
|
366
404
|
}
|
|
367
405
|
}
|
|
@@ -369,6 +407,7 @@ async function bootstrap(opts) {
|
|
|
369
407
|
if (healIfShaMatches(finalPath, expectedSha, okSentinel, partialPath, 'plugkit')) {
|
|
370
408
|
if (!opts.silent) log(`decision: heal reason: sha-match (${finalPath})`);
|
|
371
409
|
spawnDetachedRtkFetch(wrapperDir);
|
|
410
|
+
copyToGmTools(finalPath, wrapperDir, version);
|
|
372
411
|
return finalPath;
|
|
373
412
|
}
|
|
374
413
|
|
|
@@ -377,11 +416,13 @@ async function bootstrap(opts) {
|
|
|
377
416
|
try {
|
|
378
417
|
if (fs.existsSync(finalPath) && fs.existsSync(okSentinel)) {
|
|
379
418
|
log(`decision: hit reason: lock-race-resolved (${finalPath})`);
|
|
419
|
+
copyToGmTools(finalPath, wrapperDir, version);
|
|
380
420
|
return finalPath;
|
|
381
421
|
}
|
|
382
422
|
if (healIfShaMatches(finalPath, expectedSha, okSentinel, partialPath, 'plugkit')) {
|
|
383
423
|
log(`decision: heal reason: sha-match-under-lock (${finalPath})`);
|
|
384
424
|
spawnDetachedRtkFetch(wrapperDir);
|
|
425
|
+
copyToGmTools(finalPath, wrapperDir, version);
|
|
385
426
|
return finalPath;
|
|
386
427
|
}
|
|
387
428
|
|
|
@@ -442,6 +483,7 @@ async function bootstrap(opts) {
|
|
|
442
483
|
proactiveKillForNewInstall(version, finalPath);
|
|
443
484
|
pruneOldVersions(root, version, readRtkVersion(wrapperDir));
|
|
444
485
|
spawnDetachedRtkFetch(wrapperDir);
|
|
486
|
+
copyToGmTools(finalPath, wrapperDir, version);
|
|
445
487
|
return finalPath;
|
|
446
488
|
} finally {
|
|
447
489
|
releaseLock(lockPath);
|
package/bin/plugkit.js
CHANGED
|
@@ -1,159 +1,50 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
|
-
|
|
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.
|
|
8
|
+
|
|
9
|
+
const { spawnSync } = require('child_process');
|
|
4
10
|
const path = require('path');
|
|
5
11
|
const fs = require('fs');
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
const dir = __dirname;
|
|
9
|
-
|
|
10
|
-
function readPinnedVersion() {
|
|
11
|
-
try { return fs.readFileSync(path.join(dir, 'plugkit.version'), 'utf8').trim(); } catch (_) { return null; }
|
|
12
|
-
}
|
|
12
|
+
const os = require('os');
|
|
13
13
|
|
|
14
|
-
function
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const text = `${r.stdout || ''} ${r.stderr || ''}`.trim();
|
|
19
|
-
const m = text.match(/(\d+\.\d+\.\d+)/);
|
|
20
|
-
return m ? m[1] : null;
|
|
21
|
-
} catch (_) { return null; }
|
|
14
|
+
function toolsBin() {
|
|
15
|
+
const home = process.env.USERPROFILE || process.env.HOME || os.homedir();
|
|
16
|
+
const exe = process.platform === 'win32' ? 'plugkit.exe' : 'plugkit';
|
|
17
|
+
return path.join(home, '.claude', 'gm-tools', exe);
|
|
22
18
|
}
|
|
23
19
|
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
function envWithRtkOnPath() {
|
|
35
|
-
const rtkPath = resolveCachedRtk({ wrapperDir: dir });
|
|
36
|
-
if (!rtkPath) return process.env;
|
|
37
|
-
const rtkDir = path.dirname(rtkPath);
|
|
38
|
-
const sep = process.platform === 'win32' ? ';' : ':';
|
|
39
|
-
return { ...process.env, PATH: `${rtkDir}${sep}${process.env.PATH || ''}` };
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async function resolveBinary() {
|
|
43
|
-
const cached = resolveCachedBinary({ wrapperDir: dir });
|
|
44
|
-
if (cached) return cached;
|
|
45
|
-
return await bootstrap({ wrapperDir: dir });
|
|
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;
|
|
29
|
+
return null;
|
|
46
30
|
}
|
|
47
31
|
|
|
48
|
-
|
|
32
|
+
function main() {
|
|
49
33
|
const args = process.argv.slice(2);
|
|
50
|
-
// Detached bootstrap entry: just run bootstrap() and exit. Used by session-start
|
|
51
|
-
// to avoid blocking CC startup on a slow GitHub download.
|
|
52
|
-
if (args[0] === '__rtk_only__') {
|
|
53
|
-
try { await bootstrap({ wrapperDir: dir, silent: false }); }
|
|
54
|
-
catch (e) { try { process.stderr.write(`[plugkit-bootstrap-detached] ${e.message}\n`); } catch (_) {} }
|
|
55
|
-
process.exit(0);
|
|
56
|
-
}
|
|
57
34
|
const isHook = args[0] === 'hook';
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
// session-start ALWAYS bootstraps: this is the once-per-session moment
|
|
68
|
-
// where we guarantee the cached binary matches the wrapper-pinned version.
|
|
69
|
-
// If the bootstrap fails (offline) we fall through to whatever the cache
|
|
70
|
-
// currently has — the hook itself isn't blocking, just refreshing.
|
|
71
|
-
if (isHook && hookSubcmd === 'session-start') {
|
|
72
|
-
obsEvent('plugkit_wrapper', 'hook_bootstrap_session_start', { argv: args.slice(0, 4) });
|
|
73
|
-
// Bootstrap can stall 60s+ on a slow GitHub mirror — never block CC startup
|
|
74
|
-
// on it. Detach into a background child; this hook returns immediately
|
|
75
|
-
// with whatever cached binary exists. Subsequent hooks pick up the new
|
|
76
|
-
// binary once the detached bootstrap completes.
|
|
77
|
-
bin = resolveCachedBinary({ wrapperDir: dir }) || legacyFallback();
|
|
78
|
-
try {
|
|
79
|
-
const child = spawn(process.execPath, [__filename, '__rtk_only__'], {
|
|
80
|
-
detached: true,
|
|
81
|
-
stdio: 'ignore',
|
|
82
|
-
windowsHide: true,
|
|
83
|
-
env: { ...process.env, PLUGKIT_BOOTSTRAP_DETACHED: '1' },
|
|
84
|
-
});
|
|
85
|
-
child.unref();
|
|
86
|
-
obsEvent('plugkit_wrapper', 'session_start_bootstrap_detached', { pid: child.pid });
|
|
87
|
-
} catch (e) {
|
|
88
|
-
process.stderr.write(`[plugkit] detached bootstrap spawn failed: ${e.message}\n`);
|
|
89
|
-
}
|
|
90
|
-
// If no cached binary yet (fresh install with bootstrap still downloading),
|
|
91
|
-
// skip running the session-start handler this turn — it will fire next
|
|
92
|
-
// session-start once binary is in place.
|
|
93
|
-
if (!bin) {
|
|
94
|
-
process.stderr.write(`[plugkit] session-start skipped: binary not yet installed (bootstrap running in background).\n`);
|
|
95
|
-
process.exit(0);
|
|
96
|
-
}
|
|
97
|
-
} else if (isHook) {
|
|
98
|
-
bin = (await resolveBinaryWithPinCheck()) || legacyFallback();
|
|
99
|
-
if (!bin) {
|
|
100
|
-
process.stderr.write(`[plugkit] hook ${hookSubcmd} skipped: binary not yet installed. Bootstrap will run on session-start.\n`);
|
|
101
|
-
obsEvent('plugkit_wrapper', 'hook_skip_uncached', { argv: args.slice(0, 4), dur_ms: Date.now() - startedAt });
|
|
102
|
-
process.exit(0);
|
|
103
|
-
}
|
|
104
|
-
} else {
|
|
105
|
-
bin = await resolveBinary();
|
|
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');
|
|
43
|
+
process.exit(1);
|
|
106
44
|
}
|
|
107
|
-
} catch (err) {
|
|
108
|
-
process.stderr.write(`[plugkit] bootstrap failed: ${err.message}\n`);
|
|
109
|
-
obsEvent('plugkit_wrapper', 'bootstrap_failed', { err: err.message, dur_ms: Date.now() - startedAt, argv: args.slice(0, 4), is_hook: isHook });
|
|
110
|
-
const legacy = legacyFallback();
|
|
111
|
-
if (legacy) { bin = legacy; }
|
|
112
|
-
else if (isHook) { process.exit(0); }
|
|
113
|
-
else process.exit(1);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const env = envWithRtkOnPath();
|
|
117
|
-
|
|
118
|
-
if (isHook && !process.stdin.isTTY) {
|
|
119
|
-
const chunks = [];
|
|
120
|
-
process.stdin.on('data', c => chunks.push(c));
|
|
121
|
-
process.stdin.on('end', () => {
|
|
122
|
-
const child = spawn(bin, args, { stdio: ['pipe', 'inherit', 'inherit'], windowsHide: true, env });
|
|
123
|
-
child.stdin.end(Buffer.concat(chunks));
|
|
124
|
-
child.on('close', code => process.exit(code ?? 1));
|
|
125
|
-
child.on('error', () => process.exit(1));
|
|
126
|
-
});
|
|
127
|
-
process.stdin.on('error', () => process.exit(1));
|
|
128
|
-
} else {
|
|
129
|
-
const result = spawnSync(bin, args, { stdio: 'inherit', windowsHide: true, env });
|
|
130
|
-
obsEvent('plugkit_wrapper', 'exit', { dur_ms: Date.now() - startedAt, code: result.status ?? -1 });
|
|
131
|
-
process.exit(result.status ?? 1);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// legacyFallback only returns a binary that lives next to the wrapper. We
|
|
136
|
-
// never reach across to ~/.claude/gm-tools/plugkit.exe or other ambient
|
|
137
|
-
// install dirs — those have proven to mask bootstrap failures by serving a
|
|
138
|
-
// stale version whose hooks silently mismatch the active wrapper code (see
|
|
139
|
-
// the v0.1.292-vs-v0.1.294 incident).
|
|
140
|
-
function legacyFallback() {
|
|
141
|
-
const os = require('os');
|
|
142
|
-
const p = os.platform();
|
|
143
|
-
const a = os.arch();
|
|
144
|
-
let candidates = [];
|
|
145
|
-
if (p === 'win32') {
|
|
146
|
-
candidates = [path.join(dir, a === 'arm64' ? 'plugkit-win32-arm64.exe' : 'plugkit-win32-x64.exe')];
|
|
147
|
-
} else if (p === 'darwin') {
|
|
148
|
-
candidates = [path.join(dir, a === 'arm64' ? 'plugkit-darwin-arm64' : 'plugkit-darwin-x64')];
|
|
149
|
-
} else {
|
|
150
|
-
candidates = [path.join(dir, (a === 'arm64' || a === 'aarch64') ? 'plugkit-linux-arm64' : 'plugkit-linux-x64')];
|
|
151
45
|
}
|
|
152
|
-
|
|
153
|
-
|
|
46
|
+
const r = spawnSync(bin, args, { stdio: 'inherit', windowsHide: true });
|
|
47
|
+
process.exit(r.status ?? 1);
|
|
154
48
|
}
|
|
155
49
|
|
|
156
|
-
main()
|
|
157
|
-
process.stderr.write(`[plugkit] fatal: ${err.message}\n`);
|
|
158
|
-
process.exit(1);
|
|
159
|
-
});
|
|
50
|
+
main();
|
package/gm.json
CHANGED