conpub
conpub is an agent-first CLI for publishing local knowledge files to Confluence Cloud.
The local filesystem is the source of truth. Confluence is the viewing and sharing surface.
Commands
All commands emit JSON by default. Use --pretty to format JSON for humans.
# Or use CONPUB_* defaults from the shell environment:
publish --dry-run stages the local publish set and returns the documents that would be published without calling Confluence.
publish --yes performs Confluence writes through the public typub crates from crates.io.
sync --dry-run compares the current local documents with conpub's local publish state and reports create, update, unchanged, and deleted classifications without calling Confluence.
sync --yes publishes only create and update documents, then records successful publishes in the local sync state. It does not read from Confluence and does not delete remote pages for locally deleted files.
sync accepts optional file or directory paths relative to the bound source or configured root. In subset mode it reports only selected documents and does not report global deleted entries.
A deleted entry whose Confluence page id is owned by a live document classifies as superseded: after a local move, provision adopts the remote page by title under the new path, so the old entry points at a page that is still alive. Superseded entries are never archived or deleted remotely — sync and prune drop them from local state without any remote action.
prune reconciles deleted state entries explicitly. Without flags it only drops them from local state, leaving remote pages in place; prune --yes --archive archives the pages first; prune --yes --delete deletes them permanently (a 404 counts as already gone, so reruns are idempotent). --archive and --delete are mutually exclusive. Without --yes, prune reports what it would do and changes nothing.
index writes a persistent local search index. search uses the index only when it is fresh for the current source and document fingerprints; otherwise it falls back to scanning local files.
Page hierarchy
Root-level documents in the bound source are published directly under the configured Confluence parent page.
For non-root directories, add _index.md or index.md. That index document becomes the Confluence parent page for documents in the directory. Nested directories must also have index documents for every ancestor directory.
Example:
projects/cuda-agent/
notes.md
perf/
_index.md
occupancy.md
memory/
_index.md
bandwidth.md
notes.md publishes under the configured parent. perf/_index.md also publishes under the configured parent, perf/occupancy.md publishes under perf/_index.md, and perf/memory/bandwidth.md publishes under perf/memory/_index.md.
publish and sync publish parent index pages before child pages. If you run sync perf/occupancy.md, conpub includes perf/_index.md automatically so the child page has a local parent in the same plan.
publish and sync run serially by default. Use --delay-ms <n> to pause between Confluence publish calls. --concurrency is present for forward compatibility, but values above 1 are rejected until typub publish state can be safely shared across concurrent writes.
Shared publish assets live under the configured KB root:
<root>/_assets/
Reference these files from Markdown or Typst as assets/<name>, for example  or #image("assets/diagram.png"). During staging, conpub maps the root _assets directory into each typub post's assets/ directory. conpub stages only safe asset extensions such as images and PDFs, and skips dotfiles and common key or credential filenames.
Sync fingerprints include the document and the safe shared _assets set. Changing _assets can mark documents as changed because conpub intentionally leaves exact reference parsing to typub. Other Markdown and Typst files are treated as separate documents, not assets. conpub rejects slug collisions before planning, syncing, or publishing.
plan additionally scans each document for assets/<name> references (markdown images/links, typst #image("assets/..."), html src="assets/...") and marks documents whose references are not present in _assets/ as blocked, naming each missing reference. publish and sync refuse such a set locally, before staging or any remote write — an asset placed next to the document instead of under <root>/_assets/ is caught here instead of failing half-way through a remote publish.
plan consults the local publish state (the same view status reports): unblocked documents carry the state-derived action — create, update, unchanged, or deleted — with the reason, the last published Confluence URL when typub status knows the page, and a publishable count plus per-action summary. A clean plan is publishable: 0. plan is entirely local; it takes no lock and writes nothing. Note that publish does not consult this state — it republishes the whole set unconditionally; sync is the incremental verb.
The local sync state is written under a per-target file lock with atomic replacement and is bound to the configured root, base URL, space, and parent page. Multiple project bindings that share the same root and Confluence target share this state. The bound source only limits the current scan and deleted-file detection scope. The state stores only local KB metadata such as fingerprints, titles, slugs, parent paths, and sync timestamps.
Remote Confluence IDs, URLs, and publish status are owned by typub's status database under the generated stage root:
<stage-root>/.typub/status.db
For a real Confluence smoke test, bind a disposable source directory to a disposable parent page and run conpub sync --yes <smoke-file>. conpub intentionally has no default remote delete/archive behavior; deleted local files are reported as deleted so a human can decide with conpub prune (state-only, --archive, or --delete) what should happen remotely.
Use sync --archive-deleted --yes to archive deleted pages whose Confluence page IDs are already known in typub status. This calls Confluence Cloud's POST /wiki/rest/api/content/archive endpoint and removes accepted archived entries from local sync state. It does not search Confluence for pages to archive.
Confluence credentials resolve per field with this precedence:
CONFLUENCE_API_KEY / CONFLUENCE_EMAIL (environment, wins)
[confluence] api_key / email in .conpub.toml (project)
[confluence] api_key / email in ~/.config/conpub/conpub.toml (user)
[]
= "<atlassian-api-token>"
= "you@example.com"
Resolved credentials travel only inside the in-memory typub platform config; they are never written to the stage, and never appear in resolve, plan, status, or bind output. Re-running conpub bind or conpub root preserves an existing [confluence] section.
The --base-url value may include /wiki; conpub normalizes it before passing it to typub.
Configuration
User configuration is stored at:
~/.config/conpub/conpub.toml
Set CONPUB_HOME to override this location, primarily for tests and isolated agent runs.
These environment variables are used as fallbacks when the matching config value or CLI flag is missing:
CONPUB_KB_ROOT
CONPUB_BASE_URL
CONPUB_SPACE
CONPUB_PARENT_ID
Config files and explicit CLI flags take precedence over these environment variables. CONPUB_KB_ROOT can also be used without a user config file, so a project can be bound or resolved in a temporary agent environment after sourcing a shared .env.
Project binding is stored in the current project as:
.conpub.toml
The effective source directory is:
<user root>/<project source>
Information Boundaries
Default JSON output is agent-friendly, not share-ready. It can include local paths such as root, source_abs, stage_root, state_file, config paths, and Confluence target IDs. Redact or summarize raw JSON before pasting it into shared pages or chats.
index stores a persistent local search index under the generated stage root. The index includes full document lines so search can return local context quickly. Do not commit or share generated stage roots, search indexes, sync state, typub status databases, or .env files.
Precise title extraction for Typst and Markdown uses the typst CLI. When typst is unavailable or title evaluation fails, conpub falls back to the filename-derived title.
Release Builds
GitHub Actions builds release artifacts with cargo-zigbuild for:
x86_64-unknown-linux-muslaarch64-unknown-linux-muslx86_64-apple-darwinaarch64-apple-darwinx86_64-pc-windows-gnu
Tag pushes matching v* create a GitHub Release with bare binaries,
per-target archives, and .sha256 checksums.