@open-mercato/cezar 0.8.0 → 0.9.0-pr470.650
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +109 -14
- package/dist/agent-config/catalog.d.ts +64 -0
- package/dist/agent-config/catalog.js +205 -0
- package/dist/agent-config/catalog.js.map +1 -0
- package/dist/agent-config/files.d.ts +46 -0
- package/dist/agent-config/files.js +119 -0
- package/dist/agent-config/files.js.map +1 -0
- package/dist/agent-config/seed.d.ts +7 -0
- package/dist/agent-config/seed.js +90 -0
- package/dist/agent-config/seed.js.map +1 -0
- package/dist/agent-config/service.d.ts +44 -0
- package/dist/agent-config/service.js +71 -0
- package/dist/agent-config/service.js.map +1 -0
- package/dist/agent-config/validate.d.ts +21 -0
- package/dist/agent-config/validate.js +76 -0
- package/dist/agent-config/validate.js.map +1 -0
- package/dist/config.d.ts +34 -4
- package/dist/config.js +97 -2
- package/dist/config.js.map +1 -1
- package/dist/core/agent-env.js +21 -15
- package/dist/core/agent-env.js.map +1 -1
- package/dist/core/agent-runner.d.ts +16 -6
- package/dist/core/agent-runner.js +15 -3
- package/dist/core/agent-runner.js.map +1 -1
- package/dist/core/ask.d.ts +184 -0
- package/dist/core/ask.js +97 -0
- package/dist/core/ask.js.map +1 -0
- package/dist/core/backend-detect.d.ts +5 -5
- package/dist/core/backend-detect.js +37 -5
- package/dist/core/backend-detect.js.map +1 -1
- package/dist/core/claude-cli-runner.d.ts +17 -2
- package/dist/core/claude-cli-runner.js +8 -3
- package/dist/core/claude-cli-runner.js.map +1 -1
- package/dist/core/claude-ui-mapper.d.ts +9 -0
- package/dist/core/claude-ui-mapper.js +34 -3
- package/dist/core/claude-ui-mapper.js.map +1 -1
- package/dist/core/codex-app-server-runner.d.ts +15 -3
- package/dist/core/codex-app-server-runner.js +158 -127
- package/dist/core/codex-app-server-runner.js.map +1 -1
- package/dist/core/codex-app-server-transport.d.ts +30 -0
- package/dist/core/codex-app-server-transport.js +136 -0
- package/dist/core/codex-app-server-transport.js.map +1 -0
- package/dist/core/codex-model-catalog.d.ts +10 -0
- package/dist/core/codex-model-catalog.js +110 -0
- package/dist/core/codex-model-catalog.js.map +1 -0
- package/dist/core/codex-ui-mapper.d.ts +47 -2
- package/dist/core/codex-ui-mapper.js +247 -29
- package/dist/core/codex-ui-mapper.js.map +1 -1
- package/dist/core/model-identity.d.ts +113 -0
- package/dist/core/model-identity.js +119 -0
- package/dist/core/model-identity.js.map +1 -0
- package/dist/core/model-presets.d.ts +15 -0
- package/dist/core/model-presets.js +43 -0
- package/dist/core/model-presets.js.map +1 -0
- package/dist/core/opencode-server-runner.d.ts +1 -1
- package/dist/core/opencode-server-runner.js +7 -12
- package/dist/core/opencode-server-runner.js.map +1 -1
- package/dist/core/opencode-ui-mapper.d.ts +11 -7
- package/dist/core/opencode-ui-mapper.js +62 -19
- package/dist/core/opencode-ui-mapper.js.map +1 -1
- package/dist/core/pi-runner.d.ts +40 -0
- package/dist/core/pi-runner.js +49 -0
- package/dist/core/pi-runner.js.map +1 -0
- package/dist/core/process-usage.d.ts +3 -0
- package/dist/core/process-usage.js +12 -0
- package/dist/core/process-usage.js.map +1 -1
- package/dist/core/runner-factory.js +3 -0
- package/dist/core/runner-factory.js.map +1 -1
- package/dist/core/runner-model-catalog.d.ts +28 -0
- package/dist/core/runner-model-catalog.js +62 -0
- package/dist/core/runner-model-catalog.js.map +1 -0
- package/dist/core/secret-redaction.js.map +1 -1
- package/dist/core/tool-display.d.ts +2 -2
- package/dist/core/tool-display.js +24 -6
- package/dist/core/tool-display.js.map +1 -1
- package/dist/core/ui-events.d.ts +29 -2
- package/dist/git-worktree.d.ts +37 -4
- package/dist/git-worktree.js +175 -12
- package/dist/git-worktree.js.map +1 -1
- package/dist/handoff.d.ts +2 -2
- package/dist/handoff.js +2 -0
- package/dist/handoff.js.map +1 -1
- package/dist/index.js +55 -5
- package/dist/index.js.map +1 -1
- package/dist/install-as-command.d.ts +1 -1
- package/dist/install-as-command.js.map +1 -1
- package/dist/paths.d.ts +32 -2
- package/dist/paths.js +47 -2
- package/dist/paths.js.map +1 -1
- package/dist/runs/auto-name.js.map +1 -1
- package/dist/runs/retention.js.map +1 -1
- package/dist/runs/review-gate.js.map +1 -1
- package/dist/runs/store.d.ts +154 -60
- package/dist/runs/store.js +103 -19
- package/dist/runs/store.js.map +1 -1
- package/dist/runs/task-markers.d.ts +6 -2
- package/dist/runs/task-markers.js +25 -4
- package/dist/runs/task-markers.js.map +1 -1
- package/dist/runs/ui-event-sink.js.map +1 -1
- package/dist/server/capabilities.d.ts +22 -6
- package/dist/server/capabilities.js +85 -8
- package/dist/server/capabilities.js.map +1 -1
- package/dist/server/checkout.d.ts +128 -0
- package/dist/server/checkout.js +296 -0
- package/dist/server/checkout.js.map +1 -0
- package/dist/server/forge/github.d.ts +281 -10
- package/dist/server/forge/github.js +539 -23
- package/dist/server/forge/github.js.map +1 -1
- package/dist/server/forge/types.d.ts +48 -1
- package/dist/server/fs-browse.d.ts +105 -0
- package/dist/server/fs-browse.js +182 -0
- package/dist/server/fs-browse.js.map +1 -0
- package/dist/server/git-changes.d.ts +12 -3
- package/dist/server/git-changes.js +23 -5
- package/dist/server/git-changes.js.map +1 -1
- package/dist/server/github.d.ts +1 -1
- package/dist/server/open-in-app.d.ts +2 -1
- package/dist/server/open-in-app.js +1 -0
- package/dist/server/open-in-app.js.map +1 -1
- package/dist/server/project-context.d.ts +103 -0
- package/dist/server/project-context.js +153 -0
- package/dist/server/project-context.js.map +1 -0
- package/dist/server/server.d.ts +110 -0
- package/dist/server/server.js +1557 -215
- package/dist/server/server.js.map +1 -1
- package/dist/server/wsl.js.map +1 -1
- package/dist/server-install/platforms/macosx-ngrok.js.map +1 -1
- package/dist/server-install/platforms/ubuntu-vps.js +7 -0
- package/dist/server-install/platforms/ubuntu-vps.js.map +1 -1
- package/dist/server-install/state.js.map +1 -1
- package/dist/server-install/steps.js +6 -2
- package/dist/server-install/steps.js.map +1 -1
- package/dist/server-install/types.d.ts +28 -28
- package/dist/server-install/ui.js.map +1 -1
- package/dist/skills-banner.js.map +1 -1
- package/dist/skills-remote.d.ts +10 -4
- package/dist/skills-remote.js +31 -14
- package/dist/skills-remote.js.map +1 -1
- package/dist/skills.d.ts +29 -0
- package/dist/skills.js +72 -14
- package/dist/skills.js.map +1 -1
- package/dist/todos.d.ts +9 -7
- package/dist/todos.js +38 -19
- package/dist/todos.js.map +1 -1
- package/dist/workflows/run.d.ts +145 -10
- package/dist/workflows/run.js +608 -66
- package/dist/workflows/run.js.map +1 -1
- package/dist/workflows/types.d.ts +42 -42
- package/dist/workflows/types.js +2 -1
- package/dist/workflows/types.js.map +1 -1
- package/dist/workspace/config.d.ts +251 -0
- package/dist/workspace/config.js +162 -0
- package/dist/workspace/config.js.map +1 -0
- package/dist/workspace/migrations.d.ts +45 -0
- package/dist/workspace/migrations.js +132 -0
- package/dist/workspace/migrations.js.map +1 -0
- package/dist/workspace/projects-cli.d.ts +23 -0
- package/dist/workspace/projects-cli.js +105 -0
- package/dist/workspace/projects-cli.js.map +1 -0
- package/dist/workspace/projects.d.ts +81 -0
- package/dist/workspace/projects.js +214 -0
- package/dist/workspace/projects.js.map +1 -0
- package/dist/workspace/semaphore.d.ts +77 -0
- package/dist/workspace/semaphore.js +57 -0
- package/dist/workspace/semaphore.js.map +1 -0
- package/dist/workspace/ui-state.d.ts +23 -0
- package/dist/workspace/ui-state.js +42 -0
- package/dist/workspace/ui-state.js.map +1 -0
- package/package.json +3 -2
- package/scripts/mock-claude.mjs +136 -2
- package/web/dist/assets/arrow-left-CnSdp92h.js +1 -0
- package/web/dist/assets/centered-state-NF79blq7.js +43 -0
- package/web/dist/assets/{chunk-BO2N2NFS-BN7h-q1E.js → chunk-BO2N2NFS-DtrdTCWa.js} +4 -4
- package/web/dist/assets/commit-list-CL8vSWGw.js +1 -0
- package/web/dist/assets/compare-variants-mI-MnhuR.js +1 -0
- package/web/dist/assets/diff-view-Bhfvnnn9.js +6 -0
- package/web/dist/assets/dist-COA_NSud.js +1 -0
- package/web/dist/assets/ellipsis-vertical-CNnSLn5a.js +1 -0
- package/web/dist/assets/{file-tuE95HNX.js → file-D4GBh1ao.js} +1 -1
- package/web/dist/assets/{git-pull-request-BztcgsRZ.js → git-pull-request-DnR8Xh4_.js} +1 -1
- package/web/dist/assets/git-toolbar-rVtFyL4t.js +1 -0
- package/web/dist/assets/github-_t0ceKcO.js +1 -0
- package/web/dist/assets/{highlighted-body-OFNGDK62-CJIHtxqW.js → highlighted-body-OFNGDK62-BuFJwunK.js} +1 -1
- package/web/dist/assets/{image-preview-Ca0UgThg.js → image-preview-DEhW0TFz.js} +1 -1
- package/web/dist/assets/index-BfXFhAGQ.js +30 -0
- package/web/dist/assets/index-Mov2Rrbw.css +2 -0
- package/web/dist/assets/lib-DPEZDBUN.js +1 -0
- package/web/dist/assets/mermaid-GHXKKRXX-CN2zQue0.js +1 -0
- package/web/dist/assets/project-router-CBzGUqvS.js +1 -0
- package/web/dist/assets/{refresh-cw-B4NW5q60.js → refresh-cw-BT8E96Tm.js} +1 -1
- package/web/dist/assets/repo-git-C3dJiX3j.js +1 -0
- package/web/dist/assets/{run-diff-BKpV1kGk.js → run-diff-HEZcHwRr.js} +3 -3
- package/web/dist/assets/run-header-DldbROhB.js +1 -0
- package/web/dist/assets/{search-x-C6xG26dv.js → search-x-BDjaMrzG.js} +1 -1
- package/web/dist/assets/skill-empty-hint-DTUSp6Vl.js +1 -0
- package/web/dist/assets/skills-BIg29xLt.js +1 -0
- package/web/dist/assets/{square-terminal-D9l_Xx7B.js → square-terminal-Ckjjn0vb.js} +1 -1
- package/web/dist/assets/tab-link-DTQ9FBZY.js +1 -0
- package/web/dist/assets/task-changes-DRhQVnFN.js +1 -0
- package/web/dist/assets/task-commits-5StODPII.js +1 -0
- package/web/dist/assets/task-files-BuylxobG.js +2 -0
- package/web/dist/assets/task-thread-DnHR0k7k.js +9 -0
- package/web/dist/assets/textarea-Bj_LCreg.js +1 -0
- package/web/dist/assets/toml-CcmNWLt0.js +1 -0
- package/web/dist/assets/{trash-2-De8Z5Y5-.js → trash-2-DAyZ0VSi.js} +1 -1
- package/web/dist/assets/{triangle-alert-DJUyRhTV.js → triangle-alert-Uy8rokUh.js} +1 -1
- package/web/dist/assets/{upload-yxUM9nk5.js → upload-CDdyPPo_.js} +1 -1
- package/web/dist/assets/use-desktop-TyHAXhMR.js +3 -0
- package/web/dist/assets/utils-B8s5qIcK.js +1 -0
- package/web/dist/assets/workflows-CwDYZLvG.js +11 -0
- package/web/dist/assets/zoomable-image-PEDOqyLQ.js +1 -0
- package/web/dist/index.html +10 -19
- package/web/dist/assets/arrow-left-bEsJ-6jp.js +0 -1
- package/web/dist/assets/centered-state-DzJJpXgm.js +0 -43
- package/web/dist/assets/command-eBgVGftz.js +0 -1
- package/web/dist/assets/compare-variants-DhqGJuHF.js +0 -1
- package/web/dist/assets/dialog-Bf1Cpc3n.js +0 -1
- package/web/dist/assets/diff-view-Cqc8LRkj.js +0 -4
- package/web/dist/assets/dist-M2QYGAcm.js +0 -1
- package/web/dist/assets/dist-ZAIqO3UX.js +0 -1
- package/web/dist/assets/folder-20ea3OVM.js +0 -1
- package/web/dist/assets/git-toolbar-DAlBoSP5.js +0 -1
- package/web/dist/assets/github--sfM4X3j.js +0 -1
- package/web/dist/assets/highlighter-ZVyRi_Cx.js +0 -3
- package/web/dist/assets/index-DlKq8gF4.css +0 -2
- package/web/dist/assets/index-h4gyDJyh.js +0 -11
- package/web/dist/assets/loader-circle-Bnw4ga19.js +0 -1
- package/web/dist/assets/markdown-DxmCMr30.js +0 -1
- package/web/dist/assets/mermaid-GHXKKRXX-DghlHZS5.js +0 -1
- package/web/dist/assets/prompt-templates-CsGr-FXH.js +0 -15
- package/web/dist/assets/repo-git-CHF2oip_.js +0 -1
- package/web/dist/assets/run-header-3CGn6FAM.js +0 -1
- package/web/dist/assets/skill-detail-DsD4-dGp.js +0 -1
- package/web/dist/assets/skill-empty-hint-Cl3ESoPu.js +0 -1
- package/web/dist/assets/skills-5kRcjM1J.js +0 -1
- package/web/dist/assets/skills-B13iqWAN.js +0 -1
- package/web/dist/assets/tab-link-DkVxOa-q.js +0 -1
- package/web/dist/assets/task-changes-DtMiVVwO.js +0 -1
- package/web/dist/assets/task-commits-VbSLn_tW.js +0 -1
- package/web/dist/assets/task-files-qYDBG3Mx.js +0 -2
- package/web/dist/assets/task-thread-BfGERKlB.js +0 -7
- package/web/dist/assets/tasks-table-C5fwJmZ7.js +0 -1
- package/web/dist/assets/use-desktop-6PwFeCW3.js +0 -3
- package/web/dist/assets/utils-CzkLr94M.js +0 -1
- package/web/dist/assets/workflows-CnTcKlfV.js +0 -11
- package/web/dist/assets/x-DaR4ZfE-.js +0 -1
- package/web/dist/assets/zoomable-image-B-76XMUw.js +0 -1
package/README.md
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
**Parallel coding agents orchestrator** — a local cockpit for running and
|
|
6
6
|
tracking AI coding-agent tasks in your repo.
|
|
7
7
|
|
|
8
|
-
Type a task, pick a workflow and an agent — **Claude Code, Codex or OpenCode
|
|
9
|
-
or a mix of them per step** — and watch it work live: steps, tool calls,
|
|
8
|
+
Type a task, pick a workflow and an agent — **Claude Code, Codex or OpenCode
|
|
9
|
+
(experimental), or a mix of them per step** — and watch it work live: steps, tool calls,
|
|
10
10
|
tokens, diffs, in a browser cockpit that runs entirely on your machine.
|
|
11
11
|
Your CLI logins, your `gh`, your files. No accounts, no database, no cloud.
|
|
12
12
|
|
|
@@ -21,7 +21,7 @@ your phone, working your backlog while you're away.
|
|
|
21
21
|
|
|
22
22
|
[](#license)
|
|
23
23
|

|
|
24
|
-

|
|
25
25
|

|
|
26
26
|

|
|
27
27
|
|
|
@@ -106,7 +106,7 @@ and an orchestrator keeps a whole queue of them moving.
|
|
|
106
106
|
Take it over interactively in one click (`claude --resume <id>`), or continue it
|
|
107
107
|
in-process from the cockpit.
|
|
108
108
|
- 🔀 **Locked into one agent vendor.** Most tools wed you to a single CLI. cezar
|
|
109
|
-
drives **Claude Code, Codex and OpenCode** through one runner seam — set a
|
|
109
|
+
drives **Claude Code, Codex and OpenCode (experimental)** through one runner seam — set a
|
|
110
110
|
default, pick a backend per task, or mix them inside one workflow (implement
|
|
111
111
|
with one agent, review with another) — and through **OpenCode** you can point
|
|
112
112
|
a run at **open-source or local models**, not just the big vendors. See
|
|
@@ -251,7 +251,8 @@ Three words, no jargon — **task**, **skill**, **chain**:
|
|
|
251
251
|
Five moves that make the cockpit worth the browser tab:
|
|
252
252
|
|
|
253
253
|
- 🗃️ **Queue + orchestration.** Start as many tasks as you like: cezar runs up to
|
|
254
|
-
`maxParallel` at once (default **2**; a non-git directory
|
|
254
|
+
`maxParallel` at once across every project (default **2**; a non-git directory
|
|
255
|
+
always runs one) and
|
|
255
256
|
holds the rest in a FIFO queue with visible positions (`#1`, `#2`, …). Cancel a
|
|
256
257
|
queued task before it starts; the queue even survives a cockpit restart —
|
|
257
258
|
everything still `queued` is re-enqueued in order. It's the orchestration layer
|
|
@@ -303,6 +304,71 @@ a GitHub page.
|
|
|
303
304
|
|
|
304
305
|
---
|
|
305
306
|
|
|
307
|
+
## Multiple projects, one cockpit
|
|
308
|
+
|
|
309
|
+
One `cezar serve` hosts **every repo you work in**, not just the one you started
|
|
310
|
+
it in. Each repo cezar boots in registers itself in a per-user registry at
|
|
311
|
+
`~/.cezar/config.json` — the workspace file that also holds the global knobs
|
|
312
|
+
(the parallel cap, the memory ceiling, the browse root, and the checkout root). Nothing is added to
|
|
313
|
+
the repo: per-project state stays exactly where it was, in that repo's
|
|
314
|
+
`.ai/cezar/`.
|
|
315
|
+
|
|
316
|
+
Every view is project-scoped:
|
|
317
|
+
|
|
318
|
+
```
|
|
319
|
+
/p/<projectId>/ tasks · git · github · skills · workflows · settings
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
`<projectId>` is a slug derived from the folder name (`my-app`, then `my-app-2`
|
|
323
|
+
on a collision), and `/p/default/…` always means the project cezar was started
|
|
324
|
+
in. The sidebar shows one collapsible group per project — each with its own nav
|
|
325
|
+
and task list — and the new-task composer names the project it will run in.
|
|
326
|
+
|
|
327
|
+
**Adding a project** — the **+** button beside *New task*:
|
|
328
|
+
|
|
329
|
+
- 📂 **Open local folder…** browses from the configured browse root
|
|
330
|
+
(**Settings → Projects**, default `~/`) in a folder picker and
|
|
331
|
+
registers the folder you pick.
|
|
332
|
+
- ⬇️ **Clone from GitHub…** clones with your logged-in `gh` into the checkout
|
|
333
|
+
root (**Settings → Projects**, default `~/cezar/projects`) with live progress,
|
|
334
|
+
then registers the clone. Close the dialog and the clone is killed and its
|
|
335
|
+
partial directory removed.
|
|
336
|
+
|
|
337
|
+
Removing a project (**Settings → Projects**) drops the registry entry only — the
|
|
338
|
+
repo and its `.ai/cezar/` are never touched, so re-adding it later finds all its
|
|
339
|
+
tasks intact. The project cezar is currently serving can't be removed: it
|
|
340
|
+
re-registers itself at the next start.
|
|
341
|
+
|
|
342
|
+
**From the terminal** — the same registry, no cockpit required (handy over ssh):
|
|
343
|
+
|
|
344
|
+
```bash
|
|
345
|
+
cezar projects # list: id, branch or status, path
|
|
346
|
+
cezar projects add ~/code/api # register a folder (defaults to the current repo)
|
|
347
|
+
cezar projects remove api # drop the registry entry; the repo is untouched
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
These read and write `~/.cezar/config.json` directly, so they work with the
|
|
351
|
+
server stopped, and `CEZ_HOME` selects which workspace they operate on.
|
|
352
|
+
|
|
353
|
+
Settings split along the same line: **Agents**, **Worktrees**, **Bookmarklets**,
|
|
354
|
+
**Prompt templates** and **MCP** describe one repo and live under
|
|
355
|
+
`/p/<projectId>/settings`; **Appearance**, **Notifications**, **Resources**,
|
|
356
|
+
**Projects** and **Keyboard** are yours or the machine's and live at
|
|
357
|
+
`/settings/global`.
|
|
358
|
+
|
|
359
|
+
**Old URLs keep working.** Every unprefixed path — `/`, `/tasks/<id>`,
|
|
360
|
+
`/settings`, and the whole `/api/…` surface — still answers exactly as before,
|
|
361
|
+
bound to the project cezar was started in; the cockpit redirects flat paths to
|
|
362
|
+
their `/p/<boot>/…` twin. Existing bookmarks, bookmarklets and scripts need no
|
|
363
|
+
change.
|
|
364
|
+
|
|
365
|
+
> **Hosted cockpit?** The folder picker is confined to the independent browse
|
|
366
|
+
> root. Set `CEZ_BROWSE_ROOT` narrowly before first boot (or save it in
|
|
367
|
+
> **Settings → Projects**) when a remote viewer should not enumerate the host's
|
|
368
|
+
> whole home. Clones continue to use the separate checkout root.
|
|
369
|
+
|
|
370
|
+
---
|
|
371
|
+
|
|
306
372
|
## Workflow format
|
|
307
373
|
|
|
308
374
|
A workflow is a small YAML file in `.ai/cezar/workflows/`:
|
|
@@ -316,7 +382,7 @@ steps:
|
|
|
316
382
|
prompt: "{{task}}"
|
|
317
383
|
skill: project-conventions # optional — from .ai/skills or .ai/cezar/skills
|
|
318
384
|
# model: opus # optional per-step model override
|
|
319
|
-
# runner: codex # optional per-step backend: claude · codex · opencode
|
|
385
|
+
# runner: codex # optional per-step backend: claude · codex · opencode · pi
|
|
320
386
|
# allowedTools: [Read, Edit, Write, Grep, Glob, Bash]
|
|
321
387
|
- id: verify
|
|
322
388
|
name: Verify
|
|
@@ -362,10 +428,13 @@ Useful environment variables:
|
|
|
362
428
|
| `CEZ_DRY_RUN=1` | Use the bundled mock instead of the real `claude` CLI — the entire cockpit works offline, for demos and development. |
|
|
363
429
|
| `CEZ_APPROVAL_GATE=1` | Opt into Claude's interactive approval UI; by default, unapproved tools are denied without interrupting the run. |
|
|
364
430
|
| `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. |
|
|
365
|
-
| `CEZ_AUTOSAVE=1` | Re-enable the periodic (90 s)
|
|
431
|
+
| `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. |
|
|
366
432
|
| `CEZ_CLAUDE_BIN=/path/to/claude` | Override which `claude` binary is used. |
|
|
367
433
|
| `CEZ_CODEX_BIN=/path/to/codex` | Override which `codex` binary is used. |
|
|
368
434
|
| `CEZ_OPENCODE_BIN=/path/to/opencode` | Override which `opencode` binary is used. |
|
|
435
|
+
| `CEZ_PI_BIN=/path/to/pi` | Override which `pi` binary is used. |
|
|
436
|
+
| `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. |
|
|
437
|
+
| `CEZ_PROJECTS_DIR=~/cezar/projects` | Default destination for **Clone from GitHub**. Saved workspace settings override it, and missing directories are created recursively. |
|
|
369
438
|
| `GITHUB_TOKEN` | Fallback for GitHub reads/PRs when `gh` isn't authenticated. |
|
|
370
439
|
| `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. |
|
|
371
440
|
| `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. |
|
|
@@ -373,23 +442,29 @@ Useful environment variables:
|
|
|
373
442
|
| `CEZ_TITLE_UPDATES=0` | Turn off the live task-title refresh (namer re-runs on each turn end). The Settings → Agents toggle overrides this default. |
|
|
374
443
|
| `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`. |
|
|
375
444
|
| `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 → Agents toggle overrides this; autonomous runs always skip it. |
|
|
376
|
-
| `CEZ_NO_BANNER=1` | Skip the `open-mercato/skills` banner on `cezar serve` startup.
|
|
445
|
+
| `CEZ_NO_BANNER=1` | Skip the `open-mercato/skills` banner on `cezar serve` startup. (The cockpit no longer shows a banner — its skills now live on the Skills page's Manage panel — so this env var is the terminal banner's only switch.) |
|
|
377
446
|
|
|
378
447
|
---
|
|
379
448
|
|
|
380
449
|
## Coding agent backends
|
|
381
450
|
|
|
382
451
|
cezar is not married to one vendor. Every agent step runs through a single
|
|
383
|
-
`AgentRunner` seam with
|
|
452
|
+
`AgentRunner` seam with four built-in backends:
|
|
384
453
|
|
|
385
454
|
| Backend | CLI | How cezar drives it | Tool access |
|
|
386
455
|
|---|---|---|---|
|
|
387
456
|
| **Claude Code** (default) | [`claude`](https://github.com/anthropics/claude-code) | Headless `stream-json` mode. | Per-tool `--allowedTools` (`bashAllowlist` scopes `Bash`); `dontAsk` denies unapproved tools without prompting (`CEZ_APPROVAL_GATE=1` → `acceptEdits` + approval UI). |
|
|
388
|
-
| **Codex** | [`codex`](https://github.com/openai/codex) | `codex app-server` — JSON-RPC over stdio, the same transport the Codex IDE extensions use. | Ignores `allowedTools`;
|
|
389
|
-
| **OpenCode** | [`opencode`](https://opencode.ai) | `opencode serve` — a local HTTP server with an SSE event stream. | Ignores `allowedTools` entirely; every permission is auto-approved. |
|
|
457
|
+
| **Codex** | [`codex`](https://github.com/openai/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). |
|
|
458
|
+
| **OpenCode** _(experimental)_ | [`opencode`](https://opencode.ai) | `opencode serve` — a local HTTP server with an SSE event stream. | Ignores `allowedTools` entirely; every permission is auto-approved. |
|
|
459
|
+
| **pi** _(experimental)_ | `pi` | Headless Claude-compatible `stream-json`; models are picked with the `provider/model` convention. | Same as Claude Code — the runner delegates to the Claude CLI driver, so per-tool `--allowedTools` applies and unapproved tools are denied. |
|
|
460
|
+
|
|
461
|
+
> ⚠️ **OpenCode and pi support are experimental.** Both runners work but are less
|
|
462
|
+
> battle-tested than the Claude Code and Codex backends, and OpenCode auto-approves
|
|
463
|
+
> every permission (it ignores `allowedTools`). Treat them as previews and expect
|
|
464
|
+
> rough edges.
|
|
390
465
|
|
|
391
466
|
On startup cezar probes which CLIs are installed and the cockpit only offers
|
|
392
|
-
the backends it found — install any one of the
|
|
467
|
+
the backends it found — install any one of the four and you're operational.
|
|
393
468
|
|
|
394
469
|
**Pick a backend at three levels** (most specific wins):
|
|
395
470
|
|
|
@@ -473,9 +548,8 @@ never blocks startup):
|
|
|
473
548
|
// path as `./name`, not a bare `name`. Pin `ref` to a full commit SHA to freeze
|
|
474
549
|
// the source against a moving branch head — cezar verifies it resolves to
|
|
475
550
|
// exactly that commit, and reports it as `team.commit`.
|
|
476
|
-
"maxParallel": 2, // how many tasks may run at once (non-git dirs always run 1)
|
|
477
551
|
"worktreeRetention": 10, // keep the last N finished worktrees on disk; 0 = unlimited (branch always kept)
|
|
478
|
-
"defaultRunner": "claude", // agent backend: "claude" (default) · "codex" · "opencode"
|
|
552
|
+
"defaultRunner": "claude", // agent backend: "claude" (default) · "codex" · "opencode" · "pi"
|
|
479
553
|
"plannerModel": "sonnet", // model the "Plan first" button uses to draft chains
|
|
480
554
|
"baseBranch": "develop" // branch worktrees fork from + PRs target (also settable in the Git tab)
|
|
481
555
|
}
|
|
@@ -484,6 +558,27 @@ never blocks startup):
|
|
|
484
558
|
Run data (`runs.json`, NDJSON event logs, worktrees, `todos.json`) is
|
|
485
559
|
git-ignored automatically; your workflows and skills stay committable.
|
|
486
560
|
|
|
561
|
+
Settings that belong to *you* rather than to a repo — the parallel cap
|
|
562
|
+
(`maxParallel`, default **2**), the per-task memory ceiling and the checkout
|
|
563
|
+
root — live once in `~/.cezar/config.json`, alongside the
|
|
564
|
+
[project registry](#multiple-projects-one-cockpit), and are edited from
|
|
565
|
+
**Settings → Resources** and **Settings → Projects**. A `maxParallel` left over
|
|
566
|
+
in a repo's `.ai/cezar/config.json` is imported into the workspace file the
|
|
567
|
+
first time cezar boots there, and ignored afterwards.
|
|
568
|
+
|
|
569
|
+
### Editing the agents' own config (Settings → Agent config)
|
|
570
|
+
|
|
571
|
+
cezar picks *which* agent runs; **Settings → Agent config** lets you edit *how* it
|
|
572
|
+
behaves — the raw config files Claude, Codex and OpenCode read for settings,
|
|
573
|
+
MCP, and memory. In the multi-project cockpit the section is project-scoped:
|
|
574
|
+
repo-relative files resolve from the selected project's root, while user-scope
|
|
575
|
+
files continue to resolve from the agent's home.
|
|
576
|
+
|
|
577
|
+
Each file keeps its native format and vendor-documented precedence. Tracked
|
|
578
|
+
files reach task worktrees after commit; Claude's gitignored personal layer is
|
|
579
|
+
seeded into each run's worktree. Editing is a local-machine capability, so a
|
|
580
|
+
hosted cockpit (`CEZ_REMOTE=1`) is read-only and never serves home-file contents.
|
|
581
|
+
|
|
487
582
|
---
|
|
488
583
|
|
|
489
584
|
## Local development
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { RunnerId } from '../core/agent-runner.js';
|
|
2
|
+
/**
|
|
3
|
+
* The catalog of coding-agent config files cezar can surface and edit (spec
|
|
4
|
+
* `.ai/specs/2026-07-16-agent-config-files.md`). This file is the ONLY place
|
|
5
|
+
* vendor knowledge lives: where each agent keeps its files, at which scope, in
|
|
6
|
+
* what format, and — the load-bearing part — the vendor's OWN documented
|
|
7
|
+
* precedence, quoted so a UI label never claims a merge cezar does not perform.
|
|
8
|
+
*
|
|
9
|
+
* Hardcoding is the design. cezar's value here is knowing where the files are
|
|
10
|
+
* and what the docs say; an unknown file is not shown rather than guessed at.
|
|
11
|
+
* A raw editor cannot drift on a vendor's *schema*; it can drift on *paths and
|
|
12
|
+
* precedence strings*, so every entry carries a `docsUrl` and this table is the
|
|
13
|
+
* single maintenance surface. Facts verified against primary docs 2026-07-16.
|
|
14
|
+
*/
|
|
15
|
+
export type ConfigFormat = 'json' | 'jsonc' | 'toml' | 'markdown';
|
|
16
|
+
export type ConfigScope = 'user' | 'project' | 'local';
|
|
17
|
+
/** `settings` = behavior knobs; `memory` = instruction/markdown; `mcp` = a dedicated MCP file. */
|
|
18
|
+
export type ConfigKind = 'settings' | 'memory' | 'mcp';
|
|
19
|
+
/**
|
|
20
|
+
* Git status *by convention* — it drives the honest label, it is not read from
|
|
21
|
+
* git. The seed path re-checks with `git check-ignore` before trusting it.
|
|
22
|
+
*/
|
|
23
|
+
export type ConfigTracked = 'tracked' | 'gitignored' | 'outside-repo';
|
|
24
|
+
/** Resolved home directories per agent, injected so the catalog stays pure and testable. */
|
|
25
|
+
export interface AgentHomePaths {
|
|
26
|
+
/** `~/.claude` */
|
|
27
|
+
claude: string;
|
|
28
|
+
/** `$CODEX_HOME` or `~/.codex` */
|
|
29
|
+
codex: string;
|
|
30
|
+
/** `$XDG_CONFIG_HOME/opencode` or `~/.config/opencode` */
|
|
31
|
+
opencodeConfig: string;
|
|
32
|
+
}
|
|
33
|
+
export interface ConfigFileDef {
|
|
34
|
+
/** Stable, opaque, URL-safe. The ONLY thing a client may name (traversal-proof). */
|
|
35
|
+
id: string;
|
|
36
|
+
/** Every runner that reads this file. `<repo>/AGENTS.md` is one file, two readers. */
|
|
37
|
+
runners: RunnerId[];
|
|
38
|
+
kind: ConfigKind;
|
|
39
|
+
scope: ConfigScope;
|
|
40
|
+
/** Absolute path, resolved per request so `$CODEX_HOME`/`$XDG_CONFIG_HOME` are honoured. */
|
|
41
|
+
resolve: (repoRoot: string, home: AgentHomePaths) => string;
|
|
42
|
+
/** What the user sees, e.g. `~/.claude/settings.json`, `.claude/settings.local.json`. */
|
|
43
|
+
label: string;
|
|
44
|
+
format: ConfigFormat;
|
|
45
|
+
tracked: ConfigTracked;
|
|
46
|
+
/** True only for Claude's gitignored personal layer — the files seeded into a run's worktree. */
|
|
47
|
+
seeded?: boolean;
|
|
48
|
+
/** True when this file holds MCP server definitions (drives the MCP section's filter). */
|
|
49
|
+
holdsMcp?: boolean;
|
|
50
|
+
/** VERBATIM from the vendor docs. Never computed, never generic. */
|
|
51
|
+
precedence: string;
|
|
52
|
+
/** Documented mid-run reload behaviour, or undefined when the vendor is silent. */
|
|
53
|
+
hotReload?: string;
|
|
54
|
+
docsUrl: string;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The table. Order is presentation order: per runner, then user → project →
|
|
58
|
+
* local so each scope ladder reads top (broad) to bottom (specific).
|
|
59
|
+
*/
|
|
60
|
+
export declare const CONFIG_FILES: ConfigFileDef[];
|
|
61
|
+
/** The whole catalog. */
|
|
62
|
+
export declare function listConfigFiles(): ConfigFileDef[];
|
|
63
|
+
/** Look up one entry by its stable id, or undefined when the id is unknown. */
|
|
64
|
+
export declare function findConfigFile(id: string): ConfigFileDef | undefined;
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
const CLAUDE_SETTINGS_DOCS = 'https://code.claude.com/docs/en/settings';
|
|
3
|
+
const CLAUDE_MEMORY_DOCS = 'https://code.claude.com/docs/en/memory';
|
|
4
|
+
const CLAUDE_MCP_DOCS = 'https://code.claude.com/docs/en/mcp';
|
|
5
|
+
const CODEX_CONFIG_DOCS = 'https://developers.openai.com/codex/config-reference';
|
|
6
|
+
const CODEX_AGENTS_DOCS = 'https://developers.openai.com/codex/guides/agents-md';
|
|
7
|
+
const OPENCODE_CONFIG_DOCS = 'https://opencode.ai/docs/config/';
|
|
8
|
+
const OPENCODE_RULES_DOCS = 'https://opencode.ai/docs/rules/';
|
|
9
|
+
/**
|
|
10
|
+
* The table. Order is presentation order: per runner, then user → project →
|
|
11
|
+
* local so each scope ladder reads top (broad) to bottom (specific).
|
|
12
|
+
*/
|
|
13
|
+
export const CONFIG_FILES = [
|
|
14
|
+
// ---- Claude Code ----
|
|
15
|
+
{
|
|
16
|
+
id: 'claude.user.settings',
|
|
17
|
+
runners: ['claude'],
|
|
18
|
+
kind: 'settings',
|
|
19
|
+
scope: 'user',
|
|
20
|
+
resolve: (_repo, home) => join(home.claude, 'settings.json'),
|
|
21
|
+
label: '~/.claude/settings.json',
|
|
22
|
+
format: 'json',
|
|
23
|
+
tracked: 'outside-repo',
|
|
24
|
+
precedence: 'Lowest priority. Project and local settings override it key by key — except permission rules, which merge across all scopes.',
|
|
25
|
+
hotReload: 'Edits to most keys — including permissions and hooks — apply to a running session without a restart.',
|
|
26
|
+
docsUrl: CLAUDE_SETTINGS_DOCS,
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: 'claude.project.settings',
|
|
30
|
+
runners: ['claude'],
|
|
31
|
+
kind: 'settings',
|
|
32
|
+
scope: 'project',
|
|
33
|
+
resolve: (repo) => join(repo, '.claude', 'settings.json'),
|
|
34
|
+
label: '.claude/settings.json',
|
|
35
|
+
format: 'json',
|
|
36
|
+
tracked: 'tracked',
|
|
37
|
+
precedence: 'Overrides user settings key by key (permission rules merge). Local settings override this.',
|
|
38
|
+
hotReload: 'Edits to most keys — including permissions and hooks — apply to a running session without a restart.',
|
|
39
|
+
docsUrl: CLAUDE_SETTINGS_DOCS,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: 'claude.local.settings',
|
|
43
|
+
runners: ['claude'],
|
|
44
|
+
kind: 'settings',
|
|
45
|
+
scope: 'local',
|
|
46
|
+
resolve: (repo) => join(repo, '.claude', 'settings.local.json'),
|
|
47
|
+
label: '.claude/settings.local.json',
|
|
48
|
+
format: 'json',
|
|
49
|
+
tracked: 'gitignored',
|
|
50
|
+
seeded: true,
|
|
51
|
+
precedence: 'Highest of the file scopes — overrides project and user (permission rules merge). Git-ignored; copied into each run’s worktree so it takes effect immediately.',
|
|
52
|
+
hotReload: 'Edits to most keys — including permissions and hooks — apply to a running session without a restart.',
|
|
53
|
+
docsUrl: CLAUDE_SETTINGS_DOCS,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: 'claude.project.mcp',
|
|
57
|
+
runners: ['claude'],
|
|
58
|
+
kind: 'mcp',
|
|
59
|
+
scope: 'project',
|
|
60
|
+
resolve: (repo) => join(repo, '.mcp.json'),
|
|
61
|
+
label: '.mcp.json',
|
|
62
|
+
format: 'json',
|
|
63
|
+
tracked: 'tracked',
|
|
64
|
+
holdsMcp: true,
|
|
65
|
+
precedence: 'Project-scoped MCP servers (key: mcpServers), shared via version control. Each requires approval before use; user- and local-scoped servers live in ~/.claude.json, which cezar does not edit.',
|
|
66
|
+
docsUrl: CLAUDE_MCP_DOCS,
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: 'claude.user.memory',
|
|
70
|
+
runners: ['claude'],
|
|
71
|
+
kind: 'memory',
|
|
72
|
+
scope: 'user',
|
|
73
|
+
resolve: (_repo, home) => join(home.claude, 'CLAUDE.md'),
|
|
74
|
+
label: '~/.claude/CLAUDE.md',
|
|
75
|
+
format: 'markdown',
|
|
76
|
+
tracked: 'outside-repo',
|
|
77
|
+
precedence: 'Not overridden — every CLAUDE.md that loads is concatenated, this user file first.',
|
|
78
|
+
docsUrl: CLAUDE_MEMORY_DOCS,
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: 'claude.project.memory',
|
|
82
|
+
runners: ['claude'],
|
|
83
|
+
kind: 'memory',
|
|
84
|
+
scope: 'project',
|
|
85
|
+
resolve: (repo) => join(repo, 'CLAUDE.md'),
|
|
86
|
+
label: 'CLAUDE.md',
|
|
87
|
+
format: 'markdown',
|
|
88
|
+
tracked: 'tracked',
|
|
89
|
+
precedence: 'Concatenated after the user file, not replacing it. Claude does not read AGENTS.md — import it here with @AGENTS.md. Runs read the committed copy.',
|
|
90
|
+
docsUrl: CLAUDE_MEMORY_DOCS,
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: 'claude.local.memory',
|
|
94
|
+
runners: ['claude'],
|
|
95
|
+
kind: 'memory',
|
|
96
|
+
scope: 'local',
|
|
97
|
+
resolve: (repo) => join(repo, 'CLAUDE.local.md'),
|
|
98
|
+
label: 'CLAUDE.local.md',
|
|
99
|
+
format: 'markdown',
|
|
100
|
+
tracked: 'gitignored',
|
|
101
|
+
seeded: true,
|
|
102
|
+
precedence: 'Loads alongside CLAUDE.md, concatenated last. Git-ignored; copied into each run’s worktree so it takes effect immediately.',
|
|
103
|
+
docsUrl: CLAUDE_MEMORY_DOCS,
|
|
104
|
+
},
|
|
105
|
+
// ---- Codex ----
|
|
106
|
+
{
|
|
107
|
+
id: 'codex.user.config',
|
|
108
|
+
runners: ['codex'],
|
|
109
|
+
kind: 'settings',
|
|
110
|
+
scope: 'user',
|
|
111
|
+
resolve: (_repo, home) => join(home.codex, 'config.toml'),
|
|
112
|
+
label: '~/.codex/config.toml',
|
|
113
|
+
format: 'toml',
|
|
114
|
+
tracked: 'outside-repo',
|
|
115
|
+
holdsMcp: true,
|
|
116
|
+
precedence: 'User-level defaults. A trusted project’s .codex/config.toml overrides these; some keys (provider, auth, telemetry) cannot be overridden at project scope. MCP servers live here under [mcp_servers.<id>].',
|
|
117
|
+
docsUrl: CODEX_CONFIG_DOCS,
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
id: 'codex.project.config',
|
|
121
|
+
runners: ['codex'],
|
|
122
|
+
kind: 'settings',
|
|
123
|
+
scope: 'project',
|
|
124
|
+
resolve: (repo) => join(repo, '.codex', 'config.toml'),
|
|
125
|
+
label: '.codex/config.toml',
|
|
126
|
+
format: 'toml',
|
|
127
|
+
tracked: 'tracked',
|
|
128
|
+
holdsMcp: true,
|
|
129
|
+
precedence: 'Applies only in projects you have trusted. Some keys (provider, auth, telemetry) cannot be overridden here. MCP servers go under [mcp_servers.<id>]. Runs read the committed copy.',
|
|
130
|
+
docsUrl: CODEX_CONFIG_DOCS,
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
id: 'codex.user.memory',
|
|
134
|
+
runners: ['codex'],
|
|
135
|
+
kind: 'memory',
|
|
136
|
+
scope: 'user',
|
|
137
|
+
resolve: (_repo, home) => join(home.codex, 'AGENTS.md'),
|
|
138
|
+
label: '~/.codex/AGENTS.md',
|
|
139
|
+
format: 'markdown',
|
|
140
|
+
tracked: 'outside-repo',
|
|
141
|
+
precedence: 'Global instructions, read first (an AGENTS.override.md beside it wins if present). Project AGENTS.md files are concatenated after and override it.',
|
|
142
|
+
docsUrl: CODEX_AGENTS_DOCS,
|
|
143
|
+
},
|
|
144
|
+
// ---- OpenCode ----
|
|
145
|
+
{
|
|
146
|
+
id: 'opencode.user.config',
|
|
147
|
+
runners: ['opencode'],
|
|
148
|
+
kind: 'settings',
|
|
149
|
+
scope: 'user',
|
|
150
|
+
resolve: (_repo, home) => join(home.opencodeConfig, 'opencode.json'),
|
|
151
|
+
label: '~/.config/opencode/opencode.json',
|
|
152
|
+
format: 'jsonc',
|
|
153
|
+
tracked: 'outside-repo',
|
|
154
|
+
holdsMcp: true,
|
|
155
|
+
precedence: 'Global config. Merged with the project config, not replaced — later configs override earlier ones only for conflicting keys. MCP servers live under the "mcp" key.',
|
|
156
|
+
docsUrl: OPENCODE_CONFIG_DOCS,
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
id: 'opencode.project.config',
|
|
160
|
+
runners: ['opencode'],
|
|
161
|
+
kind: 'settings',
|
|
162
|
+
scope: 'project',
|
|
163
|
+
resolve: (repo) => join(repo, 'opencode.json'),
|
|
164
|
+
label: 'opencode.json',
|
|
165
|
+
format: 'jsonc',
|
|
166
|
+
tracked: 'tracked',
|
|
167
|
+
holdsMcp: true,
|
|
168
|
+
precedence: 'Merged over the global config per conflicting key (not a wholesale replace). MCP servers live under the "mcp" key. Runs read the committed copy.',
|
|
169
|
+
docsUrl: OPENCODE_CONFIG_DOCS,
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
id: 'opencode.user.memory',
|
|
173
|
+
runners: ['opencode'],
|
|
174
|
+
kind: 'memory',
|
|
175
|
+
scope: 'user',
|
|
176
|
+
resolve: (_repo, home) => join(home.opencodeConfig, 'AGENTS.md'),
|
|
177
|
+
label: '~/.config/opencode/AGENTS.md',
|
|
178
|
+
format: 'markdown',
|
|
179
|
+
tracked: 'outside-repo',
|
|
180
|
+
precedence: 'Global rules. First match wins across scopes: if a project AGENTS.md exists, this global file is not read at all.',
|
|
181
|
+
docsUrl: OPENCODE_RULES_DOCS,
|
|
182
|
+
},
|
|
183
|
+
// ---- Shared: <repo>/AGENTS.md is read by BOTH Codex and OpenCode ----
|
|
184
|
+
{
|
|
185
|
+
id: 'project.agents',
|
|
186
|
+
runners: ['codex', 'opencode'],
|
|
187
|
+
kind: 'memory',
|
|
188
|
+
scope: 'project',
|
|
189
|
+
resolve: (repo) => join(repo, 'AGENTS.md'),
|
|
190
|
+
label: 'AGENTS.md',
|
|
191
|
+
format: 'markdown',
|
|
192
|
+
tracked: 'tracked',
|
|
193
|
+
precedence: 'Read by Codex and OpenCode (Claude ignores it). Codex concatenates it root-down; OpenCode uses the first match and prefers it over CLAUDE.md. Runs read the committed copy.',
|
|
194
|
+
docsUrl: OPENCODE_RULES_DOCS,
|
|
195
|
+
},
|
|
196
|
+
];
|
|
197
|
+
/** The whole catalog. */
|
|
198
|
+
export function listConfigFiles() {
|
|
199
|
+
return CONFIG_FILES;
|
|
200
|
+
}
|
|
201
|
+
/** Look up one entry by its stable id, or undefined when the id is unknown. */
|
|
202
|
+
export function findConfigFile(id) {
|
|
203
|
+
return CONFIG_FILES.find((f) => f.id === id);
|
|
204
|
+
}
|
|
205
|
+
//# sourceMappingURL=catalog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog.js","sourceRoot":"","sources":["../../src/agent-config/catalog.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type ConfigFileDef } from './catalog.js';
|
|
2
|
+
/**
|
|
3
|
+
* Read and write the coding agents' own config files, addressed by catalog id
|
|
4
|
+
* (never by a client-supplied path, so traversal is impossible by
|
|
5
|
+
* construction). Every function degrades — a missing file is "absent", an
|
|
6
|
+
* unreadable one is an honest error — and none throw. Writes validate first,
|
|
7
|
+
* refuse a stale overwrite via a content hash, write atomically through
|
|
8
|
+
* symlinks, and never touch a byte the user did not type.
|
|
9
|
+
*/
|
|
10
|
+
/** sha256 of the exact file bytes. mtime is coarse and lies across filesystems. */
|
|
11
|
+
declare function hashBytes(content: string): string;
|
|
12
|
+
export interface ReadResult {
|
|
13
|
+
id: string;
|
|
14
|
+
path: string;
|
|
15
|
+
exists: boolean;
|
|
16
|
+
content: string;
|
|
17
|
+
/** sha256 of the bytes, or null when the file does not exist. */
|
|
18
|
+
version: string | null;
|
|
19
|
+
}
|
|
20
|
+
export type WriteOutcome = {
|
|
21
|
+
ok: true;
|
|
22
|
+
read: ReadResult;
|
|
23
|
+
} | {
|
|
24
|
+
ok: false;
|
|
25
|
+
status: 400 | 409 | 500;
|
|
26
|
+
error: string;
|
|
27
|
+
};
|
|
28
|
+
declare function resolvePath(def: ConfigFileDef, repoRoot: string, env: NodeJS.ProcessEnv): string;
|
|
29
|
+
/** Read a config file by id. Unknown id → null; absent file → exists:false; unreadable → thrown-free error string via `error`. */
|
|
30
|
+
export declare function readConfigFile(id: string, repoRoot: string, env?: NodeJS.ProcessEnv): Promise<ReadResult | {
|
|
31
|
+
error: string;
|
|
32
|
+
} | null>;
|
|
33
|
+
/**
|
|
34
|
+
* Write a config file by id. Validates the content against the file's format,
|
|
35
|
+
* refuses when `version` does not match what is on disk (stale / lost-update),
|
|
36
|
+
* creates the parent dir on demand, and writes atomically through any symlink
|
|
37
|
+
* rather than replacing the link. `version: null` means "I expect no file to
|
|
38
|
+
* exist yet" — the create path.
|
|
39
|
+
*/
|
|
40
|
+
export declare function writeConfigFile(id: string, content: string, version: string | null, repoRoot: string, env?: NodeJS.ProcessEnv): Promise<WriteOutcome | null>;
|
|
41
|
+
/** Whether a path currently exists (used by the listing to report `exists`/`size`). */
|
|
42
|
+
export declare function statConfigPath(path: string): Promise<{
|
|
43
|
+
exists: boolean;
|
|
44
|
+
size: number;
|
|
45
|
+
}>;
|
|
46
|
+
export { resolvePath, hashBytes };
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { mkdir, readFile, realpath, rename, stat, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { dirname } from 'node:path';
|
|
4
|
+
import { agentHomePaths } from '../paths.js';
|
|
5
|
+
import { findConfigFile } from './catalog.js';
|
|
6
|
+
import { validateConfig } from './validate.js';
|
|
7
|
+
/**
|
|
8
|
+
* Read and write the coding agents' own config files, addressed by catalog id
|
|
9
|
+
* (never by a client-supplied path, so traversal is impossible by
|
|
10
|
+
* construction). Every function degrades — a missing file is "absent", an
|
|
11
|
+
* unreadable one is an honest error — and none throw. Writes validate first,
|
|
12
|
+
* refuse a stale overwrite via a content hash, write atomically through
|
|
13
|
+
* symlinks, and never touch a byte the user did not type.
|
|
14
|
+
*/
|
|
15
|
+
/** sha256 of the exact file bytes. mtime is coarse and lies across filesystems. */
|
|
16
|
+
function hashBytes(content) {
|
|
17
|
+
return createHash('sha256').update(content, 'utf8').digest('hex');
|
|
18
|
+
}
|
|
19
|
+
function resolvePath(def, repoRoot, env) {
|
|
20
|
+
return def.resolve(repoRoot, agentHomePaths(env));
|
|
21
|
+
}
|
|
22
|
+
/** Read a config file by id. Unknown id → null; absent file → exists:false; unreadable → thrown-free error string via `error`. */
|
|
23
|
+
export async function readConfigFile(id, repoRoot, env = process.env) {
|
|
24
|
+
const def = findConfigFile(id);
|
|
25
|
+
if (!def)
|
|
26
|
+
return null;
|
|
27
|
+
const path = resolvePath(def, repoRoot, env);
|
|
28
|
+
try {
|
|
29
|
+
const content = await readFile(path, 'utf8');
|
|
30
|
+
return { id, path, exists: true, content, version: hashBytes(content) };
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
const e = err;
|
|
34
|
+
if (e.code === 'ENOENT')
|
|
35
|
+
return { id, path, exists: false, content: '', version: null };
|
|
36
|
+
return { error: e.message };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Write a config file by id. Validates the content against the file's format,
|
|
41
|
+
* refuses when `version` does not match what is on disk (stale / lost-update),
|
|
42
|
+
* creates the parent dir on demand, and writes atomically through any symlink
|
|
43
|
+
* rather than replacing the link. `version: null` means "I expect no file to
|
|
44
|
+
* exist yet" — the create path.
|
|
45
|
+
*/
|
|
46
|
+
export async function writeConfigFile(id, content, version, repoRoot, env = process.env) {
|
|
47
|
+
const def = findConfigFile(id);
|
|
48
|
+
if (!def)
|
|
49
|
+
return null;
|
|
50
|
+
const check = validateConfig(content, def.format);
|
|
51
|
+
if (!check.ok)
|
|
52
|
+
return { ok: false, status: 400, error: `Invalid ${def.format}: ${check.error}` };
|
|
53
|
+
const path = resolvePath(def, repoRoot, env);
|
|
54
|
+
// Stale-write guard: the version the caller read must still match disk.
|
|
55
|
+
let current = null;
|
|
56
|
+
try {
|
|
57
|
+
current = await readFile(path, 'utf8');
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
if (err.code !== 'ENOENT') {
|
|
61
|
+
return { ok: false, status: 500, error: err.message };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// Footgun guard: emptying a populated file passes format validation (empty is
|
|
65
|
+
// valid TOML/markdown, and JSON just errors) but silently wipes real config —
|
|
66
|
+
// unrecoverable for the gitignored/home files that aren't in git. Refuse it;
|
|
67
|
+
// deleting a config file is a deliberate act, not a stray select-all-delete.
|
|
68
|
+
if (content.trim() === '' && current !== null && current.trim() !== '') {
|
|
69
|
+
return {
|
|
70
|
+
ok: false,
|
|
71
|
+
status: 400,
|
|
72
|
+
error: 'refusing to overwrite a non-empty config file with empty content — delete the file manually if you mean to remove it',
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
const currentVersion = current === null ? null : hashBytes(current);
|
|
76
|
+
if (currentVersion !== version) {
|
|
77
|
+
return {
|
|
78
|
+
ok: false,
|
|
79
|
+
status: 409,
|
|
80
|
+
error: currentVersion === null
|
|
81
|
+
? 'the file no longer exists on disk — reload before saving'
|
|
82
|
+
: 'the file changed on disk since you opened it — reload before saving',
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
// Resolve the real target so an atomic rename writes THROUGH a symlink
|
|
87
|
+
// (e.g. ~/.claude → a dotfiles repo) instead of replacing the link itself.
|
|
88
|
+
let target = path;
|
|
89
|
+
try {
|
|
90
|
+
target = await realpath(path);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// file/link absent — target stays as the resolved path (the create case)
|
|
94
|
+
}
|
|
95
|
+
await mkdir(dirname(target), { recursive: true });
|
|
96
|
+
// Unique per write (not just per process) so two concurrent saves of the same
|
|
97
|
+
// file can't rename the same tmp path over each other and tear the bytes.
|
|
98
|
+
const tmp = `${target}.cez-tmp-${process.pid}-${randomUUID()}`;
|
|
99
|
+
await writeFile(tmp, content, 'utf8');
|
|
100
|
+
await rename(tmp, target);
|
|
101
|
+
const written = await readFile(target, 'utf8');
|
|
102
|
+
return { ok: true, read: { id, path, exists: true, content: written, version: hashBytes(written) } };
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
return { ok: false, status: 500, error: err.message };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/** Whether a path currently exists (used by the listing to report `exists`/`size`). */
|
|
109
|
+
export async function statConfigPath(path) {
|
|
110
|
+
try {
|
|
111
|
+
const s = await stat(path);
|
|
112
|
+
return { exists: true, size: s.size };
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return { exists: false, size: 0 };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
export { resolvePath, hashBytes };
|
|
119
|
+
//# sourceMappingURL=files.js.map
|