Skip to content
Hritvik-MohanPublic

About

🐝 Configurable personal opportunity discovery and evaluation pipeline for sales leads, freelance work, and jobs.

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

48 Commits

Folders and files

Repository files navigation

Scout 🐝

Scout is a private job-search pipeline. It polls company job boards, keeps the roles that match what you are looking for, ranks them against your real experience, drafts what you need to apply, and tracks what happens next. The results live in a Google Sheet you own.

It is built to be reused. Everything about one person's search lives in three files in a my-search/ folder that Git ignores, so the same code serves any role, stack, city, or experience level, and your profile never ends up in a shared repository.

Scout never applies, never contacts anyone, and never invents a contact. It prepares; you send.

What you get

A Google Sheet with these tabs:

Tab What it holds
Job Pipeline Open roles that match, with fit score, verdict, flags, and dates
Application Kit Per role: resume evidence, an outreach draft, a referral path
Outreach Recruiters you found yourself, tied to roles that passed review
Sources The job boards Scout polls
Runs A record of every refresh
Companies Employers to probe for job boards
Reference Jobs Leads that could not be verified

Each refresh updates the pipeline, removes postings that have closed, and leaves your own columns (Applied, Application Date, Stage, Next Action, Notes) alone.

Job Pipeline ranks every open role and flags what differs from your target:

Job Pipeline tab with roles ranked by fit, verdicts, flags, application status and date added

Application Kit holds what you need to apply to each role:

Application Kit tab with resume evidence, an outreach draft and a referral path per role

Both images are illustrations with fictional data from the John Doe example, not a real person's sheet. They show a selection of the columns.

How it fits together

flowchart LR
    boards["Public job boards<br/>Greenhouse, Lever, Ashby,<br/>SmartRecruiters, Remotive"]
    search["my-search/ (private repo)<br/>ICP, matching terms,<br/>scoring profile"]
    sched["Scheduler<br/>desktop task or<br/>cloud routine"]
    update["jobs:update<br/>poll, filter, score, flag,<br/>remove closed postings"]
    sheet[("Your Google Sheet<br/>Job Pipeline, Application Kit,<br/>Outreach, Sources, Runs,<br/>Companies")]
    review["Claude Code review<br/>reads each new posting<br/>against your ICP"]
    digest["jobs:digest<br/>apply next, follow-ups,<br/>outcomes"]
    import["jobs:outreach:import<br/>checks contacts, links<br/>them to reviewed roles"]
    plan["jobs:outreach:plan<br/>today's batch: 20 a day,<br/>4 per company"]
    drafts["Claude with Gmail<br/>creates drafts,<br/>never sends"]
    you(["You<br/>apply, send, and<br/>record status"])

    boards -- "open roles" --> update
    search -- "titles, stack, locations" --> update
    sched -. "twice a day" .-> update
    update -- "kept roles and flags" --> sheet
    sheet -- "unreviewed roles" --> review
    search -- "resume facts" --> review
    review -- "fit, verdict, drafts,<br/>referral path" --> sheet
    sheet --> digest
    digest -- "ranked list,<br/>follow-ups due" --> you
    you -- "recruiters you found,<br/>with public sources" --> import
    import -- "queued contacts" --> sheet
    sheet -- "contacts and<br/>reviewed roles" --> plan
    sched -. "twice a day" .-> plan
    plan -- "today's batch" --> drafts
    drafts -- "Gmail drafts" --> you
    drafts -- "Draft Created" --> sheet
    you -- "Applied, Stage, Notes,<br/>outreach Status" --> sheet
Loading

Scout reaches the sheet with a Google service-account key. Claude does the two steps code cannot: reading a posting against your real experience, and putting the day's outreach emails in your Gmail as drafts through its Gmail connector. Nothing in the loop applies, sends a message, or contacts anyone; you send every email yourself.

Quick start

You need Node.js 22.9 or newer, a Google account, and Claude Code. Fork Hritvik-Mohan/scout so your profile stays yours, clone your fork, switch to the latest release, and open the folder in Claude Code:

git clone https://github.com/<you>/scout.git
cd scout
git checkout -b my-search "$(git describe --tags --abbrev=0)"
claude

That starts you on a branch of your own at the newest released version, rather than on unreleased work. Versions and upgrading explains how to move to a newer release later.

Then paste this prompt. It does the whole setup with you. The only steps it cannot do for you are creating the Google key and sharing the sheet, and it walks you through both.

Set up this Scout repository for my own job search. Read README.md first.

1. Prepare the project. Check that Node.js is 22.9 or newer and tell me how to
   upgrade if it is not. Run `npm ci`. If there is no .env file, copy
   .env.example to .env. Never print the contents of .env.
2. Interview me. Ask for my resume (a file path or pasted text), then ask, a
   few questions at a time: the titles I want, titles and specialisms to
   exclude, my seniority ceiling, the cities I will work in, which countries'
   remote roles I can legally take, my core stack, and anything I refuse.
3. Create my search. Copy examples/john-doe to my-search (it is ignored by
   Git). Rewrite my-search/job-search-icp.md following docs/icp-template.md,
   with the John Doe file as a model of the finished result, using only facts
   from my resume and my answers. Never invent experience, numbers, or skills.
   Where my resume and my stated goals disagree (for example years under my
   current title versus years I claim), say so in the "Resume facts" section
   and tell me. Do not copy my phone number, email, or home address into any
   file.
4. Rewrite my-search/job-search.json for my search: role titles, excluded and
   too-senior titles, maxLevel, stack terms, cities, remote-eligible terms and
   experience limits. Update my-search/jobs.json so its id, name, description,
   targetEntity, geographyPriority and criterion descriptions describe me,
   keeping the criterion ids unchanged.
5. Run `npm run jobs:config:check` and fix what it reports. Run
   `npm run check`.
6. Back up my search in a private repository. my-search/ is ignored by this
   repository's Git, so make it a repository of its own. Check that the GitHub
   CLI works (`gh auth status`). Ask me to confirm the name, defaulting to
   <my GitHub username>/scout-search. Then, inside my-search/, run `git init`,
   commit the files, and run
   `gh repo create <name> --private --source=. --remote=origin --push`.
   Confirm the new repository is private and tell me its URL. Never create it
   as public. If `gh` is missing or not signed in, show me the manual steps
   from the README's "Keep your search in a private repository" and continue.
7. Walk me through the README's "Create a Google service-account key" and
   "Create the sheet" steps one at a time, and wait while I do each. I will put
   the key path and sheet ID in .env myself. Never ask me to paste the key.
8. Run `npm run jobs:sheet:init`. Then help me fix the Sources tab for my
   market: deactivate boards that do not fit, and add public Greenhouse, Lever,
   Ashby or SmartRecruiters boards for companies I name.
9. Run `npm run jobs:update` and report sources checked, failures, and roles
   discovered, kept, inserted and pruned.
10. Run `npm run jobs:review:pending`, review each pending role against my ICP
   following the README's "Review rules", and apply the reviews.
11. Show me `npm run jobs:digest`, explain how to record an application in the
    sheet, and offer to schedule the refresh as described under "Schedule it".

Never submit an application, contact anyone, or invent a contact detail.

The rest of this README explains what that prompt sets up, for doing it by hand or changing it later.

Make it yours

Three files define a search. They live in my-search/, which Git ignores, so they stay on your machine and never conflict with updates to Scout.

File What it controls
my-search/job-search.json Matching terms: role titles, excluded titles, stack words, cities, remote regions, experience ceiling
my-search/jobs.json Scoring: which criteria are required, their weights, and the keep and action thresholds
my-search/job-search-icp.md Your ideal-role profile and resume facts, which the review step uses to rank roles and write your drafts

Create the folder from the fictional example, John Doe, a mid-level frontend engineer in London, then make the files yours:

cp -R examples/john-doe my-search

How to write your ICP explains the third file. To keep the files somewhere else, set SCOUT_JOB_SEARCH_PATH, SCOUT_PROFILE_PATH, and SCOUT_ICP_PATH.

Keep your search in a private repository

my-search/ is ignored by Scout's Git, so on its own it exists only on your computer. Make the folder a private repository of its own. That backs it up, keeps its history, and lets a cloud routine read it without your profile ever entering a public repository.

cd my-search
git init -b main
git add -A
git commit -m "Add my Scout search files"
gh repo create <you>/scout-search --private --source=. --remote=origin --push
cd ..

Without the GitHub CLI, create an empty private repository on github.com, then run git remote add origin <its URL> and git push -u origin main inside my-search/.

After editing your files, send them to the private repository with one command:

npm run jobs:search:sync

It commits any changes in my-search/ and pushes them. Cloud runs read the pushed version, so npm run jobs:update warns you when your local files are ahead of it.

On another computer, clone it back into place:

git clone https://github.com/<you>/scout-search.git my-search

my-search/job-search.json

Every term is matched as a lowercase substring.

Field Meaning
roleTitles A job title must contain one of these
nonDevelopmentTitles Titles that are not your kind of role, such as QA or support
specialistTitles Specialisms you are not targeting
tooSeniorTitles Title fragments that mean too senior, such as principal
maxLevel Highest numbered level to keep: 2 keeps "SDE II" and drops "SDE III"
technicalTerms Stack and domain words; two or more in a posting prove technical fit
systemsTerms, platformTerms Words that earn bonus signals
primaryLocations Cities you will work in
remoteEligibleTerms Location words that make a remote role open to you, such as your country
primaryLocationLabel, remoteLabel Display names; each must appear in geographyPriority in the profile
maxYears, hardMaxYears Years asked above which a role stops fitting, and at which it is rejected

my-search/jobs.json

Keep the criterion ids; the assessor reports against them. Change the descriptions, weights, required flags, geographyPriority, and thresholds. docs/profile-format.md documents the format.

After editing either file, check them:

npm run jobs:config:check

Manual setup

These are the steps the quick-start prompt performs, for doing them yourself.

1. Get the code

Fork Hritvik-Mohan/scout so your profile stays yours, then start a branch of your own at the latest release:

git clone https://github.com/<you>/scout.git
cd scout
git checkout -b my-search "$(git describe --tags --abbrev=0)"
npm ci
cp .env.example .env

2. Create a Google service-account key

Scout reads and writes the sheet as a service account, a robot Google identity with its own key. You create it once in Google Cloud. It is free, and it takes about ten minutes. Steps 2 and 3 of this guide together look like this:

Six steps: create a project, enable the Sheets and Drive APIs, create a service account, create a JSON key, share the sheet with the service account, and set two values in .env

The image is an illustration of the path, not a screenshot. How the pieces connect once it is done:

flowchart LR
    key["Key file (.json)<br/>on your computer"]
    env[".env<br/>key path + sheet ID"]
    scout["Scout"]
    google["Google<br/>checks the key"]
    sa(["Service account<br/>scout@project.iam.gserviceaccount.com"])
    sheet[("Your Google Sheet")]

    key --> env
    env --> scout
    scout -- "signs in with the key" --> google
    google -- "acts as" --> sa
    sa -- "Editor, because you shared it" --> sheet
Loading

The key proves Scout is the service account. Sharing the sheet is what lets that account in. Both halves are needed.

  1. Open the Google Cloud Console and create a project, for example scout. Make sure it is the selected project at the top of the page.

  2. Go to APIs & Services > Library and enable two APIs: Google Sheets API and Google Drive API. Open each one and press Enable.

  3. Go to IAM & Admin > Service Accounts and choose Create service account. Give it a name and finish. Skip the optional steps about roles and user access; it needs none.

  4. Open the new service account, go to the Keys tab, and choose Add key > Create new key > JSON. A .json file downloads. You cannot download the same key again, only create a new one.

  5. Move that file outside the repository and restrict it:

    mkdir -p ~/.config/scout
    mv ~/Downloads/<downloaded-key>.json ~/.config/scout/service-account.json
    chmod 600 ~/.config/scout/service-account.json
  6. In .env, set GOOGLE_APPLICATION_CREDENTIALS to that absolute path.

Note the service account's email address, shown on its page and in the key file as client_email. You share the sheet with it in the next step.

If key creation is blocked with a message about an organization policy, the project belongs to a Workspace organization that forbids keys. Create the project under a personal Google account instead, or ask the administrator.

Treat the key like a password. Never commit it, paste it into a chat, or put it in the sheet.

3. Create the sheet

  1. Create a blank Google Sheet in your own account.

  2. Share it with the service account's email address as an Editor.

  3. Copy the sheet ID from its URL, the part between /d/ and /edit, into SCOUT_JOB_SHEET_ID in .env.

  4. Build the tabs:

    npm run jobs:sheet:init

If that command fails, the message says which half is missing:

Message contains Cause Fix
HTTP 403 The sheet is not shared with the service account, or an API is not enabled Share the sheet as Editor; enable both APIs in the same project as the key
HTTP 404 The sheet ID is wrong Copy the part of the URL between /d/ and /edit
Could not load the default credentials The key path in .env is wrong or missing Set GOOGLE_APPLICATION_CREDENTIALS to the full file path
invalid_grant or Invalid JWT The key was deleted or the clock is wrong Create a new key; check your computer's date and time

To retire a key, delete it under the service account's Keys tab and create a new one. Scout uses whichever file .env points to.

4. Describe your search

  1. Create your search folder from the example:

    cp -R examples/john-doe my-search
  2. Rewrite my-search/job-search-icp.md for yourself, following docs/icp-template.md, and edit the two JSON files for your own titles, stack, and locations.

  3. Run npm run jobs:config:check until it reports no problems.

5. Choose your sources

jobs:sheet:init fills the Sources tab with a starter list aimed at Bengaluru. Edit that tab for your market:

  • Untick Active on boards you do not want. Do not delete starter rows; re-running jobs:sheet:init would add them back.

  • Add a row for each company board you do want. Scout can poll these types:

    Type URL to enter
    greenhouse https://boards-api.greenhouse.io/v1/boards/<board>/jobs
    lever https://api.lever.co/v0/postings/<company>?mode=json
    ashby https://api.ashbyhq.com/posting-api/job-board/<board>
    smartrecruiters https://api.smartrecruiters.com/v1/companies/<id>/postings
    remotive https://remotive.com/api/remote-jobs

    The board name is the last part of a company's careers URL, for example jobs.lever.co/<company>.

  • Or list employers in the Companies tab (name in column B) and let Scout find their boards:

    npm run jobs:sources:discover

6. First run

npm run jobs:update

Then rank what it found. In Claude Code, from the repository folder, say:

Run npm run jobs:review:pending, review every pending role against my-search/job-search-icp.md following the review rules in the README, apply the reviews, and show me npm run jobs:digest.

How to write your ICP

"ICP" is borrowed from sales, where it means ideal customer profile. Here it is your ideal-role profile: a short document stating what you want and what you can prove. The review step treats it as the truth about you, so a vague or flattering ICP produces a vague or flattering ranking.

Start from docs/icp-template.md. The rules that matter:

  • Write only what your resume can back up. Every claim should trace to a bullet a recruiter can check.
  • Be exact about time. Give each role's title and start and end month. If you have three years of experience but one under your current title, say so. Recruiters count title tenure, and the ranking should too.
  • Separate production from practice. A simulator, a prototype, or a course project is real work, but it is not operating a system at scale. Say which is which.
  • List what you lack. Name the common requirements you do not meet. The review then reports them as gaps instead of ranking a role you cannot get.
  • Tier your targets. Say which titles you would take today, which are adjacent, and which are a stretch.
  • Be specific about location. Name the cities, and the remote regions you can legally work for. "Remote" alone is not an answer.
  • Map needs to evidence. For each requirement that recurs in postings, note the one resume bullet that proves it. Drafts are built from this table.
  • Leave unknowns as unknowns. If you have not decided your pay floor, write that rather than guessing.

Update the ICP whenever your resume changes, then re-run the review with npm run jobs:review:pending -- --all.

Daily use

Command What it does
npm run jobs:update Poll every active source and update the pipeline
npm run jobs:review:pending Export roles awaiting review (-- --all to re-review them all)
npm run jobs:review:apply -- <reviews.json> Write reviews and drafts to the sheet
npm run jobs:digest Print what to apply to next, follow-ups due, and outcomes
npm run jobs:outcomes Print responses and interviews by verdict, level, and source
npm run jobs:outreach:import -- <file.csv> Add contacts you found to the Outreach tab
npm run jobs:outreach:plan Print today's outreach batch, capped at 20 a day
npm run jobs:outreach:drafts Export today's batch as draft emails for Claude to put in Gmail
npm run jobs:monitor Check run health and open GitHub issues for problems
npm run jobs:sources:discover Probe employers in the Companies tab for job boards
npm run jobs:config:check Validate the three files in my-search/
npm run jobs:search:sync Commit and push my-search/ to your private repository
npm run check Format, lint, typecheck, test, and build

When you apply to a role, tick Applied, set Application Date, and keep Stage current. Follow-up reminders and outcome tracking read those columns, and an applied role is never removed.

Review rules

The keyword score only decides what is kept. Ranking comes from a review of each posting against your ICP, written by Claude or by you as JSON matching jobReviewSchema in src/jobs/review.ts:

  • fit: 0 to 100. Use the whole range, and weigh the years asked against your title tenure.
  • verdict: apply_now, apply, stretch, or skip.
  • reasons (1 to 3) and gaps (0 to 3): specific to the posting.
  • leadWith (0 to 5): resume evidence, taken only from the ICP.
  • outreach: a draft under 100 words addressed to "[name]".
  • referralPath: for apply_now roles, what the posting and the employer's official pages say about the team and referral rules, plus a people-search link for you to run. It never lists individual employees or guesses a contact.

Outreach contacts

The Outreach tab holds recruiters and hiring contacts you looked up yourself. Scout does not search for people or addresses; it checks what you bring, ties each contact to a role, and tells you who to write to today.

Add contacts in either of two ways:

  • Type them straight into the Outreach tab. Run npm run jobs:outreach:plan once to create the tab.
  • Import a CSV with npm run jobs:outreach:import -- <file.csv>. The columns are name, company, email and source_url, plus optional title, opportunity_id, notes and greeting; examples/outreach-contacts.csv shows the layout. Keep your own file in .local/, which Git ignores.

The file is yours to create; Scout does not ship one. Start it from the example and replace the sample lines with your own:

mkdir -p .local
cp examples/outreach-contacts.csv .local/contacts.csv
npm run jobs:outreach:import -- .local/contacts.csv

There are two honest places to find a contact:

  • The Companies tab. Its Public Recruiting Email column holds shared hiring inboxes, such as careers@, that Scout found on an employer's own site. Use the page that shows the address as source_url, and a name such as "Acme hiring team". Expect few of these: most employers publish no hiring address. General inboxes in Other Public Emails, such as hello@ or support@, are not hiring contacts.
  • Your own research. A named recruiter is yours to find, for example from a posting that names its hiring contact or from the Referral Path in Application Kit. Add the person only when their address is published on a page you can link to.

source_url is the public page where the address is published. A contact without one is rejected on import and held back from every batch, so do not add an address you guessed from a name pattern. Import also skips an address that is already in the tab or marked Do not contact.

npm run jobs:outreach:plan prints the day's batch. It creates the Outreach tab if it is missing and otherwise changes nothing. A contact is in the batch only when:

  • its Status is Queued or blank,
  • its company has an open role you reviewed with a verdict other than skip (set opportunity_id to pin a particular role),
  • the day's cap has room. The cap is 20, less the rows whose Contacted On is today; pass -- --cap=<n> to change it, and
  • its company's cap has room. No more than 4 contacts at one company go out in a day, again counting those already contacted today; pass -- --per-company=<n> to change it. The rest wait for another day.

Contacts are ranked by the fit of the linked role, and everything left out is listed under "Held back" with the reason. After you send an email yourself, set Status to Sent and fill in Contacted On. Scout never changes those columns or Notes.

npm run jobs:digest reminds you to follow up. A contact is listed once it has been Sent for five working days (Monday to Friday) with no change of status. When you have followed up, set Status to Followed up; when they answer, set it to Replied. Either one takes the contact off the list, so each contact is prompted for one follow-up and no more. A Sent row with no Contacted On date cannot be timed, and the digest says how many there are.

Gmail drafts

Scout can turn the day's batch into Gmail drafts for you to read, edit and send. It needs Claude Code with a Gmail connector; Scout itself holds no Gmail access, and nothing is sent.

  1. npm run jobs:outreach:drafts writes .local/outreach/drafts.json, one entry per contact in the batch: to, subject, body and the tab row. The body is the Outreach Draft (not sent) written for the contact's role in Application Kit, with the contact's Greeting in place of [name]. Set Greeting to how you would address the person, such as "Asha" for Asha Rao; when it is blank the full Name is used, which suits a shared inbox such as "Acme hiring team".
  2. Claude creates one Gmail draft per entry, then writes .local/outreach/created.json listing each draft it made as { "rowNumber": 2, "to": "careers@acme.example" }.
  3. npm run jobs:outreach:drafts:record -- .local/outreach/created.json stamps today's date in Draft Created, so the contact is not drafted twice. The plan then marks it "draft ready".

A contact is left out when it already has a draft, or when its role has no outreach draft in Application Kit; review the role first. Drafts carry no attachment, so add your resume in Gmail before you send.

Paste this prompt into Claude Code to run the three steps:

Create today's Scout outreach drafts in Gmail. Read the README's "Gmail
drafts" section first.

1. Run `npm run jobs:outreach:drafts` and read .local/outreach/drafts.json.
   If it is empty, tell me why from the command's output and stop.
2. For each entry, create one Gmail draft with exactly its `to`, `subject`
   and `body` as plain text. Do not rewrite the body, add recipients or
   attach anything. Create drafts only: never send, schedule or reply.
3. Write .local/outreach/created.json with `rowNumber` and `to` for each
   draft that was created, and nothing for any that failed.
4. Run `npm run jobs:outreach:drafts:record -- .local/outreach/created.json`
   and report how many drafts were created and recorded, and any that were
   not.

Set up Claude Code

Claude Code is the assistant that runs the setup prompt, reviews roles against your ICP, and runs the refresh on a schedule. It comes as a desktop app, a terminal command, and a web app at https://claude.ai/code; install it from https://claude.com/claude-code and sign in.

You want to Use
Run the setup prompt Desktop app or terminal, opened in this folder
Review roles and see the digest Desktop app or terminal, opened in this folder
Refresh on a schedule, computer on Desktop app scheduled task
Refresh on a schedule, computer off Cloud routine at https://claude.ai/code

To start a session in the terminal, run claude inside the repository folder. In the desktop app, open the Code tab and choose the folder. The first time Claude runs a command it asks for permission; approve the npm run commands Scout uses.

Cloud environment

A cloud routine runs in a cloud environment, which holds its settings. Create one at https://claude.ai/code from the environment picker, with Add environment:

The Add cloud environment dialog in Claude Code, with fields for name, network access, network secrets, setup script and environment variables

Field What to enter
Name Any name, such as scout
Network access A level that allows the job-board APIs and Google Sheets. If runs report blocked requests, widen it
Network secrets Leave empty. Scout's code must read the key itself, and these secrets are hidden from the session
Setup script Leave empty. The refresh prompt installs dependencies
Environment variables Scroll down. Two lines, no quotes, no spaces around =: SCOUT_JOB_SHEET_ID and GOOGLE_SERVICE_ACCOUNT_JSON

The hosts Scout calls are sheets.googleapis.com, oauth2.googleapis.com, boards-api.greenhouse.io, api.lever.co, api.ashbyhq.com, api.smartrecruiters.com, and remotive.com.

Put the variable name in the Environment variables box, not in the Name field. In the cloud gives the full steps, including how to copy the key without displaying it.

Schedule it

What Claude needs

Scout reaches Google Sheets with the service-account key, not through a Claude connector, so you do not need a Google connector in Claude. What each option needs:

Where it runs Needs
Claude desktop scheduled task The app open on a computer that has this folder and its .env
Claude cloud routine GitHub connected to Claude, your private search repo, two secrets

To have a scheduled run also put the day's outreach emails in Gmail as drafts, the desktop task needs a Gmail connector in Claude. A cloud routine runs with no connectors, so it reports the outreach batch and leaves the drafts to you.

Scout itself needs no GitHub repository secrets. (The optional run monitor uses two, in your private search repository.) The included CI workflow only runs npm run check, which never touches the sheet. The two secrets for a cloud routine are environment variables in Claude's cloud environment, not GitHub Actions secrets; do not add the key to GitHub.

On your computer

In the Claude desktop app, ask Claude Code to "create a scheduled task that runs twice a day" with the prompt below. It runs only while the app is open. The first time, press Run now so you can approve the tools it uses.

In the cloud

A cloud routine runs on Anthropic's servers whether or not your computer is on. It clones your repository each time, so it has no .env and no key file.

  1. On https://claude.ai/code, connect your GitHub account and give the Claude GitHub app access to your Scout repository and your private scout-search repository.

  2. In your cloud environment's settings, add two environment variables:

    • SCOUT_JOB_SHEET_ID: your sheet ID.

    • GOOGLE_SERVICE_ACCOUNT_JSON: the key file's contents on one line. Copy it base64-encoded without displaying it:

      base64 -i ~/.config/scout/service-account.json | tr -d '\n' | pbcopy
  3. Ask Claude Code to "create a cloud routine" with the prompt below, both repositories (your Scout fork and your private scout-search), and your schedule. Tell it to attach no connectors.

  4. Run it once and read the result before relying on it.

A cloud run only sees what is on GitHub, and my-search/ is not in the Scout repository. Give the routine both repositories: your Scout fork and your private scout-search repository from Keep your search in a private repository. The Claude GitHub app needs access to both. They are cloned side by side, and the refresh prompt copies your files into my-search/ at the start of each run.

Merge changes to your default branch before expecting a cloud run to use them.

The refresh prompt

You are maintaining Scout, a job-search pipeline, for the person described in
my-search/job-search-icp.md. Work in the repository root.

Setup: run `npm ci` if node_modules is missing. If there is no .env file, check
that SCOUT_JOB_SHEET_ID and GOOGLE_SERVICE_ACCOUNT_JSON are set, without
printing them; if either is missing, stop and say which.

If the my-search folder is missing, look for a scout-search directory next to
this repository (cloud runs clone it there) and copy job-search-icp.md,
job-search.json and jobs.json from it into my-search. If neither exists, stop
and say so. Never commit or publish anything from my-search or scout-search.

Read my-search/job-search-icp.md, especially "Resume facts". It is the only source
of truth about the candidate. The roles, cities and remote regions to target
are defined there and in my-search/job-search.json.

Steps:
1. Run `npm run jobs:update` and keep its JSON summary.
2. On the first run of each week, run `npm run jobs:sources:discover`; if it
   adds sources, run `npm run jobs:update` again.
3. Run `npm run jobs:review:pending`. If roles are pending, read
   .local/job-reviews/pending.json, review each against the ICP following the
   "Review rules" in README.md, save the array to
   .local/job-reviews/reviews.json, and run
   `npm run jobs:review:apply -- .local/job-reviews/reviews.json`.
4. Run `npm run jobs:digest`.
5. Run `npm run jobs:outreach:plan` and keep its output.
6. Only if a Gmail connector is available in this session, create today's
   outreach drafts following "Gmail drafts" in README.md: run
   `npm run jobs:outreach:drafts`, create one Gmail draft per entry in
   .local/outreach/drafts.json with exactly its `to`, `subject` and `body`,
   write .local/outreach/created.json for the drafts that were created, and run
   `npm run jobs:outreach:drafts:record -- .local/outreach/created.json`. If
   recording fails after drafts were created, stop and list those drafts so
   they are not created twice. Without a Gmail connector, skip this step.

Rules:
- Never submit applications, contact anyone, or send messages.
- In Gmail, create drafts only. Never send, schedule, or reply to anything.
- Never invent experience, metrics, skills, names, or contact details.
- Never write to Job Pipeline columns R:V.
- Do not modify source files, commit, or push. Describe needed changes instead.
- Never print or store credentials or the contents of .env.
- If a command fails, stop and report exactly what failed.

Finish with one compact report: the reason this run matters or "No actionable
changes."; the counts from step 1 and how many roles were reviewed; the digest's
"Apply next" list with row numbers, fit, verdict, flags and links; any
follow-ups due, for applications and for outreach contacts; today's outreach
batch, and how many Gmail drafts were created or why none were; and the outcome
summary.

Get an issue when a run goes wrong

A scheduled refresh can fail without telling you: a usage limit, an expired key, or a job board that changed its feed. npm run jobs:monitor reads the Runs and Sources tabs and reports three things:

It reports When
The refresh is not running No run has been recorded for 16 hours
A run failed The latest run reached none of its sources
A source is failing An active source has not been checked successfully for 2 days

A single timeout is not reported; feeds blip. With SCOUT_ISSUE_REPO (owner/name) and GITHUB_TOKEN set, it opens one GitHub issue per problem, does not repeat an issue that is already open, and closes it when the problem clears. Without them it prints the findings and exits with an error.

Run it from your private repository

The monitor is most useful running somewhere other than the refresh itself, so it can notice when the refresh never started. GitHub Actions in your private search repository does that, and keeps the issues private:

  1. Copy examples/workflows/scout-monitor.yml to .github/workflows/scout-monitor.yml in your private search repository, then commit and push.

  2. Add two secrets to that repository, from the Scout folder. Neither command displays the value:

    grep '^SCOUT_JOB_SHEET_ID=' .env | cut -d= -f2- | tr -d '\n' | gh secret set SCOUT_JOB_SHEET_ID --repo <you>/scout-search
    gh secret set GOOGLE_SERVICE_ACCOUNT_JSON --repo <you>/scout-search < ~/.config/scout/service-account.json
  3. Start it once from the repository's Actions tab to confirm it works.

Keep these secrets in your private repository only. Do not add them to a public fork of Scout. The workflow's own token files the issues; you do not need to create one. Adjust the windows with SCOUT_MONITOR_MAX_SILENCE_HOURS and SCOUT_MONITOR_SOURCE_STALE_HOURS.

How it works

  • Keep or drop. Each posting is checked against my-search/job-search.json and scored with my-search/jobs.json. Only keep and action roles enter the sheet, identified by a stable ID so a role is never duplicated.
  • Flags. Column W marks what differs from your target: a senior title, an internship, a data or ML role, a high experience ask, a possible duplicate, or a posting older than 45 days.
  • Review. Fit, verdict, reasons, and gaps go to columns X:AA; drafts go to Application Kit.
  • Closed postings. Greenhouse, Lever, and Ashby feeds list every open role, so a role missing from a complete feed is treated as closing. The first miss adds a "Not found on the employer's board" flag. On the next miss the row is removed with its drafts, unless you have applied or moved its stage; those rows stay and are flagged "Closed". A role that reappears returns to normal.
  • Digest. Open roles are ranked by fit less a penalty for old postings. Applications with no recorded response after a week are listed for follow-up.
  • Outreach. Contacts you add are matched to roles by company name. The daily batch holds only contacts whose role passed review, up to the cap. The digest lists contacts emailed five working days ago with no reply recorded.
  • Date Added. Column AB records when a role first entered the pipeline.

Location-specific extras

Two commands are tied to Bengaluru data sources and are optional elsewhere:

  • npm run jobs:companies fills the Companies tab from the public Bangalore Startup Map and scans company websites for careers pages and publicly listed contacts. SCOUT_COMPANY_ENRICH_LIMIT bounds each run.
  • npm run jobs:import:lets-code imports a Bengaluru job-seeker workbook into Companies, Reference Jobs, and Resources.

For another city, put your own employer list in the Companies tab.

Other profile types

The scoring engine is not limited to jobs. profiles/sales-leads.json and profiles/freelance.json are starter profiles for other searches:

npm run build
npm run profile:validate -- profiles/freelance.json
npm run profile:score -- profiles/freelance.json examples/freelance-opportunity.json

Versions and upgrading

Scout is published as numbered releases. Each release is a tested state of main; main itself may contain unreleased work. The version has three parts, major.minor.patch:

Part Raised when What you do
patch A bug is fixed Upgrade when convenient
minor A feature is added; your setup keeps working Upgrade to get the feature
major You must change something to keep working Read the release notes first

A major release is one that changes your sheet's columns, the shape of my-search/job-search.json or my-search/jobs.json, or the name of an environment variable. Its notes say what to change.

Upgrade your fork

Your search lives in my-search/, which Git ignores, so upgrading never touches it.

git remote add upstream https://github.com/Hritvik-Mohan/scout.git   # once
git fetch upstream --tags
git merge v0.3.0                      # the release you are moving to

Then confirm your setup still works:

npm ci
npm run jobs:config:check
npm run jobs:update

If you run a cloud routine, push the upgraded branch to the branch the routine reads. If a release adds sheet columns, run npm run jobs:sheet:init once to add the new headers. It does not change your pipeline rows, but it re-adds any starter sources you deleted from the Sources tab, so untick Active on unwanted sources instead of deleting them.

Publish a release (maintainers)

  1. Merge the work into main and wait for CI to pass.

  2. Raise the version, which also commits and tags it:

    git checkout main && git pull && npm version minor   # or patch, or major
  3. Push the commit and the tag:

    git push origin main --follow-tags
  4. Publish, with notes generated from the merged pull requests:

    gh release create "v$(node -p "require('./package.json').version")" --generate-notes

For a major release, edit the notes to list exactly what users must change.

Safety

  • Scout never applies, sends messages, or contacts recruiters.
  • It records only publicly published contact details, with the source URL, and never guesses an address.
  • The Outreach tab holds only contacts you add, each with the public page the address came from. Scout plans a capped daily batch and can prepare Gmail drafts through Claude; you read and send every email yourself.
  • Evidence-required rules earn no credit without a source URL. A hard disqualifier or a missing required criterion rejects a role.
  • Keep .env, the service-account key, and your sheet ID out of Git. .env and .local/ are ignored by default.
  • Your ICP and profile are personal. They stay in my-search/, which Git ignores; do not commit them to a public repository.

Contributing

Contributions are welcome: bug reports, new job-board adapters, better matching, and clearer docs. CONTRIBUTING.md explains how to set up, the rules a change must keep, and how to open a pull request. Start with an issue for anything larger than a small fix.

npm ci
npm run check   # needs no Google account or credentials

License

MIT. You may use, change, and share Scout freely; keep the copyright notice. It comes with no warranty.

About

🐝 Configurable personal opportunity discovery and evaluation pipeline for sales leads, freelance work, and jobs.

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages