omniwire 3.1.1 → 3.1.3

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 CHANGED
@@ -350,131 +350,166 @@ watch(assert="ready") poll until
350
350
 
351
351
  ## All 81 Tools
352
352
 
353
- > **Every tool** supports `background: true` -- returns a task ID immediately. Poll with `omniwire_bg`.
353
+ > **Every tool** supports `background: true` returns a task ID immediately. Poll with `omniwire_bg`.
354
354
 
355
- ### Execution (6)
355
+ <details>
356
+ <summary><b>Execution (6)</b></summary>
356
357
 
357
358
  | Tool | Description |
358
359
  |------|-------------|
359
- | `omniwire_exec` | Run command on any node. `retry`, `assert`, `store_as`, `format:"json"`, `{{key}}`, `via_vpn` for anonymous scanning. |
360
- | `omniwire_run` | Execute multi-line scripts via temp file. Keeps tool call UI clean. |
361
- | `omniwire_batch` | N commands in 1 call. Chaining with `{{prev}}`, `abort_on_fail`, parallel or sequential. |
362
- | `omniwire_broadcast` | Execute on all nodes simultaneously. JSON format support. |
363
- | `omniwire_pipeline` | Multi-step DAG. `{{prev}}`/`{{stepN}}` interpolation, per-step error handling, cross-node. |
364
- | `omniwire_bg` | List, poll, or retrieve results from background tasks. |
360
+ | `omniwire_exec` | Run command on any node. `retry`, `assert`, `store_as`, `format:"json"`, `{{key}}`, `via_vpn`. |
361
+ | `omniwire_run` | Multi-line scripts via temp file. |
362
+ | `omniwire_batch` | N commands in 1 call. Chaining `{{prev}}`, `abort_on_fail`, parallel/sequential. |
363
+ | `omniwire_broadcast` | Execute on all nodes simultaneously. |
364
+ | `omniwire_pipeline` | Multi-step DAG with `{{prev}}`/`{{stepN}}` interpolation. |
365
+ | `omniwire_bg` | List/poll/retrieve background task results. |
365
366
 
366
- ### Agentic / A2A (13)
367
+ </details>
368
+
369
+ <details>
370
+ <summary><b>Agentic / A2A (12)</b></summary>
367
371
 
368
372
  | Tool | Description |
369
373
  |------|-------------|
370
- | `omniwire_store` | Session key-value store. Persist results across tool calls for chaining. |
371
- | `omniwire_watch` | Poll command until assert pattern matches. For deploys, builds, service readiness. |
372
- | `omniwire_healthcheck` | Parallel health probe across all nodes (connectivity, disk, mem, load, docker). Single call. |
373
- | `omniwire_agent_task` | Dispatch background tasks. Get task IDs, poll status, retrieve results. A2A async. |
374
- | `omniwire_a2a_message` | Agent-to-agent message queues. Send/receive/peek on named channels. |
375
- | `omniwire_semaphore` | Distributed locking. Atomic acquire/release to prevent race conditions. |
376
- | `omniwire_event` | Pub/sub events. Emit/poll timestamped events per topic. ACP/A2A/ACPX compatible. |
377
- | `omniwire_workflow` | Define and run reusable named workflows (DAGs). Stored on disk, triggered by any agent. |
378
- | `omniwire_agent_registry` | Register/discover agents by capabilities. Dynamic A2A routing. Heartbeat. |
379
- | `omniwire_blackboard` | Shared blackboard for agent swarms. Post findings, read, search across topics. |
380
- | `omniwire_task_queue` | Distributed task queue. Enqueue/dequeue with priorities. Complete/fail reporting. |
381
- | `omniwire_capability` | Query node capabilities (tools, runtimes, GPU). Intelligent task routing. |
382
-
383
- ### Files & Transfer (6)
374
+ | `omniwire_store` | Session key-value store for cross-call chaining. |
375
+ | `omniwire_watch` | Poll until assert matches deploys, builds, readiness. |
376
+ | `omniwire_healthcheck` | Parallel health probe all nodes (disk, mem, load, docker). |
377
+ | `omniwire_agent_task` | Background task dispatch with poll/retrieve. |
378
+ | `omniwire_a2a_message` | Agent-to-agent message queues (send/receive/peek). |
379
+ | `omniwire_semaphore` | Distributed locking atomic acquire/release. |
380
+ | `omniwire_event` | Pub/sub events per topic. |
381
+ | `omniwire_workflow` | Reusable named workflow DAGs. |
382
+ | `omniwire_agent_registry` | Agent capability discovery + heartbeat. |
383
+ | `omniwire_blackboard` | Shared blackboard for swarm coordination. |
384
+ | `omniwire_task_queue` | Distributed priority queue — enqueue/dequeue/complete. |
385
+ | `omniwire_capability` | Query node capabilities for intelligent routing. |
386
+
387
+ </details>
388
+
389
+ <details>
390
+ <summary><b>Files & Transfer (6)</b></summary>
384
391
 
385
392
  | Tool | Description |
386
393
  |------|-------------|
387
- | `omniwire_read_file` | Read file from any node. `node:/path` format. |
394
+ | `omniwire_read_file` | Read file from any node (`node:/path`). |
388
395
  | `omniwire_write_file` | Write/create file on any node. |
389
396
  | `omniwire_list_files` | List directory contents. |
390
- | `omniwire_find_files` | Glob search across all nodes. |
391
- | `omniwire_transfer_file` | Copy between nodes. Auto-selects SFTP/netcat/aria2c. |
392
- | `omniwire_deploy` | Deploy file from one node to all others in parallel. |
397
+ | `omniwire_find_files` | Glob search across nodes. |
398
+ | `omniwire_transfer_file` | Copy between nodes (auto SFTP/netcat/aria2c). |
399
+ | `omniwire_deploy` | Deploy one file to all nodes in parallel. |
400
+
401
+ </details>
393
402
 
394
- ### Monitoring (3)
403
+ <details>
404
+ <summary><b>Monitoring (3)</b></summary>
395
405
 
396
406
  | Tool | Description |
397
407
  |------|-------------|
398
- | `omniwire_mesh_status` | Health, latency, CPU/mem/disk for all nodes. Tabular output. |
399
- | `omniwire_node_info` | Detailed info for a specific node. |
408
+ | `omniwire_mesh_status` | Health, latency, CPU/mem/disk all nodes. |
409
+ | `omniwire_node_info` | Detailed info for one node. |
400
410
  | `omniwire_live_monitor` | Snapshot metrics: cpu, memory, disk, network. |
401
411
 
402
- ### System & DevOps (12)
412
+ </details>
413
+
414
+ <details>
415
+ <summary><b>System & DevOps (12)</b></summary>
403
416
 
404
417
  | Tool | Description |
405
418
  |------|-------------|
406
- | `omniwire_process_list` | List/filter processes across nodes |
407
- | `omniwire_disk_usage` | Disk usage for all nodes |
408
- | `omniwire_tail_log` | Last N lines of a log file |
409
- | `omniwire_install_package` | Install via apt/npm/pip |
410
- | `omniwire_service_control` | systemd start/stop/restart/status |
411
- | `omniwire_docker` | Docker commands on any node |
412
- | `omniwire_kernel` | dmesg, sysctl, modprobe, lsmod, strace, perf |
413
- | `omniwire_cron` | List/add/remove cron jobs |
414
- | `omniwire_env` | Get/set persistent environment variables |
415
- | `omniwire_network` | ping, traceroute, dns, ports, speed, connections |
416
- | `omniwire_git` | Git commands on repos on any node |
417
- | `omniwire_syslog` | Query journalctl with filters |
418
-
419
- ### Network, VPN & Security (9)
419
+ | `omniwire_process_list` | List/filter processes across nodes. |
420
+ | `omniwire_disk_usage` | Disk usage for all nodes. |
421
+ | `omniwire_tail_log` | Last N lines of a log file. |
422
+ | `omniwire_install_package` | Install via apt/npm/pip. |
423
+ | `omniwire_service_control` | systemd start/stop/restart/status. |
424
+ | `omniwire_docker` | Docker commands on any node. |
425
+ | `omniwire_kernel` | dmesg, sysctl, modprobe, lsmod, strace, perf. |
426
+ | `omniwire_cron` | List/add/remove cron jobs. |
427
+ | `omniwire_env` | Get/set persistent environment variables. |
428
+ | `omniwire_network` | ping, traceroute, dns, ports, speed, connections. |
429
+ | `omniwire_git` | Git commands on repos on any node. |
430
+ | `omniwire_syslog` | Query journalctl with filters. |
431
+
432
+ </details>
433
+
434
+ <details>
435
+ <summary><b>Network, VPN & Security (9)</b></summary>
420
436
 
421
437
  | Tool | Description |
422
438
  |------|-------------|
423
- | `omniwire_firewall` | nftables firewall engine. Presets (server/paranoid/pentest), rate-limit, geo-block, port-knock, ban/unban, audit. Mesh always whitelisted. |
424
- | `omniwire_vpn` | VPN (Mullvad/OpenVPN/WireGuard/Tailscale). Multi-hop, DAITA, quantum, obfuscation, killswitch. Mesh-safe. |
425
- | `omniwire_cookies` | Cookie management. JSON/Header/Netscape formats. Browser extract, CyberBase + 1Password sync. |
426
- | `omniwire_cdp` | Chrome DevTools Protocol. Launch headless Chrome, screenshot, PDF, DOM dump, cookie extract. |
427
- | `omniwire_proxy` | HTTP/SOCKS proxy management. Start/stop proxies on any node. |
428
- | `omniwire_dns` | DNS management. Resolve, set server, flush cache, block domains. |
429
- | `omniwire_port_forward` | Create/list/close SSH tunnels |
430
- | `omniwire_shell` | Persistent PTY session (preserves cwd/env) |
431
- | `omniwire_clipboard` | Shared clipboard buffer across mesh |
432
-
433
- ### Infrastructure (9)
439
+ | `omniwire_firewall` | nftables engine presets, rate-limit, geo-block, port-knock, ban/unban. Mesh whitelisted. |
440
+ | `omniwire_vpn` | Mullvad/OpenVPN/WireGuard/Tailscale — multi-hop, DAITA, quantum, killswitch. Mesh-safe. |
441
+ | `omniwire_cookies` | Cookie management JSON/Header/Netscape, browser extract, CyberBase + 1Password sync. |
442
+ | `omniwire_cdp` | Chrome DevTools Protocol headless Chrome, screenshot, PDF, DOM, cookies. |
443
+ | `omniwire_proxy` | HTTP/SOCKS proxy management on any node. |
444
+ | `omniwire_dns` | DNS resolve, set server, flush cache, block domains. |
445
+ | `omniwire_port_forward` | SSH tunnels — create/list/close/mesh-expose. |
446
+ | `omniwire_shell` | Persistent PTY session (preserves cwd/env). |
447
+ | `omniwire_clipboard` | Shared clipboard buffer across mesh. |
448
+
449
+ </details>
450
+
451
+ <details>
452
+ <summary><b>Infrastructure (9)</b></summary>
434
453
 
435
454
  | Tool | Description |
436
455
  |------|-------------|
437
- | `omniwire_backup` | Snapshot/restore paths on any node. Diff, cleanup, retention policies. |
438
- | `omniwire_container` | Full Docker lifecycle. Compose up/down, build, push, logs, prune, stats. |
439
- | `omniwire_cert` | TLS certificates. Let's Encrypt issue/renew, check expiry, self-signed generation. |
440
- | `omniwire_user` | User & SSH key management. Add/remove users, deploy keys, sudo config. |
441
- | `omniwire_schedule` | Distributed cron with failover. Add/remove/list/run-now scheduled tasks. |
442
- | `omniwire_alert` | Threshold alerting. Disk/mem/load/offline checks with webhook notifications. |
443
- | `omniwire_log_aggregate` | Cross-node log search. Grep journalctl/syslog across all nodes in parallel. |
444
- | `omniwire_benchmark` | Node performance testing. CPU/memory/disk/network benchmarks. |
445
- | `omniwire_stream` | Capture streaming output (tail -f, watch) |
446
-
447
- ### Agent Toolkit (7)
456
+ | `omniwire_backup` | Snapshot/restore paths. Diff, cleanup, retention. |
457
+ | `omniwire_container` | Docker lifecycle compose, build, push, logs, prune, stats. |
458
+ | `omniwire_cert` | TLS certs Let's Encrypt, check expiry, self-signed. |
459
+ | `omniwire_user` | User & SSH key management, sudo config. |
460
+ | `omniwire_schedule` | Distributed cron with failover. |
461
+ | `omniwire_alert` | Threshold alerting — disk/mem/load/offline + webhook notify. |
462
+ | `omniwire_log_aggregate` | Cross-node log search in parallel. |
463
+ | `omniwire_benchmark` | CPU/memory/disk/network benchmarks. |
464
+ | `omniwire_stream` | Capture streaming output (tail -f, watch). |
465
+
466
+ </details>
467
+
468
+ <details>
469
+ <summary><b>OmniMesh & Events (6)</b></summary>
448
470
 
449
471
  | Tool | Description |
450
472
  |------|-------------|
451
- | `omniwire_snippet` | Save/run reusable command templates with `{{var}}` substitution. |
452
- | `omniwire_alias` | In-session command shortcuts. Set/run aliases. |
453
- | `omniwire_trace` | Distributed tracing. Start/stop/view span waterfalls across nodes. |
454
- | `omniwire_doctor` | Health diagnostics. Checks SSH, disk, mem, docker, WireGuard, tools, CyberBase. |
455
- | `omniwire_metrics` | Prometheus-compatible metrics. Scrape/export node stats. |
456
- | `omniwire_audit` | Command audit log. View/search/stats on all executed commands. |
457
- | `omniwire_plugin` | Plugin system. List/load plugins from `~/.omniwire/plugins/`. |
458
- | `omniwire_omnimesh` | OmniMesh — built-in WireGuard mesh manager. Init/up/down/add-peer/sync-peers/health/rotate-keys/topology across all OS. |
459
- | `omniwire_mesh_expose` | Expose localhost-bound services to the entire mesh. Discover/expose/unexpose/expose-remote. |
460
- | `omniwire_mesh_gateway` | Auto-expose all localhost services mesh-wide. Sync/teardown/add-rule/remove-rule. |
461
- | `omniwire_events` | Event bus with Webhook + WebSocket + SSE. Publish events, manage webhooks, query log. |
462
- | `omniwire_knowledge` | CyberBase knowledge CRUD + text/semantic search + health + vacuum + bulk-set + export. |
463
-
464
- ### CyberSync (9)
473
+ | `omniwire_omnimesh` | WireGuard mesh manager init/up/down/add-peer/sync-peers/health/rotate-keys/topology. All OS. |
474
+ | `omniwire_mesh_expose` | Expose localhost services to mesh — discover/expose/unexpose/expose-remote. |
475
+ | `omniwire_mesh_gateway` | Auto-expose all localhost services mesh-wide. |
476
+ | `omniwire_events` | Webhook + WebSocket + SSE event bus. Publish, manage webhooks, query log. |
477
+ | `omniwire_knowledge` | CyberBase knowledge CRUD, text/semantic search, health, vacuum, bulk-set, export. |
478
+ | `omniwire_update` | Self-update from npm + GitHub. Auto-update, mesh-wide push. |
479
+
480
+ </details>
481
+
482
+ <details>
483
+ <summary><b>Agent Toolkit (7)</b></summary>
465
484
 
466
485
  | Tool | Description |
467
486
  |------|-------------|
468
- | `cybersync_status` | Sync status, item counts, pending syncs |
469
- | `cybersync_sync_now` | Trigger immediate reconciliation |
470
- | `cybersync_diff` | Show local vs database differences |
471
- | `cybersync_history` | Query sync event log |
472
- | `cybersync_search_knowledge` | Full-text search unified knowledge base |
473
- | `cybersync_get_memory` | Retrieve Claude memory from PostgreSQL |
474
- | `cybersync_manifest` | Show tracked files per tool |
475
- | `cybersync_force_push` | Force push file to all nodes |
476
- | `omniwire_secrets` | Get/set/delete/list/sync secrets (1Password, file, env) |
477
- | `omniwire_update` | Self-update OmniWire |
487
+ | `omniwire_snippet` | Reusable command templates with `{{var}}` substitution. |
488
+ | `omniwire_alias` | In-session command shortcuts. |
489
+ | `omniwire_trace` | Distributed tracing span waterfalls across nodes. |
490
+ | `omniwire_doctor` | Health diagnostics SSH, disk, mem, docker, WireGuard, CyberBase. |
491
+ | `omniwire_metrics` | Prometheus-compatible metrics scrape/export. |
492
+ | `omniwire_audit` | Command audit log view/search/stats. |
493
+ | `omniwire_plugin` | Plugin system list/load from `~/.omniwire/plugins/`. |
494
+
495
+ </details>
496
+
497
+ <details>
498
+ <summary><b>CyberSync (9)</b></summary>
499
+
500
+ | Tool | Description |
501
+ |------|-------------|
502
+ | `cybersync_status` | Sync status, item counts, pending syncs. |
503
+ | `cybersync_sync_now` | Trigger immediate reconciliation. |
504
+ | `cybersync_diff` | Local vs database differences. |
505
+ | `cybersync_history` | Sync event log. |
506
+ | `cybersync_search_knowledge` | Full-text search unified knowledge base. |
507
+ | `cybersync_get_memory` | Retrieve Claude memory from PostgreSQL. |
508
+ | `cybersync_manifest` | Tracked files per tool. |
509
+ | `cybersync_force_push` | Force push file to all nodes. |
510
+ | `omniwire_secrets` | Secrets management (1Password, file, env). |
511
+
512
+ </details>
478
513
 
479
514
  ---
480
515
 
@@ -1766,73 +1766,177 @@ echo "port-knock configured: ${ports.join(' -> ')} -> port ${target}"`;
1766
1766
  return fail('Invalid action or missing params');
1767
1767
  });
1768
1768
  // --- Tool 33: omniwire_cdp ---
1769
- server.tool('omniwire_cdp', 'Chrome DevTools Protocol browser control on mesh nodes. Launch headless Chrome, navigate, screenshot, extract DOM/cookies, save PDF. For scraping, testing, recon.', {
1770
- action: z.enum(['launch', 'navigate', 'screenshot', 'html', 'pdf', 'cookies', 'tabs', 'close', 'list']).describe('launch=start Chrome, navigate=open URL, screenshot=capture, html=dump DOM, pdf=save PDF, cookies=extract, tabs=list tabs, close=kill, list=sessions'),
1769
+ // Uses the persistent cdp-browser Docker container (puppeteer-core) for all operations.
1770
+ // Falls back to direct Chrome CLI for nodes without the container.
1771
+ const cdpScript = (js) => `docker exec cdp-browser node -e ${JSON.stringify(`const puppeteer=require('puppeteer-core');(async()=>{` +
1772
+ `const r=await fetch('http://127.0.0.1:9222/json/version');const{webSocketDebuggerUrl:ws}=await r.json();` +
1773
+ `const browser=await puppeteer.connect({browserWSEndpoint:ws});` +
1774
+ js +
1775
+ `})().catch(e=>{console.error('ERR:',e.message);process.exit(1)});`)} 2>&1`;
1776
+ server.tool('omniwire_cdp', 'Chrome DevTools Protocol — persistent headless browser via Docker container. Navigate, screenshot, HTML, PDF, cookies, evaluate JS, click, type, wait, network intercept, set-cookies, clear. Reuses pages across calls for speed.', {
1777
+ action: z.enum([
1778
+ 'navigate', 'screenshot', 'html', 'text', 'pdf', 'cookies', 'set-cookies', 'clear-cookies',
1779
+ 'tabs', 'close-tab', 'evaluate', 'click', 'type', 'wait', 'select',
1780
+ 'network', 'status', 'viewport',
1781
+ ]).describe('navigate=open URL, screenshot=capture PNG, html=DOM dump, text=innerText, pdf=save PDF, ' +
1782
+ 'cookies=get all, set-cookies=inject cookies, clear-cookies=wipe, tabs=list pages, close-tab=close page, ' +
1783
+ 'evaluate=run JS in page, click=click selector, type=type into selector, wait=wait for selector, ' +
1784
+ 'select=querySelector extract, network=recent requests, status=container health, viewport=set size'),
1771
1785
  node: z.string().optional().describe('Node (default: contabo)'),
1772
- url: z.string().optional().describe('URL for navigate/screenshot/html/pdf'),
1773
- file: z.string().optional().describe('Output path for screenshot/pdf'),
1774
- session_id: z.string().optional().describe('Session ID from launch'),
1775
- }, async ({ action, node, url, file: outFile, session_id }) => {
1786
+ url: z.string().optional().describe('URL for navigate'),
1787
+ selector: z.string().optional().describe('CSS selector for click/type/wait/select'),
1788
+ value: z.string().optional().describe('Text for type action, JS for evaluate, cookies JSON for set-cookies'),
1789
+ file: z.string().optional().describe('Output path for screenshot/pdf (default: /tmp/cdp-*)'),
1790
+ tab: z.number().optional().describe('Tab index (0-based, default: 0 = most recent)'),
1791
+ width: z.number().optional().describe('Viewport width for viewport action (default: 1920)'),
1792
+ height: z.number().optional().describe('Viewport height for viewport action (default: 1080)'),
1793
+ wait_ms: z.number().optional().describe('Wait timeout in ms (default: 10000)'),
1794
+ full_page: z.boolean().optional().describe('Full page screenshot (default: true)'),
1795
+ }, async ({ action, node, url, selector, value, file: outFile, tab, width, height, wait_ms, full_page }) => {
1776
1796
  const nodeId = node ?? 'contabo';
1777
- const sdir = '/tmp/.omniwire-cdp';
1778
- if (action === 'launch') {
1779
- const id = `cdp-${Date.now().toString(36)}`;
1780
- const port = 9222 + Math.floor(Math.random() * 100);
1781
- const cmd = `mkdir -p ${sdir}; command -v google-chrome >/dev/null || command -v chromium-browser >/dev/null || { echo "chrome not installed"; exit 1; }; ` +
1782
- `CHROME=$(command -v google-chrome || command -v chromium-browser); ` +
1783
- `$CHROME --headless --disable-gpu --no-sandbox --remote-debugging-port=${port} --user-data-dir=${sdir}/${id} ${url ? `"${url}"` : 'about:blank'} &>/dev/null & ` +
1784
- `echo $! > ${sdir}/${id}.pid; echo ${port} > ${sdir}/${id}.port; sleep 1; ` +
1785
- `echo "session=${id} port=${port} pid=$(cat ${sdir}/${id}.pid)"`;
1786
- const r = await manager.exec(nodeId, cmd);
1787
- if (r.code === 0)
1788
- resultStore.set('cdp_session', id);
1789
- return ok(nodeId, r.durationMs, r.stdout, 'chrome launch');
1790
- }
1791
- const sid = session_id ?? resultStore.get('cdp_session') ?? '';
1792
- if (action === 'navigate' && url) {
1793
- const cmd = `PORT=$(cat ${sdir}/${sid}.port 2>/dev/null); curl -sf "http://localhost:$PORT/json/new?${encodeURIComponent(url)}" 2>/dev/null | python3 -c "import json,sys;d=json.load(sys.stdin);print(f\\"tab={d.get('id','')} url={d.get('url','')}\\")" 2>/dev/null || echo "opened ${url}"`;
1794
- const r = await manager.exec(nodeId, cmd);
1795
- return ok(nodeId, r.durationMs, r.stdout, `navigate`);
1797
+ const tabIdx = tab ?? 0;
1798
+ const timeout = wait_ms ?? 10000;
1799
+ const getPage = `const pages=await browser.pages();const page=pages[${tabIdx}]||pages[0];if(!page){console.log('no pages open');process.exit(0);}`;
1800
+ if (action === 'status') {
1801
+ const r = await manager.exec(nodeId, `docker inspect cdp-browser --format '{{.State.Status}} uptime={{.State.StartedAt}}' 2>/dev/null; ` +
1802
+ `curl -sf http://127.0.0.1:9222/json/version 2>/dev/null | python3 -c "import json,sys;d=json.load(sys.stdin);print(f\\"chrome={d.get('Browser','')} proto={d.get('Protocol-Version','')}\\")" 2>/dev/null; ` +
1803
+ `curl -sf http://127.0.0.1:9222/json/list 2>/dev/null | python3 -c "import json,sys;tabs=json.load(sys.stdin);print(f\\"{len(tabs)} tabs open\\");[print(f\\" {t['id'][:8]} {t.get('url','')[:80]}\\") for t in tabs[:10]]" 2>/dev/null`);
1804
+ return ok(nodeId, r.durationMs, r.stdout, 'cdp status');
1805
+ }
1806
+ if (action === 'navigate') {
1807
+ if (!url)
1808
+ return fail('url required');
1809
+ const u = url.replace(/'/g, "\\'");
1810
+ const r = await manager.exec(nodeId, cdpScript(`${getPage}` +
1811
+ `await page.goto('${u}',{waitUntil:'networkidle2',timeout:${timeout}});` +
1812
+ `console.log('url='+page.url());console.log('title='+await page.title());`));
1813
+ return ok(nodeId, r.durationMs, r.stdout, 'navigate');
1796
1814
  }
1797
1815
  if (action === 'screenshot') {
1798
- const output = outFile ?? `/tmp/screenshot-${Date.now()}.png`;
1799
- const target = url ?? (sid ? `$(curl -sf http://localhost:$(cat ${sdir}/${sid}.port)/json/list 2>/dev/null | python3 -c "import json,sys;print(json.load(sys.stdin)[0]['url'])" 2>/dev/null)` : 'about:blank');
1800
- const cmd = `CHROME=$(command -v google-chrome || command -v chromium-browser); $CHROME --headless --no-sandbox --disable-gpu --screenshot="${output}" --window-size=1920,1080 "${target}" 2>/dev/null && echo "saved: ${output} ($(du -h "${output}" | cut -f1))"`;
1801
- const r = await manager.exec(nodeId, cmd);
1816
+ const out = outFile ?? `/tmp/cdp-screenshot-${Date.now()}.png`;
1817
+ const fp = full_page !== false;
1818
+ const r = await manager.exec(nodeId, cdpScript(`${getPage}` +
1819
+ `await page.screenshot({path:'${out}',fullPage:${fp}});` +
1820
+ `const fs=require('fs');const sz=fs.statSync('${out}').size;` +
1821
+ `console.log('saved: ${out} ('+Math.round(sz/1024)+'KB) '+page.url());`));
1802
1822
  return ok(nodeId, r.durationMs, r.stdout, 'screenshot');
1803
1823
  }
1804
1824
  if (action === 'html') {
1805
- const target = url ?? 'about:blank';
1806
- const cmd = `CHROME=$(command -v google-chrome || command -v chromium-browser); $CHROME --headless --no-sandbox --disable-gpu --dump-dom "${target}" 2>/dev/null | head -200`;
1807
- const r = await manager.exec(nodeId, cmd);
1825
+ const r = await manager.exec(nodeId, cdpScript(`${getPage}` +
1826
+ `const html=await page.content();` +
1827
+ `console.log(html.substring(0,${url ? '50000' : '10000'}));`));
1808
1828
  return ok(nodeId, r.durationMs, r.stdout, 'html');
1809
1829
  }
1830
+ if (action === 'text') {
1831
+ const sel = selector ? `.replace(/'/g,"\\\\'")` : '';
1832
+ const r = await manager.exec(nodeId, cdpScript(`${getPage}` +
1833
+ (selector
1834
+ ? `const el=await page.$('${selector.replace(/'/g, "\\'")}');const t=el?await page.evaluate(e=>e.innerText,el):'(not found)';console.log(t.substring(0,20000));`
1835
+ : `const t=await page.evaluate(()=>document.body.innerText);console.log(t.substring(0,20000));`)));
1836
+ return ok(nodeId, r.durationMs, r.stdout, 'text');
1837
+ }
1810
1838
  if (action === 'pdf') {
1811
- const output = outFile ?? `/tmp/page-${Date.now()}.pdf`;
1812
- const target = url ?? 'about:blank';
1813
- const cmd = `CHROME=$(command -v google-chrome || command -v chromium-browser); $CHROME --headless --no-sandbox --disable-gpu --print-to-pdf="${output}" --no-pdf-header-footer "${target}" 2>/dev/null && echo "saved: ${output} ($(du -h "${output}" | cut -f1))"`;
1814
- const r = await manager.exec(nodeId, cmd);
1839
+ const out = outFile ?? `/tmp/cdp-page-${Date.now()}.pdf`;
1840
+ const r = await manager.exec(nodeId, cdpScript(`${getPage}` +
1841
+ `await page.pdf({path:'${out}',format:'A4',printBackground:true});` +
1842
+ `const fs=require('fs');const sz=fs.statSync('${out}').size;` +
1843
+ `console.log('saved: ${out} ('+Math.round(sz/1024)+'KB)');`));
1815
1844
  return ok(nodeId, r.durationMs, r.stdout, 'pdf');
1816
1845
  }
1817
1846
  if (action === 'cookies') {
1818
- const cmd = `find ${sdir}/${sid}/ -name "Cookies" 2>/dev/null | head -1 | xargs -I{} sqlite3 "{}" "SELECT host_key,name,value,path,expires_utc FROM cookies LIMIT 50;" 2>/dev/null || echo "no cookies DB"`;
1819
- const r = await manager.exec(nodeId, cmd);
1820
- return ok(nodeId, r.durationMs, r.stdout, 'cdp cookies');
1847
+ const r = await manager.exec(nodeId, cdpScript(`${getPage}const cookies=await page.cookies();` +
1848
+ `cookies.forEach(c=>console.log(c.domain+'\\t'+c.name+'='+c.value.substring(0,60)+(c.value.length>60?'...':'')));` +
1849
+ `console.log('--- '+cookies.length+' cookies ---');`));
1850
+ return ok(nodeId, r.durationMs, r.stdout, 'cookies');
1851
+ }
1852
+ if (action === 'set-cookies') {
1853
+ if (!value)
1854
+ return fail('value required (JSON array of cookie objects)');
1855
+ const v = value.replace(/'/g, "\\'").replace(/\n/g, '');
1856
+ const r = await manager.exec(nodeId, cdpScript(`${getPage}const cookies=JSON.parse('${v}');` +
1857
+ `await page.setCookie(...cookies);console.log('set '+cookies.length+' cookies');`));
1858
+ return ok(nodeId, r.durationMs, r.stdout, 'set-cookies');
1859
+ }
1860
+ if (action === 'clear-cookies') {
1861
+ const r = await manager.exec(nodeId, cdpScript(`${getPage}const client=await page.createCDPSession();` +
1862
+ `await client.send('Network.clearBrowserCookies');console.log('cookies cleared');`));
1863
+ return ok(nodeId, r.durationMs, r.stdout, 'clear-cookies');
1821
1864
  }
1822
1865
  if (action === 'tabs') {
1823
- const cmd = `PORT=$(cat ${sdir}/${sid}.port 2>/dev/null); curl -sf http://localhost:$PORT/json/list 2>/dev/null | python3 -c "import json,sys;[print(f\\"{t['id'][:8]} {t.get('url','')}\\" ) for t in json.load(sys.stdin)]" 2>/dev/null || echo "no tabs"`;
1824
- const r = await manager.exec(nodeId, cmd);
1866
+ const r = await manager.exec(nodeId, cdpScript(`const pages=await browser.pages();` +
1867
+ `pages.forEach((p,i)=>console.log(i+' '+p.url().substring(0,100)));` +
1868
+ `console.log('--- '+pages.length+' tabs ---');`));
1825
1869
  return ok(nodeId, r.durationMs, r.stdout, 'tabs');
1826
1870
  }
1827
- if (action === 'close') {
1828
- const cmd = `PID=$(cat ${sdir}/${sid}.pid 2>/dev/null); [ -n "$PID" ] && kill $PID 2>/dev/null && rm -rf ${sdir}/${sid}* && echo "closed ${sid}" || echo "not found"`;
1829
- const r = await manager.exec(nodeId, cmd);
1830
- return ok(nodeId, r.durationMs, r.stdout, 'close');
1871
+ if (action === 'close-tab') {
1872
+ const r = await manager.exec(nodeId, cdpScript(`const pages=await browser.pages();` +
1873
+ `if(pages.length<=${tabIdx}){console.log('tab ${tabIdx} not found');process.exit(0);}` +
1874
+ `const url=pages[${tabIdx}].url();await pages[${tabIdx}].close();` +
1875
+ `console.log('closed tab ${tabIdx}: '+url);console.log((pages.length-1)+' tabs remaining');`));
1876
+ return ok(nodeId, r.durationMs, r.stdout, 'close-tab');
1877
+ }
1878
+ if (action === 'evaluate') {
1879
+ if (!value)
1880
+ return fail('value required (JavaScript to evaluate in page context)');
1881
+ const js = value.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/\n/g, '\\n');
1882
+ const r = await manager.exec(nodeId, cdpScript(`${getPage}const result=await page.evaluate(()=>{${js}});` +
1883
+ `console.log(typeof result==='object'?JSON.stringify(result,null,2):String(result));`));
1884
+ return ok(nodeId, r.durationMs, r.stdout, 'evaluate');
1885
+ }
1886
+ if (action === 'click') {
1887
+ if (!selector)
1888
+ return fail('selector required');
1889
+ const sel = selector.replace(/'/g, "\\'");
1890
+ const r = await manager.exec(nodeId, cdpScript(`${getPage}await page.waitForSelector('${sel}',{timeout:${timeout}});` +
1891
+ `await page.click('${sel}');console.log('clicked: ${sel}');` +
1892
+ `await new Promise(r=>setTimeout(r,500));console.log('url='+page.url());`));
1893
+ return ok(nodeId, r.durationMs, r.stdout, 'click');
1894
+ }
1895
+ if (action === 'type') {
1896
+ if (!selector || !value)
1897
+ return fail('selector and value required');
1898
+ const sel = selector.replace(/'/g, "\\'");
1899
+ const val = value.replace(/'/g, "\\'");
1900
+ const r = await manager.exec(nodeId, cdpScript(`${getPage}await page.waitForSelector('${sel}',{timeout:${timeout}});` +
1901
+ `await page.type('${sel}','${val}');console.log('typed ${value.length} chars into ${sel}');`));
1902
+ return ok(nodeId, r.durationMs, r.stdout, 'type');
1903
+ }
1904
+ if (action === 'wait') {
1905
+ if (!selector)
1906
+ return fail('selector required');
1907
+ const sel = selector.replace(/'/g, "\\'");
1908
+ const r = await manager.exec(nodeId, cdpScript(`${getPage}const el=await page.waitForSelector('${sel}',{timeout:${timeout}});` +
1909
+ `const tag=await page.evaluate(e=>e.tagName+' '+e.className,el);` +
1910
+ `console.log('found: ${sel} → '+tag);`));
1911
+ return ok(nodeId, r.durationMs, r.stdout, 'wait');
1912
+ }
1913
+ if (action === 'select') {
1914
+ if (!selector)
1915
+ return fail('selector required');
1916
+ const sel = selector.replace(/'/g, "\\'");
1917
+ const r = await manager.exec(nodeId, cdpScript(`${getPage}const els=await page.$$('${sel}');` +
1918
+ `const results=[];for(const el of els.slice(0,20)){` +
1919
+ `const d=await page.evaluate(e=>({tag:e.tagName,text:e.innerText?.substring(0,200),href:e.href||'',src:e.src||''}),el);` +
1920
+ `results.push(d);}` +
1921
+ `console.log(JSON.stringify(results,null,2));console.log('--- '+els.length+' matches ---');`));
1922
+ return ok(nodeId, r.durationMs, r.stdout, 'select');
1831
1923
  }
1832
- if (action === 'list') {
1833
- const cmd = `ls ${sdir}/*.pid 2>/dev/null | while read f; do id=$(basename "$f" .pid); port=$(cat ${sdir}/$id.port 2>/dev/null); pid=$(cat "$f"); ps -p $pid >/dev/null 2>&1 && s="running" || s="dead"; echo "$id port=$port pid=$pid $s"; done || echo "no sessions"`;
1834
- const r = await manager.exec(nodeId, cmd);
1835
- return ok(nodeId, r.durationMs, r.stdout, 'cdp sessions');
1924
+ if (action === 'network') {
1925
+ const r = await manager.exec(nodeId, cdpScript(`${getPage}const client=await page.createCDPSession();` +
1926
+ `const entries=[];client.on('Network.responseReceived',e=>{entries.push({url:e.response.url.substring(0,100),status:e.response.status,type:e.type});});` +
1927
+ `await client.send('Network.enable');` +
1928
+ `await page.reload({waitUntil:'networkidle2',timeout:${timeout}});` +
1929
+ `await client.send('Network.disable');` +
1930
+ `entries.slice(0,30).forEach(e=>console.log(e.status+' '+e.type.padEnd(12)+' '+e.url));` +
1931
+ `console.log('--- '+entries.length+' requests ---');`));
1932
+ return ok(nodeId, r.durationMs, r.stdout, 'network');
1933
+ }
1934
+ if (action === 'viewport') {
1935
+ const w = width ?? 1920;
1936
+ const h = height ?? 1080;
1937
+ const r = await manager.exec(nodeId, cdpScript(`${getPage}await page.setViewport({width:${w},height:${h}});` +
1938
+ `console.log('viewport set to ${w}x${h}');`));
1939
+ return ok(nodeId, r.durationMs, r.stdout, 'viewport');
1836
1940
  }
1837
1941
  return fail('Invalid action or missing params');
1838
1942
  });