# ai-cli-dispatch Dispatch AI CLI coding tasks to available clients (Codex, Claude Code, OpenCode) with automatic discovery, version checking, and execution. ## Scope - discover installed AI CLI clients on the host - report client versions and availability - dispatch a prompt to a specific client by name - auto-resolve the best client from prompt keywords - forward arguments natively to each client - run tasks asynchronously as background jobs with lifecycle management - run tasks synchronously when blocking until completion is desired The tool supports both async (default) and sync execution modes. Async jobs run as detached background processes and are tracked on disk. For ACP-based harnesses, see `docs/openclaw-acp-orchestration.md`. ## Setup From the repo or installed skill directory: ```bash cd tools/ai-cli-dispatch npm install ``` The dispatcher itself requires only Node.js 20+ and `npm`. The actual AI CLI clients (`codex`, `claude`, `opencode`) are discovered from the host `PATH`; they are not bundled. ## Commands ```bash ai-cli-dispatch list [--json|--text] ai-cli-dispatch run --client --prompt [--sync] [--timeout ] [--debug] [--json|--text] ai-cli-dispatch dispatch [--client ] [--sync] [--timeout ] [--debug] [--json|--text] ai-cli-dispatch start --client --prompt [--timeout ] [--debug] [--json|--text] ai-cli-dispatch status [--json|--text] ai-cli-dispatch results [--json|--text] ai-cli-dispatch cancel [--json|--text] ai-cli-dispatch list-jobs [--status running|completed|failed] [--json|--text] ai-cli-dispatch cleanup-jobs [--max-age [h|m|s|d]] [--json|--text] ai-cli-dispatch --help ``` ### `list` Discover and report all supported clients. ```bash ai-cli-dispatch list --json ``` Example JSON output: ```json [ { "name": "codex", "path": "/usr/local/bin/codex", "version": "1.2.3", "found": true }, { "name": "claude", "found": false }, { "name": "opencode", "path": "/opt/homebrew/bin/opencode", "version": "0.5.1", "found": true } ] ``` Use `--text` for human-readable checkmarks: ```bash ai-cli-dispatch list --text ``` ### `run` Execute a prompt directly through a named client. By default, this starts a background job and returns immediately. ```bash # Async (default) — returns a job ID ai-cli-dispatch run --client codex --prompt "refactor this function" # Sync — blocks until the client finishes ai-cli-dispatch run --client claude --prompt "add tests for auth middleware" --sync # With custom timeout and debug diagnostics ai-cli-dispatch run --client opencode --prompt "migrate to ESM" --timeout 600000 --debug ``` The prompt is forwarded with each client’s native argument shape: | Client | Arguments passed | |---|---| | `codex` | `exec --yolo ""` | | `claude` | `-p "" --dangerously-skip-permissions` | | `opencode` | `run --dangerously-skip-permissions ""` | ### `dispatch` Auto-resolve the client from prompt keywords, then execute. Defaults to async; use `--sync` to block. ```bash ai-cli-dispatch dispatch "use claude to write tests" ai-cli-dispatch dispatch "codex refactor auth module" ai-cli-dispatch dispatch "opencode migrate to ESM" ``` Keyword matching is case-insensitive and ordered: 1. `--client` flag (highest precedence) 2. `"open code"` (spaced variant) → `opencode` 3. `"claude"` → `claude` 4. `"codex"` → `codex` 5. `"opencode"` → `opencode` 6. `defaultClient` from config (lowest precedence) Override auto-resolution explicitly: ```bash ai-cli-dispatch dispatch "fix the bug" --client claude ``` ### `start` Explicitly start a background job (same as `run` without `--sync`). Useful when you want the async behavior unambiguously. ```bash ai-cli-dispatch start --client codex --prompt "refactor this function" ``` ### `status` Check the status of a background job. ```bash ai-cli-dispatch status ``` JSON output: ```json { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "client": "codex", "prompt": "refactor this function", "status": "running", "startedAt": "2026-05-19T12:34:56.789Z", "pid": 12345 } ``` Statuses: `running`, `completed`, `failed`, `timed_out`, `cancelled`. ### `results` Retrieve the execution result of a completed job. ```bash ai-cli-dispatch results ``` JSON output: ```json { "stdout": "...", "stderr": "...", "exitCode": 0, "client": "codex", "durationMs": 45231 } ``` Requires status `completed`. For `failed` or `timed_out` jobs, use `status` to see the captured error. ### `cancel` Cancel a running job. ```bash ai-cli-dispatch cancel ``` ### `list-jobs` List all tracked jobs, newest first. ```bash ai-cli-dispatch list-jobs --json ai-cli-dispatch list-jobs --status running --json ``` ### `cleanup-jobs` Remove job files older than a threshold. Default unit is hours. ```bash ai-cli-dispatch cleanup-jobs --max-age 24h ai-cli-dispatch cleanup-jobs --max-age 30m ``` ## Async vs Sync Mode By default, `run` and `dispatch` are **async**: they start a detached background process, persist a job record to disk, and return a job ID immediately. This is ideal for: - Fire-and-forget tasks that may run for minutes - Long-running codegen or migration tasks - Scenarios where the caller should not block Use `--sync` when you need: - The complete output before the next step - Synchronous composition in shell pipelines or scripts - Immediate error propagation to the calling process | Aspect | Async (default) | Sync (`--sync`) | |---|---|---| | Return value | Job ID + status | Full stdout/stderr + exit code | | Process model | Detached child, parent exits immediately | Attached child, parent waits | | Persistence | Job file written to disk | No job file | | Timeout | Enforced via `child.kill()` after `--timeout` | Enforced via `child.kill()` after `--timeout` | ## Client Discovery Discovery searches `PATH` in this order for each client name: 1. `codex` — OpenAI Codex CLI 2. `claude` — Anthropic Claude Code 3. `opencode` — OpenCode CLI The search uses `which` (or `where` on Windows) first, then falls back to a manual `PATH` directory scan. If a binary is found, `--version` is invoked to extract a semver string. ## Configuration Optional config file: ```text ~/.openclaw/ai-cli-dispatch.json ``` Example: ```json { "paths": { "codex": "/usr/local/bin/codex", "claude": "/opt/homebrew/bin/claude" }, "defaultClient": "claude", "timeout": 300000 } ``` Resolution priority for paths and default client (highest to lowest): 1. CLI flag (`--client`, `--codex-path`, etc.) 2. Environment variable (`AI_CLI_CODEX_PATH`, `AI_CLI_DEFAULT_CLIENT`, etc.) 3. Config file (`paths`, `defaultClient`) 4. `which` / `where` discovery Supported env vars: | Variable | Purpose | |---|---| | `AI_CLI_CODEX_PATH` | Override `codex` binary path | | `AI_CLI_CLAUDE_PATH` | Override `claude` binary path | | `AI_CLI_OPENCODE_PATH` | Override `opencode` binary path | | `AI_CLI_DEFAULT_CLIENT` | Override default client (`codex`, `claude`, or `opencode`) | ## Output Model Default output is JSON. Use `--text` to stream raw `stdout`/`stderr` directly. ### Sync JSON success shape (`run --sync`, `dispatch --sync`) ```json { "stdout": "...", "stderr": "...", "exitCode": 0, "client": "codex", "durationMs": 45231 } ``` ### Async JSON success shape (`run`, `dispatch`, `start`) ```json { "jobId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "client": "codex", "status": "running" } ``` ### Job status shape (`status`) ```json { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "client": "codex", "prompt": "refactor this function", "status": "running", "startedAt": "2026-05-19T12:34:56.789Z", "pid": 12345 } ``` ### Job result shape (`results`) ```json { "stdout": "...", "stderr": "...", "exitCode": 0, "client": "codex", "durationMs": 45231 } ``` ### JSON error shape ```json { "error": "..." } ``` Exit codes: | Code | Meaning | |---|---| | `0` | Success | | `1` | Missing/unknown command, missing argument, unknown client, resolution failure, execution error, or job lifecycle error | ## Error Handling Guidance ### `Client "" not found or not installed` Meaning: the requested client binary is not on `PATH` and not overridden by config. Actions: 1. Confirm the client is installed (`codex --version`, `claude --version`, etc.) 2. Check that its directory is on `PATH` 3. Or override the path in `~/.openclaw/ai-cli-dispatch.json` ### `Prompt cannot be empty` Meaning: the prompt string was empty or whitespace-only. Action: supply a non-empty `--prompt` or positional prompt argument. ### `Execution timed out after 600000ms` Meaning: the client subprocess did not finish within the timeout. Action: the client may be waiting for interactive input or the task is too large. Break the prompt into smaller pieces, increase `--timeout`, or run the client directly to diagnose. Async jobs that time out are recorded with status `timed_out`. ### `Could not resolve client from prompt` Meaning: `dispatch` found no matching keyword and no `defaultClient` is configured. Action: include a client name in the prompt (e.g., `"use claude to ..."`) or set `defaultClient` in config. ### `Job "" not found` Meaning: the requested job ID does not exist in the job store. Action: verify the job ID. Job files are stored under `~/.openclaw/ai-cli-dispatch/jobs/`. If the directory was cleaned or the host restarted, old jobs may have been removed. ### `Job "" result is not available (status: )` Meaning: `results` was called on a job that has not finished (`running`) or finished unsuccessfully (`failed`, `timed_out`, `cancelled`). Action: poll `status` until the job reaches `completed`, or inspect `status` output for the error field. ## Job Lifecycle Workflows ### Fire-and-forget ```bash JOB=$(ai-cli-dispatch run --client codex --prompt "refactor auth" --json | jq -r '.jobId') # caller continues immediately ``` ### Poll until completion ```bash JOB=$(ai-cli-dispatch start --client claude --prompt "write tests" --json | jq -r '.jobId') while [ "$(ai-cli-dispatch status "$JOB" --json | jq -r '.status')" = "running" ]; do sleep 5 done ai-cli-dispatch results "$JOB" --json ``` ### Sync one-shot ```bash ai-cli-dispatch run --client opencode --prompt "fix lint" --sync --text ``` ### Batch cleanup ```bash ai-cli-dispatch cleanup-jobs --max-age 24h ``` ## Common Flows ### Check what is installed ```bash ai-cli-dispatch list --json ``` ### Run a quick task through a specific client (async) ```bash ai-cli-dispatch run --client codex --prompt "fix lint errors in src/app.ts" ``` ### Run a quick task synchronously ```bash ai-cli-dispatch run --client codex --prompt "fix lint errors in src/app.ts" --sync ``` ### Let the tool pick the client from the prompt ```bash ai-cli-dispatch dispatch "claude: add unit tests for utils.ts" ``` ### Force a client when the prompt is ambiguous ```bash ai-cli-dispatch dispatch "review this PR" --client claude ``` ## Coexistence with ACP `ai-cli-dispatch` is a direct subprocess dispatcher. It is not an ACP agent and does not participate in ACP orchestration. - Use `ai-cli-dispatch` when you need a quick, local, one-shot CLI execution or a background job. - Use ACP (`docs/openclaw-acp-orchestration.md`) when you need session-bound coding harnesses with thread context, multi-turn review, or orchestrator-managed verification gates. ## Implementation Notes - The dispatcher is TypeScript/Node.js with a single external dependency (`minimist`). - Client arguments are hardcoded per tool to match each client’s stable CLI contract. - The default timeout is 10 minutes (`600_000` ms); override with `--timeout` or config. - On Windows, discovery uses `where` instead of `which` and `.exe` extensions are assumed. - Async jobs run as detached processes with `stdio: ["ignore", "pipe", "pipe"]` so the dispatcher can exit without waiting. - Job files are written atomically to `~/.openclaw/ai-cli-dispatch/jobs/.json`.