Skip to content

Latest commit

 

History

History
977 lines (818 loc) · 66.9 KB

reference.md

File metadata and controls

977 lines (818 loc) · 66.9 KB

cezar reference

Everything the README leaves out: the full configuration, every environment variable, agent backends, remote access, multiple projects and local development.


Core concepts

Three words, no jargon — task, skill, chain:

  • 📋 Tasks are the unit of work. Every task is a run: queued → running → review / done / failed / cancelled, with a live event log, per-step token and cost usage, cancel/delete, and — for anything with a diff — a review gate. Attach screenshots, PDFs, .txt or .md files to the task (paperclip, ⌘V or drag-drop; the agent gets each one as a real file on disk), or send follow-up messages into the live session while it works.
  • 📖 Skills are Markdown playbooks. Drop them in .ai/skills/ or .ai/cezar/skills/, or pull them from a shared team skills repo (a bare git clone cached globally in ~/.cache/cez/). A workflow step references one by skill: <name> and its body becomes the agent's extra system prompt — so you shape how the agent reasons without touching code.
  • 🔗 Chains (workflows) stitch steps into a pipeline: agent steps plus shell checks, with bounded onFail retry loops. Write the YAML yourself, build one by drag-ordering skills in the Workflows tab, or press Plan first and let the AI draft a chain for your task that you review, trim and start. The built-in quick-task (one agent step) works with zero setup.

Five moves that make the cockpit worth the browser tab:

  • 🗃️ Queue + orchestration. Start as many tasks as you like: cezar runs up to maxParallel at once across every project (default 2; a non-git directory always runs one) and holds the rest in a FIFO queue with visible positions (#1, #2, …). Cancel a queued task before it starts; the queue even survives a cockpit restart — everything still queued is re-enqueued in order. It's the orchestration layer that turns "one agent at a time" into a backlog that drains itself.
  • 🧠 Memory-aware runs. Each run's whole process tree is sampled (~2 s) for CPU and RSS, and its peak memory is recorded and shown in the task table. Set an optional per-task memory ceiling (memoryLimitMb) and a run that crosses it is paused — freeing its tree so the queue keeps advancing — and resumes on demand. Event logs are append-only NDJSON and streamed rather than re-serialized, and live UI deltas are coalesced so they never hit disk.
  • 🪞 Parallel variants (×2 / ×3). Run the same task as competing agents in separate worktrees, then compare their diffs side by side and pick one — the losers are archived and their worktrees cleaned up.
  • 🧹 Bounded worktree disk. Each task runs in its own full checkout, so a busy cockpit would otherwise grow without limit. cezar keeps only the last worktreeRetention finished worktrees on disk (default 10; 0 = unlimited) and reclaims the rest — directory only, the cez/<id8> branch is always kept, so the work stays recoverable. Settings → Resources shows every worktree's disk use with per-row delete and a Reclaim now button.
  • 🛡️ Review gate. A finished run with changes waits in review. Read the diff, type notes that go straight back into the agent's session, or push a gh pr create --draft. You stay the merge button.
  • 📱 Runs on your coding server, drives from your pocket. The cockpit is a responsive web app streaming over SSE, so the box running cezar can be a VPS, cloud, or dedicated server you never sit in front of. Point a browser — laptop or phone — at it and run an always-on coding team on the move: start tasks, watch them live, and hit the review gate from anywhere.

Cockpit tour

One browser window, with live task updates over Server-Sent Events:

View What's in it
Dashboard Overview shows attention, completed/failed outcomes, median cycle time, project comparisons, live work, enabled automations with next run/check times, and recent results; Usage & cost shows reported usage and trends. Counters open matching tasks. Drag widgets to reorder them, customize optional tiles and export the current view to PDF/CSV; saved layout is shared by browsers using this workspace.
Tasks Every task with its status, live event stream (agent text · tool calls · tool results · pasted/generated screenshots and file attachments), tokens and cost. Continue, cancel, open in terminal (claude --resume), review the diff, or push a draft PR.
All tasks Every registered project's tasks in one table, filtered and grouped by tag, project, status or workflow — see Grouping connected repositories. Appears once a second project is registered.
Inbox Opt-in (CEZ_FOLLOWUPS=1; hidden by default). Follow-ups an agent left behind (todos.json) — one click turns a suggestion into the next task, pre-wired to its suggested skill. Off, agents are never asked to leave follow-ups; each task's own Notes handoff journal is unaffected.
Git Branch, working-tree status, diff vs HEAD, recent commits (click one for its inline patch + GitHub link), and the configurable base branch that worktrees fork from and PRs target. Files browses the whole repository — the tree on the left, the file on the right (syntax-highlighted, images inline, Markdown rendered with a toggle to the source), and a filter box that finds a path as you type. Every file is its own URL (/git/files/<path>), so a view can be refreshed or pasted to a colleague. The tree is git's own view of the repository, so .gitignored build output and node_modules never appear — and a file git does not list cannot be opened, which is what keeps an ignored .env out of reach. Read-only: nothing here edits the checkout.
GitHub Open issues and PRs of the repo's origin, read through your logged-in gh. Hand an issue straight to the agent — pick a workflow and skills, one click runs it. GitHub Enterprise works the same on any domain: a remote counts as GitHub when its host is github.com, *.ghe.com, or one gh is signed in to (gh auth login --hostname <host>, or GH_HOST). A self-hosted server on any other domain needs that login first. github.com and Enterprise projects work side by side; cezar pins the right host per repo, so GH_HOST never has to be exported.
Skills Local skills plus the team skills repo, with a rendered body + prompt preview. Refresh pulls the latest from the remote.
Workflows Build a workflow as a node graph, or let the planner draft one from a description; save it as YAML (a plain stack of skills stays in the portable skills: form), import/export, or delete. Built-ins always come back.
Settings Appearance (dark/light theme, accent, density), agent backends, notifications, and the skills catalog.

The cockpit is a React app served pre-built from the package — npx cezar-run still means no build step and no dev server on your machine — with a dark/light theme, a ⌘K command palette, and bookmarklets that launch a task straight from a GitHub page.


Multiple projects, one cockpit

One cezar serve hosts every repo you work in, not just the one you started it in. Projects live in a per-user registry at ~/.cezar/config.json — the workspace file that also holds the global knobs (the parallel cap, the memory ceiling, the browse root, and the checkout root). Nothing is added to the repo: per-project state stays exactly where it was, in that repo's .ai/cezar/.

Your first run registers the repo you start it in — that is the whole setup. After that the registry is yours to curate: starting cezar somewhere else serves that folder as usual (its own tasks, its own .ai/cezar/, reachable at /p/<slug>/) but neither adds it to the list nor shows it there — a launch folder is where you opened the cockpit from, not a project, so the sidebar keeps showing the projects you chose and a bare / opens the most recent of them. Keeping the folder is the explicit click: Settings → Add project (or cezar projects add from a terminal).

Project views are scoped to their repository; Dashboard at /dashboard, All tasks, and global settings span the workspace:

/p/<projectId>/            tasks · git · github · skills · workflows · settings

<projectId> is a slug derived from the folder name (my-app, then my-app-2 on a collision), and /p/default/… always means the project cezar was started in. The sidebar shows one collapsible group per project — each with its own nav and task list — and the new-task composer names the project it will run in.

Adding a project — the + button beside New task:

  • 📂 Open local folder… browses from the configured browse root (Settings → Projects, default ~/) in a folder picker and registers the folder you pick.
  • ⬇️ Clone from GitHub… clones with your logged-in gh into the checkout root (Settings → Projects, default ~/cezar/projects) with live progress, then registers the clone. Close the dialog and the clone is killed and its partial directory removed.

Removing a project (Settings → Projects) drops the registry entry only — the repo and its .ai/cezar/ are never touched, so re-adding it later finds all its tasks intact. The project cezar is currently serving can't be removed from the cockpit — stop the server and use cezar projects remove <id> instead.

From the terminal — the same registry, no cockpit required (handy over ssh):

cezar projects                    # list: id, branch or status, path, tags
cezar projects add ~/code/api     # register a folder (defaults to the current repo)
cezar projects remove api         # drop the registry entry; the repo is untouched
cezar projects tag api storefront backend   # set the grouping tags (no tags clears them)

These read and write ~/.cezar/config.json directly, so they work with the server stopped, and CEZ_HOME selects which workspace they operate on.

Settings split along the same line: General (the project's folder, its registry facts, its parallel-task ceiling, and Remove), Agents, Instructions, Tasks, Worktrees, Bookmarklets and MCP describe one repo and live under /p/<projectId>/settings; Appearance, Notifications, Resources, Projects and Keyboard are yours or the machine's and live at /settings/global.

Grouping connected repositories: tags and the All tasks page

Work rarely stops at a repo boundary. A storefront is an API, a web app and a design system; a platform is a handful of services plus the infra that runs them. Tags are how you say so, and All tasks is where saying so pays off.

Tag a repo in Settings → Projects: type into the Tags cell on its row and press Enter (comma works too; the × on a chip, or Backspace in an empty field, removes one). The field autocompletes from the tags already used in the workspace — click the field to see them all, arrow keys and Enter to pick — which is what keeps the second repo landing on the first one's spelling instead of inventing store-front next to storefront. Anything not on the list is just typed. A tag is a free-form label — storefront, infra, client-acme — and a project can carry several, because a repo can belong to more than one piece of work. Tags are trimmed, deduplicated case-insensitively (API and api are one tag) and stored in ~/.cezar/config.json beside the rest of the registry, so they are yours and this machine's, never something added to the repo.

All tasks — the top item in the sidebar, /tasks, or ⌘K → All tasks — then shows every registered project's work in one table:

  • Filter by tag, status and workflow. Tags are one-click chips; status and workflow are searchable multi-selects. Every facet ORs inside itself and ANDs across, so "anything running or waiting in storefront or infra" is one set of clicks. Each option carries how many tasks it would leave, so a filter that would empty the table says so before you click it. The search box matches title, project, workflow, branch and tags.
  • Group by tag, project, status or workflow — click the pressed one again to ungroup. Grouping by tag is the reason tags exist: three repos tagged storefront become one section, and a repo tagged twice appears under both — it genuinely belongs to both.

The filters, the grouping and the Active/Archived tab live in the URL, so a filtered view survives a refresh, pastes into a chat, and sits in a bookmark — /tasks?tag=storefront&status=running&group=tag is a link to exactly what you were looking at. Only what you changed shows up: Active is the default, so the Archived view is ?archived=1 and a normal link carries no key for it.

Each row shows every PR and issue it references — a task opened on an issue that landed a PR shows both — plus its cost and live CPU/memory, and can be marked read/unread (the eye) or archived (or restored) right there. Every task title, project name and project group heading links into that project, so the thread, its diff and its worktree are one click away and stay exactly where they were.

There is deliberately no project filter: narrowing this page to one project is that project's own Tasks page, which is a better version of the same answer (live updates, the full column set, the composer). So picking a project leaves for it rather than turning the global view into a worse local one.

Nothing else in cezar reads tags, on purpose: a tag is a lens, not a permission, a queue or a routing rule. Removing one changes what you see and nothing else.

The page reads a workspace-wide index capped at the newest 200 tasks per project — it says so, and names the projects it capped, rather than showing a short list as if it were complete. Older tasks are always in that project's own Tasks page.

Old page URLs keep working. Every unprefixed page path — /, /tasks/<id>, /settings — still answers, bound to the project cezar was started in; the cockpit redirects flat paths to their /p/<boot>/… twin, so existing bookmarks and bookmarklets need no change. The HTTP API is the exception: it moved to /api/v1/… (see the CHANGELOG and The HTTP API), so a script that calls it needs the extra segment.

Hosted cockpit? The folder picker is confined to the independent browse root. Set CEZ_BROWSE_ROOT narrowly before first boot (or save it in Settings → Projects) when a remote viewer should not enumerate the host's whole home. Clones continue to use the separate checkout root.


Workflow format

A workflow is a small YAML file in .ai/cezar/workflows/:

name: fix-and-verify
description: Implement the task, then verify; retry with failing output on red.
steps:
  - id: implement
    name: Implement
    prompt: "{{task}}"
    skill: project-conventions   # optional — from .ai/skills or .ai/cezar/skills
    # model: opus                # optional per-step model override
    # runner: codex              # optional per-step backend: claude · codex · junie · opencode · pi
    # allowedTools: [Read, Edit, Write, Grep, Glob, Bash]
  - id: verify
    name: Verify
    command: "npm test"          # a check step: exit 0 passes
    onFail:
      retry: implement           # loop back to an earlier step…
      max: 2                     # …at most twice
      # retryOn: [1]             # optional: only these exit codes loop back

{{task}} is replaced with the task text you typed. When a check fails and loops back, its failing output — with host secrets, the run's registered secrets and known token shapes redacted — is appended to the retried agent's prompt so the next attempt can see what broke.

A check step runs bash -lc <command> in the task's worktree with the server's environment plus the secrets it may read, which no agent step ever receives. A secret has a scope — the project (Settings → Secrets, cezar secrets …) or the whole workspace (Settings → Global → Secrets, cezar secrets … --workspace) — and audiences: checks (check steps) and/or cezar (cezar's own features). By default a check step gets every checks-audience secret of the workspace and the project, the project's winning on a shared name; a step can bind exactly the ones it needs instead:

- id: e2e
  command: npx e2e run
  secrets:
    - E2E_GATEWAY_KEY                        # as itself
    - { name: STAGING_LOGIN, as: APP_PASSWORD }   # renamed on the way in

A bound name that is not stored, or that checks may not read, is said on the step as a note; secrets: on an agent step is a load-time error. Manage secrets with cezar secrets list | set <NAME> [--audience checks,cezar] | unset <NAME> (set reads the value from stdin; nothing is taken from argv or printed back) or in Settings. Names are [A-Z_][A-Z0-9_]*; CEZ_*, DYLD_* and shell/loader variables (PATH, HOME, NODE_OPTIONS, LD_PRELOAD, …) are refused. This is where an e2e runner's model key belongs: an exported ANTHROPIC_API_KEY or OPENAI_API_KEY also reaches the agents and switches them to API billing.

Values are stored encrypted (AES-256-GCM) under ~/.cezar/secrets/, with one per-machine data key in the OS keychain (macOS Keychain, Linux Secret Service, Windows Credential Manager) through the optional @napi-rs/keyring package. Without a usable keychain — a server, a container, an install that skipped optional packages, or CEZ_SECRETS_KEYCHAIN=0 — the data key is a 0600 file beside the secrets, which makes the store exactly as strong as a private plaintext file; Settings and cezar secrets list say which one is in use. A secret is written for one machine: a reset keychain or a deleted key file leaves the store unreadable (reported on the run as a note) until the secrets are stored again.

cezar also tells a check step about the run it is verifying. These are set by cezar, not settings — a variable the run does not have is absent (never empty), and a value inherited from the server's own environment is replaced:

Variable Value
CEZ_RUN_ID the run id
CEZ_PROJECT_ID the project id
CEZ_WORKTREE absolute path of the task's worktree (the check's cwd)
CEZ_BRANCH the run's branch
CEZ_BASE the recorded fork point
CEZ_STEP_ID this check step's id
CEZ_ATTEMPT 1-based attempt number of this check within the run
CEZ_SHARED_CACHE_DIR ~/.cezar/cache/<projectId> (0700), shared by the project's tasks — e.g. an e2e replay cache
CEZ_GITHUB_REPO, CEZ_GITHUB_NUMBER, CEZ_GITHUB_EVENT runs a GitHub automation launched only
CEZ_PR_HEAD_SHA, CEZ_PR_HEAD_REF, CEZ_PR_BASE_REF runs a checkout: "pr-head" automation launched on a pull request's head only

CEZ_PROJECT_ID is the one name in that table cezar also reads as an input (it is how cez task and cez automation address a cockpit, see backward compatibility §1). A run that knows its own project replaces it; a headless cezar run that does not leaves whatever you exported alone, so a check step can still reach the cockpit you named.

A GitHub automation whose events are all pull_request.* can set "checkout": "pr-head" in its task: each launched run forks its worktree from the matched PR's head (fetched into refs/cezar/pr/<n>) instead of the base branch, so its check steps verify the PR itself. A closed PR or a fork head launches nothing (skipped in the execution log) unless "allowForkHeads": true admits forks — whose checks then run without secrets. Such a run cannot open a draft PR (409). See Verify a pull request.

onFail.retryOn narrows the loop to the exit codes that mean the work is wrong. Omitted, any non-zero code loops back — right for npm test, which exits 1 whether a test failed or the runner could not start. A richer check distinguishes the two: an e2e browser run exits 1 on a failed test, but 2 on a config or credential error and 3 on an engine failure, none of which the agent can fix and each of which would otherwise cost a full agent attempt per retry. retryOn: [1] loops on the verdict and fails the run on the infrastructure, naming the code. See browser and mobile e2e as a verification step for the worked chains, including an independent QA exploration as the gate.

Prefer skills over steps? A workflow can also be written in the portable shorthand — an ordered list of skill names, each becoming one agent step:

name: triage-and-fix
skills: [reproduce, root-cause, implement, self-review]

Workflow graphs (version: 2)

The Workflows page is a node editor: every step is a block on a canvas, and the graph shows exactly how a task will run — which edge a failing check takes, where a loop goes back, what happens when it gives up. It saves a version: 2 file next to the step-list ones (both keep loading; a step-list file opens as a graph):

version: 2
name: implement-test-review
nodes:
  - { id: start, type: start }
  - { id: implement, type: agent, prompt: "{{task}}" }
  - { id: tests, type: check, command: npm test }
  - { id: retry, type: loop, max: 3 }
  - id: fix
    type: agent
    session: { continue: implement }    # reopen implement's session (claude · codex · pi)
    prompt: "Tests failed ({{nodes.retry.iteration}}/{{nodes.retry.max}}): {{nodes.tests.output}}"
  - { id: review, type: agent, skill: code-review, verdicts: [approve, changes] }
  - { id: ok, type: end, status: success }
edges:
  - { from: start, to: implement }
  - { from: implement.done, to: tests }
  - { from: tests.pass, to: review }
  - { from: tests.fail, to: retry }
  - { from: retry.repeat, to: fix }
  - { from: fix.done, to: tests }
  - { from: review.approve, to: ok }
  - { from: review.changes, to: retry }

How a graph runs. One node runs at a time. A node finishes by leaving through exactly one output port; the edge wired to that port is the next step. An unwired port ends the run — failed for failure ports (failed, fail, exhausted, reject, red, timeout), successful otherwise. Every cycle must pass through a loop node, whose counter never resets within a run, so every run is finite.

Node Ports Outputs ({{nodes.<id>.<field>}})
start / end next / — (status: success|failed) —
loop (max) repeat, exhausted iteration, max
agent (prompt, skill, runner, model, session.continue, verdicts; after a fork also review, budgetUsd) done, failed — or one port per verdict summary, verdict, costUsd (a fork branch: runId, status, summary)
check (command) pass, fail exitCode, output
gate.human (message, timeoutMs?) approve, reject (+timeout) comment
ask-user (question, options?, timeoutMs?) answered (+timeout) answer
dispatch (prompt, runner?, budgetUsd?) done, failed runId, status, summary
git.commit (message) done, nothing, failed sha
github.draft-pr (title?) created, failed url, number
github.wait-ci (timeoutMs, pollMs) green, red, timeout, failed (the task has no PR) status
github.pr-comment (body) done, failed —
fork (branches: 2–4) 1 … 4, each wired to an agent runIds
join (wait: all|any) done, failed succeeded, failed
workflow (workflow, prompt?, runner?, budgetUsd?) done, failed runId, status, summary
if (condition) true, false result, value
git.push done, failed —
git.sync-base done, conflict, failed conflicts
github.pr-update (ready?, addLabels?, reviewers?) done, failed —
github.issue-comment (body, issue? — default: the task's issue) done, failed issue
notify.webhook (url, body? — needs CEZ_WORKFLOW_WEBHOOKS=1) done, failed status
  • Fork / join. Each fork port leads to one agent node, and every one of those agents leads into the same join. The agents run at once as subtasks through dispatch — fresh sessions, each in its own worktree forked from the task branch (the fork commits the task's work first), with a budget carved from the parent's, at most four in flight. An agent with review: true runs as a reviewer of the task branch instead of an implementer. wait: any takes the first branch to succeed and cancels the rest. Sub-workflows run as subtasks the same way.
  • if conditions: diff-lines / diff-files (op, value) on the task's own diff, paths-changed (glob: * within a folder, ** across), output (ref: <node>.<field>, op, value — numeric when both sides are numbers) and branch (equals / matches on the base branch).
  • git.sync-base merges the freshest base into the task branch. On a conflict it leaves the merge in progress and leaves by conflict — wire that to an agent to resolve it ({{nodes.<id>.conflicts}} lists the files).
  • Built-in templates — implement-and-verify, implement-review-pr, review-council (three fresh reviewers at once, then a judge that ships or sends it back), fix-ci — sit next to quick-task (still the default). Open one in the editor; it saves as your copy.
  • Restarts resume a walk parked at a gate, question, CI wait or subtask (or mid-check) at that node; an interrupted agent node keeps the usual Continue path.
  • Verdicts. An agent with verdicts ends its turn with CEZ:VERDICT <name>; the graph branches on it. No verdict → one reminder in the same session, then failed.
  • Waits hold no slot. A gate, question, subtask or CI wait gives its maxParallel slot back while it waits, and always has an exit: the answer, Cancel, Finish, its timeout, or a restart.
  • CEZ_DRY_RUN=1 fakes the PR and reports CI green, so a graph can be walked end to end offline. In the editor, Simulate walks it without running anything.
  • Watching a run. A task started from a graph workflow gets a Graph tab: each node's status, iterations and cost, loop counters, and the edges the run took.

How it runs agents

cezar shells out to your locally installed, logged-in agent CLI — your subscription, no API key. With the default Claude Code backend that means headless stream-json mode, tool access via --allowedTools, with unapproved tools denied without prompting (--permission-mode dontAsk) inside the task's worktree — but note the zero-config default list (Read, Edit, Write, Grep, Glob, Bash) grants unrestricted Bash unless a step sets bashAllowlist, so treat a run as having full shell access in its worktree, not a sandboxed allowlist. Set CEZ_APPROVAL_GATE=1 to select Claude's acceptEdits mode, but cezar has no cockpit permission response channel, so approval prompts are not actionable in the cockpit. Codex, Junie and OpenCode are driven through their own native protocols and don't honor allowedTools at all — see Coding agent backends for what each one actually locks down. Nothing runs on a server you don't own.

Useful environment variables:

Var Effect
CEZ_DRY_RUN=1 Use the bundled mock instead of the real claude CLI — the entire cockpit works offline, for demos and development.
CEZ_TELEMETRY=0 Block usage telemetry even after you opted in. Telemetry is opt-in — nothing is sent until you answer yes in the cockpit, Settings → Privacy or cezar telemetry enable (docs/telemetry.md). DO_NOT_TRACK=1 works too. CEZ_TELEMETRY=1 only lifts the CI / test-runner / CEZ_DRY_RUN=1 block; it never grants consent.
CEZ_INSTANCE_ID Internal server-install identity set automatically in generated systemd/launchd services; normally leave unset. It is surfaced additively by /api/v1/health for install verification.
CEZ_AGENT_MODELS_LOCKED=1 Globally lock each runner to the model configured in its native Claude/Codex/OpenCode settings while keeping runner selection available. Exact 1 also delegates authentication and provider enablement to those native agents, so Cezar skips its credential probes and provider-disable preferences. Existing Cezar presets are preserved but ignored, and an environment change requires a restart. The config-file equivalent is "modelsLocked": true in global ~/.cezar/config.json or one repository's .ai/cezar/config.json; config-file locks do not disable provider checks.
CEZ_APPROVAL_GATE=1 Select Claude's acceptEdits mode. Cezar has no cockpit permission response channel; by default, unapproved tools are denied without interrupting the run.
CEZ_FOLLOWUPS=1 Turn on the global follow-up Inbox: agents are asked to leave follow-ups in todos.json when they finish, and the Inbox view appears. Off by default — each task's own Notes handoff journal runs either way.
CEZ_AUTOMATIONS=0 Turn automations off. On by default since the automations redesign (spec .ai/specs/2026-09-14-automations-redesign.md): the Automations view lists GitHub-triggered and scheduled automations, and cezar polls GitHub or fires schedules on each enabled one while it is running — nothing runs until you enable an automation yourself. An agent can also create an automation from a prompt: type "whenever a PR is opened, review it" into New task — pick the built-in create-cezar-automation skill, or just ask; every task's system prompt teaches it to recognise the intent — and the agent writes the definition and creates it through cez automation create (paused, with a cez automation check preview of what it would match), then links the Automations page. Only the exact value 0 opts out (CEZ_AUTOMATIONS=1, the old opt-in, is accepted and changes nothing); opted out, the nav item is absent, the endpoints answer 409, and the workspace scheduler never starts. Read at boot, so restart after changing it; definitions, receipts and high-watermarks are kept either way.
CEZ_WORKFLOW_WEBHOOKS=1 Let a workflow graph's notify.webhook node POST to the URL it names (JSON: text, run, node). Off by default: a workflow file — which can be imported from anywhere — calling out to an arbitrary URL widens exposure, so it is opt-in. Off, the node leaves by failed with a note; under CEZ_DRY_RUN=1 nothing is sent either way.
CEZ_TERMINAL=1 / =0 The task view's interactive terminal (spec .ai/specs/2026-10-07-task-workspace.md §6). Unset it is on for a local cockpit and off on a hosted one (CEZ_REMOTE=1 or a non-loopback bind). 1 allows it there too — without a sign-in in front of cezar this hands a shell on the server to anyone your reverse proxy admits. 0 turns it off everywhere. A platform with no prebuilt PTY binary reports the terminal unavailable with a reason instead of failing.
CEZ_DISPATCH=0 Turn OFF task dispatch, which is on by default: a running task may start other cezar tasks with cez task create — each in its OWN worktree forked off the parent's branch, with a budget carved out of the parent's — and they report back into the parent's session when they settle (cez task report). Children appear nested under their parent in the task lists. Tasks talk through a tree directory (.ai/cezar/dispatch/<root>/: the brief, each task's order/notes/report, an inbox per task) and cezar wakes a parked recipient when a file lands. A --kind review child judges another task's branch and answers with a verdict. ON by default (the owner-approved exception to "cost-widening features are opt-in" — see AGENTS.md), and only the exact value 0 turns it off; with it off the /runs/:id/dispatch and /runs/:id/report routes answer 409, no task is told about the CLI, and the cockpit hides the "Review open PRs" template. A headless cezar run never dispatches either way — there is no cockpit for the CLI to reach, so no task is told about it. Read at boot, so restart after changing it. This is the widest cost-widening flag here — one task can start four more agents — so give dispatching tasks a budget.
CEZ_AUTOSAVE=1 Re-enable the periodic (90 s) autosave commit in task worktrees. Off by default (#471) — turn-end and pre-PR flushes always run, so branches still end complete. Every autosave names its trigger in the commit subject (cezar autosave (periodic) vs (turn end) / (run finalize) / (pre-PR)), so the flushes you keep are distinguishable from the timer you disabled.
CEZ_CLAUDE_BIN=/path/to/claude Override which claude binary is used. Rarely needed: when it is unset, cezar takes claude from PATH, and failing that looks where Claude Code's own installers put it — ~/.local/bin, ~/.claude/local, /opt/homebrew/bin, /usr/local/bin — so an install the launching shell never added to PATH is still found.
CEZ_CODEX_BIN=/path/to/codex Override which codex binary is used.
CEZ_JUNIE_BIN=/path/to/junie Override which junie binary is used.
CEZ_OPENCODE_BIN=/path/to/opencode Override which opencode binary is used.
CEZ_CURSOR_AGENT_BIN=/path/to/agent Override which Cursor Agent CLI (agent) binary is used.
CEZ_PI_BIN=/path/to/pi Override which pi binary is used.
CEZ_COPILOT_BIN=/path/to/copilot Override which GitHub Copilot CLI binary is used.
CLAUDE_CONFIG_DIR, CODEX_HOME The agents' own variables, honoured where the vendor documents one. Setting one moves that agent's default account — the config folder cezar discovers. A second login of the same CLI is deliberately not an environment setting, since one process-wide value cannot differ per project: add it under Settings → Agent accounts and pick it per project.
CEZ_BROWSE_ROOT=~/ Default root for Add project → Open local folder…. The picker cannot navigate above it; a saved workspace value overrides the environment default and must name an existing folder.
CEZ_PROJECTS_DIR=~/cezar/projects Default destination for Clone from GitHub. Saved workspace settings override it, and missing directories are created recursively.
CEZ_SKILLS_AUTO_UPDATE=0 Disable automatic checks and updates for upstream-CLI-tracked Open Mercato skill installations. On by default; a saved global Skills setting overrides this environment default. Checks are delayed, bounded, cached, and non-blocking.
CEZ_UPDATE_CHANNEL=nightly Release channel the self-updater follows: stable (npm latest, the default), nightly, or development (no automatic updates; pick a cezar worktree or an open PR's preview build by hand). A channel saved from the version chip's dialog overrides this seed.
CEZ_SUPERVISED=1 A supervisor relaunches cezar (the desktop shell sets CEZ_DESKTOP=1, which implies it): after an update the process exits with status 75 instead of re-exec'ing itself, so the supervisor starts the new version. Off by default.
CEZ_AUTONOMOUS_DEFAULT=0 Seed the New Task Autonomous default (0 or 1). Without a seed, skills default on and workflows off; a saved global Resources setting overrides it.
CEZ_WORKTREE_DEFAULT=1 Seed the New Task Worktree default (0 or 1). Without a seed, eligible runs default on; a saved global Resources setting overrides it.
CEZ_DISABLE_REPO_LOCK=1 Dangerous escape hatch: allow any run executing in the repository root — an explicit worktree=false run, non-Git degradation, or a continuation whose worktree cannot be restored — to proceed without Cezar’s repository-root lease. Agents can overwrite each other’s files or Git state; isolated worktree runs are unaffected. Off by default; only the exact value 1 enables it.
CEZ_SINGLE_PROJECT=1 Opt into a launch-project-only cockpit: only the exact value 1 enables it. Project add, edit, checkout, folder browsing, and removal are refused and only the launch project is shown. Off by default; stored registry rows are retained, so unsetting it and restarting restores the full multi-project workspace without migration or data loss.
CEZ_HIDE_TOKEN_USAGE=1 Hide raw input/output token counts throughout the browser cockpit while leaving backend-reported cost visible. Only the exact value 1 enables it; telemetry and API payloads are unchanged, and a restart is required after changing it.
CEZ_HIDE_COST=1 Hide backend-reported monetary cost throughout the browser cockpit while leaving raw input/output token counts visible. Only the exact value 1 enables it; telemetry and API payloads are unchanged, and a restart is required after changing it.
CEZ_HIDE_TOKEN_METRICS=1 Legacy master switch that hides both token usage and cost. It takes precedence over the two independent flags; only the exact value 1 enables it, payloads are unchanged, and a restart is required.
GITHUB_TOKEN Fallback for GitHub reads/PRs when gh isn't authenticated.
CEZ_ENV_PASSTHROUGH=A,B Forward these extra host env vars to spawned agents. By default agents get a least-privilege env (safe shell/toolchain vars + the backend's own auth + GITHUB_TOKEN + CEZ_*), not your full environment — use this to add a var an agent needs.
CEZ_AGENT_ENV_FULL=1 Escape hatch: give spawned agents the full host environment (pre-hardening behavior). Off by default; only set it if you understand that this hands every host secret to the agent process.
CEZ_AGENT_TMPDIR=0 Stop giving each task its own temp directory and hand agents the host TMPDIR again (pre-#785 behavior). On by default: every run gets TMPDIR/TEMP/TMP pointing at .ai/cezar/tmp/<task-id>, created and write-probed before the agent spawns and reaped when the run ends, so concurrent tasks stop sharing one directory and a task refuses to start rather than run against a temp directory that silently swallows its shell output (see Troubleshooting below). Only an exact 0 disables it, and it disables the whole thing — the pre-spawn check included, so this stays an escape hatch you can actually take.
CEZ_SECRETS_KEYCHAIN=0 Keep the secret store's data key out of the OS keychain and use the 0600 key file ~/.cezar/secrets/.key only. By default the key goes to the keychain (macOS Keychain, Linux Secret Service, Windows Credential Manager, via the optional @napi-rs/keyring) when one is usable, and to the file otherwise; Settings → Secrets and cezar secrets list say which. Only the exact value 0.
CEZ_REDACT_SECRETS=0 Disable scrubbing of credential values/token shapes from the on-disk state (the NDJSON transcript and the free-text fields of runs.json). On by default; leave it on. Best-effort defense-in-depth, not a guarantee: it catches known token shapes and the values of your own secret-named env vars, so a credential in neither category can still get through.
CEZ_TITLE_UPDATES=0 Turn off the live task-title refresh (namer re-runs on each turn end). The Settings → Tasks toggle overrides this default.
CEZ_AUTONAME=0 Disable ALL LLM task naming (creation + live) — titles stay heuristic (437: /om-auto-review-pr). Under CEZ_DRY_RUN=1 naming is already off unless forced with CEZ_AUTONAME=1.
CEZ_REVIEW_GATE=1 Turn ON the optional diff-first review gate (#489): a successful, non-autonomous run with changes parks at review (Accept / Send back / Draft PR) instead of finishing. Off by default — changed runs settle to done with the diff left in the worktree. Only 1 enables. The Settings → Tasks toggle overrides this; autonomous runs always skip it.
CEZ_NO_BANNER=1 Silence every promo: the open-mercato/skills banner and the star-ask line on cezar serve startup, the sidebar's ⭐ chip, and the star-ask dialog (shown at most three times per browser, only after a task ends well once three have, and only while you are at the screen). It is also the star count's off switch — with it set, GET /api/v1/star-count answers available: false and cezar makes no request to github.com for it. Unset, the count refreshes every 10 seconds while a cockpit tab is visible, paced to GitHub's rate limit (about once a minute unauthenticated; every 10 s with GITHUB_TOKEN/GH_TOKEN set, which is then sent with the request). Dismissing the banner in the cockpit (back when it had one) still silences the terminal half on its own.
VITE_CEZ_API_BASE=http://localhost:4321 Build time only, and only when the cockpit bundle is deployed apart from the service it talks to. Empty (the default) means "the origin that served this page", which is right for both normal cases: the CLI serves the bundle itself, and npm run dev proxies /api to the local service. A deployment that must be configured without a rebuild can put <meta name="cez-api-base" content="…"> in the served HTML instead, which wins over this.

Troubleshooting: the agent's shell returns nothing

Symptom. A task on the Claude backend keeps working, but every shell command comes back with no output and a spurious non-zero exit status — echo hello included. Redirecting into a file inside the worktree still produces the right content, so the commands genuinely run; only the capture is lost. Codex tasks on the same machine are unaffected, because that backend streams over stdio pipes instead of round-tripping a command's output through a temp file.

Diagnosis. The temp directory the agent was given is out of space or out of quota. One line tells you:

echo probe > "${TMPDIR:-/tmp}/probe"   # "Disk quota exceeded" / "No space left on device"
df -i "${TMPDIR:-/tmp}"                # a tmpfs can exhaust inodes long before bytes

Under quota the file is created and the write then fails, so the backend reads back a zero-byte capture file and hands the agent an empty result.

Fix. Since #785 cezar gives each task its own TMPDIR under .ai/cezar/tmp/<task-id> and write-probes it before spawning, so a broken temp directory fails the task with agent temp directory is not writable: … on the task thread instead of corrupting its work. If you see that error, free space on the disk holding the repo. CEZ_AGENT_TMPDIR=0 turns the whole mechanism off — per-task directory and pre-spawn check alike — and hands agents the host TMPDIR again, which is the way out if the check itself is wrong on your platform.


Talking to a running cockpit

The cockpit is a local HTTP server; three CLIs and any script can talk to it while it runs. They find it through CEZ_API_URL — the address the cockpit printed after cockpit → when it started (http://127.0.0.1:4321 unless that port was busy or you passed --port).

From a shell: cez runs, cez task, cez automation

Inside a task, cezar sets CEZ_API_URL, CEZ_PROJECT_ID and CEZ_TASK_ID for the agent. From your own terminal, set the address yourself:

export CEZ_API_URL=http://127.0.0.1:4321
cez runs list                       # this project's tasks, newest first
cez runs list --status running,review --json
cez automation list                 # automations, with state and counts
cez task tree <run id>              # a dispatch tree

On a cockpit that serves several projects, add CEZ_PROJECT_ID=<id> (cezar projects lists the ids); without it every command addresses the project the cockpit was started in.

CEZ_API_URL is never guessed. For an agent, its absence is how the cockpit says dispatch or automations are unavailable, and a fallback would let a task write to whatever cockpit owns the port. So a command run without it exits 2 and explains how to set it (inside a task, it tells the agent to stop and report instead).

cez runs is read-only. Cancel, relaunch and archive from the cockpit, or call the API below.

The HTTP API

  • Everything is under /api/v1. The unversioned /api/* spelling was removed on purpose and answers 404 — GET /api/v1/runs is right, GET /api/runs is not.
  • Project scope. Every project route also answers under /api/v1/p/<projectId>/…; the unscoped path is bound to the project the cockpit was started in. Workspace-wide routes (/api/v1/projects, /api/v1/workspace/…) have no scoped twin.
  • Discovery. GET /api/v1/health answers without the request-origin guard, so it is the call to probe whether a cockpit is up and which version it runs.
  • Routes and shapes. The route inventory is section 2 of BACKWARD_COMPATIBILITY.md; every request and response is a zod schema in packages/contract. The common ones: GET/POST /runs, GET /runs/:id, POST /runs/:id/{cancel,messages,continue,archive}, GET /automations.
  • No built-in auth. The cockpit binds 127.0.0.1 and trusts the machine; see Remote access before exposing it.

Coding agent backends

cezar is not married to one vendor. Every agent step runs through a single AgentRunner seam with five built-in backends:

Backend CLI How cezar drives it Tool access
Claude Code (default) claude Headless stream-json mode. Per-tool --allowedTools (bashAllowlist scopes Bash); dontAsk denies unapproved tools without prompting (CEZ_APPROVAL_GATE=1 selects acceptEdits, but cezar cannot answer permission prompts).
Codex codex codex app-server — JSON-RPC over stdio, the same transport the Codex IDE extensions use. Ignores allowedTools; the default auto mode uses danger-full-access with approvalPolicy: never (CEZ_CODEX_NETWORK=0 opts into the network-blocked workspace-write sandbox).
Junie (experimental) junie junie --acp=true — the real Agent Client Protocol over stdio, the same transport JetBrains IDEs use. Ignores allowedTools; every permission is auto-approved.
OpenCode (experimental) opencode opencode serve — a local HTTP server with an SSE event stream. Ignores allowedTools entirely; every permission is auto-approved.
Cursor agent (Cursor Agent CLI) Headless print mode: agent -p --force --trust --output-format stream-json. Continue is fresh-session in v1 (print mode exits per turn). Its documented output carries no token/cost figures, so a Cursor run's header always reads zero tokens and no cost — that is the backend not reporting usage, not the run using nothing. Ignores allowedTools; --force / --trust auto-approves tool use for unattended runs.
pi (experimental) pi Persistent --mode rpc over JSONL; models are picked with the provider/model convention. Maps allowedTools onto pi's --tools allowlist; a configured bashAllowlist disables Bash because pi cannot express command-prefix rules.

⚠️ Junie, OpenCode and pi support are experimental. All three runners work but are less battle-tested than the Claude Code and Codex backends, and Junie and OpenCode auto-approve every permission (they ignore allowedTools). Treat them as previews and expect rough edges.

On startup cezar probes which CLIs are installed and the cockpit only offers the backends it found — install any one of the five and you're operational.

Models come from your own machine. The model picker does not ship a list of vendor releases that goes stale between cezar versions. For Claude, Codex, Cursor, Junie and OpenCode cezar asks the CLI on your host what it currently offers (Claude Code's list_models control request; the Codex app-server's model/list; Junie's ACP session/new config options; opencode models) and shows exactly that, in that order — so a model your account gained yesterday is selectable today with no cezar release, and one your provider retired stops being offered. Discovery is read-only, costs no tokens, and is cached briefly in memory. If the CLI is missing, logged out, too old or slow, the picker quietly falls back to that runner's built-in entries (auto plus Claude's tier aliases) and says so in a status row; pi, which has no host-local catalog yet, always shows its built-in entries. auto — send no model at all and let the CLI decide — is always available, and a model you pinned by hand stays selectable even when it is no longer advertised.

Pick a backend at three levels (most specific wins):

  1. Config default — "defaultRunner": "codex" in .ai/cezar/config.json.
  2. Per task — the backend picker next to the task box in the cockpit.
  3. Per workflow step — runner: on any step in the YAML.

Per-step overrides are what make mixed-agent strategies a one-liner: implement with one agent, review with another, and let a shell check referee:

name: implement-and-cross-review
steps:
  - id: implement
    name: Implement
    prompt: "{{task}}"
    runner: codex                # one vendor writes the code…
  - id: review
    name: Cross-review
    prompt: "Review the diff produced for: {{task}}. Fix real issues only."
    runner: claude               # …another one reviews it
  - id: verify
    name: Verify
    command: "npm test"
    onFail: { retry: implement, max: 2 }

Parallel variants (×2/×3) of one task share that task's backend — mixing happens per task and per step, not inside a variant group.

The seam is deliberately small: a backend is one class implementing the AgentRunner interface (packages/cezar/src/core/agent-runner.ts) that turns a prompt into a stream of normalized events. Other CLIs — pi, aider, whatever ships next — can slot in the same way.


Remote access (host cezar on a server)

cezar runs on localhost by default. To reach the cockpit from another machine — a shared team box, a VPS, your phone — put an authenticated public front in front of it. The built-in installer does this interactively, per platform strategy, and never escalates silently: every privileged command is printed and verified, and it ends with a real authenticated end-to-end check.

npx cezar-run server-install   --platform ubuntu-vps   # stand it up
npx cezar-run server-deploy    --platform ubuntu-vps   # roll out a new version (reload the service)
npx cezar-run server-uninstall --platform ubuntu-vps   # reverse it

# host a SECOND cockpit for another domain on the same box (ubuntu-vps):
npx cezar-run server-install   --platform ubuntu-vps --domain shop.example.com

On ubuntu-vps a single host can run several independent cockpits — add --domain <host> and each gets its own port, nginx site, login and service; a new domain never resumes or clobbers the first install.

Already running a reverse proxy? If Dokploy, Coolify, Caddy or your own nginx already owns :80/:443, cezar's would fight it for the ports. Install the service only and let your proxy front it:

npx cezar-run server-install --platform ubuntu-vps \
  --external-proxy --domain cezar.example.com --bind-host 172.17.0.1

--bind-host is only needed when the proxy runs in a container (Traefik can't reach the host's loopback); a host-installed proxy uses the 127.0.0.1 default. In this mode your proxy must enforce authentication — cezar has none of its own. Details →

Provider --platform Public front Guide
Ubuntu / Debian VPS ubuntu-vps nginx + Let's Encrypt HTTPS, htpasswd login, systemd Step-by-step →
Ubuntu + existing proxy ubuntu-vps --external-proxy your Dokploy/Traefik/Caddy front; cezar ships the service only Step-by-step →
macOS + ngrok macosx-ngrok ngrok tunnel + --basic-auth, launchd Step-by-step →

See the Remote access overview for how it works and how to redeploy new versions.


Configuration (optional)

Zero config is the default — everything below is opt-in via .ai/cezar/config.json (a missing or invalid file simply uses the defaults, and never blocks startup):

{
  "skillsRepos": [{ "repo": "open-mercato/skills", "ref": "main" }], // team skills; [] disables
  // Team-skill repos are code-trusted: a skill body becomes an agent system prompt.
  // Only owner/name, https/ssh URLs, or local paths (`/abs`, `./rel`, `~/dir`,
  // `C:\dir`) are accepted — no ext::/fd:: transport helpers. Write a relative
  // path as `./name`, not a bare `name`. Pin `ref` to a full commit SHA to freeze
  // the source against a moving branch head — cezar verifies it resolves to
  // exactly that commit, and reports it as `team.commit`.
  "worktreeRetention": 10,   // keep the last N finished worktrees on disk; 0 = unlimited (branch always kept)
  "defaultRunner": "claude", // agent backend: "claude" (default) · "codex" · "junie" · "opencode" · "pi"
  "modelsLocked": true,      // optional: native per-runner model is fixed/read-only; runner stays selectable
  "plannerModel": "sonnet",  // model the "Plan first" button uses to draft chains
  "baseBranch": "develop",   // branch worktrees fork from + PRs target (also settable in the Git tab)
  "branchNaming": "your branch naming preferences"
                             // optional: task branches are named from the first prompt under these
                             // instructions (Settings → Instructions); absent = cez/<id8>
}

Put the same "modelsLocked": true key in ~/.cezar/config.json to apply it to every registered project. When the key is absent or false in both config files (and CEZ_AGENT_MODELS_LOCKED is not 1), each runner's normal model selector uses that runner's discovered model list. While locked, the model is shown read-only and follows the selected runner's native settings; the runner itself remains selectable.

Run data (runs.json, NDJSON event logs, worktrees, todos.json) is git-ignored automatically; your workflows and skills stay committable.

Settings that belong to you rather than to a repo — the parallel cap (maxParallel, default 2), the per-task memory ceiling and the checkout root — live once in ~/.cezar/config.json, alongside the project registry, and are edited from Settings → Resources and Settings → Projects. A maxParallel left over in a repo's .ai/cezar/config.json is imported into the workspace file the first time cezar boots there, and ignored afterwards.

Settings → Resources opens on a live Machine card, and the sidebar carries the same numbers as a one-row glance (CPU, its 60 s sparkline, compact RAM) that links here. Samples arrive every ~2 s: a local cockpit gets them pushed over the host WebSocket topic, a remote one reads GET /api/v1/workspace/host-usage on mount, on a reconnect and when the tab becomes visible again - and follows a first answer that carries no CPU figure with exactly one warm-up read ~2.5 s later. That gap is honest, not a bug: CPU utilization is a delta between two samples, so the first read after an idle period has no window to measure and the readout shows sampling… instead of a number it cannot back. A metric the OS does not expose (swap outside Linux, load on Windows) is omitted rather than printed as a zero. The updated N s ago line is the age of the sample's own server timestamp and ticks every second while the card is mounted, so a cockpit that has stopped receiving data counts up instead of freezing at a fresh-looking value. The 60 s sparkline is local-only: a remote cockpit's route answers are sparse, so it shows the instantaneous bar and no chart rather than plotting minutes as if they were seconds. A remount always re-reads the route (the host query overrides the workspace's five-minute staleTime), so a cached sample can never be stamped as fresh.

Which numbers are effective. The plain process reads host totals. When cezar runs inside a cgroup with a real limit - a Docker --cpus/--cpuset-cpus, a systemd scope, a sandbox - the same payload carries an optional container object with the process's OWN cgroup limits and usage, and the card and the glance show those as the effective values, labelled, with the host totals kept as context (host 64 CPU · 755 GB). A usage-only cgroup emits no container at all, so a normal host reads exactly as it always did. A limit whose value cannot be read shows —; the host figure is never substituted for it. When one is present, cpu/memory labels say (effective) and the load chip pairs with the host core count. One case is deliberately NOT rendered as "no limit": when the probe cannot read the process's cgroup at all (a masked /proc, no cgroupfs mounted) the payload carries cgroupProbe: 'unavailable' and the card says no cgroup information available — the process may well be capped, and host totals are not evidence that it is not.

When the sampler runs. Below md the card's own subscription is the demand (sampling lasts while the card is on screen, exactly as before). On a local desktop the topic is held for the session, because the sidebar glance is always there; the sidebar's machine row carries the staleness clock (stale after ~10 s without a frame). A remote cockpit never opens a socket and keeps reading the route. The host topic is trusted-only: the hub admits a socket whose page origin matches the server's authority, and refuses the subscription (forbidden topic) for anything else - a dev server proxying a localhost page to 127.0.0.1 without Sec-Fetch-Site: same-origin is the common case. The card says so and falls back to the authenticated same-origin route instead of showing sampling… forever.

Editing the agents' own config (Settings → Agent config)

cezar picks which agent runs; Settings → Agent config lets you edit how it behaves — the raw config files Claude, Codex and OpenCode read for settings, MCP, and memory. In the multi-project cockpit the section is project-scoped: repo-relative files resolve from the selected project's root, while user-scope files continue to resolve from the agent's home.

Each file keeps its native format and vendor-documented precedence. Tracked files reach task worktrees after commit; Claude's gitignored personal layer is seeded into each run's worktree. Editing is a local-machine capability, so a hosted cockpit (CEZ_REMOTE=1) is read-only and never serves home-file contents.


Nightly and preview builds

Every night we publish the trunk to npm, so the features landing in the next release are one command away:

npx cezar-run@nightly      # everything merged as of last night

Come build this with us. cezar is shaped by the people who run it on real repos: if you try a nightly and something feels wrong — a workflow that stalls, a diff that reads badly, a runner that should exist — open an issue and tell us. That feedback, early, is worth more than a bug report six weeks after a release, and it is how most of the features here got their final shape.

Know what you're installing. A nightly is verified (typecheck, unit suites, packaged-CLI e2e — the same gate a release runs) but it is not a release: it can be rough, a flag or a screen may change under you, and something occasionally breaks in a way no test caught. Nothing is at risk beyond your patience — every task runs in its own git worktree and cezar never auto-merges — but if you need a boring day, stay on the stable release. Pin a nightly you liked with its exact version (npx cezar-run@0.9.2-nightly.20260813.126 — the cockpit prints the version it booted, and the date in it tells you how old the build is), and drop back to stable any time with a plain npx cezar-run.

Preview builds

Every green CI run also publishes an installable npm snapshot (how it works), so you can try code that has not even merged yet:

npx cezar-run@develop      # current develop head

Every pull request gets its own preview too — the CI bot posts a sticky comment on the PR with the exact pinned version to copy-paste (npx cezar-run@<version>-pr<N>.<run>). Nightlies and previews are all prerelease versions under their own dist-tags; a plain npx cezar-run always resolves to the latest stable release.


Local development

End-to-end, from a fresh clone to a global cezar command you can run in any repo on your machine — no npm publish required.

1. Prerequisites — Node 20+ and git (plus at least one logged-in agent CLI, as in Quick start).

2. Clone & install

git clone https://github.com/open-mercato/cezar.git
cd cezar
npm install

3. Build — compiles the api-client and the server (tsc → packages/cezar/dist/) and the cockpit (vite build → packages/cezar/web/dist/), then runs the pack gate:

npm run build

4. Install as a global command — build + put cezar / cez / cezar-cli / cezar-run on your PATH pointing at this checkout:

npm run install-as-command            # live link (default) — see the change loop below
#   or: npm run install-as-command:global   # self-contained snapshot copy

Now cd into any other repo and run it:

cd ~/some-other-project
cezar            # cockpit for that repo, straight off your checkout
cezar-run --help # same binary; the name matches `npx cezar-run`

5. The change loop

  • Link mode (default): edit source → npm run build → the global command reflects it immediately. No relink needed. (It is a live symlink into this checkout — don't move or delete the checkout while it's linked.)
  • Snapshot mode (:global): re-run npm run install-as-command:global to refresh the installed copy. It survives moving/deleting the checkout.

6. Uninstall

npm run uninstall-as-command    # removes cezar / cez / cezar-cli / cezar-run (either flavor)

7. Troubleshooting

  • cezar: command not found after install → your npm global bin dir isn't on PATH. The script prints the exact dir; add it to your shell profile (export PATH="$(npm prefix -g)/bin:$PATH").
  • EACCES / permission denied → your global prefix is root-owned. Point npm at a user-writable one and retry — never sudo: npm config set prefix ~/.npm-global.
  • Already installed the published @open-mercato/cezar globally? The link/snapshot install replaces it; uninstall-as-command removes ours, and npm i -g @open-mercato/cezar brings the published one back.

Run the desktop app (or cezar) on a worktree

The desktop app and the managed cezar launcher both run whatever ~/.cezar/versions/current points at. A linked checkout puts a worktree there without copying it: the version entry is a symlink to the worktree's packages/cezar, so it runs straight off the worktree (and its own node_modules), and every rebuild is live on the next restart.

A worktree has to be built to run. The cockpit does that for you; the desktop menu and the terminal need it done by hand (npm install && npm run build in the worktree). Pick one of:

  • Cockpit: open the version chip and switch the release channel to Development. The Worktrees tab lists every worktree of every registered cezar repo (task worktrees included), newest commit first, each with its task title, last commit, open PR and build age (a build older than the last commit is marked needs rebuild). The filter matches task, branch, commit subject or #PR. Pick one and choose Switch & restart. A worktree marked not built or needs rebuild offers Build & switch / Rebuild & switch instead: cezar runs npm install (when its dependencies are missing or its lockfile changed) and the server and cockpit builds in the worktree, with the output in the dialog, then switches. Picking the running worktree while it needs a rebuild offers Rebuild & restart. The Pull requests tab lists cezar's open pull requests with the preview build CI published for them (npm dist-tag pr-<N>); pick one and choose Install & restart. A PR without a build (a fork, CI not green yet) is listed but cannot be picked. The development channel never offers updates on its own; switch back to Stable or Nightly to follow releases again.

  • Desktop menu: Cezar ▸ Versions lists linked worktrees by branch (cez/cb28888e — 0.13.0 (worktree)). Clicking one switches and restarts.

  • Terminal:

    cezar link [<dir>] --use   # link the checkout (default: cwd) and make it current
    cezar versions             # installs, links, and cezar worktrees not linked yet
    cezar use 0.13.0           # back to a published release
    cezar unlink <id>          # forget a link (the worktree is untouched)

    The desktop app picks up a terminal switch on its next start, and the Versions menu refreshes when the window regains focus.

Links are hidden from the lists once their worktree is deleted. Switch away before you remove a worktree the app is running from. Cockpits in hosted mode never list worktrees and never link one. Releases older than this feature ignore links, but the desktop menu still switches from them.

In-checkout scripts

npm run dev          # server (API :4321) + Vite dev server, opens the cockpit in the browser
npm run dev:server   # tsx packages/cezar/src/index.ts — the API server alone
npm run dev:web      # Vite dev server alone (proxies /api to :4321)
npm run build        # tsc → packages/cezar/dist/, vite build → packages/cezar/web/dist/, then the pack gate
npm run typecheck    # server + web (tsc --noEmit)
npm test             # vitest — server + cockpit unit suites
npm run test:unit    # node:test — fast core-module tests
npm run test:package # pack/install and exercise the built CLI
npm run test:e2e     # real-browser cockpit suite (agent-browser)

The stack is deliberately small: TypeScript (strict, ESM), Hono + SSE for the server, Zod at every boundary, YAML for workflows, and a React 19 + Vite + Tailwind v4 + shadcn/ui cockpit shipped pre-built in packages/cezar/web/dist/ — the published package carries the built app, so npx users never run a bundler. Every module is meant to be read in one sitting.