[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-92234":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":13,"contributorsCount":13,"subscribersCount":13,"size":13,"stars1d":13,"stars7d":13,"stars30d":14,"stars90d":13,"forks30d":13,"starsTrendScore":13,"compositeScore":15,"rankGlobal":10,"rankLanguage":10,"license":16,"archived":17,"fork":17,"defaultBranch":18,"hasWiki":19,"hasPages":17,"topics":20,"createdAt":10,"pushedAt":10,"updatedAt":29,"readmeContent":30,"aiSummary":31,"trendingCount":13,"starSnapshotCount":13,"syncStatus":32,"lastSyncTime":33,"discoverSource":34},92234,"autoapplycv","tmwclaxton\u002Fautoapplycv","tmwclaxton","AutoCVApply - AI-powered CV parser and job application assistant with browser extension","https:\u002F\u002Fwww.autocvapply.com",null,"JavaScript",117,0,1,40.1,"Other",false,"main",true,[21,22,23,24,25,26,27,28],"apply","automation","cv","greenhouse","indeed","jobs","linkedin","workday","2026-07-22 04:02:05","\u003Cp align=\"center\">\n  \u003Cimg src=\"public\u002Ffavicon.svg\" alt=\"AutoCVApply\" width=\"72\" height=\"72\" \u002F>\n\u003C\u002Fp>\n\n\u003Ch1 align=\"center\">AutoCVApply\u003C\u002Fh1>\n\n\u003Cp align=\"center\">\n  \u003Cstrong>Upload once. Apply everywhere.\u003C\u002Fstrong>\u003Cbr \u002F>\n  Stop retyping your life story into every job form.\n\u003C\u002Fp>\n\n\u003Cp align=\"center\">\n  \u003Ca href=\"https:\u002F\u002Fautocvapply.com\">\u003Cimg src=\"https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FWebsite-autocvapply.com-C8102E?style=for-the-badge\" alt=\"Website\" \u002F>\u003C\u002Fa>\n  \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Ftmwclaxton\u002Fautoapplycv\">\u003Cimg src=\"https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FGitHub-Source-181717?style=for-the-badge&logo=github&logoColor=white\" alt=\"GitHub\" \u002F>\u003C\u002Fa>\n\u003C\u002Fp>\n\n\u003Cp align=\"center\">\n  \u003Ca href=\"https:\u002F\u002Fautocvapply.com\">Website\u003C\u002Fa> ·\n  \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Ftmwclaxton\u002Fautoapplycv\">GitHub\u003C\u002Fa> ·\n  \u003Ca href=\"https:\u002F\u002Fautocvapply.com\u002Fhow-to\">How it works\u003C\u002Fa> ·\n  \u003Ca href=\"https:\u002F\u002Fautocvapply.com\u002Fdashboard\">Dashboard\u003C\u002Fa>\n\u003C\u002Fp>\n\n\u003Cp align=\"center\">\n  \u003Cimg src=\"https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FLaravel-13-FF2D20?logo=laravel&logoColor=white\" alt=\"Laravel 13\" \u002F>\n  \u003Cimg src=\"https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FVue-3-4FC08D?logo=vue.js&logoColor=white\" alt=\"Vue 3\" \u002F>\n  \u003Cimg src=\"https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FInertia-3-E644AD\" alt=\"Inertia v3\" \u002F>\n  \u003Cimg src=\"https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FPHP-8.5-777BB4?logo=php&logoColor=white\" alt=\"PHP 8.5\" \u002F>\n  \u003Cimg src=\"https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FChrome-MV3-4285F4?logo=googlechrome&logoColor=white\" alt=\"Chrome MV3\" \u002F>\n  \u003Cimg src=\"https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Fform%20corpus-4%2C604%20scenarios-2ea44f\" alt=\"4,604 form scenarios\" \u002F>\n  \u003Cimg src=\"https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FLicense-PolyForm%20Noncommercial%201.0.0-orange.svg\" alt=\"PolyForm Noncommercial License 1.0.0\" \u002F>\n\u003C\u002Fp>\n\n\u003Cp align=\"center\">\n  \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fautocvapply.com\">Sign up free at autocvapply.com\u003C\u002Fa>\u003C\u002Fstrong> - upload your CV, connect the extension, fill forms in minutes.\u003Cbr \u002F>\n  \u003Csub>\u003Cstrong>Job board Auto Apply:\u003C\u002Fstrong> LinkedIn, Indeed, Totaljobs, Glassdoor, and Reed - full end-to-end apply from the extension sidebar. On other ATS forms, you review every field and click Submit yourself.\u003C\u002Fsub>\n\u003C\u002Fp>\n\n---\n\n## Table of contents\n\n**Getting started**\n\n- [See it in action](#see-it-in-action)\n- [What is AutoCVApply?](#what-is-autocvapply)\n- [Without vs with AutoCVApply](#without-vs-with-autocvapply)\n- [Quick install](#quick-install)\n\n**Using AutoCVApply**\n\n- [How it works](#how-it-works)\n- [Job board Auto Apply](#job-board-auto-apply)\n- [Features](#features)\n- [Supported platforms](#supported-platforms)\n- [Postbox design](#postbox-design)\n- [Pricing](#pricing)\n- [Security & privacy](#security--privacy)\n- [Testing overview](#testing-overview)\n- [Job search tips](#job-search-tips)\n- [Links](#links)\n\n**For developers**\n\n- [Architecture](#architecture)\n- [Tech stack](#tech-stack)\n- [Project structure](#project-structure)\n- [Getting started (local dev)](#getting-started-local-dev)\n- [Extension bridge (MCP)](#extension-bridge-mcp)\n- [Form corpus quality engineering](#form-corpus-quality-engineering)\n- [Job board Auto Apply testing](#job-board-auto-apply-testing)\n- [Key commands](#key-commands)\n- [API](#api)\n- [Deployment](#deployment)\n- [Contributing](#contributing)\n- [License](#license)\n\n---\n\n## See it in action\n\n\u003C!-- TODO: replace with YouTube thumbnail + link once demo video is recorded -->\n\u003C!-- [![AutoCVApply demo](https:\u002F\u002Fimg.youtube.com\u002Fvi\u002FVIDEO_ID\u002Fmaxresdefault.jpg)](https:\u002F\u002Fyoutu.be\u002FVIDEO_ID) -->\n\n> **Demo video coming soon.** Until then, see [How it works on autocvapply.com](https:\u002F\u002Fautocvapply.com\u002Fhow-to) or sign up and try Draft All on a real Greenhouse or Ashby form.\n\n---\n\n## What is AutoCVApply?\n\nJob applications are a copy-paste endurance test. Workday wants your address. Greenhouse wants it again. Ashby wants a cover letter you've already written three times this week. Every ATS renders the same questions differently - custom widgets, shadow DOM, multi-step wizards, iframe embeds.\n\n**AutoCVApply** reads your CV once, builds a structured profile, and stamps it onto application forms through a battle-tested Chrome extension - so you spend time on roles that matter, not on retyping your phone number for the forty-seventh time.\n\n## Without vs with AutoCVApply\n\n| Without AutoCVApply | With AutoCVApply |\n|---------------------|------------------|\n| Retype contact details, education, and skills on every ATS | **One structured profile** - filled from your uploaded CV |\n| Blank stare at \"Why do you want this role?\" textareas | **Draft All** streams AI answers for free-text questions |\n| Skip cover letters because they're tedious | **One-click cover letter** tailored to the job description |\n| Same generic CV for every posting | **Tailored resume draft** matched to the role |\n| No idea how your CV reads against the JD | **ATS score** with keyword and formatting feedback |\n| Pray comboboxes and wizards don't break mid-form | **4,604-scenario test corpus** - Greenhouse, Ashby, Workday, UK job boards, and more |\n| Obvious bot-like paste fills that trip ATS checks | **Anti-bot detection** - character-by-character typing, natural pauses, human-like navigation on job board Auto Apply |\n| Click through Easy Apply one job at a time | **Job board Auto Apply** - LinkedIn, Indeed, Totaljobs, Glassdoor, and Reed from the extension sidebar |\n| Submit applications blindly | **You stay in control on ATS forms** - we fill fields; you review and submit |\n\n> **Job board Auto Apply** (LinkedIn, Indeed, Totaljobs, Glassdoor, Reed) runs end-to-end: search filtered jobs, open each posting, fill every step, and submit. More boards are on the way.\n\n## Who it's for\n\n- **Job seekers** applying across multiple ATS platforms in a week\n- **Career changers** who need tailored answers without rewriting everything\n- **Privacy-conscious applicants** who want a clear account, revocable API tokens, and a published [privacy policy](https:\u002F\u002Fautocvapply.com\u002Fprivacy)\n- **Developers and contributors** who care about verified form-fill quality, not just demo GIFs\n\n**Why choose AutoCVApply?**\n\n- **Save time** - autofill plus AI drafting for the questions that actually slow you down\n- **Stay honest** - answers draw from your profile and preferences, not invented credentials\n- **Sound human, not generic** - Draft All answers are scored on hundreds of scenarios to catch AI tropes, em dashes, and filler while staying grounded in your CV\n- **Trust the engineering** - four-layer fill verification, Playwright smoke tests, and 410 PHPUnit methods\n- **Avoid bot flags** - human-like typing, pauses between fields, and natural navigation on job board Auto Apply runs\n- **British Postbox UI** - Royal Mail red, navy, warm paper tones. Feels like sending a letter, not filling a spreadsheet\n\n## Quick install\n\n> **Recommended:** create a free account at **[autocvapply.com](https:\u002F\u002Fautocvapply.com)**, upload your CV, then download the extension from your dashboard. Chrome Web Store listing is not live yet - sideload via zip for now.\n\n### Production (autocvapply.com)\n\n1. **[Sign up](https:\u002F\u002Fautocvapply.com)** and upload your CV\n2. Open **Dashboard → Extension** and choose your browser\n3. Download the zip (Chrome, Edge, Brave, or Firefox)\n4. Sideload using the on-screen instructions\n5. Copy your connection JSON (`token` + `api_base`) into the extension sidebar\n\n| Browser | Install method | Store listing |\n|---------|----------------|---------------|\n| **Chrome \u002F Edge \u002F Brave** | Download zip from dashboard → Load unpacked | Chrome Web Store - *coming soon* |\n| **Firefox** | Download zip from dashboard → Load Temporary Add-on | Firefox Add-ons - *coming soon* |\n\n### From source (developers)\n\nClone this repo, run `composer run setup`, build the extension with `npm run build:extension`, then load `extension\u002Fdist\u002F` unpacked in Chrome. See [Getting started (local dev)](#getting-started-local-dev) for full setup.\n\n---\n\n## How it works\n\n```mermaid\nflowchart LR\n    A[\"📄 Upload CV\u003Cbr\u002F>PDF or Word\"] --> B[\"🤖 AI extraction\u003Cbr\u002F>NanoGPT\"]\n    B --> C[\"✏️ Review profile\u003Cbr\u002F>Dashboard\"]\n    C --> D[\"🔌 Connect extension\u003Cbr\u002F>API token\"]\n    D --> E[\"⚡ Autofill + Draft All\u003Cbr\u002F>Any ATS · any site\"]\n    D --> L[\"🤖 Auto Apply tab\u003Cbr\u002F>LinkedIn · Indeed · Totaljobs · Glassdoor · Reed\"]\n    E --> F[\"📝 Cover letters\u003Cbr\u002F>Tailored resumes · ATS score\"]\n    L --> F\n```\n\n| Step | What happens |\n|------|--------------|\n| **1. Post your CV** | Drop a PDF or DOCX. Tesseract OCR + NanoGPT extract name, contact, skills, experience, and education into a structured profile. |\n| **2. Check the details** | Tweak anything we missed - summary, visa status, salary expectations, application preferences. |\n| **3. Connect the extension** | Install the Chrome or Firefox extension, paste your connection JSON (`token` + `api_base`) from the dashboard. |\n| **4. Fill and draft** | Autofill fields on any job form. **Draft All** streams AI-written answers for free-text questions, cover letters, and tailored resumes. **You submit when ready.** |\n| **5. Auto Apply (job boards)** | Open the extension sidebar **Auto Apply** tab, pick LinkedIn, Indeed, Totaljobs, Glassdoor, or Reed, and let the extension search, open each job, fill every step, and submit. |\n\n## Job board Auto Apply\n\nFull end-to-end applications on **LinkedIn Easy Apply**, **Indeed Apply**, **Totaljobs Quick Apply**, **Glassdoor Easy Apply**, and **Reed Easy Apply** - not just field fill:\n\n| Step | What happens |\n|------|--------------|\n| **Search** | Extension runs job search with Easy Apply filters from the sidebar **Auto Apply** tab |\n| **Open** | Each matching job opens in a tab; listings without one-click apply are skipped |\n| **Fill** | Contact info, screening questions, resume steps, and multi-step wizards are filled from your profile |\n| **Submit** | Continue, review, and submit buttons advance and complete the application |\n\nOn Greenhouse, Ashby, Workday, and other ATS platforms, autofill and Draft All still work as before - **you review and click Submit yourself**. More full Auto Apply job boards are on the way.\n\n## Features\n\n| Module | Feature | What it does |\n|--------|---------|--------------|\n| **CV parsing** | PDF & Word upload | Structured profile extraction with Tesseract OCR + local `pdftoppm` preprocessing |\n| **CV parsing** | Editable profile | Skills, experience, education, summary, application preferences - you control the source of truth |\n| **Autofill** | One-click fill | Profile data stamped onto native inputs, comboboxes, radios, checkboxes, multi-step wizards |\n| **Autofill** | Anti-bot detection | Character-by-character typing on short fields, pauses between applies, hover-and-click navigation on job board Auto Apply |\n| **Autofill** | Shadow DOM & iframes | Content scripts traverse embedded ATS widgets other extensions miss |\n| **Auto Apply** | LinkedIn Easy Apply E2E | Sidebar **Auto Apply** tab: search, open jobs, fill steps, submit applications |\n| **Auto Apply** | Indeed Apply E2E | Indeed Apply search, contact\u002Fresume\u002Fquestions\u002Freview steps, submit via **Auto Apply** tab |\n| **Auto Apply** | Totaljobs Quick Apply E2E | Genesis Quick Apply search, fill, and submit from the sidebar |\n| **Auto Apply** | Glassdoor Easy Apply E2E | Host-page search plus Indeed Apply iframe steps where required |\n| **Auto Apply** | Reed Easy Apply E2E | Reed search, job detail, modal review, and submit |\n| **Application Assistant** | Field inventory | AI maps the page's questions to fillable refs before drafting |\n| **Application Assistant** | Job context | Extracts title, company, and description from the posting |\n| **Application Assistant** | Draft All | Streams batch answers for unanswered fields (NDJSON) |\n| **Application Assistant** | Draft field | Single-field AI answer on demand |\n| **Application Assistant** | Answer quality | Rubric scoring on AI drafts - human tone, profile grounding, banned AI phrases, no em dashes |\n| **Documents** | Cover letter | Job-specific letter from your profile + posting |\n| **Documents** | Tailored resume | Role-matched resume draft |\n| **Documents** | ATS score | Keyword and compatibility feedback against the job description |\n| **Dashboard** | Usage & billing | Monthly autofill allowance, extension connection, GoCardless subscriptions (UK) |\n| **Analytics** | Public aggregate stats | Daily totals across all users - no personal application history exposed |\n\n### Human-sounding AI drafts\n\nDraft All and Quick Answer use AI, but we score generated answers extensively before shipping changes - not just on a handful of demo forms.\n\n| What we check | Why it matters |\n|---------------|----------------|\n| **Grounding** | Real employers, roles, and skills from your profile - not invented credentials |\n| **Human tone** | Banned AI phrases and overused words (\"leverage\", \"proven track record\", \"I am thrilled to apply\") |\n| **Formatting** | No em dashes or markdown - plain text ready to paste into employer forms |\n| **Honesty** | Gaps acknowledged instead of fabricated experience |\n\nScoring runs across **190** profile-mapping scenarios, **124** answer-quality scenarios, **150** real ATS form E2E scoring fixtures, and Assist sidebar test cases. Developer audit commands: `answer-quality:audit`, `assist-answer-quality:audit`, `form-e2e:score` (see [`scripts\u002Fextension-benchmark\u002FREADME.md`](scripts\u002Fextension-benchmark\u002FREADME.md)).\n\n### Anti-bot detection\n\nJob sites increasingly flag instant paste fills and robotic click patterns. AutoCVApply fills like a person where it matters:\n\n| Behaviour | What it does |\n|-----------|--------------|\n| **Character-by-character typing** | Short text fields (names, emails, years, locations) type one keystroke at a time with realistic delays |\n| **Paced batch apply** | Draft All fills one field at a time with scroll, focus, and pause between each |\n| **Human-like job board navigation** | Smooth scroll, hover-before-click, and variable delays when opening apply flows and stepping through multi-page forms on LinkedIn, Indeed, Totaljobs, Glassdoor, and Reed |\n| **Smart fallbacks** | Long free-text answers still paste instantly so multi-paragraph drafts do not take minutes |\n\n## Supported platforms\n\nAutoCVApply works on most major ATS and employer career sites - including Workday, Greenhouse, Lever, Ashby, SmartRecruiters, and many more. Autofill is verified against real and synthetic fixtures on the platforms below:\n\n| Platform | Coverage | Notes |\n|----------|----------|-------|\n| **LinkedIn Easy Apply** | Full Auto Apply E2E | Search, open, fill, and submit via extension sidebar **Auto Apply** tab |\n| **Indeed Apply** | Full Auto Apply E2E | Indeed Apply search, contact\u002Fresume\u002Fquestions\u002Freview steps, submit via **Auto Apply** tab |\n| **Totaljobs Quick Apply** | Full Auto Apply E2E | Genesis Quick Apply search and multi-step submit from the sidebar |\n| **Glassdoor Easy Apply** | Full Auto Apply E2E | Job board search plus Indeed Apply iframe steps where required |\n| **Reed Easy Apply** | Full Auto Apply E2E | Reed search, job detail, modal review, and submit |\n| **Ashby** | Curated + smoke + widget checks | Yes\u002Fno and checkbox widget scenarios |\n| **Greenhouse** | Curated + smoke | Scraped real boards in corpus |\n| **Lever** | Curated + smoke | Multi-step apply flows |\n| **Workday** | Curated + smoke | Wizard-style applications |\n| **SmartRecruiters** | Curated + smoke | Long multi-section forms |\n| **Teamtailor** | Curated + smoke | Nordic\u002FEU hiring stacks |\n| **BambooHR** | Curated tier | HR suite apply pages |\n| **Trakstar** | Curated tier | Performance\u002Fhiring forms |\n| **WordPress \u002F WPForms** | Curated + smoke | Generic employer sites |\n| **Any site** | Extension runs on `\u003Call_urls>` | Deepest test coverage on ATS platforms above - not where we stop |\n\n## Postbox design\n\nBritish utilitarian UI - Royal Mail red, navy, warm paper tones. Built to feel like sending a letter, not filling in a spreadsheet.\n\n## Pricing\n\nPlans are based on **extension autofill** allowance. CV upload and profile editing are free on every plan.\n\n| Plan | Price | Autofills \u002F month |\n|------|-------|-------------------|\n| **Free** | £0 | 250 |\n| **Starter** | £7\u002Fmo | 2,500 |\n| **Pro** | £17\u002Fmo | 15,000 |\n\n> Each successfully filled form input uses one autofill. Allowances reset on the 1st of each month.\n\n## Security & privacy\n\n- **Your profile, your account** - CV and structured profile data live on autocvapply.com under WorkOS authentication. You can delete your account from settings.\n- **Extension fills locally** - the browser extension fetches your profile via a revocable Sanctum token and writes values into the page DOM. It does not send completed submissions back to us.\n- **No data selling** - we do not sell personal data. See the full [privacy policy](https:\u002F\u002Fautocvapply.com\u002Fprivacy).\n- **AI processing** - CV parsing and drafting send text to our NanoGPT provider as needed to extract fields or generate answers. Job context from the page may be included in draft requests.\n- **Source available** - PolyForm Noncommercial-licensed core. Inspect the extension, backend, and 4,604-scenario test corpus on GitHub. Free for personal and non-commercial use; commercial use requires permission.\n- **You submit on ATS forms** - autofill and Draft All never auto-click Submit on Greenhouse, Ashby, Workday, and similar sites. **Job board Auto Apply** (LinkedIn, Indeed, Totaljobs, Glassdoor, Reed) completes submissions end-to-end from the sidebar; more boards are on the way.\n\n## Testing overview\n\nAutoCVApply is verified by two complementary test pyramids - not a handful of smoke tests:\n\n| Pyramid | Scope | Fast tier (CI on every push) |\n|---------|-------|----------------------------|\n| **ATS form corpus** | 4,604 extraction scenarios (3,495 vetted), 124 curated fill-verify cases, 13 platform smoke fixtures | Curated JSDOM (70) + Playwright smoke |\n| **Job board Auto Apply** | 212 LinkedIn captures, 44 Indeed step fixtures, 1,100 synthetic job-board scenarios (Totaljobs, Glassdoor, Reed), unit + offline corpus tests | Unit tests + offline corpus |\n\nBoth pyramids enforce **100% pass rates** on critical tiers before merge. Full tier breakdowns, commands, and fixture layout are in [Form corpus quality engineering](#form-corpus-quality-engineering) and [Job board Auto Apply testing](#job-board-auto-apply-testing) below.\n\n## Job search tips\n\nPractical advice for high-volume applications (no magic numbers - your mileage varies):\n\n1. **Apply while the posting is fresh.** Early applicants often face less competition; automation helps you move faster without cutting corners on quality.\n2. **Keep your master CV ATS-simple.** Single column, standard headings, no graphics in the parse path. Use AutoCVApply's ATS score against the job description before you submit.\n3. **Tailor the narrative, not just keywords.** Draft All and cover letters work best when your profile summary and application preferences reflect what you actually want.\n4. **Review every field.** We fill aggressively; you confirm accuracy - especially salary, visa, and eligibility questions.\n5. **Track applications yourself for now.** Use your own spreadsheet or notes; a personal application tracker in the dashboard is on the roadmap.\n\n## Links\n\n| | |\n|---|---|\n| **Website** | [autocvapply.com](https:\u002F\u002Fautocvapply.com) |\n| **Dashboard** | [autocvapply.com\u002Fdashboard](https:\u002F\u002Fautocvapply.com\u002Fdashboard) |\n| **How it works** | [autocvapply.com\u002Fhow-to](https:\u002F\u002Fautocvapply.com\u002Fhow-to) |\n| **Analytics** | [autocvapply.com\u002Fanalytics](https:\u002F\u002Fautocvapply.com\u002Fanalytics) |\n| **Blog** | [autocvapply.com\u002Fblog](https:\u002F\u002Fautocvapply.com\u002Fblog) |\n| **Privacy** | [autocvapply.com\u002Fprivacy](https:\u002F\u002Fautocvapply.com\u002Fprivacy) |\n| **Contact** | [autocvapply.com\u002Fcontact](https:\u002F\u002Fautocvapply.com\u002Fcontact) |\n| **GitHub** | [github.com\u002Ftmwclaxton\u002Fautoapplycv](https:\u002F\u002Fgithub.com\u002Ftmwclaxton\u002Fautoapplycv) |\n| **Discord** | [discord.gg\u002FDqqqTv3Spt](https:\u002F\u002Fdiscord.gg\u002FDqqqTv3Spt) - extension help & community |\n| **Chrome Web Store** | *Listing not published yet - use dashboard zip download* |\n| **Form corpus docs** | [`scripts\u002Fform-corpus\u002FREADME.md`](scripts\u002Fform-corpus\u002FREADME.md) |\n| **Extension bridge (MCP)** | [`scripts\u002Fextension-bridge\u002FREADME.md`](scripts\u002Fextension-bridge\u002FREADME.md) |\n| **Job board automation playbook** | [`docs\u002Fplatform-automation-playbook.md`](docs\u002Fplatform-automation-playbook.md) |\n\n---\n\n## Architecture\n\n```mermaid\nflowchart TB\n    subgraph Browser[\"Chrome Extension (MV3)\"]\n        CS[\"Content scripts\u003Cbr\u002F>form-heuristics · field-inventory\"]\n        SP[\"Side panel\u003Cbr\u002F>assist · documents\"]\n        BG[\"Service worker\u003Cbr\u002F>connection · file transfer\"]\n    end\n\n    subgraph Laravel[\"Laravel 13 Backend\"]\n        WEB[\"Inertia + Vue dashboard\"]\n        API[\"Sanctum API\u003Cbr\u002F>profile · assist · autofill\"]\n        AI[\"NanoGPT\u003Cbr\u002F>CV parse · draft · score\"]\n        DB[(PostgreSQL)]\n    end\n\n    CS \u003C-->|\"field inventory\u003Cbr\u002F>apply answers\"| SP\n    SP -->|\"Bearer token\"| API\n    BG -->|\"Bearer token\"| API\n    WEB -->|\"WorkOS SSO\"| WEB\n    API --> AI\n    API --> DB\n```\n\n| Component | Role |\n|-----------|------|\n| `extension\u002Fsrc\u002Fcontent\u002F` | DOM heuristics, field inventory, iframe traversal, portal bar |\n| `extension\u002Fsrc\u002Fsidepanel\u002F` | Connection setup, Draft All UI, document uploads |\n| `app\u002FServices\u002FApplicationAssistantService.php` | Inventory, job context, streaming draft-all, cover letters |\n| `app\u002FServices\u002FCvParserService.php` | PDF\u002FWord ingestion, OCR, NanoGPT structured extraction |\n| `scripts\u002Fform-corpus\u002F` | Synthetic corpus generation, fill verification pyramid, E2E harness |\n\n## Tech stack\n\n| Layer | Technology |\n|-------|------------|\n| Backend | Laravel 13, PHP 8.5 |\n| Frontend | Inertia v3, Vue 3, Tailwind CSS v4 |\n| Auth | WorkOS (web), Laravel Sanctum (extension API) |\n| AI | NanoGPT (`gpt-4.1-mini`) - CV extraction, drafting, ATS scoring |\n| OCR | Tesseract + poppler (`pdftoppm`) locally; NanoGPT vision as fallback |\n| Payments | GoCardless (UK Direct Debit subscriptions) |\n| Extension | Chrome MV3 - content scripts, side panel, service worker |\n| Fill verification | JSDOM, Playwright, pixelmatch, Tesseract.js |\n| Routing | Laravel Wayfinder (typed TS route helpers) |\n\n## Project structure\n\n```\nautocvapply\u002F\n├── app\u002F\n│   ├── Http\u002FControllers\u002F       # Web + API + billing + webhooks\n│   ├── Models\u002F                 # User, CvProfile, CvUpload\n│   └── Services\u002F               # CV parser, Application Assistant, NanoGPT\n├── extension\u002F\n│   ├── src\u002Fcontent\u002F            # form-heuristics.js, field-inventory.js\n│   ├── src\u002Fsidepanel\u002F          # Connection, Draft All, documents\n│   └── dist\u002F                   # Built extension (load unpacked)\n├── scripts\u002Fform-corpus\u002F        # Corpus generation + fill verification pyramid\n├── scripts\u002Fextension-bridge\u002F   # Localhost bridge + MCP server for agent-driven Chrome\n├── resources\u002Fjs\u002F\n│   ├── pages\u002F                  # Inertia pages (Welcome, Dashboard, Billing…)\n│   └── components\u002Fpostbox\u002F     # Shared Postbox UI components\n├── tests\u002F\n│   ├── Unit\u002FExtension\u002F         # 11 extension test suites (fill, E2E, extraction)\n│   └── fixtures\u002F\n│       ├── form-extraction\u002F    # 4,604-scenario corpus (html, expected, manifest)\n│       └── extension-e2e\u002F      # E2E mocks, scenarios, reports\n└── config\u002Fsubscriptions.php    # Plan tiers and token limits\n```\n\n## Getting started (local dev)\n\n### Prerequisites\n\n- PHP 8.5+, Composer\n- Node.js 20+, npm\n- PostgreSQL (or SQLite for quick local dev)\n- [Docker Sail](https:\u002F\u002Flaravel.com\u002Fdocs\u002Fsail) optional\n\n### Install\n\n```bash\ngit clone https:\u002F\u002Fgithub.com\u002Ftmwclaxton\u002Fautoapplycv.git\ncd autoapplycv\n\ncp .env.example .env\ncomposer install\nnpm install\n\nphp artisan key:generate\nphp artisan migrate\nnpm run build\n```\n\nOr use the one-shot setup:\n\n```bash\ncomposer run setup\n```\n\n### Environment\n\nCopy `.env.example` to `.env` and configure:\n\n```env\nAPP_URL=http:\u002F\u002Flocalhost\n\n# WorkOS - required for login\nWORKOS_CLIENT_ID=\nWORKOS_API_KEY=\nWORKOS_REDIRECT_URL=\"${APP_URL}\u002Fauthenticate\"\n\n# NanoGPT - required for CV parsing\nNANOGPT_API_KEY=\n\n# GoCardless - optional, for paid subscriptions\nGOCARDLESS_ACCESS_TOKEN=\nGOCARDLESS_WEBHOOK_SECRET=\n```\n\n### Run locally\n\n```bash\ncomposer run dev\n```\n\nStarts the Laravel server, queue worker, log tail, and Vite dev server together.\n\nWith Docker Sail:\n\n```bash\n.\u002Fvendor\u002Fbin\u002Fsail up -d\n.\u002Fvendor\u002Fbin\u002Fsail npm run dev\n```\n\nVisit [http:\u002F\u002Flocalhost](http:\u002F\u002Flocalhost).\n\n### Build the browser extension\n\n```bash\nnpm run build:extension\n```\n\nThe build uses `APP_URL` from `.env` only to exclude your local dashboard from content-script injection. The extension API endpoint comes from the dashboard connection JSON (`token` + `api_base`).\n\nThen in Chrome:\n\n1. Open `chrome:\u002F\u002Fextensions`\n2. Enable **Developer mode**\n3. Click **Load unpacked**\n4. Select the `extension\u002Fdist\u002F` folder\n\nGenerate a connection from the dashboard (**Copy** includes `token` + `api_base`) and paste it into the extension sidebar.\n\n## Extension bridge (MCP)\n\nFor extension work, the slow path is edit → rebuild → manually click through Chrome → guess what broke. The **extension bridge** connects Cursor (or any MCP client) to your **real unpacked Chrome profile** over localhost so agents can inspect live DOM state, fill fields, advance apply steps, and run Auto Apply without you acting as the robot.\n\nTypical wins:\n\n- **See what the extension sees** - `get_field_inventory`, `read_field_values`, `get_page_html` on the tab you already have open (cookies, SSO, Reed\u002FIndeed session intact).\n- **Step through apply flows** - platform tools like `reed_tab_message`, `indeed_tab_message`, `linkedin_tab_message` call the same content-script handlers the orchestrator uses (`REED_APPLY_STATE`, `REED_FILL_AND_ADVANCE`, etc.).\n- **Run Auto Apply from the agent** - `start_auto_apply` + `auto_apply_status` \u002F `auto_apply_stop` for LinkedIn, Indeed, Totaljobs, Glassdoor, or Reed.\n- **Tight rebuild loop** - `npm run extension:build-reload` rebuilds `extension\u002Fdist\u002F` and asks the connected extension to reload without a manual trip to `chrome:\u002F\u002Fextensions`.\n\n### How it fits together\n\n```mermaid\nflowchart LR\n    Cursor[\"Cursor agent\u003Cbr\u002F>MCP tools\"]\n    MCP[\"mcp-server.mjs\u003Cbr\u002F>stdio\"]\n    HTTP[\"Bridge HTTP\u003Cbr\u002F>127.0.0.1:7433\"]\n    WS[\"Bridge WebSocket\u003Cbr\u002F>127.0.0.1:7432\"]\n    BG[\"Extension\u003Cbr\u002F>service worker\"]\n    CS[\"Content scripts\u003Cbr\u002F>on job tabs\"]\n\n    Cursor --> MCP --> HTTP --> WS --> BG --> CS\n```\n\n| Piece | Role |\n|-------|------|\n| `extension\u002Fsrc\u002Fshared\u002Fbridge-client.js` | Outbound WebSocket from the MV3 background worker |\n| `scripts\u002Fextension-bridge\u002Fserver.mjs` | Local WebSocket + HTTP command API (`npm run extension-bridge`) |\n| `scripts\u002Fextension-bridge\u002Fmcp-server.mjs` | MCP stdio wrapper over the HTTP API |\n\nThe bridge is **dev-only**: unpacked builds from `extension\u002Fdist\u002F` auto-connect on localhost. It binds to `127.0.0.1` only.\n\n### Setup\n\n**Terminal 1** - bridge (leave running):\n\n```bash\nnpm run extension-bridge\n```\n\n**Terminal 2** - Laravel + Vite as usual (`composer run dev`).\n\n**Chrome** - load unpacked from `extension\u002Fdist\u002F` (see [Build the browser extension](#build-the-browser-extension)). Connect the extension to your dashboard token.\n\n**Cursor** - register the MCP server in `.cursor\u002Fmcp.json` (use your repo path for `cwd`):\n\n```json\n{\n  \"mcpServers\": {\n    \"autocvapply-extension\": {\n      \"command\": \"node\",\n      \"args\": [\"scripts\u002Fextension-bridge\u002Fmcp-server.mjs\"],\n      \"cwd\": \"\u002Fpath\u002Fto\u002Fautocvapply\"\n    }\n  }\n}\n```\n\nRestart Cursor after editing MCP config. The bridge server must be running before MCP tools work.\n\nVerify:\n\n```bash\ncurl -s http:\u002F\u002F127.0.0.1:7433\u002Fstatus\n```\n\nExpect `\"extensionConnected\": true` when Chrome has the unpacked extension loaded.\n\n### Fast iteration loop\n\n```bash\n# After editing extension\u002Fsrc\u002F** or form-heuristics:\nnpm run extension:build-reload\n\n# Optional: targeted platform unit test (no browser)\nnode --test scripts\u002Fextension-test\u002Freed-platform.test.mjs\n\n# Optional: drive Auto Apply from Node (uses the same bridge HTTP API)\nnode scripts\u002Fextension-test\u002Fauto-apply-marathon.mjs --platform=reed --target=5 --role=\"software engineer\" --location=London --fit=false\n```\n\nIn Cursor, a productive apply-debug session usually looks like:\n\n1. `extension_status` or `list_tabs` - find the apply tab.\n2. `set_active_tab` - pin it for follow-up commands.\n3. `reed_tab_message` \u002F `indeed_tab_message` \u002F … with `REED_APPLY_STATE` (or equivalent) - read step fingerprint, validation errors, review\u002Fsubmit flags.\n4. `get_field_inventory` + `apply_answer` or `start_draft_all` - fill the current step.\n5. Platform `_FILL_AND_ADVANCE` message - click Continue\u002FSubmit in the real page.\n6. `save_fixture` - capture redacted HTML into the form corpus when you find a new edge case.\n\nFor multi-step Indeed-style flows: fill → `click_control` (`Continue`) → `wait_for_tab` → repeat.\n\n### MCP tools (summary)\n\n| Tool | Use when |\n|------|----------|\n| `extension_status` | Check bridge + extension connection and active tab |\n| `get_field_inventory` \u002F `read_field_values` | See fillable refs and live DOM values |\n| `apply_answer` \u002F `start_draft_all` | Fill one field or run Draft All on the pinned tab |\n| `click_control` \u002F `click_ref` | Advance wizards (Continue, Next, etc.) |\n| `navigate_tab` \u002F `wait_for_tab` | Open jobs or wait for SPA navigation |\n| `linkedin_tab_message`, `indeed_tab_message`, `totaljobs_tab_message`, `glassdoor_tab_message`, `reed_tab_message` | Call platform content-script handlers directly |\n| `start_auto_apply`, `auto_apply_status`, `auto_apply_stop` | Run or monitor job-board Auto Apply |\n| `save_fixture` | Add redacted HTML to `tests\u002Ffixtures\u002Fform-extraction\u002F` |\n\nFull tool list, HTTP examples, env vars, and security notes: [`scripts\u002Fextension-bridge\u002FREADME.md`](scripts\u002Fextension-bridge\u002FREADME.md).\n\nFor the full job-board shipping workflow (MCP autofill → synthetic corpus → live marathon → commit), see [`docs\u002Fplatform-automation-playbook.md`](docs\u002Fplatform-automation-playbook.md).\n\n## Form corpus quality engineering\n\n> **This is not a side-project extension with a handful of smoke tests.** AutoCVApply ships with one of the most exhaustive form-autofill verification pipelines in the job-application tooling space - built because a single missed combobox on a Greenhouse form is a failed application.\n\n### The numbers\n\n| Metric | Count | Source |\n|--------|------:|--------|\n| Form extraction scenarios | **4,604** | `tests\u002Ffixtures\u002Fform-extraction\u002Fmanifest.json` |\n| Vetted scenarios | **3,495** | same manifest (`status: vetted`; remainder pending review) |\n| HTML fixtures + expected snapshots | **4,604 each** | `tests\u002Ffixtures\u002Fform-extraction\u002Fhtml\u002F` · `expected\u002F` |\n| Scraped real apply pages (`web-*`) | **1,136** | same manifest |\n| Job board synthetic (`syn-tj-500-*`, `syn-gd-300-*`, `syn-reed-300-*`) | **1,100** | Totaljobs, Glassdoor, Reed Auto Apply flows |\n| Curated fill-verify scenarios | **124** (70 JSDOM · 54 Playwright) | `tests\u002Ffixtures\u002Fform-extraction\u002Ffill-verify-curated.json` |\n| Platform smoke scenarios | **13** (+ 2 Ashby widget checks) | `fill-verify-smoke.json` · `run-ashby-*-playwright.mjs` |\n| Extension E2E scenarios | **103** (10 in CI) | `tests\u002Ffixtures\u002Fextension-e2e\u002Fe2e-scenarios.json` |\n| Profile-mapping benchmark scenarios | **190** | `scripts\u002Fextension-benchmark\u002Fprofile-mapping-corpus.json` |\n| Answer-quality benchmark scenarios | **124** | `scripts\u002Fextension-benchmark\u002Fanswer-quality-corpus.json` |\n| PHPUnit test methods | **410** | `tests\u002F**\u002F*Test.php` |\n| Platform buckets in curated tier | **16** | `scripts\u002Fform-corpus\u002Flib\u002Fcurated-manifest.mjs` |\n\nThe corpus blends **1,136 scraped real ATS and job-board pages** with **3,468 synthetic scenarios** - including 500 **syn-complex-500** ATS-style fixtures, 500 **syn-corpus2** bulk forms, 500 **syn-tj-500** Totaljobs flows, 300 **syn-gd-300** Glassdoor flows, 300 **syn-reed-300** Reed flows, 60 **syn-weird** edge cases, plus framework mega-forms for React, Vue, Angular, Svelte, Shadow DOM, Workday wizards, conditional fields, and combobox edge cases.\n\n### The test pyramid\n\nEvery change to `form-heuristics.js` or `field-inventory.js` must survive the full pyramid before merge:\n\n```mermaid\nflowchart TB\n    subgraph L1[\"Layer 1 - Fast feedback\"]\n        U[\"PHPUnit unit tests\u003Cbr\u002F>410 methods\"]\n        P[\"Propagation + mock answer tests\"]\n        D[\"Debug log golden replay\"]\n    end\n\n    subgraph L2[\"Layer 2 - Curated JSDOM\"]\n        J[\"70 synthetic scenarios\u003Cbr\u002F>4-layer verification · 100% pass\"]\n    end\n\n    subgraph L3[\"Layer 3 - Real browser\"]\n        S[\"Platform smoke\u003Cbr\u002F>13 ATS fixtures + Ashby widgets\"]\n        PW[\"Curated Playwright\u003Cbr\u002F>54 scraped ATS pages\"]\n        VR[\"Visual regression\u003Cbr\u002F>screenshot baselines\"]\n    end\n\n    subgraph L4[\"Layer 4 - Full extension\"]\n        E2E[\"Extension E2E\u003Cbr\u002F>103 scenarios · real MV3 + mocked API\"]\n    end\n\n    L1 --> L2 --> L3 --> L4\n```\n\n| Tier | Engine | Scope | CI job |\n|------|--------|-------|--------|\n| **Unit** | JSDOM \u002F Node | Propagation, mock answers, debug-log replay | `tests.yml` → `php-tests` |\n| **Corpus sanity** | PHPUnit | Corpus size checks (4,604 scenarios, 3,495 vetted) | `tests.yml` → `php-tests` |\n| **Form extraction eval** | JSDOM | All 3,495 vetted scenarios vs expected field inventory | `tests-heavy.yml` (manual) |\n| **Curated JSDOM** | JSDOM | 70 synthetic scenarios, 4-layer checks at 100% | `tests.yml` → `extension-fill` |\n| **Platform smoke** | Playwright | 1 scenario per ATS\u002Fplatform + Ashby yes\u002Fno + checkbox | `tests.yml` → `extension-fill` |\n| **Curated Playwright** | Playwright | 54 priority scraped ATS fixtures | `tests-heavy.yml` (manual) |\n| **Visual regression** | Playwright + pixelmatch | Baseline compare on smoke subset | `tests-heavy.yml` (manual) |\n| **Extension E2E** | Playwright + unpacked MV3 | Full Draft All with mocked assist API (103 total · 10 in CI) | `extension-fill` (CI subset) · `tests-heavy.yml` (full batch) |\n\n### Four layers of fill verification\n\nEach curated JSDOM scenario passes **four independent checks** - not just \"did we set a value?\":\n\n| Layer | What it proves |\n|-------|----------------|\n| **DOM readback** | Re-reads filled values from the DOM after `applyAnswerByRefAllFrames` |\n| **HTML5 validity** | `element.checkValidity()` \u002F `form.checkValidity()` on native controls |\n| **Accessibility state** | `aria-checked`, `aria-selected`, `aria-pressed`, combobox collapsed state |\n| **Error banners** | Ashby\u002FGreenhouse-style validation messages, `[role=\"alert\"]`, `[aria-invalid=\"true\"]` |\n\nAdditional tiers add **OCR readback** (Playwright + Tesseract on Ashby fixtures), **pixel diff** (before\u002Fafter screenshot % change), and **debug log golden replay** (extension phase summaries).\n\n### Pass-rate thresholds (enforced in CI)\n\n| Tier | Critical | Overall |\n|------|----------|---------|\n| JSDOM curated | 100% | 100% |\n| Playwright priority | 100% | 100% |\n| Platform smoke | 100% | 100% |\n| Extension E2E | 100% | 100% |\n\n### CI pipeline\n\nFast feedback runs on every push to `main` and `develop`; the heavy corpus eval tier is manual-only:\n\n| Workflow | Trigger | What runs |\n|----------|---------|-----------|\n| **`tests.yml` → `php-tests`** | Push \u002F PR | Laravel suite on PostgreSQL 17 - excludes `@group playwright` and `@group extension-e2e`; corpus sanity checks only (not full extraction eval) |\n| **`tests.yml` → `extension-fill`** | Push \u002F PR | `npm run build:extension` → curated JSDOM verify (70 scenarios) → Playwright platform smoke |\n| **`tests-heavy.yml`** | Manual dispatch | Full 3,495-scenario extraction eval · comprehensive fill verify · curated Playwright · visual regression · extension E2E batch (~103 scenarios) |\n| **`lint.yml`** | Push \u002F PR | Laravel Pint, ESLint, Prettier |\n| **`prod_deploy.yml`** | Push to `main` | Docker build → GHCR push → deploy to production |\n\nAfter changing form heuristics locally, run the smoke tier before opening a PR:\n\n```bash\nnpm run form-corpus:fill-verify:smoke\n# or\nFORM_CORPUS_PLAYWRIGHT=1 php artisan test --compact --filter=test_platform_smoke_playwright_passes\n```\n\nSee [`scripts\u002Fform-corpus\u002FREADME.md`](scripts\u002Fform-corpus\u002FREADME.md) for the full maintenance workflow, report paths, and the manual heavy tier.\n\n## Job board Auto Apply testing\n\nLinkedIn, Indeed, Totaljobs, Glassdoor, and Reed Auto Apply have their own verification pyramid, separate from the 4,604-scenario ATS form corpus. It runs on every push via PHPUnit wrappers and can be exercised locally without a live session for most tiers.\n\n```mermaid\nflowchart TB\n    subgraph L1[\"Layer 1 - Unit (JSDOM \u002F Node)\"]\n        U1[\"linkedin-auto-apply.mjs\u003Cbr\u002F>search URLs, job cards, modal state\"]\n        U2[\"auto-apply-pause-resume.mjs\u003Cbr\u002F>blockers + session pause\u002Fresume\"]\n        U3[\"auto-apply-activity-visibility.mjs\u003Cbr\u002F>sidebar activity UI rules\"]\n        U4[\"linkedin-full-flow-report.mjs\u003Cbr\u002F>E2E report parsing\"]\n    end\n\n    subgraph L2[\"Layer 2 - Offline corpus (JSDOM)\"]\n        C[\"linkedin-easy-apply-corpus.mjs\u003Cbr\u002F>212 live captures + 52 synthetic edge cases\"]\n        I[\"indeed-auto-apply-offline.mjs\u003Cbr\u002F>44 apply-step fixtures\"]\n        TJ[\"syn-tj-500-* · syn-gd-300-* · syn-reed-300-*\u003Cbr\u002F>1,100 job board synthetic scenarios\"]\n    end\n\n    subgraph L3[\"Layer 3 - Browser offline\"]\n        O[\"linkedin-auto-apply-offline-step.mjs\u003Cbr\u002F>Playwright + fixture HTML\"]\n    end\n\n    subgraph L4[\"Layer 4 - Live E2E (manual)\"]\n        E[\"linkedin-auto-apply-full-flow.mjs\u003Cbr\u002F>real LinkedIn session + MV3 extension\"]\n    end\n\n    L1 --> L2 --> L3 --> L4\n```\n\n| Tier | Script \u002F test | What it validates | CI |\n|------|---------------|-------------------|-----|\n| **Unit** | `scripts\u002Fextension-test\u002Flinkedin-auto-apply.mjs` | Search URL building, job card parsing, Easy Apply button state, modal detection, step fingerprints, cookie\u002Fsave-dialog dismiss | `LinkedInAutoApplyTest` |\n| **Unit** | `auto-apply-pause-resume.mjs`, `auto-apply-activity-visibility.mjs` | Validation blockers, pause\u002Fresume session state, sidebar activity panel visibility | Default PHPUnit |\n| **Offline corpus** | `linkedin-easy-apply-corpus.mjs` | Each fixture: modal open\u002Fclosed, primary actions (Next\u002FReview\u002FSubmit), validation errors, submitted confirmation, multi-step flow progression | `LinkedInEasyApplyCorpusTest` |\n| **Offline corpus** | `indeed-auto-apply-offline.mjs`, `indeed-apply-*.mjs` | Indeed Apply search URLs, step detection, contact\u002Fresume\u002Fquestions\u002Freview fixtures | `npm run test:indeed-auto-apply-offline` |\n| **Offline corpus** | `syn-tj-500-*`, `syn-gd-300-*`, `syn-reed-300-*` | 1,100 synthetic Totaljobs, Glassdoor, and Reed search\u002Fapply scenarios in the form corpus | `npm run form-corpus:validate-*-corpus` |\n| **Unit** | `reed-platform.test.mjs` | Reed search URLs, job ID parsing, apply URL builders | `node --test scripts\u002Fextension-test\u002Freed-platform.test.mjs` |\n| **Browser offline** | `linkedin-auto-apply-offline-step.mjs` | Real Chrome + unpacked extension clicking through a fixture modal step | `LinkedInFullFlowReportTest` (requires `EXTENSION_E2E=1`) |\n| **Live E2E** | `linkedin-auto-apply-full-flow.mjs` | Full orchestrator: search, open jobs, Draft All per step, advance, submit; writes `tests\u002Foutput\u002Flinkedin-auto-apply-full-flow\u002Freport.json` | Manual (`LINKEDIN_LIVE_E2E=1`) |\n\n### Fixtures\n\n| Location | Contents |\n|----------|----------|\n| `tests\u002Ffixtures\u002Fauto-apply\u002Flinkedin\u002Fcaptured\u002F` | **212 live-captured** LinkedIn HTML files (authoritative regression source) |\n| `tests\u002Ffixtures\u002Fform-extraction\u002Fhtml\u002Fweb-indeed-*` | **44 Indeed Apply** step fixtures (contact, resume, questions, review) |\n| `tests\u002Ffixtures\u002Fform-extraction\u002Fhtml\u002Fsyn-tj-500-*` | **500 Totaljobs** Quick Apply synthetic scenarios |\n| `tests\u002Ffixtures\u002Fform-extraction\u002Fhtml\u002Fsyn-gd-300-*` | **300 Glassdoor** Easy Apply host + iframe scenarios |\n| `tests\u002Ffixtures\u002Fform-extraction\u002Fhtml\u002Fsyn-reed-300-*` | **300 Reed** Easy Apply search, job detail, and modal review scenarios |\n| `tests\u002Ffixtures\u002Fauto-apply\u002Flinkedin\u002Fcaptured-manifest.json` | Metadata per capture: step, `capture_reason`, validation errors, stuck diagnostics |\n| `tests\u002Ffixtures\u002Fauto-apply\u002Flinkedin\u002F` | **50 synthetic** progression flows plus `error-*` and `edge-*` fixtures (fallback when captures are unavailable) |\n| `tests\u002Ffixtures\u002Fauto-apply\u002Flinkedin-search-results*.html` | Search results and job detail page fixtures for card parsing |\n\nSee [`tests\u002Ffixtures\u002Fauto-apply\u002Flinkedin\u002FREADME.md`](tests\u002Ffixtures\u002Fauto-apply\u002Flinkedin\u002FREADME.md) for capture flags, stuck-step suffixes, and fixture layout.\n\n### Commands\n\nRun the fast tiers before changing `linkedin-auto-apply.js`, `linkedin-easy-apply-fields.js`, or the auto-apply orchestrator:\n\n```bash\n# Unit tests (search, cards, modal state)\nnode scripts\u002Fextension-test\u002Flinkedin-auto-apply.mjs\n\n# Pause\u002Fresume blockers and sidebar activity UI\nnpm run test:auto-apply-pause-resume\nnpm run test:auto-apply-activity-visibility\n\n# Offline corpus (CI default - prefers live captures)\nnpm run test:linkedin-easy-apply-corpus:run -- --captured-only\n\n# Full corpus including synthetic fallback\nnpm run test:linkedin-easy-apply-corpus\n\n# PHPUnit wrappers (runs Node scripts above)\nphp artisan test --compact tests\u002FUnit\u002FExtension\u002FLinkedInAutoApplyTest.php\nphp artisan test --compact tests\u002FUnit\u002FExtension\u002FLinkedInEasyApplyCorpusTest.php\n```\n\nBrowser offline step (build extension first):\n\n```bash\nnpm run build:extension\nnpm run test:linkedin-full-flow:offline-step\n# or via PHPUnit:\nEXTENSION_E2E=1 php artisan test --compact --filter=test_linkedin_auto_apply_offline_step\n```\n\nLive full-flow E2E (requires LinkedIn test account in `.env`):\n\n```bash\nnpm run build:extension\nLINKEDIN_LIVE_E2E=1 npm run test:linkedin-full-flow -- --max-jobs=3 --roles=\"software engineer\"\n# or via PHPUnit:\nLINKEDIN_LIVE_E2E=1 php artisan test --compact tests\u002FFeature\u002FExtension\u002FLinkedInAutoApplyFullFlowTest.php\n```\n\n### Indeed Apply fast tiers\n\nRun before changing `indeed-auto-apply.js`, `indeed-platform.js`, or the auto-apply orchestrator:\n\n```bash\n# Offline orchestrator + step fixtures (contact, resume, questions, review)\nnpm run test:indeed-auto-apply-offline\nnpm run test:indeed-apply-contact\nnpm run test:indeed-apply-questions\nnpm run test:indeed-apply-job-006\nnpm run test:indeed-apply-job-007\nnpm run test:indeed-apply-job-008\n```\n\nIndeed step fixtures live under `tests\u002Ffixtures\u002Fform-extraction\u002Fhtml\u002Fweb-indeed-*` and in the form-extraction manifest.\n\n### Adding live captures\n\nUse a headed Playwright session to grow the offline corpus from real LinkedIn Easy Apply modals:\n\n```bash\n# Credentials in .env only (never commit):\n# LINKEDIN_TEST_EMAIL=...\n# LINKEDIN_TEST_PASSWORD=...\n\nnpm run extension-e2e:capture-linkedin-corpus -- --target-fixtures=50 --max-jobs=20 --roles=\"software engineer,frontend developer\"\n```\n\nThe capture script logs in, rotates search roles, opens Easy Apply jobs, saves step states (open, filled, validation errors, multi-step review, stuck flows), redacts PII before write, and updates `captured-manifest.json`.\n\nAfter capture or sanitizer rule changes, re-process fixtures before committing:\n\n```bash\nnode scripts\u002Fextension-e2e\u002Fresanitize-linkedin-captured.mjs\nnode scripts\u002Fextension-e2e\u002Frebuild-captured-manifest.mjs\nnpm run test:linkedin-easy-apply-corpus:run -- --captured-only\n```\n\n### PII redaction and secret checks\n\nCaptured HTML is sanitized on save and can be re-sanitized in bulk:\n\n- Strips `\u003Cscript>` tags\n- Replaces emails with `candidate@example.com`, phone with `+44 7700 900123`, names with `Alex Candidate`\n- Scrubs `.env` credentials (`LINKEDIN_TEST_EMAIL`, `LINKEDIN_TEST_PASSWORD`) and known API key patterns via `redactSecrets`\n\nBefore committing new fixtures:\n\n```bash\nnpm run secrets:check-fixtures\n```\n\nDo not paste unredacted live LinkedIn HTML into `tests\u002Ffixtures\u002Fauto-apply\u002Flinkedin\u002Fcaptured\u002F`.\n\n## Key commands\n\n| Command | Purpose |\n|---------|---------|\n| `composer run dev` | Laravel + queue + Pail + Vite |\n| `composer test` | Pint check + full PHPUnit suite |\n| `npm run build:extension` | Build MV3 extension to `extension\u002Fdist\u002F` |\n| `npm run extension:build-reload` | Build + hot-reload unpacked extension via bridge |\n| `npm run extension-bridge` | Start localhost bridge (WebSocket + HTTP for MCP) |\n| `npm run form-corpus:fill-verify:curated` | Curated JSDOM tier (CI default) |\n| `npm run form-corpus:fill-verify:smoke` | Per-platform Playwright smoke |\n| `npm run form-corpus:extension-e2e` | Extension E2E CI subset (~10 scenarios) |\n| `npm run form-corpus:visual-regression` | Screenshot baseline compare |\n| `npm run form-corpus:build-curated` | Regenerate curated + smoke manifests |\n| `npm run test:linkedin-easy-apply-corpus:run -- --captured-only` | LinkedIn offline corpus (212 live captures) |\n| `npm run test:linkedin-full-flow` | LinkedIn live E2E (requires `LINKEDIN_LIVE_E2E=1` + credentials) |\n| `npm run extension-e2e:capture-linkedin-corpus` | Capture live LinkedIn Easy Apply HTML for offline tests |\n| `npm run lint:check` | ESLint |\n| `composer lint:check` | Laravel Pint |\n\n### PHPUnit tiers\n\n```bash\n# Default CI (excludes playwright + extension-e2e groups)\nphp artisan test --compact --exclude-group=extension-e2e,playwright\n\n# Playwright smoke (fast CI tier)\nFORM_CORPUS_PLAYWRIGHT=1 php artisan test --compact --group=playwright --exclude-group=extension-e2e\n\n# Extension E2E CI subset\nEXTENSION_E2E=1 php artisan test --compact --group=extension-e2e\n\n# Full ~103 scenario extension E2E (manual via tests-heavy.yml, 30-60+ min)\nEXTENSION_E2E=1 EXTENSION_E2E_FULL=1 php artisan test --compact --group=extension-e2e\n```\n\n## API\n\nThe extension authenticates with Laravel Sanctum bearer tokens.\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| `GET` | `\u002Fapi\u002Fprofile` | Fetch user profile + subscription usage |\n| `POST` | `\u002Fextension\u002Fconnection` | Generate extension connection JSON (dashboard session) |\n| `POST` | `\u002Fapi\u002Fapplications\u002Fassist\u002Finventory` | Field inventory for current page |\n| `POST` | `\u002Fapi\u002Fapplications\u002Fassist\u002Fjob-context` | Extract job title, company, description |\n| `POST` | `\u002Fapi\u002Fapplications\u002Fassist\u002Fdraft-all` | Stream batch field answers (NDJSON) |\n| `POST` | `\u002Fapi\u002Fapplications\u002Fassist\u002Fdraft-field` | Single field draft |\n| `POST` | `\u002Fapi\u002Fapplications\u002Fassist\u002Fcover-letter` | Generate cover letter |\n| `POST` | `\u002Fapi\u002Fapplications\u002Fassist\u002Ftailored-resume` | Tailored resume draft |\n| `DELETE` | `\u002Fapi\u002Ftokens\u002F{token}` | Revoke a token |\n\n## Deployment\n\nProduction runs in Docker (`DockerfileProd`) with Nginx, PHP-FPM, and a queue worker. Pushes to `main` build a GHCR image and deploy via GitHub Actions.\n\nCV extraction and Draft All use `deepseek\u002Fdeepseek-v4-flash:throughput` by default (`config\u002Fcv.php`). To override the model in production, set `NANOGPT_CV_MODEL` in the server `.env` at `\u002Fopt\u002Fautocvapply\u002F.env`; remove that key to use the config default after deploy.\n\nLive site: **[autocvapply.com](https:\u002F\u002Fautocvapply.com)**\n\n## Contributing\n\nIssues and pull requests welcome on [GitHub](https:\u002F\u002Fgithub.com\u002Ftmwclaxton\u002Fautoapplycv).\n\n1. Fork the repo\n2. Create a feature branch\n3. Write tests for your changes\n4. If you touched `form-heuristics.js` or `field-inventory.js`, run `npm run form-corpus:fill-verify:smoke`\n5. Run `composer test` and `npm run lint:check`\n6. Open a PR\n\n## License\n\n[PolyForm Noncommercial License 1.0.0](LICENSE). Free for personal and non-commercial use - fork it, study it, run it locally. Commercial use (selling, SaaS, paid services built on this code, etc.) requires permission; contact us via [autocvapply.com\u002Fcontact](https:\u002F\u002Fautocvapply.com\u002Fcontact).\n\n---\n\n\u003Cp align=\"center\">\n  \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fautocvapply.com\">Get started free at autocvapply.com\u003C\u002Fa>\u003C\u002Fstrong>\u003Cbr \u002F>\n  \u003Csub>Built for people who'd rather apply to jobs than retype their CV.\u003Cbr \u002F>\n  Verified against 4,604 form scenarios. Battle-tested on real ATS platforms and UK job boards.\u003C\u002Fsub>\n\u003C\u002Fp>\n","AutoCVApply 是一款基于 AI 的简历解析与求职表单自动填充工具，通过浏览器扩展实现跨招聘平台的简历信息智能提取与表单预填。核心功能包括 CV 文本结构化解析、4600+种招聘表单场景识别、LinkedIn\u002FIndeed\u002FGlassdoor 等主流平台一键投递，以及对 Greenhouse、Workday 等 ATS 系统的手动辅助填写。采用 Vue 3 + Laravel 构建，支持 Chrome MV3 扩展，强调隐私安全与字段级人工审核控制。适用于频繁投递岗位的求职者、应届生批量申请及猎头高效初筛等场景。",2,"2026-07-08 04:29:53","CREATED_QUERY"]