{"id":51407934,"url":"https://github.com/jpoindexter/browser-session","last_synced_at":"2026-07-04T12:30:21.456Z","repository":{"id":364757200,"uuid":"1269091891","full_name":"jpoindexter/browser-session","owner":"jpoindexter","description":"Reuse your real logged-in browser session from Node — read+decrypt Brave/Chrome/Edge cookies (zero deps) and open any login-walled page in a headless browser with that session.","archived":false,"fork":false,"pushed_at":"2026-06-14T10:00:23.000Z","size":24,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-14T12:04:46.530Z","etag":null,"topics":["brave","browser-cookies","chrome-cookies","cli","cookies","cookies-from-browser","headless-browser","macos","nodejs","playwright","session","typescript","web-scraping","zero-dependency"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jpoindexter.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-14T09:49:32.000Z","updated_at":"2026-06-14T10:00:27.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/jpoindexter/browser-session","commit_stats":null,"previous_names":["jpoindexter/browser-session"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/jpoindexter/browser-session","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jpoindexter%2Fbrowser-session","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jpoindexter%2Fbrowser-session/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jpoindexter%2Fbrowser-session/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jpoindexter%2Fbrowser-session/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jpoindexter","download_url":"https://codeload.github.com/jpoindexter/browser-session/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jpoindexter%2Fbrowser-session/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35122497,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-04T02:00:05.987Z","response_time":113,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["brave","browser-cookies","chrome-cookies","cli","cookies","cookies-from-browser","headless-browser","macos","nodejs","playwright","session","typescript","web-scraping","zero-dependency"],"created_at":"2026-07-04T12:30:19.824Z","updated_at":"2026-07-04T12:30:21.450Z","avatar_url":"https://github.com/jpoindexter.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# browser-session\n\n[![CI](https://github.com/jpoindexter/browser-session/actions/workflows/ci.yml/badge.svg)](https://github.com/jpoindexter/browser-session/actions/workflows/ci.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n[![Node](https://img.shields.io/badge/node-%3E%3D18-green.svg)](https://nodejs.org)\n[![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178c6.svg)](https://www.typescriptlang.org/)\n\n**Give an AI agent (or any Node code) your real logged-in browser session.** So an agent can read your login-walled pages — X, Reddit, LinkedIn, internal dashboards — and act on what it finds, **without you pasting cookies**. Built for agent \"internet reach\"; works as a plain library too.\n\nThree things:\n\n1. **Read + decrypt your browser cookies** (Brave / Chrome / Edge) — **zero dependencies**. The Node equivalent of yt-dlp's `--cookies-from-browser`.\n2. **Open any page in a headless browser with that session** — read login-walled / JS-rendered pages a plain `fetch` can't (via Playwright).\n3. **YouTube reach** — video metadata + subtitle/transcript extraction via yt-dlp (`pip install yt-dlp`). No login needed for public videos.\n\nNo extension, no manual cookie export. Errors-as-values, never throws — drop any of these straight into an agent tool.\n\n### For agents\n\nWrap the calls as tools your agent can call:\n- `getCookies({ browser, domain })` → a cookie header it can attach to API calls.\n- `readPage(url, { browser })` → the rendered text of a logged-in page, for the model to read.\n- `fetchYouTubeInfo(url)` → title, channel, duration, description, view count.\n- `fetchYouTubeSubtitles(url)` → deduplicated plain-text transcript.\n\nThe agent reuses *your* session instead of needing its own logins or API keys. YouTube doesn't require login — just yt-dlp.\n\nA drop-in tool (any framework):\n\n```ts\nimport { readPage } from \"browser-session\";\n\nexport const browserReadTool = {\n  name: \"browser_read\",\n  description: \"Read any web page (incl. login-walled) using the user's logged-in browser session.\",\n  parameters: { type: \"object\", properties: { url: { type: \"string\" } }, required: [\"url\"] },\n  async run({ url }: { url: string }) {\n    const r = await readPage(url, { browser: \"brave\" });\n    return r.ok ? r.text.slice(0, 20000) : `failed: ${r.error}`;\n  },\n};\n```\n\n\u003e Sibling project: [`x-native`](https://github.com/jpoindexter/x-native) — a native X/Twitter GraphQL client (search, bookmarks) for agents, using the same cookie approach.\n\n\u003e ⚠️ **macOS only for now** (the cookie key lives in the login Keychain — one approval prompt the first time). The headless-read part is cross-platform; only the cookie auto-read is macOS-specific so far. Use a dedicated account for scraping — automated access can get accounts flagged.\n\n## Install\n\n```bash\nnpm install browser-session\n# for the headless-read part:\nnpm install playwright-core \u0026\u0026 npx playwright install chromium\n```\n\n## Use\n\n### Library\n\n```ts\nimport { getCookies, openWithSession, readPage } from \"browser-session\";\n\n// 1) just the cookies (zero-dep)\nconst c = getCookies({ browser: \"brave\", domain: \"x.com\" });\nif (c.ok) console.log(c.cookie); // \"auth_token=…; ct0=…; …\"\n\n// 2) read a logged-in page in a real browser, session auto-injected\nconst r = await readPage(\"https://www.reddit.com/\", { browser: \"brave\" });\nif (r.ok) console.log(r.text);            // rendered text of YOUR feed\n//        console.log(r.requests);        // every URL the page requested\n\n// 3) bring your own cookie (any source)\nawait openWithSession(\"https://x.com/i/bookmarks\", \"auth_token=…; ct0=…\");\n```\n\nEvery call returns `{ ok: true, … } | { ok: false, error }` — errors as values, never throws.\n\n### CLI\n\n```bash\nbrowser-session cookies x.com --browser brave          # print the cookie header\nbrowser-session cookies x.com --names                  # just the cookie names\nbrowser-session read https://www.reddit.com/ --browser brave   # render your logged-in page\nbrowser-session read https://example.com               # anonymous read\nbrowser-session youtube https://youtube.com/watch?v=… # title + transcript\nbrowser-session youtube https://youtube.com/watch?v=… --subs   # transcript only\nbrowser-session youtube https://youtube.com/watch?v=… --info   # metadata only\n```\n\nYouTube requires `yt-dlp`: `pip install yt-dlp`\n\n## How it works\n\n- **Cookies** (`src/cookies.ts`): pulls the AES key from the macOS Keychain (`\u003cBrowser\u003e Safe Storage`), derives it (`PBKDF2(pw, \"saltysalt\", 1003, 16, sha1)`), reads the Cookies SQLite (copied to dodge the browser lock via the system `sqlite3`), and AES-128-CBC-decrypts each value. Handles the `v10`/`v11` prefix **and** the 32-byte `SHA256(host)` prefix newer Chromium prepends (stripped when the decrypted head isn't printable). Zero runtime deps.\n- **Session** (`src/session.ts`): launches headless Chromium (Playwright), injects the cookies scoped to the page origin (so `__Host-`/`__Secure-` prefixes resolve), navigates, and returns the body text + the request URLs. `playwright-core` is an **optional** dependency — the cookie reader works without it.\n\n## API\n\n`getCookies({ browser?, domain, profile? })` · `decryptCookie(hex, key)` · `openWithSession(url, cookie, { settleMs? })` · `readPage(url, { browser?, profile? })` · `cookieToPlaywright(header, url)` · `domainOf(url)`.\n\n**YouTube:** `fetchYouTubeInfo(url)` → `{ok,info:{title,channel?,duration?,description?,uploadDate?,viewCount?,url}}` · `fetchYouTubeSubtitles(url)` → `{ok,subtitles}`.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjpoindexter%2Fbrowser-session","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjpoindexter%2Fbrowser-session","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjpoindexter%2Fbrowser-session/lists"}