A Space is more than where your files and work output live. With workflows, the filesystem itself becomes a running system — schedules, change-driven pipelines, and app deploys, all defined and maintained as files. Session after session with your agents, it accumulates into your — or your team's — own operating system: a home base you keep building on, not a pile of outputs. (Every write is versioned; a single change can build and deploy an app.)
.capora/workflows/ holds the automations
that run it, folders hold the components/apps, save points hold its whole
history. Everything you and your agents work out over many sessions settles
here as a durable, reproducible operating system you (or your team) return to.remote://<space> addresses it with no owner prefix.save writes an immutable version. Diff, restore, or pin a deploy to any of them — history is free.capora.app.json. Deploys to /x/<you>/<app> as a Worker + static assets.Everything below is just the same primitives composed — state = files, logic = a triggered script. If you can express it as "some files + a script run on a schedule / on change / on demand", you can build it.
app deploys a site. Each run is a save point = a dated archive.data/raw/; an on-change workflow transforms it and writes data/out/. "Change X → run Y", loop-guarded.skills/*.sh, playbooks, and workflows across sessions — a reusable, versioned operating system your agent extends and reruns.app init from a template; an on-change workflow builds & deploys on every edit — "change code = ship" with no CI to wire.archive/. Content-addressing dedupes; every run is an immutable version you can restore.on.change input — the sync feeds the pipeline, which feeds the dashboard; your base's skills/ back them all. Not isolated features — a composable, file-driven control plane.The CLI is a single self-contained binary (no runtime needed). The installer detects your OS/arch and puts capora on your PATH.
# installs the capora binary to /usr/local/bin (sudo fallback → ~/.local/bin) curl -fsSL https://capora.cc/install | sh capora help capora update # upgrade later, in place (sudo if needed) # Windows: download https://capora.cc/dl/capora-windows-amd64.exe onto your PATH
Get a token from the web console — sign in, open Connect an agent, and generate one (starts with cpt_). Then hand it to the CLI, easiest first:
# 1 · recommended (esp. for agents): save the console token to ~/.capora/config capora login --token cpt_xxxxxxxxxxxxxxxxxxxx # 2 · or, stateless (CI / ephemeral shells) — nothing written to disk export CAPORA_URL=https://capora.cc export CAPORA_TOKEN=cpt_xxxxxxxxxxxxxxxxxxxx capora whoami # confirm: user + Space + server
capora login opens the browser to authorize and saves the token for you. An agent should skip the browser flow — get a token from the console and use capora login --token … (or the env vars). Env vars always take precedence over the saved config; reads on public Spaces need no token.Capora is a netdisk: your Space is a remote filesystem you operate on directly with S3-style commands. Remote paths are written remote://<space>/<path>; anything without that prefix is a local path. There's no pull/save dance — every write is versioned automatically. (Want git semantics on your own machine? Just use git.)
capora cp ./apps/todo remote:///apps # upload a folder → remote apps/todo/ capora ls remote:// /apps # browse the remote tree capora cp remote:// /apps/todo/app.js ./ # pull one file down # …edit ./app.js with any tool… capora cp ./app.js remote:// /apps/todo/ # push it back → a new save point
Omit the space (remote:/apps) to use your default Space from ~/.capora/config. Directory copies are rsync-style: cp apps/todo remote://…/apps keeps the todo name; a trailing slash on the source (cp apps/todo/ …) copies its contents. cp only adds/overwrites — it never deletes remote files.
| Account | |
| capora login [--code <code>] | sign in (saves a token to ~/.capora/config) |
| capora logout | remove saved credentials |
| capora whoami | current user + current Space + server URL |
| capora space list | list the Spaces you can reach |
| Files (netdisk) | |
| capora cp <src> <dst> [-m <msg>] [--mirror] | copy files/folders up, down, or remote→remote (direction set by which side is remote://). --mirror makes dest match src (deletes dest extras) for dir copies into a Space. |
| capora ls [remote://…] | list one directory level |
| capora tree [remote://…] | list all files under a path |
| capora cat remote://… | print a file to stdout · --at <save> for an old version |
| capora stat remote://… | one file's hash + size |
| capora find <glob> | find paths by glob (e.g. "src/**") |
| capora mkdir remote://… [-m] | create an empty directory |
| capora rm remote://… [-m] | delete file(s) or a folder (recursive) |
| capora mv <a> <b> [-m] | move/rename (zero-byte, content-addressed) |
| History | |
| capora log [remote://…] | save-point history (newest first) |
| capora show <save> | files changed in a save |
| capora diff [a] [b] | changes between two saves |
| capora restore <save> [path] | roll back to a save point |
| Workflows | |
| capora workflows | list workflows + their trigger |
| capora run <name> | trigger a workflow once |
| capora runs | recent run history |
| Apps | |
| capora app templates | list official templates |
| capora app init <name> | scaffold an app + deploy workflow · --template <t> --dir <d> |
| capora app deploy | deploy the app here · -c capora.app.json |
| capora app list | recent deployments |
| Secrets · starter kits | |
| capora secret set <NAME> [value] | set a Space secret (injected into runs) · secret list / secret rm |
| capora starters | list official starter kits |
| capora init <starter> | scaffold a whole-Space starter locally (incl. .capora/) · --dir <d> --force |
Global: --space <space> to target a Space from anywhere. Env: CAPORA_URL, CAPORA_TOKEN, CAPORA_PROJECT. Run capora help for the built-in cheat sheet.
Don't start from zero. A starter is a whole-Space scaffold (workflows + scripts + folders + a README) you pull, review, and upload. init lays it at your current directory including .capora/, so uploading it turns your Space into a running system.
capora starters # list official starter kits capora init daily-brief --dir ./brief cd brief && capora cp . remote://-m "init daily-brief" capora run daily-brief # fetch → summarize (Claude Code) → deploy a site
daily-brief fetches your sources, summarizes them with Claude Code, and deploys a dated brief to /x/<you>/brief (bring your own key: capora secret set ANTHROPIC_API_KEY …; works key-free with a placeholder). Starters ship with manual triggers — arm a cron yourself per the README (no surprise container spend).
A workflow is a file at .capora/workflows/<name>.json: a trigger plus one entry script.
# .capora/workflows/nightly.json { "on": { "schedule": "0 3 * * *" }, // or {"change":"data/**"}, or omit for manual "run": "scripts/nightly.sh" // a script in your Space }
Triggers: manual (capora run), change (on.change path globs, fired on matching saves), and cron (on.schedule, 5-field UTC). Jobs run asynchronously, so long builds are fine.
The runner container ships node/python/git, the capora CLI, and Claude Code (claude) — pre-configured — so a workflow can do AI steps (edit files, review a diff, gather data, generate content) then capora cp the result back. No key setup: runs route through Capora's AI gateway on a per-Space key, all permissions are bypassed by default, and a built-in capora skill lets claude read/write your Space. Just call it headless: claude -p "…".
Agent automations: keep the task's intent as a plain-language brief at .capora/automations/<name>.md and make the workflow's script a one-liner — claude -p "capora session start, then read .capora/automations/<name>.md and do exactly what it says, saving output back with the capora CLI". The agent logs in with a session (required before any write), then does the fetching, generation, error-handling and write-back; since the brief is a Space file, it can even refine it across runs.
Deploying is just a workflow that builds your app and calls capora app deploy. Scaffold one from a template locally, then copy it up:
capora app templates # list official templates capora app init myapp --template vite-react-tailwindcss-shadcn capora cp ./myapp remote:///apps # upload → the change workflow builds + deploys # → live at https://capora.cc/x/ /myapp
app init writes an app folder with a capora.app.json
(declares an assets directory and/or a worker entry), a
deploy.sh, and a change-triggered workflow. On save the workflow builds in a
container and hands the artifact to the platform, which uploads a Worker + real static assets —
your build never holds any cloud keys.
Deploys are workflow-driven. You can't publish from your machine directly — the platform only accepts a deploy from inside a workflow run, so what ships is always a committed Space state (every deployment ties to a save point). Publish by saving (capora cp ./myapp remote://<you>/apps) → the on.change workflow deploys; re-deploy the current save with capora run <deploy-workflow>.
Templates: vite-react-tailwindcss-shadcn, static-basic, worker-api, fullstack. One app can be a static site (assets only), a backend API (worker only), or full-stack (both — a worker for /api/* plus static assets).
A deployed app can read/write its own Space and trigger workflows from inside its Worker — via a zero-dependency SDK. This is what turns an app into an inbound gateway: receive a webhook/request, drop a file (fires an on.change workflow) or call one directly.
# vendor the SDK (single ESM file) into your app, then build/bundle it
curl -fsSL https://capora.cc/sdk/capora.js -o capora.js
// worker.js — inside a deployed Capora app import { createClient } from "./capora.js"; export default { async fetch(req, env) { const capora = createClient(env); // reads injected CAPORA_URL/SPACE/TOKEN + service binding await capora.write("events/hit.json", JSON.stringify({ t: Date.now() })); await capora.run("process"); // trigger a workflow const cfg = await capora.read("config.json"); // read a file return new Response("ok"); } };
On deploy, Capora injects a Space-scoped token (read/write files + trigger workflows — never your secrets or other Spaces) plus a service binding, so createClient(env) just works. API: list · stat · read · readBytes · write · remove · mkdir · workflows · run · runs. TypeScript types at https://capora.cc/sdk/capora.d.ts. Outside an app (Node, etc.) pass createClient({ url, space, token }).
.capora/AGENT.mdA Space is a home base for you and your agents. .capora/AGENT.md is the file every agent reads first when it docks in — the equivalent of CLAUDE.md / AGENTS.md in a repo, but living inside the Space so it travels with the data and is shared across every framework (Claude Code, Codex, pi, …). It is a plain file — nothing special server-side; create it and cp it up.
Put in it whatever an agent needs to work here safely:
capora secret, what .caporaignore excludes, how to restore..capora/skills/<name>/ as SKILL.md + optional scripts/." Agents obey AGENT.md, so you get a Skill system with no new engine — just files plus the agreed contract.# create the contract, then cp it up (the CLI syncs .capora/ as normal content)
capora cp .capora/AGENT.md remote://<space>/.capora/AGENT.md
Layout is per-Space: different Spaces can define entirely different structures — the only rule is that AGENT.md describes the layout that Space actually uses, and agents honor it. When docking, an agent should read .capora/AGENT.md before acting; if it references Skills, list .capora/skills/*/SKILL.md and read each description to decide what applies.
SKILL.md with name + description frontmatter — so accumulated skills are portable: Claude Code can consume them directly, other agents parse the same frontmatter.| GET / | sign in / register (landing) |
| GET /app | your Spaces (home) |
| GET /app/<owner>[/<space>][/…] | Space browser — Files, Workflows, Apps |
| GET /x/<owner>/<app>[/…] | a deployed app (Worker + static assets) |
| GET /site/<owner>/<space>[/path] | serve a Space's files directly (raw) |
| GET /docs | this page |
| GET /docs/cli(.md) | CLI reference as markdown (for agents) |
| GET /dl/capora-<os>-<arch> | prebuilt CLI binary (what /install downloads) |
| GET /install | install script (pipe to sh) |
| GET /health | health check |
Auth is a bearer token: Authorization: Bearer <CAPORA_TOKEN> (or the cap_sess cookie in the browser).
| POST /api/register | {email, password, handle, name} → creates a user + default Space |
| POST /api/login | {email, password} → { token } |
| POST /api/logout | clear session |
| GET /api/me | current user |
| GET /api/me/overview | your Spaces + summary |
| POST /api/me/tokens | mint a CLI/agent access token (PAT) |
| GET /api/me/tokens · POST …/<id>/revoke | list / revoke tokens |
| POST /api/teams · …/<slug>/members | create a team / add members |
| GET /api/owners/<owner>/spaces | list an owner's Spaces |
All under /api/p/<owner>/<space>/. Reads on a public Space need no token.
| Read | |
| GET …/manifest[?path=&at=] | full path → [hash, size] |
| GET …/list?dir=[&at=] | one directory level |
| GET …/stat?path= · …/file?path= | one file's metadata / bytes (Range ok) |
| GET …/find?glob= · …/commits · …/commit/<id> · …/diff?from=&to= | search + history |
| Write (token) | |
| POST …/check-blobs · PUT …/blob/<sha256> | upload content-addressed bytes |
| POST …/save · …/restore | write a save point / roll back |
| Workflows · apps · secrets | |
| GET …/workflows · POST …/workflows/<name>/run | list / trigger |
| GET …/runs · POST …/runs/<id>/finish | run history / container completion callback |
| POST …/deploy · GET …/deployments | deploy an app / deployment history |
| GET/POST/DELETE …/secrets | manage secrets (values encrypted; only names returned) |
Capora runs on TiDB (MySQL-compatible). Every table is cap_-prefixed; IDs are short prefixed strings. File bytes live in R2 (deduped by sha256) — the DB keeps only metadata.
| Accounts & ownership | |
| cap_users | accounts — handle (unique namespace), name, email, password_hash |
| cap_tokens | CLI/agent access tokens (PAT) — stores only the token's sha256 + prefix/label; revoked_at |
| cap_teams | shared namespaces (like an org) — slug, owner_user_id |
| cap_team_members | team membership — team_id, user_id, role (owner|member) |
| cap_spaces | a Space — owner_type(user|team)/owner_id, slug, visibility (public|private), head_commit_id. Namespace = <owner>/<slug> |
| Content-addressed storage | |
| cap_blobs | byte-store metadata — hash (sha256, PK), r2_key, size, content_type. Bytes live in R2, deduped by hash |
| cap_commits | save points — parent_id (linear chain), tree_hash (→ a blob holding the {path:[hash,size]} tree manifest), message, author_id |
| cap_files | working tree (current state) — one row per live file: path/dir/name, blob_hash, size. Updated in place by save |
| Workflows · apps · secrets | |
| cap_runs | one row per workflow run — workflow name, trigger_src (manual|schedule|change), status, note, commit_id (pinned save point) |
| cap_deployments | one row per deploy — app, script_name, status (deployed|failed), url, commit_id |
| cap_secrets | per-Space secrets — name + value_enc (AES-GCM encrypted). The API returns names only; values injected into runs |