--- name: auth description: Give `kettle` a Gitea credential and choose which login this project runs under — `kettle auth list/add/remove` manages the machine-wide token file, `kettle init --login ` pins one of those names into `.kettle/config.yaml`. Load when a sync command reports no login, a 401, or an unknown login name, or when the user asks to switch the account this project's issues are pushed under. The OPERATOR picks the login; you never type a token. --- # /kettle:auth — the credential a project runs under Two files, and the split is the whole design. | where | what is in it | who writes it | |---|---|---| | `~/.config/kettle/logins.yaml` | the tokens, one file per machine, mode 0600, outside every working tree | `kettle auth add` | | `/.kettle/config.yaml` | the **name** of one of those logins, and the tracker repo | `kettle init --login … --repo …` | A name is worth nothing on its own, which is what makes it safe to keep in a file inside a repository. A token in a working tree ends up in a commit eventually, and a secret that has ever been pushed has to be rotated. `$KETTLE_CONFIG_HOME` or `$XDG_CONFIG_HOME` move the machine file; `KETTLE_LOGIN`, `KETTLE_URL` and `KETTLE_TOKEN` override it outright, which is how CI runs with no token on disk. **There is no login pinned in `.claude/settings.local.json` any more, and no hook that rewrites a `--login` argument.** That mechanism is gone with the Python scripts; nothing here reads Claude's settings. If you find a `GITEA_LOGIN` in a settings file, it is dead weight from the old plugin. ## The one hard rule: the operator chooses, and holds the token - **Never pick a login.** Not from memory, not from the repo URL, not from a previous session. Present the choice with `AskUserQuestion` — name, url and user out of `kettle auth list` — and let the operator answer. Exactly one login on the machine is the only case where you may propose, and you still confirm. - **Never type, echo, paste or read a token.** `kettle auth add` takes it on stdin precisely so it does not land in shell history; a token that goes through a model's context is a token in a transcript. Adding a login is the operator's own terminal, not a Bash call you make for them. ## Steps 1. See what this machine holds. It never prints a token, and there is no flag to make it: ```bash kettle auth list ``` 2. **Nothing there** — stop and hand the operator the command to run themselves: ```bash kettle auth add --name noodles --url https://git.example.com --user naudachu < token.txt pass show gitea/token | kettle auth add --name noodles --url https://git.example.com ``` `--user` is documentation only. `kettle auth remove ` forgets one. 3. **Pin the choice into the project.** Ask `kettle config` first and only proceed if it answers with a project — `kettle init` in a directory that is not one would *create* a project there, which is the one statement that is never yours to make (`/kettle:init`): ```bash kettle config kettle init --login noodles ``` `init` on an initialized project prints `already initialized — nothing to do` and rewrites only the settings it was given, so the repo pinned earlier stays. 4. Confirm with `kettle config`. Tell the operator which login is pinned and which file it went in. It is live immediately — nothing caches it, no restart. ``` login noodles url https://git.example.com token (set) repo claude-skills/marketplace ``` ## When it goes wrong | what you see | what it means | |---|---| | `no login "X" in …/logins.yaml — known: …` | the project pins a name this machine does not hold. Either add it (step 2) or pin one that is there | | `no .kettle/ found — searched up from …` | not a project. `/kettle:init`, and it is the operator's to run | | `401` / `403` from a sync command | report it verbatim. Do **not** try another login, and do not edit or remove one to route around it — that is somebody's identity, not a setting | | `token none` in `kettle config` | a name is pinned but no credential answers to it | ## Scopes: what the token is allowed to do Gitea mints a token with scopes, spelled `:`. A token made for issues carries `write:issue` — and that is enough for everything `/kettle:issue` and `/kettle:sync` do, and **not** enough for anything `/kettle:api` reaches: releases, pull requests, branches, tags and actions all sit under `repository`. | doing | needs | |---|---| | pull, push, comment, close, evict | `write:issue` | | `kettle labels` | `write:issue` | | `kettle api` on releases, PRs, tags, branches, actions | `write:repository` too | | reading any of those without writing | the `read:` half is enough | `kettle auth add --scopes write:issue,write:repository` writes that down beside the login. **It is a note and nothing else** — 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 rather than a token, so a token cannot be asked what it may do. `kettle auth list` and `kettle config` show what was recorded; `(not recorded)` means nobody wrote it down, never "none". A **403** from a sync command or from `kettle api` is usually this and says so. Minting a new token is the operator's job in the web UI — the same flow as step 2 above, with both scopes ticked this time. Never remove or re-point a login to route around a 403. **No `kettle` on PATH?** `command not found: kettle` is the whole story. 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`. The full flag table for `auth`, `config` and `init` is the generated block in `/kettle:project`.