|
| 1 | +# cf — Agent-Friendly Confluence CLI |
| 2 | + |
| 3 | +## Quick Start |
| 4 | +``` |
| 5 | +cf configure --base-url https://yoursite.atlassian.net --token YOUR_API_TOKEN |
| 6 | +cf configure --test # test saved credentials |
| 7 | +cf configure --test --profile work # test a specific profile |
| 8 | +cf configure --profile myprofile --delete # remove a profile |
| 9 | +``` |
| 10 | + |
| 11 | +## Key Patterns |
| 12 | + |
| 13 | +- **All output is JSON** on stdout. Errors are JSON on stderr. |
| 14 | +- **Exit codes are semantic**: 0=ok, 1=error, 2=auth, 3=not_found, 4=validation, 5=rate_limited, 6=permission |
| 15 | +- **Use `--preset`** for common field sets: `cf pages get --id 12345 --preset agent` (presets: agent, brief, titles, meta, tree, search, diff) |
| 16 | +- **Use `--jq`** to reduce output tokens: `cf pages get --id 12345 --jq '{id: .id, title: .title}'` |
| 17 | +- **Use `--fields`** to limit Confluence response fields: `cf pages get --id 12345 --fields id,title,status` |
| 18 | +- **Use `cf batch`** to run multiple operations in one call |
| 19 | +- **Use `cf raw`** for any API endpoint not covered by generated commands |
| 20 | +- **Content is XHTML** (Confluence storage format), not Markdown |
| 21 | + |
| 22 | +## Common Operations |
| 23 | + |
| 24 | +```bash |
| 25 | +# Get page |
| 26 | +cf pages get --id 12345 |
| 27 | + |
| 28 | +# Search content with CQL |
| 29 | +cf search search-content --cql "space = DEV AND type = page" --jq '.results[] | {id, title}' |
| 30 | + |
| 31 | +# Create page (content in Confluence storage format — XHTML) |
| 32 | +cf pages create --spaceId 123456 --title "New Page" --body "<p>Content here</p>" |
| 33 | + |
| 34 | +# Update page (requires current version number) |
| 35 | +cf pages update --id 12345 --version-number 3 --title "Updated" --body "<p>New content</p>" |
| 36 | + |
| 37 | +# Delete page |
| 38 | +cf pages delete --id 12345 |
| 39 | + |
| 40 | +# List spaces |
| 41 | +cf spaces list --jq '.results[] | {id, key: .key, name: .name}' |
| 42 | + |
| 43 | +# Blog posts |
| 44 | +cf blogposts create --spaceId 123456 --title "Sprint Recap" --body "<p>What we shipped</p>" |
| 45 | +cf blogposts list --jq '.results[] | {id, title}' |
| 46 | + |
| 47 | +# Comments |
| 48 | +cf workflow comment --id 12345 --body "Reviewed and approved" |
| 49 | + |
| 50 | +# Labels |
| 51 | +cf labels add --page-id 12345 --name "reviewed" |
| 52 | +cf labels remove --page-id 12345 --name "draft" |
| 53 | + |
| 54 | +# Attachments |
| 55 | +cf attachments upload --page-id 12345 --file ./diagram.png |
| 56 | +cf attachments list --page-id 12345 |
| 57 | + |
| 58 | +# Workflow commands |
| 59 | +cf workflow move --id 12345 --target 67890 --position append |
| 60 | +cf workflow copy --id 12345 --target 67890 --title "Copy of Runbook" |
| 61 | +cf workflow archive --id 12345 |
| 62 | +cf workflow restrict --id 12345 --user "john@company.com" --operation read |
| 63 | + |
| 64 | +# Diff — structured version comparison |
| 65 | +cf diff --id 12345 # all changes |
| 66 | +cf diff --id 12345 --since 2h # changes in last 2 hours |
| 67 | +cf diff --id 12345 --since 2026-01-01 # changes since date |
| 68 | + |
| 69 | +# Export — page content extraction |
| 70 | +cf export --id 12345 # single page body |
| 71 | +cf export --id 12345 --tree # page + all descendants |
| 72 | +cf export --id 12345 --format storage # raw storage format |
| 73 | + |
| 74 | +# Raw API call (method is positional, not a flag; POST/PUT/PATCH require --body) |
| 75 | +cf raw GET /wiki/api/v2/pages/12345 |
| 76 | +cf raw POST /wiki/api/v2/pages --body '{"spaceId":"123","title":"New"}' |
| 77 | +echo '{"spaceId":"123"}' | cf raw POST /wiki/api/v2/pages --body - # stdin |
| 78 | + |
| 79 | +# Batch operations |
| 80 | +echo '[{"command":"pages get","args":{"id":"12345"},"jq":".title"},{"command":"pages get","args":{"id":"67890"},"jq":".title"}]' | cf batch |
| 81 | + |
| 82 | +# Watch for changes (NDJSON stream — always use --max-events in automated contexts) |
| 83 | +cf watch --cql "space = DEV" --interval 30s --max-events 50 |
| 84 | + |
| 85 | +# Templates — create pages from predefined patterns |
| 86 | +cf templates list # list all templates |
| 87 | +cf templates show meeting-notes # show template definition |
| 88 | +cf pages create --template meeting-notes --var title="Q1 Review" --var date="2026-03-28" |
| 89 | +cf templates create my-template --from 12345 # create from existing page |
| 90 | +``` |
| 91 | + |
| 92 | +## Discovery |
| 93 | + |
| 94 | +```bash |
| 95 | +cf schema # resource → verbs mapping (default) |
| 96 | +cf schema --list # all resource names only |
| 97 | +cf schema pages # all operations for 'pages' |
| 98 | +cf schema pages get # full schema with flags for one operation |
| 99 | +cf preset list # list available output presets |
| 100 | +cf templates list # list available templates |
| 101 | +cf templates show <name> # show a template's variables |
| 102 | +``` |
| 103 | + |
| 104 | +## Batch Command Names |
| 105 | + |
| 106 | +In `cf batch`, use `"resource verb"` strings. Notable: `"diff diff"` (not just `"diff"`), `"export export"` (not just `"export"`). |
| 107 | + |
| 108 | +```bash |
| 109 | +echo '[ |
| 110 | + {"command": "pages get", "args": {"id": "12345"}, "jq": ".title"}, |
| 111 | + {"command": "workflow comment", "args": {"id": "12345", "body": "Reviewed"}}, |
| 112 | + {"command": "diff diff", "args": {"id": "12345", "since": "2h"}} |
| 113 | +]' | cf batch |
| 114 | +``` |
| 115 | + |
| 116 | +Batch exit code = highest-severity code from all operations. |
| 117 | + |
| 118 | +## Global Flags |
| 119 | + |
| 120 | +| Flag | Description | |
| 121 | +|------|-------------| |
| 122 | +| `--preset <name>` | named output preset (agent, brief, titles, meta, tree, search, diff) | |
| 123 | +| `--jq <expr>` | jq filter on response | |
| 124 | +| `--fields <list>` | comma-separated fields to return (GET only) | |
| 125 | +| `--cache <duration>` | cache GET responses (e.g. 5m, 1h) | |
| 126 | +| `--pretty` | pretty-print JSON | |
| 127 | +| `--no-paginate` | disable auto-pagination | |
| 128 | +| `--dry-run` | show request without executing | |
| 129 | +| `--verbose` | log HTTP details to stderr (JSON) | |
| 130 | +| `--timeout <duration>` | HTTP request timeout (default 30s) | |
| 131 | +| `--profile <name>` | use named config profile | |
| 132 | +| `--audit <path>` | NDJSON audit log file path | |
| 133 | +| `--max-batch <N>` | max operations per batch (default 50, batch command only) | |
| 134 | + |
| 135 | +## Security |
| 136 | + |
| 137 | +### Operation Policy (per profile) |
| 138 | +Restrict which operations a profile can execute: |
| 139 | + |
| 140 | +```json |
| 141 | +{ |
| 142 | + "profiles": { |
| 143 | + "agent": { |
| 144 | + "base_url": "...", |
| 145 | + "auth": {"type": "basic", "token": "..."}, |
| 146 | + "allowed_operations": ["pages get", "search *", "workflow *"] |
| 147 | + }, |
| 148 | + "readonly": { |
| 149 | + "base_url": "...", |
| 150 | + "auth": {"type": "basic", "token": "..."}, |
| 151 | + "denied_operations": ["* delete*", "workflow *", "raw *"] |
| 152 | + } |
| 153 | + } |
| 154 | +} |
| 155 | +``` |
| 156 | + |
| 157 | +Rules: |
| 158 | +- Use `allowed_operations` OR `denied_operations`, not both |
| 159 | +- Patterns use glob matching: `*` matches any sequence |
| 160 | +- `allowed_operations`: implicit deny-all, only matching ops run |
| 161 | +- `denied_operations`: implicit allow-all, only matching ops blocked |
| 162 | + |
| 163 | +### Batch Limits |
| 164 | +Default max batch size is 50. Override with `--max-batch N`. |
| 165 | + |
| 166 | +### Audit Logging |
| 167 | +Enable per-invocation with `--audit <path>`. Logs NDJSON to the specified file. |
| 168 | + |
| 169 | +## Development |
| 170 | + |
| 171 | +```bash |
| 172 | +make generate # regenerate commands from OpenAPI spec |
| 173 | +make build # build binary |
| 174 | +make test # run tests |
| 175 | +make lint # run golangci-lint |
| 176 | +``` |
0 commit comments