Baton is a CLI for managing private, per-project agent instructions outside the repository you are working in.
The project is distributed as a normal npm package for Node users. Bun is used for local development, testing, bundling, and optional standalone executable builds.
Each project's specs live in a private GitHub repository named baton-specs, cloned locally into ~/.baton. When you touch a working repository, Baton adds a .baton-specs symlink that points at the matching project folder inside ~/.baton, so local agents can read your private instructions without committing them into the shared repository.
- Keep personal agent instructions out of team repositories.
- Reuse the same specs across multiple clones or worktrees of the same project.
- Make agent instructions visible to local coding agents through a consistent
.baton-specs/README.mdentrypoint. - Prevent Baton’s temporary
AGENTS.mdreminder block from being committed.
- Install Baton.
- Run
baton sync --create.
baton sync --create uses the currently authenticated gh user and ensures that a private GitHub repository exists at:
https://github.com/<gh-user>/baton-specs
If the repository does not exist, Baton creates it. Baton then clones it into ~/.baton.
Register a project with:
baton add <id> <path-or-url>Rules:
idis the stable local identifier for the project.- If
<path-or-url>is a filesystem path, it must point to a git repository with anoriginremote. - If
<path-or-url>is a URL, it must be an exact GitHub repository URL inowner/repoform, with optional.gitor trailing slash. - Baton normalizes the GitHub URL and stores it in
baton.json. - Baton creates a project folder at
~/.baton/<id>. - Baton creates stub
baton.jsonandREADME.mdif they do not already exist.
Example baton.json:
{
"id": "pet-project",
"githubUrl": "https://github.com/user/my-project",
"scripts": {
"up": "npm ci && cp ~/projects/my-project/.env .env"
}
}Requirements:
baton.jsonis required.README.mdis required.- Both files are stored inside the private
baton-specsrepository.
Activate specs inside a working repository with:
baton touch [id]Behavior:
- Baton operates on the current git repository root.
- If
idis provided, Baton uses it directly. - If
idis omitted, Baton reads the current repository’soriginURL and matches it against registered projects by normalized GitHub URL. - Baton creates or refreshes
.baton-specsas a symlink to~/.baton/<id>. - Baton adds
.baton-specsto.git/info/exclude. - Baton records the touched repository in local Baton state.
When a repository is touched, Baton ensures that AGENTS.md contains exactly one Baton block:
<!-- baton:start -->
You must read `.baton-specs/README.md`
<!-- baton:end -->Rules:
- If
AGENTS.mddoes not exist, Baton creates it. - If
AGENTS.mdalready exists, Baton preserves the user’s non-Baton content and refreshes the Baton block. - Baton rejects malformed Baton markers instead of guessing how to rewrite the file.
- Baton rejects multiple Baton blocks.
When a repository is touched, Baton installs pre-commit and post-commit hooks.
pre-commit behavior:
- It validates the Baton block in the worktree before doing anything else.
- It never stages
AGENTS.mdfrom the worktree. - If
AGENTS.mdis already staged, it strips the Baton block from the staged version only. - If stripping leaves staged user content, that stripped content remains staged.
- If stripping leaves the file empty and Baton originally created
AGENTS.md, the staged file is removed. - If stripping leaves the file empty but
AGENTS.mdexisted before Baton touched the repo, an empty staged file is preserved.
post-commit behavior:
- It restores the Baton block in the worktree after the commit completes.
Run a script from the current project’s baton.json with:
baton run <script>Resolution rules:
- Baton first checks whether the current repository was previously touched and uses that touched-project mapping.
- If the repository is not in Baton’s local touched state, Baton falls back to matching by normalized
originURL. - The script runs in the current working directory, not inside
~/.baton.
Sync private specs with:
baton sync [--create]Behavior:
--createbootstraps~/.batonand the remotebaton-specsrepository when needed.- Baton stages and commits local changes in
~/.batonif necessary. - Baton pulls and pushes the current checked-out branch.
List registered projects and touched repositories with:
baton lsOutput includes:
- the project id
- the normalized GitHub URL
- any currently known touched repository roots
Example ~/.baton tree:
~/.baton/
pet-project/
baton.json
README.md
CODESTYLE.md
work-project/
baton.json
README.md
docs/
A.md
B.md
Notes:
- Baton’s local state is stored under
~/.baton/.baton-local/. - Local state is excluded from git.
- Working repositories contain symlinks only; the canonical specs live in
~/.baton.
- Baton validates loaded
baton.jsonfiles instead of trusting raw JSON. - Baton validates local Baton state instead of trusting raw JSON.
- Baton rejects malformed GitHub URLs.
- Baton rejects malformed Baton marker blocks instead of silently truncating user content.