{"id":"cabb9314-dd78-4f9e-87a3-9f134d60d485","slug":"tailored","kind":"mcp","name":"tailored","summary":"Local AI resume & cover-letter builder: paste a job URL, get a tailored, truthfulness-guarded resume in four templates (PDF/HTML/ATS). Use the Anthropic API, or bring your own agent via MCP.","owner":"end1989","repoUrl":"https://github.com/end1989/tailored","license":"MIT","licenseTier":"permissive","trust":"community","stars":3,"match":null,"fewStars":true,"format":"mcp","createdAt":"2026-07-23T19:20:32.000Z","addedAt":"2026-10-21T04:50:53.898Z","logoUrl":"https://avatars.githubusercontent.com/u/34949137?u=4f9a9c66b10a3f922132dfc2e2836f253bcd87e6&v=4","linkOnly":false,"collection":null,"skillPath":null,"categories":["browser-web","files-documents","databases"],"tags":["ai","anthropic","claude","cover-letter","fastapi","job-search","pdf-generation","playwright"],"runtime":"binary","local":true,"cloudCapable":false,"cloudCapability":"unknown","cloudReadyOnPlatform":false,"needsApiKey":false,"reviewed":false,"syncedAt":"2026-10-26T01:47:10.771Z","sample":false,"url":"https://conduits.dev/tools/tailored","language":"Python","transports":["stdio"],"install":{},"requirements":{"env":[],"upstreamMayCharge":false},"tools":null,"readme":"# Tailored — AI Resume & Cover Letter Builder\n\nTailored is a local web app that turns job-posting URLs into customized,\nwell-structured resumes and cover letters. You maintain one **Master Profile** —\neverything you have ever done, structured as JSON — and Tailored *selects, reorders,\nand emphasizes* from it per job. It never invents anything\n(see [Truthfulness](#truthfulness)). Several people can share one install,\neach with their own profile, applications and settings; see\n[Several people, one install](#several-people-one-install).\n\n## Highlights\n\n- **Truthfulness enforced in the data layer, not the prompt.** Most AI resume tools ask the model nicely not to exaggerate. Tailored rejects any generated resume that contains an employer, title, date, degree, or certification not present in your Master Profile — a structural check on the write path, so the guarantee holds no matter which AI produced the text.\n- **Voice enforced the same way.** Generated text is rejected if it carries the\nmechanical tells of machine writing: em dashes, emoji, curly quotes, the\nellipsis character, invisible spaces, and a short curated list of recruitment\ncliches. The check runs on the write path, so it holds for both the built-in\npipeline and any MCP agent, whichever model produced the text. The API pipeline\nretries once with the violations attached; an agent gets the list back and\ncorrects it.\n- **Two ways to supply the intelligence, one contract.** Use the built-in Anthropic API pipeline (paste a batch of job URLs and walk away), or connect your own agent over MCP — a from-scratch MCP server (14 tools) that lets Claude Code, Codex, or any MCP-capable client do the work on its own subscription, no API key, and read login-walled postings its browser can reach. The same truthfulness guard applies to both.\n- **Your codebase becomes resume evidence.** A portfolio-scan prompt plus an MCP write tool let an agent read the repos in your workspace and write evidence-backed, skill-tagged project entries straight into your profile (additive-only, validated, never destructive).\n- **Built to be handed to a non-engineer.** Double-click launcher (Windows `.bat` + Unix `.sh`) that self-installs on first run, a fully offline demo mode needing no API key, eight print-tuned templates exporting PDF / HTML / ATS plain text, dark mode, and a committed frontend build so cloning needs only Python.\n- **It tracks the job hunt, not just the generation.** Stages from Saved through Offer, a dated timeline for callbacks, interviews and notes, archive and permanent delete, and saved jobs you can park for free and generate later.\n- **Engineered, not vibe-coded.** Spec → implementation plan → test-driven development, every task independently reviewed. 1119 automated tests (851 backend including real headless-Chromium PDF rendering and text extraction, 268 frontend), and validated end to end against the live Anthropic API — two API-only bugs were found and fixed that way.\n\nJob URLs can be queued for immediate generation or parked as a saved job to\ngenerate later at no cost. For each job URL you choose to generate, it runs a\nfour-stage pipeline:\n\n1. **Fetch** — downloads the posting and extracts the text (paste fallback for\n   login-walled sites, and for JavaScript-only pages that carry no schema.org\n   job data).\n2. **Research** — parses the posting; optionally researches the company\n   (per-job depth dial, see below).\n3. **Tailor** — Claude (`claude-opus-4-8`) selects and emphasizes the most relevant\n   parts of your Master Profile and writes a matching cover letter.\n4. **Render** — eight print-tuned templates (see [Templates](#templates)) → PDF,\n   standalone HTML, and ATS-safe plain text.\n\nCompare all eight templates side by side, with live previews, on the in-app\n**Templates** page. The UI also follows your system's light/dark preference\nautomatically, with a one-click override in the nav bar or Settings.\n\nGot a workspace full of your own repos? Use the\n[portfolio scan prompt](docs/portfolio-scan-prompt.md) with Claude Code (or any\ncapable coding agent) to turn your actual codebases into evidence-backed project\nmaterial for your master profile.\n\nEverything runs on your machine. The only network traffic is fetching postings and\ncalling the Anthropic API. All state lives in the `data/` folder — a backup is\ncopying one folder.\n\n## Quickstart (Windows)\n\n1. Install **Python 3.11+** from [python.org](https://www.python.org/downloads/) — on\n   the first install screen, **check \"Add python.exe to PATH\"**.\n2. Clone or download this repo, then double-click **`Tailored.bat`** in the project\n   folder.\n\nThe first launch does the setup for you — creates a virtual environment, installs\ndependencies, downloads the Chromium browser used for PDF export — which takes a\nfew minutes. Every launch after that starts in a couple of seconds.\n\nIf you don't have an [Anthropic API key](https://console.anthropic.com/) yet, the\nlauncher offers a **demo mode** with sample data — no key or network access needed.\n\n**Having issues?** See [TROUBLESHOOTING.md](TROUBLESHOOTING.md) for common solutions.\nOr try: `Tailored.bat --clean` to rebuild the environment from scratch.\n\n## Quickstart (macOS / Linux)\n\nYou need **Python 3.11+** (check with `python3 --version`, or install via your\npackage manager / [python.org](https://www.python.org/downloads/)).\n\n```\ngit clone <this-repo-url> tailored\ncd tailored\nbash start_tailored.sh\n```\n\nSame idea as Windows: the first run sets everything up (a few minutes), later runs\nare instant, and the script offers a no-key **demo mode** if you don't have an API\nkey yet.\n\n**Having issues?** See [TROUBLESHOOTING.md](TROUBLESHOOTING.md) for common solutions.\nOr try: `bash start_tailored.sh --clean` to rebuild the environment from scratch.\n\n## Manual setup (developers)\n\nThis is what `Tailored.bat` / `start_tailored.sh` automate above. Use it if you're\ndeveloping, on an unsupported OS, or just want full manual control.\n\nYou need **Python 3.11+** and an [Anthropic API key](https://console.anthropic.com/).\nNode.js is **not** required — the built frontend is committed.\n\n```\ngit clone <this-repo-url> tailored\ncd tailored\npython -m venv .venv\n.venv\\Scripts\\activate          # Windows   (macOS/Linux: source .venv/bin/activate)\npip install -r requirements.txt\nplaywright install chromium\ncopy .env.example .env          # macOS/Linux: cp .env.example .env\n```\n\nEdit `.env` and set your key:\n\n```\nANTHROPIC_API_KEY=sk-ant-...\n```\n\nThen start it:\n\n```\npython run.py\n```\n\nThe server starts on http://127.0.0.1:8547 and your browser opens automatically.\n\n### Use your own AI agent instead of the API (MCP mode)\n\nTailored ships an MCP server (`backend/mcp_server.py`) that lets any\nMCP-capable agent — Claude Code, Codex CLI, and friends — act as the\nintelligence instead of the built-in Anthropic API pipeline. Your coding\nagent's subscription does the thinking, so **no API key is needed**, and\nbecause the agent fetches postings with its own browser/tools, it can read\nlogin-walled postings the app's fetcher cannot.\n\nRegister it with Claude Code in one line (replace both paths with your\nclone's absolute paths — any MCP-capable agent can register the same\ncommand):\n\n```\nclaude mcp add tailored -- \"<abs path>\\.venv\\Scripts\\python.exe\" \"<abs path>\\backend\\mcp_server.py\"\n```\n\nThen ask your agent to read Tailored's workflow guide (the\n`get_workflow_guide` tool), and from there it's just:\n\n> tailor my profile for &lt;job url&gt;\n\nWhen several people share the install, say whose profile to use. The\ncopyable prompts on the Getting Started page name the person selected in the\nnav for you, and an agent never follows the picker by itself (see\n[Several people, one install](#several-people-one-install)).\n\nThe agent reads your master profile, fetches and analyzes the posting,\noptionally researches the company, and writes the tailored resume and cover\nletter back into Tailored, which renders the same PDF/HTML/ATS exports as the\nbuilt-in pipeline. The [truthfulness guard](#truthfulness) applies to agents\ntoo — it is enforced server-side on the write path, so a connected agent\ncannot save invented employers, titles, dates, degrees, or certifications;\nit gets the violation list back and must correct the resume. See\n[docs/EXTENDING.md](docs/EXTENDING.md) for the full tool contract. An\nMCP-driven application parks in status `tailoring` until the agent saves,\nwhich blocks that row's web-UI paste/regenerate/edit actions until you save\nor delete it, and conversely MCP saves are rejected while the built-in\npipeline is actively processing that same application.\n\nThe same connected agent can also import your workspace projects straight into\nyour master profile: run the\n[portfolio scan prompt](docs/portfolio-scan-prompt.md) and the agent writes its\nverified findings back through the `add_profile_evidence` tool (additive — it\nnever overwrites what you already have), no copy-paste needed.\n\n### Working through a list of jobs\n\nPaste a list of URLs at your agent and let it work:\n\n> Queue these twenty jobs for my profile and work through them one at a time.\n\nThe agent calls `queue_jobs` once, and all twenty appear on your dashboard as\nsaved jobs immediately, before anything has been fetched or generated. Nothing\nhas cost anything yet. It then loops on `next_pending_job`, taking one job all\nthe way to a finished resume before starting the next, and you watch the list\ndrain in real time.\n\nThe queue lives in the database, not in the agent's context. If the agent\nrestarts or runs out of context at job eleven, it picks up at job eleven. If you\ndelete a saved job partway through, the agent simply never receives it.\n\nWhen a job board refuses to be read, the agent opens the posting in your own\nChrome using your own session, which is what works for postings behind a login\nyou already hold. Tailored never asks your agent to disguise automated traffic\nor defeat a bot check, and no evasion tooling will be added. If that still does\nnot work, the job is marked blocked with the reason on its timeline and moves\nout of the queue into the needs-paste state, so you get a paste box for exactly\nthat posting instead of finding a row that never moved.\n\n## Demo mode (no API key, fully offline)\n\n`Tailored.bat` / `start_tailored.sh` offer this automatically when no key is found.\nTo set it manually (e.g. for the manual setup above): set `TAILORED_FAKE=1`:\n\n```\n# Windows PowerShell\n$env:TAILORED_FAKE = \"1\"; python run.py\n\n# macOS / Linux\nTAILORED_FAKE=1 python run.py\n```\n\nDemo mode seeds a sample profile plus one finished application and answers every AI\ncall from offline fixtures. Every screen is clickable end to end. To try the\nperson picker, add a second person with **Add a person** in the nav's person\nlist. The sample is seeded whenever the database has no people at all, so\nremoving the last person in demo mode brings the sample person and\napplication back on the next start.\n\n## Research depth = cost dial\n\nResearch depth is chosen per job when you add it, and the Add Jobs form starts\non the selected person's default depth from Settings. Approximate cost per application\n(Claude Opus 4.8; real token usage and cost are recorded per application and shown on\neach application's page):\n\n| Depth      | What it does                                                        | Approx. cost |\n|------------|---------------------------------------------------------------------|--------------|\n| `quick`    | Parse the posting only                                              | $0.15-0.30   |\n| `standard` | + fetch the company's own site (mission, products, values)          | $0.30-0.60   |\n| `deep`     | + web search: recent news, products, tech-stack & culture signals   | $1-3         |\n\n## Templates\n\nEight print-tuned templates. Every one is single-column with no sidebars, icons,\nskill bars, or text baked into images, because all of those break ATS and LLM\nparsing. They differ in typography, rhythm and hierarchy, not in structure.\n\n| Template | Id | What it is | Best for |\n|---|---|---|---|\n| Meridian | `meridian` | Classic serif with small caps and hairline rules. Understated and traditional. | Corporate, finance, healthcare, government |\n| Slate | `slate` | Neutral contemporary sans-serif that builds hierarchy from weight and whitespace rather than rules. | General purpose, safe everywhere |\n| Terminal | `terminal` | Technical layout with monospace metadata and projects placed forward. | Engineering, data, infrastructure |\n| Signal | `signal` | Confident headline treatment with a single accent used once. | Design, marketing, product |\n| Ledger | `ledger` | Executive serif with a large name, wide leading and generous whitespace. | Director level and above |\n| Quarto | `quarto` | Academic CV that carries long publication lists gracefully across pages. | Academia, research, grants |\n| Dossier | `dossier` | Dense sans-serif that fits a long career onto fewer pages without crowding. | Fifteen or more years of history |\n| Plainwork | `plainwork` | Deliberately unstyled: no rules, no colour, no letterspacing, system fonts only. | Workday and government portals, maximum ATS compatibility |\n\nTemplates are discovered from `backend/templates/*/template.json`, so the API,\nboth dropdowns and the gallery all stay in step automatically. You can switch an\nexisting application to a different template from its page at any time: it\nre-renders the resume you already have, with no model call and no cost.\n\nEvery typeface except Meridian's and Plainwork's system stacks is a latin-subset\n[SIL Open Font License](https://openfontlicense.org/) `.woff2` vendored into the\nrepo and base64-inlined at render time, so an exported `resume.html` is a single\nstandalone file that needs no network access to render correctly. Adding a ninth\ntemplate is three files and no code: see [docs/EXTENDING.md](docs/EXTENDING.md).\n\n## Truthfulness\n\nTailoring is a *selection and emphasis* problem, never invention. The generator may\nreorder, reweight, and rephrase your bullets to mirror a posting's vocabulary — but\nonly where factually supported by your Master Profile. It may never invent employers,\ntitles, dates, degrees, certifications, tools, or metrics. This is enforced twice:\nin the prompt rubric, and structurally after every generation — each employer, title,\ndate range, degree, and certification on the generated resume must match your Master\nProfile exactly, or the application lands in an error state instead of shipping a\nfabrication. Regenerations are versioned, so earlier outputs are never lost.\n\n## Editing by hand\n\nOpen an application and the resume preview is the editor: click any line and\ntype. It is the real template at real size, so what you change is what prints.\nThe facts the truthfulness check guards - company, role, dates, institution,\ncredential, certification name - are locked and carry a note saying they live\nin your Master Profile, which means a hand edit cannot break that contract. A\nsmall x beside each bullet, entry and section removes it. The cover letter is\nedited the same way, in place, on its own tab.\n\nSave rewrites all five export files immediately. There is no AI call and no\ncost. The voice check then runs over what you wrote and reports what it found\ninstead of refusing the save, since it is your own writing: curly quotes, the\nellipsis character and invisible spaces can be fixed for you in one click, and\njudgment calls like an em dash are highlighted where they sit and left to you.\n\n## Voice\n\nGenerated text that carries the mechanical tells of machine writing (em dashes,\nemoji, curly quotes, the ellipsis character, invisible spaces, and a short\ncurated list of recruitment cliches) is rejected on the write path, for both\nthe built-in pipeline and MCP agents; the pipeline retries once with the\nviolations attached, and an agent gets the list back to correct.\nThe list is deliberately short. Words with real, common, pre-LLM use in resumes - leverage, robust, scale, spearheaded - are not banned, because a false\npositive blocks a truthful resume and that is worse than an occasional\nstylistic miss.\n\nSet **Voice notes** on your profile to direct the writing explicitly, for\nexample \"Plain and direct. No salesmanship. Short sentences.\" Tailored also\nreads the register of the documents you uploaded during intake, as style only:\nevery fact still has to come from your Master Profile, and the truthfulness\ncheck is what guarantees it. The register comes from the newest document on\nthe person's profile, so removing a document on the Profiles screen changes it\nfrom the next generation on: to the newest one left, or to none.\n\n## Several people, one install\n\nOne install can serve several people, such as a household sharing a computer.\nEach person has their own Master Profile, applications and settings. The person\npicker in the nav bar, beside the theme toggle, chooses whose you are looking\nat, and every screen follows it: the dashboard lists that person's\napplications, Add Jobs adds jobs for them, Getting Started checks their\nprofile, and Profiles, Settings and Templates show theirs. Switching is one\nclick at any time, with no sign-in. To add someone, pick **Add a person** at\nthe bottom of the list.\n\n**The choice is kept per browser.** Tailored remembers the last person picked\nin the browser's own storage, and each Chrome profile has its own storage. If\neveryone in the household uses their own Chrome profile, Tailored opens on the\nright person for each of them, and anyone can still switch. A web page cannot\nsee which Chrome profile or Google account is signed in, so Tailored does not\nguess. The choice is never sent to the server and is not part of `data/`, and\nswitching in one tab leaves other open tabs as they were.\n\n**Opening another person's application.** A link to someone else's\napplication switches to its owner for that visit and says so. It does not\nchange which person this browser opens on next time.\n\n**Unsaved work.** Switching while the Profiles screen has unsaved changes or a\nsave, upload or Build still running, or while an application's editor has\nunsaved edits, asks first, with **Switch anyway** and **Stay**.\n\n**Settings are per person.** Default template, default research depth and page\nsize belong to the person selected when you change them, and the Settings\nscreen names that person in its heading. A new person starts from the\ninstall-wide defaults in `data/settings.json`. The API key and the theme are\nthe same for everyone. An application always renders with the page size of\nthe person it belongs to, whoever is selected in any browser, and that holds\nfor renders an MCP agent triggers too. Jobs an agent registers over MCP do not\npick up a person's default template or depth: they start on the Slate template\nunless the agent picks another, and a saved job an agent queued uses standard\ndepth if you later generate it from the dashboard.\n\n**Name, email and documents.** The Profiles screen edits the selected person's\nname and email. Build fills in only the contact details that are still empty,\nso building over an old resume does not change a name, email or phone number\nalready there. Each uploaded document has a **Remove** button, which is how to\nundo a resume uploaded to the wrong person. Removing a document leaves the\nbuilt Master Profile as it is; the next Build reads only what is left.\n\n**The Inbox link.** When a person's email is at Gmail (`gmail.com`,\n`googlemail.com`), iCloud (`icloud.com`, `me.com`, `mac.com`) or Outlook.com\n(`outlook.com`, `hotmail.com`, `live.com`, `msn.com`), an **Inbox** link beside\nthe picker opens their mailbox in a new tab. The Gmail link asks for that exact\naccount; the other two open whichever account is signed in on that site. A\ncustom domain, including Google Workspace, gets no link, because the domain\ndoes not say who hosts the mail. A connected agent gets the same link as\n`inbox_url` from `get_master_profile`, and the workflow guide tells it to\nconfirm the mailbox on screen is the person's before reading anything and\nnever to sign in for you.\n\n**Removing a person.** **Remove this person**, at the bottom of the Profiles\nscreen, states what will be deleted: the person's profile, documents and\napplications (archived ones included), plus their exported files. The button\nstays disabled until you type the person's name exactly. Removal is permanent\nand leaves everyone else's rows and files alone. It is refused while any of the\nperson's applications is being generated, or is parked for an agent, and has\nchanged in the last 15 minutes; the refusal links to those applications. Rows\nstuck in those states for longer count as abandoned and go with the rest. It\nis also refused, with nothing deleted, while another program has one of the\nperson's exported files open, since Windows will not move an open file; close\nit and try again. Saved jobs are removed too, including any an agent is\nworking through, so stop an agent working for that person first. There is no\nMCP tool for removing a person.\n\n**Agents and profile ids.** An agent never follows the picker. Every MCP tool\nthat works on a person takes an explicit `profile_id`, so name the person when\nyou ask an agent for work; the copyable prompts on the Getting Started and\nSettings pages name the person selected in the nav. After a removal, the next\nperson created can be given the removed person's `profile_id`. The picker\nguards against that by checking each person's creation time as well as the\nid, but an agent that kept an old id would act on the new person, so start\neach agent session from the person's name rather than a remembered id.\n\n## Development\n\n### Frontend (Node required for development only)\n\n```\ncd frontend\nnpm install\nnpm run dev        # Vite dev server; proxies /api to http://127.0.0.1:8547\n```\n\nRun the backend (`python run.py`) alongside `npm run dev`. **Before committing UI\nchanges, rebuild the bundle** — `frontend/dist/` is committed so end users don't\nneed Node, which makes it the UI people actually run:\n\n```\nnpm run build\n```\n\nThis is enforced, not left to memory. The build records a hash of every file it\nwas built from into `frontend/dist/build-inputs.sha256`, and\n`tests/test_frontend_bundle.py` recomputes it: editing `frontend/src` without\nrebuilding fails the Python suite, naming the files that moved on. Commit\n`frontend/dist` along with your source change.\n\n### Tests\n\n```\npytest -m \"not pdf\"      # fast suite (no Chromium)\npytest                   # full suite, incl. Playwright PDF tests\ncd frontend; npm test    # frontend tests\n```\n\nTests never call the real Anthropic API — the Claude wrapper has a fixture-backed\nfake mode (the same one demo mode uses).\n\n## Project layout\n\n```\ntailored/\n├── Tailored.bat             # double-click launcher (Windows)\n├── start_tailored.sh        # launch script (macOS/Linux: bash start_tailored.sh)\n├── run.py                   # one-command launcher (server + browser)\n├── requirements.txt\n├── .env.example\n├── backend/\n│   ├── app/\n│   │   ├── main.py         # FastAPI app; serves API + built frontend\n│   │   ├── config.py       # env / settings\n│   │   ├── db.py           # SQLite engine + sessions\n│   │   ├── models.py       # SQLModel entities\n│   │   ├── schemas.py      # Pydantic schemas (the resume JSON contract)\n│   │   ├── services/       # claude, intake, fetcher, research, tailor, render, pipeline\n│   │   ├── api/            # REST routes\n│   │   └── fixtures/       # offline fixtures (tests + demo mode)\n│   └── templates/          # one dir per resume template (template.json +\n│                           #   template.html + style.css), shared base.css,\n│                           #   vendored fonts/, + cover letter\n├── frontend/               # React + Vite + TypeScript (dist/ committed)\n├── tests/\n└── data/                   # gitignored: SQLite db, exports/, settings.json\n```\n\n## Known limitations\n\n- Dashboard polling stops until refresh if one status fetch fails.\n- Project bullets aren't editable in the profile editor (preserved on save).\n- Browser-printing an exported resume.html always uses Letter (PDF exports honor the page-size setting of the person the application belongs to).\n- Standard-depth research is domain-restricted only when a company domain was detected in the posting.\n- Token spend from a failed generation isn't counted into the displayed cost.\n\n## Troubleshooting\n\n- **LinkedIn or other login-walled postings** — sites that block bots land the\n  application in **\"needs paste\"** (not an error). Open the application, paste the\n  posting text into the prompt, and the pipeline resumes identically. A page that\n  comes back with almost no text (under about 400 characters, usually a\n  \"please enable JavaScript\" shell) and no schema.org job data is treated the\n  same way.\n- **Playwright browser missing** (`Executable doesn't exist` or similar) —\n  `Tailored.bat` / `start_tailored.sh` install this automatically on first run (and\n  print a warning if it fails, without blocking the rest of the app). If you're on\n  the manual setup, or the automatic install failed, install it yourself once:\n  `playwright install chromium`.\n- **Port already in use** — Tailored defaults to port 8547. If it's Tailored itself\n  already running, `run.py` detects that and just opens your browser to it instead\n  of erroring. Otherwise set `TAILORED_PORT` in `.env` (e.g. `TAILORED_PORT=8600`)\n  and restart.\n- **Missing API key** — the app still runs; generation actions will prompt you.\n  Set `ANTHROPIC_API_KEY` in `.env`, or use demo mode (`TAILORED_FAKE=1`).\n"}