kairyou/at-zentao
Work ZenTao bugs/tasks end to end: fetch details, confirm understanding, fix, verify, stage with git add, then ask before committing and before writing status back to ZenTao. Supports single items and sequential batches. Use when the user references ZenTao (禅道) bugs or tasks.
npx skills add https://github.com/kairyou/agent-tools --skill at-zentao
Primary config lives under a zentao key in ~/.agent-tools/config.jsonc — or $AGENT_TOOLS_HOME/config.jsonc when AGENT_TOOLS_HOME is set, matching the rest of agent-tools. Every ~/.agent-tools/config.jsonc below means this resolved path:
{
"zentao": {
"url": "http://zentao.example.com",
"account": "...",
"password": "..."
}
}
Resolution order (first match wins):
ZENTAO_URL → zentao.url in ~/.agent-tools/config.jsonc.ZENTAO_ACCOUNT/ZENTAO_PASSWORD → zentao.account/zentao.password in ~/.agent-tools/config.jsonc.ZenTao config comes ONLY from env vars and the global ~/.agent-tools/config.jsonc, never a repository-level file — so an untrusted repo can't redirect the endpoint to capture your credentials.
First-run setup (when config is missing or incomplete):
~/.agent-tools/config.jsonc has no zentao block, insert one INTO the root object (not appended after the closing }) — it is JSONC, so preserve existing keys and comments, and mind the trailing comma:"zentao": { "url": "", "account": "", "password": "" }
zentao.password in the file themselves or set env ZENTAO_PASSWORD, and to say "done" when finished.GET /api.php/v1/user. Report the result before doing any real work.ZenTao tokens expire, so exchange credentials for a fresh token at the start of each session:
POST $ZENTAO_URL/api.php/v1/tokens
Content-Type: application/json
{"account":"...","password":"..."}
The response's token field is used as a Token: <token> header on every subsequent API request. Keep it in memory for the session only; never write it to a file and never echo it in full. If any later call returns 401, exchange for a new token once and retry; if it still fails, stop and report.
If env ZENTAO_TOKEN is set, use it directly and skip the exchange (re-exchange is unavailable then — on 401, tell the user the token expired).
Connectivity check: after obtaining the token, GET $ZENTAO_URL/api.php/v1/user must succeed before any other work. On failure, stop and tell the user to check URL/credentials.
/at-zentao bug <id> — handle a single bug/at-zentao task <id> — handle a single task/at-zentao bugs — list bugs assigned to the configured account; the user picks one or several (multiple = batch mode)/at-zentao tasks — same for tasks/at-zentao export bug <id> (or export task <id>) — export to a self-contained Markdown bundle for handoff; does NOT fix or write back (see Export mode)All requests send the Token: <token> header — it works for both endpoint families below.
My work lists (legacy .json pages; the entry point for picking what to fix):
GET /my-work-bug.json — bugs assigned to the configured accountGET /my-work-task.json — tasks assigned to the configured account{"status":"success","data":"<JSON-encoded string>"} — the data field is a STRING containing JSON (with \uXXXX escapes), so decode it a second time. Bugs are in .bugs[] (fields: id, title, severity, pri, status, project, product), tasks in .tasks[]. The first page usually suffices, but read the pager info inside data for the total — if there are more pages, tell the user (e.g. "showing 20 of 45; say more to load the rest") instead of silently truncating, and fetch further pages only on request.Details and write-back (REST v1):
GET /api.php/v1/bugs/{id} — bug details (title, steps, severity, module)GET /api.php/v1/tasks/{id} — task detailsAttachments / inline images (legacy, same Token header; binary — save with curl -o, never read as text):
GET /file-read-{fileID}.{ext} — view/inline. Observed on 18.12: bug screenshots are embedded in the steps HTML as <img src=".../file-read-{id}.png"> while the files list is empty — so scan steps, don't rely on files.GET /file-download-{fileID}.html — download an attachment (when files is populated).Resolving a bug (the REST PUT /bugs/{id} does NOT perform a real resolve — do not use it for status changes; use the legacy action, which mirrors the web form and triggers the full workflow):
POST /bug-resolve-{id}.json with a form body (Content-Type: application/x-www-form-urlencoded, same Token header):resolution=fixed&resolvedBuild=trunk&responsibleBy=<account>&comment=<...>
where responsibleBy is the authenticated account (from config) — this instance requires it.
Encoding: NEVER pass non-ASCII (Chinese) text as a command-line argument — Windows curl.exe converts argv through the ANSI codepage and mangles it regardless of terminal. Feed such text via stdin: --data-urlencode "comment@-" plus a herestring/pipe/heredoc. ASCII fields may go inline in -d; all data flags merge into one form body. Verified one-liner:
curl -s -X POST -H "Token: $TOKEN" -d "resolution=fixed&resolvedBuild=trunk&responsibleBy=<account>" --data-urlencode "comment@-" "$ZENTAO_URL/bug-resolve-{id}.json" <<< $'<comment line 1>\n<line 2>'
POST /action-comment-bug-{id}.json, comment fed via stdin the same way (--data-urlencode "comment@-").{"status":"success","data":"..."} even on failure — the real outcome is inside data (result: "fail" + per-field message). Surface those validation messages to the user verbatim; if a required field is missing, discover the form's fields and defaults via GET /bug-resolve-{id}.json, fill it, and re-confirm with the user before retrying.Finishing a task:
GET /task-finish-{id}.json — the decoded data.task object holds current values (realStarted, consumed, openedBy, ...).POST /task-finish-{id}.json (same Token header and stdin-encoding rules as bug resolve) with:currentConsumed — hours spent; a value only the user knows. ALWAYS ask the user for it; never invent or estimate it on their behalf.consumed — TOTAL consumed, must exceed the previous total: compute as data.task.consumed + currentConsumed.realStarted — required; reuse data.task.realStarted if set, otherwise use a sensible date confirmed with the user (format YYYY-MM-DD HH:MM:SS; a space, so either --data-urlencode it or write the space as + inside -d).finishedDate — now, same format.POST /action-comment-task-{id}.json.If a call fails, report the actual HTTP status and response body to the user instead of failing silently.
Do NOT browse via products/projects — always start from the my-work lists or an explicit id the user gives.
steps HTML (<img src=".../file-read-{id}.png">; the files list is often empty) — download each with the Token header to a temp file and Read it now, so the screenshot informs the fix. If you cannot see images, try an image-inspection tool with the downloaded file path — e.g. inspect_image (MCP server agent-tools-vision) — never the token-gated ZenTao URL; if none is available, work from the text details and note that screenshots were skipped.git diff --staged --name-only; if the index already holds unrelated changes, STOP and ask the user (commit those separately / unstage them / proceed anyway) so the bug#<id> commit isn't polluted. Then stage only the files changed for THIS item, listing them explicitly (never git add -A).type(scope):, add the ZenTao link token — bug#<id> or task#<id> — e.g. fix(<scope>): bug#30887 <desc> (scope optional). Rewrite the description from the diff rather than copying the title, keeping the title's domain terms.fixed resolution or a commit hash — at most a comment with the status left unchanged, since a fixed write-back must reference a real commit:fixed 已解决 (default after a code fix), notrepro 无法重现, duplicate 重复Bug (needs the duplicate bug id), bydesign 设计如此, external 外部原因, postponed 延期处理, willnotfix 不予解决. If investigation showed the bug needs no code fix, propose the fitting non-fixed resolution instead.currentConsumed) and submits once. For multi-day tasks or teams that log per-day workhours, do NOT attempt finish via API; post the comment and point the user to the web UI's 记录工时/完成 forms, which handle per-day entries properly.export bug <id> / export task <id>)Produce a self-contained handoff for someone (or another agent) WITHOUT ZenTao access. Read-only: do NOT fix, commit, or write status back.
steps converted from HTML to Markdown, and comments/history when useful. Strip anything auth-bound — never include the token, credentials, or login-gated URLs.<dest>/zentao-<bug|task>-<id>.md.<dest>/zentao-<bug|task>-<id>/ holding that .md plus the downloaded images; rewrite each <img src=".../file-read-..."> to a relative ! link. Keep images as real files (never base64) so another agent can Read/see them and every viewer renders them; to hand the folder over as one item, zip it.git add/commit would sweep up the still-staged changes (and same-file edits can't be untangled later). Instead ask: commit now / stash this item's changes and continue / stop the batch here.bug#<id> / task#<id> token right after type(scope): — ZenTao's repo integration parses it to auto-link the commit, and it keeps IDs aligned in git log --oneline.zentao block of the user's global config (the password there is filled in by the user, not by you).Take kairyou/at-zentao from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.