craterview 0.1.0 → 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.
Files changed (3) hide show
  1. package/README.md +40 -15
  2. package/index.ts +32 -21
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -7,9 +7,15 @@ restoration API.
7
7
  npm install craterview
8
8
  ```
9
9
 
10
- Node 18 or newer, or any modern browser. **Zero runtime dependencies** — `fetch`, `Blob`
11
- and `crypto.getRandomValues` are standard in both, so the client installs without dragging
12
- a transitive tree behind it.
10
+ **The package ships TypeScript source.** `index.ts` is what npm installs — there is no build
11
+ step and no compiled JavaScript in the tarball — so whatever compiles your own TypeScript
12
+ compiles this too: any bundler, `tsx` or `ts-node`, Bun, Deno, or Node 22.6 and newer with
13
+ type stripping (`--experimental-strip-types`, which recent releases enable by default). A
14
+ plain JavaScript project invoking `node` directly cannot import it.
15
+
16
+ The code itself needs Node 18 or newer, or any modern browser. **Zero runtime
17
+ dependencies** — `fetch`, `Blob` and `crypto.getRandomValues` are standard in both, so the
18
+ client installs without dragging a transitive tree behind it.
13
19
 
14
20
  ## Quickstart
15
21
 
@@ -17,7 +23,7 @@ a transitive tree behind it.
17
23
  import { CraterView } from "craterview";
18
24
 
19
25
  const cv = new CraterView({ apiKey: "cv_..." });
20
- const job = await cv.run(file, { style: "photo" });
26
+ const job = await cv.run(file, { scale: 4 });
21
27
  const blob = await job.blob();
22
28
  ```
23
29
 
@@ -41,10 +47,10 @@ browser a session instead.
41
47
 
42
48
  ```ts
43
49
  const job = await cv.run(image, { // Blob | ArrayBuffer | Uint8Array
44
- model: "cv-restore-v1", // default
50
+ model: "cv-enhance-v3", // default
45
51
  wait: 30, // seconds to hold the connection open
46
52
  timeoutMs: 600_000, // total before giving up
47
- style: "photo", // model parameters pass straight through
53
+ scale: 4, // model parameters pass straight through
48
54
  });
49
55
  ```
50
56
 
@@ -60,7 +66,7 @@ Useful when you want to hold the key, submit later, or fan out.
60
66
 
61
67
  ```ts
62
68
  const inputKey = await cv.upload(file); // → "inputs/..."
63
- let job = await cv.submit(inputKey, { style: "photo" });
69
+ let job = await cv.submit(inputKey, { scale: 4 });
64
70
  job = await cv.waitFor(job, 600_000);
65
71
  const blob = await job.blob();
66
72
  ```
@@ -84,7 +90,7 @@ const key = newIdempotencyKey(); // once, before the first attempt
84
90
  let job;
85
91
  for (let attempt = 0; attempt < 3; attempt++) {
86
92
  try {
87
- job = await cv.submit(inputKey, { idempotencyKey: key, style: "photo" });
93
+ job = await cv.submit(inputKey, { idempotencyKey: key, scale: 4 });
88
94
  break;
89
95
  } catch (e) {
90
96
  if (e instanceof CraterViewError) throw e; // the server answered; do not retry
@@ -126,7 +132,7 @@ Every field the API publishes on a job is exposed here.
126
132
  | `errorCode` | The same fact, as a stable identifier. Branch on this, show the other |
127
133
  | `credits` | **What you were billed** |
128
134
  | `etaSeconds` | The estimate made at submit. Absent once the job has settled |
129
- | `community` | True when the job is on the community queue: served after priority work, taking a small share of it |
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 |
130
136
  | `outputUrl`, `downloadUrl` | The result, presigned. One to display, one to save |
131
137
  | `thumbUrl` | A small JPEG of the result, for listings. Null when none was drawn |
132
138
  | `inputUrl` | The file you sent. Null once it has expired — inputs go after a day |
@@ -149,9 +155,11 @@ accepts exactly that length and nothing else. You do not pass it — it is read
149
155
  in hand, which is what makes it impossible to get wrong.
150
156
 
151
157
  **Running out of credit does not stop you.** A job submitted against a balance of zero is
152
- accepted, charged and run — it simply waits in the community queue for capacity that paid
153
- work is not using, and comes back with `community` set. There is no payment error to
154
- handle: credit buys a place at the front of the queue rather than the right to submit.
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.
155
163
  `etaSeconds` covers the whole wait, queue time included, so a community job simply reports
156
164
  a longer one.
157
165
 
@@ -198,7 +206,7 @@ Every copy of a delivery states the same thing, so the first one you accept is t
198
206
  answer — later copies of an `id` you have already handled can be dropped rather than
199
207
  reconciled.
200
208
 
201
- **Pass the raw body.** Most frameworks parse JSON for you, and re-serialising it changes the
209
+ **Pass the raw body.** Most frameworks parse JSON for you, and re-serializing it changes the
202
210
  bytes the signature was computed over — hence `express.raw` above.
203
211
 
204
212
  `verifyWebhook` is async because it uses WebCrypto, which is what lets it run unchanged in
@@ -248,6 +256,10 @@ for await (const job of cv.jobs({ limit: 50, status: "succeeded" })) {
248
256
  }
249
257
  ```
250
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
+
251
263
  ## Configuration
252
264
 
253
265
  ```ts
@@ -257,7 +269,20 @@ new CraterView({
257
269
  });
258
270
  ```
259
271
 
260
- `baseUrl` is what you change to point at a local gateway.
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
+ ```
261
286
 
262
287
  ## Versioning
263
288
 
@@ -266,7 +291,7 @@ or `ModelInfo`, or changes the type of one, gets a minor bump while this is `0.x
266
291
  bump after `1.0.0`; anything purely additive gets a patch. Pin what you depend on.
267
292
 
268
293
  The API this wraps adds fields to its responses without warning, so treat an unfamiliar key in
269
- `result` or a `status` you do not recognise as something to ignore rather than to fail on.
294
+ `result` or a `status` you do not recognize as something to ignore rather than to fail on.
270
295
 
271
296
  ## License
272
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.0";
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 a
25
- // longer wait. Keep in step with MAX_WAIT_SECONDS in the gateway.
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-serialising it changes the bytes the signature was computed over. In Express that means
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 catalogue, exactly as it arrives. Snake case is the wire's, as
224
- * with `JobData` — there is no wrapper class here because there is no behaviour to add.
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 small share of it. False once the account holds credit.
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 was submitted against a balance of zero and is on the queue
404
- * served after priority work, which takes a small share of it rather than only what is left.
405
- * Nothing is refused for want of credit — credit buys a place at the front of the queue, not
406
- * the right to submit — so an empty balance means a longer wait and never an error.
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 gateway too old to send the field is not running 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 catalogue.
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-restore-v1",
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({ limit: String(options.limit ?? 50) });
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "craterview",
3
- "version": "0.1.0",
3
+ "version": "0.1.5",
4
4
  "dependencies": {},
5
5
  "description": "TypeScript client for the CraterView image enhancement API",
6
6
  "type": "module",