@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.
@@ -0,0 +1,4047 @@
1
+ import { GenericNode, GenericEdge, WorkflowExport, ConnectedReference, TtsProvider, EntityStyle, CharacterAspectRatio, CharacterAttachColumn, LocationAssetType, LocationAttachColumn, SurroundDirection, ObjectAssetType, ObjectAttachColumn, ObjectAspectRatio, CreatureAttachColumn, PipelineInput, PipelineStatus, PipelineMode, PipelineStageName, SubGateName, ChatEnabledStage, ProposedChange, ReduceStrategyId, ReduceMeta, Voice, VoiceLibraryParams, VoiceLibraryResponse, VoiceClone, AudioFxPreset, BrowseCommunityParams, BrowseCommunityResult, CommunityCard, CommunityFullDetail, CommunityEntityType, CloneListingResult, FavoriteListingResult, CommunityReportReason, ReportListingResult, PublishListingParams, PublishListingResult, SharedListing } from '@nodaro/shared';
2
+ export { BrowseCommunityParams, BrowseCommunityResult, CHARACTER_ASPECT_DEFAULTS, CHARACTER_ASPECT_OPTIONS, CHARACTER_STYLES, OBJECT_ASPECT_DEFAULTS as CREATURE_ASPECT_DEFAULTS, OBJECT_ASPECT_OPTIONS as CREATURE_ASPECT_OPTIONS, CREATURE_ATTACH_COLUMNS, CharacterAspectRatio, CloneListingResult, CommunityCard, CommunityEntityType, CommunityReportReason, CommunitySort, ConnectedReference, ObjectAspectRatio as CreatureAspectRatio, CreatureAttachColumn, EntityStyle, FavoriteListingResult, GenericEdge, GenericNode, LOCATION_ASSET_TYPES, LOCATION_ATTACH_COLUMNS, LocationAssetType, LocationAttachColumn, OBJECT_ASPECT_DEFAULTS, OBJECT_ASPECT_OPTIONS, OBJECT_ASSET_TYPES, OBJECT_ATTACH_COLUMNS, ObjectAspectRatio, ObjectAssetType, ObjectAttachColumn, PipelineStageName, PublishListingParams, PublishListingResult, ReduceMeta, ReduceStrategyId, ReportListingResult, SURROUND_DIRECTIONS, SharedListing, SharedVoice, SurroundDirection, Voice, VoiceClone, VoiceLibraryParams, VoiceLibraryResponse, WorkflowExport, WorkflowExportCharacter, WorkflowExportLocation, WorkflowExportObject } from '@nodaro/shared';
3
+ import { PersonValue, WizardNodeContext, WizardQuestion, WizardSelection, RecommendedModel, FactoryPreset } from '@nodaro/prompts';
4
+ export { PEOPLE, PERSON_DIMENSION_LABELS, PERSON_DIMENSION_ORDER, PersonValue, RecommendedModel, WizardNodeContext, WizardOption, WizardQuestion, WizardSelection, buildPersonHints } from '@nodaro/prompts';
5
+
6
+ /**
7
+ * Auth provides the token used for `Authorization: Bearer <token>` on each request.
8
+ * Implementations:
9
+ * - StaticTokenAuth — fixed string (server-side, OAuth access token, API token)
10
+ * - CallbackAuth — caller-supplied async function (BYO logic)
11
+ * - supabaseAuth — pulls JWT from a Supabase client live (browser frontends)
12
+ */
13
+ interface Auth {
14
+ /** Returns the current Bearer token, or null if not authenticated. */
15
+ getToken(): Promise<string | null>;
16
+ }
17
+ declare class StaticTokenAuth implements Auth {
18
+ private token;
19
+ constructor(token: string);
20
+ getToken(): Promise<string>;
21
+ }
22
+ declare class CallbackAuth implements Auth {
23
+ private fn;
24
+ constructor(fn: () => string | null | Promise<string | null>);
25
+ getToken(): Promise<string | null>;
26
+ }
27
+ interface SupabaseLikeClient {
28
+ auth: {
29
+ getSession(): Promise<{
30
+ data: {
31
+ session: {
32
+ access_token: string;
33
+ } | null;
34
+ };
35
+ }>;
36
+ };
37
+ }
38
+ /** Pulls a JWT from a Supabase v2 client. Caller supplies their own supabase. */
39
+ declare function supabaseAuth(supabase: SupabaseLikeClient): Auth;
40
+
41
+ /**
42
+ * Workflow metadata + (when fetched as a single record) full nodes/edges/settings.
43
+ *
44
+ * The list endpoint returns metadata only; `get`, `create`, and `update` return the
45
+ * full record. `nodes`, `edges`, `settings`, and `sourcePrompt` are present only on
46
+ * full records and omitted in list responses.
47
+ */
48
+ interface Workflow {
49
+ id: string;
50
+ projectId: string | null;
51
+ userId: string;
52
+ name: string;
53
+ description?: string | null;
54
+ folderId?: string | null;
55
+ isTemplate?: boolean;
56
+ version?: number;
57
+ thumbnailUrl?: string | null;
58
+ nodes?: GenericNode[];
59
+ edges?: GenericEdge[];
60
+ settings?: Record<string, unknown>;
61
+ sourcePrompt?: string | null;
62
+ createdAt: string;
63
+ updatedAt: string;
64
+ }
65
+ interface ListWorkflowsParams {
66
+ /** Required — list endpoint is `/v1/projects/:projectId/workflows`. */
67
+ projectId: string;
68
+ }
69
+ interface CreateWorkflowInput {
70
+ /** Required — workflow is created under this project. */
71
+ projectId: string;
72
+ name: string;
73
+ description?: string;
74
+ folderId?: string | null;
75
+ nodes?: GenericNode[];
76
+ edges?: GenericEdge[];
77
+ settings?: Record<string, unknown>;
78
+ sourcePrompt?: string;
79
+ }
80
+ interface UpdateWorkflowInput {
81
+ name?: string;
82
+ description?: string;
83
+ folderId?: string | null;
84
+ nodes?: GenericNode[];
85
+ edges?: GenericEdge[];
86
+ settings?: Record<string, unknown>;
87
+ sourcePrompt?: string;
88
+ thumbnailUrl?: string | null;
89
+ }
90
+ interface RunWorkflowParams {
91
+ /** Optional subset of node IDs to execute. Omit to run the full workflow. */
92
+ nodeIds?: string[];
93
+ }
94
+ interface RunWorkflowResult {
95
+ executionId: string;
96
+ status: "pending" | "running";
97
+ }
98
+ declare class WorkflowsResource {
99
+ private client;
100
+ constructor(client: NodaroClient);
101
+ /** List workflows for a project. Returns metadata only — `nodes`/`edges` are not included. */
102
+ list(params: ListWorkflowsParams): Promise<{
103
+ data: Workflow[];
104
+ }>;
105
+ /** Get a workflow including its full nodes/edges/settings. */
106
+ get(id: string): Promise<{
107
+ data: Workflow;
108
+ }>;
109
+ /**
110
+ * Get a PUBLICLY-SHARED workflow by id (`GET /v1/public/workflows/:id`) — the
111
+ * unauthenticated share-by-link read. Returns the workflow's nodes/edges/
112
+ * settings ONLY when it's opted into sharing server-side (`settings.studio.shared
113
+ * === true`); otherwise the route 404s (→ `NotFoundError`). No auth required —
114
+ * a share viewer has no session; the SDK omits the bearer when no token exists.
115
+ */
116
+ getPublic(id: string): Promise<{
117
+ data: Workflow;
118
+ }>;
119
+ /**
120
+ * Create a workflow under a project. Returns the full record.
121
+ * NOTE: server route is `POST /v1/projects/:projectId/workflows`.
122
+ */
123
+ create(input: CreateWorkflowInput): Promise<{
124
+ data: Workflow;
125
+ }>;
126
+ /** Patch a workflow. Returns the full updated record. */
127
+ update(id: string, input: UpdateWorkflowInput): Promise<{
128
+ data: Workflow;
129
+ }>;
130
+ /** Delete a workflow. Returns `{ success: true }`. */
131
+ delete(id: string): Promise<{
132
+ success: true;
133
+ }>;
134
+ /**
135
+ * Run a workflow. Returns the executionId for polling via
136
+ * `client.executions.get(executionId)`.
137
+ */
138
+ run(id: string, params?: RunWorkflowParams): Promise<RunWorkflowResult>;
139
+ /**
140
+ * Export a workflow as a portable JSON bundle.
141
+ * Pass `opts.assets = true` to include character/object/location entity data.
142
+ */
143
+ export(workflowId: string, opts?: {
144
+ assets?: boolean;
145
+ }): Promise<{
146
+ data: WorkflowExport;
147
+ }>;
148
+ /**
149
+ * Import a `WorkflowExport` bundle into the specified project.
150
+ * Re-creates any bundled assets (characters, objects, locations) under your account.
151
+ */
152
+ import(input: WorkflowExport & {
153
+ projectId: string;
154
+ }): Promise<{
155
+ data: Workflow;
156
+ }>;
157
+ }
158
+
159
+ interface Project {
160
+ id: string;
161
+ userId: string;
162
+ name: string;
163
+ description?: string | null;
164
+ settings?: Record<string, unknown>;
165
+ createdAt: string;
166
+ updatedAt: string;
167
+ }
168
+ interface CreateProjectInput {
169
+ name: string;
170
+ description?: string;
171
+ settings?: Record<string, unknown>;
172
+ }
173
+ interface UpdateProjectInput {
174
+ name?: string;
175
+ description?: string;
176
+ settings?: Record<string, unknown>;
177
+ }
178
+ declare class ProjectsResource {
179
+ private client;
180
+ constructor(client: NodaroClient);
181
+ /** List the authenticated user's projects. */
182
+ list(): Promise<{
183
+ data: Project[];
184
+ }>;
185
+ /** Get a project by ID. */
186
+ get(id: string): Promise<{
187
+ data: Project;
188
+ }>;
189
+ /** Create a new project. */
190
+ create(input: CreateProjectInput): Promise<{
191
+ data: Project;
192
+ }>;
193
+ /** Update a project. At least one field must be provided. */
194
+ update(id: string, input: UpdateProjectInput): Promise<{
195
+ data: Project;
196
+ }>;
197
+ /** Delete a project. Returns `{ success: true }`. */
198
+ delete(id: string): Promise<{
199
+ success: true;
200
+ }>;
201
+ }
202
+
203
+ type JobStatus = "pending" | "queued" | "processing" | "completed" | "failed" | "cancelled";
204
+ /**
205
+ * Job record returned to non-admin SDK consumers. Field names are snake_case
206
+ * to match the wire format (per the OpenAPI fix in Phase 1).
207
+ *
208
+ * Sensitive fields stripped server-side for non-admin callers:
209
+ * `provider`, `provider_cost`, `display_cost`, `credits_actual`. USD
210
+ * pricing is admin-only across api/sdk/mcp — non-admin consumers see
211
+ * only the `credits` abstraction.
212
+ */
213
+ interface Job {
214
+ id: string;
215
+ status: JobStatus;
216
+ progress: number;
217
+ user_id: string;
218
+ input_data: unknown;
219
+ output_data: unknown;
220
+ error_message: string | null;
221
+ credits: number | null;
222
+ job_type: string | null;
223
+ created_at: string;
224
+ started_at: string | null;
225
+ completed_at: string | null;
226
+ }
227
+ interface CancelJobResult {
228
+ success: true;
229
+ cancelled: number;
230
+ }
231
+ /**
232
+ * Lean job status returned by `GET /v1/jobs/:id/status`. Skips the
233
+ * `input_data` JSONB, cost/timestamp columns, and the public sanitize pass —
234
+ * intended for poll loops that only need progress/output/error.
235
+ */
236
+ interface JobStatusResult {
237
+ id: string;
238
+ status: JobStatus;
239
+ progress?: number;
240
+ output_data?: unknown;
241
+ error_message?: string | null;
242
+ }
243
+ declare class JobsResource {
244
+ private client;
245
+ constructor(client: NodaroClient);
246
+ /** Get a single job by ID. */
247
+ get(id: string): Promise<{
248
+ data: Job;
249
+ }>;
250
+ /**
251
+ * Get the lean status of a single job (poll-loop friendly).
252
+ * Hits `GET /v1/jobs/:id/status` — returns only id/status/progress/
253
+ * output_data/error_message, with far less wire/CPU cost than `get()`.
254
+ * Same auth + ownership semantics as {@link get}.
255
+ */
256
+ getStatus(id: string): Promise<{
257
+ data: JobStatusResult;
258
+ }>;
259
+ /**
260
+ * Cancel a job. Server route is `POST /v1/jobs/:jobId/cancel`.
261
+ * Refunds any reserved credit holds.
262
+ */
263
+ cancel(id: string): Promise<CancelJobResult>;
264
+ }
265
+
266
+ type ExecutionStatus = "pending" | "running" | "completed" | "failed" | "cancelled" | "stopping" | "timed_out" | "discarded";
267
+ type ExecutionTriggerType = "manual" | "webhook" | "schedule" | "app_run" | "single-node";
268
+ /**
269
+ * Per-node state inside an execution's `nodeStates` map. Keys are node IDs.
270
+ *
271
+ * Shape mirrors `services/workflow-engine/types.ts` plus the synthetic
272
+ * single-node-job shape from `routes/workflow-execution.ts`.
273
+ */
274
+ interface NodeExecutionState {
275
+ status: string;
276
+ nodeType?: string;
277
+ jobId?: string | null;
278
+ creditsUsed?: number;
279
+ error?: string | null;
280
+ startedAt?: string | null;
281
+ completedAt?: string | null;
282
+ [key: string]: unknown;
283
+ }
284
+ /**
285
+ * Workflow execution record. Returned by `get()` and `cancel()` (the cancel
286
+ * endpoint returns `{ success: true }`, not the execution itself).
287
+ */
288
+ interface WorkflowExecution {
289
+ id: string;
290
+ workflowId: string | null;
291
+ userId: string;
292
+ status: ExecutionStatus;
293
+ triggerType: ExecutionTriggerType;
294
+ triggerData?: unknown;
295
+ nodeStates: Record<string, NodeExecutionState>;
296
+ totalNodes: number;
297
+ completedNodes: number;
298
+ failedNodes: number;
299
+ totalCreditsUsed: number;
300
+ errorMessage: string | null;
301
+ startedAt: string | null;
302
+ completedAt: string | null;
303
+ createdAt: string;
304
+ updatedAt: string;
305
+ }
306
+ /** Summary returned by `listForWorkflow()`. Excludes per-row `triggerData`/`updatedAt`. */
307
+ interface WorkflowExecutionSummary {
308
+ id: string;
309
+ status: ExecutionStatus;
310
+ triggerType: ExecutionTriggerType;
311
+ nodeStates: Record<string, NodeExecutionState>;
312
+ totalNodes: number;
313
+ completedNodes: number;
314
+ failedNodes: number;
315
+ totalCreditsUsed: number;
316
+ errorMessage: string | null;
317
+ startedAt: string | null;
318
+ completedAt: string | null;
319
+ createdAt: string;
320
+ }
321
+ interface ListExecutionsForWorkflowParams {
322
+ limit?: number;
323
+ cursor?: string;
324
+ /** Comma-separated list of statuses, e.g. "pending,running". */
325
+ status?: string;
326
+ /** "editor" excludes app_run / component / webhook / schedule executions. */
327
+ source?: "editor" | "all";
328
+ }
329
+ interface ListExecutionsPage<T> {
330
+ data: T[];
331
+ nextCursor?: string;
332
+ }
333
+ interface CancelExecutionParams {
334
+ /**
335
+ * "after_current" sets the execution to "stopping" (let in-flight nodes
336
+ * finish, then stop). Default behavior cancels immediately.
337
+ */
338
+ mode?: "after_current" | "discard";
339
+ }
340
+ declare class ExecutionsResource {
341
+ private client;
342
+ constructor(client: NodaroClient);
343
+ /** Get an execution by ID. Falls back to standalone single-node jobs server-side. */
344
+ get(id: string): Promise<{
345
+ data: WorkflowExecution;
346
+ }>;
347
+ /** List executions for a workflow. Merges workflow_executions + standalone single-node jobs. */
348
+ listForWorkflow(workflowId: string, params?: ListExecutionsForWorkflowParams): Promise<ListExecutionsPage<WorkflowExecutionSummary>>;
349
+ /** Cancel an execution. Returns `{ success: true }`. */
350
+ cancel(id: string, params?: CancelExecutionParams): Promise<{
351
+ success: true;
352
+ }>;
353
+ }
354
+
355
+ type NodeCategory = "input" | "parameter" | "ai-image" | "ai-video" | "ai-audio" | "ai-text" | "processing" | "composition" | "trigger" | "output" | "control" | "entity" | "utility";
356
+ type OutputType = "text" | "image" | "video" | "audio" | "data" | "none";
357
+ /**
358
+ * Field shape inside a node's `inputSchema.fields[]`. Mirrors
359
+ * `backend/src/lib/node-registry.ts`.
360
+ */
361
+ interface NodeInputField {
362
+ key: string;
363
+ type: string;
364
+ required?: boolean;
365
+ options?: string[];
366
+ }
367
+ interface NodeInputSchema {
368
+ fields: NodeInputField[];
369
+ }
370
+ /**
371
+ * Node descriptor returned by `GET /v1/nodes` and `GET /v1/nodes/:type`.
372
+ * Mirrors `backend/src/lib/node-registry.ts#NodeDescriptor`.
373
+ */
374
+ interface NodeDescriptor {
375
+ type: string;
376
+ label: string;
377
+ category: NodeCategory;
378
+ description: string;
379
+ outputType: OutputType;
380
+ /** Credit cost. Number when fixed, string range like "1-8" when model-dependent, undefined if free. */
381
+ creditCost?: number | string;
382
+ /** Input fields the node exposes for user override (subset of full config). */
383
+ inputSchema?: NodeInputSchema;
384
+ /** For AI nodes: list of provider IDs supported. */
385
+ providers?: string[];
386
+ /** Capability flags such as "supports-reference-image" or "supports-end-frame". */
387
+ capabilities?: string[];
388
+ }
389
+ /**
390
+ * Result of a direct node execution. Most node types return `{ jobId }` and
391
+ * are processed asynchronously by a worker — the caller polls
392
+ * `client.jobs.get(jobId)` until status is `completed`/`failed`.
393
+ *
394
+ * A small subset (combine-text, split-text, composite — the "inline"
395
+ * orchestrator categories) execute synchronously and return their full
396
+ * result body. The shape is route-specific; consumers should branch on the
397
+ * presence of `jobId`.
398
+ */
399
+ type RunNodeResult = {
400
+ jobId: string;
401
+ usageLogId?: string;
402
+ [k: string]: unknown;
403
+ } | Record<string, unknown>;
404
+ /**
405
+ * Structured references — the editor's wired-reference shape — shared by
406
+ * `generate-image` and `generate-video`. The route assembles them server-side
407
+ * into per-ref `@image_N` directives and resolves `{image:N}` prompt tokens, so
408
+ * a direct SDK run binds inline references exactly like the canvas. Pass the
409
+ * same `ConnectedReference[]` the editor builds; the route dedupes + caps them
410
+ * to the provider's image-reference limit.
411
+ */
412
+ interface StructuredReferenceParams {
413
+ /** Wired references assembled server-side (deduped + capped per provider). */
414
+ connectedReferences?: ConnectedReference[];
415
+ /** Reorder the assembled reference list by stable ref ids; renumbers `@image_N`. */
416
+ referenceOrder?: string[];
417
+ }
418
+ /**
419
+ * Typed request body for `nodes.run("generate-image", …)` / `runAndWait`.
420
+ * Common fields are typed; any other route field passes through via the index
421
+ * signature (the route Zod-validates the full body).
422
+ */
423
+ interface GenerateImageParams extends StructuredReferenceParams {
424
+ prompt?: string;
425
+ provider?: string;
426
+ /** Flat reference image URLs — appended after `connectedReferences`. */
427
+ referenceImageUrls?: string[];
428
+ negativePrompt?: string;
429
+ [k: string]: unknown;
430
+ }
431
+ /**
432
+ * Typed request body for `nodes.run("generate-video", …)` / `runAndWait`.
433
+ * Common fields are typed; any other route field passes through.
434
+ */
435
+ interface GenerateVideoParams extends StructuredReferenceParams {
436
+ prompt?: string;
437
+ provider?: string;
438
+ /** Start-frame image (image-to-video). */
439
+ imageUrl?: string;
440
+ referenceImageUrls?: string[];
441
+ referenceVideoUrls?: string[];
442
+ referenceAudioUrls?: string[];
443
+ [k: string]: unknown;
444
+ }
445
+ /**
446
+ * Typed request body for `nodes.run("assemble-narrated-video", …)` / `runAndWait`.
447
+ * Assembles blocks of video with audio narration into a single composed video.
448
+ */
449
+ interface AssembleNarratedVideoParams {
450
+ blocks: {
451
+ videoUrl: string;
452
+ audioUrl?: string;
453
+ }[];
454
+ voiceVolume?: number;
455
+ clipAudioVolume?: number;
456
+ maxSlowdown?: number;
457
+ trimStartFrames?: number;
458
+ trimEndFrames?: number;
459
+ [k: string]: unknown;
460
+ }
461
+ /**
462
+ * The `output_data` shape a finalized generation job writes. Every async
463
+ * generation node persists one (or more) of these media URLs to
464
+ * `jobs.output_data` on completion — `generate-image` → `imageUrl`,
465
+ * `generate-video` / `combine-videos` / `merge-video-audio` / `video-upscale`
466
+ * → `videoUrl` (+ `thumbnailUrl`), `text-to-speech` / `generate-music` →
467
+ * `audioUrl`. Resolved by {@link NodesResource.runAndWait}. Extra fields may be
468
+ * present, so the index signature is open.
469
+ */
470
+ interface NodeJobOutput {
471
+ /** `text-to-speech` / `generate-music` / audio nodes write here. For
472
+ * `audio-separation` this is the primary stem (vocals). */
473
+ readonly audioUrl?: string;
474
+ /** `audio-separation` (Demucs) per-stem URLs. `vocalUrl`/`instrumentalUrl`
475
+ * in vocal/instrumental mode; the rest in full-stems mode. */
476
+ readonly vocalUrl?: string;
477
+ readonly instrumentalUrl?: string;
478
+ readonly drumsUrl?: string;
479
+ readonly bassUrl?: string;
480
+ readonly otherUrl?: string;
481
+ readonly guitarUrl?: string;
482
+ readonly pianoUrl?: string;
483
+ /** `generate-video` / `combine-videos` / `merge-video-audio` / `video-upscale` write here. */
484
+ readonly videoUrl?: string;
485
+ /** `generate-image` / `edit-image` / `extract-frame` write here. */
486
+ readonly imageUrl?: string;
487
+ /** Poster frame for video outputs. */
488
+ readonly thumbnailUrl?: string;
489
+ readonly [k: string]: unknown;
490
+ }
491
+ /** Options for {@link NodesResource.runAndWait} and {@link NodesResource.runMany}. */
492
+ interface RunAndWaitOptions {
493
+ /**
494
+ * Abort the run/poll loop. Aborting (or passing an already-aborted signal)
495
+ * stops polling and rejects with {@link JobAbortedError}.
496
+ */
497
+ readonly signal?: AbortSignal;
498
+ /** Called with each lean status the poll loop observes (running → terminal). */
499
+ readonly onProgress?: (status: JobStatusResult) => void;
500
+ /** Poll interval in ms. Default 2000. */
501
+ readonly pollMs?: number;
502
+ /** Wall-clock cap before giving up, in ms. Default ~15 min (900_000). */
503
+ readonly maxMs?: number;
504
+ }
505
+ /** One settled result from {@link NodesResource.runMany}. */
506
+ interface RunManyResult {
507
+ readonly jobId: string;
508
+ readonly output: NodeJobOutput;
509
+ }
510
+ declare class NodesResource {
511
+ private client;
512
+ constructor(client: NodaroClient);
513
+ /** List all known node descriptors. Server caches publicly for 5 minutes. */
514
+ list(): Promise<{
515
+ data: NodeDescriptor[];
516
+ }>;
517
+ /** Get a single node descriptor by type slug (e.g. "generate-image"). */
518
+ get(type: string): Promise<{
519
+ data: NodeDescriptor;
520
+ }>;
521
+ /**
522
+ * Run a single node directly without wrapping it in a workflow. Posts
523
+ * `params` as the request body to `POST /v1/<type>` (the route convention
524
+ * every generation node follows: `generate-image`, `image-to-video`,
525
+ * `text-to-speech`, etc.).
526
+ *
527
+ * This is the SDK equivalent of the MCP server's verb tools — and the
528
+ * path the Nodaro CLI uses for `nodaro nodes run <type>`.
529
+ *
530
+ * Most node types are async: the response includes `{ jobId }` and the
531
+ * actual generation runs on a worker. Poll `client.jobs.get(jobId)` until
532
+ * completed. Inline node types (combine-text, etc.) return their full
533
+ * result synchronously without a `jobId` field.
534
+ *
535
+ * @param type Node type slug — must match an entry in the registry
536
+ * returned by `list()` (e.g. "generate-image").
537
+ * @param params Request body. Field names must match the node's
538
+ * `inputSchema` (see `get(type).inputSchema`).
539
+ */
540
+ run(type: "generate-image", params?: GenerateImageParams): Promise<RunNodeResult>;
541
+ run(type: "generate-video", params?: GenerateVideoParams): Promise<RunNodeResult>;
542
+ run(type: "assemble-narrated-video", params?: AssembleNarratedVideoParams): Promise<RunNodeResult>;
543
+ run(type: string, params?: Record<string, unknown>): Promise<RunNodeResult>;
544
+ /**
545
+ * Run a single async node to completion: {@link run} it, extract the
546
+ * `{ jobId }`, then client-poll `jobs.getStatus(jobId)` every `pollMs`
547
+ * (default 2000) until a terminal status, up to `maxMs` (default ~15 min).
548
+ *
549
+ * Resolves the job's typed `output_data` ({@link NodeJobOutput}) on
550
+ * `completed`. Throws (all typed, catchable by `instanceof`):
551
+ * - {@link InsufficientCreditsError} / {@link StorageExceededError} etc. —
552
+ * surfaced by the underlying {@link run} on 402/413/… before any poll.
553
+ * - {@link JobFailedError} — terminal `failed`/`cancelled` (carries the
554
+ * job's `error_message` + `jobId`).
555
+ * - {@link JobTimeoutError} — `maxMs` deadline exceeded before terminal.
556
+ * - {@link JobAbortedError} — `signal` fired (or was already aborted);
557
+ * polling stops immediately.
558
+ *
559
+ * Polling is fully client-side (no server function blocks) — the same model
560
+ * thin clients use, lifted out of their hand-rolled run→poll loops.
561
+ *
562
+ * @param type Node type slug (e.g. "generate-video"). See {@link run}.
563
+ * @param params Request body — field names match the node's `inputSchema`.
564
+ * @param opts `signal` / `onProgress` / `pollMs` / `maxMs`.
565
+ */
566
+ runAndWait(type: "generate-image", params?: GenerateImageParams, opts?: RunAndWaitOptions): Promise<NodeJobOutput>;
567
+ runAndWait(type: "generate-video", params?: GenerateVideoParams, opts?: RunAndWaitOptions): Promise<NodeJobOutput>;
568
+ runAndWait(type: "assemble-narrated-video", params?: AssembleNarratedVideoParams, opts?: RunAndWaitOptions): Promise<NodeJobOutput>;
569
+ runAndWait(type: string, params?: Record<string, unknown>, opts?: RunAndWaitOptions): Promise<NodeJobOutput>;
570
+ /**
571
+ * Fan out N async runs of the same node `type` to completion concurrently —
572
+ * the candidate-grid path (generate N stills/clips in parallel). Each runs
573
+ * via {@link runAndWait}; resolves once ALL settle, to an array of
574
+ * `{ jobId, output }` in input order. Rejects (and the rejection wins) if any
575
+ * single run rejects — same typed errors as {@link runAndWait}. A shared
576
+ * `signal` aborts the whole batch.
577
+ *
578
+ * @param type Node type slug, applied to every entry.
579
+ * @param paramsList One request body per candidate.
580
+ * @param opts Shared `signal` / `onProgress` / `pollMs` / `maxMs`.
581
+ */
582
+ runMany(type: string, paramsList: Record<string, unknown>[], opts?: RunAndWaitOptions): Promise<RunManyResult[]>;
583
+ /** Poll an already-kicked job id until terminal; resolve output_data or throw. */
584
+ private pollJob;
585
+ }
586
+
587
+ /**
588
+ * OAuth scopes that a developer app may request. Mirrors
589
+ * `backend/src/lib/scopes.ts#ALL_SCOPES`.
590
+ */
591
+ type DeveloperAppScope = "workflows:read" | "workflows:write" | "workflows:execute" | "jobs:read" | "assets:read" | "assets:write" | "credits:read" | "apps:read" | "pipelines:read" | "pipelines:execute" | "pipelines:approve";
592
+ type DeveloperAppStatus = "active" | "suspended" | "pending_review";
593
+ interface DeveloperApp {
594
+ id: string;
595
+ name: string;
596
+ description: string | null;
597
+ logoUrl: string | null;
598
+ homepageUrl: string | null;
599
+ redirectUris: string[];
600
+ allowedOrigins: string[];
601
+ scopesRequested: DeveloperAppScope[];
602
+ clientId: string;
603
+ status: DeveloperAppStatus;
604
+ createdAt: string;
605
+ updatedAt: string;
606
+ }
607
+ /**
608
+ * One-shot create response — `clientSecret` is returned exactly ONCE here.
609
+ * Store it securely; subsequent `get`/`list` calls will not include it.
610
+ */
611
+ interface CreateDeveloperAppResult extends DeveloperApp {
612
+ clientSecret: string;
613
+ }
614
+ interface CreateDeveloperAppInput {
615
+ name: string;
616
+ description?: string;
617
+ homepageUrl?: string;
618
+ logoUrl?: string;
619
+ /** At least 1, at most 10 redirect URIs. Each must be https or http://localhost. */
620
+ redirectUris: string[];
621
+ /** Up to 5 bare origins (no path/query/hash), e.g. "https://example.com". */
622
+ allowedOrigins?: string[];
623
+ /** At least 1 scope required. */
624
+ scopesRequested: DeveloperAppScope[];
625
+ }
626
+ interface UpdateDeveloperAppInput {
627
+ name?: string;
628
+ description?: string;
629
+ homepageUrl?: string;
630
+ logoUrl?: string;
631
+ redirectUris?: string[];
632
+ allowedOrigins?: string[];
633
+ scopesRequested?: DeveloperAppScope[];
634
+ }
635
+ interface RotateSecretResult {
636
+ /** New client secret. Returned exactly once — old secret is invalidated. */
637
+ clientSecret: string;
638
+ }
639
+ declare class DeveloperAppsResource {
640
+ private client;
641
+ constructor(client: NodaroClient);
642
+ /** List the authenticated user's developer apps. */
643
+ list(): Promise<{
644
+ data: DeveloperApp[];
645
+ }>;
646
+ /** Get a developer app by ID. */
647
+ get(id: string): Promise<{
648
+ data: DeveloperApp;
649
+ }>;
650
+ /**
651
+ * Create a new developer app. Returns the app PLUS a one-time `clientSecret`
652
+ * — store it now, the secret hash is the only copy kept server-side.
653
+ */
654
+ create(input: CreateDeveloperAppInput): Promise<{
655
+ data: CreateDeveloperAppResult;
656
+ }>;
657
+ /** Update a developer app's metadata, redirect URIs, origins, or requested scopes. */
658
+ update(id: string, input: UpdateDeveloperAppInput): Promise<{
659
+ data: DeveloperApp;
660
+ }>;
661
+ /** Delete a developer app. Returns `{ success: true }`. */
662
+ delete(id: string): Promise<{
663
+ success: true;
664
+ }>;
665
+ /**
666
+ * Generate a new `clientSecret`. The previous secret is invalidated.
667
+ * Server returns ONLY the new secret, not the full app record.
668
+ */
669
+ rotateSecret(id: string): Promise<RotateSecretResult>;
670
+ }
671
+
672
+ /**
673
+ * Server-side authorization-code exchange payload. Field names are snake_case
674
+ * per OAuth 2.0 (RFC 6749).
675
+ */
676
+ interface ExchangeCodeInput {
677
+ client_id: string;
678
+ client_secret: string;
679
+ /** Authorization code received from the consent redirect. */
680
+ code: string;
681
+ /** Must match the redirect_uri used when issuing the code. */
682
+ redirect_uri: string;
683
+ }
684
+ /**
685
+ * `POST /v1/oauth/token` response — snake_case per RFC 6749. `expires_in` is
686
+ * the token's lifetime in seconds.
687
+ */
688
+ interface AccessTokenResponse {
689
+ access_token: string;
690
+ token_type: "Bearer";
691
+ /** Space-separated list of granted scopes. */
692
+ scope: string;
693
+ expires_in: number;
694
+ }
695
+ /**
696
+ * Public app metadata for consent screens. Only safe-to-display fields —
697
+ * no secret, no full origin list, no owner_user_id.
698
+ */
699
+ interface OAuthAppInfo {
700
+ name: string;
701
+ description: string | null;
702
+ logoUrl: string | null;
703
+ homepageUrl: string | null;
704
+ scopesRequested: DeveloperAppScope[];
705
+ }
706
+ declare class OAuthResource {
707
+ private client;
708
+ constructor(client: NodaroClient);
709
+ /**
710
+ * Server-side authorization-code exchange. Sends the standard OAuth 2.0
711
+ * `application/json` body to `POST /v1/oauth/token`.
712
+ *
713
+ * NEVER call this from a browser — `client_secret` must stay on the server.
714
+ */
715
+ exchangeCode(input: ExchangeCodeInput): Promise<AccessTokenResponse>;
716
+ /**
717
+ * Revoke an access token (RFC 7009). Always returns `{ success: true }`,
718
+ * even for unknown tokens — the spec forbids leaking token validity.
719
+ */
720
+ revoke(token: string): Promise<{
721
+ success: true;
722
+ }>;
723
+ /**
724
+ * Get public app metadata for a consent screen.
725
+ * `GET /v1/oauth/app-info?client_id=<id>`. Public route — no auth needed.
726
+ */
727
+ getAppInfo(clientId: string): Promise<OAuthAppInfo>;
728
+ }
729
+
730
+ /**
731
+ * A published app — a workflow wrapped in a curated input/output presentation.
732
+ * Returned by the public `/v1/apps/browse` endpoint.
733
+ */
734
+ interface PublishedApp {
735
+ id: string;
736
+ slug: string;
737
+ name: string;
738
+ description?: string | null;
739
+ creatorId: string;
740
+ creatorName?: string | null;
741
+ thumbnailUrl?: string | null;
742
+ category?: string | null;
743
+ isFeatured?: boolean;
744
+ runCount?: number;
745
+ createdAt: string;
746
+ updatedAt: string;
747
+ }
748
+ interface ListAppsParams {
749
+ /** Substring search across app name + description. */
750
+ search?: string;
751
+ /** Page size; backend caps at 50. */
752
+ limit?: number;
753
+ /** Cursor token returned by the previous page. */
754
+ cursor?: string;
755
+ /** Filter to a single category slug. */
756
+ category?: string;
757
+ }
758
+ interface ListAppsResult {
759
+ data: PublishedApp[];
760
+ nextCursor?: string | null;
761
+ }
762
+ /**
763
+ * App detail — includes the input schema (required + optional fields end users
764
+ * fill in) and the output mapping (which workflow nodes produce which display
765
+ * cards).
766
+ */
767
+ interface PublishedAppDetail extends PublishedApp {
768
+ inputSchema: Record<string, unknown>;
769
+ outputs: Array<{
770
+ nodeId: string;
771
+ label: string;
772
+ type: string;
773
+ }>;
774
+ }
775
+ interface AppRunResult {
776
+ /** The execution-id that was started — poll via client.executions.get(). */
777
+ executionId: string;
778
+ status: "pending" | "running";
779
+ /** App-run id (distinct from executionId — used by listRuns/getRun). */
780
+ runId?: string;
781
+ }
782
+ interface AppRun {
783
+ id: string;
784
+ appSlug: string;
785
+ executionId: string;
786
+ status: "pending" | "running" | "completed" | "failed" | "cancelled";
787
+ inputs: Record<string, unknown>;
788
+ outputs?: Array<{
789
+ nodeId: string;
790
+ type: string;
791
+ url?: string;
792
+ text?: string;
793
+ }>;
794
+ startedAt: string;
795
+ finishedAt?: string | null;
796
+ }
797
+ interface ListAppRunsParams {
798
+ limit?: number;
799
+ cursor?: string;
800
+ }
801
+ /**
802
+ * Result of a soft-delete (archive) operation. The run is moved to the user's
803
+ * archive in the Nodaro UI; restoration and permanent deletion are UI-only by
804
+ * design — SDK / MCP / API delete callers can't accidentally destroy data.
805
+ */
806
+ interface DeleteAppRunResult {
807
+ success: true;
808
+ archived: true;
809
+ }
810
+ declare class AppsResource {
811
+ private client;
812
+ constructor(client: NodaroClient);
813
+ /** List published apps. Public — no auth required for community apps. */
814
+ list(params?: ListAppsParams): Promise<ListAppsResult>;
815
+ /** Get one app's metadata + input schema by slug. */
816
+ get(slug: string): Promise<{
817
+ data: PublishedAppDetail;
818
+ }>;
819
+ /**
820
+ * Trigger an app run with the given input values. The keys in `inputs` must
821
+ * match the app's input-schema field names (see `get(slug).inputSchema`).
822
+ * Returns the execution-id for status polling via client.executions.get().
823
+ */
824
+ run(slug: string, inputs?: Record<string, unknown>): Promise<AppRunResult>;
825
+ /** List past runs for an app (the caller must own the app or the runs). */
826
+ listRuns(slug: string, params?: ListAppRunsParams): Promise<{
827
+ data: AppRun[];
828
+ nextCursor?: string | null;
829
+ }>;
830
+ /** Get one app-run by id. */
831
+ getRun(slug: string, runId: string): Promise<{
832
+ data: AppRun;
833
+ }>;
834
+ /**
835
+ * Archive (soft-delete) a published-app run. The run is hidden from the
836
+ * default run list and can be restored or permanently deleted from the
837
+ * archive view at https://app.nodaro.ai/archived-runs.
838
+ *
839
+ * @param slug The published app's slug (the last path segment of its URL).
840
+ * @param runId The run's UUID.
841
+ */
842
+ deleteRun(slug: string, runId: string): Promise<DeleteAppRunResult>;
843
+ }
844
+
845
+ /**
846
+ * Re-export the shared `EntityStyle` union (realistic | anime | 3d-pixar |
847
+ * illustration) and `CHARACTER_STYLES` runtime tuple so SDK consumers don't
848
+ * have to add `@nodaro/shared` as a second dependency just to typecheck the
849
+ * `style` field. Single source of truth lives in `@nodaro/shared/entity-prompts`.
850
+ */
851
+ /**
852
+ * Re-export the 4-value aspect-ratio union accepted by the generate-character*
853
+ * routes. Single source of truth lives in `@nodaro/shared`. See
854
+ * `CHARACTER_ASPECT_DEFAULTS` for the per-asset-type defaults.
855
+ */
856
+ /**
857
+ * Structured Person composer, re-exported from `@nodaro/shared` so SDK
858
+ * consumers can build a detailed person description — Identity, Body, and the
859
+ * Face facial-geometry layer (cheekbones, canthal tilt, eyelid type, lip
860
+ * fullness/shape, nose tip, etc.) — and feed it as `seedPrompt` / `description`
861
+ * to `generate()` / `upsert()` without adding `@nodaro/shared` as a second dep.
862
+ *
863
+ * `buildPersonSeedPrompt` collapses a `PersonValue` into the same comma-joined
864
+ * fragment the editor's Person picker produces (e.g. "almond-shaped eyes,
865
+ * sharply sculpted high cheekbones, full plump lips"). Returns "" when empty.
866
+ */
867
+ /**
868
+ * A character record returned by Nodaro's REST API. Mirrors the camelCase
869
+ * shape produced by `backend/src/routes/characters.ts::toCamel()`.
870
+ *
871
+ * `expressions`, `poses`, `motions`, `angles`, `bodyAngles`,
872
+ * `lightingVariations` are independent buckets keyed by a human-readable
873
+ * variant name (e.g. `"smile"`, `"standing"`, `"3/4 left"`). Each entry's
874
+ * `url` points at an R2-hosted asset.
875
+ *
876
+ * Identity-foundation fields:
877
+ * - `referencePhotos` — caller-supplied real-life refs (max one per
878
+ * non-`other` kind; cap 20 total). Drive the i2v / i2i path when a
879
+ * provider supports multi-image conditioning.
880
+ * - `realLifeRefsByVariant` — per-variant reference URLs (cap 20 keys,
881
+ * 5 URLs per key). Keys are lowercased+trimmed.
882
+ * - `referenceVideosByVariant` — per-label user-uploaded reference VIDEO
883
+ * URLs (cap 20 keys, 5 URLs per key, lowercased+trimmed keys). Mirrors
884
+ * `realLifeRefsByVariant` for video clips (e.g. emotion takes). Read the
885
+ * chosen URLs off the row to drive generate-video's `referenceVideoUrls`.
886
+ * - `seedPrompt` — short prompt fragment that scaffolds portrait gen.
887
+ * - `canonicalDescription` — ~80–120-word LLM-authored visual caption,
888
+ * populated by `approvePortrait()` / `recaption()`.
889
+ */
890
+ interface Character {
891
+ id: string;
892
+ userId: string;
893
+ /** Canvas node linkage. `null` only on legacy clone rows that predate the
894
+ * clone-side node_id fix (new clones mint one; creates always had one). */
895
+ nodeId: string | null;
896
+ projectId: string | null;
897
+ name: string;
898
+ description: string | null;
899
+ gender: string | null;
900
+ style: string | null;
901
+ baseOutfit: string | null;
902
+ sourceImageUrl: string | null;
903
+ /** MODEL_CATALOG image-model id the main image was generated with (or `null`).
904
+ * Set on create (the provider you generated with) + editable via `upsert`. */
905
+ imageProvider: string | null;
906
+ expressions: Array<{
907
+ name: string;
908
+ url: string;
909
+ }> | null;
910
+ poses: Array<{
911
+ name: string;
912
+ url: string;
913
+ }> | null;
914
+ lightingVariations: Array<{
915
+ name: string;
916
+ url: string;
917
+ }> | null;
918
+ angles: Array<{
919
+ name: string;
920
+ url: string;
921
+ }> | null;
922
+ bodyAngles: Array<{
923
+ name: string;
924
+ url: string;
925
+ }> | null;
926
+ motions: Array<{
927
+ name: string;
928
+ url: string;
929
+ }> | null;
930
+ /** Named Character Boards — dense reference sheets, one per persona/look
931
+ * (the `generate-image/character-board` factory preset rendered from the
932
+ * character's images). A first-class bucket: community publish snapshots it
933
+ * and clone hands the consumer their own copy to extend. Defaults to `[]`.
934
+ * `type` marks an image-collage `"identity"` sheet vs a plain `"looks"`
935
+ * board; `sourceImages` are the R2 URLs it was collaged from. Both optional
936
+ * + backward-compatible (legacy boards have neither). */
937
+ boards?: Array<{
938
+ name: string;
939
+ url: string;
940
+ type?: "looks" | "identity";
941
+ sourceImages?: string[];
942
+ }> | null;
943
+ /** Per-label user-uploaded reference VIDEO URLs (R2), keyed by a
944
+ * caller-owned label (lowercased+trimmed server-side). Mirrors
945
+ * `realLifeRefsByVariant` for video clips; read the chosen URLs off the row
946
+ * to feed generate-video's `referenceVideoUrls`. Defaults to `{}`. */
947
+ referenceVideosByVariant?: Record<string, string[]> | null;
948
+ /** The user's chosen DEFAULT asset take per variant (Studio version history).
949
+ * OPAQUE map: key `"<bucket>:<variant>"` (e.g. `"bodyAngles:front"`,
950
+ * `"expressions:smile"`) → the chosen asset URL (one already present in that
951
+ * bucket). Stored verbatim — keys are NOT normalized; soft-capped server-side
952
+ * at 200 keys / 2048-char values (overflow dropped silently). Defaults to `{}`. */
953
+ selectedAssetByVariant?: Record<string, string> | null;
954
+ /** `voiceType` records the selected voice's KIND (premade voices are
955
+ * addressed by name; library/custom voices by id at text-to-speech time).
956
+ * `previewUrl` is a playable audio sample (the voice's `preview_url` / clone
957
+ * sample) the studio plays in an `<audio>` element — persisted so Voice Library
958
+ * voices (which have no by-id lookup) stay previewable after reload. Client-
959
+ * played only; the server never fetches it. `ttsProvider` is the library
960
+ * voice's verified TTS provider (see `SharedVoice.recommendedProvider`) —
961
+ * send it as the text-to-speech `provider` so the voice renders on a model
962
+ * it's verified for. All optional — a character may have no voice, or a
963
+ * legacy voice predating these fields. */
964
+ voice: {
965
+ voiceId: string;
966
+ voiceName: string;
967
+ traits: string;
968
+ voiceType?: "premade" | "library" | "custom";
969
+ previewUrl?: string;
970
+ ttsProvider?: TtsProvider;
971
+ } | null;
972
+ personality: {
973
+ mood: string;
974
+ speechStyle: string;
975
+ movementStyle: string;
976
+ behavioralNotes: string;
977
+ } | null;
978
+ /** ~80–120-word LLM-authored visual caption (approve-portrait / recaption).
979
+ * Optional on the read surface so existing literal consumers don't break;
980
+ * the route always returns it (string | null). */
981
+ canonicalDescription?: string | null;
982
+ /** Identity-lock strength for Character Studio asset generation. */
983
+ identityLock?: "off" | "soft" | "strict";
984
+ deletedAt: string | null;
985
+ createdAt: string;
986
+ updatedAt: string;
987
+ }
988
+ /**
989
+ * GET /v1/characters/:id appends three live-progress buckets the studio uses
990
+ * to rehydrate spinners after a reload. Optional in the SDK surface — they
991
+ * don't appear on `list()` rows.
992
+ */
993
+ interface CharacterDetail extends Character {
994
+ pendingJobs?: Array<{
995
+ jobId: string;
996
+ assetType: "expressions" | "poses" | "angles" | "bodyAngles" | "lighting" | "motions";
997
+ name: string;
998
+ }>;
999
+ portraitCandidates?: Array<{
1000
+ jobId: string;
1001
+ url: string | undefined;
1002
+ progress: number;
1003
+ status: string;
1004
+ }>;
1005
+ previousCandidates?: Array<{
1006
+ jobId: string;
1007
+ url: string;
1008
+ createdAt: string;
1009
+ }>;
1010
+ }
1011
+ type ReferencePhotoKind = "frontFace" | "sideLeft" | "sideRight" | "threeQuarterLeft" | "threeQuarterRight" | "frontBody" | "other";
1012
+ interface ReferencePhoto {
1013
+ url: string;
1014
+ kind: ReferencePhotoKind;
1015
+ }
1016
+ /**
1017
+ * Body for `client.characters.upsert()`. Mirrors `upsertCharacterBody` in
1018
+ * `backend/src/routes/characters.ts`. Omitting `id` triggers an INSERT;
1019
+ * supplying it triggers an UPDATE that only writes the fields you pass —
1020
+ * undefined keys are NOT touched on the row.
1021
+ *
1022
+ * `name` is optional at the type level. The route requires `name` on INSERT
1023
+ * (id absent) and rejects with `validation_error` otherwise; on UPDATE the
1024
+ * route just ignores `name` when omitted, which lets partial updates like
1025
+ * `update(id, { gender: "female" })` succeed without re-sending the same
1026
+ * name the caller already has.
1027
+ */
1028
+ interface UpsertCharacterInput {
1029
+ /** UUID of the character row; omit to create. */
1030
+ id?: string;
1031
+ /** Canvas node id the character belongs to. REQUIRED on create (the route
1032
+ * 400s without it, like `name`); optional on update — the update branch
1033
+ * never touches node_id, so partial updates needn't round-trip it. */
1034
+ nodeId?: string;
1035
+ workflowId?: string;
1036
+ projectId?: string;
1037
+ name?: string;
1038
+ description?: string;
1039
+ gender?: string;
1040
+ style?: string;
1041
+ baseOutfit?: string;
1042
+ sourceImageUrl?: string;
1043
+ /** Persistent image-model id (a MODEL_CATALOG image model). Validated
1044
+ * server-side — unknown / non-image / "" is stored as `null`. */
1045
+ imageProvider?: string | null;
1046
+ expressions?: Array<{
1047
+ name: string;
1048
+ url: string;
1049
+ }>;
1050
+ poses?: Array<{
1051
+ name: string;
1052
+ url: string;
1053
+ }>;
1054
+ lightingVariations?: Array<{
1055
+ name: string;
1056
+ url: string;
1057
+ }>;
1058
+ angles?: Array<{
1059
+ name: string;
1060
+ url: string;
1061
+ }>;
1062
+ bodyAngles?: Array<{
1063
+ name: string;
1064
+ url: string;
1065
+ }>;
1066
+ motions?: Array<{
1067
+ name: string;
1068
+ url: string;
1069
+ }>;
1070
+ /** Named Character Boards (see `Character.boards`) — whole-array replace,
1071
+ * like the asset buckets. Server caps: 24 boards, 200-char names, 30
1072
+ * sourceImages per board. */
1073
+ boards?: Array<{
1074
+ name: string;
1075
+ url: string;
1076
+ type?: "looks" | "identity";
1077
+ sourceImages?: string[];
1078
+ }>;
1079
+ /** See `Character.voice` — persisted alongside the voice so TTS can resolve a
1080
+ * library/custom voice by id, `previewUrl` keeps the sample playable after
1081
+ * reload, and `ttsProvider` keeps generation on a model the voice is
1082
+ * verified for. All optional. */
1083
+ voice?: {
1084
+ voiceId: string;
1085
+ voiceName: string;
1086
+ traits: string;
1087
+ voiceType?: "premade" | "library" | "custom";
1088
+ previewUrl?: string;
1089
+ ttsProvider?: TtsProvider;
1090
+ } | null;
1091
+ personality?: {
1092
+ mood: string;
1093
+ speechStyle: string;
1094
+ movementStyle: string;
1095
+ behavioralNotes: string;
1096
+ } | null;
1097
+ seedPrompt?: string;
1098
+ canonicalDescription?: string;
1099
+ /** Identity-lock strength for Character Studio asset generation (off/soft/strict). */
1100
+ identityLock?: "off" | "soft" | "strict";
1101
+ referencePhotos?: ReferencePhoto[];
1102
+ /** Per-variant real-life reference URLs. Keys are lowercased+trimmed server-side. */
1103
+ realLifeRefsByVariant?: Record<string, string[]>;
1104
+ /** Per-label user-uploaded reference VIDEO URLs (e.g. emotion takes). Keys
1105
+ * are lowercased+trimmed server-side; max 20 keys, 5 URLs each. Stored R2
1106
+ * URLs are read back off the row to drive generate-video's
1107
+ * `referenceVideoUrls`. */
1108
+ referenceVideosByVariant?: Record<string, string[]>;
1109
+ /** The user's chosen DEFAULT asset take per variant. OPAQUE
1110
+ * `"<bucket>:<variant>"` → chosen-URL map; a write REPLACES the whole map
1111
+ * (the studio sends the full map each save). Omit to leave the row untouched.
1112
+ * Keys stored verbatim; soft-capped server-side at 200 keys / 2048-char values. */
1113
+ selectedAssetByVariant?: Record<string, string>;
1114
+ }
1115
+ interface UpsertCharacterResult {
1116
+ id: string;
1117
+ name?: string;
1118
+ }
1119
+ interface ListCharactersParams {
1120
+ /** Restrict to a single project. */
1121
+ projectId?: string;
1122
+ /** When true, return archived characters instead of active ones. */
1123
+ archived?: boolean;
1124
+ /**
1125
+ * Max rows to return. Server defaults to 100, caps at 500. Omit to take the
1126
+ * server default — passing it just narrows further.
1127
+ */
1128
+ limit?: number;
1129
+ }
1130
+ interface DuplicateCharacterInput {
1131
+ /** Optional canvas node id to bind the new row to. */
1132
+ nodeId?: string;
1133
+ /** Optional project to drop the new row into. */
1134
+ projectId?: string;
1135
+ }
1136
+ interface CharacterUsage {
1137
+ workflowCount: number;
1138
+ workflows: Array<{
1139
+ id: string;
1140
+ name: string;
1141
+ }>;
1142
+ }
1143
+ /**
1144
+ * Input for `client.characters.generate()` — fires the
1145
+ * `POST /v1/generate-character` route. Produces 1–10 portrait candidates;
1146
+ * each lands as one `jobs` row in `pending` state and is then enqueued for
1147
+ * the worker.
1148
+ *
1149
+ * Provide at least one of `seedPrompt`, `referencePhotos`, or `description`
1150
+ * (the backend's refinement rejects empty input with `validation_error`).
1151
+ *
1152
+ * When `attachToCharacterId` is set, the worker writes the resulting URL
1153
+ * directly to `characters.source_image_url` on completion — caller doesn't
1154
+ * need a separate `approvePortrait` call for single-candidate runs.
1155
+ */
1156
+ interface GenerateCharacterInput {
1157
+ name: string;
1158
+ description?: string;
1159
+ userPrompt?: string;
1160
+ gender?: string;
1161
+ style?: EntityStyle;
1162
+ baseOutfit?: string;
1163
+ sourceImageUrl?: string;
1164
+ provider?: string;
1165
+ /** Auto-attach the result to this character row. */
1166
+ attachToCharacterId?: string;
1167
+ seedPrompt?: string;
1168
+ referencePhotos?: ReferencePhoto[];
1169
+ /** Number of candidate images to generate (1–10; server-validated). */
1170
+ count?: number;
1171
+ /**
1172
+ * Explicit aspect ratio. Highest precedence — overrides both the character
1173
+ * node toggle and the per-asset-type default (portraits default to `3:4`).
1174
+ * Must be one of the 4-value `CharacterAspectRatio` union.
1175
+ */
1176
+ aspectRatio?: CharacterAspectRatio;
1177
+ /**
1178
+ * Character node toggle (per-canvas-node `defaultAssetAspectRatio`). Wins
1179
+ * against the per-asset-type default, loses to `aspectRatio`.
1180
+ */
1181
+ characterNodeAspectRatio?: CharacterAspectRatio;
1182
+ /**
1183
+ * Credit-affecting quality tier (e.g. `"high"` for gpt-image). Priced like
1184
+ * generate-image (composite ids such as `gpt-image:high`); values the chosen
1185
+ * model doesn't support are ignored server-side, never rejected.
1186
+ */
1187
+ quality?: string;
1188
+ /**
1189
+ * Credit-affecting output resolution (e.g. `"2K"` / `"4K"` / `"2 MP"`).
1190
+ * Priced like generate-image (composite ids such as `nano-banana-pro:4K`);
1191
+ * values the chosen model doesn't support are ignored server-side, never
1192
+ * rejected.
1193
+ */
1194
+ resolution?: string;
1195
+ }
1196
+ interface GenerateCharacterResult {
1197
+ /** First job-id; convenience alias for `jobIds[0]`. */
1198
+ jobId: string;
1199
+ /** All job-ids when `count > 1`. */
1200
+ jobIds: string[];
1201
+ }
1202
+ interface GenerateAssetInput {
1203
+ assetType: "expressions" | "poses" | "lighting" | "angles" | "headAngles" | "bodyAngles" | "custom";
1204
+ /** The named variant (e.g. `"smile"`, `"standing"`, `"3/4 left"`). */
1205
+ variant: string;
1206
+ /** Display name of the character; appears in the prompt. */
1207
+ name: string;
1208
+ description?: string;
1209
+ userPrompt?: string;
1210
+ gender?: string;
1211
+ style?: EntityStyle;
1212
+ baseOutfit?: string;
1213
+ sourceImageUrl?: string;
1214
+ /** Real-life reference URLs (cap 5). */
1215
+ realLifeRefs?: string[];
1216
+ provider?: string;
1217
+ /** Auto-attach to character row + asset bucket on completion. */
1218
+ attachToCharacterId?: string;
1219
+ /** Shared type — auto-includes new buckets (sheets/detail_closeups/outfit_variations); mirrors objects.ts/locations.ts. */
1220
+ attachToColumn?: CharacterAttachColumn;
1221
+ attachName?: string;
1222
+ /**
1223
+ * Explicit aspect ratio. Highest precedence — overrides both the character
1224
+ * node toggle and the per-asset-type default (expressions=1:1, poses=9:16,
1225
+ * headAngles=3:4, bodyAngles=9:16, lighting=3:4, angles=3:4, custom=3:4).
1226
+ */
1227
+ aspectRatio?: CharacterAspectRatio;
1228
+ /**
1229
+ * Character node toggle (per-canvas-node `defaultAssetAspectRatio`). Wins
1230
+ * against the per-asset-type default, loses to `aspectRatio`.
1231
+ */
1232
+ characterNodeAspectRatio?: CharacterAspectRatio;
1233
+ /**
1234
+ * Credit-affecting quality tier (e.g. `"high"` for gpt-image). Values the
1235
+ * chosen model doesn't support are ignored server-side, never rejected.
1236
+ */
1237
+ quality?: string;
1238
+ /**
1239
+ * Credit-affecting output resolution (e.g. `"2K"` / `"4K"` / `"2 MP"`).
1240
+ * Values the chosen model doesn't support are ignored server-side, never
1241
+ * rejected.
1242
+ */
1243
+ resolution?: string;
1244
+ }
1245
+ interface GenerateMotionInput {
1246
+ motionPrompt: string;
1247
+ /** Optional when `attachToCharacterId` is set — falls back to the row's portrait. */
1248
+ sourceImageUrl?: string;
1249
+ provider?: string;
1250
+ name: string;
1251
+ description?: string;
1252
+ motionDescription?: string;
1253
+ gender?: string;
1254
+ style?: EntityStyle;
1255
+ baseOutfit?: string;
1256
+ realLifeRefs?: string[];
1257
+ attachToCharacterId?: string;
1258
+ attachName?: string;
1259
+ /**
1260
+ * Explicit aspect ratio. Highest precedence — overrides both the character
1261
+ * node toggle and the motions default (`9:16`).
1262
+ */
1263
+ aspectRatio?: CharacterAspectRatio;
1264
+ /**
1265
+ * Character node toggle (per-canvas-node `defaultAssetAspectRatio`). Wins
1266
+ * against the motions default, loses to `aspectRatio`.
1267
+ */
1268
+ characterNodeAspectRatio?: CharacterAspectRatio;
1269
+ }
1270
+ interface ApprovePortraitResult {
1271
+ portraitUrl: string;
1272
+ /**
1273
+ * LLM-authored caption. `null` when the LLM call failed during the approval
1274
+ * — the portrait is still set; call `recaption()` to retry.
1275
+ */
1276
+ canonicalDescription: string | null;
1277
+ }
1278
+ interface RecaptionResult {
1279
+ canonicalDescription: string;
1280
+ }
1281
+ declare class CharactersResource {
1282
+ private client;
1283
+ constructor(client: NodaroClient);
1284
+ /**
1285
+ * List the caller's characters. By default returns active characters only;
1286
+ * pass `archived: true` to fetch soft-deleted rows for an "archive" view.
1287
+ * When `projectId` is set, only characters belonging to that project are
1288
+ * returned.
1289
+ */
1290
+ list(params?: ListCharactersParams): Promise<{
1291
+ characters: Character[];
1292
+ }>;
1293
+ /**
1294
+ * Fetch a single character including in-flight portrait / asset job state.
1295
+ * Soft-deleted (archived) rows are returned by id intentionally so canvas
1296
+ * nodes that hold a stale `characterDbId` keep loading.
1297
+ */
1298
+ get(id: string): Promise<CharacterDetail>;
1299
+ /**
1300
+ * Create or update a character. Omit `id` to create; supply it to update
1301
+ * (only the fields you pass get written — undefined keys are untouched).
1302
+ *
1303
+ * If the caller-supplied `name` collides with an existing active character
1304
+ * for this user, the request returns 409 `name_taken`. To auto-number a
1305
+ * placeholder, pass the placeholder name from `@nodaro/shared` and the
1306
+ * server will derive "Untitled character 2", "Untitled character 3", etc.
1307
+ */
1308
+ upsert(input: UpsertCharacterInput): Promise<UpsertCharacterResult>;
1309
+ /**
1310
+ * Convenience wrapper around `upsert()` for creating new characters.
1311
+ * Equivalent to `upsert({ ...input, id: undefined })`. `name` is REQUIRED
1312
+ * on create — the route 400s on INSERT-without-name; we narrow the type
1313
+ * here so callers fail at compile-time rather than runtime.
1314
+ */
1315
+ create(input: Omit<UpsertCharacterInput, "id"> & {
1316
+ name: string;
1317
+ }): Promise<UpsertCharacterResult>;
1318
+ /**
1319
+ * Convenience wrapper around `upsert()` for updating an existing character.
1320
+ * Equivalent to `upsert({ ...input, id })`.
1321
+ */
1322
+ update(id: string, input: Omit<UpsertCharacterInput, "id">): Promise<UpsertCharacterResult>;
1323
+ /**
1324
+ * Soft-delete (archive) a character. The row is hidden from `list()` by
1325
+ * default but still loadable via `get(id)` so canvas nodes pointing at it
1326
+ * keep working. Restore with `restore(id)`.
1327
+ */
1328
+ delete(id: string): Promise<{
1329
+ success: true;
1330
+ archived: true;
1331
+ }>;
1332
+ /**
1333
+ * Un-archive a character. If the original name now collides with an
1334
+ * active row, the server auto-suffixes "(restored)" and returns the
1335
+ * effective name.
1336
+ */
1337
+ restore(id: string): Promise<{
1338
+ id: string;
1339
+ name: string;
1340
+ }>;
1341
+ /**
1342
+ * Duplicate (fork) a character to a new row with a `"(copy)"` suffix.
1343
+ * Asset URLs are shared by reference — the new row can diverge by
1344
+ * regenerating any of them.
1345
+ */
1346
+ duplicate(id: string, input?: DuplicateCharacterInput): Promise<{
1347
+ id: string;
1348
+ name: string;
1349
+ }>;
1350
+ /**
1351
+ * Count of the caller's workflows that reference this character. Powers the
1352
+ * library "Archive" confirmation modal in the editor.
1353
+ */
1354
+ usage(id: string): Promise<CharacterUsage>;
1355
+ /**
1356
+ * Fire `POST /v1/generate-character` to produce one or more portrait
1357
+ * candidates. With `count > 1`, all jobs are reserved up-front before any
1358
+ * is enqueued — mid-batch failures roll back atomically.
1359
+ *
1360
+ * When `attachToCharacterId` is set, the worker writes the result directly
1361
+ * to the row's `source_image_url`; otherwise you must call
1362
+ * `approvePortrait()` after picking a candidate.
1363
+ */
1364
+ generate(input: GenerateCharacterInput): Promise<GenerateCharacterResult>;
1365
+ /**
1366
+ * Fire `POST /v1/generate-character-asset` to produce a single
1367
+ * expression / pose / angle / lighting variant. When the studio path is
1368
+ * set (`attachToCharacterId` + `attachToColumn` + `attachName`), the
1369
+ * worker appends `{ name: attachName, url: <result> }` to the named
1370
+ * JSONB array column on completion.
1371
+ */
1372
+ generateAsset(input: GenerateAssetInput): Promise<{
1373
+ jobId: string;
1374
+ }>;
1375
+ /**
1376
+ * Fire `POST /v1/generate-character-motion` to animate the character's
1377
+ * portrait into a motion clip. The result is appended to the character's
1378
+ * `motions[]` bucket when `attachToCharacterId` is set.
1379
+ */
1380
+ generateMotion(input: GenerateMotionInput): Promise<{
1381
+ jobId: string;
1382
+ }>;
1383
+ /**
1384
+ * Approve a completed `generate-character` job as the character's portrait.
1385
+ * Sets `source_image_url` and fires the LLM caption (Claude Sonnet vision)
1386
+ * inline. Returns the new portrait URL plus the caption — `canonicalDescription`
1387
+ * is `null` if the LLM call sub-failed (portrait still set; retry via `recaption()`).
1388
+ */
1389
+ approvePortrait(id: string, candidateJobId: string): Promise<ApprovePortraitResult>;
1390
+ /**
1391
+ * Re-fire the LLM caption against the character's current portrait. 502s on
1392
+ * LLM failure; returns 400 `no_portrait` if no portrait is set yet.
1393
+ */
1394
+ recaption(id: string): Promise<RecaptionResult>;
1395
+ }
1396
+
1397
+ /**
1398
+ * Collapse a PersonValue into the comma-joined seed-prompt fragment used by
1399
+ * `characters.generate({ seedPrompt })` — same composition the Nodaro editor
1400
+ * performs. Powered by @nodaro/prompts.
1401
+ */
1402
+ declare function buildPersonSeedPrompt(value: PersonValue): string;
1403
+
1404
+ /**
1405
+ * Re-export the shared `LocationAssetType` / `LocationAttachColumn` unions and
1406
+ * their runtime tuples so SDK consumers don't have to add `@nodaro/shared` as a
1407
+ * second dependency just to typecheck the `assetType` / `attachToColumn`
1408
+ * fields. Single source of truth lives in `@nodaro/shared/entity-prompts`.
1409
+ *
1410
+ * `CharacterAspectRatio` is re-exported alongside them — `generateMotion`'s
1411
+ * `aspectRatio` field reuses the same 4-value enum (1:1 / 3:4 / 16:9 / 9:16)
1412
+ * as characters; the route enforces this with `z.enum(CHARACTER_ASPECT_OPTIONS)`.
1413
+ */
1414
+ /**
1415
+ * Reference-photo kind discriminator — the mood-board roles a user can attach
1416
+ * to a location. Mirrors the `reference_photos.kind` Zod enum in
1417
+ * `backend/src/routes/locations.ts`. `other` is the free-form bucket.
1418
+ */
1419
+ type LocationReferencePhotoKind = "wide" | "interior" | "exterior" | "detail" | "moodBoard" | "other";
1420
+ interface LocationReferencePhoto {
1421
+ url: string;
1422
+ kind: LocationReferencePhotoKind;
1423
+ }
1424
+ /**
1425
+ * A location record returned by Nodaro's REST API. Mirrors the camelCase
1426
+ * shape produced by `backend/src/routes/locations.ts::toCamel()`.
1427
+ *
1428
+ * Asset buckets (`timeOfDay`, `weather`, `angles`, `lighting`, `seasons`,
1429
+ * `atmosphereMotions`) are independent JSONB arrays keyed by a human-readable
1430
+ * variant name (e.g. `"dawn"`, `"clear"`, `"wide"`). Each entry's `url` points
1431
+ * at an R2-hosted asset.
1432
+ *
1433
+ * Identity-foundation fields:
1434
+ * - `referencePhotos` — caller-supplied mood-board refs (cap 20).
1435
+ * - `canonicalDescription` — ~80–120-word LLM-authored visual caption,
1436
+ * populated by `approveMainImage()` / `recaption()`. The wire still sends
1437
+ * `""` on caption sub-failure (the breaking wire change is deferred to a
1438
+ * major bump), but `get()` normalizes `""` → `null` so consumers see the
1439
+ * same `string | null` semantics as characters.
1440
+ * - `styleLock` — whether asset gens should anchor to the canonical style
1441
+ * captured at approval time. Defaults to `true` on new rows.
1442
+ */
1443
+ interface Location {
1444
+ id: string;
1445
+ userId: string;
1446
+ nodeId: string;
1447
+ projectId: string | null;
1448
+ name: string;
1449
+ description: string | null;
1450
+ category: string | null;
1451
+ style: string | null;
1452
+ sourceImageUrl: string | null;
1453
+ /** MODEL_CATALOG image-model id the main image was generated with (or `null`).
1454
+ * Set on create + editable via the update route. */
1455
+ imageProvider: string | null;
1456
+ timeOfDay: Array<{
1457
+ name: string;
1458
+ url: string;
1459
+ }>;
1460
+ weather: Array<{
1461
+ name: string;
1462
+ url: string;
1463
+ }>;
1464
+ angles: Array<{
1465
+ name: string;
1466
+ url: string;
1467
+ }>;
1468
+ lighting: Array<{
1469
+ name: string;
1470
+ url: string;
1471
+ }>;
1472
+ seasons: Array<{
1473
+ name: string;
1474
+ url: string;
1475
+ }>;
1476
+ atmosphereMotions: Array<{
1477
+ name: string;
1478
+ url: string;
1479
+ }>;
1480
+ /** Named Location Boards — dense reference sheets, one per variant/mood
1481
+ * (the `generate-image/location-board` factory preset rendered from the
1482
+ * location's images). A first-class bucket: community publish snapshots it
1483
+ * and clone hands the consumer their own copy to extend. Defaults to `[]`.
1484
+ * `type` marks an image-collage `"identity"` sheet vs a plain `"looks"`
1485
+ * board; `sourceImages` are the R2 URLs it was collaged from. Both optional
1486
+ * + backward-compatible (legacy boards have neither). */
1487
+ boards?: Array<{
1488
+ name: string;
1489
+ url: string;
1490
+ type?: "looks" | "identity";
1491
+ sourceImages?: string[];
1492
+ }> | null;
1493
+ referencePhotos: LocationReferencePhoto[];
1494
+ /** `null` when no caption is set (or the LLM caption sub-failed) — the wire
1495
+ * sends `""`, normalized to `null` in `get()` to match character semantics. */
1496
+ canonicalDescription: string | null;
1497
+ styleLock: boolean;
1498
+ /** The user's chosen DEFAULT asset take per variant (Studio version history).
1499
+ * OPAQUE map: key `"<bucket>:<variant>"` (e.g. `"timeOfDay:dawn"`) → the chosen
1500
+ * asset URL (one already present in that bucket). Stored verbatim — keys are
1501
+ * NOT normalized; soft-capped server-side at 200 keys / 2048-char values
1502
+ * (overflow dropped silently). Defaults to `{}`. */
1503
+ selectedAssetByVariant?: Record<string, string> | null;
1504
+ deletedAt: string | null;
1505
+ createdAt: string;
1506
+ updatedAt: string;
1507
+ }
1508
+ /**
1509
+ * GET /v1/locations/:id appends a `pendingJobs` bucket the studio uses to
1510
+ * rehydrate spinners after a reload. Optional on the SDK surface — it doesn't
1511
+ * appear on `list()` rows.
1512
+ */
1513
+ interface LocationDetail extends Location {
1514
+ pendingJobs?: Array<{
1515
+ jobId: string;
1516
+ assetType: string;
1517
+ name: string;
1518
+ status: string;
1519
+ }>;
1520
+ /**
1521
+ * Completed candidate main images for this location whose URL differs from
1522
+ * the current `sourceImageUrl`, newest first (max 5). Surfaced so a
1523
+ * "pick from N" UI can offer alternatives the user can promote via
1524
+ * `approveMainImage(id, jobId)`; until they pick, the current main image is
1525
+ * left untouched. Mirrors `CharacterDetail.previousCandidates`. Optional -
1526
+ * absent on `list()` rows, present (possibly empty) on `get()`.
1527
+ */
1528
+ previousCandidates?: Array<{
1529
+ jobId: string;
1530
+ url: string;
1531
+ createdAt: string;
1532
+ }>;
1533
+ }
1534
+ /**
1535
+ * Body for `client.locations.create()`. Mirrors the INSERT branch of
1536
+ * `upsertLocationBody` in `backend/src/routes/locations.ts`. `name` + `nodeId`
1537
+ * are required on create.
1538
+ */
1539
+ interface CreateLocationInput {
1540
+ nodeId: string;
1541
+ name: string;
1542
+ description?: string;
1543
+ category?: string;
1544
+ style?: string;
1545
+ workflowId?: string;
1546
+ projectId?: string;
1547
+ sourceImageUrl?: string;
1548
+ /** Persistent image-model id (a MODEL_CATALOG image model). Validated
1549
+ * server-side — unknown / non-image / "" is stored as `null`. */
1550
+ imageProvider?: string | null;
1551
+ referencePhotos?: LocationReferencePhoto[];
1552
+ canonicalDescription?: string;
1553
+ styleLock?: boolean;
1554
+ /** The user's chosen DEFAULT asset take per variant. OPAQUE
1555
+ * `"<bucket>:<variant>"` → chosen-URL map; a write REPLACES the whole map.
1556
+ * Keys stored verbatim; soft-capped server-side at 200 keys / 2048-char values. */
1557
+ selectedAssetByVariant?: Record<string, string>;
1558
+ }
1559
+ /**
1560
+ * Body for `client.locations.update()`. Mirrors the UPDATE branch of
1561
+ * `upsertLocationBody` in `backend/src/routes/locations.ts`.
1562
+ *
1563
+ * Worker-owned asset buckets (`timeOfDay`/`weather`/`angles`/`lighting`/
1564
+ * `seasons`/`atmosphereMotions`) are deliberately omitted — the route drops
1565
+ * them on UPDATE so a Studio auto-save with a stale snapshot cannot clobber
1566
+ * the worker's atomic `append_location_asset()` writes.
1567
+ *
1568
+ * `expectedUpdatedAt` is the optimistic-concurrency token: when present, the
1569
+ * UPDATE only succeeds if the row's `updated_at` still matches; on mismatch
1570
+ * the route returns 409 so the studio can re-fetch + merge.
1571
+ */
1572
+ interface UpdateLocationInput {
1573
+ name?: string;
1574
+ description?: string;
1575
+ category?: string;
1576
+ style?: string;
1577
+ sourceImageUrl?: string;
1578
+ /** Persistent image-model id (a MODEL_CATALOG image model). Validated
1579
+ * server-side — unknown / non-image / "" is stored as `null`. */
1580
+ imageProvider?: string | null;
1581
+ referencePhotos?: LocationReferencePhoto[];
1582
+ canonicalDescription?: string;
1583
+ styleLock?: boolean;
1584
+ /** The user's chosen DEFAULT asset take per variant. OPAQUE
1585
+ * `"<bucket>:<variant>"` → chosen-URL map; a write REPLACES the whole map
1586
+ * (omit to leave untouched). Keys stored verbatim; soft-capped server-side
1587
+ * at 200 keys / 2048-char values. */
1588
+ selectedAssetByVariant?: Record<string, string>;
1589
+ /** Named Location Boards (see `Location.boards`) — whole-array replace,
1590
+ * USER-owned (unlike the worker-owned buckets it flows through UPDATE).
1591
+ * Server caps: 24 boards, 200-char names, 30 sourceImages per board. */
1592
+ boards?: Array<{
1593
+ name: string;
1594
+ url: string;
1595
+ type?: "looks" | "identity";
1596
+ sourceImages?: string[];
1597
+ }>;
1598
+ /** ISO-8601 timestamp recording when PII consent was captured for this location. */
1599
+ piiConsentAt?: string;
1600
+ expectedUpdatedAt?: string;
1601
+ }
1602
+ interface UpdateLocationResult {
1603
+ id: string;
1604
+ updatedAt: string;
1605
+ }
1606
+ interface ListLocationsParams {
1607
+ /** When true, return archived locations instead of active ones. */
1608
+ archived?: boolean;
1609
+ }
1610
+ /**
1611
+ * Input for `client.locations.generate()` — fires the
1612
+ * `POST /v1/generate-location` route. Produces 1–10 candidate
1613
+ * establishing shots; each lands as one `jobs` row in `pending` state and
1614
+ * is then enqueued for the worker.
1615
+ *
1616
+ * When `attachToLocationId` is set AND `count === 1`, the worker writes the
1617
+ * resulting URL directly to `locations.source_image_url` on completion —
1618
+ * caller doesn't need a separate `approveMainImage` call. Multi-candidate
1619
+ * batches MUST go through explicit approval so the user picks the winner.
1620
+ */
1621
+ interface GenerateLocationInput {
1622
+ name: string;
1623
+ description?: string;
1624
+ userPrompt?: string;
1625
+ category?: "indoor" | "outdoor" | "urban" | "nature" | "fantasy" | "sci-fi" | "historical" | "futuristic" | "other";
1626
+ style?: "realistic" | "anime" | "3d-pixar" | "illustration";
1627
+ sourceImageUrl?: string;
1628
+ provider?: string;
1629
+ /** Number of candidate images to generate (1–10; server-validated). */
1630
+ count?: number;
1631
+ /** Auto-attach the result to this location row (single-candidate only). */
1632
+ attachToLocationId?: string;
1633
+ /**
1634
+ * Credit-affecting quality tier (e.g. `"high"` for gpt-image). Priced like
1635
+ * generate-image (composite ids such as `gpt-image:high`); values the chosen
1636
+ * model doesn't support are ignored server-side, never rejected.
1637
+ */
1638
+ quality?: string;
1639
+ /**
1640
+ * Credit-affecting output resolution (e.g. `"2K"` / `"4K"` / `"2 MP"`).
1641
+ * Priced like generate-image (composite ids such as `nano-banana-pro:4K`);
1642
+ * values the chosen model doesn't support are ignored server-side, never
1643
+ * rejected.
1644
+ */
1645
+ resolution?: string;
1646
+ }
1647
+ /**
1648
+ * `generate()` response — `jobIds` is ALWAYS present (the harmonized contract,
1649
+ * matching characters). `jobId` is a deprecated back-compat alias populated only
1650
+ * on `count === 1`; prefer `jobIds`. (Will be removed on the next major.)
1651
+ */
1652
+ interface GenerateLocationResult {
1653
+ jobIds: string[];
1654
+ /** @deprecated count===1 back-compat alias — use `jobIds`. */
1655
+ jobId?: string;
1656
+ }
1657
+ /**
1658
+ * Input for `client.locations.generateAsset()` — fires the
1659
+ * `POST /v1/generate-location-asset` route. Produces a single
1660
+ * timeOfDay / weather / seasons / angles / lighting / custom variant.
1661
+ *
1662
+ * When all three studio-path fields are set (`attachToLocationId` +
1663
+ * `attachToColumn` + `attachName`), the worker appends
1664
+ * `{ name: attachName, url: <result> }` to the named JSONB array column on
1665
+ * the user's location row on completion. `attachToColumn` is REQUIRED for
1666
+ * `assetType === "custom"` — the worker can't infer the bucket from the
1667
+ * asset type.
1668
+ */
1669
+ interface GenerateLocationAssetInput {
1670
+ assetType: LocationAssetType;
1671
+ variant: string;
1672
+ name: string;
1673
+ description?: string;
1674
+ userPrompt?: string;
1675
+ category?: string;
1676
+ style?: "realistic" | "anime" | "3d-pixar" | "illustration";
1677
+ sourceImageUrl?: string;
1678
+ provider?: string;
1679
+ /** Optional framing override (the same 4-value enum as `generateMotion`).
1680
+ * Absent = the image model's default. The studio's 360° surround path pins
1681
+ * `"16:9"` so every ring view matches the establishing shot's frame. */
1682
+ aspectRatio?: CharacterAspectRatio;
1683
+ attachToLocationId?: string;
1684
+ attachToColumn?: LocationAttachColumn;
1685
+ attachName?: string;
1686
+ /**
1687
+ * Credit-affecting quality tier (e.g. `"high"` for gpt-image). Values the
1688
+ * chosen model doesn't support are ignored server-side, never rejected.
1689
+ */
1690
+ quality?: string;
1691
+ /**
1692
+ * Credit-affecting output resolution (e.g. `"2K"` / `"4K"` / `"2 MP"`).
1693
+ * Values the chosen model doesn't support are ignored server-side, never
1694
+ * rejected.
1695
+ */
1696
+ resolution?: string;
1697
+ }
1698
+ /**
1699
+ * Input for `client.locations.generateSurroundContinuation()` — fires the
1700
+ * `POST /v1/generate-surround-continuation` route. Generates one seamless 360°
1701
+ * ring view as an image-to-image continuation of `referenceImageUrl` (the
1702
+ * previous ring view, or the establishing shot for the first ring).
1703
+ *
1704
+ * The platform owns the whole pipeline: it builds the half-carry composite
1705
+ * server-side (carry the reference's trailing half into the new frame's leading
1706
+ * half per `direction`, gray the rest), paints the gray region, then
1707
+ * color-harmonizes the painted half to the carried half so there is no tonal
1708
+ * seam down the frame's center. The carried half stays pixel-exact, so a
1709
+ * panorama viewer stitching adjacent ring views stays geometrically seamless.
1710
+ *
1711
+ * When the studio path is set (`attachToLocationId` + `attachToColumn` +
1712
+ * `attachName`), the worker appends `{ name: attachName, url: <result> }` to the
1713
+ * location's bucket (studio uses `attachToColumn: "angles"`,
1714
+ * `attachName: "Surround 45°"`).
1715
+ */
1716
+ interface GenerateSurroundContinuationInput {
1717
+ /** The previous ring view to continue from (i2i anchor). */
1718
+ referenceImageUrl: string;
1719
+ /** Carry/paint axis: turn right, turn left, tilt up, or tilt down. Tilts
1720
+ * render the sky/ground (a thin horizon strip), not a horizontal continuation. */
1721
+ direction: SurroundDirection;
1722
+ /** Ring angle (45, 90, …) — stored on the result as metadata. */
1723
+ degrees?: number;
1724
+ /** Fraction of the frame carried from the reference. Omitted ⇒ per-direction
1725
+ * default (0.5 for a pan, 0.12 thin strip for a tilt). */
1726
+ carriedFraction?: number;
1727
+ /** Upscale/denoise the result before it's chained as the next reference, to
1728
+ * slow cumulative softening down a long ring chain. Default false. */
1729
+ refine?: boolean;
1730
+ /** Refine model when `refine` is set. `recraft-upscale` (1 cr, default) or
1731
+ * `topaz-image-upscale` (3 cr). */
1732
+ refineProvider?: "recraft-upscale" | "topaz-image-upscale";
1733
+ /** Optional free-form scene hint woven into the fill prompt. */
1734
+ userPrompt?: string;
1735
+ /** Image model. Studio pins `nano-banana-pro`; default `nano-banana`. */
1736
+ provider?: string;
1737
+ /** Studio pins `"16:9"` so every ring view matches the establishing frame. */
1738
+ aspectRatio?: CharacterAspectRatio;
1739
+ attachToLocationId?: string;
1740
+ attachToColumn?: LocationAttachColumn;
1741
+ attachName?: string;
1742
+ }
1743
+ /**
1744
+ * Input for `client.locations.generateMotion()` — fires the
1745
+ * `POST /v1/generate-location-motion` route. Produces a single atmospheric
1746
+ * motion clip (drifting fog, snowfall, rolling waves, etc.) animated FROM a
1747
+ * static establishing-shot image.
1748
+ *
1749
+ * Mirrors `client.characters.generateMotion()` minus the character-specific
1750
+ * fields (gender / baseOutfit / realLifeRefs). The route hardcodes the attach
1751
+ * column to `atmosphere_motions` — callers supply `attachToLocationId` +
1752
+ * `attachName` only.
1753
+ *
1754
+ * `sourceImageUrl` is REQUIRED — image-to-video needs a source frame and the
1755
+ * route has no fallback (no `source_image_url` column to pull from on the
1756
+ * locations row; the studio path supplies the canonical establishing-shot URL
1757
+ * explicitly).
1758
+ *
1759
+ * When the studio path is set (`attachToLocationId` + `attachName`), the
1760
+ * worker appends `{ name: attachName, url: <result> }` to the location row's
1761
+ * `atmosphere_motions` JSONB column on completion.
1762
+ */
1763
+ interface GenerateLocationMotionInput {
1764
+ motionPrompt: string;
1765
+ sourceImageUrl: string;
1766
+ provider?: string;
1767
+ name: string;
1768
+ category?: string;
1769
+ style?: "realistic" | "anime" | "3d-pixar" | "illustration";
1770
+ canonicalDescription?: string;
1771
+ attachToLocationId?: string;
1772
+ attachName?: string;
1773
+ /**
1774
+ * Optional aspect ratio override. Defaults to 16:9 server-side via
1775
+ * `resolveLocationAspectRatio` (locations are cinematic establishing shots).
1776
+ * One of the 4-value `CharacterAspectRatio` union — locations reuse the
1777
+ * character aspect enum since the supported ratios are identical.
1778
+ */
1779
+ aspectRatio?: CharacterAspectRatio;
1780
+ }
1781
+ interface ApproveMainImageResult {
1782
+ sourceImageUrl: string;
1783
+ /**
1784
+ * LLM-authored caption. `null` when the LLM caption sub-failed — the wire
1785
+ * sends `""`, normalized to `null` here so consumers see the same
1786
+ * `string | null` semantics as characters. The main image is still set; call
1787
+ * `recaption()` to retry.
1788
+ */
1789
+ canonicalDescription: string | null;
1790
+ }
1791
+ interface RecaptionLocationResult {
1792
+ canonicalDescription: string;
1793
+ }
1794
+ declare class LocationsResource {
1795
+ private client;
1796
+ constructor(client: NodaroClient);
1797
+ /**
1798
+ * List the caller's locations. By default returns active locations only;
1799
+ * pass `archived: true` to fetch soft-deleted rows for an "archive" view.
1800
+ */
1801
+ list(params?: ListLocationsParams): Promise<{
1802
+ locations: Location[];
1803
+ }>;
1804
+ /**
1805
+ * Convenience wrapper for `list({ archived: true })`. Returns soft-deleted
1806
+ * rows so callers can drive a UI "Archived" tab without re-encoding the
1807
+ * query param. Mirrors `ObjectsResource.listArchived`.
1808
+ *
1809
+ * `archived` is omitted from the param type — it's always set to `true` here.
1810
+ */
1811
+ listArchived(params?: Omit<ListLocationsParams, "archived">): Promise<{
1812
+ locations: Location[];
1813
+ }>;
1814
+ /**
1815
+ * Fetch a single location including in-flight asset job state. Soft-deleted
1816
+ * (archived) rows are returned by id intentionally so canvas nodes that
1817
+ * hold a stale `locationDbId` keep loading.
1818
+ */
1819
+ get(id: string): Promise<LocationDetail>;
1820
+ /**
1821
+ * Create a new location. `name` + `nodeId` are required — the route 400s
1822
+ * otherwise. Returns the new row's id.
1823
+ *
1824
+ * Note: the underlying route is the same `POST /v1/locations` upsert that
1825
+ * powers `update()`. This convenience wrapper enforces the INSERT-required
1826
+ * fields at the type level and never sends an `id`.
1827
+ */
1828
+ create(data: CreateLocationInput): Promise<{
1829
+ id: string;
1830
+ }>;
1831
+ /**
1832
+ * Update a location. Only the fields you pass are written — undefined keys
1833
+ * are NOT touched on the row. Worker-owned asset buckets are intentionally
1834
+ * not exposed on this surface (see `UpdateLocationInput` for the rationale).
1835
+ *
1836
+ * Optimistic-concurrency: pass `expectedUpdatedAt` to require the row's
1837
+ * `updated_at` still matches; on mismatch the route returns 409
1838
+ * `concurrent_modification`. The SDK surfaces that as a generic
1839
+ * `NodaroError` with the same code.
1840
+ */
1841
+ update(id: string, data: UpdateLocationInput): Promise<UpdateLocationResult>;
1842
+ /**
1843
+ * Soft-delete (archive) a location. The row is hidden from `list()` by
1844
+ * default but still loadable via `get(id)` so canvas nodes pointing at it
1845
+ * keep working. Restore with `restore(id)`.
1846
+ */
1847
+ delete(id: string): Promise<{
1848
+ success: true;
1849
+ archived: true;
1850
+ }>;
1851
+ /**
1852
+ * Un-archive a location. If the original name now collides (case-
1853
+ * insensitive) with an active row, the server auto-suffixes "(restored)"
1854
+ * and returns the effective name.
1855
+ */
1856
+ restore(id: string): Promise<{
1857
+ id: string;
1858
+ name: string;
1859
+ }>;
1860
+ /**
1861
+ * Fire `POST /v1/generate-location` to produce one or more candidate main
1862
+ * images. With `count > 1`, all jobs are reserved up-front before any
1863
+ * is enqueued — mid-batch failures roll back atomically.
1864
+ *
1865
+ * When `attachToLocationId` is set AND `count === 1`, the worker writes
1866
+ * the result directly to the row's `source_image_url`; otherwise you must
1867
+ * call `approveMainImage()` after picking a candidate.
1868
+ */
1869
+ generate(data: GenerateLocationInput): Promise<GenerateLocationResult>;
1870
+ /**
1871
+ * Fire `POST /v1/generate-location-asset` to produce a single variant.
1872
+ * When the studio path is set (`attachToLocationId` + `attachToColumn` +
1873
+ * `attachName`), the worker appends `{ name: attachName, url: <result> }`
1874
+ * to the named JSONB array column on completion.
1875
+ */
1876
+ generateAsset(data: GenerateLocationAssetInput): Promise<{
1877
+ jobId: string;
1878
+ }>;
1879
+ /**
1880
+ * Fire `POST /v1/generate-surround-continuation` to produce one seamless 360°
1881
+ * ring view as an i2i continuation of `referenceImageUrl`. The platform builds
1882
+ * the half-carry composite, paints the missing half, and color-harmonizes it
1883
+ * to the carried half (no tonal seam; carried half stays pixel-exact). When the
1884
+ * studio path is set, the worker appends the result to the location's bucket.
1885
+ */
1886
+ generateSurroundContinuation(data: GenerateSurroundContinuationInput): Promise<{
1887
+ jobId: string;
1888
+ }>;
1889
+ /**
1890
+ * Fire `POST /v1/generate-location-motion` to animate the location's
1891
+ * establishing shot into an atmospheric motion clip. Image-to-video, single
1892
+ * clip per call; the attach column is hardcoded to `atmosphere_motions`
1893
+ * server-side (locations have a single motion bucket so the caller doesn't
1894
+ * supply `attachToColumn`). When the studio path is set
1895
+ * (`attachToLocationId` + `attachName`), the worker appends
1896
+ * `{ name: attachName, url: <result> }` to the row's `atmosphere_motions`
1897
+ * column on completion.
1898
+ */
1899
+ generateMotion(data: GenerateLocationMotionInput): Promise<{
1900
+ jobId: string;
1901
+ }>;
1902
+ /**
1903
+ * Atomically remove ONE asset take (every entry matching `url`) from a
1904
+ * worker-owned bucket column — `POST /v1/locations/:id/remove-asset`. The
1905
+ * worker-owned buckets are deliberately not writable through `update()`
1906
+ * (a stale snapshot would race concurrent worker appends), so deleting a
1907
+ * take — e.g. a 360° surround view being regenerated — goes through this
1908
+ * single-statement server-side filter instead. 404s (`NotFoundError`) when
1909
+ * the url isn't in that bucket or the location isn't yours.
1910
+ */
1911
+ removeAsset(id: string, data: {
1912
+ column: LocationAttachColumn;
1913
+ url: string;
1914
+ }): Promise<{
1915
+ removed: true;
1916
+ }>;
1917
+ /**
1918
+ * Approve a completed `generate-location` job as the location's main image.
1919
+ * Sets `source_image_url` and fires the LLM caption (Claude Sonnet vision)
1920
+ * inline. Returns the new main-image URL plus the caption.
1921
+ *
1922
+ * Caption-failure semantics: the route still sends `""` on LLM sub-failure,
1923
+ * but the SDK normalizes `""` → `null` here so `canonicalDescription` carries
1924
+ * the same `string | null` semantics as characters. The main image is still
1925
+ * set; call `recaption()` to retry.
1926
+ */
1927
+ approveMainImage(id: string, candidateJobId: string): Promise<ApproveMainImageResult>;
1928
+ /**
1929
+ * Re-fire the LLM caption against the location's current main image. 502s
1930
+ * on LLM failure (unlike `approveMainImage` which preserves the side-effect
1931
+ * and returns ""); returns 400 `no_source_image` if no main image is set
1932
+ * yet.
1933
+ */
1934
+ recaption(id: string): Promise<RecaptionLocationResult>;
1935
+ }
1936
+
1937
+ /**
1938
+ * Re-export the shared `ObjectAssetType` / `ObjectAttachColumn` unions and
1939
+ * their runtime tuples so SDK consumers don't have to add `@nodaro/shared` as
1940
+ * a second dependency just to typecheck the `assetType` / `attachToColumn`
1941
+ * fields. Single source of truth lives in `@nodaro/shared/entity-prompts`.
1942
+ *
1943
+ * `ObjectAspectRatio` is re-exported alongside them — `generateMotion`'s
1944
+ * `aspectRatio` field is the 5-value object enum (1:1 / 3:4 / 16:9 / 9:16 /
1945
+ * 4:3) from `@nodaro/shared/object-aspect-defaults`. Distinct from
1946
+ * `CharacterAspectRatio` because objects support an extra 4:3 framing for
1947
+ * product-showcase shots.
1948
+ */
1949
+ /**
1950
+ * Reference-photo kind discriminator — the mood-board roles a user can attach
1951
+ * to an object. Mirrors the `reference_photos.kind` field accepted by
1952
+ * `backend/src/routes/objects.ts` (the route accepts open strings; this SDK
1953
+ * type narrows to the 6 canonical roles surfaced by the Studio). `other` is
1954
+ * the free-form bucket.
1955
+ */
1956
+ type ObjectReferencePhotoKind = "front" | "side" | "detail" | "context" | "moodBoard" | "other";
1957
+ interface ObjectReferencePhoto {
1958
+ url: string;
1959
+ kind: ObjectReferencePhotoKind;
1960
+ }
1961
+ /**
1962
+ * An object record returned by Nodaro's REST API. Mirrors the camelCase
1963
+ * shape produced by `backend/src/routes/objects.ts::toCamel()`.
1964
+ *
1965
+ * Asset buckets (`angles`, `materials`, `variations`, `motionClips`) are
1966
+ * independent JSONB arrays keyed by a human-readable variant name (e.g.
1967
+ * `"front"`, `"wood"`, `"weathered"`). Each entry's `url` points at an
1968
+ * R2-hosted asset.
1969
+ *
1970
+ * Identity-foundation fields:
1971
+ * - `referencePhotos` — caller-supplied mood-board refs (cap 20). Objects
1972
+ * do NOT carry a `piiConsentAt` field (location Phase 2 #7 only).
1973
+ * - `canonicalDescription` — ~80–120-word LLM-authored visual caption,
1974
+ * populated by `approveMainImage()` / `recaption()`. The wire still sends
1975
+ * `""` on caption sub-failure (the breaking wire change is deferred to a
1976
+ * major bump), but `get()` normalizes `""` → `null` so consumers see the
1977
+ * same `string | null` semantics as characters.
1978
+ * - `styleLock` — whether asset gens should anchor to the canonical style
1979
+ * captured at approval time. Defaults to `true` on new rows.
1980
+ *
1981
+ * `Object` shadows the JS global, which TypeScript handles cleanly via
1982
+ * local-scope resolution. Consumers who need both can alias as
1983
+ * `import type { Object as NodaroObject } from "@nodaro/sdk"`.
1984
+ */
1985
+ interface Object$1 {
1986
+ id: string;
1987
+ userId: string;
1988
+ nodeId: string;
1989
+ projectId: string | null;
1990
+ name: string;
1991
+ description: string | null;
1992
+ category: string | null;
1993
+ style: string | null;
1994
+ sourceImageUrl: string | null;
1995
+ /** MODEL_CATALOG image-model id the main image was generated with (or `null`).
1996
+ * Set on create + editable via the update route. */
1997
+ imageProvider: string | null;
1998
+ angles: Array<{
1999
+ name: string;
2000
+ url: string;
2001
+ }>;
2002
+ materials: Array<{
2003
+ name: string;
2004
+ url: string;
2005
+ }>;
2006
+ variations: Array<{
2007
+ name: string;
2008
+ url: string;
2009
+ }>;
2010
+ motionClips: Array<{
2011
+ name: string;
2012
+ url: string;
2013
+ }>;
2014
+ /** Named Product Boards — dense reference sheets, one per variant/colorway
2015
+ * (the `generate-image/product-board` factory preset rendered from the
2016
+ * object's images). A first-class bucket: community publish snapshots it
2017
+ * and clone hands the consumer their own copy to extend. Defaults to `[]`.
2018
+ * `type` marks an image-collage `"identity"` sheet vs a plain `"looks"`
2019
+ * board; `sourceImages` are the R2 URLs it was collaged from. Both optional
2020
+ * + backward-compatible (legacy boards have neither). */
2021
+ boards?: Array<{
2022
+ name: string;
2023
+ url: string;
2024
+ type?: "looks" | "identity";
2025
+ sourceImages?: string[];
2026
+ }> | null;
2027
+ referencePhotos: ObjectReferencePhoto[];
2028
+ /** `null` when no caption is set (or the LLM caption sub-failed) — the wire
2029
+ * sends `""`, normalized to `null` in `get()` to match character semantics. */
2030
+ canonicalDescription: string | null;
2031
+ styleLock: boolean;
2032
+ /** The user's chosen DEFAULT asset take per variant (Studio version history).
2033
+ * OPAQUE map: key `"<bucket>:<variant>"` (e.g. `"angles:front"`) → the chosen
2034
+ * asset URL (one already present in that bucket). Stored verbatim — keys are
2035
+ * NOT normalized; soft-capped server-side at 200 keys / 2048-char values
2036
+ * (overflow dropped silently). Defaults to `{}`. */
2037
+ selectedAssetByVariant?: Record<string, string> | null;
2038
+ deletedAt: string | null;
2039
+ createdAt: string;
2040
+ updatedAt: string;
2041
+ }
2042
+ /**
2043
+ * GET /v1/objects/:id may append a `pendingJobs` bucket the studio uses to
2044
+ * rehydrate spinners after a reload. Optional on the SDK surface — it doesn't
2045
+ * appear on `list()` rows.
2046
+ */
2047
+ interface ObjectDetail extends Object$1 {
2048
+ pendingJobs?: Array<{
2049
+ jobId: string;
2050
+ assetType: string;
2051
+ name: string;
2052
+ status: string;
2053
+ }>;
2054
+ }
2055
+ /**
2056
+ * Body for `client.objects.create()`. Mirrors the INSERT branch of
2057
+ * `upsertObjectBody` in `backend/src/routes/objects.ts`. `name` + `nodeId`
2058
+ * are required on create.
2059
+ */
2060
+ interface CreateObjectInput {
2061
+ nodeId: string;
2062
+ name: string;
2063
+ description?: string;
2064
+ category?: ObjectCategory;
2065
+ style?: string;
2066
+ workflowId?: string;
2067
+ projectId?: string;
2068
+ sourceImageUrl?: string;
2069
+ /** Persistent image-model id (a MODEL_CATALOG image model). Validated
2070
+ * server-side — unknown / non-image / "" is stored as `null`. */
2071
+ imageProvider?: string | null;
2072
+ referencePhotos?: ObjectReferencePhoto[];
2073
+ canonicalDescription?: string;
2074
+ styleLock?: boolean;
2075
+ /** The user's chosen DEFAULT asset take per variant. OPAQUE
2076
+ * `"<bucket>:<variant>"` → chosen-URL map; a write REPLACES the whole map.
2077
+ * Keys stored verbatim; soft-capped server-side at 200 keys / 2048-char values. */
2078
+ selectedAssetByVariant?: Record<string, string>;
2079
+ }
2080
+ /**
2081
+ * The 10-value Object category enum. Mirrors the literal accepted by
2082
+ * `POST /v1/generate-object` and surfaced in the Object Studio category
2083
+ * picker. Distinct from location's geography-based set.
2084
+ */
2085
+ type ObjectCategory = "furniture" | "vehicle" | "weapon" | "food" | "clothing" | "electronics" | "nature" | "tool" | "animal" | "other";
2086
+ /**
2087
+ * Body for `client.objects.update()`. Mirrors the UPDATE branch of
2088
+ * `upsertObjectBody` in `backend/src/routes/objects.ts`.
2089
+ *
2090
+ * Worker-owned asset buckets (`angles` / `materials` / `variations` /
2091
+ * `motionClips`) are deliberately omitted — the route drops them on UPDATE
2092
+ * so a Studio auto-save with a stale snapshot cannot clobber the worker's
2093
+ * atomic `append_object_asset()` writes.
2094
+ *
2095
+ * `expectedUpdatedAt` is the optimistic-concurrency token: when present, the
2096
+ * UPDATE only succeeds if the row's `updated_at` still matches; on mismatch
2097
+ * the route returns 409 so the studio can re-fetch + merge.
2098
+ */
2099
+ interface UpdateObjectInput {
2100
+ name?: string;
2101
+ description?: string;
2102
+ category?: ObjectCategory;
2103
+ style?: string;
2104
+ sourceImageUrl?: string;
2105
+ /** Persistent image-model id (a MODEL_CATALOG image model). Validated
2106
+ * server-side — unknown / non-image / "" is stored as `null`. */
2107
+ imageProvider?: string | null;
2108
+ referencePhotos?: ObjectReferencePhoto[];
2109
+ canonicalDescription?: string;
2110
+ styleLock?: boolean;
2111
+ /** The user's chosen DEFAULT asset take per variant. OPAQUE
2112
+ * `"<bucket>:<variant>"` → chosen-URL map; a write REPLACES the whole map
2113
+ * (omit to leave untouched). Keys stored verbatim; soft-capped server-side
2114
+ * at 200 keys / 2048-char values. */
2115
+ selectedAssetByVariant?: Record<string, string>;
2116
+ /** Named Product Boards (see `Object.boards`) — whole-array replace,
2117
+ * USER-owned (unlike the worker-owned buckets it flows through UPDATE).
2118
+ * Server caps: 24 boards, 200-char names, 30 sourceImages per board. */
2119
+ boards?: Array<{
2120
+ name: string;
2121
+ url: string;
2122
+ type?: "looks" | "identity";
2123
+ sourceImages?: string[];
2124
+ }>;
2125
+ expectedUpdatedAt?: string;
2126
+ }
2127
+ interface UpdateObjectResult {
2128
+ id: string;
2129
+ updatedAt: string;
2130
+ }
2131
+ /**
2132
+ * Combined create + update body (parameter for both branches). Exported for
2133
+ * callers that want to drive a single `upsert` flow without picking between
2134
+ * `Create*` and `Update*`. Mirrors the `upsertObjectBody` Zod schema in
2135
+ * `backend/src/routes/objects.ts`. `nodeId` + `name` are required on INSERT;
2136
+ * `id` flips the route into UPDATE mode.
2137
+ */
2138
+ interface UpsertObjectInput extends CreateObjectInput {
2139
+ id?: string;
2140
+ expectedUpdatedAt?: string;
2141
+ }
2142
+ type UpsertObjectResult = {
2143
+ id: string;
2144
+ } | UpdateObjectResult;
2145
+ interface ListObjectsParams {
2146
+ /** When true, return archived objects instead of active ones. */
2147
+ archived?: boolean;
2148
+ /** Optional project filter — server-scoped to the caller's user. */
2149
+ projectId?: string;
2150
+ }
2151
+ /**
2152
+ * Input for `client.objects.generate()` — fires the
2153
+ * `POST /v1/generate-object` route. Produces 1–10 candidate
2154
+ * main images; each lands as one `jobs` row in `pending` state and is then
2155
+ * enqueued for the worker.
2156
+ *
2157
+ * When `attachToObjectId` is set AND `count === 1`, the worker writes the
2158
+ * resulting URL directly to `objects.source_image_url` on completion —
2159
+ * caller doesn't need a separate `approveMainImage` call. Multi-candidate
2160
+ * batches MUST go through explicit approval so the user picks the winner.
2161
+ *
2162
+ * `seedPromptHint` (Pass 7 F-77) flows the parameter-picker's prompt fragment
2163
+ * through to the worker so a catalog selection (e.g. "antique brass lantern")
2164
+ * gets appended to the generated prompt context.
2165
+ */
2166
+ interface GenerateObjectInput {
2167
+ name: string;
2168
+ description?: string;
2169
+ userPrompt?: string;
2170
+ category?: ObjectCategory;
2171
+ style?: "realistic" | "anime" | "3d-pixar" | "illustration";
2172
+ sourceImageUrl?: string;
2173
+ provider?: string;
2174
+ /** Number of candidate images to generate (1–10; server-validated). */
2175
+ count?: number;
2176
+ /** Auto-attach the result to this object row (single-candidate only). */
2177
+ attachToObjectId?: string;
2178
+ /** Parameter-picker prompt-fragment pass-through. */
2179
+ seedPromptHint?: string;
2180
+ /** Optional name to set on the attached row alongside the main image. */
2181
+ attachName?: string;
2182
+ /** Optimistic-concurrency token for the single-candidate auto-attach path. */
2183
+ expectedUpdatedAt?: string;
2184
+ }
2185
+ /**
2186
+ * `generate()` response — `jobIds` is ALWAYS present (the harmonized contract,
2187
+ * matching characters). `jobId` is a deprecated back-compat alias populated only
2188
+ * on `count === 1`; prefer `jobIds`. (Will be removed on the next major.)
2189
+ */
2190
+ interface GenerateObjectResult {
2191
+ jobIds: string[];
2192
+ /** @deprecated count===1 back-compat alias — use `jobIds`. */
2193
+ jobId?: string;
2194
+ }
2195
+ /**
2196
+ * Input for `client.objects.generateAsset()` — fires the
2197
+ * `POST /v1/generate-object-asset` route. Produces a single
2198
+ * angles / materials / variations / custom variant.
2199
+ *
2200
+ * When all three studio-path fields are set (`attachToObjectId` +
2201
+ * `attachToColumn` + `attachName`), the worker appends
2202
+ * `{ name: attachName, url: <result> }` to the named JSONB array column on
2203
+ * the user's object row on completion. `attachToColumn` is REQUIRED for
2204
+ * `assetType === "custom"` — the worker can't infer the bucket from the
2205
+ * asset type.
2206
+ */
2207
+ interface GenerateObjectAssetInput {
2208
+ assetType: ObjectAssetType;
2209
+ variant: string;
2210
+ name: string;
2211
+ description?: string;
2212
+ userPrompt?: string;
2213
+ category?: string;
2214
+ style?: "realistic" | "anime" | "3d-pixar" | "illustration";
2215
+ sourceImageUrl?: string;
2216
+ provider?: string;
2217
+ attachToObjectId?: string;
2218
+ attachToColumn?: ObjectAttachColumn;
2219
+ attachName?: string;
2220
+ /** Parameter-picker prompt-fragment pass-through (Pass 7 F-77). */
2221
+ seedPromptHint?: string;
2222
+ }
2223
+ interface GenerateObjectAssetResult {
2224
+ jobId: string;
2225
+ }
2226
+ /**
2227
+ * Input for `client.objects.generateMotion()` — fires the
2228
+ * `POST /v1/generate-object-motion` route. Produces a single motion clip
2229
+ * (rotation, orbit, hover, drift, etc.) animated FROM a static product-shot
2230
+ * image.
2231
+ *
2232
+ * Mirrors `client.locations.generateMotion()` minus the location-specific
2233
+ * atmospheric fields. The route hardcodes the attach column to `motion_clips`
2234
+ * — callers supply `attachToObjectId` + `attachName` only.
2235
+ *
2236
+ * `sourceImageUrl` is REQUIRED — image-to-video needs a source frame and the
2237
+ * route has no fallback (no `source_image_url` column to pull from on the
2238
+ * objects row at this point in the flow; the studio path supplies the
2239
+ * canonical product-shot URL explicitly).
2240
+ *
2241
+ * Object-specific defaults vs location:
2242
+ * - `provider` defaults to `"kling-turbo"` (not location's `"kling"`)
2243
+ * - `aspectRatio` defaults to `"1:1"` server-side via
2244
+ * `resolveObjectAspectRatio({ assetType: "motion" })` — objects are
2245
+ * product-showcase framing, not cinematic establishing shots.
2246
+ *
2247
+ * When the studio path is set (`attachToObjectId` + `attachName`), the
2248
+ * worker appends `{ name: attachName, url: <result> }` to the object row's
2249
+ * `motion_clips` JSONB column on completion.
2250
+ */
2251
+ interface GenerateObjectMotionInput {
2252
+ motionPrompt: string;
2253
+ sourceImageUrl: string;
2254
+ provider?: string;
2255
+ name: string;
2256
+ /** Source clip URL — when set, worker routes to video-to-video refine. */
2257
+ refineFromVideoUrl?: string;
2258
+ category?: string;
2259
+ style?: "realistic" | "anime" | "3d-pixar" | "illustration";
2260
+ canonicalDescription?: string;
2261
+ /** Parameter-picker prompt-fragment pass-through (Pass 7 F-77). */
2262
+ seedPromptHint?: string;
2263
+ attachToObjectId?: string;
2264
+ attachName?: string;
2265
+ /**
2266
+ * Optional aspect ratio override. Defaults to 1:1 server-side. One of the
2267
+ * 5-value `ObjectAspectRatio` union (1:1 / 3:4 / 16:9 / 9:16 / 4:3) —
2268
+ * objects have their own enum (with 4:3 added) vs the character set.
2269
+ */
2270
+ aspectRatio?: ObjectAspectRatio;
2271
+ /**
2272
+ * Optional clip duration in seconds. Validated server-side against the chosen
2273
+ * provider's allowed durations (e.g. kling 5/10, wan-i2v 5/10/15); omitted →
2274
+ * the model's own default (no behavior change). Mirrors generate-video's
2275
+ * per-model i2v duration lever.
2276
+ */
2277
+ duration?: number;
2278
+ }
2279
+ interface GenerateObjectMotionResult {
2280
+ jobId: string;
2281
+ }
2282
+ interface ApproveObjectMainImageResult {
2283
+ sourceImageUrl: string;
2284
+ /**
2285
+ * LLM-authored caption. `null` when the LLM caption sub-failed — the wire
2286
+ * sends `""`, normalized to `null` here so consumers see the same
2287
+ * `string | null` semantics as characters. The main image is still set; call
2288
+ * `recaption()` to retry.
2289
+ */
2290
+ canonicalDescription: string | null;
2291
+ }
2292
+ interface RecaptionObjectResult {
2293
+ canonicalDescription: string;
2294
+ }
2295
+ declare class ObjectsResource {
2296
+ private client;
2297
+ constructor(client: NodaroClient);
2298
+ /**
2299
+ * List the caller's objects. By default returns active objects only;
2300
+ * pass `archived: true` to fetch soft-deleted rows for an "archive" view.
2301
+ * Optional `projectId` scopes the result to a single project.
2302
+ */
2303
+ list(params?: ListObjectsParams): Promise<{
2304
+ objects: Object$1[];
2305
+ }>;
2306
+ /**
2307
+ * Convenience wrapper for `list({ archived: true })`. Returns soft-deleted
2308
+ * rows so callers can drive a UI "Archived" tab without re-encoding the
2309
+ * query param.
2310
+ *
2311
+ * `archived` is omitted from the param type — it's always set to `true` here.
2312
+ */
2313
+ listArchived(params?: Omit<ListObjectsParams, "archived">): Promise<{
2314
+ objects: Object$1[];
2315
+ }>;
2316
+ /**
2317
+ * Fetch a single object including in-flight asset job state. Soft-deleted
2318
+ * (archived) rows are NOT returned by id — the route enforces
2319
+ * `deleted_at IS NULL` so archived objects 404 (uniform Pass 10 F-90b
2320
+ * "not_found" — does not leak the deleted vs non-existent distinction).
2321
+ */
2322
+ get(id: string): Promise<ObjectDetail>;
2323
+ /**
2324
+ * Create a new object. `name` + `nodeId` are required — the route 400s
2325
+ * otherwise. Returns the new row's id.
2326
+ *
2327
+ * Note: the underlying route is the same `POST /v1/objects` upsert that
2328
+ * powers `update()`. This convenience wrapper enforces the INSERT-required
2329
+ * fields at the type level and never sends an `id`.
2330
+ */
2331
+ create(data: CreateObjectInput): Promise<{
2332
+ id: string;
2333
+ }>;
2334
+ /**
2335
+ * Update an object. Only the fields you pass are written — undefined keys
2336
+ * are NOT touched on the row. Worker-owned asset buckets are intentionally
2337
+ * not exposed on this surface (see `UpdateObjectInput` for the rationale).
2338
+ *
2339
+ * Optimistic-concurrency: pass `expectedUpdatedAt` to require the row's
2340
+ * `updated_at` still matches; on mismatch the route returns 409
2341
+ * `concurrent_modification` carrying the fresh `updatedAt`. The SDK
2342
+ * surfaces that as a generic `NodaroError` with the same code (per Phase
2343
+ * E1 calibration finding — error centralization in `throwApiError`).
2344
+ */
2345
+ update(id: string, data: UpdateObjectInput): Promise<UpdateObjectResult>;
2346
+ /**
2347
+ * Soft-delete (archive) an object. The row is hidden from `list()` by
2348
+ * default but recoverable via `restore(id)` or visible under
2349
+ * `listArchived()`. Idempotent — repeating a delete on an already-archived
2350
+ * row is a no-op.
2351
+ */
2352
+ delete(id: string): Promise<{
2353
+ success: true;
2354
+ archived: true;
2355
+ }>;
2356
+ /**
2357
+ * Hard-delete (permanent) an object — the row + every R2 asset it
2358
+ * references. Archived rows ONLY: active objects return 400 `not_archived`.
2359
+ * Call `delete()` first to archive, then `permanentDelete()` to destroy.
2360
+ *
2361
+ * Mirrors the `app_runs` permanent-delete pattern (archive-first) so a
2362
+ * stray SDK / curl caller cannot bypass the studio's archive-first UI
2363
+ * flow.
2364
+ */
2365
+ permanentDelete(id: string): Promise<{
2366
+ success: true;
2367
+ permanent: true;
2368
+ }>;
2369
+ /**
2370
+ * Un-archive an object. If the original name now collides (case-
2371
+ * insensitive) with an active row, the server auto-suffixes "(restored)"
2372
+ * and returns the effective name.
2373
+ */
2374
+ restore(id: string): Promise<{
2375
+ id: string;
2376
+ name: string;
2377
+ }>;
2378
+ /**
2379
+ * Fire `POST /v1/generate-object` to produce one or more candidate main
2380
+ * images. With `count > 1`, all jobs are reserved up-front before any
2381
+ * is enqueued — mid-batch failures roll back atomically.
2382
+ *
2383
+ * When `attachToObjectId` is set AND `count === 1`, the worker writes
2384
+ * the result directly to the row's `source_image_url`; otherwise you must
2385
+ * call `approveMainImage()` after picking a candidate.
2386
+ */
2387
+ generate(data: GenerateObjectInput): Promise<GenerateObjectResult>;
2388
+ /**
2389
+ * Fire `POST /v1/generate-object-asset` to produce a single variant.
2390
+ * When the studio path is set (`attachToObjectId` + `attachToColumn` +
2391
+ * `attachName`), the worker appends `{ name: attachName, url: <result> }`
2392
+ * to the named JSONB array column on completion.
2393
+ *
2394
+ * Note: `attachToColumn` is REQUIRED for `assetType === "custom"` — the
2395
+ * worker can't infer the bucket from the asset type. For canonical asset
2396
+ * types (`angles` / `materials` / `variations` / `motion`), the column is
2397
+ * derived automatically by the route.
2398
+ */
2399
+ generateAsset(data: GenerateObjectAssetInput): Promise<GenerateObjectAssetResult>;
2400
+ /**
2401
+ * Fire `POST /v1/generate-object-motion` to animate the object's main
2402
+ * image into a motion clip. Image-to-video, single clip per call; the
2403
+ * attach column is hardcoded to `motion_clips` server-side (objects have a
2404
+ * single motion bucket so the caller doesn't supply `attachToColumn`).
2405
+ * When the studio path is set (`attachToObjectId` + `attachName`), the
2406
+ * worker appends `{ name: attachName, url: <result> }` to the row's
2407
+ * `motion_clips` column on completion.
2408
+ *
2409
+ * Defaults: `provider` → `"kling-turbo"`, `aspectRatio` → `"1:1"` (set
2410
+ * server-side via `resolveObjectAspectRatio({ assetType: "motion" })`).
2411
+ */
2412
+ generateMotion(data: GenerateObjectMotionInput): Promise<GenerateObjectMotionResult>;
2413
+ /**
2414
+ * Approve a completed `generate-object` job as the object's main image.
2415
+ * Sets `source_image_url` and fires the LLM caption (Claude Sonnet vision)
2416
+ * inline. Returns the new main-image URL plus the caption.
2417
+ *
2418
+ * Caption-failure semantics: the route still sends `""` on LLM sub-failure,
2419
+ * but the SDK normalizes `""` → `null` here so `canonicalDescription` carries
2420
+ * the same `string | null` semantics as characters. The main image is still
2421
+ * set; call `recaption()` to retry.
2422
+ *
2423
+ * Optimistic-concurrency: pass `expectedUpdatedAt` to gate the update on
2424
+ * the row's current `updated_at`; on mismatch the route returns 409
2425
+ * `concurrent_modification` carrying the fresh token.
2426
+ */
2427
+ approveMainImage(id: string, candidateJobId: string, expectedUpdatedAt?: string): Promise<ApproveObjectMainImageResult>;
2428
+ /**
2429
+ * Re-fire the LLM caption against the object's current main image. 502s
2430
+ * on LLM failure (unlike `approveMainImage` which preserves the side-effect
2431
+ * and returns ""); returns 400 `main_image_required` if no main image is
2432
+ * set yet.
2433
+ *
2434
+ * The route is a pure idempotent retry — it does NOT accept an
2435
+ * `expectedUpdatedAt` token (per Phase E1 calibration finding: backend
2436
+ * route is idempotent retry, not gated on optimistic-concurrency).
2437
+ */
2438
+ recaption(id: string): Promise<RecaptionObjectResult>;
2439
+ }
2440
+
2441
+ /**
2442
+ * Creature voice — IDENTICAL shape + semantics to `Character["voice"]` (the
2443
+ * "talking creature" stack reuses the character voice plumbing verbatim).
2444
+ * Render speech via `client.nodes.run("text-to-speech", { text, voice:
2445
+ * voiceId, provider: ttsProvider, voiceType })`, then feed the audio + the
2446
+ * creature's `sourceImageUrl` to `client.nodes.run("lip-sync", …)` (or
2447
+ * speech-to-video) for a talking-creature clip.
2448
+ */
2449
+ interface CreatureVoice {
2450
+ voiceId: string;
2451
+ voiceName: string;
2452
+ traits: string;
2453
+ voiceType?: "premade" | "library" | "custom";
2454
+ /** Playable preview sample (client-played only). */
2455
+ previewUrl?: string;
2456
+ /** Recommended TTS provider — send as `provider` on text-to-speech. */
2457
+ ttsProvider?: TtsProvider;
2458
+ }
2459
+ /**
2460
+ * Re-export the shared `CreatureAttachColumn` union + its runtime tuple so SDK
2461
+ * consumers don't have to add `@nodaro/shared` as a second dependency just to
2462
+ * typecheck the `attachToColumn` field. Single source of truth lives in
2463
+ * `@nodaro/shared/entity-prompts` (`CREATURE_ATTACH_COLUMNS`).
2464
+ *
2465
+ * `CreatureAspectRatio` is re-exported as an alias of the shared
2466
+ * `ObjectAspectRatio` — `generateMotion`'s `aspectRatio` field is the 5-value
2467
+ * object enum (1:1 / 3:4 / 16:9 / 9:16 / 4:3). The creature motion route
2468
+ * deliberately REUSES `OBJECT_ASPECT_OPTIONS` server-side (a creature reference
2469
+ * clip is centered product-showcase framing, not cinematic 16:9), so the SDK
2470
+ * surfaces the same enum under a creature-friendly name. The runtime tuple is
2471
+ * re-exported as `CREATURE_ASPECT_OPTIONS` / `CREATURE_ASPECT_DEFAULTS` aliases.
2472
+ */
2473
+ /**
2474
+ * Creature asset-type enum — the kinds of variant a user can generate off a
2475
+ * creature's anchor main image. Mirrors the literal accepted by
2476
+ * `POST /v1/generate-creature-asset` (`backend/src/routes/generate-creature-asset.ts`).
2477
+ *
2478
+ * Delta vs `ObjectAssetType`: object's `materials` becomes `poses` (a creature
2479
+ * has poses, not materials), and there is NO `motion` value — creature motion
2480
+ * variants flow through the dedicated `/v1/generate-creature-motion` endpoint
2481
+ * (worker-side a different BullMQ job type). `custom` is the free-form bucket;
2482
+ * callers must supply `attachToColumn` explicitly since the worker can't infer
2483
+ * the destination from the asset type.
2484
+ *
2485
+ * The shared `@nodaro/shared` package does NOT export a `CREATURE_ASSET_TYPES`
2486
+ * tuple (the route validates an inline Zod enum), so the SDK defines its own
2487
+ * single-source-of-truth tuple here.
2488
+ */
2489
+ declare const CREATURE_ASSET_TYPES: readonly ["angles", "poses", "variations", "custom"];
2490
+ type CreatureAssetType = (typeof CREATURE_ASSET_TYPES)[number];
2491
+ /**
2492
+ * Reference-photo kind discriminator — the mood-board roles a user can attach
2493
+ * to a creature. Mirrors the `reference_photos.kind` field accepted by
2494
+ * `backend/src/routes/creatures.ts` (the route accepts open strings; this SDK
2495
+ * type narrows to the 6 canonical roles surfaced by the Studio). `other` is
2496
+ * the free-form bucket.
2497
+ */
2498
+ type CreatureReferencePhotoKind = "front" | "side" | "detail" | "context" | "moodBoard" | "other";
2499
+ interface CreatureReferencePhoto {
2500
+ url: string;
2501
+ kind: CreatureReferencePhotoKind;
2502
+ }
2503
+ /**
2504
+ * A creature record returned by Nodaro's REST API. Mirrors the camelCase
2505
+ * shape produced by `backend/src/routes/creatures.ts::toCamel()`.
2506
+ *
2507
+ * Asset buckets (`angles`, `poses`, `variations`, `motionClips`) are
2508
+ * independent JSONB arrays keyed by a human-readable variant name (e.g.
2509
+ * `"front"`, `"walking"`, `"scarred"`). Each entry's `url` points at an
2510
+ * R2-hosted asset.
2511
+ *
2512
+ * Creature delta vs object:
2513
+ * - `species` — free-text creature type (e.g. `"dragon"`, `"wolf"`). This is
2514
+ * the subject of the establishing-shot prompt and the primary creature
2515
+ * differentiator (object has no equivalent).
2516
+ * - `poses` (where object has `materials`) — the pose-variant asset bucket.
2517
+ * - `category` is free-text (NOT object's fixed 10-value enum) — a creature
2518
+ * can be any animal/type.
2519
+ *
2520
+ * Identity-foundation fields:
2521
+ * - `referencePhotos` — caller-supplied mood-board refs (cap 20).
2522
+ * - `canonicalDescription` — ~80–120-word LLM-authored visual caption,
2523
+ * populated by `approveMainImage()` / `recaption()`. The wire still sends
2524
+ * `""` on caption sub-failure, but `get()` normalizes `""` → `null` so
2525
+ * consumers see the same `string | null` semantics as characters.
2526
+ * - `styleLock` — whether asset gens should anchor to the canonical style
2527
+ * captured at approval time. Defaults to `true` on new rows.
2528
+ */
2529
+ interface Creature {
2530
+ id: string;
2531
+ userId: string;
2532
+ nodeId: string;
2533
+ projectId: string | null;
2534
+ name: string;
2535
+ description: string | null;
2536
+ /** Free-text creature type/species (e.g. "dragon", "wolf") — the creature
2537
+ * delta vs object. `null` when unset. */
2538
+ species: string | null;
2539
+ category: string | null;
2540
+ style: string | null;
2541
+ sourceImageUrl: string | null;
2542
+ /** MODEL_CATALOG image-model id the main image was generated with (or `null`).
2543
+ * Set on create + editable via the update route. */
2544
+ imageProvider: string | null;
2545
+ angles: Array<{
2546
+ name: string;
2547
+ url: string;
2548
+ }>;
2549
+ poses: Array<{
2550
+ name: string;
2551
+ url: string;
2552
+ }>;
2553
+ variations: Array<{
2554
+ name: string;
2555
+ url: string;
2556
+ }>;
2557
+ motionClips: Array<{
2558
+ name: string;
2559
+ url: string;
2560
+ }>;
2561
+ /** Named Creature Boards — dense reference sheets, one per variant/mood
2562
+ * (the `generate-image/creature-board` factory preset rendered from the
2563
+ * creature's images). A first-class bucket: community publish snapshots it
2564
+ * and clone hands the consumer their own copy to extend. Defaults to `[]`.
2565
+ * `type` marks an image-collage `"identity"` sheet vs a plain `"looks"`
2566
+ * board; `sourceImages` are the R2 URLs it was collaged from. Both optional
2567
+ * + backward-compatible (legacy boards have neither). */
2568
+ boards?: Array<{
2569
+ name: string;
2570
+ url: string;
2571
+ type?: "looks" | "identity";
2572
+ sourceImages?: string[];
2573
+ }> | null;
2574
+ /** The creature's voice (the "talking creature" stack) — same shape and
2575
+ * flow as `Character["voice"]`. `null` when no voice is selected. */
2576
+ voice?: CreatureVoice | null;
2577
+ referencePhotos: CreatureReferencePhoto[];
2578
+ /** `null` when no caption is set (or the LLM caption sub-failed) — the wire
2579
+ * sends `""`, normalized to `null` in `get()` to match character semantics. */
2580
+ canonicalDescription: string | null;
2581
+ styleLock: boolean;
2582
+ /** The user's chosen DEFAULT asset take per variant (Studio version history).
2583
+ * OPAQUE map: key `"<bucket>:<variant>"` (e.g. `"angles:front"`) → the chosen
2584
+ * asset URL (one already present in that bucket). Stored verbatim — keys are
2585
+ * NOT normalized; soft-capped server-side at 200 keys / 2048-char values
2586
+ * (overflow dropped silently). Defaults to `{}`. */
2587
+ selectedAssetByVariant?: Record<string, string> | null;
2588
+ deletedAt: string | null;
2589
+ createdAt: string;
2590
+ updatedAt: string;
2591
+ }
2592
+ /**
2593
+ * GET /v1/creatures/:id may append a `pendingJobs` bucket the studio uses to
2594
+ * rehydrate spinners after a reload. Optional on the SDK surface — it doesn't
2595
+ * appear on `list()` rows.
2596
+ */
2597
+ interface CreatureDetail extends Creature {
2598
+ pendingJobs?: Array<{
2599
+ jobId: string;
2600
+ assetType: string;
2601
+ name: string;
2602
+ status: string;
2603
+ }>;
2604
+ }
2605
+ /**
2606
+ * Body for `client.creatures.create()`. Mirrors the INSERT branch of
2607
+ * `upsertCreatureBody` in `backend/src/routes/creatures.ts`. `name` + `nodeId`
2608
+ * are required on create.
2609
+ */
2610
+ interface CreateCreatureInput {
2611
+ nodeId: string;
2612
+ name: string;
2613
+ description?: string;
2614
+ /** Free-text creature type/species (the creature delta vs object). */
2615
+ species?: string;
2616
+ /** Free-text category (NOT object's fixed enum — a creature can be anything). */
2617
+ category?: string;
2618
+ style?: string;
2619
+ workflowId?: string;
2620
+ projectId?: string;
2621
+ sourceImageUrl?: string;
2622
+ /** Persistent image-model id (a MODEL_CATALOG image model). Validated
2623
+ * server-side — unknown / non-image / "" is stored as `null`. */
2624
+ imageProvider?: string | null;
2625
+ referencePhotos?: CreatureReferencePhoto[];
2626
+ canonicalDescription?: string;
2627
+ styleLock?: boolean;
2628
+ /** The user's chosen DEFAULT asset take per variant. OPAQUE
2629
+ * `"<bucket>:<variant>"` → chosen-URL map; a write REPLACES the whole map.
2630
+ * Keys stored verbatim; soft-capped server-side at 200 keys / 2048-char values. */
2631
+ selectedAssetByVariant?: Record<string, string>;
2632
+ /** Initial voice selection (see `CreatureVoice`). Omit for a voiceless creature. */
2633
+ voice?: CreatureVoice | null;
2634
+ }
2635
+ /**
2636
+ * Body for `client.creatures.update()`. Mirrors the UPDATE branch of
2637
+ * `upsertCreatureBody` in `backend/src/routes/creatures.ts`.
2638
+ *
2639
+ * Worker-owned asset buckets (`angles` / `poses` / `variations` /
2640
+ * `motionClips`) are deliberately omitted — the route drops them on UPDATE
2641
+ * so a Studio auto-save with a stale snapshot cannot clobber the worker's
2642
+ * atomic `append_creature_asset()` writes.
2643
+ *
2644
+ * `expectedUpdatedAt` is the optimistic-concurrency token: when present, the
2645
+ * UPDATE only succeeds if the row's `updated_at` still matches; on mismatch
2646
+ * the route returns 409 so the studio can re-fetch + merge.
2647
+ */
2648
+ interface UpdateCreatureInput {
2649
+ name?: string;
2650
+ description?: string;
2651
+ /** Free-text creature type/species (the creature delta vs object). */
2652
+ species?: string;
2653
+ category?: string;
2654
+ style?: string;
2655
+ sourceImageUrl?: string;
2656
+ /** Persistent image-model id (a MODEL_CATALOG image model). Validated
2657
+ * server-side — unknown / non-image / "" is stored as `null`. */
2658
+ imageProvider?: string | null;
2659
+ referencePhotos?: CreatureReferencePhoto[];
2660
+ canonicalDescription?: string;
2661
+ styleLock?: boolean;
2662
+ /** The user's chosen DEFAULT asset take per variant. OPAQUE
2663
+ * `"<bucket>:<variant>"` → chosen-URL map; a write REPLACES the whole map
2664
+ * (omit to leave untouched). Keys stored verbatim; soft-capped server-side
2665
+ * at 200 keys / 2048-char values. */
2666
+ selectedAssetByVariant?: Record<string, string>;
2667
+ /** Named Creature Boards (see `Creature.boards`) — whole-array replace,
2668
+ * USER-owned (unlike the worker-owned buckets it flows through UPDATE).
2669
+ * Server caps: 24 boards, 200-char names, 30 sourceImages per board. */
2670
+ boards?: Array<{
2671
+ name: string;
2672
+ url: string;
2673
+ type?: "looks" | "identity";
2674
+ sourceImages?: string[];
2675
+ }>;
2676
+ /** Voice selection (see `CreatureVoice`) — whole-object replace; pass
2677
+ * `null` to clear the voice, omit to leave untouched. */
2678
+ voice?: CreatureVoice | null;
2679
+ expectedUpdatedAt?: string;
2680
+ }
2681
+ interface UpdateCreatureResult {
2682
+ id: string;
2683
+ updatedAt: string;
2684
+ }
2685
+ /**
2686
+ * Combined create + update body (parameter for both branches). Exported for
2687
+ * callers that want to drive a single `upsert` flow without picking between
2688
+ * `Create*` and `Update*`. Mirrors the `upsertCreatureBody` Zod schema in
2689
+ * `backend/src/routes/creatures.ts`. `nodeId` + `name` are required on INSERT;
2690
+ * `id` flips the route into UPDATE mode.
2691
+ */
2692
+ interface UpsertCreatureInput extends CreateCreatureInput {
2693
+ id?: string;
2694
+ expectedUpdatedAt?: string;
2695
+ }
2696
+ type UpsertCreatureResult = {
2697
+ id: string;
2698
+ } | UpdateCreatureResult;
2699
+ interface ListCreaturesParams {
2700
+ /** When true, return archived creatures instead of active ones. */
2701
+ archived?: boolean;
2702
+ /** Optional project filter — server-scoped to the caller's user. */
2703
+ projectId?: string;
2704
+ }
2705
+ /**
2706
+ * Input for `client.creatures.generate()` — fires the
2707
+ * `POST /v1/generate-creature` route. Produces 1–10 candidate
2708
+ * main images; each lands as one `jobs` row in `pending` state and is then
2709
+ * enqueued for the worker.
2710
+ *
2711
+ * When `attachToCreatureId` is set AND `count === 1`, the worker writes the
2712
+ * resulting URL directly to `creatures.source_image_url` on completion —
2713
+ * caller doesn't need a separate `approveMainImage` call. Multi-candidate
2714
+ * batches MUST go through explicit approval so the user picks the winner.
2715
+ *
2716
+ * `seedPromptHint` flows the parameter-picker's prompt fragment through to the
2717
+ * worker so a catalog selection (e.g. "armored frost dragon") gets appended to
2718
+ * the generated prompt context.
2719
+ */
2720
+ interface GenerateCreatureInput {
2721
+ name: string;
2722
+ description?: string;
2723
+ userPrompt?: string;
2724
+ /** Free-text creature type/species (the creature delta vs object). */
2725
+ species?: string;
2726
+ category?: string;
2727
+ style?: "realistic" | "anime" | "3d-pixar" | "illustration";
2728
+ sourceImageUrl?: string;
2729
+ provider?: string;
2730
+ /** Number of candidate images to generate (1–10; server-validated). */
2731
+ count?: number;
2732
+ /** Auto-attach the result to this creature row (single-candidate only). */
2733
+ attachToCreatureId?: string;
2734
+ /** Parameter-picker prompt-fragment pass-through. */
2735
+ seedPromptHint?: string;
2736
+ /** Optional name to set on the attached row alongside the main image. */
2737
+ attachName?: string;
2738
+ /** Optimistic-concurrency token for the single-candidate auto-attach path. */
2739
+ expectedUpdatedAt?: string;
2740
+ }
2741
+ /**
2742
+ * `generate()` response — `jobIds` is ALWAYS present (the harmonized contract,
2743
+ * matching characters). `jobId` is a deprecated back-compat alias populated only
2744
+ * on `count === 1`; prefer `jobIds`. (Will be removed on the next major.)
2745
+ */
2746
+ interface GenerateCreatureResult {
2747
+ jobIds: string[];
2748
+ /** @deprecated count===1 back-compat alias — use `jobIds`. */
2749
+ jobId?: string;
2750
+ }
2751
+ /**
2752
+ * Input for `client.creatures.generateAsset()` — fires the
2753
+ * `POST /v1/generate-creature-asset` route. Produces a single
2754
+ * angles / poses / variations / custom variant.
2755
+ *
2756
+ * When all three studio-path fields are set (`attachToCreatureId` +
2757
+ * `attachToColumn` + `attachName`), the worker appends
2758
+ * `{ name: attachName, url: <result> }` to the named JSONB array column on
2759
+ * the user's creature row on completion. `attachToColumn` is REQUIRED for
2760
+ * `assetType === "custom"` — the worker can't infer the bucket from the
2761
+ * asset type.
2762
+ */
2763
+ interface GenerateCreatureAssetInput {
2764
+ assetType: CreatureAssetType;
2765
+ variant: string;
2766
+ name: string;
2767
+ description?: string;
2768
+ userPrompt?: string;
2769
+ category?: string;
2770
+ style?: string;
2771
+ sourceImageUrl?: string;
2772
+ provider?: string;
2773
+ attachToCreatureId?: string;
2774
+ attachToColumn?: CreatureAttachColumn;
2775
+ attachName?: string;
2776
+ /** Parameter-picker prompt-fragment pass-through. */
2777
+ seedPromptHint?: string;
2778
+ }
2779
+ interface GenerateCreatureAssetResult {
2780
+ jobId: string;
2781
+ }
2782
+ /**
2783
+ * Input for `client.creatures.generateMotion()` — fires the
2784
+ * `POST /v1/generate-creature-motion` route. Produces a single motion clip
2785
+ * (idle, prowl, attack, etc.) animated FROM a static creature-shot image.
2786
+ *
2787
+ * Mirrors `client.objects.generateMotion()` — the creature motion route reuses
2788
+ * the entity-agnostic object motion helpers server-side. The route hardcodes
2789
+ * the attach column to `motion_clips` — callers supply `attachToCreatureId` +
2790
+ * `attachName` only.
2791
+ *
2792
+ * `sourceImageUrl` is REQUIRED — image-to-video needs a source frame and the
2793
+ * route has no fallback (the studio path supplies the canonical creature-shot
2794
+ * URL explicitly).
2795
+ *
2796
+ * Defaults vs location:
2797
+ * - `provider` defaults to `"kling-turbo"` (not location's `"kling"`)
2798
+ * - `aspectRatio` defaults to `"1:1"` server-side via
2799
+ * `resolveObjectAspectRatio({ assetType: "motion" })` — creatures use
2800
+ * centered reference framing, not cinematic establishing shots.
2801
+ *
2802
+ * When the studio path is set (`attachToCreatureId` + `attachName`), the
2803
+ * worker appends `{ name: attachName, url: <result> }` to the creature row's
2804
+ * `motion_clips` JSONB column on completion.
2805
+ */
2806
+ interface GenerateCreatureMotionInput {
2807
+ motionPrompt: string;
2808
+ sourceImageUrl: string;
2809
+ provider?: string;
2810
+ name: string;
2811
+ /** Source clip URL — when set, worker routes to video-to-video refine. */
2812
+ refineFromVideoUrl?: string;
2813
+ category?: string;
2814
+ style?: "realistic" | "anime" | "3d-pixar" | "illustration";
2815
+ canonicalDescription?: string;
2816
+ /** Parameter-picker prompt-fragment pass-through. */
2817
+ seedPromptHint?: string;
2818
+ attachToCreatureId?: string;
2819
+ attachName?: string;
2820
+ /**
2821
+ * Optional aspect ratio override. Defaults to 1:1 server-side. One of the
2822
+ * 5-value `CreatureAspectRatio` union (1:1 / 3:4 / 16:9 / 9:16 / 4:3) — the
2823
+ * creature route reuses the object aspect enum.
2824
+ */
2825
+ aspectRatio?: ObjectAspectRatio;
2826
+ /**
2827
+ * Optional clip duration in seconds. Validated server-side against the chosen
2828
+ * provider's allowed durations (e.g. kling 5/10, wan-i2v 5/10/15); omitted →
2829
+ * the model's own default (no behavior change). Mirrors generate-video's
2830
+ * per-model i2v duration lever.
2831
+ */
2832
+ duration?: number;
2833
+ }
2834
+ interface GenerateCreatureMotionResult {
2835
+ jobId: string;
2836
+ }
2837
+ interface ApproveCreatureMainImageResult {
2838
+ sourceImageUrl: string;
2839
+ /**
2840
+ * LLM-authored caption. `null` when the LLM caption sub-failed — the wire
2841
+ * sends `""`, normalized to `null` here so consumers see the same
2842
+ * `string | null` semantics as characters. The main image is still set; call
2843
+ * `recaption()` to retry.
2844
+ */
2845
+ canonicalDescription: string | null;
2846
+ }
2847
+ interface RecaptionCreatureResult {
2848
+ canonicalDescription: string;
2849
+ }
2850
+ declare class CreaturesResource {
2851
+ private client;
2852
+ constructor(client: NodaroClient);
2853
+ /**
2854
+ * List the caller's creatures. By default returns active creatures only;
2855
+ * pass `archived: true` to fetch soft-deleted rows for an "archive" view.
2856
+ * Optional `projectId` scopes the result to a single project.
2857
+ */
2858
+ list(params?: ListCreaturesParams): Promise<{
2859
+ creatures: Creature[];
2860
+ }>;
2861
+ /**
2862
+ * Convenience wrapper for `list({ archived: true })`. Returns soft-deleted
2863
+ * rows so callers can drive a UI "Archived" tab without re-encoding the
2864
+ * query param.
2865
+ *
2866
+ * `archived` is omitted from the param type — it's always set to `true` here.
2867
+ */
2868
+ listArchived(params?: Omit<ListCreaturesParams, "archived">): Promise<{
2869
+ creatures: Creature[];
2870
+ }>;
2871
+ /**
2872
+ * Fetch a single creature including in-flight asset job state. Soft-deleted
2873
+ * (archived) rows are NOT returned by id — the route enforces
2874
+ * `deleted_at IS NULL` so archived creatures 404 (uniform "not_found" — does
2875
+ * not leak the deleted vs non-existent distinction).
2876
+ */
2877
+ get(id: string): Promise<CreatureDetail>;
2878
+ /**
2879
+ * Create a new creature. `name` + `nodeId` are required — the route 400s
2880
+ * otherwise. Returns the new row's id.
2881
+ *
2882
+ * Note: the underlying route is the same `POST /v1/creatures` upsert that
2883
+ * powers `update()`. This convenience wrapper enforces the INSERT-required
2884
+ * fields at the type level and never sends an `id`.
2885
+ */
2886
+ create(data: CreateCreatureInput): Promise<{
2887
+ id: string;
2888
+ }>;
2889
+ /**
2890
+ * Update a creature. Only the fields you pass are written — undefined keys
2891
+ * are NOT touched on the row. Worker-owned asset buckets are intentionally
2892
+ * not exposed on this surface (see `UpdateCreatureInput` for the rationale).
2893
+ *
2894
+ * Optimistic-concurrency: pass `expectedUpdatedAt` to require the row's
2895
+ * `updated_at` still matches; on mismatch the route returns 409
2896
+ * `concurrent_modification` carrying the fresh `updatedAt`. The SDK
2897
+ * surfaces that as a generic `NodaroError` with the same code.
2898
+ */
2899
+ update(id: string, data: UpdateCreatureInput): Promise<UpdateCreatureResult>;
2900
+ /**
2901
+ * Soft-delete (archive) a creature. The row is hidden from `list()` by
2902
+ * default but recoverable via `restore(id)` or visible under
2903
+ * `listArchived()`. Idempotent — repeating a delete on an already-archived
2904
+ * row is a no-op.
2905
+ */
2906
+ delete(id: string): Promise<{
2907
+ success: true;
2908
+ archived: true;
2909
+ }>;
2910
+ /**
2911
+ * Hard-delete (permanent) a creature — the row + every R2 asset it
2912
+ * references. Archived rows ONLY: active creatures return 400 `not_archived`.
2913
+ * Call `delete()` first to archive, then `permanentDelete()` to destroy.
2914
+ *
2915
+ * Mirrors the `app_runs` permanent-delete pattern (archive-first) so a
2916
+ * stray SDK / curl caller cannot bypass the studio's archive-first UI
2917
+ * flow.
2918
+ */
2919
+ permanentDelete(id: string): Promise<{
2920
+ success: true;
2921
+ permanent: true;
2922
+ }>;
2923
+ /**
2924
+ * Un-archive a creature. If the original name now collides (case-
2925
+ * insensitive) with an active row, the server auto-suffixes "(restored)"
2926
+ * and returns the effective name.
2927
+ */
2928
+ restore(id: string): Promise<{
2929
+ id: string;
2930
+ name: string;
2931
+ }>;
2932
+ /**
2933
+ * Fire `POST /v1/generate-creature` to produce one or more candidate main
2934
+ * images. With `count > 1`, all jobs are reserved up-front before any
2935
+ * is enqueued — mid-batch failures roll back atomically.
2936
+ *
2937
+ * When `attachToCreatureId` is set AND `count === 1`, the worker writes
2938
+ * the result directly to the row's `source_image_url`; otherwise you must
2939
+ * call `approveMainImage()` after picking a candidate.
2940
+ */
2941
+ generate(data: GenerateCreatureInput): Promise<GenerateCreatureResult>;
2942
+ /**
2943
+ * Fire `POST /v1/generate-creature-asset` to produce a single variant.
2944
+ * When the studio path is set (`attachToCreatureId` + `attachToColumn` +
2945
+ * `attachName`), the worker appends `{ name: attachName, url: <result> }`
2946
+ * to the named JSONB array column on completion.
2947
+ *
2948
+ * Note: `attachToColumn` is REQUIRED for `assetType === "custom"` — the
2949
+ * worker can't infer the bucket from the asset type. For canonical asset
2950
+ * types (`angles` / `poses` / `variations`), the column is derived
2951
+ * automatically by the route.
2952
+ */
2953
+ generateAsset(data: GenerateCreatureAssetInput): Promise<GenerateCreatureAssetResult>;
2954
+ /**
2955
+ * Fire `POST /v1/generate-creature-motion` to animate the creature's main
2956
+ * image into a motion clip. Image-to-video, single clip per call; the
2957
+ * attach column is hardcoded to `motion_clips` server-side (creatures have a
2958
+ * single motion bucket so the caller doesn't supply `attachToColumn`).
2959
+ * When the studio path is set (`attachToCreatureId` + `attachName`), the
2960
+ * worker appends `{ name: attachName, url: <result> }` to the row's
2961
+ * `motion_clips` column on completion.
2962
+ *
2963
+ * Defaults: `provider` → `"kling-turbo"`, `aspectRatio` → `"1:1"` (set
2964
+ * server-side via `resolveObjectAspectRatio({ assetType: "motion" })`).
2965
+ */
2966
+ generateMotion(data: GenerateCreatureMotionInput): Promise<GenerateCreatureMotionResult>;
2967
+ /**
2968
+ * Approve a completed `generate-creature` job as the creature's main image.
2969
+ * Sets `source_image_url` and fires the LLM caption (Claude Sonnet vision)
2970
+ * inline. Returns the new main-image URL plus the caption.
2971
+ *
2972
+ * Caption-failure semantics: the route still sends `""` on LLM sub-failure,
2973
+ * but the SDK normalizes `""` → `null` here so `canonicalDescription` carries
2974
+ * the same `string | null` semantics as characters. The main image is still
2975
+ * set; call `recaption()` to retry.
2976
+ *
2977
+ * Optimistic-concurrency: pass `expectedUpdatedAt` to gate the update on
2978
+ * the row's current `updated_at`; on mismatch the route returns 409
2979
+ * `concurrent_modification` carrying the fresh token.
2980
+ */
2981
+ approveMainImage(id: string, candidateJobId: string, expectedUpdatedAt?: string): Promise<ApproveCreatureMainImageResult>;
2982
+ /**
2983
+ * Re-fire the LLM caption against the creature's current main image. 502s
2984
+ * on LLM failure (unlike `approveMainImage` which preserves the side-effect
2985
+ * and returns ""); returns 400 `main_image_required` if no main image is
2986
+ * set yet.
2987
+ *
2988
+ * The route is a pure idempotent retry — it does NOT accept an
2989
+ * `expectedUpdatedAt` token (backend route is idempotent retry, not gated on
2990
+ * optimistic-concurrency).
2991
+ */
2992
+ recaption(id: string): Promise<RecaptionCreatureResult>;
2993
+ }
2994
+
2995
+ /**
2996
+ * Owner-scoped pipeline record returned by `get` / `list` (the server strips
2997
+ * `user_id`). Mirrors the public field set of `GET /v1/pipelines/:id`.
2998
+ */
2999
+ interface PipelineRecord {
3000
+ id: string;
3001
+ status: PipelineStatus;
3002
+ current_stage: string | null;
3003
+ spent_credits: number;
3004
+ reserved_credits: number;
3005
+ upfront_credit_estimate: number;
3006
+ branched_from_pipeline_id: string | null;
3007
+ branched_from_stage: string | null;
3008
+ mode: PipelineMode | null;
3009
+ failure_reason: string | null;
3010
+ current_progress_message: string | null;
3011
+ }
3012
+ /** One stage currently awaiting approval (from `pendingApprovals`). */
3013
+ interface PendingApproval {
3014
+ stage_name: PipelineStageName;
3015
+ /** Stage output snapshot; shape varies by stage. */
3016
+ output: unknown;
3017
+ }
3018
+ /**
3019
+ * Assembled timeline (`GET /v1/pipelines/:id/timeline`) — ordered scene
3020
+ * composites + their durations, plus optional music/narration and live
3021
+ * per-shot animate progress. The data the studio turns into a render.
3022
+ */
3023
+ interface PipelineTimeline {
3024
+ fps: number;
3025
+ width: number;
3026
+ height: number;
3027
+ scenes: Array<{
3028
+ compositeUrl: string;
3029
+ durationSeconds: number;
3030
+ }>;
3031
+ musicUrl?: string;
3032
+ narrationUrl?: string;
3033
+ animateProgress?: {
3034
+ totalShots: number;
3035
+ shotsDone: number;
3036
+ percent: number;
3037
+ };
3038
+ }
3039
+ interface BranchPipelineInput {
3040
+ /** The stage to re-run from. Upstream stages are cloned as approved. */
3041
+ fromStage: PipelineStageName;
3042
+ }
3043
+ interface BranchPipelineResult {
3044
+ /** The id of the newly created pipeline. */
3045
+ pipelineId: string;
3046
+ /** Stage names that were cloned as 'approved' (stages before `fromStage`). */
3047
+ clonedStages: string[];
3048
+ /** Number of entity rows cloned into the new pipeline. */
3049
+ clonedEntities: number;
3050
+ }
3051
+ /**
3052
+ * A single chat turn returned by `getStageChat`. Mirrors the
3053
+ * `pipeline_chat_turns` row shape selected by the GET handler.
3054
+ *
3055
+ * `@nodaro/shared` does not yet export a Zod schema for the full row; it only
3056
+ * exports `ChatTurnResponseSchema` (the LLM response shape) and the
3057
+ * `ProposedChange` discriminated union. Define the wire-format row locally so
3058
+ * callers get end-to-end typing today without re-shaping the backend payload.
3059
+ */
3060
+ interface ChatTurn {
3061
+ id: string;
3062
+ turn_n: number;
3063
+ role: "user" | "assistant";
3064
+ content: string;
3065
+ proposed_change: ProposedChange | null;
3066
+ llm_call_id: string | null;
3067
+ applied_to_attempt_id: string | null;
3068
+ created_at: string;
3069
+ }
3070
+ /**
3071
+ * Result of `chatStage` — the assistant turn that was just persisted. The
3072
+ * route always echoes the assistant message back so callers can render the
3073
+ * reply without a follow-up GET (SSE is the secondary delivery channel).
3074
+ */
3075
+ interface ChatStageResult {
3076
+ turnId: string;
3077
+ role: "assistant";
3078
+ content: string;
3079
+ proposed_change: ProposedChange | null;
3080
+ }
3081
+ /**
3082
+ * Result of `applyChatProposal`. Discriminated on `applied`:
3083
+ *
3084
+ * - `applied: true` — `applyStageEdit` validated + persisted a new attempt and
3085
+ * flipped the stage to approved. `newOutput` is the post-patch artifact.
3086
+ * - `applied: false` — recoverable failure (schema_invalid or
3087
+ * reference_integrity_failed); the backend has already inserted a follow-up
3088
+ * assistant turn with a human-readable hint so the user can iterate via
3089
+ * chat. Hard failures throw via the client's error pipeline (409).
3090
+ */
3091
+ type ApplyChatProposalResult = {
3092
+ applied: true;
3093
+ attemptId: string;
3094
+ newOutput: unknown;
3095
+ } | {
3096
+ applied: false;
3097
+ error: {
3098
+ code: string;
3099
+ detail?: unknown;
3100
+ };
3101
+ };
3102
+ declare class PipelinesResource {
3103
+ private client;
3104
+ constructor(client: NodaroClient);
3105
+ /**
3106
+ * Start a new pipeline (headless film generation) — the programmatic
3107
+ * equivalent of the studio's "Create film". In Auto mode the engine
3108
+ * self-advances to completion; poll {@link get} for status and
3109
+ * {@link getTimeline} for the assembled output. In manual/guided mode, drive
3110
+ * it with {@link pendingApprovals} + {@link approveStage} /
3111
+ * {@link approveSubGate}.
3112
+ *
3113
+ * Requires `pipelines:execute` scope. Returns the new pipeline id.
3114
+ */
3115
+ create(input: PipelineInput): Promise<{
3116
+ id: string;
3117
+ }>;
3118
+ /**
3119
+ * Fetch current pipeline state: `status`, `current_stage`, credit counters,
3120
+ * `mode`, and `failure_reason` (set when `status='failed'`). Poll this to
3121
+ * track a headless Auto run to completion. Requires `pipelines:read`.
3122
+ */
3123
+ get(id: string): Promise<PipelineRecord>;
3124
+ /** List the caller's pipelines (most recent first). Requires `pipelines:read`. */
3125
+ list(): Promise<PipelineRecord[]>;
3126
+ /**
3127
+ * Cancel a running pipeline. Unspent reserved credits refund. Idempotent on
3128
+ * an already-terminal pipeline. Requires `pipelines:execute`.
3129
+ */
3130
+ cancel(id: string): Promise<{
3131
+ ok: true;
3132
+ }>;
3133
+ /**
3134
+ * Stages currently `awaiting_approval`. Empty in a clean Auto run (the engine
3135
+ * self-approves); populated in manual/guided mode at each gate. Requires
3136
+ * `pipelines:read`.
3137
+ */
3138
+ pendingApprovals(id: string): Promise<PendingApproval[]>;
3139
+ /**
3140
+ * Approve a stage so the engine advances to the next one. An optional `edits`
3141
+ * JSON-Patch is applied to the stage output before approval. Requires
3142
+ * `pipelines:approve`.
3143
+ */
3144
+ approveStage(id: string, stage: PipelineStageName, edits?: unknown): Promise<{
3145
+ ok: true;
3146
+ }>;
3147
+ /**
3148
+ * Reject a stage with feedback; the engine re-runs it incorporating the note.
3149
+ * Requires `pipelines:approve`.
3150
+ */
3151
+ rejectStage(id: string, stage: PipelineStageName, feedback: string): Promise<{
3152
+ ok: true;
3153
+ }>;
3154
+ /**
3155
+ * Approve a Stage-7 sub-gate (`dialogue_recheck` / `silent_cut`) so the
3156
+ * orchestrator resumes from the next sub-step. Requires `pipelines:approve`.
3157
+ */
3158
+ approveSubGate(id: string, gate: SubGateName): Promise<{
3159
+ ok: true;
3160
+ gate: SubGateName;
3161
+ resumed_at: string;
3162
+ }>;
3163
+ /**
3164
+ * Read a single stage's `status`, `output`, and `critic_feedback`. Useful for
3165
+ * inspecting the script/plan before approving. Requires `pipelines:read`.
3166
+ */
3167
+ getStage(id: string, stage: PipelineStageName): Promise<{
3168
+ status: string;
3169
+ output: unknown;
3170
+ critic_feedback: unknown;
3171
+ }>;
3172
+ /**
3173
+ * Assembled timeline — ordered scene composites + durations + audio URLs +
3174
+ * live animate progress. The output a headless caller renders or hands to a
3175
+ * downstream editor. Requires `pipelines:read`.
3176
+ */
3177
+ getTimeline(id: string): Promise<PipelineTimeline>;
3178
+ /**
3179
+ * Branch a completed pipeline into a new pipeline that re-runs from the
3180
+ * given stage. The original pipeline's upstream stages and entities are
3181
+ * cloned into the new pipeline; downstream stages are created by the
3182
+ * orchestrator as it advances.
3183
+ *
3184
+ * Requires `pipelines:execute` scope.
3185
+ * The source pipeline must have `status='completed'`.
3186
+ *
3187
+ * @returns 201 with `{ pipelineId, clonedStages, clonedEntities }`.
3188
+ */
3189
+ branch(id: string, input: BranchPipelineInput): Promise<BranchPipelineResult>;
3190
+ /**
3191
+ * Send a chat message to the Showrunner Refinement Director (Guided Mode).
3192
+ * Persists user + assistant turns; returns the assistant's reply and an
3193
+ * optional `proposed_change` the user can `applyChatProposal` to commit.
3194
+ *
3195
+ * Requires `pipelines:approve` scope. The pipeline must have
3196
+ * `mode='guided'` and the stage must be `awaiting_approval`.
3197
+ *
3198
+ * Only the Script stage ships a wired specialist in Phase 1D.2b — the other
3199
+ * chat-enabled stages (`shot_list`, `post_merge`) return 501 until 1D.2d.
3200
+ */
3201
+ chatStage(pipelineId: string, stage: ChatEnabledStage, message: string): Promise<ChatStageResult>;
3202
+ /**
3203
+ * Accept a proposed change from a prior assistant turn. Routes through
3204
+ * `applyStageEdit` (validates JSON Patch + per-stage schema +
3205
+ * reference-integrity, inserts a new pipeline_stage_attempts row, CAS-flips
3206
+ * the stage to approved, emits `chat:proposal_applied` SSE).
3207
+ *
3208
+ * Requires `pipelines:approve` scope.
3209
+ *
3210
+ * Returns `{ applied: true, attemptId, newOutput }` on success, or
3211
+ * `{ applied: false, error }` on recoverable failures (the backend already
3212
+ * inserted a follow-up assistant turn with a hint). Hard failures
3213
+ * (`patch_invalid`, `stage_not_awaiting`) throw via the standard error
3214
+ * pipeline (HTTP 409).
3215
+ */
3216
+ applyChatProposal(pipelineId: string, stage: ChatEnabledStage, turnId: string): Promise<ApplyChatProposalResult>;
3217
+ /**
3218
+ * Fetch the chat history for a stage. Returns an empty array when no turns
3219
+ * exist yet (e.g., stage has not been started or the user hasn't sent any
3220
+ * messages). Used by the frontend chat panel on initial mount; subsequent
3221
+ * updates arrive via SSE (`chat:turn` events).
3222
+ *
3223
+ * Requires `pipelines:read` scope.
3224
+ */
3225
+ getStageChat(pipelineId: string, stage: ChatEnabledStage): Promise<{
3226
+ turns: ChatTurn[];
3227
+ }>;
3228
+ }
3229
+
3230
+ interface ReduceInput {
3231
+ /** Which fan-in strategy to run. */
3232
+ strategyId: ReduceStrategyId;
3233
+ /**
3234
+ * Strategy-specific config. Defaults to `{}` server-side, which uses every
3235
+ * strategy's `defaultConfig`. Schemas (from `@nodaro/shared`):
3236
+ * - `pick-best-llm`: `{ criteria: string, inputKind?: "text" | "image-url" }`
3237
+ * - `concat`: `{ separator?: string }`
3238
+ * - `vote`: `{ caseSensitive?: boolean }`
3239
+ * - `merge-json`: `{ strategy?: "deep" | "shallow" }`
3240
+ * - `first-non-empty`, `count`: `{}`
3241
+ */
3242
+ strategyConfig?: Record<string, unknown>;
3243
+ /** Up to 1000 input strings (URLs, text fragments, etc.). */
3244
+ inputs: string[];
3245
+ /**
3246
+ * Optional — associates this reduce run with a workflow execution. The
3247
+ * server reads this from the body before Zod strips it (same path as
3248
+ * other job-creating routes).
3249
+ */
3250
+ workflowId?: string;
3251
+ }
3252
+ interface ReduceResult {
3253
+ jobId: string;
3254
+ /**
3255
+ * Stringified result — for `count` this is a numeric string, for
3256
+ * `merge-json` this is the JSON-encoded merged object, otherwise the
3257
+ * chosen / joined text.
3258
+ */
3259
+ output: string;
3260
+ meta: ReduceMeta;
3261
+ }
3262
+ declare class ReduceResource {
3263
+ private client;
3264
+ constructor(client: NodaroClient);
3265
+ /**
3266
+ * Run the Reduce (fan-in) node directly — useful for scripted batch
3267
+ * scoring, picking the best of N generations outside a workflow, or
3268
+ * one-shot programmatic merges.
3269
+ *
3270
+ * Throws `NodaroError` on 4xx/5xx responses (e.g. `code: "no_valid_inputs"`
3271
+ * with status 400 when every input is empty / whitespace; the underlying
3272
+ * `EmptyInputError` is mapped to a 400 server-side).
3273
+ */
3274
+ run(input: ReduceInput): Promise<ReduceResult>;
3275
+ }
3276
+
3277
+ interface CommonInput {
3278
+ nodeType: string;
3279
+ provider?: string;
3280
+ style?: string;
3281
+ aspectRatio?: string;
3282
+ duration?: number;
3283
+ llmModel?: string;
3284
+ nodeContext?: WizardNodeContext;
3285
+ userPreference?: string;
3286
+ /** Associates this call with a workflow execution. Read server-side before Zod. */
3287
+ workflowId?: string;
3288
+ }
3289
+ interface AnalyzeInput extends CommonInput {
3290
+ /** The user's rough idea. Omit to build questions from scratch. */
3291
+ prompt?: string;
3292
+ }
3293
+ interface AnalyzeResult {
3294
+ jobId: string;
3295
+ questions: WizardQuestion[];
3296
+ }
3297
+ interface GenerateInput extends CommonInput {
3298
+ /** The chosen answers from analyze. */
3299
+ selections: WizardSelection[];
3300
+ /** The user's original rough idea, woven into the generated prompt. */
3301
+ originalPrompt?: string;
3302
+ }
3303
+ interface EnhanceInput extends CommonInput {
3304
+ /** The rough idea to improve one-shot. Omit to build from scratch. */
3305
+ prompt?: string;
3306
+ }
3307
+ interface PromptResult {
3308
+ jobId: string;
3309
+ prompt: string;
3310
+ recommendedModel?: RecommendedModel;
3311
+ }
3312
+ /**
3313
+ * AI Prompt Wizard — help write/improve prompts for generation nodes.
3314
+ *
3315
+ * - `analyze` -> guided questions, `generate` -> prompt from selections (the
3316
+ * 2-step human flow), or `enhance` -> one-shot "improve this prompt".
3317
+ *
3318
+ * All three delegate to `POST /v1/prompt-helper/wizard`. Throws `NodaroError`
3319
+ * on 4xx/5xx (e.g. `validation_error`, `malformed_response`).
3320
+ */
3321
+ declare class PromptHelperResource {
3322
+ private client;
3323
+ constructor(client: NodaroClient);
3324
+ analyze(input: AnalyzeInput): Promise<AnalyzeResult>;
3325
+ generate(input: GenerateInput): Promise<PromptResult>;
3326
+ enhance(input: EnhanceInput): Promise<PromptResult>;
3327
+ }
3328
+
3329
+ /**
3330
+ * Re-export the shared voice types so SDK consumers don't have to add
3331
+ * `@nodaro/shared` as a second dependency just to type a `Voice` row, a
3332
+ * `VoiceClone`, or a `searchLibrary` call. Single source of truth lives in
3333
+ * `@nodaro/shared`.
3334
+ */
3335
+ /** Audio-FX preset union (reverb spaces / telephone / megaphone / echo / custom) — used by {@link VoiceChangerProInput.voiceFx}. */
3336
+ /**
3337
+ * Read access to ElevenLabs voices: the premade catalog plus the shared
3338
+ * community Voice Library (both public GETs, no body), and the signed-in
3339
+ * user's own voice clones (list / create-from-url / delete).
3340
+ */
3341
+ declare class VoicesResource {
3342
+ private client;
3343
+ constructor(client: NodaroClient);
3344
+ /**
3345
+ * List the premade ElevenLabs voices (`GET /v1/voices`). Falls back to a
3346
+ * curated set server-side when no ElevenLabs API key is configured.
3347
+ */
3348
+ list(): Promise<Voice[]>;
3349
+ /**
3350
+ * Search the shared/community Voice Library (`GET /v1/voices/library`). All
3351
+ * params are optional and forwarded as a querystring; `undefined` / `null` /
3352
+ * empty-string values are omitted so the server defaults apply. `hasMore`
3353
+ * drives "load more" pagination.
3354
+ */
3355
+ searchLibrary(params?: VoiceLibraryParams): Promise<VoiceLibraryResponse>;
3356
+ /**
3357
+ * List the signed-in user's voice clones (`GET /v1/voice-clones`). The route
3358
+ * wraps the rows in `{ voiceClones }`; we unwrap to the bare array.
3359
+ */
3360
+ listClones(): Promise<VoiceClone[]>;
3361
+ /**
3362
+ * Clone a voice from an already-uploaded audio URL
3363
+ * (`POST /v1/voice-clones/from-url`). Costs credits. Returns the create
3364
+ * subset of `VoiceClone` (`elevenlabsVoiceId` is the id to use at
3365
+ * text-to-speech time).
3366
+ */
3367
+ createClone(input: {
3368
+ name: string;
3369
+ audioUrl: string;
3370
+ }): Promise<VoiceClone>;
3371
+ /** Delete one of the user's voice clones (`DELETE /v1/voice-clones/:id`). */
3372
+ deleteClone(id: string): Promise<void>;
3373
+ /**
3374
+ * Replace the voice in a recording — or in a whole talking video — with a
3375
+ * different voice (`POST /v1/voice-changer`). Pass `audioUrl` to revoice
3376
+ * audio→audio, or `videoUrl` to revoice an entire clip (the server demuxes
3377
+ * the audio, runs speech-to-speech, and remuxes onto the original video,
3378
+ * returning the video plus the new audio track). Exactly one of `audioUrl` /
3379
+ * `videoUrl` is required; when both are sent, video wins. `removeBackgroundNoise`
3380
+ * off keeps the music/SFX bed under the new voice; on yields a clean voice-only
3381
+ * result. Costs credits and runs async — poll `jobs.get(jobId)` for the result
3382
+ * (`output_data.videoUrl` + `output_data.audioUrl` in video mode).
3383
+ */
3384
+ change(input: {
3385
+ voiceId: string;
3386
+ audioUrl?: string;
3387
+ videoUrl?: string;
3388
+ stability?: number;
3389
+ similarityBoost?: number;
3390
+ /** Style exaggeration (0–1). Default 0; >0 amplifies delivery at the cost of latency/stability. */
3391
+ style?: number;
3392
+ removeBackgroundNoise?: boolean;
3393
+ }): Promise<{
3394
+ jobId: string;
3395
+ }>;
3396
+ /**
3397
+ * Recast each detected speaker in a multi-speaker recording to a different
3398
+ * voice (`POST /v1/voice-changer-pro`). `orderedVoices` maps speaker positions to
3399
+ * voices in detection order — speaker 0 → `orderedVoices[0]`, speaker 1 →
3400
+ * `orderedVoices[1]`, etc. Speakers beyond the end of `orderedVoices` keep
3401
+ * their original voice. Each entry is EITHER a bare voice id (premade name or
3402
+ * ElevenLabs UUID) OR a {@link VoiceChangerProVoice} object carrying per-voice
3403
+ * ElevenLabs speech-to-speech settings (stability / similarityBoost / style /
3404
+ * useSpeakerBoost / `seed`) plus a loudness `volumeMode` (and a manual
3405
+ * `volume`). A per-voice `seed` makes that speaker's recast reproducible.
3406
+ *
3407
+ * Pass `audioUrl` for audio-only recast or `videoUrl` to recast the audio
3408
+ * track of a video clip (the server demuxes, recasts, and remuxes).
3409
+ *
3410
+ * Voice and music are ALWAYS separated first — ElevenLabs only ever sees the
3411
+ * isolated vocal stem, never the music bed. `preserveBackground` (default
3412
+ * `true`) only controls whether that music/instrumental stem is mixed back
3413
+ * under the new voices; set it `false` for a clean voice-only result.
3414
+ * `separationQuality` selects the demucs model used for that split: `"fast"`
3415
+ * (default, htdemucs — preserves more of the voice) or `"best"` (htdemucs_ft —
3416
+ * finer separation). `removeBackgroundNoise` additionally denoises the result.
3417
+ * `musicVolumeMode` sets the level of that preserved background (only relevant
3418
+ * when `preserveBackground` is on): `"match"` (default) keeps the original
3419
+ * level, `"normalize"` loudnorms it, `"manual"` uses `musicVolume`%.
3420
+ * `voiceFx` applies a reverb/echo to the COMBINED recast voices BEFORE the
3421
+ * background is mixed back in (effect sits on the voices, not the music bed).
3422
+ *
3423
+ * Cloud-only — costs credits and runs async; poll `jobs.get(jobId)` for the
3424
+ * result (`output_data.videoUrl` + `output_data.audioUrl` in video mode).
3425
+ */
3426
+ recast(input: VoiceChangerProInput): Promise<{
3427
+ jobId: string;
3428
+ }>;
3429
+ }
3430
+ /**
3431
+ * One entry in {@link VoiceChangerProInput.orderedVoices}. Either a bare voice id
3432
+ * (premade name like `"Rachel"` or an ElevenLabs UUID for a custom clone), or
3433
+ * an object pinning per-voice speech-to-speech settings and the recast's
3434
+ * loudness behaviour for that speaker.
3435
+ */
3436
+ type VoiceChangerProVoice = string | {
3437
+ /** Target voice — premade name (`"Rachel"`, `"Aria"`, …) or an ElevenLabs UUID for a custom clone. */
3438
+ voiceId: string;
3439
+ /** ElevenLabs stability (0–1). Higher = steadier, lower = more expressive. */
3440
+ stability?: number;
3441
+ /** ElevenLabs similarity boost (0–1) — how closely the output hugs the target voice's timbre. */
3442
+ similarityBoost?: number;
3443
+ /** Style exaggeration (0–1). Default 0; >0 amplifies delivery at the cost of latency / stability. */
3444
+ style?: number;
3445
+ /** ElevenLabs speaker boost — sharpens fidelity to the target speaker. */
3446
+ useSpeakerBoost?: boolean;
3447
+ /**
3448
+ * Deterministic speech-to-speech seed (integer 0–4294967295) for
3449
+ * reproducible per-voice output — the same source + settings + seed
3450
+ * recast this speaker identically across runs. Omit for a random seed.
3451
+ */
3452
+ seed?: number;
3453
+ /**
3454
+ * Loudness handling for this recast voice. `"match"` (default) matches the
3455
+ * original speaker's loudness; `"normalize"` applies EBU R128 loudnorm;
3456
+ * `"manual"` uses `volume` as a percentage.
3457
+ */
3458
+ volumeMode?: "match" | "normalize" | "manual";
3459
+ /** Manual output volume as a percentage (0–200). Consulted only when `volumeMode === "manual"`. */
3460
+ volume?: number;
3461
+ };
3462
+ /** Input for {@link VoicesResource.recast}. */
3463
+ interface VoiceChangerProInput {
3464
+ /** URL of an audio file to recast (audio → audio). Exactly one of `audioUrl` / `videoUrl` is required. */
3465
+ audioUrl?: string;
3466
+ /** URL of a video file to recast (the audio track is recast and remuxed). Exactly one of `audioUrl` / `videoUrl` is required. */
3467
+ videoUrl?: string;
3468
+ /**
3469
+ * Voices in speaker-detection order. Speaker N is mapped to `orderedVoices[N]`;
3470
+ * speakers beyond the array keep their original voice. Each entry is a bare
3471
+ * voice id OR a {@link VoiceChangerProVoice} object with per-voice settings.
3472
+ */
3473
+ orderedVoices: Array<VoiceChangerProVoice>;
3474
+ /** Model to use for speech-to-speech. Defaults to the server-configured default when omitted. */
3475
+ model?: string;
3476
+ /**
3477
+ * Mix the separated music / SFX stem back under the recast voices. Default
3478
+ * `true`. The voice is ALWAYS split out before recasting regardless of this
3479
+ * flag — `false` simply drops the music for a clean voice-only result.
3480
+ */
3481
+ preserveBackground?: boolean;
3482
+ /**
3483
+ * Demucs model used to split voice from music. `"fast"` (default, htdemucs —
3484
+ * preserves more of the voice) or `"best"` (htdemucs_ft — finer separation).
3485
+ */
3486
+ separationQuality?: "fast" | "best";
3487
+ /** Strip background noise for a clean voice-only result. */
3488
+ removeBackgroundNoise?: boolean;
3489
+ /**
3490
+ * Level of the preserved background music / SFX stem in the final mix. Only
3491
+ * relevant when `preserveBackground` is on (otherwise there is no background to
3492
+ * level). `"match"` (default) leaves the separated instrumental at its original
3493
+ * level; `"normalize"` applies EBU R128 loudnorm; `"manual"` sets its level to
3494
+ * `musicVolume`%.
3495
+ */
3496
+ musicVolumeMode?: "match" | "normalize" | "manual";
3497
+ /** Background music level as a percentage (0–200). Consulted only when `musicVolumeMode === "manual"`. */
3498
+ musicVolume?: number;
3499
+ /**
3500
+ * Node-level reverb/echo applied to the COMBINED recast voices **before** the
3501
+ * background is mixed back in (so the effect sits on the voices only, not the
3502
+ * music/SFX bed). Reverb presets (`room`, `hall`, `church`, …) use
3503
+ * `wetDryMix`; the `echo` / `custom` presets use `delayMs` + `decay`. Omit for
3504
+ * no effect.
3505
+ */
3506
+ voiceFx?: {
3507
+ /** Effect preset — reverb space, `telephone`, `megaphone`, `echo`, or `custom`. */
3508
+ preset: AudioFxPreset;
3509
+ /** Reverb wet/dry mix as a percentage (0–100). Higher = wetter (more reverb). */
3510
+ wetDryMix?: number;
3511
+ /** Echo delay in milliseconds (20–2000). Used by the `echo` / `custom` presets. */
3512
+ delayMs?: number;
3513
+ /** Echo decay / feedback (0–1). Higher = more repeats. Used by the `echo` / `custom` presets. */
3514
+ decay?: number;
3515
+ };
3516
+ }
3517
+
3518
+ /**
3519
+ * Authenticated user's credit balance — the shape of `GET /v1/user/credits`'s
3520
+ * `data` field. Mirrors the canonical `UserBalance` from the backend billing
3521
+ * module (`backend/src/ee/billing/credits.ts`), the source of truth: keep this
3522
+ * in sync if that interface changes.
3523
+ */
3524
+ interface UserBalance {
3525
+ total: number;
3526
+ subscription: number;
3527
+ topup: number;
3528
+ dailySpent: number;
3529
+ dailyLimit: number | null;
3530
+ monthlyAllocation: number;
3531
+ tier: string;
3532
+ features: Record<string, unknown>;
3533
+ periodEnd: string | null;
3534
+ /** Credits earned for app usage (free tier only — earned by running flows). */
3535
+ appCreditsAllowance: number;
3536
+ }
3537
+ /**
3538
+ * Result of `POST /v1/credits/model-costs` — a batch cost lookup for editor
3539
+ * cost previews. `data` maps each priced identifier → its credit cost.
3540
+ *
3541
+ * Per-model fault isolation (the route runs the lookups under
3542
+ * `Promise.allSettled`): identifiers with no pricing row are reported in
3543
+ * `missing` (undisplayable until an operator seeds a price) and lookup
3544
+ * failures in `errors`, instead of failing the whole batch. Callers typically
3545
+ * render `'—'` for any identifier that lands in `missing`. The hard-fail
3546
+ * policy still triggers at reservation time when the user actually runs the
3547
+ * node — this preview lookup is intentionally lenient.
3548
+ */
3549
+ interface ModelCostsResult {
3550
+ data: Record<string, number>;
3551
+ missing: string[];
3552
+ errors: string[];
3553
+ }
3554
+ declare class CreditsResource {
3555
+ private client;
3556
+ constructor(client: NodaroClient);
3557
+ /**
3558
+ * `GET /v1/user/credits` → the authenticated user's credit balance and tier
3559
+ * info. Throws `UnauthorizedError` (401) when signed out, and the SDK's
3560
+ * other typed errors on the usual statuses.
3561
+ */
3562
+ balance(): Promise<UserBalance>;
3563
+ /**
3564
+ * `POST /v1/credits/model-costs` → per-identifier credit cost, for editor
3565
+ * cost previews. Capped at the first {@link MODEL_COSTS_LIMIT} identifiers
3566
+ * (the route's request limit). Preserves the `{ data, missing, errors }`
3567
+ * fault-isolation shape verbatim (see {@link ModelCostsResult}).
3568
+ */
3569
+ modelCosts(ids: string[]): Promise<ModelCostsResult>;
3570
+ }
3571
+
3572
+ /**
3573
+ * Result of a successful `POST /v1/upload`. Mirrors the route's `data` envelope
3574
+ * (`backend/src/routes/upload.ts`), the source of truth — keep in sync if that
3575
+ * response changes. The route also returns loosely-typed extracted `metadata`;
3576
+ * it is omitted here until a consumer needs a typed shape (add it shaped to
3577
+ * that need rather than inventing `Record<string, unknown>` surface now).
3578
+ */
3579
+ interface UploadResult {
3580
+ /** Public R2 URL of the stored asset (always present on success). */
3581
+ readonly url: string;
3582
+ /** Storage row id; `null` when no asset row was written (e.g. unauthenticated). */
3583
+ readonly assetId: string | null;
3584
+ /** Generated thumbnail URL (images/video); `null` for audio or on failure. */
3585
+ readonly thumbnailUrl: string | null;
3586
+ /** Server-classified asset category (e.g. "image" | "video" | "audio"). */
3587
+ readonly category: string;
3588
+ /** Display filename (server override or the original). */
3589
+ readonly filename: string;
3590
+ /** Final MIME type after server normalization. */
3591
+ readonly mimeType: string;
3592
+ /** Stored byte size. */
3593
+ readonly sizeBytes: number;
3594
+ /** R2 object key. */
3595
+ readonly r2Key: string;
3596
+ }
3597
+ declare class UploadsResource {
3598
+ private client;
3599
+ constructor(client: NodaroClient);
3600
+ /**
3601
+ * Upload one file (`POST /v1/upload`, multipart — the file rides the `file`
3602
+ * field). The SDK's `request` detects the `FormData` body and lets the
3603
+ * runtime set the multipart boundary. Returns the persisted asset's public
3604
+ * URL + storage metadata (unwraps the `{ data }` envelope). Throws
3605
+ * `StorageExceededError` (413) over the storage cap and the SDK's other typed
3606
+ * errors on the usual statuses.
3607
+ */
3608
+ upload(file: File): Promise<UploadResult>;
3609
+ }
3610
+
3611
+ /**
3612
+ * A media asset in the caller's library (`GET /v1/library`). Mirrors the route's
3613
+ * camelCase row shape (`backend/src/routes/library.ts`), the source of truth —
3614
+ * keep in sync if that response changes. Covers BOTH uploaded files and saved
3615
+ * generations; `type` discriminates the media kind.
3616
+ */
3617
+ interface LibraryAsset {
3618
+ readonly id: string;
3619
+ /** Media kind — filter the list with the same values via `type`. */
3620
+ readonly type: "image" | "video" | "audio";
3621
+ /** Display filename (server override or the original); `null` if unknown. */
3622
+ readonly filename: string | null;
3623
+ readonly mimeType: string | null;
3624
+ readonly sizeBytes: number | null;
3625
+ /** Public R2 URL of the asset. */
3626
+ readonly url: string;
3627
+ /** Generated thumbnail URL (images/video); `null` for audio or when absent. */
3628
+ readonly thumbnailUrl: string | null;
3629
+ readonly metadata: Record<string, unknown>;
3630
+ /** True when promoted to the shared (admin) library. */
3631
+ readonly isLibraryItem: boolean;
3632
+ /** How the asset entered storage (e.g. "manual_upload" | "generated"). */
3633
+ readonly uploadSource: string;
3634
+ readonly createdAt: string;
3635
+ }
3636
+ interface ListLibraryParams {
3637
+ /** Filter by media kind; `"all"` (default) returns every kind. */
3638
+ readonly type?: "all" | "image" | "video" | "audio";
3639
+ /** Case-insensitive filename substring filter. */
3640
+ readonly search?: string;
3641
+ /** Page size, 1–100 (default 40). */
3642
+ readonly limit?: number;
3643
+ /** Opaque cursor from a prior page's `nextCursor` (fetches the next page). */
3644
+ readonly cursor?: string;
3645
+ /**
3646
+ * `true` → EVERY asset the caller owns (the "Storage" view: uploads +
3647
+ * generations, regardless of the in-library flag). `false` (default) → only
3648
+ * assets explicitly saved to the library plus shared items (the in-editor
3649
+ * Media Library picker).
3650
+ */
3651
+ readonly owned?: boolean;
3652
+ }
3653
+ interface ListLibraryResult {
3654
+ readonly data: LibraryAsset[];
3655
+ /** Pass back as `cursor` for the next page; `null` when there are no more. */
3656
+ readonly nextCursor: string | null;
3657
+ /** Exact total (first page only — omitted on cursor-paged requests). */
3658
+ readonly totalCount?: number;
3659
+ }
3660
+ /**
3661
+ * Library — the caller's media assets (uploaded files + saved generations).
3662
+ *
3663
+ * Read surface over `GET /v1/library`: a cursor-paginated, type-filterable,
3664
+ * filename-searchable list. This is the "bring from your media" source for
3665
+ * pickers that also offer upload + in-production stills. Writes (saving a
3666
+ * generation, promoting/removing) stay on their dedicated routes — not exposed
3667
+ * here until a consumer needs them.
3668
+ */
3669
+ declare class LibraryResource {
3670
+ private client;
3671
+ constructor(client: NodaroClient);
3672
+ /**
3673
+ * `GET /v1/library` → a page of the caller's media assets (newest first) plus
3674
+ * a `nextCursor`. Pass the returned `nextCursor` back as `cursor` for the next
3675
+ * page. Filter by `type` and a filename `search`; `owned: true` returns the
3676
+ * full Storage set (uploads + generations), the default only library-saved +
3677
+ * shared items.
3678
+ */
3679
+ list(params?: ListLibraryParams): Promise<ListLibraryResult>;
3680
+ }
3681
+
3682
+ /**
3683
+ * A user's saved custom preset (`GET /v1/node-presets`). Mirrors the backend's
3684
+ * camelCase row shape. `data` is captured node config — merge it into a node's
3685
+ * data when building/running a workflow to "apply" the preset.
3686
+ */
3687
+ interface NodePreset {
3688
+ id: string;
3689
+ nodeType: string;
3690
+ name: string;
3691
+ description?: string;
3692
+ data: Record<string, unknown>;
3693
+ groupId?: string;
3694
+ tags: string[];
3695
+ sortOrder: number;
3696
+ createdAt: string;
3697
+ updatedAt: string;
3698
+ }
3699
+ /** A user's preset folder/section (`GET /v1/node-preset-groups`). */
3700
+ interface NodePresetGroup {
3701
+ id: string;
3702
+ nodeType: string;
3703
+ name: string;
3704
+ kind: "folder" | "section";
3705
+ sortOrder: number;
3706
+ createdAt: string;
3707
+ updatedAt: string;
3708
+ }
3709
+ /**
3710
+ * Result of `GET /v1/node-presets/factory` — the built-in (factory) catalog for
3711
+ * a node type.
3712
+ */
3713
+ interface FactoryPresetsResult {
3714
+ data: FactoryPreset[];
3715
+ }
3716
+ /**
3717
+ * Node presets — reusable, named node configurations.
3718
+ *
3719
+ * Read-only over the API today: list your own custom presets and their folders,
3720
+ * and list the built-in factory catalog. To *use* a preset, take its `data` and
3721
+ * merge it into a node's config when you create/update a workflow. (Creating and
3722
+ * editing presets remains in the editor for now.)
3723
+ */
3724
+ declare class PresetsResource {
3725
+ private client;
3726
+ constructor(client: NodaroClient);
3727
+ /**
3728
+ * `GET /v1/node-presets` → your custom presets, newest first. Pass `nodeType`
3729
+ * (e.g. `"generate-image"`) to filter to one node type.
3730
+ */
3731
+ list(nodeType?: string): Promise<NodePreset[]>;
3732
+ /**
3733
+ * `GET /v1/node-preset-groups` → your preset folders/sections, in display
3734
+ * order. Pass `nodeType` to filter to one node type.
3735
+ */
3736
+ listGroups(nodeType?: string): Promise<NodePresetGroup[]>;
3737
+ /**
3738
+ * `GET /v1/node-presets/factory` → the built-in catalog for `nodeType`. These
3739
+ * ship with the app (no account needed to exist), so they're a good starting
3740
+ * point for "what configs are available".
3741
+ */
3742
+ listFactory(nodeType: string): Promise<FactoryPresetsResult>;
3743
+ }
3744
+
3745
+ /**
3746
+ * Picker-catalog types. Mirrors `@nodaro/shared`'s `ProjectedPickerCatalog` /
3747
+ * `PickerCatalogSummary` so the SDK stays dependency-free (same convention as
3748
+ * `NodeDescriptor` mirroring node-registry).
3749
+ */
3750
+ interface PickerOption {
3751
+ id: string;
3752
+ label: string;
3753
+ description?: string;
3754
+ category?: string;
3755
+ /** The prompt fragment this id injects downstream. Present only when detail="full". */
3756
+ promptHint?: string;
3757
+ icon?: string;
3758
+ }
3759
+ interface PickerDimension {
3760
+ field: string;
3761
+ label: string;
3762
+ options: PickerOption[];
3763
+ }
3764
+ interface PickerCatalog {
3765
+ nodeType: string;
3766
+ label: string;
3767
+ catalogId: string;
3768
+ kind: "single" | "multi";
3769
+ /** single only — the node-data field the chosen id writes to. */
3770
+ valueField?: string;
3771
+ defaultValue?: string;
3772
+ categoryOrder?: string[];
3773
+ categoryLabels?: Record<string, string>;
3774
+ /** single-dim catalogs. */
3775
+ options?: PickerOption[];
3776
+ /** multi-dim catalogs. */
3777
+ fields?: string[];
3778
+ dimensions?: PickerDimension[];
3779
+ detail?: "compact" | "full";
3780
+ }
3781
+ interface PickerCatalogSummary {
3782
+ nodeType: string;
3783
+ label: string;
3784
+ catalogId: string;
3785
+ kind: "single" | "multi";
3786
+ valueField?: string;
3787
+ fields?: string[];
3788
+ optionCount: number;
3789
+ }
3790
+ interface GetPickerCatalogOptions {
3791
+ /** "compact" (default) = id, label, category, icon; "full" additionally includes description + promptHint. */
3792
+ detail?: "compact" | "full";
3793
+ /** single-dim: filter to one category. */
3794
+ category?: string;
3795
+ /** multi-dim: only this dimension field. */
3796
+ field?: string;
3797
+ }
3798
+ declare class PickerCatalogsResource {
3799
+ private client;
3800
+ constructor(client: NodaroClient);
3801
+ /** List every parameter-picker node type + its option count. Cached publicly 5 min. */
3802
+ list(): Promise<{
3803
+ data: PickerCatalogSummary[];
3804
+ }>;
3805
+ /** Get one picker's catalog of valid values. */
3806
+ get(nodeType: string, opts?: GetPickerCatalogOptions): Promise<{
3807
+ data: PickerCatalog;
3808
+ }>;
3809
+ }
3810
+
3811
+ /**
3812
+ * The community-listing types are the single source of truth in
3813
+ * `@nodaro/shared` (re-used by the backend, frontend, and CLI). Re-export them
3814
+ * here so SDK consumers don't have to add `@nodaro/shared` as a second
3815
+ * dependency just to typecheck `browse`/`clone`/`favorite`/`report`.
3816
+ */
3817
+ /**
3818
+ * Community — browse, favorite, clone, and report shared characters,
3819
+ * locations, and objects.
3820
+ *
3821
+ * Publishing is intentionally NOT exposed here: it is admin-only via the
3822
+ * editor, and the publish route rejects personal access tokens (which is what
3823
+ * the SDK uses).
3824
+ */
3825
+ declare class CommunityResource {
3826
+ private client;
3827
+ constructor(client: NodaroClient);
3828
+ /**
3829
+ * `GET /v1/community/browse` → a page of public listings plus a `nextCursor`.
3830
+ * Pass the returned `nextCursor` back as `cursor` to fetch the next page.
3831
+ */
3832
+ browse(params?: BrowseCommunityParams): Promise<BrowseCommunityResult>;
3833
+ /** `GET /v1/community/detail/:slug` → a single listing by its slug. */
3834
+ get(slug: string): Promise<{
3835
+ data: CommunityCard;
3836
+ }>;
3837
+ /**
3838
+ * `GET /v1/community/detail/:slug/full` → the full read-only detail (card
3839
+ * identity + the stored public snapshot). Like {@link get}, but includes the
3840
+ * snapshot asset/voice/text blob needed to render the full cross-user view.
3841
+ */
3842
+ getFull(slug: string): Promise<{
3843
+ data: CommunityFullDetail;
3844
+ }>;
3845
+ /** `GET /v1/community/favorites` → the listings you've favorited. */
3846
+ favorites(): Promise<{
3847
+ data: CommunityCard[];
3848
+ }>;
3849
+ /**
3850
+ * `POST /v1/community/listings/:id/clone` → copy a listing into your library.
3851
+ * Returns the new asset's `entityType` and `id`. Requires the `assets:write`
3852
+ * scope when called with an OAuth app token.
3853
+ */
3854
+ clone(id: string, entityType: CommunityEntityType): Promise<CloneListingResult>;
3855
+ /**
3856
+ * `POST /v1/community/listings/:id/favorite` → toggle a favorite. Returns the
3857
+ * resulting `favorited` state (`true` after adding, `false` after removing).
3858
+ */
3859
+ favorite(id: string): Promise<FavoriteListingResult>;
3860
+ /**
3861
+ * `POST /v1/community/listings/:id/report` → flag a listing for moderation.
3862
+ * `reason` must be one of {@link CommunityReportReason}.
3863
+ */
3864
+ report(id: string, reason: CommunityReportReason): Promise<ReportListingResult>;
3865
+ /**
3866
+ * `POST /v1/admin/community/:entityType/:id/publish` → share one of YOUR
3867
+ * entities to the community, returning the new listing's `slug` + `id`.
3868
+ *
3869
+ * **Requires an admin token** (the route is `requireAdmin`) AND the caller
3870
+ * must own the source entity. Personal/OAuth tokens without admin role get a
3871
+ * 401. For `character` listings, `params.likenessAttestation` must be `true`.
3872
+ */
3873
+ publish(entityType: CommunityEntityType, entityId: string, params: PublishListingParams): Promise<PublishListingResult>;
3874
+ /**
3875
+ * `DELETE /v1/admin/community/listings/:id` → unshare (deactivate) a listing
3876
+ * you published. **Requires an admin token** (the route is `requireAdmin`).
3877
+ */
3878
+ unpublish(listingId: string): Promise<{
3879
+ ok: boolean;
3880
+ }>;
3881
+ /**
3882
+ * `GET /v1/admin/community/by-source/:entityType/:sourceId` → look up YOUR
3883
+ * existing listing (if any) for a source entity. Returns `{ data: null }`
3884
+ * when the entity hasn't been shared. **Requires an admin token** (the route
3885
+ * is `requireAdmin`); only returns listings created by the caller.
3886
+ */
3887
+ sharedListing(entityType: CommunityEntityType, sourceId: string): Promise<{
3888
+ data: SharedListing | null;
3889
+ }>;
3890
+ }
3891
+
3892
+ interface ClientOptions {
3893
+ /** Backend base URL, e.g. "https://nodaro.example.com" or empty string for same-origin. */
3894
+ baseUrl: string;
3895
+ /** Auth provider. Use StaticTokenAuth, supabaseAuth, or CallbackAuth. */
3896
+ auth: Auth;
3897
+ /** Optional fetch override (for tests or custom transports). */
3898
+ fetch?: typeof fetch;
3899
+ /** Default request timeout in ms. Default 60s. */
3900
+ timeoutMs?: number;
3901
+ }
3902
+ interface RequestOptions {
3903
+ body?: unknown;
3904
+ query?: Record<string, string | number | boolean | undefined>;
3905
+ headers?: Record<string, string>;
3906
+ signal?: AbortSignal;
3907
+ }
3908
+ /**
3909
+ * The authenticated user's canonical identity (`GET /v1/me`). A token-
3910
+ * introspection primitive: any valid bearer token (first-party Supabase JWT or
3911
+ * a developer-app OAuth token) resolves to its owner's identity. Mirrors the
3912
+ * `profiles` identity columns server-side — the route is the source of truth.
3913
+ */
3914
+ interface UserIdentity {
3915
+ /** Nodaro user id (= the Supabase auth user id). */
3916
+ readonly id: string;
3917
+ readonly email: string;
3918
+ /** Human-readable display name (from `profiles.full_name`); `null` if unset. */
3919
+ readonly displayName: string | null;
3920
+ /** Avatar URL; `null` if unset. */
3921
+ readonly avatarUrl: string | null;
3922
+ /** Subscription tier (e.g. "free", "pro"). */
3923
+ readonly tier: string;
3924
+ }
3925
+ declare class NodaroClient {
3926
+ readonly baseUrl: string;
3927
+ readonly auth: Auth;
3928
+ readonly timeoutMs: number;
3929
+ private readonly fetchOverride;
3930
+ /**
3931
+ * Resolved lazily so consumers can swap `globalThis.fetch` after the
3932
+ * client has been constructed (e.g. test mocks). Always rebound to the
3933
+ * global object — native fetch throws "Illegal invocation" when its
3934
+ * `this` is anything else.
3935
+ */
3936
+ get fetch(): typeof fetch;
3937
+ readonly workflows: WorkflowsResource;
3938
+ readonly projects: ProjectsResource;
3939
+ readonly jobs: JobsResource;
3940
+ readonly executions: ExecutionsResource;
3941
+ readonly nodes: NodesResource;
3942
+ readonly developerApps: DeveloperAppsResource;
3943
+ readonly oauth: OAuthResource;
3944
+ readonly apps: AppsResource;
3945
+ readonly characters: CharactersResource;
3946
+ readonly locations: LocationsResource;
3947
+ readonly objects: ObjectsResource;
3948
+ readonly creatures: CreaturesResource;
3949
+ readonly pipelines: PipelinesResource;
3950
+ readonly reduce: ReduceResource;
3951
+ readonly promptHelper: PromptHelperResource;
3952
+ readonly voices: VoicesResource;
3953
+ readonly credits: CreditsResource;
3954
+ readonly uploads: UploadsResource;
3955
+ readonly library: LibraryResource;
3956
+ readonly presets: PresetsResource;
3957
+ readonly pickerCatalogs: PickerCatalogsResource;
3958
+ readonly community: CommunityResource;
3959
+ constructor(opts: ClientOptions);
3960
+ request<T>(method: string, path: string, options?: RequestOptions): Promise<T>;
3961
+ /**
3962
+ * `GET /v1/me` → the authenticated user's identity (see {@link UserIdentity}).
3963
+ * Unwraps the `{ data }` envelope. Throws `UnauthorizedError` (401) when the
3964
+ * token is missing/invalid, and the SDK's other typed errors as usual.
3965
+ */
3966
+ me(): Promise<UserIdentity>;
3967
+ private buildUrl;
3968
+ }
3969
+ /** Factory function — preferred entry point. */
3970
+ declare function createClient(opts: ClientOptions): NodaroClient;
3971
+
3972
+ declare class NodaroError extends Error {
3973
+ readonly code: string;
3974
+ readonly status: number;
3975
+ constructor(message: string, code: string, status: number);
3976
+ }
3977
+ declare class UnauthorizedError extends NodaroError {
3978
+ constructor(message?: string);
3979
+ }
3980
+ declare class ForbiddenError extends NodaroError {
3981
+ readonly missingScope?: string | undefined;
3982
+ constructor(message?: string, missingScope?: string | undefined);
3983
+ }
3984
+ declare class NotFoundError extends NodaroError {
3985
+ constructor(message?: string);
3986
+ }
3987
+ declare class RateLimitedError extends NodaroError {
3988
+ constructor(message?: string);
3989
+ }
3990
+ declare class InsufficientCreditsError extends NodaroError {
3991
+ readonly required?: number | undefined;
3992
+ readonly available?: number | undefined;
3993
+ constructor(message?: string, required?: number | undefined, available?: number | undefined);
3994
+ }
3995
+ declare class StorageExceededError extends NodaroError {
3996
+ readonly limitBytes?: number | undefined;
3997
+ constructor(message?: string, limitBytes?: number | undefined);
3998
+ }
3999
+ /**
4000
+ * A job reached a terminal `failed`/`cancelled` status while being awaited by
4001
+ * `nodes.runAndWait` / `nodes.runMany`. Not an HTTP-level error (the polls
4002
+ * themselves succeeded), so `status` is 0 — distinguish it by type/`code`.
4003
+ * Carries the job's own `error_message` (as the message) and `jobId`.
4004
+ */
4005
+ declare class JobFailedError extends NodaroError {
4006
+ readonly jobId: string;
4007
+ /** The terminal status that triggered the failure (`failed` | `cancelled`). */
4008
+ readonly jobStatus: "failed" | "cancelled";
4009
+ constructor(message: string, jobId: string,
4010
+ /** The terminal status that triggered the failure (`failed` | `cancelled`). */
4011
+ jobStatus?: "failed" | "cancelled");
4012
+ }
4013
+ /**
4014
+ * `nodes.runAndWait` polled past its `maxMs` deadline without the job reaching
4015
+ * a terminal status. Not an HTTP error — `status` is 0; catch by type/`code`.
4016
+ */
4017
+ declare class JobTimeoutError extends NodaroError {
4018
+ readonly jobId: string;
4019
+ /** The wall-clock deadline (ms) that was exceeded. */
4020
+ readonly timeoutMs: number;
4021
+ constructor(message: string, jobId: string,
4022
+ /** The wall-clock deadline (ms) that was exceeded. */
4023
+ timeoutMs: number);
4024
+ }
4025
+ /**
4026
+ * The caller's `AbortSignal` fired while `nodes.runAndWait` was polling (or it
4027
+ * was already aborted on entry). Polling stops and this rejects. Not an HTTP
4028
+ * error — `status` is 0; catch by type/`code`.
4029
+ */
4030
+ declare class JobAbortedError extends NodaroError {
4031
+ readonly jobId?: string | undefined;
4032
+ constructor(message?: string, jobId?: string | undefined);
4033
+ }
4034
+ interface ApiErrorBody {
4035
+ error?: {
4036
+ code?: string;
4037
+ message?: string;
4038
+ missingScope?: string;
4039
+ required?: number;
4040
+ available?: number;
4041
+ limitBytes?: number;
4042
+ [key: string]: unknown;
4043
+ };
4044
+ }
4045
+ declare function throwFromResponse(status: number, body: ApiErrorBody): never;
4046
+
4047
+ export { type AccessTokenResponse, type AnalyzeInput, type AnalyzeResult, type AppRun, type AppRunResult, type ApplyChatProposalResult, type ApproveCreatureMainImageResult, type ApproveMainImageResult, type ApproveObjectMainImageResult, type ApprovePortraitResult, AppsResource, type Auth, type BranchPipelineInput, type BranchPipelineResult, CREATURE_ASSET_TYPES, CallbackAuth, type CancelExecutionParams, type CancelJobResult, type Character, type CharacterDetail, type CharacterUsage, CharactersResource, type ChatStageResult, type ChatTurn, type ClientOptions, CommunityResource, type CreateCreatureInput, type CreateDeveloperAppInput, type CreateDeveloperAppResult, type CreateLocationInput, type CreateObjectInput, type CreateProjectInput, type CreateWorkflowInput, type Creature, type CreatureAssetType, type CreatureDetail, type CreatureReferencePhoto, type CreatureReferencePhotoKind, CreaturesResource, CreditsResource, type DeleteAppRunResult, type DeveloperApp, type DeveloperAppScope, type DeveloperAppStatus, DeveloperAppsResource, type DuplicateCharacterInput, type EnhanceInput, type ExchangeCodeInput, type ExecutionStatus, type ExecutionTriggerType, ExecutionsResource, type FactoryPresetsResult, ForbiddenError, type GenerateAssetInput, type GenerateCharacterInput, type GenerateCharacterResult, type GenerateCreatureAssetInput, type GenerateCreatureAssetResult, type GenerateCreatureInput, type GenerateCreatureMotionInput, type GenerateCreatureMotionResult, type GenerateCreatureResult, type GenerateImageParams, type GenerateInput, type GenerateLocationAssetInput, type GenerateLocationInput, type GenerateLocationResult, type GenerateMotionInput, type GenerateObjectAssetInput, type GenerateObjectAssetResult, type GenerateObjectInput, type GenerateObjectMotionInput, type GenerateObjectMotionResult, type GenerateObjectResult, type GenerateSurroundContinuationInput, type GenerateVideoParams, type GetPickerCatalogOptions, InsufficientCreditsError, type Job, JobAbortedError, JobFailedError, type JobStatus, type JobStatusResult, JobTimeoutError, JobsResource, type LibraryAsset, LibraryResource, type ListAppRunsParams, type ListAppsParams, type ListAppsResult, type ListCharactersParams, type ListCreaturesParams, type ListExecutionsForWorkflowParams, type ListExecutionsPage, type ListLibraryParams, type ListLibraryResult, type ListLocationsParams, type ListObjectsParams, type ListWorkflowsParams, type Location, type LocationDetail, type LocationReferencePhoto, type LocationReferencePhotoKind, LocationsResource, type ModelCostsResult, NodaroClient, NodaroError, type NodeCategory, type NodeDescriptor, type NodeExecutionState, type NodeInputField, type NodeInputSchema, type NodeJobOutput, type NodePreset, type NodePresetGroup, NodesResource, NotFoundError, type OAuthAppInfo, OAuthResource, type Object$1 as Object, type ObjectCategory, type ObjectDetail, type ObjectReferencePhoto, type ObjectReferencePhotoKind, ObjectsResource, type OutputType, type PickerCatalog, type PickerCatalogSummary, PickerCatalogsResource, type PickerDimension, type PickerOption, PipelinesResource, PresetsResource, type Project, ProjectsResource, PromptHelperResource, type PromptResult, type PublishedApp, type PublishedAppDetail, RateLimitedError, type RecaptionCreatureResult, type RecaptionLocationResult, type RecaptionObjectResult, type RecaptionResult, type ReduceInput, ReduceResource, type ReduceResult, type ReferencePhoto, type ReferencePhotoKind, type RotateSecretResult, type RunAndWaitOptions, type RunManyResult, type RunNodeResult, type RunWorkflowParams, type RunWorkflowResult, StaticTokenAuth, StorageExceededError, type StructuredReferenceParams, UnauthorizedError, type UpdateCreatureInput, type UpdateCreatureResult, type UpdateDeveloperAppInput, type UpdateLocationInput, type UpdateLocationResult, type UpdateObjectInput, type UpdateObjectResult, type UpdateProjectInput, type UpdateWorkflowInput, type UploadResult, UploadsResource, type UpsertCharacterInput, type UpsertCharacterResult, type UpsertCreatureInput, type UpsertCreatureResult, type UpsertObjectInput, type UpsertObjectResult, type UserBalance, type UserIdentity, type VoiceChangerProInput, type VoiceChangerProVoice, VoicesResource, type Workflow, type WorkflowExecution, type WorkflowExecutionSummary, WorkflowsResource, buildPersonSeedPrompt, createClient, supabaseAuth, throwFromResponse };