diff --git a/.gitignore b/.gitignore index 81841fb..a515597 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ salary_data.json job_scraper/seen_jobs.json job_scraper/hackpost_applications.json job_scraper/hackpost_saved.json +job_scraper/hackpost_ships.json job_scraper/*.md *_BehavioralReport.pdf linkedin_Profile.pdf diff --git a/ACKNOWLEDGEMENTS.md b/ACKNOWLEDGEMENTS.md new file mode 100644 index 0000000..da2e59c --- /dev/null +++ b/ACKNOWLEDGEMENTS.md @@ -0,0 +1,34 @@ +# Acknowledgements and project lineage + +Hackpost CLI is a modified fork of [Mads Lorentzen's AI Job Search](https://github.com/MadsLorentzen/ai-job-search). + +The upstream project supplied the foundation for: + +- Claude Code profile setup and candidate-data structure +- job-posting fit evaluation +- the drafter-reviewer application workflow +- LaTeX CV and cover-letter generation and verification +- interview preparation, outcomes, ranking, templates, portal adapters, and upskilling +- the original security guards, tests, and documentation structure + +We are grateful to Mads Lorentzen and the upstream contributors for publishing this work under the MIT License. The original copyright and license notice remain in [`LICENSE`](LICENSE), and the upstream Git history is preserved in this repository. + +## What Hackpost added + +Hackpost maintains a separate product layer for AI-native freelance bounties: + +- a dependency-free `hackpost` executable +- live bounty discovery from `hackpost.io/api` +- bounty search by skill and minimum budget +- local save, application-intent, ship, and token-activity records +- `/hackpost-apply`, a project-bounty workflow that produces a capability brief and stops before external submission +- an inspectable privacy command and explicit no-upload boundary +- an environment doctor for local prerequisites +- an opt-in hacker activity leaderboard foundation +- offline fictional bounty data when the public API is unavailable + +Hackpost CLI is independently maintained by xAGI Labs. It is not endorsed by or affiliated with Mads Lorentzen, the upstream contributors, Anthropic, or Claude. + +## Upstream support + +If the original framework is useful to you, please star the [upstream repository](https://github.com/MadsLorentzen/ai-job-search) and consider supporting its author through the links in the upstream README. diff --git a/HACKPOST.md b/HACKPOST.md index c7598f2..0310532 100644 --- a/HACKPOST.md +++ b/HACKPOST.md @@ -1,14 +1,30 @@ # Hackpost integration -This fork preserves the upstream AI Job Search workflows and adds a local-first Hackpost bounty surface. +This fork preserves the upstream AI Job Search workflows and adds a local-first Hackpost bounty surface. See [ACKNOWLEDGEMENTS.md](ACKNOWLEDGEMENTS.md) for exact project lineage and attribution. ```bash npm link hackpost gigs --skill agents --min-budget 2000 hackpost bounty hp-1048 -hackpost apply hp-1048 --url https://hackpost.dev/bounties/example +hackpost apply hp-1048 +hackpost ship --title "Browser agent eval harness" --tokens 250000 +hackpost privacy +hackpost doctor ``` Then start Claude Code in this repository and run `/hackpost-apply HP-1048`. The workflow evaluates project fit, prepares truthful application materials, and stops before external submission. -`HACKPOST_API_URL` can point the CLI at another Hackpost API deployment. If it is unavailable, the CLI uses the included fictional offline snapshot. +The CLI reads live bounties from `https://hackpost.io/api` by default. `HACKPOST_API_URL` can point it at another deployment. If the API is unavailable, the CLI uses the included fictional offline snapshot. + +## How Hackpost differs from upstream + +| Area | Upstream AI Job Search | Hackpost CLI | +| --- | --- | --- | +| Primary opportunity | Employment postings | Scoped AI bounties and freelance projects | +| Discovery | Country and portal-specific search skills | Hackpost's normalized bounty API plus offline snapshot | +| Application output | Tailored CV and cover letter | Fit assessment, capability brief, concise application note, and optional portfolio/CV material | +| Submission | Prepares job-application documents | Stops before external submission and points back to the canonical bounty source | +| Local records | Job tracker and outcomes | Saved bounties, application intent, shipped projects, and optional token totals | +| Reputation | Not included | Local proof-of-work and opt-in leaderboard foundation | +| Privacy UX | Gitignore and security guards | Adds `hackpost privacy`; token/ship records are local and never uploaded by v0.2 | +| Runtime UX | Claude slash-command framework | Adds a dependency-free Node executable alongside the inherited Claude workflows | diff --git a/LICENSE b/LICENSE index dd86a45..14769f4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ MIT License Copyright (c) 2026 Mads Lorentzen +Copyright (c) 2026 xAGI Labs contributors (Hackpost modifications) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..8d37522 --- /dev/null +++ b/NOTICE @@ -0,0 +1,10 @@ +Hackpost CLI +Copyright (c) 2026 xAGI Labs contributors + +This software is a modified fork of AI Job Search: +https://github.com/MadsLorentzen/ai-job-search + +AI Job Search copyright (c) 2026 Mads Lorentzen and is distributed under the MIT License. +The original MIT license notice is preserved in LICENSE. The Hackpost-specific command surface, +bounty integration, local activity records, privacy boundary, and associated documentation are +independent modifications maintained by xAGI Labs. diff --git a/README.md b/README.md index 3ccd49f..bdfc0f0 100644 --- a/README.md +++ b/README.md @@ -1,342 +1,191 @@ -

- AI Job Search Assistant -

- # Hackpost CLI -> A Hackpost-modified, local-first application workspace based on AI Job Search. Discover premium AI bounties, evaluate fit, prepare truthful application materials, and keep private career data on your machine. - -```bash -npm link -hackpost gigs --skill agents --min-budget 2000 -hackpost apply hp-1048 --url https://hackpost.dev/bounties/example -``` +> A local-first workspace for discovering AI bounties, evaluating project fit, preparing truthful application materials, and recording proof of work. -See [HACKPOST.md](HACKPOST.md) for the bounty workflow. All original job-search, CV, cover-letter, interview, ranking, outcome, template, portal, and upskilling workflows remain available below. +[![Upstream](https://img.shields.io/badge/upstream-AI%20Job%20Search-f97316)](https://github.com/MadsLorentzen/ai-job-search) +[![License](https://img.shields.io/badge/license-MIT-15803d)](LICENSE) +[![Hackpost](https://img.shields.io/badge/bounties-hackpost.io-1d4ed8)](https://hackpost.io/bounties) -This project is a modified distribution of Mads Lorentzen's MIT-licensed [AI Job Search](https://github.com/MadsLorentzen/ai-job-search). It is independently maintained by Hackpost and is not affiliated with the upstream author, Anthropic, or Claude. +## Fork acknowledgement ---- +**Hackpost CLI is a modified fork of [Mads Lorentzen's AI Job Search](https://github.com/MadsLorentzen/ai-job-search), created by Mads Lorentzen.** -## Upstream framework: AI Job Search +The upstream project supplied the foundation for candidate profiles, job-fit evaluation, the drafter-reviewer pipeline, LaTeX CV and cover-letter generation, interview preparation, outcome tracking, ranking, portal adapters, security guards, tests, and the original Claude Code workflow structure. -[![CI](https://github.com/MadsLorentzen/ai-job-search/actions/workflows/ci.yml/badge.svg)](https://github.com/MadsLorentzen/ai-job-search/actions/workflows/ci.yml) +The original copyright and MIT license are preserved in [LICENSE](LICENSE), the upstream Git history remains intact, and detailed credit appears in [ACKNOWLEDGEMENTS.md](ACKNOWLEDGEMENTS.md) and [NOTICE](NOTICE). Hackpost is independently maintained by xAGI Labs and is not endorsed by or affiliated with Mads Lorentzen, the upstream contributors, Anthropic, or Claude. -An AI-powered job application framework built on [Claude Code](https://claude.com/claude-code). Fork it, fill in your profile, and let Claude evaluate job postings, tailor your CV, write cover letters, and prepare you for interviews. +If you need the original employment-focused framework, use and star the [upstream repository](https://github.com/MadsLorentzen/ai-job-search). -> Note: This is an independent open-source project and is not affiliated with, endorsed by, sponsored by, or maintained by Anthropic. Anthropic and Claude Code are referenced only to describe the toolchain this workflow uses. +## What Hackpost changes -

- Did this save you a Sunday of cover-letter writing? Consider a coffee.
- Did it land you the job? Maybe two.
☕ -

+Hackpost does not present the inherited work as original. It adds a separate AI-bounty product layer: -

- - Buy me a coffee at ko-fi.com - -

+| Hackpost addition | Difference from upstream | +| --- | --- | +| `hackpost gigs` and `hackpost bounty` | Queries Hackpost's normalized AI-bounty API instead of relying only on job-portal skills | +| `hackpost apply HP-…` | Resolves a bounty, records application intent locally, and hands it to `/hackpost-apply` | +| `/hackpost-apply` | Evaluates project-delivery fit and drafts a capability brief and application note instead of assuming a conventional employment cover letter | +| `hackpost save`, `stats`, and `profile` | Maintains a local bounty pipeline separate from the inherited employment tracker | +| `hackpost ship` | Records shipped work and optional token totals locally for proof-of-work experiments | +| `hackpost privacy` | Shows the collection boundary and confirms that v0.2 uploads nothing | +| `hackpost leaderboard` | Connects to Hackpost's opt-in activity leaderboard; token spend is explicitly not treated as ability | +| `hackpost doctor` | Checks Node, Claude Code, LaTeX, and optional PDF tooling | +| Live API plus offline snapshot | Uses `hackpost.io/api` when available and fictional bundled bounties when offline | -## What this is +The inherited workflows remain available because they are useful building blocks. Their source and authorship are identified in the repository history and acknowledgement files. -A structured workflow that turns Claude Code into a full-stack job application assistant. The core workflow (self-profiling, fit evaluation, and the drafter-reviewer application pipeline) is **language- and country-agnostic**. The job portal search skills are built for the Danish market (Jobindex, Jobnet, Akademikernes Jobbank, etc.), but the pattern is designed to be swapped for your local job boards. +## Install -``` -/setup /scrape /apply - | | | - v v v -Fill in Search job Evaluate fit -your profile portals Score & recommend - | | | - v v v -Profile Present matches Draft CV + Cover Letter -files ready with fit ratings (LaTeX, tailored) - | | - v v - Pick a match Reviewer agent critiques - -> /apply -> Revise -> Final output -``` +Requirements for the Hackpost command: -The framework encodes career guidance best practices, including structured evaluation criteria, forward-looking cover letter framing, and optional salary benchmarking. - -## Prerequisites - -- [Claude Code](https://claude.com/claude-code) (CLI) -- Python 3.10+ -- [Bun](https://bun.sh) (for job search CLI tools) -- LaTeX distribution with `lualatex` and `xelatex`: [TeX Live](https://tug.org/texlive/), [MacTeX](https://tug.org/mactex/), [TinyTeX](https://yihui.org/tinytex/), or [MiKTeX](https://miktex.org/). The CV compiles with `lualatex` (pdflatex often fails on modern MiKTeX installs with `fontawesome5` font-expansion errors); the cover letter compiles with `xelatex` because `cover.cls` requires `fontspec`. If using a minimal TeX install such as TinyTeX or BasicTeX, install the extra packages listed in [SETUP.md](SETUP.md#minimal-tex-install-tinytexbasictex). -- Optional: `pdftotext` from [poppler](https://poppler.freedesktop.org/) (macOS: `brew install poppler`, Debian/Ubuntu: `apt install poppler-utils`, Windows: `choco install poppler`) — used by `/apply`'s ATS parseability check on the compiled CV. If missing, the check degrades gracefully to a visual keyword review. - -## Quick start - -### 1. Fork and clone +- Node.js 20+ +- Optional: Claude Code for the full application workflow +- Optional: LuaLaTeX and XeLaTeX for compiled application documents ```bash -gh repo fork MadsLorentzen/ai-job-search --clone -cd ai-job-search +git clone https://github.com/xAGI-labs/hackpost-cli.git +cd hackpost-cli +npm link +hackpost doctor ``` -### 2. Install job search tools - -PowerShell: - -```powershell -$tools = @("jobbank-search", "jobdanmark-search", "jobindex-search", "jobnet-search", "linkedin-search", "freehire-search") -foreach ($tool in $tools) { - Set-Location ".agents/skills/$tool/cli" - bun install - Set-Location "..\..\..\.." -} -``` +The inherited portal tools may additionally require Bun. See the [upstream setup guide](https://github.com/MadsLorentzen/ai-job-search/blob/master/SETUP.md) for the original framework's full dependencies. -Bash / zsh / Git Bash: +## Find AI bounties ```bash -for tool in jobbank-search jobdanmark-search jobindex-search jobnet-search linkedin-search freehire-search; do - cd .agents/skills/$tool/cli && bun install && cd ../../../.. -done -``` +# Browse the live Hackpost feed +hackpost gigs -For `linkedin-search` and `freehire-search` the install is optional: both have zero runtime dependencies and run with plain `bun`; `bun install` only pulls TypeScript dev types. +# Filter by skill and budget +hackpost gigs --skill agents --min-budget 2000 -### 3. Set up your profile +# Inspect one listing +hackpost bounty hp-1048 -```bash -claude -# Then inside Claude Code: -/setup +# Save it locally +hackpost save hp-1048 ``` -`/setup` offers three paths: read your `documents/` folder if you have one populated (CV PDF, LinkedIn export, diplomas, reference letters, past applications), import a single CV pasted in chat, or walk through an interview. It auto-detects what you have and asks. Documents-folder mode is idempotent and safe to re-run as you add more material; see `documents/README.md` for the layout. - -### 4. Search for jobs +The default API is `https://hackpost.io/api`. Override it when developing: ```bash -/scrape +HACKPOST_API_URL=http://localhost:8788/api hackpost gigs ``` -This searches multiple job portals for positions matching your profile, deduplicates results, and presents them sorted by fit. Pick a match to run `/apply` on it directly — or, when a scrape returns more jobs than you want to eyeball, run `/rank` to batch-score them all against the fit framework and get a ranked shortlist first. +If the API cannot be reached, the CLI says that it is using the bundled fictional offline snapshot. -### 5. Apply to a job +## Prepare a bounty application ```bash -/apply https://jobindex.dk/job/1234567 +hackpost apply hp-1048 ``` -If the URL can't be fetched (some job portals block automated access), you can paste the job description directly instead: +This records an application target in a gitignored local JSON file. It does not apply or send a message. + +For the full workflow: ```bash -/apply +claude +/hackpost-apply HP-1048 ``` -This runs the full workflow: evaluate fit, draft CV + cover letter, review with a second agent, revise, and present the final output. +The Hackpost workflow: -## Other commands +1. Retrieves the normalized brief and canonical source. +2. Evaluates delivery fit, availability, scope risk, and relevant shipped work. +3. Identifies gaps without inventing experience. +4. Drafts a concise application note and one-page capability brief. +5. Reviews the artifacts against the source requirements. +6. Stops before external submission and shows exactly what would be shared. -`/setup`, `/scrape`, and `/apply` form the core workflow. Seven more commands extend it once your profile is in place: +## Record proof of work -- **`/interview`** preps you for a scheduled interview on a tracked application. It builds a stage-specific prep pack from the application's archive (the exact posting, the CV and cover letter the interviewer actually read, feedback recorded from earlier rounds), researches the company and interviewers with a verify-before-use rule, maps likely questions to your STAR examples, and offers a mock interview following the roleplay protocol in `07-interview-prep.md`. Gaps get honest bridge answers, never invented experience. -- **`/outcome`** records what happened to an application - interview stages, offers, rejections, silence. It archives the submitted CV, cover letter, and posting text into `documents/applications/_/`, keeps `outcome.md` in the format `/setup` Path A parses, and updates the tracker. Once a few applications resolve, it points you back to `/setup` to calibrate the fit framework from what actually got interviews. -- **`/rank`** bridges `/scrape` and `/apply`: it batch-scores all newly scraped postings against the fit framework (parallel agents fetch each posting and score the five evaluation dimensions) and returns a ranked shortlist with honest per-job strengths and gaps. Deal-breakers veto, deadlines get urgency flags, dead postings get marked expired. Pick a number and it hands off to the full `/apply` workflow. -- **`/expand`** enriches your profile by scanning public sources you've already linked in it (GitHub repos, portfolio site, Kaggle, Google Scholar) and looking up syllabi for named courses and certifications. Discovered competencies are added to your profile with a source tag. Useful right after `/setup` to surface skills that documents alone don't make explicit. -- **`/upskill`** analyzes the gap between your profile and your tracked job postings (or a single posting via `/upskill `). Produces a prioritized heatmap of skill gaps and a learning plan with web-searched study resources and time estimates. Useful for career planning between applications. -- **`/add-template`** registers your own LaTeX CV or cover letter template in place of the stock ones. It captures the template's instructions (compile engine, fonts, style rules, page limit), runs a mandatory test compile, and wires the template into `/apply`. See [LaTeX templates](#latex-templates) below. -- **`/add-portal`** generates a job-portal search skill for a job board in your market. It investigates the portal (search URL pattern, result structure, access rules), scaffolds the CLI skill from the same structure as the shipped ones, and test-runs a live query before registering. See [Job search tools](#job-search-tools) below. - -`/reset` is also available, see [Starting over](#starting-over) below. - -## File structure +```bash +hackpost ship \ + --title "Browser agent evaluation harness" \ + --url https://github.com/you/eval-harness \ + --tokens 250000 +hackpost profile +hackpost leaderboard ``` -ai-job-search/ -├── CLAUDE.md # Main candidate profile + workflow rules -├── .claude/ -│ ├── commands/ -│ │ ├── apply.md # /apply workflow (drafter-reviewer) -│ │ ├── setup.md # /setup onboarding (documents folder, CV import, or interview) -│ │ ├── expand.md # /expand competency enrichment from documents and online presence -│ │ ├── add-template.md # /add-template register custom LaTeX templates -│ │ ├── add-portal.md # /add-portal generate a job-portal search skill for your market -│ │ ├── rank.md # /rank triage scraped jobs into a ranked shortlist -│ │ ├── outcome.md # /outcome record application results, archive materials -│ │ ├── interview.md # /interview stage-specific prep pack + mock interview -│ │ └── reset.md # /reset wipe profile data or documents folder -│ ├── skills/ -│ │ ├── job-application-assistant/ # Core application skill -│ │ │ ├── SKILL.md # Skill definition -│ │ │ ├── 01-candidate-profile.md # Your education, experience, skills -│ │ │ ├── 02-behavioral-profile.md# PI/DISC/personality assessment -│ │ │ ├── 03-writing-style.md # Tone, structure, do's and don'ts -│ │ │ ├── 04-job-evaluation.md # Scoring framework for job fit -│ │ │ ├── 05-cv-templates.md # LaTeX CV structure + tailoring rules -│ │ │ ├── 06-cover-letter-templates.md # LaTeX cover letter templates -│ │ │ └── 07-interview-prep.md # STAR examples + interview framework -│ │ ├── job-scraper/ # Job search orchestration -│ │ └── upskill/ # /upskill skill gap analysis and learning plan -│ └── settings.json # Claude Code permissions (shared, scoped) -├── .agents/skills/ # Job portal CLI tools -│ ├── jobbank-search/ # Akademikernes Jobbank (Denmark) -│ ├── jobdanmark-search/ # Jobdanmark.dk (Denmark) -│ ├── jobindex-search/ # Jobindex.dk (Denmark) -│ ├── jobnet-search/ # Jobnet.dk (Denmark, government portal) -│ ├── linkedin-search/ # LinkedIn public job listings (country-agnostic) -│ └── freehire-search/ # freehire.dev tech job aggregator (multi-market, REST API) -├── cv/ -│ └── main_example.tex # moderncv LaTeX template -├── cover_letters/ -│ ├── cover.cls # Custom cover letter LaTeX class -│ ├── cover_example.tex # Example cover letter (structural reference + CI smoke test) -│ └── OpenFonts/ # Lato + Raleway fonts -├── templates/ # Custom templates registered via /add-template -│ └── README.md # Folder layout instructions -├── documents/ # Career source materials for /setup Path A and /expand -│ ├── README.md # Folder layout instructions -│ ├── cv/ # Master CV (PDF or .tex) -│ ├── linkedin/ # LinkedIn profile export (PDF) -│ ├── diplomas/ # Degree certificates and transcripts -│ ├── references/ # Reference letters -│ └── applications/ # Past application records (_/) -├── .github/workflows/ci.yml # CI: LaTeX smoke compiles, skill lint, CLI typechecks -├── salary_lookup.py # Salary benchmarking tool (BYO data) -├── tools/ -│ ├── convert_salary_excel.py # Convert salary Excel to JSON -│ ├── lint_skills.py # CI lint for skills, commands, settings.json -│ ├── security_guards.py # CI guards: permission allowlist, gitignore rules, manifests -│ └── README_SALARY_TOOL.md # Salary tool setup instructions -├── job_scraper/ # Scraper state (seen jobs, results) -├── upskill/ # /upskill report output (markdown reports per run) -├── job_search_tracker.csv # Application tracking spreadsheet -└── SETUP.md # Detailed setup guide -``` - -## How `/apply` works - -The `/apply` command runs a **drafter-reviewer workflow** with mandatory PDF compilation: - -1. **Parse** the job posting (URL or text) -2. **Evaluate fit** against your profile (skills, experience, culture, location, career alignment) -3. **Draft** a tailored CV and cover letter in LaTeX -4. **Spawn a reviewer agent** that researches the company and critiques the drafts -5. **Revise** based on the reviewer's feedback -6. **Compile and inspect** both PDFs: lualatex for the CV, xelatex for the cover letter. Claude reads the rendered pages and iterates on the LaTeX until the CV is exactly 2 pages with no orphaned entry titles, and the cover letter is exactly 1 page with the signature visible and fonts consistent. -7. **ATS-check the CV**: extract the PDF's text layer (`pdftotext`, optional dependency) and verify it the way an ATS parser sees it — contact details present as literal text, no garbled glyphs, sane reading order — then score the posting's keyword coverage against the extraction. Keywords the profile genuinely supports get added; genuine gaps stay visible, never stuffed. -8. **Present** the final output with a verification checklist -All claims in the CV and cover letter are verified against your actual profile. The system never fabricates skills or experience. +Ships and token totals remain local in v0.2. The public leaderboard is currently a synthetic Season 00 demonstration. Future participation must be opt-in and inspectable before upload. Token consumption measures activity, task size, and model choice—not engineering ability. -### What makes this workflow different +## Privacy boundary -- **PDF verification loop.** Most LaTeX-resume templates produce "looks fine in the .tex" output that breaks in the PDF: job titles orphan to the next page, cover letters spill onto page 2, bullet fonts silently fall back to the body font. The `/apply` command compiles and visually inspects every PDF and applies targeted fixes (`\needspace`, `\enlargethispage`, font-matching wrappers for list items) until the layout is clean. This runs automatically on every application. -- **ATS verification on the PDF text layer.** An ATS reads the PDF's embedded text, not the rendered page — and LaTeX can silently produce PDFs whose text extracts as garbage (icon glyphs where the email should be, interleaved lines from multi-column layouts). `/apply` extracts the compiled CV's text layer with `pdftotext` and verifies contact details, reading order, and the posting's keyword coverage against what a parser actually sees. Honesty rule enforced: a keyword the profile doesn't support is acknowledged as a gap, never stuffed in. -- **Relevance-weighted CV cutting.** When a CV overflows 2 pages, the workflow does not cut mechanically from the "oldest" section. It scores each candidate line by (a) relevance to the target posting, (b) uniqueness in the document, and (c) whether the cover letter depends on it, and cuts the lowest-total-score line first. An older-role bullet that hits posting keywords survives ahead of a recent-role bullet that does not. -- **Drafter-reviewer separation.** The drafter writes; a second Claude agent, spawned with a fresh context, researches the company and critiques the drafts. The drafter then revises. This catches missed keywords, weak framing, and generic language that a single pass often leaves in. -- **Token-efficient reviewer dispatch.** The reviewer agent receives drafts inline rather than re-reading them, and the verification checklist runs once at the end of the workflow rather than being duplicated by both agents. Note: the new compile-and-inspect step in Step 5 spends some of those savings on PDF rendering and layout iteration — the workflow trades some end-to-end token cost for a real reduction in broken PDFs reaching the user. - -## Customization - -### Which files to edit manually - -If you prefer editing files directly instead of using `/setup`: - -| File | What to change | -|------|---------------| -| `CLAUDE.md` | Your full profile (name, education, experience, skills, goals) | -| `01-candidate-profile.md` | Structured version of your CV data | -| `02-behavioral-profile.md` | Your behavioral assessment or self-assessment | -| `04-job-evaluation.md` | Skill match areas, career goals, motivation filters | -| `05-cv-templates.md` | Profile statement templates for different role types | -| `07-interview-prep.md` | Your STAR examples from actual experience | -| `search-queries.md` | Job search queries for your skills and location | - -### Updating your search queries - -As your priorities evolve, you can reconfigure just the job search without re-running the full profile setup: - -``` -/setup --section search +```bash +hackpost privacy ``` -This re-runs the search configuration interview: which roles to target, which skills to search for, which locations, and which portals. It also suggests role types you may not have considered based on your profile. - -### LaTeX templates +Hackpost CLI v0.2 never uploads: -The CV uses [moderncv](https://ctan.org/pkg/moderncv) (banking style). The cover letter uses a custom `cover.cls` with Lato/Raleway fonts. +- prompts or conversations +- source code or private files +- API keys or credentials +- raw model responses -To use your own template instead, run: +The following are local JSON records under `job_scraper/` and ignored by git: -``` -/add-template -``` +- saved bounty IDs +- application targets and status +- shipped-project records +- optional aggregate token totals -Point it at your `.tex` file (plus any `.cls`/`.sty` files or bundled fonts). The command interviews you for the template's instructions — compile engine, fonts and where they live, style rules to preserve, hard page limit — stores everything under `templates/`, runs a mandatory test compile, and activates the template so `/apply` drafts from it. Templates are stored with `[PLACEHOLDER]` tokens instead of personal data, so they're safe to commit and share. +Nothing is submitted to a bounty source automatically. -- `/add-template --list` shows registered templates -- `/add-template --use ` switches between them -- `/add-template --use default` reverts to the stock moderncv / cover.cls templates +## Commands -If you prefer doing it by hand, the manual route still works: update the guidance in `05-cv-templates.md` and `06-cover-letter-templates.md`. +```text +Discovery + hackpost gigs [--skill agents] [--min-budget 2000] + hackpost bounty hp-1042 + hackpost save hp-1042 -### Job search tools +Application workflow + hackpost apply hp-1042 [--url ] + hackpost stats + hackpost profile -The four Danish CLI tools in `.agents/skills/` (Jobbank, Jobdanmark, Jobindex, Jobnet) demonstrate the pattern for building a job-portal integration for a specific market. If you're in a different country, run: +Proof of work + hackpost ship --title "What you shipped" [--url ] [--tokens 250000] + hackpost leaderboard +Trust + hackpost privacy + hackpost doctor ``` -/add-portal -``` - -Give it your local job board's URL. The command investigates the portal (search-URL pattern, result-page structure, robots.txt/access rules), scaffolds a CLI skill with the same structure, commands, and output contract as the shipped ones, and test-runs a live query before registering anything. Auth-walled portals are declined, and portals with restrictive terms get a prominent personal-use-only warning in the generated skill. The generated skill is market-specific and lives in your fork; the generator itself is the universal part. - -Maintaining a fork adapted to your market or language? Add it to the [Community forks & adaptations](https://github.com/MadsLorentzen/ai-job-search/discussions/78) thread so others can find it. -For **country-agnostic** starting points outside Denmark, the repo ships two portal skills alongside the Danish demos: +## Inherited upstream workflows -- **`linkedin-search`** — built on LinkedIn's public, unauthenticated `jobs-guest` endpoints. Field-agnostic, **zero runtime dependencies** (runs with just `bun`), and takes the search location as an explicit flag, so it works for any market out of the box (`-l "Berlin, Germany"`, `-l "Mumbai, Maharashtra, India"`, `-l "Remote"`, …). Intended for **personal use only** — automated access is against LinkedIn's Terms of Service, so keep volume low. See `.agents/skills/linkedin-search/SKILL.md`. -- **`freehire-search`** — queries the [freehire.dev](https://freehire.dev) aggregator's public REST API (JSON, no API key). Tech-focused (software, data, engineering, DevOps, remote), multi-market via facet flags (`--region`, `--country`, `--remote`), and **zero runtime dependencies**. Unlike the HTML-scraping Danish portals, results come back structured (skills, seniority, category). The backend is MIT-licensed and [self-hostable](https://github.com/strelov1/freehire) — point `FREEHIRE_API_URL` at your own instance if you prefer. See `.agents/skills/freehire-search/SKILL.md`. +Hackpost retains these upstream Claude Code workflows: -### Salary benchmarking +- `/setup` +- `/scrape` +- `/apply` +- `/rank` +- `/interview` +- `/outcome` +- `/expand` +- `/upskill` +- `/add-template` +- `/add-portal` +- `/reset` -The salary tool works with any salary data you provide (union statistics, Glassdoor exports, personal research, etc.). See `tools/README_SALARY_TOOL.md` for the expected format and setup. If you don't have salary data, the salary step is simply skipped. +Their design and implementation come from [AI Job Search](https://github.com/MadsLorentzen/ai-job-search). Consult the [upstream README](https://github.com/MadsLorentzen/ai-job-search#readme) for authoritative documentation and consider supporting its author. -### Starting over +## Development -To wipe your profile data and start fresh: - -``` -/reset profile # clears skill files, preserves framework rules -/reset documents # deletes files from documents/ folder -/reset all # both +```bash +npm test +npm run lint:hackpost +python3 tools/security_guards.py +python3 -m unittest discover -s tests ``` -`/reset` shows exactly what will be deleted and requires you to type `RESET` to confirm. Nothing is deleted until you do. - -## Tips for better results - -### Profile depth matters - -The single biggest factor in output quality is how much detail you put into your profile. A thin profile produces generic applications; a detailed one enables genuinely tailored results. - -- **Role descriptions:** Don't just list job titles. Describe what you actually did in each position: specific projects, tools used, responsibilities, and measurable achievements. The more material you provide, the more precisely the system can reframe your experience for different roles. -- **Skills in context:** Instead of listing "Python" or "project management," describe how and where you applied them. "Built ML pipelines for customer churn prediction in Python using scikit-learn" gives the system far more to work with than "Python, machine learning." -- **All onboarding paths work:** Whether you point `/setup` at your `documents/` folder, paste a single CV, or walk through the interview, the principle is the same: richer input produces sharper output. - -### Career path discovery - -The framework supports two distinct modes of job searching: - -- **Explicit targeting:** You know which roles or sectors you want. The system helps refine and prioritize based on fit. -- **Latent opportunity discovery:** By analyzing your full history (not just job titles, but the actual work you did), the system can surface career paths you haven't considered. Transferable skills that map to unexpected industries, patterns in what you enjoyed or excelled at, or emerging roles that combine your domain expertise with new technology. - -To get the most from this, invest time during `/setup` in describing not just your experience, but what energized you, what drained you, and what you'd want more of. This context directly shapes how the system evaluates fit and which roles it surfaces during `/scrape`. - -## Contributing - -Thinking about a PR? Read [CONTRIBUTING.md](CONTRIBUTING.md) first - it explains what gets merged, what lives in forks, and why. - -## Acknowledgements - -- [Mikkel Krogholm](https://github.com/mikkelkrogsholm) ([skills repo](https://github.com/mikkelkrogsholm/skills)) for the job search CLI skills -- Built with [Claude Code](https://claude.com/claude-code) by [Anthropic](https://anthropic.com) +The repository deliberately preserves upstream tests and security guards. Hackpost-specific CLI tests live in `tests/hackpost-cli.test.mjs`. ## License -MIT +MIT. See [LICENSE](LICENSE), [NOTICE](NOTICE), and [ACKNOWLEDGEMENTS.md](ACKNOWLEDGEMENTS.md). diff --git a/bin/hackpost.mjs b/bin/hackpost.mjs index dee7e0d..887dd6a 100755 --- a/bin/hackpost.mjs +++ b/bin/hackpost.mjs @@ -3,20 +3,23 @@ import { mkdir, readFile, writeFile } from 'node:fs/promises'; import { existsSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; +import { spawnSync } from 'node:child_process'; const root = join(dirname(fileURLToPath(import.meta.url)), '..'); const stateDir = join(root, 'job_scraper'); const trackerPath = join(stateDir, 'hackpost_applications.json'); const fallbackPath = join(root, 'data', 'bounties.json'); -const apiBase = (process.env.HACKPOST_API_URL || 'https://hackpost.dev/api').replace(/\/$/, ''); +const shipsPath = join(stateDir, 'hackpost_ships.json'); +const getApiBase = () => (process.env.HACKPOST_API_URL || 'https://hackpost.io/api').replace(/\/$/, ''); const money = (b) => b.budgetMin === b.budgetMax ? `$${b.budgetMin.toLocaleString()}` : `$${b.budgetMin.toLocaleString()}–$${b.budgetMax.toLocaleString()}`; const readJson = async (path, fallback) => existsSync(path) ? JSON.parse(await readFile(path, 'utf8')) : fallback; const writeJson = async (path, value) => { await mkdir(dirname(path), { recursive: true }); await writeFile(path, `${JSON.stringify(value, null, 2)}\n`); }; +const available = (command) => spawnSync(command, ['--version'], { stdio: 'ignore' }).status === 0; export async function loadBounties(params = new URLSearchParams()) { try { - const response = await fetch(`${apiBase}/bounties?${params}`, { signal: AbortSignal.timeout(5000), headers: { 'User-Agent': 'HackpostCLI/0.1 (+https://github.com/xAGI-labs/hackpost-cli)' } }); + const response = await fetch(`${getApiBase()}/bounties?${params}`, { signal: AbortSignal.timeout(5000), headers: { 'User-Agent': 'HackpostCLI/0.2 (+https://github.com/xAGI-labs/hackpost-cli)' } }); if (!response.ok) throw new Error(`HTTP ${response.status}`); const data = await response.json(); return { ...data, offline: false }; @@ -38,9 +41,9 @@ const parseFlags = (args) => { async function main(args = process.argv.slice(2)) { const [command, id, ...rest] = args; - const flags = parseFlags(command === 'gigs' ? args.slice(1) : rest); + const flags = parseFlags(command === 'gigs' || command === 'ship' ? args.slice(1) : rest); if (!command || ['help', '--help', '-h'].includes(command)) { - console.log(`hackpost — local-first application workspace\n\nCommands:\n hackpost gigs [--skill agents] [--min-budget 2000]\n hackpost bounty hp-1042\n hackpost apply hp-1042 --url \n hackpost save hp-1042\n hackpost stats\n\nThen open Claude Code and run /hackpost-apply .`); return; + console.log(`hackpost — local-first AI bounty workspace\n\nDiscovery\n hackpost gigs [--skill agents] [--min-budget 2000]\n hackpost bounty hp-1042\n hackpost save hp-1042\n\nApplication workflow\n hackpost apply hp-1042 [--url ]\n hackpost stats\n hackpost profile\n\nProof of work\n hackpost ship --title "Agent eval harness" [--url ] [--tokens 250000]\n hackpost leaderboard\n\nTrust\n hackpost privacy\n hackpost doctor\n\nThen open Claude Code and run /hackpost-apply .`); return; } if (command === 'gigs') { const params = new URLSearchParams(); @@ -56,12 +59,30 @@ async function main(args = process.argv.slice(2)) { } if (command === 'apply') { if (!id) throw new Error('Usage: hackpost apply --url '); - const tracker = await readJson(trackerPath, []); const record = { id: id.toUpperCase(), url: flags.url || null, status: 'preparing', createdAt: new Date().toISOString() }; + const tracker = await readJson(trackerPath, []); + const { items } = await loadBounties(); + const bounty = items.find((item) => item.id.toLowerCase() === id.toLowerCase()); + const record = { id: id.toUpperCase(), title: bounty?.title || null, url: flags.url || (bounty?.slug ? `https://hackpost.io/bounties/${bounty.slug}` : null), sourceUrl: bounty?.sourceUrl || null, status: 'preparing', createdAt: new Date().toISOString() }; await writeJson(trackerPath, [...tracker.filter((item) => item.id !== record.id), record]); console.log(`Target saved locally: ${record.id}\n\nNext step:\n claude\n /hackpost-apply ${record.id}\n\nNothing was submitted. You review every artifact and apply on the original source.`); return; } if (command === 'save') { const savedPath = join(stateDir, 'hackpost_saved.json'); const saved = await readJson(savedPath, []); await writeJson(savedPath, [...new Set([...saved, id?.toUpperCase()])].filter(Boolean)); console.log(`Saved ${id?.toUpperCase()} locally.`); return; } - if (command === 'stats') { const tracker = await readJson(trackerPath, []); const saved = await readJson(join(stateDir, 'hackpost_saved.json'), []); console.log(`${saved.length} saved · ${tracker.length} preparing/tracked`); return; } + if (command === 'stats' || command === 'profile') { const tracker = await readJson(trackerPath, []); const saved = await readJson(join(stateDir, 'hackpost_saved.json'), []); const ships = await readJson(shipsPath, []); const tokens = ships.reduce((sum, ship) => sum + Number(ship.tokens || 0), 0); console.log(`${saved.length} saved · ${tracker.length} preparing/tracked · ${ships.length} ships · ${tokens.toLocaleString()} locally recorded tokens`); return; } + if (command === 'ship') { + if (!flags.title) throw new Error('Usage: hackpost ship --title "What you shipped" [--url ] [--tokens 250000]'); + const ships = await readJson(shipsPath, []); const tokens = Number(flags.tokens || 0); + if (!Number.isFinite(tokens) || tokens < 0) throw new Error('--tokens must be a positive number'); + const ship = { id: `ship_${Date.now()}`, title: String(flags.title), url: flags.url || null, tokens, recordedAt: new Date().toISOString(), shared: false }; + await writeJson(shipsPath, [...ships, ship]); + console.log(`Ship recorded locally: ${ship.title}\nToken activity: ${tokens.toLocaleString()}\nShared: no\n\nNo telemetry was uploaded. Run hackpost privacy to review the boundary.`); return; + } + if (command === 'leaderboard') { console.log('Hackpost hacker activity leaderboard\nhttps://hackpost.io/hackers\n\nSeason 00 is demo data. Future participation is opt-in and locally aggregated; token spend measures activity, not ability.'); return; } + if (command === 'privacy') { console.log(`Hackpost privacy boundary\n\nNever collected for the leaderboard:\n • prompts or conversations\n • source code or private files\n • API keys or credentials\n • raw model responses\n\nLocal by default:\n • saved bounties\n • application targets and status\n • ships and optional token totals\n\nNothing is uploaded by this CLI version. Every local state file is JSON under job_scraper/ and ignored by git.`); return; } + if (command === 'doctor') { + const checks = [['Node 20+', Number(process.versions.node.split('.')[0]) >= 20], ['Claude Code', available('claude')], ['LuaLaTeX', available('lualatex')], ['XeLaTeX', available('xelatex')], ['pdftotext (optional)', available('pdftotext')]]; + checks.forEach(([label, ok]) => console.log(`${ok ? '✓' : '○'} ${label}`)); + console.log('\nRequired for bounty discovery: Node 20+. Document compilation uses Claude Code and LaTeX. pdftotext is optional.'); return; + } throw new Error(`Unknown command: ${command}. Run hackpost help.`); } diff --git a/package.json b/package.json index 35f19fc..11ee9ac 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hackpost/cli", - "version": "0.1.0", + "version": "0.2.0", "description": "Local-first AI application workspace with Hackpost bounty support", "type": "module", "bin": { "hackpost": "bin/hackpost.mjs" }, diff --git a/tests/hackpost-cli.test.mjs b/tests/hackpost-cli.test.mjs index cd79cf3..4c073b5 100644 --- a/tests/hackpost-cli.test.mjs +++ b/tests/hackpost-cli.test.mjs @@ -1,2 +1,4 @@ -import test from 'node:test'; import assert from 'node:assert/strict'; import { loadBounties } from '../bin/hackpost.mjs'; +import test from 'node:test'; import assert from 'node:assert/strict'; import { execFileSync } from 'node:child_process'; import { loadBounties } from '../bin/hackpost.mjs'; test('offline catalog can filter by skill and budget', async () => { process.env.HACKPOST_API_URL = 'http://127.0.0.1:1'; const result = await loadBounties(new URLSearchParams({ skill: 'agents', min_budget: '2000' })); assert.ok(result.count >= 1); assert.ok(result.items.every((item) => item.budgetMax >= 2000)); }); +test('privacy command states that nothing is uploaded', () => { const output = execFileSync(process.execPath, ['bin/hackpost.mjs', 'privacy'], { encoding: 'utf8' }); assert.match(output, /Nothing is uploaded/); assert.match(output, /source code/); }); +test('help exposes Hackpost-specific command groups', () => { const output = execFileSync(process.execPath, ['bin/hackpost.mjs', 'help'], { encoding: 'utf8' }); assert.match(output, /Proof of work/); assert.match(output, /hackpost leaderboard/); assert.match(output, /hackpost doctor/); }); diff --git a/tools/security_guards.py b/tools/security_guards.py index 4dcb517..0810353 100644 --- a/tools/security_guards.py +++ b/tools/security_guards.py @@ -46,6 +46,7 @@ "job_scraper/seen_jobs.json", "job_scraper/hackpost_applications.json", "job_scraper/hackpost_saved.json", + "job_scraper/hackpost_ships.json", "cv/main_*.tex", "!cv/main_example.tex", "cover_letters/cover_*.tex",