gm-copilot-cli 2.0.239 → 2.0.241

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.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: gm
3
- version: 2.0.239
3
+ version: 2.0.241
4
4
  description: State machine agent with hooks, skills, and automated git enforcement
5
5
  author: AnEntrypoint
6
6
  repository: https://github.com/AnEntrypoint/gm-copilot-cli
@@ -225,15 +225,88 @@ const run = () => {
225
225
  const command = (tool_input?.command || '').trim();
226
226
  const stripFooter = (s) => s.replace(/\n\[Running tools\][\s\S]*$/, '').trimEnd();
227
227
 
228
- if (/^exec:pm2list\s*$/.test(command)) {
229
- const r = runGmExec(['pm2list']);
230
- return allowWithNoop(`exec:pm2list output:\n\n${stripFooter((r.stdout || '') + (r.stderr || ''))}`);
231
- }
232
- if (/^exec:pm2logs(\s|$)/.test(command)) {
233
- const args = command.replace(/^exec:pm2logs\s*/, '').trim();
234
- const pmArgs = args ? ['logs', '--nostream', '--lines', '50', args] : ['logs', '--nostream', '--lines', '50'];
235
- const r = spawnSync('pm2', pmArgs, { encoding: 'utf-8', timeout: 15000, windowsHide: true });
236
- return allowWithNoop(`exec:pm2logs output:\n\n${stripFooter((r.stdout || '') + (r.stderr || '')) || '(no logs)'}`);
228
+ // ─── agent-browser: CLI commands ──────────────────────────────────────────
229
+ const abCliMatch = command.match(/^agent-browser:\n([\s\S]+)$/);
230
+ if (abCliMatch) {
231
+ const abCode = abCliMatch[1];
232
+ const abNative = (() => {
233
+ const abDir = path.join(TOOLS_DIR, 'node_modules', 'agent-browser', 'bin');
234
+ const ext = IS_WIN ? '.exe' : '';
235
+ const archMap = { x64: 'x64', arm64: 'arm64', ia32: 'x64' };
236
+ const osMap = { win32: 'win32', darwin: 'darwin', linux: 'linux' };
237
+ const candidate = path.join(abDir, `agent-browser-${osMap[process.platform] || process.platform}-${archMap[process.arch] || process.arch}${ext}`);
238
+ return fs.existsSync(candidate) ? candidate : null;
239
+ })();
240
+ const abBin = abNative || (fs.existsSync(localBin('agent-browser')) ? localBin('agent-browser') : 'agent-browser');
241
+ const AB_CMDS = new Set(['open','goto','navigate','close','quit','exit','back','forward','reload','click','dblclick','type','fill','press','check','uncheck','select','drag','upload','hover','focus','scroll','scrollintoview','wait','screenshot','pdf','snapshot','get','is','find','eval','connect','tab','frame','dialog','state','session','network','cookies','storage','set','trace','profiler','record','console','errors','highlight','inspect','diff','keyboard','mouse','install','upgrade','confirm','deny','auth','device','window']);
242
+ const AB_GLOBAL_FLAGS = new Set(['--cdp','--headed','--headless','--session','--session-name','--auto-connect','--profile','--allow-file-access','--color-scheme','-p','--platform','--device']);
243
+ const AB_GLOBAL_FLAGS_WITH_VALUE = new Set(['--cdp','--session','--session-name','--profile','--color-scheme','-p','--platform','--device']);
244
+ const AB_SESSION_STATE = path.join(os.tmpdir(), 'gm-ab-sessions.json');
245
+ function readAbSessions() { try { return JSON.parse(fs.readFileSync(AB_SESSION_STATE, 'utf8')); } catch { return {}; } }
246
+ function writeAbSessions(s) { try { fs.writeFileSync(AB_SESSION_STATE, JSON.stringify(s)); } catch {} }
247
+ function parseAbLine(line) {
248
+ const tokens = line.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || [];
249
+ const globalArgs = [], rest = [];
250
+ let i = 0;
251
+ while (i < tokens.length) {
252
+ if (AB_GLOBAL_FLAGS.has(tokens[i])) {
253
+ globalArgs.push(tokens[i]);
254
+ if (AB_GLOBAL_FLAGS_WITH_VALUE.has(tokens[i]) && i + 1 < tokens.length && !tokens[i+1].startsWith('--')) globalArgs.push(tokens[++i]);
255
+ i++;
256
+ } else { rest.push(...tokens.slice(i)); break; }
257
+ }
258
+ return { globalArgs, rest };
259
+ }
260
+ const spawnAb = (bin, args, stdin) => {
261
+ const opts = { encoding: 'utf-8', timeout: 60000, windowsHide: true, ...(IS_WIN && { shell: true }), cwd: process.cwd(), ...(stdin !== undefined && { input: stdin }) };
262
+ const r = spawnSync(bin, args, opts);
263
+ if (!r.stdout && !r.stderr && r.error) return `[spawn error: ${r.error.message}]`;
264
+ const out = (r.stdout || '').trimEnd(), err = stripFooter(r.stderr || '').trimEnd();
265
+ return out && err ? out + '\n[stderr]\n' + err : stripFooter(out || err);
266
+ };
267
+ try {
268
+ const safeAb = abCode.trim();
269
+ const firstParsed = parseAbLine(safeAb.split('\n')[0].trim());
270
+ const firstWord = (firstParsed.rest[0] || '').toLowerCase();
271
+ const sessionName = (() => { const si = firstParsed.globalArgs.indexOf('--session'); return si >= 0 ? firstParsed.globalArgs[si+1] : 'default'; })();
272
+ const sessions = readAbSessions();
273
+ if (['open','goto','navigate'].includes(firstWord)) sessions[sessionName] = { url: firstParsed.rest[1] || '?', ts: Date.now() };
274
+ if (['close','quit','exit'].includes(firstWord)) delete sessions[sessionName];
275
+ writeAbSessions(sessions);
276
+ const openSessions = Object.entries(sessions);
277
+ let result;
278
+ if (AB_CMDS.has(firstWord)) {
279
+ const lines = safeAb.split('\n').map(l => l.trim()).filter(Boolean);
280
+ if (lines.length === 1) {
281
+ const { globalArgs, rest } = parseAbLine(lines[0]);
282
+ result = spawnAb(abBin, [...globalArgs, ...rest]);
283
+ } else {
284
+ const hasClose = lines.some(l => { const w = (parseAbLine(l).rest[0]||'').toLowerCase(); return ['close','quit','exit'].includes(w); });
285
+ const batchGlobals = firstParsed.globalArgs;
286
+ const cmds = lines.map(l => {
287
+ const { globalArgs, rest } = parseAbLine(l);
288
+ const mergedGlobals = [...batchGlobals.filter(f => !globalArgs.includes(f)), ...globalArgs];
289
+ const w = (rest[0]||'').toLowerCase();
290
+ if (['open','goto','navigate'].includes(w)) sessions[sessionName] = { url: rest[1]||'?', ts: Date.now() };
291
+ if (['close','quit','exit'].includes(w)) delete sessions[sessionName];
292
+ if (!AB_CMDS.has(w)) return [...mergedGlobals, 'eval', l.trim()];
293
+ return [...mergedGlobals, ...rest];
294
+ });
295
+ writeAbSessions(sessions);
296
+ result = spawnAb(abBin, ['batch'], JSON.stringify(cmds));
297
+ if (!hasClose && openSessions.length > 0) result += `\n\n[tab] Browser session "${sessionName}" still open. Close when done:\n agent-browser:\n close`;
298
+ }
299
+ } else {
300
+ result = spawnAb(abBin, ['eval', '--stdin'], safeAb);
301
+ }
302
+ if (openSessions.length > 1) {
303
+ const stale = openSessions.filter(([n]) => n !== sessionName).map(([n,v]) => ` "${n}" → ${v.url} (${Math.round((Date.now()-v.ts)/60000)}min ago)`).join('\n');
304
+ result = (result || '') + `\n\n[tab] ${openSessions.length - 1} other session(s) still open:\n${stale}\n Close with: agent-browser:\\nclose (or --session <name> close)`;
305
+ }
306
+ return allowWithNoop(`agent-browser output:\n\n${result || '(no output)'}`);
307
+ } catch(e) {
308
+ return allowWithNoop(`agent-browser error:\n\n${e.message || '(exec failed)'}`);
309
+ }
237
310
  }
238
311
 
239
312
  const execMatch = command.match(/^exec(?::(\S+))?\n([\s\S]+)$/);
@@ -241,13 +314,13 @@ const run = () => {
241
314
  const rawLang = (execMatch[1] || '').toLowerCase();
242
315
  const code = execMatch[2];
243
316
  if (/^\s*agent-browser\s/.test(code)) {
244
- return deny(`Do not call agent-browser via exec:bash. Use exec:agent-browser instead:\n\nexec:agent-browser\nopen http://example.com\n\nMultiple commands in one block:\n\nexec:agent-browser\nopen http://localhost:3001\nwait 2000\nsnapshot -i\n\nFor JS eval (DOM inspection, custom logic):\n\nexec:agent-browser\ndocument.title\n\nCLI commands (open, click, screenshot, snapshot, wait, console, tab, etc.) run directly.\nAnything that is not a CLI command goes through eval --stdin.\nClose tabs when done: exec:agent-browser\\nclose`);
317
+ return deny(`Do not call agent-browser via exec:bash. Use agent-browser: for CLI commands:\n\nagent-browser:\nopen http://example.com\n\nMultiple commands:\n\nagent-browser:\nopen http://localhost:3001\nwait 2000\nsnapshot -i\n\nFor headed mode:\n\nagent-browser:\n--headed open http://localhost:3001\nwait --load networkidle\nsnapshot -i\n\nFor JS eval in browser:\n\nexec:agent-browser\ndocument.title`);
245
318
  }
246
319
  const cwd = tool_input?.cwd;
247
320
 
248
321
  // ─── Lang plugin dispatch ─────────────────────────────────────────────
249
322
  if (rawLang) {
250
- const builtins = new Set(['js','javascript','ts','typescript','node','nodejs','py','python','sh','bash','shell','zsh','powershell','ps1','go','rust','c','cpp','java','deno','cmd','browser','ab','agent-browser','codesearch','search','status','sleep','close','runner','type','pm2list']);
323
+ const builtins = new Set(['js','javascript','ts','typescript','node','nodejs','py','python','sh','bash','shell','zsh','powershell','ps1','go','rust','c','cpp','java','deno','cmd','browser','ab','agent-browser','codesearch','search','status','sleep','close','runner','type']);
251
324
  if (!builtins.has(rawLang)) {
252
325
  const plugins = loadLangPlugins(projectDir);
253
326
  const plugin = plugins.find(p => p.exec.match.test(`exec:${rawLang}\n${code}`));
@@ -274,7 +347,7 @@ const run = () => {
274
347
  return 'nodejs';
275
348
  };
276
349
  // Note: 'cmd' is NOT aliased to 'bash' — it has its own handler below
277
- const aliases = { js: 'nodejs', javascript: 'nodejs', ts: 'typescript', node: 'nodejs', py: 'python', sh: 'bash', shell: 'bash', zsh: 'bash', powershell: 'powershell', ps1: 'powershell', browser: 'agent-browser', ab: 'agent-browser', codesearch: 'codesearch', search: 'search', status: 'status', sleep: 'sleep', close: 'close', runner: 'runner', type: 'type', pm2list: 'pm2list' };
350
+ const aliases = { js: 'nodejs', javascript: 'nodejs', ts: 'typescript', node: 'nodejs', py: 'python', sh: 'bash', shell: 'bash', zsh: 'bash', powershell: 'powershell', ps1: 'powershell', browser: 'agent-browser', ab: 'agent-browser', codesearch: 'codesearch', search: 'search', status: 'status', sleep: 'sleep', close: 'close', runner: 'runner', type: 'type' };
278
351
  const lang = aliases[rawLang] || rawLang || detectLang(code);
279
352
  const langExts = { nodejs: 'mjs', typescript: 'ts', deno: 'ts', python: 'py', bash: 'sh', powershell: 'ps1', go: 'go', rust: 'rs', c: 'c', cpp: 'cpp', java: 'java' };
280
353
 
@@ -343,11 +416,6 @@ const run = () => {
343
416
  const r = runGmExec(['type', taskId, inputData], { timeout: 15000 });
344
417
  return allowWithNoop(`exec:type output:\n\n${stripFooter((r.stdout || '') + (r.stderr || ''))}`);
345
418
  }
346
- if (lang === 'pm2list') {
347
- const r = runGmExec(['pm2list'], { timeout: 15000 });
348
- return allowWithNoop(`exec:pm2list output:\n\n${stripFooter((r.stdout || '') + (r.stderr || ''))}`);
349
- }
350
-
351
419
  try {
352
420
  let result;
353
421
  if (lang === 'bash') {
@@ -368,8 +436,8 @@ const run = () => {
368
436
  const wrapped = `const __result = await (async () => {\n${safeCode}\n})();\nif (__result !== undefined) { if (typeof __result === 'object') { console.log(JSON.stringify(__result, null, 2)); } else { console.log(__result); } }`;
369
437
  result = runWithFile(lang || 'nodejs', wrapped);
370
438
  } else if (lang === 'agent-browser') {
371
- // agent-browser reads agent-browser.json from cwd automatically (headed, profile, session, etc.)
372
- // Just run with shell:true so .cmd wrappers resolve, and use process.cwd() so config is picked up
439
+ // exec:agent-browser = JS eval in browser page context only.
440
+ // Browser CLI commands (open, click, snapshot, headed mode, etc.) use agent-browser: prefix.
373
441
  const abNative = (() => {
374
442
  const abDir = path.join(TOOLS_DIR, 'node_modules', 'agent-browser', 'bin');
375
443
  const ext = IS_WIN ? '.exe' : '';
@@ -379,63 +447,7 @@ const run = () => {
379
447
  return fs.existsSync(candidate) ? candidate : null;
380
448
  })();
381
449
  const abBin = abNative || (fs.existsSync(localBin('agent-browser')) ? localBin('agent-browser') : 'agent-browser');
382
- const AB_CMDS = new Set(['open','goto','navigate','close','quit','exit','back','forward','reload','click','dblclick','type','fill','press','check','uncheck','select','drag','upload','hover','focus','scroll','scrollintoview','wait','screenshot','pdf','snapshot','get','is','find','eval','connect','tab','frame','dialog','state','session','network','cookies','storage','set','trace','profiler','record','console','errors','highlight','inspect','diff','keyboard','mouse','install','upgrade','confirm','deny','auth','device','window']);
383
- const AB_GLOBAL_FLAGS = new Set(['--cdp','--headed','--headless','--session','--session-name','--auto-connect','--profile','--allow-file-access','--color-scheme','-p','--platform','--device']);
384
- const AB_GLOBAL_FLAGS_WITH_VALUE = new Set(['--cdp','--session','--session-name','--profile','--color-scheme','-p','--platform','--device']);
385
- const AB_SESSION_STATE = path.join(os.tmpdir(), 'gm-ab-sessions.json');
386
- function readAbSessions() { try { return JSON.parse(fs.readFileSync(AB_SESSION_STATE, 'utf8')); } catch { return {}; } }
387
- function writeAbSessions(s) { try { fs.writeFileSync(AB_SESSION_STATE, JSON.stringify(s)); } catch {} }
388
- function parseAbLine(line) {
389
- const tokens = line.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || [];
390
- const globalArgs = [], rest = [];
391
- let i = 0;
392
- while (i < tokens.length) {
393
- if (AB_GLOBAL_FLAGS.has(tokens[i])) {
394
- globalArgs.push(tokens[i]);
395
- if (AB_GLOBAL_FLAGS_WITH_VALUE.has(tokens[i]) && i + 1 < tokens.length && !tokens[i+1].startsWith('--')) {
396
- globalArgs.push(tokens[++i]);
397
- }
398
- i++;
399
- } else { rest.push(...tokens.slice(i)); break; }
400
- }
401
- return { globalArgs, rest };
402
- }
403
- const firstLineParsed = parseAbLine(safeCode.trim().split('\n')[0].trim());
404
- const firstWord = (firstLineParsed.rest[0] || '').toLowerCase();
405
- const sessionName = (() => { const si = firstLineParsed.globalArgs.indexOf('--session'); return si >= 0 ? firstLineParsed.globalArgs[si+1] : 'default'; })();
406
- const isOpen = ['open','goto','navigate'].includes(firstWord);
407
- const isClose = ['close','quit','exit'].includes(firstWord);
408
- const sessions = readAbSessions();
409
- if (isOpen) sessions[sessionName] = { url: (firstLineParsed.rest[1] || '?'), ts: Date.now() };
410
- if (isClose) delete sessions[sessionName];
411
- writeAbSessions(sessions);
412
- const openSessions = Object.entries(sessions);
413
- if (AB_CMDS.has(firstWord)) {
414
- const lines = safeCode.split('\n').map(l => l.trim()).filter(Boolean);
415
- if (lines.length === 1) {
416
- const { globalArgs, rest } = parseAbLine(lines[0]);
417
- result = spawnDirect(abBin, [...globalArgs, ...rest]);
418
- } else {
419
- const hasClose = lines.some(l => { const w = (parseAbLine(l).rest[0]||'').toLowerCase(); return ['close','quit','exit'].includes(w); });
420
- const cmds = lines.map(l => {
421
- const { globalArgs, rest } = parseAbLine(l);
422
- const w = (rest[0]||'').toLowerCase();
423
- if (['open','goto','navigate'].includes(w)) sessions[sessionName] = { url: rest[1]||'?', ts: Date.now() };
424
- if (['close','quit','exit'].includes(w)) delete sessions[sessionName];
425
- if (!AB_CMDS.has(w)) return [...globalArgs, 'eval', l.trim()];
426
- return [...globalArgs, ...rest];
427
- });
428
- writeAbSessions(sessions);
429
- result = spawnDirect(abBin, ['batch'], JSON.stringify(cmds));
430
- if (!hasClose && openSessions.length > 0) result += `\n\n[tab] Browser session "${sessionName}" still open. Close when done:\n exec:agent-browser\n close`;
431
- }
432
- } else {
433
- result = spawnDirect(abBin, ['eval', '--stdin'], safeCode);
434
- }
435
- if (openSessions.length > 1) {
436
- const stale = openSessions.filter(([n]) => n !== sessionName).map(([n,v]) => ` "${n}" → ${v.url} (${Math.round((Date.now()-v.ts)/60000)}min ago)`).join('\n');
437
- result = (result || '') + `\n\n[tab] ${openSessions.length - 1} other session(s) still open:\n${stale}\n Close with: exec:agent-browser\\nclose (or --session <name> close)`;
438
- }
450
+ result = spawnDirect(abBin, ['eval', '--stdin'], safeCode);
439
451
  } else {
440
452
  result = runWithFile(lang, safeCode);
441
453
  }
@@ -449,8 +461,8 @@ const run = () => {
449
461
  return deny(`Do not call ${command.match(/^bun\s+x\s+(\S+)/)[1]} directly. Use exec:<lang> syntax instead.\n\nExamples:\n exec:nodejs\n console.log("hello")\n\n exec:codesearch\n find all database queries\n\n exec:bash\n ls -la\n\nThe exec: prefix routes through the hook dispatcher which handles language detection, background tasks, and tool management automatically.`);
450
462
  }
451
463
 
452
- if (!/^exec(\s|:)/.test(command) && !/^git /.test(command) && !/(\bclaude\b)/.test(command) && !/^npm install .* \/config\/.gmweb/.test(command) && !/^bun install --cwd \/config\/.gmweb/.test(command)) {
453
- return deny(`Bash is restricted to exec:<lang> and git.\n\nexec:<lang> syntax (lang auto-detected if omitted):\n exec:nodejs / exec:python / exec:bash / exec:typescript\n exec:go / exec:rust / exec:java / exec:c / exec:cpp\n exec:cmd ← runs cmd.exe /c on Windows\n exec:agent-browser ← browser CLI (open, click, snapshot, wait, tab, console...)\n OR JS eval when body is not a CLI command\n exec ← auto-detects language\n\nexec:agent-browser examples:\n open http://localhost:3001 ← navigate\n snapshot -i ← get element refs\n wait 2000 ← wait ms\n console ← read browser console\n close ← ALWAYS close when done\n document.title ← JS eval (not a CLI command)\n\nMultiple CLI commands in one block run as batch:\n exec:agent-browser\n open http://localhost:3001\n wait 2000\n snapshot -i\n\nTask management shortcuts (body = args):\n exec:status\n <task_id>\n\n exec:sleep\n <task_id> [seconds] [--next-output]\n\n exec:type\n <task_id>\n <input to send to stdin>\n\n exec:close\n <task_id>\n\n exec:runner\n start|stop|status\n\nCode search shortcut:\n exec:codesearch\n <natural language query>\n\nAll other Bash commands are blocked.`);
464
+ if (!/^exec(\s|:)/.test(command) && !/^agent-browser:/.test(command) && !/^git /.test(command) && !/(\bclaude\b)/.test(command) && !/^npm install .* \/config\/.gmweb/.test(command) && !/^bun install --cwd \/config\/.gmweb/.test(command)) {
465
+ return deny(`Bash is restricted to exec:<lang>, agent-browser:, and git.\n\nexec:<lang> syntax (lang auto-detected if omitted):\n exec:nodejs / exec:python / exec:bash / exec:typescript\n exec:go / exec:rust / exec:java / exec:c / exec:cpp\n exec:cmd ← runs cmd.exe /c on Windows\n exec:agent-browser ← JS eval in browser page context (document.title, DOM queries, etc.)\n exec ← auto-detects language\n\nexec:agent-browser JS eval in browser page context:\n exec:agent-browser\n document.title\n\n exec:agent-browser\n JSON.stringify([...document.querySelectorAll('h1')].map(h => h.textContent))\n\nagent-browser: — browser CLI commands (open, click, snapshot, headed mode, etc.):\n agent-browser:\n open http://localhost:3001\n\n agent-browser:\n --headed open http://localhost:3001\n wait --load networkidle\n snapshot -i\n\n agent-browser:\n close\n\nTask management shortcuts (body = args):\n exec:status\n <task_id>\n\n exec:sleep\n <task_id> [seconds] [--next-output]\n\n exec:type\n <task_id>\n <input to send to stdin>\n\n exec:close\n <task_id>\n\n exec:runner\n start|stop|status\n\nCode search shortcut:\n exec:codesearch\n <natural language query>\n\nAll other Bash commands are blocked.`);
454
466
  }
455
467
  }
456
468
 
package/index.html CHANGED
@@ -18,7 +18,7 @@
18
18
  <script type="module">
19
19
  import { createElement as h, applyDiff, Fragment } from "webjsx";
20
20
  const PLATFORM_NAME="Copilot CLI",PLATFORM_TYPE="CLI Tool",PLATFORM_TYPE_COLOR="#3b82f6";
21
- const DESCRIPTION="State machine agent with hooks, skills, and automated git enforcement",VERSION="2.0.239";
21
+ const DESCRIPTION="State machine agent with hooks, skills, and automated git enforcement",VERSION="2.0.241";
22
22
  const GITHUB_URL="https://github.com/AnEntrypoint/gm-copilot-cli",BADGE_LABEL="copilot-cli";
23
23
  const FEATURES=[{"title":"State Machine","desc":"Immutable PLAN→EXECUTE→EMIT→VERIFY→COMPLETE phases with full mutable tracking"},{"title":"Semantic Search","desc":"Natural language codebase exploration via codesearch skill — no grep needed"},{"title":"Hooks","desc":"Pre-tool, session-start, prompt-submit, and stop hooks for full lifecycle control"},{"title":"Agents","desc":"gm, codesearch, and websearch agents pre-configured and ready to use"},{"title":"MCP Integration","desc":"Model Context Protocol server support built in"},{"title":"Auto-Recovery","desc":"Supervisor hierarchy ensures the system never crashes"}],INSTALL_STEPS=[{"desc":"Install via GitHub CLI","cmd":"gh extension install AnEntrypoint/gm-copilot-cli"},{"desc":"Restart your terminal — activates automatically"}];
24
24
  const CURRENT_PLATFORM="gm-copilot-cli";
package/manifest.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  name: gm
2
- version: 2.0.239
2
+ version: 2.0.241
3
3
  description: State machine agent with hooks, skills, and automated git enforcement
4
4
  author: AnEntrypoint
5
5
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-copilot-cli",
3
- "version": "2.0.239",
3
+ "version": "2.0.241",
4
4
  "description": "State machine agent with hooks, skills, and automated git enforcement",
5
5
  "author": "AnEntrypoint",
6
6
  "license": "MIT",
@@ -1,32 +1,47 @@
1
1
  ---
2
2
  name: agent-browser
3
3
  description: Browser automation CLI for AI agents. Use when the user needs to interact with websites, including navigating pages, filling forms, clicking buttons, taking screenshots, extracting data, testing web apps, or automating any browser task. Triggers include requests to "open a website", "fill out a form", "click a button", "take a screenshot", "scrape data from a page", "test this web app", "login to a site", "automate browser actions", or any task requiring programmatic web interaction.
4
- allowed-tools: agent-browser, Bash(exec:agent-browser*)
4
+ allowed-tools: agent-browser, Bash(agent-browser:*), Bash(exec:agent-browser*)
5
5
  ---
6
6
 
7
7
  # Browser Automation with agent-browser
8
8
 
9
9
  ## Two Pathways
10
10
 
11
- **Page control** — use the `agent-browser` tool directly for all browser interaction: navigating, clicking, filling forms, taking screenshots, reading snapshots. This is the primary pathway for driving the browser.
11
+ **Browser CLI commands** — use `agent-browser:` prefix via Bash for all browser control: navigating, clicking, filling forms, taking screenshots, reading snapshots.
12
12
 
13
- **Code execution** — use `exec:agent-browser` via Bash when you need to run JavaScript in the page context. The body is piped to `eval --stdin`. Use this for DOM inspection, custom extraction logic, or anything requiring programmatic page access.
13
+ ```
14
+ agent-browser:
15
+ open http://localhost:3001
16
+ wait 2000
17
+ snapshot -i
18
+ ```
19
+
20
+ Single commands:
21
+
22
+ ```
23
+ agent-browser:
24
+ open http://example.com
25
+ ```
26
+
27
+ ```
28
+ agent-browser:
29
+ close
30
+ ```
31
+
32
+ **JS eval in browser** — use `exec:agent-browser` via Bash when you need to run JavaScript in the page context. The body is piped to `eval --stdin`. Use this for DOM inspection, custom extraction logic, or anything requiring programmatic page access.
14
33
 
15
34
  ```
16
35
  exec:agent-browser
17
36
  document.title
18
37
  ```
19
38
 
20
- Multi-line `exec:agent-browser` blocks where lines are recognized CLI commands run as a batch instead of eval — useful for sequencing page control steps when the `agent-browser` tool isn't available:
21
-
22
39
  ```
23
40
  exec:agent-browser
24
- open http://localhost:3001
25
- wait 2000
26
- snapshot -i
41
+ JSON.stringify([...document.querySelectorAll('h1')].map(h => h.textContent))
27
42
  ```
28
43
 
29
- **Always close tabs when done**: every `open` is tracked. Use `exec:agent-browser\nclose` (or `--session <name> close`) when finished. Leaving sessions open accumulates stale tabs — the hook will warn you when other sessions are still open.
44
+ **Always close tabs when done**: every `open` is tracked. Use `agent-browser:\nclose` (or `--session <name> close`) when finished. Leaving sessions open accumulates stale tabs — the hook will warn you when other sessions are still open.
30
45
 
31
46
  ## Core Workflow
32
47
 
@@ -155,12 +170,22 @@ agent-browser --auto-connect snapshot
155
170
  agent-browser --cdp 9222 snapshot
156
171
  ```
157
172
 
158
- ### Visual Browser (Debugging)
173
+ ### Visual Browser (Headed Mode)
159
174
 
160
- ```bash
161
- agent-browser --headed open https://example.com
162
- agent-browser highlight @e1 # Highlight element
163
- agent-browser record start demo.webm # Record session
175
+ Use `--headed` as the first flag on the first line — it propagates to all commands in the block:
176
+
177
+ ```
178
+ agent-browser:
179
+ --headed open https://example.com
180
+ wait --load networkidle
181
+ snapshot -i
182
+ ```
183
+
184
+ ```
185
+ agent-browser:
186
+ --headed open https://example.com
187
+ highlight @e1
188
+ record start demo.webm
164
189
  ```
165
190
 
166
191
  ### Local Files (PDFs, HTML)
@@ -451,10 +476,14 @@ agent-browser profiler stop [path] # Stop and save .json profile
451
476
  ```
452
477
 
453
478
  ### Visual Debugging
454
- ```bash
455
- agent-browser --headed open <url> # Headless=false, show visual browser
456
- agent-browser record start <file.webm> # Record session
457
- agent-browser record stop # Stop recording
479
+ ```
480
+ agent-browser:
481
+ --headed open <url>
482
+ record start <file.webm>
483
+ ```
484
+ ```
485
+ agent-browser:
486
+ record stop
458
487
  ```
459
488
 
460
489
  ### Comparisons & Diffs
@@ -543,6 +572,6 @@ exec:agent-browser
543
572
  document.title
544
573
  ```
545
574
 
546
- **Debugging complex interactions**: Use `agent-browser --headed open <url>` to see visual browser, then `agent-browser highlight @e1` to verify element targeting.
575
+ **Debugging complex interactions**: Use headed mode — put `--headed` as the first flag on the first line of an `agent-browser:` block. It propagates to all subsequent commands in the block.
547
576
 
548
577
  **Ground truth verification**: Use the ordinary pathway (`agent-browser screenshot`) for visual confirmation; use the exec pathway for JavaScript-level inspection.
@@ -67,14 +67,12 @@ exec:close
67
67
  <task_id>
68
68
  ```
69
69
 
70
- **Runner management** (the runner itself is a PM2 process named `gm-exec-runner`):
70
+ **Runner management**:
71
71
  ```
72
72
  exec:runner
73
73
  start|stop|status
74
74
  ```
75
75
 
76
- `exec:runner start` launches the `gm-exec-runner` PM2 process. Each `exec:<lang>` call creates its own `gm-exec-task-{id}` PM2 process — all appear in `pm2 list`. Use `exec:runner status` to check the runner. Use `exec:pm2list` to see all processes including exec tasks.
77
-
78
76
  ## CODEBASE EXPLORATION
79
77
 
80
78
  ```
@@ -101,6 +99,15 @@ Invoke `agent-browser` skill. Escalation — exhaust each before advancing:
101
99
  **`update-docs`** — Refresh README, CLAUDE.md, and docs to reflect session changes. Invoked by `gm-complete`.
102
100
  **`agent-browser`** — Browser automation. Invoke inside EXECUTE for all browser/UI work.
103
101
 
102
+ ## DO NOT STOP
103
+
104
+ **You may not respond to the user or stop working while any of these are true:**
105
+ - .prd file exists and has items
106
+ - git has uncommitted changes
107
+ - git has unpushed commits
108
+
109
+ Completing a phase is NOT stopping. After every phase: read .prd, check git, invoke next skill. Only when .prd is deleted AND git is clean AND all commits are pushed may you return a final response to the user.
110
+
104
111
  ## CONSTRAINTS
105
112
 
106
113
  **Tier 0**: no_crash, no_exit, ground_truth_only, real_execution
@@ -108,6 +115,6 @@ Invoke `agent-browser` skill. Escalation — exhaust each before advancing:
108
115
  **Tier 2**: no_duplication, no_hardcoded_values, modularity
109
116
  **Tier 3**: no_comments, convention_over_code
110
117
 
111
- **Never**: `Bash(node/npm/npx/bun)` | skip planning | sequential independent items | screenshot before JS exhausted | narrate past unresolved mutables
118
+ **Never**: `Bash(node/npm/npx/bun)` | skip planning | sequential independent items | screenshot before JS exhausted | narrate past unresolved mutables | stop while .prd has items | ask the user what to do next while work remains
112
119
 
113
- **Always**: invoke named skill at every transition | snake to planning on any new unknown | witnessed execution only | keep going until .prd empty and git clean
120
+ **Always**: invoke named skill at every transition | snake to planning on any new unknown | witnessed execution only | keep going until .prd deleted and git clean
@@ -55,7 +55,7 @@ For browser/UI: invoke `agent-browser` skill with real workflows. Server + clien
55
55
 
56
56
  `exec:nodejs` (default) | `exec:bash` | `exec:python` | `exec:typescript` | `exec:go` | `exec:rust` | `exec:java` | `exec:deno` | `exec:cmd`
57
57
 
58
- Only git in bash directly. Background tasks: `exec:sleep\n<id>`, `exec:status\n<id>`, `exec:close\n<id>`. Runner: `exec:runner\nstart|stop|status`. All activity visible in `pm2 list` and `pm2 monit` in user terminal.
58
+ Only git in bash directly. Background tasks: `exec:sleep\n<id>`, `exec:status\n<id>`, `exec:close\n<id>`. Runner: `exec:runner\nstart|stop|status`.
59
59
 
60
60
  ## CODEBASE EXPLORATION
61
61
 
@@ -82,16 +82,20 @@ Must return 0. If not: stage → commit → push → re-verify. Local commit wit
82
82
 
83
83
  All of: witnessed end-to-end output | all failure paths exercised | .prd empty | git clean and pushed | `user_steps_remaining=0`
84
84
 
85
+ ## DO NOT STOP
86
+
87
+ After end-to-end verification passes: read .prd from disk. If any items remain, immediately invoke `gm-execute` skill — do not respond to the user. Only respond when .prd is deleted AND git is clean AND all commits are pushed.
88
+
85
89
  ## CONSTRAINTS
86
90
 
87
- **Never**: claim done without witnessed output | uncommitted changes | unpushed commits | .prd items remaining | stop at first green | absorb surprises silently
91
+ **Never**: claim done without witnessed output | uncommitted changes | unpushed commits | .prd items remaining | stop at first green | absorb surprises silently | respond to user while .prd has items
88
92
 
89
- **Always**: triage failure before snaking | witness end-to-end | snake to planning on any new unknown | enumerate remaining after every success
93
+ **Always**: triage failure before snaking | witness end-to-end | snake to planning on any new unknown | enumerate remaining after every success | check .prd after every verification pass
90
94
 
91
95
  ---
92
96
 
93
- **→ FORWARD**: .prd items remain → invoke `gm-execute` skill.
94
- **→ FORWARD**: .prd empty + feature work pushed → invoke `update-docs` skill.
97
+ **→ FORWARD**: .prd items remain → invoke `gm-execute` skill (keep going, do not stop).
98
+ **→ FORWARD**: .prd deleted + feature work pushed → invoke `update-docs` skill.
95
99
  **↩ SNAKE to EMIT**: file output wrong → invoke `gm-emit` skill.
96
100
  **↩ SNAKE to EXECUTE**: logic wrong → invoke `gm-execute` skill.
97
101
  **↩ SNAKE to PLAN**: new unknown or wrong requirements → invoke `planning` skill, restart chain.
@@ -87,15 +87,19 @@ Invoke `agent-browser` skill. Escalation: (1) `exec:agent-browser\n<js>` → (2)
87
87
 
88
88
  File ≤200 lines | No duplication | Pre-emit passed | No mocks | No comments | Docs match | All spotted issues fixed
89
89
 
90
+ ## DO NOT STOP
91
+
92
+ Never respond to the user from this phase. When all gate conditions pass, immediately invoke `gm-complete` skill. Do not pause, summarize, or ask questions.
93
+
90
94
  ## CONSTRAINTS
91
95
 
92
- **Never**: write before pre-emit passes | advance with post-emit variance | absorb surprises silently | comments | hardcoded values | defer spotted issues
96
+ **Never**: write before pre-emit passes | advance with post-emit variance | absorb surprises silently | comments | hardcoded values | defer spotted issues | respond to user or pause for input
93
97
 
94
- **Always**: pre-emit debug before writing | post-emit verify from disk | snake to planning on any new unknown | fix immediately
98
+ **Always**: pre-emit debug before writing | post-emit verify from disk | snake to planning on any new unknown | fix immediately | invoke next skill immediately when gates pass
95
99
 
96
100
  ---
97
101
 
98
- **→ FORWARD**: All gates pass → invoke `gm-complete` skill.
102
+ **→ FORWARD**: All gates pass → invoke `gm-complete` skill immediately.
99
103
  **↺ SELF-LOOP**: Known post-emit variance → fix, re-verify.
100
104
  **↩ SNAKE to EXECUTE**: Known logic error → invoke `gm-execute` skill.
101
105
  **↩ SNAKE to PLAN**: Any new unknown → invoke `planning` skill, restart chain.
@@ -47,7 +47,7 @@ exec:close
47
47
  <task_id>
48
48
  ```
49
49
 
50
- **Runner** (PM2-backed — all activity visible in `pm2 list` and `pm2 monit` in user terminal):
50
+ **Runner**:
51
51
  ```
52
52
  exec:runner
53
53
  start|stop|status
@@ -108,14 +108,18 @@ window.__gm = { captures: [], log: (...a) => window.__gm.captures.push({t:Date.n
108
108
 
109
109
  Real services, real data, real timing. Mocks/fakes/stubs = delete immediately. No .test.js/.spec.js. Delete on discovery.
110
110
 
111
+ ## DO NOT STOP
112
+
113
+ Never respond to the user from this phase. When all mutables are KNOWN, immediately invoke `gm-emit` skill. The chain continues until .prd is deleted and git is clean — that happens in `gm-complete`, not here.
114
+
111
115
  ## CONSTRAINTS
112
116
 
113
- **Never**: `Bash(node/npm/npx/bun)` | fake data | mock files | Glob/Grep/Explore | sequential independent items | absorb surprises silently
117
+ **Never**: `Bash(node/npm/npx/bun)` | fake data | mock files | Glob/Grep/Explore | sequential independent items | absorb surprises silently | respond to user or pause for input
114
118
 
115
- **Always**: witness every hypothesis | import real modules | snake to planning on any new unknown | fix immediately on discovery
119
+ **Always**: witness every hypothesis | import real modules | snake to planning on any new unknown | fix immediately on discovery | invoke next skill immediately when done
116
120
 
117
121
  ---
118
122
 
119
- **→ FORWARD**: All mutables KNOWN → invoke `gm-emit` skill.
123
+ **→ FORWARD**: All mutables KNOWN → invoke `gm-emit` skill immediately.
120
124
  **↺ SELF-LOOP**: Still UNKNOWN → re-run (max 2 passes).
121
125
  **↩ SNAKE to PLAN**: Any new unknown → invoke `planning` skill, restart chain.
@@ -83,8 +83,12 @@ Independent items (empty `blockedBy`) run in parallel waves of ≤3 subagents.
83
83
 
84
84
  **Skip planning entirely** if: task is single-step, trivially bounded, zero unknowns, under 5 minutes.
85
85
 
86
+ ## DO NOT STOP
87
+
88
+ Never respond to the user from this phase. When .prd is complete (zero new items in last pass), immediately invoke `gm-execute` skill. Do not pause, summarize, or ask for confirmation.
89
+
86
90
  ---
87
91
 
88
- **→ FORWARD**: No new mutables → invoke `gm-execute` skill.
92
+ **→ FORWARD**: No new mutables → invoke `gm-execute` skill immediately.
89
93
  **↺ SELF-LOOP**: New items discovered → add to .prd → plan again.
90
94
  **↩ SNAKE here**: New unknown surfaces in any later phase → add it, re-plan, re-advance.
package/tools.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm",
3
- "version": "2.0.239",
3
+ "version": "2.0.241",
4
4
  "description": "State machine agent with hooks, skills, and automated git enforcement",
5
5
  "tools": [
6
6
  {