gm-skill 2.0.1303 → 2.0.1319
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/AGENTS.md +172 -1
- package/README.md +80 -21
- package/bin/gm-validate.js +329 -0
- package/bin/gmsniff.js +19 -6
- package/bin/plugkit.version +1 -1
- package/bin/plugkit.wasm.sha256 +1 -1
- package/gm-plugkit/package.json +10 -3
- package/gm-plugkit/plugkit-wasm-wrapper.js +347 -152
- package/gm.json +2 -2
- package/package.json +5 -5
- package/skills/gm-skill/SKILL.md +16 -2
- package/LICENSE +0 -21
- package/bin/plugkit.wasm +0 -0
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const cp = require('child_process');
|
|
7
|
+
const os = require('os');
|
|
8
|
+
|
|
9
|
+
const ROOT = process.cwd();
|
|
10
|
+
const ARGS = new Set(process.argv.slice(2));
|
|
11
|
+
const REQUIRE_NEW_EMBED = ARGS.has('--require-new-embed');
|
|
12
|
+
const SPOOL = path.join(ROOT, '.gm', 'exec-spool');
|
|
13
|
+
const IN = path.join(SPOOL, 'in');
|
|
14
|
+
const OUT = path.join(SPOOL, 'out');
|
|
15
|
+
const PROFILE = path.join(ROOT, '.gm', 'browser-profile');
|
|
16
|
+
const PORTS = path.join(SPOOL, 'browser-ports.json');
|
|
17
|
+
const SESSIONS = path.join(SPOOL, 'browser-sessions.json');
|
|
18
|
+
const STATUS = path.join(SPOOL, '.status.json');
|
|
19
|
+
const WATCHER_LOG = path.join(SPOOL, '.watcher.log');
|
|
20
|
+
const WITNESS_DIR = path.join(ROOT, '.gm', 'witness');
|
|
21
|
+
|
|
22
|
+
function log(...a) { process.stderr.write('[gm-validate] ' + a.join(' ') + '\n'); }
|
|
23
|
+
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
|
|
24
|
+
function rmrf(p) { try { fs.rmSync(p, { recursive: true, force: true }); } catch (_) {} }
|
|
25
|
+
function readJson(p) { try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch (_) { return null; } }
|
|
26
|
+
|
|
27
|
+
let DISPATCH_SEQ = 0;
|
|
28
|
+
function nextTask(tag) { DISPATCH_SEQ++; return `validate-${process.pid}-${DISPATCH_SEQ}-${tag}`; }
|
|
29
|
+
|
|
30
|
+
async function dispatch(verb, body, timeoutMs = 60000) {
|
|
31
|
+
const task = nextTask(verb.replace(/[^a-z0-9-]/gi, ''));
|
|
32
|
+
const inDir = path.join(IN, verb);
|
|
33
|
+
fs.mkdirSync(inDir, { recursive: true });
|
|
34
|
+
const inFile = path.join(inDir, `${task}.txt`);
|
|
35
|
+
const outFile = path.join(OUT, `${verb}-${task}.json`);
|
|
36
|
+
const payload = typeof body === 'string' ? body : JSON.stringify(body);
|
|
37
|
+
const tmp = inFile + '.tmp.' + process.pid;
|
|
38
|
+
fs.writeFileSync(tmp, payload);
|
|
39
|
+
fs.renameSync(tmp, inFile);
|
|
40
|
+
const t0 = Date.now();
|
|
41
|
+
while (Date.now() - t0 < timeoutMs) {
|
|
42
|
+
if (fs.existsSync(outFile)) {
|
|
43
|
+
const txt = fs.readFileSync(outFile, 'utf8');
|
|
44
|
+
try { return { ok: true, latency_ms: Date.now() - t0, response: JSON.parse(txt) }; }
|
|
45
|
+
catch (e) { return { ok: false, latency_ms: Date.now() - t0, error: 'parse: ' + e.message, raw: txt }; }
|
|
46
|
+
}
|
|
47
|
+
await sleep(100);
|
|
48
|
+
}
|
|
49
|
+
return { ok: false, latency_ms: timeoutMs, error: 'timeout' };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function ensureWatcher() {
|
|
53
|
+
let st = readJson(STATUS);
|
|
54
|
+
const now = Date.now();
|
|
55
|
+
if (st && st.pid && (now - (st.ts || 0)) < 15000) {
|
|
56
|
+
try { process.kill(st.pid, 0); log('watcher already up pid=' + st.pid); return st.pid; } catch (_) {}
|
|
57
|
+
}
|
|
58
|
+
log('booting watcher via bun x gm-plugkit@latest spool');
|
|
59
|
+
const child = cp.spawn('bun', ['x', 'gm-plugkit@latest', 'spool'], {
|
|
60
|
+
cwd: ROOT, detached: true, stdio: ['ignore', 'ignore', 'ignore'], windowsHide: true, shell: true,
|
|
61
|
+
});
|
|
62
|
+
child.unref();
|
|
63
|
+
const t0 = Date.now();
|
|
64
|
+
while (Date.now() - t0 < 60000) {
|
|
65
|
+
st = readJson(STATUS);
|
|
66
|
+
if (st && st.pid && (Date.now() - (st.ts || 0)) < 10000) {
|
|
67
|
+
try { process.kill(st.pid, 0); log('watcher up pid=' + st.pid); return st.pid; } catch (_) {}
|
|
68
|
+
}
|
|
69
|
+
await sleep(500);
|
|
70
|
+
}
|
|
71
|
+
throw new Error('watcher boot timed out');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function findChromiumProcs() {
|
|
75
|
+
try {
|
|
76
|
+
const ps = `Get-CimInstance Win32_Process | Where-Object { $_.Name -match 'chrome|chromium|msedge' } | Select-Object ProcessId, Name, CommandLine | ConvertTo-Json -Compress -Depth 3`;
|
|
77
|
+
const out = cp.execFileSync('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', ps], { encoding: 'utf8', windowsHide: true, maxBuffer: 8 * 1024 * 1024 });
|
|
78
|
+
if (!out.trim()) return [];
|
|
79
|
+
const j = JSON.parse(out);
|
|
80
|
+
return Array.isArray(j) ? j : [j];
|
|
81
|
+
} catch (e) { return []; }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function findChromiumMainWindowTitleForPids(pids) {
|
|
85
|
+
try {
|
|
86
|
+
const list = pids.map(p => String(p)).join(',');
|
|
87
|
+
const ps = `$ids = @(${list}); Get-Process -Id $ids -ErrorAction SilentlyContinue | Where-Object { $_.MainWindowTitle } | Select-Object Id, MainWindowTitle | ConvertTo-Json -Compress`;
|
|
88
|
+
const out = cp.execFileSync('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', ps], { encoding: 'utf8', windowsHide: true, maxBuffer: 4 * 1024 * 1024 });
|
|
89
|
+
if (!out.trim()) return [];
|
|
90
|
+
const j = JSON.parse(out);
|
|
91
|
+
return Array.isArray(j) ? j : [j];
|
|
92
|
+
} catch (e) { return []; }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function validateChromium() {
|
|
96
|
+
const v = { ok: false, session_id: '', headed: false, window_title: '', errors: [] };
|
|
97
|
+
log('Validation 1: chromium headed');
|
|
98
|
+
rmrf(PROFILE);
|
|
99
|
+
try { fs.unlinkSync(PORTS); } catch (_) {}
|
|
100
|
+
try { fs.unlinkSync(SESSIONS); } catch (_) {}
|
|
101
|
+
|
|
102
|
+
await dispatch('instruction', '{}', 30000).catch(() => {});
|
|
103
|
+
const r = await dispatch('browser', 'session new', 90000);
|
|
104
|
+
if (!r.ok) { v.errors.push('dispatch: ' + r.error); return v; }
|
|
105
|
+
const resp = r.response || {};
|
|
106
|
+
const data = resp.data || {};
|
|
107
|
+
const stdout = data.stdout || '';
|
|
108
|
+
const m = stdout.match(/Session\s+(\S+)\s+created/i) || stdout.match(/session_id["':\s]+([A-Za-z0-9_-]+)/i);
|
|
109
|
+
v.session_id = (resp.session_id || data.session_id || (m && m[1]) || '').toString();
|
|
110
|
+
if (!resp.ok && !data.ok) { v.errors.push('browser session new not ok: ' + JSON.stringify(resp).slice(0, 400)); }
|
|
111
|
+
|
|
112
|
+
const t0 = Date.now();
|
|
113
|
+
let matched = null;
|
|
114
|
+
while (Date.now() - t0 < 30000) {
|
|
115
|
+
const procs = findChromiumProcs();
|
|
116
|
+
matched = procs.filter(p => p && typeof p.CommandLine === 'string' && p.CommandLine.toLowerCase().includes(PROFILE.toLowerCase().replace(/\\/g, '\\').toLowerCase()));
|
|
117
|
+
if (matched.length === 0) {
|
|
118
|
+
matched = procs.filter(p => p && typeof p.CommandLine === 'string' && /browser-profile/i.test(p.CommandLine) && p.CommandLine.includes(ROOT));
|
|
119
|
+
}
|
|
120
|
+
if (matched.length > 0) break;
|
|
121
|
+
await sleep(500);
|
|
122
|
+
}
|
|
123
|
+
if (!matched || matched.length === 0) { v.errors.push('no chromium process with cwd browser-profile cmdline within 30s'); return v; }
|
|
124
|
+
const pids = matched.map(p => p.ProcessId);
|
|
125
|
+
log('chromium pids matching profile: ' + pids.join(','));
|
|
126
|
+
|
|
127
|
+
const titles = findChromiumMainWindowTitleForPids(pids);
|
|
128
|
+
if (titles.length > 0) {
|
|
129
|
+
v.window_title = titles[0].MainWindowTitle || '';
|
|
130
|
+
}
|
|
131
|
+
if (!v.window_title) {
|
|
132
|
+
await sleep(3000);
|
|
133
|
+
const t2 = findChromiumMainWindowTitleForPids(pids);
|
|
134
|
+
if (t2.length > 0) v.window_title = t2[0].MainWindowTitle || '';
|
|
135
|
+
}
|
|
136
|
+
v.headed = !!v.window_title;
|
|
137
|
+
if (!v.headed) v.errors.push('no MainWindowTitle on any matched chromium pid (headless?)');
|
|
138
|
+
|
|
139
|
+
const ports = readJson(PORTS) || {};
|
|
140
|
+
let portMatch = false;
|
|
141
|
+
for (const k of Object.keys(ports)) {
|
|
142
|
+
const e = ports[k];
|
|
143
|
+
if (e && pids.includes(e.pid)) { portMatch = true; break; }
|
|
144
|
+
}
|
|
145
|
+
if (!portMatch) v.errors.push('browser-ports.json has no entry whose pid matches running chromium');
|
|
146
|
+
|
|
147
|
+
if (v.session_id) {
|
|
148
|
+
try {
|
|
149
|
+
fs.mkdirSync(WITNESS_DIR, { recursive: true });
|
|
150
|
+
const shot = path.join(WITNESS_DIR, 'chromium-headed.png');
|
|
151
|
+
const script = `await page.goto('about:blank'); const buf = await page.screenshot(); require('fs').writeFileSync(${JSON.stringify(shot)}, buf);`;
|
|
152
|
+
await dispatch('browser', `session -s ${v.session_id} -e ${JSON.stringify(script)}`, 30000);
|
|
153
|
+
v.screenshot = fs.existsSync(shot) ? shot : '';
|
|
154
|
+
} catch (e) { v.errors.push('screenshot: ' + e.message); }
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
v.ok = v.headed && portMatch;
|
|
158
|
+
return v;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function readWatcherLogTail(bytes = 64 * 1024) {
|
|
162
|
+
try {
|
|
163
|
+
const stat = fs.statSync(WATCHER_LOG);
|
|
164
|
+
const start = Math.max(0, stat.size - bytes);
|
|
165
|
+
const fd = fs.openSync(WATCHER_LOG, 'r');
|
|
166
|
+
const buf = Buffer.alloc(stat.size - start);
|
|
167
|
+
fs.readSync(fd, buf, 0, buf.length, start);
|
|
168
|
+
fs.closeSync(fd);
|
|
169
|
+
return buf.toString('utf8');
|
|
170
|
+
} catch (_) { return ''; }
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async function validateEmbed() {
|
|
174
|
+
const v = { ok: false, calls: [], p50_ms: 0, p95_ms: 0, crashed: false, recall_mode: '', recall_top_text: '', errors: [] };
|
|
175
|
+
log('Validation 2: embed end-to-end (node)');
|
|
176
|
+
const before = readWatcherLogTail();
|
|
177
|
+
const beforeLen = before.length;
|
|
178
|
+
|
|
179
|
+
const texts = [
|
|
180
|
+
'gm-validate witness one ' + Date.now(),
|
|
181
|
+
'gm-validate witness two ' + Date.now(),
|
|
182
|
+
'gm-validate witness three ' + Date.now(),
|
|
183
|
+
];
|
|
184
|
+
for (const text of texts) {
|
|
185
|
+
const r = await dispatch('memorize-fire', { text, namespace: 'validate' }, 60000);
|
|
186
|
+
const d = r.response && (r.response.data || r.response) || {};
|
|
187
|
+
const ok = !!(r.ok && (r.response && r.response.ok) && (d.embedded === true || d.embedded === undefined) && (d.memorized !== false));
|
|
188
|
+
v.calls.push({ text, latency_ms: r.latency_ms, ok, embedded: d.embedded === true, memorized: d.memorized !== false, error: r.error || (r.response && r.response.error) || null });
|
|
189
|
+
}
|
|
190
|
+
const lats = v.calls.map(c => c.latency_ms).sort((a, b) => a - b);
|
|
191
|
+
v.p50_ms = lats[Math.floor(lats.length * 0.5)] || 0;
|
|
192
|
+
v.p95_ms = lats[Math.max(0, lats.length - 1)] || 0;
|
|
193
|
+
|
|
194
|
+
const r = await dispatch('recall', { query: 'gm-validate witness', namespace: 'validate', limit: 3 }, 60000);
|
|
195
|
+
const rd = (r.response && (r.response.data || r.response)) || {};
|
|
196
|
+
v.recall_mode = rd.mode || '';
|
|
197
|
+
const rows = rd.rows || rd.hits || [];
|
|
198
|
+
v.recall_top_text = (rows[0] && (rows[0].text || rows[0].content)) || '';
|
|
199
|
+
|
|
200
|
+
const after = readWatcherLogTail(256 * 1024);
|
|
201
|
+
const delta = after.slice(Math.max(0, after.length - (after.length - beforeLen) - 2048));
|
|
202
|
+
if (/proc_exit|panicked|wasm trap|RuntimeError/i.test(delta)) {
|
|
203
|
+
v.crashed = true;
|
|
204
|
+
v.errors.push('watcher log shows wasm crash');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const allOk = v.calls.every(c => c.ok);
|
|
208
|
+
const recallOk = v.recall_top_text.includes('gm-validate witness');
|
|
209
|
+
v.ok = allOk && recallOk && !v.crashed;
|
|
210
|
+
if (!allOk) v.errors.push('not all memorize calls ok');
|
|
211
|
+
if (!recallOk) v.errors.push('recall top hit did not contain witness text');
|
|
212
|
+
if (REQUIRE_NEW_EMBED && v.recall_mode !== 'vector_top_k') v.errors.push('recall mode != vector_top_k (require-new-embed)');
|
|
213
|
+
return v;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function which(cmd) {
|
|
217
|
+
try {
|
|
218
|
+
const out = cp.execFileSync(process.platform === 'win32' ? 'where.exe' : 'which', [cmd], { encoding: 'utf8', windowsHide: true }).split(/\r?\n/).filter(Boolean);
|
|
219
|
+
return out[0] || '';
|
|
220
|
+
} catch (_) { return ''; }
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async function validateBrowserEmbed() {
|
|
224
|
+
const v = { ok: false, calls: [], p50_ms: 0, p95_ms: 0, recall_top_text: '', skipped: false, errors: [] };
|
|
225
|
+
log('Validation 3: embed in browser via playwriter');
|
|
226
|
+
const pw = which('playwriter') || which('playwriter.cmd');
|
|
227
|
+
if (!pw) { v.skipped = true; v.errors.push('playwriter not found'); return v; }
|
|
228
|
+
|
|
229
|
+
const tbDir = 'C:/dev/thebird';
|
|
230
|
+
if (!fs.existsSync(path.join(tbDir, 'docs'))) { v.skipped = true; v.errors.push('thebird docs missing'); return v; }
|
|
231
|
+
|
|
232
|
+
let serveProc = null;
|
|
233
|
+
const port = 3088;
|
|
234
|
+
try {
|
|
235
|
+
serveProc = cp.spawn(process.platform === 'win32' ? 'npx.cmd' : 'npx', ['serve', 'docs', '-l', String(port)], {
|
|
236
|
+
cwd: tbDir, detached: true, stdio: 'ignore', windowsHide: true, shell: process.platform === 'win32',
|
|
237
|
+
});
|
|
238
|
+
serveProc.unref();
|
|
239
|
+
} catch (e) { v.errors.push('serve spawn: ' + e.message); return v; }
|
|
240
|
+
|
|
241
|
+
const t0 = Date.now();
|
|
242
|
+
let ready = false;
|
|
243
|
+
while (Date.now() - t0 < 20000) {
|
|
244
|
+
try {
|
|
245
|
+
await new Promise((res, rej) => {
|
|
246
|
+
const req = require('http').get(`http://127.0.0.1:${port}/`, r => { r.resume(); res(); });
|
|
247
|
+
req.on('error', rej);
|
|
248
|
+
req.setTimeout(1500, () => req.destroy(new Error('timeout')));
|
|
249
|
+
});
|
|
250
|
+
ready = true; break;
|
|
251
|
+
} catch (_) { await sleep(500); }
|
|
252
|
+
}
|
|
253
|
+
if (!ready) { v.errors.push('thebird serve never came up on ' + port); return v; }
|
|
254
|
+
|
|
255
|
+
let sessionId = '';
|
|
256
|
+
try {
|
|
257
|
+
const out = cp.execSync('playwriter session new', { encoding: 'utf8', windowsHide: true });
|
|
258
|
+
const m = out.match(/Session\s+(\S+)\s+created/i) || out.match(/([A-Za-z0-9_-]{1,40})/);
|
|
259
|
+
sessionId = (m && m[1]) || '';
|
|
260
|
+
} catch (e) { v.errors.push('playwriter session new: ' + e.message); return v; }
|
|
261
|
+
if (!sessionId) { v.errors.push('no session id from playwriter'); return v; }
|
|
262
|
+
|
|
263
|
+
const script = `
|
|
264
|
+
await page.goto('http://127.0.0.1:${port}/');
|
|
265
|
+
await page.waitForLoadState('domcontentloaded');
|
|
266
|
+
const has = await page.evaluate(() => !!(window.__debug && window.__debug.gm));
|
|
267
|
+
if (!has) { return { skipped: true, reason: 'no window.__debug.gm' }; }
|
|
268
|
+
const out = { mems: [], recall: null };
|
|
269
|
+
for (const t of ['bw one ${Date.now()}', 'bw two ${Date.now()}', 'bw three ${Date.now()}']) {
|
|
270
|
+
const t0 = performance.now();
|
|
271
|
+
const r = await window.__debug.gm.memorize({ text: t, namespace: 'validate-browser' });
|
|
272
|
+
out.mems.push({ ok: !!(r && r.ok), latency_ms: performance.now() - t0 });
|
|
273
|
+
}
|
|
274
|
+
out.recall = await window.__debug.gm.recall({ query: 'bw', namespace: 'validate-browser', limit: 3 });
|
|
275
|
+
return out;
|
|
276
|
+
`;
|
|
277
|
+
let res = null;
|
|
278
|
+
try {
|
|
279
|
+
const tmpScript = path.join(os.tmpdir(), 'gm-validate-' + Date.now() + '.js');
|
|
280
|
+
fs.writeFileSync(tmpScript, script);
|
|
281
|
+
const out = cp.execSync('playwriter -s ' + sessionId + ' --timeout 60000 -e "' + script.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, ' ') + '"', { encoding: 'utf8', windowsHide: true, maxBuffer: 4 * 1024 * 1024 });
|
|
282
|
+
try { fs.unlinkSync(tmpScript); } catch (_) {}
|
|
283
|
+
const m = out.match(/\{[\s\S]*\}\s*$/);
|
|
284
|
+
if (m) { try { res = JSON.parse(m[0]); } catch (_) {} }
|
|
285
|
+
if (!res) v.errors.push('could not parse playwriter eval output');
|
|
286
|
+
} catch (e) { v.errors.push('playwriter -e: ' + e.message); }
|
|
287
|
+
|
|
288
|
+
if (res && res.skipped) { v.skipped = true; v.errors.push(res.reason || 'browser debug surface missing'); return v; }
|
|
289
|
+
if (res && Array.isArray(res.mems)) {
|
|
290
|
+
v.calls = res.mems;
|
|
291
|
+
const lats = res.mems.map(c => c.latency_ms).sort((a, b) => a - b);
|
|
292
|
+
v.p50_ms = Math.round(lats[Math.floor(lats.length * 0.5)] || 0);
|
|
293
|
+
v.p95_ms = Math.round(lats[Math.max(0, lats.length - 1)] || 0);
|
|
294
|
+
const rows = (res.recall && (res.recall.rows || res.recall.hits)) || [];
|
|
295
|
+
v.recall_top_text = (rows[0] && (rows[0].text || rows[0].content)) || '';
|
|
296
|
+
const allOk = v.calls.every(c => c.ok);
|
|
297
|
+
v.ok = allOk && v.recall_top_text.startsWith('bw');
|
|
298
|
+
if (!allOk) v.errors.push('not all browser memorize calls ok');
|
|
299
|
+
}
|
|
300
|
+
return v;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
(async () => {
|
|
304
|
+
const final = { chromium_headed: false, chromium_window_title: '', embed_node: { ok: false, p50_ms: 0, p95_ms: 0, crashed: false }, embed_browser: { ok: false, p50_ms: 0, p95_ms: 0, skipped: false } };
|
|
305
|
+
let exit = 0;
|
|
306
|
+
try {
|
|
307
|
+
await ensureWatcher();
|
|
308
|
+
const v1 = await validateChromium();
|
|
309
|
+
final.chromium_headed = !!v1.ok;
|
|
310
|
+
final.chromium_window_title = v1.window_title || '';
|
|
311
|
+
final._v1 = v1;
|
|
312
|
+
if (!v1.ok) exit = 1;
|
|
313
|
+
|
|
314
|
+
const v2 = await validateEmbed();
|
|
315
|
+
final.embed_node = { ok: v2.ok, p50_ms: v2.p50_ms, p95_ms: v2.p95_ms, crashed: v2.crashed, recall_mode: v2.recall_mode, errors: v2.errors };
|
|
316
|
+
final._v2 = v2;
|
|
317
|
+
if (!v2.ok) exit = 1;
|
|
318
|
+
|
|
319
|
+
const v3 = await validateBrowserEmbed();
|
|
320
|
+
final.embed_browser = { ok: v3.ok, p50_ms: v3.p50_ms, p95_ms: v3.p95_ms, skipped: !!v3.skipped, errors: v3.errors };
|
|
321
|
+
final._v3 = v3;
|
|
322
|
+
if (!v3.ok && !v3.skipped) exit = 1;
|
|
323
|
+
} catch (e) {
|
|
324
|
+
final.error = e.message;
|
|
325
|
+
exit = 1;
|
|
326
|
+
}
|
|
327
|
+
process.stdout.write(JSON.stringify(final, null, 2) + '\n');
|
|
328
|
+
process.exit(exit);
|
|
329
|
+
})();
|
package/bin/gmsniff.js
CHANGED
|
@@ -19,12 +19,25 @@ function parseDuration(s) {
|
|
|
19
19
|
function parseArgs(argv) {
|
|
20
20
|
const flags = new Set();
|
|
21
21
|
let since = null;
|
|
22
|
+
let project = null;
|
|
22
23
|
for (let i = 2; i < argv.length; i++) {
|
|
23
24
|
const a = argv[i];
|
|
24
25
|
if (a === '--since') { since = parseDuration(argv[++i]); continue; }
|
|
26
|
+
if (a === '--project') { project = argv[++i]; continue; }
|
|
25
27
|
if (a.startsWith('--')) flags.add(a.slice(2));
|
|
26
28
|
}
|
|
27
|
-
return { flags, since };
|
|
29
|
+
return { flags, since, project };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function findProjectRoot(start) {
|
|
33
|
+
let dir = start;
|
|
34
|
+
for (let i = 0; i < 12; i++) {
|
|
35
|
+
if (fs.existsSync(path.join(dir, '.gm', 'exec-spool'))) return dir;
|
|
36
|
+
const parent = path.dirname(dir);
|
|
37
|
+
if (parent === dir) break;
|
|
38
|
+
dir = parent;
|
|
39
|
+
}
|
|
40
|
+
return start;
|
|
28
41
|
}
|
|
29
42
|
|
|
30
43
|
function readLines(p) {
|
|
@@ -33,11 +46,11 @@ function readLines(p) {
|
|
|
33
46
|
catch (e) { console.log(col('r', `read err ${p}: ${e.message}`)); return []; }
|
|
34
47
|
}
|
|
35
48
|
|
|
36
|
-
function collectEvents(sinceMs) {
|
|
49
|
+
function collectEvents(sinceMs, projectOverride) {
|
|
37
50
|
const events = [];
|
|
38
51
|
const cutoff = sinceMs ? Date.now() - sinceMs : 0;
|
|
39
|
-
const
|
|
40
|
-
const wlog = path.join(
|
|
52
|
+
const projectRoot = projectOverride || findProjectRoot(process.cwd());
|
|
53
|
+
const wlog = path.join(projectRoot, '.gm', 'exec-spool', '.watcher.log');
|
|
41
54
|
for (const line of readLines(wlog)) {
|
|
42
55
|
const m = line.match(/evt:\s*(\{.*\})\s*$/);
|
|
43
56
|
if (!m) continue;
|
|
@@ -120,8 +133,8 @@ function summarize(events) {
|
|
|
120
133
|
}
|
|
121
134
|
|
|
122
135
|
function main() {
|
|
123
|
-
const { flags, since } = parseArgs(process.argv);
|
|
124
|
-
const allEvents = collectEvents(since);
|
|
136
|
+
const { flags, since, project } = parseArgs(process.argv);
|
|
137
|
+
const allEvents = collectEvents(since, project);
|
|
125
138
|
const activeFilters = [...flags].filter(f => FILTERS[f]);
|
|
126
139
|
const useAll = activeFilters.length === 0;
|
|
127
140
|
const kinds = new Set(activeFilters);
|
package/bin/plugkit.version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.1.
|
|
1
|
+
0.1.494
|
package/bin/plugkit.wasm.sha256
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
5ae359515ff63afb35e823466cf053e3e9d39e5591fde5264445d83cc38ea5e7 plugkit.wasm
|
package/gm-plugkit/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm-plugkit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "2.0.1319",
|
|
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": {
|
|
@@ -15,12 +15,19 @@
|
|
|
15
15
|
"plugkit.version",
|
|
16
16
|
"plugkit.sha256"
|
|
17
17
|
],
|
|
18
|
-
"keywords": [
|
|
18
|
+
"keywords": [
|
|
19
|
+
"gm",
|
|
20
|
+
"plugkit",
|
|
21
|
+
"bootstrap",
|
|
22
|
+
"daemon",
|
|
23
|
+
"spool",
|
|
24
|
+
"wasm"
|
|
25
|
+
],
|
|
19
26
|
"author": "AnEntrypoint",
|
|
20
27
|
"license": "MIT",
|
|
21
28
|
"repository": {
|
|
22
29
|
"type": "git",
|
|
23
30
|
"url": "https://github.com/AnEntrypoint/gm.git",
|
|
24
|
-
"directory": "gm-
|
|
31
|
+
"directory": "gm-plugkit"
|
|
25
32
|
}
|
|
26
33
|
}
|