par is a small Rust CLI that gives automation one stable prompt interface and routes the call to a local AI agent harness.
It is designed around the useful claude -p "prompt" style of workflow:
par -p "review this repository" --harness codex --model gpt-5.4
par -p "fix the failing tests" --harness opencode --provider anthropic --model claude-sonnet-4-6
git diff | par -p "review this patch" --harness goose --provider openai --model gpt-5.4The router does not call model APIs directly. It starts an already-installed harness CLI, maps shared router flags into that harness's command surface, streams stdout/stderr, and exits with the child process status.
Programmatic agent CLIs all solve a similar problem, but their headless interfaces differ:
- Claude Code uses
claude -p. - Codex uses
codex exec. - Cursor uses
cursor-agent -p. - Gemini uses
gemini --prompt. - Goose uses
goose run -t. - OpenCode uses
opencode run. - Aider uses
aider --message. - Antigravity uses the
agyCLI surface.
That makes scripts brittle. Switching harnesses means editing commands, model flags, provider syntax, JSON output flags, and environment variables. par keeps scripts focused on intent:
par --harness <name> --provider <provider> --model <model> -p "<task>"The harness adapter owns the translation.
This is early, working infrastructure for local automation.
Implemented:
- Rust-native CLI with no runtime dependencies.
- Shared
claude -p-style prompt surface. - Harness factory pattern with isolated adapters.
- Harness installers via
par install <harness>. - Provider/model resolution layer.
- Dry-run mode for deterministic routing tests.
- Setup script that validates Rust, tests, linting, release build, and installed downstream harness CLIs.
Not implemented yet:
- GitHub Actions release builds.
- Homebrew formula.
- End-to-end smoke tests against every vendor CLI.
- Stable semver contract for every harness mapping.
curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/KerryRitter/programmatic-agent-router/main/install.sh | shThis installs par into ~/.local/bin and creates an agent-router compatibility alias.
You need Rust for source installs:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | shMake sure Cargo is available:
cargo --versionThe setup script will also find Cargo at ~/.cargo/bin even if that directory has not been added to your shell PATH yet.
scripts/setup.shThis checks:
- Repository layout.
cargoandrustc.cargo fmt --check.cargo test.cargo clippy --all-targets -- -D warnings.cargo build --release.- Which supported downstream harness CLIs are installed.
Use strict harness validation if this machine should already have at least one supported agent CLI:
scripts/setup.sh --strict-harnessespar can install supported downstream harness CLIs:
par install list
par install claude
par install codex
par install antigravity
par install --dry-run allThe installer registry is intentionally transparent: --dry-run prints the exact upstream install command without executing it. For harnesses that do not expose a stable terminal one-liner, such as Amazon Q, par install <name> prints the official install page and verification command instead of guessing.
Current installer coverage:
| Harness | Installer |
|---|---|
claude |
curl -fsSL https://claude.ai/install.sh | bash |
codex |
npm install -g @openai/codex |
cursor |
curl https://cursor.com/install -fsS | bash |
gemini |
npm install -g @google/gemini-cli |
goose |
curl -fsSL https://github.com/block/goose/releases/download/stable/download_cli.sh | bash |
opencode |
curl -fsSL https://opencode.ai/install | bash |
qwen |
curl -fsSL https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen.sh | bash |
aider |
curl -LsSf https://aider.chat/install.sh | sh |
amazon-q |
Manual official installer page; verify with q --version |
copilot |
curl -fsSL https://gh.io/copilot-install | bash |
antigravity |
curl -fsSL https://antigravity.google/cli/install.sh | bash |
scripts/setup.sh --installThis runs validation, installs the binary with:
cargo install --path . --forceand creates:
~/.local/bin/par
~/.local/bin/agent-router -> ~/.local/bin/parMake sure both Cargo's bin directory and ~/.local/bin are on your PATH:
export PATH="$HOME/.cargo/bin:$HOME/.local/bin:$PATH"The quick install command is:
curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/KerryRitter/programmatic-agent-router/main/install.sh | shThe script tries to install a release binary for your platform first. Until release binaries exist, it falls back to:
cargo install --git https://github.com/KerryRitter/programmatic-agent-router.git --branch main --forceUseful options:
curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/KerryRitter/programmatic-agent-router/main/install.sh | sh -s -- --install-dir /usr/local/bin
curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/KerryRitter/programmatic-agent-router/main/install.sh | sh -s -- --from-source
curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/KerryRitter/programmatic-agent-router/main/install.sh | sh -s -- --from-source --git-protocol ssh
curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/KerryRitter/programmatic-agent-router/main/install.sh | sh -s -- --no-agent-routerThe intended public release flow is to publish platform archives from CI:
par-aarch64-apple-darwin.tar.gzpar-x86_64-apple-darwin.tar.gzpar-x86_64-unknown-linux-gnu.tar.gzpar-x86_64-pc-windows-msvc.zip
Then installation is:
tar -xzf par-x86_64-unknown-linux-gnu.tar.gz
install -m 0755 par ~/.local/bin/par
ln -sf ~/.local/bin/par ~/.local/bin/agent-routerA public distribution should add a tap:
brew tap <org>/tap
brew install parThat should install the same release binary and provide par as the primary CLI name.
Default harness is claude:
par
par -p "summarize this repository"Equivalent routed command:
claude
claude -p "summarize this repository"Use Codex:
par --harness codex --model gpt-5.4 -p "add parser tests"Set the default harness on this machine:
par default codex
par
par -p "add parser tests"Persist a default harness with permission bypass enabled:
par default claude --yolo
par currentThe default is stored in ~/.config/par/default, or $XDG_CONFIG_HOME/par/default when XDG_CONFIG_HOME is set. Override the path with PAR_DEFAULT_FILE.
Create shortcut scripts for yolo-capable harnesses:
par shims install
claudey -p "work in this sandbox"
codexy "work in this sandbox"By default, shims are written to ~/.local/bin. Override this with par shims install --dir <dir> or PAR_SHIM_DIR.
Use OpenCode with provider-qualified model routing:
par \
--harness opencode \
--provider anthropic \
--model claude-sonnet-4-6 \
-p "review this branch"Use Goose with environment-backed configuration:
par \
--harness goose \
--provider openai \
--model gpt-5.4 \
--permission-mode auto \
--max-turns 50 \
-p "fix the failing tests"Pipe context:
git diff | par --harness aider -p "fix the problems in this patch"Preview without execution:
par --harness qwen --model qwen3-coder-plus -p "review src" --dry-runPass harness-specific flags after --:
par --harness claude -p "review this" -- --verbose
par --harness aider -p "fix lint" -- --yes --no-auto-commitspar [options]
par [options] [-p <prompt>] [positional prompt]
| Option | Purpose |
|---|---|
-p, --prompt, --print |
Prompt text. --print is accepted for Claude compatibility. |
--harness <name> |
Target CLI adapter. Defaults to claude. |
--provider <name> |
Provider namespace when the harness uses provider-qualified model names or env configuration. |
--model, -m <name> |
Model name. |
--agent <name> |
Agent/persona/profile where supported. |
--output-format <fmt> |
Output mode where supported by the target harness. |
--input-format <fmt> |
Claude-compatible input format. |
--permission-mode <mode> |
Permission/sandbox mode where supported. |
--max-turns <n> |
Maximum agent turns where supported. |
--cwd <path> |
Working directory for the child process. |
--yolo |
Add the harness-specific permission bypass flag where supported. |
--no-yolo |
Disable a persisted yolo default for this run. |
--dry-run |
Print the routed invocation as JSON. |
--help, -h |
Print help. |
--version, -v |
Print version. |
-- |
Pass all remaining args directly to the target CLI. |
Environment defaults:
export AGENT_ROUTER_HARNESS=codex
export AGENT_ROUTER_PROVIDER=openai
export AGENT_ROUTER_MODEL=gpt-5.4
export AGENT_ROUTER_YOLO=truePersisted default commands:
par default # show persisted defaults
par default codex --yolo # set harness and yolo default
par default --no-yolo # keep harness, disable yolo
par default --path # print the default file path
par current # alias for showing defaults
par list # list supported harness namesThis follows the useful part of nvm's command shape: a default alias, use-style setter, current, and list. par does not manage installed versions; downstream CLIs still own their own install and upgrade flows.
Prompt input rules:
- If no prompt and no stdin are provided,
parlaunches the default harness's interactive entrypoint. - If stdin is piped and
-pis provided, stdin is placed before the prompt with a blank line between them. - If stdin is piped and no prompt is provided, stdin becomes the prompt.
| Harness | Aliases | Routed command |
|---|---|---|
claude |
none | claude -p "<prompt>" |
codex |
openai |
codex exec "<prompt>" |
cursor |
cursor-agent |
cursor-agent -p "<prompt>" |
gemini |
google, google-gemini |
gemini --prompt "<prompt>" |
goose |
none | goose run -t "<prompt>" |
opencode |
open-code |
opencode run "<prompt>" |
qwen |
none | qwen -p "<prompt>" |
aider |
none | aider --message "<prompt>" |
amazon-q |
amazonq, aws-q, amazon |
q chat "<prompt>" |
copilot |
github-copilot |
copilot -p "<prompt>" |
antigravity |
agy, google-antigravity |
agy "<prompt>" |
Claude:
- Uses
claude -p. - Supports
--model,--output-format,--input-format,--permission-mode, and--max-turns. --yolomaps to--dangerously-skip-permissions.
Codex:
- Uses
codex exec. --output-format jsonand--output-format stream-jsonmap to--json.- Provider is currently preserved in
AGENT_ROUTER_PROVIDERfor future policy, but Codex receives the plain model name. --yolomaps to--dangerously-bypass-approvals-and-sandboxfor routed noninteractive runs. Thecodexyshim uses the shortercodex --yoloentrypoint.
Cursor:
- Uses
cursor-agent -p. - Supports plain
--model. - Supports
--output-formatwhen accepted by the installed Cursor agent. --yolomaps to--force, which Cursor requires for print-mode file writes.
Gemini:
- Uses
gemini --prompt. - Supports plain
--model. - Supports
--output-formatwhen accepted by the installed Gemini CLI. --yolomaps to--yolo.
Goose:
- Uses
goose run -t. --providermaps toGOOSE_PROVIDER.--modelmaps toGOOSE_MODEL.--permission-modemaps toGOOSE_MODE.--max-turnsmaps toGOOSE_MAX_TURNS.--agent <name>maps togoose run --with-builtin <name>.--yolosetsGOOSE_MODE=autounless--permission-modeis provided.
OpenCode:
- Uses
opencode run. --provider anthropic --model claude-sonnet-4-6becomes--model anthropic/claude-sonnet-4-6.--output-format jsonand--output-format stream-jsonmap to--format json.--agentmaps to--agent.--yolomaps to--dangerously-skip-permissions.
Qwen:
- Uses
qwen -p. - Supports plain
--model. - Supports
--output-formatwhen accepted by the installed Qwen CLI. --yolomaps to--yolo.
Aider:
- Uses
aider --message. - Provider and model are joined for
--model, such asanthropic/claude-sonnet-4-6. - Use
--for Aider-specific automation flags, for example--yes. --yolomaps to--yes-always.
Amazon Q:
- Uses
q chat. --agentmaps to--agent.- Model selection is owned by Amazon Q CLI configuration.
Copilot:
- Provisional adapter.
- Uses
copilot -p. --yolomaps to--yolo.- Validate against the installed CLI before relying on it in automation.
Antigravity:
- Experimental adapter.
- Uses the
agycommand installed by Google Antigravity. - Official docs currently describe the interactive AGY CLI rather than a stable print/headless mode. The adapter passes the prompt to
agyand leaves version-specific flags to--. --yolomaps to--dangerously-skip-permissions.
The code is intentionally small and explicit.
src/
main.rs entrypoint, stdin handling, dry-run, process dispatch
cli.rs shared command-line parser
model.rs provider/model resolution
process.rs child process execution
harness/
mod.rs Harness trait, Request, HarnessFactory
invocation.rs command/args/env representation
claude.rs Claude adapter
codex.rs Codex adapter
cursor.rs Cursor adapter
gemini.rs Gemini adapter
goose.rs Goose adapter
opencode.rs OpenCode adapter
qwen.rs Qwen adapter
aider.rs Aider adapter
amazon_q.rs Amazon Q adapter
antigravity.rs Antigravity adapter
copilot.rs provisional Copilot adapter
installer.rs harness installer registry
Core types:
Request: normalized router input.Invocation: concrete command, args, and environment.Harness: adapter trait implemented by each harness.HarnessFactory: registry that maps harness names to adapter constructors.ModelFactory: central place for provider/model normalization and formatting.installer.rs: explicit upstream install commands for downstream harness CLIs.
Design constraints:
- No
sh -c; adapters build argv directly. - No hidden API calls; the router only starts local CLIs.
- No login handling; authenticate each downstream harness separately.
- Harness-specific behavior stays in that harness module.
- Provider/model transformation stays centralized in
model.rs. --dry-runoutput should be stable enough for tests and shell debugging.
- Create
src/harness/<name>.rs. - Implement
Harnessfor a small adapter struct. - Register the adapter in
HarnessFactory::default(). - Add aliases in
normalize_harness()if useful. - Add dry-run tests for command, args, provider/model behavior, env vars, and passthrough.
- Document the harness and source docs in this README.
Adapter skeleton:
use super::{add_passthrough, plain_model, Harness, Invocation, Request};
pub(crate) fn new() -> Box<dyn Harness> {
Box::new(ExampleHarness)
}
struct ExampleHarness;
impl Harness for ExampleHarness {
fn build(&self, request: &Request) -> Result<Invocation, String> {
let mut args = vec!["run".to_string(), request.prompt.clone()];
if let Some(model) = plain_model(request) {
args.extend(["--model".to_string(), model]);
}
Ok(Invocation::new("example", add_passthrough(args, request)))
}
}Run the full local validation:
scripts/setup.shInstall after validation:
scripts/setup.sh --installCommon direct commands:
cargo fmt
cargo test
cargo clippy --all-targets -- -D warnings
cargo build --releaseTry a dry run:
cargo run -- --harness opencode --provider anthropic --model claude-sonnet-4-6 -p "review" --dry-runExpected dry-run shape:
{
"command": "opencode",
"args": ["run", "--model", "anthropic/claude-sonnet-4-6", "review"],
"env": {}
}Recommended first public release checklist:
- Run
scripts/setup.sh. - Smoke-test installed CLIs that are available locally with
--dry-run. - Add GitHub Actions for:
cargo fmt --checkcargo testcargo clippy --all-targets -- -D warnings- release binaries for Linux, macOS, and Windows
- Add archive checksums.
- Publish a GitHub release.
- Add a Homebrew tap once the release artifact names are stable.
par does not inspect or redact prompt content. Anything passed to stdin or -p is forwarded to the selected harness. The selected harness may send that content to its configured provider.
The router does not weaken or bypass harness sandboxing unless you explicitly opt into --yolo or persist yolo=true in the default file. Permission behavior is delegated to the downstream CLI. When a shared option like --permission-mode or --yolo is mapped, it uses the target harness's command surface.
Use --dry-run when validating automation that may include secrets or proprietary context.
High priority:
crush: add once its one-shot/headless command surface is pinned.amp: useful commercial harness, but verify whether it has a true non-interactive one-shot mode before adding.windsurf: support only if/when the agent CLI exposes a stable non-interactive prompt command.
Medium priority:
qoder,trae,kimi,openclaw,droid: support if they expose either ACP or a stable direct CLI.factory: worth tracking if the local CLI is scriptable outside its hosted workflow.continue,cline,roo,kilo: these are often extension-first rather than CLI-first; only add if there is a real headless command.
Out of scope for now:
- Web-only agents with no local CLI.
- Interactive-only CLIs that block on a TTY.
- Raw model providers where there is no agent harness. This project routes harnesses, not chat completions.
This README was written against public docs checked on May 19, 2026:
- Claude Code CLI reference documents
claude -p/--print. - Codex exec mode docs document
codex exec. - Cursor CLI docs document
cursor-agent -p/--print. - Gemini CLI docs document
--prompt/-p. - OpenCode CLI docs document
opencode run. - Qwen Code CLI docs document
qwen -p/--prompt. - Aider scripting docs document
aider --message. - Amazon Q Developer CLI reference documents
q chat --agent <name> "<prompt>". - Goose headless docs document
goose run -t. - Antigravity CLI docs document the
agyCLI surface and configuration model.
Installer commands are kept in src/installer.rs and checked against the official install docs where a stable one-liner exists. Amazon Q remains manual because the official AWS CLI docs point users through platform-specific installer flows rather than a single shell installer command.
Re-check these command surfaces before a public release. Agent CLIs change fast.