
# CLI

`acahti` is a thin client for `<instance>/acahti/v1`, the same catalog MCP uses. IDE agents (Codex, Cursor, Claude Code) keep [MCP](/docs/mcp/) and do not need this package. Shell agents, pipeline jobs, and people at a terminal use the CLI.

## Install

The package is private on GitHub Packages (`npm.pkg.github.com`), not on registry.npmjs.org. Authenticate npm to the `acahti-ai` organization, then install:

```bash
echo '@acahti-ai:registry=https://npm.pkg.github.com' >> ~/.npmrc
# GitHub PAT with read:packages for acahti-ai
echo '//npm.pkg.github.com/:_authToken=GITHUB_TOKEN' >> ~/.npmrc
npm install -g @acahti-ai/acahti
```

The package carries binaries for macOS and Linux on amd64 and arm64. `acahti version` prints the release. The CLI talks to any instance through the `/acahti/v1` API and is released on its own schedule (`cli-vX.Y.Z` tags). Each member supplies their own GitHub credentials; do not commit or share tokens.

## Configure

Two values: the instance and an access token.

```bash
export ACAHTI_ROOT_URL=https://acahti.example.com
export ACAHTI_TOKEN=…
acahti whoami
```

A person creates the token from the avatar menu → Access tokens; it is shown once, and deleting it revokes it. Pipeline jobs already have both variables. The CLI sends the token as `Authorization: Bearer`, never the account password.

To store them instead of exporting:

```bash
acahti auth login --hostname https://acahti.example.com --with-token < token.txt
```

This checks the token with `GET /me` and saves both to `~/.config/acahti/credentials.json` (mode `0600`). `acahti auth login https://acahti.example.com` prints setup guidance without opening a browser. In Benchyard, set ACAHTI_ROOT_URL and ACAHTI_TOKEN in Secrets and use them directly; do not copy injected placeholders into a credential file. `acahti auth status` shows the host and login; `acahti auth logout` deletes the file.

Environment variables win over stored credentials. Without `ACAHTI_ROOT_URL` or a stored host, the CLI uses the HTTPS URL of the git remote named `acahti`.

## Commands

| Command | REST |
|---|---|
| `acahti whoami` | `GET /me` |
| `acahti user list [--q TEXT]` | `GET /users` |
| `acahti user create LOGIN [--author NAME] [--email EMAIL] [--admin]` | `POST /users` |
| `acahti user update LOGIN [--author NAME] [--email EMAIL]` | `PATCH /users/{login}` |
| `acahti user permissions LOGIN [--create-team true\|false --create-repo true\|false]` | `GET`/`PUT /users/{login}/permissions` |
| `acahti team create\|delete NAME` | `POST /teams`, `DELETE /teams/NAME` |
| `acahti repo create NAME [--team TEAM]` | `POST /repos` |
| `acahti repo delete OWNER/REPO` | `DELETE /repos/OWNER/REPO` |
| `acahti repo move OWNER/REPO TEAM` | `PUT /repos/OWNER/REPO/team/TEAM` |
| `acahti repo archive\|unarchive OWNER/REPO` | `PUT /repos/OWNER/REPO/archived` |
| `acahti pr list OWNER/REPO [--state open]` | `GET /repos/OWNER/REPO/pulls` |
| `acahti pr view OWNER/REPO N` | `GET …/pulls/N` |
| `acahti pr create OWNER/REPO --title T --head BRANCH [--base B] [--body B]` | `POST …/pulls` |
| `acahti pr merge OWNER/REPO N` | `POST …/pulls/N/merge` |
| `acahti pr close OWNER/REPO N` | `DELETE …/pulls/N` |
| `acahti checks OWNER/REPO SHA [--wait] [--timeout 30m]` | `GET …/checks/SHA`; `--wait` polls until the latest pipeline finishes and exits 1 unless it is green |
| `acahti pipeline list OWNER/REPO [--sha S] [--branch B] [--status S]` | `GET …/pipelines` |
| `acahti pipeline view OWNER/REPO N` | `GET …/pipelines/N` |
| `acahti pipeline log OWNER/REPO N [--step N] [--tail N]` | `GET …/pipelines/N/log` |
| `acahti pipeline trigger OWNER/REPO [--ref REF]` | `POST …/pipelines` (default branch without `--ref`) |
| `acahti pipeline rerun\|cancel\|approve OWNER/REPO N` | `POST …/pipelines/N/rerun`, `/cancel`, `/approve` |
| `acahti inbox [--section pipes\|prs]` | `GET /inbox` |
| `acahti search [--q TEXT] [--type …] [--assignee …] [--state …] [--scope …]` | `GET /work/search` |
| `acahti iteration list\|view\|create\|update\|close …` | `/work/iterations` |
| `acahti ticket\|todo\|document list\|view\|create\|update\|comment …` | `/work/tickets`, `/work/todos`, `/work/docs` |
| `acahti attachment view\|download\|upload …` | `/work/attachments` |
| `acahti delivery list\|draft\|view\|submit\|retry …` | `/work/deliveries` |
| `acahti track list [--view work\|tickets\|argos] [--project ID] [--owner LOGIN] [--pack P] [--group G] [--env E] [--stuck-days N]` | `GET /work/track` |
| `acahti track view KIND ID` | `GET /work/track/KIND/ID` |
| `acahti track activity [--login LOGIN] [--days N] [--until YYYY-MM-DD]` | `GET /work/track/activity` |
| `acahti track templates` | `GET /work/track-templates` |
| `acahti track template set\|reset TYPE …` | `PUT`/`DELETE /work/track-templates/TYPE` |
| `acahti track flow KIND ID [--template TYPE] [--add k,…] [--skip k,…] \| --clear` | `PUT /work/track/KIND/ID/flow` |
| `acahti aidy ask --question TEXT [--conversation ID]` | `POST /work/aidy/ask` |
| `acahti aidy confirm ID --call CALL_ID [--cancel]` | `POST /work/aidy/conversations/ID/confirm` |
| `acahti aidy list\|view ID\|rename ID --title T\|delete ID` | `/work/aidy/conversations` |
| `acahti aidy settings\|configure …` | `/work/aidy/settings` |
| `acahti mcp tools`, `acahti mcp call TOOL [JSON \| -]` | `/mcp` |

The full tool catalog is [MCP](/docs/mcp/); shell agents use the verbs above for the delivery loop, `acahti mcp call` for any other tool, and `acahti aidy` to hand a question or an instruction to Aidy.

## After a push

```bash
sha=$(git rev-parse HEAD)
acahti checks acme/demo "$sha" --wait
acahti pipeline list acme/demo --sha "$sha"
acahti pipeline log acme/demo 12
acahti pr merge acme/demo 3
```

`pr merge` requires the newest pipeline on the head SHA to be green, exactly as the MCP tool does.

## Git

Git stays HTTPS. Username is the Acahti login; password is the account password or an access token. An SSH key is not a credential for Git, MCP, or the CLI.

## Full MCP catalog from a shell

`acahti mcp tools` lists the live tool schemas. `acahti mcp call TOOL '{"argument":"value"}'` invokes a tool through Streamable HTTP with the same personal token. Use `-` in place of JSON to read arguments from stdin. A tool error exits nonzero. Requests have bounded timeouts and do not follow redirects or send stored credentials to a different instance.

## Work: tickets, todos, documents and files

The same personal Access Token or OAuth identity used for repositories can use Work without a website session. All Work objects are instance-shared; personal views filter the list and do not restrict object access.

```bash
acahti ticket view 267
acahti ticket view https://your-instance.example/tickets/267
acahti ticket list --view assigned --page 2 --page-size 20
acahti ticket create --body-file ticket.json
acahti ticket update 267 --body-file changes.json
acahti ticket comment 267 --body-file comment.txt --file evidence.png
acahti ticket archive 267
acahti ticket unarchive 267
acahti todo create --body-file todo.json
acahti todo comment 12 --body 'Progress'
acahti todo link-ticket 12
acahti todo delete 12
acahti document create --body-file document.json
acahti document update 8 --body-file changes.json
acahti document folder-create --body-file folder.json
acahti document folder-update 3 --body-file folder.json
acahti document folder-delete 3
acahti document delete 8
acahti mcp call project_list '{}'
acahti mcp call project_context '{"id":"turbomesh"}'
acahti mcp call time_entry_save '{"todo_id":12,"work_date":"2026-10-30","minutes":120,"summary":"Completed task implementation"}'
acahti mcp call time_entry_list '{"work_date":"2026-10-30"}'
acahti attachment view 123
acahti attachment download 123 --output evidence.png
acahti attachment upload document 8 --file evidence.png
```

`ticket`, `todo`, and `document` share `list`, `view`, `create`, and `update`. `--body-file -` reads stdin. Creation and update files contain a JSON object. Comment files contain plain text. Commands return JSON; downloads write a new file with private permissions and refuse to overwrite an existing file. Page URLs must use the selected instance. The token is never put in a URL.

Ticket details include all comments and attachment metadata. Binary files travel through authenticated multipart upload and content download endpoints, not JSON-encoded file payloads. PDF and Office attachments also expose an authenticated preview URL that returns a PDF (Office files are converted). Ticket comments accept up to nine 200 MiB files; todo/document uploads accept one 200 MiB file. Only non-archived tickets accept updates and comments. Documents default to instance-shared (`visibility=instance`). Restricted documents use per-login view or edit grants; owners, Work managers and org admins always retain full access.

Tickets and todos accept a `project` ID from `project_list` (filtered by repository access); clearing it uses an empty string. Linking a todo to a ticket preserves its project. Move a document with `document update --body-file` using `{"folder_id":3}` or `{"folder_id":null}` for the root. Empty folders can be deleted; occupied folders return a conflict until their contents are moved.

Todo list and detail responses include `logged_minutes`, the total recorded across all people and dates for that task. Time reports belong to the authenticated person. Each save adds an entry with its minutes (1 to 960) and a completion summary. Dates use `YYYY-MM-DD`; the UI defaults to the current date in Asia/Shanghai. `time_entry_list` shows the day's total against an eight-hour reference; daily totals are unrestricted. `time_entry_delete` accepts an entry ID and only deletes your own entry. Tasks with reports cannot be deleted until those reports are removed.

```bash
acahti search --q TurboMesh
acahti search --type ticket --scope mine --state open
acahti iteration list --project turbomesh --state open
acahti iteration view 9
acahti aidy ask --question "Which of my tickets are still open?"
acahti aidy ask --question "创建一个 Star Map 新项目"
acahti aidy confirm 12 --call call_ab12
acahti aidy settings
```

`search`, `iteration`, and `aidy` are the named verbs for product planning and
source-grounded answers. `aidy ask` runs Aidy in a conversation (a new one
unless `--conversation` names one): reads run at once, and a write comes back
under `pending` until `aidy confirm` approves it or `--cancel` drops it. Agents
that call the operations themselves cite URLs and use write verbs only when the
user authorizes a mutation. `aidy ask` and `aidy confirm` may take up to a few
minutes while the host model runs.

Operations without a named command run through `acahti mcp call`, for example `acahti mcp call ticket_get '{"id":267}'`; `acahti mcp tools` lists the live catalog.

### Delivery commands

```bash
acahti delivery draft --iteration 12
acahti delivery submit --body-file delivery.json
acahti delivery list --iteration 12 --page 1
acahti delivery view 7
acahti delivery retry 7
```

The delivery manifest and retry rules are documented in [iterations](iterations.md).

### Track commands

```bash
acahti track list                                   # open todos and tickets no todo carries
acahti track list --view tickets --owner wei --stuck-days 3
acahti track list --view argos --pack nebula --env bj-test
acahti track view todo 123
acahti track view argos_case nebula.upload
```

`--project` and `--owner` filter the work and tickets views; `--pack`, `--group`
and `--env` filter the argos view. `KIND` is `ticket`, `todo`, `iteration`,
`delivery` or `argos_case`. Stages and views are described in [Track](track.md).
`todo update` requires the current `revision` in its JSON body. Batch planning and
structured agent steps use `acahti mcp call planning_move_todos`,
`planning_todo_links`, `planning_run_step` and the corresponding read tools.
