Skip to content

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 machinery

The provider mints an adapter when the command actually needs one, which is at send() and not before.

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' });

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.

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.

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.