
Doc Snapshot Agent
- 45 installs
- 241 repo stars
- Updated July 22, 2026
- felo-inc/felo-skills
Helps with ai & agent building tasks.
About
doc-snapshot-agent is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- doc-snapshot-agent
- AI & Agent Building
- AI-coding skill
Doc Snapshot Agent by the numbers
- 45 all-time installs (skills.sh)
- Ranked #7,749 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/felo-inc/felo-skills --skill doc-snapshot-agentAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 45 |
|---|---|
| repo stars | ★ 241 |
| Last updated | July 22, 2026 |
| Repository | felo-inc/felo-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
When to Use
Load this skill when a Markdown document needs real images — screenshots of live web pages, AI-generated editorial illustrations, or a rerun that only fixes image placement in an already-processed file.
Use it when the user asks to:
- add images to a Markdown article
- process a case file with image markers
- capture screenshots for documentation
- generate article visuals and insert them into a document
- rerun or fix image placement in an already processed document
Do not use it for pure text editing, proofreading, or translation — those tasks do not benefit from browser automation or image generation and should be handled directly.
Architecture
This skill has a single entry point (this file) plus four sibling references for depth. It does not create hidden memory folders, does not persist browser state, and does not send any data beyond what the target workflow requires.
All paths (input cases, output images, illustrated Markdown, cache) resolve under one {project-root} the user names at the start of the run. Browser work routes exclusively through the Playwright MCP server; generated images route through a bundled Python script that calls OpenRouter.
Quick Start
1. Check Playwright MCP tools — confirm mcp__playwright__browser_navigate and other mcp__playwright__* tools are available. If missing, send the user the install snippet from references/mcp-setup.md and stop. 2. Confirm the project root — ask once; default to /tmp/doc-snapshot-agent if the user has no preference. 3. Inspect existing artifacts — reuse anything already on disk (see Incremental Execution). 4. Parse the case file — merge markers from heading form, HTML-comment form, and the Image Summary table. 5. Capture, generate, place, write README — follow the Workflow section.
Quick Reference
| Topic | File |
|---|---|
| Install Playwright MCP for each client, grant permissions, runtime setup | references/mcp-setup.md |
| Navigate, snapshot, login, capture, verify — full browser loop and tool patterns | references/browser-capture.md |
| Build and maintain site-specific navigation knowledge | references/site-explorer.md |
| Prompt construction and script usage for generated images | references/image-generation.md |
| Image generation CLI | scripts/generate_image.py |
Approach Selection
| Situation | Best path | Why |
|---|---|---|
Article already has screenshots in output/{article-id}/raw/ and the user only wants the Markdown rebuilt | Skip capture, rerun Step 5 (Illustrated Markdown) | Browser work is expensive; Markdown regeneration is cheap |
Marker type is screenshot and the page is publicly reachable | Playwright MCP navigate → snapshot → capture | Reliable, inspectable, handles JS rendering |
Marker type is screenshot and the page is behind auth | Playwright MCP with PLAYWRIGHT_CRED_* env vars | Keeps secrets out of prompts and the transcript |
Marker type is generated (editorial, hero, conceptual) | scripts/generate_image.py via OpenRouter | Screenshots cannot render conceptual imagery |
| Marker landed on the wrong paragraph | Reparse case file, reapply semantic placement | Re-capturing won't fix placement bugs |
| Required MCP tools are missing in the runtime | Stop, point user to references/mcp-setup.md | Workflow cannot proceed without MCP |
Workflow
Step 0: Verify Playwright MCP
Run this check at the start of every execution, not just the first time.
1. Detect tools whose name starts with mcp__playwright__. Required: browser_navigate, browser_snapshot, browser_take_screenshot. 2. If they are missing, stop and hand the user the matching install snippet from references/mcp-setup.md (Claude Code, Codex, VS Code/Cursor/Kiro, Claude Desktop, or standalone). Include the permissions.allow: ["mcp__playwright__*"] note for Claude Code and Codex. 3. After the user installs and restarts the client, resume from here rather than restarting the run.
Do not substitute direct Playwright library calls or any browser tool that lacks the mcp__playwright__ prefix. If the prefix is missing, the call does not go through the MCP server.
Step 0.5: Confirm the project root
Ask once:
Which directory should I use as the project root for this run?
- If the user provides a path, use it as
{project-root}. - If the user says "no preference", skips, or does not answer, default to
/tmp/doc-snapshot-agent. - Create the directory if it does not exist.
All subsequent paths (cases/, output/, .cache/, scripts/, references/) resolve under {project-root}/.
Recommended layout inside {project-root}/:
{project-root}/
├── cases/
│ └── {article-id}.md
├── output/
│ ├── {article-id}/
│ │ ├── raw/
│ │ │ ├── A1_example.png
│ │ │ └── A2_example.png
│ │ ├── A1_example.png
│ │ ├── A2_example.png
│ │ └── README.md
│ └── markdowns/
│ └── {article-id}.md
└── .cache/
└── screenshots/
└── {article-id}/Conventions:
cases/holds the source Markdown.output/{article-id}/raw/holds original browser screenshots — never overwrite files here.output/{article-id}/holds post-processed assets that the final Markdown references.output/markdowns/holds the final illustrated Markdown..cache/screenshots/holds reusable screenshot cache entries.
If the user specifies a different layout, follow their instruction.
Step 1: Parse the case file
Merge image requirements from three sources:
1. inline heading-based screenshot markers 2. inline <!-- IMAGE: ... --> markers 3. the Image Summary table
For each image, record: type (screenshot or generated), filename, marker id if present, description or purpose, source URL if present, post-processing instruction if present, exact inline location if present, and whether semantic placement is still required. Also detect the target websites referenced by the article.
Step 2: Prepare the environment
- create output directories
- check the screenshot cache for reusable entries
- load credentials from environment variables (pattern:
PLAYWRIGHT_CRED_{SERVICE}_{FIELD}) - re-confirm Playwright MCP tools are present
- if the Chromium runtime is missing, run
npx playwright install chromium(seereferences/mcp-setup.md) - if the target flow needs login/signup/invite/verification and the required information is not already supplied, pause and ask the user before taking any account-specific action
Step 2.5: Understand the target site
Bad screenshots usually come from landing on the wrong page, not from the wrong capture command. Before capturing:
1. Check for existing site knowledge under $IMAGE_AGENT_SITE_KNOWLEDGE_DIR/ and $IMAGE_AGENT_SITE_LEARNING_DIR/. 2. Derive a stable site-key from the domain (memclaw.me → memclaw, app.felo.ai → felo). 3. If {site-key}.md exists and is recent, read it before browsing. 4. If knowledge is missing or stale, run a structured site exploration — see references/site-explorer.md — and save findings for reuse. 5. Map every screenshot description to a specific page or UI state: target URL or click path, required visible elements, scroll/tab/expand actions needed. 6. Append new knowledge to the site knowledge files whenever browsing discovers something worth remembering.
Step 3: Capture browser screenshots
Follow references/browser-capture.md for the full navigate → snapshot → act → wait → capture → verify loop and the concrete tool patterns.
Typical flow:
- open the target website
- log in if required (credentials from env)
- navigate to the correct page or UI state
- wait for key content to load
- resize the viewport if the requested layout needs it
- save screenshots to
{project-root}/output/{article-id}/raw/
Naming rule:
- if a marker id exists, save as
{marker-id}_{filename}(e.g.A1_workspace-dashboard.png) - otherwise use the original filename
After each capture, open the image file and confirm it matches the description. DOM inspection is not a substitute for looking at the saved PNG.
Step 4: Post-process screenshots
Apply the Processing: instruction if present (crop, resize, aspect-ratio adjustment). Copy from raw/ into the final output directory — never edit raw/ in place.
raw/keeps untouched originals.output/{article-id}/holds the assets the Markdown references.
Step 5: Generate the illustrated Markdown
5.1 Replace inline markers in place
Heading marker:
### 📷 Screenshot: A1 (workspace-dashboard.png)
Use: Show the authenticated workspace homepage
Processing: Full-width screenshotbecomes:
HTML comment marker:
<!-- IMAGE: screenshot (https://example.com/app)
Description: Workspace dashboard showing Architecture Decisions
Filename: architecture-decisions.png
-->becomes:
5.2 Semantically place images that have no inline marker
For images that appear only in the Image Summary table:
- read the description carefully
- extract its key concepts
- search the document paragraph by paragraph
- find the paragraph that discusses the same concept most directly
- insert the image immediately after that paragraph — not at the end of the section, not at the end of the article
Example: a description of Share panel showing team members and invite controls belongs next to the paragraph that mentions inviting teammates, not at the end of a general onboarding section.
5.3 Handle generated images
For generated markers, follow references/image-generation.md and call the bundled script:
python {project-root}/scripts/generate_image.py "{description}" -o "{project-root}/output/{article-id}/{filename}"For text-heavy images, use the stronger model:
python {project-root}/scripts/generate_image.py "{description}" -o "{project-root}/output/{article-id}/{filename}" -m google/gemini-3-pro-image-previewIf generation succeeds, insert the normal Markdown image reference. If it fails, insert a warning block and record the failure in the README:
> Warning: AI image generation failed for {filename}Generation prompt guidance: include the subject clearly, mention visual style if the article implies one, flag whether the image is for a technical article or tutorial, and state any required visible text explicitly.
5.4 Remove the Image Summary table
The Image Summary block is workflow metadata. Strip it from the final illustrated Markdown.
Step 6: Write the README inventory
Create {project-root}/output/{article-id}/README.md:
# {article-id} Illustration Output
Article: {title}
Completed: {timestamp}
## Image Inventory
| Filename | Marker | Description | Size | Processing |
|----------|--------|-------------|------|------------|
| A1_example.png | A1 | Workspace dashboard | 1200x800 | resized |
## Notes
- Credentials source: environment variables
- Additional comments
## Remaining Work
- [ ] Any missing screenshot or failed generated imageReturn a concise run summary containing: article id, what was reused vs newly generated, output Markdown path, image output directory, and any failed or missing images.
Marker Formats
The skill supports three marker formats. A single document may mix them.
A. Heading-based screenshot marker
### 📷 Screenshot: {marker-id} ({filename})
Use: {why this screenshot exists}
Processing: {post-processing instruction}
Difference: {optional distinction from similar screenshots}Fields: marker-id is a unique id like A1, B3-1, D3; filename is the base filename without the marker prefix; Use describes what the screenshot should communicate; Processing covers crop/resize; Difference disambiguates similar shots.
B. HTML comment marker
Screenshot:
<!-- IMAGE: screenshot (https://example.com/app)
Description: Workspace dashboard showing project activity and team sidebar
Filename: workspace-dashboard.png
-->Generated image:
<!-- IMAGE: generated
Description: Editorial illustration of a collaborative AI workflow with folders and browser windows
Filename: ai-workflow-hero.png
-->C. Image Summary table
A document may end with a summary table listing every required image:
## Image Summary
| # | Type | Description | Filename |
|---|------|-------------|----------|
| 1 | generated | Description... | `hero.png` |
| 2 | screenshot | Description... | `dashboard.png` |Important:
- the summary table is the complete inventory
- some images also appear as inline markers in the body
- some images exist only in the summary table and must be placed semantically during Step 5.2
Incremental Execution
Do not assume the workflow starts from zero. Inspect state first, then continue from the right step.
Check existing artifacts
For a given article id, inspect:
{project-root}/output/{article-id}/raw/*.png{project-root}/output/{article-id}/*.png{project-root}/output/{article-id}/README.md{project-root}/output/markdowns/{article-id}.md{project-root}/.cache/screenshots/{article-id}/
Decision rules
- New article — nothing exists → run the full workflow.
- Screenshots exist but Markdown does not — skip capture, rebuild Markdown and README.
- Markdown exists and user asks for fixes — reparse case file, rebuild placement without recapturing.
- Some screenshots are missing — capture only the missing ones, then continue.
- User asks to recapture specific images — regenerate only those, then rebuild Markdown.
- User asks to start over — ignore caches and rebuild everything.
Core principles: default to incremental work, reuse screenshots whenever possible, treat Markdown regeneration as cheap and browser work as expensive, and tell the user what will be skipped vs rerun.
Cache policy
Simple file-based cache:
- directory:
{project-root}/.cache/screenshots/{article-id}/ - cache key: screenshot filename
- if a matching cache file exists and the user did not ask for a refresh, reuse it
- if the user explicitly asks to recapture or refresh, ignore cache entries
Core Rules
1. MCP or nothing
Every browser interaction routes through mcp__playwright__* tools. If those tools are absent, stop and ask the user to install (see references/mcp-setup.md). Do not fall back to direct Playwright or generic browser tools — they bypass the contract this skill relies on.
2. Snapshot before click, re-snapshot after change
Clicks must reference refs from the latest accessibility snapshot, not memory. After navigation, modal open, tab switch, or accordion expand, snapshot again before the next action.
3. Choose the interaction type deliberately
Single left click is the default. Use double click only when the page semantics, site knowledge, or visible UI cues clearly indicate "open", "rename", "drill into", or another double-click-specific behavior. Use right click only when you explicitly need a context menu. Do not use double click as a retry for a failed single click, and do not right click just to "see what happens". After any double click or right click, wait for the visible state change and snapshot again before the next action.
4. raw/ is write-once
Original screenshots land in output/{article-id}/raw/ and stay untouched. Crop, resize, and processing all happen into the parent output/{article-id}/ directory. Never overwrite a raw/ file.
5. Reuse before recapturing
Browser work is the expensive step. Default to reusing existing screenshots and cache entries; only recapture when the user asks, the image is missing, or it visibly fails verification.
6. Credentials live in the environment
Read them from PLAYWRIGHT_CRED_{SERVICE}_{FIELD} env vars. Never hardcode, never echo secrets back to the user, and if a required variable is missing, surface its exact name.
7. Gated flows pause and ask
If the page is a sign-up, registration, invite, email verification, 2FA, or onboarding gate and the required user-specific information is not already supplied, stop and ask. Do not create accounts, accept invitations, or invent profile data without explicit user input. When the user answers, continue from the interrupted step rather than restarting.
8. Verify the actual image
A DOM snapshot is not enough. After each capture, open the saved PNG and confirm the described content is visible, no modal or loading skeleton blocks it, and the language matches the article. If the image does not match, retake — do not paper over it in the README.
9. Semantic placement over end-of-section dumping
For images from the summary table, read the paragraph content and insert the image next to the paragraph that discusses the same concept. Do not append leftover images to the end of the article or the end of a broad section.
Traps
- substituting a non-MCP browser tool because it is "faster" — breaks reproducibility and the MCP snapshot flow
- clicking from memory instead of from the latest snapshot — works once, then flakes
- using double click as a generic retry when a single click did nothing — usually opens the wrong state or hides the real issue
- right clicking without a clear goal or without waiting for a context menu signal — often leaves the page in an ambiguous state
- screenshotting before loading indicators clear — captures skeletons
- forgetting to re-snapshot after a modal or tab opens — next click targets a stale ref
- saving only the cropped asset and losing the
raw/original — recovery requires a full recapture - appending all unanchored images to the end of the article instead of placing them semantically
- hardcoding credentials or echoing them in prompts or the transcript
- treating every run as a fresh start — recapturing images that already exist on disk
- assuming a DOM assertion means the screenshot is correct — always review the PNG
- capturing the wrong language version of the site for a language-specific article
External Endpoints
| Endpoint | Data sent | Purpose |
|---|---|---|
| User-requested websites | Browser requests, form input, cookies, and interactions needed for the task | Screenshot capture and authenticated navigation |
https://openrouter.ai/api/v1/chat/completions | Image generation prompt text and requested model id | Generated-image markers via scripts/generate_image.py |
https://registry.npmjs.org | Package metadata and tarballs during optional installation | Installing @playwright/mcp and the Chromium runtime |
No other data is sent externally.
Security & Privacy
Data that leaves your machine:
- requests sent to the websites the user asked to capture
- prompt text sent to OpenRouter when generating images
- optional npm traffic when installing Playwright MCP or the Chromium runtime
Data that stays local:
- the source Markdown, generated screenshots, generated images, the illustrated Markdown, and the run README
- the screenshot cache under
{project-root}/.cache/ - environment variables (credentials and API keys) — this skill reads them but never writes them into files or transcripts
This skill does NOT:
- create hidden memory files or persistent profile folders
- persist browser session state across runs by default
- upload screenshots, generated images, or source Markdown anywhere
- create accounts, accept invitations, or complete verification flows on behalf of the user
- hardcode or echo credentials, API keys, or personal data
Trust
By running this skill, browser traffic goes to the websites you asked to capture, generation prompts go to OpenRouter, and optional package downloads go through npm. Only run it against sites and generation providers you trust. For destructive, financial, medical, or production flows, prefer staging environments and confirm with the user before proceeding.
Feedback
Issues and improvements: https://github.com/Felo-Inc/felo-skills/issues
{
"name": "Doc Snapshot Agent",
"tagline": "Automatically add screenshots and generated images into Markdown documents",
"description": "Doc Snapshot Agent parses image markers in Markdown, captures website screenshots or generates conceptual images, and outputs an image-enriched Markdown file. Supports incremental reruns, semantic image placement, and structured output directories. Requires relevant credentials/API keys for protected sites and image generation.",
"category": "productivity",
"tags": ["documentation", "markdown", "screenshots", "image-generation", "browser-automation", "agent-workflow"],
"version": "1.1.0",
"license": "MIT",
"pricing": "free",
"support_url": "https://github.com/Felo-Inc/felo-skills/issues",
"homepage": "https://github.com/Felo-Inc/felo-skills"
}
MIT License
Copyright (c) 2026 Open Source Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
doc-snapshot-agent
doc-snapshot-agent is a skill package for automatically adding screenshots and generated images to Markdown documents.
It is designed for agent workflows that need to:
- parse Markdown image markers
- capture product or website screenshots
- generate conceptual illustrations
- place images back into the most relevant paragraph
- produce a clean illustrated Markdown output
Package Structure
doc-snapshot-agent/
├── SKILL.md
├── references/
│ ├── mcp-setup.md
│ ├── browser-capture.md
│ ├── site-explorer.md
│ └── image-generation.md
├── scripts/
│ └── generate_image.py
├── README.md
└── LICENSEDesign Principles
- one main skill entry point
- supporting guidance stored as references instead of extra skills
- Playwright MCP as the preferred browser automation path
- incremental execution instead of full reruns by default
- strict separation between raw screenshots and final output assets
- environment-based credentials and API keys
What the Skill Expects
Typical input:
- a Markdown document under
cases/{article-id}.md - inline image markers or an
Image Summarytable - environment variables for any website credentials or image-generation provider keys
Typical output:
output/{article-id}/raw/*.pngoutput/{article-id}/*.pngoutput/{article-id}/README.mdoutput/markdowns/{article-id}.md
Marker Formats
The skill supports:
- heading-based screenshot markers
- HTML comment image markers for screenshots and generated images
- end-of-document
Image Summarytables
See SKILL.md for the full workflow.
Environment Variables
Examples:
export PLAYWRIGHT_CRED_FELO_EMAIL="user@example.com"
export PLAYWRIGHT_CRED_FELO_PASSWORD="secret"
export OPENROUTER_API_KEY="..."Included Script
Generate images with:
python scripts/generate_image.py "Editorial illustration of a collaborative AI workflow" -o output/hero.pngPublishing Notes
This repository is intentionally packaged for skill systems that prefer:
- a single top-level skill
- linked references
- bundled scripts for reusable automation
License
MIT
Browser Capture Reference
Use this guide when doc-snapshot-agent is capturing screenshots through the Playwright MCP server.
This is a reference document for doc-snapshot-agent, not a standalone skill. See mcp-setup.md for install instructions and site-explorer.md for how to build site knowledge before browsing.
Core idea
The browser is not just a screenshot machine — it is how you reach the exact product page, workspace state, or documentation view described in the source Markdown. Bad screenshots usually come from navigating to the wrong page, not from using the wrong screenshot command.
Always:
- inspect page structure before clicking
- re-inspect after every significant interaction
- verify screenshots visually against the description, not only against DOM text
Required tools
All browser interactions must go through the Playwright MCP server. Tool names are prefixed mcp__playwright__ (double underscores on both sides of playwright).
Standard tool names used by this workflow:
mcp__playwright__browser_navigatemcp__playwright__browser_snapshotmcp__playwright__browser_take_screenshotmcp__playwright__browser_clickmcp__playwright__browser_typemcp__playwright__browser_fill_formmcp__playwright__browser_wait_formcp__playwright__browser_evaluatemcp__playwright__browser_console_messagesmcp__playwright__browser_network_requestsmcp__playwright__browser_resizemcp__playwright__browser_tabsmcp__playwright__browser_close
Do not substitute direct Playwright library calls, generic browser tools, or any tool that does not start with mcp__playwright__. If the prefix is missing, the call is not routed through the MCP server and must not be used. If the mcp__playwright__* tools are absent, stop and hand the user the matching snippet from mcp-setup.md.
Mental model
Playwright MCP gives you two views of the page:
- Accessibility snapshot — tells you what is currently interactable. Use it to decide what to click.
- Screenshot — tells you what the page looks like. Use it to confirm the visual result matches the article.
Snapshot for interaction, screenshot for verification. Do not click from memory, do not verify from DOM alone.
Choosing the interaction type
Treat interaction choice as part of navigation, not as an improvisation after a failed click.
- Single left click is the default.
- Double click is for controls or rows that are clearly meant to open, drill in, rename, or enter a detail state on double click.
- Right click is only for opening a context menu that the workflow explicitly needs.
Rules:
- decide the click type before acting by reading the latest snapshot, site knowledge, and the screenshot requirement
- do not use double click as a generic retry for a failed single click
- do not right click "just in case" or to explore randomly
- after any double click or right click, wait for a visible state change and snapshot again before the next action
- if the required state can be reached through an explicit button or menu item, prefer that over guessing with alternate click types
Good signals for double click:
- file manager, canvas, editor, or table rows that normally open on double click
- copy or site knowledge that explicitly says "double-click", "open", "rename", or "drill into"
- the article asks for a detail view that is commonly entered from a list row rather than from a separate button
Good signals for right click:
- the article asks for a context menu, row menu, copy link menu, rename menu, or admin actions
- the UI hides actions until a context menu is opened
If the signal is weak, stop guessing:
- inspect the latest snapshot again
- check saved site knowledge
- look for an explicit overflow button, kebab menu, or action icon
- use console/network/debugging only if the page looks broken, not as a substitute for choosing the right interaction
Standard workflow
1. Open the site
- navigate to the target URL
- take a fresh accessibility snapshot
- identify whether the page is logged out, logged in, marketing, app, or docs
- read the returned refs before clicking anything
2. Sign in if needed
Credentials come from the environment using the pattern PLAYWRIGHT_CRED_{SERVICE}_{FIELD} (e.g. PLAYWRIGHT_CRED_FELO_EMAIL, PLAYWRIGHT_CRED_FELO_PASSWORD).
Rules:
- read credentials from environment variables; never hardcode
- never echo passwords into the transcript
- if a credential is missing, surface the required variable name
- if the page is a sign-up, registration, invite, verification, or onboarding gate and the needed information is not already available, pause and ask the user before continuing
- do not create accounts or complete registration flows with guessed data
Login sequence:
1. navigate to the login page 2. snapshot the form 3. if credentials are missing or the gate requires user-specific decisions, pause and ask 4. fill email and password using browser_fill_form once refs are known 5. submit 6. wait for a visible success signal (dashboard heading, success text) 7. snapshot again to confirm the authenticated state
3. Navigate to the correct page state
Before capturing, confirm the visible page matches the description:
- URL pattern
- page title or heading
- important panels and controls visible
- correct organization, workspace, language, or tab selected
- correct empty vs populated state
Do not confuse:
- a marketing homepage with an app dashboard
- a list page with a detail page
- an empty state with a populated workspace
- a docs landing page with the exact section the article asks for
4. Wait for real state changes
Prefer explicit waits over blind sleeps:
- wait for key text to appear
- wait for loading indicators to disappear
- wait for modal, accordion, or tab transitions to settle
- wait for animations to finish
Only use a fixed time wait when no stable page signal is available.
5. Close visual noise
Before capture, dismiss anything that distracts from the subject:
- cookie banners
- chat widgets
- onboarding popovers
- notification toasts
- user menus left open
6. Capture the right kind of screenshot
Choose intentionally:
- viewport screenshot when composition matters
- element screenshot when a single panel or card is the subject
- full-page screenshot only when the article really needs the whole page
Naming:
- save originals to
{project-root}/output/{article-id}/raw/ - if a marker id exists, prefix the filename:
A1_workspace-dashboard.png - otherwise use the requested filename
7. Verify the saved image
A DOM snapshot is not enough. After capture, open the image file and confirm:
- requested content is present
- no modal, toast, or loading skeleton blocks the subject
- layout is readable at the chosen size
- screenshot language matches the article language
- the exact feature or panel described is visible, not just the surrounding page
If it does not match, re-navigate and retake. Do not explain a wrong screenshot away in the README.
Concrete tool patterns
Navigate and snapshot
{
"tool": "mcp__playwright__browser_navigate",
"arguments": {"url": "https://example.com/app"}
}{
"tool": "mcp__playwright__browser_snapshot",
"arguments": {}
}Fill a login form
{
"tool": "mcp__playwright__browser_fill_form",
"arguments": {
"fields": [
{"name": "email", "type": "textbox", "ref": "email-ref", "value": "${PLAYWRIGHT_CRED_FELO_EMAIL}"},
{"name": "password", "type": "textbox", "ref": "password-ref", "value": "${PLAYWRIGHT_CRED_FELO_PASSWORD}"}
]
}
}If form refs are unknown, snapshot first and use the returned refs.
Click and wait
{
"tool": "mcp__playwright__browser_click",
"arguments": {"ref": "open-share-panel-ref", "element": "Share button"}
}{
"tool": "mcp__playwright__browser_wait_for",
"arguments": {"text": "Invite members", "time": 5}
}Double click
Use only when the UI clearly requires it.
{
"tool": "mcp__playwright__browser_click",
"arguments": {
"ref": "workspace-row-ref",
"element": "Workspace row",
"doubleClick": true
}
}Then wait for the expected detail signal and snapshot again.
Right click and open a context menu
Use only when the workflow explicitly needs a context menu.
{
"tool": "mcp__playwright__browser_click",
"arguments": {
"ref": "file-row-ref",
"element": "File row",
"button": "right"
}
}{
"tool": "mcp__playwright__browser_wait_for",
"arguments": {"text": "Rename", "time": 5}
}After the menu appears, snapshot again and click the menu item using the new ref from that snapshot.
Take a full-page screenshot
{
"tool": "mcp__playwright__browser_take_screenshot",
"arguments": {
"type": "png",
"filename": "output/article-123/raw/A1_workspace-dashboard.png",
"fullPage": true
}
}Evaluate page state
{
"tool": "mcp__playwright__browser_evaluate",
"arguments": {
"function": "() => ({ title: document.title, url: location.href })"
}
}Recommended command sequences
Open a site and inspect it
1. mcp__playwright__browser_navigate({ url })
2. mcp__playwright__browser_snapshot()
3. read the returned refs before clicking anythingLog in
1. navigate to the login page
2. snapshot the page
3. if credentials are missing, pause and ask the user
4. fill email and password after the user provides or confirms them
5. submit the form
6. wait for success text or dashboard heading
7. snapshot again to confirm authenticated stateOpen a specific panel
1. snapshot current page
2. click the control that opens the panel
3. wait for panel text to appear
4. snapshot again to verify the panel state
5. take screenshot only after required controls are visibleOpen a detail view that needs double click
1. snapshot current page
2. confirm the target row or tile is the right one
3. double click only if the UI or site knowledge clearly indicates that behavior
4. wait for a strong detail-view signal such as a title, breadcrumb, or editor panel
5. snapshot again before any follow-up clickOpen a context menu
1. snapshot current page
2. right click the exact target element
3. wait for a concrete menu item label to appear
4. snapshot again to get refs for the visible menu items
5. click the needed menu item from the new snapshotCapture a screenshot
1. resize if the requested layout needs it
2. wait for UI to settle
3. take screenshot to the raw output path
4. open the image and review it before marking completeDebugging a suspicious page
Use console and network inspection when:
- the page appears blank
- a button click does nothing
- the app silently redirects
- data panels stay empty
- login succeeds visually but the expected page never appears
Checks to run:
mcp__playwright__browser_console_messagesmcp__playwright__browser_network_requestsmcp__playwright__browser_evaluatefor small state probes
These often find problems that are invisible in the screenshot alone.
Per-screenshot automation notes
For each requested screenshot, write down a short plan before touching the browser:
Target: workspace dashboard showing team members and invite controls
URL: https://example.com/app/workspace/123
Preconditions: logged in, English UI, sidebar expanded
Required visible elements: workspace title, team avatars, Invite button
Extra actions: open Share panel before captureCommon failure modes
- clicking from memory instead of from the latest snapshot
- using double click as a fallback when a single click failed, instead of confirming that double click is the intended interaction
- screenshotting the wrong page because the description was mapped too loosely
- capturing before loading indicators disappear
- forgetting to re-snapshot after a modal, tab, or accordion opens
- right clicking without verifying that the context menu actually appeared
- using the wrong language version of the app
- missing an expanded panel or submenu mentioned in the article
- capturing an empty workspace instead of a populated one
- saving only a final cropped image and losing the raw original
- full-page screenshot where a focused element capture would be clearer
- ignoring console or network errors when the UI looks incomplete
Pre-capture checklist
Before marking a screenshot complete, confirm:
- correct URL and product state
- correct login state
- correct language
- required panels and controls visible
- raw screenshot saved under
output/{article-id}/raw/ - image file opened and reviewed visually
- no modal, toast, or loading skeleton blocks the subject
Image Generation Reference
Use this reference when the document needs conceptual visuals, diagrams, editorial illustrations, or any non-screenshot image.
This is a reference document for doc-snapshot-agent, not a standalone skill.
Purpose
Generated images fill the gaps where a screenshot is impossible or inappropriate.
Typical cases:
- article hero images
- conceptual diagrams
- editorial illustrations
- workflow visuals
- product abstractions that do not correspond to a single literal screen
Provider Assumption
This package includes a Python helper script that targets OpenRouter image-capable Gemini models.
Required environment variable:
OPENROUTER_API_KEYPython dependency:
pip install requestsNever ask users to paste API keys into chat or hardcode them into files.
Bundled Script
Use the included script:
python scripts/generate_image.py "A watercolor painting of a cat" -o output.pngChoose a model explicitly if needed:
python scripts/generate_image.py "Technical hero illustration" -o hero.png -m google/gemini-3-pro-image-previewRecommended Models
| Model ID | Best For |
|---|---|
google/gemini-3.1-flash-image-preview | default fast generation |
google/gemini-3-pro-image-preview | stronger instruction following and better text rendering |
Use the pro model when the image needs readable labels, UI-like text, or tighter compliance with detailed instructions.
Prompt Construction
A good prompt should include:
- subject
- purpose in the document
- style direction
- any required text in the image
- constraints such as aspect ratio, palette, or things to avoid
Template:
Goal: cover image for a technical article about collaborative AI workflows
Subject: multiple browser windows, markdown files, screenshots, and organized folders
Style: clean editorial illustration, modern flat design, soft shadows, warm neutral palette
Text: none
Constraints: landscape composition, no watermarks, avoid photorealistic facesPrompting Tips
- be specific about what the image is for
- avoid overloading the prompt with too many unrelated ideas
- specify text explicitly if the image needs words on it
- mention composition and aspect ratio when layout matters
- if the article has an established visual tone, keep the generated image consistent with it
Generation Workflow
1. Extract the Description
Use the description from the image marker or summary table.
2. Expand It Into a Real Prompt
Turn terse notes into an explicit image brief.
Weak:
AI workflow
Better:
Editorial illustration for a technical blog post showing an AI-assisted coding workflow with a browser, Markdown files, screenshots, and a tidy project folder structure. Flat vector style, warm neutrals, subtle depth, no logos, no text.
3. Run the Generator
python scripts/generate_image.py "{prompt}" -o "output/{article-id}/{filename}"4. Review the Result
Check whether:
- the main idea is visible
- the style fits the article
- any required text is legible
- the image is usable at article scale
If the result is weak, refine the prompt and retry.
Failure Handling
If generation fails:
- keep the workflow running
- note the failure in the article README
- insert a warning block in the output Markdown
Suggested warning block:
> Warning: AI image generation failed for hero.png - timeout from providerSecurity and Privacy
What leaves the machine:
- prompt text sent to the provider endpoint
What stays local:
- API key in the environment
- generated image files written to disk
This workflow should not:
- hardcode secrets
- store user API keys in repository files
- upload output images anywhere unless the user explicitly asks
Playwright MCP Setup
This is a reference document for doc-snapshot-agent, not a standalone skill.
Use this guide when Step 0 of the workflow detects that the mcp__playwright__* tools are not available. Pick the block that matches the user's current client.
How to verify
The workflow uses Playwright MCP for every browser interaction. "Available" means the runtime exposes tools starting with mcp__playwright__ (double underscores on each side), for example:
mcp__playwright__browser_navigatemcp__playwright__browser_snapshotmcp__playwright__browser_take_screenshot
If these are missing, stop and send the user the matching install snippet below. Do not substitute direct Playwright library calls or any non-MCP browser tool.
Install recipes
Claude Code
claude mcp add playwright -- npx @playwright/mcp@latestCodex
codex mcp add playwright -- npx @playwright/mcp@latestVS Code / Cursor / Kiro
Add to the MCP settings JSON (for example .vscode/mcp.json, .cursor/mcp.json, .kiro/settings/mcp.json):
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": ["@playwright/mcp@latest"]
}
}
}Claude Desktop
Add to claude_desktop_config.json:
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": ["@playwright/mcp@latest"]
}
}
}Standalone server (headless hosts, workers)
npx @playwright/mcp@latest --port 8931Then point the client config at the running server:
{
"mcpServers": {
"playwright": {
"url": "http://localhost:8931/mcp"
}
}
}Grant tool permissions
Some clients block MCP tool use until the tool prefix is explicitly allowed. For Claude Code and Codex, add to the settings file:
{
"permissions": {
"allow": ["mcp__playwright__*"]
}
}Install the Chromium runtime
The first time Playwright MCP runs on a machine it needs a browser binary:
npx playwright install chromiumRun this once before the first screenshot capture. Later runs reuse the installed binary.
Standalone launch options
When running the standalone server, common flags for the workflow:
# Headless (CI, servers, worker processes)
npx @playwright/mcp@latest --headless
# Use Firefox instead of Chromium
npx @playwright/mcp@latest --browser firefox
# Larger viewport for desktop-layout screenshots
npx @playwright/mcp@latest --viewport-size 1440x960
# Accept staging-site certificates
npx @playwright/mcp@latest --ignore-https-errorsMost integrated clients do not surface browser options in the MCP config and will use the defaults: Chromium, headed, viewport 1280x720. Switch to standalone mode if the workflow needs customised browser options.
After install
1. Restart the client or reload the session so the MCP config takes effect. 2. Rerun Step 0 of the workflow — the mcp__playwright__* tools should now be available. 3. Continue from the interrupted step instead of restarting the run, unless the user asks for a fresh run.
Site Explorer Reference
Use this reference when the document requires screenshots from a website that has not yet been mapped clearly.
This is a reference document for doc-snapshot-agent, not a standalone skill.
Purpose
The goal of site exploration is not casual browsing. The goal is to build reusable operational knowledge about how to reliably reach the right pages for future screenshot work.
Good site knowledge records:
- stable entry points
- correct page mappings
- successful navigation paths
- failed attempts and why they failed
- screenshot-specific caveats
Knowledge Storage
Store persistent site knowledge outside the skill package, using environment-provided directories:
$IMAGE_AGENT_SITE_KNOWLEDGE_DIR/{site-key}.md
$IMAGE_AGENT_SITE_LEARNING_DIR/{site-key}.mdSuggested split:
sites/or the knowledge directory stores stable navigation knowledgelearnings/stores failures, corrections, recent discoveries, and user-specific preferences
Do not keep runtime site memories inside the skill directory.
Site Key Rules
Derive a stable key from the domain:
- remove protocol
- remove
www. - remove low-value subdomain noise when appropriate
Examples:
memclaw.me->memclawapp.felo.ai->felowww.notion.so->notionlinear.app->lineardocs.example.com->example-docsapp.example.com->example-app
What to Record
A useful site knowledge file should contain:
- primary domain
- first explored date
- last updated date
- last verified date
- login method
- page map
- stable navigation paths
- known pitfalls
- screenshot notes
- failures and corrections
Suggested structure:
# Example Site - Site Knowledge
- Primary domain: example.com
- Site key: example
- First explored: 2026-01-01
- Last updated: 2026-01-01
- Last verified: 2026-01-01
- Login method: PLAYWRIGHT_CRED_EXAMPLE_EMAIL / PLAYWRIGHT_CRED_EXAMPLE_PASSWORD
## Current Conclusions
- Best path into the app is through /login.
- The marketing homepage and product dashboard are different states.
- Team screenshots require the Share panel to be open.
## Verified Paths
| Goal | Start | Path | Success Signal | Last Verified | Confidence |
|------|-------|------|----------------|---------------|------------|
| Open dashboard | Homepage | Open /login -> submit credentials | URL becomes /dashboard | 2026-01-01 | high |
## Page Map
| Page Type | URL Pattern | Description | Key Visible Elements | Difference From Similar Pages |
|-----------|-------------|-------------|----------------------|-------------------------------|
| Marketing homepage | / | Logged-out landing page | Hero, CTA | No user data |
| User dashboard | /dashboard | Authenticated app home | Sidebar, project list | Requires login |
## Login Flow
1. Open /login.
2. Fill email and password.
3. Submit.
4. Confirm the dashboard URL and user avatar.
## Navigation Patterns
- Use the sidebar to reach projects.
- Project details open from the project title, not the whole card.
## Failures and Corrections
| Date | Task | Wrong Move | Failure Signal | Correct Move | Lesson |
|------|------|------------|----------------|--------------|--------|
| 2026-01-01 | Capture workspace dashboard | Used homepage | No project list visible | Log in and open /dashboard | Description referred to the app, not marketing |
## Screenshot Notes
- Wait for async panels to finish loading.
- Force English if the article is English.
- Close onboarding popovers before capture.Exploration Workflow
1. Read Existing Knowledge First
If a site knowledge file already exists, read it before opening the site.
Extract:
- verified paths
- common page confusions
- known login quirks
- language behavior
- screenshot caveats
- unresolved questions
Do not rediscover everything from scratch unless the knowledge is stale or obviously wrong.
2. Define the Exploration Goal
Before browsing, answer:
- which page types need to be identified
- which screenshot descriptions need real page mappings
- which old assumptions need rechecking
- which states need confirmation with real browser interaction
3. Explore and Validate
As you browse, classify each finding as one of three states:
- verified
- corrected
- still uncertain
Useful things to validate:
- homepage versus dashboard
- list page versus detail page
- how to reach settings, share dialogs, history views, or team panels
- whether language is controlled by URL or by a UI preference
- whether deep links are stable
4. Update Knowledge Immediately
After each meaningful discovery, update the site knowledge file with:
- successful path
- failure and correction
- preconditions
- visible success signal
- verification date
Exploration Priorities
Distinguish Public and Authenticated States
This is the most common source of screenshot mistakes.
Always determine:
- what the logged-out homepage looks like
- what the logged-in landing page looks like
- what visible elements distinguish them quickly
Map Descriptions to Real Pages
Words like these should always be validated against the actual product:
- homepage
- workspace
- dashboard
- settings
- project
- history
- decisions
- invite
- team
- panel
Do not trust your first guess. Confirm with the page itself.
Record Preconditions
Some pages only make sense when:
- a project is selected
- the correct organization is active
- a panel is expanded
- a language is switched
- a populated account is used instead of an empty one
Knowledge without preconditions is incomplete.
What Makes Site Knowledge High Quality
High-quality notes emphasize paths, not impressions.
Weak:
This page looks like a dashboard.
Strong:
Open /login, sign in, wait for /dashboard, confirm sidebar plus project list.
Also:
- keep old mistakes in the failure log instead of silently deleting them
- mark uncertain claims as unverified
- prefer recently verified shorter paths over older brittle paths
Output Back to the Caller
When exploration ends, report:
- which site was explored
- where the knowledge file was written
- what new verified paths were found
- which old assumptions were corrected
- what still needs validation
#!/usr/bin/env python3
"""Generate images via OpenRouter using Gemini image-capable models.
Usage:
python generate_image.py "A watercolor painting of a cat" -o output.png
python generate_image.py "Logo design for a coffee shop" -o logo.png -m google/gemini-3-pro-image-preview
python generate_image.py "A sunset over mountains" -o sunset.png -n 2
Requires:
- OPENROUTER_API_KEY environment variable
- requests library
"""
import argparse
import base64
import os
import sys
import time
from pathlib import Path
DEFAULT_MODEL = "google/gemini-3.1-flash-image-preview"
API_URL = "https://openrouter.ai/api/v1/chat/completions"
MAX_RETRIES = 3
def generate_image(
prompt: str,
output_path: str,
model: str = DEFAULT_MODEL,
num_images: int = 1,
api_key: str | None = None,
) -> list[str]:
"""Generate one or more images from a text prompt via OpenRouter."""
api_key = api_key or os.environ.get("OPENROUTER_API_KEY")
if not api_key:
print("Error: OPENROUTER_API_KEY not set.", file=sys.stderr)
sys.exit(1)
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
}
payload = {
"model": model,
"messages": [{"role": "user", "content": prompt}],
"modalities": ["image", "text"],
}
output = Path(output_path)
output.parent.mkdir(parents=True, exist_ok=True)
saved_files: list[str] = []
for batch_idx in range(num_images):
response = _request_with_retry(headers, payload)
data = response.json()
if "error" in data:
print(f"API error: {data['error']}", file=sys.stderr)
sys.exit(1)
try:
message = data["choices"][0]["message"]
except (KeyError, IndexError) as exc:
raise RuntimeError(f"Unexpected response format: {data}") from exc
images_saved = _extract_and_save_images(message, output, batch_idx, num_images)
saved_files.extend(images_saved)
text_response = _extract_text(message)
if text_response:
print(f"Model response: {text_response}")
if not saved_files:
print("Warning: No images were returned by the model.", file=sys.stderr)
return saved_files
def _request_with_retry(headers: dict, payload: dict):
import requests
last_response = None
for attempt in range(MAX_RETRIES):
try:
response = requests.post(API_URL, headers=headers, json=payload, timeout=120)
last_response = response
if response.status_code == 429:
wait_seconds = 2 ** (attempt + 1)
print(f"Rate limited, retrying in {wait_seconds}s...", file=sys.stderr)
time.sleep(wait_seconds)
continue
if response.status_code >= 500:
wait_seconds = 2 ** (attempt + 1)
print(
f"Server error {response.status_code}, retrying in {wait_seconds}s...",
file=sys.stderr,
)
time.sleep(wait_seconds)
continue
response.raise_for_status()
return response
except requests.exceptions.Timeout:
if attempt < MAX_RETRIES - 1:
print("Request timed out, retrying...", file=sys.stderr)
continue
raise
if last_response is None:
raise RuntimeError("Image generation request did not produce a response.")
return last_response
def _extract_and_save_images(message: dict, output: Path, batch_idx: int, total_batches: int) -> list[str]:
saved: list[str] = []
content = message.get("content")
if isinstance(content, list):
image_count = 0
for part in content:
if isinstance(part, dict) and "inline_data" in part:
inline = part["inline_data"]
mime_type = inline.get("mime_type", "image/png")
extension = "png" if "png" in mime_type else "jpg"
file_path = _build_path(output, batch_idx, image_count, total_batches, extension)
_save_base64(inline["data"], file_path)
saved.append(str(file_path))
image_count += 1
images = message.get("images")
if isinstance(images, list):
for image_idx, image in enumerate(images):
image_url = image.get("image_url", {}).get("url", "")
if image_url.startswith("data:"):
encoded = image_url.split(",", 1)[1]
extension = "png" if "png" in image_url else "jpg"
file_path = _build_path(output, batch_idx, image_idx, total_batches, extension)
_save_base64(encoded, file_path)
saved.append(str(file_path))
return saved
def _extract_text(message: dict) -> str:
content = message.get("content", "")
if isinstance(content, str):
return content
if isinstance(content, list):
texts = [part.get("text", "") for part in content if isinstance(part, dict) and "text" in part]
return " ".join(texts).strip()
return ""
def _build_path(output: Path, batch_idx: int, image_idx: int, total_batches: int, extension: str) -> Path:
if total_batches == 1 and image_idx == 0:
return output.with_suffix(f".{extension}") if output.suffix == "" else output
suffix = f"_{batch_idx * 10 + image_idx + 1}"
return output.parent / f"{output.stem}{suffix}.{extension}"
def _save_base64(encoded: str, file_path: Path) -> None:
file_path.parent.mkdir(parents=True, exist_ok=True)
file_path.write_bytes(base64.b64decode(encoded))
print(f"Saved: {file_path}")
def main() -> None:
parser = argparse.ArgumentParser(description="Generate images via OpenRouter")
parser.add_argument("prompt", help="Text prompt describing the image to generate")
parser.add_argument(
"-o",
"--output",
default="generated_image.png",
help="Output file path (default: generated_image.png)",
)
parser.add_argument(
"-m",
"--model",
default=DEFAULT_MODEL,
help=f"Model ID (default: {DEFAULT_MODEL})",
)
parser.add_argument(
"-n",
"--num-images",
type=int,
default=1,
help="Number of images to generate (default: 1)",
)
args = parser.parse_args()
generate_image(args.prompt, args.output, model=args.model, num_images=args.num_images)
if __name__ == "__main__":
main()