gm-skill 2.0.1091 → 2.0.1093
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/README.md +1 -1
- package/bin/plugkit.js +0 -19
- package/bin/plugkit.version +1 -1
- package/bin/plugkit.wasm +0 -0
- package/bin/plugkit.wasm.sha256 +1 -1
- package/gm-plugkit/plugkit-wasm-wrapper.js +364 -52
- package/gm.json +2 -2
- package/lang/browser.js +8 -10
- package/lib/skill-bootstrap.js +47 -35
- package/package.json +2 -2
- package/scripts/run-hook.sh +3 -4
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ npx gm-skill-bootstrap
|
|
|
28
28
|
|
|
29
29
|
## Version
|
|
30
30
|
|
|
31
|
-
`2.0.
|
|
31
|
+
`2.0.1093` — auto-bumped from the canonical `gm` repo. Every push to `AnEntrypoint/gm` republishes this package alongside all 15 platform packages.
|
|
32
32
|
|
|
33
33
|
## Source of truth
|
|
34
34
|
|
package/bin/plugkit.js
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
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.
|
|
7
|
-
|
|
8
3
|
const { spawnSync } = require('child_process');
|
|
9
4
|
const path = require('path');
|
|
10
5
|
const fs = require('fs');
|
|
@@ -12,12 +7,6 @@ const os = require('os');
|
|
|
12
7
|
|
|
13
8
|
const wrapperDir = __dirname;
|
|
14
9
|
|
|
15
|
-
function toolsBin() {
|
|
16
|
-
const home = process.env.USERPROFILE || process.env.HOME || os.homedir();
|
|
17
|
-
const exe = process.platform === 'win32' ? 'plugkit.exe' : 'plugkit';
|
|
18
|
-
return path.join(home, '.claude', 'gm-tools', exe);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
10
|
function sha256OfFileSync(filePath) {
|
|
22
11
|
try {
|
|
23
12
|
const crypto = require('crypto');
|
|
@@ -32,14 +21,6 @@ function sha256OfFileSync(filePath) {
|
|
|
32
21
|
} catch (_) { return null; }
|
|
33
22
|
}
|
|
34
23
|
|
|
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
24
|
function readPinnedVersion() {
|
|
44
25
|
try { return fs.readFileSync(path.join(wrapperDir, 'plugkit.version'), 'utf8').trim(); } catch (_) { return null; }
|
|
45
26
|
}
|
package/bin/plugkit.version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.1.
|
|
1
|
+
0.1.377
|
package/bin/plugkit.wasm
CHANGED
|
Binary file
|
package/bin/plugkit.wasm.sha256
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
26cc527cd72ee8d53711020c5f2bbcd9fad8c252329e5c72bb3ea1434ff4480c plugkit.wasm
|
|
@@ -2,44 +2,329 @@ import fs from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import os from 'os';
|
|
4
4
|
import { watch } from 'fs';
|
|
5
|
+
import { spawn, spawnSync } from 'child_process';
|
|
6
|
+
|
|
7
|
+
const KV_DIR = path.join(os.homedir(), '.claude', 'gm-tools', 'kv');
|
|
8
|
+
fs.mkdirSync(KV_DIR, { recursive: true });
|
|
9
|
+
|
|
10
|
+
const RS_LEARN_URL = process.env.RS_LEARN_URL || 'http://127.0.0.1:8000';
|
|
11
|
+
const VEC_K_DEFAULT = 10;
|
|
12
|
+
|
|
13
|
+
const browserSessions = new Map();
|
|
14
|
+
let nextBrowserSessionId = 1;
|
|
5
15
|
|
|
6
16
|
function createWasiShim() {
|
|
7
|
-
|
|
17
|
+
return new Proxy({}, {
|
|
8
18
|
get(target, prop) {
|
|
9
19
|
if (prop === 'proc_exit') return (code) => process.exit(code);
|
|
20
|
+
if (prop === 'fd_write') return () => 0;
|
|
21
|
+
if (prop === 'environ_get') return () => 0;
|
|
22
|
+
if (prop === 'environ_sizes_get') return () => 0;
|
|
10
23
|
return () => 0;
|
|
11
24
|
}
|
|
12
25
|
});
|
|
13
|
-
return shim;
|
|
14
26
|
}
|
|
15
27
|
|
|
16
|
-
function
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
buf.set(bytes.slice(0, len));
|
|
20
|
-
return Math.min(bytes.length, len);
|
|
28
|
+
function readWasmBytes(instance, ptr, len) {
|
|
29
|
+
if (ptr === 0 || len === 0) return new Uint8Array(0);
|
|
30
|
+
return new Uint8Array(instance.exports.memory.buffer, ptr, len).slice();
|
|
21
31
|
}
|
|
22
32
|
|
|
23
|
-
function
|
|
24
|
-
|
|
25
|
-
|
|
33
|
+
function readWasmStr(instance, ptr, len) {
|
|
34
|
+
if (ptr === 0 || len === 0) return '';
|
|
35
|
+
const bytes = new Uint8Array(instance.exports.memory.buffer, ptr, len);
|
|
36
|
+
return new TextDecoder('utf-8').decode(bytes);
|
|
26
37
|
}
|
|
27
38
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const ptr =
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if (nextMemPtr > memory.buffer.byteLength - 4096) nextMemPtr = 0x10000;
|
|
35
|
-
return { ptr, len: data.length };
|
|
39
|
+
function writeWasmBytes(instance, bytes) {
|
|
40
|
+
if (bytes.length === 0) return 0n;
|
|
41
|
+
const ptr = instance.exports.plugkit_alloc(bytes.length);
|
|
42
|
+
if (ptr === 0) return 0n;
|
|
43
|
+
new Uint8Array(instance.exports.memory.buffer, ptr, bytes.length).set(bytes);
|
|
44
|
+
return (BigInt(ptr) & 0xffffffffn) | (BigInt(bytes.length) << 32n);
|
|
36
45
|
}
|
|
37
46
|
|
|
38
|
-
function
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
47
|
+
function writeWasmStr(instance, str) {
|
|
48
|
+
if (!str) return 0n;
|
|
49
|
+
return writeWasmBytes(instance, new TextEncoder().encode(str));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function writeWasmJson(instance, value) {
|
|
53
|
+
return writeWasmStr(instance, JSON.stringify(value));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function kvFilePath(ns, key) {
|
|
57
|
+
const safeNs = String(ns).replace(/[^A-Za-z0-9._-]/g, '_');
|
|
58
|
+
const safeKey = String(key).replace(/[^A-Za-z0-9._-]/g, '_');
|
|
59
|
+
const dir = path.join(KV_DIR, safeNs);
|
|
60
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
61
|
+
return path.join(dir, safeKey + '.json');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function makeHostFunctions(instanceRef) {
|
|
65
|
+
return {
|
|
66
|
+
host_fs_read: (pathPtr, pathLen) => {
|
|
67
|
+
try {
|
|
68
|
+
const filePath = readWasmStr(instanceRef.value, pathPtr, pathLen);
|
|
69
|
+
if (!filePath) return 0n;
|
|
70
|
+
const data = fs.readFileSync(filePath, 'utf-8');
|
|
71
|
+
return writeWasmStr(instanceRef.value, data);
|
|
72
|
+
} catch (e) {
|
|
73
|
+
return 0n;
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
host_fs_write: (pathPtr, pathLen, dataPtr, dataLen) => {
|
|
78
|
+
try {
|
|
79
|
+
const filePath = readWasmStr(instanceRef.value, pathPtr, pathLen);
|
|
80
|
+
const data = readWasmStr(instanceRef.value, dataPtr, dataLen);
|
|
81
|
+
if (!filePath) return 0;
|
|
82
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
83
|
+
fs.writeFileSync(filePath, data);
|
|
84
|
+
return 1;
|
|
85
|
+
} catch (e) {
|
|
86
|
+
return 0;
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
host_fs_readdir: (pathPtr, pathLen) => {
|
|
91
|
+
try {
|
|
92
|
+
const dirPath = readWasmStr(instanceRef.value, pathPtr, pathLen);
|
|
93
|
+
if (!dirPath) return 0n;
|
|
94
|
+
const entries = fs.readdirSync(dirPath, { withFileTypes: true }).map(e => ({
|
|
95
|
+
name: e.name,
|
|
96
|
+
is_dir: e.isDirectory(),
|
|
97
|
+
is_file: e.isFile(),
|
|
98
|
+
}));
|
|
99
|
+
return writeWasmJson(instanceRef.value, entries);
|
|
100
|
+
} catch (e) {
|
|
101
|
+
return 0n;
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
host_fs_stat: (pathPtr, pathLen) => {
|
|
106
|
+
try {
|
|
107
|
+
const filePath = readWasmStr(instanceRef.value, pathPtr, pathLen);
|
|
108
|
+
if (!filePath) return 0n;
|
|
109
|
+
const s = fs.statSync(filePath);
|
|
110
|
+
return writeWasmJson(instanceRef.value, {
|
|
111
|
+
is_dir: s.isDirectory(),
|
|
112
|
+
is_file: s.isFile(),
|
|
113
|
+
size: s.size,
|
|
114
|
+
mtime_ms: s.mtimeMs,
|
|
115
|
+
});
|
|
116
|
+
} catch (e) {
|
|
117
|
+
return 0n;
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
host_fetch: (urlPtr, urlLen, optsPtr, optsLen) => {
|
|
122
|
+
try {
|
|
123
|
+
const url = readWasmStr(instanceRef.value, urlPtr, urlLen);
|
|
124
|
+
const optsStr = readWasmStr(instanceRef.value, optsPtr, optsLen);
|
|
125
|
+
const opts = optsStr ? JSON.parse(optsStr) : {};
|
|
126
|
+
const result = spawnSync(process.execPath, ['-e', `
|
|
127
|
+
const url = ${JSON.stringify(url)};
|
|
128
|
+
const opts = ${JSON.stringify(opts)};
|
|
129
|
+
fetch(url, opts).then(r => r.text().then(body => {
|
|
130
|
+
process.stdout.write(JSON.stringify({ status: r.status, body }));
|
|
131
|
+
})).catch(e => process.stdout.write(JSON.stringify({ status: 0, error: e.message })));
|
|
132
|
+
`], { encoding: 'utf-8', timeout: 10000 });
|
|
133
|
+
if (result.status !== 0) return writeWasmJson(instanceRef.value, { status: 0, error: result.stderr || 'fetch failed' });
|
|
134
|
+
return writeWasmStr(instanceRef.value, result.stdout || '{}');
|
|
135
|
+
} catch (e) {
|
|
136
|
+
return writeWasmJson(instanceRef.value, { status: 0, error: e.message });
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
host_kv_get: (nsPtr, nsLen, keyPtr, keyLen) => {
|
|
141
|
+
try {
|
|
142
|
+
const ns = readWasmStr(instanceRef.value, nsPtr, nsLen);
|
|
143
|
+
const key = readWasmStr(instanceRef.value, keyPtr, keyLen);
|
|
144
|
+
if (!ns || !key) return 0n;
|
|
145
|
+
const fp = kvFilePath(ns, key);
|
|
146
|
+
if (!fs.existsSync(fp)) return 0n;
|
|
147
|
+
const data = fs.readFileSync(fp, 'utf-8');
|
|
148
|
+
return writeWasmStr(instanceRef.value, data);
|
|
149
|
+
} catch (e) {
|
|
150
|
+
return 0n;
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
host_kv_put: (nsPtr, nsLen, keyPtr, keyLen, valPtr, valLen) => {
|
|
155
|
+
try {
|
|
156
|
+
const ns = readWasmStr(instanceRef.value, nsPtr, nsLen);
|
|
157
|
+
const key = readWasmStr(instanceRef.value, keyPtr, keyLen);
|
|
158
|
+
const val = readWasmStr(instanceRef.value, valPtr, valLen);
|
|
159
|
+
if (!ns || !key) return 0;
|
|
160
|
+
fs.writeFileSync(kvFilePath(ns, key), val);
|
|
161
|
+
return 1;
|
|
162
|
+
} catch (e) {
|
|
163
|
+
return 0;
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
host_kv_query: (nsPtr, nsLen, qPtr, qLen) => {
|
|
168
|
+
try {
|
|
169
|
+
const ns = readWasmStr(instanceRef.value, nsPtr, nsLen);
|
|
170
|
+
const q = readWasmStr(instanceRef.value, qPtr, qLen);
|
|
171
|
+
if (!ns) return 0n;
|
|
172
|
+
const dir = path.join(KV_DIR, String(ns).replace(/[^A-Za-z0-9._-]/g, '_'));
|
|
173
|
+
if (!fs.existsSync(dir)) return writeWasmJson(instanceRef.value, []);
|
|
174
|
+
const ql = q ? String(q).toLowerCase() : '';
|
|
175
|
+
const results = [];
|
|
176
|
+
for (const f of fs.readdirSync(dir)) {
|
|
177
|
+
if (!f.endsWith('.json')) continue;
|
|
178
|
+
const value = fs.readFileSync(path.join(dir, f), 'utf-8');
|
|
179
|
+
if (ql && !value.toLowerCase().includes(ql) && !f.toLowerCase().includes(ql)) continue;
|
|
180
|
+
results.push({ key: f.replace(/\.json$/, ''), value });
|
|
181
|
+
}
|
|
182
|
+
return writeWasmJson(instanceRef.value, results);
|
|
183
|
+
} catch (e) {
|
|
184
|
+
return 0n;
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
host_vec_search: (qPtr, qLen, k) => {
|
|
189
|
+
try {
|
|
190
|
+
const q = readWasmStr(instanceRef.value, qPtr, qLen);
|
|
191
|
+
if (!q) return writeWasmJson(instanceRef.value, []);
|
|
192
|
+
const k_ = k > 0 ? k : VEC_K_DEFAULT;
|
|
193
|
+
const body = JSON.stringify({ query: q, limit: k_, scope: 'episodes' });
|
|
194
|
+
const result = spawnSync(process.execPath, ['-e', `
|
|
195
|
+
fetch('${RS_LEARN_URL}/search', { method: 'POST', headers: { 'content-type': 'application/json' }, body: ${JSON.stringify(body)} })
|
|
196
|
+
.then(r => r.text().then(t => process.stdout.write(t)))
|
|
197
|
+
.catch(e => process.stdout.write(JSON.stringify({ error: e.message, results: [] })));
|
|
198
|
+
`], { encoding: 'utf-8', timeout: 5000 });
|
|
199
|
+
if (result.status !== 0 || !result.stdout) return writeWasmJson(instanceRef.value, []);
|
|
200
|
+
try {
|
|
201
|
+
const parsed = JSON.parse(result.stdout);
|
|
202
|
+
return writeWasmJson(instanceRef.value, parsed.results || parsed);
|
|
203
|
+
} catch (_) {
|
|
204
|
+
return writeWasmJson(instanceRef.value, []);
|
|
205
|
+
}
|
|
206
|
+
} catch (e) {
|
|
207
|
+
return writeWasmJson(instanceRef.value, []);
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
host_vec_embed: (textPtr, textLen) => {
|
|
212
|
+
try {
|
|
213
|
+
const text = readWasmStr(instanceRef.value, textPtr, textLen);
|
|
214
|
+
if (!text) return 0n;
|
|
215
|
+
const body = JSON.stringify({ text });
|
|
216
|
+
const result = spawnSync(process.execPath, ['-e', `
|
|
217
|
+
fetch('${RS_LEARN_URL}/embed', { method: 'POST', headers: { 'content-type': 'application/json' }, body: ${JSON.stringify(body)} })
|
|
218
|
+
.then(r => r.text().then(t => process.stdout.write(t)))
|
|
219
|
+
.catch(e => process.stdout.write(''));
|
|
220
|
+
`], { encoding: 'utf-8', timeout: 5000 });
|
|
221
|
+
if (result.status !== 0 || !result.stdout) return 0n;
|
|
222
|
+
return writeWasmStr(instanceRef.value, result.stdout);
|
|
223
|
+
} catch (e) {
|
|
224
|
+
return 0n;
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
host_browser_spawn: (urlPtr, urlLen) => {
|
|
229
|
+
try {
|
|
230
|
+
const url = readWasmStr(instanceRef.value, urlPtr, urlLen);
|
|
231
|
+
const id = BigInt(nextBrowserSessionId++);
|
|
232
|
+
browserSessions.set(id, { url, opened_at: Date.now() });
|
|
233
|
+
return id;
|
|
234
|
+
} catch (e) {
|
|
235
|
+
return 0n;
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
|
|
239
|
+
host_browser_eval: (sessionId, codePtr, codeLen) => {
|
|
240
|
+
try {
|
|
241
|
+
const code = readWasmStr(instanceRef.value, codePtr, codeLen);
|
|
242
|
+
const session = browserSessions.get(BigInt(sessionId));
|
|
243
|
+
if (!session) return writeWasmJson(instanceRef.value, { error: 'session not found' });
|
|
244
|
+
return writeWasmJson(instanceRef.value, { ok: false, error: 'browser eval not implemented in JS host; route via spool browser verb' });
|
|
245
|
+
} catch (e) {
|
|
246
|
+
return writeWasmJson(instanceRef.value, { error: e.message });
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
|
|
250
|
+
host_browser_close: (sessionId) => {
|
|
251
|
+
try {
|
|
252
|
+
return browserSessions.delete(BigInt(sessionId)) ? 1 : 0;
|
|
253
|
+
} catch (e) {
|
|
254
|
+
return 0;
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
|
|
258
|
+
host_exec_js: (codePtr, codeLen, optsPtr, optsLen) => {
|
|
259
|
+
try {
|
|
260
|
+
const code = readWasmStr(instanceRef.value, codePtr, codeLen);
|
|
261
|
+
const optsStr = readWasmStr(instanceRef.value, optsPtr, optsLen);
|
|
262
|
+
const opts = optsStr ? JSON.parse(optsStr) : {};
|
|
263
|
+
const lang = opts.lang || 'nodejs';
|
|
264
|
+
const cwd = opts.cwd || process.cwd();
|
|
265
|
+
const timeoutMs = opts.timeoutMs || 30000;
|
|
266
|
+
let cmd, args;
|
|
267
|
+
if (lang === 'nodejs' || lang === 'js') { cmd = process.execPath; args = ['-e', code]; }
|
|
268
|
+
else if (lang === 'python') { cmd = 'python'; args = ['-c', code]; }
|
|
269
|
+
else if (lang === 'bash') { cmd = 'bash'; args = ['-c', code]; }
|
|
270
|
+
else if (lang === 'deno') { cmd = 'deno'; args = ['eval', code]; }
|
|
271
|
+
else { return writeWasmJson(instanceRef.value, { ok: false, error: `unsupported lang: ${lang}` }); }
|
|
272
|
+
const result = spawnSync(cmd, args, { encoding: 'utf-8', timeout: timeoutMs, cwd, env: process.env });
|
|
273
|
+
return writeWasmJson(instanceRef.value, {
|
|
274
|
+
ok: result.status === 0,
|
|
275
|
+
stdout: result.stdout || '',
|
|
276
|
+
stderr: result.stderr || '',
|
|
277
|
+
exit_code: result.status === null ? -1 : result.status,
|
|
278
|
+
timed_out: result.signal === 'SIGTERM',
|
|
279
|
+
});
|
|
280
|
+
} catch (e) {
|
|
281
|
+
return writeWasmJson(instanceRef.value, { ok: false, error: e.message });
|
|
282
|
+
}
|
|
283
|
+
},
|
|
284
|
+
|
|
285
|
+
host_log: (level, msgPtr, msgLen) => {
|
|
286
|
+
try {
|
|
287
|
+
const msg = readWasmStr(instanceRef.value, msgPtr, msgLen);
|
|
288
|
+
const prefix = level >= 3 ? '[plugkit-wasm:err]' : level >= 2 ? '[plugkit-wasm:warn]' : '[plugkit-wasm]';
|
|
289
|
+
if (level >= 2) console.error(`${prefix} ${msg}`);
|
|
290
|
+
else console.log(`${prefix} ${msg}`);
|
|
291
|
+
return 0;
|
|
292
|
+
} catch (e) {
|
|
293
|
+
return 0;
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
|
|
297
|
+
host_now_ms: () => BigInt(Date.now()),
|
|
298
|
+
|
|
299
|
+
host_env_get: (keyPtr, keyLen) => {
|
|
300
|
+
try {
|
|
301
|
+
const key = readWasmStr(instanceRef.value, keyPtr, keyLen);
|
|
302
|
+
if (!key) return 0n;
|
|
303
|
+
const v = process.env[key];
|
|
304
|
+
if (v === undefined) return 0n;
|
|
305
|
+
return writeWasmStr(instanceRef.value, v);
|
|
306
|
+
} catch (e) {
|
|
307
|
+
return 0n;
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function resolveVersion(instance) {
|
|
314
|
+
try {
|
|
315
|
+
return fs.readFileSync(path.join(os.homedir(), '.claude', 'gm-tools', 'plugkit.version'), 'utf8').trim();
|
|
316
|
+
} catch (_) {}
|
|
317
|
+
try {
|
|
318
|
+
const fn = instance && instance.exports && instance.exports.plugkit_version;
|
|
319
|
+
if (typeof fn === 'function') {
|
|
320
|
+
const result = fn();
|
|
321
|
+
const ptr = Number(result & 0xffffffffn);
|
|
322
|
+
const len = Number(result >> 32n);
|
|
323
|
+
const bytes = new Uint8Array(instance.exports.memory.buffer, ptr, len);
|
|
324
|
+
return new TextDecoder().decode(bytes).trim();
|
|
325
|
+
}
|
|
326
|
+
} catch (_) {}
|
|
327
|
+
return 'unknown';
|
|
43
328
|
}
|
|
44
329
|
|
|
45
330
|
async function runSpoolWatcher(instance, spoolDir) {
|
|
@@ -48,6 +333,7 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
48
333
|
fs.mkdirSync(inDir, { recursive: true });
|
|
49
334
|
fs.mkdirSync(outDir, { recursive: true });
|
|
50
335
|
|
|
336
|
+
console.log(`[plugkit-wasm] plugkit v${resolveVersion(instance)} (wasm)`);
|
|
51
337
|
console.log(`[plugkit-wasm] watching ${inDir}`);
|
|
52
338
|
|
|
53
339
|
const processed = new Set();
|
|
@@ -69,22 +355,38 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
69
355
|
const verbBytes = new TextEncoder().encode(verb);
|
|
70
356
|
const bodyBytes = new TextEncoder().encode(body);
|
|
71
357
|
|
|
72
|
-
const
|
|
73
|
-
const
|
|
358
|
+
const verbPtr = instance.exports.plugkit_alloc(verbBytes.length);
|
|
359
|
+
const bodyPtr = instance.exports.plugkit_alloc(bodyBytes.length);
|
|
360
|
+
new Uint8Array(instance.exports.memory.buffer, verbPtr, verbBytes.length).set(verbBytes);
|
|
361
|
+
new Uint8Array(instance.exports.memory.buffer, bodyPtr, bodyBytes.length).set(bodyBytes);
|
|
74
362
|
|
|
75
|
-
const result = dispatch(verbPtr,
|
|
363
|
+
const result = dispatch(verbPtr, verbBytes.length, bodyPtr, bodyBytes.length);
|
|
76
364
|
|
|
77
365
|
const ptr = Number(result & 0xffffffffn);
|
|
78
366
|
const len = Number(result >> 32n);
|
|
79
|
-
const
|
|
367
|
+
const resultBytes = new Uint8Array(instance.exports.memory.buffer, ptr, len);
|
|
368
|
+
const resultStr = new TextDecoder().decode(resultBytes);
|
|
369
|
+
|
|
370
|
+
const taskBase = path.basename(filePath, path.extname(filePath));
|
|
371
|
+
const outName = dir === '.' ? `${taskBase}.json` : `${verb}-${taskBase}.json`;
|
|
372
|
+
fs.writeFileSync(path.join(outDir, outName), resultStr);
|
|
80
373
|
|
|
81
|
-
|
|
82
|
-
|
|
374
|
+
try { instance.exports.plugkit_free(verbPtr, verbBytes.length); } catch (_) {}
|
|
375
|
+
try { instance.exports.plugkit_free(bodyPtr, bodyBytes.length); } catch (_) {}
|
|
376
|
+
try { instance.exports.plugkit_free(ptr, len); } catch (_) {}
|
|
83
377
|
|
|
84
378
|
fs.unlinkSync(filePath);
|
|
85
379
|
processed.delete(key);
|
|
86
380
|
} catch (e) {
|
|
87
381
|
console.error(`[plugkit-wasm] error processing ${key}: ${e.message}`);
|
|
382
|
+
const taskBase = path.basename(filePath, path.extname(filePath));
|
|
383
|
+
const relPath = path.relative(inDir, filePath);
|
|
384
|
+
const dir = path.dirname(relPath);
|
|
385
|
+
const verb = dir === '.' ? taskBase : dir;
|
|
386
|
+
const outName = dir === '.' ? `${taskBase}.json` : `${verb}-${taskBase}.json`;
|
|
387
|
+
try {
|
|
388
|
+
fs.writeFileSync(path.join(outDir, outName), JSON.stringify({ ok: false, error: e.message }));
|
|
389
|
+
} catch (_) {}
|
|
88
390
|
try { fs.unlinkSync(filePath); } catch (_) {}
|
|
89
391
|
processed.delete(key);
|
|
90
392
|
}
|
|
@@ -108,6 +410,18 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
108
410
|
return files;
|
|
109
411
|
}
|
|
110
412
|
|
|
413
|
+
const heartbeatPath = path.join(spoolDir, '.watcher.heartbeat');
|
|
414
|
+
setInterval(() => {
|
|
415
|
+
try { fs.writeFileSync(heartbeatPath, String(Date.now())); } catch (_) {}
|
|
416
|
+
}, 5000);
|
|
417
|
+
|
|
418
|
+
const pollDeadline = setInterval(async () => {
|
|
419
|
+
const existing = walkDir(inDir);
|
|
420
|
+
for (const fullPath of existing) {
|
|
421
|
+
await processFile(fullPath);
|
|
422
|
+
}
|
|
423
|
+
}, 250);
|
|
424
|
+
|
|
111
425
|
const existing = walkDir(inDir);
|
|
112
426
|
for (const fullPath of existing) {
|
|
113
427
|
await processFile(fullPath);
|
|
@@ -139,38 +453,20 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
139
453
|
const wasmBuffer = fs.readFileSync(wasmPath);
|
|
140
454
|
const wasmModule = new WebAssembly.Module(wasmBuffer);
|
|
141
455
|
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
const hostFunctions = {
|
|
145
|
-
host_fs_read: () => 0,
|
|
146
|
-
host_fs_write: () => 0,
|
|
147
|
-
host_fs_readdir: () => 0,
|
|
148
|
-
host_fs_stat: () => 0,
|
|
149
|
-
host_kv_get: () => 0,
|
|
150
|
-
host_kv_put: () => 0,
|
|
151
|
-
host_kv_query: () => 0,
|
|
152
|
-
host_fetch: () => 0,
|
|
153
|
-
host_vec_search: () => 0,
|
|
154
|
-
host_vec_embed: () => 0,
|
|
155
|
-
host_browser_spawn: () => 0,
|
|
156
|
-
host_browser_eval: () => 0,
|
|
157
|
-
host_browser_close: () => 0,
|
|
158
|
-
host_exec_js: () => 0,
|
|
159
|
-
host_log: (ptr, len) => { console.log('[host_log]'); return 0; },
|
|
160
|
-
host_now_ms: () => BigInt(Date.now()),
|
|
161
|
-
host_env_get: () => 0,
|
|
162
|
-
};
|
|
456
|
+
const instanceRef = { value: null };
|
|
457
|
+
const hostFunctions = makeHostFunctions(instanceRef);
|
|
163
458
|
|
|
164
459
|
const importObject = {
|
|
165
|
-
env:
|
|
460
|
+
env: hostFunctions,
|
|
166
461
|
wasi_snapshot_preview1: createWasiShim(),
|
|
167
462
|
};
|
|
168
463
|
|
|
169
464
|
const instance = new WebAssembly.Instance(wasmModule, importObject);
|
|
465
|
+
instanceRef.value = instance;
|
|
170
466
|
|
|
171
467
|
const args = process.argv.slice(2);
|
|
172
468
|
if (args.includes('--version')) {
|
|
173
|
-
console.log(
|
|
469
|
+
console.log(`plugkit v${resolveVersion(instance)} (wasm)`);
|
|
174
470
|
process.exit(0);
|
|
175
471
|
}
|
|
176
472
|
|
|
@@ -178,6 +474,22 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
178
474
|
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
179
475
|
const spoolDir = path.join(projectDir, '.gm', 'exec-spool');
|
|
180
476
|
await runSpoolWatcher(instance, spoolDir);
|
|
477
|
+
} else if (args[0] === 'dispatch') {
|
|
478
|
+
const verb = args[1] || '';
|
|
479
|
+
const body = args[2] || '{}';
|
|
480
|
+
const dispatch = instance.exports.dispatch_verb;
|
|
481
|
+
const verbBytes = new TextEncoder().encode(verb);
|
|
482
|
+
const bodyBytes = new TextEncoder().encode(body);
|
|
483
|
+
const verbPtr = instance.exports.plugkit_alloc(verbBytes.length);
|
|
484
|
+
const bodyPtr = instance.exports.plugkit_alloc(bodyBytes.length);
|
|
485
|
+
new Uint8Array(instance.exports.memory.buffer, verbPtr, verbBytes.length).set(verbBytes);
|
|
486
|
+
new Uint8Array(instance.exports.memory.buffer, bodyPtr, bodyBytes.length).set(bodyBytes);
|
|
487
|
+
const result = dispatch(verbPtr, verbBytes.length, bodyPtr, bodyBytes.length);
|
|
488
|
+
const ptr = Number(result & 0xffffffffn);
|
|
489
|
+
const len = Number(result >> 32n);
|
|
490
|
+
const out = new TextDecoder().decode(new Uint8Array(instance.exports.memory.buffer, ptr, len));
|
|
491
|
+
process.stdout.write(out);
|
|
492
|
+
process.exit(0);
|
|
181
493
|
} else {
|
|
182
494
|
console.log('[plugkit-wasm] args:', args.join(' '));
|
|
183
495
|
process.exit(0);
|
package/gm.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1093",
|
|
4
4
|
"description": "Spool-dispatch orchestration engine with unified state machine, skills, and automated git enforcement",
|
|
5
5
|
"author": "AnEntrypoint",
|
|
6
6
|
"license": "MIT",
|
|
@@ -23,5 +23,5 @@
|
|
|
23
23
|
"publishConfig": {
|
|
24
24
|
"access": "public"
|
|
25
25
|
},
|
|
26
|
-
"plugkitVersion": "0.1.
|
|
26
|
+
"plugkitVersion": "0.1.377"
|
|
27
27
|
}
|
package/lang/browser.js
CHANGED
|
@@ -1,27 +1,24 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const os = require('os');
|
|
4
|
-
const { spawnSync
|
|
4
|
+
const { spawnSync } = require('child_process');
|
|
5
5
|
const fsSync = require('fs');
|
|
6
6
|
|
|
7
7
|
function findPlugkit() {
|
|
8
8
|
if (process.env.PLUGKIT_BIN && fsSync.existsSync(process.env.PLUGKIT_BIN)) return process.env.PLUGKIT_BIN;
|
|
9
9
|
const home = os.homedir();
|
|
10
|
-
const isWin = process.platform === 'win32';
|
|
11
|
-
const exe = isWin ? 'plugkit.exe' : 'plugkit';
|
|
12
10
|
const candidates = [
|
|
13
|
-
path.join(
|
|
14
|
-
path.join(home, '.
|
|
15
|
-
path.join(home, '.claude', 'plugins', 'marketplaces', 'gm-cc', 'bin',
|
|
11
|
+
path.join(__dirname, '..', 'bin', 'plugkit.js'),
|
|
12
|
+
path.join(home, '.claude', 'gm-tools', 'plugkit.js'),
|
|
13
|
+
path.join(home, '.claude', 'plugins', 'marketplaces', 'gm-cc', 'bin', 'plugkit.js'),
|
|
16
14
|
];
|
|
17
15
|
if (process.env.CLAUDE_PLUGIN_ROOT) {
|
|
18
|
-
candidates.push(path.join(process.env.CLAUDE_PLUGIN_ROOT, 'bin',
|
|
16
|
+
candidates.push(path.join(process.env.CLAUDE_PLUGIN_ROOT, 'bin', 'plugkit.js'));
|
|
19
17
|
}
|
|
20
18
|
for (const p of candidates) {
|
|
21
19
|
if (fsSync.existsSync(p)) return p;
|
|
22
20
|
}
|
|
23
|
-
|
|
24
|
-
return 'plugkit';
|
|
21
|
+
return null;
|
|
25
22
|
}
|
|
26
23
|
|
|
27
24
|
module.exports = {
|
|
@@ -32,8 +29,9 @@ module.exports = {
|
|
|
32
29
|
try {
|
|
33
30
|
fsSync.writeFileSync(tmp, code, 'utf-8');
|
|
34
31
|
const plugkit = findPlugkit();
|
|
32
|
+
if (!plugkit) return '[plugkit wrapper not found]';
|
|
35
33
|
const opts = { encoding: 'utf-8', timeout: 120000, windowsHide: true, ...(cwd && { cwd }) };
|
|
36
|
-
const r = spawnSync(plugkit,
|
|
34
|
+
const r = spawnSync(process.execPath, [plugkit, 'exec', '--lang', 'browser', '--file', tmp], opts);
|
|
37
35
|
const out = (r.stdout || '').trimEnd();
|
|
38
36
|
const err = (r.stderr || '').trimEnd();
|
|
39
37
|
return out && err ? out + '\n[stderr]\n' + err : out || err || '(no output)';
|
package/lib/skill-bootstrap.js
CHANGED
|
@@ -113,39 +113,54 @@ async function downloadPlugkitBinary(version) {
|
|
|
113
113
|
});
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
function
|
|
116
|
+
function findPlugkitWasmPids() {
|
|
117
|
+
const pids = [];
|
|
117
118
|
try {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const output = execSync('tasklist /FO CSV /NH', { encoding: 'utf8' });
|
|
119
|
+
if (process.platform === 'win32') {
|
|
120
|
+
const output = execSync('wmic process where "Name=\'bun.exe\' or Name=\'node.exe\'" get ProcessId,CommandLine /FORMAT:CSV', { encoding: 'utf8' });
|
|
121
121
|
const lines = output.split('\n').filter(Boolean);
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
122
|
+
for (const line of lines) {
|
|
123
|
+
if (!line.includes('plugkit-wasm-wrapper.js')) continue;
|
|
124
|
+
const parts = line.split(',');
|
|
125
|
+
const pid = parts[parts.length - 1].trim();
|
|
126
|
+
if (/^\d+$/.test(pid)) pids.push(pid);
|
|
127
|
+
}
|
|
126
128
|
} else {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
129
|
+
const output = execSync("ps -eo pid,args", { encoding: 'utf8' });
|
|
130
|
+
const lines = output.split('\n').filter(Boolean);
|
|
131
|
+
for (const line of lines) {
|
|
132
|
+
if (!line.includes('plugkit-wasm-wrapper')) continue;
|
|
133
|
+
const m = line.trim().match(/^(\d+)\s/);
|
|
134
|
+
if (m) pids.push(m[1]);
|
|
132
135
|
}
|
|
133
136
|
}
|
|
134
137
|
} catch (e) {
|
|
135
|
-
return false;
|
|
136
138
|
}
|
|
139
|
+
return pids;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function isProcessRunning() {
|
|
143
|
+
return findPlugkitWasmPids().length > 0;
|
|
137
144
|
}
|
|
138
145
|
|
|
139
146
|
function killExistingPlugkit() {
|
|
140
147
|
try {
|
|
141
|
-
const
|
|
142
|
-
if (
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
} else {
|
|
146
|
-
execSync('pkill -f "plugkit" || true', { shell: true });
|
|
147
|
-
emitBootstrapEvent('info', 'Killed existing plugkit process on Unix');
|
|
148
|
+
const pids = findPlugkitWasmPids();
|
|
149
|
+
if (pids.length === 0) {
|
|
150
|
+
emitBootstrapEvent('info', 'No existing plugkit WASM watcher to kill');
|
|
151
|
+
return;
|
|
148
152
|
}
|
|
153
|
+
for (const pid of pids) {
|
|
154
|
+
try {
|
|
155
|
+
if (process.platform === 'win32') {
|
|
156
|
+
execSync(`taskkill /F /PID ${pid}`, { stdio: 'ignore' });
|
|
157
|
+
} else {
|
|
158
|
+
execSync(`kill -9 ${pid}`, { stdio: 'ignore' });
|
|
159
|
+
}
|
|
160
|
+
} catch (e) {
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
emitBootstrapEvent('info', 'Killed existing plugkit WASM watcher', { pids });
|
|
149
164
|
} catch (e) {
|
|
150
165
|
emitBootstrapEvent('warn', 'Failed to kill existing plugkit', { error: e.message });
|
|
151
166
|
}
|
|
@@ -235,7 +250,7 @@ async function spawnPlugkitWatcher(wasmPath) {
|
|
|
235
250
|
}
|
|
236
251
|
}
|
|
237
252
|
|
|
238
|
-
async function bootstrapPlugkit() {
|
|
253
|
+
async function bootstrapPlugkit(sessionId) {
|
|
239
254
|
const startTime = Date.now();
|
|
240
255
|
|
|
241
256
|
try {
|
|
@@ -251,18 +266,19 @@ async function bootstrapPlugkit() {
|
|
|
251
266
|
if (!binaryMissing && !versionMismatch) {
|
|
252
267
|
emitBootstrapEvent('info', 'Binary up-to-date', { version: installedVersion });
|
|
253
268
|
|
|
254
|
-
if (isProcessRunning(
|
|
269
|
+
if (isProcessRunning()) {
|
|
255
270
|
emitBootstrapEvent('info', 'Plugkit watcher already running');
|
|
256
271
|
const statusPayload = {
|
|
257
272
|
ok: true,
|
|
258
273
|
version: installedVersion,
|
|
259
274
|
status: 'running',
|
|
275
|
+
sessionId: sessionId || null,
|
|
260
276
|
timestamp: new Date().toISOString(),
|
|
261
277
|
durationMs: Date.now() - startTime,
|
|
262
278
|
};
|
|
263
279
|
fs.mkdirSync(path.dirname(BOOTSTRAP_STATUS_FILE), { recursive: true });
|
|
264
280
|
fs.writeFileSync(BOOTSTRAP_STATUS_FILE, JSON.stringify(statusPayload, null, 2));
|
|
265
|
-
return { ok: true };
|
|
281
|
+
return { ok: true, binaryPath: PLUGKIT_WASM_PATH };
|
|
266
282
|
}
|
|
267
283
|
}
|
|
268
284
|
|
|
@@ -308,10 +324,10 @@ async function bootstrapPlugkit() {
|
|
|
308
324
|
emitBootstrapEvent('warn', 'Binary health check failed, but proceeding');
|
|
309
325
|
}
|
|
310
326
|
|
|
311
|
-
const watcherRunning = isProcessRunning(
|
|
327
|
+
const watcherRunning = isProcessRunning();
|
|
312
328
|
let watcherPid;
|
|
313
329
|
if (!watcherRunning) {
|
|
314
|
-
watcherPid = await spawnPlugkitWatcher(
|
|
330
|
+
watcherPid = await spawnPlugkitWatcher(PLUGKIT_WASM_PATH);
|
|
315
331
|
} else {
|
|
316
332
|
watcherPid = 'already-running';
|
|
317
333
|
emitBootstrapEvent('info', 'Watcher already running');
|
|
@@ -322,6 +338,7 @@ async function bootstrapPlugkit() {
|
|
|
322
338
|
ok: true,
|
|
323
339
|
version: currentVersion,
|
|
324
340
|
watcherPid,
|
|
341
|
+
sessionId: sessionId || null,
|
|
325
342
|
timestamp: new Date().toISOString(),
|
|
326
343
|
durationMs: Date.now() - startTime,
|
|
327
344
|
};
|
|
@@ -330,7 +347,7 @@ async function bootstrapPlugkit() {
|
|
|
330
347
|
fs.writeFileSync(BOOTSTRAP_STATUS_FILE, JSON.stringify(statusPayload, null, 2));
|
|
331
348
|
|
|
332
349
|
emitBootstrapEvent('info', 'Bootstrap completed successfully', statusPayload);
|
|
333
|
-
return { ok: true };
|
|
350
|
+
return { ok: true, binaryPath: PLUGKIT_WASM_PATH };
|
|
334
351
|
} catch (err) {
|
|
335
352
|
const errorPayload = {
|
|
336
353
|
ok: false,
|
|
@@ -381,17 +398,12 @@ async function bootstrapAcptoapi() {
|
|
|
381
398
|
}
|
|
382
399
|
|
|
383
400
|
async function getSnapshot(sessionId, cwd) {
|
|
384
|
-
const plugkitPath = getPlugkitPath();
|
|
385
|
-
if (!fs.existsSync(plugkitPath)) {
|
|
386
|
-
return { git: { ok: false }, tasks: [], error: 'plugkit not found' };
|
|
387
|
-
}
|
|
388
|
-
|
|
389
401
|
try {
|
|
390
402
|
const sid = sessionId || process.env.CLAUDE_SESSION_ID || 'default';
|
|
391
403
|
const c = cwd || process.cwd();
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
return
|
|
404
|
+
const result = await spool.execSpool('snapshot', 'snapshot', { sessionId: sid, cwd: c, timeoutMs: 5000 });
|
|
405
|
+
if (result && typeof result === 'object') return result;
|
|
406
|
+
return { git: { ok: false }, tasks: [], error: 'no snapshot result' };
|
|
395
407
|
} catch (e) {
|
|
396
408
|
emitBootstrapEvent('warn', 'Failed to get snapshot', { error: e.message });
|
|
397
409
|
return { git: { ok: false }, tasks: [], error: e.message };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm-skill",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1093",
|
|
4
4
|
"description": "Canonical universal harness — AI-native software engineering via skill-driven orchestration; bootstraps plugkit for task execution and session isolation. Install in any AI coding agent host.",
|
|
5
5
|
"author": "AnEntrypoint",
|
|
6
6
|
"license": "MIT",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"gm.json"
|
|
40
40
|
],
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"gm-plugkit": "^2.0.
|
|
42
|
+
"gm-plugkit": "^2.0.1093"
|
|
43
43
|
},
|
|
44
44
|
"engines": {
|
|
45
45
|
"node": ">=16.0.0"
|
package/scripts/run-hook.sh
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
2
|
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT}}"
|
|
3
3
|
[ -z "$PLUGIN_ROOT" ] && exit 0
|
|
4
|
-
|
|
5
|
-
[ -f "$
|
|
6
|
-
|
|
7
|
-
"$PLUGKIT" hook "$1"
|
|
4
|
+
PLUGKIT_JS="$PLUGIN_ROOT/bin/plugkit.js"
|
|
5
|
+
[ ! -f "$PLUGKIT_JS" ] && exit 0
|
|
6
|
+
node "$PLUGKIT_JS" hook "$1"
|