What is a provider
A caller names a provider (Claude), never an adapter. The distinction is deliberate: an adapter is a working object with collaborators and state, while a provider is the identity of a CLI — the thing you point at when you say “talk to claude” or “switch to codex”.
command.provider = Codex; // a value assignment, not handing over machineryThe provider mints an adapter when the command actually needs one, which is at send() and not before.
Identity against machinery
Section titled “Identity against machinery”The two words name genuinely different things, and the split is what keeps the assignment above honest.
| Provider | Adapter | |
|---|---|---|
| What it is | The identity of a CLI | A working object |
| What it holds | A name, and how to mint an adapter | Collaborators and state — argv builder, path augmenter, job spawner, event parser |
| How many exist | One per CLI, exported as a singleton | A fresh one per session |
| What a caller does with it | Names it | Never touches it |
Claude is exported as a singleton rather than as a class to instantiate, because there is one claude CLI. Command.open(Claude, …) should read as naming it rather than as constructing something.
import { Claude, Command } from 'activecli';
const command = Command.open(Claude, { workingDir: '/proj' });Why the assignment is free
Section titled “Why the assignment is free”Were command.provider = Codex handing over machinery, it would have to cost something — a process to start, an executable to find, an environment to assemble. It does not, because a provider carries none of that.
This is the same deferral Command borrows from ActiveRecord’s Relation. Setters accumulate intent, toArgv() shows what would run, and send() executes. Switching the connection costs nothing until execution.
| ActiveRecord | ActiveCLI |
|---|---|
| Switching the connection costs nothing until execution | command.provider = Codex costs nothing until the next send() |
Pointing a command at a different CLI takes effect at the next send(), not at the moment of assignment. A CLI process cannot become another one, so switching means replacing the process — and there is no reason to pay for that until there is something to run.
The provider mints the adapter
Section titled “The provider mints the adapter”AbstractProvider declares two members that a subclass must fill in, and nothing else is required of it:
abstract get name(): string;abstract createAdapter(): AbstractAdapter;createAdapter() returns a fresh adapter. A session is a running process with its own pipes and its own state, so it gets its own adapter; the provider is what both sessions were named through.
That is also why the provider can be swapped for free. Nothing about Codex is alive until send() asks it for an adapter, so naming it is just naming it.
What this buys
Section titled “What this buys”The competing plugin surveyed before this library was written attaches six CLI channels with no common contract between them. Its send takes sessionId, disableThinking and agentPrompt for Claude, and threadId, baseUrl, apiKey and serviceTier for Codex — the same verb with different parameters, so the caller must know which provider it is addressing in order to build a payload. That is not integration; it is separate storage under one roof.
Keeping identity apart from machinery is what makes the other arrangement possible. A caller says which CLI, in a word; what that word implies is the adapter’s problem, not theirs.
Where to go next
Section titled “Where to go next”- Abstract provider — the surface every provider presents, and why
ask()lives on it. - Claude provider — the one implementation that exists today.
- Writing a provider — attaching a CLI of your own.