--- name: chrome-session description: Use this skill to drive a real Chrome instance that is signed in to Oleg's accounts (LinkedIn, Gmail, GitHub, HR platforms, etc.) via the chrome-devtools MCP. The browser is launched against a forked copy of his Chrome profile, so authenticated pages, cookies, and saved sessions are available — letting you read job postings on auth-walled HR platforms, scrape logged-in LinkedIn feeds, inspect application-form fields, and pull data from any site where Oleg is already signed in. Also use this skill as the automatic fallback when WebFetch/curl/standard tools fail to retrieve a page (bot blocking, 403, JS-only render, login wall). --- # Chrome Session This skill operates a real Chrome browser, attached via the `chrome-devtools` MCP, using a local copy of Oleg's signed-in Chrome profile. It exists so you can fetch information from services where Oleg is logged in — without him having to copy-paste content manually. ## When to use Use this skill when any of the following apply: - The user asks you to open / read / search something on **LinkedIn** (feed, profiles, jobs, messages, notifications). - The user asks to read **job descriptions** or **application forms** on HR platforms (Greenhouse, Lever, Workable, Ashby, SmartRecruiters, Workday, LinkedIn Jobs, Wellfound, YC Work at a Startup, company career pages, etc.). - The user asks to **search for jobs** by criteria, scroll a feed, or pull a list of postings. - Any page is **gated by login**, and Oleg is likely already signed in (Gmail, GitHub private repos, Notion, Trello UI, Slack web, etc.). - A **standard fetch tool failed** — WebFetch returned bot-blocking errors, 401/403, empty body because JS isn't executed, captcha, "please log in", or similar. In that case switch to this skill immediately, without asking for confirmation. Do NOT use this skill for: - Pages reachable anonymously where WebFetch works fine. - Anything destructive (don't submit forms, don't send messages) unless the user explicitly asked. ## Setup overview - **Launcher script**: `scripts/launch-chrome.sh` — starts Chrome with `--remote-debugging-port=9222` against the local profile copy at `.chrome/`. The script is idempotent: if Chrome is already running it exits immediately. - **Profile copy**: `.chrome/` is a fork of Oleg's `Profile 1` (the `usulpro@gmail.com` profile). Logged-in cookies live here. The directory is gitignored. - **MCP server**: `chrome-devtools` is configured in `.mcp.json` with `--browserUrl=http://127.0.0.1:9222`, i.e. it attaches to the already-running Chrome rather than launching its own. ## Standard workflow ### 1. Ensure Chrome is running and attached Before any browser action, confirm the debug port is live, and launch Chrome automatically if it isn't: ```bash curl -sf http://127.0.0.1:9222/json/version >/dev/null && echo OK || echo DOWN ``` **If `DOWN`** — launch the script in the background and poll until Chrome is ready: ```bash # Launch Chrome in background (script uses exec, so backgrounding the script is correct) nohup /projects/my-projects/cv-2026/scripts/launch-chrome.sh >/tmp/chrome-launch.log 2>&1 & # Poll up to 15 seconds for i in $(seq 1 15); do sleep 1 curl -sf http://127.0.0.1:9222/json/version >/dev/null && echo "Chrome up after ${i}s" && break echo "Waiting... ${i}s" done curl -sf http://127.0.0.1:9222/json/version >/dev/null || echo "FAILED" ``` If Chrome does not respond after 15 seconds, stop and report: > Chrome не запускается. Посмотри `/tmp/chrome-launch.log` и проверь, что `.chrome/` существует. If the launcher prints `Error: .chrome/ does not exist`, stop immediately — there is no profile to use and you cannot create one. Do NOT try to sign Oleg in, copy profiles, or work around a missing `.chrome/` directory. ### 2. Open / select the right page - Use `mcp__chrome-devtools__list_pages` to see open tabs. - Use `mcp__chrome-devtools__new_page` with the target URL to open a new tab. If navigation times out (10s default), it's usually fine — the tab still opens; check `list_pages` to confirm the final URL. - Use `mcp__chrome-devtools__select_page` to switch context if you need to drive a tab other than the selected one. ### 3. Verify the page actually loaded what was expected After navigation, **always** confirm you landed on the expected URL/state before extracting content. Check: - Final URL — if LinkedIn redirected `/feed/` → `/login` or `/checkpoint/...`, Oleg is not signed in on that site. - A small `evaluate_script` to check `document.title` / a known DOM marker. If you detect a login wall, a "session expired" page, a captcha, or any other "you are not signed in" state — **stop and tell Oleg immediately**: > Не получается открыть `` — похоже, я не залогинен (``). Залогинься в этом Chrome-инстансе вручную и скажи, когда готово. Same rule for soft-fail cases (bot wall, "are you human?", 429, sudden empty body). ### 4. Extract content Prefer `evaluate_script` for structured extraction. Snapshots (`take_snapshot`) give you the a11y tree with uids and are useful for finding clickable elements and form fields. #### Scrolling — important detail for LinkedIn and similar SPAs `window.scrollTo` / `PageDown` / `End` often do **nothing** because the real scroll container is an inner element (LinkedIn uses `
`; some apps use a div with custom `overflow:auto`). To trigger lazy loading: 1. Find the scrollable container: ```js const scrollables = [...document.querySelectorAll('*')].filter(el => { const s = getComputedStyle(el); return (s.overflowY === 'auto' || s.overflowY === 'scroll') && el.scrollHeight > el.clientHeight + 50; }); ``` 2. Increment its `scrollTop` in steps (e.g. +1200px), dispatch a `wheel` event for safety, and `await` ~700-900ms between steps so lazy chunks can fetch. 3. Re-check `scrollHeight` after each step — it grows as new content streams in. For LinkedIn feed specifically: the container is `
`, posts are marked by `

` with text `"Feed post"`, and the post container is the nearest ancestor whose `innerText.length` exceeds a few hundred characters. #### Form-field inspection (job applications, etc.) When the user asks "what fields does this application form want?", use `take_snapshot` to enumerate inputs by their accessibility labels — that maps cleanly to "Name / Email / Resume / Cover letter / Custom question 1 / ...". Don't fill or submit anything unless Oleg explicitly tells you to. ### 5. Be efficient with content - Cap extracted text per item (e.g. `.slice(0, 2500)`) — LinkedIn posts and job descriptions can be very long and you'll usually summarize anyway. - Deduplicate by container element when scanning lists. - Return data as JSON from `evaluate_script` — it's structured and easy to summarize afterwards. ## Failure modes and how to react | Symptom | Likely cause | Action | |---------|--------------|--------| | `curl http://127.0.0.1:9222/json/version` fails | Chrome not launched with debug port | Run `./scripts/launch-chrome.sh` in background and poll (see step 1) | | `new_page` times out but the tab appears in `list_pages` | Slow site / heavy SPA | Continue — check the final URL via `list_pages` | | Final URL is `/login`, `/checkpoint`, `/signin` | Not signed in | **Stop. Tell Oleg.** Don't try to log in for him. | | Feed/list shows only the first few items | Lazy loading hasn't fired | Scroll the inner container, not `window` | | `evaluate_script` returns `count: 0` for known content | Wrong selectors (sites change markup) | Dump `document.body.innerText.slice(0, 500)` and a structural probe before guessing more selectors | | Captcha / "verify you're human" / 429 | Bot detection or rate limit | Stop. Tell Oleg. Don't retry in a loop. | ## Don'ts - **Don't launch Chrome interactively** — always use `nohup ... &` so it detaches from the terminal. - **Don't try to sign Oleg in** when you hit a login wall — just report and wait. - **Don't submit forms, click "Apply", send messages, or post anything** unless explicitly instructed. - **Don't loop on bot-detection pages** — one detection means stop. - **Don't commit anything from `.chrome/`** — it's gitignored, keep it that way. - **Don't write secrets, cookies, or tokens** anywhere outside the running browser. If `evaluate_script` returns sensitive data, summarize, don't echo it back verbatim. ## Example: "read first N LinkedIn posts" 1. `curl -sf http://127.0.0.1:9222/json/version` → if DOWN, launch via `nohup ./scripts/launch-chrome.sh &` and poll until up. 2. `list_pages` — is LinkedIn already open? If not, `new_page(https://www.linkedin.com/feed/)`. 3. Verify final URL contains `/feed/` and not `/login`. 4. `evaluate_script`: find `
`, step `scrollTop += 1200` with 800ms waits until enough `"Feed post"` h2s have appeared. 5. `evaluate_script`: extract `{actor, text}` for the first N posts (slice text to ~2500 chars). 6. Summarize in Russian to Oleg. ## Example: "what fields does this Greenhouse form want?" 1. `new_page()`. 2. `take_snapshot` — read the accessibility tree, list every `textbox`, `combobox`, `radio`, `file upload`, `checkbox` with its label. 3. Report the fields grouped: required vs optional (snapshot exposes `required` state), grouped by section if the form has fieldsets. 4. Do not fill or submit. Wait for Oleg's instruction.