{"id":48832169,"url":"https://github.com/hacker-valley-media/interceptor","last_synced_at":"2026-05-06T05:02:28.274Z","repository":{"id":350092012,"uuid":"1185435385","full_name":"Hacker-Valley-Media/Interceptor","owner":"Hacker-Valley-Media","description":"Agent-driven Chrome extension for full browser control via CLI","archived":false,"fork":false,"pushed_at":"2026-04-26T17:51:26.000Z","size":10607,"stargazers_count":217,"open_issues_count":16,"forks_count":16,"subscribers_count":3,"default_branch":"main","last_synced_at":"2026-05-01T03:07:57.396Z","etag":null,"topics":["ai-agents","ai-tools","browser-automation","browser-extension","browser-use"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Hacker-Valley-Media.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-18T15:24:40.000Z","updated_at":"2026-04-30T05:45:25.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/Hacker-Valley-Media/Interceptor","commit_stats":null,"previous_names":["hacker-valley-media/slop-browser","hacker-valley-media/interceptor"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/Hacker-Valley-Media/Interceptor","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hacker-Valley-Media%2FInterceptor","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hacker-Valley-Media%2FInterceptor/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hacker-Valley-Media%2FInterceptor/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hacker-Valley-Media%2FInterceptor/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Hacker-Valley-Media","download_url":"https://codeload.github.com/Hacker-Valley-Media/Interceptor/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hacker-Valley-Media%2FInterceptor/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32679445,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-06T02:33:58.958Z","status":"ssl_error","status_checked_at":"2026-05-06T02:33:39.611Z","response_time":117,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai-agents","ai-tools","browser-automation","browser-extension","browser-use"],"created_at":"2026-04-14T21:01:02.929Z","updated_at":"2026-05-06T05:02:28.261Z","avatar_url":"https://github.com/Hacker-Valley-Media.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"docs/assets/interceptor-logo-square.png\" alt=\"Interceptor logo\" width=\"180\"\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003eInterceptor\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cstrong\u003eAI agents use your real browser and macOS apps like a human would.\u003c/strong\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  No CDP. No separate automated browser. No starting from zero.\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"#install-in-60-seconds\"\u003e\u003cstrong\u003eInstall\u003c/strong\u003e\u003c/a\u003e\n  ·\n  \u003ca href=\"#quick-start\"\u003e\u003cstrong\u003eQuick Start\u003c/strong\u003e\u003c/a\u003e\n  ·\n  \u003ca href=\"#the-two-surfaces\"\u003e\u003cstrong\u003ePick a Surface\u003c/strong\u003e\u003c/a\u003e\n  ·\n  \u003ca href=\"#surface-1-interceptor-browser\"\u003e\u003cstrong\u003eBrowser\u003c/strong\u003e\u003c/a\u003e\n  ·\n  \u003ca href=\"#surface-2-interceptor-macos\"\u003e\u003cstrong\u003emacOS\u003c/strong\u003e\u003c/a\u003e\n  ·\n  \u003ca href=\"ARCHITECTURE.md\"\u003e\u003cstrong\u003eArchitecture\u003c/strong\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://img.shields.io/github/v/release/Hacker-Valley-Media/Interceptor?label=release\" alt=\"Latest release\"\u003e\n  \u003cimg src=\"https://img.shields.io/github/license/Hacker-Valley-Media/Interceptor\" alt=\"License\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/macOS-supported-black?logo=apple\" alt=\"macOS supported\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Chrome%20%26%20Brave-supported-4285F4?logo=googlechrome\" alt=\"Chrome and Brave supported\"\u003e\n\u003c/p\u003e\n\n![Interceptor running cinematic overlays on top of a live website](docs/assets/interceptor-cook-mode.jpg)\n\nInterceptor gives agents human-style control of the tools you already use — **computer-use** for the native macOS apps on your desktop, **browser-use** for the web apps in your browser. Work happens in both places, so Interceptor meets you in the middle. One CLI, two product surfaces:\n\n- **Interceptor Browser** — runs as a Chrome extension inside your actual browser. Your cookies, sessions, logins, and tabs stay intact. Read pages, click, type, navigate, observe network traffic, automate rich editors, record-and-replay user flows.\n- **Interceptor macOS** — runs as a Swift bridge daemon. Drives native macOS apps the same way: structured accessibility trees, OS-level trusted input, on-device vision/speech/NLP, system-wide event monitoring.\n\nThe agent calls `interceptor` CLI commands, reads the output, and decides what to do next. No MCP required. No API keys required.\n\n\u003e **Warning**\n\u003e Interceptor gives agents real autonomy over your browser and apps. Treat it like an agent, not a toy script runner.\n\n## Why Interceptor Exists\n\nMost browser automation stacks start a separate browser and talk to it through DevTools. That is fine until the site notices, your authenticated context disappears, or your agent has to relearn a workflow from scratch.\n\nInterceptor was built from the opposite premise: use the browser and apps the human is already using, let the agent see what is really happening underneath, and make the workflow reusable after a single live walkthrough.\n\n| Capability | Interceptor | Playwright / Puppeteer / CDP-first tooling |\n|---|---|---|\n| Uses your existing logged-in browser profile | Yes | Usually no |\n| Reads passive fetch/XHR/SSE/WebSocket/sendBeacon/BroadcastChannel using only standard Web APIs | Yes | Partial — typically requires the DevTools protocol |\n| Synthetic clicks/keys via `userActivation` override + `__interceptor_trust` event marker for `isTrusted`-gated handlers | Yes | Often requires DevTools-protocol fallback |\n| Drives canvas-rendered editors (Docs / Slides / Sheets / map viewers / design tools) without OS keyboard | Yes — dispatched events on the canvas | Usually requires `--os` or OS-level CGEvent |\n| Captures native client-side exports (PNG/PDF/SVG) without Save dialog | Yes — `URL.createObjectURL` patch + auto-download suppression | Not built in |\n| Records real human sessions and exports replay plans | Yes | Not built in |\n| Extends the same CLI to native macOS apps | Yes | No |\n| Avoids a separate automated browser by default | Yes | No |\n\n## Demo Preview\n\n[![Preview from the current Interceptor walkthrough](docs/assets/interceptor-demo-preview.jpg)](https://hacker-valley-media.github.io/Interceptor/walkthrough.html)\n\nClick the preview to watch the current walkthrough. It shows the CLI flow and live browser overlays working together in the same session.\n\n## Install In 60 Seconds\n\nDownload a signed installer and double-click. macOS does the rest.\n\nTwo installers ship per release. Pick the one that matches what you actually need:\n\n| Installer | What it installs | macOS TCC consents | When to pick it |\n|---|---|---|---|\n| **`Interceptor-Browser-\u003cversion\u003e.pkg`** *(recommended default)* | CLI + daemon + extension | **None.** No Screen Recording / Accessibility / Apple Events prompts. | You drive web pages. Web scraping, CI flow tests, browser automation. macOS 11+. |\n| **`Interceptor-Full-\u003cversion\u003e.pkg`** | All of the above **plus** `interceptor-bridge.app` + LaunchAgent | Screen Recording, Accessibility, Apple Events (per target app, on first dispatch) | You also drive native macOS apps (Finder, Slack, Notes), need on-screen text capture, dispatch keystrokes outside the browser. macOS 14+. |\n\nBoth download from the same [Releases](https://github.com/Hacker-Valley-Media/Interceptor/releases) page. Start with **Browser** unless you know you need native macOS commands — you can always upgrade to Full later via `interceptor upgrade --full`.\n\n### Install steps\n\n1. Download the matching `.pkg` from Releases.\n2. Double-click it. Walk through the installer (admin password required once).\n3. *(Full pkg only)* Open **System Settings → Privacy \u0026 Security**. The first time you run `interceptor macos *`, macOS will prompt for **Accessibility**, **Screen Recording**, and **Apple Events** access for `interceptor-bridge` — allow each. The Browser pkg never triggers these prompts.\n4. Open a terminal:\n\n```bash\ninterceptor open \"https://example.com\"        # browser surface (works in both pkgs)\ninterceptor macos tree                         # macOS surface (Full pkg only, after Privacy \u0026 Security grants)\n```\n\n### What each pkg lays down\n\n**Browser pkg** (`Interceptor-Browser-\u003cversion\u003e.pkg`):\n\n| Component | Destination |\n|---|---|\n| `interceptor` CLI | `/usr/local/bin/interceptor` (on `PATH`) |\n| Daemon + extension files | `/Library/Application Support/Interceptor/` |\n| Chrome + Brave native messaging hosts | Per-user `~/Library/Application Support/.../NativeMessagingHosts/` |\n\n**Full pkg** (`Interceptor-Full-\u003cversion\u003e.pkg`) adds:\n\n| Component | Destination |\n|---|---|\n| `interceptor-bridge.app` | `/Applications/interceptor-bridge.app` |\n| LaunchAgent (auto-start at login) | `/Library/LaunchAgents/com.interceptor.bridge.plist` |\n\n**Browser extension load** is the one manual step neither installer can do for you, because Chrome and Brave do not allow programmatic extension installation outside of the Web Store. After install:\n\n- **Brave:** open `brave://extensions/`, enable Developer Mode, click **Load unpacked**, select `/Library/Application Support/Interceptor/extension/`.\n- **Chrome:** open `chrome://extensions/`, enable Developer Mode, click **Load unpacked**, select `/Library/Application Support/Interceptor/extension/`.\n\nVerify after install with `interceptor status` — the `mode:` line reports `browser-only` or `full` depending on which pkg you installed.\n\nTo uninstall later: `sudo bash \"/Library/Application Support/Interceptor/uninstall.sh\"`. To downgrade Full → Browser: `sudo bash \"/Library/Application Support/Interceptor/uninstall.sh\" --bridge-only`.\n\nPrefer to build from source instead of using the installer? See [Developer Setup](#developer-setup-build-from-source) below.\n\n## Quick Start\n\nExamples below assume `interceptor` is on your `PATH`. From a repo install, use `./dist/interceptor` if you have not added a symlink.\n\n```bash\n# Browser surface\ninterceptor open \"https://example.com\"        # Open, wait, return tree + text (1 command)\ninterceptor act e1                             # Click element, return updated tree + diff\ninterceptor inspect                            # Tree + text + network log + headers\n\n# macOS surface (bridge installed)\ninterceptor macos open \"Finder\"                # Activate + tree + windows\ninterceptor macos act e5                       # Click + wait + updated tree\n```\n\nOnce installed, the daemon auto-starts on first command. No manual launch needed.\n\n## The Two Surfaces\n\nInterceptor ships one CLI binary with two product surfaces. Pick by what you're driving — a webpage in your browser, or a native app on macOS. Both surfaces share the same daemon, the same wire format, and the same `e1`/`e2`/... ref system.\n\n| Task | Surface | Command shape |\n|---|---|---|\n| Click / type on a webpage | Browser | `interceptor act e5`, `interceptor click`, `interceptor type` |\n| Read a webpage's DOM tree, visible text, or HTML | Browser | `interceptor tree`, `interceptor text`, `interceptor html` |\n| Capture passive `fetch`/XHR/SSE/WebSocket traffic on a page | Browser | `interceptor net log`, `interceptor sse log` |\n| Rewrite outbound page requests in flight | Browser | `interceptor override` |\n| Drive Canva / Google Docs / Google Slides scene graph | Browser | `interceptor scene *` |\n| Record \u0026 replay a human's web flow | Browser | `interceptor monitor *` |\n| Click / type in a native macOS app | macOS | `interceptor macos act`, `interceptor macos click`, `interceptor macos type` |\n| Read a native app's accessibility tree | macOS | `interceptor macos tree`, `interceptor macos find` |\n| Capture a native app window (occluded / off-screen / cross-Space) | macOS | `interceptor macos screenshot --app \"X\"` |\n| List / activate / move / resize native apps \u0026 windows | macOS | `interceptor macos apps`, `interceptor macos app *`, `interceptor macos move`, `interceptor macos resize` |\n| Real-time speech, sound classification, OCR, on-device NLP/LLM | macOS | `interceptor macos listen / sounds / vision / nlp / ai` |\n| Record \u0026 replay a human's native-app flow | macOS | `interceptor macos monitor *` |\n| Drive Apple Events to background apps without raising them | macOS | `interceptor macos intent dispatch` |\n\nIf the task is content **inside** a browser tab, use Browser. If the task is the **shell** the browser runs inside (or any other macOS app), use macOS.\n\nThe deep dives live in the per-surface sections below. Skill packages mirror this split: agent operators load `.agents/skills/interceptor-browser/` for web work and `.agents/skills/interceptor-macos/` for native work.\n\n---\n\n\u003ca name=\"surface-1-interceptor-browser\"\u003e\u003c/a\u003e\n\n# Surface 1: Interceptor Browser\n\n`interceptor` (no prefix) drives a real Chrome / Brave session. Pages, network, scene graph, monitor, screenshots — every browser command lives at the top of the CLI namespace.\n\n## Why Browser\n\n- **Your real browser session**: operate inside the browser you already use, with your cookies, logins, tabs, and context intact.\n- **Passive network visibility**: capture `fetch()`, `XMLHttpRequest`, `EventSource`, `WebSocket`, `sendBeacon`, and `BroadcastChannel` traffic without turning on the debugger or triggering an infobanner.\n- **Synthetic events that sites accept**: a pre-load `userActivation` override + per-event `__interceptor_trust` marker satisfies `isTrusted` and transient-activation checks on the vast majority of sites — `--os` is a fallback, not the default. Synthetic clicks and keystrokes drive rich-editor typing, canvas pan/zoom/click, layer selection in design tools, form fills, and keyboard shortcuts.\n- **Teach-and-replay workflows**: record real clicks, keystrokes, DOM changes, and correlated network calls, then export a replayable `interceptor` plan.\n- **Canvas-rendered editor input**: drive Google Docs / Slides / Sheets cell-precisely (caret positioning, table fills, paragraph styles) via dispatched iframe-window `KeyboardEvent`. See [`use-cases/interaction-skills/canvas-rendered-editor-input.md`](use-cases/interaction-skills/canvas-rendered-editor-input.md).\n- **Canvas camera apps**: pan/zoom WebGL viewers via dispatched `MouseEvent`/`WheelEvent`, anchor lat/lng overlays via Web Mercator projection, restyle the rendered viewport with CSS filters. See [`use-cases/interaction-skills/canvas-camera-overlays.md`](use-cases/interaction-skills/canvas-camera-overlays.md) and [`use-cases/interaction-skills/webgl-camera-control.md`](use-cases/interaction-skills/webgl-camera-control.md).\n- **Native client-side export capture**: intercept any webapp's \"export as PNG/PDF/SVG\" by patching `URL.createObjectURL` and suppressing the auto-download. No clipboard hop, no Save dialog. See [`use-cases/interaction-skills/blob-export-capture.md`](use-cases/interaction-skills/blob-export-capture.md).\n- **Non-CDP architecture**: avoid the debugger-protocol footprint that separate automated browsers depend on.\n\n## Browser Install\n\nThe recommended install path for end users is the signed `.pkg` documented in [Install In 60 Seconds](#install-in-60-seconds) above. The sections below cover building from source if you want to modify Interceptor or run it without the installer.\n\n### Developer Setup (build from source)\n\n#### Prerequisites\n\n- [Bun](https://bun.sh/) runtime\n- [Brave Browser](https://brave.com/download/) (or Chrome — see Chrome path below)\n  - **macOS:** `brew install --cask brave-browser`\n  - **Windows:** `winget install Brave.Brave` (or `choco install brave`)\n  - **Linux:** `sudo snap install brave` or `flatpak install flathub com.brave.Browser`. For native package-manager installs (apt/dnf/zypper/AUR), see the [official Linux guide](https://brave.com/linux/).\n- **Developer mode enabled** in the target Brave / Chrome profile. `--load-extension` is silently dropped by Chromium when Dev mode is off, leaving a dormant install with no error. `scripts/install.sh` preflights this and offers to flip it for you (Brave-closed only) or fail loudly with remediation steps. To enable manually: open `brave://extensions/` (or `chrome://extensions/`) and toggle Developer mode in the top-right.\n- Xcode command line tools (only required if you want to build the macOS bridge)\n\n#### Two install modes\n\n`scripts/install.sh` ships with two named install modes. Pick by what you actually need:\n\n| Mode | What it installs | macOS TCC prompts | When to pick it |\n|---|---|---|---|\n| **`--browser-only`** | CLI + daemon + extension | None | You only want browser control. Smallest footprint, no Screen Recording / Accessibility / Apple Events prompts. Works on macOS and Windows. |\n| **`--full`** | Everything in browser-only **plus** the Swift bridge `.app`, the LaunchAgent, and the macOS subcommands | Screen Recording, Accessibility, Apple Events (per-target-app on first dispatch) | You need `interceptor macos *` (native AX tree, OS-level input, ScreenCaptureKit, Vision/Speech/NLP). macOS only. |\n\nIf you don't pass either flag, the script prompts. The default in the prompt is `--full` on macOS, `--browser-only` everywhere else.\n\n#### Build and Install\n\n```bash\ngit clone https://github.com/Hacker-Valley-Media/Interceptor.git\ncd Interceptor\nbun install\nbash scripts/build.sh\n\n# Pick a mode:\nbash scripts/install.sh --browser-only --brave --profile Default   # browser only, no TCC\nbash scripts/install.sh --full --brave --profile Default           # browser + macOS bridge\nbash scripts/install.sh --brave --profile Default                  # interactive prompt\nbash scripts/install.sh --full --dry-run                           # print steps without running\n```\n\nThis builds the host binaries and extension, writes native messaging manifests pointing at the repo paths, and relaunches Brave with the unpacked extension from `extension/dist/`. Under `--full` it then chains into `scripts/install-bridge.sh` to install the bridge `.app`, register it with LaunchServices, and bootstrap the LaunchAgent. Use `bash scripts/install.sh --brave --profiles` to list profile directories before choosing a non-default profile.\n\nTo upgrade a `--browser-only` install later, run `interceptor upgrade --full` (macOS only).\n\nThe dev install produces and uses these artifacts:\n\n| Artifact | Browser-only | Full |\n|----------|:---:|:---:|\n| `dist/interceptor` (CLI) | ✅ | ✅ |\n| `daemon/interceptor-daemon` | ✅ | ✅ |\n| `extension/dist/` | ✅ | ✅ |\n| `~/Library/Application Support/{Chrome,Brave}/NativeMessagingHosts/com.interceptor.host.json` (symlink) | ✅ | ✅ |\n| `~/.local/share/interceptor/interceptor-bridge.app` | — | ✅ |\n| `~/.local/bin/interceptor-bridge` (symlink) | — | ✅ |\n| `~/Library/LaunchAgents/com.interceptor.bridge.plist` | — | ✅ |\n\n#### Put the CLI on PATH\n\nRun commands from the repo with `./dist/interceptor ...`, or symlink the binary into an existing PATH directory:\n\n```bash\nmkdir -p ~/.local/bin\nln -sf \"$PWD/dist/interceptor\" ~/.local/bin/interceptor\n```\n\n#### Chrome Development Path\n\nGoogle Chrome ignores `--load-extension` in branded desktop builds. `scripts/install.sh --chrome` still writes the native messaging manifest, but load the extension manually:\n\n1. Open Chrome and navigate to `chrome://extensions`\n2. Enable **Developer mode**\n3. Click **Load unpacked**\n4. Select `extension/dist/`\n\n#### Uninstall\n\n```bash\nbash scripts/uninstall.sh                   # Remove everything (both modes)\nbash scripts/uninstall.sh --bridge-only     # Remove only the macOS bridge (downgrade to browser-only)\n```\n\n#### Verify\n\n```bash\n./dist/interceptor status                     # Self-check (local pre-spawn) — does NOT start the daemon\n./dist/interceptor open https://example.com   # Canonical first-run check — spawns the daemon and exercises the full stack\n# Browser-only install reports:\n#   mode: browser-only\n#   daemon: running\n#   ...\n# Full install reports:\n#   mode: full\n#   daemon: running\n#   bridge: running\n#   ...\n```\n\n`daemon: not running` from `status` on a fresh install is normal — `status` is a local pre-spawn check that never starts the daemon. Run `interceptor open \u003curl\u003e` to spawn the daemon and verify the daemon, native messaging bridge, extension, and page together.\n\n#### Troubleshooting\n\n| Symptom | Likely cause | Fix |\n|---|---|---|\n| `interceptor open \u003curl\u003e` returns `error: timeout: no response for 'tab_create' after 15s` | Browser extension is not loaded — most often because **Developer mode is off** in the target profile. Chromium silently drops `--load-extension` when Dev mode is off. | Open `brave://extensions/` or `chrome://extensions/`, toggle Developer mode ON. Quit the browser fully. Re-run `bash scripts/install.sh` (it will preflight Dev mode and re-launch). |\n| `interceptor status --verbose` says `extension: not reachable` | Same as above, or extension is registered but the Interceptor extension was disabled in the browser. | Open the extensions page, confirm Interceptor (ID `hkjbaciefhhgekldhncknbjkofbpenng`) is present and enabled. If missing, click **Load unpacked** and select `extension/dist/`. |\n| `chrome://extensions/` reports the extension as version `0.10.0` while `interceptor --version` reports a higher version | Extension manifest drift fixed in this release — rebuild from current source: `bash scripts/build.sh` then re-run `scripts/install.sh`. | Restart the browser after re-loading the extension so Chromium picks up the bumped manifest. |\n\nIn browser-only mode, running an `interceptor macos *` command returns a structured \"requires full computer-use install\" error within 1 second instead of timing out at 15 seconds.\n\n## Browser Quick Start\n\n```bash\ninterceptor open \"https://example.com\"       # Open, wait, return tree + text (1 command)\ninterceptor act e1                            # Click element, return updated tree + diff\ninterceptor act e2 \"hello world\"              # Type into field, return updated tree\ninterceptor read                              # Re-read current page (tree + text)\ninterceptor inspect                           # Tree + text + network log + headers\n```\n\nThe legacy individual commands (`interceptor tab new`, `interceptor tree`, `interceptor click`, etc.) still work, but the compound commands above are preferred — they reduce round-trips and agent deliberation time.\n\n## Core Concepts\n\n**Element Refs** — `interceptor tree` returns elements with refs like `e1`, `e5`, `e23`. Use these to click, type, hover. Refs survive between commands until the DOM changes.\n\n**Interceptor Group** — Every `interceptor tab new` adds tabs to a cyan \"interceptor\" group. Commands only work on tabs in this group. Your personal tabs are never touched. Use `--any-tab` to override.\n\n**Passive Network** — All `fetch()` and `XMLHttpRequest` traffic on every page is captured automatically. No debugger, no infobanner. Query it with `interceptor net log`.\n\n**Scene Graph** — Profile-driven access to visual editors that don't render to the DOM normally: Canva, Google Docs, Google Slides. Enumerate objects by stable ID, click shapes, read full document text, navigate slide decks, render pages to PNG. `interceptor scene` — no CDP, no vision, no screenshots needed.\n\n**Session Monitor** — Record a user's real interactions (clicks, keystrokes, form changes, DOM mutations, network calls) as a sparse event stream that replays as an `interceptor` script. Sessions are **document-scoped and tab-following**: a single recording survives refreshes and SPA navigation, automatically hands off to child tabs that the monitored page opens (e.g. Canva's \"Create new design\"), and follows you when you manually switch focus between tabs in the interceptor group. Personal tabs outside the cyan interceptor group are never auto-attached. Each session writes its own durable artifact directory (`/tmp/interceptor-monitor-sessions/\u003csid\u003e/`) so exports don't depend on a rolling log, and `monitor stop` is transport-resilient — it cannot throw on a disconnected native port. See [`ARCHITECTURE.md`](ARCHITECTURE.md) for the full monitor model.\n\n**Synthetic input acceptance** — The pre-load `userActivation` override (shipped in `extension/src/inject-net.ts` at `document_start`, MAIN world) is what makes synthetic clicks and keystrokes acceptable to sites that gate on transient activation — historically the reason most automation tools had to fall back to OS-level CGEvent input. With that gate satisfied, dispatched `MouseEvent`/`KeyboardEvent`/`WheelEvent` tagged with `event.__interceptor_trust = true` flow through site handlers as if the user had typed or clicked.\n\n**Use Cases** — The [`use-cases/`](use-cases/) folder is the cookbook for workflows we have already proven in live pages. When a browser workflow is discovered or stabilized, document the exact path there so future agents can reuse it instead of rediscovering it.\n\n## Browser Commands\n\n### Compound Commands (Agent-Optimized)\n\nThese collapse multi-step patterns into single CLI invocations:\n\n```bash\ninterceptor open \"https://example.com\"        # Open URL, wait, return tree + text\ninterceptor open \"https://example.com\" --tree-only   # Skip text\ninterceptor open \"https://example.com\" --text-only   # Skip tree\ninterceptor open \"https://example.com\" --full        # Full text (no 2000-char limit)\ninterceptor open \"https://example.com\" --no-wait     # Don't wait for load\ninterceptor open \"https://example.com\" --reuse        # Reuse the most recent Interceptor-group tab (long automation: avoids tab accumulation)\ninterceptor read                              # Tree + text for current page\ninterceptor read e5                           # Tree + text for element subtree\ninterceptor read --tree-only                  # Just tree\ninterceptor read --include-style              # Inline computed styles (display, color, opacity, etc.) on each element\ninterceptor read --include-frames             # Walk every reachable frame; refs from non-top frames are e\u003cframeId\u003e_\u003cn\u003e\ninterceptor read e2_7 --include-frames        # Read only a framed element subtree\ninterceptor style inject --css \"button{outline:3px solid red}\" # Inject stylesheet into all frames\ninterceptor style inject --css \"body{zoom:1.1}\" --top-only     # Inject only into the top frame\ninterceptor style remove \u003chandle\u003e             # Remove a previously injected stylesheet\ninterceptor act e2_7                          # Act on element in frame 2 (routed automatically)\ninterceptor act e5                            # Click + wait + return updated tree + diff\ninterceptor act e3 \"hello\"                    # Type + wait + return updated tree\ninterceptor act e5 --os                       # FALLBACK ONLY — OS-level CGEvent click; try synthetic first (pre-load userActivation override + __interceptor_trust marker satisfies most isTrusted checks)\ninterceptor act e5 --keys \"Enter\"             # Send keyboard shortcut instead\ninterceptor act e5 --no-read                  # Skip post-action tree read\ninterceptor inspect                           # Tree + text + network log + headers\ninterceptor inspect --net-only                # Just network data\ninterceptor inspect --filter api              # Filter network entries\n```\n\n### Read the Page\n```bash\ninterceptor tree                             # Interactive elements with refs\ninterceptor tree --filter all                # Include headings + landmarks\ninterceptor tree --depth 5                   # Limit tree depth\ninterceptor text                             # All visible text\ninterceptor text e5                          # Text from specific element\ninterceptor html e5                          # HTML of specific element\ninterceptor find \"Submit\"                    # Find elements by name\ninterceptor find \"Submit\" --role button      # Filter by ARIA role\ninterceptor diff                             # What changed since last tree read\ninterceptor state                            # Full DOM tree + scroll + focused element\n```\n\n### Interact\n```bash\ninterceptor click e5                         # Click element (synthetic; default — userActivation override + __interceptor_trust marker handle most isTrusted gates)\ninterceptor click e5 --os                    # FALLBACK — OS-level CGEvent click (only when synthetic input is observed to fail)\ninterceptor click e5 --at 10,20             # Click at offset within element\ninterceptor type e3 \"hello\"                  # Type into element (synthetic; default)\ninterceptor type e3 \"more\" --append          # Append without clearing\ninterceptor type \"textbox:Search\" \"query\"    # Type using semantic selector (role:name)\ninterceptor select e7 \"option-value\"         # Select dropdown option\ninterceptor hover e5                         # Hover over element\ninterceptor keys \"Control+A\"                 # Keyboard shortcut (synthetic; default)\ninterceptor keys \"Enter\" --os               # FALLBACK — OS-level CGEvent key\ninterceptor focus e5                         # Focus element\ninterceptor drag e5 --from 0,0 --to 100,50  # Drag gesture\ninterceptor dblclick e5                      # Double-click\ninterceptor rightclick e5                    # Right-click (context menu)\n```\n\n### Navigate\n```bash\ninterceptor tab new \"https://example.com\"    # New tab in interceptor group\ninterceptor navigate \"https://example.com\"   # Navigate current tab\ninterceptor back                             # History back\ninterceptor forward                          # History forward\ninterceptor scroll down                      # Scroll (up/down/top/bottom)\ninterceptor wait 2000                        # Wait milliseconds\ninterceptor wait-stable                      # Wait for DOM to stop changing\n```\n\n### Tabs\n```bash\ninterceptor tabs                             # List all tabs (* = active)\ninterceptor tab new \"https://example.com\"    # Open new tab\ninterceptor tab switch 12345                 # Switch to tab by ID\ninterceptor tab close                        # Close current tab\ninterceptor tab close 12345                  # Close specific tab\ninterceptor window new \"https://example.com\" # New window\ninterceptor window list                      # List all windows\n```\n\n### Network — Passive Capture (always on)\nEvery page's fetch/XHR traffic is intercepted automatically. Full response bodies included.\n```bash\ninterceptor net log                          # All captured traffic\ninterceptor net log --filter graphql         # Filter by URL substring\ninterceptor net log --filter api.example.com # Any URL pattern\ninterceptor net log --since 1700000000000    # After timestamp\ninterceptor net log --limit 50              # Max entries (default 100)\ninterceptor net clear                        # Flush buffer\ninterceptor net headers                      # Captured request headers (CSRF, auth tokens)\ninterceptor net headers --filter api         # Filter by URL\n```\n\n### Network — Request Overrides (rewrite before send)\nModify outgoing requests at the JavaScript level. No CDP, no debugger.\n```bash\n# Change a query parameter on matching URLs\ninterceptor override \"*eventAttending*\" count=50\n\n# Multiple params\ninterceptor override \"*api/search*\" limit=50 offset=0\n\n# Clear all overrides\ninterceptor override clear\n```\n\n### SSE Stream Capture\n\ninterceptor intercepts Server-Sent Events (text/event-stream) in real-time, chunk by chunk.\n\n```bash\ninterceptor sse streams                                  # List active SSE streams\ninterceptor sse log [--filter \u003cpattern\u003e] [--limit N]     # Show completed SSE streams\ninterceptor sse tail [--filter \u003cpattern\u003e]                # Live tail SSE chunks\n```\n\nWorks automatically on any site using fetch-based SSE or EventSource. No CDP. No setup.\n\n### Scene Graph (Canva, Google Docs, Google Slides)\nRead and manipulate visual editors whose \"canvas\" is actually a DOM / SVG / hidden-iframe structure. No CDP, no debugger, no detection risk. Profile-driven — each editor has its own detection and capability set. Works today on canva.com/design/, docs.google.com/document/, and docs.google.com/presentation/.\n\n```bash\ninterceptor scene profile                     # Detect active editor profile\ninterceptor scene profile --verbose           # Include capabilities list\ninterceptor scene list                        # Enumerate scene objects on current page\ninterceptor scene list --type shape           # Filter by type (image|shape|text|page|slide|embed)\ninterceptor scene click \u003cid\u003e                  # Click a scene object by stable id (Canva: LBxxxxxxxxxxxxxx)\ninterceptor scene dblclick \u003cid\u003e               # Double-click to enter text edit\ninterceptor scene hit \u003cx\u003e,\u003cy\u003e                 # Identify the scene object at viewport coordinates\ninterceptor scene selected                    # Read current selection (host-aware)\ninterceptor scene zoom                        # Read editor zoom factor\n\ninterceptor scene text                        # Read full document text (Google Docs hidden iframe mirror)\ninterceptor scene text --with-html            # Include inline HTML with data-ri offsets\ninterceptor scene insert \"\u003ctext\u003e\"             # Insert text at cursor position (Google Docs)\n\ninterceptor scene slide list                  # List all slides in a Google Slides deck\ninterceptor scene slide current               # Show current slide index and id\ninterceptor scene slide goto \u003cn\u003e              # Navigate to slide \u003cn\u003e (URL fragment method)\ninterceptor scene slide \u003cn\u003e                   # Shorthand for slide goto \u003cn\u003e\ninterceptor scene notes                       # Read speaker notes of current slide\n\ninterceptor scene render \u003cid\u003e                 # Render a scene object as PNG data URL\ninterceptor scene render \u003cid\u003e --save          # Save the PNG to disk\n```\n\n**How it works per editor:**\n\n- **Canva**: every object on the canvas is a `\u003cdiv id=\"LB…\"\u003e` with `style.transform: translate(x, y)` and `style.width/height`. The IDs are stable per-document (they survive page reloads). `scene list` enumerates them; `scene click` computes the viewport center from `getBoundingClientRect()` and dispatches a click through `elementFromPoint`.\n- **Google Docs**: the page is rendered to `\u003ccanvas\u003e` but the full document HTML lives inside a hidden iframe at `.docs-texteventtarget-iframe \u003e [role=textbox]`, complete with `\u003cp\u003e` / `\u003cspan\u003e` elements carrying `data-ri` range-index offsets. `scene text` reads it, `scene insert` writes via `document.execCommand('insertText')` on the iframe's contenteditable.\n- **Google Slides**: each slide is a SVG `\u003cg id=\"filmstrip-slide-N-gd…\"\u003e` with a pre-rendered PNG blob URL on the child `\u003cimage\u003e`. The real slide-navigation page ID lives on the `data-slide-page-id` attribute of the parent `.punch-filmstrip-thumbnail`. `scene slide goto` navigates by setting `window.location.hash = \"#slide=id.\" + pageId`.\n\n### Recording (Session Monitor)\nRecord every real user click, keystroke, form change, navigation, DOM mutation, and the network calls each action triggered — then export the trace as either a pretty timeline or a runnable `interceptor` replay script. No CDP, no infobanner, no detection.\n\nMonitor commands (`start`, `stop`, `pause`, `resume`) auto-resolve the target tab from the interceptor group when `--tab` is omitted. If the content script port is disconnected (e.g. after a service worker restart or long SPA session), the extension automatically re-injects `content.js` and retries — no `interceptor reload` needed.\n\nA session follows your focus across the interceptor tab group. Switch to another in-group tab and the monitor emits `mon_detach (reason: focus_switch_handoff)` + `mon_attach (reason: focus_switch)` and starts capturing there. Child tabs that the monitored page opens itself (via a trusted click) take the dedicated child-tab handoff path (`reason: child_tab`). Tabs outside the cyan interceptor group are never auto-attached. Reloads and SPA history/fragment navigations create new document-scoped attachments on the same tab (`reason: reload` / `history` / `fragment`).\n\n```bash\ninterceptor monitor start                              # Begin recording on the active interceptor tab\ninterceptor monitor start --instruction \"...\"          # Annotate with task intent\ninterceptor monitor stop                               # End recording, print summary\ninterceptor monitor status                             # Show active session(s)\ninterceptor monitor pause                              # Stop emitting events without ending\ninterceptor monitor resume                             # Resume a paused session\ninterceptor monitor list                               # All sessions in the event log\ninterceptor monitor tail                               # Live tail current session (pretty)\ninterceptor monitor tail --raw                         # Live tail (raw JSONL)\ninterceptor monitor export \u003csessionId\u003e                 # Aligned text rendering\ninterceptor monitor export \u003csessionId\u003e --json          # Raw JSONL for that session\ninterceptor monitor export \u003csessionId\u003e --plan          # Emit interceptor ... replay script\ninterceptor monitor export \u003csessionId\u003e --with-bodies   # Include persisted net-body context when available\n```\n\nEach event line is sparse JSON (short keys: `t`, `s`, `k`, `sid`, `ref`, `r`, `n`, `cause`) so an agent can read a 30-minute session in a few KB. User actions get a session-monotonic `seq`; mutations and network calls fired within 500ms of an action carry `cause: \u003caction_seq\u003e`. Real user events have `tr: true`; interceptor's own synthetic clicks have `tr: false`. The replay-plan generator automatically includes synthetic clicks when no real user events exist in the session (common when an agent drove the browser). Use `--include-synthetic` to force inclusion regardless.\n\nThe rolling live event stream lives in `/tmp/interceptor-events.jsonl`. Export prefers per-session artifacts under `/tmp/interceptor-monitor-sessions/\u003csessionId\u003e/` (one directory per session containing `events.jsonl`, `session.json`, and `net.jsonl`) and falls back to the rolling event log for legacy sessions. `--with-bodies` uses persisted correlated net-body artifacts when present (body previews are capped at 64 KiB, redact `Authorization` / `Cookie` / token-shaped strings, and only persist JSON / text content types) and otherwise leaves `interceptor net log` hints in the replay output.\n\nThe replay script uses semantic selectors that survive DOM churn, and for multi-tab sessions it emits explicit tab-handoff lines:\n```\ninterceptor tab new \"https://example.com/\"\ninterceptor wait-stable\ninterceptor click \"button:Search\"\ninterceptor type \"textbox:Query\" \"bun docs\"\ninterceptor keys \"Enter\"\n# focus-switch to tab 1729165117 (https://www.youtube.com/)\ninterceptor tab switch 1729165117\ninterceptor wait-stable\ninterceptor click \"button:Play\"\n```\n\n### Screenshots\n\nDefault capture is **DOM render** — no `chrome.tabs.captureVisibleTab` in the hot path. Works regardless of window focus, macOS Space, or service-worker activation context. A vendored `html-to-image` bundle is injected on demand and paired with a per-tab CORS-clearance DNR rule. Use `--pixel` for compositor-accurate capture (requires Chrome focused).\n\n```bash\ninterceptor screenshot                       # Default DOM-render full-page (works without focus)\ninterceptor screenshot --save                # Save to disk; result has filePath, no dataUrl\ninterceptor screenshot --selector \"h1\"       # Capture matching element (off-screen supported)\ninterceptor screenshot --element 5           # Capture by ref/index from a recent read\ninterceptor screenshot --region X,Y,W,H      # Render full + crop to region in content script\ninterceptor screenshot --scale 2             # Override pixel ratio\ninterceptor screenshot --pixel               # Pixel-true compositor capture (legacy captureVisibleTab)\ninterceptor screenshot --pixel --full        # Pixel-true full-page (scroll + in-SW stitch)\ninterceptor screenshot --format webp         # png (default), jpeg, or webp\ninterceptor screenshot --quality 80          # Encode quality 0-100 (defaults: png 92, jpeg 92, webp 85)\ninterceptor screenshot --target-max-long-edge 1568   # Auto-resize at capture (clamps long edge)\n```\n\n`screenshot` invocations are auto-routed through the WebSocket transport because base64 dataUrl responses larger than ~50KB are unreliable over the native-messaging port on Brave/Chromium. Override with `--no-ws` if needed.\n\n**Agent recipe for any consumer:** `interceptor screenshot --save --format webp --target-max-long-edge 1568 --quality 85` produces a ~50–100 KB WebP on disk and a path-only response — fits Anthropic's 1568 px cap exactly, eats zero context window, no auto-resize destruction.\n\n### Data\n```bash\ninterceptor cookies example.com              # List cookies for domain\ninterceptor storage                          # Read localStorage\ninterceptor storage set key value            # Write localStorage\ninterceptor eval \"document.title\"            # Run JS in page\ninterceptor history \"search term\"            # Search browser history\ninterceptor bookmarks \"query\"                # Search bookmarks\n```\n\n### Batch \u0026 Raw\n```bash\ninterceptor batch '[{\"type\":\"click\",\"ref\":\"e5\"},{\"type\":\"wait\",\"ms\":500},{\"type\":\"extract_text\"}]'\ninterceptor batch '...' --stop-on-error      # Halt on first failure\ninterceptor raw '{\"type\":\"any_action\",\"key\":\"value\"}'  # Send any raw action\n```\n\n### Meta\n```bash\ninterceptor status                           # Daemon status (local check, no connection needed)\ninterceptor help                             # Full CLI help\ninterceptor reload                           # Reload extension\ninterceptor capabilities                     # Check available input layers\n```\n\n## Browser Flags\n\n| Flag | Effect |\n|------|--------|\n| `--json` | JSON output instead of plain text |\n| `--tab \u003cid\u003e` | Target specific tab by ID |\n| `--any-tab` | Operate outside the interceptor group |\n| `--os` | FALLBACK: use OS-level CGEvent (macOS) when synthetic input is observed to fail. Default to synthetic — the pre-load `userActivation` override + `__interceptor_trust` event marker satisfy most `isTrusted` checks. |\n| `--frame \u003cid\u003e` | Target specific iframe |\n| `--changes` | Include DOM diff in response |\n\n## Browser Recipes\n\n### Extract data from an SPA\n```bash\ninterceptor tab new \"https://app.example.com\"\nsleep 3\ninterceptor tree                              # Find the data\ninterceptor net log --filter api              # See what API calls the page made\ninterceptor net headers --filter api          # Grab auth tokens from captured headers\ninterceptor text                              # Read visible content\ninterceptor tab close\n```\n\n### Fill and submit a form\n```bash\ninterceptor tab new \"https://example.com/form\"\nsleep 2\ninterceptor tree                              # Find form fields\ninterceptor type e3 \"John Doe\"               # Fill name\ninterceptor type e5 \"john@example.com\"       # Fill email\ninterceptor select e7 \"option2\"              # Pick dropdown\ninterceptor click e10                         # Submit\nsleep 2\ninterceptor text                              # Read result\n```\n\n### Monitor network traffic from any page\n```bash\ninterceptor tab new \"https://app.example.com\"\nsleep 3\ninterceptor net log --filter api              # See all API calls with full response bodies\ninterceptor net headers --filter api          # See request headers (auth, CSRF, cookies)\n# Navigate around — capture keeps running\ninterceptor click e5\nsleep 2\ninterceptor net log --filter api --limit 5    # See latest calls\n```\n\n### Override API requests (change page size, params)\n```bash\ninterceptor tab new \"https://app.example.com\"\nsleep 2\n# Push override: change page_size to 100 on any matching URL\ninterceptor override \"*api/list*\" page_size=100\n# Now interact — when the page fetches, the URL is rewritten before it fires\ninterceptor click e5                          # Trigger a load\nsleep 2\ninterceptor net log --filter api/list         # See the rewritten request + response\ninterceptor override clear                    # Clean up\n```\n\n### Interact with sites that check isTrusted\n\nTry synthetic first. The pre-load `userActivation` override (`extension/src/inject-net.ts` at `document_start`, MAIN world) makes `navigator.userActivation.isActive` always read `true`, satisfying the transient-activation gate that most \"isTrusted-checking\" sites actually rely on. For sites that ALSO read `event.isTrusted` via the prototype, dispatched events tagged with `event.__interceptor_trust = true` (via `interceptor eval --main`) pass through. Only fall back to `--os` when synthetic input is observed to fail.\n\n```bash\n# Default — synthetic, no --os needed. Works on most sites with rich-editor or canvas-rendered surfaces.\ninterceptor open \"https://strict-site.com\"\ninterceptor act e5\ninterceptor act e3 \"text\"\n\n# Fallback — OS-level CGEvent. Use when synthetic input is observed to fail (banking/payment gateways, IME composition, sites that cache the per-instance own-property isTrusted at boot).\ninterceptor click e5 --os\ninterceptor type e3 \"text\" --os\n```\n\n### Read a Google Doc programmatically\n```bash\ninterceptor tab new \"https://docs.google.com/document/d/\u003cid\u003e/edit\"\nsleep 5\ninterceptor scene profile                     # -\u003e google-docs\ninterceptor scene text                        # Full document text from hidden iframe mirror\ninterceptor scene text --with-html            # Full HTML model with data-ri offsets\ninterceptor scene insert \"new paragraph at cursor\"\ninterceptor keys \"Meta+z\"                     # Undo the insert\n```\n\n### Manipulate a Canva design\n```bash\ninterceptor tab new \"https://www.canva.com/design/\u003cid\u003e/edit\"\nsleep 6\ninterceptor scene profile                     # -\u003e canva\ninterceptor scene list --type shape           # Every LB layer that's a shape\ninterceptor scene zoom                        # Current editor zoom factor\ninterceptor scene hit 537,516                 # What's at this viewport coord?\ninterceptor scene click LBKfjtRwQHt7D0Cf      # Click a layer by stable id\n```\n\n### Navigate and render Google Slides\n```bash\ninterceptor tab new \"https://docs.google.com/presentation/d/\u003cid\u003e/edit\"\nsleep 6\ninterceptor scene slide list                  # All slides with stable IDs + blob URLs\ninterceptor scene slide goto 5                # Navigate via URL fragment\ninterceptor scene slide current               # Verify index 5 is now active\ninterceptor scene notes                       # Read speaker notes for current slide\ninterceptor scene render filmstrip-slide-3-gd02e148143_0_6 --save  # PNG of slide 3\n```\n\n### Pan/zoom a WebGL canvas viewer + anchor lat/lng overlays\n```bash\ninterceptor open \"https://example-webgl-map.com/\"\n# Dispatch wheel events on the canvas to pan/zoom, project lat/lng to viewport pixels\n# for custom overlays. Full recipe in\n# use-cases/interaction-skills/webgl-camera-control.md.\n```\n\n### Bulk-capture every native client-side export in a webapp\n```bash\ninterceptor open \"https://example.com/editor\"\n# Patch URL.createObjectURL to capture blobs, patch HTMLAnchorElement.prototype.click\n# to swallow auto-downloads. Then loop: select each frame/slide/page, trigger the\n# webapp's own Export button, fetch each blob's bytes.\ninterceptor eval --main \"$(cat install-patches.js)\"\ninterceptor eval --main \"$(cat bulk-export-loop.js)\"     # fire-and-forget\n# Poll until done, then chunk-extract each PNG's base64 from window.__interceptor_pngs.\n# Full recipe in use-cases/interaction-skills/blob-export-capture.md.\n```\n\n### Record a user session and replay it\n```bash\ninterceptor monitor start --instruction \"search bun docs, open first result, copy paragraph\"\n# ... user interacts for 60 seconds ...\ninterceptor monitor stop                      # Prints session summary\ninterceptor monitor list                      # Shows all historical sessions\ninterceptor monitor export \u003csessionId\u003e        # Aligned text rendering\ninterceptor monitor export \u003csessionId\u003e --plan # Replayable script of interceptor commands\n```\n\n### Inspect and read canvas-heavy pages\n```bash\ninterceptor canvas list                       # Discover \u003ccanvas\u003e elements (HTMLCanvasElement)\ninterceptor canvas status                     # Canvas list + host/model/observer signals\ninterceptor canvas log                        # Captured drawing operations across canvases\ninterceptor canvas log 0 --kind fillText      # Drawing operations for canvas index 0\ninterceptor canvas objects 0 --kind text      # Derived objects for canvas index 0\ninterceptor canvas model                      # Host-state and app-model signals\ninterceptor canvas routes --filter save       # First-party canvas-related network routes\ninterceptor canvas read 0 --format png        # Read canvas as data URL\ninterceptor canvas diff url1.png url2.png     # Pixel diff between images\n```\n\nCanvas indexes come from the DOM canvas order reported by `canvas list`. The observer-backed `log` and `objects` commands resolve that DOM index to the internal observer `canvasId`, so `canvas log 0` and `canvas log 1` stay separated on multi-canvas pages. `canvas ocr` exists as an experimental command, but use `canvas read` when you need a stable image export.\n\n---\n\n\u003ca name=\"surface-2-interceptor-macos\"\u003e\u003c/a\u003e\n\u003ca name=\"native-computer-use\"\u003e\u003c/a\u003e\n\u003ca name=\"macos-native-control\"\u003e\u003c/a\u003e\n\n# Surface 2: Interceptor macOS\n\n`interceptor macos *` extends the same agent-first pattern to native macOS applications. No screenshots, no vision models. Structured AX trees, trusted input, real-time audio intelligence, and system-wide event monitoring. Same CLI binary, same wire format, same ref convention (`e1`, `e2`, ...) — same agent loop.\n\n## Why macOS\n\nBeyond the browser, Interceptor drives the rest of macOS through a Swift bridge daemon. The surface includes 28 domains across **Accessibility, Apps, Files, Filesystem (`fs`), Networking (`url_fetch`), Log query, Apple Events (`app_intent`), Containers, Capture, Stream, Display, Audio, Speech, Sound, Vision, NLP, Intelligence, Notifications, Clipboard, Trust, Monitor, Text, Compound, and Overlays** — plus a hardened `.app` bundle with TCC tracking, the panic hotkey `Ctrl+Opt+Cmd+Escape` for overlays, and the headline visual modes (particles, Godzilla-vs-Kong SpriteKit scene, dynamic scene-script, HTML HUD).\n\n**Background-first** is the default contract: when the user names a specific app (\"screenshot of Brave\", \"scroll Signal\", \"open a tab in Brave\"), Interceptor stays invisible to the user. Captures use `CGSHWCaptureWindowList` (works on occluded / minimized / cross-Space windows). AX reads use `AXManualAccessibility` to wake Electron apps without focus. Apple Events deliver to a target bundle id without `activate`. Scroll routes via `CGEvent.postToPid` to a specific PID. Your focused window stays where it was — unless you ask otherwise. See [`AGENTS.md`](AGENTS.md) for the full background-first table and the browser-vs-bridge decision matrix.\n\n### How It Works\n\nWhen the native bridge is installed, the daemon routes `macos_` commands to the bridge over Unix socket. Same CLI binary, same wire format, same ref convention (`e1`, `e2`, ...). Browser automation through Brave works through the CLI install path without the native bridge.\n\n```\nCLI ──unix──▸ Daemon ──native-msg──▸ Chrome Extension (web commands)\n                    ──unix──▸ Native Bridge (macOS commands)\n```\n\n\u003ca name=\"surface-2-install\"\u003e\u003c/a\u003e\n\n## macOS Install\n\nThe Brave CLI browser install does not require the macOS bridge. Use the standalone `interceptor-bridge` path below when developing or debugging native macOS automation.\n\n### Build and Install\n\n```bash\nbash scripts/build-bridge.sh\nbash scripts/install-bridge.sh\n```\n\nRequires full Xcode (not just Command Line Tools) — the bridge links Apple frameworks including ScreenCaptureKit, Speech, Vision, NaturalLanguage, **WebKit (overlays), SpriteKit (overlays), and Carbon (Apple Events)**.\n\n### Permissions\n\nAfter installing, check and grant required permissions:\n\n```bash\ninterceptor macos trust                          # Permission snapshot + System Settings paths\ninterceptor macos trust --no-prompt              # Forced read-only (overrides every other prompt flag)\ninterceptor macos trust --prompt                 # Fire all three TCC prompts (non-blocking)\ninterceptor macos trust --walkthrough            # Prompt + open the next missing Privacy pane\ninterceptor macos trust --accessibility-prompt   # Single-permission prompt\ninterceptor macos trust --screen-prompt          # Single-permission prompt\ninterceptor macos trust --microphone-prompt      # Single-permission prompt\n```\n\nEvery field — top-level `accessibility` / `screenRecording` / `microphone` plus `permissions[].status` — is a string drawn from Apple's `AVAuthorizationStatus` vocabulary: `granted | denied | not_determined | restricted`. AX and Screen Recording entries carry a `limitation` field because Apple's `AXIsProcessTrusted` / `CGPreflightScreenCaptureAccess` return `Bool` only and can't surface `not_determined` / `restricted`. The microphone prompt is non-blocking — when the user hasn't responded yet the response carries `microphone: \"not_determined\"` plus `pending_user_action: [\"Microphone\"]`; re-poll `interceptor macos trust` to observe the resolved state. The first time the mic prompt fires, the bridge briefly upgrades its activation policy to `.regular` (you'll see a Dock icon for ~5 s) so macOS surfaces a real modal dialog instead of a transient banner — same canonical pattern Hammerspoon and Bartender use for LSUIElement utilities.\n\nTreat `interceptor macos trust` as a permission snapshot. Use `interceptor status` to confirm the daemon, helper, and bridge socket are actually alive before debugging native runtime failures.\n\n| Permission | Required | What It Enables |\n|-----------|----------|-----------------|\n| Accessibility | Yes | UI element inspection, clicking, typing, window management |\n| Screen Recording | No | Screenshots, screen capture, vision analysis |\n| Microphone | No | Speech recognition, voice activity detection |\n| Input Monitoring | No | `interceptor macos monitor` global key/click capture |\n\nGrant permissions in: System Settings → Privacy \u0026 Security → [Permission] → Interceptor\n\n## macOS Quick Start\n\n```bash\ninterceptor macos open \"Finder\"                  # Tree + windows (background-first; pass --activate to foreground)\ninterceptor macos open \"Finder\" --activate       # Explicit foregrounding opt-in\ninterceptor macos read                           # Tree + frontmost app info\ninterceptor macos act e5                         # Click + wait + updated tree (AX press; no focus change)\ninterceptor macos act e3 \"hello\"                 # Type + wait + updated tree (AX value-set; no focus change)\ninterceptor macos inspect                        # Tree + apps + frontmost info\n```\n\nBackground-first contract: only `interceptor macos app activate \u003capp\u003e` and `interceptor macos open \u003capp\u003e --activate` are allowed to move the user's frontmost window. Every other macOS verb leaves focus alone.\n\n## macOS Commands\n\nThe Swift bridge exposes 28 domains. The five **daily-driver domains** are surfaced in full below; the remaining **specialized domains** are listed in tiered form with deep links to `docs/native/`. *Tiering means presentation, not deprecation — every domain the bridge supports today is still supported.*\n\n### Daily-Driver Domain 1: Accessibility (AX)\n\nRefs (`e1`, `e2`, ...) work the same as browser refs. AXObserver auto-invalidates when the tree changes.\n\n```bash\ninterceptor macos tree                           # AX tree for frontmost app\ninterceptor macos tree --app \"Finder\"            # Specific app\ninterceptor macos tree --filter interactive      # Only actionable elements (default)\ninterceptor macos tree --depth 5                 # Limit depth\ninterceptor macos find \"Save\" --role button      # Find elements by name/role\ninterceptor macos inspect e5                     # All attributes + actions for ref\ninterceptor macos value e5                       # Read element value\ninterceptor macos value e5 \"new text\"            # Set element value\ninterceptor macos action e5 press                # Perform AX action\ninterceptor macos focused                        # Current focused element\ninterceptor macos windows                        # All windows with frames\ninterceptor macos windows --app \"Finder\"         # Specific app\ninterceptor macos move e1 --x 0 --y 25           # Move window\ninterceptor macos resize e1 --width 672 --height 983  # Resize window\n```\n\n### Daily-Driver Domain 2: Input (AX-first, PID-routed CGEvent fallback)\n\n```bash\ninterceptor macos click e5                       # AX press on the ref — no focus change\ninterceptor macos click 500,300 --app \"TextEdit\" # CGEvent.postToPid → no focus change\ninterceptor macos click 500,300                  # CGEvent on system HID tap → follows frontmost (legacy)\ninterceptor macos click e5 --double              # Double-click\ninterceptor macos click e5 --right               # Right-click\ninterceptor macos type e5 \"hello world\"          # AX value-set on text-bearing role; no focus change\ninterceptor macos type \"hello\" --app \"TextEdit\"  # PID-routed keys; no focus change\ninterceptor macos type \"hello world\"             # Type at current focus (legacy fallback)\ninterceptor macos keys \"Meta+C\" --app \"TextEdit\" # PID-routed combo; no focus change\ninterceptor macos keys \"Meta+C\"                  # System HID tap (legacy)\ninterceptor macos scroll down --app \"Signal\"     # PID-routed scroll on backgrounded app\ninterceptor macos drag e5 e8                     # Drag between refs\ninterceptor macos drag 100,100 200,200 --app X   # PID-routed coordinate drag\n```\n\nRouting rule: when a ref is provided, input goes through AX (`AXUIElementPerformAction(kAXPressAction)` for clicks; `AXUIElementSetAttributeValue(kAXValueAttribute, ...)` for text-bearing roles). When `--app` or `--pid` is provided, synthesized events post via `CGEvent.postToPid` — the target does NOT need to be frontmost. With neither, input falls back to the system HID tap and follows the user's current frontmost app.\n\nEach input verb returns a routing tag in its success message: `\"ax-pressed ref\"`, `\"ax-set value (N chars)\"`, `\"clicked … → pid=NNNN\"`, or `\"clicked … → frontmost\"`. If you see `→ frontmost` when you expected per-PID delivery, the target wasn't resolvable — pass `--app` or `--pid`.\n\n### Daily-Driver Domain 3: Capture (ScreenCaptureKit)\n\n```bash\ninterceptor macos screenshot                     # Frontmost window\ninterceptor macos screenshot --app \"Finder\"      # Specific app (works occluded / minimized / cross-Space)\ninterceptor macos screenshot --save              # Save to disk; payload key is `filePath` (not `path`)\ninterceptor macos capture start                  # Continuous 30fps capture\ninterceptor macos capture frame                  # Get latest frame; blocks ≤3000ms for first frame\ninterceptor macos capture frame --timeout-ms 5000  # Override the wait window for first-frame delivery\ninterceptor macos capture stop                   # Stop\n```\n\nThe `--save` response contains `{ \"filePath\": \"...\", \"format\": ..., \"bytes\": ..., \"width\": ..., \"height\": ... }`. Read `filePath` for the path on disk.\n\n### Daily-Driver Domain 4: Monitor (Teach and Replay)\n\nSame pattern as the browser monitor. Record what the user does across native apps, export a replayable script.\n\n```bash\ninterceptor macos monitor start                  # Record all user interactions\ninterceptor macos monitor start --instruction \"Show me how you file expenses\"\ninterceptor macos monitor stop                   # Stop + summary\ninterceptor macos monitor tail                   # Live event stream\ninterceptor macos monitor export \u003csid\u003e           # Pretty timeline with timestamps\ninterceptor macos monitor export \u003csid\u003e --plan    # Replayable interceptor macos script\n```\n\nCaptures clicks, keystrokes, scrolls, app switches with timestamps and AX element annotations. Same sparse JSON format as browser monitor.\n\n### Daily-Driver Domain 5: Clipboard\n\n```bash\ninterceptor macos clipboard read                 # Read clipboard\ninterceptor macos clipboard write \"...\"          # Write to clipboard\ninterceptor macos clipboard tail                 # Monitor clipboard changes\n```\n\n### Specialized Domains\n\n*Every domain below is fully supported. Grouped here for discoverability — see `docs/native/` for the deep dive on each.*\n\n#### Apps \u0026 Windows\n\n```bash\ninterceptor macos apps                           # List running apps (name, pid, bundle ID)\ninterceptor macos app activate \"Finder\"          # Bring to front\ninterceptor macos app hide \"Finder\"              # Hide\ninterceptor macos app quit \"Finder\"              # Quit\ninterceptor macos app launch \"com.apple.finder\"  # Launch by bundle ID\ninterceptor macos frontmost                      # Current frontmost app\n```\n\n#### Menu Traversal\n\n```bash\ninterceptor macos menu                           # Frontmost app's full menu tree\ninterceptor macos menu --app \"Finder\"            # Specific app\ninterceptor macos menu \"File\" \"New Folder\"       # Invoke menu item by path\n```\n\n#### Audio (system + microphone)\n\n```bash\ninterceptor macos audio output start             # Capture system audio\ninterceptor macos audio input start              # Capture microphone\ninterceptor macos audio input start --save       # Capture mic AND write CAF file to /tmp/interceptor-audio-input-\u003cunix-ts\u003e.caf\ninterceptor macos audio input stop               # Stop; response carries `filePath` when --save was set\n```\n\n#### Speech \u0026 Voice Activity\n\n```bash\ninterceptor macos listen start                   # Start speech recognition\ninterceptor macos listen stop                    # Stop + return transcript\ninterceptor macos listen transcript              # Current transcript\ninterceptor macos listen tail                    # Poll-friendly transcript stream\ninterceptor macos vad start                      # Voice activity detection\ninterceptor macos vad status                     # Is someone speaking? + RMS level\n```\n\n#### Sound Classification\n\n```bash\ninterceptor macos sounds start                   # Sound classification (300+ types)\ninterceptor macos sounds status                  # Current detected sounds\n```\n\n#### Vision (on-device)\n\n```bash\ninterceptor macos vision faces                   # Detect faces in frontmost window\ninterceptor macos vision text                    # OCR\ninterceptor macos vision hands                   # Hand pose detection\ninterceptor macos vision bodies                  # Body pose detection\n```\n\n#### NLP (on-device)\n\n```bash\ninterceptor macos nlp entities \"Ron in Austin\"   # Named entity recognition\ninterceptor macos nlp sentiment \"great product\"  # Sentiment analysis\ninterceptor macos nlp language \"bonjour\"         # Language detection\n```\n\n#### Apple Intelligence (on-device LLM, macOS 26+)\n\n```bash\ninterceptor macos ai prompt \"Summarize this\"     # On-device LLM (macOS 26+, Apple Intelligence)\n```\n\n#### Notifications\n\n```bash\ninterceptor macos notifications tail             # Live notification stream\n```\n\n#### Trust \u0026 Permissions\n\n```bash\ninterceptor macos trust                          # Read-only snapshot (status strings + System Settings paths)\ninterceptor macos trust --no-prompt              # Forced read-only — overrides every other prompt flag\ninterceptor macos trust --prompt                 # Fire all three TCC prompts (non-blocking)\ninterceptor macos trust --walkthrough            # Prompt + auto-open next missing Privacy pane\ninterceptor macos trust --accessibility-prompt   # Single-permission prompt\ninterceptor macos trust --screen-prompt          # Single-permission prompt\ninterceptor macos trust --microphone-prompt      # Single-permission prompt\n```\n\nEvery status is a string from Apple's `AVAuthorizationStatus` vocabulary (`granted | denied | not_determined | restricted`). Mic prompt is non-blocking — re-poll to observe the user's response. See [Permissions](#permissions) above for the response-shape details.\n\n#### Files \u0026 Filesystem\n\n```bash\ninterceptor macos files watch ~/Desktop          # Watch directory for changes\ninterceptor macos fs read /path/to/file          # Native FileManager read\ninterceptor macos fs write /path/to/file \"...\"   # Native FileManager write\ninterceptor macos fs search \"query\"              # Spotlight (NSMetadataQuery)\n```\n\nSee [`docs/native/fs.md`](docs/native/fs.md) for the `fs` domain detail.\n\n#### URL Fetch (`url`)\n\n```bash\ninterceptor macos url get \"https://api.example.com/data\"\ninterceptor macos url post \"https://api.example.com/x\" --body '{\"k\":\"v\"}'\n```\n\nURLSession + cookies + ETag + bodyRef sidecar for \u003e64 KB responses. See [`docs/native/url-fetch.md`](docs/native/url-fetch.md).\n\n#### Log Query\n\n```bash\ninterceptor macos log query --subsystem com.apple.network --level error --last 30s\n```\n\n`OSLogStore` query with subsystem/category/level filters. See [`docs/native/log-query.md`](docs/native/log-query.md).\n\n#### Apple Events (Intent)\n\n```bash\ninterceptor macos intent dispatch --bundle com.brave.Browser --script 'open location \"https://example.com\"'\ninterceptor macos intent warmup --bundle com.brave.Browser\n```\n\nApple Events → cross-app verb dispatch via `NSAppleScript`. TCC consent per (bridge, target_app) pair. See [`docs/native/app-intent.md`](docs/native/app-intent.md).\n\n#### Container Runtime (macOS 26+)\n\n```bash\ninterceptor macos container run \u003cimage\u003e\n```\n\nRun an OCI image in Apple's `container` runtime. See [`docs/native/container-run.md`](docs/native/container-run.md).\n\n#### Display \u0026 Streaming\n\n```bash\ninterceptor macos display list                   # All displays (physical + virtual)\ninterceptor macos display create 1920x1080       # Create virtual display\ninterceptor macos display remove \u003cid\u003e            # Remove virtual display\ninterceptor macos stream start --app \"Finder\"    # Start screen stream\ninterceptor macos stream frame                   # Latest frame\ninterceptor macos stream fps                     # Current FPS\ninterceptor macos stream stop                    # Stop\n```\n\n#### Text (selection / visible / full)\n\n```bash\ninterceptor macos text                           # Read selection / visible / full text from frontmost app\n```\n\n#### Overlays\n\nParticles / titans / scene-script / HTML overlays. Panic hotkey `Ctrl+Opt+Cmd+Escape` closes every active overlay regardless of session. See [`docs/native/overlays.md`](docs/native/overlays.md) and [`docs/native/scene-script-cookbook.md`](docs/native/scene-script-cookbook.md).\n\n## macOS Recipes\n\n### Resize browser and open a URL\n```bash\ninterceptor macos app activate \"Brave Browser\"\ninterceptor macos windows --app \"Brave Browser\"\ninterceptor macos move e1 --x 0 --y 25\ninterceptor macos resize e1 --width 672 --height 983\ninterceptor macos keys \"Meta+t\"\ninterceptor macos keys \"Meta+l\"\ninterceptor macos type \"https://example.com\"\ninterceptor macos keys \"Return\"\n```\n\n### Watch a user, learn the workflow, replay it\n```bash\ninterceptor macos monitor start --instruction \"file an expense\"\n# ... user works in native apps ...\ninterceptor macos monitor stop                   # Summary: 230 events, 2 minutes\ninterceptor macos monitor export \u003csid\u003e           # Pretty timeline with timestamps\ninterceptor macos monitor export \u003csid\u003e --plan    # Replayable interceptor macos commands\n```\n\n### Drive a backgrounded app without raising it\n```bash\ninterceptor macos screenshot --app \"Signal\"      # Capture occluded window via CGSHWCaptureWindowList\ninterceptor macos tree --app \"Signal\"            # AX read auto-wakes via AXManualAccessibility\ninterceptor macos scroll down --app \"Signal\" --times 3   # Routes via CGEvent.postToPid\ninterceptor macos intent dispatch --bundle org.whispersystems.signal-desktop --script '...'\n```\n\nEnd-to-end \"type into a backgrounded TextEdit while another app stays frontmost\":\n\n```bash\ninterceptor macos open \"TextEdit\"                            # background-first; reads AX state\nREF=$(interceptor macos focused --app \"TextEdit\" --json | jq -r '.ref')\ninterceptor macos type \"$REF\" \"hello, background world\"      # → \"ax-set value (...)\"\ninterceptor macos value \"$REF\"                               # confirms the text landed\ninterceptor macos frontmost                                  # whatever was frontmost is unchanged\n```\n\n## macOS Safety\n\n- **Panic hotkey** — `Ctrl+Opt+Cmd+Escape` closes every active overlay regardless of owning session. Bridge-side handler — no agent involvement required.\n- **Sensitive frontmost-app gate** — Before `mac_type` / `mac_keys` / `mac_click(coords)` / `mac_drag` hit the bridge, identity gating rejects the call if the bundle ID is on the denylist (Keychain, 1Password, Dashlane, LastPass, Bitwarden, System Settings, Chase, Bank of America, Wells Fargo).\n- **Permission tiers** — Allow (observational) / Ask (interactive: click, type, keys, drag, app quit/hide, clipboard write) / Deny (none by default — tune per environment).\n- **TCC tracking** — Bridge ships as `.app` bundle so macOS TCC tracks grants correctly across reinstalls.\n\nSee [`docs/native/safety.md`](docs/native/safety.md) for the full safety contract.\n\n---\n\n## Future: Interceptor Windows\n\nThe CLI grammar reserves the `interceptor windows` namespace for a future Windows surface (UIA accessibility tree, Win32 input, ETW traces, native window management). It is **not built**, has no timeline, and ships no commands today. When it lands it will be a peer of the Browser and macOS surfaces — same daemon model, same ref convention, same agent loop, separate skill package at `.agents/skills/interceptor-windows/`. Documenting the reservation now keeps anyone from accidentally claiming the namespace for something else.\n\n---\n\n## Agent Instructions\n\n`AGENTS.md` is the canonical repo instruction file for agentic tools. `CLAUDE.md` remains in the repo as a compatibility file for tools that still expect that filename.\n\nShared repo-local skills live under [`.agents/skills/`](.agents/skills/). For cross-tool compatibility, [`.codex/skills`](.codex/skills), [`.claude/skills`](.claude/skills), and [`.gemini/skills`](.gemini/skills) all point at the same backing directory.\n\nThe skill packages mirror the surface split:\n\n- [`.agents/skills/interceptor-browser/`](.agents/skills/interceptor-browser/) — Browser surface fast path, network/scene/monitor references.\n- [`.agents/skills/interceptor-macos/`](.agents/skills/interceptor-macos/) — macOS surface fast path, daily-driver and specialized-domain references, background-first contract.\n- [`.agents/skills/interceptor/`](.agents/skills/interceptor/) — thin index skill: surface decision table + pointers. Kept as a compatibility shim for one release.\n- `.agents/skills/interceptor-windows/` — **reserved path, not yet created.** Slot for the future Windows surface; do not create until the surface ships.\n\n## Architecture Notes\n\nNo CDP is used for any default operation on the browser surface. Network capture is done by monkey-patching `fetch`/`XHR` in the page's JavaScript context — operating fully in user-space rather than via the debugger protocol. The macOS surface uses Apple-blessed APIs only (Accessibility, ScreenCaptureKit, AVFoundation, Speech, Vision, NaturalLanguage, OSLogStore, NSAppleScript, container runtime). Both surfaces multiplex over the same `interceptor` daemon Unix socket — see [`ARCHITECTURE.md`](ARCHITECTURE.md) for the full transport diagram.\n\n## Development Verification\n\nUse the verification command that matches the kind of change you made:\n\n```bash\nbun run typecheck    # Static typing across Bun host code and extension code\nbun test             # Runtime tests and CLI/parser coverage\nbash scripts/build.sh # Build compiled host binaries and extension bundles\n```\n\n- Run `bun run typecheck` when you change TypeScript types, runtime wiring, Chrome API usage, Bun socket usage, or any code that crosses host/extension boundaries.\n- Run `bun test` when you change parser behavior, monitor/scene helpers, or any logic already covered by the repo test suite.\n- Run `bash scripts/build.sh` when you need to verify the actual host binaries and extension bundles still compile.\n- For changes that affect browser behavior or shared infrastructure, run all three.\n\n## What NOT to Do\n\n- **Don't take screenshots to understand a page** — use `interceptor tree` and `interceptor text`. Screenshots waste tokens.\n- **Don't chain commands without sleep** — the extension needs time to process. `sleep 1` between actions.\n- **Don't interact with tabs outside the interceptor group** without `--any-tab`.\n- **Don't use CDP commands** (`interceptor network on`) unless you have a specific reason. Passive capture (`interceptor net log`) sees everything without the debugger infobanner.\n- **Don't start the daemon manually** — it auto-starts on first command.\n- **Don't `interceptor macos app activate` reflexively** — the macOS bridge is background-first. Activate only when the user asks for it.\n\n## Credits\n\n- [Ron Eddings](https://github.com/ronaldeddings/) created Interceptor.\n- [Pedram Amini](https://github.com/pedramamini/) provided early feedback on the project. Pedram's platform, [Maestro](https://runmaestro.ai), was used as part of developing this project.\n- [Daniel Miessler](https://github.com/danielmiessler/) for graciously coming up with the name `Interceptor` and EPIC project, [PAI](https://github.com/danielmiessler/PAI))\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhacker-valley-media%2Finterceptor","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhacker-valley-media%2Finterceptor","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhacker-valley-media%2Finterceptor/lists"}