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 claude CLI (“Claude Code”)

  • OpenAI ChatGPT → the codex CLI

  • Google Gemini → the gemini CLI

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.

Module Contents

class spacr.qt.ai.providers.ChatProvider[source]

Bases: abc.ABC

Abstract base for AI chat providers that shell out to a vendor CLI.

Subclasses set the name/label/cli_name/install_hint/ login_command class attributes and implement stream_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.

  • login_command – shell one-liner the user runs to authenticate.

name: str = ''[source]
label: str = ''[source]
cli_name: str = ''[source]
install_hint: str = ''[source]
login_command: str = ''[source]
is_installed() bool[source]

Return True when the provider’s CLI executable is on PATH.

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.

is_configured() bool[source]

Return True when the CLI is both installed and logged in.

source_of_key() str[source]

Compat string for the old KeysDialog — now describes the CLI’s install/login state.

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.

abstract stream_chat(messages: List[Dict], system: str = '', model: str | None = None) Iterator[str][source]

Yield text chunks streaming from the CLI subprocess.

class spacr.qt.ai.providers.ClaudeCliProvider[source]

Bases: ChatProvider

Anthropic Claude via the claude (Claude Code) CLI.

name = 'claude'[source]
label = 'Claude (via Claude Code)'[source]
cli_name = 'claude'[source]
install_hint = 'curl -fsSL https://claude.ai/install.sh | bash   # or npm install -g @anthropic-ai/claude-code'[source]
login_command = 'claude setup-token'[source]
stream_chat(messages: List[Dict], system: str = '', model: str | None = None) Iterator[str][source]

Stream a chat completion from the claude CLI.

Parameters:
  • messages – conversation history as {role, content} dicts.

  • system – optional system prompt appended via --append-system-prompt.

  • model – optional model override passed via --model.

Returns:

iterator yielding stdout text chunks.

class spacr.qt.ai.providers.CodexCliProvider[source]

Bases: ChatProvider

OpenAI ChatGPT via the codex CLI.

name = 'codex'[source]
label = 'ChatGPT (via Codex CLI)'[source]
cli_name = 'codex'[source]
install_hint = 'npm install -g @openai/codex   # or brew install codex'[source]
login_command = 'codex login'[source]
stream_chat(messages: List[Dict], system: str = '', model: str | None = None) Iterator[str][source]

Stream a chat completion from the codex CLI.

Parameters:
  • messages – conversation history as {role, content} dicts.

  • system – optional system prompt folded into the prompt body.

  • model – optional model override passed via --model.

Returns:

iterator yielding stdout text chunks.

class spacr.qt.ai.providers.GeminiCliProvider[source]

Bases: ChatProvider

Google Gemini via the gemini CLI.

name = 'gemini'[source]
label = 'Gemini (via Gemini CLI)'[source]
cli_name = 'gemini'[source]
install_hint = 'npm install -g @google/gemini-cli   # or brew install gemini-cli'[source]
login_command = 'gemini'[source]
stream_chat(messages: List[Dict], system: str = '', model: str | None = None) Iterator[str][source]

Stream a chat completion from the gemini CLI.

Parameters:
  • messages – conversation history as {role, content} dicts.

  • system – optional system prompt folded into the prompt body.

  • model – optional model override passed via -m.

Returns:

iterator yielding stdout text chunks.

spacr.qt.ai.providers.list_providers() List[ChatProvider][source]

Return every registered provider, regardless of install state.

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 None if no such id.