dux dux · getdux.app
Documentation menu

Custom CLI Agents

Configure any CLI as a dux provider, no adapters or protocol layer, just config.

A provider is the CLI behind an agent. Claude Code, Codex, OpenCode, and Gemini are configured out of the box, but the whole point of dux’s design is that any CLI can be a provider. Adding one is a config change, not a code change. There are no adapters and no protocol layer to implement. dux runs the command exactly as it would in a normal terminal.

The one rule

A tool can be a provider if and only if it supports two modes:

  • PTY mode: an interactive session dux can embed in a pseudo-terminal. This is how you actually work with the agent.
  • Oneshot mode (headless): hand it a prompt, get one response back. dux uses this for automated tasks like generating commit messages.

If your CLI can do both, dux can drive it.

Anatomy of a provider

Providers live under [providers.<name>] in config.toml. Here’s the full set of fields, with what each one does:

[providers.claude]
# The CLI command for this provider's sessions.
command = "claude"
# Arguments passed when launching an interactive PTY session.
args = []
# Optional args used when reconnecting a detached session. Leave empty for CLIs
# that don't support resuming a session scoped to the working directory.
resume_args = ["--continue"]
# Optional timeout (ms) for a resumed session that renders nothing. If a resume
# hangs before showing output, dux kills it and starts fresh. 0 disables it.
resume_wait_timeout_ms = 0
# Oneshot args for non-interactive use (e.g. AI commit messages).
# Placeholders: {prompt} = the prompt text, {tempfile} = a temp file path.
oneshot_args = ["--bare", "-p", "{prompt}", "--tools", "", "--max-turns", "1"]
# Where to read the oneshot response from: "stdout" or "tempfile".
oneshot_output = "stdout"
# Hint shown to the user when the command isn't found on PATH.
install_hint = "curl -fsSL https://claude.ai/install.sh | bash"
# When true, scroll events go to the provider instead of dux's own scrollback.
# Enable this for agents that manage their own scrollback buffer.
forward_scroll = false

The oneshot placeholders

oneshot_args is a template. dux substitutes two placeholders before running it:

  • {prompt}: the prompt text, inserted as a single argument.
  • {tempfile}: the path to a temp file dux creates for the run.

How you read the result depends on the CLI:

  • oneshot_output = "stdout": dux captures the command’s standard output. Use this when the CLI prints its answer.
  • oneshot_output = "tempfile": dux reads the file at {tempfile} after the command exits. Use this when the CLI writes its answer to a file you pass it.

A worked example

Say you have a CLI called myagent that takes a prompt with --prompt for interactive use and writes a oneshot answer to a file given by --out. The whole integration is this:

[providers.myagent]
command = "myagent"
args = []
resume_args = []
oneshot_args = ["--out", "{tempfile}", "--prompt", "{prompt}"]
oneshot_output = "tempfile"
install_hint = "see https://example.com/install"
forward_scroll = false

Save the config, and myagent is now a provider you can pick when creating an agent. That’s the entire process.

Choosing a provider per project

A project can pin a default provider so new agents start with the right CLI:

[[projects]]
id   = "a4f3..."
path = "$HOME/projects/web-app"
name = "web-app"
default_provider = "myagent"

You can still pick a different provider when you create an individual agent; this just sets the default.

Why no adapters?

Because the CLI runs as-is. dux embeds a real terminal emulator and spawns the command in a pseudo-terminal, so the tool behaves exactly like it does in your shell: same prompts, same colors, same login flow, same everything. Keeping it generic is what lets any future CLI become a provider with nothing more than a few lines of TOML.