-
Notifications
You must be signed in to change notification settings - Fork 0
CLI Wrapper
A reference Provider extension that talks to an LLM through a subprocess instead of an HTTP endpoint. The canonical use case is a subscription CLI the user has already authenticated — e.g., a vendor's own claude or codex command-line tool — where the user wants stud-cli to orchestrate conversations that bill against that subscription rather than a separate API key.
This page is a reference extension — it describes one way a Provider extension can work, not a normative contract. The normative surface is Providers (contract). A CLI-wrapper is not bundled with stud-cli in v1.
Two classes of users:
-
API-key users reach providers over HTTPS with
openai-compatible,anthropic, orgeminiadapters. Auth is an API key; billing flows through the vendor's API dashboard. -
Subscription users have already installed and authenticated a vendor-owned CLI (
claude,codex, etc.) that bills against a seat-based subscription. They want stud-cli's orchestration — state machines, context providers, hooks — to layer on top of the subscription they already pay for, without re-authenticating.
Subscription users cannot use the bundled adapters because those adapters require an API key, not the CLI's own session. The CLI-wrapper is the pattern.
flowchart LR
Session[stud-cli session] --> Adapter[CLI-wrapper adapter]
Adapter --> Spawn[spawn vendor CLI<br/>with prompt on stdin]
Spawn --> CLI[vendor CLI process]
CLI --> Subscription[vendor subscription auth]
CLI --> Out[stdout + stderr]
Out --> Adapter
Adapter --> Core[core's internal event shape]
The adapter translates core's request shape into a subprocess invocation, reads the subprocess's output, and translates back into core's internal events. The CLI handles auth and billing; stud-cli stays out of that loop.
The adapter's provider config might look like this (conceptually — exact field names follow Providers). The provider entry sits at settings.json.providers.<id>; the user picks the id (e.g., claude-subscription).
| Field | Meaning |
|---|---|
protocol |
cli-wrapper. |
command |
The executable to invoke. Must be on PATH or an absolute path. |
argsTemplate |
Template for subprocess arguments. Placeholder for the prompt. |
stdinMode |
prompt-only or streaming. |
parseMode |
text (assume the CLI returns prose) or jsonl (structured events). |
models |
string[] — model names the CLI accepts, passed via --model or the CLI's own flag. |
timeoutMs |
Per-request subprocess timeout. |
env |
Environment variables to pass through. Typically empty — the CLI reads its own auth from $HOME/.<vendor>/. |
baseURL is absent — there is no HTTP endpoint. apiKeyRef is absent — the wrapped CLI handles auth. An adapter that requires an API key for a CLI-wrapper protocol is misdesigned.
A subscription CLI typically declares a reduced capability set compared to the API adapter. What a given CLI supports is vendor-specific; conservative defaults apply when unknown:
| Capability | Typical CLI posture |
|---|---|
streaming |
Vendor-dependent. Many CLIs buffer. A CLI that emits JSONL can stream; one that prints prose at end does not. |
toolCalling |
Often not supported through the CLI path. An SM that depends on tool calling must not switch to a CLI-wrapper without re-validation. |
structuredOutput |
Usually false unless the CLI advertises a structured-output flag. |
multimodalInputs |
Text-only unless the CLI accepts file references. |
contextWindow |
Inherits the vendor's model default; not adapter-negotiated. |
reasoning |
Vendor-dependent. |
parallelToolCalls |
False by default. |
A session that depends on toolCalling: strict cannot switch to a CLI-wrapper whose models declare toolCalling: false — the switch fails fast per Capability-Mismatch-Switch. This is the point: the user sees the mismatch before a turn silently degrades.
For each turn:
- Core assembles the request (messages, tools, parameters) as usual.
- The adapter serializes the request into a prompt string per the CLI's input contract. A CLI that accepts only a single prompt flattens the conversation; one that accepts JSONL preserves structure.
- The adapter spawns the subprocess with
stdinModecontrolling how the prompt is fed (one-shot on stdin, or a streamed pipe). - The adapter reads stdout (and optionally stderr) and applies
parseModeto produce events.
Tool-call translation is out of scope for simple CLI-wrappers. A CLI that emits structured tool-call events may be parseable; most do not. Sessions that need tool calls should use an HTTP adapter or a CLI that itself is tool-aware.
The adapter does not handle auth. The wrapped CLI is assumed to be installed and authenticated through its own flow (e.g., the user has already run claude login or codex auth).
| Concern | Posture |
|---|---|
| Session token | Stored by the CLI under its own config directory (~/.<vendor>/). Not stud-cli's surface. |
| Refresh | Handled by the CLI. |
| Re-auth prompts | The CLI may print an auth-needed message to stderr. The adapter surfaces it as a ProviderTerminal / AuthFailure; the session does not auto-resolve — the user runs the CLI's auth command manually. |
stud-cli's env surface |
Not used. The CLI reads its own config. |
See First Run — the CLI-wrapper's first-run experience is "install and authenticate the vendor CLI first," then add the provider to settings.json.
| Subprocess condition | Core class / code |
|---|---|
| Exit 0, non-empty stdout | Success. |
| Exit 0, empty stdout |
ProviderTerminal / EmptyResponse. |
| Non-zero exit with stderr "not authenticated" |
ProviderTerminal / AuthFailure. User must run CLI auth. |
| Non-zero exit with stderr "rate limited" / "quota" |
ProviderTransient / RateLimited. Adapter honors the core default retry schedule unless vendor guidance suggests otherwise. |
| Non-zero exit, generic |
ProviderTerminal / SubprocessFailed. Details in audit. |
Subprocess exceeded timeoutMs
|
ProviderTransient / NetworkTimeout. Subprocess is sent a terminate signal per Platform Integration § Signal handling. |
| Command not on PATH | Fails adapter init; session does not start with this provider; diagnostic emitted. |
Retry policy mirrors HTTP adapters: transient classes retry under Error Model; terminal classes do not.
| Phase | Behavior |
|---|---|
init |
Verify command is reachable. If not, fail init; session can start with other providers. |
activate |
No persistent subprocess — each request spawns fresh. |
| Per request | Spawn, feed prompt, read output, reap. |
deactivate |
Reap any outstanding subprocess; send terminate then kill. |
dispose |
Final reap; idempotent. |
A long-running subprocess is not kept across turns in the reference design. Each turn is a fresh spawn — simpler, more predictable, avoids the stateful-subprocess failure modes. An adapter that keeps a subprocess alive for warm performance declares that behavior and documents its crash semantics.
- Subprocess inherits stud-cli's env by default. Per Platform Integration § Subprocess invocation, core does not filter. A CLI-wrapper extension is trusted not to leak env to its wrapped CLI in harmful ways.
-
The wrapped CLI runs with the user's privileges. A malicious CLI in
PATHwould have the same blast radius as running it manually. Trust is the user's problem to have solved before adding the provider. - Prompt injection surface. The adapter builds a prompt from core's assembled request. If the wrapped CLI supports magic strings that escape the prompt (some do), those strings can reach the subscription LLM. The reference adapter disallows unescaped control sequences and rejects prompts that contain them with a diagnostic rather than silently forwarding.
-
No shell wrapping. The adapter invokes
commanddirectly, not through a shell, to avoid shell-injection footguns. A CLI that needs shell semantics opts in explicitly inargsTemplate. -
Audit.
ProviderRequestStartedcarries the provider id and model; the argv is redacted by default because a CLI may accept the prompt on argv in some modes. Loggers that need full argv must explicitly declare so.
Two conceptual examples (exact vendor CLI flags change — check each vendor's documentation):
{
"id": "claude-subscription",
"protocol": "cli-wrapper",
"command": "claude",
"argsTemplate": ["-p", "--model", "{model}"],
"stdinMode": "prompt-only",
"parseMode": "text",
"models": ["claude-opus-4-7"]
}
{
"id": "codex-subscription",
"protocol": "cli-wrapper",
"command": "codex",
"argsTemplate": ["exec", "--jsonl"],
"stdinMode": "streaming",
"parseMode": "jsonl",
"models": ["gpt-5.4"]
}
Flag names are illustrative; check each vendor's CLI for current flags.
- You have an API key — use the bundled HTTP adapter. Faster, streaming-ready, tool-call-capable.
- You need tool calling — most CLIs do not forward tool-use protocol cleanly. The HTTP adapter is more reliable.
- You need prompt caching — only the HTTP adapter can mark cache breakpoints per Anthropic § Prompt caching.
- You need parallel tool calls — subprocess output rarely conveys them.
The CLI-wrapper is a pragmatic bridge for subscription users. It is not a general-purpose replacement for the HTTP adapter.
- Execution Model
- Message Loop
- Concurrency and Cancellation
- Error Model
- Event and Command Ordering
- Event Bus
- Command Model
- Interaction Protocol
- Hook Taxonomy
- Host API
- Extension Lifecycle
- Env Provider
- Prompt Registry
- Resource Registry
- Session Lifecycle
- Session Manifest
- Persistence and Recovery
- Stage Executions
- Subagent Sessions
- Contract Pattern
- Versioning and Compatibility
- Deprecation Policy
- Capability Negotiation
- Dependency Resolution
- Validation Pipeline
- Cardinality and Activation
- Extension State
- Conformance and Testing
- Providers
- Provider Params
- Tools
- Hooks
- UI
- Loggers
- State Machines
- SM Stage Lifecycle
- Stage Definitions
- Commands
- Session Store
- Context Providers
- Settings Shape
- Trust Model
- Project Trust
- Extension Isolation
- Extension Integrity
- LLM Context Isolation
- Secrets Hygiene
- Security Modes
- Tool Approvals
- MCP Trust
- Sandboxing
- Configuration Scopes
- Project Root
- Extension Discovery
- Extension Installation
- Extension Reloading
- Headless and Interactor
- Determinism and Ordering
- Launch Arguments
- Network Policy
- Platform Integration
Tools
UI
Session Stores
Loggers
Providers
Hooks
Context Providers
Commands
- First Run
- Default Chat
- Tool Call Cycle
- Hook Interception
- Guard Deny Reproposal
- State Machine Workflow
- SM Stage Retry
- Hot Model Switch
- Capability Mismatch Switch
- Session Resume
- Session Resume Drift
- Approval and Auth
- Interaction Timeout
- Headless Run
- Parallel Tool Approvals
- Subagent Delegation
- Scope Layering
- Project First-Run Trust
- Reload Mid-Turn
- Compaction Warning
- MCP Remote Tool Call
- MCP Prompt Consume
- MCP Resource Bind
- MCP Reconnect