bonito-cli 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,12 @@
1
+ node_modules/
2
+ .next/
3
+ __pycache__/
4
+ *.pyc
5
+ .env
6
+ .venv/
7
+ dist/
8
+ *.egg-info/
9
+ .env.secrets
10
+ secrets/age-key.txt
11
+ secrets/*.yaml
12
+ !secrets/*.enc.yaml
@@ -0,0 +1,262 @@
1
+ # Bonito CLI — Design Spec
2
+
3
+ ## Overview
4
+ `bonito` is a CLI tool that gives enterprise AI teams a unified command-line interface
5
+ to manage multi-cloud AI workloads through the Bonito platform. Instead of juggling
6
+ `aws bedrock`, `az cognitiveservices`, and `gcloud ai`, teams use one tool.
7
+
8
+ ## Install
9
+ ```bash
10
+ pip install bonito-cli
11
+ ```
12
+
13
+ ## Tech Stack
14
+ - **Python 3.10+** with **Typer** (CLI framework, auto-help, auto-completion)
15
+ - **Rich** for beautiful terminal output (tables, panels, progress bars, syntax highlighting)
16
+ - **httpx** for async HTTP calls to the Bonito API
17
+ - **keyring** (optional) for secure credential storage, fallback to file-based config
18
+
19
+ ## Config Storage
20
+ - `~/.bonito/config.json` — API endpoint, default org, preferences
21
+ - `~/.bonito/credentials.json` — API key (or use env var `BONITO_API_KEY`)
22
+ - Environment variables override file config:
23
+ - `BONITO_API_KEY` — API key
24
+ - `BONITO_API_URL` — API endpoint (default: `https://getbonito.com/api`)
25
+
26
+ ## Authentication Flow
27
+ Users sign up on the Bonito web app, then:
28
+ ```bash
29
+ bonito auth login # Opens browser for OAuth, or prompts for API key
30
+ bonito auth login --api-key bk-xxx # Direct API key auth
31
+ bonito auth status # Show current auth state + org info
32
+ bonito auth logout # Clear stored credentials
33
+ ```
34
+
35
+ ## Command Structure
36
+
37
+ ### `bonito auth` — Authentication & API Keys
38
+ ```bash
39
+ bonito auth login [--api-key KEY] # Authenticate (browser OAuth or API key)
40
+ bonito auth logout # Clear credentials
41
+ bonito auth status # Show auth status, org, user info
42
+ bonito auth keys list # List API/gateway keys
43
+ bonito auth keys create [--name N] # Create new gateway key
44
+ bonito auth keys revoke KEY_ID # Revoke a key
45
+ ```
46
+
47
+ ### `bonito providers` — Cloud Provider Management
48
+ ```bash
49
+ bonito providers list # List connected providers
50
+ bonito providers add aws --access-key X --secret-key Y --region us-east-1
51
+ bonito providers add azure --tenant-id X --client-id Y --client-secret Z --subscription-id S --endpoint E
52
+ bonito providers add gcp --project-id X --service-account-json path/to/sa.json --region us-central1
53
+ bonito providers test PROVIDER_ID # Verify credentials
54
+ bonito providers remove PROVIDER_ID # Disconnect provider
55
+ bonito providers models PROVIDER_ID # List models for a provider
56
+ bonito providers costs PROVIDER_ID [--days 30] # Show provider costs
57
+ ```
58
+
59
+ ### `bonito models` — Model Management
60
+ ```bash
61
+ bonito models list [--provider aws] [--enabled-only] [--search QUERY]
62
+ bonito models info MODEL_ID # Detailed model info (pricing, capabilities, status)
63
+ bonito models enable MODEL_ID # Activate model on cloud account
64
+ bonito models enable --bulk ID1 ID2 ID3 # Bulk activate
65
+ bonito models sync [--provider PROVIDER_ID] # Sync model catalog from cloud
66
+ ```
67
+
68
+ ### `bonito chat` — Interactive AI Chat (Playground)
69
+ ```bash
70
+ bonito chat # Interactive chat (picks default model)
71
+ bonito chat -m claude-3-sonnet # Chat with specific model
72
+ bonito chat -m gpt-4o --temperature 0.3 # With parameters
73
+ bonito chat --compare model1 model2 # Compare mode
74
+ echo "Summarize this" | bonito chat -m claude # Pipe input
75
+ bonito chat -m claude "What is 2+2?" # One-shot (non-interactive)
76
+ ```
77
+
78
+ ### `bonito gateway` — API Gateway Management
79
+ ```bash
80
+ bonito gateway status # Gateway health + config
81
+ bonito gateway keys list # List gateway API keys
82
+ bonito gateway keys create [--name N] # Create key
83
+ bonito gateway keys revoke KEY_ID # Revoke key
84
+ bonito gateway logs [--limit 50] [--model X] # View recent gateway logs
85
+ bonito gateway config # Show gateway config
86
+ bonito gateway config set FIELD VALUE # Update gateway config
87
+ ```
88
+
89
+ ### `bonito policies` — Routing Policies
90
+ ```bash
91
+ bonito policies list # List routing policies
92
+ bonito policies create --name N --strategy cost_optimized --models M1,M2
93
+ bonito policies info POLICY_ID # Policy details + stats
94
+ bonito policies test POLICY_ID "test prompt" # Dry-run test
95
+ bonito policies toggle POLICY_ID # Enable/disable
96
+ bonito policies delete POLICY_ID # Delete policy
97
+ ```
98
+
99
+ ### `bonito analytics` — Usage Analytics & Costs
100
+ ```bash
101
+ bonito analytics overview # Dashboard summary (requests, cost, top model)
102
+ bonito analytics usage [--period day|week|month] # Usage over time
103
+ bonito analytics costs [--period daily|weekly|monthly] # Cost breakdown
104
+ bonito analytics trends # Trend analysis
105
+ bonito analytics digest # Weekly digest
106
+ ```
107
+
108
+ ### `bonito costs` — Cloud Cost Intelligence
109
+ ```bash
110
+ bonito costs summary [--period monthly] # Total spend across providers
111
+ bonito costs breakdown # By provider, model, department
112
+ bonito costs forecast # 14-day cost forecast
113
+ bonito costs recommendations # Optimization recommendations
114
+ bonito costs export [--format csv] # Export cost data
115
+ ```
116
+
117
+ ### `bonito config` — CLI Configuration
118
+ ```bash
119
+ bonito config show # Show current config
120
+ bonito config set api_url https://... # Set API endpoint
121
+ bonito config set default_model claude-3-sonnet # Set default model
122
+ bonito config reset # Reset to defaults
123
+ ```
124
+
125
+ ### `bonito completion` — Shell Completions
126
+ ```bash
127
+ bonito completion install bash # Install bash completions
128
+ bonito completion install zsh # Install zsh completions
129
+ bonito completion install fish # Install fish completions
130
+ ```
131
+
132
+ ## Output Formatting
133
+ - Default: Rich-formatted tables, panels, and styled text
134
+ - `--json` flag on any command: raw JSON output (for piping/scripting)
135
+ - `--quiet` flag: minimal output (for CI/CD)
136
+ - Color auto-detection (disable with `--no-color` or `NO_COLOR=1`)
137
+
138
+ ## Interactive Chat UX
139
+ ```
140
+ ╭─ Bonito Chat ─────────────────────────────────────╮
141
+ │ Model: claude-3-sonnet (AWS Bedrock) │
142
+ │ Temperature: 0.7 │ Max Tokens: 1000 │
143
+ ╰────────────────────────────────────────────────────╯
144
+
145
+ You: What are the main differences between transformers and RNNs?
146
+
147
+ Claude 3 Sonnet: Transformers and RNNs differ in several key ways...
148
+ [tokens: 847 | cost: $0.0042 | latency: 1.2s]
149
+
150
+ You: /help
151
+ Commands: /model <name>, /temp <0-2>, /tokens <n>, /clear, /export, /quit
152
+
153
+ You: /quit
154
+ Session saved. Total: 3 messages, $0.012, 2847 tokens.
155
+ ```
156
+
157
+ ## Error Handling
158
+ - Clear error messages with suggested fixes
159
+ - `bonito doctor` command to diagnose common issues (connectivity, auth, provider status)
160
+ - Retry logic for transient failures (network timeouts, rate limits)
161
+
162
+ ## API Endpoint Reference
163
+ The CLI talks to the existing Bonito backend. Key endpoints:
164
+
165
+ ### Auth
166
+ - POST /api/auth/login → TokenResponse
167
+ - GET /api/auth/me → UserResponse
168
+
169
+ ### Providers
170
+ - GET /api/providers/ → List[ProviderResponse]
171
+ - POST /api/providers/connect → ProviderResponse
172
+ - POST /api/providers/{id}/verify → VerifyResponse
173
+ - DELETE /api/providers/{id}
174
+ - GET /api/providers/{id}/models → List[ModelInfo]
175
+ - GET /api/providers/{id}/costs → CostDataResponse
176
+
177
+ ### Models
178
+ - GET /api/models/ → List[ModelResponse]
179
+ - GET /api/models/{id} → ModelResponse
180
+ - GET /api/models/{id}/details → ModelDetailsResponse
181
+ - POST /api/models/{id}/playground → PlaygroundResponse
182
+ - POST /api/models/compare → CompareResponse
183
+ - POST /api/models/{id}/activate
184
+ - POST /api/models/activate-bulk
185
+ - POST /api/models/sync
186
+
187
+ ### Gateway
188
+ - GET /api/gateway/keys → List[GatewayKeyResponse]
189
+ - POST /api/gateway/keys → GatewayKeyCreated
190
+ - DELETE /api/gateway/keys/{id}
191
+ - GET /api/gateway/logs → List[GatewayLogEntry]
192
+ - GET /api/gateway/config → GatewayConfigResponse
193
+ - PUT /api/gateway/config → GatewayConfigResponse
194
+ - GET /api/gateway/usage → UsageSummary
195
+
196
+ ### Gateway Proxy (OpenAI-compatible)
197
+ - POST /v1/chat/completions
198
+ - POST /v1/completions
199
+ - POST /v1/embeddings
200
+ - GET /v1/models
201
+
202
+ ### Routing Policies
203
+ - GET /api/routing-policies/ → List[RoutingPolicyResponse]
204
+ - POST /api/routing-policies/ → RoutingPolicyResponse
205
+ - GET /api/routing-policies/{id} → RoutingPolicyDetailResponse
206
+ - PUT /api/routing-policies/{id}
207
+ - DELETE /api/routing-policies/{id}
208
+ - POST /api/routing-policies/{id}/test → PolicyTestResult
209
+ - GET /api/routing-policies/{id}/stats → PolicyStats
210
+
211
+ ### Analytics
212
+ - GET /api/analytics/overview
213
+ - GET /api/analytics/usage?period=day|week|month
214
+ - GET /api/analytics/costs
215
+ - GET /api/analytics/trends
216
+ - GET /api/analytics/digest
217
+
218
+ ### Costs
219
+ - GET /api/costs/?period=daily|weekly|monthly
220
+ - GET /api/costs/breakdown
221
+ - GET /api/costs/forecast
222
+ - GET /api/costs/recommendations
223
+
224
+ ### Health
225
+ - GET /api/health
226
+ - GET /api/health/ready
227
+
228
+ ## File Structure
229
+ ```
230
+ cli/
231
+ ├── pyproject.toml # Package config, entry point
232
+ ├── README.md # CLI documentation
233
+ ├── bonito_cli/
234
+ │ ├── __init__.py # Version
235
+ │ ├── __main__.py # python -m bonito_cli
236
+ │ ├── app.py # Main Typer app, register subcommands
237
+ │ ├── config.py # Config file management (~/.bonito/)
238
+ │ ├── api.py # HTTP client (httpx) wrapper
239
+ │ ├── commands/
240
+ │ │ ├── __init__.py
241
+ │ │ ├── auth.py # auth login/logout/status/keys
242
+ │ │ ├── providers.py # providers list/add/test/remove
243
+ │ │ ├── models.py # models list/info/enable/sync
244
+ │ │ ├── chat.py # Interactive chat + one-shot
245
+ │ │ ├── gateway.py # gateway status/keys/logs/config
246
+ │ │ ├── policies.py # routing policies CRUD + test
247
+ │ │ ├── analytics.py # analytics overview/usage/costs/trends
248
+ │ │ ├── costs.py # cost intelligence
249
+ │ │ └── config_cmd.py # CLI config management
250
+ │ └── utils/
251
+ │ ├── __init__.py
252
+ │ ├── display.py # Rich formatting helpers (tables, panels, etc.)
253
+ │ └── auth.py # Token refresh, credential storage
254
+ ```
255
+
256
+ ## Notes
257
+ - The CLI must work with the EXISTING backend API — no new backend endpoints needed
258
+ - Auth tokens: store access_token + refresh_token, auto-refresh on 401
259
+ - All commands that require auth should check credentials first and give clear "run bonito auth login" messages
260
+ - The `bonito chat` interactive mode is the killer feature — make it feel great
261
+ - Support piping: `cat file.txt | bonito chat -m claude "Summarize this"`
262
+ - The `--json` flag is critical for CI/CD automation
@@ -0,0 +1,108 @@
1
+ Metadata-Version: 2.4
2
+ Name: bonito-cli
3
+ Version: 0.1.0
4
+ Summary: Bonito CLI — Unified multi-cloud AI management from your terminal
5
+ Project-URL: Homepage, https://getbonito.com
6
+ Project-URL: Documentation, https://getbonito.com/docs
7
+ Project-URL: Repository, https://github.com/ShabariRepo/bonito
8
+ Project-URL: Issues, https://github.com/ShabariRepo/bonito/issues
9
+ Author-email: Bonito <hello@getbonito.com>
10
+ License: MIT
11
+ Keywords: ai,aws,azure,bedrock,cli,gcp,llm,multi-cloud,openai,vertex-ai
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: System Administrators
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Software Development :: Libraries
23
+ Classifier: Topic :: System :: Systems Administration
24
+ Requires-Python: >=3.10
25
+ Requires-Dist: httpx>=0.25.0
26
+ Requires-Dist: rich>=13.0.0
27
+ Requires-Dist: typer[all]>=0.9.0
28
+ Description-Content-Type: text/markdown
29
+
30
+ # 🐟 Bonito CLI
31
+
32
+ **Unified multi-cloud AI management from your terminal.**
33
+
34
+ Bonito gives enterprise AI teams a single CLI to manage models, costs, and workloads across AWS Bedrock, Azure OpenAI, and Google Vertex AI — instead of juggling `aws bedrock`, `az cognitiveservices`, and `gcloud ai`.
35
+
36
+ ## Install
37
+
38
+ ```bash
39
+ pip install bonito-cli
40
+ ```
41
+
42
+ ## Quick Start
43
+
44
+ ```bash
45
+ # Authenticate
46
+ bonito auth login
47
+
48
+ # List connected cloud providers
49
+ bonito providers list
50
+
51
+ # Browse 300+ models across all providers
52
+ bonito models list
53
+ bonito models list --search "claude"
54
+
55
+ # Chat with any model
56
+ bonito chat -m <model-id> "What is quantum computing?"
57
+
58
+ # Interactive chat
59
+ bonito chat
60
+
61
+ # View deployments
62
+ bonito deployments list
63
+
64
+ # Check gateway logs
65
+ bonito gateway logs
66
+ ```
67
+
68
+ ## Commands
69
+
70
+ | Command | Description |
71
+ |---------|------------|
72
+ | `bonito auth` | 🔐 Authentication & API keys |
73
+ | `bonito providers` | ☁️ Cloud provider management |
74
+ | `bonito models` | 🤖 AI model catalogue |
75
+ | `bonito deployments` | 🚀 Deployment management |
76
+ | `bonito chat` | 💬 Interactive AI chat |
77
+ | `bonito gateway` | 🌐 API gateway management |
78
+ | `bonito policies` | 🎯 Routing policies |
79
+ | `bonito analytics` | 📊 Usage analytics & costs |
80
+
81
+ ## Features
82
+
83
+ - **Multi-cloud** — AWS Bedrock, Azure OpenAI, Google Vertex AI in one tool
84
+ - **Interactive chat** — Talk to any model with `/model`, `/temp`, `/export` commands
85
+ - **Compare models** — `bonito chat --compare model1 --compare model2 "prompt"`
86
+ - **Routing policies** — Cost-optimized, failover, A/B testing
87
+ - **JSON output** — `--json` flag on every command for CI/CD automation
88
+ - **Rich terminal UI** — Beautiful tables, progress bars, and formatted output
89
+
90
+ ## Configuration
91
+
92
+ ```bash
93
+ # Environment variables (override config file)
94
+ export BONITO_API_KEY=your-api-key
95
+ export BONITO_API_URL=https://your-instance.example.com
96
+
97
+ # Or use config file (~/.bonito/config.json)
98
+ bonito auth login --email you@company.com
99
+ ```
100
+
101
+ ## Requirements
102
+
103
+ - Python 3.10+
104
+ - A [Bonito](https://getbonito.com) account
105
+
106
+ ## License
107
+
108
+ MIT
@@ -0,0 +1,79 @@
1
+ # 🐟 Bonito CLI
2
+
3
+ **Unified multi-cloud AI management from your terminal.**
4
+
5
+ Bonito gives enterprise AI teams a single CLI to manage models, costs, and workloads across AWS Bedrock, Azure OpenAI, and Google Vertex AI — instead of juggling `aws bedrock`, `az cognitiveservices`, and `gcloud ai`.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pip install bonito-cli
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```bash
16
+ # Authenticate
17
+ bonito auth login
18
+
19
+ # List connected cloud providers
20
+ bonito providers list
21
+
22
+ # Browse 300+ models across all providers
23
+ bonito models list
24
+ bonito models list --search "claude"
25
+
26
+ # Chat with any model
27
+ bonito chat -m <model-id> "What is quantum computing?"
28
+
29
+ # Interactive chat
30
+ bonito chat
31
+
32
+ # View deployments
33
+ bonito deployments list
34
+
35
+ # Check gateway logs
36
+ bonito gateway logs
37
+ ```
38
+
39
+ ## Commands
40
+
41
+ | Command | Description |
42
+ |---------|------------|
43
+ | `bonito auth` | 🔐 Authentication & API keys |
44
+ | `bonito providers` | ☁️ Cloud provider management |
45
+ | `bonito models` | 🤖 AI model catalogue |
46
+ | `bonito deployments` | 🚀 Deployment management |
47
+ | `bonito chat` | 💬 Interactive AI chat |
48
+ | `bonito gateway` | 🌐 API gateway management |
49
+ | `bonito policies` | 🎯 Routing policies |
50
+ | `bonito analytics` | 📊 Usage analytics & costs |
51
+
52
+ ## Features
53
+
54
+ - **Multi-cloud** — AWS Bedrock, Azure OpenAI, Google Vertex AI in one tool
55
+ - **Interactive chat** — Talk to any model with `/model`, `/temp`, `/export` commands
56
+ - **Compare models** — `bonito chat --compare model1 --compare model2 "prompt"`
57
+ - **Routing policies** — Cost-optimized, failover, A/B testing
58
+ - **JSON output** — `--json` flag on every command for CI/CD automation
59
+ - **Rich terminal UI** — Beautiful tables, progress bars, and formatted output
60
+
61
+ ## Configuration
62
+
63
+ ```bash
64
+ # Environment variables (override config file)
65
+ export BONITO_API_KEY=your-api-key
66
+ export BONITO_API_URL=https://your-instance.example.com
67
+
68
+ # Or use config file (~/.bonito/config.json)
69
+ bonito auth login --email you@company.com
70
+ ```
71
+
72
+ ## Requirements
73
+
74
+ - Python 3.10+
75
+ - A [Bonito](https://getbonito.com) account
76
+
77
+ ## License
78
+
79
+ MIT
@@ -0,0 +1,3 @@
1
+ """Bonito CLI — Unified multi-cloud AI management from your terminal."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,4 @@
1
+ """Allow running as `python -m bonito_cli`."""
2
+ from bonito_cli.app import main
3
+
4
+ main()
@@ -0,0 +1,167 @@
1
+ """Synchronous HTTP API client for the Bonito backend."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from typing import Any, Dict, Generator, Optional
7
+
8
+ import httpx
9
+ from rich.console import Console
10
+
11
+ from .config import get_api_key, get_api_url
12
+
13
+ console = Console()
14
+
15
+
16
+ class APIError(Exception):
17
+ """Raised when an API request fails."""
18
+
19
+ def __init__(self, message: str, status_code: Optional[int] = None):
20
+ super().__init__(message)
21
+ self.status_code = status_code
22
+
23
+
24
+ class BonitoAPI:
25
+ """Synchronous Bonito API client backed by ``httpx.Client``."""
26
+
27
+ def __init__(self) -> None:
28
+ self.base_url: str = get_api_url()
29
+ self._client: Optional[httpx.Client] = None
30
+
31
+ # ── internal helpers ────────────────────────────────────────
32
+
33
+ @property
34
+ def client(self) -> httpx.Client:
35
+ if self._client is None or self._client.is_closed:
36
+ self._client = httpx.Client(
37
+ base_url=self.base_url,
38
+ timeout=30.0,
39
+ follow_redirects=True,
40
+ )
41
+ return self._client
42
+
43
+ def _headers(self) -> Dict[str, str]:
44
+ headers: Dict[str, str] = {
45
+ "Content-Type": "application/json",
46
+ "User-Agent": "bonito-cli/0.1.0",
47
+ }
48
+ token = get_api_key()
49
+ if token:
50
+ headers["Authorization"] = f"Bearer {token}"
51
+ return headers
52
+
53
+ def _request(
54
+ self,
55
+ method: str,
56
+ endpoint: str,
57
+ data: Any = None,
58
+ params: Optional[Dict[str, Any]] = None,
59
+ ) -> Any:
60
+ url = endpoint if endpoint.startswith("http") else f"/api{endpoint}"
61
+ try:
62
+ resp = self.client.request(
63
+ method, url, json=data, params=params, headers=self._headers()
64
+ )
65
+ except httpx.RequestError as exc:
66
+ raise APIError(f"Connection failed: {exc}") from exc
67
+
68
+ if resp.status_code == 401:
69
+ raise APIError(
70
+ "Authentication failed — run [cyan]bonito auth login[/cyan].", 401
71
+ )
72
+ if resp.status_code == 204:
73
+ return {"status": "ok"}
74
+ if resp.status_code >= 400:
75
+ try:
76
+ body = resp.json()
77
+ detail = body.get(
78
+ "detail",
79
+ body.get("error", {}).get("message", f"HTTP {resp.status_code}"),
80
+ )
81
+ except Exception:
82
+ detail = f"HTTP {resp.status_code}: {resp.text[:200]}"
83
+
84
+ # Parse Pydantic 422 validation errors into friendly messages
85
+ if resp.status_code == 422 and isinstance(detail, list):
86
+ parts: list[str] = []
87
+ for err in detail:
88
+ if isinstance(err, dict):
89
+ err_type = err.get("type", "")
90
+ err_msg = err.get("msg", "Validation error")
91
+ loc = err.get("loc", [])
92
+ field = str(loc[-1]).replace("_", " ") if loc else "input"
93
+ if err_type == "uuid_parsing":
94
+ parts.append(
95
+ f"Invalid {field} format. "
96
+ "Expected a UUID (e.g. a1b2c3d4-e5f6-...)"
97
+ )
98
+ else:
99
+ parts.append(f"{err_msg} (field: {field})")
100
+ else:
101
+ parts.append(str(err))
102
+ msg = "; ".join(parts) if parts else "Validation error"
103
+ elif isinstance(detail, list):
104
+ msg = "; ".join(str(d) for d in detail)
105
+ else:
106
+ msg = str(detail)
107
+
108
+ raise APIError(msg, resp.status_code)
109
+
110
+ try:
111
+ return resp.json()
112
+ except Exception:
113
+ return {"status": "ok", "text": resp.text}
114
+
115
+ # ── public verbs ────────────────────────────────────────────
116
+
117
+ def get(self, endpoint: str, params: Optional[Dict[str, Any]] = None) -> Any:
118
+ return self._request("GET", endpoint, params=params)
119
+
120
+ def post(self, endpoint: str, data: Any = None) -> Any:
121
+ return self._request("POST", endpoint, data=data)
122
+
123
+ def put(self, endpoint: str, data: Any = None) -> Any:
124
+ return self._request("PUT", endpoint, data=data)
125
+
126
+ def patch(self, endpoint: str, data: Any = None) -> Any:
127
+ return self._request("PATCH", endpoint, data=data)
128
+
129
+ def delete(self, endpoint: str) -> Any:
130
+ return self._request("DELETE", endpoint)
131
+
132
+ # ── streaming (SSE) ─────────────────────────────────────────
133
+
134
+ def stream_post(
135
+ self,
136
+ url: str,
137
+ data: dict,
138
+ headers: Optional[Dict[str, str]] = None,
139
+ ) -> Generator[dict, None, None]:
140
+ """Stream a POST request that returns SSE ``data:`` lines."""
141
+ hdrs = self._headers()
142
+ if headers:
143
+ hdrs.update(headers)
144
+
145
+ full_url = url if url.startswith("http") else f"{self.base_url}{url}"
146
+
147
+ with httpx.stream(
148
+ "POST", full_url, json=data, headers=hdrs, timeout=120.0
149
+ ) as resp:
150
+ if resp.status_code >= 400:
151
+ raise APIError(
152
+ f"HTTP {resp.status_code}: {resp.read().decode()[:200]}",
153
+ resp.status_code,
154
+ )
155
+ for line in resp.iter_lines():
156
+ if line.startswith("data: "):
157
+ chunk = line[6:]
158
+ if chunk.strip() == "[DONE]":
159
+ break
160
+ try:
161
+ yield json.loads(chunk)
162
+ except json.JSONDecodeError:
163
+ continue
164
+
165
+
166
+ # ── module-level singleton ──────────────────────────────────────
167
+ api = BonitoAPI()