Skip to main content
Glama
README.md
# Job Pilot

[![tests](https://github.com/chaitu0608/jobpilot/actions/workflows/test.yml/badge.svg)](https://github.com/chaitu0608/jobpilot/actions/workflows/test.yml)
[![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

**A local-first job-search cockpit — and its own MCP server.** Job Pilot is the
practical child of [Browsy](https://github.com/adityabelgaonkar05/browsy) and
[Clinqy](https://github.com/Amrit-Nigam/clinqy):

- **Browsy's 17-tool browser surface**, served end to end by our own Chrome
  extension in your real logged-in session — no Browsy install required.
- **Clinqy's native-Mac reach** (look / stats / gated tasks), plus a local
  **watch-and-learn** recorder that replays browser workflows and a durable
  memory store.
- **A job workflow that stays human**: discover → gate eligibility → capture and
  review a posting → link a company and contact → write a factual brief → track
  the next action → pre-fill the application **you** submit.

Job Pilot speaks **MCP over stdio** with **46 tools**, so any MCP agent (OpenCode,
Claude Code, Codex, Cursor…) can drive the whole cockpit: search jobs, inspect
tabs, click and type with snapshots, fill applications, remember preferences,
record workflows, and call Clinqy — all on `127.0.0.1`.

No API keys. No cloud. No npm runtime dependencies. **Nothing is ever submitted
or sent for you** — Job Pilot opens, reads, fills and prepares; you review and
act.

> **Integrations are real.** The built-in browser engine, the optional external
> Browsy MCP server, and the real Clinqy macOS app are all spoken to over
> bounded stdio/HTTP with strict gating. What each engine can and cannot do is
> documented honestly in [`docs/capabilities.md`](docs/capabilities.md) and
> reflected live in the **Connections** section.

```
┌──────────────── your agent (OpenCode · Claude Code · Codex …) ───────────────┐
│                        MCP over stdio · 46 tools                             │
└───────────────────────────────────┬──────────────────────────────────────────┘
                                    ▼
┌──────────────────────────────── your Mac ────────────────────────────────────┐
│  Job Pilot MCP (server/mcp.js) ── HTTP ── Node server → 127.0.0.1:8787        │
│                                         ┌──────────────────────────────────┐ │
│  Chrome (your real session)             │ SQLite: jobs · captures ·        │ │
│  ┌────────────────────────┐  commands   │ companies · contacts · pipeline  │ │
│  │ Job Pilot extension    │◄───────────►│ research · memories · workflows  │ │
│  │ • 17 browser tools     │             │ eligibility · exports · dashboard│ │
│  │ • scans X / Reddit     │             └──────────┬───────────┬───────────┘ │
│  │ • fills forms          │                        │ stdio     │ stdio       │
│  │ • never submits        │          ┌─────────────▼──┐   ┌────▼───────────┐ │
│  └────────────────────────┘          │ Browsy (opt.)  │   │ Clinqy (real)  │ │
│                                      │ capture / tabs │   │ look·stats·run │ │
│                                      └────────────────┘   └────────────────┘ │
└──────────────────────────────────────────────────────────────────────────────┘
```

---

## Quick start

```bash
cd ~/jobpilot

# 1. answer a few questions (writes config.json)
node server/cli.js setup

# 2. start the server + dashboard
node server/cli.js serve          # → http://127.0.0.1:8787
```

Check what is connected:

```bash
node server/cli.js connections    # built-in engine + optional Browsy + Clinqy
```

**3. Load the extension once** (powers the browser tools, X/Reddit scanning and
autofill):

1. Open `chrome://extensions`
2. Turn on **Developer mode** (top right)
3. **Load unpacked** → select `~/jobpilot/extension`
4. Pin the Job Pilot icon

**4. Make it available to your agent** (OpenCode example):

```bash
opencode mcp add --global jobpilot -- node "$HOME/jobpilot/server/mcp.js"
# restart the agent session, then ask: "jobpilot status"
```

Other clients use the same command in their MCP config — see
[Use it from any MCP agent](#use-it-from-any-mcp-agent).

**5. Run a scan** (optional — Job Pilot also works purely from captured pages):

```bash
node server/cli.js scan           # boards + HN + reddit RSS (server-side)
node server/cli.js scan-x         # X  (needs the extension + login on x.com)
node server/cli.js scan-reddit    # reddit through your logged-in session
```

Open **http://127.0.0.1:8787** — that's the real UI.

---

## Use it from any MCP agent

Job Pilot is the MCP server; the HTTP server auto-starts on the first tool call
if it isn't already running. stdout carries JSON-RPC only; logs go to stderr.

```jsonc
// Claude Desktop / Codex / Cursor style
{
  "mcpServers": {
    "jobpilot": {
      "command": "node",
      "args": ["/Users/you/jobpilot/server/mcp.js"]
    }
  }
}
```

**OpenCode**

```bash
opencode mcp add --global jobpilot -- node "$HOME/jobpilot/server/mcp.js"
opencode mcp list
```

**46 tools in five groups**

| Group | Count | What it does |
|---|---|---|
| `jobpilot_*` | 17 | status, scans, list/get, digest, status changes, apply packet, apply/fill, capture/import, rescore, research plan/list, brief |
| `browser_*` | 17 | Browsy parity via the extension: tabs, snapshot + refs, HTML, screenshot, click, type, keys, scroll, nav, wait, release |
| `clinqy_*` | 4 | `look`, `stats`, gated `run`, fixed native handoff |
| `memory_*` | 3 | remember / recall / forget local facts |
| `workflow_*` | 5 | record, stop, list, replay, delete browser workflows |

Resources: `jobpilot://status` and `jobpilot://capabilities`.

**Try these**

```text
jobpilot status
scan greenhouse + hn, then show me the top 10 backend roles in India
list jobs scoring 70+ that need review, pick one, and show its packet
capture the posting in my active tab, then import it after I review the fields
remember that I want remote-only roles with no on-call
record a workflow for this application form, then replay it on the next job
```

**Safety in the MCP layer**

- Every mutating browser call needs `confirmed: true`; submit / send / pay /
  delete clicks additionally need `approved: true` after you approve them.
- `jobpilot_apply` and `jobpilot_fill_current` only pre-fill. They never submit.
- `clinqy_run` is gated the same way and is never run automatically.
- Unknown mutation outcomes latch the tab until a fresh `browser_snapshot`.
- The server answers only on loopback; memory and workflows stay in local SQLite.

Full reference: [`docs/mcp.md`](docs/mcp.md). Agent skill:
[`skills/jobpilot/SKILL.md`](skills/jobpilot/SKILL.md).

---

## The workflow this is built around

```
find a posting in Chrome
        │  node server/cli.js tabs → capture --tab=<id>
        ▼
browser snapshot → editable capture   (review / fix the fields FIRST)
        │  import-capture <id>
        ▼
job + provenance + eligibility        (suitable · needs review · excluded)
        │
        ▼
link / create company + contact       (public source + verification)
        │
        ▼
editable factual brief → pipeline      (next action · date · status)
        │
        ▼
export CSV / JSON + per-job Markdown
```

The extension **never submits by itself**. It fills the form, highlights what it
answered in green and what it couldn't answer in amber, and shows a review bar.
You check it and press **Submit application ▶**. Only then is the job marked
*applied*.

---

## The five sections

| Section | What it holds |
|---|---|
| **Opportunities** | Captured + scanned jobs with eligibility. Sub-views: captured · potentially suitable · needs review · excluded. Filters live behind an expandable panel. |
| **Research** | Research runs that build official careers / LinkedIn / Indeed / Unstop queries and open them through the browser engine, with steps and checkpoints you can stop and resume. |
| **Pipeline** | Linked applications: status, next action and date, editable facts. |
| **People & Companies** | Employer targets and public recruiter contacts (source link, verification, approach angle, draft message — never sent). |
| **Connections** | Live status of the built-in browser engine, the optional external Browsy server, the real Clinqy app, the extension, the truthful Clinqy capability map, and the audit log. |

The UI is intentionally minimal: a short header, five sections, short rows,
evidence in a drawer, one primary action per context, and empty states with an
action. There are no mock results.

---

## Eligibility — gated before ranking

Eligibility is a pure, dependency-free classifier (`server/eligibility.js`)
served to the browser at `/eligibility.js` (importing `/criteria.js`), so the
server and UI always agree. It is **computed when read**, so editing criteria
changes verdicts **without** any destructive rescoring of the existing records.

Rules, in order:

1. **Gate before ranking** — senior / staff / principal / manager / director /
   head / VP / chief / architect titles, PhD / doctorate roles, incompatible
   required experience, explicit non-India restrictions, and closed or
   past-deadline postings are **excluded**, with visible reasons.
2. **Remote is never auto-eligible** — remote without an explicit India or
   worldwide statement is **UNKNOWN → needs review**.
3. **Title-first role family** — TA specialist / technical writer / designer are
   never promoted to software / data because a description mentions code.
4. **Pay and duration** are shown only when the posting states them.
5. **Unknown stays review.**

Buckets: **captured · potentially suitable · needs review · excluded**.

---

## Candidate criteria (separate from `config.json`)

Editable in the dashboard and via the CLI, stored in the database — deliberately
separate from your private `config.json`:

- **2027 BTech** (provisional, editable — not treated as verified).
- City order **Mumbai > Pune > Bengaluru > Hyderabad**, other India allowed.
- Role families: SWE, data / AI / ML, cloud, QA, consulting / implementation,
  graduate trainee, analytics / BA.
- Preferred types: **paid semester 4–6 month internships first**, then
  internships, then full-time.
- Employer preference: **MNC before startups**.
- Early-career / India-focus.

---

## Configuration (`config.json`)

| Section | What it does |
|---|---|
| `profile` | What gets typed into forms. `resumePath` can be `~/Documents/resume.pdf`. |
| `answers` | `{pattern, value}` bank for questions the profile rules don't cover — regex, case-insensitive. |
| `targets` | Fit scoring: `titles`, `skills`, `locations`, `negative`. Transparent — hover a score to see `why`. |
| `x.queries` / `x.accounts` | X searches and always-scan handles. |
| `reddit.subs` | Subreddits for the reddit scan. |
| `boards` | Which `companies.json` to poll + `perCompany` cap. |
| `port` | Server port (default 8787). |

`companies.json` holds the board tokens. Add your own from a careers URL
(`boards.greenhouse.io/<token>` → token = `<token>`).

---

## CLI reference

```
node server/cli.js setup                 first-run wizard
node server/cli.js serve                 server + dashboard
node server/cli.js mcp [--stdio]         run Job Pilot as an MCP server / print setup
node server/cli.js scan [--sources=boards,hn,reddit]   # also: greenhouse, lever, ashby, remoteok
node server/cli.js scan-x [--queries="a|b"]
node server/cli.js scan-reddit [--subs=forhire,developersIndia]
node server/cli.js list [--min-score=55] [--status=new] [--source=greenhouse] [--q=backend] [--remote] [--limit=25] [--json]
node server/cli.js show <id>
node server/cli.js save|skip|applied|archive <id>
node server/cli.js apply <id> [--packet]     queue autofill (or print a copy-paste packet)
node server/cli.js open <id>
node server/cli.js digest [--open]
node server/cli.js rescore                   re-score everything after editing targets
node server/cli.js stats

# integrations & durable workflow
node server/cli.js connections [--refresh]   browser engine + Browsy + Clinqy status, tools
node server/cli.js tabs                       list open browser tabs (read-only)
node server/cli.js capture --tab=<id>         snapshot a tab → editable capture (no submit)
node server/cli.js captures                   list saved captures
node server/cli.js import-capture <id> [--title= --company= --location=]
node server/cli.js pipeline                   list linked pipeline items
node server/cli.js brief <id>                 write a per-job markdown brief
node server/cli.js research                   list research runs
node server/cli.js criteria [--file=f.json]   show or update candidate criteria
node server/cli.js export [jobs|pipeline|companies|contacts] [--format=csv|json] [--out=path] [--print]
```

---

## How autofill works ("easy apply")

1. The server builds a **packet** from `config.json`: name/email/phone/links/
   resume + an answer bank.
2. The extension opens the apply URL and injects `content/ats.js` into every
   frame.
3. The filler matches each field's label against your profile rules and answer
   patterns, filling with native setters so React/Greenhouse/Lever/Ashby forms
   register the values.
4. Your resume is uploaded straight into the file input via `DataTransfer`.
5. A review bar appears; **you** submit.

Works well on **Greenhouse, Lever, Ashby**, and most career-page forms.
LinkedIn/Indeed "Easy Apply" is deliberately out of scope — use the direct apply
links.

---

## Safety & hygiene

- Filling and submitting are **two separate acts** — Job Pilot only does the
  first. `jobpilot_apply` and `jobpilot_fill_current` stop at the review bar.
- Every browser **mutation** requires `confirmed: true`; submit / send / pay /
  delete clicks also require `approved: true` in the built-in engine.
- Browsy/external mutations are **preview-and-confirm**, args are validated and
  capped, and an **unknown outcome requires a fresh snapshot** before the
  mutation can be repeated.
- `clinqy_run` requires an explicit confirmation; the dev workflow never runs
  native action tasks automatically. Cancellation is supported.
- Recorded workflows store **selectors and typed text locally** in SQLite. Avoid
  recording passwords or secrets; replay still asks for approval on submit-like
  clicks.
- Password fields, captchas and demographic questions are never touched.
- The server binds `127.0.0.1` only; your data sits in `data/jobpilot.db`.
- Only loopback `Host` names on its own port are served (DNS-rebinding
  defense), and any request whose `Origin` is a different web origin is rejected
  before handlers run. Mutations require `application/json` and are body-size
  limited. Credentials, private config and arbitrary paths are never served.
- The extension requests `<all_urls>` because browser tools must operate on
  arbitrary postings, but it only acts on commands from
  `http://127.0.0.1:8787`; no page content is sent anywhere else.
- `safety.autoSubmit` exists in config for future use; it is `false` and not
  implemented.

See [`docs/capabilities.md`](docs/capabilities.md) for the full, honest
capability map and the remaining native limitations.

---

## Sources

| Source | Path | Notes |
|---|---|---|
| Greenhouse / Lever / Ashby | server (`scan`) | Public JSON APIs, one request per company. |
| RemoteOK | server (`scan`) | Public feed. |
| HN "Who is hiring" | server (`scan`) | Algolia API, latest thread. |
| Reddit | server (`scan`) + extension (`scan-reddit`) | Server uses RSS; extension uses your session. |
| X | extension (`scan-x`) | Real search tabs in your logged-in Chrome. Rate limits apply. |
| Any posting you open | built-in engine (or Browsy) (`capture`) | Snapshot → editable capture → import. |

## Troubleshooting

- **Connections shows a tool "not found"** → install/locate it, or set
  `JOBPILOT_BROWSY_CLI` / `JOBPILOT_CLINQY_BIN` to the executable path.
- **MCP browser tools say the extension is offline** → load `extension/` in
  Chrome (`chrome://extensions` → Developer mode → Load unpacked) and keep
  Chrome running. Job search/DB tools still work without it.
- **The agent doesn't list jobpilot** → check the client config, then restart the
  agent session. `node server/mcp.js` must stay stdout-clean; diagnostics go to
  stderr.
- **Extension shows offline** → the server isn't running (`node server/cli.js
  serve`), or the extension isn't loaded.
- **X scan fails "not logged in"** → open x.com in Chrome and log in.
- **Reddit scan HTTP 403** → Reddit blocks datacenter IPs; the RSS path is the
  fallback.
- **Autofill says "review N"** → those fields have no matching answer; add a
  pattern to `answers` in `config.json`.
- **Port clash** → change `port` in `config.json` or set `JOBPILOT_PORT`.

## Files

```
jobpilot/
  config.json           your profile/targets (gitignored once you edit it)
  companies.json        board tokens to poll
  docs/capabilities.md  honest capability map + native limitations
  docs/mcp.md           MCP server reference: tools, setup, safety
  skills/jobpilot/      agent skill (when-to-use + workflow)
  server/
    mcp.js              Job Pilot's own MCP server (stdio, 46 tools)
    http.js             API + dashboard server, /ext/* routes, all CRUD
    cli.js              the CLI
    adapters.js         external Browsy + Clinqy adapters, schemas, capabilities
    browser-local.js    built-in browser engine adapter (Browsy tool surface)
    mcp-client.js       bounded stdio JSON-RPC client (timeouts, cleanup)
    eligibility.js      shared eligibility gate (served to the browser too)
    criteria.js         editable candidate criteria defaults
    capture.js          snapshot → editable job-field extraction
    research.js         research query builder (careers/LinkedIn/Indeed/Unstop)
    exporters.js        CSV (formula-injection safe) / JSON / brief builder
    db.js               SQLite — jobs, captures, companies, contacts, pipeline, research, memories, workflows, audit
    normalize.js        title/company/location/salary/apply-link extraction
    scoring.js          the 0–100 fit score
    sources/            greenhouse · lever · ashby · remoteok · hn · reddit(rss)
    commands.js         command queue the extension long-polls
    dashboard.html      the UI (5 sections)
  extension/
    manifest.json       MV3
    background.js       poll loop, scan_x / scan_reddit / fill, 17 browser tools
    content/browser.js  in-page snapshot/refs/click/type engine
    content/ats.js      form matching + filling + review bar
    popup.html/.js      status, quick scans, "fill this page"
  tests/                eligibility · end-to-end · MCP fixtures
  data/jobpilot.db      all your data (SQLite, gitignored)
```

## Tests

```bash
node --test tests/*.test.mjs
```

Covers the eligibility gate (observed failure cases), capture → import →
company/contact link → brief → pipeline → export, CSV formula-injection
escaping, mutation content-type/body-size rejection, cross-origin rejection,
Browsy argument validation, unknown-tool rejection, MCP client timeout / crash /
spawn-failure behaviour, and the Job Pilot MCP server end to end (initialize,
tools/list, jobs, browser tools, confirmation gates, memory, workflow
record/replay) against a simulated extension.

Maintenance

ActivityMaintained
ResponsivenessNo issues