Home Tracker
Tracker — one spreadsheet, the whole hunt
One spreadsheet you own. Every role, its status, and the next action. Ask where you stand any time.
What your agent will do. Your agent keeps one spreadsheet of your whole hunt, on your computer: every role, its status, the next action and dated notes, with a backup before every change. Ask "where do I stand?" any time for a short answer, or ask for the weekly review: what moved, what is stuck, and what to do this week.
Open the AI you already use, point it at this page, and say: “where do I stand”
For AI agents: fetch the clean Markdown of this skill at /skills/tracker.md (or the GitHub raw source).
Hope Tracker · One sheet you own
job-tracker.xlsx is the live log of the whole hunt: one sheet, one row per role, owned by the user. This skill keeps its shape, answers “where do I stand?”, makes the changes the user asks for, and runs the weekly review. Every write goes through scripts/tracker.py, which backs the file up first.
Before you start
Find the plugin root:
# Find the Hope plugin root (references/, assets/, scripts/ live there).
# $CLAUDE_PLUGIN_ROOT is not expanded inside this Markdown, so resolve it in the shell.
# Works whether Hope is installed, marketplace-cached, or run with --plugin-dir, in bash or zsh.
PLUGIN_ROOT=""
if [ -n "$CLAUDE_PLUGIN_ROOT" ] && [ -f "$CLAUDE_PLUGIN_ROOT/plugin.json" ]; then PLUGIN_ROOT="$CLAUDE_PLUGIN_ROOT"; fi
if [ -z "$PLUGIN_ROOT" ]; then
c="$(find "$HOME/.claude/plugins/cache/hope/hope" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | sort -V | tail -1)" # newest cached version
if [ -n "$c" ] && [ -f "${c%/}/plugin.json" ]; then PLUGIN_ROOT="${c%/}"; fi
fi
if [ -z "$PLUGIN_ROOT" ] && [ -f "$HOME/.claude/plugins/marketplaces/hope/plugin.json" ]; then PLUGIN_ROOT="$HOME/.claude/plugins/marketplaces/hope"; fi
if [ -z "$PLUGIN_ROOT" ]; then f="$(find "$HOME/.claude/plugins" -path '*hope*/plugin.json' -print -quit 2>/dev/null)"; if [ -n "$f" ]; then PLUGIN_ROOT="$(dirname "$f")"; fi; fi
echo "PLUGIN_ROOT=$PLUGIN_ROOT" # check this before reading bundled files; empty means ask the user where Hope is
If PLUGIN_ROOT is empty, ask the user where the Hope plugin is. Do not guess. Check: ls "$PLUGIN_ROOT/scripts/tracker.py" finds the file. If not, that folder is an older Hope: ask the user where Hope 2.0 is installed. Then read:
$PLUGIN_ROOT/references/tracker-contract.md, all of it. It is this skill’s contract.$PLUGIN_ROOT/references/hard-rules.md$PLUGIN_ROOT/references/voice-guide.md(principle 6: every question is a set of choices)- In the hunt folder:
user-story.md,HUNT-PROTOCOL.md(sections 4 and 5),applications/_common/profile.md(section 5, the per-company cap).
<hunt folder> below means the absolute path of the folder that holds job-tracker.xlsx and applications/.
What you make
- Changes to
job-tracker.xlsx, only through$PLUGIN_ROOT/scripts/tracker.py(init,add,update). The sheetApplicationshas eight columns, in this order:Status·Company·Role·Location·Band·Next action·Package·Notes. - A backup before every write, made by the script:
job-tracker.backup-<YYYY-MM-DD>-<tag>.xlsx. - Answers in chat: where the user stands, and the weekly review. No markdown or CSV copy of the tracker is kept.
- One dated line in
user-story.md.
Steps
tracker.py below is short for python3 "$PLUGIN_ROOT/scripts/tracker.py". Always run step 1. Then pick the step for what the user asked: “where do I stand”, “what needs me”, “what’s live”, “show my tracker” or “how’s the hunt going” is step 2. A change is steps 3 and 4. A new role is steps 3 and 5. “Weekly review” is step 6, and “funnel” is its first part.
- Find the tracker. Run
ls "<hunt folder>"/job-tracker.xlsx "<hunt folder>"/job-tracker.csvand pick the case (backup files do not count):job-tracker.xlsxis there: go on.- Only
job-tracker.csv(no.xlsx): openpyxl is missing, and the CSV is the live log. Add--file job-tracker.csvto everytracker.pycommand, and usejob-tracker.csvin the notes view. Never runinitwhile it exists: that starts a second, empty log. Tell the user once:python3 -m pip install openpyxl. - Neither, and
applications/_common/profile.mdis missing too: offer hope-onboarding (part 2). It sets up the whole hunt folder, the tracker included. - Neither, but the profile exists: run
python3 "$PLUGIN_ROOT/scripts/tracker.py" init --hunt-dir "<hunt folder>".
Check:
python3 "$PLUGIN_ROOT/scripts/tracker.py" report --hunt-dir "<hunt folder>"exits0(with--file job-tracker.csvin the CSV case). - “Where do I stand?” Run
tracker.py report --hunt-dir "<hunt folder>", then the notes view below. The notes view prints each row with its status, role folder, the age of its newest note, and the start of its notes. Passliveto skipRejected,ClosedandWithdrawnrows, orallfor every row.
date "+%A %d %B %Y"
python3 - "$PLUGIN_ROOT/scripts" "<hunt folder>/job-tracker.xlsx" live <<'PY'
import re, sys
from datetime import date, datetime
from pathlib import Path
sys.path.insert(0, sys.argv[1])
from tracker import read_rows
for r in read_rows(Path(sys.argv[2])):
if sys.argv[3] == "live" and r.status.startswith(("Rejected", "Closed", "Withdrawn")):
continue
m = re.match(r"(\d{1,2} [A-Z][a-z]{2} \d{4}):", r.notes)
age = (date.today() - datetime.strptime(m.group(1), "%d %b %Y").date()).days if m else "?"
print(f"Row {r.number} | {r.status} | {r.company} | {r.role} | {r.package} | newest note {age} days ago")
print(f" {r.notes[:400]}")
PY
Answer in five to ten lines, in this order:
- Needs you: every row the report lists under “Needs you” (a next action addressed to the user, or a status of
Interviewing,Offer,InboundorRecruiter contact). Each gives the company, the role, the posting link (the**Posting:**line in the role’sJD.md) and the role folder path when the role has them, and the next action. - Overdue: a next action whose date has already passed.
- Stale: a live row (not
Parked) whose newest note is 14 or more days old, unless its next action names a date still to come. - The counts, in one line: “3 roles: 1 shortlisted, 1 applied and confirmed, 1 interviewing.”
Check: every row the report printed under “Needs you” is in your answer.
- Before every write, the user’s own edits come first. They edit the sheet by hand too.
- Check the file is not open:
ls -a "<hunt folder>" | grep -E '^(~\$|\.~lock\.)job-tracker'. If it prints a name, say “Your tracker is open in Excel. Save and close it, then tell me.” and wait. A write that fails with “Permission denied” means the same. - Read the row first:
tracker.py list --hunt-dir "<hunt folder>" --company "<company>". Change only the cells the user asked about, or that new evidence settles. A Status or Next action that no note explains was typed by the user, and it is theirs: ask before replacing it. - The script can only change
Status,Next action,PackageandNotes, and it only ever prepends toNotes, never replaces them.Company,Role,LocationandBandare the user’s to edit by hand.
Check: the lock check printed nothing, and you know the row’s current values.
- Check the file is not open:
- “Update the tracker” or “Mark X as Y.” One
updateper change, with a note that says what happened and who said so. Use--datewhen it happened on another day. Never type a date into the note text.
python3 "$PLUGIN_ROOT/scripts/tracker.py" update --hunt-dir "<hunt folder>" \
--match "Contoso" --role "Senior Product Designer" --status "Rejected" --next "-" \
--date 2026-10-13 --note "Rejection email, no reason given (Jane told Hope)"
- Status words come only from the list in “Rules for this skill”. A detail may follow a spaced hyphen:
Interviewing - round 2 on 15 Oct. Appliedonly when the user names what they submitted. A confirmation email makes itApplied - confirmed.- Exit code
2: the match fits more than one row, or none. Read the candidates it printed and ask the user which one (AskUserQuestion, the candidates as options). Never guess.
Check: the output has Updated row <N>, a Notes now start: line with the new note first and one date, and a Backup: line.
- “Add this to my tracker.” Run
tracker.py list --company "<company>"first. If the role is there, useupdate. Otherwiseaddwith all eight columns:
python3 "$PLUGIN_ROOT/scripts/tracker.py" add --hunt-dir "<hunt folder>" \
--company "Northwind Labs" --role "Design Engineer, Interfaces" --status "Shortlisted" \
--location "Remote (US)" --band "not published" --next "You decide: apply or skip" \
--package "-" --note "Jane added it from a LinkedIn post"
Package: runls -d "<hunt folder>"/applications/*/. If a folder for this company and role exists, pass it (applications/northwind-design-engineer-interfaces/). Otherwise-.- Company, role, location and band come from the posting or the user. Missing: ask, never guess. Inside double quotes write every dollar sign as
\$:--band "\$170,000 - \$200,000".
Check: the output has Added row <N>: <status> | <company> | <role> and a Backup: line.
- The weekly review. Run
report, then the notes view withall. Give the user these parts, in order:-
The funnel. One table, then one line on how you counted (the counts come from the Status column and the notes):
Stage Count the rows where Shortlisted every row (every role that entered the tracker) Applied the status starts with Applied,InterviewingorOffer, or aRejectedorWithdrawnrow’s notes show the user appliedScreened the status starts with InterviewingorOffer, or aRejectedorWithdrawnrow’s notes record a callInterviewed the status or notes name a round after the first call (hiring manager, panel, round 2, onsite) Offers the status starts with OfferRejected the status starts with Rejected - What moved this week: rows whose newest note is 7 or fewer days old.
- Stale and overdue rows, as in step 2.
- The per-company cap: count the rows per company whose status starts with
Ready to apply,Applied,InterviewingorOffer. Name any company at the cap inprofile.mdsection 5 (default 2). - A pattern worth facing, only after three or more rejections since the last review (
grep -n "Weekly review" user-story.mdshows its date). Read the rejection notes and anyapplications/<slug>/debrief-*.mdfix lists. Say in plain words what the rejections share: the stage, the role shape, the reasons given. Then offer hope-discovery (change the sweep filters) and hope-interview (add it to the next prep). Write nothing intoprofile.mdorHUNT-PROTOCOL.mdwithout the user’s yes. - What to close or park: a table (row, why, proposed status). Change only the rows the user picks, one
updateeach. - This week’s next actions: every next action addressed to the user, soonest date first.
- Protocol review: list the rows dated this week in
HUNT-PROTOCOL.mdsections 4 and 5. Ask: “Did you correct me on anything this week that is not written here?” If yes, write it there the same turn, dated, in the user’s words.
Check: every part that applies is in the review (part 5 only after three rejections), and every change the user agreed to printed a
Backup:line. -
- Keep the notebook current. Add one dated line to “The journey so far” in
user-story.md, for example- 14 Oct 2026: Weekly review: 3 tracked, 2 applied, 1 screened. Contoso parked.Skip it for a quick look that changed nothing. Tell the user in one line. Check:grep -nw "$(date '+%-d %b %Y')" user-story.mdshows the line.
Rules for this skill
- The workbook wins. If a markdown file (a status line in
APPLICATION-ANSWERS.md, a list inuser-story.md) disagrees withjob-tracker.xlsx, fix the markdown. If the workbook looks wrong, ask the user before changing it. - Status words, exactly:
Shortlisted·Ready to apply·Applied·Applied - confirmed·Interviewing·Offer·Rejected·Closed·Withdrawn·Recruiter contact·Inbound·Parked. Appliedis never a guess. Only on the user’s word, or a confirmation email (Applied - confirmed). A filled form isReady to apply.- Writes go through
tracker.py. If the script is missing, use the fallback inreferences/tracker-contract.md, section 8. It backs up first. - One live log. No CSV or markdown copy of the tracker is kept as a second truth. Reports live in chat.
- No openpyxl: the script writes
job-tracker.csvinstead. Say the install line once:python3 -m pip install openpyxl. - Never delete a row, a note or a backup. To end a row, set
Closed,WithdrawnorParked. - Rule 4: never invent a fact. A note says what the user or a source said, and who said it.
- Rule 14: the tracker never reaches an employer. Never attach it, and remind the user to close it before a screen share.
- Rule 15: a change the user did not ask for (from the weekly review) is shown as a table first.
- Rule 16: answer the question the user asked first.
- Questions to the user go through
AskUserQuestion(two to four options), or a plain yes/no.
What to tell the user when you finish
For “where do I stand”, the step 2 answer: needs you, overdue, stale, then the counts. For the weekly review, the eight parts of step 6, each short.
Example (Jane Doe, Wednesday 14 Oct 2026):
Where you stand, Wed 14 Oct
Needs you
- Acme Design Systems, Senior Product Designer. https://jobs.ashbyhq.com/acme-design-systems/7c1e2f40-5a6b-4c3d-9e8f-0a1b2c3d4e5f ·
applications/acme-senior-product-designer/. Overdue: Sam’s hiring manager slots were due 7 Oct and have not come, and your plan was one nudge after 9 Oct. Want a nudge draft?Stale: none. Contoso has been quiet for 19 days, but its next action waits until 16 Oct.
3 roles: 1 shortlisted, 1 applied and confirmed, 1 interviewing.
Done when
- The sheet matches what the user told you. Every change printed
Updated roworAdded row. - Every write printed a
Backup:line, and the backup file exists. - The answer or the review is out, with the “Needs you” rows first.
- No second copy of the tracker was made.
user-story.mdhas today’s line if anything changed.
Next
- Every day: hope-sync, so the rows follow the inbox. Offer: “Want me to check your email and LinkedIn now?”
- No
Shortlistedrows left: hope-discovery, for a fresh sweep. - A nudge or a reply is due: hope-write.