cfl is a command-line tool for Confluence Server / Data Center that makes
the Confluence URL the unit of identity. Paste the URL you would open in a
browser; cfl resolves the credential, fetches the page in a stable schema, and
lets you edit it with version-safe updates.
Server / Data Center only. Confluence Cloud uses a different auth model and API surface and is out of scope.
What cfl is
- A daily-driver CLI for developers and writers who work with Confluence from the terminal: read a page, edit it, create one, search, all driven by the URL you'd paste into a browser.
- Agent-friendly by design. The output is a stable, self-owned schema
(
schemaVersion: "1",-o jsonfor machines), exit codes are simple (0/>=10), and errors are one-sentence messages with a suggested next step — so an AI coding agent can drivecflreliably. It ships a companion skill for exactly that. - A thin transport, not a converter. Page bodies move verbatim as Confluence
storage format (XHTML) in both directions.
cfldeliberately does no Markdown/wiki conversion: the caller — a human who knows storage format, or an LLM that understands both — owns the format. This keepscflsmall and lets a capable model generate or read the XHTML far better than a fixed library could.
What cfl is not
- Not a Confluence Cloud tool (different auth + v2 API).
- Not a Markdown/wiki editor — bodies are storage-format XHTML, passed through unchanged. Raw Markdown sent as a body is stored literally, not rendered.
- Not a Confluence administration tool — no attachment, comment, label, or user-management commands; page + space read/write (plus search) is the surface.
- URL as identity — every page/space command accepts a Confluence URL
(display URL,
pageIdURL,/spaces/KEY/pages/ID/TitleURL, REST URL) or a bare numeric page ID (optionallyalias:id). - PAT Bearer auth — credentials are Personal Access Tokens sent as
Authorization: Bearer <token>; no username is stored or transmitted. - Stable self-owned schema —
-o yaml|json|raw, YAML by default, every structured response begins withschemaVersion: "1". Seedocs/schema.md. - Version-safe updates —
cfl page updatereads the current version and submitscurrent + 1, never guessing; stale-version conflicts are a clear error. - Actionable errors — every failure renders a one-sentence message plus a
suggested next step.
--debugshows the redacted raw exchange.
brew install addozhang/tap/cflPulls from the tap repo addozhang/homebrew-tap.
go install github.com/addozhang/cfl/cmd/cfl@latestRequires Go 1.22+. The Go toolchain fetches the module directly from GitHub; no proxy or token is needed for this public repo.
Download cfl_<version>_<os>_<arch>.tar.gz from the
Releases page, extract,
and move cfl onto your PATH.
git clone https://github.com/addozhang/confluence-cli
cd confluence-cli
make build # produces ./bin/cfl# 1. Store a Personal Access Token for your instance (hidden prompt).
# Create the PAT in Confluence: profile -> Settings -> Personal Access Tokens.
cfl auth add https://wiki.example.com
# 2. Verify the token works.
cfl auth whoami https://wiki.example.com
# 3. Read a page (any Confluence URL shape, or a bare page ID).
cfl page get https://wiki.example.com/pages/viewpage.action?pageId=12345
cfl page get https://wiki.example.com/spaces/ENG/pages/12345/Runbook
cfl page get https://wiki.example.com/display/ENG/Runbook
cfl page get 12345 # works when exactly one instance is configuredcfl auth add <url> [--alias <name>] # store a PAT (hidden prompt); optional short alias
cfl auth list # list configured instances + aliases (never tokens)
cfl auth remove <url> # remove a stored credential (idempotent)
cfl auth whoami <url> # verify a stored token against its instanceAn alias is a short name for an instance. Once set, use it anywhere an instance is named:
cfl auth add https://wiki.example.com --alias prod
cfl space list --instance prod # alias instead of the full URL
cfl page get prod:12345 # <alias>:<id> picks the instance for a bare page IDcfl page get <url-or-id> [--instance URL|alias]
cfl page create --space KEY --title T --body <input> [--parent ID] [--instance URL|alias]
cfl page update <url-or-id> --body <input> [--title T] [--instance URL|alias]
cfl page delete <url-or-id> [--yes] [--instance URL|alias]
cfl page children <url-or-id> [--instance URL|alias]--body accepts three forms:
--body @path— read the body from a file--body -— read the body from stdin--body '<p>literal</p>'— use the string verbatim
Bodies are Confluence storage format (XHTML), passed through unchanged — no Markdown/wiki conversion.
--instance selects the target instance for a bare page ID. A full URL or an
<alias>:<id> argument carries its own instance, so --instance is ignored
there. A bare numeric ID needs --instance (or the <alias>:<id> form) only when
several instances are configured; with a single instance it is optional.
cfl page delete requires explicit intent: pass --yes, or confirm the
interactive prompt. In a non-interactive session it refuses without --yes.
cfl search <text> [--space KEY] [--type page|blogpost] [--limit N] [--start N] [--instance URL|alias]
cfl search --cql '<raw CQL>' [--limit N] [--start N] [--instance URL|alias]The friendly form compiles your inputs into Confluence CQL: the search term is
matched as free text (always escaped, never injected), --space/--type add
constraints, and --type defaults to page.
--cql has the highest precedence: when supplied it is used as the complete
query and the term/--space/--type are ignored (a note is printed to stderr).
CQL is not validated client-side; a malformed query surfaces the server's error.
cfl search "release notes" --space ENG --instance prod
cfl search --cql 'space = ENG AND title ~ "runbook" AND created > now("-7d")' --instance prodsearch returns a single bounded page; --limit/--start map onto the REST
pagination parameters.
cfl space list [--limit N] [--start N] [--instance URL]
cfl space get <key> [--instance URL]space list returns a single bounded page; --limit/--start map directly onto
the REST pagination parameters.
cfl version # offline; prints version/commit/date| Flag | Default | Description |
|---|---|---|
-o, --output |
yaml |
Output format: yaml, json, or raw. |
--timeout |
30s |
Per-request timeout (Go duration, e.g. 30s, 2m). |
--insecure |
off | Disable TLS verification (prints a stderr warning). |
--debug |
off | Log the raw HTTP exchange to stderr (Authorization redacted). |
Common flags have single-letter short forms:
| Short | Long | Used by |
|---|---|---|
-o |
--output |
all commands |
-i |
--instance |
page, space, search |
-s |
--space |
page create, search |
-t |
--title |
page create / update |
-b |
--body |
page create / update |
-l |
--limit |
space list, search |
-p |
--parent |
page create |
-y |
--yes |
page delete |
cfl page get 12345 -i prod
cfl search "runbook" -s ENG -l 10 -i prod
cfl page create -s ENG -t "Notes" -b @notes.xhtml -i prodcfl honors the SSL_CERT_FILE environment variable with no flag. Point it at a
PEM CA bundle and cfl trusts a self-signed instance without --insecure:
export SSL_CERT_FILE=/path/to/corporate-ca.pem
cfl page get https://wiki.internal/pages/viewpage.action?pageId=12345An invalid SSL_CERT_FILE path produces a clear error.
Default output is YAML; -o json is the structurally-identical compact form;
-o raw prints the verbatim Confluence response. Every yaml/json response begins
with schemaVersion: "1".
Pin the schema version in scripts: read schemaVersion and fail fast if it
is not the value you tested against. Field stability tiers are documented in
docs/schema.md — only stable fields carry the
compatibility promise.
ver=$(cfl page get "$URL" -o json | jq -r .schemaVersion)
[ "$ver" = "1" ] || { echo "unexpected cfl schemaVersion: $ver" >&2; exit 1; }cfl ships a companion skill for AI coding agents at
skills/cfl-confluence-cli/. The skill is a
concise operating guide for agents that need to read Confluence pages, create or
update content, search with CQL, or manage credentials — all from the terminal,
driven by the Confluence URL the user pastes.
Use it when an agent has terminal access and needs to work with Confluence
through cfl. The skill emphasizes:
- using Confluence URLs as the unit of identity (Server/DC only — not Cloud);
- preferring
-o jsonfor machine-readable inspection; - the instance-selection rules for bare page IDs (
-i <url|alias>or<alias>:<id>); - version-safe updates and retrying on a version conflict;
- treating page bodies as storage-format XHTML (no Markdown conversion);
--cqltaking precedence in search;- confirming before creating, overwriting, or deleting content;
- never printing or pasting Personal Access Tokens.
| Code | Meaning |
|---|---|
0 |
Success. |
>= 10 |
Any cfl-level failure (bad URL, auth, network, parse, configuration). |
There are no intermediate command-result codes. The exact value within the
>= 10 range is not stable; assert only on >= 10.
Tokens are stored in plaintext TOML at ~/.config/cfl/credentials (file mode
0600), the same posture as ~/.aws/credentials and ~/.kube/config. The
lookup key is scheme://host[:port] plus an optional context-path prefix, so one
host can front several reverse-proxy-mounted Confluence instances.
make build # build ./bin/cfl
make test # unit + integration tests, race + coverage
make lint # golangci-lint
make fmt # gofmt + goimportsEnd-to-end tests against a real Confluence run under test/e2e/ behind the
e2e build tag; see test/e2e/README.md.
This project uses OpenSpec for change management
(openspec/) and SPEC.md as the engineering constitution.
MIT