gm-cc 2.0.695 → 2.0.699

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.
@@ -4,7 +4,7 @@
4
4
  "name": "AnEntrypoint"
5
5
  },
6
6
  "description": "State machine agent with hooks, skills, and automated git enforcement",
7
- "version": "2.0.695",
7
+ "version": "2.0.699",
8
8
  "metadata": {
9
9
  "description": "State machine agent with hooks, skills, and automated git enforcement"
10
10
  },
@@ -0,0 +1,295 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const os = require('os');
7
+ const https = require('https');
8
+ const crypto = require('crypto');
9
+ const { URL } = require('url');
10
+
11
+ const RELEASE_REPO = 'AnEntrypoint/plugkit-bin';
12
+ const ATTEMPT_TIMEOUT_MS = 30 * 60 * 1000;
13
+ const MAX_ATTEMPTS = 3;
14
+ const BACKOFF_MS = [5000, 30000, 120000];
15
+ const LOCK_STALE_MS = 45 * 60 * 1000;
16
+
17
+ function log(msg) {
18
+ try { process.stderr.write(`[plugkit-bootstrap] ${msg}\n`); } catch (_) {}
19
+ }
20
+
21
+ function platformKey() {
22
+ const p = os.platform();
23
+ const a = os.arch();
24
+ if (p === 'win32') return a === 'arm64' ? 'win32-arm64' : 'win32-x64';
25
+ if (p === 'darwin') return a === 'arm64' ? 'darwin-arm64' : 'darwin-x64';
26
+ return (a === 'arm64' || a === 'aarch64') ? 'linux-arm64' : 'linux-x64';
27
+ }
28
+
29
+ function binaryName() {
30
+ const key = platformKey();
31
+ return key.startsWith('win32') ? `plugkit-${key}.exe` : `plugkit-${key}`;
32
+ }
33
+
34
+ function cacheRoot() {
35
+ const home = os.homedir();
36
+ if (process.env.PLUGKIT_CACHE_DIR) return process.env.PLUGKIT_CACHE_DIR;
37
+ if (os.platform() === 'win32') {
38
+ const base = process.env.LOCALAPPDATA || path.join(home, 'AppData', 'Local');
39
+ return path.join(base, 'plugkit', 'bin');
40
+ }
41
+ if (os.platform() === 'darwin') return path.join(home, 'Library', 'Caches', 'plugkit', 'bin');
42
+ const xdg = process.env.XDG_CACHE_HOME || path.join(home, '.cache');
43
+ return path.join(xdg, 'plugkit', 'bin');
44
+ }
45
+
46
+ function fallbackCacheRoot() {
47
+ return path.join(os.tmpdir(), 'plugkit-cache', 'bin');
48
+ }
49
+
50
+ function ensureDir(dir) {
51
+ fs.mkdirSync(dir, { recursive: true });
52
+ }
53
+
54
+ function readVersionFile(wrapperDir) {
55
+ const p = path.join(wrapperDir, 'plugkit.version');
56
+ if (!fs.existsSync(p)) throw new Error(`plugkit.version not found at ${p}`);
57
+ return fs.readFileSync(p, 'utf8').trim();
58
+ }
59
+
60
+ function readShaManifest(wrapperDir) {
61
+ const p = path.join(wrapperDir, 'plugkit.sha256');
62
+ if (!fs.existsSync(p)) return null;
63
+ const out = {};
64
+ for (const line of fs.readFileSync(p, 'utf8').split(/\r?\n/)) {
65
+ const m = line.match(/^([0-9a-f]{64})\s+(\S+)\s*$/i);
66
+ if (m) out[m[2]] = m[1].toLowerCase();
67
+ }
68
+ return out;
69
+ }
70
+
71
+ function pidAlive(pid) {
72
+ try { process.kill(pid, 0); return true; } catch (e) { return e.code === 'EPERM'; }
73
+ }
74
+
75
+ function acquireLock(lockPath) {
76
+ const start = Date.now();
77
+ for (;;) {
78
+ try {
79
+ const fd = fs.openSync(lockPath, 'wx');
80
+ fs.writeSync(fd, String(process.pid));
81
+ fs.closeSync(fd);
82
+ return true;
83
+ } catch (err) {
84
+ if (err.code !== 'EEXIST') throw err;
85
+ let stale = false;
86
+ try {
87
+ const st = fs.statSync(lockPath);
88
+ if (Date.now() - st.mtimeMs > LOCK_STALE_MS) stale = true;
89
+ const owner = parseInt(fs.readFileSync(lockPath, 'utf8').trim(), 10);
90
+ if (Number.isFinite(owner) && owner !== process.pid && !pidAlive(owner)) stale = true;
91
+ } catch (_) { stale = true; }
92
+ if (stale) {
93
+ try { fs.unlinkSync(lockPath); } catch (_) {}
94
+ continue;
95
+ }
96
+ if (Date.now() - start > ATTEMPT_TIMEOUT_MS) throw new Error(`lock wait timeout: ${lockPath}`);
97
+ const waitMs = 2000;
98
+ const deadline = Date.now() + waitMs;
99
+ while (Date.now() < deadline) {}
100
+ }
101
+ }
102
+ }
103
+
104
+ function releaseLock(lockPath) {
105
+ try { fs.unlinkSync(lockPath); } catch (_) {}
106
+ }
107
+
108
+ function sha256OfFile(filePath) {
109
+ return new Promise((resolve, reject) => {
110
+ const h = crypto.createHash('sha256');
111
+ const s = fs.createReadStream(filePath);
112
+ s.on('data', c => h.update(c));
113
+ s.on('end', () => resolve(h.digest('hex')));
114
+ s.on('error', reject);
115
+ });
116
+ }
117
+
118
+ function fetchToFile(url, destPath, expectedTotal) {
119
+ return new Promise((resolve, reject) => {
120
+ let existing = 0;
121
+ try { existing = fs.statSync(destPath).size; } catch (_) {}
122
+ const headers = { 'User-Agent': 'plugkit-bootstrap', 'Accept': '*/*' };
123
+ if (existing > 0) headers['Range'] = `bytes=${existing}-`;
124
+
125
+ const u = new URL(url);
126
+ const req = https.request({
127
+ method: 'GET',
128
+ hostname: u.hostname,
129
+ path: u.pathname + u.search,
130
+ headers,
131
+ timeout: ATTEMPT_TIMEOUT_MS,
132
+ }, (res) => {
133
+ if (res.statusCode === 301 || res.statusCode === 302 || res.statusCode === 307 || res.statusCode === 308) {
134
+ res.resume();
135
+ return resolve(fetchToFile(res.headers.location, destPath, expectedTotal));
136
+ }
137
+ if (res.statusCode === 416) {
138
+ res.resume();
139
+ try { fs.unlinkSync(destPath); } catch (_) {}
140
+ return reject(new Error('range-not-satisfiable: cleared partial, retry'));
141
+ }
142
+ if (!(res.statusCode === 200 || res.statusCode === 206)) {
143
+ res.resume();
144
+ return reject(new Error(`HTTP ${res.statusCode} for ${url}`));
145
+ }
146
+ const append = res.statusCode === 206 && existing > 0;
147
+ const out = fs.createWriteStream(destPath, { flags: append ? 'a' : 'w' });
148
+ let bytes = append ? existing : 0;
149
+ let lastLog = Date.now();
150
+ res.on('data', c => {
151
+ bytes += c.length;
152
+ if (Date.now() - lastLog > 5000) {
153
+ const pct = expectedTotal ? ` ${Math.floor(bytes / expectedTotal * 100)}%` : '';
154
+ log(`downloading: ${(bytes / 1048576).toFixed(1)} MiB${pct}`);
155
+ lastLog = Date.now();
156
+ }
157
+ });
158
+ res.pipe(out);
159
+ out.on('finish', () => out.close(() => resolve(bytes)));
160
+ out.on('error', reject);
161
+ res.on('error', reject);
162
+ });
163
+ req.on('timeout', () => { req.destroy(new Error(`timeout after ${ATTEMPT_TIMEOUT_MS}ms`)); });
164
+ req.on('error', reject);
165
+ req.end();
166
+ });
167
+ }
168
+
169
+ async function downloadWithRetry(url, destPath) {
170
+ let lastErr;
171
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
172
+ try {
173
+ log(`fetch attempt ${attempt}/${MAX_ATTEMPTS}: ${url}`);
174
+ await fetchToFile(url, destPath);
175
+ return;
176
+ } catch (err) {
177
+ lastErr = err;
178
+ log(`attempt ${attempt} failed: ${err.message}`);
179
+ if (attempt < MAX_ATTEMPTS) {
180
+ const wait = BACKOFF_MS[attempt - 1] || 120000;
181
+ log(`backing off ${wait}ms`);
182
+ await new Promise(r => setTimeout(r, wait));
183
+ }
184
+ }
185
+ }
186
+ throw lastErr;
187
+ }
188
+
189
+ function pruneOldVersions(root, keepVersion) {
190
+ try {
191
+ const entries = fs.readdirSync(root);
192
+ for (const e of entries) {
193
+ if (!e.startsWith('v')) continue;
194
+ if (e === `v${keepVersion}`) continue;
195
+ const dir = path.join(root, e);
196
+ const lock = path.join(dir, '.lock');
197
+ if (fs.existsSync(lock)) continue;
198
+ try {
199
+ fs.rmSync(dir, { recursive: true, force: true });
200
+ log(`pruned ${dir}`);
201
+ } catch (err) { log(`prune skip ${dir}: ${err.message}`); }
202
+ }
203
+ } catch (_) {}
204
+ }
205
+
206
+ async function bootstrap(opts) {
207
+ opts = opts || {};
208
+ const wrapperDir = opts.wrapperDir || __dirname;
209
+ const version = opts.version || readVersionFile(wrapperDir);
210
+ const shaManifest = readShaManifest(wrapperDir);
211
+ const binName = binaryName();
212
+ const expectedSha = shaManifest ? shaManifest[binName] : null;
213
+
214
+ let root = cacheRoot();
215
+ try { ensureDir(root); }
216
+ catch (_) { root = fallbackCacheRoot(); ensureDir(root); }
217
+
218
+ const verDir = path.join(root, `v${version}`);
219
+ ensureDir(verDir);
220
+
221
+ const finalPath = path.join(verDir, binName);
222
+ const okSentinel = path.join(verDir, '.ok');
223
+
224
+ if (fs.existsSync(finalPath) && fs.existsSync(okSentinel)) {
225
+ if (!opts.silent) log(`cache hit: ${finalPath}`);
226
+ pruneOldVersions(root, version);
227
+ return finalPath;
228
+ }
229
+
230
+ const lockPath = path.join(verDir, '.lock');
231
+ acquireLock(lockPath);
232
+ try {
233
+ if (fs.existsSync(finalPath) && fs.existsSync(okSentinel)) {
234
+ pruneOldVersions(root, version);
235
+ return finalPath;
236
+ }
237
+
238
+ const tmpPath = `${finalPath}.partial`;
239
+ const url = `https://github.com/${RELEASE_REPO}/releases/download/v${version}/${binName}`;
240
+ await downloadWithRetry(url, tmpPath);
241
+
242
+ if (expectedSha) {
243
+ const got = await sha256OfFile(tmpPath);
244
+ if (got !== expectedSha) {
245
+ try { fs.unlinkSync(tmpPath); } catch (_) {}
246
+ throw new Error(`sha256 mismatch for ${binName}: expected ${expectedSha}, got ${got}`);
247
+ }
248
+ log('sha256 verified');
249
+ } else {
250
+ log('no sha256 manifest — skipping verify');
251
+ }
252
+
253
+ try { fs.renameSync(tmpPath, finalPath); }
254
+ catch (err) {
255
+ if (err.code === 'EEXIST' || err.code === 'EPERM') {
256
+ try { fs.unlinkSync(finalPath); } catch (_) {}
257
+ fs.renameSync(tmpPath, finalPath);
258
+ } else throw err;
259
+ }
260
+
261
+ if (os.platform() !== 'win32') {
262
+ try { fs.chmodSync(finalPath, 0o755); } catch (_) {}
263
+ }
264
+
265
+ fs.writeFileSync(okSentinel, new Date().toISOString());
266
+ log(`installed ${finalPath}`);
267
+ pruneOldVersions(root, version);
268
+ return finalPath;
269
+ } finally {
270
+ releaseLock(lockPath);
271
+ }
272
+ }
273
+
274
+ function resolveCachedBinary(opts) {
275
+ opts = opts || {};
276
+ const wrapperDir = opts.wrapperDir || __dirname;
277
+ const version = opts.version || readVersionFile(wrapperDir);
278
+ const root = (() => {
279
+ try { const r = cacheRoot(); ensureDir(r); return r; }
280
+ catch (_) { const r = fallbackCacheRoot(); ensureDir(r); return r; }
281
+ })();
282
+ const verDir = path.join(root, `v${version}`);
283
+ const finalPath = path.join(verDir, binaryName());
284
+ const okSentinel = path.join(verDir, '.ok');
285
+ if (fs.existsSync(finalPath) && fs.existsSync(okSentinel)) return finalPath;
286
+ return null;
287
+ }
288
+
289
+ module.exports = { bootstrap, resolveCachedBinary, platformKey, binaryName, cacheRoot };
290
+
291
+ if (require.main === module) {
292
+ bootstrap({ silent: false })
293
+ .then(p => { process.stdout.write(p + '\n'); process.exit(0); })
294
+ .catch(err => { log(`FATAL: ${err.message}`); process.exit(1); });
295
+ }
package/bin/plugkit.js CHANGED
@@ -2,41 +2,67 @@
2
2
  'use strict';
3
3
  const { spawn, spawnSync } = require('child_process');
4
4
  const path = require('path');
5
- const os = require('os');
6
5
  const fs = require('fs');
6
+ const { bootstrap, resolveCachedBinary } = require('./bootstrap');
7
7
 
8
8
  const dir = __dirname;
9
- const platform = os.platform();
10
- const arch = os.arch();
11
9
 
12
- let bin;
13
- if (platform === 'win32') {
14
- bin = path.join(dir, arch === 'arm64' ? 'plugkit-win32-arm64.exe' : 'plugkit-win32-x64.exe');
15
- if (!fs.existsSync(bin)) bin = path.join(dir, 'plugkit.exe');
16
- } else if (platform === 'darwin') {
17
- bin = path.join(dir, arch === 'arm64' ? 'plugkit-darwin-arm64' : 'plugkit-darwin-x64');
18
- } else {
19
- bin = path.join(dir, arch === 'arm64' || arch === 'aarch64' ? 'plugkit-linux-arm64' : 'plugkit-linux-x64');
10
+ async function resolveBinary() {
11
+ const cached = resolveCachedBinary({ wrapperDir: dir });
12
+ if (cached) return cached;
13
+ return await bootstrap({ wrapperDir: dir });
20
14
  }
21
15
 
22
- const args = process.argv.slice(2);
23
- const isHook = args[0] === 'hook';
16
+ async function main() {
17
+ let bin;
18
+ try {
19
+ bin = await resolveBinary();
20
+ } catch (err) {
21
+ process.stderr.write(`[plugkit] bootstrap failed: ${err.message}\n`);
22
+ const legacy = legacyFallback();
23
+ if (legacy) { bin = legacy; }
24
+ else process.exit(1);
25
+ }
24
26
 
25
- if (isHook && !process.stdin.isTTY) {
26
- // Claude Code pipes JSON to the wrapper's stdin. On Windows, spawnSync with
27
- // stdio:'inherit' does NOT reliably forward a piped stdin to the native child
28
- // (the child sees EOF immediately and the hook treats tool_name as empty →
29
- // silently allows every command). Read stdin ourselves and write it explicitly.
30
- const chunks = [];
31
- process.stdin.on('data', c => chunks.push(c));
32
- process.stdin.on('end', () => {
33
- const child = spawn(bin, args, { stdio: ['pipe', 'inherit', 'inherit'], windowsHide: true });
34
- child.stdin.end(Buffer.concat(chunks));
35
- child.on('close', code => process.exit(code ?? 1));
36
- child.on('error', () => process.exit(1));
37
- });
38
- process.stdin.on('error', () => process.exit(1));
39
- } else {
40
- const result = spawnSync(bin, args, { stdio: 'inherit', windowsHide: true });
41
- process.exit(result.status ?? 1);
27
+ const args = process.argv.slice(2);
28
+ const isHook = args[0] === 'hook';
29
+
30
+ if (isHook && !process.stdin.isTTY) {
31
+ const chunks = [];
32
+ process.stdin.on('data', c => chunks.push(c));
33
+ process.stdin.on('end', () => {
34
+ const child = spawn(bin, args, { stdio: ['pipe', 'inherit', 'inherit'], windowsHide: true });
35
+ child.stdin.end(Buffer.concat(chunks));
36
+ child.on('close', code => process.exit(code ?? 1));
37
+ child.on('error', () => process.exit(1));
38
+ });
39
+ process.stdin.on('error', () => process.exit(1));
40
+ } else {
41
+ const result = spawnSync(bin, args, { stdio: 'inherit', windowsHide: true });
42
+ process.exit(result.status ?? 1);
43
+ }
44
+ }
45
+
46
+ function legacyFallback() {
47
+ const os = require('os');
48
+ const p = os.platform();
49
+ const a = os.arch();
50
+ let candidates = [];
51
+ if (p === 'win32') {
52
+ candidates = [
53
+ path.join(dir, a === 'arm64' ? 'plugkit-win32-arm64.exe' : 'plugkit-win32-x64.exe'),
54
+ path.join(dir, 'plugkit.exe'),
55
+ ];
56
+ } else if (p === 'darwin') {
57
+ candidates = [path.join(dir, a === 'arm64' ? 'plugkit-darwin-arm64' : 'plugkit-darwin-x64')];
58
+ } else {
59
+ candidates = [path.join(dir, (a === 'arm64' || a === 'aarch64') ? 'plugkit-linux-arm64' : 'plugkit-linux-x64')];
60
+ }
61
+ for (const c of candidates) if (fs.existsSync(c)) return c;
62
+ return null;
42
63
  }
64
+
65
+ main().catch(err => {
66
+ process.stderr.write(`[plugkit] fatal: ${err.message}\n`);
67
+ process.exit(1);
68
+ });
@@ -0,0 +1,6 @@
1
+ a68c00135cba98e20cf36f087987b2d487d9288451543bcf7ce628663e6fc728 plugkit-win32-x64.exe
2
+ 7688f1b182af9b9c588ee1f4b10b05a5fcf40765a380a74f9e8b0e20a156d010 plugkit-win32-arm64.exe
3
+ 1fb0e30c75d1911552ec55f6e73bc96ac30aafa0a72bf4a90d39c25ce2deb336 plugkit-darwin-x64
4
+ 060ace93778d92db280ecd0b81ef079092a8e184407902d10f9ebf02e6b199b6 plugkit-darwin-arm64
5
+ a680e43872feb324f387e873714041c0fe9668dd9202d451d645f95118e4233d plugkit-linux-x64
6
+ f59bbf825f618e512e939a7bcb3824d6a3bf214f2359df78b37e998956b39fc2 plugkit-linux-arm64
@@ -0,0 +1 @@
1
+ 0.1.227
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-cc",
3
- "version": "2.0.695",
3
+ "version": "2.0.699",
4
4
  "description": "State machine agent with hooks, skills, and automated git enforcement",
5
5
  "author": "AnEntrypoint",
6
6
  "license": "MIT",
package/plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm",
3
- "version": "2.0.695",
3
+ "version": "2.0.699",
4
4
  "description": "State machine agent with hooks, skills, and automated git enforcement",
5
5
  "author": {
6
6
  "name": "AnEntrypoint",
package/bin/plugkit DELETED
@@ -1,16 +0,0 @@
1
- #!/bin/sh
2
- DIR="$(cd "$(dirname "$0")" && pwd)"
3
- case "$(uname -s)" in
4
- Darwin) OS=darwin ;;
5
- MINGW*|MSYS*|CYGWIN*)
6
- case "$(uname -m)" in
7
- arm64|aarch64) exec "$DIR/plugkit-win32-arm64.exe" "$@" ;;
8
- *) exec "$DIR/plugkit-win32-x64.exe" "$@" ;;
9
- esac ;;
10
- *) OS=linux ;;
11
- esac
12
- case "$(uname -m)" in
13
- arm64|aarch64) ARCH=arm64 ;;
14
- *) ARCH=x64 ;;
15
- esac
16
- exec "$DIR/plugkit-$OS-$ARCH" "$@"
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/bin/plugkit.exe DELETED
Binary file