jobbsok could not start on stock Windows. `.mcp.json` named `bash` as the
command, and the five entry points behind it reached for grep, sed, find, awk,
zip and unzip. `bootstrap.sh` built the virtualenv, so a Windows adopter could
not even reach an interpreter. Two adopters are waiting and neither is
guaranteed to be on macOS, so this is the install, not a rough edge.
The Python layer was already clean -- no /tmp, no /usr, no os.uname, no home
directory assumption -- so only the shell layer moved. Behaviour is carried
over unchanged; the deliberate exceptions are listed in docs/.
THE ONE OPEN DECISION, AND WHY IT WAS FORCED
How does .mcp.json start an interpreter without a POSIX shell, when it is
called python3 on macOS and python or py on Windows? Measured against the
installed CLI, not assumed:
- The plugin mcpServers stdio schema has NO platform-conditional form. A
config carrying invented windows/darwin/platform keys was accepted and the
keys were silently discarded -- it fails quietly, not loudly.
- ${VAR:-default} IS expanded, in command, args and env.
- ${VAR} without a default is not safe: unset, it is passed through
unexpanded, so the spawn would try to run a program named ${VAR}.
- Windows spawns with shell:false, so a .py path as command is out.
- No single literal works. On this Mac, python and py are not on PATH.
So the default form is the only lever the schema offers:
"${JOBBSOK_LAUNCH_PYTHON:-python3}". macOS and Linux keep working with nothing
set; Windows sets one variable and needs no shell.
A SECOND VARIABLE, NOT A REUSE OF JOBBSOK_PYTHON
JOBBSOK_PYTHON names the interpreter to SERVE on: the launcher treats it as an
explicit operator choice, so it wins over the bootstrapped virtualenv. A
Windows adopter setting it merely to spell `python` would silently bypass that
virtualenv and serve WITHOUT the ingestion guard. JOBBSOK_LAUNCH_PYTHON only
says how to start the launcher. A test asserts the two never collapse into one.
O4 IS LEFT STANDING
The launcher still gates on the interpreter's version rather than on the guard
being importable. That is the shell version's semantics carried over on
purpose: harmless while nothing writes, a defect from M2, and an M2 decision.
VERIFY
- pytest tests/ -> 124 passed, exit 0 (was 112)
- grep -c '"command": "bash"' .mcp.json -> 0
- git ls-files 'scripts/*.sh' -> 0
- README install block names Windows, and neither WSL nor Git Bash
- server started end to end exactly as .mcp.json expands, and answered
initialize and tools/list
- the ported probe checker reproduces the shell version's output and exits 0
NOT MEASURED, AND NOT ASSUMED
Nothing here has ever run on Windows. Whether Cowork on Windows bridges to a
host-side stdio MCP as it does on this Mac is unmeasured -- docs/cowork-probe.md
covered macOS only. docs/cross-platform-port.md says what a Windows probe would
have to measure, and records two findings left deliberately untouched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
135 lines
6 KiB
Markdown
135 lines
6 KiB
Markdown
# 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.*
|
||
|
||

|
||

|
||

|
||
|
||
**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:
|
||
|
||
```bash
|
||
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*:
|
||
|
||
```bash
|
||
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:
|
||
|
||
```bash
|
||
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:
|
||
|
||
```bash
|
||
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](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](CHANGELOG.md).
|
||
|
||
## License
|
||
|
||
MIT — see [LICENSE](LICENSE).
|