Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,17 @@ See the [documentation](https://minicodemonkey.github.io/chief/concepts/how-it-w

## Requirements

- [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) installed and authenticated
- **[Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code)**, **[Codex CLI](https://developers.openai.com/codex/cli/reference)**, or **[OpenCode CLI](https://opencode.ai)** installed and authenticated

Use Claude by default, or configure Codex or OpenCode in `.chief/config.yaml`:

```yaml
agent:
provider: opencode
cliPath: /usr/local/bin/opencode # optional
```

Or run with `chief --agent opencode` or set `CHIEF_AGENT=opencode`.

## License

Expand Down
159 changes: 134 additions & 25 deletions cmd/chief/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ import (
"strings"

tea "github.com/charmbracelet/bubbletea"
"github.com/minicodemonkey/chief/internal/agent"
"github.com/minicodemonkey/chief/internal/cmd"
"github.com/minicodemonkey/chief/internal/config"
"github.com/minicodemonkey/chief/internal/git"
"github.com/minicodemonkey/chief/internal/loop"
"github.com/minicodemonkey/chief/internal/prd"
"github.com/minicodemonkey/chief/internal/tui"
)
Expand All @@ -26,6 +28,8 @@ type TUIOptions struct {
Merge bool
Force bool
NoRetry bool
Agent string // --agent claude|codex
AgentPath string // --agent-path
}

func main() {
Expand Down Expand Up @@ -147,6 +151,26 @@ func parseTUIFlags() *TUIOptions {
opts.Force = true
case arg == "--no-retry":
opts.NoRetry = true
case arg == "--agent":
if i+1 < len(os.Args) {
i++
opts.Agent = os.Args[i]
} else {
fmt.Fprintf(os.Stderr, "Error: --agent requires a value (claude or codex)\n")
os.Exit(1)
}
case strings.HasPrefix(arg, "--agent="):
opts.Agent = strings.TrimPrefix(arg, "--agent=")
case arg == "--agent-path":
if i+1 < len(os.Args) {
i++
opts.AgentPath = os.Args[i]
} else {
fmt.Fprintf(os.Stderr, "Error: --agent-path requires a value\n")
os.Exit(1)
}
case strings.HasPrefix(arg, "--agent-path="):
opts.AgentPath = strings.TrimPrefix(arg, "--agent-path=")
case arg == "--max-iterations" || arg == "-n":
// Next argument should be the number
if i+1 < len(os.Args) {
Expand Down Expand Up @@ -210,15 +234,47 @@ func parseTUIFlags() *TUIOptions {

func runNew() {
opts := cmd.NewOptions{}
flagAgent, flagPath := "", ""
var positional []string

// Parse arguments: chief new [name] [context...]
if len(os.Args) > 2 {
opts.Name = os.Args[2]
// Parse arguments: chief new [name] [context...] [--agent X] [--agent-path X]
for i := 2; i < len(os.Args); i++ {
arg := os.Args[i]
switch {
case arg == "--agent":
if i+1 < len(os.Args) {
i++
flagAgent = os.Args[i]
} else {
fmt.Fprintf(os.Stderr, "Error: --agent requires a value (claude or codex)\n")
os.Exit(1)
}
case strings.HasPrefix(arg, "--agent="):
flagAgent = strings.TrimPrefix(arg, "--agent=")
case arg == "--agent-path":
if i+1 < len(os.Args) {
i++
flagPath = os.Args[i]
} else {
fmt.Fprintf(os.Stderr, "Error: --agent-path requires a value\n")
os.Exit(1)
}
case strings.HasPrefix(arg, "--agent-path="):
flagPath = strings.TrimPrefix(arg, "--agent-path=")
case strings.HasPrefix(arg, "-"):
// skip unknown flags
default:
positional = append(positional, arg)
}
}
if len(os.Args) > 3 {
opts.Context = strings.Join(os.Args[3:], " ")
if len(positional) > 0 {
opts.Name = positional[0]
}
if len(positional) > 1 {
opts.Context = strings.Join(positional[1:], " ")
}

opts.Provider = resolveProvider(flagAgent, flagPath)
if err := cmd.RunNew(opts); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
Expand All @@ -227,23 +283,44 @@ func runNew() {

func runEdit() {
opts := cmd.EditOptions{}
flagAgent, flagPath := "", ""

// Parse arguments: chief edit [name] [--merge] [--force]
// Parse arguments: chief edit [name] [--merge] [--force] [--agent X] [--agent-path X]
for i := 2; i < len(os.Args); i++ {
arg := os.Args[i]
switch arg {
case "--merge":
switch {
case arg == "--merge":
opts.Merge = true
case "--force":
case arg == "--force":
opts.Force = true
case arg == "--agent":
if i+1 < len(os.Args) {
i++
flagAgent = os.Args[i]
} else {
fmt.Fprintf(os.Stderr, "Error: --agent requires a value (claude or codex)\n")
os.Exit(1)
}
case strings.HasPrefix(arg, "--agent="):
flagAgent = strings.TrimPrefix(arg, "--agent=")
case arg == "--agent-path":
if i+1 < len(os.Args) {
i++
flagPath = os.Args[i]
} else {
fmt.Fprintf(os.Stderr, "Error: --agent-path requires a value\n")
os.Exit(1)
}
case strings.HasPrefix(arg, "--agent-path="):
flagPath = strings.TrimPrefix(arg, "--agent-path=")
default:
// If not a flag, treat as PRD name (first non-flag arg)
if opts.Name == "" && !strings.HasPrefix(arg, "-") {
opts.Name = arg
}
}
}

opts.Provider = resolveProvider(flagAgent, flagPath)
if err := cmd.RunEdit(opts); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
Expand Down Expand Up @@ -282,7 +359,33 @@ func runList() {
}
}

// resolveProvider loads config and resolves the agent provider, exiting on error.
func resolveProvider(flagAgent, flagPath string) loop.Provider {
cwd, err := os.Getwd()
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
cfg, err := config.Load(cwd)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: failed to load .chief/config.yaml: %v\n", err)
os.Exit(1)
}
provider, err := agent.Resolve(flagAgent, flagPath, cfg)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
if err := agent.CheckInstalled(provider); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
return provider
}

func runTUIWithOptions(opts *TUIOptions) {
provider := resolveProvider(opts.Agent, opts.AgentPath)

prdPath := opts.PRDPath

// If no PRD specified, try to find one
Expand Down Expand Up @@ -322,7 +425,8 @@ func runTUIWithOptions(opts *TUIOptions) {

// Create the PRD
newOpts := cmd.NewOptions{
Name: result.PRDName,
Name: result.PRDName,
Provider: provider,
}
if err := cmd.RunNew(newOpts); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
Expand All @@ -344,19 +448,19 @@ func runTUIWithOptions(opts *TUIOptions) {
fmt.Printf("Warning: failed to check conversion status: %v\n", err)
} else if needsConvert {
fmt.Println("prd.md is newer than prd.json, running conversion...")
convertOpts := prd.ConvertOptions{
PRDDir: prdDir,
Merge: opts.Merge,
Force: opts.Force,
}
if err := prd.Convert(convertOpts); err != nil {
if err := cmd.RunConvertWithOptions(cmd.ConvertOptions{
PRDDir: prdDir,
Merge: opts.Merge,
Force: opts.Force,
Provider: provider,
}); err != nil {
fmt.Printf("Error converting PRD: %v\n", err)
os.Exit(1)
}
fmt.Println("Conversion complete.")
}

app, err := tui.NewAppWithOptions(prdPath, opts.MaxIterations)
app, err := tui.NewAppWithOptions(prdPath, opts.MaxIterations, provider)
if err != nil {
// Check if this is a missing PRD file error
if os.IsNotExist(err) || strings.Contains(err.Error(), "no such file") {
Expand Down Expand Up @@ -403,7 +507,8 @@ func runTUIWithOptions(opts *TUIOptions) {
case tui.PostExitInit:
// Run new command then restart TUI
newOpts := cmd.NewOptions{
Name: finalApp.PostExitPRD,
Name: finalApp.PostExitPRD,
Provider: provider,
}
if err := cmd.RunNew(newOpts); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
Expand All @@ -416,9 +521,10 @@ func runTUIWithOptions(opts *TUIOptions) {
case tui.PostExitEdit:
// Run edit command then restart TUI
editOpts := cmd.EditOptions{
Name: finalApp.PostExitPRD,
Merge: opts.Merge,
Force: opts.Force,
Name: finalApp.PostExitPRD,
Merge: opts.Merge,
Force: opts.Force,
Provider: provider,
}
if err := cmd.RunEdit(editOpts); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
Expand Down Expand Up @@ -447,9 +553,11 @@ Commands:
help Show this help message

Global Options:
--agent <provider> Agent CLI to use: claude (default), codex, or opencode
--agent-path <path> Custom path to agent CLI binary
--max-iterations N, -n N Set maximum iterations (default: dynamic)
--no-retry Disable auto-retry on Claude crashes
--verbose Show raw Claude output in log
--no-retry Disable auto-retry on agent crashes
--verbose Show raw agent output in log
--merge Auto-merge progress on conversion conflicts
--force Auto-overwrite on conversion conflicts
--help, -h Show this help message
Expand All @@ -470,7 +578,8 @@ Examples:
chief -n 20 Launch with 20 max iterations
chief --max-iterations=5 auth
Launch auth PRD with 5 max iterations
chief --verbose Launch with raw Claude output visible
chief --verbose Launch with raw agent output visible
chief --agent codex Use Codex CLI instead of Claude
chief new Create PRD in .chief/prds/main/
chief new auth Create PRD in .chief/prds/auth/
chief new auth "JWT authentication for REST API"
Expand Down
37 changes: 32 additions & 5 deletions docs/guide/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ Chief is distributed as a single binary with no runtime dependencies. Choose you

## Prerequisites

Before installing Chief, ensure you have **Claude Code CLI** installed and authenticated:
Chief needs an agent CLI: **Claude Code** (default), **Codex**, or **OpenCode**. Install at least one and authenticate.

### Option A: Claude Code CLI (default)

::: code-group

Expand All @@ -27,8 +29,32 @@ npx @anthropic-ai/claude-code login

:::

::: tip Verify Claude Code Installation
Run `claude --version` to confirm Claude Code is installed. Chief will not work without it.
::: tip Verify Claude Code
Run `claude --version` to confirm Claude Code is installed.
:::

### Option B: Codex CLI

To use [OpenAI Codex CLI](https://developers.openai.com/codex/cli/reference) instead of Claude:

1. Install Codex per the [official reference](https://developers.openai.com/codex/cli/reference).
2. Ensure `codex` is on your PATH, or set `agent.cliPath` in `.chief/config.yaml` (see [Configuration](/reference/configuration#agent)).
3. Run Chief with `chief --agent codex` or set `CHIEF_AGENT=codex`, or set `agent.provider: codex` in `.chief/config.yaml`.

::: tip Verify Codex
Run `codex --version` (or your custom path) to confirm Codex is available.
:::

### Option C: OpenCode CLI

To use [OpenCode CLI](https://opencode.ai) as an alternative:

1. Install OpenCode per the [official docs](https://opencode.ai/docs/).
2. Ensure `opencode` is on your PATH, or set `agent.cliPath` in `.chief/config.yaml` (see [Configuration](/reference/configuration#agent)).
3. Run Chief with `chief --agent opencode` or set `CHIEF_AGENT=opencode`, or set `agent.provider: opencode` in `.chief/config.yaml`.

::: tip Verify OpenCode
Run `opencode --version` (or your custom path) to confirm OpenCode is available.
:::

### Optional: GitHub CLI (`gh`)
Expand Down Expand Up @@ -238,11 +264,12 @@ chief --version
# View help
chief --help

# Check that Claude Code is accessible
# Check that your agent CLI is accessible (Claude default, or codex if configured)
claude --version
# or: codex --version
```

Expected output:
Expected output (example with Claude):

```
$ chief --version
Expand Down
Loading