Compare commits
7 Commits
bb4ad963ee
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 47a95ba6cb | |||
| a97c1a6638 | |||
| b8b4cb3eae | |||
| dc8706e809 | |||
| bac45028bf | |||
| d4aa0a9038 | |||
| b3db734cd8 |
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"name": "tea",
|
||||||
|
"owner": {
|
||||||
|
"name": "naudachu"
|
||||||
|
},
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "tea",
|
||||||
|
"source": "./",
|
||||||
|
"description": "Gitea CLI (tea) reference plus a mandatory-login guard. Ships /tea:login, /tea:use, and a PreToolUse hook that blocks any tea command that would touch Gitea without --login."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"name": "tea",
|
||||||
|
"description": "Gitea CLI (tea) reference plus a mandatory-login guard. Ships /tea:login (pin a login) and /tea:use (command reference), and a PreToolUse hook that blocks any tea command that would touch Gitea without --login.",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "naudachu"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"keywords": ["gitea", "tea", "cli", "git", "login-guard"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
.DS_Store
|
||||||
|
.docs/
|
||||||
|
.claude/
|
||||||
|
tmp/
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
# tea — Claude Code plugin for the Gitea CLI
|
||||||
|
|
||||||
|
A Claude Code plugin that gives Claude a reference for the `tea` CLI and enforces a hard rule: every `tea` command runs under the login **the operator chose**, never one Claude picked.
|
||||||
|
|
||||||
|
## What it ships
|
||||||
|
|
||||||
|
| Piece | What it does |
|
||||||
|
|---|---|
|
||||||
|
| `/tea:login` skill | Prompts you to pick a Gitea login and pins it to the project |
|
||||||
|
| `/tea:use` skill | Tea CLI reference — loads command docs on demand |
|
||||||
|
| `tea-guard` hook | PreToolUse hook that blocks or rewrites every `tea` invocation |
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- **Claude Code** — CLI, desktop app, or IDE extension
|
||||||
|
- **Python 3** — required by the `tea-guard` hook (`python3` must be on `$PATH`)
|
||||||
|
- **`tea`** — Gitea's official CLI. Install with `brew install tea` (macOS) or from [gitea.com/gitea/tea/releases](https://gitea.com/gitea/tea/releases)
|
||||||
|
- At least one login configured: `tea logins add` (interactive — run it in a terminal, not via Claude)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
This is a Claude Code plugin — install it through the plugin marketplace, not by hand-editing `settings.json`.
|
||||||
|
|
||||||
|
1. Register this repo as a marketplace:
|
||||||
|
|
||||||
|
```
|
||||||
|
/plugin marketplace add https://git.noodles.cam/claude-skills/tea.git
|
||||||
|
```
|
||||||
|
|
||||||
|
Already have a local clone? Point at the directory instead:
|
||||||
|
|
||||||
|
```
|
||||||
|
/plugin marketplace add /path/to/tea
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Install the plugin:
|
||||||
|
|
||||||
|
```
|
||||||
|
/plugin install tea@tea
|
||||||
|
```
|
||||||
|
|
||||||
|
The skills (`/tea:login`, `/tea:use`) and the `tea-guard` hook load immediately. Use `/plugin` to enable, disable, or update it later.
|
||||||
|
|
||||||
|
> The marketplace registration is written to `extraKnownMarketplaces` and the plugin to `enabledPlugins` in your settings automatically — you don't edit those by hand. There is **no** top-level `"plugins"` settings key; if you've added one from older instructions, remove it.
|
||||||
|
|
||||||
|
## First use
|
||||||
|
|
||||||
|
Run `/tea:login` once per project. Claude will list your available Gitea logins and ask you to pick one. The choice is written to `.claude/settings.local.json` and takes effect immediately — no restart needed.
|
||||||
|
|
||||||
|
```
|
||||||
|
/tea:login
|
||||||
|
```
|
||||||
|
|
||||||
|
After that, use `/tea:use` to look up commands, or just ask Claude to do something with Gitea and it will load the reference automatically.
|
||||||
|
|
||||||
|
## How the login guard works
|
||||||
|
|
||||||
|
Every `tea` invocation Claude writes must carry the literal placeholder `--login "$GITEA_LOGIN"`. The `tea-guard` hook intercepts the Bash call before it runs, looks up the pinned login from `.claude/settings.local.json`, and rewrites the command to use it.
|
||||||
|
|
||||||
|
Claude is **blocked** from:
|
||||||
|
- running `tea` without `--login` at all
|
||||||
|
- naming a login itself (e.g. `--login myaccount`)
|
||||||
|
- using any variable other than `$GITEA_LOGIN`
|
||||||
|
|
||||||
|
This prevents silent fallback to the machine's default login (often a personal account) when working in a project that belongs to a different identity.
|
||||||
|
|
||||||
|
`tea logins list` and `tea --version / --help` are exempt — they don't touch Gitea data.
|
||||||
|
|
||||||
|
## Project layout
|
||||||
|
|
||||||
|
```
|
||||||
|
.claude-plugin/
|
||||||
|
plugin.json plugin manifest
|
||||||
|
marketplace.json marketplace catalog (makes `/plugin install` work)
|
||||||
|
hooks/
|
||||||
|
hooks.json registers the PreToolUse hook
|
||||||
|
tea-guard.sh the guard (Python 3, no deps)
|
||||||
|
skills/
|
||||||
|
login/SKILL.md /tea:login skill
|
||||||
|
use/SKILL.md /tea:use skill
|
||||||
|
use/references/tea/ tea CLI reference docs
|
||||||
|
```
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
---
|
|
||||||
name: gitea-docs
|
|
||||||
description: Reference docs for the `tea` CLI — Gitea's command-line client. Load when the user asks about Gitea repos, issues, pulls, releases, actions, or other Gitea entities, to look up the right `tea` command and flags.
|
|
||||||
---
|
|
||||||
|
|
||||||
# gitea-docs
|
|
||||||
|
|
||||||
Reference material for the `tea` CLI (Gitea's official command-line client). Use these docs to look up commands, flags, filters, and output fields before running `tea` via Bash.
|
|
||||||
|
|
||||||
## How to use
|
|
||||||
|
|
||||||
1. Identify the entity in the user request: issues, pulls, labels, milestones, releases, times, repos, branches, actions, webhooks, comments, notifications, etc.
|
|
||||||
2. Find the matching command in the index below.
|
|
||||||
3. Run it via Bash, e.g. `tea issues list --repo owner/repo --state open`.
|
|
||||||
|
|
||||||
`tea` auto-detects owner/repo/login from `$PWD` when inside a git repo; otherwise pass `--repo owner/repo` (or `-r`) explicitly. Config lives in `$XDG_CONFIG_HOME/tea`.
|
|
||||||
|
|
||||||
## Index
|
|
||||||
|
|
||||||
- [tea CLI overview](references/tea/index.md) — global flags, common options, output formats
|
|
||||||
- [ENTITIES](references/tea/entities.md) — issues, pulls, labels, milestones, releases, times, repos, branches, actions, webhooks, comment
|
|
||||||
- [HELPERS](references/tea/helpers.md) — open, notifications, clone, api
|
|
||||||
- [MISC](references/tea/misc.md) — whoami, admin
|
|
||||||
- [SETUP](references/tea/setup.md) — logins, logout, ssh-keys
|
|
||||||
|
|
||||||
## Tips
|
|
||||||
|
|
||||||
- Pass `-o json` for structured output when parsing programmatically.
|
|
||||||
- Use `--fields, -f` to narrow columns.
|
|
||||||
- Pagination: `--page, -p <n>` and `--limit, --lm <n>` (defaults 1 / 30).
|
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"hooks": {
|
||||||
|
"PreToolUse": [
|
||||||
|
{
|
||||||
|
"matcher": "Bash",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/tea-guard.sh"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+133
@@ -0,0 +1,133 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
tea-guard — PreToolUse(Bash) hook for the `tea` plugin.
|
||||||
|
|
||||||
|
Enforces, deterministically, the one rule prose cannot: every `tea` command
|
||||||
|
that touches Gitea runs under the login the OPERATOR pinned — never one Claude
|
||||||
|
chose. It does this by *resolving and rewriting* the command rather than just
|
||||||
|
checking it:
|
||||||
|
|
||||||
|
Claude must write: tea ... --login "$GITEA_LOGIN" ...
|
||||||
|
The guard rewrites: tea ... --login <operator-pinned-login> ...
|
||||||
|
|
||||||
|
The pin is read from .claude/settings.local.json (env.GITEA_LOGIN) at call
|
||||||
|
time — from the FILE, not the environment — so a freshly pinned login works in
|
||||||
|
the same session with no restart.
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
- not a `tea` command ............................. allow (passthrough)
|
||||||
|
- tea logins list/ls, tea --version/--help ........ allow (no identity used)
|
||||||
|
- no --login / -l ................................. BLOCK
|
||||||
|
- --login <literal> or --login "$OTHER_VAR" ....... BLOCK (Claude may not pick)
|
||||||
|
- --login "$GITEA_LOGIN", pin found ............... REWRITE to the pin, allow
|
||||||
|
- --login "$GITEA_LOGIN", no pin .................. BLOCK (run /tea:login)
|
||||||
|
|
||||||
|
Output protocol: exit 0 + JSON {hookSpecificOutput:{updatedInput,...}} to
|
||||||
|
rewrite; exit 2 + stderr to block.
|
||||||
|
"""
|
||||||
|
import sys, os, re, json, shlex
|
||||||
|
|
||||||
|
PLACEHOLDERS = {"$GITEA_LOGIN", "${GITEA_LOGIN}"}
|
||||||
|
|
||||||
|
|
||||||
|
def block(msg):
|
||||||
|
sys.stderr.write("tea-guard: BLOCKED — " + msg + "\n")
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
|
||||||
|
def allow_passthrough():
|
||||||
|
# exit 0 with no stdout → tool runs unchanged
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
def rewrite(tool_input, new_cmd, note):
|
||||||
|
updated = dict(tool_input)
|
||||||
|
updated["command"] = new_cmd
|
||||||
|
print(json.dumps({
|
||||||
|
"hookSpecificOutput": {
|
||||||
|
"hookEventName": "PreToolUse",
|
||||||
|
"updatedInput": updated,
|
||||||
|
"additionalContext": note,
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
def find_pin(start_dir):
|
||||||
|
"""Walk up from start_dir; return (login, path) from the first
|
||||||
|
.claude/settings.local.json that carries a non-empty env.GITEA_LOGIN."""
|
||||||
|
try:
|
||||||
|
d = os.path.abspath(start_dir or ".")
|
||||||
|
except Exception:
|
||||||
|
return None, None
|
||||||
|
while True:
|
||||||
|
p = os.path.join(d, ".claude", "settings.local.json")
|
||||||
|
if os.path.isfile(p):
|
||||||
|
try:
|
||||||
|
with open(p) as f:
|
||||||
|
data = json.load(f)
|
||||||
|
v = (data.get("env") or {}).get("GITEA_LOGIN")
|
||||||
|
if isinstance(v, str) and v.strip():
|
||||||
|
return v.strip(), p
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
parent = os.path.dirname(d)
|
||||||
|
if parent == d:
|
||||||
|
return None, None
|
||||||
|
d = parent
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
try:
|
||||||
|
payload = json.load(sys.stdin)
|
||||||
|
except Exception:
|
||||||
|
# Can't parse the hook payload — fail open for non-tea safety, but we
|
||||||
|
# can't even read the command, so don't block arbitrary Bash.
|
||||||
|
allow_passthrough()
|
||||||
|
|
||||||
|
tool_input = payload.get("tool_input") or {}
|
||||||
|
cmd = tool_input.get("command") or ""
|
||||||
|
|
||||||
|
# Not a `tea` invocation → not our concern.
|
||||||
|
if not re.search(r'(^|[;&|(]|\s)tea(\s|$)', cmd):
|
||||||
|
allow_passthrough()
|
||||||
|
|
||||||
|
# Whitelist: login enumeration + meta. No identity is used; /tea:login
|
||||||
|
# needs `tea logins list` while no pin exists yet.
|
||||||
|
if re.search(r'tea\s+(logins\s+(list|ls)|--version|-v|--help|help)(\s|$)', cmd):
|
||||||
|
allow_passthrough()
|
||||||
|
|
||||||
|
# Locate --login / -l and its value (logins never contain spaces).
|
||||||
|
m = re.search(r'(--login|(?<![\w-])-l)(\s+|=)(\S+)', cmd)
|
||||||
|
if not m:
|
||||||
|
block('every `tea` command must include --login "$GITEA_LOGIN" '
|
||||||
|
'(the guard substitutes the operator-pinned login). '
|
||||||
|
'Run /tea:login if no login is pinned.')
|
||||||
|
|
||||||
|
raw_val = m.group(3)
|
||||||
|
inner = raw_val
|
||||||
|
for q in ('"', "'"):
|
||||||
|
if len(inner) >= 2 and inner[0] == q and inner[-1] == q:
|
||||||
|
inner = inner[1:-1]
|
||||||
|
break
|
||||||
|
|
||||||
|
if inner not in PLACEHOLDERS:
|
||||||
|
block('do not name the login yourself (got `%s`). Write exactly '
|
||||||
|
'--login "$GITEA_LOGIN"; the guard replaces it with the login '
|
||||||
|
'the operator pinned via /tea:login. This prevents acting under '
|
||||||
|
'the wrong identity.' % raw_val)
|
||||||
|
|
||||||
|
start = os.environ.get("CLAUDE_PROJECT_DIR") or payload.get("cwd") or os.getcwd()
|
||||||
|
pin, src = find_pin(start)
|
||||||
|
if not pin:
|
||||||
|
block('no login is pinned. Run /tea:login to choose one (writes '
|
||||||
|
'.claude/settings.local.json env.GITEA_LOGIN). The guard reads '
|
||||||
|
'the file at call time, so it takes effect with no restart.')
|
||||||
|
|
||||||
|
new_cmd = cmd[:m.start(3)] + shlex.quote(pin) + cmd[m.end(3):]
|
||||||
|
rewrite(tool_input, new_cmd,
|
||||||
|
'tea-guard: resolved --login -> %s (pinned in %s)' % (pin, src))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
---
|
||||||
|
name: login
|
||||||
|
description: Pin the Gitea login used by the tea CLI in this project. Run when the tea-guard hook reports no login is pinned, or when the user types /tea:login. Enumerates available logins, makes the OPERATOR pick one, and persists it to .claude/settings.local.json. The pin takes effect immediately — no restart.
|
||||||
|
---
|
||||||
|
|
||||||
|
# /tea:login — pin the project Gitea login
|
||||||
|
|
||||||
|
Goal: have the **operator** select exactly one `tea` login for this project and
|
||||||
|
persist it to `.claude/settings.local.json` under `env.GITEA_LOGIN`. The
|
||||||
|
`tea-guard` hook reads this file at call time and rewrites every
|
||||||
|
`--login "$GITEA_LOGIN"` to the pinned value, so the choice takes effect
|
||||||
|
**immediately, with no session restart**.
|
||||||
|
|
||||||
|
## The one hard rule: the operator chooses, never you
|
||||||
|
|
||||||
|
Picking the wrong identity is the exact failure this command exists to prevent.
|
||||||
|
So:
|
||||||
|
|
||||||
|
- **ALWAYS** present the choice with `AskUserQuestion` and let the operator
|
||||||
|
pick — even if memory, context, the repo URL, or a previous session suggests
|
||||||
|
a "likely" login. Do **not** auto-select from memory or infer it. A wrong
|
||||||
|
guess writes under the wrong account.
|
||||||
|
- The only exception: exactly **one** login exists on the machine — then
|
||||||
|
propose it and still confirm before writing.
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. Enumerate logins (allowed by the guard even with no pin):
|
||||||
|
`tea logins list -o json`
|
||||||
|
2. **No logins:** stop and ask the operator to run `tea logins add` themselves
|
||||||
|
— it is interactive (prompts for URL/token). Do not run it for them.
|
||||||
|
3. **One login:** propose it; confirm before writing.
|
||||||
|
4. **Several logins:** `AskUserQuestion` with each login's `name`, `user`, and
|
||||||
|
`url` so the operator's choice is unambiguous. Never decide for them.
|
||||||
|
5. Merge the chosen name into `.claude/settings.local.json` under `env`
|
||||||
|
(do not clobber other keys):
|
||||||
|
```json
|
||||||
|
{ "env": { "GITEA_LOGIN": "<chosen-name>" } }
|
||||||
|
```
|
||||||
|
6. Done — it is live. The guard resolves the pin from the file on the next
|
||||||
|
`tea` call; no restart needed. Tell the operator which login is now pinned.
|
||||||
|
|
||||||
|
## Identity-safety rules
|
||||||
|
|
||||||
|
- NEVER run commands that mutate logins or global login state:
|
||||||
|
`tea logins add/edit/delete/default`, `tea logout`. Read-only
|
||||||
|
`tea logins list` is the only allowed login command.
|
||||||
|
- If a `tea` call fails with a permission/scope error, report it. Do NOT try to
|
||||||
|
fix it by switching to, or editing, a different login.
|
||||||
|
- If you ever see `no gitea login detected, falling back to login '...'`, treat
|
||||||
|
it as a hard failure: stop, do not act on the result, surface it.
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
---
|
||||||
|
name: use
|
||||||
|
description: Reference docs for the `tea` CLI — Gitea's command-line client. Load when the user asks about Gitea repos, issues, pulls, releases, actions, or other Gitea entities, to look up the right `tea` command and flags. Always write the login as the literal placeholder --login "$GITEA_LOGIN" — the tea-guard hook substitutes the operator-pinned login; set it with /tea:login.
|
||||||
|
---
|
||||||
|
|
||||||
|
# /tea:use — tea CLI reference
|
||||||
|
|
||||||
|
Reference material for the `tea` CLI (Gitea's official command-line client).
|
||||||
|
Use these docs to look up commands, flags, filters, and output fields before
|
||||||
|
running `tea` via Bash.
|
||||||
|
|
||||||
|
## Login: always write the placeholder, never a name (enforced)
|
||||||
|
|
||||||
|
Every `tea` invocation that touches Gitea MUST carry the login as the **literal
|
||||||
|
placeholder** `--login "$GITEA_LOGIN"` (or `-l "$GITEA_LOGIN"`). Do **not**
|
||||||
|
substitute an actual login name yourself.
|
||||||
|
|
||||||
|
The **`tea-guard`** PreToolUse hook enforces this and resolves it:
|
||||||
|
|
||||||
|
- no `--login` → blocked.
|
||||||
|
- `--login "$GITEA_LOGIN"` → the hook reads the operator's pinned login from
|
||||||
|
`.claude/settings.local.json` (`env.GITEA_LOGIN`) **at call time** and
|
||||||
|
rewrites the command to use that literal before it runs.
|
||||||
|
- `--login <some-name>` or any other variable → blocked. You may not choose the
|
||||||
|
login; only the operator does (via `/tea:login`).
|
||||||
|
- no login pinned → blocked with a pointer to run `/tea:login`.
|
||||||
|
|
||||||
|
Why: without an explicit login `tea` silently falls back to the machine's
|
||||||
|
default (possibly the user's personal account), and a login *you* pick may be
|
||||||
|
the wrong identity. Pinning is the operator's decision; the hook guarantees it.
|
||||||
|
The pin takes effect immediately — no restart. Only `tea logins list` and
|
||||||
|
`tea --version/--help` are exempt from the guard.
|
||||||
|
|
||||||
|
## How to use
|
||||||
|
|
||||||
|
1. Identify the entity in the request: issues, pulls, labels, milestones,
|
||||||
|
releases, times, repos, branches, actions, webhooks, comments,
|
||||||
|
notifications, etc.
|
||||||
|
2. Find the matching command in the index below.
|
||||||
|
3. Run it via Bash with the placeholder login, e.g.
|
||||||
|
`tea issues list --login "$GITEA_LOGIN" --repo owner/repo --state open`.
|
||||||
|
(The hook rewrites `"$GITEA_LOGIN"` to the operator-pinned login.)
|
||||||
|
|
||||||
|
`tea` auto-detects owner/repo from `$PWD` inside a git repo; otherwise pass
|
||||||
|
`--repo owner/repo` (or `-r`). Login is **not** auto-detected — it is pinned
|
||||||
|
per-project by the operator (see `/tea:login`) and injected by the guard.
|
||||||
|
Config lives in `$XDG_CONFIG_HOME/tea`.
|
||||||
|
|
||||||
|
## Index
|
||||||
|
|
||||||
|
- [tea CLI overview](references/tea/index.md) — global flags, common options, output formats
|
||||||
|
- [ENTITIES](references/tea/entities.md) — issues, pulls, labels, milestones, releases, times, repos, branches, actions, webhooks, comment
|
||||||
|
- [HELPERS](references/tea/helpers.md) — open, notifications, clone, api
|
||||||
|
- [MISC](references/tea/misc.md) — whoami, admin
|
||||||
|
- [SETUP](references/tea/setup.md) — logins, logout, ssh-keys
|
||||||
|
|
||||||
|
## Rich payloads — write to `$PWD/tmp/` first, then `tea api`
|
||||||
|
|
||||||
|
Entity subcommands (`tea comment`, `tea issues create`, `tea pulls create`, …)
|
||||||
|
are built for humans at a TTY. With a large or formatted body they can hang
|
||||||
|
silently — an empty-looking positional arg triggers `$EDITOR` fallback, or a
|
||||||
|
scope/confirm prompt waits on a TTY that doesn't exist. The harness eventually
|
||||||
|
kills the process (e.g. exit 144 = 128 + SIGURG on macOS).
|
||||||
|
|
||||||
|
**Rule:** for any non-trivial body (multi-line, or containing markdown / code
|
||||||
|
fences / backticks / pipes / tables), bypass entity commands. Save the full
|
||||||
|
request payload to `$PWD/tmp/` first, then POST via `tea api`.
|
||||||
|
|
||||||
|
### Procedure
|
||||||
|
|
||||||
|
1. Ensure the target dir exists: `mkdir -p tmp/{kind}` where `{kind}` is
|
||||||
|
`comment`, `issue`, `pull`, `release`, etc.
|
||||||
|
2. Write the **complete request body as JSON** to `$PWD/tmp/{kind}/<slug>.json`.
|
||||||
|
One file = one request. Use a quoted heredoc to avoid shell expansion:
|
||||||
|
```bash
|
||||||
|
mkdir -p tmp/comment
|
||||||
|
cat > tmp/comment/issue-60.json <<'EOF'
|
||||||
|
{"body": "## Heading\n\nMulti-line markdown with `code`, | tables |, and ```fences```."}
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
Newlines inside the body must be encoded as `\n` in the JSON string. If
|
||||||
|
composing programmatically, pipe through
|
||||||
|
`jq -Rs '{body: .}' < body.md > tmp/comment/issue-60.json`.
|
||||||
|
3. POST with `tea api`, passing the file with `-d @<path>`:
|
||||||
|
```bash
|
||||||
|
tea api --login "$GITEA_LOGIN" \
|
||||||
|
-X POST -d @tmp/comment/issue-60.json \
|
||||||
|
repos/{owner}/{repo}/issues/60/comments
|
||||||
|
```
|
||||||
|
4. Keep the file. `tmp/` should be gitignored; the saved payload is useful for
|
||||||
|
retries, edits (`PATCH`), and debugging failed posts.
|
||||||
|
|
||||||
|
### Common endpoints
|
||||||
|
|
||||||
|
| Action | Method + endpoint |
|
||||||
|
|---|---|
|
||||||
|
| Comment on issue/PR | `POST repos/{owner}/{repo}/issues/{n}/comments` |
|
||||||
|
| Edit comment | `PATCH repos/{owner}/{repo}/issues/comments/{id}` |
|
||||||
|
| Create issue | `POST repos/{owner}/{repo}/issues` |
|
||||||
|
| Edit issue/PR body or title | `PATCH repos/{owner}/{repo}/issues/{n}` |
|
||||||
|
| Create PR | `POST repos/{owner}/{repo}/pulls` |
|
||||||
|
| Create release | `POST repos/{owner}/{repo}/releases` |
|
||||||
|
|
||||||
|
Short single-line bodies (e.g. `tea comment 42 "lgtm" --login "$GITEA_LOGIN"`)
|
||||||
|
are still fine via entity commands. Always the placeholder, never a login name.
|
||||||
|
|
||||||
|
## Tips
|
||||||
|
|
||||||
|
- Pass `-o json` for structured output when parsing programmatically.
|
||||||
|
- Use `--fields, -f` to narrow columns.
|
||||||
|
- Pagination: `--page, -p <n>` and `--limit, --lm <n>` (defaults 1 / 30).
|
||||||
|
- If a `tea` command is blocked by `tea-guard`: either you forgot
|
||||||
|
`--login "$GITEA_LOGIN"`, you wrote a literal login name instead of the
|
||||||
|
placeholder (not allowed — let the guard substitute), or no login is pinned
|
||||||
|
(run `/tea:login`).
|
||||||
Reference in New Issue
Block a user