skills/browse/SKILL.md
MANUAL TRIGGER ONLY: invoke only when user types /browse. Fast headless browser for QA testing and site dogfooding. Navigate any URL, interact with elements, verify page state, diff before/after actions, take annotated screenshots, check responsive layouts, test forms and uploads, handle dialogs, and assert element states. ~100ms per command. Use when you need to test a feature, verify a deployment, dogfood a user flow, or file a bug with evidence. Use when asked to "open in browser", "test the site", "take a screenshot", or "dogfood this".
npx skillsauth add agwacom/adeel browseInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Check the SessionStart hook output in this conversation context for ADEEL_AUTO_UPDATE=.
If it says ADEEL_AUTO_UPDATE=false, use AskUserQuestion to ask:
"Auto-updates are off. Run /adeel-update to enable?" If yes, invoke
/adeel-update. If ADEEL_AUTO_UPDATE=true or not found, proceed directly
without mentioning it.
mkdir -p $HOME/.adeel/sessions
touch $HOME/.adeel/sessions/"$PPID"
_SESSIONS=$(find $HOME/.adeel/sessions -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' ')
find $HOME/.adeel/sessions -mmin +120 -type f -delete 2>/dev/null || true
_CONTRIB=$(${CLAUDE_PLUGIN_ROOT}/bin/adeel-config get adeel_contributor 2>/dev/null || true)
_PROACTIVE=$(${CLAUDE_PLUGIN_ROOT}/bin/adeel-config get proactive 2>/dev/null || echo "true")
_BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown")
echo "BRANCH: $_BRANCH"
echo "PROACTIVE: $_PROACTIVE"
source <(${CLAUDE_PLUGIN_ROOT}/bin/adeel-repo-mode 2>/dev/null) || true
REPO_MODE=${REPO_MODE:-unknown}
echo "REPO_MODE: $REPO_MODE"
echo "LAKE_INTRO: $_LAKE_SEEN"
_TEL=$(${CLAUDE_PLUGIN_ROOT}/bin/adeel-config get telemetry 2>/dev/null || true)
_TEL_PROMPTED=$([ -f $HOME/.adeel/.telemetry-prompted ] && echo "yes" || echo "no")
_TEL_START=$(date +%s)
_SESSION_ID="$$-$(date +%s)"
echo "TELEMETRY: ${_TEL:-off}"
echo "TEL_PROMPTED: $_TEL_PROMPTED"
mkdir -p $HOME/.adeel/analytics
echo '{"skill":"browse","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> $HOME/.adeel/analytics/skill-usage.jsonl 2>/dev/null || true
# zsh-compatible: use find instead of glob to avoid NOMATCH error
If PROACTIVE is "false", do not proactively suggest adeel skills — only invoke
them when the user explicitly asks. The user opted out of proactive suggestions.
If LAKE_INTRO is no: Before continuing, introduce the Completeness Principle.
Tell the user: "adeel follows the Go all the way principle — always do the complete
Then offer to open the essay in their default browser:
Only run open if the user says yes. Always run touch to mark as seen. This only happens once.
If TEL_PROMPTED is no AND LAKE_INTRO is yes: After the lake intro is handled,
ask the user about telemetry. Use AskUserQuestion:
Help adeel get better! Community mode shares usage data (which skills you use, how long they take, crash info) with a stable device ID so we can track trends and fix bugs faster. No code, file paths, or repo names are ever sent. Change anytime with
adeel-config set telemetry off.
Options:
If A: run ${CLAUDE_PLUGIN_ROOT}/bin/adeel-config set telemetry community
If B: ask a follow-up AskUserQuestion:
How about anonymous mode? We just learn that someone used adeel — no unique ID, no way to connect sessions. Just a counter that helps us know if anyone's out there.
Options:
If B→A: run ${CLAUDE_PLUGIN_ROOT}/bin/adeel-config set telemetry anonymous
If B→B: run ${CLAUDE_PLUGIN_ROOT}/bin/adeel-config set telemetry off
Always run:
touch $HOME/.adeel/.telemetry-prompted
This only happens once. If TEL_PROMPTED is yes, skip this entirely.
If _CONTRIB is true: you are in contributor mode. At the end of each major workflow step, rate your adeel experience 0-10. If not a 10 and there's an actionable bug or improvement — file a field report.
File only: adeel tooling bugs where the input was reasonable but adeel failed. Skip: user app bugs, network errors, auth failures on user's site.
To file: write $HOME/.adeel/contributor-logs/{slug}.md:
# {Title}
**What I tried:** {action} | **What happened:** {result} | **Rating:** {0-10}
## Repro
1. {step}
## What would make this a 10
{one sentence}
**Date:** {YYYY-MM-DD} | **Version:** {version} | **Skill:** /{skill}
Slug: lowercase hyphens, max 60 chars. Skip if exists. Max 3/session. File inline, don't stop.
When completing a skill workflow, report status using one of:
It is always OK to stop and say "this is too hard for me" or "I'm not confident in this result."
Bad work is worse than no work. You will not be penalized for escalating.
Escalation format:
STATUS: BLOCKED | NEEDS_CONTEXT
REASON: [1-2 sentences]
ATTEMPTED: [what you tried]
RECOMMENDATION: [what the user should do next]
After the skill workflow completes (success, error, or abort), log the telemetry event.
Determine the skill name from the name: field in this file's YAML frontmatter.
Determine the outcome from the workflow result (success if completed normally, error
if it failed, abort if the user interrupted).
PLAN MODE EXCEPTION — ALWAYS RUN: This command writes telemetry to
$HOME/.adeel/analytics/ (user config directory, not project files). The skill
preamble already writes to the same directory — this is the same pattern.
Skipping this command loses session duration and outcome data.
Run this bash:
_TEL_END=$(date +%s)
_TEL_DUR=$(( _TEL_END - _TEL_START ))
rm -f $HOME/.adeel/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true
--skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \
--used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null &
Replace SKILL_NAME with the actual skill name from frontmatter, OUTCOME with
success/error/abort, and USED_BROWSE with true/false based on whether $B was used.
If you cannot determine the outcome, use "unknown". This runs in the background and
never blocks the user.
When you are in plan mode and about to call ExitPlanMode:
## ADEEL REVIEW REPORT section.```bash ${CLAUDE_PLUGIN_ROOT}/bin/adeel-review-read ```
Then write a ## ADEEL REVIEW REPORT section to the end of the plan file:
---CONFIG---): format the
standard report table with runs/status/findings per skill, same format as the review
skills use.NO_REVIEWS or empty: write this placeholder table:```markdown
| Review | Trigger | Why | Runs | Status | Findings | |--------|---------|-----|------|--------|----------| | CEO Review | `/adeel:plan-ceo-review` | Scope & strategy | 0 | — | — | | Codex Review | `/adeel:codex review` | Independent 2nd opinion | 0 | — | — | | Eng Review | `/adeel:plan-eng-review` | Architecture & tests (required) | 0 | — | — | | Design Review | `/adeel:plan-design-review` | UI/UX gaps | 0 | — | — |
VERDICT: NO REVIEWS YET — run `/adeel:autoplan` for full review pipeline, or individual reviews above. ```
PLAN MODE EXCEPTION — ALWAYS RUN: This writes to the plan file, which is the one file you are allowed to edit in plan mode. The plan file review report is part of the plan's living status.
Persistent headless Chromium. First call auto-starts (~3s), then ~100ms per command. State persists between calls (cookies, tabs, login sessions).
_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
B=""
[ -n "$_ROOT" ] && [ -x "$_ROOT/.claude/skills/adeel/browse/dist/browse" ] && B="$_ROOT/.claude/skills/adeel/browse/dist/browse"
[ -z "$B" ] && B=${CLAUDE_PLUGIN_ROOT}/browse/dist/browse
if [ -x "$B" ]; then
echo "READY: $B"
else
echo "NEEDS_SETUP"
fi
If NEEDS_SETUP:
cd <SKILL_DIR> && ./setupbun is not installed: curl -fsSL https://bun.sh/install | bash$B goto https://yourapp.com
$B text # content loads?
$B console # JS errors?
$B network # failed requests?
$B is visible ".main-content" # key elements present?
$B goto https://app.com/login
$B snapshot -i # see all interactive elements
$B fill @e3 "[email protected]"
$B fill @e4 "password"
$B click @e5 # submit
$B snapshot -D # diff: what changed after submit?
$B is visible ".dashboard" # success state present?
$B snapshot # baseline
$B click @e3 # do something
$B snapshot -D # unified diff shows exactly what changed
$B snapshot -i -a -o /tmp/annotated.png # labeled screenshot
$B screenshot /tmp/bug.png # plain screenshot
$B console # error log
$B snapshot -C # finds divs with cursor:pointer, onclick, tabindex
$B click @c1 # interact with them
$B is visible ".modal"
$B is enabled "#submit-btn"
$B is disabled "#submit-btn"
$B is checked "#agree-checkbox"
$B is editable "#name-field"
$B is focused "#search-input"
$B js "document.body.textContent.includes('Success')"
$B responsive /tmp/layout # mobile + tablet + desktop screenshots
$B viewport 375x812 # or set specific viewport
$B screenshot /tmp/mobile.png
$B upload "#file-input" /path/to/file.pdf
$B is visible ".upload-success"
$B dialog-accept "yes" # set up handler
$B click "#delete-button" # trigger dialog
$B dialog # see what appeared
$B snapshot -D # verify deletion happened
$B diff https://staging.app.com https://prod.app.com
After $B screenshot, $B snapshot -a -o, or $B responsive, always use the Read tool on the output PNG(s) so the user can see them. Without this, screenshots are invisible.
When you hit something you can't handle in headless mode (CAPTCHA, complex auth, multi-factor login), hand off to the user:
# 1. Open a visible Chrome at the current page
$B handoff "Stuck on CAPTCHA at login page"
# 2. Tell the user what happened (via AskUserQuestion)
# "I've opened Chrome at the login page. Please solve the CAPTCHA
# and let me know when you're done."
# 3. When user says "done", re-snapshot and continue
$B resume
When to use handoff:
The browser preserves all state (cookies, localStorage, tabs) across the handoff.
After resume, you get a fresh snapshot of wherever the user left off.
The snapshot is your primary tool for understanding and interacting with pages.
-i --interactive Interactive elements only (buttons, links, inputs) with @e refs
-c --compact Compact (no empty structural nodes)
-d <N> --depth Limit tree depth (0 = root only, default: unlimited)
-s <sel> --selector Scope to CSS selector
-D --diff Unified diff against previous snapshot (first call stores baseline)
-a --annotate Annotated screenshot with red overlay boxes and ref labels
-o <path> --output Output path for annotated screenshot (default: <temp>/browse-annotated.png)
-C --cursor-interactive Cursor-interactive elements (@c refs — divs with pointer, onclick)
All flags can be combined freely. -o only applies when -a is also used.
Example: $B snapshot -i -a -C -o /tmp/annotated.png
Ref numbering: @e refs are assigned sequentially (@e1, @e2, ...) in tree order.
@c refs from -C are numbered separately (@c1, @c2, ...).
After snapshot, use @refs as selectors in any command:
$B click @e3 $B fill @e4 "value" $B hover @e1
$B html @e2 $B css @e5 "color" $B attrs @e6
$B click @c1 # cursor-interactive ref (from -C)
Output format: indented accessibility tree with @ref IDs, one element per line.
@e1 [heading] "Welcome" [level=1]
@e2 [textbox] "Email"
@e3 [button] "Submit"
Refs are invalidated on navigation — run snapshot again after goto.
| Command | Description |
|---------|-------------|
| back | History back |
| forward | History forward |
| goto <url> | Navigate to URL |
| reload | Reload page |
| url | Print current URL |
| Command | Description |
|---------|-------------|
| accessibility | Full ARIA tree |
| forms | Form fields as JSON |
| html [selector] | innerHTML of selector (throws if not found), or full page HTML if no selector given |
| links | All links as "text → href" |
| text | Cleaned page text |
| Command | Description |
|---------|-------------|
| click <sel> | Click element |
| cookie <name>=<value> | Set cookie on current page domain |
| cookie-import <json> | Import cookies from JSON file |
| cookie-import-browser [browser] [--domain d] | Import cookies from installed Chromium browsers (opens picker, or use --domain for direct import) |
| dialog-accept [text] | Auto-accept next alert/confirm/prompt. Optional text is sent as the prompt response |
| dialog-dismiss | Auto-dismiss next dialog |
| fill <sel> <val> | Fill input |
| header <name>:<value> | Set custom request header (colon-separated, sensitive values auto-redacted) |
| hover <sel> | Hover element |
| press <key> | Press key — Enter, Tab, Escape, ArrowUp/Down/Left/Right, Backspace, Delete, Home, End, PageUp, PageDown, or modifiers like Shift+Enter |
| scroll [sel] | Scroll element into view, or scroll to page bottom if no selector |
| select <sel> <val> | Select dropdown option by value, label, or visible text |
| type <text> | Type into focused element |
| upload <sel> <file> [file2...] | Upload file(s) |
| useragent <string> | Set user agent |
| viewport <WxH> | Set viewport size |
| wait <sel|--networkidle|--load> | Wait for element, network idle, or page load (timeout: 15s) |
| Command | Description |
|---------|-------------|
| attrs <sel|@ref> | Element attributes as JSON |
| console [--clear|--errors] | Console messages (--errors filters to error/warning) |
| cookies | All cookies as JSON |
| css <sel> <prop> | Computed CSS value |
| dialog [--clear] | Dialog messages |
| eval <file> | Run JavaScript from file and return result as string (path must be under /tmp or cwd) |
| is <prop> <sel> | State check (visible/hidden/enabled/disabled/checked/editable/focused) |
| js <expr> | Run JavaScript expression and return result as string |
| network [--clear] | Network requests |
| perf | Page load timings |
| storage [set k v] | Read all localStorage + sessionStorage as JSON, or set <key> <value> to write localStorage |
| Command | Description |
|---------|-------------|
| diff <url1> <url2> | Text diff between pages |
| pdf [path] | Save as PDF |
| responsive [prefix] | Screenshots at mobile (375x812), tablet (768x1024), desktop (1280x720). Saves as {prefix}-mobile.png etc. |
| screenshot [--viewport] [--clip x,y,w,h] [selector|@ref] [path] | Save screenshot (supports element crop via CSS/@ref, --clip region, --viewport) |
| Command | Description |
|---------|-------------|
| snapshot [flags] | Accessibility tree with @e refs for element selection. Flags: -i interactive only, -c compact, -d N depth limit, -s sel scope, -D diff vs previous, -a annotated screenshot, -o path output, -C cursor-interactive @c refs |
| Command | Description |
|---------|-------------|
| chain | Run commands from JSON stdin. Format: [["cmd","arg1",...],...] |
| frame <sel|@ref|--name n|--url pattern|main> | Switch to iframe context (or main to return) |
| inbox [--clear] | List messages from sidebar scout inbox |
| watch [stop] | Passive observation — periodic snapshots while user browses |
| Command | Description |
|---------|-------------|
| closetab [id] | Close tab |
| newtab [url] | Open new tab |
| tab <id> | Switch to tab |
| tabs | List open tabs |
| Command | Description |
|---------|-------------|
| connect | Launch headed Chromium with Chrome extension |
| disconnect | Disconnect headed browser, return to headless mode |
| focus [@ref] | Bring headed browser window to foreground (macOS) |
| handoff [message] | Open visible Chrome at current page for user takeover |
| restart | Restart server |
| resume | Re-snapshot after user takeover, return control to AI |
| state save|load <name> | Save/load browser state (cookies + URLs) |
| status | Health check |
| stop | Shutdown server |
data-ai
MANUAL TRIGGER ONLY: invoke only when user types /unfreeze. Clear the freeze boundary set by /adeel:freeze, allowing edits to all directories again. Use when you want to widen edit scope without ending the session. Use when asked to "unfreeze", "unlock edits", "remove freeze", or "allow all edits".
development
MANUAL TRIGGER ONLY: invoke only when user types /ship. Ship workflow: detect + merge base branch, run tests, review diff, bump VERSION, update CHANGELOG, commit, push, create PR. Use when asked to "ship", "deploy", "push to main", "create a PR", or "merge and push". Proactively suggest when the user says code is ready or asks about deploying.
testing
MANUAL TRIGGER ONLY: invoke only when user types /setup-deploy. Configure deployment settings for /adeel:land-and-deploy. Detects your deploy platform (Fly.io, Render, Vercel, Netlify, Heroku, GitHub Actions, custom), production URL, health check endpoints, and deploy status commands. Writes the configuration to CLAUDE.md so all future deploys are automatic. Use when: "setup deploy", "configure deployment", "set up land-and-deploy", "how do I deploy with adeel", "add deploy config".
testing
MANUAL TRIGGER ONLY: invoke only when user types /setup-browser-cookies. Import cookies from your real Chromium browser into the headless browse session. Opens an interactive picker UI where you select which cookie domains to import. Use before QA testing authenticated pages. Use when asked to "import cookies", "login to the site", or "authenticate the browser".