tea-axi
v0.0.1
Published
Agent-shaped CLI sidecar for Gitea tea — dense TOON output, never prompts
Downloads
21
Readme
tea-axi
An agent-shaped CLI for Gitea. A sidecar to upstream
tea: it reads tea's own config and
credentials, keeps none of its own, and never replaces the tea binary.
Built to the AXI (Agent eXperience Interface) principles, as a
Gitea sibling of gh-axi.
firstmate dependency path: GitHub keeps using gh-axi; Gitea uses
tea-axi. Both stay supported.
- Dense TOON output (~40% fewer tokens than JSON).
- Never prompts. Missing login or ambiguous targets fail immediately with the
exact fix command (e.g.
fix: tea logins default gitea.example.com). - Contextual
help[N]:next steps (axi principle 9). --jsonwhen you want machine JSON instead of TOON.-q/--jq <path>for a single bare field value (firstmate poll shape).- Content-first home view; per-command
--helpfor every group.
Spec sources
| Source | Role |
|--------|------|
| axi.md / kunchenguid/axi | 10 AXI principles (output, empty states, errors, content-first, …) |
| gh-axi | Closest sibling: forge CLI wrap of an official CLI. Command nouns (issue view, not invent-your-own), count lines, truncation footers, auth error shape, empty-list help |
| Local ~/.npm-global install of gh-axi | Runtime reference while building |
Where Gitea genuinely differs from GitHub, divergences are listed below — not silent invention.
Install
Requires Node 20+ and a working tea on PATH (v0.15.1+ recommended).
pnpm install && pnpm build
node dist/bin/tea-axi.js --help
# optional: npm link / install -g from this packageThe binary is tea-axi. It does not shadow, replace, or modify
/opt/homebrew/bin/tea (or any other tea install).
How auth works
tea-axi has no config of its own. It never writes to tea's config either.
| Platform | tea config directory (read-only) |
|----------|----------------------------------|
| macOS | ~/Library/Application Support/tea/ |
| Linux/XDG | $XDG_CONFIG_HOME/tea or ~/.config/tea |
Contents (owned by tea): config.yml, credentials.json.enc, lock file.
Credentials are encrypted via tea's credstore / OS keyring (since tea v0.13.0).
tea-axi does not decrypt them. It shells out to tea (and tea api) so
upstream owns token material. Stale GITEA_AUTH_TOKEN / GITEA_TOKEN env vars
are stripped from the child environment so a bad export cannot 401 over tea's
own login.
Never-prompt gate
Before any command that needs auth, tea-axi reads config.yml and requires:
- At least one login, and
- Either a
default: truelogin or an explicit--login <name>.
If neither is true, it exits non-zero immediately:
error: no default login for gitea.example.com
code: AUTH_REQUIRED
help[2]:
fix: tea logins default gitea.example.com
Or pass --login gitea.example.comChild processes are spawned with execFile (shell: false,
stdio: ['ignore','pipe','pipe']). Stdin is not a TTY, so tea's interactive
canPrompt path cannot block an agent.
Auth / health check
For firstmate bootstrap (the gh auth status role):
tea-axi auth status # compact health record; non-zero + fix line if unusable
tea-axi whoami # same live API check, user-oriented schemaEither works as a health probe. Both call tea api user and fail closed.
Command surface
Mirrored from gh-axi where the domain maps; Gitea-only names stay tea-shaped.
Every group has tea-axi <cmd> --help.
tea-axi # dashboard: login + current repo summary
tea-axi whoami # authenticated user (health probe)
tea-axi auth status # health probe (gh auth status equivalent)
tea-axi login list|show # read-only view of tea logins
tea-axi repo list|show
tea-axi issue list|view|create|comment|close
tea-axi pr list|view|create|merge|review-comment
tea-axi label list|create|delete
tea-axi release list|view|create
tea-axi run list|view # Gitea Actions runs
tea-axi workflow list|view # Gitea Actions workflows
tea-axi secret list|set|delete # Actions secrets (values write-only)
tea-axi variable list|get|set|delete
tea-axi search repos|issues <query>
tea-axi api <endpoint> # escape hatch (wraps tea api)Explicitly not supported on Gitea
These gh-axi verbs have no Gitea equivalent. They are listed in top-level
--help and return NOT_SUPPORTED with a fix line (not silent omission):
| Verb | Why |
|------|-----|
| gist | Gitea has no GitHub Gist equivalent |
| project | Gitea has no GitHub Projects v2 equivalent |
| setup | SessionStart hooks not shipped yet (axi principle 7 secondary path) |
Global flags: -r/--repo owner/name, -l/--login name, --json,
-q/--jq <path> (single bare field), --help, -v/--version.
Bodies: --body / --description / --body-file (gh-axi-compatible).
firstmate call-site coverage
| firstmate need | tea-axi |
|----------------|---------|
| PR merge poll state from URL | tea-axi pr view <gitea-pr-url> --json -q .merged → bare true/false |
| PR merge by number + repo | tea-axi pr merge <n> -r owner/name (v1) |
| Auth bootstrap | tea-axi auth status or tea-axi whoami |
| Discover surface without source | tea-axi --help / tea-axi pr --help / … |
PR URLs
Accept a full Gitea PR URL anywhere a PR number is accepted:
https://<host>/<owner>/<repo>/pulls/<n>Note pulls (Gitea), not GitHub's pull. Host is validated against
configured tea logins — never assumed. Wrong host / GitHub-shaped /pull/ fail
with an explicit fix line.
Merge state (load-bearing — both captain repos squash-merge)
Read merge state only through tea-axi (or raw tea api). Never trust
tea pull <n> -o json / tea -o json for whether a PR is merged.
Established facts on this Gitea instance:
| Source | Squash-merged PR |
|--------|------------------|
| Gitea API /repos/.../pulls/<n> | merged: true |
| Upstream tea pull <n> -o json | merged: null, merge_commit_sha: null |
If an agent or poll uses tea's JSON renderer, firstmate never sees the merge land and work sits "unmerged" forever with nothing visibly broken.
tea-axi pr view always goes through tea api. Prefer the poll shape:
tea-axi pr view <url-or-number> --json -q .merged # bare true | falseSingle-value selector
tea-axi pr view https://gitea.example.com/ryan/tea-axi/pulls/2 --json -q .merged
# truePrints the bare value only (no TOON, no JSON braces). Missing field → non-zero
exit + NOT_FOUND.
Divergences from gh-axi (deliberate)
| Topic | gh-axi | tea-axi | Why |
|-------|--------|---------|-----|
| Repo flag | -R/--repo | -r/--repo | Match upstream tea (-r) so muscle memory transfers from tea, not gh |
| Auth | gh auth login / gh auth status | tea logins add / tea-axi auth status | Different CLI; fix lines must name tea |
| Config path | gh's hosts.yml | macOS Application Support tea/ | tea's real path (not ~/.config/tea on macOS) |
| Detail verb | view primary | view primary; show alias | Aligned with gh-axi; keep show for tea users |
| PR path | /pull/<n> | /pulls/<n> | Gitea URL shape |
| Field select | gh -q via upstream | -q / --jq on tea-axi JSON objects | No full jq engine; path select only |
| Merge styles | GitHub merge/squash/rebase | + rebase-merge | Gitea API supports it |
| Host flag | --hostname / GH_HOST | tea login name (-l) | Multi-instance is tea logins, not GH_HOST |
| gist / project / setup | full surface | explicit NOT_SUPPORTED | No Gitea equivalent (or not shipped) |
Fork vs wrap
v1 wraps tea; it does not fork it. Evidence:
- JSON surface is enough.
tea issues|pulls|repos … -o json+tea apicover list, detail, and mutations. tea apiis a complete escape hatch. Missing commands never block.- Credentials are encrypted. Forking would vendor credstore/keyring code; wrapping reuses it.
- Upstream is 0.x ~monthly and has broken flags before (v0.14.1). Fork pays permanent merge tax.
Cost of wrap: one process spawn per call. Acceptable vs network I/O.
Same architectural choice as gh-axi wrapping gh.
AXI principle checklist
| # | Principle | tea-axi |
|---|-----------|---------|
| 1 | Token-efficient TOON | default stdout via @toon-format/toon |
| 2 | Minimal default schemas | list: number,title,state,author |
| 3 | Content truncation + --full | truncateBody size hint |
| 4 | Pre-computed aggregates | count: lines; truncation note when page full |
| 5 | Definitive empty states | count: 0 + create-oriented help |
| 6 | Structured errors, no prompts, idempotent close | AUTH_REQUIRED fix lines; close no-op |
| 7 | Ambient context / setup | not shipped (setup → NOT_SUPPORTED) |
| 8 | Content first | bare tea-axi → live dashboard |
| 9 | Contextual disclosure | help[N]: after list/view/mutations |
| 10 | Consistent --help | top-level + every command group |
Development / testing
Follows the captain's /ry-testing discipline: red-first for load-bearing
invariants, boundary fakes (FakeTeaRunner) instead of network, offline
default gate, discriminating asserts (never-prompt tests check fix line +
AUTH_REQUIRED + no tea invocation + elapsed < 2s — not a vacuous non-zero exit).
pnpm install # needs committed pnpm-workspace.yaml allowBuilds.esbuild=true
pnpm build
pnpm test # offline, ~seconds — must work from a clean clone
node dist/bin/tea-axi.js whoami # live smoke (needs tea login)
node dist/bin/tea-axi.js pr view https://gitea.example.com/ryan/tea-axi/pulls/2 --json -q .mergedpnpm-workspace.yaml explicitly allows esbuild's postinstall (required by
vitest/vite). A clean clone must not need pnpm approve-builds.
There is no tea-axi update self-update path (package is not on npm). That
command is not advertised in --help.
Test-only env
| Variable | Purpose |
|----------|---------|
| TEA_AXI_TEA_CONFIG_DIR | Point at a fake tea config directory |
License
MIT
