commoncompute 0.1.2__tar.gz → 0.3.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.
Files changed (24) hide show
  1. commoncompute-0.3.0/PKG-INFO +349 -0
  2. commoncompute-0.3.0/README.md +314 -0
  3. {commoncompute-0.1.2 → commoncompute-0.3.0}/commoncompute/__init__.py +5 -1
  4. commoncompute-0.3.0/commoncompute/_async_client.py +474 -0
  5. commoncompute-0.3.0/commoncompute/_async_tasks.py +349 -0
  6. commoncompute-0.3.0/commoncompute/_config.py +161 -0
  7. {commoncompute-0.1.2 → commoncompute-0.3.0}/commoncompute/_transport.py +27 -43
  8. commoncompute-0.3.0/commoncompute/cli.py +828 -0
  9. {commoncompute-0.1.2 → commoncompute-0.3.0}/commoncompute/client.py +18 -88
  10. {commoncompute-0.1.2 → commoncompute-0.3.0}/commoncompute/connect.py +8 -18
  11. {commoncompute-0.1.2 → commoncompute-0.3.0}/commoncompute/models.py +3 -3
  12. {commoncompute-0.1.2 → commoncompute-0.3.0}/commoncompute/tasks.py +144 -34
  13. {commoncompute-0.1.2 → commoncompute-0.3.0}/pyproject.toml +18 -2
  14. commoncompute-0.1.2/PKG-INFO +0 -216
  15. commoncompute-0.1.2/README.md +0 -181
  16. commoncompute-0.1.2/commoncompute/cli.py +0 -481
  17. {commoncompute-0.1.2 → commoncompute-0.3.0}/.gitignore +0 -0
  18. {commoncompute-0.1.2 → commoncompute-0.3.0}/LICENSE +0 -0
  19. {commoncompute-0.1.2 → commoncompute-0.3.0}/commoncompute/_generated_workloads.py +0 -0
  20. {commoncompute-0.1.2 → commoncompute-0.3.0}/commoncompute/aws_batch.py +0 -0
  21. {commoncompute-0.1.2 → commoncompute-0.3.0}/commoncompute/compat/__init__.py +0 -0
  22. {commoncompute-0.1.2 → commoncompute-0.3.0}/commoncompute/compat/openai.py +0 -0
  23. {commoncompute-0.1.2 → commoncompute-0.3.0}/commoncompute/errors.py +0 -0
  24. {commoncompute-0.1.2 → commoncompute-0.3.0}/commoncompute/py.typed +0 -0
@@ -0,0 +1,349 @@
1
+ Metadata-Version: 2.4
2
+ Name: commoncompute
3
+ Version: 0.3.0
4
+ Summary: Official Python SDK for Common Compute — the batch AI bill you shouldn't be paying.
5
+ Project-URL: Homepage, https://commoncompute.ai
6
+ Project-URL: Documentation, https://commoncompute.ai/docs
7
+ Project-URL: Source, https://github.com/Ikaikaalika/commoncomputeai
8
+ Project-URL: Changelog, https://commoncompute.ai/changelog
9
+ Author-email: Common Compute <support@commoncompute.ai>
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: ai,apple-silicon,embeddings,inference,llm,openai-compatible
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
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 :: Scientific/Engineering :: Artificial Intelligence
23
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
+ Requires-Python: >=3.9
25
+ Requires-Dist: httpx<1.0,>=0.24
26
+ Requires-Dist: pydantic<3.0,>=2.0
27
+ Provides-Extra: cli
28
+ Requires-Dist: rich>=13; extra == 'cli'
29
+ Requires-Dist: typer>=0.9; extra == 'cli'
30
+ Provides-Extra: test
31
+ Requires-Dist: pytest-asyncio>=0.21; extra == 'test'
32
+ Requires-Dist: pytest>=7.0; extra == 'test'
33
+ Requires-Dist: respx>=0.20; extra == 'test'
34
+ Description-Content-Type: text/markdown
35
+
36
+ # Common Compute — Python SDK
37
+
38
+ The official Python SDK + CLI for [Common Compute](https://commoncompute.ai) —
39
+ batch AI compute without the AWS tax, on Apple Silicon hardware AWS can't offer.
40
+
41
+ One SDK call replaces the IAM roles, compute environments, and job definitions.
42
+ Every job returns its **price and ETA before it runs**, and you're only billed
43
+ for **successful** jobs — each with a verifiable receipt.
44
+
45
+ ```bash
46
+ pip install commoncompute # core: httpx + pydantic only
47
+ pip install "commoncompute[cli]" # + the `commoncompute` command line
48
+ commoncompute login # opens the browser — no key to copy-paste
49
+ ```
50
+
51
+ The command is `commoncompute`. A short `cc` is **not** installed — that name
52
+ belongs to the system C compiler. Want the shorthand? `alias cc="commoncompute"`.
53
+
54
+ `commoncompute login` (or `commoncompute.connect()` from a script or notebook) opens an
55
+ approval page in your browser; click **Approve** and a fresh API key is saved
56
+ to `~/.config/commoncompute/credentials`, where the SDK finds it automatically.
57
+ Prefer explicit config? `export CC_API_KEY=cc_live_...` works everywhere and
58
+ takes precedence.
59
+
60
+ ## The 10-minute path
61
+
62
+ ```python
63
+ import commoncompute as cc
64
+
65
+ client = cc.Client() # reads CC_API_KEY, or the saved credentials file
66
+
67
+ job = client.submit("coreml_embed", {"input": ["what is the neural engine?"]},
68
+ model_id="bge-base")
69
+ print(job.job_id, job.locked_price_usd, job.eta_seconds) # price known BEFORE it runs
70
+
71
+ result = client.result(job) # waits, downloads, attaches the receipt
72
+ print(result.output)
73
+ print(result.receipt) # signed proof of what ran, where, for how much
74
+ ```
75
+
76
+ Prefer the OpenAI-style surface? `client.embeddings.create(input=[...],
77
+ model="bge-base")` returns vectors synchronously.
78
+
79
+ ## Examples — live today
80
+
81
+ ### 1. OCR / document extraction — vs AWS Textract
82
+
83
+ ```python
84
+ job = client.ocr.extract("https://example.com/contracts.pdf", structured=True)
85
+ result = client.result(job) # blocks + bounding boxes as JSON
86
+ ```
87
+
88
+ ### 2. Embeddings in bulk
89
+
90
+ ```python
91
+ for result in client.submit_many(
92
+ "coreml_embed",
93
+ [{"input": chunked(doc)} for doc in corpus],
94
+ max_concurrent=16,
95
+ ):
96
+ save(result.output) # results stream as they complete
97
+ ```
98
+
99
+ ### 3. Background removal
100
+
101
+ ```python
102
+ job = client.images.remove_background("product-shot.png")
103
+ ```
104
+
105
+ Also live: speech synthesis, image classification/detection/pose, barcode
106
+ reading, HEIC conversion — plus the generic `client.submit(workload_id,
107
+ payload)` for anything marked **live** in
108
+ [the catalog](https://commoncompute.ai/workloads).
109
+
110
+ ### In preview
111
+
112
+ `client.translate(...)` and `client.video.transcode(...)` run on preview
113
+ capacity — functional, not yet GA.
114
+
115
+ ### Not live yet
116
+
117
+ `client.transcription.*`, `client.rerank(...)`, `client.images.generate(...)`,
118
+ `client.chat.*`, and `client.build.ios_test(...)` target workloads still
119
+ marked *coming soon*: the API refuses them with a clear
120
+ `workload_not_available` error (HTTP 409) rather than queueing a job that
121
+ won't run. They activate automatically as those lanes go live — watch
122
+ [the catalog](https://commoncompute.ai/workloads).
123
+
124
+ ## Price before execution, hard caps, dry runs
125
+
126
+ ```python
127
+ quote = client.ocr.extract("scan.pdf", dry_run=True) # price + ETA, no job
128
+ job = client.ocr.extract("scan.pdf", max_spend_usd=1.0) # refused if it would exceed
129
+ ```
130
+
131
+ ## Batches that stream back
132
+
133
+ ```python
134
+ for result in client.submit_many("vision_bgremove", images, max_concurrent=8):
135
+ save(result.output) # results stream as they complete
136
+ ```
137
+
138
+ ## Receipts
139
+
140
+ Every successful job carries a receipt (`job_id`, cost, provider id,
141
+ timestamps, input/output hashes, signature):
142
+
143
+ ```python
144
+ client.receipts.list()
145
+ csv_blob = client.receipts.export(format="csv")
146
+ ```
147
+
148
+ ## Account
149
+
150
+ ```python
151
+ client.account.balance() # card-on-file billing state (cash only)
152
+ client.account.spend(days=30) # spend summary by workload
153
+ client.account.tier() # your volume tier + the next threshold
154
+ ```
155
+
156
+ Per-task prices step down automatically as your monthly usage grows —
157
+ your current rate is always the one a quote returns.
158
+
159
+ ## Async
160
+
161
+ `AsyncClient` mirrors `Client` method-for-method — `submit`, `wait`, `result`,
162
+ and `submit_many` all have `await`-able twins. Use it as an async context
163
+ manager so the underlying HTTP pool is closed for you:
164
+
165
+ ```python
166
+ import asyncio
167
+ import commoncompute as cc
168
+
169
+ async def main():
170
+ async with cc.AsyncClient() as client: # closes the pool on exit
171
+ job = await client.submit("coreml_embed", {"input": ["hello world"]})
172
+ print(job.job_id, job.locked_price_usd, job.eta_seconds) # price BEFORE it runs
173
+
174
+ await client.wait(job) # poll until terminal
175
+ result = await client.result(job) # + download output & receipt
176
+ print(result.output)
177
+
178
+ asyncio.run(main())
179
+ ```
180
+
181
+ Batches stream back the same way — `submit_many` is an async generator:
182
+
183
+ ```python
184
+ async with cc.AsyncClient() as client:
185
+ async for r in client.submit_many("coreml_embed", batches, max_concurrent=8):
186
+ save(r.output) # each result as it completes
187
+ ```
188
+
189
+ The lower-level `client.jobs.*` namespace (`jobs.submit`, `jobs.wait`,
190
+ `jobs.get`, `jobs.events`, `jobs.download`) is async here too when you want the
191
+ raw request/response dicts instead of typed `Job`/`TaskResult` objects.
192
+
193
+ ## CLI
194
+
195
+ `pip install "commoncompute[cli]"` installs the `commoncompute` command.
196
+
197
+ > **Why not a short `cc`?** Because `cc` is the Unix C compiler
198
+ > (`/usr/bin/cc`). Shipping a binary by that name puts it ahead of the real
199
+ > compiler on `PATH` for anyone whose Python bin directory sorts first, and
200
+ > every build that shells out to `cc` then fails with a CLI usage error
201
+ > instead of compiling. So `commoncompute` is the only command installed. If
202
+ > you want the shorthand, opt in yourself:
203
+ >
204
+ > ```bash
205
+ > alias cc="commoncompute" # add to ~/.zshrc or ~/.bashrc
206
+ > ```
207
+
208
+ ```bash
209
+ commoncompute login # stores the key locally
210
+ commoncompute quote vision_ocr --units 5 # price + ETA, no execution
211
+ commoncompute submit coreml_embed --payload '{"input":["hi"]}' --wait
212
+ commoncompute jobs list
213
+ commoncompute jobs get <id>
214
+ commoncompute balance
215
+ commoncompute receipts export --format csv --out receipts.csv
216
+ ```
217
+
218
+ ### Commands
219
+
220
+ | Command | What it does |
221
+ |---------|--------------|
222
+ | `commoncompute login` / `commoncompute logout` | Store / remove the API key (browser approval, or `--api-key`) |
223
+ | `commoncompute whoami` | Show the account behind the current key |
224
+ | `commoncompute balance` | Current workspace balance |
225
+ | `commoncompute usage` | Spend + call-count rollups over a date range (`--start`, `--end`, `--group-by`) |
226
+ | `commoncompute spend` | Total spend over the last `--days` N, grouped by workload |
227
+ | `commoncompute tier` | Current volume tier and the next threshold |
228
+ | `commoncompute workloads` | List available workloads |
229
+ | `commoncompute models` | List models (`--workload` to filter) |
230
+ | `commoncompute quote` | Price + ETA for a workload without submitting (`--units`, `--priority`, `--model`) |
231
+ | `commoncompute submit` | Submit one job (`--payload/-f`, `--model`, `--priority`, `--wait`, `--timeout`) |
232
+ | `commoncompute submit-many` | Submit a JSONL file of payloads; stream one result per line as NDJSON (`--input/-i`, `--max-concurrent`, `--no-wait`) |
233
+ | `commoncompute chat` | One-shot chat message to a model (`--model`, `--stream/--no-stream`) |
234
+ | `commoncompute embed` | Embed a single string (prints the vector length) |
235
+ | `commoncompute playground` | Open the web playground |
236
+ | `commoncompute jobs list` | Recent submissions, newest first (`--limit/-n`) |
237
+ | `commoncompute jobs get <id>` | Fetch one job |
238
+ | `commoncompute jobs wait <id>` | Poll a job to a terminal state (`--timeout/-t`) |
239
+ | `commoncompute jobs events <id>` | Event timeline for a job |
240
+ | `commoncompute jobs download <id>` | Download a job's result (`--out/-o`, else stdout) |
241
+ | `commoncompute keys list` / `create` / `revoke` | Manage API keys |
242
+ | `commoncompute receipts list` | Receipts for completed jobs (`--limit/-n`) |
243
+ | `commoncompute receipts get <id>` | Signed receipt for a single job |
244
+ | `commoncompute receipts export` | Export receipts as CSV or JSON (`--format/-f`, `--out/-o`, `--limit/-n`) |
245
+ | `commoncompute config path` / `show` / `set` / `unset` | Inspect and edit local settings (see below) |
246
+
247
+ `commoncompute --version` (or `-V`) prints the version. Every read command that renders a
248
+ table also accepts **`--json`** for plain, ANSI-free machine-readable output —
249
+ `whoami`, `balance`, `usage`, `spend`, `tier`, `workloads`, `models`, `quote`,
250
+ `submit`, `jobs list/get/wait/events`, `keys list`, and `receipts list`.
251
+
252
+ ### Shell completion
253
+
254
+ Typer ships completion for bash, zsh, fish, and PowerShell:
255
+
256
+ ```bash
257
+ commoncompute --install-completion # install for the current shell
258
+ commoncompute --show-completion # print the script to inspect or customise
259
+ ```
260
+
261
+ ### Exit codes
262
+
263
+ Scripts can branch on `commoncompute`'s exit status:
264
+
265
+ | Code | Meaning |
266
+ |------|---------|
267
+ | `0` | Success |
268
+ | `1` | API or runtime error (network, server, bad request) |
269
+ | `2` | Usage error — missing/invalid arguments, or no credentials found |
270
+ | `3` | The job reached a **failed** terminal state (`failed` / `dead_letter` / `cancelled`) — `commoncompute jobs wait` and `commoncompute submit --wait` only |
271
+
272
+ ## Migrating from OpenAI (optional)
273
+
274
+ Existing OpenAI-based pipelines (embeddings, chat, transcription) can point
275
+ at Common Compute by swapping two env vars — or:
276
+
277
+ ```python
278
+ from commoncompute.compat import openai # sets OPENAI_BASE_URL / OPENAI_API_KEY
279
+ client = openai.OpenAI()
280
+ ```
281
+
282
+ This is a migration path, not the recommended interface: the native client
283
+ returns locked prices, ETAs, and receipts that the OpenAI wire format can't
284
+ express.
285
+
286
+ ## Errors
287
+
288
+ Typed, always:
289
+
290
+ ```python
291
+ try:
292
+ client.ocr.extract("scan.pdf", max_spend_usd=0.01)
293
+ except cc.InsufficientFundsError: # quote exceeded the cap / no card
294
+ ...
295
+ except cc.PermissionDeniedError: # key lacks scope for this workload
296
+ ...
297
+ except cc.ConflictError: # idempotency-key or state conflict (409)
298
+ ...
299
+ except cc.UnsupportedFormatError: # wrong file type for the workload
300
+ ...
301
+ except cc.JobTimeoutError: # wait() expired; job still running
302
+ ...
303
+ except cc.NetworkError: # no HTTP response after retries
304
+ ...
305
+ except cc.CommonComputeError as e: # everything raises from this
306
+ print(e.request_id)
307
+ ```
308
+
309
+ The full hierarchy — `AuthenticationError`, `PermissionDeniedError`,
310
+ `NotFoundError`, `ConflictError`, `RateLimitError`, `InsufficientFundsError`,
311
+ `BadRequestError`, `ValidationError`, `UnsupportedFormatError`, `NetworkError`,
312
+ `JobTimeoutError`, `APIError` — all subclass `CommonComputeError`.
313
+
314
+ ## Configuration
315
+
316
+ Credentials and settings live under one canonical directory (override the whole
317
+ directory with `$CC_CONFIG_DIR`):
318
+
319
+ ```
320
+ ~/.config/commoncompute/credentials # the API key (single line)
321
+ ~/.config/commoncompute/config # settings: base_url, org (key = value)
322
+ ```
323
+
324
+ Two **legacy** locations are still read (never written) so older installs keep
325
+ working: `~/.commoncompute/config` and `~/.commoncompute/credentials`.
326
+
327
+ Environment variables take precedence over both files at runtime:
328
+
329
+ | Setting | Env var | Resolution order (first match wins) |
330
+ |----------|-----------------|-------------------------------------|
331
+ | API key | `CC_API_KEY` | env → `~/.config/commoncompute/credentials` → `~/.commoncompute/config` → `~/.commoncompute/credentials` |
332
+ | Base URL | `CC_BASE_URL` | env → `config` file `base_url` → `https://api.commoncompute.ai` |
333
+ | Org id | `CC_ORG` | env → `config` file `org` → default workspace |
334
+ | Config dir | `CC_CONFIG_DIR` | overrides the `~/.config/commoncompute` location above |
335
+
336
+ Inspect and edit the local config from the CLI:
337
+
338
+ ```bash
339
+ commoncompute config path # print the config directory
340
+ commoncompute config show # settings + active credential source (key masked)
341
+ commoncompute config set base_url https://api.commoncompute.ai
342
+ commoncompute config set org my-workspace
343
+ commoncompute config unset org
344
+ ```
345
+
346
+ Env vars still win at runtime — `commoncompute config set` writes the file, but
347
+ `CC_BASE_URL` / `CC_ORG` override it for a given process.
348
+
349
+ Python 3.9+. Core dependencies: `httpx`, `pydantic`. MIT license.
@@ -0,0 +1,314 @@
1
+ # Common Compute — Python SDK
2
+
3
+ The official Python SDK + CLI for [Common Compute](https://commoncompute.ai) —
4
+ batch AI compute without the AWS tax, on Apple Silicon hardware AWS can't offer.
5
+
6
+ One SDK call replaces the IAM roles, compute environments, and job definitions.
7
+ Every job returns its **price and ETA before it runs**, and you're only billed
8
+ for **successful** jobs — each with a verifiable receipt.
9
+
10
+ ```bash
11
+ pip install commoncompute # core: httpx + pydantic only
12
+ pip install "commoncompute[cli]" # + the `commoncompute` command line
13
+ commoncompute login # opens the browser — no key to copy-paste
14
+ ```
15
+
16
+ The command is `commoncompute`. A short `cc` is **not** installed — that name
17
+ belongs to the system C compiler. Want the shorthand? `alias cc="commoncompute"`.
18
+
19
+ `commoncompute login` (or `commoncompute.connect()` from a script or notebook) opens an
20
+ approval page in your browser; click **Approve** and a fresh API key is saved
21
+ to `~/.config/commoncompute/credentials`, where the SDK finds it automatically.
22
+ Prefer explicit config? `export CC_API_KEY=cc_live_...` works everywhere and
23
+ takes precedence.
24
+
25
+ ## The 10-minute path
26
+
27
+ ```python
28
+ import commoncompute as cc
29
+
30
+ client = cc.Client() # reads CC_API_KEY, or the saved credentials file
31
+
32
+ job = client.submit("coreml_embed", {"input": ["what is the neural engine?"]},
33
+ model_id="bge-base")
34
+ print(job.job_id, job.locked_price_usd, job.eta_seconds) # price known BEFORE it runs
35
+
36
+ result = client.result(job) # waits, downloads, attaches the receipt
37
+ print(result.output)
38
+ print(result.receipt) # signed proof of what ran, where, for how much
39
+ ```
40
+
41
+ Prefer the OpenAI-style surface? `client.embeddings.create(input=[...],
42
+ model="bge-base")` returns vectors synchronously.
43
+
44
+ ## Examples — live today
45
+
46
+ ### 1. OCR / document extraction — vs AWS Textract
47
+
48
+ ```python
49
+ job = client.ocr.extract("https://example.com/contracts.pdf", structured=True)
50
+ result = client.result(job) # blocks + bounding boxes as JSON
51
+ ```
52
+
53
+ ### 2. Embeddings in bulk
54
+
55
+ ```python
56
+ for result in client.submit_many(
57
+ "coreml_embed",
58
+ [{"input": chunked(doc)} for doc in corpus],
59
+ max_concurrent=16,
60
+ ):
61
+ save(result.output) # results stream as they complete
62
+ ```
63
+
64
+ ### 3. Background removal
65
+
66
+ ```python
67
+ job = client.images.remove_background("product-shot.png")
68
+ ```
69
+
70
+ Also live: speech synthesis, image classification/detection/pose, barcode
71
+ reading, HEIC conversion — plus the generic `client.submit(workload_id,
72
+ payload)` for anything marked **live** in
73
+ [the catalog](https://commoncompute.ai/workloads).
74
+
75
+ ### In preview
76
+
77
+ `client.translate(...)` and `client.video.transcode(...)` run on preview
78
+ capacity — functional, not yet GA.
79
+
80
+ ### Not live yet
81
+
82
+ `client.transcription.*`, `client.rerank(...)`, `client.images.generate(...)`,
83
+ `client.chat.*`, and `client.build.ios_test(...)` target workloads still
84
+ marked *coming soon*: the API refuses them with a clear
85
+ `workload_not_available` error (HTTP 409) rather than queueing a job that
86
+ won't run. They activate automatically as those lanes go live — watch
87
+ [the catalog](https://commoncompute.ai/workloads).
88
+
89
+ ## Price before execution, hard caps, dry runs
90
+
91
+ ```python
92
+ quote = client.ocr.extract("scan.pdf", dry_run=True) # price + ETA, no job
93
+ job = client.ocr.extract("scan.pdf", max_spend_usd=1.0) # refused if it would exceed
94
+ ```
95
+
96
+ ## Batches that stream back
97
+
98
+ ```python
99
+ for result in client.submit_many("vision_bgremove", images, max_concurrent=8):
100
+ save(result.output) # results stream as they complete
101
+ ```
102
+
103
+ ## Receipts
104
+
105
+ Every successful job carries a receipt (`job_id`, cost, provider id,
106
+ timestamps, input/output hashes, signature):
107
+
108
+ ```python
109
+ client.receipts.list()
110
+ csv_blob = client.receipts.export(format="csv")
111
+ ```
112
+
113
+ ## Account
114
+
115
+ ```python
116
+ client.account.balance() # card-on-file billing state (cash only)
117
+ client.account.spend(days=30) # spend summary by workload
118
+ client.account.tier() # your volume tier + the next threshold
119
+ ```
120
+
121
+ Per-task prices step down automatically as your monthly usage grows —
122
+ your current rate is always the one a quote returns.
123
+
124
+ ## Async
125
+
126
+ `AsyncClient` mirrors `Client` method-for-method — `submit`, `wait`, `result`,
127
+ and `submit_many` all have `await`-able twins. Use it as an async context
128
+ manager so the underlying HTTP pool is closed for you:
129
+
130
+ ```python
131
+ import asyncio
132
+ import commoncompute as cc
133
+
134
+ async def main():
135
+ async with cc.AsyncClient() as client: # closes the pool on exit
136
+ job = await client.submit("coreml_embed", {"input": ["hello world"]})
137
+ print(job.job_id, job.locked_price_usd, job.eta_seconds) # price BEFORE it runs
138
+
139
+ await client.wait(job) # poll until terminal
140
+ result = await client.result(job) # + download output & receipt
141
+ print(result.output)
142
+
143
+ asyncio.run(main())
144
+ ```
145
+
146
+ Batches stream back the same way — `submit_many` is an async generator:
147
+
148
+ ```python
149
+ async with cc.AsyncClient() as client:
150
+ async for r in client.submit_many("coreml_embed", batches, max_concurrent=8):
151
+ save(r.output) # each result as it completes
152
+ ```
153
+
154
+ The lower-level `client.jobs.*` namespace (`jobs.submit`, `jobs.wait`,
155
+ `jobs.get`, `jobs.events`, `jobs.download`) is async here too when you want the
156
+ raw request/response dicts instead of typed `Job`/`TaskResult` objects.
157
+
158
+ ## CLI
159
+
160
+ `pip install "commoncompute[cli]"` installs the `commoncompute` command.
161
+
162
+ > **Why not a short `cc`?** Because `cc` is the Unix C compiler
163
+ > (`/usr/bin/cc`). Shipping a binary by that name puts it ahead of the real
164
+ > compiler on `PATH` for anyone whose Python bin directory sorts first, and
165
+ > every build that shells out to `cc` then fails with a CLI usage error
166
+ > instead of compiling. So `commoncompute` is the only command installed. If
167
+ > you want the shorthand, opt in yourself:
168
+ >
169
+ > ```bash
170
+ > alias cc="commoncompute" # add to ~/.zshrc or ~/.bashrc
171
+ > ```
172
+
173
+ ```bash
174
+ commoncompute login # stores the key locally
175
+ commoncompute quote vision_ocr --units 5 # price + ETA, no execution
176
+ commoncompute submit coreml_embed --payload '{"input":["hi"]}' --wait
177
+ commoncompute jobs list
178
+ commoncompute jobs get <id>
179
+ commoncompute balance
180
+ commoncompute receipts export --format csv --out receipts.csv
181
+ ```
182
+
183
+ ### Commands
184
+
185
+ | Command | What it does |
186
+ |---------|--------------|
187
+ | `commoncompute login` / `commoncompute logout` | Store / remove the API key (browser approval, or `--api-key`) |
188
+ | `commoncompute whoami` | Show the account behind the current key |
189
+ | `commoncompute balance` | Current workspace balance |
190
+ | `commoncompute usage` | Spend + call-count rollups over a date range (`--start`, `--end`, `--group-by`) |
191
+ | `commoncompute spend` | Total spend over the last `--days` N, grouped by workload |
192
+ | `commoncompute tier` | Current volume tier and the next threshold |
193
+ | `commoncompute workloads` | List available workloads |
194
+ | `commoncompute models` | List models (`--workload` to filter) |
195
+ | `commoncompute quote` | Price + ETA for a workload without submitting (`--units`, `--priority`, `--model`) |
196
+ | `commoncompute submit` | Submit one job (`--payload/-f`, `--model`, `--priority`, `--wait`, `--timeout`) |
197
+ | `commoncompute submit-many` | Submit a JSONL file of payloads; stream one result per line as NDJSON (`--input/-i`, `--max-concurrent`, `--no-wait`) |
198
+ | `commoncompute chat` | One-shot chat message to a model (`--model`, `--stream/--no-stream`) |
199
+ | `commoncompute embed` | Embed a single string (prints the vector length) |
200
+ | `commoncompute playground` | Open the web playground |
201
+ | `commoncompute jobs list` | Recent submissions, newest first (`--limit/-n`) |
202
+ | `commoncompute jobs get <id>` | Fetch one job |
203
+ | `commoncompute jobs wait <id>` | Poll a job to a terminal state (`--timeout/-t`) |
204
+ | `commoncompute jobs events <id>` | Event timeline for a job |
205
+ | `commoncompute jobs download <id>` | Download a job's result (`--out/-o`, else stdout) |
206
+ | `commoncompute keys list` / `create` / `revoke` | Manage API keys |
207
+ | `commoncompute receipts list` | Receipts for completed jobs (`--limit/-n`) |
208
+ | `commoncompute receipts get <id>` | Signed receipt for a single job |
209
+ | `commoncompute receipts export` | Export receipts as CSV or JSON (`--format/-f`, `--out/-o`, `--limit/-n`) |
210
+ | `commoncompute config path` / `show` / `set` / `unset` | Inspect and edit local settings (see below) |
211
+
212
+ `commoncompute --version` (or `-V`) prints the version. Every read command that renders a
213
+ table also accepts **`--json`** for plain, ANSI-free machine-readable output —
214
+ `whoami`, `balance`, `usage`, `spend`, `tier`, `workloads`, `models`, `quote`,
215
+ `submit`, `jobs list/get/wait/events`, `keys list`, and `receipts list`.
216
+
217
+ ### Shell completion
218
+
219
+ Typer ships completion for bash, zsh, fish, and PowerShell:
220
+
221
+ ```bash
222
+ commoncompute --install-completion # install for the current shell
223
+ commoncompute --show-completion # print the script to inspect or customise
224
+ ```
225
+
226
+ ### Exit codes
227
+
228
+ Scripts can branch on `commoncompute`'s exit status:
229
+
230
+ | Code | Meaning |
231
+ |------|---------|
232
+ | `0` | Success |
233
+ | `1` | API or runtime error (network, server, bad request) |
234
+ | `2` | Usage error — missing/invalid arguments, or no credentials found |
235
+ | `3` | The job reached a **failed** terminal state (`failed` / `dead_letter` / `cancelled`) — `commoncompute jobs wait` and `commoncompute submit --wait` only |
236
+
237
+ ## Migrating from OpenAI (optional)
238
+
239
+ Existing OpenAI-based pipelines (embeddings, chat, transcription) can point
240
+ at Common Compute by swapping two env vars — or:
241
+
242
+ ```python
243
+ from commoncompute.compat import openai # sets OPENAI_BASE_URL / OPENAI_API_KEY
244
+ client = openai.OpenAI()
245
+ ```
246
+
247
+ This is a migration path, not the recommended interface: the native client
248
+ returns locked prices, ETAs, and receipts that the OpenAI wire format can't
249
+ express.
250
+
251
+ ## Errors
252
+
253
+ Typed, always:
254
+
255
+ ```python
256
+ try:
257
+ client.ocr.extract("scan.pdf", max_spend_usd=0.01)
258
+ except cc.InsufficientFundsError: # quote exceeded the cap / no card
259
+ ...
260
+ except cc.PermissionDeniedError: # key lacks scope for this workload
261
+ ...
262
+ except cc.ConflictError: # idempotency-key or state conflict (409)
263
+ ...
264
+ except cc.UnsupportedFormatError: # wrong file type for the workload
265
+ ...
266
+ except cc.JobTimeoutError: # wait() expired; job still running
267
+ ...
268
+ except cc.NetworkError: # no HTTP response after retries
269
+ ...
270
+ except cc.CommonComputeError as e: # everything raises from this
271
+ print(e.request_id)
272
+ ```
273
+
274
+ The full hierarchy — `AuthenticationError`, `PermissionDeniedError`,
275
+ `NotFoundError`, `ConflictError`, `RateLimitError`, `InsufficientFundsError`,
276
+ `BadRequestError`, `ValidationError`, `UnsupportedFormatError`, `NetworkError`,
277
+ `JobTimeoutError`, `APIError` — all subclass `CommonComputeError`.
278
+
279
+ ## Configuration
280
+
281
+ Credentials and settings live under one canonical directory (override the whole
282
+ directory with `$CC_CONFIG_DIR`):
283
+
284
+ ```
285
+ ~/.config/commoncompute/credentials # the API key (single line)
286
+ ~/.config/commoncompute/config # settings: base_url, org (key = value)
287
+ ```
288
+
289
+ Two **legacy** locations are still read (never written) so older installs keep
290
+ working: `~/.commoncompute/config` and `~/.commoncompute/credentials`.
291
+
292
+ Environment variables take precedence over both files at runtime:
293
+
294
+ | Setting | Env var | Resolution order (first match wins) |
295
+ |----------|-----------------|-------------------------------------|
296
+ | API key | `CC_API_KEY` | env → `~/.config/commoncompute/credentials` → `~/.commoncompute/config` → `~/.commoncompute/credentials` |
297
+ | Base URL | `CC_BASE_URL` | env → `config` file `base_url` → `https://api.commoncompute.ai` |
298
+ | Org id | `CC_ORG` | env → `config` file `org` → default workspace |
299
+ | Config dir | `CC_CONFIG_DIR` | overrides the `~/.config/commoncompute` location above |
300
+
301
+ Inspect and edit the local config from the CLI:
302
+
303
+ ```bash
304
+ commoncompute config path # print the config directory
305
+ commoncompute config show # settings + active credential source (key masked)
306
+ commoncompute config set base_url https://api.commoncompute.ai
307
+ commoncompute config set org my-workspace
308
+ commoncompute config unset org
309
+ ```
310
+
311
+ Env vars still win at runtime — `commoncompute config set` writes the file, but
312
+ `CC_BASE_URL` / `CC_ORG` override it for a given process.
313
+
314
+ Python 3.9+. Core dependencies: `httpx`, `pydantic`. MIT license.
@@ -30,13 +30,15 @@ in the catalog — the API declines them with `workload_not_available`
30
30
  # `commoncompute.__version__` at import time for the User-Agent header,
31
31
  # and the previous ordering (`__version__` defined at the bottom) made
32
32
  # every clean install of the SDK fail with a circular ImportError.
33
- __version__ = "0.1.2"
33
+ __version__ = "0.3.0"
34
34
 
35
35
  from .client import Client, AsyncClient
36
36
  from .errors import (
37
37
  CommonComputeError,
38
38
  AuthenticationError,
39
+ PermissionDeniedError,
39
40
  NotFoundError,
41
+ ConflictError,
40
42
  RateLimitError,
41
43
  InsufficientFundsError,
42
44
  InsufficientCreditsError, # legacy alias of InsufficientFundsError
@@ -58,7 +60,9 @@ __all__ = [
58
60
  "login",
59
61
  "CommonComputeError",
60
62
  "AuthenticationError",
63
+ "PermissionDeniedError",
61
64
  "NotFoundError",
65
+ "ConflictError",
62
66
  "RateLimitError",
63
67
  "InsufficientFundsError",
64
68
  "InsufficientCreditsError",