Imported from zytact/GramGrab (
AGENTS.md). Install upstream withnpx skills add zytact/GramGrab. Copyright stays with the author.
AGENTS.md
Project
GramGrab resolves Instagram and WhatsApp media into items a person can inspect and download. It ships as a Chrome/Firefox MV3 extension plus a local CLI bridge.
apps/extension- popup, Watches options page, background worker, runner document, WhatsApp page controllerapps/cliandapps/native-host- terminal access to the same operations, bundled byvp packintoartifacts/packages/protocol- wire contracts shared by extension, CLI, and native host
Page access
The manifest declares no content scripts and no WhatsApp host permission. apps/extension/scripts/verify-whatsapp-package.mjs enforces both against built output, so treat them as fixed. Page execution still happens two ways:
- WhatsApp controller. The popup drives a capture session (
whatsapp/capture.ts), which callsscripting.executeScriptto injectjs/whatsapp-controller.jsinto the top frame of the active tab in theISOLATEDworld, then speaks a bounded chunk protocol to it over atabs.connectport. One invocation, one capture, underactiveTab. Seedocs/adr/0001-activetab-for-whatsapp-page-access.md. - Runner document. Frame extraction and silent-video re-encode need DOM and media APIs a service worker lacks, so the background worker opens
runner.htmlin a minimized popup window (getRunnerinbackground.ts) and sends it aRUN_EXPORTmessage.src/runner.tsexecutes the plan and reports back withRUNNER_READYandRUNNER_PROGRESS.
Commands
Scripts live in the root package.json and run through vp run <script>. The ones worth knowing:
vp run build # both targets → extension/{chromium,firefox}/
vp run dev # chromium watch (dev:firefox for the other target)
vp run verify:whatsapp-packages # manifest policy checks against built output
vp run package:chromium # CRX (generates/uses chromium.pem key)
vp run package:firefox # XPI
vp run package:tools # vp pack → artifacts/ for the CLI and native host
Use Vite+ as the primary workflow surface: prefer vp commands over package-manager wrappers.
WhatsApp acquisition
apps/extension/src/whatsapp/ acquires one Visible Status at a time and hands the bytes to an edit session. Its privacy constraints are binding on every change: docs/whatsapp-privacy.md is the contract, docs/adr/0001-0004 record why. Load those before touching this path. The invariants that most often get broken by accident:
- Page reach is
activeTabplusscripting. A WhatsApp host permission, persistent or optional, is out. - Captured bytes live in memory only. No
storage, IndexedDB, OPFS, or filesystem. - One flat 10-minute edit lease from capture-complete. Interaction never resets or extends it, and terminal operations are pre-flight checked against the remaining lease.
- WhatsApp diagnostics are a distinct structural-only type that cannot hold a URL, name, or identifier.
- A WhatsApp history entry is a receipt, not a handle: no re-download affordance, no display name.
Files: contracts.ts (Effect schemas for the port protocol), controller-runtime.ts (page side), capture.ts (extension side), limits.ts (bounds), plus export.ts, mode.ts, mute.ts, lease.ts.
Synthetic tests are authoritative for every extension-owned boundary; docs/whatsapp-boundary-coverage.md maps boundary to test. docs/whatsapp-live-verification.md is a human-run procedure for browser facts tests cannot establish.
Vendored Repositories
This project vendors external repositories under .repos/.
- Use vendored repositories as read-only reference material when working with related libraries
- Prefer examples and patterns from the vendored source code over generated guesses or web search results
- Do not edit files under
.repos/unless explicitly asked - Do not import from
.repos/- application code should continue importing from normal package dependencies - When writing Effect code, inspect
.repos/effect/for examples of idiomatic usage, tests, module structure, and API design. Treat it as the source of truth for Effect patterns.
Testing
- Vitest config is
vitest.config.tsat the repo root: jsdom, globals, v8 coverage, tests matched underapps/**andpackages/**. - Setup is
apps/extension/src/test/setup.ts(polyfillsBlob.arrayBuffer, installs a mockglobalThis.browser). Helpers:resetBrowserMocks(),setMockMessageHandler(type, handler),getDownloadCalls(). - Background tests dynamically import
background.tsto capture the registered listener. - Use
instagram, Instagram's own public account, wherever a test, fixture, or doc needs a username. Keep real people's handles out of the repo.
IG Schema Fixtures & Strict-Schema Posture
All Instagram API responses are decoded through Effect Schema tagged unions, not ad-hoc casts. The posture is strict + loud: decode failures surface as ResponseShapeUnknown with a user-actionable message. Unknown __typename values pass through silently so partial changes do not brick the whole response.
Sanitized fixtures live in apps/extension/src/effect/__fixtures__/ (see the README there) and are decoded by schemas.fixtures.test.ts. Handwritten tests in schemas.test.ts cover edge cases only: missing required fields, null variants, Unknown passthrough, union dispatch.
When ResponseShapeUnknown fires in the wild:
- If the request itself stopped working (App ID, ASBD ID, GraphQL doc ID, endpoint, transport), follow
docs/instagram-protocol.mdfirst. vp run generate:ig-fixtures, then paste.local/capture-ig-fixtures.mjsinto DevTools on a logged-ininstagram.com.- Download raw JSON into
.local/raw-fixtures/, reviewvp run sanitize:ig-fixtures, then install withvp run sanitize:ig-fixtures -- --write. The sanitizer is the privacy boundary for committed captures. vp test run. Failing fixture tests show exactly what changed.- Update
apps/extension/src/effect/schemas.ts, re-run tests, ship sanitized fixtures only.
Domain language
Ubiquitous language lives in CONTEXT.md. Use its terms (Status, Visible Status, Instant, Highlight, Avatar) and honor its Avoid list. Ambiguous decisions are recorded in docs/adr/.
Documentation
Write processes and procedures as docs in docs/. AGENTS.md holds only what steers agents, plus pointers to those docs.
Operation errors
The canonical failure registry and compatibility contract live in docs/error-model.md. When adding a failure code, update its producer, schema, normalizer, exhaustive presentation/recovery policy, diagnostics policy, documentation row, and focused tests together. Render diagnostic causes only through the diagnostics surface, never as ordinary UI copy.
Pre-commit
Vite+ controls the pre-commit hook.
Using Vite+, the Unified Toolchain for the Web
This project is using Vite+, a unified toolchain built on top of Vite, Rolldown, Vitest, tsdown, Oxlint, Oxfmt, and Vite Task. Vite+ wraps runtime management, package management, and frontend tooling in a single global CLI called vp. Vite+ is distinct from Vite, and it invokes Vite through vp dev and vp build. Run vp help to print a list of commands and vp <command> --help for information about a specific command.
Docs are local at node_modules/vite-plus/docs or online at https://viteplus.dev/guide/.
Validation
Run vp install after pulling remote changes. After any change, run:
vp check
vp test run
vp run fallow
vp check formats, lints and type checks in one pass. Clear vp run fallow findings by refactoring the code. Suppression comments, threshold or config changes, and baselines need the user's approval first. After a build or manifest change, also run vp run verify:whatsapp-packages against the built output.
Check package.json and vite.config.ts for scripts or tasks a change touches, and run them with vp run <name>. If setup, runtime, or package-manager behavior looks wrong, run vp env doctor and include its output when asking for help.
