diff --git a/README.md b/README.md index 7677476..d4f2d23 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ This repository contains practical OpenClaw skills and companion integrations. I - Per-skill runtime instructions: `skills//SKILL.md` - Integration implementation files: `integrations//` - Integration docs: `docs/*.md` +- Tool implementation files: `tools//` +- Tool docs: `docs/*.md` ## Skills @@ -34,6 +36,12 @@ This repository contains practical OpenClaw skills and companion integrations. I | `google-maps` | Traffic-aware ETA and leave-by calculations using Google Maps APIs. | `integrations/google-maps` | | `google-workspace` | Gmail and Google Calendar helper CLI for profile, mail, attachment-capable send, calendar search, and event creation. | `integrations/google-workspace` | +## Tools + +| Tool | What it does | Path | +|---|---|---| +| `ai-cli-dispatch` | Dispatch AI CLI coding tasks to available clients (Codex, Claude Code, OpenCode) with automatic discovery, version checking, and execution. | `tools/ai-cli-dispatch` | + ## Operator docs | Doc | What it covers | diff --git a/docs/README.md b/docs/README.md index be7a662..abb3ec9 100644 --- a/docs/README.md +++ b/docs/README.md @@ -20,6 +20,12 @@ This folder contains detailed docs for each skill in this repository. - [`google-maps`](google-maps.md) — Traffic-aware ETA and leave-by calculations via Google Maps APIs - [`google-workspace`](google-workspace.md) — Gmail and Google Calendar helper CLI with attachment-capable send support +## Tools + +- [`ai-cli-dispatch`](ai-cli-dispatch.md) — Dispatch AI CLI coding tasks to available clients with automatic discovery, version checking, and execution +- [`installation`](installation.md) — Prerequisites, install steps, PATH configuration, and optional config file setup for `ai-cli-dispatch` +- [`architecture`](architecture.md) — Design decisions, module breakdown, data flow, coexistence with ACP, and extension points for `ai-cli-dispatch` + ## Operator Docs - [`openclaw-acp-orchestration`](openclaw-acp-orchestration.md) — OpenClaw ACP orchestration for Codex and Claude Code on the gateway host diff --git a/docs/ai-cli-dispatch.md b/docs/ai-cli-dispatch.md new file mode 100644 index 0000000..66928ef --- /dev/null +++ b/docs/ai-cli-dispatch.md @@ -0,0 +1,258 @@ +# 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 + +The tool is a lightweight sync-only dispatcher. It does not implement streaming, chat sessions, or ACP orchestration. 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 [--json|--text] +ai-cli-dispatch dispatch [--client ] [--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. + +```bash +ai-cli-dispatch run --client codex --prompt "refactor this function" +ai-cli-dispatch run --client claude --prompt "add tests for auth middleware" +ai-cli-dispatch run --client opencode --prompt "migrate to ESM" +``` + +The prompt is forwarded with each client’s native argument shape: + +| Client | Arguments passed | +|---|---| +| `codex` | `exec ""` | +| `claude` | `-p ""` | +| `opencode` | `""` | + +### `dispatch` + +Auto-resolve the client from prompt keywords, then execute. + +```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 +``` + +## 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" +} +``` + +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. + +JSON success shape (`run` and `dispatch`): + +```json +{ + "stdout": "...", + "stderr": "...", + "exitCode": 0 +} +``` + +JSON error shape: + +```json +{ + "error": "..." +} +``` + +Exit codes: + +| Code | Meaning | +|---|---| +| `0` | Success | +| `1` | Missing/unknown command, missing argument, unknown client, resolution failure, or execution 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 300000ms` + +Meaning: the client subprocess did not finish within the default 5-minute timeout. + +Action: the client may be waiting for interactive input or the task is too large. Break the prompt into smaller pieces, or run the client directly to diagnose. + +### `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. + +## Common Flows + +### Check what is installed + +```bash +ai-cli-dispatch list --json +``` + +### Run a quick task through a specific client + +```bash +ai-cli-dispatch run --client codex --prompt "fix lint errors in src/app.ts" +``` + +### 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 runs the client binary synchronously and returns its output. 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. +- 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 5 minutes (`300_000` ms). +- On Windows, discovery uses `where` instead of `which` and `.exe` extensions are assumed. diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..8955934 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,213 @@ +# ai-cli-dispatch Architecture + +This document describes the internal design of `ai-cli-dispatch`, the module breakdown, data flow, key design decisions, and how to extend the tool. + +## Module Breakdown + +```text +src/ +├── cli.ts — Entry point: argument parsing, command routing, I/O formatting +├── types.ts — Shared types and error classes +├── constants.ts — Client name registry and platform helpers +├── config.ts — Layered configuration resolution (flags → env → file → PATH) +├── detect.ts — Client discovery: binary lookup and version extraction +├── dispatch.ts — Prompt-to-client resolution (explicit flag → keywords → default) +└── execute.ts — Subprocess spawning, stdout/stderr capture, timeout handling +``` + +### Responsibilities + +| Module | Responsibility | +|---|---| +| `cli.ts` | Parses `argv` with `minimist`, routes to `list` / `run` / `dispatch`, prints JSON or text output, and controls the process exit code. | +| `types.ts` | Defines `ClientName`, `ClientInfo`, `ExecResult`, `ToolConfig`, and the error hierarchy (`ClientNotFoundError`, `ExecError`). | +| `constants.ts` | Holds the canonical `CLIENT_NAMES` array and `isWindows()` helper used by discovery and config. | +| `config.ts` | Resolves per-client binary paths and the optional `defaultClient` from four layered sources. | +| `detect.ts` | Locates each client binary on `PATH`, falls back to a manual directory scan, and invokes `--version` to extract a semver string. | +| `dispatch.ts` | Chooses the target client from a prompt string using ordered keyword matching, with overrides for explicit `--client` and `defaultClient`. | +| `execute.ts` | Spawns the chosen client with its native argument shape, buffers `stdout`/`stderr`, enforces a timeout, and returns an `ExecResult` or throws a typed error. | + +## Data Flow + +A typical `dispatch` invocation flows through four stages: + +``` +┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ +│ detect │ ──► │ config │ ──► │ dispatch │ ──► │ execute │ +└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ + │ │ │ │ + which/where flags/env/file keyword scan spawn child + PATH walk defaultClient --client override capture output + --version fallback default timeout / exitCode +``` + +### 1. Detect + +`detectClients()` iterates over `CLIENT_NAMES` and attempts to locate each binary: + +1. Invoke `which ` (or `where ` on Windows). +2. If that fails, walk `PATH` segments manually and test `existsSync()`. +3. If a binary is found, run ` --version` and parse the first semver-like match. + +Result: an array of `ClientInfo` objects with `name`, `found`, `path`, and `version`. + +### 2. Config + +`resolveConfig()` builds a `ResolvedConfig` by layering sources (highest to lowest precedence): + +1. **CLI flags** — `--codex-path`, `--claude-path`, `--opencode-path`, `--default-client` +2. **Environment variables** — `AI_CLI_CODEX_PATH`, `AI_CLI_CLAUDE_PATH`, `AI_CLI_OPENCODE_PATH`, `AI_CLI_DEFAULT_CLIENT` +3. **Config file** — `~/.openclaw/ai-cli-dispatch.json` (`paths` and `defaultClient` keys) +4. **PATH discovery** — `which`/`where` fallback via `defaultWhichSync()` + +Only values for the three known `ClientName` entries are accepted; unknown `defaultClient` values are ignored. + +### 3. Dispatch + +`resolveClient(prompt, config)` decides which client to use: + +1. If `config.client` is a valid `ClientName`, return it immediately. +2. Lower-case the prompt and scan for substrings in order: + - `"open code"` → `opencode` + - `"claude"` → `claude` + - `"codex"` → `codex` + - `"opencode"` → `opencode` +3. If no keyword matches, return `config.defaultClient` or `null`. + +This ordering intentionally prioritizes `"open code"` before `"opencode"` so the spaced natural-language variant wins. + +### 4. Execute + +`executePrompt(client, prompt, options)` runs the selected client: + +1. Reject empty or whitespace-only prompts with `ExecError`. +2. Validate that an explicit `clientPath` exists on disk (if provided). +3. Map the client to its native argument array via `CLIENT_ARGS`: + - `codex` → `["exec", prompt]` + - `claude` → `["-p", prompt]` + - `opencode` → `[prompt]` +4. `spawn()` the process with `shell: false`. +5. Buffer `stdout` and `stderr` via `"data"` listeners. +6. Start a `setTimeout`; if it fires, `child.kill()` is sent. +7. On `close`, resolve with `{ stdout, stderr, exitCode }`. +8. On `error`, reject with `ClientNotFoundError` for `ENOENT` or `ExecError` for anything else. +9. On timeout, reject with `ExecError` containing the buffered output so far. + +The default timeout is **5 minutes** (`300_000` ms). + +## Design Decisions + +### Coexistence with ACP + +`ai-cli-dispatch` is intentionally **not** an ACP agent. It is a thin, local subprocess wrapper with no session state, no thread binding, and no orchestrator protocol. + +- Use `ai-cli-dispatch` when you need a quick, one-shot CLI execution on the gateway host. +- Use ACP (`docs/openclaw-acp-orchestration.md`) when you need session-bound coding harnesses, multi-turn review, or orchestrator-managed verification gates. + +This separation keeps the dispatcher small and avoids duplicating ACP’s scheduling, context persistence, and review-loop responsibilities. + +### Keyword Dispatch vs NLP + +Client resolution uses deterministic substring matching instead of natural-language parsing or an LLM call. + +**Rationale:** +- **Speed:** No network round-trip or model load; resolution is synchronous and sub-millisecond. +- **Predictability:** The same prompt always resolves to the same client. There is no temperature, context window, or model-version drift. +- **Debuggability:** A user can read the ordered keyword list and know exactly why a given prompt resolved to a given client. +- **Scope fit:** The dispatcher only needs to distinguish three clients. A full NLP pipeline would be overkill. + +The trade-off is that prompts like `"compare codex and claude"` resolve to `codex` because `"codex"` is checked first. Users can always override with `--client`. + +### Sync-Only Initial Release + +The current implementation is entirely synchronous from the caller’s perspective: `executePrompt` returns a promise that resolves only when the child process exits or the timeout fires. + +**Rationale:** +- The primary use case is one-shot tasks (refactor, add tests, migrate) where the agent needs the complete output before proceeding. +- Streaming would require a different output contract (callbacks, generators, or an event emitter) and complicates the JSON error model. +- ACP already covers interactive, streaming, and session-based use cases. + +Streaming is an intentional future extension point (see below). + +### Error Taxonomy + +All runtime failures are represented as typed errors so callers and tests can branch precisely: + +| Error | When thrown | Data carried | +|---|---|---| +| `ClientNotFoundError` | Binary not on `PATH`, explicit `clientPath` missing, or `ENOENT` from `spawn` | `message` with client name | +| `ExecError` | Empty prompt, unknown client, timeout, non-`ENOENT` spawn error, or child exit | `message` + full `ExecResult` (`stdout`, `stderr`, `exitCode`) | + +`ExecError` carries the `ExecResult` so that timeout handlers still return partial output. This avoids losing buffered stdout/stderr when a long-running task is killed. + +### Injection-Friendly Module Boundaries + +Every non-trivial module accepts an `options` bag with injectable dependencies (`spawnSync`, `spawn`, `existsSync`, `whichSync`, `readFileSync`, etc.). + +**Rationale:** +- Unit tests can run without touching the real filesystem, `PATH`, or subprocess layer. +- The CLI itself injects its real dependencies through default parameters, so production behavior is unchanged. +- There is no global mocking required; each test provides its own narrow fakes. + +### Minimal Dependency Surface + +The runtime dependency graph contains exactly one external package: `minimist` (argument parsing). Everything else uses Node.js built-ins (`child_process`, `fs`, `os`, `path`). + +**Rationale:** +- Reduces supply-chain risk and install time. +- Avoids version-lock issues across Node.js 20+ environments. +- Keeps the compiled/bundled footprint negligible for a tool that is often installed as a sidecar. + +## Extension Points + +### Adding a New Client + +To support a fourth (or fifth) AI CLI client, change four files in `src/` and the corresponding tests: + +1. **`src/types.ts`** — Add the new name to the `ClientName` union type. +2. **`src/constants.ts`** — Append the new name to `CLIENT_NAMES`. +3. **`src/execute.ts`** — Add an entry to `CLIENT_ARGS` with the client’s native argument shape. +4. **`src/config.ts`** — No change required; the existing loop over `CLIENT_NAMES` automatically picks up the new env/flag/file keys. +5. **`src/dispatch.ts`** — Add a keyword check for the new client in `resolveClient`. Decide its precedence relative to existing keywords. +6. **Tests** — Add colocated test cases in `tests/dispatch.test.ts`, `tests/execute.test.ts`, and `tests/detect.test.ts`. + +No changes are needed in `cli.ts` because it iterates over `CLIENT_NAMES` for validation. + +### Streaming Support + +If a future use case requires real-time output (e.g., long-running codegen with progressive feedback), the cleanest extension is to add an optional `onData` callback to `ExecuteOptions`: + +```typescript +export interface ExecuteOptions { + clientPath?: string; + timeoutMs?: number; + spawn?: ...; + existsSync?: ...; + onData?: (chunk: string, stream: "stdout" | "stderr") => void; +} +``` + +When `onData` is provided, `executePrompt` would: +- Continue buffering internally for the final `ExecResult`. +- Also emit each chunk through `onData` so the caller can stream to a UI or logger. +- Reject/resolve with the same error taxonomy. + +This preserves backward compatibility: existing callers that omit `onData` receive the exact same buffered `ExecResult` they get today. + +### Platform Backends + +The current Windows support is limited to discovery (`where` instead of `which`, `.exe` extension assumptions). If future clients require platform-specific spawn options (e.g., PowerShell quoting rules), the extension point is `CLIENT_ARGS` or a new `CLIENT_SPAWN_OPTIONS` record keyed by `ClientName`. + +## Testing Strategy + +The test suite in `tests/` mirrors the `src/` structure: + +| Test file | Coverage | +|---|---| +| `cli.test.ts` | Argument parsing, command routing, JSON/text output modes, exit codes, error formatting | +| `config.test.ts` | Layered precedence of flags, env, file, and `which` fallback; malformed JSON tolerance | +| `detect.test.ts` | `which` success/failure, PATH directory fallback, version parsing, missing binary handling | +| `dispatch.test.ts` | Keyword matching, case insensitivity, `--client` precedence, `defaultClient` fallback, invalid flag handling | +| `execute.test.ts` | Successful execution, stderr capture, non-zero exit codes, `ENOENT` → `ClientNotFoundError`, timeout, empty prompt rejection, special-character preservation | + +All tests use injected mocks; no test spawns real client binaries or reads the real filesystem. diff --git a/docs/installation.md b/docs/installation.md new file mode 100644 index 0000000..869c3f8 --- /dev/null +++ b/docs/installation.md @@ -0,0 +1,219 @@ +# Installation + +This page covers installing the `ai-cli-dispatch` tool, its prerequisites, and post-install verification. + +## Prerequisites + +- **Node.js** ≥ 20 (required for `tsx`, `import` attributes, and modern `node:child_process` APIs) +- **npm** (bundled with Node.js) +- **Homebrew** (macOS/Linux) — recommended for installing the underlying AI CLI clients (`codex`, `claude`, `opencode`) +- One or more supported AI CLI clients: + - [Codex CLI](https://github.com/openai/codex) + - [Claude Code](https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/overview) + - [OpenCode](https://github.com/opencode-ai/opencode) + +## Install the tool + +### 1. Clone the repository + +```bash +git clone ~/.openclaw/workspace/skills/ai-cli-dispatch +cd ~/.openclaw/workspace/skills/ai-cli-dispatch +``` + +If you already have the full `stef-openclaw-skills` repo checked out, use the path inside it instead: + +```bash +cd ~/.openclaw/workspace/skills/stef-openclaw-skills/tools/ai-cli-dispatch +``` + +### 2. Install Node dependencies + +```bash +npm install +``` + +This installs: +- `tsx` — TypeScript execution runtime +- `minimist` — argument parsing +- `typescript` — type checking + +## PATH configuration + +The helper script lives at `scripts/ai-cli-dispatch`. Add it to your shell PATH so OpenClaw (or your terminal) can invoke it without a full path: + +```bash +# ~/.zshrc or ~/.bashrc +export PATH="$HOME/.openclaw/workspace/skills/ai-cli-dispatch/scripts:$PATH" +``` + +Reload your shell: + +```bash +source ~/.zshrc # or ~/.bashrc +``` + +Verify the script is reachable: + +```bash +which ai-cli-dispatch +ai-cli-dispatch --help +``` + +## Optional configuration file + +Create `~/.openclaw/ai-cli-dispatch.json` to customize client paths and set a default client: + +```bash +mkdir -p ~/.openclaw +$EDITOR ~/.openclaw/ai-cli-dispatch.json +``` + +Example configuration: + +```json +{ + "paths": { + "codex": "/opt/homebrew/bin/codex", + "claude": "/opt/homebrew/bin/claude", + "opencode": "/opt/homebrew/bin/opencode" + }, + "defaultClient": "claude" +} +``` + +### Configuration precedence + +When resolving a client binary, the tool checks sources in this order (first match wins): + +1. CLI flag: `--codex-path`, `--claude-path`, `--opencode-path` +2. Environment variable: `AI_CLI_CODEX_PATH`, `AI_CLI_CLAUDE_PATH`, `AI_CLI_OPENCODE_PATH` +3. File config: `paths.` in `~/.openclaw/ai-cli-dispatch.json` +4. System `PATH` lookup via `which` / `where` + +The `defaultClient` follows the same precedence: + +1. CLI flag: `--default-client` +2. Environment variable: `AI_CLI_DEFAULT_CLIENT` +3. File config: `defaultClient` in `~/.openclaw/ai-cli-dispatch.json` + +## Install AI CLI clients + +### Codex + +```bash +npm install -g @openai/codex +``` + +### Claude Code + +```bash +npm install -g @anthropic-ai/claude-code +``` + +### OpenCode + +```bash +npm install -g @opencode-ai/opencode +``` + +Or via Homebrew where formulas are available: + +```bash +brew install codex # if available in your tap +brew install claude-code # if available in your tap +``` + +## Verification + +### 1. Check local tool health + +```bash +ai-cli-dispatch --help +``` + +Expected output: + +```text +AI CLI Dispatch + +Usage: + ai-cli-dispatch list [--json|--text] + ai-cli-dispatch run --client --prompt [--json|--text] + ai-cli-dispatch dispatch [--client ] [--json|--text] + ai-cli-dispatch --help + +Clients: codex, claude, opencode +``` + +### 2. List discovered clients + +```bash +ai-cli-dispatch list --json +``` + +Example output when two clients are installed: + +```json +[ + { + "name": "codex", + "path": "/opt/homebrew/bin/codex", + "version": "1.2.3", + "found": true + }, + { + "name": "claude", + "path": "/opt/homebrew/bin/claude", + "version": "0.7.8", + "found": true + }, + { + "name": "opencode", + "found": false + } +] +``` + +### 3. Run a quick dispatch + +```bash +ai-cli-dispatch run --client codex --prompt "hello" --json +``` + +This should return a JSON result with `stdout`, `stderr`, and `exitCode`. + +### 4. Test keyword dispatch + +```bash +ai-cli-dispatch dispatch "refactor this using claude" +``` + +The tool inspects the prompt for client keywords (`claude`, `codex`, `opencode`, `open code`) and routes to the matching client. + +## Troubleshooting + +### `Missing local Node dependencies for ai-cli-dispatch` + +Run `npm install` from the skill directory: + +```bash +cd ~/.openclaw/workspace/skills/ai-cli-dispatch +npm install +``` + +### `Client "codex" not found or not installed` + +- Ensure the client is installed globally or via Homebrew +- Verify it is on your PATH: `which codex` +- Or override the path in `~/.openclaw/ai-cli-dispatch.json` or with an environment variable + +### `Prompt cannot be empty` + +The `run` and `dispatch` commands require a non-empty `--prompt` or trailing prompt text. + +### Config file is not being read + +- Verify the file is at exactly `~/.openclaw/ai-cli-dispatch.json` +- Check for JSON syntax errors (trailing commas are not allowed) +- Use `--debug` for deeper troubleshooting if supported by the calling context