There is no CI: the instance has no act_runner and none is planned, so releases are cut by hand. That makes `make check` the only thing standing between a mistake and the tracker, and it is one command: gofmt, vet, the suite with the cache defeated, `go mod verify`, a vendored build, and `kettle gen skills --check`. The last one is the invariant worth having — the plugin's SKILL.md command reference is generated from the binary's registry, so a flag that changed cannot ship with documentation that recommends the old one. `cli/cmd/release` publishes to Gitea using the same SDK the binary already vendors, which is a pleasing thing to be able to say: nothing third-party handles the artifacts. It is a second binary rather than a `kettle` subcommand on purpose — `kettle`'s command tree is what generates the plugin's skills, so a verb there ships to every operator, and publishing a release is build infrastructure. It is idempotent end to end: an existing release for the tag is reused, an asset of the same name is replaced rather than doubled, and a retried run converges instead of duplicating. `make release` refuses three things, each with its own message: a dirty working tree, a TAG that is not what `git describe` reports, and a tag the remote does not have. A release built from uncommitted code is unreproducible and nobody finds out until they need to reproduce it. `kettle version` reports the stamp, the toolchain and the VCS revision. The default is `dev`, and a hand build says so and means it — a binary out of somebody's working tree is not a release and must not claim to be one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
8.8 KiB
name, description
| name | description |
|---|---|
| project | Generated flag reference for the project-level `kettle` commands — `kettle init`, `kettle auth`, `kettle config`, `kettle gen`. Load it to look up the exact flags and defaults of one of those four, or when a command answers "no project" / "no login" and you need `kettle config` to say what this directory resolved to. The rules around initializing are /kettle:init and the credential workflow is /kettle:auth; this file is the flag table both of them point at. |
kettle project — the project itself
kettle resolves everything from one marker. <project>/.kettle/ is created by
kettle init and never inferred: .git is in every clone, so a tool that
guessed a root from one would write issues into whatever tree it happened to be
standing in. With no marker anywhere the command stops and names the directories
it searched — that is an answer, not a fallback.
Two configuration files, and the split is the point. <project>/.kettle/config.yaml
holds the tracker repository and the name of a login; the name is worth
nothing on its own, which is what makes it safe inside a working tree.
~/.config/kettle/logins.yaml (0600, one per machine, $KETTLE_CONFIG_HOME or
$XDG_CONFIG_HOME move it) holds the tokens. KETTLE_LOGIN, KETTLE_REPO,
KETTLE_URL and KETTLE_TOKEN each override the file they shadow.
No kettle on PATH? command not found: kettle is the whole story — no
script and no tea invocation substitutes for it. Stop and tell the operator to
install it: cd cli && go build -o ~/.local/bin/kettle ./cmd/kettle in the
marketplace repository (go.mod requires go 1.26), or
go install git.noodles.cam/claude-skills/marketplace/cli/cmd/kettle@latest.
kettle gen is a maintainer command: it rewrites the generated region of these
SKILL.md files from the command registry the binary was built from. Run it after
changing the CLI, never to "fix" documentation by hand.
Generated from the kettle command registry by kettle gen skills. Everything between the two markers is replaced on the next run — hand-written prose belongs outside them.
kettle auth list | add | remove <name>
manage the tokens this machine holds
Credentials live in one file per machine, outside every working tree, mode 0600. A project pins a login by NAME; the name is worth nothing on its own, which is what makes it safe to keep in a file inside the repository.
The token is read from standard input unless --token is given, because an argument is in the shell history the moment it is typed:
kettle auth add --name noodles --url https://git.example.com < token.txt
pass show gitea/token | kettle auth add --name noodles --url https://git.example.com
list never prints a token. There is no flag to make it.
| flag | default | what it does |
|---|---|---|
--name |
— | login name (add) |
--token |
— | token, if you would rather not use stdin (add) |
--url |
— | instance URL, e.g. https://git.example.com (add) |
--user |
— | account this token belongs to; documentation only (add) |
kettle auth list # what this machine holds
pass show gitea | kettle auth add --name noodles --url https://git.example.com # add one, token on stdin
kettle auth remove noodles # forget it
kettle config
show what this project resolved to
Every path and every setting, with the overrides already applied, so a run that went somewhere unexpected can be explained without guessing.
The token is never printed — only whether one was found.
This is the command to reach for when the store looks empty, when a push says 401, or when two directories disagree about which project they are in.
kettle config # resolved paths and settings
kettle gen skills
write the plugin's SKILL.md files from the command registry
A SKILL.md tells an agent how to invoke this binary. Hand-written, it drifts: a flag is renamed here and the documentation goes on recommending the old one, and the agent that reads it fails in a way nobody traces back to a stale sentence. Everything those files say about a command — its usage line, its flags with their defaults, its worked examples — is already in the registry this binary is built from, so it is written from there and cannot disagree.
THE GENERATOR OWNS A REGION, NOT A FILE. Each SKILL.md carries a pair of HTML
comment markers — kettle:gen to open and /kettle:gen to close, both written in
the <!-- … --> form and visible at the top and bottom of the block below.
Everything between them is replaced on every run; every byte outside them comes
back exactly as it was, which matters most for description:, the prose that
decides whether an agent loads the skill at all, and the one thing here that no
generator can write.
A file with no markers is REPORTED AND LEFT ALONE, never overwritten: clobbering somebody's prose because they forgot a marker is the failure this design exists to prevent. A file that does not exist yet is created with a frontmatter stub around a generated block, for a human to fill in.
The output is deterministic to the byte — no timestamps, no map iteration — so regenerating something that has not changed produces no diff. --check is that property made useful: it writes nothing and exits 1 when any file on disk differs from what would be generated, which is what a pre-commit hook or a CI step calls. It wins over --dry-run when both are given.
| flag | default | what it does |
|---|---|---|
--check |
false |
write nothing, exit 1 if anything is out of date |
--dry-run |
false |
print what would change; write nothing |
--out |
— | directory the skills live in; one /SKILL.md under it |
kettle gen skills --out ../plugins/kettle/skills # write the region in every group's SKILL.md
kettle gen skills --out ../plugins/kettle/skills --dry-run # print what would change; write nothing
kettle gen skills --out ../plugins/kettle/skills --check # exit 1 if the docs are out of date
kettle init
make this directory a project that tracks issues
Creates .kettle/ — the marker every other command resolves the store from,
and .kettle/config.yaml, which says which tracker repository these issues
belong to and which login to reach it under.
The marker is deliberately something an operator makes, not something inferred
from the tree: .git is in every clone, so anything that inferred a root from
one would write issues into whatever it happened to be installed in.
--login pins a name, never a credential. The tokens live in one file per
machine, outside every working tree, managed with kettle auth.
All of it is idempotent: it creates .kettle/issues and .kettle/payload, migrates an older store in if it finds one (either layout the tea plugin used, oldest first), writes the config without disturbing settings it was not given, and adds .kettle/ to .gitignore. Each migration is a move, not a copy — two stores is the state the marker exists to prevent — and it refuses to pick a winner when both sides hold a file of the same name.
Do NOT run this inside a linked worktree. A worktree is the same project on another branch and reaches the store by a hop out to the main checkout; a marker here would give one project two stores, and the directory holding the second one disappears with the branch.
| flag | default | what it does |
|---|---|---|
--at |
— | directory to initialize (default: the working directory) |
--dry-run |
false |
report what would happen; change nothing |
--login |
— | name of a login in the machine-wide file (see kettle auth) |
--repo |
— | tracker repository, as owner/name |
kettle init # initialize the current directory
kettle init --login noodles --repo claude-skills/marketplace # and point it at a tracker
kettle init --at ~/code/x # initialize somewhere else
kettle init --dry-run # say what it would do, touch nothing
kettle version
print the version this binary was built as
A binary that cannot say what it is, is a support problem: an operator with an old copy on PATH and a skill written against a newer one has no way to see the mismatch, and neither does anybody reading their transcript.
The version is stamped at link time. A build from source says "dev" and means it — that is not a placeholder to be edited, it is the answer for a binary that came out of somebody's working tree rather than off a tag.
The commit is reported when the build recorded one, which go build does from
git and a build from an unpacked tarball cannot. A tree with uncommitted
changes in it says so beside the commit.
| flag | default | what it does |
|---|---|---|
--short |
false |
print the version alone, with nothing around it |
kettle version # the version, the toolchain and the commit
kettle version --short # just the version, for a script