gm-plugkit 2.0.1075 → 2.0.1076

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 (2) hide show
  1. package/bootstrap.js +55 -257
  2. package/package.json +1 -1
package/bootstrap.js CHANGED
@@ -51,18 +51,6 @@ function clearBootstrapError() {
51
51
  } catch (_) {}
52
52
  }
53
53
 
54
- function platformKey() {
55
- const p = os.platform();
56
- const a = os.arch();
57
- if (p === 'win32') return a === 'arm64' ? 'win32-arm64' : 'win32-x64';
58
- if (p === 'darwin') return a === 'arm64' ? 'darwin-arm64' : 'darwin-x64';
59
- return (a === 'arm64' || a === 'aarch64') ? 'linux-arm64' : 'linux-x64';
60
- }
61
-
62
- function binaryName() {
63
- const key = platformKey();
64
- return key.startsWith('win32') ? `plugkit-${key}.exe` : `plugkit-${key}`;
65
- }
66
54
 
67
55
  function cacheRoot() {
68
56
  const home = os.homedir();
@@ -223,74 +211,13 @@ async function extractNpmPackageWithRetry(destPath, version) {
223
211
  throw lastErr;
224
212
  }
225
213
 
226
- function killHoldersOfPath(targetPath) {
227
- if (process.platform !== 'win32') return 0;
228
- try {
229
- const norm = path.resolve(targetPath).replace(/\//g, '\\');
230
- const r = spawnSync('wmic', ['process', 'where', `ExecutablePath='${norm.replace(/\\/g, '\\\\')}'`, 'get', 'ProcessId', '/format:value'], { encoding: 'utf8', windowsHide: true, timeout: 5000 });
231
- if (r.status !== 0 || !r.stdout) return 0;
232
- const pids = [];
233
- for (const line of r.stdout.split(/\r?\n/)) {
234
- const m = line.match(/ProcessId=(\d+)/);
235
- if (m) {
236
- const pid = parseInt(m[1], 10);
237
- if (Number.isFinite(pid) && pid !== process.pid) pids.push(pid);
238
- }
239
- }
240
- for (const pid of pids) {
241
- try { spawnSync('taskkill', ['/F', '/PID', String(pid)], { windowsHide: true, timeout: 3000 }); } catch (_) {}
242
- }
243
- return pids.length;
244
- } catch (_) { return 0; }
245
- }
246
-
247
- function renameWithRetry(src, dst, attempts) {
248
- for (let i = 0; i < attempts; i++) {
249
- try { fs.renameSync(src, dst); return true; }
250
- catch (err) {
251
- if (err.code !== 'EEXIST' && err.code !== 'EPERM' && err.code !== 'EBUSY' && err.code !== 'EACCES') throw err;
252
- if (i === Math.floor(attempts / 2)) killHoldersOfPath(dst);
253
- try { spawnSync(process.execPath, ['-e', 'setTimeout(()=>{}, 200)'], { timeout: 400, killSignal: 'SIGKILL', stdio: 'ignore', windowsHide: true }); } catch (_) {}
254
- }
255
- }
256
- return false;
257
- }
258
214
 
259
- function copyToGmTools(finalPath, version) {
260
- const dst = gmToolsDir();
261
- fs.mkdirSync(dst, { recursive: true });
262
- const exeName = process.platform === 'win32' ? 'plugkit.exe' : 'plugkit';
263
- const target = path.join(dst, exeName);
264
- const targetTmp = target + '.new';
265
-
266
- if (fs.existsSync(target)) {
267
- let needsRefresh = true;
268
- try {
269
- const cur = sha256OfFileSync(target);
270
- const src = sha256OfFileSync(finalPath);
271
- if (cur === src) needsRefresh = false;
272
- } catch (_) {}
273
- if (!needsRefresh) {
274
- try { fs.writeFileSync(path.join(dst, 'plugkit.version'), version); } catch (_) {}
275
- return;
276
- }
277
- try { killHoldersOfPath(target); } catch (_) {}
278
- }
279
-
280
- fs.copyFileSync(finalPath, targetTmp);
281
- if (!renameWithRetry(targetTmp, target, 8)) {
282
- try { killHoldersOfPath(target); } catch (_) {}
283
- try { fs.unlinkSync(target); } catch (_) {}
284
- try { fs.renameSync(targetTmp, target); }
285
- catch (_) {
286
- try { fs.unlinkSync(targetTmp); } catch (_) {}
287
- throw new Error(`gm-tools update blocked: cannot replace ${target}`);
288
- }
289
- }
290
- if (process.platform !== 'win32') {
291
- try { fs.chmodSync(target, 0o755); } catch (_) {}
292
- }
293
- fs.writeFileSync(path.join(dst, 'plugkit.version'), version);
215
+ function platformKey() {
216
+ const p = os.platform();
217
+ const a = os.arch();
218
+ if (p === 'win32') return a === 'arm64' ? 'win32-arm64' : 'win32-x64';
219
+ if (p === 'darwin') return a === 'arm64' ? 'darwin-arm64' : 'darwin-x64';
220
+ return (a === 'arm64' || a === 'aarch64') ? 'linux-arm64' : 'linux-x64';
294
221
  }
295
222
 
296
223
  function rtkBinaryName() {
@@ -389,71 +316,6 @@ function killSpoolWatcherInCwd(reason) {
389
316
  return null;
390
317
  }
391
318
 
392
- function listRunningPlugkitImagePaths() {
393
- const out = [];
394
- try {
395
- if (os.platform() === 'win32') {
396
- let parsed = null;
397
- try {
398
- const p = spawnSync('powershell', ['-NoProfile', '-NonInteractive', '-Command', "Get-Process plugkit* -ErrorAction SilentlyContinue | Select-Object Id,Path | ConvertTo-Json -Compress"], { windowsHide: true, encoding: 'utf8', timeout: 5000, killSignal: 'SIGKILL', stdio: ['ignore', 'pipe', 'pipe'] });
399
- const text = ((p && p.stdout) || '').trim();
400
- if (text) {
401
- const j = JSON.parse(text);
402
- parsed = Array.isArray(j) ? j : [j];
403
- }
404
- } catch (_) {}
405
- if (parsed) {
406
- for (const item of parsed) {
407
- if (!item) continue;
408
- const pid = parseInt(item.Id, 10);
409
- if (!Number.isFinite(pid)) continue;
410
- out.push({ pid, path: (item.Path || '').trim() });
411
- }
412
- } else {
413
- const r = spawnSync('tasklist', ['/FI', 'IMAGENAME eq plugkit*', '/FO', 'CSV', '/NH'], { windowsHide: true, encoding: 'utf8', timeout: 5000, killSignal: 'SIGKILL', stdio: ['ignore', 'pipe', 'pipe'] });
414
- const text = (r && r.stdout) || '';
415
- const seen = new Set();
416
- for (const line of text.split(/\r?\n/)) {
417
- const m = line.match(/^"([^"]+)","(\d+)"/);
418
- if (!m) continue;
419
- const pid = parseInt(m[2], 10);
420
- if (!Number.isFinite(pid) || seen.has(pid)) continue;
421
- seen.add(pid);
422
- out.push({ pid, path: '' });
423
- }
424
- }
425
- } else if (os.platform() === 'linux') {
426
- let entries = [];
427
- try { entries = fs.readdirSync('/proc'); } catch (_) {}
428
- for (const e of entries) {
429
- if (!/^\d+$/.test(e)) continue;
430
- const pid = parseInt(e, 10);
431
- let comm = '';
432
- try { comm = fs.readFileSync(`/proc/${pid}/comm`, 'utf8').trim(); } catch (_) { continue; }
433
- if (!/^plugkit/i.test(comm)) continue;
434
- let imagePath = '';
435
- try { imagePath = fs.readlinkSync(`/proc/${pid}/exe`); } catch (_) {}
436
- out.push({ pid, path: imagePath });
437
- }
438
- } else {
439
- const r = spawnSync('ps', ['-axo', 'pid=,comm='], { encoding: 'utf8', timeout: 5000, killSignal: 'SIGKILL', stdio: ['ignore', 'pipe', 'pipe'] });
440
- const text = (r && r.stdout) || '';
441
- for (const line of text.split(/\r?\n/)) {
442
- const m = line.match(/^\s*(\d+)\s+(.+?)\s*$/);
443
- if (!m) continue;
444
- if (!/plugkit/i.test(m[2])) continue;
445
- const pid = parseInt(m[1], 10);
446
- let imagePath = '';
447
- try {
448
- const p = spawnSync('ps', ['-p', String(pid), '-o', 'command='], { encoding: 'utf8', timeout: 3000, killSignal: 'SIGKILL', stdio: ['ignore', 'pipe', 'pipe'] });
449
- imagePath = ((p && p.stdout) || '').trim().split(/\s+/)[0] || '';
450
- } catch (_) {}
451
- out.push({ pid, path: imagePath });
452
- }
453
- }
454
- } catch (_) {}
455
- return out;
456
- }
457
319
 
458
320
  function isLockStale(lockPath) {
459
321
  try {
@@ -486,25 +348,9 @@ function pruneOldVersions(root, keepVersion, keepRtkVersion) {
486
348
  } catch (_) {}
487
349
  }
488
350
 
489
- function proactiveKillForNewInstall(installedVersion, finalPath) {
351
+ function proactiveKillForNewInstall(installedVersion) {
490
352
  try {
491
353
  const reason = `install:v${installedVersion}`;
492
- const target = finalPath ? path.resolve(finalPath).toLowerCase() : null;
493
- const cacheRootNorm = (() => {
494
- try { return path.resolve(cacheRoot()).toLowerCase(); } catch (_) { return null; }
495
- })();
496
- const procs = listRunningPlugkitImagePaths();
497
- for (const { pid, path: imagePath } of procs) {
498
- if (!Number.isFinite(pid) || pid === process.pid) continue;
499
- if (!imagePath) continue;
500
- const norm = path.resolve(imagePath).toLowerCase();
501
- if (target && norm === target) continue;
502
- if (!cacheRootNorm || !norm.startsWith(cacheRootNorm + path.sep.toLowerCase())) continue;
503
- if (killPid(pid)) {
504
- try { process.stderr.write(`[bootstrap] killed stale daemon pid=${pid} path=${imagePath} (current install: v${installedVersion})\n`); } catch (_) {}
505
- obsEvent('bootstrap', 'daemon.killed', { pid, oldPath: imagePath, installedVersion, mechanism: 'process-path' });
506
- }
507
- }
508
354
  killRunningDaemons(reason);
509
355
  killSpoolWatcherInCwd(reason);
510
356
  writeDaemonVersion(installedVersion);
@@ -525,19 +371,6 @@ function killStaleDaemonIfVersionChanged() {
525
371
  writeDaemonVersion(currentVersion);
526
372
  }
527
373
 
528
- function resolveCachedBinary(opts) {
529
- opts = opts || {};
530
- const version = opts.version || readVersionFile();
531
- const root = (() => {
532
- try { const r = cacheRoot(); ensureDir(r); return r; }
533
- catch (_) { const r = fallbackCacheRoot(); ensureDir(r); return r; }
534
- })();
535
- const verDir = path.join(root, `v${version}`);
536
- const finalPath = path.join(verDir, binaryName());
537
- const okSentinel = path.join(verDir, '.ok');
538
- if (fs.existsSync(finalPath) && fs.existsSync(okSentinel)) return finalPath;
539
- return null;
540
- }
541
374
 
542
375
  function resolveCachedRtk() {
543
376
  const version = readVersionFile();
@@ -638,8 +471,8 @@ async function bootstrap(opts) {
638
471
  opts = opts || {};
639
472
  const version = readVersionFile();
640
473
  const shaManifest = readShaManifest();
641
- const binName = binaryName();
642
- const expectedSha = shaManifest ? shaManifest[binName] : null;
474
+ const wasmName = 'plugkit.wasm';
475
+ const expectedSha = shaManifest ? shaManifest[wasmName] : null;
643
476
 
644
477
  let root = cacheRoot();
645
478
  try { ensureDir(root); }
@@ -648,7 +481,7 @@ async function bootstrap(opts) {
648
481
  const verDir = path.join(root, `v${version}`);
649
482
  ensureDir(verDir);
650
483
 
651
- const finalPath = path.join(verDir, binName);
484
+ const finalPath = path.join(verDir, wasmName);
652
485
  const okSentinel = path.join(verDir, '.ok');
653
486
  const partialPath = `${finalPath}.partial`;
654
487
 
@@ -657,7 +490,7 @@ async function bootstrap(opts) {
657
490
  const actualSha = sha256OfFileSync(finalPath);
658
491
  if (actualSha === expectedSha) {
659
492
  obsEvent('bootstrap', 'decision.hit', { reason: 'sha-match', version, path: finalPath });
660
- copyToGmTools(finalPath, version);
493
+ copyWasmToGmTools(finalPath, version);
661
494
  clearBootstrapError();
662
495
  return finalPath;
663
496
  }
@@ -665,22 +498,22 @@ async function bootstrap(opts) {
665
498
  writeBootstrapError({
666
499
  expected_version: version, cached_version: null,
667
500
  error_phase: 'cache-hit-sha-mismatch',
668
- error_message: `cached binary at ${finalPath} sha=${actualSha} but manifest expects ${expectedSha}`,
501
+ error_message: `cached wasm at ${finalPath} sha=${actualSha} but manifest expects ${expectedSha}`,
669
502
  });
670
503
  try { fs.unlinkSync(finalPath); } catch (_) {}
671
504
  try { fs.unlinkSync(okSentinel); } catch (_) {}
672
505
  } else {
673
506
  obsEvent('bootstrap', 'decision.hit', { reason: 'sentinel+no-sha-manifest', path: finalPath });
674
- copyToGmTools(finalPath, version);
507
+ copyWasmToGmTools(finalPath, version);
675
508
  clearBootstrapError();
676
509
  return finalPath;
677
510
  }
678
511
  }
679
512
 
680
- if (healIfShaMatches(finalPath, expectedSha, okSentinel, partialPath, 'plugkit')) {
513
+ if (healIfShaMatches(finalPath, expectedSha, okSentinel, partialPath, 'plugkit-wasm')) {
681
514
  obsEvent('bootstrap', 'decision.heal', { reason: 'sha-match', path: finalPath });
682
515
  spawnDetachedRtkFetch();
683
- copyToGmTools(finalPath, version);
516
+ copyWasmToGmTools(finalPath, version);
684
517
  clearBootstrapError();
685
518
  return finalPath;
686
519
  }
@@ -690,14 +523,14 @@ async function bootstrap(opts) {
690
523
  try {
691
524
  if (fs.existsSync(finalPath) && fs.existsSync(okSentinel)) {
692
525
  obsEvent('bootstrap', 'decision.hit', { reason: 'lock-race-resolved', path: finalPath });
693
- copyToGmTools(finalPath, version);
526
+ copyWasmToGmTools(finalPath, version);
694
527
  clearBootstrapError();
695
528
  return finalPath;
696
529
  }
697
- if (healIfShaMatches(finalPath, expectedSha, okSentinel, partialPath, 'plugkit')) {
530
+ if (healIfShaMatches(finalPath, expectedSha, okSentinel, partialPath, 'plugkit-wasm')) {
698
531
  obsEvent('bootstrap', 'decision.heal', { reason: 'sha-match-under-lock', path: finalPath });
699
532
  spawnDetachedRtkFetch();
700
- copyToGmTools(finalPath, version);
533
+ copyWasmToGmTools(finalPath, version);
701
534
  clearBootstrapError();
702
535
  return finalPath;
703
536
  }
@@ -729,9 +562,9 @@ async function bootstrap(opts) {
729
562
  writeBootstrapError({
730
563
  expected_version: version, cached_version: null,
731
564
  error_phase: 'sha256-mismatch',
732
- error_message: `sha256 mismatch for ${binName}: expected ${expectedSha}, got ${got}`,
565
+ error_message: `sha256 mismatch for ${wasmName}: expected ${expectedSha}, got ${got}`,
733
566
  });
734
- throw new Error(`sha256 mismatch for ${binName}: expected ${expectedSha}, got ${got}`);
567
+ throw new Error(`sha256 mismatch for ${wasmName}: expected ${expectedSha}, got ${got}`);
735
568
  }
736
569
  log('sha256 verified');
737
570
  } else {
@@ -746,17 +579,13 @@ async function bootstrap(opts) {
746
579
  } else throw err;
747
580
  }
748
581
 
749
- if (os.platform() !== 'win32') {
750
- try { fs.chmodSync(finalPath, 0o755); } catch (_) {}
751
- }
752
-
753
582
  fs.writeFileSync(okSentinel, new Date().toISOString());
754
583
  log(`decision: fetch reason: install-complete (${finalPath})`);
755
- obsEvent('bootstrap', 'install.done', { path: finalPath, version, kind: 'plugkit' });
756
- proactiveKillForNewInstall(version, finalPath);
584
+ obsEvent('bootstrap', 'install.done', { path: finalPath, version, kind: 'plugkit-wasm' });
585
+ proactiveKillForNewInstall(version);
757
586
  pruneOldVersions(root, version, readRtkVersion());
758
587
  spawnDetachedRtkFetch();
759
- copyToGmTools(finalPath, version);
588
+ copyWasmToGmTools(finalPath, version);
760
589
  clearBootstrapError();
761
590
  return finalPath;
762
591
  } finally {
@@ -764,61 +593,51 @@ async function bootstrap(opts) {
764
593
  }
765
594
  }
766
595
 
767
- function getBinaryPath() {
768
- const home = process.env.USERPROFILE || process.env.HOME || os.homedir();
769
- const exe = process.platform === 'win32' ? 'plugkit.exe' : 'plugkit';
770
- return path.join(home, '.claude', 'gm-tools', exe);
771
- }
772
-
773
- function isReady() {
774
- const bin = getBinaryPath();
775
- if (!fs.existsSync(bin)) return false;
776
- try {
777
- const r = spawnSync(bin, ['--version'], { timeout: 3000, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true });
778
- return r.status === 0;
779
- } catch (_) { return false; }
780
- }
781
-
782
- function startSpoolDaemon() {
783
- const bin = getBinaryPath();
784
- if (!fs.existsSync(bin)) return { ok: false, error: 'binary not found' };
596
+ function copyWasmToGmTools(wasmPath, version) {
597
+ const dst = gmToolsDir();
598
+ fs.mkdirSync(dst, { recursive: true });
599
+ const target = path.join(dst, 'plugkit.wasm');
785
600
 
786
- const pidFile = path.join(os.tmpdir(), 'gm-plugkit-spool.pid');
787
- if (fs.existsSync(pidFile)) {
788
- const pid = parseInt(fs.readFileSync(pidFile, 'utf8').trim(), 10);
789
- if (Number.isFinite(pid) && pidAlive(pid)) {
790
- return { ok: true, pid, status: 'already-running' };
601
+ if (fs.existsSync(target)) {
602
+ let needsRefresh = true;
603
+ try {
604
+ const cur = sha256OfFileSync(target);
605
+ const src = sha256OfFileSync(wasmPath);
606
+ if (cur === src) needsRefresh = false;
607
+ } catch (_) {}
608
+ if (!needsRefresh) {
609
+ try { fs.writeFileSync(path.join(dst, 'plugkit.version'), version); } catch (_) {}
610
+ return;
791
611
  }
792
- try { fs.unlinkSync(pidFile); } catch (_) {}
793
612
  }
794
613
 
795
- const child = spawn(bin, ['spool'], {
796
- detached: true,
797
- stdio: 'ignore',
798
- windowsHide: true,
799
- });
800
- child.unref();
614
+ fs.copyFileSync(wasmPath, target);
615
+ fs.writeFileSync(path.join(dst, 'plugkit.version'), version);
616
+ }
801
617
 
802
- try { fs.writeFileSync(pidFile, String(child.pid)); } catch (_) {}
803
- return { ok: true, pid: child.pid, status: 'started' };
618
+ function getWasmPath() {
619
+ const home = process.env.USERPROFILE || process.env.HOME || os.homedir();
620
+ return path.join(home, '.claude', 'gm-tools', 'plugkit.wasm');
621
+ }
622
+
623
+ function isReady() {
624
+ const wasm = getWasmPath();
625
+ return fs.existsSync(wasm);
804
626
  }
805
627
 
806
628
  async function ensureReady() {
807
629
  if (isReady()) {
808
- return { ok: true, binaryPath: getBinaryPath(), status: 'already-ready' };
630
+ return { ok: true, wasmPath: getWasmPath(), status: 'already-ready' };
809
631
  }
810
- const binaryPath = await bootstrap();
811
- return { ok: true, binaryPath, status: 'bootstrapped' };
632
+ const wasmPath = await bootstrap();
633
+ return { ok: true, wasmPath, status: 'bootstrapped' };
812
634
  }
813
635
 
814
636
  module.exports = {
815
637
  bootstrap,
816
638
  ensureReady,
817
- startSpoolDaemon,
818
- getBinaryPath,
639
+ getWasmPath,
819
640
  isReady,
820
- platformKey,
821
- binaryName,
822
641
  rtkBinaryName,
823
642
  cacheRoot,
824
643
  obsEvent,
@@ -826,7 +645,6 @@ module.exports = {
826
645
  killStaleDaemonIfVersionChanged,
827
646
  killSpoolWatcherInCwd,
828
647
  proactiveKillForNewInstall,
829
- resolveCachedBinary,
830
648
  resolveCachedRtk,
831
649
  bootstrapRtk,
832
650
  readDaemonVersion,
@@ -847,38 +665,18 @@ if (require.main === module) {
847
665
  ensureDir(verDir);
848
666
  await bootstrapRtk(verDir, version, true, root);
849
667
  process.exit(0);
850
- } else if (args.includes('--daemon')) {
851
- const result = await ensureReady();
852
- if (!result.ok) {
853
- console.error('Bootstrap failed:', result.error);
854
- process.exit(1);
855
- }
856
- const daemon = startSpoolDaemon();
857
- console.log(JSON.stringify({ bootstrap: result, daemon }));
858
- process.exit(0);
859
- } else if (args.includes('--binary')) {
860
- const result = await ensureReady();
861
- if (result.ok) {
862
- console.log(result.binaryPath);
863
- process.exit(0);
864
- } else {
865
- console.error('Bootstrap failed:', result.error);
866
- process.exit(1);
867
- }
868
668
  } else if (args.includes('--status')) {
869
669
  console.log(JSON.stringify({
870
670
  ready: isReady(),
871
- binaryPath: getBinaryPath(),
872
- platform: platformKey(),
671
+ wasmPath: getWasmPath(),
873
672
  daemonVersion: readDaemonVersion(),
874
673
  cachedRtk: resolveCachedRtk(),
875
674
  }));
876
675
  process.exit(0);
877
676
  } else {
878
677
  const result = await ensureReady();
879
- const daemon = startSpoolDaemon();
880
- console.log(JSON.stringify({ bootstrap: result, daemon }));
881
- process.exit(result.ok && daemon.ok ? 0 : 1);
678
+ console.log(JSON.stringify({ bootstrap: result }));
679
+ process.exit(result.ok ? 0 : 1);
882
680
  }
883
681
  } catch (err) {
884
682
  obsEvent('bootstrap', 'fatal', { err: String(err.message || err) });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-plugkit",
3
- "version": "2.0.1075",
3
+ "version": "2.0.1076",
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": {