@nodaro/sdk 1.1.1

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 ADDED
@@ -0,0 +1,2085 @@
1
+ 'use strict';
2
+
3
+ var prompts = require('@nodaro/prompts');
4
+ var shared = require('@nodaro/shared');
5
+
6
+ // src/errors.ts
7
+ var NodaroError = class extends Error {
8
+ constructor(message, code, status) {
9
+ super(message);
10
+ this.code = code;
11
+ this.status = status;
12
+ this.name = "NodaroError";
13
+ }
14
+ code;
15
+ status;
16
+ };
17
+ var UnauthorizedError = class extends NodaroError {
18
+ constructor(message = "Authentication required") {
19
+ super(message, "unauthorized", 401);
20
+ this.name = "UnauthorizedError";
21
+ }
22
+ };
23
+ var ForbiddenError = class extends NodaroError {
24
+ constructor(message = "Forbidden", missingScope) {
25
+ super(message, "forbidden", 403);
26
+ this.missingScope = missingScope;
27
+ this.name = "ForbiddenError";
28
+ }
29
+ missingScope;
30
+ };
31
+ var NotFoundError = class extends NodaroError {
32
+ constructor(message = "Not found") {
33
+ super(message, "not_found", 404);
34
+ this.name = "NotFoundError";
35
+ }
36
+ };
37
+ var RateLimitedError = class extends NodaroError {
38
+ constructor(message = "Rate limited") {
39
+ super(message, "rate_limited", 429);
40
+ this.name = "RateLimitedError";
41
+ }
42
+ };
43
+ var InsufficientCreditsError = class extends NodaroError {
44
+ constructor(message = "Insufficient credits", required, available) {
45
+ super(message, "insufficient_credits", 402);
46
+ this.required = required;
47
+ this.available = available;
48
+ this.name = "InsufficientCreditsError";
49
+ }
50
+ required;
51
+ available;
52
+ };
53
+ var StorageExceededError = class extends NodaroError {
54
+ constructor(message = "Storage exceeded", limitBytes) {
55
+ super(message, "storage_exceeded", 413);
56
+ this.limitBytes = limitBytes;
57
+ this.name = "StorageExceededError";
58
+ }
59
+ limitBytes;
60
+ };
61
+ var JobFailedError = class extends NodaroError {
62
+ constructor(message, jobId, jobStatus = "failed") {
63
+ super(message, "job_failed", 0);
64
+ this.jobId = jobId;
65
+ this.jobStatus = jobStatus;
66
+ this.name = "JobFailedError";
67
+ }
68
+ jobId;
69
+ jobStatus;
70
+ };
71
+ var JobTimeoutError = class extends NodaroError {
72
+ constructor(message, jobId, timeoutMs) {
73
+ super(message, "job_timeout", 0);
74
+ this.jobId = jobId;
75
+ this.timeoutMs = timeoutMs;
76
+ this.name = "JobTimeoutError";
77
+ }
78
+ jobId;
79
+ timeoutMs;
80
+ };
81
+ var JobAbortedError = class extends NodaroError {
82
+ constructor(message = "Aborted", jobId) {
83
+ super(message, "job_aborted", 0);
84
+ this.jobId = jobId;
85
+ this.name = "JobAbortedError";
86
+ }
87
+ jobId;
88
+ };
89
+ function throwFromResponse(status, body) {
90
+ const code = body.error?.code ?? "internal_error";
91
+ const message = body.error?.message ?? "Request failed";
92
+ if (status === 401) throw new UnauthorizedError(message);
93
+ if (status === 403 && code === "insufficient_scope") {
94
+ throw new ForbiddenError(message, body.error?.missingScope);
95
+ }
96
+ if (status === 403) throw new ForbiddenError(message);
97
+ if (status === 404) throw new NotFoundError(message);
98
+ if (status === 429) throw new RateLimitedError(message);
99
+ if (status === 402) {
100
+ throw new InsufficientCreditsError(message, body.error?.required, body.error?.available);
101
+ }
102
+ if (status === 413) throw new StorageExceededError(message, body.error?.limitBytes);
103
+ throw new NodaroError(message, code, status);
104
+ }
105
+
106
+ // src/resources/workflows.ts
107
+ var WorkflowsResource = class {
108
+ constructor(client) {
109
+ this.client = client;
110
+ }
111
+ client;
112
+ /** List workflows for a project. Returns metadata only — `nodes`/`edges` are not included. */
113
+ list(params) {
114
+ return this.client.request(
115
+ "GET",
116
+ `/v1/projects/${encodeURIComponent(params.projectId)}/workflows`
117
+ );
118
+ }
119
+ /** Get a workflow including its full nodes/edges/settings. */
120
+ get(id) {
121
+ return this.client.request("GET", `/v1/workflows/${encodeURIComponent(id)}`);
122
+ }
123
+ /**
124
+ * Get a PUBLICLY-SHARED workflow by id (`GET /v1/public/workflows/:id`) — the
125
+ * unauthenticated share-by-link read. Returns the workflow's nodes/edges/
126
+ * settings ONLY when it's opted into sharing server-side (`settings.studio.shared
127
+ * === true`); otherwise the route 404s (→ `NotFoundError`). No auth required —
128
+ * a share viewer has no session; the SDK omits the bearer when no token exists.
129
+ */
130
+ getPublic(id) {
131
+ return this.client.request("GET", `/v1/public/workflows/${encodeURIComponent(id)}`);
132
+ }
133
+ /**
134
+ * Create a workflow under a project. Returns the full record.
135
+ * NOTE: server route is `POST /v1/projects/:projectId/workflows`.
136
+ */
137
+ create(input) {
138
+ const { projectId, ...body } = input;
139
+ return this.client.request(
140
+ "POST",
141
+ `/v1/projects/${encodeURIComponent(projectId)}/workflows`,
142
+ { body }
143
+ );
144
+ }
145
+ /** Patch a workflow. Returns the full updated record. */
146
+ update(id, input) {
147
+ return this.client.request(
148
+ "PATCH",
149
+ `/v1/workflows/${encodeURIComponent(id)}`,
150
+ { body: input }
151
+ );
152
+ }
153
+ /** Delete a workflow. Returns `{ success: true }`. */
154
+ delete(id) {
155
+ return this.client.request("DELETE", `/v1/workflows/${encodeURIComponent(id)}`);
156
+ }
157
+ /**
158
+ * Run a workflow. Returns the executionId for polling via
159
+ * `client.executions.get(executionId)`.
160
+ */
161
+ run(id, params = {}) {
162
+ return this.client.request(
163
+ "POST",
164
+ `/v1/workflows/${encodeURIComponent(id)}/run`,
165
+ { body: params }
166
+ );
167
+ }
168
+ /**
169
+ * Export a workflow as a portable JSON bundle.
170
+ * Pass `opts.assets = true` to include character/object/location entity data.
171
+ */
172
+ export(workflowId, opts) {
173
+ return this.client.request(
174
+ "GET",
175
+ `/v1/workflows/${encodeURIComponent(workflowId)}/export`,
176
+ { query: { assets: opts?.assets ?? false } }
177
+ );
178
+ }
179
+ /**
180
+ * Import a `WorkflowExport` bundle into the specified project.
181
+ * Re-creates any bundled assets (characters, objects, locations) under your account.
182
+ */
183
+ import(input) {
184
+ const { projectId, ...workflowJson } = input;
185
+ return this.client.request("POST", "/v1/workflows/import", {
186
+ body: { projectId, workflow_json: workflowJson }
187
+ });
188
+ }
189
+ };
190
+
191
+ // src/resources/projects.ts
192
+ var ProjectsResource = class {
193
+ constructor(client) {
194
+ this.client = client;
195
+ }
196
+ client;
197
+ /** List the authenticated user's projects. */
198
+ list() {
199
+ return this.client.request("GET", "/v1/projects");
200
+ }
201
+ /** Get a project by ID. */
202
+ get(id) {
203
+ return this.client.request("GET", `/v1/projects/${encodeURIComponent(id)}`);
204
+ }
205
+ /** Create a new project. */
206
+ create(input) {
207
+ return this.client.request("POST", "/v1/projects", { body: input });
208
+ }
209
+ /** Update a project. At least one field must be provided. */
210
+ update(id, input) {
211
+ return this.client.request(
212
+ "PATCH",
213
+ `/v1/projects/${encodeURIComponent(id)}`,
214
+ { body: input }
215
+ );
216
+ }
217
+ /** Delete a project. Returns `{ success: true }`. */
218
+ delete(id) {
219
+ return this.client.request("DELETE", `/v1/projects/${encodeURIComponent(id)}`);
220
+ }
221
+ };
222
+
223
+ // src/resources/jobs.ts
224
+ var JobsResource = class {
225
+ constructor(client) {
226
+ this.client = client;
227
+ }
228
+ client;
229
+ /** Get a single job by ID. */
230
+ get(id) {
231
+ return this.client.request("GET", `/v1/jobs/${encodeURIComponent(id)}`);
232
+ }
233
+ /**
234
+ * Get the lean status of a single job (poll-loop friendly).
235
+ * Hits `GET /v1/jobs/:id/status` — returns only id/status/progress/
236
+ * output_data/error_message, with far less wire/CPU cost than `get()`.
237
+ * Same auth + ownership semantics as {@link get}.
238
+ */
239
+ getStatus(id) {
240
+ return this.client.request(
241
+ "GET",
242
+ `/v1/jobs/${encodeURIComponent(id)}/status`
243
+ );
244
+ }
245
+ /**
246
+ * Cancel a job. Server route is `POST /v1/jobs/:jobId/cancel`.
247
+ * Refunds any reserved credit holds.
248
+ */
249
+ cancel(id) {
250
+ return this.client.request(
251
+ "POST",
252
+ `/v1/jobs/${encodeURIComponent(id)}/cancel`
253
+ );
254
+ }
255
+ };
256
+
257
+ // src/resources/executions.ts
258
+ var ExecutionsResource = class {
259
+ constructor(client) {
260
+ this.client = client;
261
+ }
262
+ client;
263
+ /** Get an execution by ID. Falls back to standalone single-node jobs server-side. */
264
+ get(id) {
265
+ return this.client.request(
266
+ "GET",
267
+ `/v1/workflow-executions/${encodeURIComponent(id)}`
268
+ );
269
+ }
270
+ /** List executions for a workflow. Merges workflow_executions + standalone single-node jobs. */
271
+ listForWorkflow(workflowId, params = {}) {
272
+ return this.client.request(
273
+ "GET",
274
+ `/v1/workflows/${encodeURIComponent(workflowId)}/executions`,
275
+ {
276
+ query: {
277
+ limit: params.limit,
278
+ cursor: params.cursor,
279
+ status: params.status,
280
+ source: params.source
281
+ }
282
+ }
283
+ );
284
+ }
285
+ /** Cancel an execution. Returns `{ success: true }`. */
286
+ cancel(id, params = {}) {
287
+ return this.client.request(
288
+ "POST",
289
+ `/v1/workflow-executions/${encodeURIComponent(id)}/cancel`,
290
+ { body: params }
291
+ );
292
+ }
293
+ };
294
+
295
+ // src/resources/nodes.ts
296
+ var DEFAULT_POLL_MS = 2e3;
297
+ var DEFAULT_MAX_MS = 15 * 60 * 1e3;
298
+ var sleep = (ms, signal) => new Promise((resolve, reject) => {
299
+ if (signal?.aborted) {
300
+ reject(new JobAbortedError());
301
+ return;
302
+ }
303
+ const timer = setTimeout(() => {
304
+ signal?.removeEventListener("abort", onAbort);
305
+ resolve();
306
+ }, ms);
307
+ function onAbort() {
308
+ clearTimeout(timer);
309
+ reject(new JobAbortedError());
310
+ }
311
+ signal?.addEventListener("abort", onAbort, { once: true });
312
+ });
313
+ function extractJobId(result, label) {
314
+ if (result && typeof result === "object" && "jobId" in result) {
315
+ const jobId = result.jobId;
316
+ if (typeof jobId === "string") return jobId;
317
+ }
318
+ throw new JobFailedError(`${label} did not return a jobId`, "");
319
+ }
320
+ var NodesResource = class {
321
+ constructor(client) {
322
+ this.client = client;
323
+ }
324
+ client;
325
+ /** List all known node descriptors. Server caches publicly for 5 minutes. */
326
+ list() {
327
+ return this.client.request("GET", "/v1/nodes");
328
+ }
329
+ /** Get a single node descriptor by type slug (e.g. "generate-image"). */
330
+ get(type) {
331
+ return this.client.request("GET", `/v1/nodes/${encodeURIComponent(type)}`);
332
+ }
333
+ run(type, params = {}) {
334
+ return this.client.request("POST", `/v1/${encodeURIComponent(type)}`, { body: params });
335
+ }
336
+ async runAndWait(type, params = {}, opts = {}) {
337
+ if (opts.signal?.aborted) throw new JobAbortedError();
338
+ const result = await this.run(type, params);
339
+ const jobId = extractJobId(result, type);
340
+ return this.pollJob(jobId, type, opts);
341
+ }
342
+ /**
343
+ * Fan out N async runs of the same node `type` to completion concurrently —
344
+ * the candidate-grid path (generate N stills/clips in parallel). Each runs
345
+ * via {@link runAndWait}; resolves once ALL settle, to an array of
346
+ * `{ jobId, output }` in input order. Rejects (and the rejection wins) if any
347
+ * single run rejects — same typed errors as {@link runAndWait}. A shared
348
+ * `signal` aborts the whole batch.
349
+ *
350
+ * @param type Node type slug, applied to every entry.
351
+ * @param paramsList One request body per candidate.
352
+ * @param opts Shared `signal` / `onProgress` / `pollMs` / `maxMs`.
353
+ */
354
+ async runMany(type, paramsList, opts = {}) {
355
+ if (opts.signal?.aborted) throw new JobAbortedError();
356
+ return Promise.all(
357
+ paramsList.map(async (params) => {
358
+ if (opts.signal?.aborted) throw new JobAbortedError();
359
+ const result = await this.run(type, params);
360
+ const jobId = extractJobId(result, type);
361
+ const output = await this.pollJob(jobId, type, opts);
362
+ return { jobId, output };
363
+ })
364
+ );
365
+ }
366
+ /** Poll an already-kicked job id until terminal; resolve output_data or throw. */
367
+ async pollJob(jobId, label, opts) {
368
+ const pollMs = opts.pollMs ?? DEFAULT_POLL_MS;
369
+ const maxMs = opts.maxMs ?? DEFAULT_MAX_MS;
370
+ const deadline = Date.now() + maxMs;
371
+ for (; ; ) {
372
+ if (opts.signal?.aborted) throw new JobAbortedError(void 0, jobId);
373
+ const { data } = await this.client.jobs.getStatus(jobId);
374
+ opts.onProgress?.(data);
375
+ if (data.status === "completed") {
376
+ return data.output_data ?? {};
377
+ }
378
+ if (data.status === "failed" || data.status === "cancelled") {
379
+ throw new JobFailedError(
380
+ data.error_message ?? `${label} ${data.status}`,
381
+ jobId,
382
+ data.status
383
+ );
384
+ }
385
+ if (Date.now() > deadline) {
386
+ throw new JobTimeoutError(`${label} timed out`, jobId, maxMs);
387
+ }
388
+ await sleep(pollMs, opts.signal);
389
+ }
390
+ }
391
+ };
392
+
393
+ // src/resources/developer-apps.ts
394
+ var DeveloperAppsResource = class {
395
+ constructor(client) {
396
+ this.client = client;
397
+ }
398
+ client;
399
+ /** List the authenticated user's developer apps. */
400
+ list() {
401
+ return this.client.request("GET", "/v1/developer-apps");
402
+ }
403
+ /** Get a developer app by ID. */
404
+ get(id) {
405
+ return this.client.request("GET", `/v1/developer-apps/${encodeURIComponent(id)}`);
406
+ }
407
+ /**
408
+ * Create a new developer app. Returns the app PLUS a one-time `clientSecret`
409
+ * — store it now, the secret hash is the only copy kept server-side.
410
+ */
411
+ create(input) {
412
+ return this.client.request("POST", "/v1/developer-apps", { body: input });
413
+ }
414
+ /** Update a developer app's metadata, redirect URIs, origins, or requested scopes. */
415
+ update(id, input) {
416
+ return this.client.request(
417
+ "PATCH",
418
+ `/v1/developer-apps/${encodeURIComponent(id)}`,
419
+ { body: input }
420
+ );
421
+ }
422
+ /** Delete a developer app. Returns `{ success: true }`. */
423
+ delete(id) {
424
+ return this.client.request(
425
+ "DELETE",
426
+ `/v1/developer-apps/${encodeURIComponent(id)}`
427
+ );
428
+ }
429
+ /**
430
+ * Generate a new `clientSecret`. The previous secret is invalidated.
431
+ * Server returns ONLY the new secret, not the full app record.
432
+ */
433
+ rotateSecret(id) {
434
+ return this.client.request(
435
+ "POST",
436
+ `/v1/developer-apps/${encodeURIComponent(id)}/rotate-secret`
437
+ );
438
+ }
439
+ };
440
+
441
+ // src/resources/oauth.ts
442
+ var OAuthResource = class {
443
+ constructor(client) {
444
+ this.client = client;
445
+ }
446
+ client;
447
+ /**
448
+ * Server-side authorization-code exchange. Sends the standard OAuth 2.0
449
+ * `application/json` body to `POST /v1/oauth/token`.
450
+ *
451
+ * NEVER call this from a browser — `client_secret` must stay on the server.
452
+ */
453
+ exchangeCode(input) {
454
+ return this.client.request("POST", "/v1/oauth/token", {
455
+ body: { grant_type: "authorization_code", ...input }
456
+ });
457
+ }
458
+ /**
459
+ * Revoke an access token (RFC 7009). Always returns `{ success: true }`,
460
+ * even for unknown tokens — the spec forbids leaking token validity.
461
+ */
462
+ revoke(token) {
463
+ return this.client.request("POST", "/v1/oauth/revoke", { body: { token } });
464
+ }
465
+ /**
466
+ * Get public app metadata for a consent screen.
467
+ * `GET /v1/oauth/app-info?client_id=<id>`. Public route — no auth needed.
468
+ */
469
+ getAppInfo(clientId) {
470
+ return this.client.request("GET", "/v1/oauth/app-info", {
471
+ query: { client_id: clientId }
472
+ });
473
+ }
474
+ };
475
+
476
+ // src/resources/apps.ts
477
+ var AppsResource = class {
478
+ constructor(client) {
479
+ this.client = client;
480
+ }
481
+ client;
482
+ /** List published apps. Public — no auth required for community apps. */
483
+ list(params = {}) {
484
+ const qs = new URLSearchParams();
485
+ if (params.search) qs.set("search", params.search);
486
+ if (params.limit !== void 0) qs.set("limit", String(params.limit));
487
+ if (params.cursor) qs.set("cursor", params.cursor);
488
+ if (params.category) qs.set("category", params.category);
489
+ const query = qs.toString();
490
+ return this.client.request("GET", `/v1/apps/browse${query ? `?${query}` : ""}`);
491
+ }
492
+ /** Get one app's metadata + input schema by slug. */
493
+ get(slug) {
494
+ return this.client.request("GET", `/v1/app/${encodeURIComponent(slug)}`);
495
+ }
496
+ /**
497
+ * Trigger an app run with the given input values. The keys in `inputs` must
498
+ * match the app's input-schema field names (see `get(slug).inputSchema`).
499
+ * Returns the execution-id for status polling via client.executions.get().
500
+ */
501
+ run(slug, inputs = {}) {
502
+ return this.client.request("POST", `/v1/app/${encodeURIComponent(slug)}/run`, {
503
+ body: { inputs }
504
+ });
505
+ }
506
+ /** List past runs for an app (the caller must own the app or the runs). */
507
+ listRuns(slug, params = {}) {
508
+ const qs = new URLSearchParams();
509
+ if (params.limit !== void 0) qs.set("limit", String(params.limit));
510
+ if (params.cursor) qs.set("cursor", params.cursor);
511
+ const query = qs.toString();
512
+ return this.client.request(
513
+ "GET",
514
+ `/v1/app/${encodeURIComponent(slug)}/runs${query ? `?${query}` : ""}`
515
+ );
516
+ }
517
+ /** Get one app-run by id. */
518
+ getRun(slug, runId) {
519
+ return this.client.request(
520
+ "GET",
521
+ `/v1/app/${encodeURIComponent(slug)}/runs/${encodeURIComponent(runId)}`
522
+ );
523
+ }
524
+ /**
525
+ * Archive (soft-delete) a published-app run. The run is hidden from the
526
+ * default run list and can be restored or permanently deleted from the
527
+ * archive view at https://app.nodaro.ai/archived-runs.
528
+ *
529
+ * @param slug The published app's slug (the last path segment of its URL).
530
+ * @param runId The run's UUID.
531
+ */
532
+ deleteRun(slug, runId) {
533
+ return this.client.request(
534
+ "DELETE",
535
+ `/v1/app/${encodeURIComponent(slug)}/runs/${encodeURIComponent(runId)}`
536
+ );
537
+ }
538
+ };
539
+ var CharactersResource = class {
540
+ constructor(client) {
541
+ this.client = client;
542
+ }
543
+ client;
544
+ /**
545
+ * List the caller's characters. By default returns active characters only;
546
+ * pass `archived: true` to fetch soft-deleted rows for an "archive" view.
547
+ * When `projectId` is set, only characters belonging to that project are
548
+ * returned.
549
+ */
550
+ list(params = {}) {
551
+ const query = {};
552
+ if (params.projectId) query.projectId = params.projectId;
553
+ if (params.archived) query.archived = "true";
554
+ if (params.limit !== void 0) query.limit = String(params.limit);
555
+ return this.client.request("GET", "/v1/characters", { query });
556
+ }
557
+ /**
558
+ * Fetch a single character including in-flight portrait / asset job state.
559
+ * Soft-deleted (archived) rows are returned by id intentionally so canvas
560
+ * nodes that hold a stale `characterDbId` keep loading.
561
+ */
562
+ get(id) {
563
+ return this.client.request("GET", `/v1/characters/${encodeURIComponent(id)}`);
564
+ }
565
+ /**
566
+ * Create or update a character. Omit `id` to create; supply it to update
567
+ * (only the fields you pass get written — undefined keys are untouched).
568
+ *
569
+ * If the caller-supplied `name` collides with an existing active character
570
+ * for this user, the request returns 409 `name_taken`. To auto-number a
571
+ * placeholder, pass the placeholder name from `@nodaro/shared` and the
572
+ * server will derive "Untitled character 2", "Untitled character 3", etc.
573
+ */
574
+ upsert(input) {
575
+ return this.client.request("POST", "/v1/characters", { body: input });
576
+ }
577
+ /**
578
+ * Convenience wrapper around `upsert()` for creating new characters.
579
+ * Equivalent to `upsert({ ...input, id: undefined })`. `name` is REQUIRED
580
+ * on create — the route 400s on INSERT-without-name; we narrow the type
581
+ * here so callers fail at compile-time rather than runtime.
582
+ */
583
+ create(input) {
584
+ return this.upsert(input);
585
+ }
586
+ /**
587
+ * Convenience wrapper around `upsert()` for updating an existing character.
588
+ * Equivalent to `upsert({ ...input, id })`.
589
+ */
590
+ update(id, input) {
591
+ return this.upsert({ ...input, id });
592
+ }
593
+ /**
594
+ * Soft-delete (archive) a character. The row is hidden from `list()` by
595
+ * default but still loadable via `get(id)` so canvas nodes pointing at it
596
+ * keep working. Restore with `restore(id)`.
597
+ */
598
+ delete(id) {
599
+ return this.client.request("DELETE", `/v1/characters/${encodeURIComponent(id)}`);
600
+ }
601
+ /**
602
+ * Un-archive a character. If the original name now collides with an
603
+ * active row, the server auto-suffixes "(restored)" and returns the
604
+ * effective name.
605
+ */
606
+ restore(id) {
607
+ return this.client.request("POST", `/v1/characters/${encodeURIComponent(id)}/restore`);
608
+ }
609
+ /**
610
+ * Duplicate (fork) a character to a new row with a `"(copy)"` suffix.
611
+ * Asset URLs are shared by reference — the new row can diverge by
612
+ * regenerating any of them.
613
+ */
614
+ duplicate(id, input = {}) {
615
+ return this.client.request(
616
+ "POST",
617
+ `/v1/characters/${encodeURIComponent(id)}/duplicate`,
618
+ { body: input }
619
+ );
620
+ }
621
+ /**
622
+ * Count of the caller's workflows that reference this character. Powers the
623
+ * library "Archive" confirmation modal in the editor.
624
+ */
625
+ usage(id) {
626
+ return this.client.request("GET", `/v1/characters/${encodeURIComponent(id)}/usage`);
627
+ }
628
+ /**
629
+ * Fire `POST /v1/generate-character` to produce one or more portrait
630
+ * candidates. With `count > 1`, all jobs are reserved up-front before any
631
+ * is enqueued — mid-batch failures roll back atomically.
632
+ *
633
+ * When `attachToCharacterId` is set, the worker writes the result directly
634
+ * to the row's `source_image_url`; otherwise you must call
635
+ * `approvePortrait()` after picking a candidate.
636
+ */
637
+ generate(input) {
638
+ return this.client.request("POST", "/v1/generate-character", { body: input });
639
+ }
640
+ /**
641
+ * Fire `POST /v1/generate-character-asset` to produce a single
642
+ * expression / pose / angle / lighting variant. When the studio path is
643
+ * set (`attachToCharacterId` + `attachToColumn` + `attachName`), the
644
+ * worker appends `{ name: attachName, url: <result> }` to the named
645
+ * JSONB array column on completion.
646
+ */
647
+ generateAsset(input) {
648
+ return this.client.request("POST", "/v1/generate-character-asset", { body: input });
649
+ }
650
+ /**
651
+ * Fire `POST /v1/generate-character-motion` to animate the character's
652
+ * portrait into a motion clip. The result is appended to the character's
653
+ * `motions[]` bucket when `attachToCharacterId` is set.
654
+ */
655
+ generateMotion(input) {
656
+ return this.client.request("POST", "/v1/generate-character-motion", { body: input });
657
+ }
658
+ /**
659
+ * Approve a completed `generate-character` job as the character's portrait.
660
+ * Sets `source_image_url` and fires the LLM caption (Claude Sonnet vision)
661
+ * inline. Returns the new portrait URL plus the caption — `canonicalDescription`
662
+ * is `null` if the LLM call sub-failed (portrait still set; retry via `recaption()`).
663
+ */
664
+ approvePortrait(id, candidateJobId) {
665
+ return this.client.request(
666
+ "POST",
667
+ `/v1/characters/${encodeURIComponent(id)}/approve-portrait`,
668
+ { body: { candidateJobId } }
669
+ );
670
+ }
671
+ /**
672
+ * Re-fire the LLM caption against the character's current portrait. 502s on
673
+ * LLM failure; returns 400 `no_portrait` if no portrait is set yet.
674
+ */
675
+ recaption(id) {
676
+ return this.client.request(
677
+ "POST",
678
+ `/v1/characters/${encodeURIComponent(id)}/llm-caption`
679
+ );
680
+ }
681
+ };
682
+ function buildPersonSeedPrompt(value) {
683
+ return prompts.buildPersonHints(value).join(", ");
684
+ }
685
+ var LocationsResource = class {
686
+ constructor(client) {
687
+ this.client = client;
688
+ }
689
+ client;
690
+ /**
691
+ * List the caller's locations. By default returns active locations only;
692
+ * pass `archived: true` to fetch soft-deleted rows for an "archive" view.
693
+ */
694
+ list(params = {}) {
695
+ const query = {};
696
+ if (params.archived) query.archived = "true";
697
+ return this.client.request("GET", "/v1/locations", { query });
698
+ }
699
+ /**
700
+ * Convenience wrapper for `list({ archived: true })`. Returns soft-deleted
701
+ * rows so callers can drive a UI "Archived" tab without re-encoding the
702
+ * query param. Mirrors `ObjectsResource.listArchived`.
703
+ *
704
+ * `archived` is omitted from the param type — it's always set to `true` here.
705
+ */
706
+ listArchived(params = {}) {
707
+ return this.list({ ...params, archived: true });
708
+ }
709
+ /**
710
+ * Fetch a single location including in-flight asset job state. Soft-deleted
711
+ * (archived) rows are returned by id intentionally so canvas nodes that
712
+ * hold a stale `locationDbId` keep loading.
713
+ */
714
+ async get(id) {
715
+ const res = await this.client.request(
716
+ "GET",
717
+ `/v1/locations/${encodeURIComponent(id)}`
718
+ );
719
+ return { ...res, canonicalDescription: res.canonicalDescription || null };
720
+ }
721
+ /**
722
+ * Create a new location. `name` + `nodeId` are required — the route 400s
723
+ * otherwise. Returns the new row's id.
724
+ *
725
+ * Note: the underlying route is the same `POST /v1/locations` upsert that
726
+ * powers `update()`. This convenience wrapper enforces the INSERT-required
727
+ * fields at the type level and never sends an `id`.
728
+ */
729
+ create(data) {
730
+ return this.client.request("POST", "/v1/locations", { body: data });
731
+ }
732
+ /**
733
+ * Update a location. Only the fields you pass are written — undefined keys
734
+ * are NOT touched on the row. Worker-owned asset buckets are intentionally
735
+ * not exposed on this surface (see `UpdateLocationInput` for the rationale).
736
+ *
737
+ * Optimistic-concurrency: pass `expectedUpdatedAt` to require the row's
738
+ * `updated_at` still matches; on mismatch the route returns 409
739
+ * `concurrent_modification`. The SDK surfaces that as a generic
740
+ * `NodaroError` with the same code.
741
+ */
742
+ update(id, data) {
743
+ return this.client.request("POST", "/v1/locations", {
744
+ body: { id, ...data }
745
+ });
746
+ }
747
+ /**
748
+ * Soft-delete (archive) a location. The row is hidden from `list()` by
749
+ * default but still loadable via `get(id)` so canvas nodes pointing at it
750
+ * keep working. Restore with `restore(id)`.
751
+ */
752
+ delete(id) {
753
+ return this.client.request("DELETE", `/v1/locations/${encodeURIComponent(id)}`);
754
+ }
755
+ /**
756
+ * Un-archive a location. If the original name now collides (case-
757
+ * insensitive) with an active row, the server auto-suffixes "(restored)"
758
+ * and returns the effective name.
759
+ */
760
+ restore(id) {
761
+ return this.client.request("POST", `/v1/locations/${encodeURIComponent(id)}/restore`);
762
+ }
763
+ /**
764
+ * Fire `POST /v1/generate-location` to produce one or more candidate main
765
+ * images. With `count > 1`, all jobs are reserved up-front before any
766
+ * is enqueued — mid-batch failures roll back atomically.
767
+ *
768
+ * When `attachToLocationId` is set AND `count === 1`, the worker writes
769
+ * the result directly to the row's `source_image_url`; otherwise you must
770
+ * call `approveMainImage()` after picking a candidate.
771
+ */
772
+ async generate(data) {
773
+ const res = await this.client.request(
774
+ "POST",
775
+ "/v1/generate-location",
776
+ { body: data }
777
+ );
778
+ const jobIds = res.jobIds ?? (res.jobId ? [res.jobId] : []);
779
+ return res.jobId ? { jobIds, jobId: res.jobId } : { jobIds };
780
+ }
781
+ /**
782
+ * Fire `POST /v1/generate-location-asset` to produce a single variant.
783
+ * When the studio path is set (`attachToLocationId` + `attachToColumn` +
784
+ * `attachName`), the worker appends `{ name: attachName, url: <result> }`
785
+ * to the named JSONB array column on completion.
786
+ */
787
+ generateAsset(data) {
788
+ return this.client.request("POST", "/v1/generate-location-asset", { body: data });
789
+ }
790
+ /**
791
+ * Fire `POST /v1/generate-surround-continuation` to produce one seamless 360°
792
+ * ring view as an i2i continuation of `referenceImageUrl`. The platform builds
793
+ * the half-carry composite, paints the missing half, and color-harmonizes it
794
+ * to the carried half (no tonal seam; carried half stays pixel-exact). When the
795
+ * studio path is set, the worker appends the result to the location's bucket.
796
+ */
797
+ generateSurroundContinuation(data) {
798
+ return this.client.request("POST", "/v1/generate-surround-continuation", { body: data });
799
+ }
800
+ /**
801
+ * Fire `POST /v1/generate-location-motion` to animate the location's
802
+ * establishing shot into an atmospheric motion clip. Image-to-video, single
803
+ * clip per call; the attach column is hardcoded to `atmosphere_motions`
804
+ * server-side (locations have a single motion bucket so the caller doesn't
805
+ * supply `attachToColumn`). When the studio path is set
806
+ * (`attachToLocationId` + `attachName`), the worker appends
807
+ * `{ name: attachName, url: <result> }` to the row's `atmosphere_motions`
808
+ * column on completion.
809
+ */
810
+ generateMotion(data) {
811
+ return this.client.request("POST", "/v1/generate-location-motion", { body: data });
812
+ }
813
+ /**
814
+ * Atomically remove ONE asset take (every entry matching `url`) from a
815
+ * worker-owned bucket column — `POST /v1/locations/:id/remove-asset`. The
816
+ * worker-owned buckets are deliberately not writable through `update()`
817
+ * (a stale snapshot would race concurrent worker appends), so deleting a
818
+ * take — e.g. a 360° surround view being regenerated — goes through this
819
+ * single-statement server-side filter instead. 404s (`NotFoundError`) when
820
+ * the url isn't in that bucket or the location isn't yours.
821
+ */
822
+ removeAsset(id, data) {
823
+ return this.client.request(
824
+ "POST",
825
+ `/v1/locations/${encodeURIComponent(id)}/remove-asset`,
826
+ { body: data }
827
+ );
828
+ }
829
+ /**
830
+ * Approve a completed `generate-location` job as the location's main image.
831
+ * Sets `source_image_url` and fires the LLM caption (Claude Sonnet vision)
832
+ * inline. Returns the new main-image URL plus the caption.
833
+ *
834
+ * Caption-failure semantics: the route still sends `""` on LLM sub-failure,
835
+ * but the SDK normalizes `""` → `null` here so `canonicalDescription` carries
836
+ * the same `string | null` semantics as characters. The main image is still
837
+ * set; call `recaption()` to retry.
838
+ */
839
+ async approveMainImage(id, candidateJobId) {
840
+ const res = await this.client.request(
841
+ "POST",
842
+ `/v1/locations/${encodeURIComponent(id)}/approve-main-image`,
843
+ { body: { candidateJobId } }
844
+ );
845
+ return { sourceImageUrl: res.sourceImageUrl, canonicalDescription: res.canonicalDescription || null };
846
+ }
847
+ /**
848
+ * Re-fire the LLM caption against the location's current main image. 502s
849
+ * on LLM failure (unlike `approveMainImage` which preserves the side-effect
850
+ * and returns ""); returns 400 `no_source_image` if no main image is set
851
+ * yet.
852
+ */
853
+ recaption(id) {
854
+ return this.client.request(
855
+ "POST",
856
+ `/v1/locations/${encodeURIComponent(id)}/llm-caption`
857
+ );
858
+ }
859
+ };
860
+ var ObjectsResource = class {
861
+ constructor(client) {
862
+ this.client = client;
863
+ }
864
+ client;
865
+ /**
866
+ * List the caller's objects. By default returns active objects only;
867
+ * pass `archived: true` to fetch soft-deleted rows for an "archive" view.
868
+ * Optional `projectId` scopes the result to a single project.
869
+ */
870
+ list(params = {}) {
871
+ const query = {};
872
+ if (params.archived) query.archived = "true";
873
+ if (params.projectId) query.projectId = params.projectId;
874
+ return this.client.request("GET", "/v1/objects", { query });
875
+ }
876
+ /**
877
+ * Convenience wrapper for `list({ archived: true })`. Returns soft-deleted
878
+ * rows so callers can drive a UI "Archived" tab without re-encoding the
879
+ * query param.
880
+ *
881
+ * `archived` is omitted from the param type — it's always set to `true` here.
882
+ */
883
+ listArchived(params = {}) {
884
+ return this.list({ ...params, archived: true });
885
+ }
886
+ /**
887
+ * Fetch a single object including in-flight asset job state. Soft-deleted
888
+ * (archived) rows are NOT returned by id — the route enforces
889
+ * `deleted_at IS NULL` so archived objects 404 (uniform Pass 10 F-90b
890
+ * "not_found" — does not leak the deleted vs non-existent distinction).
891
+ */
892
+ async get(id) {
893
+ const res = await this.client.request(
894
+ "GET",
895
+ `/v1/objects/${encodeURIComponent(id)}`
896
+ );
897
+ return { ...res, canonicalDescription: res.canonicalDescription || null };
898
+ }
899
+ /**
900
+ * Create a new object. `name` + `nodeId` are required — the route 400s
901
+ * otherwise. Returns the new row's id.
902
+ *
903
+ * Note: the underlying route is the same `POST /v1/objects` upsert that
904
+ * powers `update()`. This convenience wrapper enforces the INSERT-required
905
+ * fields at the type level and never sends an `id`.
906
+ */
907
+ create(data) {
908
+ return this.client.request("POST", "/v1/objects", { body: data });
909
+ }
910
+ /**
911
+ * Update an object. Only the fields you pass are written — undefined keys
912
+ * are NOT touched on the row. Worker-owned asset buckets are intentionally
913
+ * not exposed on this surface (see `UpdateObjectInput` for the rationale).
914
+ *
915
+ * Optimistic-concurrency: pass `expectedUpdatedAt` to require the row's
916
+ * `updated_at` still matches; on mismatch the route returns 409
917
+ * `concurrent_modification` carrying the fresh `updatedAt`. The SDK
918
+ * surfaces that as a generic `NodaroError` with the same code (per Phase
919
+ * E1 calibration finding — error centralization in `throwApiError`).
920
+ */
921
+ update(id, data) {
922
+ return this.client.request("POST", "/v1/objects", {
923
+ body: { id, ...data }
924
+ });
925
+ }
926
+ /**
927
+ * Soft-delete (archive) an object. The row is hidden from `list()` by
928
+ * default but recoverable via `restore(id)` or visible under
929
+ * `listArchived()`. Idempotent — repeating a delete on an already-archived
930
+ * row is a no-op.
931
+ */
932
+ delete(id) {
933
+ return this.client.request("DELETE", `/v1/objects/${encodeURIComponent(id)}`);
934
+ }
935
+ /**
936
+ * Hard-delete (permanent) an object — the row + every R2 asset it
937
+ * references. Archived rows ONLY: active objects return 400 `not_archived`.
938
+ * Call `delete()` first to archive, then `permanentDelete()` to destroy.
939
+ *
940
+ * Mirrors the `app_runs` permanent-delete pattern (archive-first) so a
941
+ * stray SDK / curl caller cannot bypass the studio's archive-first UI
942
+ * flow.
943
+ */
944
+ permanentDelete(id) {
945
+ return this.client.request("DELETE", `/v1/objects/${encodeURIComponent(id)}`, {
946
+ query: { permanent: "true" }
947
+ });
948
+ }
949
+ /**
950
+ * Un-archive an object. If the original name now collides (case-
951
+ * insensitive) with an active row, the server auto-suffixes "(restored)"
952
+ * and returns the effective name.
953
+ */
954
+ restore(id) {
955
+ return this.client.request("POST", `/v1/objects/${encodeURIComponent(id)}/restore`);
956
+ }
957
+ /**
958
+ * Fire `POST /v1/generate-object` to produce one or more candidate main
959
+ * images. With `count > 1`, all jobs are reserved up-front before any
960
+ * is enqueued — mid-batch failures roll back atomically.
961
+ *
962
+ * When `attachToObjectId` is set AND `count === 1`, the worker writes
963
+ * the result directly to the row's `source_image_url`; otherwise you must
964
+ * call `approveMainImage()` after picking a candidate.
965
+ */
966
+ async generate(data) {
967
+ const res = await this.client.request(
968
+ "POST",
969
+ "/v1/generate-object",
970
+ { body: data }
971
+ );
972
+ const jobIds = res.jobIds ?? (res.jobId ? [res.jobId] : []);
973
+ return res.jobId ? { jobIds, jobId: res.jobId } : { jobIds };
974
+ }
975
+ /**
976
+ * Fire `POST /v1/generate-object-asset` to produce a single variant.
977
+ * When the studio path is set (`attachToObjectId` + `attachToColumn` +
978
+ * `attachName`), the worker appends `{ name: attachName, url: <result> }`
979
+ * to the named JSONB array column on completion.
980
+ *
981
+ * Note: `attachToColumn` is REQUIRED for `assetType === "custom"` — the
982
+ * worker can't infer the bucket from the asset type. For canonical asset
983
+ * types (`angles` / `materials` / `variations` / `motion`), the column is
984
+ * derived automatically by the route.
985
+ */
986
+ generateAsset(data) {
987
+ return this.client.request("POST", "/v1/generate-object-asset", { body: data });
988
+ }
989
+ /**
990
+ * Fire `POST /v1/generate-object-motion` to animate the object's main
991
+ * image into a motion clip. Image-to-video, single clip per call; the
992
+ * attach column is hardcoded to `motion_clips` server-side (objects have a
993
+ * single motion bucket so the caller doesn't supply `attachToColumn`).
994
+ * When the studio path is set (`attachToObjectId` + `attachName`), the
995
+ * worker appends `{ name: attachName, url: <result> }` to the row's
996
+ * `motion_clips` column on completion.
997
+ *
998
+ * Defaults: `provider` → `"kling-turbo"`, `aspectRatio` → `"1:1"` (set
999
+ * server-side via `resolveObjectAspectRatio({ assetType: "motion" })`).
1000
+ */
1001
+ generateMotion(data) {
1002
+ return this.client.request("POST", "/v1/generate-object-motion", { body: data });
1003
+ }
1004
+ /**
1005
+ * Approve a completed `generate-object` job as the object's main image.
1006
+ * Sets `source_image_url` and fires the LLM caption (Claude Sonnet vision)
1007
+ * inline. Returns the new main-image URL plus the caption.
1008
+ *
1009
+ * Caption-failure semantics: the route still sends `""` on LLM sub-failure,
1010
+ * but the SDK normalizes `""` → `null` here so `canonicalDescription` carries
1011
+ * the same `string | null` semantics as characters. The main image is still
1012
+ * set; call `recaption()` to retry.
1013
+ *
1014
+ * Optimistic-concurrency: pass `expectedUpdatedAt` to gate the update on
1015
+ * the row's current `updated_at`; on mismatch the route returns 409
1016
+ * `concurrent_modification` carrying the fresh token.
1017
+ */
1018
+ async approveMainImage(id, candidateJobId, expectedUpdatedAt) {
1019
+ const res = await this.client.request(
1020
+ "POST",
1021
+ `/v1/objects/${encodeURIComponent(id)}/approve-main-image`,
1022
+ { body: { candidateJobId, expectedUpdatedAt } }
1023
+ );
1024
+ return { sourceImageUrl: res.sourceImageUrl, canonicalDescription: res.canonicalDescription || null };
1025
+ }
1026
+ /**
1027
+ * Re-fire the LLM caption against the object's current main image. 502s
1028
+ * on LLM failure (unlike `approveMainImage` which preserves the side-effect
1029
+ * and returns ""); returns 400 `main_image_required` if no main image is
1030
+ * set yet.
1031
+ *
1032
+ * The route is a pure idempotent retry — it does NOT accept an
1033
+ * `expectedUpdatedAt` token (per Phase E1 calibration finding: backend
1034
+ * route is idempotent retry, not gated on optimistic-concurrency).
1035
+ */
1036
+ recaption(id) {
1037
+ return this.client.request(
1038
+ "POST",
1039
+ `/v1/objects/${encodeURIComponent(id)}/llm-caption`
1040
+ );
1041
+ }
1042
+ };
1043
+ var CREATURE_ASSET_TYPES = ["angles", "poses", "variations", "custom"];
1044
+ var CreaturesResource = class {
1045
+ constructor(client) {
1046
+ this.client = client;
1047
+ }
1048
+ client;
1049
+ /**
1050
+ * List the caller's creatures. By default returns active creatures only;
1051
+ * pass `archived: true` to fetch soft-deleted rows for an "archive" view.
1052
+ * Optional `projectId` scopes the result to a single project.
1053
+ */
1054
+ list(params = {}) {
1055
+ const query = {};
1056
+ if (params.archived) query.archived = "true";
1057
+ if (params.projectId) query.projectId = params.projectId;
1058
+ return this.client.request("GET", "/v1/creatures", { query });
1059
+ }
1060
+ /**
1061
+ * Convenience wrapper for `list({ archived: true })`. Returns soft-deleted
1062
+ * rows so callers can drive a UI "Archived" tab without re-encoding the
1063
+ * query param.
1064
+ *
1065
+ * `archived` is omitted from the param type — it's always set to `true` here.
1066
+ */
1067
+ listArchived(params = {}) {
1068
+ return this.list({ ...params, archived: true });
1069
+ }
1070
+ /**
1071
+ * Fetch a single creature including in-flight asset job state. Soft-deleted
1072
+ * (archived) rows are NOT returned by id — the route enforces
1073
+ * `deleted_at IS NULL` so archived creatures 404 (uniform "not_found" — does
1074
+ * not leak the deleted vs non-existent distinction).
1075
+ */
1076
+ async get(id) {
1077
+ const res = await this.client.request(
1078
+ "GET",
1079
+ `/v1/creatures/${encodeURIComponent(id)}`
1080
+ );
1081
+ return { ...res, canonicalDescription: res.canonicalDescription || null };
1082
+ }
1083
+ /**
1084
+ * Create a new creature. `name` + `nodeId` are required — the route 400s
1085
+ * otherwise. Returns the new row's id.
1086
+ *
1087
+ * Note: the underlying route is the same `POST /v1/creatures` upsert that
1088
+ * powers `update()`. This convenience wrapper enforces the INSERT-required
1089
+ * fields at the type level and never sends an `id`.
1090
+ */
1091
+ create(data) {
1092
+ return this.client.request("POST", "/v1/creatures", { body: data });
1093
+ }
1094
+ /**
1095
+ * Update a creature. Only the fields you pass are written — undefined keys
1096
+ * are NOT touched on the row. Worker-owned asset buckets are intentionally
1097
+ * not exposed on this surface (see `UpdateCreatureInput` for the rationale).
1098
+ *
1099
+ * Optimistic-concurrency: pass `expectedUpdatedAt` to require the row's
1100
+ * `updated_at` still matches; on mismatch the route returns 409
1101
+ * `concurrent_modification` carrying the fresh `updatedAt`. The SDK
1102
+ * surfaces that as a generic `NodaroError` with the same code.
1103
+ */
1104
+ update(id, data) {
1105
+ return this.client.request("POST", "/v1/creatures", {
1106
+ body: { id, ...data }
1107
+ });
1108
+ }
1109
+ /**
1110
+ * Soft-delete (archive) a creature. The row is hidden from `list()` by
1111
+ * default but recoverable via `restore(id)` or visible under
1112
+ * `listArchived()`. Idempotent — repeating a delete on an already-archived
1113
+ * row is a no-op.
1114
+ */
1115
+ delete(id) {
1116
+ return this.client.request("DELETE", `/v1/creatures/${encodeURIComponent(id)}`);
1117
+ }
1118
+ /**
1119
+ * Hard-delete (permanent) a creature — the row + every R2 asset it
1120
+ * references. Archived rows ONLY: active creatures return 400 `not_archived`.
1121
+ * Call `delete()` first to archive, then `permanentDelete()` to destroy.
1122
+ *
1123
+ * Mirrors the `app_runs` permanent-delete pattern (archive-first) so a
1124
+ * stray SDK / curl caller cannot bypass the studio's archive-first UI
1125
+ * flow.
1126
+ */
1127
+ permanentDelete(id) {
1128
+ return this.client.request("DELETE", `/v1/creatures/${encodeURIComponent(id)}`, {
1129
+ query: { permanent: "true" }
1130
+ });
1131
+ }
1132
+ /**
1133
+ * Un-archive a creature. If the original name now collides (case-
1134
+ * insensitive) with an active row, the server auto-suffixes "(restored)"
1135
+ * and returns the effective name.
1136
+ */
1137
+ restore(id) {
1138
+ return this.client.request("POST", `/v1/creatures/${encodeURIComponent(id)}/restore`);
1139
+ }
1140
+ /**
1141
+ * Fire `POST /v1/generate-creature` to produce one or more candidate main
1142
+ * images. With `count > 1`, all jobs are reserved up-front before any
1143
+ * is enqueued — mid-batch failures roll back atomically.
1144
+ *
1145
+ * When `attachToCreatureId` is set AND `count === 1`, the worker writes
1146
+ * the result directly to the row's `source_image_url`; otherwise you must
1147
+ * call `approveMainImage()` after picking a candidate.
1148
+ */
1149
+ async generate(data) {
1150
+ const res = await this.client.request(
1151
+ "POST",
1152
+ "/v1/generate-creature",
1153
+ { body: data }
1154
+ );
1155
+ const jobIds = res.jobIds ?? (res.jobId ? [res.jobId] : []);
1156
+ return res.jobId ? { jobIds, jobId: res.jobId } : { jobIds };
1157
+ }
1158
+ /**
1159
+ * Fire `POST /v1/generate-creature-asset` to produce a single variant.
1160
+ * When the studio path is set (`attachToCreatureId` + `attachToColumn` +
1161
+ * `attachName`), the worker appends `{ name: attachName, url: <result> }`
1162
+ * to the named JSONB array column on completion.
1163
+ *
1164
+ * Note: `attachToColumn` is REQUIRED for `assetType === "custom"` — the
1165
+ * worker can't infer the bucket from the asset type. For canonical asset
1166
+ * types (`angles` / `poses` / `variations`), the column is derived
1167
+ * automatically by the route.
1168
+ */
1169
+ generateAsset(data) {
1170
+ return this.client.request("POST", "/v1/generate-creature-asset", { body: data });
1171
+ }
1172
+ /**
1173
+ * Fire `POST /v1/generate-creature-motion` to animate the creature's main
1174
+ * image into a motion clip. Image-to-video, single clip per call; the
1175
+ * attach column is hardcoded to `motion_clips` server-side (creatures have a
1176
+ * single motion bucket so the caller doesn't supply `attachToColumn`).
1177
+ * When the studio path is set (`attachToCreatureId` + `attachName`), the
1178
+ * worker appends `{ name: attachName, url: <result> }` to the row's
1179
+ * `motion_clips` column on completion.
1180
+ *
1181
+ * Defaults: `provider` → `"kling-turbo"`, `aspectRatio` → `"1:1"` (set
1182
+ * server-side via `resolveObjectAspectRatio({ assetType: "motion" })`).
1183
+ */
1184
+ generateMotion(data) {
1185
+ return this.client.request("POST", "/v1/generate-creature-motion", { body: data });
1186
+ }
1187
+ /**
1188
+ * Approve a completed `generate-creature` job as the creature's main image.
1189
+ * Sets `source_image_url` and fires the LLM caption (Claude Sonnet vision)
1190
+ * inline. Returns the new main-image URL plus the caption.
1191
+ *
1192
+ * Caption-failure semantics: the route still sends `""` on LLM sub-failure,
1193
+ * but the SDK normalizes `""` → `null` here so `canonicalDescription` carries
1194
+ * the same `string | null` semantics as characters. The main image is still
1195
+ * set; call `recaption()` to retry.
1196
+ *
1197
+ * Optimistic-concurrency: pass `expectedUpdatedAt` to gate the update on
1198
+ * the row's current `updated_at`; on mismatch the route returns 409
1199
+ * `concurrent_modification` carrying the fresh token.
1200
+ */
1201
+ async approveMainImage(id, candidateJobId, expectedUpdatedAt) {
1202
+ const res = await this.client.request(
1203
+ "POST",
1204
+ `/v1/creatures/${encodeURIComponent(id)}/approve-main-image`,
1205
+ { body: { candidateJobId, expectedUpdatedAt } }
1206
+ );
1207
+ return { sourceImageUrl: res.sourceImageUrl, canonicalDescription: res.canonicalDescription || null };
1208
+ }
1209
+ /**
1210
+ * Re-fire the LLM caption against the creature's current main image. 502s
1211
+ * on LLM failure (unlike `approveMainImage` which preserves the side-effect
1212
+ * and returns ""); returns 400 `main_image_required` if no main image is
1213
+ * set yet.
1214
+ *
1215
+ * The route is a pure idempotent retry — it does NOT accept an
1216
+ * `expectedUpdatedAt` token (backend route is idempotent retry, not gated on
1217
+ * optimistic-concurrency).
1218
+ */
1219
+ recaption(id) {
1220
+ return this.client.request(
1221
+ "POST",
1222
+ `/v1/creatures/${encodeURIComponent(id)}/llm-caption`
1223
+ );
1224
+ }
1225
+ };
1226
+
1227
+ // src/resources/pipelines.ts
1228
+ var PipelinesResource = class {
1229
+ constructor(client) {
1230
+ this.client = client;
1231
+ }
1232
+ client;
1233
+ /**
1234
+ * Start a new pipeline (headless film generation) — the programmatic
1235
+ * equivalent of the studio's "Create film". In Auto mode the engine
1236
+ * self-advances to completion; poll {@link get} for status and
1237
+ * {@link getTimeline} for the assembled output. In manual/guided mode, drive
1238
+ * it with {@link pendingApprovals} + {@link approveStage} /
1239
+ * {@link approveSubGate}.
1240
+ *
1241
+ * Requires `pipelines:execute` scope. Returns the new pipeline id.
1242
+ */
1243
+ create(input) {
1244
+ return this.client.request("POST", "/v1/pipelines", { body: input });
1245
+ }
1246
+ /**
1247
+ * Fetch current pipeline state: `status`, `current_stage`, credit counters,
1248
+ * `mode`, and `failure_reason` (set when `status='failed'`). Poll this to
1249
+ * track a headless Auto run to completion. Requires `pipelines:read`.
1250
+ */
1251
+ get(id) {
1252
+ return this.client.request(
1253
+ "GET",
1254
+ `/v1/pipelines/${encodeURIComponent(id)}`
1255
+ );
1256
+ }
1257
+ /** List the caller's pipelines (most recent first). Requires `pipelines:read`. */
1258
+ list() {
1259
+ return this.client.request("GET", "/v1/pipelines");
1260
+ }
1261
+ /**
1262
+ * Cancel a running pipeline. Unspent reserved credits refund. Idempotent on
1263
+ * an already-terminal pipeline. Requires `pipelines:execute`.
1264
+ */
1265
+ cancel(id) {
1266
+ return this.client.request(
1267
+ "POST",
1268
+ `/v1/pipelines/${encodeURIComponent(id)}/cancel`,
1269
+ { body: {} }
1270
+ );
1271
+ }
1272
+ /**
1273
+ * Stages currently `awaiting_approval`. Empty in a clean Auto run (the engine
1274
+ * self-approves); populated in manual/guided mode at each gate. Requires
1275
+ * `pipelines:read`.
1276
+ */
1277
+ pendingApprovals(id) {
1278
+ return this.client.request(
1279
+ "GET",
1280
+ `/v1/pipelines/${encodeURIComponent(id)}/pending-approvals`
1281
+ );
1282
+ }
1283
+ /**
1284
+ * Approve a stage so the engine advances to the next one. An optional `edits`
1285
+ * JSON-Patch is applied to the stage output before approval. Requires
1286
+ * `pipelines:approve`.
1287
+ */
1288
+ approveStage(id, stage, edits) {
1289
+ return this.client.request(
1290
+ "POST",
1291
+ `/v1/pipelines/${encodeURIComponent(id)}/stages/${encodeURIComponent(stage)}/approve`,
1292
+ { body: edits ? { edits } : {} }
1293
+ );
1294
+ }
1295
+ /**
1296
+ * Reject a stage with feedback; the engine re-runs it incorporating the note.
1297
+ * Requires `pipelines:approve`.
1298
+ */
1299
+ rejectStage(id, stage, feedback) {
1300
+ return this.client.request(
1301
+ "POST",
1302
+ `/v1/pipelines/${encodeURIComponent(id)}/stages/${encodeURIComponent(stage)}/reject`,
1303
+ { body: { feedback } }
1304
+ );
1305
+ }
1306
+ /**
1307
+ * Approve a Stage-7 sub-gate (`dialogue_recheck` / `silent_cut`) so the
1308
+ * orchestrator resumes from the next sub-step. Requires `pipelines:approve`.
1309
+ */
1310
+ approveSubGate(id, gate) {
1311
+ return this.client.request(
1312
+ "POST",
1313
+ `/v1/pipelines/${encodeURIComponent(id)}/sub-gates/${encodeURIComponent(gate)}/approve`,
1314
+ { body: {} }
1315
+ );
1316
+ }
1317
+ /**
1318
+ * Read a single stage's `status`, `output`, and `critic_feedback`. Useful for
1319
+ * inspecting the script/plan before approving. Requires `pipelines:read`.
1320
+ */
1321
+ getStage(id, stage) {
1322
+ return this.client.request(
1323
+ "GET",
1324
+ `/v1/pipelines/${encodeURIComponent(id)}/stages/${encodeURIComponent(stage)}`
1325
+ );
1326
+ }
1327
+ /**
1328
+ * Assembled timeline — ordered scene composites + durations + audio URLs +
1329
+ * live animate progress. The output a headless caller renders or hands to a
1330
+ * downstream editor. Requires `pipelines:read`.
1331
+ */
1332
+ getTimeline(id) {
1333
+ return this.client.request(
1334
+ "GET",
1335
+ `/v1/pipelines/${encodeURIComponent(id)}/timeline`
1336
+ );
1337
+ }
1338
+ /**
1339
+ * Branch a completed pipeline into a new pipeline that re-runs from the
1340
+ * given stage. The original pipeline's upstream stages and entities are
1341
+ * cloned into the new pipeline; downstream stages are created by the
1342
+ * orchestrator as it advances.
1343
+ *
1344
+ * Requires `pipelines:execute` scope.
1345
+ * The source pipeline must have `status='completed'`.
1346
+ *
1347
+ * @returns 201 with `{ pipelineId, clonedStages, clonedEntities }`.
1348
+ */
1349
+ branch(id, input) {
1350
+ return this.client.request(
1351
+ "POST",
1352
+ `/v1/pipelines/${encodeURIComponent(id)}/branch`,
1353
+ { body: input }
1354
+ );
1355
+ }
1356
+ /**
1357
+ * Send a chat message to the Showrunner Refinement Director (Guided Mode).
1358
+ * Persists user + assistant turns; returns the assistant's reply and an
1359
+ * optional `proposed_change` the user can `applyChatProposal` to commit.
1360
+ *
1361
+ * Requires `pipelines:approve` scope. The pipeline must have
1362
+ * `mode='guided'` and the stage must be `awaiting_approval`.
1363
+ *
1364
+ * Only the Script stage ships a wired specialist in Phase 1D.2b — the other
1365
+ * chat-enabled stages (`shot_list`, `post_merge`) return 501 until 1D.2d.
1366
+ */
1367
+ chatStage(pipelineId, stage, message) {
1368
+ return this.client.request(
1369
+ "POST",
1370
+ `/v1/pipelines/${encodeURIComponent(pipelineId)}/stages/${encodeURIComponent(stage)}/chat`,
1371
+ { body: { message } }
1372
+ );
1373
+ }
1374
+ /**
1375
+ * Accept a proposed change from a prior assistant turn. Routes through
1376
+ * `applyStageEdit` (validates JSON Patch + per-stage schema +
1377
+ * reference-integrity, inserts a new pipeline_stage_attempts row, CAS-flips
1378
+ * the stage to approved, emits `chat:proposal_applied` SSE).
1379
+ *
1380
+ * Requires `pipelines:approve` scope.
1381
+ *
1382
+ * Returns `{ applied: true, attemptId, newOutput }` on success, or
1383
+ * `{ applied: false, error }` on recoverable failures (the backend already
1384
+ * inserted a follow-up assistant turn with a hint). Hard failures
1385
+ * (`patch_invalid`, `stage_not_awaiting`) throw via the standard error
1386
+ * pipeline (HTTP 409).
1387
+ */
1388
+ applyChatProposal(pipelineId, stage, turnId) {
1389
+ return this.client.request(
1390
+ "POST",
1391
+ `/v1/pipelines/${encodeURIComponent(pipelineId)}/stages/${encodeURIComponent(stage)}/chat/turns/${encodeURIComponent(turnId)}/apply`,
1392
+ { body: {} }
1393
+ );
1394
+ }
1395
+ /**
1396
+ * Fetch the chat history for a stage. Returns an empty array when no turns
1397
+ * exist yet (e.g., stage has not been started or the user hasn't sent any
1398
+ * messages). Used by the frontend chat panel on initial mount; subsequent
1399
+ * updates arrive via SSE (`chat:turn` events).
1400
+ *
1401
+ * Requires `pipelines:read` scope.
1402
+ */
1403
+ getStageChat(pipelineId, stage) {
1404
+ return this.client.request(
1405
+ "GET",
1406
+ `/v1/pipelines/${encodeURIComponent(pipelineId)}/stages/${encodeURIComponent(stage)}/chat`
1407
+ );
1408
+ }
1409
+ };
1410
+
1411
+ // src/resources/reduce.ts
1412
+ var ReduceResource = class {
1413
+ constructor(client) {
1414
+ this.client = client;
1415
+ }
1416
+ client;
1417
+ /**
1418
+ * Run the Reduce (fan-in) node directly — useful for scripted batch
1419
+ * scoring, picking the best of N generations outside a workflow, or
1420
+ * one-shot programmatic merges.
1421
+ *
1422
+ * Throws `NodaroError` on 4xx/5xx responses (e.g. `code: "no_valid_inputs"`
1423
+ * with status 400 when every input is empty / whitespace; the underlying
1424
+ * `EmptyInputError` is mapped to a 400 server-side).
1425
+ */
1426
+ run(input) {
1427
+ return this.client.request("POST", "/v1/reduce", {
1428
+ body: {
1429
+ strategyId: input.strategyId,
1430
+ strategyConfig: input.strategyConfig ?? {},
1431
+ inputs: input.inputs,
1432
+ ...input.workflowId !== void 0 ? { workflowId: input.workflowId } : {}
1433
+ }
1434
+ });
1435
+ }
1436
+ };
1437
+
1438
+ // src/resources/prompt-helper.ts
1439
+ var PromptHelperResource = class {
1440
+ constructor(client) {
1441
+ this.client = client;
1442
+ }
1443
+ client;
1444
+ analyze(input) {
1445
+ return this.client.request("POST", "/v1/prompt-helper/wizard", { body: { action: "analyze", ...input } });
1446
+ }
1447
+ generate(input) {
1448
+ return this.client.request("POST", "/v1/prompt-helper/wizard", { body: { action: "generate", ...input } });
1449
+ }
1450
+ enhance(input) {
1451
+ return this.client.request("POST", "/v1/prompt-helper/wizard", { body: { action: "enhance", ...input } });
1452
+ }
1453
+ };
1454
+
1455
+ // src/resources/voices.ts
1456
+ var VoicesResource = class {
1457
+ constructor(client) {
1458
+ this.client = client;
1459
+ }
1460
+ client;
1461
+ /**
1462
+ * List the premade ElevenLabs voices (`GET /v1/voices`). Falls back to a
1463
+ * curated set server-side when no ElevenLabs API key is configured.
1464
+ */
1465
+ async list() {
1466
+ const res = await this.client.request("GET", "/v1/voices");
1467
+ return res.voices;
1468
+ }
1469
+ /**
1470
+ * Search the shared/community Voice Library (`GET /v1/voices/library`). All
1471
+ * params are optional and forwarded as a querystring; `undefined` / `null` /
1472
+ * empty-string values are omitted so the server defaults apply. `hasMore`
1473
+ * drives "load more" pagination.
1474
+ */
1475
+ searchLibrary(params = {}) {
1476
+ const query = {};
1477
+ for (const [k, v] of Object.entries(params)) {
1478
+ if (v !== void 0 && v !== null && v !== "") query[k] = v;
1479
+ }
1480
+ return this.client.request("GET", "/v1/voices/library", { query });
1481
+ }
1482
+ /**
1483
+ * List the signed-in user's voice clones (`GET /v1/voice-clones`). The route
1484
+ * wraps the rows in `{ voiceClones }`; we unwrap to the bare array.
1485
+ */
1486
+ async listClones() {
1487
+ const res = await this.client.request("GET", "/v1/voice-clones");
1488
+ return res.voiceClones;
1489
+ }
1490
+ /**
1491
+ * Clone a voice from an already-uploaded audio URL
1492
+ * (`POST /v1/voice-clones/from-url`). Costs credits. Returns the create
1493
+ * subset of `VoiceClone` (`elevenlabsVoiceId` is the id to use at
1494
+ * text-to-speech time).
1495
+ */
1496
+ createClone(input) {
1497
+ return this.client.request("POST", "/v1/voice-clones/from-url", { body: input });
1498
+ }
1499
+ /** Delete one of the user's voice clones (`DELETE /v1/voice-clones/:id`). */
1500
+ async deleteClone(id) {
1501
+ await this.client.request("DELETE", `/v1/voice-clones/${encodeURIComponent(id)}`);
1502
+ }
1503
+ /**
1504
+ * Replace the voice in a recording — or in a whole talking video — with a
1505
+ * different voice (`POST /v1/voice-changer`). Pass `audioUrl` to revoice
1506
+ * audio→audio, or `videoUrl` to revoice an entire clip (the server demuxes
1507
+ * the audio, runs speech-to-speech, and remuxes onto the original video,
1508
+ * returning the video plus the new audio track). Exactly one of `audioUrl` /
1509
+ * `videoUrl` is required; when both are sent, video wins. `removeBackgroundNoise`
1510
+ * off keeps the music/SFX bed under the new voice; on yields a clean voice-only
1511
+ * result. Costs credits and runs async — poll `jobs.get(jobId)` for the result
1512
+ * (`output_data.videoUrl` + `output_data.audioUrl` in video mode).
1513
+ */
1514
+ change(input) {
1515
+ return this.client.request("POST", "/v1/voice-changer", { body: input });
1516
+ }
1517
+ /**
1518
+ * Recast each detected speaker in a multi-speaker recording to a different
1519
+ * voice (`POST /v1/voice-changer-pro`). `orderedVoices` maps speaker positions to
1520
+ * voices in detection order — speaker 0 → `orderedVoices[0]`, speaker 1 →
1521
+ * `orderedVoices[1]`, etc. Speakers beyond the end of `orderedVoices` keep
1522
+ * their original voice. Each entry is EITHER a bare voice id (premade name or
1523
+ * ElevenLabs UUID) OR a {@link VoiceChangerProVoice} object carrying per-voice
1524
+ * ElevenLabs speech-to-speech settings (stability / similarityBoost / style /
1525
+ * useSpeakerBoost / `seed`) plus a loudness `volumeMode` (and a manual
1526
+ * `volume`). A per-voice `seed` makes that speaker's recast reproducible.
1527
+ *
1528
+ * Pass `audioUrl` for audio-only recast or `videoUrl` to recast the audio
1529
+ * track of a video clip (the server demuxes, recasts, and remuxes).
1530
+ *
1531
+ * Voice and music are ALWAYS separated first — ElevenLabs only ever sees the
1532
+ * isolated vocal stem, never the music bed. `preserveBackground` (default
1533
+ * `true`) only controls whether that music/instrumental stem is mixed back
1534
+ * under the new voices; set it `false` for a clean voice-only result.
1535
+ * `separationQuality` selects the demucs model used for that split: `"fast"`
1536
+ * (default, htdemucs — preserves more of the voice) or `"best"` (htdemucs_ft —
1537
+ * finer separation). `removeBackgroundNoise` additionally denoises the result.
1538
+ * `musicVolumeMode` sets the level of that preserved background (only relevant
1539
+ * when `preserveBackground` is on): `"match"` (default) keeps the original
1540
+ * level, `"normalize"` loudnorms it, `"manual"` uses `musicVolume`%.
1541
+ * `voiceFx` applies a reverb/echo to the COMBINED recast voices BEFORE the
1542
+ * background is mixed back in (effect sits on the voices, not the music bed).
1543
+ *
1544
+ * Cloud-only — costs credits and runs async; poll `jobs.get(jobId)` for the
1545
+ * result (`output_data.videoUrl` + `output_data.audioUrl` in video mode).
1546
+ */
1547
+ recast(input) {
1548
+ return this.client.request("POST", "/v1/voice-changer-pro", { body: input });
1549
+ }
1550
+ };
1551
+
1552
+ // src/resources/credits.ts
1553
+ var MODEL_COSTS_LIMIT = 50;
1554
+ var CreditsResource = class {
1555
+ constructor(client) {
1556
+ this.client = client;
1557
+ }
1558
+ client;
1559
+ /**
1560
+ * `GET /v1/user/credits` → the authenticated user's credit balance and tier
1561
+ * info. Throws `UnauthorizedError` (401) when signed out, and the SDK's
1562
+ * other typed errors on the usual statuses.
1563
+ */
1564
+ async balance() {
1565
+ const res = await this.client.request(
1566
+ "GET",
1567
+ "/v1/user/credits"
1568
+ );
1569
+ return res.data;
1570
+ }
1571
+ /**
1572
+ * `POST /v1/credits/model-costs` → per-identifier credit cost, for editor
1573
+ * cost previews. Capped at the first {@link MODEL_COSTS_LIMIT} identifiers
1574
+ * (the route's request limit). Preserves the `{ data, missing, errors }`
1575
+ * fault-isolation shape verbatim (see {@link ModelCostsResult}).
1576
+ */
1577
+ modelCosts(ids) {
1578
+ return this.client.request("POST", "/v1/credits/model-costs", {
1579
+ body: { models: ids.slice(0, MODEL_COSTS_LIMIT) }
1580
+ });
1581
+ }
1582
+ // NOTE: no `estimate(...)` helper. The backend exposes
1583
+ // `POST /v1/credits/estimate-workflow` (body `{ nodes }` → `{ data: {
1584
+ // totalCredits, nodeCount } }`), but no consumer has a settled shape for it
1585
+ // yet (studio pre-checks `balance >= Σ modelCosts` client-side rather than
1586
+ // calling an estimate endpoint). Adding it now would be inventing an API
1587
+ // surface ahead of a real caller, so it's deliberately omitted — add it when
1588
+ // a consumer needs it, shaped to that need.
1589
+ };
1590
+
1591
+ // src/resources/uploads.ts
1592
+ var UploadsResource = class {
1593
+ constructor(client) {
1594
+ this.client = client;
1595
+ }
1596
+ client;
1597
+ /**
1598
+ * Upload one file (`POST /v1/upload`, multipart — the file rides the `file`
1599
+ * field). The SDK's `request` detects the `FormData` body and lets the
1600
+ * runtime set the multipart boundary. Returns the persisted asset's public
1601
+ * URL + storage metadata (unwraps the `{ data }` envelope). Throws
1602
+ * `StorageExceededError` (413) over the storage cap and the SDK's other typed
1603
+ * errors on the usual statuses.
1604
+ */
1605
+ async upload(file) {
1606
+ const form = new FormData();
1607
+ form.append("file", file);
1608
+ const res = await this.client.request(
1609
+ "POST",
1610
+ "/v1/upload",
1611
+ { body: form }
1612
+ );
1613
+ return res.data;
1614
+ }
1615
+ };
1616
+
1617
+ // src/resources/library.ts
1618
+ var LibraryResource = class {
1619
+ constructor(client) {
1620
+ this.client = client;
1621
+ }
1622
+ client;
1623
+ /**
1624
+ * `GET /v1/library` → a page of the caller's media assets (newest first) plus
1625
+ * a `nextCursor`. Pass the returned `nextCursor` back as `cursor` for the next
1626
+ * page. Filter by `type` and a filename `search`; `owned: true` returns the
1627
+ * full Storage set (uploads + generations), the default only library-saved +
1628
+ * shared items.
1629
+ */
1630
+ list(params = {}) {
1631
+ const qs = new URLSearchParams();
1632
+ if (params.type) qs.set("type", params.type);
1633
+ if (params.search) qs.set("search", params.search);
1634
+ if (params.limit !== void 0) qs.set("limit", String(params.limit));
1635
+ if (params.cursor) qs.set("cursor", params.cursor);
1636
+ if (params.owned !== void 0) qs.set("owned", String(params.owned));
1637
+ const query = qs.toString();
1638
+ return this.client.request("GET", `/v1/library${query ? `?${query}` : ""}`);
1639
+ }
1640
+ };
1641
+
1642
+ // src/resources/node-presets.ts
1643
+ var PresetsResource = class {
1644
+ constructor(client) {
1645
+ this.client = client;
1646
+ }
1647
+ client;
1648
+ /**
1649
+ * `GET /v1/node-presets` → your custom presets, newest first. Pass `nodeType`
1650
+ * (e.g. `"generate-image"`) to filter to one node type.
1651
+ */
1652
+ async list(nodeType) {
1653
+ const qs = nodeType ? `?nodeType=${encodeURIComponent(nodeType)}` : "";
1654
+ const res = await this.client.request("GET", `/v1/node-presets${qs}`);
1655
+ return res.data;
1656
+ }
1657
+ /**
1658
+ * `GET /v1/node-preset-groups` → your preset folders/sections, in display
1659
+ * order. Pass `nodeType` to filter to one node type.
1660
+ */
1661
+ async listGroups(nodeType) {
1662
+ const qs = nodeType ? `?nodeType=${encodeURIComponent(nodeType)}` : "";
1663
+ const res = await this.client.request("GET", `/v1/node-preset-groups${qs}`);
1664
+ return res.data;
1665
+ }
1666
+ /**
1667
+ * `GET /v1/node-presets/factory` → the built-in catalog for `nodeType`. These
1668
+ * ship with the app (no account needed to exist), so they're a good starting
1669
+ * point for "what configs are available".
1670
+ */
1671
+ listFactory(nodeType) {
1672
+ return this.client.request(
1673
+ "GET",
1674
+ `/v1/node-presets/factory?nodeType=${encodeURIComponent(nodeType)}`
1675
+ );
1676
+ }
1677
+ };
1678
+
1679
+ // src/resources/picker-catalogs.ts
1680
+ var PickerCatalogsResource = class {
1681
+ constructor(client) {
1682
+ this.client = client;
1683
+ }
1684
+ client;
1685
+ /** List every parameter-picker node type + its option count. Cached publicly 5 min. */
1686
+ list() {
1687
+ return this.client.request("GET", "/v1/picker-catalogs");
1688
+ }
1689
+ /** Get one picker's catalog of valid values. */
1690
+ get(nodeType, opts = {}) {
1691
+ const qs = new URLSearchParams();
1692
+ if (opts.detail) qs.set("detail", opts.detail);
1693
+ if (opts.category) qs.set("category", opts.category);
1694
+ if (opts.field) qs.set("field", opts.field);
1695
+ const query = qs.toString();
1696
+ return this.client.request(
1697
+ "GET",
1698
+ `/v1/picker-catalogs/${encodeURIComponent(nodeType)}${query ? `?${query}` : ""}`
1699
+ );
1700
+ }
1701
+ };
1702
+
1703
+ // src/resources/community.ts
1704
+ var CommunityResource = class {
1705
+ constructor(client) {
1706
+ this.client = client;
1707
+ }
1708
+ client;
1709
+ /**
1710
+ * `GET /v1/community/browse` → a page of public listings plus a `nextCursor`.
1711
+ * Pass the returned `nextCursor` back as `cursor` to fetch the next page.
1712
+ */
1713
+ browse(params = {}) {
1714
+ const qs = new URLSearchParams();
1715
+ if (params.entityType) qs.set("entityType", params.entityType);
1716
+ if (params.q) qs.set("q", params.q);
1717
+ if (params.category) qs.set("category", params.category);
1718
+ if (params.sort) qs.set("sort", params.sort);
1719
+ if (params.cursor) qs.set("cursor", params.cursor);
1720
+ if (params.limit !== void 0) qs.set("limit", String(params.limit));
1721
+ const query = qs.toString();
1722
+ return this.client.request(
1723
+ "GET",
1724
+ `/v1/community/browse${query ? `?${query}` : ""}`
1725
+ );
1726
+ }
1727
+ /** `GET /v1/community/detail/:slug` → a single listing by its slug. */
1728
+ get(slug) {
1729
+ return this.client.request(
1730
+ "GET",
1731
+ `/v1/community/detail/${encodeURIComponent(slug)}`
1732
+ );
1733
+ }
1734
+ /**
1735
+ * `GET /v1/community/detail/:slug/full` → the full read-only detail (card
1736
+ * identity + the stored public snapshot). Like {@link get}, but includes the
1737
+ * snapshot asset/voice/text blob needed to render the full cross-user view.
1738
+ */
1739
+ getFull(slug) {
1740
+ return this.client.request(
1741
+ "GET",
1742
+ `/v1/community/detail/${encodeURIComponent(slug)}/full`
1743
+ );
1744
+ }
1745
+ /** `GET /v1/community/favorites` → the listings you've favorited. */
1746
+ favorites() {
1747
+ return this.client.request("GET", "/v1/community/favorites");
1748
+ }
1749
+ /**
1750
+ * `POST /v1/community/listings/:id/clone` → copy a listing into your library.
1751
+ * Returns the new asset's `entityType` and `id`. Requires the `assets:write`
1752
+ * scope when called with an OAuth app token.
1753
+ */
1754
+ clone(id, entityType) {
1755
+ return this.client.request(
1756
+ "POST",
1757
+ `/v1/community/listings/${encodeURIComponent(id)}/clone`,
1758
+ { body: { entityType } }
1759
+ );
1760
+ }
1761
+ /**
1762
+ * `POST /v1/community/listings/:id/favorite` → toggle a favorite. Returns the
1763
+ * resulting `favorited` state (`true` after adding, `false` after removing).
1764
+ */
1765
+ favorite(id) {
1766
+ return this.client.request(
1767
+ "POST",
1768
+ `/v1/community/listings/${encodeURIComponent(id)}/favorite`
1769
+ );
1770
+ }
1771
+ /**
1772
+ * `POST /v1/community/listings/:id/report` → flag a listing for moderation.
1773
+ * `reason` must be one of {@link CommunityReportReason}.
1774
+ */
1775
+ report(id, reason) {
1776
+ return this.client.request(
1777
+ "POST",
1778
+ `/v1/community/listings/${encodeURIComponent(id)}/report`,
1779
+ { body: { reason } }
1780
+ );
1781
+ }
1782
+ /**
1783
+ * `POST /v1/admin/community/:entityType/:id/publish` → share one of YOUR
1784
+ * entities to the community, returning the new listing's `slug` + `id`.
1785
+ *
1786
+ * **Requires an admin token** (the route is `requireAdmin`) AND the caller
1787
+ * must own the source entity. Personal/OAuth tokens without admin role get a
1788
+ * 401. For `character` listings, `params.likenessAttestation` must be `true`.
1789
+ */
1790
+ publish(entityType, entityId, params) {
1791
+ return this.client.request(
1792
+ "POST",
1793
+ `/v1/admin/community/${encodeURIComponent(entityType)}/${encodeURIComponent(entityId)}/publish`,
1794
+ { body: params }
1795
+ );
1796
+ }
1797
+ /**
1798
+ * `DELETE /v1/admin/community/listings/:id` → unshare (deactivate) a listing
1799
+ * you published. **Requires an admin token** (the route is `requireAdmin`).
1800
+ */
1801
+ unpublish(listingId) {
1802
+ return this.client.request(
1803
+ "DELETE",
1804
+ `/v1/admin/community/listings/${encodeURIComponent(listingId)}`
1805
+ );
1806
+ }
1807
+ /**
1808
+ * `GET /v1/admin/community/by-source/:entityType/:sourceId` → look up YOUR
1809
+ * existing listing (if any) for a source entity. Returns `{ data: null }`
1810
+ * when the entity hasn't been shared. **Requires an admin token** (the route
1811
+ * is `requireAdmin`); only returns listings created by the caller.
1812
+ */
1813
+ sharedListing(entityType, sourceId) {
1814
+ return this.client.request(
1815
+ "GET",
1816
+ `/v1/admin/community/by-source/${encodeURIComponent(entityType)}/${encodeURIComponent(sourceId)}`
1817
+ );
1818
+ }
1819
+ };
1820
+
1821
+ // src/client.ts
1822
+ var NodaroClient = class {
1823
+ baseUrl;
1824
+ auth;
1825
+ timeoutMs;
1826
+ fetchOverride;
1827
+ /**
1828
+ * Resolved lazily so consumers can swap `globalThis.fetch` after the
1829
+ * client has been constructed (e.g. test mocks). Always rebound to the
1830
+ * global object — native fetch throws "Illegal invocation" when its
1831
+ * `this` is anything else.
1832
+ */
1833
+ get fetch() {
1834
+ return this.fetchOverride ?? globalThis.fetch.bind(globalThis);
1835
+ }
1836
+ workflows;
1837
+ projects;
1838
+ jobs;
1839
+ executions;
1840
+ nodes;
1841
+ developerApps;
1842
+ oauth;
1843
+ apps;
1844
+ characters;
1845
+ locations;
1846
+ objects;
1847
+ creatures;
1848
+ pipelines;
1849
+ reduce;
1850
+ promptHelper;
1851
+ voices;
1852
+ credits;
1853
+ uploads;
1854
+ library;
1855
+ presets;
1856
+ pickerCatalogs;
1857
+ community;
1858
+ constructor(opts) {
1859
+ this.baseUrl = opts.baseUrl.replace(/\/$/, "");
1860
+ this.auth = opts.auth;
1861
+ this.fetchOverride = opts.fetch;
1862
+ this.timeoutMs = opts.timeoutMs ?? 6e4;
1863
+ this.workflows = new WorkflowsResource(this);
1864
+ this.projects = new ProjectsResource(this);
1865
+ this.jobs = new JobsResource(this);
1866
+ this.executions = new ExecutionsResource(this);
1867
+ this.nodes = new NodesResource(this);
1868
+ this.developerApps = new DeveloperAppsResource(this);
1869
+ this.oauth = new OAuthResource(this);
1870
+ this.apps = new AppsResource(this);
1871
+ this.characters = new CharactersResource(this);
1872
+ this.locations = new LocationsResource(this);
1873
+ this.objects = new ObjectsResource(this);
1874
+ this.creatures = new CreaturesResource(this);
1875
+ this.pipelines = new PipelinesResource(this);
1876
+ this.reduce = new ReduceResource(this);
1877
+ this.promptHelper = new PromptHelperResource(this);
1878
+ this.voices = new VoicesResource(this);
1879
+ this.credits = new CreditsResource(this);
1880
+ this.uploads = new UploadsResource(this);
1881
+ this.library = new LibraryResource(this);
1882
+ this.presets = new PresetsResource(this);
1883
+ this.pickerCatalogs = new PickerCatalogsResource(this);
1884
+ this.community = new CommunityResource(this);
1885
+ }
1886
+ async request(method, path, options = {}) {
1887
+ const url = this.buildUrl(path, options.query);
1888
+ const token = await this.auth.getToken();
1889
+ const isFormData = typeof FormData !== "undefined" && options.body instanceof FormData;
1890
+ const headers = {
1891
+ ...isFormData ? {} : { "Content-Type": "application/json" },
1892
+ ...options.headers ?? {}
1893
+ };
1894
+ if (token) headers["Authorization"] = `Bearer ${token}`;
1895
+ const ac = new AbortController();
1896
+ const timeoutId = setTimeout(() => ac.abort(), this.timeoutMs);
1897
+ if (options.signal) {
1898
+ options.signal.addEventListener("abort", () => ac.abort(), { once: true });
1899
+ }
1900
+ try {
1901
+ const res = await this.fetch(url, {
1902
+ method,
1903
+ headers,
1904
+ body: options.body === void 0 ? void 0 : isFormData ? options.body : JSON.stringify(options.body),
1905
+ signal: ac.signal
1906
+ });
1907
+ if (!res.ok) {
1908
+ let errBody = {};
1909
+ try {
1910
+ errBody = await res.json();
1911
+ } catch {
1912
+ }
1913
+ throwFromResponse(res.status, errBody);
1914
+ }
1915
+ if (res.status === 204) return void 0;
1916
+ return await res.json();
1917
+ } finally {
1918
+ clearTimeout(timeoutId);
1919
+ }
1920
+ }
1921
+ /**
1922
+ * `GET /v1/me` → the authenticated user's identity (see {@link UserIdentity}).
1923
+ * Unwraps the `{ data }` envelope. Throws `UnauthorizedError` (401) when the
1924
+ * token is missing/invalid, and the SDK's other typed errors as usual.
1925
+ */
1926
+ async me() {
1927
+ const res = await this.request("GET", "/v1/me");
1928
+ return res.data;
1929
+ }
1930
+ buildUrl(path, query) {
1931
+ const base = this.baseUrl || (typeof window !== "undefined" ? window.location.origin : "http://placeholder");
1932
+ const url = new URL(path, base);
1933
+ const fullUrl = this.baseUrl ? url.toString() : url.pathname + url.search;
1934
+ if (query) {
1935
+ const u = new URL(this.baseUrl ? fullUrl : fullUrl, base);
1936
+ for (const [k, v] of Object.entries(query)) {
1937
+ if (v !== void 0) u.searchParams.set(k, String(v));
1938
+ }
1939
+ return this.baseUrl ? u.toString() : u.pathname + u.search;
1940
+ }
1941
+ return fullUrl;
1942
+ }
1943
+ };
1944
+ function createClient(opts) {
1945
+ return new NodaroClient(opts);
1946
+ }
1947
+
1948
+ // src/auth.ts
1949
+ var StaticTokenAuth = class {
1950
+ constructor(token) {
1951
+ this.token = token;
1952
+ }
1953
+ token;
1954
+ async getToken() {
1955
+ return this.token;
1956
+ }
1957
+ };
1958
+ var CallbackAuth = class {
1959
+ constructor(fn) {
1960
+ this.fn = fn;
1961
+ }
1962
+ fn;
1963
+ async getToken() {
1964
+ return this.fn();
1965
+ }
1966
+ };
1967
+ function supabaseAuth(supabase) {
1968
+ return {
1969
+ async getToken() {
1970
+ const { data } = await supabase.auth.getSession();
1971
+ return data.session?.access_token ?? null;
1972
+ }
1973
+ };
1974
+ }
1975
+
1976
+ Object.defineProperty(exports, "PEOPLE", {
1977
+ enumerable: true,
1978
+ get: function () { return prompts.PEOPLE; }
1979
+ });
1980
+ Object.defineProperty(exports, "PERSON_DIMENSION_LABELS", {
1981
+ enumerable: true,
1982
+ get: function () { return prompts.PERSON_DIMENSION_LABELS; }
1983
+ });
1984
+ Object.defineProperty(exports, "PERSON_DIMENSION_ORDER", {
1985
+ enumerable: true,
1986
+ get: function () { return prompts.PERSON_DIMENSION_ORDER; }
1987
+ });
1988
+ Object.defineProperty(exports, "buildPersonHints", {
1989
+ enumerable: true,
1990
+ get: function () { return prompts.buildPersonHints; }
1991
+ });
1992
+ Object.defineProperty(exports, "CHARACTER_ASPECT_DEFAULTS", {
1993
+ enumerable: true,
1994
+ get: function () { return shared.CHARACTER_ASPECT_DEFAULTS; }
1995
+ });
1996
+ Object.defineProperty(exports, "CHARACTER_ASPECT_OPTIONS", {
1997
+ enumerable: true,
1998
+ get: function () { return shared.CHARACTER_ASPECT_OPTIONS; }
1999
+ });
2000
+ Object.defineProperty(exports, "CHARACTER_STYLES", {
2001
+ enumerable: true,
2002
+ get: function () { return shared.CHARACTER_STYLES; }
2003
+ });
2004
+ Object.defineProperty(exports, "CREATURE_ASPECT_DEFAULTS", {
2005
+ enumerable: true,
2006
+ get: function () { return shared.OBJECT_ASPECT_DEFAULTS; }
2007
+ });
2008
+ Object.defineProperty(exports, "CREATURE_ASPECT_OPTIONS", {
2009
+ enumerable: true,
2010
+ get: function () { return shared.OBJECT_ASPECT_OPTIONS; }
2011
+ });
2012
+ Object.defineProperty(exports, "CREATURE_ATTACH_COLUMNS", {
2013
+ enumerable: true,
2014
+ get: function () { return shared.CREATURE_ATTACH_COLUMNS; }
2015
+ });
2016
+ Object.defineProperty(exports, "LOCATION_ASSET_TYPES", {
2017
+ enumerable: true,
2018
+ get: function () { return shared.LOCATION_ASSET_TYPES; }
2019
+ });
2020
+ Object.defineProperty(exports, "LOCATION_ATTACH_COLUMNS", {
2021
+ enumerable: true,
2022
+ get: function () { return shared.LOCATION_ATTACH_COLUMNS; }
2023
+ });
2024
+ Object.defineProperty(exports, "OBJECT_ASPECT_DEFAULTS", {
2025
+ enumerable: true,
2026
+ get: function () { return shared.OBJECT_ASPECT_DEFAULTS; }
2027
+ });
2028
+ Object.defineProperty(exports, "OBJECT_ASPECT_OPTIONS", {
2029
+ enumerable: true,
2030
+ get: function () { return shared.OBJECT_ASPECT_OPTIONS; }
2031
+ });
2032
+ Object.defineProperty(exports, "OBJECT_ASSET_TYPES", {
2033
+ enumerable: true,
2034
+ get: function () { return shared.OBJECT_ASSET_TYPES; }
2035
+ });
2036
+ Object.defineProperty(exports, "OBJECT_ATTACH_COLUMNS", {
2037
+ enumerable: true,
2038
+ get: function () { return shared.OBJECT_ATTACH_COLUMNS; }
2039
+ });
2040
+ Object.defineProperty(exports, "SURROUND_DIRECTIONS", {
2041
+ enumerable: true,
2042
+ get: function () { return shared.SURROUND_DIRECTIONS; }
2043
+ });
2044
+ exports.AppsResource = AppsResource;
2045
+ exports.CREATURE_ASSET_TYPES = CREATURE_ASSET_TYPES;
2046
+ exports.CallbackAuth = CallbackAuth;
2047
+ exports.CharactersResource = CharactersResource;
2048
+ exports.CommunityResource = CommunityResource;
2049
+ exports.CreaturesResource = CreaturesResource;
2050
+ exports.CreditsResource = CreditsResource;
2051
+ exports.DeveloperAppsResource = DeveloperAppsResource;
2052
+ exports.ExecutionsResource = ExecutionsResource;
2053
+ exports.ForbiddenError = ForbiddenError;
2054
+ exports.InsufficientCreditsError = InsufficientCreditsError;
2055
+ exports.JobAbortedError = JobAbortedError;
2056
+ exports.JobFailedError = JobFailedError;
2057
+ exports.JobTimeoutError = JobTimeoutError;
2058
+ exports.JobsResource = JobsResource;
2059
+ exports.LibraryResource = LibraryResource;
2060
+ exports.LocationsResource = LocationsResource;
2061
+ exports.NodaroClient = NodaroClient;
2062
+ exports.NodaroError = NodaroError;
2063
+ exports.NodesResource = NodesResource;
2064
+ exports.NotFoundError = NotFoundError;
2065
+ exports.OAuthResource = OAuthResource;
2066
+ exports.ObjectsResource = ObjectsResource;
2067
+ exports.PickerCatalogsResource = PickerCatalogsResource;
2068
+ exports.PipelinesResource = PipelinesResource;
2069
+ exports.PresetsResource = PresetsResource;
2070
+ exports.ProjectsResource = ProjectsResource;
2071
+ exports.PromptHelperResource = PromptHelperResource;
2072
+ exports.RateLimitedError = RateLimitedError;
2073
+ exports.ReduceResource = ReduceResource;
2074
+ exports.StaticTokenAuth = StaticTokenAuth;
2075
+ exports.StorageExceededError = StorageExceededError;
2076
+ exports.UnauthorizedError = UnauthorizedError;
2077
+ exports.UploadsResource = UploadsResource;
2078
+ exports.VoicesResource = VoicesResource;
2079
+ exports.WorkflowsResource = WorkflowsResource;
2080
+ exports.buildPersonSeedPrompt = buildPersonSeedPrompt;
2081
+ exports.createClient = createClient;
2082
+ exports.supabaseAuth = supabaseAuth;
2083
+ exports.throwFromResponse = throwFromResponse;
2084
+ //# sourceMappingURL=index.cjs.map
2085
+ //# sourceMappingURL=index.cjs.map