job-workbench · Usage Guide
Everything you need to take the workbench from zero to running, plus how to use it day to day.
If something breaks, go straight to the FAQ at the end — it covers the traps already hit (blank screen, ports taken by another process, and so on).
简体中文同步版:usage-guide.zh-CN.md
I. One-time environment setup
Do this once. Skip it if your environment is already set up.
Required
| Software | What it is for | How to check |
|---|---|---|
| Python 3.12+ | Backend and CLI scripts (3.12 is the supported, CI-verified baseline; 3.9+ may run unverified) | python --version |
| Node.js 18+ | Frontend build | node --version |
| Chrome or Edge | PDF generation (headless rendering) | Usually already present |
Install dependencies (once each)
# Backend (upper-bound pins — relaxed step by step since the 3.12 baseline, each verified by a full regression run; current values in web/backend/requirements.txt)
cd <repo>\web\backend
pip install -r requirements.txt
# The backend imports the domain package at startup — without it uvicorn exits immediately:
pip install ../../packages/jobws-core
# Frontend
cd <repo>\web\frontend
npm install
Initialise your workspace (skip if you already have personal/)
python tools/jobws.py init --target personal --domain hvac-cooling
Swap in your own domain: see the "Domain profiles" table in
docs/README.en-US.md(with target audiences) for valid--domainvalues.
Distribute the AI workflows to your AI CLI (desktop-installer users need this once too) — the scoring / application / tracking skills are delivered by this step; without it the web UI works but the AI side has no workflows:
python tools/jobws.py skills install --target user
If you just want to see what it looks like without filling in any real information, use --demo:
python tools/jobws.py init --target demo --demo
That also lays down a full set of placeholder data (8 applications / 3 interviews / 2 contacts / 1 offer / 3 talks & job fairs / 6 question-bank items / 6 emails / 7 parsed JD cards / 1 resume). Every company and person name is fake, so you can click around or take screenshots safely; the software-backend domain profile is installed by default because the demo data's "direction" field depends on it. Once it is running, pick demo from the workspace dropdown in the top-right corner.
Fill in your profile: AGENTS.md (the prerequisite for scoring)
After init, AGENTS.md appears at the workspace root (template and per-item notes in template/AGENTS.example.md; English version: template/AGENTS.example.en-US.md). Section 3, "Hard-gate facts", must be filled — degree / major / graduation year / English / city; it is the single source of facts for the eligibility gate:
- If left unfilled: jobs whose fields stay
[待填]are classified "awaiting profile facts" — not scored, not terminated, but with no verdict either; - Once filled: the gate judges on facts, and only a definite mismatch terminates;
- City requirements come in two tiers: hard infeasibility (local hukou required, long-term field posting) terminates; preferences like "local candidates preferred" only deduct a few points from "Growth & stability" (-3 / -5, capped at 5 in total) instead of vetoing.
Section 5 carries your custom honesty red lines (two by default: every resume verb must survive questioning; never fabricate experience) — append as needed.
II. Day-to-day startup: the web UI
Option 1: one command (recommended)
cd <repo>\web
.\start.ps1
The script clears any occupied ports, starts the backend (8765), starts the frontend (5173), waits until both are ready, and opens your browser.
To stop it: close the console window the script runs in — both services stop together.
Option 2: manual startup (when you need backend logs)
Open two terminals:
# Terminal 1: backend
cd <repo>\web\backend
python -m uvicorn main:app --port 8765
# Terminal 2: frontend (on Windows you MUST use npm.cmd; plain npm fails silently)
cd <repo>\web\frontend
npm.cmd run dev
Addresses
| Address | What it is |
|---|---|
| http://localhost:5173 | The workbench UI (this is the one you use) |
| http://localhost:8765/docs | Backend API docs (generated by FastAPI) |
The frontend proxies
/apito 8765, so your browser only ever talks to 5173.
Option 3: double-click the exe (no environment needed; good for handing to someone or a quick look)
The build output lives in web/backend/dist/job-workbench-backend/ — double-click job-workbench-backend.exe inside it:
- Nothing is running yet → it starts the backend and opens a browser (the black console window is the service log; closing it stops the service)
- Already running → it just opens the browser, without starting a second service
- The port is taken by another program → you get a plain-language message and the window stays open (it does not flash and vanish)
⚠️ The exe is a "clean distribution" and does not read your personal data. The portable workspace sits next to the exe (
personal/, empty by default) — that is deliberate, so your job-search data never travels with an installer. Use option 1 or 2 for your own day-to-day work (data lives at the repo root); use the exe to validate a build or to hand to someone else. If you insist on pointing the exe at your own data, setJOBWS_DATA_DIRto the repo root (not recommended — it forks your data).
Rebuild the exe:
powershell -ExecutionPolicy Bypass -File scripts\build_backend_exe.ps1
Desktop installer (Electron shell, since v0.1.1)
If you would rather not set up an environment at all, grab job-workbench-setup-*.exe from the latest release (an Electron window plus a packaged backend; install it and run). Where data goes: the installed version uses %APPDATA%\job-workbench\personal\; the portable version (a portable.txt next to the backend exe) uses personal\ beside the exe — uninstalling the installed version does not touch the data in %APPDATA%.
The installer is now a wizard: you can pick the installation folder and choose between "for all users" and "just me". When upgrading an existing install, keep the default choices ("just me" + the existing folder) — picking "all users" installs into Program Files and forks away from the old install. Silent install still works: /S (add /D=<dir> to override the folder).
The desktop app can zoom the interface: drag the Interface size slider in Settings (the whole UI scales, text included), or use Ctrl + = / Ctrl + - (half-step, roughly 0.58x–1.73x) / Ctrl + 0 to reset. The level is remembered across restarts, and the slider and the shortcuts stay in sync.
Rebuild the installer in one command (frontend dist → PyInstaller backend exe → NSIS):
powershell -ExecutionPolicy Bypass -File scripts\build_desktop.ps1
The output is web/electron/release/job-workbench-setup-<version>-win64.exe (the product name is Job Workbench: installer and Start Menu entry are English; the window title follows the interface language). Smoke test: <installer> /S installs silently to the default per-user location (/D=<dir> overrides the folder), launch it, then check http://127.0.0.1:8765.
III. The eight pages of the web UI
Dashboard (home)
This is what opens first. It answers three questions: how many companies have I applied to, how many are still in flight, and what needs doing in the next seven days.
- Four stat cards at the top: total applications / in progress / to-do in 7 days / past deadline (click to drill into the tracker)
- Application funnel: how many people at each stage (to apply → applied → written test → … → signed); click a bar to drill into that stage
- Needs action: active roles with an unhealthy status (urgent / overdue / stalled), each with a concrete reason; click to drill in
- To-do in 7 days: records whose next-action or deadline falls within the next 7 days (one click to postpone by 7 days)
- Talks in the next 7 days: talks / job fairs whose time falls within the next 7 days (click to jump to the Talks tab under Prepare)
- Past deadline: records still sitting at "to apply" whose deadline has passed (red)
- Silent reminder: active roles with no progress for a long time (over 14 days by default)
- Cycle review: stage conversion rates (rebuilt from the timeline), median days spent per stage, failure attribution and failure clustering
Tracker
A table of every application. Six things you can do:
- Filter and search: by stage / direction / batch, plus keyword search over company, role and notes
- Sort: by next-action date / score / days in stage / health (urgent > overdue > stalled > normal; hover the badge for the reason)
- Change stage: click the "current stage" dropdown on a row to change it (to apply → applied → written test → …); it saves automatically
- Add, or import in bulk: "Add application" at the top right for one at a time; "Bulk import" to paste or upload a CSV, which first shows a diff preview (added / duplicate / error rows in different colours with line numbers and reasons) and blocks submission while any row has an error
- Status reason: edit in place per row; moving to "declined / gave up" requires a reason, and once in either of those states the stage can no longer be changed (to apply again, create a new record)
- Row expansion: see that record's change timeline and how many days it has sat in the current stage
Two rules (shared by CLI and web, enforced automatically): 1. Terminal states do not roll back: after "declined / gave up", the current stage is locked; only the status reason and notes stay editable 2. Deduplicate by company + role: a duplicate entry is blocked and points you at the existing record id (applying again after being declined is allowed)
The options in the direction dropdown come from the domain profiles installed in your workspace (
config/directions/), not from a hardcoded list.
Job pool
Manage the roles you have looked at. Each card shows company_role, its score and its tier.
- New role: paste the full JD (responsibilities + requirements) and it is archived verbatim; or paste a URL and let it fetch the page (pages that need a login, block scrapers or render with JS produce an explicit "paste it manually" message — it never pretends to succeed)
- Open details: JD on the left, parsed card on the right
- Eligibility gate (pinned above everything): the "hard eligibility" card at the top of the parsed card is shown before the score — pass (green) / fail (red) / awaiting profile facts (amber: your profile is missing facts — not scored, not terminated, the card names what to fill and points at AGENTS.md) / pending (neutral outline). A fail is pinned and highlighted in red, and the score becomes secondary information (it means you are not eligible — do not apply)
- Score drill-down: each of the four dimensions (technical / experience / direction / growth) expands to per-item hits, each with a capability tier (Primary/Secondary/Weak), an evidence label (exact / vague / semantic badge) and a hit explanation. If you want to know "why this score", open it
- Resume gap panel: the gaps between this JD and your resume, split into "recallable" (it exists in your master resume) and "real gaps" (only new experience can fill them)
- Parsed card: the score is produced by the AI inside CodeBuddy and written to
解析卡.md; this page renders the four dimension bars, total score and tier - Roles without a score show "no parsed card yet" plus instructions — the web UI does not score anything; scoring is the AI's job
Resume workshop
Two modes:
- Standard layout (data-driven): structured form on the left, live A4 preview on the right; create versions right on the page; after generating a PDF it reports the ATS check and paper-size check, with an overflow guard
- Layout & accent color: the layout dropdown ships Classic / Compact / Accent (all single-column and ATS-checked; Compact fits more onto one page); four accent presets (石墨灰 / 商务蓝 / 深墨绿 / 酒红 — Graphite / Business blue / Deep green / Wine) combine freely with any layout — switching refreshes the preview, and the generated PDF uses the same parameters. CLI equivalent:
resume render --template std_compact --resume-accent 酒红 - One-click import: upload a PDF / Word (.docx) / Markdown / plain text and it is extracted into structured fields. Extraction, not generation: fields the model did not find are marked yellow, suspected fabrications red, and nothing is written until you confirm section by section
- AI rewrite suggestions: a BYOK model only rephrases existing facts; suggestions that fail the anti-fabrication guardrails cannot be accepted
- Export Word: a dependency-free
.doc, handy for pasting text into online application systems (the PDF is the source of truth for layout) - Advanced template (hand-written HTML, precise layout): read-only viewing and one-click generation
At the bottom there is also a version lineage: which resume version went to which roles, and how far each got.
Prepare
Everything before you apply lives in one place — talks, your question bank and notes (moved out of Progress on 2026-09-18, since that page is about the after-applying side):
- Talks / job fairs: time, format, place and takeaways, exportable to
.ics(reminder 1 hour ahead); they do not advance any stage. The dashboard's "Talks in the next 7 days" jumps straight here - Question bank (two views): My bank = your own editable
questions.csv(domain / subject / status未看·看过·会了/ answer notes), which you can fill by read-only parsing → preview → confirm from03_面试准备/**/*.md(two-phase); click any row to open its details and edit the answer notes, flip the three-state status, adjust difficulty, or edit tags and notes right there — the same two-phase feel (review the diff, then confirm; nothing is written before you confirm, and cancelling leaves no trace); Asked before = questions you were actually asked, grouped by company + role, with keyword search — read what this company already asked you before the next round. On the CLI,jobws bank export --csv <file>exports the whole bank,jobws bank import-csv <file>imports one (preview first, then apply with a token; exporting and re-importing is idempotent),jobws bank duelists today's review (unseen is always due; seen 3 days / known 14 days; empty or dirty dates count as due, conservatively), andjobws bank wrongshows the wrong-answer book (--add/--remove <id>toggles it — tagging is just adding/removing the "错题" tag, same effect as editing tags in the question details dialog);jobws bank deleteremoves questions — a single one with--id Q012, or a whole mistaken import with--origin 导入 --today(also combinable with--domain/--subject/--keyword/--company). Deletion keeps the same preview-first flow (it lists exactly which rows would go, including source and creation date), then applies with a token; before writing it snapshots the whole table outside the workspace (default path<snapshot root>/<workspace name>/question-deletes/questions-before-delete-<timestamp>.csv—jobws applyprints the exact path; to restore, copy that CSV back over05_投递追踪/questions.csv, which resets the table to the moment before the deletion, so any later edits are overwritten as well). It also re-checks a row fingerprint (id + question + domain + subject + created date) before writing: ids get reused, so if the row was edited or replaced after the preview, the whole deletion is refused and you must preview again. In the UI, "Delete this question" sits at the bottom of the question details dialog (bulk delete is CLI-only) - Adding a question yourself (2026-09-21): "Add question" at the top of the panel adds one card to the bank — fill in domain / subject / difficulty / answer notes and it goes through the same preview the fields, then confirm flow (the very same validation as the CLI
jobws bank addand the MCPpreview_add_question). Rows also carry a "due" and a "wrong" badge — due is computed from the review intervals (unseen is always due; seen 3 days, known 14 days) and hovering the badge explains why it counts as due; "wrong" follows the tag (the same tag rule the training panel uses) - Training (the "practice" entry for your bank): draw questions → answer blind (the answer is collapsed by default) → reveal and compare → self-grade across the three states / flag as wrong → next question. Three modes: the retry queue (wrong ∪ due today), wrong-only, and random; narrow by domain / subject / status / keyword. No new write path — self-grading edits "status", flagging edits "tags", both preview-then-confirm; the CLI equivalent is
jobws bank drill --mode due|wrong|random --n 5. Three behaviours worth knowing: the round lives in the session — every write trips the workspace fingerprint and refreshes the page, but the drawn questions and your progress (including the collapsed state) come back exactly as they were (the token itself is not kept; it expires in ten minutes); an "未看" card that is already unseen does not fire a write request (there is nothing to change) and moves straight to the next question; and "看过" deliberately does not write "recently reviewed", so the card stays in the retry queue (unseen, or seen without a recorded review date, conservatively counts as due) — press "会了" if you want it gone for 14 days. The finish card at the end of a round reports how many writes actually landed (cancelled, failed previews and conflicts do not count) plus the current three-state split of your bank — seeing "会了" grow is the actual progress signal. The whole round can be driven from the keyboard: Space / Enter reveals, 1 / 2 / 3 self-grade across the three states, W flags as wrong, ← / → move between questions, Esc closes the confirmation card (but not while a request is in flight). Keys are not captured while focus sits in an input or a dropdown — opening the "questions per round" select does not turn Space into "reveal", so the blind-answer discipline is not broken by the UI itself - Notes: read the Markdown in
03_面试准备and04_知识库right in the app — a directory tree on the left (READMEshows as "Directory guide",_模板_/_示例_templates are dimmed, empty and unreadable files are flagged, filter by file name), rendered text on the right (headings / lists / checkboxes / tables / code blocks / quotes) with an "On this page" outline; files over 256 KB are truncated with an explicit notice. Edit files in your own editor — the view refreshes within ~10 s (returning to the same tab and file); checkboxes are clickable: a click shows the "before → after" line diff and, once confirmed, flips exactly one character on that line — the rest of the file stays byte-identical (it goes through the app's single two-phase write path; if the line changed after the preview, the write is refused and asks for a fresh preview). Full-text search looks for a keyword in file names and body text; picking a hit opens the file and scrolls to that line (only the first 50 hits are listed, and it says so; files that cannot be read or exceed 256 KB are listed as "not searched fully" rather than silently skipped)
Progress
The main arena after you apply. Four sub-tabs:
- Interviews: three-part record (question → answer points → retro); anything still pending within 48 hours is highlighted amber; one-click export to
.ics - Emails: a ledger of messages (interview invites, test notices, rejections…), linkable to an application record with inline tag editing; emails with a Message-ID get "Open original" (a Gmail search deep link) while mailboxes without a usable deep link (Outlook / QQ / 163 …) get a "copy the subject and search in your mailbox" fallback; a Meeting link column carries the parsed join URL (Tencent Meeting / Zoom / Teams / Meet…); emails never change stages automatically
- Contacts: follow-up cadence for recruiter contacts, amber when overdue, one-click "contacted"
- Offer comparison: known facts of several offers side by side. Side by side only — no recommendation
Library
Read-only view of the fact base (00_事实库/, maintained by the CLI / AI): fact cards are rendered as formatted text (tables / checkboxes / quotes / code blocks); files over 256 KB are truncated with an explicit notice; edits made outside refresh within ~10 s. Résumé-related files moved to Resume Studio (2026-09-03) and are no longer shown here.
Settings (Provider)
The "Settings" page in the nav configures your AI provider (BYOK, bring your own key):
- Provider preset: pick one from the dropdown (DeepSeek / Zhipu GLM / Qwen / Kimi / SiliconFlow / OpenAI) and the endpoint template fills itself in; or choose Custom (OpenAI-compatible) and type any compatible endpoint. A suspicious URL only earns a non-blocking hint (Qwen needs the
…/compatible-mode/v1path, and/chat/completionsis not a base URL) — saving is never blocked, because plenty of gateways use custom paths. - Base URL: an OpenAI-compatible endpoint (including
/v1, e.g.https://api.orcarouter.ai/v1) - API Key: stored locally only; the UI shows just the masked last 4 characters
- Default model: set it once here and resume import / resume rewriting / email AI all share it. Each of the three can still be changed in place — that change is a local override and never rewrites this default. Leave it blank and all three ask you for a model name.
- Test connection: calls
{base_url}/modelsto verify the key and list models (it also reports the total, and says so when only the first N are shown); the returned model names are clickable chips — one click fills in the default model. An empty model list does not mean it will not work: some servers do not implement/models, so just type the model name. Outbound HTTPS verifies certificates by default (JD fetching and resume rewrite use the same policy): if the system certificate store cannot be loaded, the request is refused with a "fix the store (certmgr.msc)" message instead of silently skipping verification — your API key never travels over an unverified connection. The explicit opt-out isJOBWS_HTTP_TLS=insecure(not recommended) - Data and privacy: export everything as a zip, snapshot backup to the system user directory, one-click snapshot restore (rehearse first, then restore), diagnostics package export (version / platform / self-check summary / log tail — no workspace data, no credentials), open the data directory, no-telemetry statement
- Due reminders: on/off switch + "remind N days ahead" (3 / 5 / 7); notifications dedupe per item per day, clicking one lands on that tracker row (full wording under Progress)
- The settings page is searchable (including
@modifiedto show only changed items) with per-item reset; changed items carry a mark - Export as Obsidian notes (CLI):
python tools/jobws.py export --obsidian <dir>— one directory per CSV and one Markdown note per row, with all original columns in frontmatter (so Obsidian Bases / Dataview can filter and sort), question-bank notes carry spaced-repetition card syntax; output is timestamped, never overwrites a previous export, and never touches the workspace (a snapshot, not a sync). With--notes, the Markdown under03_面试准备/04_知识库/00_事实库is projected into notes as well (directory layout kept, full reads, no truncation, body copied as-is), so your phone holds the full material besides the flashcards;训练卡is not projected again (those cards already appear in the bank); hidden files (drafts, atomic-write temp names) are skipped by design and not listed, while files that fail to read or exceed 2 MiB are skipped and listed in the export's README; the output directory must live outside the workspace (pointing it inside is refused outright). Updating your phone vault: add--sync-to <vault dir>to mirror the new snapshot into a fixed Obsidian vault — the whitelisted entries (the eight table directories /README.md/jobws.base/ the material directories) follow managed semantics (they get overwritten; rows deleted in the source disappear here too — deletions require--yesor the command refuses and leaves the vault untouched); everything outside the whitelist and the vault's.obsidian/stays byte-identical; and notes that differ only by spaced-repetition annotations (<!--SR:…-->) or whitespace (blank lines / trailing spaces) are not overwritten — the annotations are your phone-side review progress and overwriting them would reset it — only real body changes update a note (that note's card progress resets). There is no force-overwrite switch: to realign a file, delete it in the vault and re-run (it comes back as a new file). Every run prints the new/updated/deleted plan before executing, and--dry-runstops after the plan; after a version upgrade, run--dry-runfirst (if the rendering template changed, the whole vault updates and all card progress resets). The full update order when copying by hand: ① copy the phone vault back to the computer, over the computer's vault directory (this brings the progress home) → ② run the sync → ③ copy the vault back to the phone; with a two-way cloud-sync folder the round trip happens automatically - No API key yet?: while no key is stored, the page shows a sign-up entry for a recommended provider, plus a button that fills in that provider's Base URL for you (the preset lives in one place in the source). The entry is labelled on screen as a referral link — signing up through it credits the project author; your pricing is unaffected, and clicking it only opens a web page (no data leaves the app). The entry currently points to OrcaRouter.
By default the AI judgement in this workbench is done by your AI CLI (CodeBuddy and friends); the provider is an optional BYOK enhancement (used for resume import and AI rewrite).
Optional: read-only email fetch
If your mail provider supports IMAP, the workbench can pull recent recruiting emails so you do not have to copy-paste each one. Configure it under Settings → Read-only mailbox fetch (optional) — 「邮箱只读拉取」in the Chinese UI (the field takes your IMAP authorization code, not your web login password; most providers require you to enable IMAP in the web mail settings first).
Which providers are supported: the top of the card is a provider dropdown — QQ Mail / Foxmail, NetEase 163, NetEase 126, yeah.net, Gmail, Outlook / Hotmail, Alibaba Cloud Mail, Sina Mail, Sohu Mail, plus Other (enter manually). Picking one fills in the server and port — once you edit those fields yourself, nothing overwrites them again; you can also leave the server blank and let the domain decide. Each provider carries its own inline guidance (where to enable IMAP in the web client, how to obtain the app password / authorization code). 163 / 126 / yeah.net have one extra hard requirement — the client must declare its identity (IMAP ID) before opening a folder — and this workbench does that for you automatically. Outlook / Hotmail personal accounts are not supported yet: Microsoft retired basic authentication on 2024-09-16 and third-party clients must use OAuth2, while this workbench only supports username + app password, so the UI says exactly that instead of offering a field you can fill in and never connect (see the OAuth2 + IMAP feasibility study). After a successful test you can press Load available folders to get candidates (read-only LIST, one connection per click): click one to fill it in, or keep typing your own.
How it works, and what it will not do:
- Read-only: the connection uses
SELECT (readonly)andBODY.PEEK— it never sends, marks as read, moves, or deletes anything. - Connected only when you click: the mailbox is contacted exactly when you press Fetch from mailbox (「从邮箱拉取」) — there is no background timer, polling, or keep-alive.
- Credentials go to the system credential store by default: the desktop build writes the app password to Windows Credential Manager, and
<workspace>/config/imap.jsonkeeps only a reference; values are masked in the UI and in error messages, and are excluded from export / backup zips. The source / CLI form (or an explicitJOBWS_CREDENTIAL_STORE=plaintext) is a plaintext fallback: the app password then lives in<workspace>/config/imap.json, readable by any process that can read your user directory — treat the workspace as sensitive and keep it out of synced folders or public shares. When the desktop build reads an old plaintext value it migrates it into Credential Manager; if that write fails the plaintext stays untouched (your password is never lost). After moving to a new machine, switching Windows accounts, or copying the workspace elsewhere, that reference cannot be resolved — the app tells you to save it again instead of pretending nothing was configured. When you want to forget the secret, the settings card can clear it for you — the entry in the system store and the reference in the config file are removed together (you will need to enter it again to use it). - Dry-run by default: fetching only lists emails; nothing is written to the tracker until you confirm a suggestion item by item.
- TLS certificate verification is on by default: the connection verifies the mail server's certificate against your system trust store. On machines whose Windows certificate store is corrupted,
create_default_context()fails and the connection is refused (your authorization code must not travel over an unverified connection). If you understand the risk — the connection can then be intercepted by a man-in-the-middle — you can explicitly set the environment variableJOBWS_IMAP_TLS=insecureto skip verification.
Typical flow: Tracker → Fetch from mailbox → pick a time window (last 7 / 30 / 90 days) → filter by keyword locally → click an email → review the parsed suggestion → confirm what to write back. All three body wordings are labelled in the UI: the fetch list shows the original (quotes and signature kept, truncated past 140 characters); opening one pre-fills the full original (capped at 4000 characters server-side, marked "Truncated" when it hits the cap); and the snippet on a ✨ suggestion card is an excerpt (quotes and signature already removed, used for parsing only). They differ on purpose — it is not lost data. If the email belongs to an application you have not recorded yet, the empty state offers a create-record form (the stage defaults to what the email implies).
Each fetched email has three destinations: clicking the message body goes through parse → suggestion → confirm to update an application's stage; clicking ✨ expands parse suggestions in place (six kinds: time / deadline / link validity / meeting link / suggested stage / matched record — each with its source snippet and confidence, written only after you confirm; invitations are read from the calendar attachment first, which beats regex on the body); clicking the small "+" icon on the right records the email's metadata into the email ledger (subject / sender / date / Message-ID) — later, add a tag and a link under Progress → Emails instead of typing it all again.
Values marked "Needs check" (no year in the text, relative dates like "tomorrow" or "this Friday") require ticking "I've checked the value" before writing; meeting-link cards can be copied or opened directly, and once recorded the link stays openable/copyable under Progress → Emails. Parse suggestions live inside Fetch from mailbox — a manually pasted body has no calendar attachment and keeps using the original Paste email dialog. With a BYOK provider configured (Settings → Provider), an optional AI boost row appears at the bottom: enter a model name and run it manually — same suggestion flow, always labelled "AI", still confirmed by you. AI only suggests; it never writes.
Sentences like "please finish the online assessment before the 25th" become a deadline fact — the fifth kind. The card carries the task it belongs to ("Finish the online assessment") and the date; confirming it writes both the tracker's next-action date and its next-action text. Those are exactly the fields the due reminder reads (the dashboard's upcoming list and the system notification), so the countdown appears on its own — no extra setting. Relative dates cover today / tomorrow / the day after / "this X" and "next X" (both Monday-based weeks); a date that has already passed is the overdue signal itself and stays marked "Needs check". Duration phrasings ("within N days / N working days / the link is valid for N hours") are counted from the day the email was sent — a mail received three days ago saying "within 3 days" is already overdue today, not "3 days out"; working days skip weekends but not public holidays; "the link … is valid" is its own kind, separate from the thing you must finish.
What the due reminder covers in full (desktop): Settings lets you pick how many days ahead to remind (3 / 5 / 7, default 3). A notification fires when any of these is true: overdue (both "deadline passed and never applied" and "the next action date passed", sorted by how long ago), a to-do within the lead time (today's items and "N days left" items read differently in the body), or a talk within 7 days. Deduplication is per item, once a day — one you were already told about stays quiet, but one that appears later the same day still fires. Clicking the notification opens the tracker with that row expanded. A persistent reminder bar sits at the top of the content area (same endpoint, same switch): opening the app shows "N overdue / N to-dos within the lead time / N talks", each linking to the page that handles it — when nothing is due it renders nothing at all. Turning the switch off stops both the notification and the bar; closing the window stops everything — no background service, no tray icon.
IV. Putting the AI to work: the jwb-jd / jwb-apply workflows
Plugging into another AI host (MCP, optional)
The workbench also ships an MCP server, so hosts that speak MCP (Claude Code, Codex, Gemini CLI, …) can read your tracker, job pool and dashboard — and write only after you confirm (the AI shows you a diff first, then writes with a one-shot token).
- Install and per-host config shapes:
docs/mcp-integration.md(the config key differs per host — do not copy blindly). - What each of the four entries (CLI / AI host / editor plugin / desktop UI) can do:
docs/four-ends.md.
The web UI is for looking and recording; judgement belongs to the AI. Talk to it in natural language inside CodeBuddy:
Parse a role (jwb-jd workflow)
Paste the JD, or say "parse this JD". The AI will:
- Save the JD text under
personal/01_岗位池/<company>_<role>/ - Run the hard eligibility filter (degree → major → cohort → English → city) against
personal/AGENTS.md - If it passes, score four dimensions (technical 30 / experience 25 / direction 30 / growth 15) and cross-check against your fact cards
- Verify the total and output a tier (75–100 apply strongly / 60–74 apply / 45–59 think about it / 30–44 likely skip / 0–29 do not apply)
Failing a hard requirement stops everything — no score, no materials. For example a role with a hard language requirement is blocked by your own language red line.
Generate an application package (jwb-apply workflow)
Say "generate an application package for role XX". The AI will: pick the resume version for that direction → adjust four places as needed (project facts are never touched) → generate the PDF → run the ATS check (page count / text layer / key facts) → archive under 05_投递追踪/applications/ → write to the tracker → list the changes and hand them to you to confirm before committing to git.
Update progress (jwb-track)
After an interview, say "move XX to first round", or just use the dropdown in the tracker.
Rebuild the resume PDF after edits (jwb-resume workflow)
Say "rebuild the resume PDF", or from the command line:
python tools/jobws.py resume --workspace personal --version hvac
V. CLI quick reference (when you would rather not use the web UI)
Run every command from the repo root. --workspace personal can be omitted (it is the default).
# Tracker
python tools/jobws.py track list # every record
python tools/jobws.py track list --due-within 7 # due within 7 days
python tools/jobws.py track list --stage 笔试 # filter by stage
python tools/jobws.py track add --company "某公司" --role "岗位" --direction hvac --batch 正式批 ...
python tools/jobws.py track update --id A001 --stage 一面 --next "准备口述" --next-date 2026-09-10
python tools/jobws.py track history --id A001 # change timeline
python tools/jobws.py track import --file 待导入.csv --dry-run # bulk CSV import (preview diff only)
python tools/jobws.py track import --file 待导入.csv # write, once the preview is clean
python tools/jobws.py track interview add --app A001 --round 一面 --questions "..." # interview record
python tools/jobws.py track contact add --name "张工" --app A001 # recruiter contact
python tools/jobws.py track offer add --company "某公司" --monthly "..." # offer facts
python tools/jobws.py track mail add --subject "Interview invite (round 1)" --app A001 --tag 邀约 # email ledger
python tools/jobws.py track mail list --app A001 # emails linked to one application
python tools/jobws.py track check # schema self-check (quarantines broken files)
# Application funnel report (Markdown, incl. cycle review and failure clustering)
python tools/jobws.py report --stdout
# Resume PDF + ATS check
python tools/jobws.py resume --workspace personal # all versions
python tools/jobws.py resume --version hvac # one version
python tools/jobws.py resume render --workspace personal # data-driven standard layout
python tools/jobws.py resume render --template std_compact --resume-accent 酒红 # layout + accent color
# JD parsed-card score validation
python tools/jobws.py jd "personal/01_岗位池/<dir>/解析卡.md" --domain hvac-cooling --direction hvac
python tools/jobws.py jd --gap --resume hvac "personal/01_岗位池/<dir>/解析卡.md" # JD↔resume gaps
The stage names above (
笔试,一面, …) are the actual values written into your data files, which is why they appear in Chinese. They are validated against a fixed list (STAGESinjobws_core.tracker), so they cannot simply be renamed to English.
VI. Where your data lives, and how to back it up
All data is plain text, under personal/:
| Directory | Contents |
|---|---|
00_事实库/ |
Project/internship fact cards — the single source of truth |
01_岗位池/ |
One directory per role: the JD text + parsed card |
02_简历工坊/ |
Resume markdown + HTML templates + generated PDFs + photo |
03_面试准备/ |
Self-intro, project pitches, behavioural stories, retros (the question bank lives in 05_投递追踪/questions.csv below) |
04_知识库/ |
Knowledge dictionaries — built by you on demand (the skeleton ships a README only) |
05_投递追踪/ |
Eight tables: tracker.csv (applications) + history.csv (change timeline) + interviews / contacts / offers / talks / mails / questions.csv |
AGENTS.md |
Your profile: hard-requirement facts, honesty red lines |
Backup (important): all of personal/ is gitignored — git does not track your data, and a clone of the repo carries none of it. The two supported ways to back up:
- Settings → "Back up now": snapshot to the system user directory (outside the workspace; one click; recommended)
- Settings → "Export everything as zip": a complete workspace you can take anywhere
⚠️ Sharing the repository itself is safe —
personal/is not in it. Never send thepersonal/directory to anyone (it holds your name, photo and contact details).
VII. FAQ
localhost:5173 is blank / will not connect
Work through these in order:
- Is the service alive? Restart with
cd web && .\start.ps1(it clears the ports for you). Vite is a foreground process — if the terminal that started it is closed, the service is gone. This is by far the most common cause - Is the browser caching old JS? Force a reload with
Ctrl + F5 - Still blank? The app now has an ErrorBoundary, so a crash shows an error message instead of a blank page. Open the Console (F12) and send me the error
The startup script errors
start.ps1 locates the repo root with three fallbacks (argument → script location → walking up for AGENTS.md). If it says it cannot locate the repo root, run it from inside the repo, or pass the path explicitly:
.\start.ps1 -RepoRoot <repo dir>
npm does nothing
On Windows you must start the frontend manually with npm.cmd run dev. A plain npm run dev can fail silently depending on how it is invoked (no error, and nothing listens on the port).
The port is taken
start.ps1 frees 8765/5173 automatically. To do it by hand:
Get-NetTCPConnection -LocalPort 8765 -State Listen | ForEach-Object { Stop-Process -Id $_.OwningProcess -Force }
A job card in the UI has no score
A score needs two things: the 解析卡.md must exist and its four dimensions must add up to a consistent total (validated by the jd_score rules). If you hand-edited a parsed card and broke the format, the UI treats it as "not scored". Run python tools/jobws.py jd <path to parsed card> to see exactly what does not add up.
CSV opens as gibberish in Excel
It should not — files are written with a UTF-8 BOM (utf-8-sig). If it really is garbled, some other tool rewrote the encoding; tell me and I will fix it.
Double-clicking the exe does nothing
With console=True, a black window that flashes past means it failed at startup. Causes and what to do:
- Port taken → the exe now prints a clear message and keeps the window open (unless your antivirus interferes). First check who holds the port:
Get-NetTCPConnection -LocalPort 8765 -State Listen - A freshly built exe being empty is expected — see "Option 3" above (the exe is a clean distribution; it does not read the repo-root data)
The page title says "Vite + React + TS"
That is a stale artifact from an older build or a cached bundle. Force-reload with Ctrl+F5; if it persists, rebuild the frontend (cd web/frontend && npm.cmd run build) and restart the backend. The new correct title is "Job Workbench" (the window title follows the interface language — 求职工作台 in Chinese).
.ps1 scripts throw ParserError / mojibake
A .ps1 containing Chinese must be saved as UTF-8 with BOM. Save it without the BOM and Windows PowerShell 5.1 decodes it as GBK, producing syntax errors. Both scripts (start.ps1, build_backend_exe.ps1) ship with a BOM — do not strip it if you edit them.
VIII. Where to go next in the docs
| What you want | Where to look |
|---|---|
| Overall architecture (three-layer split, domain profiles) | docs/specs/2026-08-30-general-workbench-design.md |
| Web layer design (API contracts, concurrency, security) | docs/specs/2026-08-30-web-prototype-design.md |
| AI workflow definitions | The nine SKILL.md files under skills/ (five job-hunting + one setup/extension + three maintainer-facing; each keeps the main file lean and puts details in its own references/. tools/jobws.py skills install distributes all three asset types — skills, commands, subagents) |
| Support & compatibility | docs/support-and-compatibility.md (what counts as a stable release, what is supported, why upgrades never require converting data) |
| Key decisions (why it is built this way) | the short records under docs/decisions/ (human-confirmed writes, single release node, data compatibility, hooks off by default) |
| Documentation index | docs/README.en-US.md |
Appearance: themes and typefaces
The "Appearance" card on the Settings page (device-level preference — this machine only, not exported with the workspace):
- Themes: dark by default, plus a light theme and 8 open-source skins (Catppuccin Mocha·Latte / Nord / Tokyo Night / Rosé Pine + Dawn / Gruvbox / Everforest) and a "System" option; "Custom theme…" tweaks key colors with live contrast feedback and saves into the list; JSON and tweakcn / shadcn CSS can be imported.
- Typefaces: 12 UI options (Inter by default; Geist, IBM Plex Sans, Manrope, Plus Jakarta Sans, DM Sans, Figtree, Outfit, Public Sans, Source Sans 3, Work Sans, Atkinson Hyperlegible Next; plus the system stack and serif) and a separate monospace slot with 6 options (Maple Mono by default; JetBrains Mono, Fira Code, Geist Mono, IBM Plex Mono, Source Code Pro) for code / IDs / dates, and a dedicated numerals slot (Geist Mono by default; JetBrains Mono, IBM Plex Mono, or "follow the UI typeface") for values — all bundled locally under OFL-1.1, Latin subsets only, so CJK text falls back to the system stack. Interface size is a continuous slider (80%–150%, 5% steps). Terminal fonts live in your terminal app —
jobws prefs doctorprints a recommended list (Maple Mono / JetBrains Mono / Sarasa Gothic). - Layering: themes / typefaces are device-level (localStorage);
jobws prefs(theme / font / resume_style) is workspace-level material preference for the CLI, skills and exports.
Theme changes are gated by jobws lint themes (parity / contrast / elevation ladder) — all 10 built-in themes satisfy 4.5:1 for body text and 3:1 for large text and graphics.