craterview 0.1.3 → 0.1.5
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.
- package/README.md +31 -12
- package/index.ts +32 -21
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ client installs without dragging a transitive tree behind it.
|
|
|
23
23
|
import { CraterView } from "craterview";
|
|
24
24
|
|
|
25
25
|
const cv = new CraterView({ apiKey: "cv_..." });
|
|
26
|
-
const job = await cv.run(file, {
|
|
26
|
+
const job = await cv.run(file, { scale: 4 });
|
|
27
27
|
const blob = await job.blob();
|
|
28
28
|
```
|
|
29
29
|
|
|
@@ -47,10 +47,10 @@ browser a session instead.
|
|
|
47
47
|
|
|
48
48
|
```ts
|
|
49
49
|
const job = await cv.run(image, { // Blob | ArrayBuffer | Uint8Array
|
|
50
|
-
model: "cv-
|
|
50
|
+
model: "cv-enhance-v3", // default
|
|
51
51
|
wait: 30, // seconds to hold the connection open
|
|
52
52
|
timeoutMs: 600_000, // total before giving up
|
|
53
|
-
|
|
53
|
+
scale: 4, // model parameters pass straight through
|
|
54
54
|
});
|
|
55
55
|
```
|
|
56
56
|
|
|
@@ -66,7 +66,7 @@ Useful when you want to hold the key, submit later, or fan out.
|
|
|
66
66
|
|
|
67
67
|
```ts
|
|
68
68
|
const inputKey = await cv.upload(file); // → "inputs/..."
|
|
69
|
-
let job = await cv.submit(inputKey, {
|
|
69
|
+
let job = await cv.submit(inputKey, { scale: 4 });
|
|
70
70
|
job = await cv.waitFor(job, 600_000);
|
|
71
71
|
const blob = await job.blob();
|
|
72
72
|
```
|
|
@@ -90,7 +90,7 @@ const key = newIdempotencyKey(); // once, before the first attempt
|
|
|
90
90
|
let job;
|
|
91
91
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
92
92
|
try {
|
|
93
|
-
job = await cv.submit(inputKey, { idempotencyKey: key,
|
|
93
|
+
job = await cv.submit(inputKey, { idempotencyKey: key, scale: 4 });
|
|
94
94
|
break;
|
|
95
95
|
} catch (e) {
|
|
96
96
|
if (e instanceof CraterViewError) throw e; // the server answered; do not retry
|
|
@@ -132,7 +132,7 @@ Every field the API publishes on a job is exposed here.
|
|
|
132
132
|
| `errorCode` | The same fact, as a stable identifier. Branch on this, show the other |
|
|
133
133
|
| `credits` | **What you were billed** |
|
|
134
134
|
| `etaSeconds` | The estimate made at submit. Absent once the job has settled |
|
|
135
|
-
| `community` | True when the job is on the community queue: served after priority work, taking a
|
|
135
|
+
| `community` | True when the job is on the community queue: served after priority work, always taking a share of it, so it never stalls behind paid work |
|
|
136
136
|
| `outputUrl`, `downloadUrl` | The result, presigned. One to display, one to save |
|
|
137
137
|
| `thumbUrl` | A small JPEG of the result, for listings. Null when none was drawn |
|
|
138
138
|
| `inputUrl` | The file you sent. Null once it has expired — inputs go after a day |
|
|
@@ -155,9 +155,11 @@ accepts exactly that length and nothing else. You do not pass it — it is read
|
|
|
155
155
|
in hand, which is what makes it impossible to get wrong.
|
|
156
156
|
|
|
157
157
|
**Running out of credit does not stop you.** A job submitted against a balance of zero is
|
|
158
|
-
accepted, charged and run — it simply waits in the community queue
|
|
159
|
-
work
|
|
160
|
-
|
|
158
|
+
accepted, charged and run — it simply waits in the community queue, which is served after
|
|
159
|
+
paid work and always takes a share of it, so it never stalls behind paid work. It comes back
|
|
160
|
+
with `community` set. There is no payment error to handle:
|
|
161
|
+
paying — with credit, or with a subscription — buys a place at the front of the queue rather
|
|
162
|
+
than the right to submit.
|
|
161
163
|
`etaSeconds` covers the whole wait, queue time included, so a community job simply reports
|
|
162
164
|
a longer one.
|
|
163
165
|
|
|
@@ -204,7 +206,7 @@ Every copy of a delivery states the same thing, so the first one you accept is t
|
|
|
204
206
|
answer — later copies of an `id` you have already handled can be dropped rather than
|
|
205
207
|
reconciled.
|
|
206
208
|
|
|
207
|
-
**Pass the raw body.** Most frameworks parse JSON for you, and re-
|
|
209
|
+
**Pass the raw body.** Most frameworks parse JSON for you, and re-serializing it changes the
|
|
208
210
|
bytes the signature was computed over — hence `express.raw` above.
|
|
209
211
|
|
|
210
212
|
`verifyWebhook` is async because it uses WebCrypto, which is what lets it run unchanged in
|
|
@@ -254,6 +256,10 @@ for await (const job of cv.jobs({ limit: 50, status: "succeeded" })) {
|
|
|
254
256
|
}
|
|
255
257
|
```
|
|
256
258
|
|
|
259
|
+
`limit` is how many jobs one request fetches, not how many you get: the loop keeps going
|
|
260
|
+
until your history runs out. 200 is the largest page the API serves, and a bigger number is
|
|
261
|
+
fetched as 200.
|
|
262
|
+
|
|
257
263
|
## Configuration
|
|
258
264
|
|
|
259
265
|
```ts
|
|
@@ -263,7 +269,20 @@ new CraterView({
|
|
|
263
269
|
});
|
|
264
270
|
```
|
|
265
271
|
|
|
266
|
-
`baseUrl` is what you change to point at a local
|
|
272
|
+
`baseUrl` is what you change to point at a local server.
|
|
273
|
+
|
|
274
|
+
## From Claude Code
|
|
275
|
+
|
|
276
|
+
This repository is also a Claude Code marketplace. Two plugins: `craterview` connects
|
|
277
|
+
CraterView's hosted tools so the assistant enhances images in the conversation, and
|
|
278
|
+
`craterview-api` teaches an agent to call the API from code with this client. See
|
|
279
|
+
[`plugins/`](plugins/) for what each does and how it finds a key.
|
|
280
|
+
|
|
281
|
+
```
|
|
282
|
+
/plugin marketplace add craterviewai/craterview-ts
|
|
283
|
+
/plugin install craterview@craterviewai
|
|
284
|
+
/plugin install craterview-api@craterviewai
|
|
285
|
+
```
|
|
267
286
|
|
|
268
287
|
## Versioning
|
|
269
288
|
|
|
@@ -272,7 +291,7 @@ or `ModelInfo`, or changes the type of one, gets a minor bump while this is `0.x
|
|
|
272
291
|
bump after `1.0.0`; anything purely additive gets a patch. Pin what you depend on.
|
|
273
292
|
|
|
274
293
|
The API this wraps adds fields to its responses without warning, so treat an unfamiliar key in
|
|
275
|
-
`result` or a `status` you do not
|
|
294
|
+
`result` or a `status` you do not recognize as something to ignore rather than to fail on.
|
|
276
295
|
|
|
277
296
|
## License
|
|
278
297
|
|
package/index.ts
CHANGED
|
@@ -9,21 +9,19 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Zero dependencies: fetch and Blob are standard in Node 18+ and every browser, so the
|
|
11
11
|
* client stays installable anywhere without dragging a transitive tree behind it.
|
|
12
|
-
*
|
|
13
|
-
* **This file is published.** It goes to GitHub and npm, and `main` points at the source, so
|
|
14
|
-
* every comment here ships and a doc-comment on an exported member appears in a consumer's
|
|
15
|
-
* editor. Write for someone who can see this package and nothing else: what the client does
|
|
16
|
-
* and what a caller has to know, never how the service behind it is built.
|
|
17
12
|
*/
|
|
18
13
|
|
|
19
14
|
// Mirrored from package.json, which is the number a release bumps. It cannot be imported
|
|
20
15
|
// from there — this ships as TypeScript, so the import would have to resolve in the
|
|
21
16
|
// consumer's toolchain — so test/version.test.ts asserts the two agree.
|
|
22
|
-
export const VERSION = "0.1.
|
|
17
|
+
export const VERSION = "0.1.5";
|
|
23
18
|
const DEFAULT_BASE_URL = "https://api.craterview.ai";
|
|
24
|
-
// The server rejects a longer wait outright, so asking for one costs a 422 rather than
|
|
25
|
-
//
|
|
19
|
+
// The server rejects a longer wait outright, so asking for one costs a 422 rather than the
|
|
20
|
+
// wait you asked for. `run()` clamps to this rather than letting that happen.
|
|
26
21
|
const MAX_SERVER_WAIT = 30;
|
|
22
|
+
// The largest page the job history serves. Same reasoning: a bigger `limit` is refused, so
|
|
23
|
+
// `jobs()` clamps rather than spending a request to be told no.
|
|
24
|
+
const MAX_PAGE = 200;
|
|
27
25
|
|
|
28
26
|
/**
|
|
29
27
|
* A random key for safely repeating a submission.
|
|
@@ -132,7 +130,7 @@ export class InvalidSignature extends CraterViewError {}
|
|
|
132
130
|
*
|
|
133
131
|
* Throws {@link InvalidSignature} if it does not check out. **Verify before you parse**, and
|
|
134
132
|
* pass the raw body exactly as received — most frameworks parse JSON for you, and
|
|
135
|
-
* re-
|
|
133
|
+
* re-serializing it changes the bytes the signature was computed over. In Express that means
|
|
136
134
|
* `express.raw({ type: "application/json" })` on this route.
|
|
137
135
|
*
|
|
138
136
|
* Async because it uses WebCrypto, which is what makes this work unchanged in Node and in a
|
|
@@ -220,8 +218,8 @@ function timingSafeEqual(a: string, b: string): boolean {
|
|
|
220
218
|
}
|
|
221
219
|
|
|
222
220
|
/**
|
|
223
|
-
* One entry from the model
|
|
224
|
-
* with `JobData` — there is no wrapper class here because there is no
|
|
221
|
+
* One entry from the model catalog, exactly as it arrives. Snake case is the wire's, as
|
|
222
|
+
* with `JobData` — there is no wrapper class here because there is no behavior to add.
|
|
225
223
|
*/
|
|
226
224
|
export interface ModelInfo {
|
|
227
225
|
/** The public name, and what you pass as `model` when submitting. */
|
|
@@ -248,6 +246,11 @@ export interface ModelInfo {
|
|
|
248
246
|
* look identical in `accepts` alone.
|
|
249
247
|
*/
|
|
250
248
|
video_coming_soon?: boolean;
|
|
249
|
+
/**
|
|
250
|
+
* How long this model is for. `fixed` is a lasting part of the service; `comet` is a
|
|
251
|
+
* featured model that may be withdrawn at short notice, so build on it knowing that.
|
|
252
|
+
*/
|
|
253
|
+
tenure?: "fixed" | "comet";
|
|
251
254
|
max_input_bytes: number;
|
|
252
255
|
/**
|
|
253
256
|
* The ceilings **your key** is held to, not the model's widest. Community work is bounded
|
|
@@ -279,7 +282,8 @@ export interface ModelInfo {
|
|
|
279
282
|
queue_depth: number;
|
|
280
283
|
/**
|
|
281
284
|
* Whether your key's work goes to the community queue, which is served after priority
|
|
282
|
-
* work and takes a
|
|
285
|
+
* work and always takes a share of it, so it never stalls behind paid work. False once
|
|
286
|
+
* the account holds credit.
|
|
283
287
|
*
|
|
284
288
|
* The same field, meaning the same thing, as `Job.community` — these are the two places
|
|
285
289
|
* the API describes a wait, and they describe it the same way.
|
|
@@ -287,7 +291,6 @@ export interface ModelInfo {
|
|
|
287
291
|
community: boolean;
|
|
288
292
|
/** Of `params_schema`, the ones that apply to still images only. */
|
|
289
293
|
image_only_params: string[];
|
|
290
|
-
/** A representative job, for sizing a progress indicator before any eta arrives. */
|
|
291
294
|
}
|
|
292
295
|
|
|
293
296
|
/** One key on the account. Never the key itself — only the prefix, which identifies it. */
|
|
@@ -400,10 +403,12 @@ export interface JobData {
|
|
|
400
403
|
* counting time spent waiting for a GPU as well as time spent on one. An estimate and never
|
|
401
404
|
* a promise — read it as guidance, not a deadline. Absent once a job has settled.
|
|
402
405
|
*
|
|
403
|
-
* `community` says the job
|
|
404
|
-
*
|
|
405
|
-
*
|
|
406
|
-
*
|
|
406
|
+
* `community` says the job is on the queue served after priority work, which always takes
|
|
407
|
+
* a share of it rather than only what is left over — so it waits longer at busy times and
|
|
408
|
+
* never stalls behind paid work. That is where an account goes when it has not
|
|
409
|
+
* paid for priority — by holding credit or by subscribing. Nothing is refused for want of
|
|
410
|
+
* either: paying buys a place at the front of the queue, not the right to submit, so an
|
|
411
|
+
* account that has not paid means a longer wait and never an error.
|
|
407
412
|
*/
|
|
408
413
|
export class Job {
|
|
409
414
|
constructor(private readonly data: JobData) {}
|
|
@@ -418,7 +423,7 @@ export class Job {
|
|
|
418
423
|
get etaSeconds() { return this.data.eta_seconds ?? null; }
|
|
419
424
|
/** Your own name for this job, or null if you did not send one. */
|
|
420
425
|
get customId() { return this.data.custom_id ?? null; }
|
|
421
|
-
// Defaulted rather than nulled: a
|
|
426
|
+
// Defaulted rather than nulled: a server too old to send the field is not running a
|
|
422
427
|
// community queue at all, so its jobs are paid work.
|
|
423
428
|
get community() { return this.data.community ?? false; }
|
|
424
429
|
// Nulled rather than defaulted, unlike `community` above: absent means the image was not
|
|
@@ -557,7 +562,7 @@ export class CraterView {
|
|
|
557
562
|
* at the same moment can get different numbers, and buying credit changes yours.
|
|
558
563
|
*
|
|
559
564
|
* A model that is available is not always listed — a model in trial, or being retired,
|
|
560
|
-
* stays usable by name while absent from this
|
|
565
|
+
* stays usable by name while absent from this catalog.
|
|
561
566
|
*/
|
|
562
567
|
async models(): Promise<ModelInfo[]> {
|
|
563
568
|
return await this.request("GET", "/v1/models") as ModelInfo[];
|
|
@@ -594,7 +599,7 @@ export class CraterView {
|
|
|
594
599
|
* job and is billed for both.
|
|
595
600
|
*/
|
|
596
601
|
async submit(inputKey: string, options: SubmitOptions = {}): Promise<Job> {
|
|
597
|
-
const { wait = 0, idempotencyKey, webhookUrl, customId, model = "cv-
|
|
602
|
+
const { wait = 0, idempotencyKey, webhookUrl, customId, model = "cv-enhance-v3",
|
|
598
603
|
...params } = options;
|
|
599
604
|
// Annotated, not inferred: without this the two branches unify to a type whose key
|
|
600
605
|
// may be `undefined`, which is not assignable to Record<string, string>.
|
|
@@ -618,11 +623,17 @@ export class CraterView {
|
|
|
618
623
|
*
|
|
619
624
|
* Scoped to the account rather than to this key, so a key sees every job the account has
|
|
620
625
|
* run and not only the ones it submitted itself.
|
|
626
|
+
*
|
|
627
|
+
* `limit` is how many jobs one request fetches, not how many you get: iteration continues
|
|
628
|
+
* until the history runs out. 200 is the largest page the API serves, and a bigger number
|
|
629
|
+
* is fetched as 200 rather than sent and refused.
|
|
621
630
|
*/
|
|
622
631
|
async *jobs(options: { limit?: number; status?: JobStatus } = {}): AsyncGenerator<Job> {
|
|
623
632
|
let before: string | undefined;
|
|
624
633
|
for (;;) {
|
|
625
|
-
const params = new URLSearchParams({
|
|
634
|
+
const params = new URLSearchParams({
|
|
635
|
+
limit: String(Math.min(options.limit ?? 50, MAX_PAGE)),
|
|
636
|
+
});
|
|
626
637
|
if (options.status) params.set("status", options.status);
|
|
627
638
|
if (before) params.set("before", before);
|
|
628
639
|
|