Skip to content

Commit 96fd2f6

Browse files
committed
Support multiple agent CLIs (Claude & Codex)
Introduce an agent abstraction to support Claude and OpenAI Codex CLIs. Added internal/agent providers (ClaudeProvider, CodexProvider), resolution and installation checks (Resolve, CheckInstalled), and tests. Wire provider selection via --agent / --agent-path flags, CHIEF_AGENT env vars, and .chief/config.yaml (agent.provider, agent.cliPath). Propagate the provider into TUI, new, edit, and convert flows; convert and fix-json now run through the provider. Added Codex JSONL parser for the loop output and accompanying tests. Updated config struct, docs (README, installation, configuration, troubleshooting), and various command code to use the provider abstraction. Key new files: internal/agent/*.go, internal/cmd/convert.go, internal/loop/codex_parser*.go and tests; updated main.go, cmd handlers, prd conversion wiring and docs to reflect multi-agent support.
1 parent bfe7816 commit 96fd2f6

25 files changed

Lines changed: 1259 additions & 235 deletions

README.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,17 @@ See the [documentation](https://minicodemonkey.github.io/chief/concepts/how-it-w
4444

4545
## Requirements
4646

47-
- [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) installed and authenticated
47+
- **[Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code)** or **[Codex CLI](https://developers.openai.com/codex/cli/reference)** installed and authenticated
48+
49+
Use Claude by default, or configure Codex in `.chief/config.yaml`:
50+
51+
```yaml
52+
agent:
53+
provider: codex
54+
cliPath: /usr/local/bin/codex # optional
55+
```
56+
57+
Or run with `chief --agent codex` or set `CHIEF_AGENT=codex`.
4858

4959
## License
5060

cmd/chief/main.go

Lines changed: 89 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"strings"
99

1010
tea "github.com/charmbracelet/bubbletea"
11+
"github.com/minicodemonkey/chief/internal/agent"
1112
"github.com/minicodemonkey/chief/internal/cmd"
1213
"github.com/minicodemonkey/chief/internal/config"
1314
"github.com/minicodemonkey/chief/internal/git"
@@ -26,6 +27,8 @@ type TUIOptions struct {
2627
Merge bool
2728
Force bool
2829
NoRetry bool
30+
Agent string // --agent claude|codex
31+
AgentPath string // --agent-path
2932
}
3033

3134
func main() {
@@ -147,6 +150,20 @@ func parseTUIFlags() *TUIOptions {
147150
opts.Force = true
148151
case arg == "--no-retry":
149152
opts.NoRetry = true
153+
case arg == "--agent":
154+
if i+1 < len(os.Args) {
155+
i++
156+
opts.Agent = os.Args[i]
157+
}
158+
case strings.HasPrefix(arg, "--agent="):
159+
opts.Agent = strings.TrimPrefix(arg, "--agent=")
160+
case arg == "--agent-path":
161+
if i+1 < len(os.Args) {
162+
i++
163+
opts.AgentPath = os.Args[i]
164+
}
165+
case strings.HasPrefix(arg, "--agent-path="):
166+
opts.AgentPath = strings.TrimPrefix(arg, "--agent-path=")
150167
case arg == "--max-iterations" || arg == "-n":
151168
// Next argument should be the number
152169
if i+1 < len(os.Args) {
@@ -210,14 +227,36 @@ func parseTUIFlags() *TUIOptions {
210227

211228
func runNew() {
212229
opts := cmd.NewOptions{}
213-
214230
// Parse arguments: chief new [name] [context...]
215231
if len(os.Args) > 2 {
216232
opts.Name = os.Args[2]
217233
}
218234
if len(os.Args) > 3 {
219235
opts.Context = strings.Join(os.Args[3:], " ")
220236
}
237+
// Resolve provider (support --agent/--agent-path after "new")
238+
cwd, _ := os.Getwd()
239+
cfg, _ := config.Load(cwd)
240+
flagAgent, flagPath := "", ""
241+
for i := 2; i < len(os.Args); i++ {
242+
switch os.Args[i] {
243+
case "--agent":
244+
if i+1 < len(os.Args) {
245+
i++
246+
flagAgent = os.Args[i]
247+
}
248+
case "--agent-path":
249+
if i+1 < len(os.Args) {
250+
i++
251+
flagPath = os.Args[i]
252+
}
253+
}
254+
}
255+
opts.Provider = agent.Resolve(flagAgent, flagPath, cfg)
256+
if err := agent.CheckInstalled(opts.Provider); err != nil {
257+
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
258+
os.Exit(1)
259+
}
221260

222261
if err := cmd.RunNew(opts); err != nil {
223262
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
@@ -227,22 +266,38 @@ func runNew() {
227266

228267
func runEdit() {
229268
opts := cmd.EditOptions{}
230-
231-
// Parse arguments: chief edit [name] [--merge] [--force]
269+
flagAgent, flagPath := "", ""
270+
// Parse arguments: chief edit [name] [--merge] [--force] [--agent] [--agent-path]
232271
for i := 2; i < len(os.Args); i++ {
233272
arg := os.Args[i]
234273
switch arg {
235274
case "--merge":
236275
opts.Merge = true
237276
case "--force":
238277
opts.Force = true
278+
case "--agent":
279+
if i+1 < len(os.Args) {
280+
i++
281+
flagAgent = os.Args[i]
282+
}
283+
case "--agent-path":
284+
if i+1 < len(os.Args) {
285+
i++
286+
flagPath = os.Args[i]
287+
}
239288
default:
240-
// If not a flag, treat as PRD name (first non-flag arg)
241289
if opts.Name == "" && !strings.HasPrefix(arg, "-") {
242290
opts.Name = arg
243291
}
244292
}
245293
}
294+
cwd, _ := os.Getwd()
295+
cfg, _ := config.Load(cwd)
296+
opts.Provider = agent.Resolve(flagAgent, flagPath, cfg)
297+
if err := agent.CheckInstalled(opts.Provider); err != nil {
298+
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
299+
os.Exit(1)
300+
}
246301

247302
if err := cmd.RunEdit(opts); err != nil {
248303
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
@@ -283,6 +338,15 @@ func runList() {
283338
}
284339

285340
func runTUIWithOptions(opts *TUIOptions) {
341+
// Resolve agent provider early (used for conversion, app, new, edit)
342+
cwd, _ := os.Getwd()
343+
cfg, _ := config.Load(cwd)
344+
provider := agent.Resolve(opts.Agent, opts.AgentPath, cfg)
345+
if err := agent.CheckInstalled(provider); err != nil {
346+
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
347+
os.Exit(1)
348+
}
349+
286350
prdPath := opts.PRDPath
287351

288352
// If no PRD specified, try to find one
@@ -322,7 +386,8 @@ func runTUIWithOptions(opts *TUIOptions) {
322386

323387
// Create the PRD
324388
newOpts := cmd.NewOptions{
325-
Name: result.PRDName,
389+
Name: result.PRDName,
390+
Provider: provider,
326391
}
327392
if err := cmd.RunNew(newOpts); err != nil {
328393
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
@@ -344,19 +409,19 @@ func runTUIWithOptions(opts *TUIOptions) {
344409
fmt.Printf("Warning: failed to check conversion status: %v\n", err)
345410
} else if needsConvert {
346411
fmt.Println("prd.md is newer than prd.json, running conversion...")
347-
convertOpts := prd.ConvertOptions{
348-
PRDDir: prdDir,
349-
Merge: opts.Merge,
350-
Force: opts.Force,
351-
}
352-
if err := prd.Convert(convertOpts); err != nil {
412+
if err := cmd.RunConvertWithOptions(cmd.ConvertOptions{
413+
PRDDir: prdDir,
414+
Merge: opts.Merge,
415+
Force: opts.Force,
416+
Provider: provider,
417+
}); err != nil {
353418
fmt.Printf("Error converting PRD: %v\n", err)
354419
os.Exit(1)
355420
}
356421
fmt.Println("Conversion complete.")
357422
}
358423

359-
app, err := tui.NewAppWithOptions(prdPath, opts.MaxIterations)
424+
app, err := tui.NewAppWithOptions(prdPath, opts.MaxIterations, provider)
360425
if err != nil {
361426
// Check if this is a missing PRD file error
362427
if os.IsNotExist(err) || strings.Contains(err.Error(), "no such file") {
@@ -403,7 +468,8 @@ func runTUIWithOptions(opts *TUIOptions) {
403468
case tui.PostExitInit:
404469
// Run new command then restart TUI
405470
newOpts := cmd.NewOptions{
406-
Name: finalApp.PostExitPRD,
471+
Name: finalApp.PostExitPRD,
472+
Provider: provider,
407473
}
408474
if err := cmd.RunNew(newOpts); err != nil {
409475
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
@@ -416,9 +482,10 @@ func runTUIWithOptions(opts *TUIOptions) {
416482
case tui.PostExitEdit:
417483
// Run edit command then restart TUI
418484
editOpts := cmd.EditOptions{
419-
Name: finalApp.PostExitPRD,
420-
Merge: opts.Merge,
421-
Force: opts.Force,
485+
Name: finalApp.PostExitPRD,
486+
Merge: opts.Merge,
487+
Force: opts.Force,
488+
Provider: provider,
422489
}
423490
if err := cmd.RunEdit(editOpts); err != nil {
424491
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
@@ -447,9 +514,11 @@ Commands:
447514
help Show this help message
448515
449516
Global Options:
517+
--agent <provider> Agent CLI to use: claude (default) or codex
518+
--agent-path <path> Custom path to agent CLI binary
450519
--max-iterations N, -n N Set maximum iterations (default: dynamic)
451-
--no-retry Disable auto-retry on Claude crashes
452-
--verbose Show raw Claude output in log
520+
--no-retry Disable auto-retry on agent crashes
521+
--verbose Show raw agent output in log
453522
--merge Auto-merge progress on conversion conflicts
454523
--force Auto-overwrite on conversion conflicts
455524
--help, -h Show this help message
@@ -470,7 +539,8 @@ Examples:
470539
chief -n 20 Launch with 20 max iterations
471540
chief --max-iterations=5 auth
472541
Launch auth PRD with 5 max iterations
473-
chief --verbose Launch with raw Claude output visible
542+
chief --verbose Launch with raw agent output visible
543+
chief --agent codex Use Codex CLI instead of Claude
474544
chief new Create PRD in .chief/prds/main/
475545
chief new auth Create PRD in .chief/prds/auth/
476546
chief new auth "JWT authentication for REST API"

docs/guide/installation.md

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ Chief is distributed as a single binary with no runtime dependencies. Choose you
88

99
## Prerequisites
1010

11-
Before installing Chief, ensure you have **Claude Code CLI** installed and authenticated:
11+
Chief needs an agent CLI: **Claude Code** (default) or **Codex**. Install at least one and authenticate.
12+
13+
### Option A: Claude Code CLI (default)
1214

1315
::: code-group
1416

@@ -27,8 +29,20 @@ npx @anthropic-ai/claude-code login
2729

2830
:::
2931

30-
::: tip Verify Claude Code Installation
31-
Run `claude --version` to confirm Claude Code is installed. Chief will not work without it.
32+
::: tip Verify Claude Code
33+
Run `claude --version` to confirm Claude Code is installed.
34+
:::
35+
36+
### Option B: Codex CLI
37+
38+
To use [OpenAI Codex CLI](https://developers.openai.com/codex/cli/reference) instead of Claude:
39+
40+
1. Install Codex per the [official reference](https://developers.openai.com/codex/cli/reference).
41+
2. Ensure `codex` is on your PATH, or set `agent.cliPath` in `.chief/config.yaml` (see [Configuration](/reference/configuration#agent)).
42+
3. Run Chief with `chief --agent codex` or set `CHIEF_AGENT=codex`, or set `agent.provider: codex` in `.chief/config.yaml`.
43+
44+
::: tip Verify Codex
45+
Run `codex --version` (or your custom path) to confirm Codex is available.
3246
:::
3347

3448
### Optional: GitHub CLI (`gh`)
@@ -238,11 +252,12 @@ chief --version
238252
# View help
239253
chief --help
240254

241-
# Check that Claude Code is accessible
255+
# Check that your agent CLI is accessible (Claude default, or codex if configured)
242256
claude --version
257+
# or: codex --version
243258
```
244259

245-
Expected output:
260+
Expected output (example with Claude):
246261

247262
```
248263
$ chief --version

docs/reference/configuration.md

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ Chief stores project-level settings in `.chief/config.yaml`. This file is create
1313
### Format
1414

1515
```yaml
16+
agent:
17+
provider: claude # or "codex"
18+
cliPath: "" # optional path to CLI binary
1619
worktree:
1720
setup: "npm install"
1821
onComplete:
@@ -24,6 +27,8 @@ onComplete:
2427
2528
| Key | Type | Default | Description |
2629
|-----|------|---------|-------------|
30+
| `agent.provider` | string | `"claude"` | Agent CLI to use: `claude` or `codex` |
31+
| `agent.cliPath` | string | `""` | Optional path to the agent binary (e.g. `/usr/local/bin/codex`). If empty, Chief uses the provider name from PATH. |
2732
| `worktree.setup` | string | `""` | Shell command to run in new worktrees (e.g., `npm install`, `go mod download`) |
2833
| `onComplete.push` | bool | `false` | Automatically push the branch to remote when a PRD completes |
2934
| `onComplete.createPR` | bool | `false` | Automatically create a pull request when a PRD completes (requires `gh` CLI) |
@@ -83,17 +88,29 @@ These settings are saved to `.chief/config.yaml` and can be changed at any time
8388

8489
| Flag | Description | Default |
8590
|------|-------------|---------|
91+
| `--agent <provider>` | Agent CLI to use: `claude` or `codex` | From config / env / `claude` |
92+
| `--agent-path <path>` | Custom path to the agent CLI binary | From config / env |
8693
| `--max-iterations <n>`, `-n` | Loop iteration limit | Dynamic |
87-
| `--no-retry` | Disable auto-retry on Claude crashes | `false` |
88-
| `--verbose` | Show raw Claude output in log | `false` |
94+
| `--no-retry` | Disable auto-retry on agent crashes | `false` |
95+
| `--verbose` | Show raw agent output in log | `false` |
8996
| `--merge` | Auto-merge progress on conversion conflicts | `false` |
9097
| `--force` | Auto-overwrite on conversion conflicts | `false` |
9198

99+
Agent resolution order: `--agent` / `--agent-path` → `CHIEF_AGENT` / `CHIEF_AGENT_PATH` env vars → `agent.provider` / `agent.cliPath` in `.chief/config.yaml` → default `claude`.
100+
92101
When `--max-iterations` is not specified, Chief calculates a dynamic limit based on the number of remaining stories plus a buffer. You can also adjust the limit at runtime with `+`/`-` in the TUI.
93102

103+
## Agent
104+
105+
Chief can use **Claude Code** (default) or **Codex CLI** as the agent. Choose via:
106+
107+
- **Config:** `agent.provider: codex` and optionally `agent.cliPath: /path/to/codex` in `.chief/config.yaml`
108+
- **Environment:** `CHIEF_AGENT=codex`, `CHIEF_AGENT_PATH=/path/to/codex`
109+
- **CLI:** `chief --agent codex --agent-path /path/to/codex`
110+
94111
## Claude Code Configuration
95112

96-
Chief invokes Claude Code under the hood. Claude Code has its own configuration:
113+
When using Claude, Chief invokes Claude Code under the hood. Claude Code has its own configuration:
97114

98115
```bash
99116
# Authentication

0 commit comments

Comments
 (0)