Skip to main content

Thin Python client for running Claude Code and Codex via local executables.

Project description

stan_ai_client

stan_ai_client is a thin Python wrapper around local AI coding CLIs.

It supports Claude Code through the local claude executable, Codex through the local codex exec executable, and Grok through the local grok executable (grok -p). It does not call Anthropic, OpenAI, or xAI APIs directly; the relevant CLI must already be installed and authenticated on the machine.

The library is intentionally small and pragmatic:

  • run_text() for plain-text output
  • run_json() for Claude JSON envelopes or Codex JSONL events
  • run_structured() for schema-validated structured output
  • typed results
  • structured exceptions
  • local JSON Schema validation
  • rate-limit parsing helpers
  • opt-in rate-limit retry policy
  • stdlib logging

Why Use It

Use stan_ai_client when you want:

  • a small Python API on top of Claude Code or Codex
  • text mode and JSON mode without hand-rolling subprocess logic
  • strongly guided structured output with local validation
  • command metadata, typed JSON payloads, and normalized exceptions
  • safe-by-default prompt logging behavior
  • local automation that already depends on Claude Code or Codex being installed

Typical use cases:

  • article summarization
  • tagging or YAML generation
  • one-shot repository or directory analysis
  • local scripts that need session metadata, usage, cost, or duration

It is not a replacement for the Anthropic SDK, the OpenAI SDK, or the Codex SDK. It intentionally stays at the local process-wrapper layer.

Install

From PyPI

pip install stan-ai-client

From a local checkout

python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"

From GitHub

pip install "git+https://github.com/<your-user>/stan_ai_client.git"

Releases

  • the package version lives in pyproject.toml
  • every non-bot push or merge to main bumps patch automatically
  • tags use vX.Y.Z
  • main releases build and publish to PyPI automatically
  • release commits are created by GitHub Actions as chore: release vX.Y.Z [skip ci]

Quickstart

1. Install Claude Code or Codex

Make sure the CLI you want is already available on your machine and authenticated:

claude --version
codex --version

2. Run the smoke test

python examples/smoke_test.py
python examples/codex_smoke_test.py

Those run text-mode and JSON-mode calls against the selected local CLI.

Minimal Usage

Claude text mode

from stan_ai_client import ClaudeCodeClient

client = ClaudeCodeClient()
result = client.run_text("Reply with the single word: ok")
print(result.text)

Claude JSON mode

from pathlib import Path

from stan_ai_client import ClaudeCodeClient, RunOptions

client = ClaudeCodeClient(
    default_model="claude-opus-4-8",
    default_effort="max",
    default_timeout_seconds=180,
)

result = client.run_json(
    "Summarize this article.",
    options=RunOptions(
        cwd=Path("."),
        allowed_tools=("Read", "Glob", "Grep", "Bash"),
    ),
)

print(result.payload.result)
print(result.payload.total_cost_usd)
print(result.payload.session_id)

Claude structured mode

from stan_ai_client import ClaudeCodeClient, StructuredSchema

client = ClaudeCodeClient()

schema = StructuredSchema.from_dict(
    {
        "type": "object",
        "properties": {
            "summary": {"type": "string"},
            "tags": {"type": "array", "items": {"type": "string"}},
        },
        "required": ["summary", "tags"],
        "additionalProperties": False,
    }
)

result = client.run_structured(
    "Summarize this article and return tags.",
    schema=schema,
)

print(result.structured_output["summary"])
print(result.payload.session_id)
print(result.payload.total_cost_usd)

run_structured() validates the schema before Claude runs, requires structured_output in the response, and validates the returned object locally against the same schema.

Codex text mode

from stan_ai_client import CodexClient

client = CodexClient()
result = client.run_text("Reply with the single word: ok")
print(result.text)

CodexClient targets codex exec and defaults to gpt-5.6-sol with medium reasoning effort. GPT-5.6's max effort is available through default_reasoning_effort or CodexRunOptions(reasoning_effort="max"). By default it passes --dangerously-bypass-approvals-and-sandbox, matching the current automation preference for this package. Use CodexRunOptions(permission_mode="default") to omit that flag and let Codex use its configured defaults.

Codex JSONL mode

from stan_ai_client import CodexClient

client = CodexClient()
result = client.run_json("Summarize this repository.")

print(result.payload.result)
print(result.payload.thread_id)
print(result.payload.usage)

Codex JSON mode uses codex exec --json, so the payload represents parsed JSONL events instead of a Claude-style one-object envelope.

Codex structured mode

from stan_ai_client import CodexClient, StructuredSchema

client = CodexClient()
schema = StructuredSchema.from_dict(
    {
        "type": "object",
        "properties": {"summary": {"type": "string"}},
        "required": ["summary"],
        "additionalProperties": False,
    }
)

result = client.run_structured("Summarize this repository.", schema=schema)
print(result.structured_output["summary"])

Codex structured mode writes the validated schema to a temporary JSON file, passes it with --output-schema, parses the final response as JSON, and validates the returned object locally.

Codex additionally validates schemas against the OpenAI structured-output subset before invoking the CLI. The root schema must be an object, every object property must be required, and objects must set additionalProperties: false. Unsupported schema keywords such as allOf, oneOf, not, dependentRequired, dependentSchemas, if, then, and else are rejected locally. Structured Codex runs may also resume existing sessions with session_id or continue_last_session.

Grok text / JSON / structured

from stan_ai_client import GrokClient, GrokRunOptions, StructuredSchema

client = GrokClient()  # defaults to model="grok-4.5"

result = client.run_text("Reply with the single word: ok")
print(result.text)

# JSON + stable named session
r = client.run_json("Summarize briefly.", options=GrokRunOptions(session_id="..."))
print(r.payload.text)
print(r.payload.session_id)

# Structured
schema = StructuredSchema.from_dict({"type": "object", "properties": {"ok": {"type": "boolean"}}, "required": ["ok"], "additionalProperties": False})
res = client.run_structured("Return {ok: true}", schema=schema)
print(res.structured_output)

# Limit a headless review to read-only built-in tools. This changes which
# tools Grok can see; --allow/--deny permission rules do not.
options = GrokRunOptions(tools=("read_file", "grep", "list_dir"))
res = client.run_structured("Review this repository", schema=schema, options=options)

GrokClient drives grok --no-auto-update -p --output-format .... Prompt delivery is handled transparently (direct arg or temp --prompt-file for long prompts). The JSON payload is thinner than Claude's (no cost/usage fields); duration_ms is provided client-side. Structured mode accepts either Grok's envelope structuredOutput field or the raw validated JSON value returned by newer Grok builds, while Grok {"type": "error"} envelopes remain process failures. Session support (session_id, continue_last_session) works well for automation.

Cancelled JSON turns raise GrokCancelledError even when the CLI exits 0. Malformed structured text, including concatenated JSON roots, raises GrokMalformedStructuredOutputError; raw output remains on the exception while its detail and json_value_count fields are safe for routine diagnostics.

Grok permission rules and built-in tool filtering are separate APIs: permission_allow_rules / permission_deny_rules map to --allow / --deny, while tools / excluded_tools map to --tools / --disallowed-tools. The old allowed_tools / disallowed_tools names remain deprecated aliases for permission rules so existing callers keep their behavior.

Logging

import logging

from stan_ai_client import ClaudeCodeClient

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("my_app.claude")

client = ClaudeCodeClient(
    logger=logger,
    log_prompts=False,
)

client.run_text("Reply with the single word: ok")

By default, logging includes execution metadata, not full prompt text. Set log_prompts=True only if you explicitly want prompts written to logs.

Error handling

If you automate Claude Code in batch jobs, pass a RateLimitRetryPolicy to let the client wait through parseable Claude rate limits up to your budget.

from stan_ai_client import ClaudeCodeClient, RateLimitRetryPolicy

client = ClaudeCodeClient()

result = client.run_json(
    "Summarize this repository.",
    rate_limit_policy=RateLimitRetryPolicy(
        max_wait_seconds=5 * 60 * 60,
        label="repo summary",
    ),
)

For user-facing workflows, omit rate_limit_policy and catch ClaudeRateLimitError so you can return the reset time to the user.

from stan_ai_client import ClaudeCodeClient, ClaudeRateLimitError

client = ClaudeCodeClient()

try:
    result = client.run_json("Summarize this repository.")
except ClaudeRateLimitError as exc:
    print(exc.reset_at or exc.retry_after_seconds)

Public Surface

Top-level exports:

from stan_ai_client import (
    __version__,
    ClaudeCodeClient,
    ClaudeEffort,
    CodexClient,
    CodexReasoningEffort,
    GrokClient,
    GrokEffort,
    RunOptions,
    CodexRunOptions,
    GrokRunOptions,
    TextRunResult,
    JsonRunResult,
    StructuredRunResult,
    CodexJsonRunResult,
    CodexStructuredRunResult,
    ClaudeJsonPayload,
    CodexJsonPayload,
    GrokJsonPayload,
    GrokJsonRunResult,
    GrokPermissionMode,
    GrokStructuredRunResult,
    CommandMetadata,
    StructuredSchema,
    AIClientError,
    AIClientTimeoutError,
    ClaudeCodeError,
    CodexCodeError,
    GrokCodeError,
    ClaudeExecutableNotFoundError,
    CodexExecutableNotFoundError,
    GrokExecutableNotFoundError,
    ClaudeLimitError,
    CodexLimitError,
    ClaudeTimeoutError,
    CodexTimeoutError,
    GrokTimeoutError,
    ClaudeProcessError,
    CodexProcessError,
    GrokProcessError,
    ClaudeProtocolError,
    CodexProtocolError,
    GrokProtocolError,
    ClaudeRateLimitError,
    CodexRateLimitError,
    GrokRateLimitError,
    GrokCancelledError,
    GrokMalformedStructuredOutputError,
    GrokStructuredOutputMissingError,
    GrokStructuredOutputValidationError,
    StructuredSchemaValidationError,
    ClaudeSchemaValidationError,
    CodexSchemaValidationError,
    ClaudeStructuredOutputMissingError,
    CodexStructuredOutputMissingError,
    ClaudeStructuredOutputValidationError,
    CodexStructuredOutputValidationError,
    RateLimitRetryPolicy,
    RateLimitInfo,
    parse_rate_limit_info,
)

Effort types are provider-specific: use ClaudeEffort, CodexReasoningEffort, or GrokEffort when annotating configuration. This keeps each CLI's vocabulary explicit while the provider validates support for the selected model.

Supported Features

  • text mode via run_text()
  • JSON mode via run_json()
  • structured mode via run_structured()
  • prompts sent over stdin by default
  • optional argv prompt mode
  • per-call working directory control
  • model, effort/reasoning-effort, timeout, environment, and session controls
  • support for Claude CLI flags via typed RunOptions
  • support for Codex CLI flags via typed CodexRunOptions
  • support for Grok CLI flags via typed GrokRunOptions
  • raw stdout and stderr preserved on results and errors
  • opt-in stdlib logging with safe default prompt handling
  • typed JSON payload parsing with unknown fields preserved in extras
  • typed Codex JSONL payload parsing with raw events preserved
  • local input and output validation for structured mode
  • rate-limit detection, reset-time parsing, and opt-in retry policy

Examples

Documentation

See DOCS.md for:

  • full RunOptions reference
  • logging behavior
  • result types
  • structured output usage
  • exception model
  • rate-limit handling
  • session usage
  • common patterns
  • current limitations
  • maintainer release flow

Notes

  • prompts default to stdin instead of argv
  • Claude JSON mode always requests --output-format json
  • Claude structured mode always requests --output-format json and --json-schema
  • Claude text mode always requests --output-format text
  • Codex JSON mode uses codex exec --json
  • Codex structured mode uses codex exec --output-schema <tempfile>
  • Codex defaults to --dangerously-bypass-approvals-and-sandbox
  • Grok uses grok -p --output-format plain|json (prompt via arg or --prompt-file transparently)
  • logging uses stdlib logging
  • prompts are not written to logs unless log_prompts=True
  • the library is sync-only in 0.1.x
  • streaming is intentionally out of scope right now

Current Limitations

  • no streaming support
  • no async API
  • no background scheduler or persistent job queue
  • no standalone CLI wrapper command
  • no first-class typed wrapper yet for every Claude Code flag
  • no first-class typed wrapper yet for every Codex flag
  • shared structured mode accepts dict-backed JSON Schema objects only
  • Codex structured mode additionally enforces the OpenAI structured-output subset

For unsupported Claude Code flags, use RunOptions(extra_args=...). For unsupported Codex exec flags, use CodexRunOptions(extra_args=...); for unsupported Codex resume flags, use CodexRunOptions(resume_extra_args=...).

Development

pytest
mypy src tests

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

stan_ai_client-0.1.8.tar.gz (52.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

stan_ai_client-0.1.8-py3-none-any.whl (42.9 kB view details)

Uploaded Python 3

File details

Details for the file stan_ai_client-0.1.8.tar.gz.

File metadata

  • Download URL: stan_ai_client-0.1.8.tar.gz
  • Upload date:
  • Size: 52.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for stan_ai_client-0.1.8.tar.gz
Algorithm Hash digest
SHA256 9edd27d535a4b8fae684d3a9519959d6e79cf2933d56a9d1015a5d1a8bdb8abf
MD5 9ded0b0aee8b31b2dac18f25425cc46f
BLAKE2b-256 94dc033073acf01046fc65a0a8bee28004109dd36f000898519a58d18849e962

See more details on using hashes here.

Provenance

The following attestation bundles were made for stan_ai_client-0.1.8.tar.gz:

Publisher: ci.yml on stanislavkozlovski/stan_ai_client

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file stan_ai_client-0.1.8-py3-none-any.whl.

File metadata

  • Download URL: stan_ai_client-0.1.8-py3-none-any.whl
  • Upload date:
  • Size: 42.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for stan_ai_client-0.1.8-py3-none-any.whl
Algorithm Hash digest
SHA256 1ba226b2380624b939b44f6c7bcb2fae819b691e9092f72b4019e102a63226f4
MD5 9d44aa13a7b74fb2c01d2fa0869ff151
BLAKE2b-256 a35a33f18983264509f847f8089cef934ceaa3c113ff9cac399a2f82f2e9befe

See more details on using hashes here.

Provenance

The following attestation bundles were made for stan_ai_client-0.1.8-py3-none-any.whl:

Publisher: ci.yml on stanislavkozlovski/stan_ai_client

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page