Files
marketplace/plugins/kettle/skills/project/SKILL.md
T
naudachu f18a633185 feat: reach the rest of Gitea with kettle api, and drop tea
The plugin required `tea`, Gitea's own CLI, for everything that is not an
issue: releases, pull requests, milestones, branches, actions, webhooks. That
put a second binary, a second set of logins nothing here could see, and 400
lines documenting somebody else's flags outside anything this repository can
test. One command over the transport that already existed removes all three.

Transport: `post` — the hand-rolled request the SDK cannot express, written for
the dependency endpoint — is generalized to an exported `Do`, and `post` is
three lines on top of it. Same http.Client, so the same RoundTripper files the
body under .kettle/payload/, the same `token …` header authenticates it, and a
non-2xx is the same *APIError. It does not paginate, does not reformat the
answer, and names no domain concept, so the layering test is untouched.

The endpoint rule is `tea api`'s, so an endpoint table written for that tool
still works — with one restriction it did not have: a full URL must be on this
instance. Every request carries the project's token in a header, and a URL on
another host would hand the token to whatever was typed.

Command: `kettle api <endpoint>` in a new `api` group, so the generator writes
plugins/kettle/skills/api/SKILL.md — group, directory and /kettle:api are one
word. No --repo and no --login, for the reason no sync command has them: a
cross-repository address is an address, and another instance is KETTLE_URL.
`-X DELETE` needs `--yes`; a flag typed on purpose is an operator's decision.

Scopes: a token minted for issues carries write:issue and answers 403 on the
first request outside issues, naming no scope. Gitea cannot be asked what a
token may do — its own token listing needs a password — so `auth add --scopes`
records it, `auth list` and `config` show it, and a 403 says which category it
is likely to be. Documentation only; nothing is checked against it.

skills/use — the tea reference, 239 lines of it — becomes skills/api: what to
ask for, which endpoints paginate, and how to write a body. Every mention of
`tea` as a requirement is gone from the manifests, the READMEs, the runner and
the four other skills; what survives is the back-compat with the old plugin,
which is a decision and not a debt.

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

9.7 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 other CLI 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.

--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 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