Run SoulForge without the TUI —pipe in a prompt, get back results.
Quick start
# Inline prompt
soulforge --headless "explain the auth middleware"
# Pipe from stdin
echo "find all unused exports" | soulforge --headless
# JSON output for scripting
soulforge --headless --json "list all TODO comments"
# JSONL event stream (real-time)
soulforge --headless --events "refactor the store"
# Override model and mode
soulforge --headless --model anthropic/claude-sonnet-4-20250514 --mode architect "review auth"
# Inject system prompt + include files
soulforge --headless --system "you are a security auditor" --include src/auth.ts "find vulnerabilities"
# CI/CD with safety limits
soulforge --headless --max-steps 20 --timeout 120000 --save-session "fix lint errors"
# Interactive multi-turn chat
soulforge --headless --chat
CLI flags
Headless execution
| Flag | Description |
|---|
--headless <prompt> | Run without TUI. Prompt is all non-flag arguments joined. |
--model <provider/model> | Override the configured default model. |
--mode <mode> | Set forge mode: default, architect, socratic, challenge, plan, auto. |
--json | Output structured JSON after completion. |
--events | JSONL event stream —one JSON object per line, real-time. |
--quiet / -q | Suppress header/footer on stderr. |
--max-steps <n> | Limit agent to N steps, then abort. |
--timeout <ms> | Abort after N milliseconds. |
--cwd <dir> | Set working directory. |
--system "..." | Inject custom system prompt instructions. |
--include <file> | Pre-load a file into context (repeatable). |
--no-repomap | Skip repo map scan. |
--diff | Show list of files changed after the run. |
--no-render | Output raw text without ANSI styling. |
--session <id> | Resume a previous session (prefix match supported). |
--save-session | Save the conversation for later resume. |
--chat | Interactive multi-turn chat with auto-save. |
Exit codes: 0 success, 1 error, 2 timeout, 130 abort (Ctrl+C).
Provider management
These work standalone —no --headless needed:
soulforge --list-providers # Show providers + key status
soulforge --list-models # Show models for all providers
soulforge --list-models anthropic # Show models for one provider
soulforge --set-key anthropic sk-ant-... # Save API key to OS keychain
Output modes
Streaming (default)
JSON mode
JSONL events
Agent text streams to stdout in real time. Status messages go to stderr.# Only agent output goes to the file
soulforge --headless "summarize the architecture" > summary.txt
# stderr shows progress:
# Model: anthropic/claude-sonnet-4-20250514
# Repo: 847 files, 12340 symbols
# 3 steps -- 12.1k in, 2.1k out, 89% cached -- 8.4s
With --json, a single JSON object is written to stdout after completion:{
"model": "anthropic/claude-sonnet-4-20250514",
"prompt": "list all unused exports",
"output": "Found 12 unused exports across 8 files...",
"steps": 4,
"tokens": {
"input": 8234,
"output": 1456,
"cacheRead": 6100
},
"toolCalls": ["soul_analyze", "read_file", "read_file"],
"duration": 12345
}
With --events, each event is a JSON object on its own line:{"type":"start","model":"anthropic/claude-sonnet-4-20250514","mode":"default","repoMap":{"files":280,"symbols":4494}}
{"type":"text","content":"Let me "}
{"type":"text","content":"check the code..."}
{"type":"tool-call","tool":"soul_grep"}
{"type":"tool-result","tool":"soul_grep","summary":"3 matches found in src/"}
{"type":"step","step":1,"tokens":{"input":12000,"output":450,"cacheRead":10000}}
{"type":"done","output":"...","steps":2,"tokens":{...},"toolCalls":["soul_grep"],"duration":4521}
Event types: start, text, tool-call, tool-result, step, error, done, session-saved.
Chat mode events
In --chat --events mode, additional events are emitted:
ready —agent is ready for the next prompt
turn-done —a turn completed (per-turn stats)
chat-done —chat session ended (total stats, sessionId if saved)
What’s available
Headless runs the full Forge agent with all tools:
- All 31 tools
- Multi-agent dispatch with shared cache
- Repo map (tree-sitter analysis, PageRank, cochange, blast radius)
- Intelligence layer (LSP, ts-morph, tree-sitter fallback)
- Context management (compaction, tool result pruning)
- Provider options (prompt caching, thinking modes)
What’s skipped
- Splash animation and TUI renderer
- Neovim editor embedding
- Interactive approval prompts (destructive actions auto-allowed)
- Keyboard shortcuts and modal UI
- Plan review flow
- User steering (no stdin during execution)
Examples
CI/CD: lint and fix
soulforge --headless --max-steps 20 --timeout 120000 --diff \
"run the linter, fix all issues, then verify typecheck passes"
Security review
soulforge --headless --mode architect \
--system "you are a security auditor. focus on OWASP top 10." \
--include src/auth/middleware.ts \
"review this authentication code for vulnerabilities"
Batch analysis
for dir in packages/*/; do
echo "analyze $dir for unused exports" | soulforge --headless --json >> report.jsonl
done
Multi-step workflow with sessions
# Step 1: analyze and save
soulforge --headless --save-session "analyze the auth module, find all issues"
# Step 2: resume and fix
soulforge --headless --session <id> --save-session "now fix the issues you found"
# Step 3: resume and test
soulforge --headless --session <id> "write tests for the fixes"
Real-time monitoring
soulforge --headless --events "refactor the store module" | while read -r line; do
type=$(echo "$line" | jq -r '.type')
case "$type" in
tool-call) echo "Tool: $(echo "$line" | jq -r '.tool')" ;;
done) echo "Done in $(echo "$line" | jq -r '.duration')ms" ;;
esac
done
Custom providers
Add any OpenAI-compatible API as a provider via config:
{
"providers": [
{
"id": "deepseek",
"name": "DeepSeek",
"baseURL": "https://api.deepseek.com/v1",
"envVar": "DEEPSEEK_API_KEY",
"models": ["deepseek-chat", "deepseek-coder"]
}
]
}
| Field | Required | Description |
|---|
id | Yes | Provider ID, used in model strings like deepseek/deepseek-chat. |
name | No | Display name. Defaults to id. |
baseURL | Yes | OpenAI-compatible API endpoint. |
envVar | No | Env var name for the API key. |
models | No | Fallback model list. |
modelsAPI | No | URL to fetch models dynamically (OpenAI /v1/models format). |
Custom providers can be scoped globally (~/.soulforge/config.json) or per-project (.soulforge/config.json). Project providers override global entries with the same id.
Architecture
Headless bypasses the entire TUI stack:
boot.tsx (CLI flag detected)
-> headless/index.ts
-> parse.ts (parseHeadlessArgs)
-> initConfig() + registerCustomProviders()
-> run.ts (runPrompt)
-> ContextManager.createAsync() (repo map, memory)
-> loadInstructions() (SOULFORGE.md + --system)
-> SessionManager.loadSession() (if --session)
-> createForgeAgent() (same agent as TUI)
-> agent.stream() (async iterator)
-> SessionManager.saveSession() (if --save-session)
-> stdout (text, JSON, or JSONL events)
The createForgeAgent() factory is shared with the TUI —same tools, same prompts, same agent loop. The only difference is no interactive callbacks and no renderer.