From dfrysinger-skills
Read websites that need a login. Use when a URL is behind SSO/MFA/auth that the agent can't do itself — internal repos, wikis, dashboards, vendor portals. User logs in once, agent browses after.
How this skill is triggered — by the user, by Claude, or both
Slash command
/dfrysinger-skills:authenticated-browseThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
A two-phase protocol for letting the user hand off an authenticated browser session to the agent.
A two-phase protocol for letting the user hand off an authenticated browser session to the agent.
The profile is reusable across sessions until cookies expire or the user runs purge.
When the user pastes a URL alongside a navigational or page-understanding question, treat that as a strong signal they want help with the rendered UI of that specific URL, not a programmatic call against the same backend. Default to this skill in those cases, even when an alternative tool (API CLI, MCP, REST endpoint) could technically answer.
The principle: respect the view the user pointed at. They pasted the URL because they're looking at that page; substituting a different view (API JSON, CLI output, GraphQL response) is changing the question.
Trigger phrases (URL + any of):
Also invoke when:
web_fetch / curl probe on the URL returns HTTP 401 / 403 / 404 / a login redirect / a "this page is private" body when the user expected real contentgh / aws / jira / etc. against this". Use the appropriate CLI / MCP / REST tool instead.web_fetch can already read cleanly - use web_fetch.When the user has pasted a URL with a navigational question, do not silently call an equivalent API/CLI/MCP first to "check" whether you can answer without the browser. Examples of the wrong pattern:
github.com/... URL → don't first run gh api repos/<owner>/<repo>aws describe-somethingThat kind of probe burns time and produces a different answer (structured backend data) than what the user is looking at (rendered UI). Go straight to Step 1 below.
Only reach for an alternative tool if the user explicitly redirects you ("actually just use the API" / "use gh") or if this skill's auth phase fails (e.g. bot-detection blocks Playwright).
Playwright (chromium.launchPersistentContext) in headed mode for auth, headless for browse. Cookies / IndexedDB / localStorage / service workers persist in the profile directory so the same login is reused on subsequent commands.
This skill does not store passwords, tokens, or credentials anywhere itself - it stores only whatever the site's own auth flow writes into the browser profile (typically session cookies).
The skill ships two files alongside this SKILL.md:
pw-session.sh - wrapper that lazily installs Playwright + Chromium into a user cache dir (~/.cache/copilot-skills/authenticated-browse/) on first runpw-session.mjs - Node script that implements the subcommandsLocate the wrapper before each command run:
PW="$(find "$HOME/.copilot/installed-plugins" -maxdepth 7 -type f -name pw-session.sh -path '*authenticated-browse*' 2>/dev/null | head -1)"
[ -x "$PW" ] || { echo "authenticated-browse skill not found on disk"; exit 1; }
All subsequent invocations call bash "$PW" <subcommand> ....
Before touching the browser, confirm with the user (use ask_user):
Profile name is optional. A shared default profile is used if none is given, which keeps the UX clean for one-off browses. Only ask the user for a profile name if any of these apply:
default for unrelated browsesdefaultIf you do prompt, suggest a slug derived from the URL hostname (e.g. corp-wiki, vendor-portal). Allowed chars: letters, digits, ., -, _.
Show the user the cache directory path (~/.cache/copilot-skills/authenticated-browse/profiles/<profile>/) and note that closing the browser saves the session.
Check whether the chosen profile already exists:
bash "$PW" list-profiles | grep -Fxq "<profile>" && echo "exists" || echo "new"
(Use default as the profile name if you didn't prompt in Step 1.)
If the profile is new, or if a prior fetch returned an obvious "please log in" page, run the auth phase:
# With explicit profile:
bash "$PW" auth <profile> <url>
# Or, using the default profile (omit the profile arg entirely):
bash "$PW" auth <url>
This blocks until the user closes the browser window. In Copilot CLI, run it with a long initial_wait (e.g. 600s) and be ready to read remaining output via read_bash if the user takes longer. Do not background-detach this; the user needs the foreground window and the agent needs the exit signal.
On exit, the command prints a single JSON line on stdout: {"profile":"...","profile_dir":"...","last_url":"..."}. Use last_url as a strong hint for where the user wants you to start browsing - they likely navigated to it deliberately.
If the profile already exists and a sample fetch returns logged-in content, skip the auth phase entirely. Re-auth only when needed.
Use these subcommands as needed. In every case, the [profile] argument is optional - omit it (or just pass the URL as the first arg) to use the shared default profile.
| Command | Purpose |
|---|---|
bash "$PW" fetch [profile] <url> text | Rendered visible text (document.body.innerText). Default starting point for "summarize this page". |
bash "$PW" fetch [profile] <url> html | Full rendered HTML after JS hydration. Use when you need attributes, structure, or hidden elements. |
bash "$PW" fetch [profile] <url> links | JSON array of {text, href} for every <a href>. Use to discover navigation. |
bash "$PW" fetch [profile] <url> title | Page <title>. Cheap probe to detect login redirects. |
bash "$PW" screenshot [profile] <url> [out.png] | Full-page PNG. Prints the output path on stdout. Use when the visual layout matters or text extraction is insufficient. |
bash "$PW" eval [profile] <url> '<jsExpr>' | Run a single JS expression in page context; result printed as JSON. Use sparingly for structured extraction (e.g. Array.from(document.querySelectorAll('.row')).map(r => r.innerText)). |
The wrapper distinguishes profile from URL by detecting http(s):// on the first positional arg, so fetch https://example.com text works exactly like fetch default https://example.com text.
Tips:
title or a small text fetch first when re-using a profile, to confirm you're still logged in. If you see a login page, drop back to Step 2.head, wc -l, or save to a temp file and then grep/view it.networkidle wait may legitimately time out on long-polling SPAs; the helper prints a warning to stderr and proceeds with the current DOM. That's usually fine - re-run with a more specific selector via eval if you need to wait for a particular element.Summarize findings to the user. Mention:
~/.cache/copilot-skills/authenticated-browse/profiles/<profile>/bash "$PW" purge [profile] deletes that profile's persisted data (defaults to the default profile if omitted)Do not delete the profile automatically - re-auth is annoying. Only purge when the user asks.
~/.cache/copilot-skills/authenticated-browse/profiles/<profile>/. Nothing is uploaded by this skill.fetch text - visible page text, which can include account info, names, internal datafetch html - full DOM, which may include hidden fields, signed URLs, tokens embedded in markup or data-* attributesfetch links - href values, which may include signed URLs and one-time access tokensscreenshot - pixel content of the page (browser chrome is not captured)eval - whatever the JS expression returns, including localStorage values if you read themeval runs arbitrary authenticated JavaScript in the page - use read-only expressions, never mutations or POSTs.purge deletes the local profile data only. It does not revoke server-side sessions or tokens issued during auth. If the user wants to invalidate the session, they must sign out via the site itself first, then purge.node or npm not on PATH - wrapper exits with code 127 and a clear message. Tell the user to install Node 18+.HTTP_PROXY / HTTPS_PROXY env vars or npm config set registry.fetch returns the login page after a previously-good auth - cookies expired, or the site uses sessionStorage (which does not persist across browser close - only cookies, localStorage, IndexedDB, and service workers do). Re-run auth. If the site relies on sessionStorage, every browse session needs a fresh interactive auth.fetch redirects to login but headed auth worked fine - some SSO / device-trust / bot-detection systems treat headless Chromium differently from headed. Workaround: rerun auth against the target page, navigate to it manually so it's the last_url, then immediately fetch. If that still fails, the site is incompatible with this skill in headless mode.Profile "..." is already in use by another command - wrapper exits with code 75. A previous auth or fetch against the same profile is still running. Wait for it to finish, or use a different profile name.auth step is exactly where the user handles this. If fetch later still hits an interstitial, re-run auth and have the user dismiss it.This skill explicitly does not:
npx claudepluginhub dfrysinger/skillsProvides behavioral guidelines to reduce common LLM coding mistakes, focusing on simplicity, surgical changes, assumption surfacing, and verifiable success criteria.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.