149 lines
9.4 KiB
Markdown
149 lines
9.4 KiB
Markdown
---
|
||
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**:
|
||
|
||
> Не получается открыть `<URL>` — похоже, я не залогинен (`<observed state>`). Залогинься в этом 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 `<main>`; 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 `<main>`, posts are marked by `<h2>` 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 `<main>`, 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(<job-application-url>)`.
|
||
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.
|