A local agent CLI compatibility layer for running prompt-driven turns through supported backend CLIs.
openp wraps one prompt turn through a selected backend and returns the result through a stable openp JSON interface.
- Node.js >= 20
- tmux (used internally by the Claude backend PTY runner;
brew install tmuxon macOS,apt install tmuxon Debian/Ubuntu) - At least one backend CLI installed:
| Backend | Required CLI | Install |
|---|---|---|
| Claude | claude |
Claude Code CLI |
| Codex | codex |
Codex CLI |
| Kiro | kiro-cli |
Kiro CLI |
git clone https://github.com/sky1core/open-p.git
cd open-p
npm install
npm run build
npm link
openp --versionopenp claude "hello"
openp codex "hello"
openp kiro "hello"The first positional argument selects the backend. There is no default backend.
Pipe from stdin:
echo "summarize this" | openp claude| Flag | stdout |
|---|---|
--output-format text |
Answer text only |
--output-format json |
Single JSON result object |
--output-format stream-json |
JSONL records ending with openp.form: "result" |
Default is text.
JSON-family outputs use openp as the top-level public object. Use openp.form, openp.scope, and openp.output to read the result.
openp claude --output-format json "hello"{
"openp": {
"form": "result",
"output": {
"answer": ["hello"],
"reasoning": [],
"toolCall": [],
"toolResult": []
},
"sessionId": "...",
"metadata": { "..." }
}
}Result openp.output contains aggregate arrays for answer, reasoning, toolCall, and toolResult.
By default, stream-json output emits only the terminal result record. Use --streaming to receive active-turn streaming events as the backend works:
openp claude --output-format stream-json --streaming "hello"Streaming records use openp.form: "streaming" with a strict oneOf openp.output:
{"openp": {"form": "streaming", "output": {"answer": "hel"}}}
{"openp": {"form": "streaming", "output": {"answer": "hello"}}}
{"openp": {"form": "result", "output": {"answer": ["hello"], "reasoning": [], "toolCall": [], "toolResult": []}}}Streaming answer and reasoning values are cumulative snapshots, not deltas. Tool streaming records contain complete objects.
The Claude backend runs through a PTY-backed interactive Claude Code session and reads Claude's local session log for structured turn data. It does not use Claude print mode.
To keep one openp invocation mapped to one synchronous prompt turn, the Claude backend disables background/monitor workflows and blocking interactive question tools at launch.
The Claude backend supports configured instances: derived backend ids that run Claude Code with a separate configuration directory (CLAUDE_CONFIG_DIR). Each instance keeps its own Claude Code login, settings, and session logs, so you can keep independent Claude Code profiles side by side.
Define instances in ${XDG_CONFIG_HOME:-~/.config}/open-p/instances.yaml:
instances:
claude-alt:
backend: claude
configDir: ~/.claude-altThen select the instance like any backend:
openp claude-alt "prompt"Notes:
configDiris required and must be absolute or~/-prefixed. Runclaudeonce with thatCLAUDE_CONFIG_DIRto log the profile in before using it throughopenp.- Instance ids must not collide with built-in backend ids.
- Sessions are bound to the instance that created them; resuming a session through a different backend or instance id fails.
- The base
claudebackend always uses the default Claude Code configuration directory; it does not read an ambientCLAUDE_CONFIG_DIR. - An instance reads user-scope skills, subagents, and
settings.json(hooks, permissions) only from its ownconfigDir; they are not inherited from the default~/.claudeprofile. Replicate them in the instance directory (copy, or symlink theskills/agentsdirectories) if you want the same behavior. - Give the instance
configDirits ownCLAUDE.md. Without one, a workspace under$HOMEmakes Claude Code pick up the default~/.claude/CLAUDE.mdas a project file, and any external@import it declares raises a per-workspace "Allow external CLAUDE.md file imports?" approval prompt that an unattended run cannot answer. ACLAUDE.mdin the instanceconfigDirloads the same imports as user scope and skips that prompt.
First-turn session ids are generated by openp or the backend. To resume a session, capture the session id from JSON output and pass it with --resume:
openp codex --output-format json "first prompt"
# read openp.sessionId from the output
openp codex --resume <session-id> "follow-up"Text output does not include session ids.
No default timeout. Set one explicitly:
openp claude --timeout 60 "prompt"--timeout 0 disables it. Ctrl-C sends graceful interruption; repeated Ctrl-C escalates.
| Option | Purpose |
|---|---|
--resume <session-id> |
Resume a previous session |
--timeout <seconds> |
Per-turn wall-clock timeout |
--input-format <fmt> |
text or stream-json |
--output-format <fmt> |
text, json, or stream-json |
--model <model> |
Backend model selection |
--effort <level> |
Reasoning effort where supported |
--tools <tools> |
Tool allowlist where supported |
--json-schema <json> |
Structured output schema where supported |
--streaming |
Active-turn streaming opt-in |
--dangerously-skip-permissions |
Trust backend tool execution |
--debug-log |
Write diagnostics to the open-p state log |
--verbose |
Include diagnostic markers in output |
Options may appear before or after the backend name. Only the options listed above are the public openp interface.
Backend-native flags that are not listed above fail closed instead of being ignored or passed through. This includes Claude print-mode and raw Claude configuration flags such as -p, --print, --include-partial-messages, --brief, --permission-mode, --allowedTools, --allowed-tools, --disallowedTools, --disallowed-tools, --mcp-config, --settings, --setting-sources, and --add-dir.
Use --tools for the public tool-policy selector and --dangerously-skip-permissions for the public trusted-tool intent. The selected backend may still reject a public option when it does not support that feature.
For programmatic use, send structured input on stdin:
printf '{"type":"user","message":{"content":"hello"}}\n' \
| openp claude --input-format stream-json --output-format stream-json--debug-log writes to:
${XDG_STATE_HOME:-~/.local/state}/open-p/workspaces/<workspace-hash>/logs/debug.jsonl
Debug logs may contain session ids, prompts, response previews, and error context.
--verbose adds diagnostic markers to output. In text mode, a marker line is appended after the answer. In JSON modes, warnings appear under openp.metadata.warnings.
MIT