@findmynew/client
v0.0.3
Published
Local agent for findmynew.work — drives the user's browser for login, verify, search and apply against a remote findmynew API.
Readme
@findmynew/client
fmwork — the local agent for findmynew.work. It drives your real Chromium profile to log into job sites, harvest vacancies, and submit applications on your behalf.
Why this exists
findmynew.work scores postings and drafts cover letters for you. It deliberately doesn't host your browser session — your LinkedIn / hh / GetMatch / Wellfound cookies stay on your machine. fmwork is what runs on your laptop, owns a persistent Chromium profile under ~/.fmwork/<source>-profile, and executes the actions the findmynew dashboard asks for.
Install
# One-shot
npx @findmynew/client <command>
# Or install globally
npm install -g @findmynew/client
fmwork <command>Chromium is downloaded automatically by Playwright on first install (postinstall hook).
Configure
Set your API key (issued from the findmynew dashboard):
fmwork config set apiKey fmwork_xxxxxxxxxxxxxxxxxxxxxxxxStored at ~/.fmwork/client.json. You can also pass FMWORK_API_KEY via env.
Running from a checkout (dev)
git clone [email protected]:findmynew/client.git
cd client
npm install
# Just run the CLI:
npx tsx src/cli.ts <command> # always forwards every flag — safest
# Or via npm (NOTE: npm STRIPS --flags from script args):
npm run dev -- search linkedin --debug # the `--` separator preserves --debug
FMWORK_DEBUG=1 npm run dev search linkedin # env-var path, no `--` needed
npm run search:linkedin:debug # dedicated script with FMWORK_DEBUG baked inIf you ever see npm run dev search linkedin --debug doing nothing visible — that's npm eating --debug before it reaches the CLI. Use one of the patterns above.
Commands
| Command | What it does |
|---|---|
| fmwork login <source> | Opens a real Chromium window so you can sign in to one of hh / linkedin / getmatch / wellfound. |
| fmwork verify <source> | Headlessly opens the persistent profile, navigates to a known signed-in URL, and reports loggedIn=true/false. |
| fmwork logout <source> | Wipes the local profile dir for one source. |
| fmwork status | Per-source: profile present, last-known logged-in state, last verified-at. |
| fmwork start | Daemon mode. Stays connected and executes actions as they're triggered from the findmynew dashboard (verify, search, apply). Stays running until you ^C. |
| fmwork run [--limit N] [--dry-run] | Batch mode. Pulls your pending apply queue (vacancies you marked "send" in the dashboard), drives each apply in turn, exits. No long-running process required. |
| fmwork search <source> [-q "kw"] [--limit N] | One-shot scrape of <source>. |
| fmwork restart | Stops a running start process and starts a fresh one. |
| fmwork config <get\|set\|unset> [key] [value] | Manage local config at ~/.fmwork/client.json. |
Two ways to use the client
- Daemon (
fmwork start). Keep it running in a terminal. Actions you trigger in the dashboard ("re-verify my LinkedIn login", "scrape hh for 'staff frontend remote'", "apply to this one I just queued") run on your machine immediately. Best for "always-on" use. - Batch (
fmwork run). No daemon. You queue up several applies in the dashboard, then runfmwork runonce. It drains the queue, drives each apply, exits. Best for "I'll sit down once a day and process my queue."
Both modes share the same Playwright runners, so a fix in one flows to the other.
What gets reported back
Each action produces a result: login state, scraped vacancies, application outcomes. When an apply flow lands on an external corporate ATS (Greenhouse / Lever / SmartRecruiters / a custom company portal), fmwork also notes the URL plus a sanitized snapshot of the form structure — no field values you typed, just the shape — so future versions can grow a direct connector for that site.
Layout
src/
cli.ts # commander entry, dispatches to commands/
commands/ # one file per CLI subcommand (login, verify, start, run, search, …)
runners/
recipe.ts # search recipe shape
search.ts # headless: drive a recipe, return scraped rows
apply.ts # headful: drive LinkedIn Easy Apply / capture external ATS
transport/
api.ts # REST helper (fetch wrapper)
ws.ts # long-lived connection client with auto-reconnect
playwright/
runner.ts # shared launchPersistentContext wrapper
profile.ts # resolves $HOME/.fmwork/<source>-profile
capture/
external-sites.ts # detects + sanitizes unknown ATS forms
config.ts # read/write ~/.fmwork/client.json + env overrides
protocol.ts # zod schemas for wire messagesStatus
Login, verify, status, search, daemon (start), batch (run) work end-to-end. LinkedIn Easy Apply has a real submit path; hh / GetMatch / Wellfound currently fall through to external-handoff (open + capture, finish manually) until each gets its own connector.
