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