diff --git a/CLAUDE.md b/CLAUDE.md index db52d03..c0ad47f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -11,7 +11,7 @@ cf configure --profile myprofile --delete # remove a profile ## Key Patterns - **All output is JSON** on stdout. Errors are JSON on stderr. -- **Exit codes are semantic**: 0=ok, 1=error, 2=auth, 3=not_found, 4=validation, 5=rate_limited, 6=permission +- **Exit codes are semantic**: 0=ok, 1=error, 2=auth, 3=not_found, 4=validation, 5=rate_limited, 6=conflict, 7=server - **Use `--preset`** for common field sets: `cf pages get --id 12345 --preset agent` (presets: agent, brief, titles, meta, tree, search, diff) - **Use `--jq`** to reduce output tokens: `cf pages get --id 12345 --jq '{id: .id, title: .title}'` - **Use `--fields`** to limit Confluence response fields: `cf pages get --id 12345 --fields id,title,status` @@ -48,8 +48,8 @@ cf blogposts list --jq '.results[] | {id, title}' cf workflow comment --id 12345 --body "Reviewed and approved" # Labels -cf labels add --page-id 12345 --name "reviewed" -cf labels remove --page-id 12345 --name "draft" +cf labels add --page-id 12345 --label "reviewed" +cf labels remove --page-id 12345 --label "draft" # Attachments cf attachments upload --page-id 12345 --file ./diagram.png @@ -79,8 +79,8 @@ echo '{"spaceId":"123"}' | cf raw POST /wiki/api/v2/pages --body - # stdin # Batch operations echo '[{"command":"pages get","args":{"id":"12345"},"jq":".title"},{"command":"pages get","args":{"id":"67890"},"jq":".title"}]' | cf batch -# Watch for changes (NDJSON stream — always use --max-events in automated contexts) -cf watch --cql "space = DEV" --interval 30s --max-events 50 +# Watch for changes (NDJSON stream — always use --max-polls in automated contexts) +cf watch --cql "space = DEV" --interval 30s --max-polls 50 ``` diff --git a/go.mod b/go.mod index 603e6b5..6574ac4 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/sofq/confluence-cli -go 1.25.8 +go 1.25.9 require ( github.com/itchyny/gojq v0.12.19 diff --git a/skill/confluence-cli/SKILL.md b/skill/confluence-cli/SKILL.md index 78c332a..d25ec70 100644 --- a/skill/confluence-cli/SKILL.md +++ b/skill/confluence-cli/SKILL.md @@ -1,23 +1,29 @@ --- name: confluence-cli -description: "How to use `cf`, the agent-friendly Confluence CLI, to interact with Confluence Cloud. Use this skill whenever the user asks to work with Confluence pages, spaces, blog posts, comments, labels, attachments, or any Confluence operation — searching content, creating pages, managing spaces, exporting page trees, or automating Confluence workflows. Also use when you see `cf` commands in the codebase or the user mentions Confluence in the context of CLI tooling. Even if the user just says 'check my Confluence pages' or 'update the wiki', this skill applies." +description: "Confluence Cloud CLI (`cf`) — interact with Confluence pages, spaces, blog posts, comments, labels, attachments, and any Confluence Cloud API operation through structured JSON output and semantic exit codes. Use this skill whenever the user asks about Confluence, Atlassian wiki, knowledge base pages, or any wiki/documentation management that involves Confluence — creating pages, searching content, exporting page trees, managing spaces, or automating workflows. Also trigger when you see `cf` commands in the codebase, or the user says things like 'check the wiki', 'update the docs on Confluence', 'publish to Confluence', or 'search our knowledge base'. Even casual mentions of Confluence or Atlassian wiki operations should trigger this skill." +compatibility: + tools: ["Bash"] + requirements: ["cf CLI binary (brew install sofq/tap/cf)"] --- -# cf — Confluence CLI for AI Agents +# cf — Confluence Cloud CLI for AI Agents `cf` is a Confluence Cloud CLI designed for AI agents. Every command returns structured JSON on stdout, errors as JSON on stderr, and semantic exit codes — so you can parse, branch, and retry reliably. -**Sections:** [Setup](#setup) · [Discovering Commands](#discovering-commands) · [Common Operations](#common-operations) · [Token Efficiency](#token-efficiency) · [Batch Operations](#batch-operations) · [Error Handling](#error-handling) · [Global Flags](#global-flags) · [Common Agent Patterns](#common-agent-patterns) · [Security](#security) · [Troubleshooting](#troubleshooting) +**Sections:** [Setup](#setup) · [Discovering Commands](#discovering-commands) · [Common Operations](#common-operations) · [Token Efficiency](#token-efficiency) · [Batch Operations](#batch-operations) · [Error Handling](#error-handling) · [Global Flags](#global-flags) · [Common Agent Patterns](#common-agent-patterns) + +**Reference files** (read when needed): +- `references/presets.md` — Full preset reference table and custom preset config +- `references/batch-commands.md` — Batch command name mapping table and examples +- `references/security.md` — Operation policies, batch limits, audit logging +- `references/troubleshooting.md` — Installation, auth errors, content format notes ## Setup If `cf` is not configured yet, help the user set it up: ```bash -# Check if cf is installed -cf version - -# Configure with Confluence Cloud credentials +cf version # check if installed cf configure --base-url https://yoursite.atlassian.net --token YOUR_API_TOKEN ``` @@ -43,10 +49,10 @@ If you get exit code 2 (auth error), the token is likely expired or wrong. Ask t ## Discovering Commands -`cf` has 200+ commands auto-generated from Confluence's OpenAPI spec. You don't need to memorize them — discover at runtime: +`cf` has 200+ commands auto-generated from Confluence's OpenAPI spec. Discover at runtime: ```bash -cf schema # resource → verbs mapping (default, most useful overview) +cf schema # resource → verbs mapping (most useful overview) cf schema --list # all resource names only (pages, spaces, comments, ...) cf schema pages # all operations for a resource cf schema pages get # full schema with all flags for one operation @@ -54,41 +60,28 @@ cf schema pages get # full schema with all flags for one operation Always use `cf schema` to discover the exact command name and flags before running an unfamiliar operation. -`cf schema` (no flags) defaults to the compact resource→verbs mapping, which is the most useful starting point. - ## Common Operations -### Get a page +### Pages ```bash cf pages get --id 12345 -``` - -### Search content with CQL -```bash -cf search search-content \ - --cql "space = DEV AND type = page AND lastModified > now('-7d')" \ - --jq '.results[] | {id, title}' -``` - -### Create a page -```bash -# Content uses Confluence storage format (XHTML, not Markdown) cf pages create --spaceId 123456 --title "Deploy Runbook" \ --body "

Steps

Follow these steps...

" -``` - -### Update a page -```bash cf pages update --id 12345 --version-number 3 \ --title "Deploy Runbook v2" --body "

Updated Steps

" +cf pages delete --id 12345 ``` -### Delete a page +Content uses Confluence storage format (XHTML, not Markdown). + +### Search with CQL ```bash -cf pages delete --id 12345 +cf search search-content \ + --cql "space = DEV AND type = page AND lastModified > now('-7d')" \ + --jq '.results[] | {id, title}' ``` -### List spaces +### Spaces ```bash cf spaces list --jq '.results[] | {id, key: .key, name: .name}' ``` @@ -99,53 +92,29 @@ cf blogposts create --spaceId 123456 --title "Sprint Recap" --body "

What we s cf blogposts list --jq '.results[] | {id, title}' ``` -### Comments +### Comments, Labels, Attachments ```bash cf workflow comment --id 12345 --body "Reviewed and approved" -``` - -### Labels -```bash -cf labels add --page-id 12345 --name "reviewed" -cf labels remove --page-id 12345 --name "draft" -cf labels list --page-id 12345 --jq '.results[].name' -``` - -### Attachments -```bash +cf labels add --page-id 12345 --label "reviewed" +cf labels remove --page-id 12345 --label "draft" cf attachments upload --page-id 12345 --file ./diagram.png cf attachments list --page-id 12345 --jq '.results[] | {id, title}' -cf attachments delete --id 67890 ``` ### Workflow commands ```bash -# Move a page under another page cf workflow move --id 12345 --target 67890 --position append - -# Copy a page (async — waits for completion by default) cf workflow copy --id 12345 --target 67890 --title "Copy of Runbook" - -# Publish a draft cf workflow publish --id 12345 - -# Archive a page cf workflow archive --id 12345 - -# Restrict access cf workflow restrict --id 12345 --user "john@company.com" --operation read ``` ### Diff — version comparison ```bash -# All changes -cf diff --id 12345 - -# Changes in last 2 hours -cf diff --id 12345 --since 2h - -# Changes since a specific date -cf diff --id 12345 --since 2026-01-01 +cf diff --id 12345 # all changes +cf diff --id 12345 --since 2h # changes in last 2 hours +cf diff --id 12345 --since 2026-01-01 # changes since date ``` ### Export — page content extraction @@ -157,74 +126,47 @@ cf export --id 12345 --format storage # raw Confluence storage format ### Watch for changes (NDJSON stream) ```bash -# Poll a CQL query and emit events as NDJSON (one JSON object per line) -cf watch --cql "space = DEV" --interval 30s --max-events 50 - -# Watch a specific space -cf watch --cql "space = DEV AND type = page" --interval 1m --max-events 20 +cf watch --cql "space = DEV" --interval 30s --max-polls 50 ``` -Events: `initial` (first poll), `created`, `updated`, `removed`. - -**Important:** Always use `--max-events` when calling from an automated/agent context — agents cannot send Ctrl-C (SIGINT) to stop the stream. +Events: `initial`, `created`, `updated`, `removed`. Always use `--max-polls` in automated contexts — agents cannot send Ctrl-C to stop the stream. ### Raw API call (escape hatch) ```bash -# For any endpoint not covered by generated commands cf raw GET /wiki/api/v2/pages/12345 cf raw POST /wiki/api/v2/pages --body '{"spaceId":"123","title":"New Page"}' -cf raw POST /wiki/api/v2/pages --body @request.json -# Read body from stdin (must use --body - explicitly) -echo '{"spaceId":"123","title":"New"}' | cf raw POST /wiki/api/v2/pages --body - +echo '{"spaceId":"123"}' | cf raw POST /wiki/api/v2/pages --body - ``` -**Note:** POST/PUT/PATCH require `--body`. Without it, `cf raw` will error instead of hanging on stdin. +POST/PUT/PATCH require `--body`. Without it, `cf raw` will error instead of hanging on stdin. ## Token Efficiency Confluence responses can be large (8K+ tokens for a single page). Always minimize output: ```bash -# --preset: use a named preset for common field combinations +# --preset: named preset for common field combinations cf pages get --id 12345 --preset agent # id, title, status, spaceId cf pages get --id 12345 --preset brief # id, title, status -cf pages get --id 12345 --preset meta # id, title, version, dates - -# List all available presets -cf preset list -# --fields: tell Confluence to return only these fields (server-side filtering) +# --fields: server-side field filtering cf pages get --id 12345 --fields id,title,status -# --jq: filter the JSON response (client-side filtering) +# --jq: client-side JSON filtering cf pages get --id 12345 --jq '{id: .id, title: .title}' -# Combine both for maximum efficiency (~50 tokens vs ~8,000) +# Combine for maximum efficiency (~50 tokens vs ~8,000) cf pages get --id 12345 --fields id,title --jq '{id: .id, title: .title}' -# Cache read-heavy data to avoid redundant API calls +# Cache read-heavy data cf spaces list --cache 5m --jq '[.results[].key]' ``` -**Always use `--preset` or `--fields` + `--jq`.** `--preset` gives you common field sets with zero effort. `--fields` reduces what Confluence sends back, `--jq` shapes the output into exactly what you need. - -### Preset reference - -| Preset | Description | -|--------|-------------| -| `brief` | id, title, status | -| `titles` | id, title only | -| `agent` | id, title, status, spaceId — optimized for AI agents | -| `tree` | hierarchical page tree view | -| `meta` | id, title, version, created/modified dates | -| `search` | search result fields | -| `diff` | version comparison fields | - -User-defined presets can include a `jq` filter; store them in profile config or `~/.config/cf/presets.json`. +Always use `--preset` or `--fields` + `--jq`. Run `cf preset list` for available presets. See `references/presets.md` for the full preset table. ## Batch Operations -When you need multiple Confluence calls, use `cf batch` to run them in a single process: +Run multiple Confluence calls in a single process: ```bash echo '[ @@ -234,85 +176,30 @@ echo '[ ]' | cf batch ``` -**Batch exit code:** The process exit code is the highest-severity exit code from all operations. Check individual `exit_code` fields for per-operation status. - -### Batch command names - -Batch uses `"resource verb"` strings matching `cf schema` output: - -| CLI command | Batch `"command"` string | -|---|---| -| `cf pages get` | `"pages get"` | -| `cf pages create` | `"pages create"` | -| `cf pages update` | `"pages update"` | -| `cf workflow move` | `"workflow move"` | -| `cf workflow copy` | `"workflow copy"` | -| `cf workflow archive` | `"workflow archive"` | -| `cf workflow comment` | `"workflow comment"` | -| `cf search search-content` | `"search search-content"` | -| `cf diff` | `"diff diff"` | -| `cf export` | `"export export"` | - -Note: `"diff diff"` and `"export export"` are correct — the resource and verb share the same name. - -```bash -echo '[ - {"command": "pages get", "args": {"id": "12345"}, "jq": ".title"}, - {"command": "workflow comment", "args": {"id": "12345", "body": "Reviewed"}}, - {"command": "diff diff", "args": {"id": "12345", "since": "2h"}} -]' | cf batch - -# Or read from a file -cf batch --input ops.json -``` +Batch exit code is the highest-severity code from all operations. See `references/batch-commands.md` for the full command name mapping table. ## Error Handling -Errors are structured JSON on stderr. Branch on `exit_code` and `error_type`: - -| Exit code | error_type | Meaning | What to do | -|-----------|-----------|---------|------------| -| 0 | — | Success | Parse stdout as JSON | -| 1 | `connection_error` | Network/unknown error | Check connectivity, retry | -| 2 | `auth_failed` | Auth failed (401/403) | Check token/credentials | -| 3 | `not_found` | Resource not found (404) | Verify page ID / resource ID | -| 4 | `validation_error` | Bad request (400/422) | Fix the request payload | -| 5 | `rate_limited` | Rate limited (429) | Wait `retry_after` seconds, then retry | -| 6 | `permission_denied` | Permission denied | Check access / space permissions | - -Error JSON includes optional `hint` (actionable recovery text) and `retry_after` (integer seconds for rate limits): - -```json -{ - "error_type": "rate_limited", - "status": 429, - "message": "Rate limit exceeded", - "hint": "You are being rate limited. Wait before retrying.", - "retry_after": 30, - "request": {"method": "GET", "path": "/wiki/api/v2/pages/12345"} -} -``` - -### Retry pattern for agents +Errors are structured JSON on stderr. Branch on `exit_code`: -For rate limits (exit 5) and general errors (exit 1), retry with backoff: +| Exit code | Meaning | Action | +|-----------|---------|--------| +| 0 | Success | Parse stdout as JSON | +| 1 | Network/unknown error | Check connectivity, retry with backoff | +| 2 | Auth failed (401/403) | Check token/credentials | +| 3 | Not found (404) | Verify resource ID — do not retry | +| 4 | Bad request (400/422) | Fix the request payload — do not retry | +| 5 | Rate limited (429) | Wait `retry_after` seconds from stderr JSON, then retry | +| 6 | Conflict (409) | Resource conflict — resolve and retry | +| 7 | Server error (5xx) | Confluence server issue — retry with backoff | -``` -1. Run the command -2. If exit code is 5 (rate_limited): - - Parse stderr JSON, read "retry_after" (integer seconds) - - Wait that many seconds, then retry (max 3 retries) -3. If exit code is 1 (connection error): - - Retry with exponential backoff: wait 1s, 2s, 4s (max 3 retries) -4. If exit code is 3 (not_found) or 4 (validation_error): - - Do NOT retry — fix the request or report to user -``` +For rate limits (exit 5), parse `retry_after` from stderr JSON and wait. For connection errors (exit 1), retry with exponential backoff (1s, 2s, 4s, max 3 retries). Do not retry exit codes 3 or 4. ## Global Flags | Flag | Description | |------|-------------| -| `--preset ` | named output preset (agent, brief, titles, meta, tree, search, diff) | +| `--preset ` | named output preset | | `--jq ` | jq filter on response | | `--fields ` | comma-separated fields to return (GET only) | | `--cache ` | cache GET responses (e.g. `5m`, `1h`) | @@ -327,29 +214,20 @@ For rate limits (exit 5) and general errors (exit 1), retry with backoff: ## Common Agent Patterns ### Check-then-act: update if exists, create if not - ```bash # Try to get the page; check exit code cf pages get --id 12345 --preset agent - -# If exit code 0 → page exists, update it -cf pages update --id 12345 --version-number 3 --body "

Updated content

" - -# If exit code 3 (not_found) → create it -cf pages create --spaceId 123456 --title "New Page" --body "

Content

" +# Exit 0 → update; Exit 3 → create ``` ### Bulk page export - -Search + batch to export all pages in a space: - ```bash # Step 1: Find pages -PAGES=$(cf search search-content \ +cf search search-content \ --cql "space = DEV AND type = page" \ - --jq '[.results[].content.id]') + --jq '[.results[].content.id]' -# Step 2: Build batch payload and execute +# Step 2: Build batch payload from IDs and execute echo '[ {"command": "export export", "args": {"id": "12345"}}, {"command": "export export", "args": {"id": "67890"}} @@ -357,87 +235,19 @@ echo '[ ``` ### Create page with children - ```bash -# Step 1: Create the parent page +# Step 1: Create parent PARENT=$(cf pages create --spaceId 123456 --title "Project Docs" \ - --body "

Project documentation root

" --jq '.id') + --body "

Root

" --jq '.id') -# Step 2: Create child pages (use batch for efficiency) +# Step 2: Create children via batch echo "[ - {\"command\": \"pages create\", \"args\": {\"spaceId\": \"123456\", \"parentId\": \"$PARENT\", \"title\": \"Getting Started\", \"body\": \"

Setup guide

\"}}, - {\"command\": \"pages create\", \"args\": {\"spaceId\": \"123456\", \"parentId\": \"$PARENT\", \"title\": \"Architecture\", \"body\": \"

System design

\"}}, - {\"command\": \"pages create\", \"args\": {\"spaceId\": \"123456\", \"parentId\": \"$PARENT\", \"title\": \"Runbook\", \"body\": \"

Operations guide

\"}} + {\"command\": \"pages create\", \"args\": {\"spaceId\": \"123456\", \"parentId\": \"$PARENT\", \"title\": \"Getting Started\", \"body\": \"

Setup

\"}}, + {\"command\": \"pages create\", \"args\": {\"spaceId\": \"123456\", \"parentId\": \"$PARENT\", \"title\": \"Architecture\", \"body\": \"

Design

\"}} ]" | cf batch ``` ### Validate before executing - -Use `--dry-run` to preview requests before making API calls: - ```bash -# Preview what would be sent (no API call made) cf pages create --spaceId 123456 --title "Test" --body "

test

" --dry-run - -# If the output looks correct, run without --dry-run -cf pages create --spaceId 123456 --title "Test" --body "

test

" ``` - -## Security - -### Operation Policy (per profile) -Restrict which operations a profile can execute: - -```json -{ - "profiles": { - "agent": { - "allowed_operations": ["pages get", "search *", "workflow *"] - }, - "readonly": { - "denied_operations": ["* delete*", "workflow *", "raw *"] - } - } -} -``` - -- Use `allowed_operations` OR `denied_operations`, not both -- Patterns use glob matching: `*` matches any sequence -- `allowed_operations`: implicit deny-all, only matching ops run -- `denied_operations`: implicit allow-all, only matching ops blocked - -### Batch Limits -Default max batch size is 50. Override with `--max-batch N`. - -### Audit Logging -Enable per-invocation with `--audit `. -Logs NDJSON with: timestamp, user, profile, operation, exit code. - -## Pagination - -`cf` auto-paginates by default — it fetches all pages and merges results into a single response. Use `--no-paginate` if you only need the first page. - -## Troubleshooting - -**"command not found"** — `cf` is not installed. Install via: -```bash -brew install sofq/tap/cf # Homebrew -go install github.com/sofq/confluence-cli@latest # Go -``` - -**Exit code 2 (auth)** — Token expired or misconfigured. Test with: -```bash -cf configure --test -``` - -**Unknown command** — Command names are auto-generated from Confluence's API. Use `cf schema` to find the right name, or use `cf raw` as an escape hatch. - -**Large responses filling context** — Always use `--preset` or `--fields` + `--jq` to minimize output. - -**Content format** — Confluence uses Atlassian Storage Format (XHTML-based, not Markdown). Pass storage format through as-is. Example: `

Title

Content

` - -**"--dry-run"** — Use this to preview what `cf` will send without making the API call. - ---- - -> **Note:** The project root also contains `CLAUDE.md` with a compressed quick-reference for `cf`. If both files are loaded in context, prefer this SKILL.md as the authoritative reference — `CLAUDE.md` is a contributor-oriented summary. diff --git a/skill/confluence-cli/references/batch-commands.md b/skill/confluence-cli/references/batch-commands.md new file mode 100644 index 0000000..c083420 --- /dev/null +++ b/skill/confluence-cli/references/batch-commands.md @@ -0,0 +1,33 @@ +# Batch Command Reference + +## Command Name Mapping + +Batch uses `"resource verb"` strings matching `cf schema` output: + +| CLI command | Batch `"command"` string | +|---|---| +| `cf pages get` | `"pages get"` | +| `cf pages create` | `"pages create"` | +| `cf pages update` | `"pages update"` | +| `cf workflow move` | `"workflow move"` | +| `cf workflow copy` | `"workflow copy"` | +| `cf workflow archive` | `"workflow archive"` | +| `cf workflow comment` | `"workflow comment"` | +| `cf search search-content` | `"search search-content"` | +| `cf diff` | `"diff diff"` | +| `cf export` | `"export export"` | + +Note: `"diff diff"` and `"export export"` are correct — the resource and verb share the same name. + +## Examples + +```bash +echo '[ + {"command": "pages get", "args": {"id": "12345"}, "jq": ".title"}, + {"command": "workflow comment", "args": {"id": "12345", "body": "Reviewed"}}, + {"command": "diff diff", "args": {"id": "12345", "since": "2h"}} +]' | cf batch + +# Or read from a file +cf batch --input ops.json +``` diff --git a/skill/confluence-cli/references/presets.md b/skill/confluence-cli/references/presets.md new file mode 100644 index 0000000..da72b43 --- /dev/null +++ b/skill/confluence-cli/references/presets.md @@ -0,0 +1,25 @@ +# Preset Reference + +## Built-in Presets + +| Preset | Fields | +|--------|--------| +| `brief` | id, title, status | +| `titles` | id, title only | +| `agent` | id, title, status, spaceId — optimized for AI agents | +| `tree` | hierarchical page tree view | +| `meta` | id, title, version, created/modified dates | +| `search` | search result fields | +| `diff` | version comparison fields | + +## Usage + +```bash +cf pages get --id 12345 --preset agent +cf pages get --id 12345 --preset brief +cf preset list # list all available presets +``` + +## Custom Presets + +User-defined presets can include a `jq` filter. Store them in profile config or `~/.config/cf/presets.json`. diff --git a/skill/confluence-cli/references/security.md b/skill/confluence-cli/references/security.md new file mode 100644 index 0000000..95f5a04 --- /dev/null +++ b/skill/confluence-cli/references/security.md @@ -0,0 +1,32 @@ +# Security Configuration + +## Operation Policy (per profile) + +Restrict which operations a profile can execute: + +```json +{ + "profiles": { + "agent": { + "allowed_operations": ["pages get", "search *", "workflow *"] + }, + "readonly": { + "denied_operations": ["* delete*", "workflow *", "raw *"] + } + } +} +``` + +- Use `allowed_operations` OR `denied_operations`, not both +- Patterns use glob matching: `*` matches any sequence +- `allowed_operations`: implicit deny-all, only matching ops run +- `denied_operations`: implicit allow-all, only matching ops blocked + +## Batch Limits + +Default max batch size is 50. Override with `--max-batch N`. + +## Audit Logging + +Enable per-invocation with `--audit `. +Logs NDJSON with: timestamp, user, profile, operation, exit code. diff --git a/skill/confluence-cli/references/troubleshooting.md b/skill/confluence-cli/references/troubleshooting.md new file mode 100644 index 0000000..123dfe5 --- /dev/null +++ b/skill/confluence-cli/references/troubleshooting.md @@ -0,0 +1,20 @@ +# Troubleshooting + +**"command not found"** — `cf` is not installed. Install via: +```bash +brew install sofq/tap/cf # Homebrew +go install github.com/sofq/confluence-cli@latest # Go +``` + +**Exit code 2 (auth)** — Token expired or misconfigured. Test with: +```bash +cf configure --test +``` + +**Unknown command** — Command names are auto-generated from Confluence's API. Use `cf schema` to find the right name, or use `cf raw` as an escape hatch. + +**Large responses filling context** — Always use `--preset` or `--fields` + `--jq` to minimize output. + +**Content format** — Confluence uses Atlassian Storage Format (XHTML-based, not Markdown). Pass storage format through as-is. Example: `

Title

Content

` + +**"--dry-run"** — Use this to preview what `cf` will send without making the API call.