@nodaro/sdk 2.28.0 → 2.29.0

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/dist/index.cjs CHANGED
@@ -188,36 +188,57 @@ var JobHeldError = class extends NodaroError {
188
188
  }
189
189
  jobId;
190
190
  };
191
+ var finite = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
192
+ var BARE_CODE = /^[a-z][a-z0-9_]*$/;
193
+ var nonEmpty = (v) => typeof v === "string" && v.length > 0 ? v : void 0;
194
+ function readErrorBody(status, body) {
195
+ const raw = body.error;
196
+ const envelope = raw && typeof raw === "object" ? raw : {};
197
+ const bare = typeof raw === "string" ? raw : void 0;
198
+ const bareCode = bare !== void 0 && BARE_CODE.test(bare) ? bare : void 0;
199
+ const bareSentence = bare !== void 0 && bareCode === void 0 && /\s/.test(bare.trim()) ? bare : void 0;
200
+ const code = nonEmpty(envelope.code) ?? bareCode ?? (status === 400 && raw === void 0 && Array.isArray(body.issues) ? "validation_error" : void 0);
201
+ const outside = status >= 500 ? void 0 : nonEmpty(body.message) ?? nonEmpty(body.error_description) ?? bareSentence;
202
+ const message = nonEmpty(envelope.message) ?? outside;
203
+ return { code, message, envelope };
204
+ }
191
205
  function throwFromResponse(status, body) {
192
- const code = body.error?.code ?? "internal_error";
193
- const message = body.error?.message ?? "Request failed";
194
- if (status === 401) throw new UnauthorizedError(message);
206
+ const parsed = body && typeof body === "object" ? body : {};
207
+ const read = readErrorBody(status, parsed);
208
+ const envelope = read.envelope;
209
+ const code = read.code ?? "internal_error";
210
+ const message = read.message ?? (read.code ? read.code : "Request failed");
211
+ if (status === 401) throw new UnauthorizedError(read.message);
195
212
  if (status === 409 && (code === "workflow_conflict" || code === "production_busy")) {
196
213
  throw new WorkflowConflictError(
197
- message,
198
- body.error?.currentUpdatedAt,
199
- body.error?.currentVersion,
200
- body.error?.currentRecord,
214
+ read.message,
215
+ envelope.currentUpdatedAt,
216
+ envelope.currentVersion,
217
+ envelope.currentRecord,
201
218
  code
202
219
  );
203
220
  }
204
- if (status >= 400 && status < 500 && typeof body.error?.opIndex === "number") {
205
- throw new StudioOpError(message, code, status, body.error.opIndex);
221
+ if (status >= 400 && status < 500 && typeof envelope.opIndex === "number") {
222
+ throw new StudioOpError(message, code, status, envelope.opIndex);
206
223
  }
207
- if (status === 409 && CONTINUATION_REFUSAL_CODES.has(code) && typeof body.jobId === "string") {
208
- throw new ContinuationRefusedError(message, code, body.jobId);
224
+ if (status === 409 && CONTINUATION_REFUSAL_CODES.has(code) && typeof parsed.jobId === "string") {
225
+ throw new ContinuationRefusedError(message, code, parsed.jobId);
209
226
  }
210
- if (status === 422 && code === "job_blocked") throw new JobBlockedError(message);
227
+ if (status === 422 && code === "job_blocked") throw new JobBlockedError(read.message);
211
228
  if (status === 403 && code === "insufficient_scope") {
212
- throw new ForbiddenError(message, body.error?.missingScope);
229
+ throw new ForbiddenError(read.message, envelope.missingScope);
213
230
  }
214
- if (status === 403) throw new ForbiddenError(message);
215
- if (status === 404) throw new NotFoundError(body.error?.message ?? (typeof body.message === "string" ? body.message : void 0));
216
- if (status === 429) throw new RateLimitedError(message);
231
+ if (status === 403) throw new ForbiddenError(read.message);
232
+ if (status === 404) throw new NotFoundError(read.message);
233
+ if (status === 429) throw new RateLimitedError(read.message);
217
234
  if (status === 402) {
218
- throw new InsufficientCreditsError(message, body.error?.required, body.error?.available);
235
+ throw new InsufficientCreditsError(
236
+ read.message,
237
+ finite(envelope.required) ?? finite(parsed.required),
238
+ finite(envelope.available) ?? finite(parsed.balance)
239
+ );
219
240
  }
220
- if (status === 413) throw new StorageExceededError(message, body.error?.limitBytes);
241
+ if (status === 413) throw new StorageExceededError(read.message, envelope.limitBytes);
221
242
  throw new NodaroError(message, code, status);
222
243
  }
223
244
 
@@ -507,6 +528,10 @@ var SessionsResource = class {
507
528
  };
508
529
 
509
530
  // src/resources/jobs.ts
531
+ function listParam(value) {
532
+ if (value === void 0) return void 0;
533
+ return typeof value === "string" ? value : value.join(",");
534
+ }
510
535
  var JobsResource = class {
511
536
  constructor(client) {
512
537
  this.client = client;
@@ -520,9 +545,26 @@ var JobsResource = class {
520
545
  * Your jobs, newest first (`GET /v1/jobs`), cursor-paginated. `type` and
521
546
  * `origin` filter on the job's own `input_data`, which every job carries —
522
547
  * a client app lists its runs with `{ type: "llm-structured", origin: "studio" }`.
548
+ * `status` and `type` take one value or a list; `favorited` and `rating`
549
+ * filter on your own marks. Every filter combines with AND, and each job
550
+ * carries `favorited` and `rating`.
523
551
  */
524
552
  list(params = {}) {
525
- return this.client.request("GET", "/v1/jobs", { query: { ...params } });
553
+ const { status, type, ...rest } = params;
554
+ return this.client.request("GET", "/v1/jobs", {
555
+ query: { ...rest, status: listParam(status), type: listParam(type) }
556
+ });
557
+ }
558
+ /**
559
+ * Rate one of your jobs `"good"` or `"bad"`, or clear the rating with
560
+ * `null` (`PUT /v1/jobs/:id/rating`). Only the job's owner rates it — anyone
561
+ * else's job is a `NotFoundError`. The rating reads back as `Job.rating`
562
+ * and filters {@link list}. OAuth apps need the `assets:write` scope.
563
+ */
564
+ rate(id, rating) {
565
+ return this.client.request("PUT", `/v1/jobs/${encodeURIComponent(id)}/rating`, {
566
+ body: { rating }
567
+ });
526
568
  }
527
569
  /**
528
570
  * Get the lean status of a single job (poll-loop friendly).
@@ -649,6 +691,26 @@ function extractJobId(result, label) {
649
691
  }
650
692
  throw new JobFailedError(`${label} did not return a jobId`, "");
651
693
  }
694
+ function isImageBatchRequest(type, params) {
695
+ if (type !== "generate-image") return false;
696
+ return typeof params.count === "number" && params.count > 1 || Array.isArray(params.models);
697
+ }
698
+ function batchJobIds(result) {
699
+ const ids = result?.jobIds;
700
+ return Array.isArray(ids) && ids.every((id) => typeof id === "string") ? ids : null;
701
+ }
702
+ function batchRefusal(detail) {
703
+ return new NodaroError(
704
+ `${detail}: this waits for one job \u2014 use nodes.runBatchAndWait() for a batch (count above 1, or models).`,
705
+ "batch_requires_run_batch_and_wait",
706
+ 0
707
+ );
708
+ }
709
+ function batchResultOf(result) {
710
+ const r = result;
711
+ if (!r || typeof r.batchId !== "string" || !batchJobIds(result) || !Array.isArray(r.groups)) return null;
712
+ return r;
713
+ }
652
714
  var NodesResource = class {
653
715
  constructor(client) {
654
716
  this.client = client;
@@ -681,10 +743,55 @@ var NodesResource = class {
681
743
  }
682
744
  async runAndWait(type, params = {}, opts = {}) {
683
745
  if (opts.signal?.aborted) throw new JobAbortedError();
746
+ if (isImageBatchRequest(type, params)) throw batchRefusal(`${type} with count above 1 or models makes several jobs`);
684
747
  const result = await this.run(type, params, { idempotencyKey: opts.idempotencyKey });
748
+ const ids = batchJobIds(result);
749
+ if (ids && ids.length > 1) throw batchRefusal(`${type} started ${ids.length} jobs (${ids.join(", ")}), which keep running`);
685
750
  const jobId = extractJobId(result, type);
686
751
  return this.pollJob(jobId, type, opts);
687
752
  }
753
+ /**
754
+ * Run a generate-image BATCH — `count` above 1 and/or `models` — to the end:
755
+ * submit it once ({@link run}, with your `idempotencyKey` if any), then wait
756
+ * for every job at once, each the way {@link runAndWait} waits.
757
+ *
758
+ * Resolves `{ batch, results }`: the submit's answer
759
+ * ({@link GenerateImageBatchResult} — ids, groups by model, credits) and one
760
+ * {@link BatchJobOutcome} per job, in `batch.jobIds` order — its output, or the
761
+ * typed error that ended the wait for it. One image failing never discards the
762
+ * others. Rejects only before any job exists (the submit's own errors, e.g.
763
+ * `InsufficientCreditsError` with the batch total as `required`), and on
764
+ * `signal` ({@link JobAbortedError}; `cancelOnAbort` cancels every job).
765
+ *
766
+ * A request for one image is refused before anything is sent (code
767
+ * `not_a_batch`) — wait for it with {@link runAndWait}.
768
+ */
769
+ async runBatchAndWait(type, params, opts = {}) {
770
+ if (opts.signal?.aborted) throw new JobAbortedError();
771
+ if (!isImageBatchRequest(type, params)) {
772
+ throw new NodaroError(
773
+ "runBatchAndWait waits for a batch \u2014 count above 1, or models; wait for one image with nodes.runAndWait().",
774
+ "not_a_batch",
775
+ 0
776
+ );
777
+ }
778
+ const result = await this.run(type, params, { idempotencyKey: opts.idempotencyKey });
779
+ const batch = batchResultOf(result);
780
+ if (!batch) throw new JobFailedError(`${type} did not answer with a batch`, "");
781
+ const modelOf = new Map(batch.groups.flatMap((group) => group.jobIds.map((id) => [id, group.model])));
782
+ const results = await Promise.all(
783
+ batch.jobIds.map(async (jobId) => {
784
+ const model = modelOf.get(jobId) ?? "";
785
+ try {
786
+ return { jobId, model, status: "completed", output: await this.pollJob(jobId, type, opts) };
787
+ } catch (err) {
788
+ if (err instanceof JobAbortedError || !(err instanceof NodaroError)) throw err;
789
+ return { jobId, model, status: "error", error: err };
790
+ }
791
+ })
792
+ );
793
+ return { batch, results };
794
+ }
688
795
  /**
689
796
  * Fan out N async runs of the same node `type` to completion concurrently —
690
797
  * the candidate-grid path (generate N stills/clips in parallel). Each runs
@@ -700,6 +807,9 @@ var NodesResource = class {
700
807
  */
701
808
  async runMany(type, paramsList, opts = {}) {
702
809
  if (opts.signal?.aborted) throw new JobAbortedError();
810
+ if (paramsList.some((params) => isImageBatchRequest(type, params))) {
811
+ throw batchRefusal(`a ${type} entry with count above 1 or models makes several jobs`);
812
+ }
703
813
  return Promise.all(
704
814
  paramsList.map(async (params) => {
705
815
  if (opts.signal?.aborted) throw new JobAbortedError();
@@ -1016,6 +1126,66 @@ var OAuthResource = class {
1016
1126
  }
1017
1127
  };
1018
1128
 
1129
+ // src/resources/webhooks.ts
1130
+ var WebhooksResource = class {
1131
+ constructor(client) {
1132
+ this.client = client;
1133
+ }
1134
+ client;
1135
+ /** `POST /v1/webhook-endpoints` → the endpoint and its signing secret (shown this once). */
1136
+ create(input) {
1137
+ return this.client.request("POST", "/v1/webhook-endpoints", { body: input });
1138
+ }
1139
+ /** `GET /v1/webhook-endpoints` → your endpoints, newest first. Never a secret. */
1140
+ list() {
1141
+ return this.client.request("GET", "/v1/webhook-endpoints");
1142
+ }
1143
+ /** `GET /v1/webhook-endpoints/:id`. */
1144
+ get(id) {
1145
+ return this.client.request("GET", `/v1/webhook-endpoints/${encodeURIComponent(id)}`);
1146
+ }
1147
+ /** `PATCH /v1/webhook-endpoints/:id` → change the address, events, description; switch on or off. */
1148
+ update(id, input) {
1149
+ return this.client.request("PATCH", `/v1/webhook-endpoints/${encodeURIComponent(id)}`, { body: input });
1150
+ }
1151
+ /** `DELETE /v1/webhook-endpoints/:id` → the endpoint and its delivery log. */
1152
+ delete(id) {
1153
+ return this.client.request("DELETE", `/v1/webhook-endpoints/${encodeURIComponent(id)}`);
1154
+ }
1155
+ /**
1156
+ * `POST /v1/webhook-endpoints/:id/rotate-secret` → a new signing secret,
1157
+ * shown this once. It signs every delivery from now on (retries included);
1158
+ * the one it replaces keeps signing beside it for `gracePeriodSeconds` (a
1159
+ * day by default), so update your receiver within that time.
1160
+ */
1161
+ rotateSecret(id, input = {}) {
1162
+ return this.client.request("POST", `/v1/webhook-endpoints/${encodeURIComponent(id)}/rotate-secret`, {
1163
+ body: input
1164
+ });
1165
+ }
1166
+ /** `GET /v1/webhook-endpoints/:id/deliveries` → the delivery log, newest first (kept 30 days). */
1167
+ deliveries(id, params = {}) {
1168
+ return this.client.request("GET", `/v1/webhook-endpoints/${encodeURIComponent(id)}/deliveries`, {
1169
+ query: { status: params.status, limit: params.limit, cursor: params.cursor }
1170
+ });
1171
+ }
1172
+ /** `GET /v1/webhook-endpoints/:id/deliveries/:deliveryId` → one delivery (`test()` returns its id). */
1173
+ getDelivery(id, deliveryId) {
1174
+ return this.client.request(
1175
+ "GET",
1176
+ `/v1/webhook-endpoints/${encodeURIComponent(id)}/deliveries/${encodeURIComponent(deliveryId)}`
1177
+ );
1178
+ }
1179
+ /**
1180
+ * `POST /v1/webhook-endpoints/:id/test` → queue a `ping` (sent within
1181
+ * seconds, tried once — even to an endpoint that is switched off). Follow
1182
+ * it with `deliveries()`.
1183
+ */
1184
+ test(id) {
1185
+ return this.client.request("POST", `/v1/webhook-endpoints/${encodeURIComponent(id)}/test`);
1186
+ }
1187
+ };
1188
+
1019
1189
  // src/resources/apps.ts
1020
1190
  var AppsResource = class {
1021
1191
  constructor(client) {
@@ -1089,7 +1259,7 @@ var CharactersResource = class {
1089
1259
  * List the caller's characters. By default returns active characters only;
1090
1260
  * pass `archived: true` to fetch soft-deleted rows for an "archive" view.
1091
1261
  * When `projectId` is set, only characters belonging to that project are
1092
- * returned.
1262
+ * returned; `q` keeps the ones whose name or description contains it.
1093
1263
  *
1094
1264
  * Cursor-paginated: a response carrying a non-null `nextCursor` means more
1095
1265
  * rows exist. Pass it back as `cursor` to fetch the next page, and keep going
@@ -1111,6 +1281,7 @@ var CharactersResource = class {
1111
1281
  const query = {};
1112
1282
  if (params.projectId) query.projectId = params.projectId;
1113
1283
  if (params.archived) query.archived = "true";
1284
+ if (params.q) query.q = params.q;
1114
1285
  if (params.limit !== void 0) query.limit = String(params.limit);
1115
1286
  if (params.cursor) query.cursor = params.cursor;
1116
1287
  return this.client.request("GET", "/v1/characters", { query });
@@ -1243,6 +1414,41 @@ var CharactersResource = class {
1243
1414
  `/v1/characters/${encodeURIComponent(id)}/llm-caption`
1244
1415
  );
1245
1416
  }
1417
+ /**
1418
+ * Start training a high-fidelity model of the character on its own images
1419
+ * (Nodaro Cloud only — other editions answer `NotFoundError`). Reserves the
1420
+ * training price up front and refunds it if the training fails or is
1421
+ * cancelled. Resolves at once (202); a training takes about 15 minutes —
1422
+ * follow it with {@link trainingStatus}. Once it succeeds, a workflow's
1423
+ * Generate Image node whose prompt @-mentions this one character runs on the
1424
+ * trained model.
1425
+ *
1426
+ * An OAuth app token needs the `assets:write` scope (`ForbiddenError`
1427
+ * otherwise, before anything is reserved); a session or personal API key
1428
+ * needs none. Refusals are `NodaroError`s whose `code` is a
1429
+ * {@link TrainCharacterRefusalCode}; rate-limited to 3 calls a minute.
1430
+ */
1431
+ train(id, input = {}) {
1432
+ return this.client.request("POST", `/v1/characters/${encodeURIComponent(id)}/train`, { body: input });
1433
+ }
1434
+ /**
1435
+ * The character's training record (Nodaro Cloud only). Poll it while
1436
+ * `status` is `queued` or `training` — every 10 s or so is plenty.
1437
+ */
1438
+ trainingStatus(id) {
1439
+ return this.client.request("GET", `/v1/characters/${encodeURIComponent(id)}/training`);
1440
+ }
1441
+ /**
1442
+ * Remove the character's trained model (Nodaro Cloud only): cancels a
1443
+ * training in flight and refunds its reservation, deletes the model, and
1444
+ * clears the training record (`trainingStatus` then reads `untrained`).
1445
+ * Generations go back to reference images. Cannot be undone — a new model
1446
+ * means a new, paid training. An OAuth app token needs the `assets:write`
1447
+ * scope (`ForbiddenError` otherwise), like deleting the character.
1448
+ */
1449
+ async deleteLora(id) {
1450
+ await this.client.request("DELETE", `/v1/characters/${encodeURIComponent(id)}/lora`);
1451
+ }
1246
1452
  };
1247
1453
  function buildPersonSeedPrompt(value) {
1248
1454
  return person.buildPersonHints(value).join(", ");
@@ -1254,11 +1460,13 @@ var LocationsResource = class {
1254
1460
  client;
1255
1461
  /**
1256
1462
  * List the caller's locations. By default returns active locations only;
1257
- * pass `archived: true` to fetch soft-deleted rows for an "archive" view.
1463
+ * pass `archived: true` to fetch soft-deleted rows for an "archive" view,
1464
+ * and `q` to keep the ones whose name or description contains it.
1258
1465
  */
1259
1466
  list(params = {}) {
1260
1467
  const query = {};
1261
1468
  if (params.archived) query.archived = "true";
1469
+ if (params.q) query.q = params.q;
1262
1470
  if (params.limit !== void 0) query.limit = String(params.limit);
1263
1471
  if (params.cursor) query.cursor = params.cursor;
1264
1472
  return this.client.request("GET", "/v1/locations", { query });
@@ -1432,12 +1640,14 @@ var ObjectsResource = class {
1432
1640
  /**
1433
1641
  * List the caller's objects. By default returns active objects only;
1434
1642
  * pass `archived: true` to fetch soft-deleted rows for an "archive" view.
1435
- * Optional `projectId` scopes the result to a single project.
1643
+ * Optional `projectId` scopes the result to a single project; `q` keeps the
1644
+ * objects whose name or description contains it.
1436
1645
  */
1437
1646
  list(params = {}) {
1438
1647
  const query = {};
1439
1648
  if (params.archived) query.archived = "true";
1440
1649
  if (params.projectId) query.projectId = params.projectId;
1650
+ if (params.q) query.q = params.q;
1441
1651
  if (params.limit !== void 0) query.limit = String(params.limit);
1442
1652
  if (params.cursor) query.cursor = params.cursor;
1443
1653
  return this.client.request("GET", "/v1/objects", { query });
@@ -1618,12 +1828,14 @@ var CreaturesResource = class {
1618
1828
  /**
1619
1829
  * List the caller's creatures. By default returns active creatures only;
1620
1830
  * pass `archived: true` to fetch soft-deleted rows for an "archive" view.
1621
- * Optional `projectId` scopes the result to a single project.
1831
+ * Optional `projectId` scopes the result to a single project; `q` keeps the
1832
+ * creatures whose name or description contains it.
1622
1833
  */
1623
1834
  list(params = {}) {
1624
1835
  const query = {};
1625
1836
  if (params.archived) query.archived = "true";
1626
1837
  if (params.projectId) query.projectId = params.projectId;
1838
+ if (params.q) query.q = params.q;
1627
1839
  if (params.limit !== void 0) query.limit = String(params.limit);
1628
1840
  if (params.cursor) query.cursor = params.cursor;
1629
1841
  return this.client.request("GET", "/v1/creatures", { query });
@@ -2747,6 +2959,53 @@ var CreditsResource = class {
2747
2959
  // a consumer needs it, shaped to that need.
2748
2960
  };
2749
2961
 
2962
+ // src/resources/billing.ts
2963
+ var BillingResource = class {
2964
+ constructor(client) {
2965
+ this.client = client;
2966
+ }
2967
+ client;
2968
+ /**
2969
+ * `GET /v1/billing/plans` → the plans and credit packs on sale, with their
2970
+ * prices. Public: works with no credential, the same for every caller.
2971
+ * Nodaro Cloud only — a self-hosted instance answers 404. Throws on `403
2972
+ * self_serve_disabled` (this deployment does not sell to its users) and on
2973
+ * `503` (`stripe_not_configured`, or prices briefly unavailable).
2974
+ */
2975
+ plans() {
2976
+ return this.client.request("GET", "/v1/billing/plans");
2977
+ }
2978
+ /**
2979
+ * `GET /v1/billing/subscription` → the account's latest subscription
2980
+ * (`null` when it never subscribed) and the tier its entitlements follow.
2981
+ * Match `effectiveTier` to a {@link BillingPlan} `id` to mark the current
2982
+ * plan (no match: pay-as-you-go, or a tier not on sale). Needs a signed-in
2983
+ * session, a personal API token without a workflow limit, or an app's OAuth
2984
+ * token. Nodaro Cloud only — a self-hosted instance answers 404.
2985
+ */
2986
+ subscription() {
2987
+ return this.client.request("GET", "/v1/billing/subscription");
2988
+ }
2989
+ /**
2990
+ * `GET /v1/billing/surface` → how this deployment bills: whether it meters
2991
+ * at all (`providerId !== "none"`) and in which unit costs display. Public,
2992
+ * every edition.
2993
+ */
2994
+ async surface() {
2995
+ const res = await this.client.request("GET", "/v1/billing/surface");
2996
+ return res.data;
2997
+ }
2998
+ /**
2999
+ * `GET /v1/billing/account` → the caller's plan and balance as the
3000
+ * deployment's billing provider reports them, in its display unit. `null`
3001
+ * when the provider could not answer — not a zero balance. Every edition.
3002
+ */
3003
+ async account() {
3004
+ const res = await this.client.request("GET", "/v1/billing/account");
3005
+ return res.data;
3006
+ }
3007
+ };
3008
+
2750
3009
  // src/resources/uploads.ts
2751
3010
  var UploadsResource = class {
2752
3011
  constructor(client) {
@@ -2782,14 +3041,15 @@ var LibraryResource = class {
2782
3041
  /**
2783
3042
  * `GET /v1/library` → a page of the caller's media assets (newest first) plus
2784
3043
  * a `nextCursor`. Pass the returned `nextCursor` back as `cursor` for the next
2785
- * page. Filter by `type` and a filename `search`; `owned: true` returns the
2786
- * full Storage set (uploads + generations), the default only library-saved +
2787
- * shared items.
3044
+ * page. Filter by `type` and search the filename with `q`; `owned: true`
3045
+ * returns the full Storage set (uploads + generations), the default only
3046
+ * library-saved + shared items.
2788
3047
  */
2789
3048
  list(params = {}) {
2790
3049
  const qs = new URLSearchParams();
2791
3050
  if (params.type) qs.set("type", params.type);
2792
- if (params.search) qs.set("search", params.search);
3051
+ if (params.q) qs.set("q", params.q);
3052
+ else if (params.search) qs.set("search", params.search);
2793
3053
  if (params.limit !== void 0) qs.set("limit", String(params.limit));
2794
3054
  if (params.cursor) qs.set("cursor", params.cursor);
2795
3055
  if (params.owned !== void 0) qs.set("owned", String(params.owned));
@@ -2799,6 +3059,46 @@ var LibraryResource = class {
2799
3059
  }
2800
3060
  };
2801
3061
 
3062
+ // src/resources/gallery.ts
3063
+ var GalleryResource = class {
3064
+ constructor(client) {
3065
+ this.client = client;
3066
+ }
3067
+ client;
3068
+ /**
3069
+ * `GET /v1/gallery` → a page of the public gallery, newest first (no auth
3070
+ * needed), or one person's work with `userId`.
3071
+ */
3072
+ browse(params = {}) {
3073
+ return this.client.request("GET", "/v1/gallery", {
3074
+ query: {
3075
+ type: params.type,
3076
+ userId: params.userId,
3077
+ favoritesOnly: params.favoritesOnly,
3078
+ limit: params.limit,
3079
+ cursor: params.cursor
3080
+ }
3081
+ });
3082
+ }
3083
+ /**
3084
+ * Favorite a job, or un-favorite it when it already is
3085
+ * (`POST /v1/gallery/favorite`). You can favorite your own jobs and public
3086
+ * ones; another person's private job is a `NotFoundError`. OAuth apps need
3087
+ * the `assets:write` scope.
3088
+ */
3089
+ toggleFavorite(jobId) {
3090
+ return this.client.request("POST", "/v1/gallery/favorite", { body: { jobId } });
3091
+ }
3092
+ /**
3093
+ * Your favorited job ids, newest first (`GET /v1/gallery/favorites`). OAuth
3094
+ * apps need the `assets:read` or `jobs:read` scope.
3095
+ */
3096
+ async favorites() {
3097
+ const res = await this.client.request("GET", "/v1/gallery/favorites");
3098
+ return res.data;
3099
+ }
3100
+ };
3101
+
2802
3102
  // src/resources/node-presets.ts
2803
3103
  var PresetsResource = class {
2804
3104
  constructor(client) {
@@ -2834,6 +3134,76 @@ var PresetsResource = class {
2834
3134
  `/v1/node-presets/factory?nodeType=${encodeURIComponent(nodeType)}`
2835
3135
  );
2836
3136
  }
3137
+ /** `POST /v1/node-presets` → the new preset. */
3138
+ async create(input) {
3139
+ const res = await this.client.request("POST", "/v1/node-presets", { body: input });
3140
+ return res.data;
3141
+ }
3142
+ /**
3143
+ * `PATCH /v1/node-presets/:id` → the updated preset. Pass the `updatedAt` you
3144
+ * read as `expectedUpdatedAt` to refuse (409 `conflict`) a change made
3145
+ * elsewhere since.
3146
+ */
3147
+ async update(id, patch, opts = {}) {
3148
+ const body = opts.expectedUpdatedAt ? { ...patch, expectedUpdatedAt: opts.expectedUpdatedAt } : patch;
3149
+ const res = await this.client.request("PATCH", `/v1/node-presets/${encodeURIComponent(id)}`, { body });
3150
+ return res.data;
3151
+ }
3152
+ /** `DELETE /v1/node-presets/:id` (404 when it is not yours). `expectedUpdatedAt` as on `update`. */
3153
+ async delete(id, opts = {}) {
3154
+ const res = await this.client.request("DELETE", `/v1/node-presets/${encodeURIComponent(id)}`, {
3155
+ query: { expectedUpdatedAt: opts.expectedUpdatedAt }
3156
+ });
3157
+ return res.data;
3158
+ }
3159
+ /**
3160
+ * `POST /v1/node-presets/import` — up to 500 presets in one call. A name you
3161
+ * already use gets an "(imported N)" suffix; a preset whose data is empty
3162
+ * after stripping runtime keys is skipped.
3163
+ */
3164
+ async import(presets) {
3165
+ const res = await this.client.request("POST", "/v1/node-presets/import", { body: { presets } });
3166
+ return res.data;
3167
+ }
3168
+ /** `POST /v1/node-presets/reorder` — positions (and group membership) for many presets and groups at once. */
3169
+ async reorder(input) {
3170
+ const res = await this.client.request("POST", "/v1/node-presets/reorder", { body: input });
3171
+ return res.data;
3172
+ }
3173
+ /** `GET /v1/node-presets/favorites` → your favorite preset ids for `nodeType`, newest first. */
3174
+ async favorites(nodeType) {
3175
+ const res = await this.client.request("GET", "/v1/node-presets/favorites", { query: { nodeType } });
3176
+ return res.data;
3177
+ }
3178
+ /** `POST /v1/node-presets/favorites` — idempotent. */
3179
+ async addFavorite(input) {
3180
+ const res = await this.client.request("POST", "/v1/node-presets/favorites", { body: input });
3181
+ return res.data;
3182
+ }
3183
+ /** `DELETE /v1/node-presets/favorites` — idempotent. */
3184
+ async removeFavorite(input) {
3185
+ const res = await this.client.request("DELETE", "/v1/node-presets/favorites", {
3186
+ query: { nodeType: input.nodeType, presetId: input.presetId }
3187
+ });
3188
+ return res.data;
3189
+ }
3190
+ /** `POST /v1/node-preset-groups` → the new folder or section. */
3191
+ async createGroup(input) {
3192
+ const res = await this.client.request("POST", "/v1/node-preset-groups", { body: input });
3193
+ return res.data;
3194
+ }
3195
+ /** `PATCH /v1/node-preset-groups/:id` → the renamed or moved group. */
3196
+ async updateGroup(id, patch) {
3197
+ const res = await this.client.request("PATCH", `/v1/node-preset-groups/${encodeURIComponent(id)}`, {
3198
+ body: patch
3199
+ });
3200
+ return res.data;
3201
+ }
3202
+ /** `DELETE /v1/node-preset-groups/:id` — its presets move out of the group; none are deleted. */
3203
+ async deleteGroup(id) {
3204
+ const res = await this.client.request("DELETE", `/v1/node-preset-groups/${encodeURIComponent(id)}`);
3205
+ return res.data;
3206
+ }
2837
3207
  };
2838
3208
 
2839
3209
  // src/resources/saved-posts.ts
@@ -2974,6 +3344,68 @@ var CollectionsResource = class {
2974
3344
  }
2975
3345
  };
2976
3346
 
3347
+ // src/resources/palettes.ts
3348
+ var PALETTE_DIRECTION_PREFIX = "palette:";
3349
+ function paletteDirectionId(id) {
3350
+ return `${PALETTE_DIRECTION_PREFIX}${id}`;
3351
+ }
3352
+ var PalettesResource = class {
3353
+ constructor(client) {
3354
+ this.client = client;
3355
+ }
3356
+ client;
3357
+ /** Your palettes, newest first. Page with `nextCursor`. */
3358
+ list(params = {}) {
3359
+ return this.client.request("GET", "/v1/palettes", {
3360
+ query: { limit: params.limit, cursor: params.cursor }
3361
+ });
3362
+ }
3363
+ get(id) {
3364
+ return this.client.request("GET", `/v1/palettes/${encodeURIComponent(id)}`);
3365
+ }
3366
+ create(input) {
3367
+ return this.client.request("POST", "/v1/palettes", { body: input });
3368
+ }
3369
+ update(id, input) {
3370
+ return this.client.request("PATCH", `/v1/palettes/${encodeURIComponent(id)}`, { body: input });
3371
+ }
3372
+ /** A run that still names the palette answers 404 `direction_not_found`. */
3373
+ async delete(id) {
3374
+ await this.client.request("DELETE", `/v1/palettes/${encodeURIComponent(id)}`);
3375
+ }
3376
+ };
3377
+
3378
+ // src/resources/moodboards.ts
3379
+ var MOODBOARD_DIRECTION_PREFIX = "moodboard:";
3380
+ function moodboardDirectionId(id) {
3381
+ return `${MOODBOARD_DIRECTION_PREFIX}${id}`;
3382
+ }
3383
+ var MoodboardsResource = class {
3384
+ constructor(client) {
3385
+ this.client = client;
3386
+ }
3387
+ client;
3388
+ /** Your moodboards, newest first. Page with `nextCursor`. */
3389
+ list(params = {}) {
3390
+ return this.client.request("GET", "/v1/moodboards", {
3391
+ query: { limit: params.limit, cursor: params.cursor }
3392
+ });
3393
+ }
3394
+ get(id) {
3395
+ return this.client.request("GET", `/v1/moodboards/${encodeURIComponent(id)}`);
3396
+ }
3397
+ create(input) {
3398
+ return this.client.request("POST", "/v1/moodboards", { body: input });
3399
+ }
3400
+ update(id, input) {
3401
+ return this.client.request("PATCH", `/v1/moodboards/${encodeURIComponent(id)}`, { body: input });
3402
+ }
3403
+ /** Deletes the moodboard only; the images it links to are untouched. */
3404
+ async delete(id) {
3405
+ await this.client.request("DELETE", `/v1/moodboards/${encodeURIComponent(id)}`);
3406
+ }
3407
+ };
3408
+
2977
3409
  // src/resources/competitors.ts
2978
3410
  var CompetitorsResource = class {
2979
3411
  constructor(client) {
@@ -4234,7 +4666,7 @@ var EditResource = class {
4234
4666
  return edl.remapTranscriptThroughEdl(edl$1, transcript);
4235
4667
  }
4236
4668
  };
4237
- var SDK_VERSION = "2.28.0" ;
4669
+ var SDK_VERSION = "2.29.0" ;
4238
4670
  var CLIENT_HEADER = "X-Nodaro-Client";
4239
4671
  var isBrowser = () => typeof window !== "undefined" && typeof window.document !== "undefined";
4240
4672
  var NodaroClient = class _NodaroClient {
@@ -4266,6 +4698,8 @@ var NodaroClient = class _NodaroClient {
4266
4698
  scene3d;
4267
4699
  developerApps;
4268
4700
  oauth;
4701
+ /** Outbound webhooks: endpoints that receive your job.completed / job.failed. */
4702
+ webhooks;
4269
4703
  apps;
4270
4704
  characters;
4271
4705
  locations;
@@ -4279,11 +4713,19 @@ var NodaroClient = class _NodaroClient {
4279
4713
  media;
4280
4714
  audio;
4281
4715
  credits;
4716
+ /** Plans and prices, the subscription, and how this deployment bills. */
4717
+ billing;
4282
4718
  uploads;
4283
4719
  library;
4720
+ /** The public gallery and your favorites. */
4721
+ gallery;
4284
4722
  presets;
4285
4723
  savedPosts;
4286
4724
  collections;
4725
+ /** Your colour palettes — a generate-image direction (`direction.colorLook: paletteDirectionId(id)`). */
4726
+ palettes;
4727
+ /** Your moodboards — a generate-image direction (`direction.style: moodboardDirectionId(id)`). */
4728
+ moodboards;
4287
4729
  competitors;
4288
4730
  pickerCatalogs;
4289
4731
  catalogs;
@@ -4318,6 +4760,7 @@ var NodaroClient = class _NodaroClient {
4318
4760
  this.scene3d = new Scene3DResource(this);
4319
4761
  this.developerApps = new DeveloperAppsResource(this);
4320
4762
  this.oauth = new OAuthResource(this);
4763
+ this.webhooks = new WebhooksResource(this);
4321
4764
  this.apps = new AppsResource(this);
4322
4765
  this.characters = new CharactersResource(this);
4323
4766
  this.locations = new LocationsResource(this);
@@ -4331,11 +4774,15 @@ var NodaroClient = class _NodaroClient {
4331
4774
  this.media = new MediaResource(this);
4332
4775
  this.audio = new AudioResource(this);
4333
4776
  this.credits = new CreditsResource(this);
4777
+ this.billing = new BillingResource(this);
4334
4778
  this.uploads = new UploadsResource(this);
4335
4779
  this.library = new LibraryResource(this);
4780
+ this.gallery = new GalleryResource(this);
4336
4781
  this.presets = new PresetsResource(this);
4337
4782
  this.savedPosts = new SavedPostsResource(this);
4338
4783
  this.collections = new CollectionsResource(this);
4784
+ this.palettes = new PalettesResource(this);
4785
+ this.moodboards = new MoodboardsResource(this);
4339
4786
  this.competitors = new CompetitorsResource(this);
4340
4787
  this.pickerCatalogs = new PickerCatalogsResource(this);
4341
4788
  this.catalogs = new CatalogsResource(this);
@@ -4460,6 +4907,32 @@ var NodaroClient = class _NodaroClient {
4460
4907
  const res = await this.request("GET", "/v1/me");
4461
4908
  return res.data;
4462
4909
  }
4910
+ /**
4911
+ * `GET /v1/me/connected-apps` → every app with access to the signed-in
4912
+ * user's account (see {@link ConnectedApp}), newest grant first.
4913
+ *
4914
+ * **A signed-in browser session only.** The route answers a Supabase
4915
+ * session token (`supabaseAuth(…)`, `createSharedSupabaseClient`, or a
4916
+ * `StaticTokenAuth` holding one) and refuses every other credential with
4917
+ * `UnauthorizedError` ("Sign in required"): an app token or an API key must
4918
+ * not list the grants it lives under. Build your "Connected apps" settings
4919
+ * screen on this; a server-side integration cannot call it.
4920
+ */
4921
+ async connectedApps() {
4922
+ const res = await this.request("GET", "/v1/me/connected-apps");
4923
+ return res.apps;
4924
+ }
4925
+ /**
4926
+ * `POST /v1/me/connected-apps/:authorizationId/revoke` — ends an app's
4927
+ * access: the grant and every token issued under it, at once (the app's next
4928
+ * call is a 401). Resolves when done.
4929
+ *
4930
+ * A signed-in browser session only, like {@link connectedApps}. Throws
4931
+ * `NotFoundError` when the grant is not the user's or is already revoked.
4932
+ */
4933
+ async revokeConnectedApp(authorizationId) {
4934
+ await this.request("POST", `/v1/me/connected-apps/${encodeURIComponent(authorizationId)}/revoke`);
4935
+ }
4463
4936
  buildUrl(path2, query) {
4464
4937
  const base = this.baseUrl || (typeof window !== "undefined" ? window.location.origin : "http://placeholder");
4465
4938
  const url = new URL(path2, base);
@@ -4506,6 +4979,96 @@ function supabaseAuth(supabase) {
4506
4979
  };
4507
4980
  }
4508
4981
 
4982
+ // src/webhook-signature.ts
4983
+ var WebhookSignatureError = class extends Error {
4984
+ code;
4985
+ constructor(code, message) {
4986
+ super(message);
4987
+ this.name = "WebhookSignatureError";
4988
+ this.code = code;
4989
+ }
4990
+ };
4991
+ var DEFAULT_TOLERANCE_SECONDS = 300;
4992
+ var HEX_SIGNATURE = /^[0-9a-f]{64}$/;
4993
+ function toBytes(payload) {
4994
+ if (typeof payload === "string") return new TextEncoder().encode(payload);
4995
+ if (payload instanceof Uint8Array) return payload;
4996
+ return new Uint8Array(payload);
4997
+ }
4998
+ function hexToBytes(hex) {
4999
+ const out = new Uint8Array(hex.length / 2);
5000
+ for (let i = 0; i < out.length; i++) out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
5001
+ return out;
5002
+ }
5003
+ function constantTimeEqual(a, b) {
5004
+ if (a.length !== b.length) return false;
5005
+ let diff = 0;
5006
+ for (let i = 0; i < a.length; i++) diff |= a[i] ^ b[i];
5007
+ return diff === 0;
5008
+ }
5009
+ function parseHeader(raw) {
5010
+ let timestamp = null;
5011
+ const signatures = [];
5012
+ for (const part of raw.split(",")) {
5013
+ const eq = part.indexOf("=");
5014
+ if (eq <= 0) continue;
5015
+ const key = part.slice(0, eq).trim();
5016
+ const value = part.slice(eq + 1).trim();
5017
+ if (key === "t") {
5018
+ if (!/^\d{1,15}$/.test(value)) {
5019
+ throw new WebhookSignatureError("malformed_header", "Nodaro-Signature has no valid timestamp (t=)");
5020
+ }
5021
+ timestamp = Number(value);
5022
+ } else if (key === "v1") {
5023
+ signatures.push(value.toLowerCase());
5024
+ }
5025
+ }
5026
+ if (timestamp === null) throw new WebhookSignatureError("malformed_header", "Nodaro-Signature has no timestamp (t=)");
5027
+ if (signatures.length === 0) throw new WebhookSignatureError("malformed_header", "Nodaro-Signature has no v1 signature");
5028
+ return { timestamp, signatures };
5029
+ }
5030
+ async function verifyWebhookSignature(options) {
5031
+ const { payload, secret } = options;
5032
+ const tolerance = options.toleranceSeconds ?? DEFAULT_TOLERANCE_SECONDS;
5033
+ if (typeof tolerance !== "number" || !Number.isFinite(tolerance) || tolerance < 0) {
5034
+ throw new RangeError("toleranceSeconds must be a finite number of seconds, 0 or more");
5035
+ }
5036
+ if (!secret) throw new WebhookSignatureError("missing_secret", "No signing secret was given");
5037
+ const header = typeof options.header === "string" ? options.header : options.header?.[0];
5038
+ if (!header || !header.trim()) throw new WebhookSignatureError("missing_header", "The request has no Nodaro-Signature header");
5039
+ const { timestamp, signatures } = parseHeader(header);
5040
+ const subtle = globalThis.crypto?.subtle;
5041
+ if (!subtle) throw new WebhookSignatureError("crypto_unavailable", "Web Crypto (crypto.subtle) is not available in this runtime");
5042
+ const body = toBytes(payload);
5043
+ const prefix = new TextEncoder().encode(`${timestamp}.`);
5044
+ const message = new Uint8Array(prefix.length + body.length);
5045
+ message.set(prefix, 0);
5046
+ message.set(body, prefix.length);
5047
+ const key = await subtle.importKey("raw", new TextEncoder().encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
5048
+ const expected = new Uint8Array(await subtle.sign("HMAC", key, message));
5049
+ let matched = false;
5050
+ for (const candidate of signatures) {
5051
+ const wellFormed = HEX_SIGNATURE.test(candidate);
5052
+ const bytes = wellFormed ? hexToBytes(candidate) : new Uint8Array(expected.length);
5053
+ if (constantTimeEqual(bytes, expected) && wellFormed) matched = true;
5054
+ }
5055
+ if (!matched) throw new WebhookSignatureError("signature_mismatch", "The Nodaro-Signature does not match the body");
5056
+ if (tolerance > 0) {
5057
+ const now = Math.floor(Date.now() / 1e3);
5058
+ if (Math.abs(now - timestamp) > tolerance) {
5059
+ throw new WebhookSignatureError(
5060
+ "timestamp_outside_tolerance",
5061
+ `The signature's timestamp is more than ${tolerance} seconds from now`
5062
+ );
5063
+ }
5064
+ }
5065
+ try {
5066
+ return JSON.parse(new TextDecoder().decode(body));
5067
+ } catch {
5068
+ throw new WebhookSignatureError("invalid_payload", "The signed body is not JSON");
5069
+ }
5070
+ }
5071
+
4509
5072
  Object.defineProperty(exports, "OUTPUT_BEARING_NODE_STATUSES", {
4510
5073
  enumerable: true,
4511
5074
  get: function () { return workflow.OUTPUT_BEARING_NODE_STATUSES; }
@@ -4608,6 +5171,7 @@ Object.defineProperty(exports, "RECAST_SEGMENT_PACKS", {
4608
5171
  });
4609
5172
  exports.AppsResource = AppsResource;
4610
5173
  exports.AudioResource = AudioResource;
5174
+ exports.BillingResource = BillingResource;
4611
5175
  exports.CREATURE_ASSET_TYPES = CREATURE_ASSET_TYPES;
4612
5176
  exports.CallbackAuth = CallbackAuth;
4613
5177
  exports.CatalogsResource = CatalogsResource;
@@ -4623,6 +5187,7 @@ exports.DeveloperAppsResource = DeveloperAppsResource;
4623
5187
  exports.EditResource = EditResource;
4624
5188
  exports.ExecutionsResource = ExecutionsResource;
4625
5189
  exports.ForbiddenError = ForbiddenError;
5190
+ exports.GalleryResource = GalleryResource;
4626
5191
  exports.InsufficientCreditsError = InsufficientCreditsError;
4627
5192
  exports.JobAbortedError = JobAbortedError;
4628
5193
  exports.JobBlockedError = JobBlockedError;
@@ -4633,8 +5198,10 @@ exports.JobsResource = JobsResource;
4633
5198
  exports.LibraryResource = LibraryResource;
4634
5199
  exports.LlmResource = LlmResource;
4635
5200
  exports.LocationsResource = LocationsResource;
5201
+ exports.MOODBOARD_DIRECTION_PREFIX = MOODBOARD_DIRECTION_PREFIX;
4636
5202
  exports.MediaResource = MediaResource;
4637
5203
  exports.ModelsResource = ModelsResource;
5204
+ exports.MoodboardsResource = MoodboardsResource;
4638
5205
  exports.NodaroClient = NodaroClient;
4639
5206
  exports.NodaroError = NodaroError;
4640
5207
  exports.NodesResource = NodesResource;
@@ -4642,6 +5209,8 @@ exports.NotFoundError = NotFoundError;
4642
5209
  exports.OAuthResource = OAuthResource;
4643
5210
  exports.ObjectsResource = ObjectsResource;
4644
5211
  exports.OrganizationsResource = OrganizationsResource;
5212
+ exports.PALETTE_DIRECTION_PREFIX = PALETTE_DIRECTION_PREFIX;
5213
+ exports.PalettesResource = PalettesResource;
4645
5214
  exports.PickerCatalogsResource = PickerCatalogsResource;
4646
5215
  exports.PipelinesResource = PipelinesResource;
4647
5216
  exports.PresetsResource = PresetsResource;
@@ -4668,13 +5237,18 @@ exports.UnauthorizedError = UnauthorizedError;
4668
5237
  exports.UploadsResource = UploadsResource;
4669
5238
  exports.VideoProResource = VideoProResource;
4670
5239
  exports.VoicesResource = VoicesResource;
5240
+ exports.WebhookSignatureError = WebhookSignatureError;
5241
+ exports.WebhooksResource = WebhooksResource;
4671
5242
  exports.WorkflowConflictError = WorkflowConflictError;
4672
5243
  exports.WorkflowsResource = WorkflowsResource;
4673
5244
  exports.WorkspacesResource = WorkspacesResource;
4674
5245
  exports.buildPersonSeedPrompt = buildPersonSeedPrompt;
4675
5246
  exports.createClient = createClient;
4676
5247
  exports.isStudioGenerateEstimate = isStudioGenerateEstimate;
5248
+ exports.moodboardDirectionId = moodboardDirectionId;
5249
+ exports.paletteDirectionId = paletteDirectionId;
4677
5250
  exports.supabaseAuth = supabaseAuth;
4678
5251
  exports.throwFromResponse = throwFromResponse;
5252
+ exports.verifyWebhookSignature = verifyWebhookSignature;
4679
5253
  //# sourceMappingURL=index.cjs.map
4680
5254
  //# sourceMappingURL=index.cjs.map