Local-first job search workspace: sourcing, scoring, case tracking, applications and interview prep.
  • Python 98%
  • Shell 2%
Find a file
Kjell Tore Guttormsen 12125c1932 feat(m2): add deterministic sak_status cli with golden output
--workspace is required with no environment fall-back: this process runs
unsandboxed, and a status run that quietly picked a workspace is one nobody
can audit. --check exits 1 on a stale cache, distinct from 2 for a failed run.

Byte-stability is measured, not asserted: the CLI is run twice under two
PYTHONHASHSEED values and the bytes compared, in both output formats. The
write boundary gets two digest tests rather than one -- without --oppdater
every byte stays put, with it exactly the diverging case's four cached keys
move and logg.jsonl, the body and every other file are byte-identical.

Co-Authored-By: Claude <claude-opus-5>
2026-09-05 21:40:32 +02:00
.claude-plugin feat: initial commit — plugin manifest and build brief 2026-09-03 20:27:23 +02:00
docs feat(port): move five bash entry points to Python and drop bash from .mcp.json 2026-09-05 21:02:03 +02:00
scripts feat(m2): add deterministic sak_status cli with golden output 2026-09-05 21:40:32 +02:00
skills fix(m1): replace transcribed operator values in fixtures with invented ones 2026-09-04 21:35:24 +02:00
tests feat(m2): add deterministic sak_status cli with golden output 2026-09-05 21:40:32 +02:00
.gitignore test(m1): assert plugin manifest and pin a build stamp 2026-09-05 06:52:40 +02:00
.mcp.json feat(port): move five bash entry points to Python and drop bash from .mcp.json 2026-09-05 21:02:03 +02:00
CHANGELOG.md feat(port): move five bash entry points to Python and drop bash from .mcp.json 2026-09-05 21:02:03 +02:00
CLAUDE.md feat(port): move five bash entry points to Python and drop bash from .mcp.json 2026-09-05 21:02:03 +02:00
LICENSE feat: initial commit — plugin manifest and build brief 2026-09-03 20:27:23 +02:00
pyproject.toml build(m1): pin toolchain and guard v1.3.0 in pyproject 2026-09-04 17:26:20 +02:00
README.md feat(port): move five bash entry points to Python and drop bash from .mcp.json 2026-09-05 21:02:03 +02:00
SECURITY.md feat(port): move five bash entry points to Python and drop bash from .mcp.json 2026-09-05 21:02:03 +02:00

jobbsok

Local-first job search workspace: sourcing, scoring, case tracking, applications and interview prep.

A read-only job search operating system for one person searching for their own job. It reads listings the operator is already looking at, scores them against a maintained candidate profile, records every accept/reject decision with a reason, keeps each case's full correspondence in one folder, drafts applications as files, and flags threads that have gone silent. It never submits, never sends, never stores a credential.

Solo-maintained, fork-and-own. This plugin is a starting point, not a vendor product. Issues are welcome as signals; pull requests are not accepted.

AI-generated: all code produced by Claude Code through dialog-driven development.

Version Platform License

Status: pre-release, milestone 1 of six. The candidate profile, the scoring script, two skills and the host tool server are built; case folders, ingestion, correspondence, drafting and learning are not. The build brief in docs/build-brief.md is the contract, and milestones ship in order (M1–M6).

Install

Prerequisites. Python 3.10 or newer -- the host tool server refuses to start below that rather than serving on an interpreter the guard cannot run on -- git on PATH, and network access to git.fromaitochitta.com, the self-hosted Forgejo host the ingestion guard is installed from. That host is public and needs no account. One command tells you whether your network agrees, and it needs nothing else installed:

git ls-remote https://git.fromaitochitta.com/open/llm-ingestion-pipeline-security.git v1.3.0

A printed hash means the guarded ingestion path will build. An error means it will not, and that is not a cosmetic loss: the guard is the boundary every untrusted listing and email body passes at the write, so an install without it is a reader rather than a workspace.

Platform support. macOS, Linux and Windows. Every entry point is Python and the standard library; nothing here needs a POSIX shell, zip or the coreutils. On Windows the interpreter is normally called python or py -- python3 is not a stock Windows name -- so the commands below are given in both spellings, and the one setting that needs saying is in Windows: name the interpreter further down.

The plugin runs on two surfaces, and they install differently.

Claude Cowork -- the route that works today. There is no marketplace command: build the archive from this repository's explicit include list, then upload it through Customize -> Plugins:

python3 scripts/package_plugin.py     # writes jobbsok.plugin
python  scripts\package_plugin.py     # Windows

Never build that archive with a recursive zip of the repository root: it would ship .git, the virtualenv, the local-only STATE.md and everything under .claude/. The script exists so that cannot happen by accident.

Claude Code -- not available yet. jobbsok is deliberately absent from the ktg-plugin-marketplace catalog while it is pre-release, and claude plugin install resolves only through a marketplace, so no command here would work today. The catalog entry lands with milestone 2, pinned to a release tag. Printing an install line that fails at its first step would be worse than saying so.

Then, once, on either surface -- build the Python environment the scripts and the host tool server run on. This is also the step that installs the ingestion guard, and it prints the version it resolved:

python3 scripts/bootstrap.py          # add --med-xlsx for the spreadsheet export
python  scripts\bootstrap.py          # Windows

In an installed copy this builds the environment under $CLAUDE_PLUGIN_DATA, which survives a plugin update; the packaged archive deliberately excludes the virtualenv, so the bootstrap is the supported route there. Without it the jobbsok-tools server has no interpreter to run on and refuses to start rather than serving on whatever interpreter the PATH offers.

Windows: name the interpreter. .mcp.json has to spell the interpreter that starts the tool server, and the plugin format offers no way to spell it differently per platform -- so it reads ${JOBBSOK_LAUNCH_PYTHON:-python3}. macOS and Linux need nothing. On Windows, set it once:

setx JOBBSOK_LAUNCH_PYTHON python

Then restart the app, because a process reads its environment at startup. Without this the connector will not appear: python3 is not a Windows name, and where the Microsoft Store alias is enabled it is worse than absent -- it opens the Store rather than running anything.

JOBBSOK_LAUNCH_PYTHON and JOBBSOK_PYTHON are different settings and are not interchangeable. The first only says how to start the launcher. The second overrides which interpreter the server runs on, and it wins over the environment the bootstrap built -- so setting it to work around a spelling problem would quietly leave the server without the ingestion guard.

Finally, the workspace -- the plugin never guesses at a location:

export JOBBSOK_WORKSPACE=~/jobbsok-workspace   # or pass --workspace
setx JOBBSOK_WORKSPACE %USERPROFILE%\jobbsok-workspace   # Windows

The kandidatprofil skill scaffolds the tree on first run. The workspace is yours, lives outside this repository, and is never committed.

Non-goals

Submitting applications. Sending email. Any write to Finn.no or LinkedIn. Credential storage. Background jobs. Multi-user support. Recruiter-side features. Bulk listing harvest.

Security

The trust model, the boundaries and the risks this design accepts are in SECURITY.md. The short version: read-only against external sites, no credential handling, all data local, and every listing or email body passes the ingestion guard before it becomes a file.

Changelog

See CHANGELOG.md.

License

MIT — see LICENSE.