Files
marketplace/cli/internal/scaffold/assets/skills/kettle-project/SKILL.md
T
naudachu 8b1b11001a feat: drop the kettle plugin; the binary writes its own skills
The plugin and the binary shipped on two release cadences and nothing on an
operator's machine ever checked that the one they installed described the other.
The generated flag block existed precisely so a renamed flag could not ship with
documentation recommending the old one — and then shipped one version behind the
registry it came from, which is the same bug one hop downstream.

So the prose moved into the binary. `internal/scaffold` embeds every document;
`kettle init` and `kettle gen scaffold` write them into a project's own
`.claude/`. The two cannot disagree because there is one artefact.

The namespace survived the move. A project's skills are flat, so the prefix is
spelled into the directory name (`kettle-issue`); a project's *commands* take
their namespace from a subdirectory, so `commands/kettle/init.md` is still
`/kettle:init`. Four of the six command files are thin pointers at a skill, and
that is what kept ~1,600 lines of `/kettle:…` cross-references true without a
rewrite. `init` and `auth` lost `disable-model-invocation: true` — being a
command is that property — and `auth` now restricts `allowed-tools` so a model
cannot reach `kettle auth add` at all.

`gen scaffold` writes files whole rather than splicing a region. The old
refusal protected somebody's hand-written prose around the block; that prose is
embedded now, so there is none to protect, and preserving local edits would
freeze a project's documentation at whatever version first initialized it.
`--check` warns before an upgrade discards one.

The plugin's `agents-sync.sh` — 141 lines of Python behind a filename that said
`.sh` — became `internal/mirror` and `kettle mirror`. Same seven branches, same
refusal to merge two real files that differ, now with a table test per branch
and a check that a repair converges in one pass. `--hook` is the PreToolUse
form and exits 0 on every path including a panic. It is opt-in per project,
which is strictly narrower than the plugin hook that was on for everybody who
installed it.

`kettle init --interactive` walks a person through the login, the token (read
with the echo off, so it lands in no history and no file), the repository, the
`.claude/` tree and the mirror hook. It refuses a stdin that is not a terminal
and names the flags instead: every question it asks has one, and it performs
nothing itself, so an interactive run and a flag run are one code path.

Two rules that used to be prose are now the binary's: init refuses a linked
worktree and names the main checkout, and writing into an existing
`.claude/settings.json` is refused with the snippet printed rather than
reformatting a file the operator commits.

The scaffold version stamp went to its own `.kettle/scaffold.yaml` rather than
into `config.yaml`, because unknown keys there are a hard error and that file
may be committed and read by whatever build each machine has.

golang.org/x/term becomes a direct dependency; it was already in the tree
indirectly, so no module was added.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 16:17:24 +05:00

14 KiB

name, description
name description
kettle-project Generated flag reference for the project-level `kettle` commands — `kettle init`, `kettle auth`, `kettle config`, `kettle gen`, `kettle mirror`. Load it to look up the exact flags and defaults of one of those, 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 other CLI substitutes for it, and it means the binary that wrote this file has since moved or been removed, because nothing else could have put it here. Stop and tell the operator to reinstall it: go install git.noodles.cam/claude-skills/marketplace/cli/cmd/kettle@latest, or cd cli && make install in a checkout of that repository (go.mod requires go 1.26).

This file is written by the binary and is replaced whole on the next kettle gen scaffold. Every word of it — the prose as much as the flag table below — is embedded in the kettle that wrote it, which is why the two cannot disagree about a flag. Editing it here changes nothing durable; the fix for a wrong sentence is a newer kettle, and the fix for a stale one is kettle gen scaffold. kettle config says which version wrote this tree.

Generated from the kettle command registry by kettle gen scaffold. Everything between the two markers is replaced on the next run — the prose around it is embedded in the binary and replaced with it.

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.

--scopes RECORDS WHAT THE TOKEN WAS MINTED WITH, and records is all it does: nothing is checked against it and nothing is refused because of it. It is worth writing down because the instance will not answer the question — Gitea's own token listing needs a password, not a token, so a token cannot be asked what it may do. Gitea spells them <read|write>:; issues need write:issue, and everything kettle api reaches outside issues — releases, pull requests, branches, tags, actions — is repository. A token minted for issues alone answers 403 there, and the 403 names no scope.

flag default what it does
--name login name (add)
--scopes what the token was minted with, comma separated, e.g. write:issue,write:repository; documentation only (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 add --name noodles --url https://git.example.com --scopes write:issue,write:repository < t.txt  # and write down what it can do
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 scaffold

write this project's .claude/ commands, skills and subagent

A skill tells an agent how to invoke this binary, and a command is how an operator invokes one by hand. Both are written from here, whole, because both travel INSIDE the binary: the prose is embedded next to the code it describes and the flag tables are rendered from the command registry the binary is built from, so neither can be a version behind the other.

That is the whole reason these documents are not a plugin any more. A plugin ships on its own cadence, and nothing on an operator's machine ever checked that the one they installed described the binary they installed — so a renamed flag could still arrive with documentation recommending the old one, which is exactly the failure the generated block was invented to prevent, one hop further downstream.

EVERY FILE IS WRITTEN WHOLE, and that is a deliberate reversal. The old generator owned a region and left every byte outside it alone, because the prose around the block was somebody's hand-written file. It is not any more: it is embedded, so there is no hand-written half left to protect, and preserving local edits would mean freezing a project's documentation at whatever version first initialized it. The markers stay in the output so a reader can see which half came from the registry.

WHAT THIS MEANS FOR A LOCAL EDIT: it does not survive. Run --check before an upgrade if you have made one; the fix for a sentence that is wrong is a newer kettle, not a patch that the next run silently discards.

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 written, 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 to write into (default: /.claude)
kettle gen scaffold                         # write .claude/ under this project
kettle gen scaffold --out ~/code/x/.claude  # write it somewhere else
kettle gen scaffold --dry-run               # print what would change; write nothing
kettle gen scaffold --check                 # exit 1 if the documents 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 — and writes .claude/: the slash commands an operator invokes, the skills a model loads, and the runner subagent.

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, writes the .claude/ tree, 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.

IT REFUSES TO RUN IN A LINKED WORKTREE, and names the main checkout instead. 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.

--interactive walks a person through the whole thing — the login, the token with the echo turned off, the repository, the .claude/ tree and the AGENTS.md mirror hook. IT REQUIRES A TERMINAL and refuses a standard input that is not one, which is deliberate: every question it asks has a flag beside it, so nothing that is not a person ever needs to answer a prompt.

flag default what it does
--at directory to initialize (default: the working directory)
--dry-run false report what would happen; change nothing
--force-settings false let the hook be merged into an existing settings.json, reformatting it
--interactive false ask, one question at a time; requires a terminal
--login name of a login in the machine-wide file (see kettle auth)
--mirror-hook false register kettle mirror --hook on PreToolUse(Bash)
--no-scaffold false do not write the .claude/ commands, skills and subagent
--repo tracker repository, as owner/name
--scaffold-out where the .claude/ tree goes (default: /.claude)
kettle init                # initialize the current directory
kettle init --interactive  # be walked through it, at a terminal
kettle init --login noodles --repo claude-skills/marketplace  # and point it at a tracker
kettle init --mirror-hook  # register the AGENTS.md mirror on PreToolUse(Bash)
kettle init --at ~/code/x  # initialize somewhere else
kettle init --dry-run      # say what it would do, touch nothing

kettle mirror [<dir>]

keep CLAUDE.md a symlink to AGENTS.md in every directory below here

Two agent harnesses read two different filenames for the same document. A repository that keeps both as real files keeps TWO DOCUMENTS, and they drift — silently, until somebody reads the stale one and believes it. This walks a tree and leaves one arrangement behind everywhere:

AGENTS.md is the real file; CLAUDE.md is a symlink pointing at it.

The link is relative, so a tree that is moved, copied or cloned keeps working. AGENTS.md is the real one because the convention is not one vendor's: a repository that names its documents after a single tool has picked a side it did not need to pick.

NOTHING HERE DELETES CONTENT. Six of the seven states it can find are repaired losslessly — a missing link is created, a reversed layout is swapped round, a duplicate whose bytes match its original is replaced by the link. The seventh, two real files whose contents DIFFER, is reported and left exactly as it was: one of them is somebody's writing and no rule here knows which.

It walks the directory given, or the working directory. node_modules, vendor, venv, pycache and every dot-directory are skipped, because somebody else's tree is somebody else's business.

--hook is the PreToolUse form: it reads the hook payload on standard input, writes any report back as additionalContext, and ALWAYS EXITS 0 — including when it fails. A tool that broke somebody's Bash call because its documentation helper crashed would be worse than no tool. --check is the opposite end: it writes nothing and exits 1 when the tree is not canonical, which is what a pre-commit hook or a make target calls.

kettle init --interactive offers to register the --hook form in .claude/settings.json. It is offered rather than assumed: this is one repository's documentation convention, and a project that does not keep AGENTS.md files wants nothing to do with it.

flag default what it does
--check false write nothing, exit 1 if the tree is not canonical
--hook false PreToolUse form: payload on stdin, report as additionalContext, always exit 0
--quiet false repair without printing what was repaired
kettle mirror           # repair the working directory and everything below it
kettle mirror ~/code/x  # repair somewhere else
kettle mirror --check   # exit 1 if anything is out of place; write nothing
kettle mirror --hook    # the PreToolUse form; reads a payload, always exits 0

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