spacr.qt.ai.providers¶
Provider abstraction — one class per AI vendor. Each shells out to the vendor’s own coding-agent CLI so authentication piggy-backs on the user’s chat subscription (Claude.ai Pro, ChatGPT Plus/Pro/Team, Google account) — no separate API billing.
Anthropic Claude → the
claudeCLI (“Claude Code”)OpenAI ChatGPT → the
codexCLIGoogle Gemini → the
geminiCLI
Each provider:
is_installed() — is the CLI on PATH?
is_logged_in() — best-effort check; falls back to "assume yes if
installed" (the actual auth error surfaces on
the first stream chunk).
stream_chat() — spawn the CLI subprocess, yield stdout chunks.
Conversation context is carried by concatenating the full message history into each prompt (simplest approach that works uniformly across all three CLIs). For subscription users token count is not a concern.
How each CLI is installed is data, not prose: INSTALL_METHODS holds
one row per way of installing it on each operating system, and both the
install_hint a screen shows and the command
spacr.qt.ai.cli_install runs are read from those rows. The GitHub CLI
is described the same way by GitHubCli, which shares the
CommandLineTool interface without being a chat provider.
Exceptions¶
A provider CLI exited with a non-zero status, so what it printed is an |
Classes¶
Abstract base for AI chat providers that shell out to a vendor CLI. |
|
Anthropic Claude via the |
|
OpenAI ChatGPT via the |
|
A vendor command-line tool spaCR can find, install and sign in to. |
|
Google Gemini via the |
|
The GitHub CLI, |
|
One way of installing a command-line tool on one operating system. |
Functions¶
|
Return only providers whose CLI is installed and logged in. |
|
Look up a registered provider by its short id. |
|
The row that installs the GitHub CLI with conda. |
|
The GitHub CLI, described the way the AI providers are. |
|
The one-line install hint for tool |
|
The ways of installing tool |
|
Return every registered provider, regardless of install state. |
|
Name the operating system |
|
A path as it is typed in a command on |
|
Terminate active provider subprocesses and release their readers. |
Module Contents¶
- exception spacr.qt.ai.providers.ProviderFailed(cli: str, exit_status: int, output_tail: str, login_command: str = '')[source]¶
Bases:
RuntimeErrorA provider CLI exited with a non-zero status, so what it printed is an error message and not an answer.
The three vendor CLIs report a failure the way any command-line tool does: a line on stdout or stderr, then a non-zero exit. A signed-out
claudeprintsNot logged in · Please run /loginand exits 1. In GitHub #117 an expired one printedFailed to authenticate: OAuth session expired and could not be refreshed. Streamed as if it were a reply, that line was shown as spaCR AI’s answer to a crash, and it was filed into GitHub issues as “spaCR AI’s analysis of this error”.- Parameters:
cli – the executable that failed, for the message.
exit_status – its exit status.
output_tail – the last lines it printed, already stripped.
login_command – the command that signs in to this provider, or
""when the caller did not say which provider this was.
- Variables:
exit_status – the exit status, for a caller that needs the number.
output_tail – the quoted output, for a caller that needs the text.
Build the message a user reads after
[AI error].
- class spacr.qt.ai.providers.ChatProvider[source]¶
Bases:
CommandLineTool,abc.ABCAbstract base for AI chat providers that shell out to a vendor CLI.
Subclasses set the
name/label/cli_name/install_hint/login_commandclass attributes and implementstream_chat().- Variables:
name – short id (“claude” / “codex” / “gemini”).
label – human-readable label shown in the UI.
cli_name – executable expected on
PATH.install_hint – shell one-liner suggested for installation, built from
install_methods.login_command – shell one-liner the user runs to authenticate.
Create the provider with no child process running.
The running process is tracked so that cancelling a stream can actually terminate it – otherwise iterating the child’s stdout blocks indefinitely and the worker thread never exits.
- cancel_stream() None[source]¶
Kill the running subprocess (if any).
This is the ONLY reliable way to unblock a stream that’s stuck waiting on stdout — flipping a Python flag would only unblock between chunks, which may never come.
- is_logged_in() bool[source]¶
Best-effort — override per provider if a cheap check exists.
Default: assume yes when installed. The real auth error will surface as a normal subprocess failure on the first send.
- class spacr.qt.ai.providers.ClaudeCliProvider[source]¶
Bases:
ChatProviderAnthropic Claude via the
claude(Claude Code) CLI.Create the provider with no child process running.
The running process is tracked so that cancelling a stream can actually terminate it – otherwise iterating the child’s stdout blocks indefinitely and the worker thread never exits.
- stream_chat(messages: List[Dict], system: str = '', model: str | None = None) Iterator[str][source]¶
Stream a chat completion from the
claudeCLI.- Parameters:
messages – conversation history as
{role, content}dicts.system – optional system prompt appended via
--append-system-prompt.model – optional model override passed via
--model. When None the current response-speed setting supplies one.
- Returns:
iterator yielding stdout text chunks.
- class spacr.qt.ai.providers.CodexCliProvider[source]¶
Bases:
ChatProviderOpenAI ChatGPT via the
codexCLI.Create the provider with no child process running.
The running process is tracked so that cancelling a stream can actually terminate it – otherwise iterating the child’s stdout blocks indefinitely and the worker thread never exits.
- stream_chat(messages: List[Dict], system: str = '', model: str | None = None) Iterator[str][source]¶
Stream a chat completion from the
codexCLI.- Parameters:
messages – conversation history as
{role, content}dicts.system – optional system prompt folded into the prompt body.
model – optional model override passed via
--model. When None the current response-speed setting supplies one.
- Returns:
iterator yielding stdout text chunks.
- class spacr.qt.ai.providers.CommandLineTool[source]¶
A vendor command-line tool spaCR can find, install and sign in to.
The AI providers and the GitHub CLI share this, so “is it there?” and “is it signed in?” are asked one way for all four.
- Variables:
name – short id, and the key of
INSTALL_METHODS.label – human-readable label shown in the UI.
cli_name – executable expected on
PATH.install_methods – the ways of installing it on this system, tried in order.
install_hint – the one-line hint built from
install_methods.login_command – the command a user runs to sign in.
status_command – a command that exits 0 when the tool is signed in, or
()when the tool has none.
- check_signed_in() bool | None[source]¶
Ask the tool itself whether it is signed in.
Runs
status_commandwith its output discarded – for the GitHub CLI that output is the token – so call it off the GUI thread.- Returns:
Truewhen the command exits 0,Falsewhen it exits otherwise, cannot be started or runs pastSTATUS_TIMEOUT_S, andNonewhen the tool has no status command to ask.
- class spacr.qt.ai.providers.GeminiCliProvider[source]¶
Bases:
ChatProviderGoogle Gemini via the
geminiCLI.Create the provider with no child process running.
The running process is tracked so that cancelling a stream can actually terminate it – otherwise iterating the child’s stdout blocks indefinitely and the worker thread never exits.
- stream_chat(messages: List[Dict], system: str = '', model: str | None = None) Iterator[str][source]¶
Stream a chat completion from the
geminiCLI.- Parameters:
messages – conversation history as
{role, content}dicts.system – optional system prompt folded into the prompt body.
model – optional model override passed via
-m. When None the current response-speed setting supplies one.
- Returns:
iterator yielding stdout text chunks.
- class spacr.qt.ai.providers.GitHubCli[source]¶
Bases:
CommandLineToolThe GitHub CLI,
gh: installed and signed in like a provider.spaCR reads a token from it (
spacr.qt.ai.github_auth) to file an issue without a browser round-trip. Signing in isgh auth login;gh auth tokenexits 0 exactly when there is a token to read.
- class spacr.qt.ai.providers.InstallMethod[source]¶
Bases:
NamedTupleOne way of installing a command-line tool on one operating system.
- Variables:
needs – executables that must already be on
PATHfor this way to work, such as("npm",).command – the command as a user would type it.
runner – how
spacr.qt.ai.cli_installstarts it:"exec"runs the command’s first word as a program with the rest as its arguments,"shell"hands the command tobash -o pipefail -c(for a pipe such ascurl ... | bash), and"cmd"runs it ascmd /c "<command>"on Windows.
- spacr.qt.ai.providers.configured_providers() List[ChatProvider][source]¶
Return only providers whose CLI is installed and logged in.
- spacr.qt.ai.providers.get_provider(name: str) ChatProvider | None[source]¶
Look up a registered provider by its short id.
- Parameters:
name – provider id (
"claude","codex","gemini").- Returns:
the matching provider, or
Noneif no such id.
- spacr.qt.ai.providers.gh_conda_row(prefix: str, platform: str) InstallMethod[source]¶
The row that installs the GitHub CLI with conda.
- Parameters:
prefix – spaCR’s own environment,
sys.prefix.platform – a
sys.platformvalue, for quotingprefix.
- Returns:
when
prefixis a conda environment, a command that names it with--prefixand addsghwith--freeze-installed, so the command on screen says which environment changes and conda does not update the packages spaCR runs on; otherwise conda’s plain command, which installs into whichever environment conda treats as active.
- spacr.qt.ai.providers.github_cli() GitHubCli[source]¶
The GitHub CLI, described the way the AI providers are.
- spacr.qt.ai.providers.install_hint_for(name: str, platform: str) str[source]¶
The one-line install hint for tool
nameonplatform.Built from the same rows the Install button runs, so the two cannot drift apart. On macOS and Linux the alternatives are joined by three spaces and then
# or, which a POSIX shell reads as a comment; on Windows only the first row is shown, becausecmdhas no such comment and would hand the words after#to the installer as arguments.- Parameters:
name – a key of
INSTALL_METHODS.platform – a
sys.platformvalue.
- Returns:
the hint, or
""when the tool has no rows.
- spacr.qt.ai.providers.install_methods_for(name: str, platform: str) Tuple[InstallMethod, ...][source]¶
The ways of installing tool
nameonplatform, in order.- Parameters:
name – a key of
INSTALL_METHODS, such as"claude".platform – a
sys.platformvalue.
- Returns:
the rows, or
()for a tool spaCR cannot install.
- spacr.qt.ai.providers.list_providers() List[ChatProvider][source]¶
Return every registered provider, regardless of install state.
- spacr.qt.ai.providers.platform_family(platform: str) str[source]¶
Name the operating system
INSTALL_METHODSis keyed by.- Parameters:
platform – a
sys.platformvalue.- Returns:
"win32"for any Windows value,"darwin"for macOS, and"linux"for everything else.
- spacr.qt.ai.providers.quote_path(path: str, platform: str) str[source]¶
A path as it is typed in a command on
platform.- Parameters:
path – the path.
platform – a
sys.platformvalue.
- Returns:
on Windows the path in double quotes when it holds a space, else unchanged; elsewhere quoted for a POSIX shell.