gm-plugkit 2.0.2210 → 2.0.2212

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.
@@ -0,0 +1,236 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const os = require('os');
6
+ const { spawnSync } = require('child_process');
7
+ const { sha256OfFileSync } = require('./gm-process');
8
+ const {
9
+ obsEvent,
10
+ gmToolsDir,
11
+ resolveWindowsExe,
12
+ resolveNpmCliJs,
13
+ } = require('./bootstrap-shared');
14
+
15
+ const NPM_PACKAGE = 'plugkit-wasm';
16
+ const ATTEMPT_TIMEOUT_MS = 10 * 60 * 1000;
17
+ const MAX_ATTEMPTS = 3;
18
+ const BACKOFF_MS = [5000, 15000];
19
+
20
+ function makeLogger(prefix) {
21
+ return function log(msg) {
22
+ try { process.stderr.write(`[${prefix}] ${msg}\n`); } catch (_) {}
23
+ };
24
+ }
25
+
26
+ function resolveProjectRoot(start) {
27
+ const resolved = path.resolve(start);
28
+ try {
29
+ const r = spawnSync('git', ['rev-parse', '--git-common-dir'], { cwd: resolved, encoding: 'utf-8', windowsHide: true, timeout: 1500 });
30
+ if (r.status === 0 && r.stdout && r.stdout.trim()) {
31
+ let commonDir = r.stdout.trim();
32
+ if (!path.isAbsolute(commonDir)) commonDir = path.resolve(resolved, commonDir);
33
+ if (/(^|[\\/])\.git$/.test(commonDir)) return path.dirname(commonDir);
34
+ }
35
+ } catch (_) {}
36
+ return resolved;
37
+ }
38
+
39
+ function writeBootstrapError(spec) {
40
+ try {
41
+ const projectDir = resolveProjectRoot(process.env.CLAUDE_PROJECT_DIR || process.cwd());
42
+ const spoolDir = path.join(projectDir, '.gm', 'exec-spool');
43
+ fs.mkdirSync(spoolDir, { recursive: true });
44
+ fs.writeFileSync(path.join(spoolDir, '.bootstrap-error.json'), JSON.stringify({ ts: new Date().toISOString(), ...spec }, null, 2));
45
+ } catch (_) {}
46
+ }
47
+
48
+ function clearBootstrapError() {
49
+ try {
50
+ const projectDir = resolveProjectRoot(process.env.CLAUDE_PROJECT_DIR || process.cwd());
51
+ fs.unlinkSync(path.join(projectDir, '.gm', 'exec-spool', '.bootstrap-error.json'));
52
+ } catch (_) {}
53
+ }
54
+
55
+ function readVersionFile(wrapperDir) {
56
+ const p = path.join(wrapperDir, 'plugkit.version');
57
+ if (!fs.existsSync(p)) throw new Error(`plugkit.version not found at ${p}`);
58
+ return fs.readFileSync(p, 'utf8').trim();
59
+ }
60
+
61
+ function readShaManifest(wrapperDir, manifestName) {
62
+ const p = path.join(wrapperDir, manifestName || 'plugkit.sha256');
63
+ if (!fs.existsSync(p)) return null;
64
+ const raw = fs.readFileSync(p, 'utf8');
65
+ try {
66
+ const parsed = JSON.parse(raw);
67
+ if (parsed && typeof parsed === 'object') {
68
+ const out = {};
69
+ for (const [name, sha] of Object.entries(parsed)) {
70
+ if (typeof sha === 'string') out[name] = sha.toLowerCase();
71
+ }
72
+ return out;
73
+ }
74
+ } catch (_) {}
75
+ const out = {};
76
+ for (const line of raw.split(/\r?\n/)) {
77
+ const m = line.match(/^([0-9a-f]{64})\s+(\S+)\s*$/i);
78
+ if (m) out[m[2]] = m[1].toLowerCase();
79
+ }
80
+ return out;
81
+ }
82
+
83
+ function copyWasmToGmTools(wasmPath, version, opts) {
84
+ opts = opts || {};
85
+ const dst = gmToolsDir();
86
+ fs.mkdirSync(dst, { recursive: true });
87
+ const target = path.join(dst, 'plugkit.wasm');
88
+
89
+ let wasmFresh = false;
90
+ if (fs.existsSync(target)) {
91
+ try {
92
+ const cur = sha256OfFileSync(target);
93
+ const src = sha256OfFileSync(wasmPath);
94
+ if (cur === src) wasmFresh = true;
95
+ } catch (_) {}
96
+ }
97
+ if (!wasmFresh) {
98
+ const tmp = `${target}.partial-${process.pid}`;
99
+ fs.copyFileSync(wasmPath, tmp);
100
+ try { fs.renameSync(tmp, target); }
101
+ catch (err) {
102
+ if (err.code === 'EEXIST' || err.code === 'EPERM') {
103
+ try { fs.unlinkSync(target); } catch (_) {}
104
+ fs.renameSync(tmp, target);
105
+ } else {
106
+ try { fs.unlinkSync(tmp); } catch (_) {}
107
+ throw err;
108
+ }
109
+ }
110
+ }
111
+ fs.writeFileSync(path.join(dst, 'plugkit.version'), version);
112
+
113
+ if (opts.wrapperDir) {
114
+ try {
115
+ const srcSha = path.join(opts.wrapperDir, 'plugkit.sha256');
116
+ if (fs.existsSync(srcSha)) fs.copyFileSync(srcSha, path.join(dst, 'plugkit.sha256'));
117
+ } catch (_) {}
118
+ }
119
+ }
120
+
121
+ function resolveCacheWasmPath(root, version, wasmName) {
122
+ const verDir = path.join(root, `v${version}`);
123
+ const wasmPath = path.join(verDir, wasmName || 'plugkit.wasm');
124
+ const okSentinel = path.join(verDir, '.wasm-ok');
125
+ if (fs.existsSync(wasmPath) && fs.existsSync(okSentinel)) return wasmPath;
126
+ return null;
127
+ }
128
+
129
+ function resolveInstalledWasmPath() {
130
+ const home = process.env.USERPROFILE || process.env.HOME || os.homedir();
131
+ const primary = path.join(home, '.gm-tools', 'plugkit.wasm');
132
+ const fallback = path.join(home, '.claude', 'gm-tools', 'plugkit.wasm');
133
+ if (fs.existsSync(primary)) return primary;
134
+ if (fs.existsSync(fallback)) return fallback;
135
+ return primary;
136
+ }
137
+
138
+ async function extractNpmPackageWasm(destPath, version, opts) {
139
+ opts = opts || {};
140
+ const log = opts.log || makeLogger('plugkit-bootstrap');
141
+ const { ensureDir } = require('./bootstrap-shared');
142
+ const tempDir = path.join(path.dirname(destPath), '.npm-extract-' + Date.now());
143
+ try {
144
+ ensureDir(tempDir);
145
+ const startMs = Date.now();
146
+ log(`extracting npm package ${NPM_PACKAGE}@${version} to ${tempDir}`);
147
+ obsEvent('bootstrap', 'npm.extract.start', { package: NPM_PACKAGE, version });
148
+
149
+ fs.writeFileSync(path.join(tempDir, 'package.json'), JSON.stringify({ name: 'plugkit-extract', version: '0.0.0', private: true }));
150
+
151
+ const cmd = resolveWindowsExe('npm');
152
+ const installArgs = ['install', '--no-audit', '--no-fund', '--no-save', NPM_PACKAGE + '@' + version];
153
+ const isCmdShim = process.platform === 'win32' && /\.(cmd|bat)$/i.test(cmd);
154
+ const npmCliJs = isCmdShim ? resolveNpmCliJs(cmd) : null;
155
+
156
+ const spawnCmd = npmCliJs ? process.execPath : (isCmdShim ? `"${cmd}"` : cmd);
157
+ const rawArgs = npmCliJs ? [npmCliJs, ...installArgs] : installArgs;
158
+ const spawnArgs = (isCmdShim && !npmCliJs) ? rawArgs.map(a => /[\s"]/.test(a) ? `"${a.replace(/"/g, '\\"')}"` : a) : rawArgs;
159
+
160
+ const result = spawnSync(spawnCmd, spawnArgs, {
161
+ cwd: tempDir,
162
+ stdio: ['ignore', 'pipe', 'pipe'],
163
+ timeout: ATTEMPT_TIMEOUT_MS,
164
+ encoding: 'utf8',
165
+ windowsHide: true,
166
+ ...((isCmdShim && !npmCliJs) ? { shell: true } : {}),
167
+ });
168
+
169
+ if (result.error) throw result.error;
170
+ if (result.status !== 0) {
171
+ const detail = (result.stderr || result.stdout || '').trim().split(/\r?\n/).slice(-5).join(' | ');
172
+ const sig = result.signal ? ` signal=${result.signal}` : '';
173
+ throw new Error(`npm install failed status=${result.status}${sig}: ${detail || 'no stderr/stdout captured'}`);
174
+ }
175
+
176
+ const nodeModulesPath = path.join(tempDir, 'node_modules', NPM_PACKAGE, 'plugkit.wasm');
177
+ if (!fs.existsSync(nodeModulesPath)) {
178
+ throw new Error(`plugkit.wasm not found in extracted npm package at ${nodeModulesPath}`);
179
+ }
180
+
181
+ fs.copyFileSync(nodeModulesPath, destPath);
182
+ log(`extracted ${nodeModulesPath} -> ${destPath}`);
183
+ obsEvent('bootstrap', 'npm.extract.end', { dur_ms: Date.now() - startMs, ok: true });
184
+ } finally {
185
+ try { fs.rmSync(tempDir, { recursive: true, force: true, maxRetries: 1, retryDelay: 50 }); } catch (_) {}
186
+ }
187
+ }
188
+
189
+ async function extractNpmPackageWithRetry(destPath, version, opts) {
190
+ opts = opts || {};
191
+ const log = opts.log || makeLogger('plugkit-bootstrap');
192
+ let lastErr;
193
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
194
+ try {
195
+ log(`npm extract attempt ${attempt}/${MAX_ATTEMPTS}: ${NPM_PACKAGE}@${version}`);
196
+ await extractNpmPackageWasm(destPath, version, opts);
197
+ return;
198
+ } catch (err) {
199
+ lastErr = err;
200
+ log(`attempt ${attempt} failed: ${err.message}`);
201
+ obsEvent('bootstrap', 'npm.extract.attempt_failed', { package: NPM_PACKAGE, attempt, max: MAX_ATTEMPTS, err: String(err.message || err) });
202
+ if (err && (err.code === 'ENOENT' || /ENOENT/.test(String(err.message || '')))) {
203
+ log(`npm binary unresolvable (ENOENT); skipping retries, falling back`);
204
+ throw err;
205
+ }
206
+ if (err && (err.code === 'EINVAL' || /EINVAL/.test(String(err.message || '')))) {
207
+ log(`spawn EINVAL on npm shim; skipping retries, falling back`);
208
+ throw err;
209
+ }
210
+ if (attempt < MAX_ATTEMPTS) {
211
+ const wait = BACKOFF_MS[attempt - 1] || 120000;
212
+ log(`backing off ${wait}ms`);
213
+ await new Promise(r => setTimeout(r, wait));
214
+ }
215
+ }
216
+ }
217
+ throw lastErr;
218
+ }
219
+
220
+ module.exports = {
221
+ NPM_PACKAGE,
222
+ ATTEMPT_TIMEOUT_MS,
223
+ MAX_ATTEMPTS,
224
+ BACKOFF_MS,
225
+ makeLogger,
226
+ resolveProjectRoot,
227
+ writeBootstrapError,
228
+ clearBootstrapError,
229
+ readVersionFile,
230
+ readShaManifest,
231
+ copyWasmToGmTools,
232
+ resolveCacheWasmPath,
233
+ resolveInstalledWasmPath,
234
+ extractNpmPackageWasm,
235
+ extractNpmPackageWithRetry,
236
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-plugkit",
3
- "version": "2.0.2210",
3
+ "version": "2.0.2212",
4
4
  "description": "Bootstrap and daemon-spawn tool for gm plugkit binary. Downloads the correct platform wasm, verifies SHA256, and launches agentplug-runner (the native wasm host) as the spool watcher daemon.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -11,6 +11,7 @@
11
11
  "index.js",
12
12
  "bootstrap.js",
13
13
  "bootstrap-shared.js",
14
+ "bootstrap-wasm-core.js",
14
15
  "gm-log.js",
15
16
  "gm-process.js",
16
17
  "plugkit.version",
package/plugkit.version CHANGED
@@ -1 +1 @@
1
- 0.1.1049
1
+ 0.1.1050