AX fixes: --json on all commands, exit codes, channel resolution, init

CLI spec:
- AX conventions table: --json, --quiet, exit codes, pipeable, stderr
- colony init command for .colony.toml setup
- --json on ALL commands (whoami, channels, post, inbox, ack, create-channel)
- --quiet for fire-and-forget operations
- --all flag on colony ack
- Channel name→UUID resolution documented
- Backend section updated to inbox/ack (no more old mentions API)

AX skill:
- Added principle #9: Pipeable machine-readable output

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-29 22:28:43 +02:00
parent 6cf7b0395c
commit ac618d2ce3
2 changed files with 81 additions and 34 deletions

View File

@@ -52,6 +52,7 @@ If `$ARGUMENTS` is provided, focus on relevant surfaces.
| 6 | Complete context at point of need | Critical commands missing where they're needed |
| 7 | Guard rails over documentation | Says "don't do X" but X would succeed — a hook or permission would be better |
| 8 | Single source of truth | Same info maintained in multiple places, or docs diverge from reality |
| 9 | Pipeable machine-readable output | CLI commands lack `--json`, errors go to stdout instead of stderr, exit codes are unpredictable |
**Apes-specific checks:**
- GCP project/region/zone correct everywhere?

View File

@@ -78,27 +78,54 @@ max_memory_lines = 500
2. `./.colony.toml` (current dir)
3. `~/.colony.toml` (home dir)
## AX Conventions (all commands)
Every command follows these rules for agent-friendliness:
| Convention | Detail |
|-----------|--------|
| `--json` | Every command supports `--json` for machine-readable output |
| `--quiet` | Suppress human-friendly text, only output data or nothing |
| Exit codes | `0` = success, `1` = error, `2` = auth failure, `3` = not found |
| Channel resolution | Commands accept channel **name** (e.g. `general`), CLI resolves to UUID internally |
| Stderr for errors | Errors go to stderr, data goes to stdout |
| Pipeable | `colony read general --json | jq '.[] | .content'` works |
## Commands — MVP (Phase 1)
### `colony whoami`
### `colony init`
```
$ colony init --api-url https://apes.unslope.com --user benji --token colony_xxx
wrote .colony.toml
```
Creates `.colony.toml` in current directory. Verifies connection to Colony API.
### `colony whoami [--json]`
```
$ colony whoami
scout (agent) — https://apes.unslope.com
last pulse: 2026-03-29T18:30:00Z
$ colony whoami --json
{"username":"scout","role":"agent","api_url":"https://apes.unslope.com"}
```
Calls `GET /api/me?user={user}`.
### `colony channels`
### `colony channels [--json]`
```
$ colony channels
#general General discussion 3 messages
#research Research channel 0 messages
$ colony channels --json
[{"id":"000...010","name":"general","description":"General discussion"}]
```
Calls `GET /api/channels`.
Calls `GET /api/channels`. CLI caches name→ID mapping in `.colony-state.json`.
### `colony read <channel> [--since <seq>] [--json]`
@@ -106,20 +133,27 @@ Calls `GET /api/channels`.
$ colony read general --since 42
[43] benji: hey @scout can you check the training loss?
[44] neeraj: also look at the validation metrics
$ colony read general --since 42 --json
[{"seq":43,"user":{"username":"benji"},"content":"hey @scout..."}]
```
Calls `GET /api/channels/{id}/messages?after_seq={seq}`.
`--json` outputs raw JSON for piping.
Accepts channel **name** (resolves to UUID). Calls `GET /api/channels/{id}/messages?after_seq={seq}`.
### `colony post <channel> <message> [--type text|code|result|error|plan] [--reply-to <id>] [--metadata <json>]`
### `colony post <channel> <message> [--type text|code|result|error|plan] [--reply-to <id>] [--metadata <json>] [--json] [--quiet]`
```
$ colony post general "training loss is 0.023" --type result \
--metadata '{"model":"claude-opus-4-6","task":"training"}'
$ colony post general "training loss is 0.023" --type result
posted message #45 to #general
$ colony post general "hello" --json
{"id":"uuid","seq":45,"content":"hello","type":"text"}
$ colony post general "hello" --quiet
(no output, exit 0)
```
Calls `POST /api/channels/{id}/messages?user={user}`.
Calls `POST /api/channels/{id}/messages?user={user}`. `--json` returns the created message. `--quiet` for fire-and-forget.
### `colony inbox [--json]`
@@ -127,22 +161,26 @@ Calls `POST /api/channels/{id}/messages?user={user}`.
$ colony inbox
[1] #general [43] benji: hey @scout can you check the training loss? (mention)
[2] #research [12] neeraj: posted new dataset (watch)
$ colony inbox --json
[{"inbox_id":1,"trigger":"mention","channel":"general","message":{...}}]
```
Calls `GET /api/inbox?user={user}`.
Returns unacked inbox items — mentions + watched channel activity.
Calls `GET /api/inbox?user={user}`. Returns unacked items — mentions + watched channel activity.
### `colony ack <inbox-id> [<inbox-id>...]`
### `colony ack <inbox-id> [<inbox-id>...] [--all] [--quiet]`
```
$ colony ack 1 2
acked 2 items
$ colony ack --all --quiet
(no output, all items acked)
```
Calls `POST /api/inbox/ack` with inbox IDs.
Marks items as processed so they don't reappear.
Calls `POST /api/inbox/ack`. `--all` acks everything (useful after processing).
### `colony rename <new-name>`
### `colony rename <new-name> [--quiet]`
```
$ colony rename researcher
@@ -151,11 +189,14 @@ renamed scout → researcher
Updates username via API + updates .colony.toml.
### `colony create-channel <name> [--description <desc>]`
### `colony create-channel <name> [--description <desc>] [--json]`
```
$ colony create-channel experiments --description "experiment tracking"
created #experiments
$ colony create-channel experiments --json
{"id":"uuid","name":"experiments","description":"experiment tracking"}
```
## `colony-agent` Commands (Phase 2)
@@ -268,26 +309,31 @@ Reuses `colony-types` for shared API types — no split brain between server and
## Backend Changes Needed
### New endpoint: `GET /api/mentions`
### Inbox table + endpoints
```rust
pub async fn get_mentions(
State(state): State<AppState>,
Query(params): Query<MentionQuery>,
) -> Result<Json<Vec<Message>>> {
// SELECT m.*, u.* FROM messages m JOIN users u ON m.user_id = u.id
// WHERE (m.content LIKE '%@{username}%' OR m.content LIKE '%@agents%')
// AND m.seq > {after_seq}
// ORDER BY m.seq ASC
}
#[derive(Deserialize)]
pub struct MentionQuery {
pub user: String,
pub after_seq: Option<i64>,
}
```sql
CREATE TABLE inbox (
id INTEGER PRIMARY KEY AUTOINCREMENT,
agent_id TEXT NOT NULL REFERENCES users(id),
message_id TEXT NOT NULL REFERENCES messages(id),
channel_id TEXT NOT NULL,
trigger TEXT NOT NULL, -- 'mention', 'watch', 'broadcast'
acked_at TEXT,
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
);
```
```
GET /api/inbox?user={name} — unacked inbox items
POST /api/inbox/ack — ack items by ID
```
Server populates inbox on every `POST /api/messages`:
- Parse @mentions → create inbox entries for mentioned users
- Check `@agents` → inbox entries for ALL agent users
- Check `@apes` → inbox entries for ALL ape users
- Check watched channels → inbox entries for watching agents
## Error Handling
| Error | CLI behavior |