Metadata-Version: 2.4
Name: stimulir
Version: 0.1.193
Summary: Stimulir Console CLI and Python SDK — prompts, models, inference, realtime voice, BYOK, usage, lab and compute.
License: Proprietary
Project-URL: Homepage, https://www.stimulir.com
Project-URL: Documentation, https://www.stimulir.com/docs/sdk/python
Project-URL: Changelog, https://github.com/stimulir/stimulir-console/releases
Keywords: stimulir,hybrie,sdk,inference,realtime,voice,prompts,lora,peft,cli
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: Other/Proprietary License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.11
Description-Content-Type: text/markdown
Requires-Dist: typer>=0.12
Requires-Dist: rich>=13.7
Requires-Dist: httpx>=0.27
Provides-Extra: realtime
Requires-Dist: websockets>=15; extra == "realtime"

# stimulir

Command-line interface **and Python SDK** for the Stimulir Console platform —
workspaces, API keys, BYOK credentials, usage/billing, prompts, models,
inference, **realtime voice**, and HybrIE lab/compute.

Built with Python 3.11+, [Typer](https://typer.tiangolo.com),
[Rich](https://rich.readthedocs.io), and [httpx](https://www.python-httpx.org).
The optional `realtime` extra adds a websocket voice client
([websockets](https://websockets.readthedocs.io)).

## Install

Once published to PyPI:

```bash
uv tool install stimulir
stimulir --help
# or run without installing:
uvx stimulir --help
```

From this repo (development):

```bash
uv tool install ./cli          # isolated tool from the local source
# or, for live-editing development:
cd cli && uv venv && uv pip install -e .
```

> **Conda/miniconda note:** if you use plain `pip install -e` instead of uv, Anaconda
> Python builds skip the `__editable__.*.pth` hook, so the editable install silently
> fails to import (`ModuleNotFoundError: stimulir_cli`). uv avoids this entirely.

## Python SDK

For application or backend code, add the package to that project's runtime
environment instead of installing it as an isolated CLI tool:

```bash
uv add stimulir                 # core SDK
uv add 'stimulir[realtime]'     # + the realtime voice client (adds websockets)
```

Then import the SDK from the public package:

```python
import os

from stimulir import StimulirClient

client = StimulirClient(
    api_base=os.getenv("STIMULIR_API_BASE", "https://api.stimulir.com"),
    api_key=os.environ["STIMULIR_API_KEY"],  # hyb_...
)
```

The `hyb_*` key is the normal customer/runtime auth path. Stimulir derives the
workspace and allowed platform scope from the key, so application code does not
need to pass a workspace id for normal prompt, data, eval, or inference calls.

### Configuration & auto-resolution

`StimulirClient()` and the realtime client resolve everything from the
environment when you omit it, so most apps construct them with no args. The same
resolvers are exposed for direct use:

| `from stimulir_cli import config` | Resolves from (in order) | Default |
| --- | --- | --- |
| `config.get_api_base()` | `STIMULIR_API_BASE` | `https://api.stimulir.com` |
| `config.get_api_key()` | `STIMULIR_API_KEY` → `HYBRIE_API_KEY` → credentials file | — |
| `config.get_inference_base_url()` | `STIMULIR_INFERENCE_BASE_URL` → `HYBRIE_MANAGED_BASE_URL` | `{api_base}/api/v1/inference` |
| `config.get_realtime_url()` | `STIMULIR_REALTIME_URL` → `HYBRIE_REALTIME_URL` | `{api_base}/api/v1/inference/realtime?provider=vertex` (https→wss) |
| `config.get_realtime_model()` | `STIMULIR_REALTIME_MODEL` → `HYBRIE_REALTIME_MODEL` | canonical Vertex Live model |
| `config.get_model_preference()` | `STIMULIR_MODEL_PREFERENCE` → `HYBRIE_MODEL_PREFERENCE` | `[]` (caller supplies its own list) |

Legacy `HYBRIE_*` names are a deprecated fallback, always below the `STIMULIR_*`
name. Use `config.resolve_env(primary, *legacy, default=...)` for your own keys.

### Prompts

Versioned prompts that render themselves (`{{var}}` and `{var}`; `None` → `""`):

```python
prompt = client.prompts.get("aca.assessment.agent", label="prod")
text = prompt.render({"category": "AI fluency", "name": "Tosin"})
print(prompt.lineage)  # {"prompt_key": ..., "prompt_version": ..., "prompt_label": ...}
```

### Models

```python
models = client.models.list()  # normalized list[str] of advertised model ids
```

### Realtime voice (`stimulir[realtime]`)

The realtime client lives behind the `realtime` extra — a plain `import stimulir`
never loads `websockets`. It speaks the managed gateway's voice protocol and
enforces the **once-only setup** invariant: a second setup on a live connection
is swallowed, not forwarded (which is what keeps a voice session from fragmenting
into one trace per turn).

```python
from stimulir.realtime import RealtimeClient, AudioDelta, ResponseDone

rt = RealtimeClient(  # url / key / model auto-resolve from config if omitted
    instructions="You are a friendly interviewer.",
    temperature=0.7,
)

async with rt.connect() as conn:           # Bearer-only auth
    await conn.setup()                      # once per connection (idempotent)
    await conn.send_audio(pcm16_16k_mono)   # stream input audio
    await conn.commit()
    await conn.create_response()
    async for ev in conn.events():          # typed events
        if isinstance(ev, AudioDelta):
            play(ev.pcm16)                   # 24k mono PCM16 out
        elif isinstance(ev, ResponseDone):
            break
```

Reconnection policy is the caller's: each `connect()` is one connection, and the
once-only setup guard resets per connection (which is correct — a reconnect needs
exactly one fresh setup).

## Login

`stimulir login` uses a browser-based **device authorization** — no password is
ever typed in the terminal. It prints a link and a short code, you approve in the
console, and the CLI receives a revocable `stim_cli_` token:

```bash
stimulir login
# 1. CLI prints a link + a short user_code, e.g.
#      Go to https://console.stimulir.com/cli and confirm it shows: ABCD-1234
# 2. Approve in the browser (already signed in) — confirm the code, pick a workspace
# 3. CLI receives an opaque stim_cli_ token, stored in
#    ~/.stimulir/credentials.json (0600), with the workspace pre-selected
```

Bare `stimulir` (when logged out) offers to run this same flow inline before
starting the agent.

The `stim_cli_` token is **opaque, revocable, non-refreshable, with a 30-day TTL**,
pinned to your user and the workspace you selected. To rotate it, just log in again.
A `401` from the server means the token expired or was revoked — re-run
`stimulir login`.

(Non-production environments can override the API/auth endpoints via
`STIMULIR_API_BASE` env var or `~/.stimulir/config.json`.)

Already have a token from the console web app? Paste it as an escape hatch:

```bash
stimulir login --token <token>
```

Inspect or end your session with:

```bash
stimulir whoami           # opaque token: shows local email + prefix, plus
                          # server-confirmed identity, workspace, and expiry
stimulir logout           # clears the local credentials file
stimulir logout --remote  # revokes the server-side token, then clears locally
```

## Select a workspace

Most commands are scoped to a workspace (business profile):

```bash
stimulir workspace list
stimulir workspace use <workspace-id>
```

## Commands

### Agent — the AI engineer in your terminal

Bare `stimulir` (no subcommand, in a TTY) launches the interactive Stimulir
agent against the active workspace. The agent runs server-side (Pi through
code-runtime) and executes its tool intents — bash, file reads/writes, glob
search — on your machine, with approval prompts. The conversation lives
server-side, so the same session shows up in the Engineering traces panel.

```bash
# Start the agent (or pass an opening task)
stimulir
stimulir agent "Add a /health endpoint and wire it into the router"

# Resume an existing session
stimulir agent --session <session-key>

# Attach this machine as the local executor for a session started in the app
# (the chat composer's "Continue in: Local" dropdown)
stimulir attach [<session-key>]
```

Approvals stay on your machine — destructive commands (rm -rf, sudo, force
push, key revocation) always prompt even under an "always allow" grant.

### API keys (`hyb_*` inference keys)

```bash
stimulir keys create --name cli --env prod --expires-in-days 90 --save
stimulir keys list --include-revoked
stimulir keys revoke <key-id>
```

The plaintext key is shown exactly once; `--save` stores it in
`~/.stimulir/credentials.json` so `stimulir infer` can use it.

### BYOK provider credentials

Providers: `openai`, `anthropic`, `google_gemini`, `mistral`, `aws_bedrock`,
`azure_openai`, `together_ai`, `nebius`.

```bash
stimulir byok add --provider anthropic --label "prod key"   # secret prompted without echo
stimulir byok list
stimulir byok verify <credential-id>
stimulir byok remove <credential-id>
```

### Workspace prompts

Versioned prompt management — every save creates a new immutable version, and
labels (e.g. `prod`) move between versions so you can promote or roll back
without editing application code.

```bash
stimulir prompts list
stimulir prompts get <key> [--label prod|--version N]
stimulir prompts versions <key>
stimulir prompts create --key <key> --file ./prompt.md --label prod --notes "<change notes>"
stimulir prompts update <key> <version> --notes "<change notes>"
stimulir prompts archive <key> <version>
stimulir prompts label <key> <version> <label>
```

### Data assets

Curate datasets for the training loop, including from agent traces:

```bash
stimulir data list
stimulir data upload ./dataset.jsonl --stage raw --target sft
stimulir data from-trace <trace-id> --source agent --target sft
stimulir data stage <asset-id> <raw|cleaning|clean_view|snapshot|lab>
stimulir data unstage <asset-id>
stimulir data bulk-stage --ids <asset-id>,<asset-id> --stage lab --target sft
stimulir data update <asset-id> --name "<name>" --target sft
stimulir data snapshot <asset-id>
stimulir data remove <asset-id>
```

The typical flow is **`from-trace` → `stage … lab` → `snapshot`** — turn an
agent run into staged data, promote it, then pin a snapshot a training run
consumes.

### Usage & billing

```bash
stimulir usage --window 30d --group-by model
stimulir billing snapshot
```

### Inference

Uses a `hyb_*` API key (`stimulir keys create --save` or `STIMULIR_API_KEY`):

```bash
stimulir infer chat "Summarise IFRS 16 in one paragraph" --model hybrie-mid
stimulir infer chat "Write a haiku about ledgers" --model hybrie-small --stream
```

### HybrIE lab (training + eval)

```bash
stimulir lab train sft --family qwen3-4b --lora-rank 8 --examples 500 --epochs 3 --lr 1e-4 --eval-examples 200 --seed 42 --checkpoint-dir runs/sft
stimulir lab train d2l --family qwen3-4b --examples 500 --epochs 3 --lr 1e-4 --eval-examples 200 --seed 42 --checkpoint-dir runs/d2l
stimulir lab train rl --family qwen3-4b --environment niah --prompts 64 --group-size 8 --policy hypernet --lr 1e-5 --kl-beta 0.05
stimulir lab jobs list
stimulir lab jobs get <job-id>
stimulir lab jobs cancel <job-id>
stimulir lab eval runs
stimulir lab eval get <run-id>
stimulir lab eval create-run --data-asset-id <asset-id> --prompt <key>:<version|label> --execute
stimulir lab eval execute-run <run-id>
stimulir lab eval niah --family qwen3-4b --checkpoint-dir ~/hybrie-mounts/d2l-artifacts/<job>/checkpoint --examples 200 --seed 42
stimulir lab eval adapter --family qwen3-4b --adapter-dir ~/adapters/invoices-lora --examples 200 --seed 42
stimulir lab eval rl --family qwen3-4b --environment niah --policy hypernet --checkpoint-dir runs/rl/<job> --tasks 50 --pass-threshold 0.8
stimulir lab adapters list
stimulir lab adapters get <adapter-id>
stimulir lab adapters load <adapter-id>
stimulir lab adapters unload <adapter-id>
```

### Compute (GPU offers + instances + peers)

```bash
stimulir compute offers
stimulir compute up <offer-id> --count 2
stimulir compute list
stimulir compute status <instance-id>
stimulir compute down <instance-id>
stimulir compute peers list
stimulir compute peers add --name lambda-a100 --grpc http://10.0.0.5:9090 --realtime http://10.0.0.5:8011
stimulir compute peers remove <peer-id>
```

### Models

```bash
stimulir models
```

## HybrIE routing

`lab`, `compute`, and `models` go through the console proxy
(`{api_base}/api/v1/hybrie/*`, Supabase JWT + workspace header) by default.
To talk to a HybrIE runtime directly, pass `--endpoint http://host:port` on
any of those commands, or persist it in `~/.stimulir/config.json` as
`hybrie_endpoint` — requests then hit `{endpoint}/v1/*`.

## Scripting

Every command accepts `--json` to emit the raw API response:

```bash
stimulir keys list --json | jq '.api_keys[].prefix'
```

## Configuration reference

| File | Contents |
| --- | --- |
| `~/.stimulir/config.json` | `api_base`, `workspace_id`, `hybrie_endpoint`, `supabase_url`, `supabase_anon_key` |
| `~/.stimulir/credentials.json` (0600) | `api_key`, `cli_token` (opaque `stim_cli_` from device login), optional `access_token`, `refresh_token`, `expires_at`, `email` |

Common environment overrides: `STIMULIR_API_BASE`, `STIMULIR_API_KEY`,
`SUPABASE_URL`, `SUPABASE_ANON_KEY`. Use `stimulir workspace use <id>` to
persist the active CLI workspace.
