@nodaro/sdk 1.7.0 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts","../src/resources/workflows.ts","../src/resources/projects.ts","../src/resources/jobs.ts","../src/resources/executions.ts","../src/resources/nodes.ts","../src/resources/developer-apps.ts","../src/resources/oauth.ts","../src/resources/apps.ts","../src/resources/characters.ts","../src/resources/locations.ts","../src/resources/objects.ts","../src/resources/creatures.ts","../src/resources/pipelines.ts","../src/resources/reduce.ts","../src/resources/prompt-helper.ts","../src/resources/voices.ts","../src/resources/media.ts","../src/resources/audio.ts","../src/resources/credits.ts","../src/resources/uploads.ts","../src/resources/library.ts","../src/resources/node-presets.ts","../src/resources/picker-catalogs.ts","../src/resources/community.ts","../src/client.ts","../src/auth.ts"],"names":["buildPersonHints"],"mappings":"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","file":"index.cjs","sourcesContent":["export class NodaroError extends Error {\n constructor(\n message: string,\n public readonly code: string,\n public readonly status: number,\n ) {\n super(message)\n this.name = \"NodaroError\"\n }\n}\n\nexport class UnauthorizedError extends NodaroError {\n constructor(message = \"Authentication required\") {\n super(message, \"unauthorized\", 401)\n this.name = \"UnauthorizedError\"\n }\n}\n\nexport class ForbiddenError extends NodaroError {\n constructor(message = \"Forbidden\", public readonly missingScope?: string) {\n super(message, \"forbidden\", 403)\n this.name = \"ForbiddenError\"\n }\n}\n\nexport class NotFoundError extends NodaroError {\n constructor(message = \"Not found\") {\n super(message, \"not_found\", 404)\n this.name = \"NotFoundError\"\n }\n}\n\nexport class RateLimitedError extends NodaroError {\n constructor(message = \"Rate limited\") {\n super(message, \"rate_limited\", 429)\n this.name = \"RateLimitedError\"\n }\n}\n\nexport class InsufficientCreditsError extends NodaroError {\n constructor(\n message = \"Insufficient credits\",\n public readonly required?: number,\n public readonly available?: number,\n ) {\n super(message, \"insufficient_credits\", 402)\n this.name = \"InsufficientCreditsError\"\n }\n}\n\nexport class StorageExceededError extends NodaroError {\n constructor(message = \"Storage exceeded\", public readonly limitBytes?: number) {\n super(message, \"storage_exceeded\", 413)\n this.name = \"StorageExceededError\"\n }\n}\n\n/**\n * A job reached a terminal `failed`/`cancelled` status while being awaited by\n * `nodes.runAndWait` / `nodes.runMany`. Not an HTTP-level error (the polls\n * themselves succeeded), so `status` is 0 — distinguish it by type/`code`.\n * Carries the job's own `error_message` (as the message) and `jobId`.\n */\nexport class JobFailedError extends NodaroError {\n constructor(\n message: string,\n public readonly jobId: string,\n /** The terminal status that triggered the failure (`failed` | `cancelled`). */\n public readonly jobStatus: \"failed\" | \"cancelled\" = \"failed\",\n ) {\n super(message, \"job_failed\", 0)\n this.name = \"JobFailedError\"\n }\n}\n\n/**\n * `nodes.runAndWait` polled past its `maxMs` deadline without the job reaching\n * a terminal status. Not an HTTP error — `status` is 0; catch by type/`code`.\n */\nexport class JobTimeoutError extends NodaroError {\n constructor(\n message: string,\n public readonly jobId: string,\n /** The wall-clock deadline (ms) that was exceeded. */\n public readonly timeoutMs: number,\n ) {\n super(message, \"job_timeout\", 0)\n this.name = \"JobTimeoutError\"\n }\n}\n\n/**\n * The caller's `AbortSignal` fired while `nodes.runAndWait` was polling (or it\n * was already aborted on entry). Polling stops and this rejects. Not an HTTP\n * error — `status` is 0; catch by type/`code`.\n */\nexport class JobAbortedError extends NodaroError {\n constructor(message = \"Aborted\", public readonly jobId?: string) {\n super(message, \"job_aborted\", 0)\n this.name = \"JobAbortedError\"\n }\n}\n\ninterface ApiErrorBody {\n error?: { code?: string; message?: string; missingScope?: string; required?: number; available?: number; limitBytes?: number; [key: string]: unknown }\n}\n\nexport function throwFromResponse(status: number, body: ApiErrorBody): never {\n const code = body.error?.code ?? \"internal_error\"\n const message = body.error?.message ?? \"Request failed\"\n if (status === 401) throw new UnauthorizedError(message)\n if (status === 403 && code === \"insufficient_scope\") {\n throw new ForbiddenError(message, body.error?.missingScope)\n }\n if (status === 403) throw new ForbiddenError(message)\n if (status === 404) throw new NotFoundError(message)\n if (status === 429) throw new RateLimitedError(message)\n if (status === 402) {\n throw new InsufficientCreditsError(message, body.error?.required, body.error?.available)\n }\n if (status === 413) throw new StorageExceededError(message, body.error?.limitBytes)\n throw new NodaroError(message, code, status)\n}\n","import type { NodaroClient } from \"../client.js\"\nimport type { GenericNode, GenericEdge, WorkflowExport } from \"@nodaro/shared\"\n\n/**\n * Workflow metadata + (when fetched as a single record) full nodes/edges/settings.\n *\n * The list endpoint returns metadata only; `get`, `create`, and `update` return the\n * full record. `nodes`, `edges`, `settings`, and `sourcePrompt` are present only on\n * full records and omitted in list responses.\n */\nexport interface Workflow {\n id: string\n projectId: string | null\n userId: string\n name: string\n description?: string | null\n folderId?: string | null\n isTemplate?: boolean\n version?: number\n thumbnailUrl?: string | null\n nodes?: GenericNode[]\n edges?: GenericEdge[]\n settings?: Record<string, unknown>\n sourcePrompt?: string | null\n createdAt: string\n updatedAt: string\n}\n\nexport interface ListWorkflowsParams {\n /** Required — list endpoint is `/v1/projects/:projectId/workflows`. */\n projectId: string\n}\n\nexport interface CreateWorkflowInput {\n /** Required — workflow is created under this project. */\n projectId: string\n name: string\n description?: string\n folderId?: string | null\n nodes?: GenericNode[]\n edges?: GenericEdge[]\n settings?: Record<string, unknown>\n sourcePrompt?: string\n}\n\nexport interface UpdateWorkflowInput {\n name?: string\n description?: string\n folderId?: string | null\n nodes?: GenericNode[]\n edges?: GenericEdge[]\n settings?: Record<string, unknown>\n sourcePrompt?: string\n thumbnailUrl?: string | null\n}\n\nexport interface RunWorkflowParams {\n /** Optional subset of node IDs to execute. Omit to run the full workflow. */\n nodeIds?: string[]\n}\n\nexport interface RunWorkflowResult {\n executionId: string\n status: \"pending\" | \"running\"\n}\n\nexport class WorkflowsResource {\n constructor(private client: NodaroClient) {}\n\n /** List workflows for a project. Returns metadata only — `nodes`/`edges` are not included. */\n list(params: ListWorkflowsParams): Promise<{ data: Workflow[] }> {\n return this.client.request(\n \"GET\",\n `/v1/projects/${encodeURIComponent(params.projectId)}/workflows`,\n )\n }\n\n /** Get a workflow including its full nodes/edges/settings. */\n get(id: string): Promise<{ data: Workflow }> {\n return this.client.request(\"GET\", `/v1/workflows/${encodeURIComponent(id)}`)\n }\n\n /**\n * Get a PUBLICLY-SHARED workflow by id (`GET /v1/public/workflows/:id`) — the\n * unauthenticated share-by-link read. Returns the workflow's nodes/edges/\n * settings ONLY when it's opted into sharing server-side (`settings.studio.shared\n * === true`); otherwise the route 404s (→ `NotFoundError`). No auth required —\n * a share viewer has no session; the SDK omits the bearer when no token exists.\n */\n getPublic(id: string): Promise<{ data: Workflow }> {\n return this.client.request(\"GET\", `/v1/public/workflows/${encodeURIComponent(id)}`)\n }\n\n /**\n * Create a workflow under a project. Returns the full record.\n * NOTE: server route is `POST /v1/projects/:projectId/workflows`.\n */\n create(input: CreateWorkflowInput): Promise<{ data: Workflow }> {\n const { projectId, ...body } = input\n return this.client.request(\n \"POST\",\n `/v1/projects/${encodeURIComponent(projectId)}/workflows`,\n { body },\n )\n }\n\n /** Patch a workflow. Returns the full updated record. */\n update(id: string, input: UpdateWorkflowInput): Promise<{ data: Workflow }> {\n return this.client.request(\n \"PATCH\",\n `/v1/workflows/${encodeURIComponent(id)}`,\n { body: input },\n )\n }\n\n /** Delete a workflow. Returns `{ success: true }`. */\n delete(id: string): Promise<{ success: true }> {\n return this.client.request(\"DELETE\", `/v1/workflows/${encodeURIComponent(id)}`)\n }\n\n /**\n * Run a workflow. Returns the executionId for polling via\n * `client.executions.get(executionId)`.\n */\n run(id: string, params: RunWorkflowParams = {}): Promise<RunWorkflowResult> {\n return this.client.request(\n \"POST\",\n `/v1/workflows/${encodeURIComponent(id)}/run`,\n { body: params },\n )\n }\n\n /**\n * Export a workflow as a portable JSON bundle.\n * Pass `opts.assets = true` to include character/object/location entity data.\n */\n export(\n workflowId: string,\n opts?: { assets?: boolean },\n ): Promise<{ data: WorkflowExport }> {\n return this.client.request(\n \"GET\",\n `/v1/workflows/${encodeURIComponent(workflowId)}/export`,\n { query: { assets: opts?.assets ?? false } },\n )\n }\n\n /**\n * Import a `WorkflowExport` bundle into the specified project.\n * Re-creates any bundled assets (characters, objects, locations) under your account.\n */\n import(input: WorkflowExport & { projectId: string }): Promise<{ data: Workflow }> {\n const { projectId, ...workflowJson } = input\n return this.client.request(\"POST\", \"/v1/workflows/import\", {\n body: { projectId, workflow_json: workflowJson },\n })\n }\n}\n","import type { NodaroClient } from \"../client.js\"\n\nexport interface Project {\n id: string\n userId: string\n name: string\n description?: string | null\n settings?: Record<string, unknown>\n createdAt: string\n updatedAt: string\n}\n\nexport interface CreateProjectInput {\n name: string\n description?: string\n settings?: Record<string, unknown>\n}\n\nexport interface UpdateProjectInput {\n name?: string\n description?: string\n settings?: Record<string, unknown>\n}\n\nexport class ProjectsResource {\n constructor(private client: NodaroClient) {}\n\n /** List the authenticated user's projects. */\n list(): Promise<{ data: Project[] }> {\n return this.client.request(\"GET\", \"/v1/projects\")\n }\n\n /** Get a project by ID. */\n get(id: string): Promise<{ data: Project }> {\n return this.client.request(\"GET\", `/v1/projects/${encodeURIComponent(id)}`)\n }\n\n /** Create a new project. */\n create(input: CreateProjectInput): Promise<{ data: Project }> {\n return this.client.request(\"POST\", \"/v1/projects\", { body: input })\n }\n\n /** Update a project. At least one field must be provided. */\n update(id: string, input: UpdateProjectInput): Promise<{ data: Project }> {\n return this.client.request(\n \"PATCH\",\n `/v1/projects/${encodeURIComponent(id)}`,\n { body: input },\n )\n }\n\n /** Delete a project. Returns `{ success: true }`. */\n delete(id: string): Promise<{ success: true }> {\n return this.client.request(\"DELETE\", `/v1/projects/${encodeURIComponent(id)}`)\n }\n}\n","import type { NodaroClient } from \"../client.js\"\n\nexport type JobStatus =\n | \"pending\"\n | \"queued\"\n | \"processing\"\n | \"completed\"\n | \"failed\"\n | \"cancelled\"\n\n/**\n * Job record returned to non-admin SDK consumers. Field names are snake_case\n * to match the wire format (per the OpenAPI fix in Phase 1).\n *\n * Sensitive fields stripped server-side for non-admin callers:\n * `provider`, `provider_cost`, `display_cost`, `credits_actual`. USD\n * pricing is admin-only across api/sdk/mcp — non-admin consumers see\n * only the `credits` abstraction.\n */\nexport interface Job {\n id: string\n status: JobStatus\n progress: number\n user_id: string\n input_data: unknown\n output_data: unknown\n error_message: string | null\n credits: number | null\n job_type: string | null\n created_at: string\n started_at: string | null\n completed_at: string | null\n}\n\nexport interface CancelJobResult {\n success: true\n cancelled: number\n}\n\n/**\n * Lean job status returned by `GET /v1/jobs/:id/status`. Skips the\n * `input_data` JSONB, cost/timestamp columns, and the public sanitize pass —\n * intended for poll loops that only need progress/output/error.\n */\nexport interface JobStatusResult {\n id: string\n status: JobStatus\n progress?: number\n output_data?: unknown\n error_message?: string | null\n}\n\nexport class JobsResource {\n constructor(private client: NodaroClient) {}\n\n /** Get a single job by ID. */\n get(id: string): Promise<{ data: Job }> {\n return this.client.request(\"GET\", `/v1/jobs/${encodeURIComponent(id)}`)\n }\n\n /**\n * Get the lean status of a single job (poll-loop friendly).\n * Hits `GET /v1/jobs/:id/status` — returns only id/status/progress/\n * output_data/error_message, with far less wire/CPU cost than `get()`.\n * Same auth + ownership semantics as {@link get}.\n */\n getStatus(id: string): Promise<{ data: JobStatusResult }> {\n return this.client.request(\n \"GET\",\n `/v1/jobs/${encodeURIComponent(id)}/status`,\n )\n }\n\n /**\n * Cancel a job. Server route is `POST /v1/jobs/:jobId/cancel`.\n * Refunds any reserved credit holds.\n */\n cancel(id: string): Promise<CancelJobResult> {\n return this.client.request(\n \"POST\",\n `/v1/jobs/${encodeURIComponent(id)}/cancel`,\n )\n }\n}\n","import type { NodaroClient } from \"../client.js\"\n\nexport type ExecutionStatus =\n | \"pending\"\n | \"running\"\n | \"completed\"\n | \"failed\"\n | \"cancelled\"\n | \"stopping\"\n | \"timed_out\"\n | \"discarded\"\n\nexport type ExecutionTriggerType =\n | \"manual\"\n | \"webhook\"\n | \"schedule\"\n | \"app_run\"\n | \"single-node\"\n\n/**\n * Per-node state inside an execution's `nodeStates` map. Keys are node IDs.\n *\n * Shape mirrors `services/workflow-engine/types.ts` plus the synthetic\n * single-node-job shape from `routes/workflow-execution.ts`.\n */\nexport interface NodeExecutionState {\n status: string\n nodeType?: string\n jobId?: string | null\n creditsUsed?: number\n error?: string | null\n startedAt?: string | null\n completedAt?: string | null\n [key: string]: unknown\n}\n\n/**\n * Workflow execution record. Returned by `get()` and `cancel()` (the cancel\n * endpoint returns `{ success: true }`, not the execution itself).\n */\nexport interface WorkflowExecution {\n id: string\n workflowId: string | null\n userId: string\n status: ExecutionStatus\n triggerType: ExecutionTriggerType\n triggerData?: unknown\n nodeStates: Record<string, NodeExecutionState>\n totalNodes: number\n completedNodes: number\n failedNodes: number\n totalCreditsUsed: number\n errorMessage: string | null\n startedAt: string | null\n completedAt: string | null\n createdAt: string\n updatedAt: string\n}\n\n/** Summary returned by `listForWorkflow()`. Excludes per-row `triggerData`/`updatedAt`. */\nexport interface WorkflowExecutionSummary {\n id: string\n status: ExecutionStatus\n triggerType: ExecutionTriggerType\n nodeStates: Record<string, NodeExecutionState>\n totalNodes: number\n completedNodes: number\n failedNodes: number\n totalCreditsUsed: number\n errorMessage: string | null\n startedAt: string | null\n completedAt: string | null\n createdAt: string\n}\n\nexport interface ListExecutionsForWorkflowParams {\n limit?: number\n cursor?: string\n /** Comma-separated list of statuses, e.g. \"pending,running\". */\n status?: string\n /** \"editor\" excludes app_run / component / webhook / schedule executions. */\n source?: \"editor\" | \"all\"\n}\n\nexport interface ListExecutionsPage<T> {\n data: T[]\n nextCursor?: string\n}\n\nexport interface CancelExecutionParams {\n /**\n * \"after_current\" sets the execution to \"stopping\" (let in-flight nodes\n * finish, then stop). Default behavior cancels immediately.\n */\n mode?: \"after_current\" | \"discard\"\n}\n\nexport class ExecutionsResource {\n constructor(private client: NodaroClient) {}\n\n /** Get an execution by ID. Falls back to standalone single-node jobs server-side. */\n get(id: string): Promise<{ data: WorkflowExecution }> {\n return this.client.request(\n \"GET\",\n `/v1/workflow-executions/${encodeURIComponent(id)}`,\n )\n }\n\n /** List executions for a workflow. Merges workflow_executions + standalone single-node jobs. */\n listForWorkflow(\n workflowId: string,\n params: ListExecutionsForWorkflowParams = {},\n ): Promise<ListExecutionsPage<WorkflowExecutionSummary>> {\n return this.client.request(\n \"GET\",\n `/v1/workflows/${encodeURIComponent(workflowId)}/executions`,\n {\n query: {\n limit: params.limit,\n cursor: params.cursor,\n status: params.status,\n source: params.source,\n },\n },\n )\n }\n\n /** Cancel an execution. Returns `{ success: true }`. */\n cancel(id: string, params: CancelExecutionParams = {}): Promise<{ success: true }> {\n return this.client.request(\n \"POST\",\n `/v1/workflow-executions/${encodeURIComponent(id)}/cancel`,\n { body: params },\n )\n }\n}\n","import type { NodaroClient } from \"../client.js\"\nimport type { JobStatusResult } from \"./jobs.js\"\nimport { JobAbortedError, JobFailedError, JobTimeoutError } from \"../errors.js\"\nimport type { ConnectedReference } from \"@nodaro/shared\"\n\nexport type NodeCategory =\n | \"input\"\n | \"parameter\"\n | \"ai-image\"\n | \"ai-video\"\n | \"ai-audio\"\n | \"ai-text\"\n | \"processing\"\n | \"composition\"\n | \"trigger\"\n | \"output\"\n | \"control\"\n | \"entity\"\n | \"utility\"\n\nexport type OutputType = \"text\" | \"image\" | \"video\" | \"audio\" | \"data\" | \"none\"\n\n/**\n * Field shape inside a node's `inputSchema.fields[]`. Mirrors\n * `backend/src/lib/node-registry.ts`.\n */\nexport interface NodeInputField {\n key: string\n type: string\n required?: boolean\n options?: string[]\n}\n\nexport interface NodeInputSchema {\n fields: NodeInputField[]\n}\n\n/**\n * Node descriptor returned by `GET /v1/nodes` and `GET /v1/nodes/:type`.\n * Mirrors `backend/src/lib/node-registry.ts#NodeDescriptor`.\n */\nexport interface NodeDescriptor {\n type: string\n label: string\n category: NodeCategory\n description: string\n outputType: OutputType\n /** Credit cost. Number when fixed, string range like \"1-8\" when model-dependent, undefined if free. */\n creditCost?: number | string\n /** Input fields the node exposes for user override (subset of full config). */\n inputSchema?: NodeInputSchema\n /** For AI nodes: list of provider IDs supported. */\n providers?: string[]\n /** Capability flags such as \"supports-reference-image\" or \"supports-end-frame\". */\n capabilities?: string[]\n}\n\n/**\n * Result of a direct node execution. Most node types return `{ jobId }` and\n * are processed asynchronously by a worker — the caller polls\n * `client.jobs.get(jobId)` until status is `completed`/`failed`.\n *\n * A small subset (combine-text, split-text, composite — the \"inline\"\n * orchestrator categories) execute synchronously and return their full\n * result body. The shape is route-specific; consumers should branch on the\n * presence of `jobId`.\n */\nexport type RunNodeResult =\n | { jobId: string; usageLogId?: string; [k: string]: unknown }\n | Record<string, unknown>\n\n/**\n * Structured references — the editor's wired-reference shape — shared by\n * `generate-image` and `generate-video`. The route assembles them server-side\n * into per-ref `@image_N` directives and resolves `{image:N}` prompt tokens, so\n * a direct SDK run binds inline references exactly like the canvas. Pass the\n * same `ConnectedReference[]` the editor builds; the route dedupes + caps them\n * to the provider's image-reference limit.\n */\nexport interface StructuredReferenceParams {\n /** Wired references assembled server-side (deduped + capped per provider). */\n connectedReferences?: ConnectedReference[]\n /** Reorder the assembled reference list by stable ref ids; renumbers `@image_N`. */\n referenceOrder?: string[]\n}\n\n/**\n * Typed request body for `nodes.run(\"generate-image\", …)` / `runAndWait`.\n * Common fields are typed; any other route field passes through via the index\n * signature (the route Zod-validates the full body).\n */\nexport interface GenerateImageParams extends StructuredReferenceParams {\n prompt?: string\n provider?: string\n /** Flat reference image URLs — appended after `connectedReferences`. */\n referenceImageUrls?: string[]\n negativePrompt?: string\n [k: string]: unknown\n}\n\n/**\n * Typed request body for `nodes.run(\"generate-video\", …)` / `runAndWait`.\n * Common fields are typed; any other route field passes through.\n */\nexport interface GenerateVideoParams extends StructuredReferenceParams {\n prompt?: string\n provider?: string\n /** Start-frame image (image-to-video). */\n imageUrl?: string\n referenceImageUrls?: string[]\n referenceVideoUrls?: string[]\n referenceAudioUrls?: string[]\n [k: string]: unknown\n}\n\n/**\n * Typed request body for `nodes.run(\"assemble-narrated-video\", …)` / `runAndWait`.\n * Assembles blocks of video with audio narration into a single composed video.\n */\nexport interface AssembleNarratedVideoParams {\n blocks: { videoUrl: string; audioUrl?: string }[]\n voiceVolume?: number\n clipAudioVolume?: number\n maxSlowdown?: number\n trimStartFrames?: number\n trimEndFrames?: number\n [k: string]: unknown\n}\n\n/**\n * The `output_data` shape a finalized generation job writes. Every async\n * generation node persists one (or more) of these media URLs to\n * `jobs.output_data` on completion — `generate-image` → `imageUrl`,\n * `generate-video` / `combine-videos` / `merge-video-audio` / `video-upscale`\n * → `videoUrl` (+ `thumbnailUrl`), `text-to-speech` / `generate-music` →\n * `audioUrl`. Resolved by {@link NodesResource.runAndWait}. Extra fields may be\n * present, so the index signature is open.\n */\nexport interface NodeJobOutput {\n /** `text-to-speech` / `generate-music` / audio nodes write here. For\n * `audio-separation` this is the primary stem (vocals). */\n readonly audioUrl?: string\n /** `audio-separation` (Demucs) per-stem URLs. `vocalUrl`/`instrumentalUrl`\n * in vocal/instrumental mode; the rest in full-stems mode. */\n readonly vocalUrl?: string\n readonly instrumentalUrl?: string\n readonly drumsUrl?: string\n readonly bassUrl?: string\n readonly otherUrl?: string\n readonly guitarUrl?: string\n readonly pianoUrl?: string\n /** `generate-video` / `combine-videos` / `merge-video-audio` / `video-upscale` write here. */\n readonly videoUrl?: string\n /** `generate-image` / `edit-image` / `extract-frame` write here. */\n readonly imageUrl?: string\n /** Poster frame for video outputs. */\n readonly thumbnailUrl?: string\n readonly [k: string]: unknown\n}\n\n/** Options for {@link NodesResource.runAndWait} and {@link NodesResource.runMany}. */\nexport interface RunAndWaitOptions {\n /**\n * Abort the run/poll loop. Aborting (or passing an already-aborted signal)\n * stops polling and rejects with {@link JobAbortedError}.\n */\n readonly signal?: AbortSignal\n /** Called with each lean status the poll loop observes (running → terminal). */\n readonly onProgress?: (status: JobStatusResult) => void\n /** Poll interval in ms. Default 2000. */\n readonly pollMs?: number\n /** Wall-clock cap before giving up, in ms. Default ~15 min (900_000). */\n readonly maxMs?: number\n}\n\n/** One settled result from {@link NodesResource.runMany}. */\nexport interface RunManyResult {\n readonly jobId: string\n readonly output: NodeJobOutput\n}\n\n// Terminal statuses (poll loop stops on these) are handled explicitly in\n// `pollJob`: `completed` resolves output_data, `failed`/`cancelled` throw a\n// JobFailedError — matching studio's `completed | failed | cancelled` set.\nconst DEFAULT_POLL_MS = 2000\n/** Safety cap so a stuck job can't poll forever (~15 min at 2s). */\nconst DEFAULT_MAX_MS = 15 * 60 * 1000\n\nconst sleep = (ms: number, signal?: AbortSignal): Promise<void> =>\n new Promise<void>((resolve, reject) => {\n if (signal?.aborted) {\n reject(new JobAbortedError())\n return\n }\n const timer = setTimeout(() => {\n signal?.removeEventListener(\"abort\", onAbort)\n resolve()\n }, ms)\n function onAbort() {\n clearTimeout(timer)\n reject(new JobAbortedError())\n }\n signal?.addEventListener(\"abort\", onAbort, { once: true })\n })\n\n/** Pull a `{ jobId }` off a run result, or throw a labelled JobFailedError. */\nfunction extractJobId(result: RunNodeResult, label: string): string {\n if (result && typeof result === \"object\" && \"jobId\" in result) {\n const jobId = (result as { jobId: unknown }).jobId\n if (typeof jobId === \"string\") return jobId\n }\n // No jobId means the node ran inline/synchronously (combine-text, etc.) or\n // the route shape changed — runAndWait is for async jobs, so this is an error.\n throw new JobFailedError(`${label} did not return a jobId`, \"\")\n}\n\nexport class NodesResource {\n constructor(private client: NodaroClient) {}\n\n /** List all known node descriptors. Server caches publicly for 5 minutes. */\n list(): Promise<{ data: NodeDescriptor[] }> {\n return this.client.request(\"GET\", \"/v1/nodes\")\n }\n\n /** Get a single node descriptor by type slug (e.g. \"generate-image\"). */\n get(type: string): Promise<{ data: NodeDescriptor }> {\n return this.client.request(\"GET\", `/v1/nodes/${encodeURIComponent(type)}`)\n }\n\n /**\n * Run a single node directly without wrapping it in a workflow. Posts\n * `params` as the request body to `POST /v1/<type>` (the route convention\n * every generation node follows: `generate-image`, `image-to-video`,\n * `text-to-speech`, etc.).\n *\n * This is the SDK equivalent of the MCP server's verb tools — and the\n * path the Nodaro CLI uses for `nodaro nodes run <type>`.\n *\n * Most node types are async: the response includes `{ jobId }` and the\n * actual generation runs on a worker. Poll `client.jobs.get(jobId)` until\n * completed. Inline node types (combine-text, etc.) return their full\n * result synchronously without a `jobId` field.\n *\n * @param type Node type slug — must match an entry in the registry\n * returned by `list()` (e.g. \"generate-image\").\n * @param params Request body. Field names must match the node's\n * `inputSchema` (see `get(type).inputSchema`).\n */\n run(type: \"generate-image\", params?: GenerateImageParams): Promise<RunNodeResult>\n run(type: \"generate-video\", params?: GenerateVideoParams): Promise<RunNodeResult>\n run(type: \"assemble-narrated-video\", params?: AssembleNarratedVideoParams): Promise<RunNodeResult>\n run(type: string, params?: Record<string, unknown>): Promise<RunNodeResult>\n run(type: string, params: Record<string, unknown> = {}): Promise<RunNodeResult> {\n return this.client.request(\"POST\", `/v1/${encodeURIComponent(type)}`, { body: params })\n }\n\n /**\n * Run a single async node to completion: {@link run} it, extract the\n * `{ jobId }`, then client-poll `jobs.getStatus(jobId)` every `pollMs`\n * (default 2000) until a terminal status, up to `maxMs` (default ~15 min).\n *\n * Resolves the job's typed `output_data` ({@link NodeJobOutput}) on\n * `completed`. Throws (all typed, catchable by `instanceof`):\n * - {@link InsufficientCreditsError} / {@link StorageExceededError} etc. —\n * surfaced by the underlying {@link run} on 402/413/… before any poll.\n * - {@link JobFailedError} — terminal `failed`/`cancelled` (carries the\n * job's `error_message` + `jobId`).\n * - {@link JobTimeoutError} — `maxMs` deadline exceeded before terminal.\n * - {@link JobAbortedError} — `signal` fired (or was already aborted);\n * polling stops immediately.\n *\n * Polling is fully client-side (no server function blocks) — the same model\n * thin clients use, lifted out of their hand-rolled run→poll loops.\n *\n * @param type Node type slug (e.g. \"generate-video\"). See {@link run}.\n * @param params Request body — field names match the node's `inputSchema`.\n * @param opts `signal` / `onProgress` / `pollMs` / `maxMs`.\n */\n runAndWait(type: \"generate-image\", params?: GenerateImageParams, opts?: RunAndWaitOptions): Promise<NodeJobOutput>\n runAndWait(type: \"generate-video\", params?: GenerateVideoParams, opts?: RunAndWaitOptions): Promise<NodeJobOutput>\n runAndWait(type: \"assemble-narrated-video\", params?: AssembleNarratedVideoParams, opts?: RunAndWaitOptions): Promise<NodeJobOutput>\n runAndWait(type: string, params?: Record<string, unknown>, opts?: RunAndWaitOptions): Promise<NodeJobOutput>\n async runAndWait(\n type: string,\n params: Record<string, unknown> = {},\n opts: RunAndWaitOptions = {},\n ): Promise<NodeJobOutput> {\n if (opts.signal?.aborted) throw new JobAbortedError()\n const result = await this.run(type, params)\n const jobId = extractJobId(result, type)\n return this.pollJob(jobId, type, opts)\n }\n\n /**\n * Fan out N async runs of the same node `type` to completion concurrently —\n * the candidate-grid path (generate N stills/clips in parallel). Each runs\n * via {@link runAndWait}; resolves once ALL settle, to an array of\n * `{ jobId, output }` in input order. Rejects (and the rejection wins) if any\n * single run rejects — same typed errors as {@link runAndWait}. A shared\n * `signal` aborts the whole batch.\n *\n * @param type Node type slug, applied to every entry.\n * @param paramsList One request body per candidate.\n * @param opts Shared `signal` / `onProgress` / `pollMs` / `maxMs`.\n */\n async runMany(\n type: string,\n paramsList: Record<string, unknown>[],\n opts: RunAndWaitOptions = {},\n ): Promise<RunManyResult[]> {\n if (opts.signal?.aborted) throw new JobAbortedError()\n return Promise.all(\n paramsList.map(async (params) => {\n if (opts.signal?.aborted) throw new JobAbortedError()\n const result = await this.run(type, params)\n const jobId = extractJobId(result, type)\n const output = await this.pollJob(jobId, type, opts)\n return { jobId, output }\n }),\n )\n }\n\n /** Poll an already-kicked job id until terminal; resolve output_data or throw. */\n private async pollJob(\n jobId: string,\n label: string,\n opts: RunAndWaitOptions,\n ): Promise<NodeJobOutput> {\n const pollMs = opts.pollMs ?? DEFAULT_POLL_MS\n const maxMs = opts.maxMs ?? DEFAULT_MAX_MS\n const deadline = Date.now() + maxMs\n for (;;) {\n if (opts.signal?.aborted) throw new JobAbortedError(undefined, jobId)\n const { data } = await this.client.jobs.getStatus(jobId)\n opts.onProgress?.(data)\n if (data.status === \"completed\") {\n return (data.output_data ?? {}) as NodeJobOutput\n }\n if (data.status === \"failed\" || data.status === \"cancelled\") {\n throw new JobFailedError(\n data.error_message ?? `${label} ${data.status}`,\n jobId,\n data.status,\n )\n }\n if (Date.now() > deadline) {\n throw new JobTimeoutError(`${label} timed out`, jobId, maxMs)\n }\n await sleep(pollMs, opts.signal)\n }\n }\n}\n","import type { NodaroClient } from \"../client.js\"\n\n/**\n * OAuth scopes that a developer app may request. Mirrors\n * `backend/src/lib/scopes.ts#ALL_SCOPES`.\n */\nexport type DeveloperAppScope =\n | \"workflows:read\"\n | \"workflows:write\"\n | \"workflows:execute\"\n | \"jobs:read\"\n | \"assets:read\"\n | \"assets:write\"\n | \"credits:read\"\n | \"apps:read\"\n | \"pipelines:read\"\n | \"pipelines:execute\"\n | \"pipelines:approve\"\n\nexport type DeveloperAppStatus = \"active\" | \"suspended\" | \"pending_review\"\n\nexport interface DeveloperApp {\n id: string\n name: string\n description: string | null\n logoUrl: string | null\n homepageUrl: string | null\n redirectUris: string[]\n allowedOrigins: string[]\n scopesRequested: DeveloperAppScope[]\n clientId: string\n status: DeveloperAppStatus\n createdAt: string\n updatedAt: string\n}\n\n/**\n * One-shot create response — `clientSecret` is returned exactly ONCE here.\n * Store it securely; subsequent `get`/`list` calls will not include it.\n */\nexport interface CreateDeveloperAppResult extends DeveloperApp {\n clientSecret: string\n}\n\nexport interface CreateDeveloperAppInput {\n name: string\n description?: string\n homepageUrl?: string\n logoUrl?: string\n /** At least 1, at most 10 redirect URIs. Each must be https or http://localhost. */\n redirectUris: string[]\n /** Up to 5 bare origins (no path/query/hash), e.g. \"https://example.com\". */\n allowedOrigins?: string[]\n /** At least 1 scope required. */\n scopesRequested: DeveloperAppScope[]\n}\n\nexport interface UpdateDeveloperAppInput {\n name?: string\n description?: string\n homepageUrl?: string\n logoUrl?: string\n redirectUris?: string[]\n allowedOrigins?: string[]\n scopesRequested?: DeveloperAppScope[]\n}\n\nexport interface RotateSecretResult {\n /** New client secret. Returned exactly once — old secret is invalidated. */\n clientSecret: string\n}\n\nexport class DeveloperAppsResource {\n constructor(private client: NodaroClient) {}\n\n /** List the authenticated user's developer apps. */\n list(): Promise<{ data: DeveloperApp[] }> {\n return this.client.request(\"GET\", \"/v1/developer-apps\")\n }\n\n /** Get a developer app by ID. */\n get(id: string): Promise<{ data: DeveloperApp }> {\n return this.client.request(\"GET\", `/v1/developer-apps/${encodeURIComponent(id)}`)\n }\n\n /**\n * Create a new developer app. Returns the app PLUS a one-time `clientSecret`\n * — store it now, the secret hash is the only copy kept server-side.\n */\n create(input: CreateDeveloperAppInput): Promise<{ data: CreateDeveloperAppResult }> {\n return this.client.request(\"POST\", \"/v1/developer-apps\", { body: input })\n }\n\n /** Update a developer app's metadata, redirect URIs, origins, or requested scopes. */\n update(id: string, input: UpdateDeveloperAppInput): Promise<{ data: DeveloperApp }> {\n return this.client.request(\n \"PATCH\",\n `/v1/developer-apps/${encodeURIComponent(id)}`,\n { body: input },\n )\n }\n\n /** Delete a developer app. Returns `{ success: true }`. */\n delete(id: string): Promise<{ success: true }> {\n return this.client.request(\n \"DELETE\",\n `/v1/developer-apps/${encodeURIComponent(id)}`,\n )\n }\n\n /**\n * Generate a new `clientSecret`. The previous secret is invalidated.\n * Server returns ONLY the new secret, not the full app record.\n */\n rotateSecret(id: string): Promise<RotateSecretResult> {\n return this.client.request(\n \"POST\",\n `/v1/developer-apps/${encodeURIComponent(id)}/rotate-secret`,\n )\n }\n}\n","import type { NodaroClient } from \"../client.js\"\nimport type { DeveloperAppScope } from \"./developer-apps.js\"\n\n/**\n * Server-side authorization-code exchange payload. Field names are snake_case\n * per OAuth 2.0 (RFC 6749).\n */\nexport interface ExchangeCodeInput {\n client_id: string\n client_secret: string\n /** Authorization code received from the consent redirect. */\n code: string\n /** Must match the redirect_uri used when issuing the code. */\n redirect_uri: string\n}\n\n/**\n * `POST /v1/oauth/token` response — snake_case per RFC 6749. `expires_in` is\n * the token's lifetime in seconds.\n */\nexport interface AccessTokenResponse {\n access_token: string\n token_type: \"Bearer\"\n /** Space-separated list of granted scopes. */\n scope: string\n expires_in: number\n}\n\n/**\n * Public app metadata for consent screens. Only safe-to-display fields —\n * no secret, no full origin list, no owner_user_id.\n */\nexport interface OAuthAppInfo {\n name: string\n description: string | null\n logoUrl: string | null\n homepageUrl: string | null\n scopesRequested: DeveloperAppScope[]\n}\n\nexport class OAuthResource {\n constructor(private client: NodaroClient) {}\n\n /**\n * Server-side authorization-code exchange. Sends the standard OAuth 2.0\n * `application/json` body to `POST /v1/oauth/token`.\n *\n * NEVER call this from a browser — `client_secret` must stay on the server.\n */\n exchangeCode(input: ExchangeCodeInput): Promise<AccessTokenResponse> {\n return this.client.request(\"POST\", \"/v1/oauth/token\", {\n body: { grant_type: \"authorization_code\", ...input },\n })\n }\n\n /**\n * Revoke an access token (RFC 7009). Always returns `{ success: true }`,\n * even for unknown tokens — the spec forbids leaking token validity.\n */\n revoke(token: string): Promise<{ success: true }> {\n return this.client.request(\"POST\", \"/v1/oauth/revoke\", { body: { token } })\n }\n\n /**\n * Get public app metadata for a consent screen.\n * `GET /v1/oauth/app-info?client_id=<id>`. Public route — no auth needed.\n */\n getAppInfo(clientId: string): Promise<OAuthAppInfo> {\n return this.client.request(\"GET\", \"/v1/oauth/app-info\", {\n query: { client_id: clientId },\n })\n }\n}\n","import type { NodaroClient } from \"../client.js\"\n\n/**\n * A published app — a workflow wrapped in a curated input/output presentation.\n * Returned by the public `/v1/apps/browse` endpoint.\n */\nexport interface PublishedApp {\n id: string\n slug: string\n name: string\n description?: string | null\n creatorId: string\n creatorName?: string | null\n thumbnailUrl?: string | null\n category?: string | null\n isFeatured?: boolean\n runCount?: number\n createdAt: string\n updatedAt: string\n}\n\nexport interface ListAppsParams {\n /** Substring search across app name + description. */\n search?: string\n /** Page size; backend caps at 50. */\n limit?: number\n /** Cursor token returned by the previous page. */\n cursor?: string\n /** Filter to a single category slug. */\n category?: string\n}\n\nexport interface ListAppsResult {\n data: PublishedApp[]\n nextCursor?: string | null\n}\n\n/**\n * App detail — includes the input schema (required + optional fields end users\n * fill in) and the output mapping (which workflow nodes produce which display\n * cards).\n */\nexport interface PublishedAppDetail extends PublishedApp {\n inputSchema: Record<string, unknown>\n outputs: Array<{ nodeId: string; label: string; type: string }>\n}\n\nexport interface AppRunResult {\n /** The execution-id that was started — poll via client.executions.get(). */\n executionId: string\n status: \"pending\" | \"running\"\n /** App-run id (distinct from executionId — used by listRuns/getRun). */\n runId?: string\n}\n\nexport interface AppRun {\n id: string\n appSlug: string\n executionId: string\n status: \"pending\" | \"running\" | \"completed\" | \"failed\" | \"cancelled\"\n inputs: Record<string, unknown>\n outputs?: Array<{ nodeId: string; type: string; url?: string; text?: string }>\n startedAt: string\n finishedAt?: string | null\n}\n\nexport interface ListAppRunsParams {\n limit?: number\n cursor?: string\n}\n\n/**\n * Result of a soft-delete (archive) operation. The run is moved to the user's\n * archive in the Nodaro UI; restoration and permanent deletion are UI-only by\n * design — SDK / MCP / API delete callers can't accidentally destroy data.\n */\nexport interface DeleteAppRunResult {\n success: true\n archived: true\n}\n\nexport class AppsResource {\n constructor(private client: NodaroClient) {}\n\n /** List published apps. Public — no auth required for community apps. */\n list(params: ListAppsParams = {}): Promise<ListAppsResult> {\n const qs = new URLSearchParams()\n if (params.search) qs.set(\"search\", params.search)\n if (params.limit !== undefined) qs.set(\"limit\", String(params.limit))\n if (params.cursor) qs.set(\"cursor\", params.cursor)\n if (params.category) qs.set(\"category\", params.category)\n const query = qs.toString()\n return this.client.request(\"GET\", `/v1/apps/browse${query ? `?${query}` : \"\"}`)\n }\n\n /** Get one app's metadata + input schema by slug. */\n get(slug: string): Promise<{ data: PublishedAppDetail }> {\n return this.client.request(\"GET\", `/v1/app/${encodeURIComponent(slug)}`)\n }\n\n /**\n * Trigger an app run with the given input values. The keys in `inputs` must\n * match the app's input-schema field names (see `get(slug).inputSchema`).\n * Returns the execution-id for status polling via client.executions.get().\n */\n run(slug: string, inputs: Record<string, unknown> = {}): Promise<AppRunResult> {\n return this.client.request(\"POST\", `/v1/app/${encodeURIComponent(slug)}/run`, {\n body: { inputs },\n })\n }\n\n /** List past runs for an app (the caller must own the app or the runs). */\n listRuns(slug: string, params: ListAppRunsParams = {}): Promise<{ data: AppRun[]; nextCursor?: string | null }> {\n const qs = new URLSearchParams()\n if (params.limit !== undefined) qs.set(\"limit\", String(params.limit))\n if (params.cursor) qs.set(\"cursor\", params.cursor)\n const query = qs.toString()\n return this.client.request(\n \"GET\",\n `/v1/app/${encodeURIComponent(slug)}/runs${query ? `?${query}` : \"\"}`,\n )\n }\n\n /** Get one app-run by id. */\n getRun(slug: string, runId: string): Promise<{ data: AppRun }> {\n return this.client.request(\n \"GET\",\n `/v1/app/${encodeURIComponent(slug)}/runs/${encodeURIComponent(runId)}`,\n )\n }\n\n /**\n * Archive (soft-delete) a published-app run. The run is hidden from the\n * default run list and can be restored or permanently deleted from the\n * archive view at https://app.nodaro.ai/archived-runs.\n *\n * @param slug The published app's slug (the last path segment of its URL).\n * @param runId The run's UUID.\n */\n deleteRun(slug: string, runId: string): Promise<DeleteAppRunResult> {\n return this.client.request(\n \"DELETE\",\n `/v1/app/${encodeURIComponent(slug)}/runs/${encodeURIComponent(runId)}`,\n )\n }\n}\n","import { buildPersonHints, PEOPLE, PERSON_DIMENSION_ORDER, PERSON_DIMENSION_LABELS, type PersonValue } from \"@nodaro/prompts\"\nimport type { CharacterAspectRatio, TtsProvider, CharacterAttachColumn, EntityStyle } from \"@nodaro/shared\"\nexport type { EntityStyle, CharacterAspectRatio } from \"@nodaro/shared\"\nexport { CHARACTER_STYLES, CHARACTER_ASPECT_OPTIONS, CHARACTER_ASPECT_DEFAULTS } from \"@nodaro/shared\"\nimport type { NodaroClient } from \"../client.js\"\n\n/**\n * Re-export the shared `EntityStyle` union (realistic | anime | 3d-pixar |\n * illustration) and `CHARACTER_STYLES` runtime tuple so SDK consumers don't\n * have to add `@nodaro/shared` as a second dependency just to typecheck the\n * `style` field. Single source of truth lives in `@nodaro/shared/entity-prompts`.\n */\n\n/**\n * Re-export the 4-value aspect-ratio union accepted by the generate-character*\n * routes. Single source of truth lives in `@nodaro/shared`. See\n * `CHARACTER_ASPECT_DEFAULTS` for the per-asset-type defaults.\n */\n\n/**\n * Structured Person composer, re-exported from `@nodaro/shared` so SDK\n * consumers can build a detailed person description — Identity, Body, and the\n * Face facial-geometry layer (cheekbones, canthal tilt, eyelid type, lip\n * fullness/shape, nose tip, etc.) — and feed it as `seedPrompt` / `description`\n * to `generate()` / `upsert()` without adding `@nodaro/shared` as a second dep.\n *\n * `buildPersonSeedPrompt` collapses a `PersonValue` into the same comma-joined\n * fragment the editor's Person picker produces (e.g. \"almond-shaped eyes,\n * sharply sculpted high cheekbones, full plump lips\"). Returns \"\" when empty.\n */\n\n\n/**\n * A character record returned by Nodaro's REST API. Mirrors the camelCase\n * shape produced by `backend/src/routes/characters.ts::toCamel()`.\n *\n * `expressions`, `poses`, `motions`, `angles`, `bodyAngles`,\n * `lightingVariations` are independent buckets keyed by a human-readable\n * variant name (e.g. `\"smile\"`, `\"standing\"`, `\"3/4 left\"`). Each entry's\n * `url` points at an R2-hosted asset.\n *\n * Identity-foundation fields:\n * - `referencePhotos` — caller-supplied real-life refs (max one per\n * non-`other` kind; cap 20 total). Drive the i2v / i2i path when a\n * provider supports multi-image conditioning.\n * - `realLifeRefsByVariant` — per-variant reference URLs (cap 20 keys,\n * 5 URLs per key). Keys are lowercased+trimmed.\n * - `referenceVideosByVariant` — per-label user-uploaded reference VIDEO\n * URLs (cap 20 keys, 5 URLs per key, lowercased+trimmed keys). Mirrors\n * `realLifeRefsByVariant` for video clips (e.g. emotion takes). Read the\n * chosen URLs off the row to drive generate-video's `referenceVideoUrls`.\n * - `seedPrompt` — short prompt fragment that scaffolds portrait gen.\n * - `canonicalDescription` — ~80–120-word LLM-authored visual caption,\n * populated by `approvePortrait()` / `recaption()`.\n */\nexport interface Character {\n id: string\n userId: string\n /** Canvas node linkage. `null` only on legacy clone rows that predate the\n * clone-side node_id fix (new clones mint one; creates always had one). */\n nodeId: string | null\n projectId: string | null\n name: string\n description: string | null\n gender: string | null\n style: string | null\n baseOutfit: string | null\n sourceImageUrl: string | null\n /** MODEL_CATALOG image-model id the main image was generated with (or `null`).\n * Set on create (the provider you generated with) + editable via `upsert`. */\n imageProvider: string | null\n expressions: Array<{ name: string; url: string }> | null\n poses: Array<{ name: string; url: string }> | null\n lightingVariations: Array<{ name: string; url: string }> | null\n angles: Array<{ name: string; url: string }> | null\n bodyAngles: Array<{ name: string; url: string }> | null\n motions: Array<{ name: string; url: string }> | null\n /** Named Character Boards — dense reference sheets, one per persona/look\n * (the `generate-image/character-board` factory preset rendered from the\n * character's images). A first-class bucket: community publish snapshots it\n * and clone hands the consumer their own copy to extend. Defaults to `[]`.\n * `type` marks an image-collage `\"identity\"` sheet vs a plain `\"looks\"`\n * board; `sourceImages` are the R2 URLs it was collaged from. Both optional\n * + backward-compatible (legacy boards have neither). */\n boards?: Array<{ name: string; url: string; type?: \"looks\" | \"identity\"; sourceImages?: string[] }> | null\n /** Per-label user-uploaded reference VIDEO URLs (R2), keyed by a\n * caller-owned label (lowercased+trimmed server-side). Mirrors\n * `realLifeRefsByVariant` for video clips; read the chosen URLs off the row\n * to feed generate-video's `referenceVideoUrls`. Defaults to `{}`. */\n referenceVideosByVariant?: Record<string, string[]> | null\n /** The user's chosen DEFAULT asset take per variant (Studio version history).\n * OPAQUE map: key `\"<bucket>:<variant>\"` (e.g. `\"bodyAngles:front\"`,\n * `\"expressions:smile\"`) → the chosen asset URL (one already present in that\n * bucket). Stored verbatim — keys are NOT normalized; soft-capped server-side\n * at 200 keys / 2048-char values (overflow dropped silently). Defaults to `{}`. */\n selectedAssetByVariant?: Record<string, string> | null\n /** `voiceType` records the selected voice's KIND (premade voices are\n * addressed by name; library/custom voices by id at text-to-speech time).\n * `previewUrl` is a playable audio sample (the voice's `preview_url` / clone\n * sample) the studio plays in an `<audio>` element — persisted so Voice Library\n * voices (which have no by-id lookup) stay previewable after reload. Client-\n * played only; the server never fetches it. `ttsProvider` is the library\n * voice's verified TTS provider (see `SharedVoice.recommendedProvider`) —\n * send it as the text-to-speech `provider` so the voice renders on a model\n * it's verified for. All optional — a character may have no voice, or a\n * legacy voice predating these fields. */\n voice: { voiceId: string; voiceName: string; traits: string; voiceType?: \"premade\" | \"library\" | \"custom\"; previewUrl?: string; ttsProvider?: TtsProvider } | null\n personality: {\n mood: string\n speechStyle: string\n movementStyle: string\n behavioralNotes: string\n } | null\n /** ~80–120-word LLM-authored visual caption (approve-portrait / recaption).\n * Optional on the read surface so existing literal consumers don't break;\n * the route always returns it (string | null). */\n canonicalDescription?: string | null\n /** Identity-lock strength for Character Studio asset generation. */\n identityLock?: \"off\" | \"soft\" | \"strict\"\n deletedAt: string | null\n createdAt: string\n updatedAt: string\n}\n\n/**\n * GET /v1/characters/:id appends three live-progress buckets the studio uses\n * to rehydrate spinners after a reload. Optional in the SDK surface — they\n * don't appear on `list()` rows.\n */\nexport interface CharacterDetail extends Character {\n pendingJobs?: Array<{\n jobId: string\n assetType: \"expressions\" | \"poses\" | \"angles\" | \"bodyAngles\" | \"lighting\" | \"motions\"\n name: string\n }>\n portraitCandidates?: Array<{\n jobId: string\n url: string | undefined\n progress: number\n status: string\n }>\n previousCandidates?: Array<{\n jobId: string\n url: string\n createdAt: string\n }>\n}\n\nexport type ReferencePhotoKind =\n | \"frontFace\"\n | \"sideLeft\"\n | \"sideRight\"\n | \"threeQuarterLeft\"\n | \"threeQuarterRight\"\n | \"frontBody\"\n | \"other\"\n\nexport interface ReferencePhoto {\n url: string\n kind: ReferencePhotoKind\n}\n\n/**\n * Body for `client.characters.upsert()`. Mirrors `upsertCharacterBody` in\n * `backend/src/routes/characters.ts`. Omitting `id` triggers an INSERT;\n * supplying it triggers an UPDATE that only writes the fields you pass —\n * undefined keys are NOT touched on the row.\n *\n * `name` is optional at the type level. The route requires `name` on INSERT\n * (id absent) and rejects with `validation_error` otherwise; on UPDATE the\n * route just ignores `name` when omitted, which lets partial updates like\n * `update(id, { gender: \"female\" })` succeed without re-sending the same\n * name the caller already has.\n */\nexport interface UpsertCharacterInput {\n /** UUID of the character row; omit to create. */\n id?: string\n /** Canvas node id the character belongs to. REQUIRED on create (the route\n * 400s without it, like `name`); optional on update — the update branch\n * never touches node_id, so partial updates needn't round-trip it. */\n nodeId?: string\n workflowId?: string\n projectId?: string\n name?: string\n description?: string\n gender?: string\n style?: string\n baseOutfit?: string\n sourceImageUrl?: string\n /** Persistent image-model id (a MODEL_CATALOG image model). Validated\n * server-side — unknown / non-image / \"\" is stored as `null`. */\n imageProvider?: string | null\n expressions?: Array<{ name: string; url: string }>\n poses?: Array<{ name: string; url: string }>\n lightingVariations?: Array<{ name: string; url: string }>\n angles?: Array<{ name: string; url: string }>\n bodyAngles?: Array<{ name: string; url: string }>\n motions?: Array<{ name: string; url: string }>\n /** Named Character Boards (see `Character.boards`) — whole-array replace,\n * like the asset buckets. Server caps: 24 boards, 200-char names, 30\n * sourceImages per board. */\n boards?: Array<{ name: string; url: string; type?: \"looks\" | \"identity\"; sourceImages?: string[] }>\n /** See `Character.voice` — persisted alongside the voice so TTS can resolve a\n * library/custom voice by id, `previewUrl` keeps the sample playable after\n * reload, and `ttsProvider` keeps generation on a model the voice is\n * verified for. All optional. */\n voice?: { voiceId: string; voiceName: string; traits: string; voiceType?: \"premade\" | \"library\" | \"custom\"; previewUrl?: string; ttsProvider?: TtsProvider } | null\n personality?: {\n mood: string\n speechStyle: string\n movementStyle: string\n behavioralNotes: string\n } | null\n seedPrompt?: string\n canonicalDescription?: string\n /** Identity-lock strength for Character Studio asset generation (off/soft/strict). */\n identityLock?: \"off\" | \"soft\" | \"strict\"\n referencePhotos?: ReferencePhoto[]\n /** Per-variant real-life reference URLs. Keys are lowercased+trimmed server-side. */\n realLifeRefsByVariant?: Record<string, string[]>\n /** Per-label user-uploaded reference VIDEO URLs (e.g. emotion takes). Keys\n * are lowercased+trimmed server-side; max 20 keys, 5 URLs each. Stored R2\n * URLs are read back off the row to drive generate-video's\n * `referenceVideoUrls`. */\n referenceVideosByVariant?: Record<string, string[]>\n /** The user's chosen DEFAULT asset take per variant. OPAQUE\n * `\"<bucket>:<variant>\"` → chosen-URL map; a write REPLACES the whole map\n * (the studio sends the full map each save). Omit to leave the row untouched.\n * Keys stored verbatim; soft-capped server-side at 200 keys / 2048-char values. */\n selectedAssetByVariant?: Record<string, string>\n}\n\nexport interface UpsertCharacterResult {\n id: string\n name?: string\n}\n\nexport interface ListCharactersParams {\n /** Restrict to a single project. */\n projectId?: string\n /** When true, return archived characters instead of active ones. */\n archived?: boolean\n /**\n * Max rows to return. Server defaults to 100, caps at 500. Omit to take the\n * server default — passing it just narrows further.\n */\n limit?: number\n}\n\nexport interface DuplicateCharacterInput {\n /** Optional canvas node id to bind the new row to. */\n nodeId?: string\n /** Optional project to drop the new row into. */\n projectId?: string\n}\n\nexport interface CharacterUsage {\n workflowCount: number\n workflows: Array<{ id: string; name: string }>\n}\n\n/**\n * Input for `client.characters.generate()` — fires the\n * `POST /v1/generate-character` route. Produces 1–10 portrait candidates;\n * each lands as one `jobs` row in `pending` state and is then enqueued for\n * the worker.\n *\n * Provide at least one of `seedPrompt`, `referencePhotos`, or `description`\n * (the backend's refinement rejects empty input with `validation_error`).\n *\n * When `attachToCharacterId` is set, the worker writes the resulting URL\n * directly to `characters.source_image_url` on completion — caller doesn't\n * need a separate `approvePortrait` call for single-candidate runs.\n */\nexport interface GenerateCharacterInput {\n name: string\n description?: string\n userPrompt?: string\n gender?: string\n style?: EntityStyle\n baseOutfit?: string\n sourceImageUrl?: string\n provider?: string\n /** Auto-attach the result to this character row. */\n attachToCharacterId?: string\n seedPrompt?: string\n referencePhotos?: ReferencePhoto[]\n /** Number of candidate images to generate (1–10; server-validated). */\n count?: number\n /**\n * Explicit aspect ratio. Highest precedence — overrides both the character\n * node toggle and the per-asset-type default (portraits default to `3:4`).\n * Must be one of the 4-value `CharacterAspectRatio` union.\n */\n aspectRatio?: CharacterAspectRatio\n /**\n * Character node toggle (per-canvas-node `defaultAssetAspectRatio`). Wins\n * against the per-asset-type default, loses to `aspectRatio`.\n */\n characterNodeAspectRatio?: CharacterAspectRatio\n /**\n * Credit-affecting quality tier (e.g. `\"high\"` for gpt-image). Priced like\n * generate-image (composite ids such as `gpt-image:high`); values the chosen\n * model doesn't support are ignored server-side, never rejected.\n */\n quality?: string\n /**\n * Credit-affecting output resolution (e.g. `\"2K\"` / `\"4K\"` / `\"2 MP\"`).\n * Priced like generate-image (composite ids such as `nano-banana-pro:4K`);\n * values the chosen model doesn't support are ignored server-side, never\n * rejected.\n */\n resolution?: string\n}\n\nexport interface GenerateCharacterResult {\n /** First job-id; convenience alias for `jobIds[0]`. */\n jobId: string\n /** All job-ids when `count > 1`. */\n jobIds: string[]\n}\n\nexport interface GenerateAssetInput {\n assetType:\n | \"expressions\"\n | \"poses\"\n | \"lighting\"\n | \"angles\"\n | \"headAngles\"\n | \"bodyAngles\"\n | \"custom\"\n /** The named variant (e.g. `\"smile\"`, `\"standing\"`, `\"3/4 left\"`). */\n variant: string\n /** Display name of the character; appears in the prompt. */\n name: string\n description?: string\n userPrompt?: string\n gender?: string\n style?: EntityStyle\n baseOutfit?: string\n sourceImageUrl?: string\n /** Real-life reference URLs (cap 5). */\n realLifeRefs?: string[]\n provider?: string\n /** Auto-attach to character row + asset bucket on completion. */\n attachToCharacterId?: string\n /** Shared type — auto-includes new buckets (sheets/detail_closeups/outfit_variations); mirrors objects.ts/locations.ts. */\n attachToColumn?: CharacterAttachColumn\n attachName?: string\n /**\n * Explicit aspect ratio. Highest precedence — overrides both the character\n * node toggle and the per-asset-type default (expressions=1:1, poses=9:16,\n * headAngles=3:4, bodyAngles=9:16, lighting=3:4, angles=3:4, custom=3:4).\n */\n aspectRatio?: CharacterAspectRatio\n /**\n * Character node toggle (per-canvas-node `defaultAssetAspectRatio`). Wins\n * against the per-asset-type default, loses to `aspectRatio`.\n */\n characterNodeAspectRatio?: CharacterAspectRatio\n /**\n * Credit-affecting quality tier (e.g. `\"high\"` for gpt-image). Values the\n * chosen model doesn't support are ignored server-side, never rejected.\n */\n quality?: string\n /**\n * Credit-affecting output resolution (e.g. `\"2K\"` / `\"4K\"` / `\"2 MP\"`).\n * Values the chosen model doesn't support are ignored server-side, never\n * rejected.\n */\n resolution?: string\n}\n\nexport interface GenerateMotionInput {\n motionPrompt: string\n /** Optional when `attachToCharacterId` is set — falls back to the row's portrait. */\n sourceImageUrl?: string\n provider?: string\n name: string\n description?: string\n motionDescription?: string\n gender?: string\n style?: EntityStyle\n baseOutfit?: string\n realLifeRefs?: string[]\n attachToCharacterId?: string\n attachName?: string\n /**\n * Explicit aspect ratio. Highest precedence — overrides both the character\n * node toggle and the motions default (`9:16`).\n */\n aspectRatio?: CharacterAspectRatio\n /**\n * Character node toggle (per-canvas-node `defaultAssetAspectRatio`). Wins\n * against the motions default, loses to `aspectRatio`.\n */\n characterNodeAspectRatio?: CharacterAspectRatio\n}\n\nexport interface ApprovePortraitResult {\n portraitUrl: string\n /**\n * LLM-authored caption. `null` when the LLM call failed during the approval\n * — the portrait is still set; call `recaption()` to retry.\n */\n canonicalDescription: string | null\n}\n\nexport interface RecaptionResult {\n canonicalDescription: string\n}\n\nexport class CharactersResource {\n constructor(private client: NodaroClient) {}\n\n /**\n * List the caller's characters. By default returns active characters only;\n * pass `archived: true` to fetch soft-deleted rows for an \"archive\" view.\n * When `projectId` is set, only characters belonging to that project are\n * returned.\n */\n list(params: ListCharactersParams = {}): Promise<{ characters: Character[] }> {\n const query: Record<string, string | undefined> = {}\n if (params.projectId) query.projectId = params.projectId\n if (params.archived) query.archived = \"true\"\n if (params.limit !== undefined) query.limit = String(params.limit)\n return this.client.request(\"GET\", \"/v1/characters\", { query })\n }\n\n /**\n * Fetch a single character including in-flight portrait / asset job state.\n * Soft-deleted (archived) rows are returned by id intentionally so canvas\n * nodes that hold a stale `characterDbId` keep loading.\n */\n get(id: string): Promise<CharacterDetail> {\n return this.client.request(\"GET\", `/v1/characters/${encodeURIComponent(id)}`)\n }\n\n /**\n * Create or update a character. Omit `id` to create; supply it to update\n * (only the fields you pass get written — undefined keys are untouched).\n *\n * If the caller-supplied `name` collides with an existing active character\n * for this user, the request returns 409 `name_taken`. To auto-number a\n * placeholder, pass the placeholder name from `@nodaro/shared` and the\n * server will derive \"Untitled character 2\", \"Untitled character 3\", etc.\n */\n upsert(input: UpsertCharacterInput): Promise<UpsertCharacterResult> {\n return this.client.request(\"POST\", \"/v1/characters\", { body: input })\n }\n\n /**\n * Convenience wrapper around `upsert()` for creating new characters.\n * Equivalent to `upsert({ ...input, id: undefined })`. `name` is REQUIRED\n * on create — the route 400s on INSERT-without-name; we narrow the type\n * here so callers fail at compile-time rather than runtime.\n */\n create(\n input: Omit<UpsertCharacterInput, \"id\"> & { name: string },\n ): Promise<UpsertCharacterResult> {\n return this.upsert(input)\n }\n\n /**\n * Convenience wrapper around `upsert()` for updating an existing character.\n * Equivalent to `upsert({ ...input, id })`.\n */\n update(\n id: string,\n input: Omit<UpsertCharacterInput, \"id\">,\n ): Promise<UpsertCharacterResult> {\n return this.upsert({ ...input, id })\n }\n\n /**\n * Soft-delete (archive) a character. The row is hidden from `list()` by\n * default but still loadable via `get(id)` so canvas nodes pointing at it\n * keep working. Restore with `restore(id)`.\n */\n delete(id: string): Promise<{ success: true; archived: true }> {\n return this.client.request(\"DELETE\", `/v1/characters/${encodeURIComponent(id)}`)\n }\n\n /**\n * Un-archive a character. If the original name now collides with an\n * active row, the server auto-suffixes \"(restored)\" and returns the\n * effective name.\n */\n restore(id: string): Promise<{ id: string; name: string }> {\n return this.client.request(\"POST\", `/v1/characters/${encodeURIComponent(id)}/restore`)\n }\n\n /**\n * Duplicate (fork) a character to a new row with a `\"(copy)\"` suffix.\n * Asset URLs are shared by reference — the new row can diverge by\n * regenerating any of them.\n */\n duplicate(id: string, input: DuplicateCharacterInput = {}): Promise<{ id: string; name: string }> {\n return this.client.request(\n \"POST\",\n `/v1/characters/${encodeURIComponent(id)}/duplicate`,\n { body: input },\n )\n }\n\n /**\n * Count of the caller's workflows that reference this character. Powers the\n * library \"Archive\" confirmation modal in the editor.\n */\n usage(id: string): Promise<CharacterUsage> {\n return this.client.request(\"GET\", `/v1/characters/${encodeURIComponent(id)}/usage`)\n }\n\n /**\n * Fire `POST /v1/generate-character` to produce one or more portrait\n * candidates. With `count > 1`, all jobs are reserved up-front before any\n * is enqueued — mid-batch failures roll back atomically.\n *\n * When `attachToCharacterId` is set, the worker writes the result directly\n * to the row's `source_image_url`; otherwise you must call\n * `approvePortrait()` after picking a candidate.\n */\n generate(input: GenerateCharacterInput): Promise<GenerateCharacterResult> {\n return this.client.request(\"POST\", \"/v1/generate-character\", { body: input })\n }\n\n /**\n * Fire `POST /v1/generate-character-asset` to produce a single\n * expression / pose / angle / lighting variant. When the studio path is\n * set (`attachToCharacterId` + `attachToColumn` + `attachName`), the\n * worker appends `{ name: attachName, url: <result> }` to the named\n * JSONB array column on completion.\n */\n generateAsset(input: GenerateAssetInput): Promise<{ jobId: string }> {\n return this.client.request(\"POST\", \"/v1/generate-character-asset\", { body: input })\n }\n\n /**\n * Fire `POST /v1/generate-character-motion` to animate the character's\n * portrait into a motion clip. The result is appended to the character's\n * `motions[]` bucket when `attachToCharacterId` is set.\n */\n generateMotion(input: GenerateMotionInput): Promise<{ jobId: string }> {\n return this.client.request(\"POST\", \"/v1/generate-character-motion\", { body: input })\n }\n\n /**\n * Approve a completed `generate-character` job as the character's portrait.\n * Sets `source_image_url` and fires the LLM caption (Claude Sonnet vision)\n * inline. Returns the new portrait URL plus the caption — `canonicalDescription`\n * is `null` if the LLM call sub-failed (portrait still set; retry via `recaption()`).\n */\n approvePortrait(id: string, candidateJobId: string): Promise<ApprovePortraitResult> {\n return this.client.request(\n \"POST\",\n `/v1/characters/${encodeURIComponent(id)}/approve-portrait`,\n { body: { candidateJobId } },\n )\n }\n\n /**\n * Re-fire the LLM caption against the character's current portrait. 502s on\n * LLM failure; returns 400 `no_portrait` if no portrait is set yet.\n */\n recaption(id: string): Promise<RecaptionResult> {\n return this.client.request(\n \"POST\",\n `/v1/characters/${encodeURIComponent(id)}/llm-caption`,\n )\n }\n}\n\n/** Person catalog + hint helpers, re-exported from @nodaro/prompts (FSL-licensed). */\nexport { buildPersonHints, PEOPLE, PERSON_DIMENSION_ORDER, PERSON_DIMENSION_LABELS }\nexport type { PersonValue }\n\n/**\n * Collapse a PersonValue into the comma-joined seed-prompt fragment used by\n * `characters.generate({ seedPrompt })` — same composition the Nodaro editor\n * performs. Powered by @nodaro/prompts.\n */\nexport function buildPersonSeedPrompt(value: PersonValue): string {\n return buildPersonHints(value as Record<string, unknown> & PersonValue).join(\", \")\n}\n","import type { CharacterAspectRatio, SurroundDirection, LocationAssetType, LocationAttachColumn } from \"@nodaro/shared\"\nexport type { LocationAssetType, LocationAttachColumn, CharacterAspectRatio, SurroundDirection } from \"@nodaro/shared\"\nexport { LOCATION_ASSET_TYPES, LOCATION_ATTACH_COLUMNS, SURROUND_DIRECTIONS } from \"@nodaro/shared\"\nimport type { NodaroClient } from \"../client.js\"\n\n/**\n * Re-export the shared `LocationAssetType` / `LocationAttachColumn` unions and\n * their runtime tuples so SDK consumers don't have to add `@nodaro/shared` as a\n * second dependency just to typecheck the `assetType` / `attachToColumn`\n * fields. Single source of truth lives in `@nodaro/shared/entity-prompts`.\n *\n * `CharacterAspectRatio` is re-exported alongside them — `generateMotion`'s\n * `aspectRatio` field reuses the same 4-value enum (1:1 / 3:4 / 16:9 / 9:16)\n * as characters; the route enforces this with `z.enum(CHARACTER_ASPECT_OPTIONS)`.\n */\n\n/**\n * Reference-photo kind discriminator — the mood-board roles a user can attach\n * to a location. Mirrors the `reference_photos.kind` Zod enum in\n * `backend/src/routes/locations.ts`. `other` is the free-form bucket.\n */\nexport type LocationReferencePhotoKind =\n | \"wide\"\n | \"interior\"\n | \"exterior\"\n | \"detail\"\n | \"moodBoard\"\n | \"other\"\n\nexport interface LocationReferencePhoto {\n url: string\n kind: LocationReferencePhotoKind\n}\n\n/**\n * A location record returned by Nodaro's REST API. Mirrors the camelCase\n * shape produced by `backend/src/routes/locations.ts::toCamel()`.\n *\n * Asset buckets (`timeOfDay`, `weather`, `angles`, `lighting`, `seasons`,\n * `atmosphereMotions`) are independent JSONB arrays keyed by a human-readable\n * variant name (e.g. `\"dawn\"`, `\"clear\"`, `\"wide\"`). Each entry's `url` points\n * at an R2-hosted asset.\n *\n * Identity-foundation fields:\n * - `referencePhotos` — caller-supplied mood-board refs (cap 20).\n * - `canonicalDescription` — ~80–120-word LLM-authored visual caption,\n * populated by `approveMainImage()` / `recaption()`. The wire still sends\n * `\"\"` on caption sub-failure (the breaking wire change is deferred to a\n * major bump), but `get()` normalizes `\"\"` → `null` so consumers see the\n * same `string | null` semantics as characters.\n * - `styleLock` — whether asset gens should anchor to the canonical style\n * captured at approval time. Defaults to `true` on new rows.\n */\nexport interface Location {\n id: string\n userId: string\n nodeId: string\n projectId: string | null\n name: string\n description: string | null\n category: string | null\n style: string | null\n sourceImageUrl: string | null\n /** MODEL_CATALOG image-model id the main image was generated with (or `null`).\n * Set on create + editable via the update route. */\n imageProvider: string | null\n timeOfDay: Array<{ name: string; url: string }>\n weather: Array<{ name: string; url: string }>\n angles: Array<{ name: string; url: string }>\n lighting: Array<{ name: string; url: string }>\n seasons: Array<{ name: string; url: string }>\n atmosphereMotions: Array<{ name: string; url: string }>\n /** Named Location Boards — dense reference sheets, one per variant/mood\n * (the `generate-image/location-board` factory preset rendered from the\n * location's images). A first-class bucket: community publish snapshots it\n * and clone hands the consumer their own copy to extend. Defaults to `[]`.\n * `type` marks an image-collage `\"identity\"` sheet vs a plain `\"looks\"`\n * board; `sourceImages` are the R2 URLs it was collaged from. Both optional\n * + backward-compatible (legacy boards have neither). */\n boards?: Array<{ name: string; url: string; type?: \"looks\" | \"identity\"; sourceImages?: string[] }> | null\n referencePhotos: LocationReferencePhoto[]\n /** `null` when no caption is set (or the LLM caption sub-failed) — the wire\n * sends `\"\"`, normalized to `null` in `get()` to match character semantics. */\n canonicalDescription: string | null\n styleLock: boolean\n /** The user's chosen DEFAULT asset take per variant (Studio version history).\n * OPAQUE map: key `\"<bucket>:<variant>\"` (e.g. `\"timeOfDay:dawn\"`) → the chosen\n * asset URL (one already present in that bucket). Stored verbatim — keys are\n * NOT normalized; soft-capped server-side at 200 keys / 2048-char values\n * (overflow dropped silently). Defaults to `{}`. */\n selectedAssetByVariant?: Record<string, string> | null\n deletedAt: string | null\n createdAt: string\n updatedAt: string\n}\n\n/**\n * GET /v1/locations/:id appends a `pendingJobs` bucket the studio uses to\n * rehydrate spinners after a reload. Optional on the SDK surface — it doesn't\n * appear on `list()` rows.\n */\nexport interface LocationDetail extends Location {\n pendingJobs?: Array<{\n jobId: string\n assetType: string\n name: string\n status: string\n }>\n /**\n * Completed candidate main images for this location whose URL differs from\n * the current `sourceImageUrl`, newest first (max 5). Surfaced so a\n * \"pick from N\" UI can offer alternatives the user can promote via\n * `approveMainImage(id, jobId)`; until they pick, the current main image is\n * left untouched. Mirrors `CharacterDetail.previousCandidates`. Optional -\n * absent on `list()` rows, present (possibly empty) on `get()`.\n */\n previousCandidates?: Array<{\n jobId: string\n url: string\n createdAt: string\n }>\n}\n\n/**\n * Body for `client.locations.create()`. Mirrors the INSERT branch of\n * `upsertLocationBody` in `backend/src/routes/locations.ts`. `name` + `nodeId`\n * are required on create.\n */\nexport interface CreateLocationInput {\n nodeId: string\n name: string\n description?: string\n category?: string\n style?: string\n workflowId?: string\n projectId?: string\n sourceImageUrl?: string\n /** Persistent image-model id (a MODEL_CATALOG image model). Validated\n * server-side — unknown / non-image / \"\" is stored as `null`. */\n imageProvider?: string | null\n referencePhotos?: LocationReferencePhoto[]\n canonicalDescription?: string\n styleLock?: boolean\n /** The user's chosen DEFAULT asset take per variant. OPAQUE\n * `\"<bucket>:<variant>\"` → chosen-URL map; a write REPLACES the whole map.\n * Keys stored verbatim; soft-capped server-side at 200 keys / 2048-char values. */\n selectedAssetByVariant?: Record<string, string>\n}\n\n/**\n * Body for `client.locations.update()`. Mirrors the UPDATE branch of\n * `upsertLocationBody` in `backend/src/routes/locations.ts`.\n *\n * Worker-owned asset buckets (`timeOfDay`/`weather`/`angles`/`lighting`/\n * `seasons`/`atmosphereMotions`) are deliberately omitted — the route drops\n * them on UPDATE so a Studio auto-save with a stale snapshot cannot clobber\n * the worker's atomic `append_location_asset()` writes.\n *\n * `expectedUpdatedAt` is the optimistic-concurrency token: when present, the\n * UPDATE only succeeds if the row's `updated_at` still matches; on mismatch\n * the route returns 409 so the studio can re-fetch + merge.\n */\nexport interface UpdateLocationInput {\n name?: string\n description?: string\n category?: string\n style?: string\n sourceImageUrl?: string\n /** Persistent image-model id (a MODEL_CATALOG image model). Validated\n * server-side — unknown / non-image / \"\" is stored as `null`. */\n imageProvider?: string | null\n referencePhotos?: LocationReferencePhoto[]\n canonicalDescription?: string\n styleLock?: boolean\n /** The user's chosen DEFAULT asset take per variant. OPAQUE\n * `\"<bucket>:<variant>\"` → chosen-URL map; a write REPLACES the whole map\n * (omit to leave untouched). Keys stored verbatim; soft-capped server-side\n * at 200 keys / 2048-char values. */\n selectedAssetByVariant?: Record<string, string>\n /** Named Location Boards (see `Location.boards`) — whole-array replace,\n * USER-owned (unlike the worker-owned buckets it flows through UPDATE).\n * Server caps: 24 boards, 200-char names, 30 sourceImages per board. */\n boards?: Array<{ name: string; url: string; type?: \"looks\" | \"identity\"; sourceImages?: string[] }>\n /** ISO-8601 timestamp recording when PII consent was captured for this location. */\n piiConsentAt?: string\n expectedUpdatedAt?: string\n}\n\nexport interface UpdateLocationResult {\n id: string\n updatedAt: string\n}\n\nexport interface ListLocationsParams {\n /** When true, return archived locations instead of active ones. */\n archived?: boolean\n}\n\n/**\n * Input for `client.locations.generate()` — fires the\n * `POST /v1/generate-location` route. Produces 1–10 candidate\n * establishing shots; each lands as one `jobs` row in `pending` state and\n * is then enqueued for the worker.\n *\n * When `attachToLocationId` is set AND `count === 1`, the worker writes the\n * resulting URL directly to `locations.source_image_url` on completion —\n * caller doesn't need a separate `approveMainImage` call. Multi-candidate\n * batches MUST go through explicit approval so the user picks the winner.\n */\nexport interface GenerateLocationInput {\n name: string\n description?: string\n userPrompt?: string\n category?: \"indoor\" | \"outdoor\" | \"urban\" | \"nature\" | \"fantasy\" | \"sci-fi\" | \"historical\" | \"futuristic\" | \"other\"\n style?: \"realistic\" | \"anime\" | \"3d-pixar\" | \"illustration\"\n sourceImageUrl?: string\n provider?: string\n /** Number of candidate images to generate (1–10; server-validated). */\n count?: number\n /** Auto-attach the result to this location row (single-candidate only). */\n attachToLocationId?: string\n /**\n * Credit-affecting quality tier (e.g. `\"high\"` for gpt-image). Priced like\n * generate-image (composite ids such as `gpt-image:high`); values the chosen\n * model doesn't support are ignored server-side, never rejected.\n */\n quality?: string\n /**\n * Credit-affecting output resolution (e.g. `\"2K\"` / `\"4K\"` / `\"2 MP\"`).\n * Priced like generate-image (composite ids such as `nano-banana-pro:4K`);\n * values the chosen model doesn't support are ignored server-side, never\n * rejected.\n */\n resolution?: string\n}\n\n/**\n * `generate()` response — `jobIds` is ALWAYS present (the harmonized contract,\n * matching characters). `jobId` is a deprecated back-compat alias populated only\n * on `count === 1`; prefer `jobIds`. (Will be removed on the next major.)\n */\nexport interface GenerateLocationResult {\n jobIds: string[]\n /** @deprecated count===1 back-compat alias — use `jobIds`. */\n jobId?: string\n}\n\n/**\n * Input for `client.locations.generateAsset()` — fires the\n * `POST /v1/generate-location-asset` route. Produces a single\n * timeOfDay / weather / seasons / angles / lighting / custom variant.\n *\n * When all three studio-path fields are set (`attachToLocationId` +\n * `attachToColumn` + `attachName`), the worker appends\n * `{ name: attachName, url: <result> }` to the named JSONB array column on\n * the user's location row on completion. `attachToColumn` is REQUIRED for\n * `assetType === \"custom\"` — the worker can't infer the bucket from the\n * asset type.\n */\nexport interface GenerateLocationAssetInput {\n assetType: LocationAssetType\n variant: string\n name: string\n description?: string\n userPrompt?: string\n category?: string\n style?: \"realistic\" | \"anime\" | \"3d-pixar\" | \"illustration\"\n sourceImageUrl?: string\n provider?: string\n /** Optional framing override (the same 4-value enum as `generateMotion`).\n * Absent = the image model's default. The studio's 360° surround path pins\n * `\"16:9\"` so every ring view matches the establishing shot's frame. */\n aspectRatio?: CharacterAspectRatio\n attachToLocationId?: string\n attachToColumn?: LocationAttachColumn\n attachName?: string\n /**\n * Credit-affecting quality tier (e.g. `\"high\"` for gpt-image). Values the\n * chosen model doesn't support are ignored server-side, never rejected.\n */\n quality?: string\n /**\n * Credit-affecting output resolution (e.g. `\"2K\"` / `\"4K\"` / `\"2 MP\"`).\n * Values the chosen model doesn't support are ignored server-side, never\n * rejected.\n */\n resolution?: string\n}\n\n/**\n * Input for `client.locations.generateSurroundContinuation()` — fires the\n * `POST /v1/generate-surround-continuation` route. Generates one seamless 360°\n * ring view as an image-to-image continuation of `referenceImageUrl` (the\n * previous ring view, or the establishing shot for the first ring).\n *\n * The platform owns the whole pipeline: it builds the half-carry composite\n * server-side (carry the reference's trailing half into the new frame's leading\n * half per `direction`, gray the rest), paints the gray region, then\n * color-harmonizes the painted half to the carried half so there is no tonal\n * seam down the frame's center. The carried half stays pixel-exact, so a\n * panorama viewer stitching adjacent ring views stays geometrically seamless.\n *\n * When the studio path is set (`attachToLocationId` + `attachToColumn` +\n * `attachName`), the worker appends `{ name: attachName, url: <result> }` to the\n * location's bucket (studio uses `attachToColumn: \"angles\"`,\n * `attachName: \"Surround 45°\"`).\n */\nexport interface GenerateSurroundContinuationInput {\n /** The previous ring view to continue from (i2i anchor). */\n referenceImageUrl: string\n /** Carry/paint axis: turn right, turn left, tilt up, or tilt down. Tilts\n * render the sky/ground (a thin horizon strip), not a horizontal continuation. */\n direction: SurroundDirection\n /** Ring angle (45, 90, …) — stored on the result as metadata. */\n degrees?: number\n /** Fraction of the frame carried from the reference. Omitted ⇒ per-direction\n * default (0.5 for a pan, 0.12 thin strip for a tilt). */\n carriedFraction?: number\n /** Upscale/denoise the result before it's chained as the next reference, to\n * slow cumulative softening down a long ring chain. Default false. */\n refine?: boolean\n /** Refine model when `refine` is set. `recraft-upscale` (1 cr, default) or\n * `topaz-image-upscale` (3 cr). */\n refineProvider?: \"recraft-upscale\" | \"topaz-image-upscale\"\n /** Optional free-form scene hint woven into the fill prompt. */\n userPrompt?: string\n /** Image model. Studio pins `nano-banana-pro`; default `nano-banana`. */\n provider?: string\n /** Studio pins `\"16:9\"` so every ring view matches the establishing frame. */\n aspectRatio?: CharacterAspectRatio\n attachToLocationId?: string\n attachToColumn?: LocationAttachColumn\n attachName?: string\n}\n\n/**\n * Input for `client.locations.generateMotion()` — fires the\n * `POST /v1/generate-location-motion` route. Produces a single atmospheric\n * motion clip (drifting fog, snowfall, rolling waves, etc.) animated FROM a\n * static establishing-shot image.\n *\n * Mirrors `client.characters.generateMotion()` minus the character-specific\n * fields (gender / baseOutfit / realLifeRefs). The route hardcodes the attach\n * column to `atmosphere_motions` — callers supply `attachToLocationId` +\n * `attachName` only.\n *\n * `sourceImageUrl` is REQUIRED — image-to-video needs a source frame and the\n * route has no fallback (no `source_image_url` column to pull from on the\n * locations row; the studio path supplies the canonical establishing-shot URL\n * explicitly).\n *\n * When the studio path is set (`attachToLocationId` + `attachName`), the\n * worker appends `{ name: attachName, url: <result> }` to the location row's\n * `atmosphere_motions` JSONB column on completion.\n */\nexport interface GenerateLocationMotionInput {\n motionPrompt: string\n sourceImageUrl: string\n provider?: string\n name: string\n category?: string\n style?: \"realistic\" | \"anime\" | \"3d-pixar\" | \"illustration\"\n canonicalDescription?: string\n attachToLocationId?: string\n attachName?: string\n /**\n * Optional aspect ratio override. Defaults to 16:9 server-side via\n * `resolveLocationAspectRatio` (locations are cinematic establishing shots).\n * One of the 4-value `CharacterAspectRatio` union — locations reuse the\n * character aspect enum since the supported ratios are identical.\n */\n aspectRatio?: CharacterAspectRatio\n}\n\nexport interface ApproveMainImageResult {\n sourceImageUrl: string\n /**\n * LLM-authored caption. `null` when the LLM caption sub-failed — the wire\n * sends `\"\"`, normalized to `null` here so consumers see the same\n * `string | null` semantics as characters. The main image is still set; call\n * `recaption()` to retry.\n */\n canonicalDescription: string | null\n}\n\nexport interface RecaptionLocationResult {\n canonicalDescription: string\n}\n\nexport class LocationsResource {\n constructor(private client: NodaroClient) {}\n\n /**\n * List the caller's locations. By default returns active locations only;\n * pass `archived: true` to fetch soft-deleted rows for an \"archive\" view.\n */\n list(params: ListLocationsParams = {}): Promise<{ locations: Location[] }> {\n const query: Record<string, string | undefined> = {}\n if (params.archived) query.archived = \"true\"\n return this.client.request(\"GET\", \"/v1/locations\", { query })\n }\n\n /**\n * Convenience wrapper for `list({ archived: true })`. Returns soft-deleted\n * rows so callers can drive a UI \"Archived\" tab without re-encoding the\n * query param. Mirrors `ObjectsResource.listArchived`.\n *\n * `archived` is omitted from the param type — it's always set to `true` here.\n */\n listArchived(params: Omit<ListLocationsParams, \"archived\"> = {}): Promise<{ locations: Location[] }> {\n return this.list({ ...params, archived: true })\n }\n\n /**\n * Fetch a single location including in-flight asset job state. Soft-deleted\n * (archived) rows are returned by id intentionally so canvas nodes that\n * hold a stale `locationDbId` keep loading.\n */\n async get(id: string): Promise<LocationDetail> {\n const res = await this.client.request<LocationDetail>(\n \"GET\",\n `/v1/locations/${encodeURIComponent(id)}`,\n )\n // Normalize the wire `\"\"` caption (DB null / LLM sub-failure) → null so\n // consumers see the same `string | null` semantics as characters. New\n // object — never mutate the response.\n return { ...res, canonicalDescription: res.canonicalDescription || null }\n }\n\n /**\n * Create a new location. `name` + `nodeId` are required — the route 400s\n * otherwise. Returns the new row's id.\n *\n * Note: the underlying route is the same `POST /v1/locations` upsert that\n * powers `update()`. This convenience wrapper enforces the INSERT-required\n * fields at the type level and never sends an `id`.\n */\n create(data: CreateLocationInput): Promise<{ id: string }> {\n return this.client.request(\"POST\", \"/v1/locations\", { body: data })\n }\n\n /**\n * Update a location. Only the fields you pass are written — undefined keys\n * are NOT touched on the row. Worker-owned asset buckets are intentionally\n * not exposed on this surface (see `UpdateLocationInput` for the rationale).\n *\n * Optimistic-concurrency: pass `expectedUpdatedAt` to require the row's\n * `updated_at` still matches; on mismatch the route returns 409\n * `concurrent_modification`. The SDK surfaces that as a generic\n * `NodaroError` with the same code.\n */\n update(id: string, data: UpdateLocationInput): Promise<UpdateLocationResult> {\n return this.client.request(\"POST\", \"/v1/locations\", {\n body: { id, ...data },\n })\n }\n\n /**\n * Soft-delete (archive) a location. The row is hidden from `list()` by\n * default but still loadable via `get(id)` so canvas nodes pointing at it\n * keep working. Restore with `restore(id)`.\n */\n delete(id: string): Promise<{ success: true; archived: true }> {\n return this.client.request(\"DELETE\", `/v1/locations/${encodeURIComponent(id)}`)\n }\n\n /**\n * Un-archive a location. If the original name now collides (case-\n * insensitive) with an active row, the server auto-suffixes \"(restored)\"\n * and returns the effective name.\n */\n restore(id: string): Promise<{ id: string; name: string }> {\n return this.client.request(\"POST\", `/v1/locations/${encodeURIComponent(id)}/restore`)\n }\n\n /**\n * Fire `POST /v1/generate-location` to produce one or more candidate main\n * images. With `count > 1`, all jobs are reserved up-front before any\n * is enqueued — mid-batch failures roll back atomically.\n *\n * When `attachToLocationId` is set AND `count === 1`, the worker writes\n * the result directly to the row's `source_image_url`; otherwise you must\n * call `approveMainImage()` after picking a candidate.\n */\n async generate(data: GenerateLocationInput): Promise<GenerateLocationResult> {\n const res = await this.client.request<{ jobId?: string; jobIds?: string[] }>(\n \"POST\", \"/v1/generate-location\", { body: data },\n )\n // Tolerate the legacy `{ jobId }`-only shape (older server): synthesize jobIds.\n const jobIds = res.jobIds ?? (res.jobId ? [res.jobId] : [])\n return res.jobId ? { jobIds, jobId: res.jobId } : { jobIds }\n }\n\n /**\n * Fire `POST /v1/generate-location-asset` to produce a single variant.\n * When the studio path is set (`attachToLocationId` + `attachToColumn` +\n * `attachName`), the worker appends `{ name: attachName, url: <result> }`\n * to the named JSONB array column on completion.\n */\n generateAsset(data: GenerateLocationAssetInput): Promise<{ jobId: string }> {\n return this.client.request(\"POST\", \"/v1/generate-location-asset\", { body: data })\n }\n\n /**\n * Fire `POST /v1/generate-surround-continuation` to produce one seamless 360°\n * ring view as an i2i continuation of `referenceImageUrl`. The platform builds\n * the half-carry composite, paints the missing half, and color-harmonizes it\n * to the carried half (no tonal seam; carried half stays pixel-exact). When the\n * studio path is set, the worker appends the result to the location's bucket.\n */\n generateSurroundContinuation(data: GenerateSurroundContinuationInput): Promise<{ jobId: string }> {\n return this.client.request(\"POST\", \"/v1/generate-surround-continuation\", { body: data })\n }\n\n /**\n * Fire `POST /v1/generate-location-motion` to animate the location's\n * establishing shot into an atmospheric motion clip. Image-to-video, single\n * clip per call; the attach column is hardcoded to `atmosphere_motions`\n * server-side (locations have a single motion bucket so the caller doesn't\n * supply `attachToColumn`). When the studio path is set\n * (`attachToLocationId` + `attachName`), the worker appends\n * `{ name: attachName, url: <result> }` to the row's `atmosphere_motions`\n * column on completion.\n */\n generateMotion(data: GenerateLocationMotionInput): Promise<{ jobId: string }> {\n return this.client.request(\"POST\", \"/v1/generate-location-motion\", { body: data })\n }\n\n /**\n * Atomically remove ONE asset take (every entry matching `url`) from a\n * worker-owned bucket column — `POST /v1/locations/:id/remove-asset`. The\n * worker-owned buckets are deliberately not writable through `update()`\n * (a stale snapshot would race concurrent worker appends), so deleting a\n * take — e.g. a 360° surround view being regenerated — goes through this\n * single-statement server-side filter instead. 404s (`NotFoundError`) when\n * the url isn't in that bucket or the location isn't yours.\n */\n removeAsset(\n id: string,\n data: { column: LocationAttachColumn; url: string },\n ): Promise<{ removed: true }> {\n return this.client.request(\n \"POST\",\n `/v1/locations/${encodeURIComponent(id)}/remove-asset`,\n { body: data },\n )\n }\n\n /**\n * Approve a completed `generate-location` job as the location's main image.\n * Sets `source_image_url` and fires the LLM caption (Claude Sonnet vision)\n * inline. Returns the new main-image URL plus the caption.\n *\n * Caption-failure semantics: the route still sends `\"\"` on LLM sub-failure,\n * but the SDK normalizes `\"\"` → `null` here so `canonicalDescription` carries\n * the same `string | null` semantics as characters. The main image is still\n * set; call `recaption()` to retry.\n */\n async approveMainImage(id: string, candidateJobId: string): Promise<ApproveMainImageResult> {\n const res = await this.client.request<{ sourceImageUrl: string; canonicalDescription: string | null }>(\n \"POST\",\n `/v1/locations/${encodeURIComponent(id)}/approve-main-image`,\n { body: { candidateJobId } },\n )\n // Normalize the wire `\"\"` (LLM sub-failure) → null; build a new object\n // rather than mutating the response.\n return { sourceImageUrl: res.sourceImageUrl, canonicalDescription: res.canonicalDescription || null }\n }\n\n /**\n * Re-fire the LLM caption against the location's current main image. 502s\n * on LLM failure (unlike `approveMainImage` which preserves the side-effect\n * and returns \"\"); returns 400 `no_source_image` if no main image is set\n * yet.\n */\n recaption(id: string): Promise<RecaptionLocationResult> {\n return this.client.request(\n \"POST\",\n `/v1/locations/${encodeURIComponent(id)}/llm-caption`,\n )\n }\n}\n","import type { ObjectAspectRatio, ObjectAssetType, ObjectAttachColumn } from \"@nodaro/shared\"\nexport type { ObjectAssetType, ObjectAttachColumn, ObjectAspectRatio } from \"@nodaro/shared\"\nexport { OBJECT_ASSET_TYPES, OBJECT_ATTACH_COLUMNS, OBJECT_ASPECT_OPTIONS, OBJECT_ASPECT_DEFAULTS } from \"@nodaro/shared\"\nimport type { NodaroClient } from \"../client.js\"\n\n/**\n * Re-export the shared `ObjectAssetType` / `ObjectAttachColumn` unions and\n * their runtime tuples so SDK consumers don't have to add `@nodaro/shared` as\n * a second dependency just to typecheck the `assetType` / `attachToColumn`\n * fields. Single source of truth lives in `@nodaro/shared/entity-prompts`.\n *\n * `ObjectAspectRatio` is re-exported alongside them — `generateMotion`'s\n * `aspectRatio` field is the 5-value object enum (1:1 / 3:4 / 16:9 / 9:16 /\n * 4:3) from `@nodaro/shared/object-aspect-defaults`. Distinct from\n * `CharacterAspectRatio` because objects support an extra 4:3 framing for\n * product-showcase shots.\n */\n\n/**\n * Reference-photo kind discriminator — the mood-board roles a user can attach\n * to an object. Mirrors the `reference_photos.kind` field accepted by\n * `backend/src/routes/objects.ts` (the route accepts open strings; this SDK\n * type narrows to the 6 canonical roles surfaced by the Studio). `other` is\n * the free-form bucket.\n */\nexport type ObjectReferencePhotoKind =\n | \"front\"\n | \"side\"\n | \"detail\"\n | \"context\"\n | \"moodBoard\"\n | \"other\"\n\nexport interface ObjectReferencePhoto {\n url: string\n kind: ObjectReferencePhotoKind\n}\n\n/**\n * An object record returned by Nodaro's REST API. Mirrors the camelCase\n * shape produced by `backend/src/routes/objects.ts::toCamel()`.\n *\n * Asset buckets (`angles`, `materials`, `variations`, `motionClips`) are\n * independent JSONB arrays keyed by a human-readable variant name (e.g.\n * `\"front\"`, `\"wood\"`, `\"weathered\"`). Each entry's `url` points at an\n * R2-hosted asset.\n *\n * Identity-foundation fields:\n * - `referencePhotos` — caller-supplied mood-board refs (cap 20). Objects\n * do NOT carry a `piiConsentAt` field (location Phase 2 #7 only).\n * - `canonicalDescription` — ~80–120-word LLM-authored visual caption,\n * populated by `approveMainImage()` / `recaption()`. The wire still sends\n * `\"\"` on caption sub-failure (the breaking wire change is deferred to a\n * major bump), but `get()` normalizes `\"\"` → `null` so consumers see the\n * same `string | null` semantics as characters.\n * - `styleLock` — whether asset gens should anchor to the canonical style\n * captured at approval time. Defaults to `true` on new rows.\n *\n * `Object` shadows the JS global, which TypeScript handles cleanly via\n * local-scope resolution. Consumers who need both can alias as\n * `import type { Object as NodaroObject } from \"@nodaro/sdk\"`.\n */\nexport interface Object {\n id: string\n userId: string\n nodeId: string\n projectId: string | null\n name: string\n description: string | null\n category: string | null\n style: string | null\n sourceImageUrl: string | null\n /** MODEL_CATALOG image-model id the main image was generated with (or `null`).\n * Set on create + editable via the update route. */\n imageProvider: string | null\n angles: Array<{ name: string; url: string }>\n materials: Array<{ name: string; url: string }>\n variations: Array<{ name: string; url: string }>\n motionClips: Array<{ name: string; url: string }>\n /** Named Product Boards — dense reference sheets, one per variant/colorway\n * (the `generate-image/product-board` factory preset rendered from the\n * object's images). A first-class bucket: community publish snapshots it\n * and clone hands the consumer their own copy to extend. Defaults to `[]`.\n * `type` marks an image-collage `\"identity\"` sheet vs a plain `\"looks\"`\n * board; `sourceImages` are the R2 URLs it was collaged from. Both optional\n * + backward-compatible (legacy boards have neither). */\n boards?: Array<{ name: string; url: string; type?: \"looks\" | \"identity\"; sourceImages?: string[] }> | null\n referencePhotos: ObjectReferencePhoto[]\n /** `null` when no caption is set (or the LLM caption sub-failed) — the wire\n * sends `\"\"`, normalized to `null` in `get()` to match character semantics. */\n canonicalDescription: string | null\n styleLock: boolean\n /** The user's chosen DEFAULT asset take per variant (Studio version history).\n * OPAQUE map: key `\"<bucket>:<variant>\"` (e.g. `\"angles:front\"`) → the chosen\n * asset URL (one already present in that bucket). Stored verbatim — keys are\n * NOT normalized; soft-capped server-side at 200 keys / 2048-char values\n * (overflow dropped silently). Defaults to `{}`. */\n selectedAssetByVariant?: Record<string, string> | null\n deletedAt: string | null\n createdAt: string\n updatedAt: string\n}\n\n/**\n * GET /v1/objects/:id may append a `pendingJobs` bucket the studio uses to\n * rehydrate spinners after a reload. Optional on the SDK surface — it doesn't\n * appear on `list()` rows.\n */\nexport interface ObjectDetail extends Object {\n pendingJobs?: Array<{\n jobId: string\n assetType: string\n name: string\n status: string\n }>\n}\n\n/**\n * Body for `client.objects.create()`. Mirrors the INSERT branch of\n * `upsertObjectBody` in `backend/src/routes/objects.ts`. `name` + `nodeId`\n * are required on create.\n */\nexport interface CreateObjectInput {\n nodeId: string\n name: string\n description?: string\n category?: ObjectCategory\n style?: string\n workflowId?: string\n projectId?: string\n sourceImageUrl?: string\n /** Persistent image-model id (a MODEL_CATALOG image model). Validated\n * server-side — unknown / non-image / \"\" is stored as `null`. */\n imageProvider?: string | null\n referencePhotos?: ObjectReferencePhoto[]\n canonicalDescription?: string\n styleLock?: boolean\n /** The user's chosen DEFAULT asset take per variant. OPAQUE\n * `\"<bucket>:<variant>\"` → chosen-URL map; a write REPLACES the whole map.\n * Keys stored verbatim; soft-capped server-side at 200 keys / 2048-char values. */\n selectedAssetByVariant?: Record<string, string>\n}\n\n/**\n * The 10-value Object category enum. Mirrors the literal accepted by\n * `POST /v1/generate-object` and surfaced in the Object Studio category\n * picker. Distinct from location's geography-based set.\n */\nexport type ObjectCategory =\n | \"furniture\"\n | \"vehicle\"\n | \"weapon\"\n | \"food\"\n | \"clothing\"\n | \"electronics\"\n | \"nature\"\n | \"tool\"\n | \"animal\"\n | \"other\"\n\n/**\n * Body for `client.objects.update()`. Mirrors the UPDATE branch of\n * `upsertObjectBody` in `backend/src/routes/objects.ts`.\n *\n * Worker-owned asset buckets (`angles` / `materials` / `variations` /\n * `motionClips`) are deliberately omitted — the route drops them on UPDATE\n * so a Studio auto-save with a stale snapshot cannot clobber the worker's\n * atomic `append_object_asset()` writes.\n *\n * `expectedUpdatedAt` is the optimistic-concurrency token: when present, the\n * UPDATE only succeeds if the row's `updated_at` still matches; on mismatch\n * the route returns 409 so the studio can re-fetch + merge.\n */\nexport interface UpdateObjectInput {\n name?: string\n description?: string\n category?: ObjectCategory\n style?: string\n sourceImageUrl?: string\n /** Persistent image-model id (a MODEL_CATALOG image model). Validated\n * server-side — unknown / non-image / \"\" is stored as `null`. */\n imageProvider?: string | null\n referencePhotos?: ObjectReferencePhoto[]\n canonicalDescription?: string\n styleLock?: boolean\n /** The user's chosen DEFAULT asset take per variant. OPAQUE\n * `\"<bucket>:<variant>\"` → chosen-URL map; a write REPLACES the whole map\n * (omit to leave untouched). Keys stored verbatim; soft-capped server-side\n * at 200 keys / 2048-char values. */\n selectedAssetByVariant?: Record<string, string>\n /** Named Product Boards (see `Object.boards`) — whole-array replace,\n * USER-owned (unlike the worker-owned buckets it flows through UPDATE).\n * Server caps: 24 boards, 200-char names, 30 sourceImages per board. */\n boards?: Array<{ name: string; url: string; type?: \"looks\" | \"identity\"; sourceImages?: string[] }>\n expectedUpdatedAt?: string\n}\n\nexport interface UpdateObjectResult {\n id: string\n updatedAt: string\n}\n\n/**\n * Combined create + update body (parameter for both branches). Exported for\n * callers that want to drive a single `upsert` flow without picking between\n * `Create*` and `Update*`. Mirrors the `upsertObjectBody` Zod schema in\n * `backend/src/routes/objects.ts`. `nodeId` + `name` are required on INSERT;\n * `id` flips the route into UPDATE mode.\n */\nexport interface UpsertObjectInput extends CreateObjectInput {\n id?: string\n expectedUpdatedAt?: string\n}\n\nexport type UpsertObjectResult = { id: string } | UpdateObjectResult\n\nexport interface ListObjectsParams {\n /** When true, return archived objects instead of active ones. */\n archived?: boolean\n /** Optional project filter — server-scoped to the caller's user. */\n projectId?: string\n}\n\n/**\n * Input for `client.objects.generate()` — fires the\n * `POST /v1/generate-object` route. Produces 1–10 candidate\n * main images; each lands as one `jobs` row in `pending` state and is then\n * enqueued for the worker.\n *\n * When `attachToObjectId` is set AND `count === 1`, the worker writes the\n * resulting URL directly to `objects.source_image_url` on completion —\n * caller doesn't need a separate `approveMainImage` call. Multi-candidate\n * batches MUST go through explicit approval so the user picks the winner.\n *\n * `seedPromptHint` (Pass 7 F-77) flows the parameter-picker's prompt fragment\n * through to the worker so a catalog selection (e.g. \"antique brass lantern\")\n * gets appended to the generated prompt context.\n */\nexport interface GenerateObjectInput {\n name: string\n description?: string\n userPrompt?: string\n category?: ObjectCategory\n style?: \"realistic\" | \"anime\" | \"3d-pixar\" | \"illustration\"\n sourceImageUrl?: string\n provider?: string\n /** Number of candidate images to generate (1–10; server-validated). */\n count?: number\n /** Auto-attach the result to this object row (single-candidate only). */\n attachToObjectId?: string\n /** Parameter-picker prompt-fragment pass-through. */\n seedPromptHint?: string\n /** Optional name to set on the attached row alongside the main image. */\n attachName?: string\n /** Optimistic-concurrency token for the single-candidate auto-attach path. */\n expectedUpdatedAt?: string\n}\n\n/**\n * `generate()` response — `jobIds` is ALWAYS present (the harmonized contract,\n * matching characters). `jobId` is a deprecated back-compat alias populated only\n * on `count === 1`; prefer `jobIds`. (Will be removed on the next major.)\n */\nexport interface GenerateObjectResult {\n jobIds: string[]\n /** @deprecated count===1 back-compat alias — use `jobIds`. */\n jobId?: string\n}\n\n/**\n * Input for `client.objects.generateAsset()` — fires the\n * `POST /v1/generate-object-asset` route. Produces a single\n * angles / materials / variations / custom variant.\n *\n * When all three studio-path fields are set (`attachToObjectId` +\n * `attachToColumn` + `attachName`), the worker appends\n * `{ name: attachName, url: <result> }` to the named JSONB array column on\n * the user's object row on completion. `attachToColumn` is REQUIRED for\n * `assetType === \"custom\"` — the worker can't infer the bucket from the\n * asset type.\n */\nexport interface GenerateObjectAssetInput {\n assetType: ObjectAssetType\n variant: string\n name: string\n description?: string\n userPrompt?: string\n category?: string\n style?: \"realistic\" | \"anime\" | \"3d-pixar\" | \"illustration\"\n sourceImageUrl?: string\n provider?: string\n attachToObjectId?: string\n attachToColumn?: ObjectAttachColumn\n attachName?: string\n /** Parameter-picker prompt-fragment pass-through (Pass 7 F-77). */\n seedPromptHint?: string\n}\n\nexport interface GenerateObjectAssetResult {\n jobId: string\n}\n\n/**\n * Input for `client.objects.generateMotion()` — fires the\n * `POST /v1/generate-object-motion` route. Produces a single motion clip\n * (rotation, orbit, hover, drift, etc.) animated FROM a static product-shot\n * image.\n *\n * Mirrors `client.locations.generateMotion()` minus the location-specific\n * atmospheric fields. The route hardcodes the attach column to `motion_clips`\n * — callers supply `attachToObjectId` + `attachName` only.\n *\n * `sourceImageUrl` is REQUIRED — image-to-video needs a source frame and the\n * route has no fallback (no `source_image_url` column to pull from on the\n * objects row at this point in the flow; the studio path supplies the\n * canonical product-shot URL explicitly).\n *\n * Object-specific defaults vs location:\n * - `provider` defaults to `\"kling-turbo\"` (not location's `\"kling\"`)\n * - `aspectRatio` defaults to `\"1:1\"` server-side via\n * `resolveObjectAspectRatio({ assetType: \"motion\" })` — objects are\n * product-showcase framing, not cinematic establishing shots.\n *\n * When the studio path is set (`attachToObjectId` + `attachName`), the\n * worker appends `{ name: attachName, url: <result> }` to the object row's\n * `motion_clips` JSONB column on completion.\n */\nexport interface GenerateObjectMotionInput {\n motionPrompt: string\n sourceImageUrl: string\n provider?: string\n name: string\n /** Source clip URL — when set, worker routes to video-to-video refine. */\n refineFromVideoUrl?: string\n category?: string\n style?: \"realistic\" | \"anime\" | \"3d-pixar\" | \"illustration\"\n canonicalDescription?: string\n /** Parameter-picker prompt-fragment pass-through (Pass 7 F-77). */\n seedPromptHint?: string\n attachToObjectId?: string\n attachName?: string\n /**\n * Optional aspect ratio override. Defaults to 1:1 server-side. One of the\n * 5-value `ObjectAspectRatio` union (1:1 / 3:4 / 16:9 / 9:16 / 4:3) —\n * objects have their own enum (with 4:3 added) vs the character set.\n */\n aspectRatio?: ObjectAspectRatio\n /**\n * Optional clip duration in seconds. Validated server-side against the chosen\n * provider's allowed durations (e.g. kling 5/10, wan-i2v 5/10/15); omitted →\n * the model's own default (no behavior change). Mirrors generate-video's\n * per-model i2v duration lever.\n */\n duration?: number\n}\n\nexport interface GenerateObjectMotionResult {\n jobId: string\n}\n\nexport interface ApproveObjectMainImageResult {\n sourceImageUrl: string\n /**\n * LLM-authored caption. `null` when the LLM caption sub-failed — the wire\n * sends `\"\"`, normalized to `null` here so consumers see the same\n * `string | null` semantics as characters. The main image is still set; call\n * `recaption()` to retry.\n */\n canonicalDescription: string | null\n}\n\nexport interface RecaptionObjectResult {\n canonicalDescription: string\n}\n\nexport class ObjectsResource {\n constructor(private client: NodaroClient) {}\n\n /**\n * List the caller's objects. By default returns active objects only;\n * pass `archived: true` to fetch soft-deleted rows for an \"archive\" view.\n * Optional `projectId` scopes the result to a single project.\n */\n list(params: ListObjectsParams = {}): Promise<{ objects: Object[] }> {\n const query: Record<string, string | undefined> = {}\n if (params.archived) query.archived = \"true\"\n if (params.projectId) query.projectId = params.projectId\n return this.client.request(\"GET\", \"/v1/objects\", { query })\n }\n\n /**\n * Convenience wrapper for `list({ archived: true })`. Returns soft-deleted\n * rows so callers can drive a UI \"Archived\" tab without re-encoding the\n * query param.\n *\n * `archived` is omitted from the param type — it's always set to `true` here.\n */\n listArchived(params: Omit<ListObjectsParams, \"archived\"> = {}): Promise<{ objects: Object[] }> {\n return this.list({ ...params, archived: true })\n }\n\n /**\n * Fetch a single object including in-flight asset job state. Soft-deleted\n * (archived) rows are NOT returned by id — the route enforces\n * `deleted_at IS NULL` so archived objects 404 (uniform Pass 10 F-90b\n * \"not_found\" — does not leak the deleted vs non-existent distinction).\n */\n async get(id: string): Promise<ObjectDetail> {\n const res = await this.client.request<ObjectDetail>(\n \"GET\",\n `/v1/objects/${encodeURIComponent(id)}`,\n )\n // Normalize the wire `\"\"` caption (DB null / LLM sub-failure) → null so\n // consumers see the same `string | null` semantics as characters. New\n // object — never mutate the response.\n return { ...res, canonicalDescription: res.canonicalDescription || null }\n }\n\n /**\n * Create a new object. `name` + `nodeId` are required — the route 400s\n * otherwise. Returns the new row's id.\n *\n * Note: the underlying route is the same `POST /v1/objects` upsert that\n * powers `update()`. This convenience wrapper enforces the INSERT-required\n * fields at the type level and never sends an `id`.\n */\n create(data: CreateObjectInput): Promise<{ id: string }> {\n return this.client.request(\"POST\", \"/v1/objects\", { body: data })\n }\n\n /**\n * Update an object. Only the fields you pass are written — undefined keys\n * are NOT touched on the row. Worker-owned asset buckets are intentionally\n * not exposed on this surface (see `UpdateObjectInput` for the rationale).\n *\n * Optimistic-concurrency: pass `expectedUpdatedAt` to require the row's\n * `updated_at` still matches; on mismatch the route returns 409\n * `concurrent_modification` carrying the fresh `updatedAt`. The SDK\n * surfaces that as a generic `NodaroError` with the same code (per Phase\n * E1 calibration finding — error centralization in `throwApiError`).\n */\n update(id: string, data: UpdateObjectInput): Promise<UpdateObjectResult> {\n return this.client.request(\"POST\", \"/v1/objects\", {\n body: { id, ...data },\n })\n }\n\n /**\n * Soft-delete (archive) an object. The row is hidden from `list()` by\n * default but recoverable via `restore(id)` or visible under\n * `listArchived()`. Idempotent — repeating a delete on an already-archived\n * row is a no-op.\n */\n delete(id: string): Promise<{ success: true; archived: true }> {\n return this.client.request(\"DELETE\", `/v1/objects/${encodeURIComponent(id)}`)\n }\n\n /**\n * Hard-delete (permanent) an object — the row + every R2 asset it\n * references. Archived rows ONLY: active objects return 400 `not_archived`.\n * Call `delete()` first to archive, then `permanentDelete()` to destroy.\n *\n * Mirrors the `app_runs` permanent-delete pattern (archive-first) so a\n * stray SDK / curl caller cannot bypass the studio's archive-first UI\n * flow.\n */\n permanentDelete(id: string): Promise<{ success: true; permanent: true }> {\n return this.client.request(\"DELETE\", `/v1/objects/${encodeURIComponent(id)}`, {\n query: { permanent: \"true\" },\n })\n }\n\n /**\n * Un-archive an object. If the original name now collides (case-\n * insensitive) with an active row, the server auto-suffixes \"(restored)\"\n * and returns the effective name.\n */\n restore(id: string): Promise<{ id: string; name: string }> {\n return this.client.request(\"POST\", `/v1/objects/${encodeURIComponent(id)}/restore`)\n }\n\n /**\n * Fire `POST /v1/generate-object` to produce one or more candidate main\n * images. With `count > 1`, all jobs are reserved up-front before any\n * is enqueued — mid-batch failures roll back atomically.\n *\n * When `attachToObjectId` is set AND `count === 1`, the worker writes\n * the result directly to the row's `source_image_url`; otherwise you must\n * call `approveMainImage()` after picking a candidate.\n */\n async generate(data: GenerateObjectInput): Promise<GenerateObjectResult> {\n const res = await this.client.request<{ jobId?: string; jobIds?: string[] }>(\n \"POST\", \"/v1/generate-object\", { body: data },\n )\n // Tolerate the legacy `{ jobId }`-only shape (older server): synthesize jobIds.\n const jobIds = res.jobIds ?? (res.jobId ? [res.jobId] : [])\n return res.jobId ? { jobIds, jobId: res.jobId } : { jobIds }\n }\n\n /**\n * Fire `POST /v1/generate-object-asset` to produce a single variant.\n * When the studio path is set (`attachToObjectId` + `attachToColumn` +\n * `attachName`), the worker appends `{ name: attachName, url: <result> }`\n * to the named JSONB array column on completion.\n *\n * Note: `attachToColumn` is REQUIRED for `assetType === \"custom\"` — the\n * worker can't infer the bucket from the asset type. For canonical asset\n * types (`angles` / `materials` / `variations` / `motion`), the column is\n * derived automatically by the route.\n */\n generateAsset(\n data: GenerateObjectAssetInput,\n ): Promise<GenerateObjectAssetResult> {\n return this.client.request(\"POST\", \"/v1/generate-object-asset\", { body: data })\n }\n\n /**\n * Fire `POST /v1/generate-object-motion` to animate the object's main\n * image into a motion clip. Image-to-video, single clip per call; the\n * attach column is hardcoded to `motion_clips` server-side (objects have a\n * single motion bucket so the caller doesn't supply `attachToColumn`).\n * When the studio path is set (`attachToObjectId` + `attachName`), the\n * worker appends `{ name: attachName, url: <result> }` to the row's\n * `motion_clips` column on completion.\n *\n * Defaults: `provider` → `\"kling-turbo\"`, `aspectRatio` → `\"1:1\"` (set\n * server-side via `resolveObjectAspectRatio({ assetType: \"motion\" })`).\n */\n generateMotion(\n data: GenerateObjectMotionInput,\n ): Promise<GenerateObjectMotionResult> {\n return this.client.request(\"POST\", \"/v1/generate-object-motion\", { body: data })\n }\n\n /**\n * Approve a completed `generate-object` job as the object's main image.\n * Sets `source_image_url` and fires the LLM caption (Claude Sonnet vision)\n * inline. Returns the new main-image URL plus the caption.\n *\n * Caption-failure semantics: the route still sends `\"\"` on LLM sub-failure,\n * but the SDK normalizes `\"\"` → `null` here so `canonicalDescription` carries\n * the same `string | null` semantics as characters. The main image is still\n * set; call `recaption()` to retry.\n *\n * Optimistic-concurrency: pass `expectedUpdatedAt` to gate the update on\n * the row's current `updated_at`; on mismatch the route returns 409\n * `concurrent_modification` carrying the fresh token.\n */\n async approveMainImage(\n id: string,\n candidateJobId: string,\n expectedUpdatedAt?: string,\n ): Promise<ApproveObjectMainImageResult> {\n const res = await this.client.request<{ sourceImageUrl: string; canonicalDescription: string | null }>(\n \"POST\",\n `/v1/objects/${encodeURIComponent(id)}/approve-main-image`,\n { body: { candidateJobId, expectedUpdatedAt } },\n )\n // Normalize the wire `\"\"` (LLM sub-failure) → null; build a new object\n // rather than mutating the response.\n return { sourceImageUrl: res.sourceImageUrl, canonicalDescription: res.canonicalDescription || null }\n }\n\n /**\n * Re-fire the LLM caption against the object's current main image. 502s\n * on LLM failure (unlike `approveMainImage` which preserves the side-effect\n * and returns \"\"); returns 400 `main_image_required` if no main image is\n * set yet.\n *\n * The route is a pure idempotent retry — it does NOT accept an\n * `expectedUpdatedAt` token (per Phase E1 calibration finding: backend\n * route is idempotent retry, not gated on optimistic-concurrency).\n */\n recaption(id: string): Promise<RecaptionObjectResult> {\n return this.client.request(\n \"POST\",\n `/v1/objects/${encodeURIComponent(id)}/llm-caption`,\n )\n }\n}\n","import type { ObjectAspectRatio, TtsProvider, CreatureAttachColumn } from \"@nodaro/shared\"\nexport type { CreatureAttachColumn, ObjectAspectRatio as CreatureAspectRatio } from \"@nodaro/shared\"\nexport { CREATURE_ATTACH_COLUMNS, OBJECT_ASPECT_OPTIONS as CREATURE_ASPECT_OPTIONS, OBJECT_ASPECT_DEFAULTS as CREATURE_ASPECT_DEFAULTS } from \"@nodaro/shared\"\nimport type { NodaroClient } from \"../client.js\"\n\n/**\n * Creature voice — IDENTICAL shape + semantics to `Character[\"voice\"]` (the\n * \"talking creature\" stack reuses the character voice plumbing verbatim).\n * Render speech via `client.nodes.run(\"text-to-speech\", { text, voice:\n * voiceId, provider: ttsProvider, voiceType })`, then feed the audio + the\n * creature's `sourceImageUrl` to `client.nodes.run(\"lip-sync\", …)` (or\n * speech-to-video) for a talking-creature clip.\n */\nexport interface CreatureVoice {\n voiceId: string\n voiceName: string\n traits: string\n voiceType?: \"premade\" | \"library\" | \"custom\"\n /** Playable preview sample (client-played only). */\n previewUrl?: string\n /** Recommended TTS provider — send as `provider` on text-to-speech. */\n ttsProvider?: TtsProvider\n}\n\n/**\n * Re-export the shared `CreatureAttachColumn` union + its runtime tuple so SDK\n * consumers don't have to add `@nodaro/shared` as a second dependency just to\n * typecheck the `attachToColumn` field. Single source of truth lives in\n * `@nodaro/shared/entity-prompts` (`CREATURE_ATTACH_COLUMNS`).\n *\n * `CreatureAspectRatio` is re-exported as an alias of the shared\n * `ObjectAspectRatio` — `generateMotion`'s `aspectRatio` field is the 5-value\n * object enum (1:1 / 3:4 / 16:9 / 9:16 / 4:3). The creature motion route\n * deliberately REUSES `OBJECT_ASPECT_OPTIONS` server-side (a creature reference\n * clip is centered product-showcase framing, not cinematic 16:9), so the SDK\n * surfaces the same enum under a creature-friendly name. The runtime tuple is\n * re-exported as `CREATURE_ASPECT_OPTIONS` / `CREATURE_ASPECT_DEFAULTS` aliases.\n */\n\n/**\n * Creature asset-type enum — the kinds of variant a user can generate off a\n * creature's anchor main image. Mirrors the literal accepted by\n * `POST /v1/generate-creature-asset` (`backend/src/routes/generate-creature-asset.ts`).\n *\n * Delta vs `ObjectAssetType`: object's `materials` becomes `poses` (a creature\n * has poses, not materials), and there is NO `motion` value — creature motion\n * variants flow through the dedicated `/v1/generate-creature-motion` endpoint\n * (worker-side a different BullMQ job type). `custom` is the free-form bucket;\n * callers must supply `attachToColumn` explicitly since the worker can't infer\n * the destination from the asset type.\n *\n * The shared `@nodaro/shared` package does NOT export a `CREATURE_ASSET_TYPES`\n * tuple (the route validates an inline Zod enum), so the SDK defines its own\n * single-source-of-truth tuple here.\n */\nexport const CREATURE_ASSET_TYPES = [\"angles\", \"poses\", \"variations\", \"custom\"] as const\nexport type CreatureAssetType = (typeof CREATURE_ASSET_TYPES)[number]\n\n/**\n * Reference-photo kind discriminator — the mood-board roles a user can attach\n * to a creature. Mirrors the `reference_photos.kind` field accepted by\n * `backend/src/routes/creatures.ts` (the route accepts open strings; this SDK\n * type narrows to the 6 canonical roles surfaced by the Studio). `other` is\n * the free-form bucket.\n */\nexport type CreatureReferencePhotoKind =\n | \"front\"\n | \"side\"\n | \"detail\"\n | \"context\"\n | \"moodBoard\"\n | \"other\"\n\nexport interface CreatureReferencePhoto {\n url: string\n kind: CreatureReferencePhotoKind\n}\n\n/**\n * A creature record returned by Nodaro's REST API. Mirrors the camelCase\n * shape produced by `backend/src/routes/creatures.ts::toCamel()`.\n *\n * Asset buckets (`angles`, `poses`, `variations`, `motionClips`) are\n * independent JSONB arrays keyed by a human-readable variant name (e.g.\n * `\"front\"`, `\"walking\"`, `\"scarred\"`). Each entry's `url` points at an\n * R2-hosted asset.\n *\n * Creature delta vs object:\n * - `species` — free-text creature type (e.g. `\"dragon\"`, `\"wolf\"`). This is\n * the subject of the establishing-shot prompt and the primary creature\n * differentiator (object has no equivalent).\n * - `poses` (where object has `materials`) — the pose-variant asset bucket.\n * - `category` is free-text (NOT object's fixed 10-value enum) — a creature\n * can be any animal/type.\n *\n * Identity-foundation fields:\n * - `referencePhotos` — caller-supplied mood-board refs (cap 20).\n * - `canonicalDescription` — ~80–120-word LLM-authored visual caption,\n * populated by `approveMainImage()` / `recaption()`. The wire still sends\n * `\"\"` on caption sub-failure, but `get()` normalizes `\"\"` → `null` so\n * consumers see the same `string | null` semantics as characters.\n * - `styleLock` — whether asset gens should anchor to the canonical style\n * captured at approval time. Defaults to `true` on new rows.\n */\nexport interface Creature {\n id: string\n userId: string\n nodeId: string\n projectId: string | null\n name: string\n description: string | null\n /** Free-text creature type/species (e.g. \"dragon\", \"wolf\") — the creature\n * delta vs object. `null` when unset. */\n species: string | null\n category: string | null\n style: string | null\n sourceImageUrl: string | null\n /** MODEL_CATALOG image-model id the main image was generated with (or `null`).\n * Set on create + editable via the update route. */\n imageProvider: string | null\n angles: Array<{ name: string; url: string }>\n poses: Array<{ name: string; url: string }>\n variations: Array<{ name: string; url: string }>\n motionClips: Array<{ name: string; url: string }>\n /** Named Creature Boards — dense reference sheets, one per variant/mood\n * (the `generate-image/creature-board` factory preset rendered from the\n * creature's images). A first-class bucket: community publish snapshots it\n * and clone hands the consumer their own copy to extend. Defaults to `[]`.\n * `type` marks an image-collage `\"identity\"` sheet vs a plain `\"looks\"`\n * board; `sourceImages` are the R2 URLs it was collaged from. Both optional\n * + backward-compatible (legacy boards have neither). */\n boards?: Array<{ name: string; url: string; type?: \"looks\" | \"identity\"; sourceImages?: string[] }> | null\n /** The creature's voice (the \"talking creature\" stack) — same shape and\n * flow as `Character[\"voice\"]`. `null` when no voice is selected. */\n voice?: CreatureVoice | null\n referencePhotos: CreatureReferencePhoto[]\n /** `null` when no caption is set (or the LLM caption sub-failed) — the wire\n * sends `\"\"`, normalized to `null` in `get()` to match character semantics. */\n canonicalDescription: string | null\n styleLock: boolean\n /** The user's chosen DEFAULT asset take per variant (Studio version history).\n * OPAQUE map: key `\"<bucket>:<variant>\"` (e.g. `\"angles:front\"`) → the chosen\n * asset URL (one already present in that bucket). Stored verbatim — keys are\n * NOT normalized; soft-capped server-side at 200 keys / 2048-char values\n * (overflow dropped silently). Defaults to `{}`. */\n selectedAssetByVariant?: Record<string, string> | null\n deletedAt: string | null\n createdAt: string\n updatedAt: string\n}\n\n/**\n * GET /v1/creatures/:id may append a `pendingJobs` bucket the studio uses to\n * rehydrate spinners after a reload. Optional on the SDK surface — it doesn't\n * appear on `list()` rows.\n */\nexport interface CreatureDetail extends Creature {\n pendingJobs?: Array<{\n jobId: string\n assetType: string\n name: string\n status: string\n }>\n}\n\n/**\n * Body for `client.creatures.create()`. Mirrors the INSERT branch of\n * `upsertCreatureBody` in `backend/src/routes/creatures.ts`. `name` + `nodeId`\n * are required on create.\n */\nexport interface CreateCreatureInput {\n nodeId: string\n name: string\n description?: string\n /** Free-text creature type/species (the creature delta vs object). */\n species?: string\n /** Free-text category (NOT object's fixed enum — a creature can be anything). */\n category?: string\n style?: string\n workflowId?: string\n projectId?: string\n sourceImageUrl?: string\n /** Persistent image-model id (a MODEL_CATALOG image model). Validated\n * server-side — unknown / non-image / \"\" is stored as `null`. */\n imageProvider?: string | null\n referencePhotos?: CreatureReferencePhoto[]\n canonicalDescription?: string\n styleLock?: boolean\n /** The user's chosen DEFAULT asset take per variant. OPAQUE\n * `\"<bucket>:<variant>\"` → chosen-URL map; a write REPLACES the whole map.\n * Keys stored verbatim; soft-capped server-side at 200 keys / 2048-char values. */\n selectedAssetByVariant?: Record<string, string>\n /** Initial voice selection (see `CreatureVoice`). Omit for a voiceless creature. */\n voice?: CreatureVoice | null\n}\n\n/**\n * Body for `client.creatures.update()`. Mirrors the UPDATE branch of\n * `upsertCreatureBody` in `backend/src/routes/creatures.ts`.\n *\n * Worker-owned asset buckets (`angles` / `poses` / `variations` /\n * `motionClips`) are deliberately omitted — the route drops them on UPDATE\n * so a Studio auto-save with a stale snapshot cannot clobber the worker's\n * atomic `append_creature_asset()` writes.\n *\n * `expectedUpdatedAt` is the optimistic-concurrency token: when present, the\n * UPDATE only succeeds if the row's `updated_at` still matches; on mismatch\n * the route returns 409 so the studio can re-fetch + merge.\n */\nexport interface UpdateCreatureInput {\n name?: string\n description?: string\n /** Free-text creature type/species (the creature delta vs object). */\n species?: string\n category?: string\n style?: string\n sourceImageUrl?: string\n /** Persistent image-model id (a MODEL_CATALOG image model). Validated\n * server-side — unknown / non-image / \"\" is stored as `null`. */\n imageProvider?: string | null\n referencePhotos?: CreatureReferencePhoto[]\n canonicalDescription?: string\n styleLock?: boolean\n /** The user's chosen DEFAULT asset take per variant. OPAQUE\n * `\"<bucket>:<variant>\"` → chosen-URL map; a write REPLACES the whole map\n * (omit to leave untouched). Keys stored verbatim; soft-capped server-side\n * at 200 keys / 2048-char values. */\n selectedAssetByVariant?: Record<string, string>\n /** Named Creature Boards (see `Creature.boards`) — whole-array replace,\n * USER-owned (unlike the worker-owned buckets it flows through UPDATE).\n * Server caps: 24 boards, 200-char names, 30 sourceImages per board. */\n boards?: Array<{ name: string; url: string; type?: \"looks\" | \"identity\"; sourceImages?: string[] }>\n /** Voice selection (see `CreatureVoice`) — whole-object replace; pass\n * `null` to clear the voice, omit to leave untouched. */\n voice?: CreatureVoice | null\n expectedUpdatedAt?: string\n}\n\nexport interface UpdateCreatureResult {\n id: string\n updatedAt: string\n}\n\n/**\n * Combined create + update body (parameter for both branches). Exported for\n * callers that want to drive a single `upsert` flow without picking between\n * `Create*` and `Update*`. Mirrors the `upsertCreatureBody` Zod schema in\n * `backend/src/routes/creatures.ts`. `nodeId` + `name` are required on INSERT;\n * `id` flips the route into UPDATE mode.\n */\nexport interface UpsertCreatureInput extends CreateCreatureInput {\n id?: string\n expectedUpdatedAt?: string\n}\n\nexport type UpsertCreatureResult = { id: string } | UpdateCreatureResult\n\nexport interface ListCreaturesParams {\n /** When true, return archived creatures instead of active ones. */\n archived?: boolean\n /** Optional project filter — server-scoped to the caller's user. */\n projectId?: string\n}\n\n/**\n * Input for `client.creatures.generate()` — fires the\n * `POST /v1/generate-creature` route. Produces 1–10 candidate\n * main images; each lands as one `jobs` row in `pending` state and is then\n * enqueued for the worker.\n *\n * When `attachToCreatureId` is set AND `count === 1`, the worker writes the\n * resulting URL directly to `creatures.source_image_url` on completion —\n * caller doesn't need a separate `approveMainImage` call. Multi-candidate\n * batches MUST go through explicit approval so the user picks the winner.\n *\n * `seedPromptHint` flows the parameter-picker's prompt fragment through to the\n * worker so a catalog selection (e.g. \"armored frost dragon\") gets appended to\n * the generated prompt context.\n */\nexport interface GenerateCreatureInput {\n name: string\n description?: string\n userPrompt?: string\n /** Free-text creature type/species (the creature delta vs object). */\n species?: string\n category?: string\n style?: \"realistic\" | \"anime\" | \"3d-pixar\" | \"illustration\"\n sourceImageUrl?: string\n provider?: string\n /** Number of candidate images to generate (1–10; server-validated). */\n count?: number\n /** Auto-attach the result to this creature row (single-candidate only). */\n attachToCreatureId?: string\n /** Parameter-picker prompt-fragment pass-through. */\n seedPromptHint?: string\n /** Optional name to set on the attached row alongside the main image. */\n attachName?: string\n /** Optimistic-concurrency token for the single-candidate auto-attach path. */\n expectedUpdatedAt?: string\n}\n\n/**\n * `generate()` response — `jobIds` is ALWAYS present (the harmonized contract,\n * matching characters). `jobId` is a deprecated back-compat alias populated only\n * on `count === 1`; prefer `jobIds`. (Will be removed on the next major.)\n */\nexport interface GenerateCreatureResult {\n jobIds: string[]\n /** @deprecated count===1 back-compat alias — use `jobIds`. */\n jobId?: string\n}\n\n/**\n * Input for `client.creatures.generateAsset()` — fires the\n * `POST /v1/generate-creature-asset` route. Produces a single\n * angles / poses / variations / custom variant.\n *\n * When all three studio-path fields are set (`attachToCreatureId` +\n * `attachToColumn` + `attachName`), the worker appends\n * `{ name: attachName, url: <result> }` to the named JSONB array column on\n * the user's creature row on completion. `attachToColumn` is REQUIRED for\n * `assetType === \"custom\"` — the worker can't infer the bucket from the\n * asset type.\n */\nexport interface GenerateCreatureAssetInput {\n assetType: CreatureAssetType\n variant: string\n name: string\n description?: string\n userPrompt?: string\n category?: string\n style?: string\n sourceImageUrl?: string\n provider?: string\n attachToCreatureId?: string\n attachToColumn?: CreatureAttachColumn\n attachName?: string\n /** Parameter-picker prompt-fragment pass-through. */\n seedPromptHint?: string\n}\n\nexport interface GenerateCreatureAssetResult {\n jobId: string\n}\n\n/**\n * Input for `client.creatures.generateMotion()` — fires the\n * `POST /v1/generate-creature-motion` route. Produces a single motion clip\n * (idle, prowl, attack, etc.) animated FROM a static creature-shot image.\n *\n * Mirrors `client.objects.generateMotion()` — the creature motion route reuses\n * the entity-agnostic object motion helpers server-side. The route hardcodes\n * the attach column to `motion_clips` — callers supply `attachToCreatureId` +\n * `attachName` only.\n *\n * `sourceImageUrl` is REQUIRED — image-to-video needs a source frame and the\n * route has no fallback (the studio path supplies the canonical creature-shot\n * URL explicitly).\n *\n * Defaults vs location:\n * - `provider` defaults to `\"kling-turbo\"` (not location's `\"kling\"`)\n * - `aspectRatio` defaults to `\"1:1\"` server-side via\n * `resolveObjectAspectRatio({ assetType: \"motion\" })` — creatures use\n * centered reference framing, not cinematic establishing shots.\n *\n * When the studio path is set (`attachToCreatureId` + `attachName`), the\n * worker appends `{ name: attachName, url: <result> }` to the creature row's\n * `motion_clips` JSONB column on completion.\n */\nexport interface GenerateCreatureMotionInput {\n motionPrompt: string\n sourceImageUrl: string\n provider?: string\n name: string\n /** Source clip URL — when set, worker routes to video-to-video refine. */\n refineFromVideoUrl?: string\n category?: string\n style?: \"realistic\" | \"anime\" | \"3d-pixar\" | \"illustration\"\n canonicalDescription?: string\n /** Parameter-picker prompt-fragment pass-through. */\n seedPromptHint?: string\n attachToCreatureId?: string\n attachName?: string\n /**\n * Optional aspect ratio override. Defaults to 1:1 server-side. One of the\n * 5-value `CreatureAspectRatio` union (1:1 / 3:4 / 16:9 / 9:16 / 4:3) — the\n * creature route reuses the object aspect enum.\n */\n aspectRatio?: ObjectAspectRatio\n /**\n * Optional clip duration in seconds. Validated server-side against the chosen\n * provider's allowed durations (e.g. kling 5/10, wan-i2v 5/10/15); omitted →\n * the model's own default (no behavior change). Mirrors generate-video's\n * per-model i2v duration lever.\n */\n duration?: number\n}\n\nexport interface GenerateCreatureMotionResult {\n jobId: string\n}\n\nexport interface ApproveCreatureMainImageResult {\n sourceImageUrl: string\n /**\n * LLM-authored caption. `null` when the LLM caption sub-failed — the wire\n * sends `\"\"`, normalized to `null` here so consumers see the same\n * `string | null` semantics as characters. The main image is still set; call\n * `recaption()` to retry.\n */\n canonicalDescription: string | null\n}\n\nexport interface RecaptionCreatureResult {\n canonicalDescription: string\n}\n\nexport class CreaturesResource {\n constructor(private client: NodaroClient) {}\n\n /**\n * List the caller's creatures. By default returns active creatures only;\n * pass `archived: true` to fetch soft-deleted rows for an \"archive\" view.\n * Optional `projectId` scopes the result to a single project.\n */\n list(params: ListCreaturesParams = {}): Promise<{ creatures: Creature[] }> {\n const query: Record<string, string | undefined> = {}\n if (params.archived) query.archived = \"true\"\n if (params.projectId) query.projectId = params.projectId\n return this.client.request(\"GET\", \"/v1/creatures\", { query })\n }\n\n /**\n * Convenience wrapper for `list({ archived: true })`. Returns soft-deleted\n * rows so callers can drive a UI \"Archived\" tab without re-encoding the\n * query param.\n *\n * `archived` is omitted from the param type — it's always set to `true` here.\n */\n listArchived(params: Omit<ListCreaturesParams, \"archived\"> = {}): Promise<{ creatures: Creature[] }> {\n return this.list({ ...params, archived: true })\n }\n\n /**\n * Fetch a single creature including in-flight asset job state. Soft-deleted\n * (archived) rows are NOT returned by id — the route enforces\n * `deleted_at IS NULL` so archived creatures 404 (uniform \"not_found\" — does\n * not leak the deleted vs non-existent distinction).\n */\n async get(id: string): Promise<CreatureDetail> {\n const res = await this.client.request<CreatureDetail>(\n \"GET\",\n `/v1/creatures/${encodeURIComponent(id)}`,\n )\n // Normalize the wire `\"\"` caption (DB null / LLM sub-failure) → null so\n // consumers see the same `string | null` semantics as characters. New\n // object — never mutate the response.\n return { ...res, canonicalDescription: res.canonicalDescription || null }\n }\n\n /**\n * Create a new creature. `name` + `nodeId` are required — the route 400s\n * otherwise. Returns the new row's id.\n *\n * Note: the underlying route is the same `POST /v1/creatures` upsert that\n * powers `update()`. This convenience wrapper enforces the INSERT-required\n * fields at the type level and never sends an `id`.\n */\n create(data: CreateCreatureInput): Promise<{ id: string }> {\n return this.client.request(\"POST\", \"/v1/creatures\", { body: data })\n }\n\n /**\n * Update a creature. Only the fields you pass are written — undefined keys\n * are NOT touched on the row. Worker-owned asset buckets are intentionally\n * not exposed on this surface (see `UpdateCreatureInput` for the rationale).\n *\n * Optimistic-concurrency: pass `expectedUpdatedAt` to require the row's\n * `updated_at` still matches; on mismatch the route returns 409\n * `concurrent_modification` carrying the fresh `updatedAt`. The SDK\n * surfaces that as a generic `NodaroError` with the same code.\n */\n update(id: string, data: UpdateCreatureInput): Promise<UpdateCreatureResult> {\n return this.client.request(\"POST\", \"/v1/creatures\", {\n body: { id, ...data },\n })\n }\n\n /**\n * Soft-delete (archive) a creature. The row is hidden from `list()` by\n * default but recoverable via `restore(id)` or visible under\n * `listArchived()`. Idempotent — repeating a delete on an already-archived\n * row is a no-op.\n */\n delete(id: string): Promise<{ success: true; archived: true }> {\n return this.client.request(\"DELETE\", `/v1/creatures/${encodeURIComponent(id)}`)\n }\n\n /**\n * Hard-delete (permanent) a creature — the row + every R2 asset it\n * references. Archived rows ONLY: active creatures return 400 `not_archived`.\n * Call `delete()` first to archive, then `permanentDelete()` to destroy.\n *\n * Mirrors the `app_runs` permanent-delete pattern (archive-first) so a\n * stray SDK / curl caller cannot bypass the studio's archive-first UI\n * flow.\n */\n permanentDelete(id: string): Promise<{ success: true; permanent: true }> {\n return this.client.request(\"DELETE\", `/v1/creatures/${encodeURIComponent(id)}`, {\n query: { permanent: \"true\" },\n })\n }\n\n /**\n * Un-archive a creature. If the original name now collides (case-\n * insensitive) with an active row, the server auto-suffixes \"(restored)\"\n * and returns the effective name.\n */\n restore(id: string): Promise<{ id: string; name: string }> {\n return this.client.request(\"POST\", `/v1/creatures/${encodeURIComponent(id)}/restore`)\n }\n\n /**\n * Fire `POST /v1/generate-creature` to produce one or more candidate main\n * images. With `count > 1`, all jobs are reserved up-front before any\n * is enqueued — mid-batch failures roll back atomically.\n *\n * When `attachToCreatureId` is set AND `count === 1`, the worker writes\n * the result directly to the row's `source_image_url`; otherwise you must\n * call `approveMainImage()` after picking a candidate.\n */\n async generate(data: GenerateCreatureInput): Promise<GenerateCreatureResult> {\n const res = await this.client.request<{ jobId?: string; jobIds?: string[] }>(\n \"POST\", \"/v1/generate-creature\", { body: data },\n )\n // Tolerate the legacy `{ jobId }`-only shape (older server): synthesize jobIds.\n const jobIds = res.jobIds ?? (res.jobId ? [res.jobId] : [])\n return res.jobId ? { jobIds, jobId: res.jobId } : { jobIds }\n }\n\n /**\n * Fire `POST /v1/generate-creature-asset` to produce a single variant.\n * When the studio path is set (`attachToCreatureId` + `attachToColumn` +\n * `attachName`), the worker appends `{ name: attachName, url: <result> }`\n * to the named JSONB array column on completion.\n *\n * Note: `attachToColumn` is REQUIRED for `assetType === \"custom\"` — the\n * worker can't infer the bucket from the asset type. For canonical asset\n * types (`angles` / `poses` / `variations`), the column is derived\n * automatically by the route.\n */\n generateAsset(\n data: GenerateCreatureAssetInput,\n ): Promise<GenerateCreatureAssetResult> {\n return this.client.request(\"POST\", \"/v1/generate-creature-asset\", { body: data })\n }\n\n /**\n * Fire `POST /v1/generate-creature-motion` to animate the creature's main\n * image into a motion clip. Image-to-video, single clip per call; the\n * attach column is hardcoded to `motion_clips` server-side (creatures have a\n * single motion bucket so the caller doesn't supply `attachToColumn`).\n * When the studio path is set (`attachToCreatureId` + `attachName`), the\n * worker appends `{ name: attachName, url: <result> }` to the row's\n * `motion_clips` column on completion.\n *\n * Defaults: `provider` → `\"kling-turbo\"`, `aspectRatio` → `\"1:1\"` (set\n * server-side via `resolveObjectAspectRatio({ assetType: \"motion\" })`).\n */\n generateMotion(\n data: GenerateCreatureMotionInput,\n ): Promise<GenerateCreatureMotionResult> {\n return this.client.request(\"POST\", \"/v1/generate-creature-motion\", { body: data })\n }\n\n /**\n * Approve a completed `generate-creature` job as the creature's main image.\n * Sets `source_image_url` and fires the LLM caption (Claude Sonnet vision)\n * inline. Returns the new main-image URL plus the caption.\n *\n * Caption-failure semantics: the route still sends `\"\"` on LLM sub-failure,\n * but the SDK normalizes `\"\"` → `null` here so `canonicalDescription` carries\n * the same `string | null` semantics as characters. The main image is still\n * set; call `recaption()` to retry.\n *\n * Optimistic-concurrency: pass `expectedUpdatedAt` to gate the update on\n * the row's current `updated_at`; on mismatch the route returns 409\n * `concurrent_modification` carrying the fresh token.\n */\n async approveMainImage(\n id: string,\n candidateJobId: string,\n expectedUpdatedAt?: string,\n ): Promise<ApproveCreatureMainImageResult> {\n const res = await this.client.request<{ sourceImageUrl: string; canonicalDescription: string | null }>(\n \"POST\",\n `/v1/creatures/${encodeURIComponent(id)}/approve-main-image`,\n { body: { candidateJobId, expectedUpdatedAt } },\n )\n // Normalize the wire `\"\"` (LLM sub-failure) → null; build a new object\n // rather than mutating the response.\n return { sourceImageUrl: res.sourceImageUrl, canonicalDescription: res.canonicalDescription || null }\n }\n\n /**\n * Re-fire the LLM caption against the creature's current main image. 502s\n * on LLM failure (unlike `approveMainImage` which preserves the side-effect\n * and returns \"\"); returns 400 `main_image_required` if no main image is\n * set yet.\n *\n * The route is a pure idempotent retry — it does NOT accept an\n * `expectedUpdatedAt` token (backend route is idempotent retry, not gated on\n * optimistic-concurrency).\n */\n recaption(id: string): Promise<RecaptionCreatureResult> {\n return this.client.request(\n \"POST\",\n `/v1/creatures/${encodeURIComponent(id)}/llm-caption`,\n )\n }\n}\n","import type { NodaroClient } from \"../client.js\"\nimport type { PipelineInput, PipelineStageName, PipelineStatus, PipelineMode, SubGateName, ChatEnabledStage, ProposedChange } from \"@nodaro/shared\"\n\nexport type {\n PipelineInput,\n PipelineStageName,\n PipelineStatus,\n PipelineMode,\n SubGateName,\n ChatEnabledStage,\n ProposedChange,\n}\n\n/**\n * Owner-scoped pipeline record returned by `get` / `list` (the server strips\n * `user_id`). Mirrors the public field set of `GET /v1/pipelines/:id`.\n */\nexport interface PipelineRecord {\n id: string\n status: PipelineStatus\n current_stage: string | null\n spent_credits: number\n reserved_credits: number\n upfront_credit_estimate: number\n branched_from_pipeline_id: string | null\n branched_from_stage: string | null\n mode: PipelineMode | null\n failure_reason: string | null\n current_progress_message: string | null\n}\n\n/** One stage currently awaiting approval (from `pendingApprovals`). */\nexport interface PendingApproval {\n stage_name: PipelineStageName\n /** Stage output snapshot; shape varies by stage. */\n output: unknown\n}\n\n/**\n * Assembled timeline (`GET /v1/pipelines/:id/timeline`) — ordered scene\n * composites + their durations, plus optional music/narration and live\n * per-shot animate progress. The data the studio turns into a render.\n */\nexport interface PipelineTimeline {\n fps: number\n width: number\n height: number\n scenes: Array<{ compositeUrl: string; durationSeconds: number }>\n musicUrl?: string\n narrationUrl?: string\n animateProgress?: {\n totalShots: number\n shotsDone: number\n percent: number\n }\n}\n\nexport interface BranchPipelineInput {\n /** The stage to re-run from. Upstream stages are cloned as approved. */\n fromStage: PipelineStageName\n}\n\nexport interface BranchPipelineResult {\n /** The id of the newly created pipeline. */\n pipelineId: string\n /** Stage names that were cloned as 'approved' (stages before `fromStage`). */\n clonedStages: string[]\n /** Number of entity rows cloned into the new pipeline. */\n clonedEntities: number\n}\n\n/**\n * A single chat turn returned by `getStageChat`. Mirrors the\n * `pipeline_chat_turns` row shape selected by the GET handler.\n *\n * `@nodaro/shared` does not yet export a Zod schema for the full row; it only\n * exports `ChatTurnResponseSchema` (the LLM response shape) and the\n * `ProposedChange` discriminated union. Define the wire-format row locally so\n * callers get end-to-end typing today without re-shaping the backend payload.\n */\nexport interface ChatTurn {\n id: string\n turn_n: number\n role: \"user\" | \"assistant\"\n content: string\n proposed_change: ProposedChange | null\n llm_call_id: string | null\n applied_to_attempt_id: string | null\n created_at: string\n}\n\n/**\n * Result of `chatStage` — the assistant turn that was just persisted. The\n * route always echoes the assistant message back so callers can render the\n * reply without a follow-up GET (SSE is the secondary delivery channel).\n */\nexport interface ChatStageResult {\n turnId: string\n role: \"assistant\"\n content: string\n proposed_change: ProposedChange | null\n}\n\n/**\n * Result of `applyChatProposal`. Discriminated on `applied`:\n *\n * - `applied: true` — `applyStageEdit` validated + persisted a new attempt and\n * flipped the stage to approved. `newOutput` is the post-patch artifact.\n * - `applied: false` — recoverable failure (schema_invalid or\n * reference_integrity_failed); the backend has already inserted a follow-up\n * assistant turn with a human-readable hint so the user can iterate via\n * chat. Hard failures throw via the client's error pipeline (409).\n */\nexport type ApplyChatProposalResult =\n | { applied: true; attemptId: string; newOutput: unknown }\n | { applied: false; error: { code: string; detail?: unknown } }\n\nexport class PipelinesResource {\n constructor(private client: NodaroClient) {}\n\n /**\n * Start a new pipeline (headless film generation) — the programmatic\n * equivalent of the studio's \"Create film\". In Auto mode the engine\n * self-advances to completion; poll {@link get} for status and\n * {@link getTimeline} for the assembled output. In manual/guided mode, drive\n * it with {@link pendingApprovals} + {@link approveStage} /\n * {@link approveSubGate}.\n *\n * Requires `pipelines:execute` scope. Returns the new pipeline id.\n */\n create(input: PipelineInput): Promise<{ id: string }> {\n return this.client.request(\"POST\", \"/v1/pipelines\", { body: input })\n }\n\n /**\n * Fetch current pipeline state: `status`, `current_stage`, credit counters,\n * `mode`, and `failure_reason` (set when `status='failed'`). Poll this to\n * track a headless Auto run to completion. Requires `pipelines:read`.\n */\n get(id: string): Promise<PipelineRecord> {\n return this.client.request(\n \"GET\",\n `/v1/pipelines/${encodeURIComponent(id)}`,\n )\n }\n\n /** List the caller's pipelines (most recent first). Requires `pipelines:read`. */\n list(): Promise<PipelineRecord[]> {\n return this.client.request(\"GET\", \"/v1/pipelines\")\n }\n\n /**\n * Cancel a running pipeline. Unspent reserved credits refund. Idempotent on\n * an already-terminal pipeline. Requires `pipelines:execute`.\n */\n cancel(id: string): Promise<{ ok: true }> {\n return this.client.request(\n \"POST\",\n `/v1/pipelines/${encodeURIComponent(id)}/cancel`,\n { body: {} },\n )\n }\n\n /**\n * Stages currently `awaiting_approval`. Empty in a clean Auto run (the engine\n * self-approves); populated in manual/guided mode at each gate. Requires\n * `pipelines:read`.\n */\n pendingApprovals(id: string): Promise<PendingApproval[]> {\n return this.client.request(\n \"GET\",\n `/v1/pipelines/${encodeURIComponent(id)}/pending-approvals`,\n )\n }\n\n /**\n * Approve a stage so the engine advances to the next one. An optional `edits`\n * JSON-Patch is applied to the stage output before approval. Requires\n * `pipelines:approve`.\n */\n approveStage(\n id: string,\n stage: PipelineStageName,\n edits?: unknown,\n ): Promise<{ ok: true }> {\n return this.client.request(\n \"POST\",\n `/v1/pipelines/${encodeURIComponent(id)}/stages/${encodeURIComponent(stage)}/approve`,\n { body: edits ? { edits } : {} },\n )\n }\n\n /**\n * Reject a stage with feedback; the engine re-runs it incorporating the note.\n * Requires `pipelines:approve`.\n */\n rejectStage(\n id: string,\n stage: PipelineStageName,\n feedback: string,\n ): Promise<{ ok: true }> {\n return this.client.request(\n \"POST\",\n `/v1/pipelines/${encodeURIComponent(id)}/stages/${encodeURIComponent(stage)}/reject`,\n { body: { feedback } },\n )\n }\n\n /**\n * Approve a Stage-7 sub-gate (`dialogue_recheck` / `silent_cut`) so the\n * orchestrator resumes from the next sub-step. Requires `pipelines:approve`.\n */\n approveSubGate(\n id: string,\n gate: SubGateName,\n ): Promise<{ ok: true; gate: SubGateName; resumed_at: string }> {\n return this.client.request(\n \"POST\",\n `/v1/pipelines/${encodeURIComponent(id)}/sub-gates/${encodeURIComponent(gate)}/approve`,\n { body: {} },\n )\n }\n\n /**\n * Read a single stage's `status`, `output`, and `critic_feedback`. Useful for\n * inspecting the script/plan before approving. Requires `pipelines:read`.\n */\n getStage(\n id: string,\n stage: PipelineStageName,\n ): Promise<{ status: string; output: unknown; critic_feedback: unknown }> {\n return this.client.request(\n \"GET\",\n `/v1/pipelines/${encodeURIComponent(id)}/stages/${encodeURIComponent(stage)}`,\n )\n }\n\n /**\n * Assembled timeline — ordered scene composites + durations + audio URLs +\n * live animate progress. The output a headless caller renders or hands to a\n * downstream editor. Requires `pipelines:read`.\n */\n getTimeline(id: string): Promise<PipelineTimeline> {\n return this.client.request(\n \"GET\",\n `/v1/pipelines/${encodeURIComponent(id)}/timeline`,\n )\n }\n\n /**\n * Branch a completed pipeline into a new pipeline that re-runs from the\n * given stage. The original pipeline's upstream stages and entities are\n * cloned into the new pipeline; downstream stages are created by the\n * orchestrator as it advances.\n *\n * Requires `pipelines:execute` scope.\n * The source pipeline must have `status='completed'`.\n *\n * @returns 201 with `{ pipelineId, clonedStages, clonedEntities }`.\n */\n branch(id: string, input: BranchPipelineInput): Promise<BranchPipelineResult> {\n return this.client.request(\n \"POST\",\n `/v1/pipelines/${encodeURIComponent(id)}/branch`,\n { body: input },\n )\n }\n\n /**\n * Send a chat message to the Showrunner Refinement Director (Guided Mode).\n * Persists user + assistant turns; returns the assistant's reply and an\n * optional `proposed_change` the user can `applyChatProposal` to commit.\n *\n * Requires `pipelines:approve` scope. The pipeline must have\n * `mode='guided'` and the stage must be `awaiting_approval`.\n *\n * Only the Script stage ships a wired specialist in Phase 1D.2b — the other\n * chat-enabled stages (`shot_list`, `post_merge`) return 501 until 1D.2d.\n */\n chatStage(\n pipelineId: string,\n stage: ChatEnabledStage,\n message: string,\n ): Promise<ChatStageResult> {\n return this.client.request(\n \"POST\",\n `/v1/pipelines/${encodeURIComponent(pipelineId)}/stages/${encodeURIComponent(stage)}/chat`,\n { body: { message } },\n )\n }\n\n /**\n * Accept a proposed change from a prior assistant turn. Routes through\n * `applyStageEdit` (validates JSON Patch + per-stage schema +\n * reference-integrity, inserts a new pipeline_stage_attempts row, CAS-flips\n * the stage to approved, emits `chat:proposal_applied` SSE).\n *\n * Requires `pipelines:approve` scope.\n *\n * Returns `{ applied: true, attemptId, newOutput }` on success, or\n * `{ applied: false, error }` on recoverable failures (the backend already\n * inserted a follow-up assistant turn with a hint). Hard failures\n * (`patch_invalid`, `stage_not_awaiting`) throw via the standard error\n * pipeline (HTTP 409).\n */\n applyChatProposal(\n pipelineId: string,\n stage: ChatEnabledStage,\n turnId: string,\n ): Promise<ApplyChatProposalResult> {\n return this.client.request(\n \"POST\",\n `/v1/pipelines/${encodeURIComponent(pipelineId)}/stages/${encodeURIComponent(stage)}/chat/turns/${encodeURIComponent(turnId)}/apply`,\n { body: {} },\n )\n }\n\n /**\n * Fetch the chat history for a stage. Returns an empty array when no turns\n * exist yet (e.g., stage has not been started or the user hasn't sent any\n * messages). Used by the frontend chat panel on initial mount; subsequent\n * updates arrive via SSE (`chat:turn` events).\n *\n * Requires `pipelines:read` scope.\n */\n getStageChat(\n pipelineId: string,\n stage: ChatEnabledStage,\n ): Promise<{ turns: ChatTurn[] }> {\n return this.client.request(\n \"GET\",\n `/v1/pipelines/${encodeURIComponent(pipelineId)}/stages/${encodeURIComponent(stage)}/chat`,\n )\n }\n}\n","import type { NodaroClient } from \"../client.js\"\nimport type { ReduceStrategyId, ReduceMeta } from \"@nodaro/shared\"\n\n// Re-export the canonical types from `@nodaro/shared` (single source of\n// truth — the registry lives at `packages/shared/src/reduce-strategy-registry.ts`).\n// `@nodaro/shared` is already a hard dep of this package, so there's no\n// bundle-size cost to importing from it.\nexport type { ReduceStrategyId, ReduceMeta }\n\nexport interface ReduceInput {\n /** Which fan-in strategy to run. */\n strategyId: ReduceStrategyId\n /**\n * Strategy-specific config. Defaults to `{}` server-side, which uses every\n * strategy's `defaultConfig`. Schemas (from `@nodaro/shared`):\n * - `pick-best-llm`: `{ criteria: string, inputKind?: \"text\" | \"image-url\" }`\n * - `concat`: `{ separator?: string }`\n * - `vote`: `{ caseSensitive?: boolean }`\n * - `merge-json`: `{ strategy?: \"deep\" | \"shallow\" }`\n * - `first-non-empty`, `count`: `{}`\n */\n strategyConfig?: Record<string, unknown>\n /** Up to 1000 input strings (URLs, text fragments, etc.). */\n inputs: string[]\n /**\n * Optional — associates this reduce run with a workflow execution. The\n * server reads this from the body before Zod strips it (same path as\n * other job-creating routes).\n */\n workflowId?: string\n}\n\nexport interface ReduceResult {\n jobId: string\n /**\n * Stringified result — for `count` this is a numeric string, for\n * `merge-json` this is the JSON-encoded merged object, otherwise the\n * chosen / joined text.\n */\n output: string\n meta: ReduceMeta\n}\n\nexport class ReduceResource {\n constructor(private client: NodaroClient) {}\n\n /**\n * Run the Reduce (fan-in) node directly — useful for scripted batch\n * scoring, picking the best of N generations outside a workflow, or\n * one-shot programmatic merges.\n *\n * Throws `NodaroError` on 4xx/5xx responses (e.g. `code: \"no_valid_inputs\"`\n * with status 400 when every input is empty / whitespace; the underlying\n * `EmptyInputError` is mapped to a 400 server-side).\n */\n run(input: ReduceInput): Promise<ReduceResult> {\n return this.client.request(\"POST\", \"/v1/reduce\", {\n body: {\n strategyId: input.strategyId,\n strategyConfig: input.strategyConfig ?? {},\n inputs: input.inputs,\n ...(input.workflowId !== undefined\n ? { workflowId: input.workflowId }\n : {}),\n },\n })\n }\n}\n","import type { NodaroClient } from \"../client.js\"\nimport type { WizardQuestion, WizardSelection, RecommendedModel, WizardNodeContext } from \"@nodaro/prompts\"\nexport type { WizardQuestion, WizardOption, WizardSelection, RecommendedModel, WizardNodeContext } from \"@nodaro/prompts\"\n\n// Re-export the canonical wizard types (SSOT in @nodaro/shared).\n\ninterface CommonInput {\n nodeType: string\n provider?: string\n style?: string\n aspectRatio?: string\n duration?: number\n llmModel?: string\n /** Reasoning effort level (model-dependent). */\n reasoningEffort?: string\n nodeContext?: WizardNodeContext\n userPreference?: string\n /** Associates this call with a workflow execution. Read server-side before Zod. */\n workflowId?: string\n}\n\nexport interface AnalyzeInput extends CommonInput {\n /** The user's rough idea. Omit to build questions from scratch. */\n prompt?: string\n}\nexport interface AnalyzeResult {\n jobId: string\n questions: WizardQuestion[]\n}\n\nexport interface GenerateInput extends CommonInput {\n /** The chosen answers from analyze. */\n selections: WizardSelection[]\n /** The user's original rough idea, woven into the generated prompt. */\n originalPrompt?: string\n}\n\nexport interface EnhanceInput extends CommonInput {\n /** The rough idea to improve one-shot. Omit to build from scratch. */\n prompt?: string\n}\n\nexport interface PromptResult {\n jobId: string\n prompt: string\n recommendedModel?: RecommendedModel\n}\n\n/**\n * AI Prompt Wizard — help write/improve prompts for generation nodes.\n *\n * - `analyze` -> guided questions, `generate` -> prompt from selections (the\n * 2-step human flow), or `enhance` -> one-shot \"improve this prompt\".\n *\n * All three delegate to `POST /v1/prompt-helper/wizard`. Throws `NodaroError`\n * on 4xx/5xx (e.g. `validation_error`, `malformed_response`).\n */\nexport class PromptHelperResource {\n constructor(private client: NodaroClient) {}\n\n analyze(input: AnalyzeInput): Promise<AnalyzeResult> {\n return this.client.request(\"POST\", \"/v1/prompt-helper/wizard\", { body: { action: \"analyze\", ...input } })\n }\n\n generate(input: GenerateInput): Promise<PromptResult> {\n return this.client.request(\"POST\", \"/v1/prompt-helper/wizard\", { body: { action: \"generate\", ...input } })\n }\n\n enhance(input: EnhanceInput): Promise<PromptResult> {\n return this.client.request(\"POST\", \"/v1/prompt-helper/wizard\", { body: { action: \"enhance\", ...input } })\n }\n}\n","import type { Voice, VoiceClone, VoiceLibraryParams, VoiceLibraryResponse, AudioFxPreset } from \"@nodaro/shared\"\nexport type { Voice, SharedVoice, VoiceClone, VoiceLibraryParams, VoiceLibraryResponse, AudioFxPreset } from \"@nodaro/shared\"\nimport type { NodaroClient } from \"../client.js\"\n\n/**\n * Re-export the shared voice types so SDK consumers don't have to add\n * `@nodaro/shared` as a second dependency just to type a `Voice` row, a\n * `VoiceClone`, or a `searchLibrary` call. Single source of truth lives in\n * `@nodaro/shared`.\n */\n/** Audio-FX preset union (reverb spaces / telephone / megaphone / echo / custom) — used by {@link VoiceChangerProInput.voiceFx}. */\n\n/**\n * Read access to ElevenLabs voices: the premade catalog plus the shared\n * community Voice Library (both public GETs, no body), and the signed-in\n * user's own voice clones (list / create-from-url / delete).\n */\nexport class VoicesResource {\n constructor(private client: NodaroClient) {}\n\n /**\n * List the premade ElevenLabs voices (`GET /v1/voices`). Falls back to a\n * curated set server-side when no ElevenLabs API key is configured.\n */\n async list(): Promise<Voice[]> {\n const res = await this.client.request<{ voices: Voice[] }>(\"GET\", \"/v1/voices\")\n return res.voices\n }\n\n /**\n * Search the shared/community Voice Library (`GET /v1/voices/library`). All\n * params are optional and forwarded as a querystring; `undefined` / `null` /\n * empty-string values are omitted so the server defaults apply. `hasMore`\n * drives \"load more\" pagination.\n */\n searchLibrary(params: VoiceLibraryParams = {}): Promise<VoiceLibraryResponse> {\n const query: Record<string, string | number | boolean | undefined> = {}\n for (const [k, v] of Object.entries(params)) {\n if (v !== undefined && v !== null && v !== \"\") query[k] = v as string | number | boolean\n }\n return this.client.request(\"GET\", \"/v1/voices/library\", { query })\n }\n\n /**\n * List the signed-in user's voice clones (`GET /v1/voice-clones`). The route\n * wraps the rows in `{ voiceClones }`; we unwrap to the bare array.\n */\n async listClones(): Promise<VoiceClone[]> {\n const res = await this.client.request<{ voiceClones: VoiceClone[] }>(\"GET\", \"/v1/voice-clones\")\n return res.voiceClones\n }\n\n /**\n * Clone a voice from an already-uploaded audio URL\n * (`POST /v1/voice-clones/from-url`). Costs credits. Returns the create\n * subset of `VoiceClone` (`elevenlabsVoiceId` is the id to use at\n * text-to-speech time).\n */\n createClone(input: { name: string; audioUrl: string }): Promise<VoiceClone> {\n return this.client.request<VoiceClone>(\"POST\", \"/v1/voice-clones/from-url\", { body: input })\n }\n\n /** Delete one of the user's voice clones (`DELETE /v1/voice-clones/:id`). */\n async deleteClone(id: string): Promise<void> {\n await this.client.request<void>(\"DELETE\", `/v1/voice-clones/${encodeURIComponent(id)}`)\n }\n\n /**\n * Replace the voice in a recording — or in a whole talking video — with a\n * different voice (`POST /v1/voice-changer`). Pass `audioUrl` to revoice\n * audio→audio, or `videoUrl` to revoice an entire clip (the server demuxes\n * the audio, runs speech-to-speech, and remuxes onto the original video,\n * returning the video plus the new audio track). Exactly one of `audioUrl` /\n * `videoUrl` is required; when both are sent, video wins. `removeBackgroundNoise`\n * off keeps the music/SFX bed under the new voice; on yields a clean voice-only\n * result. Costs credits and runs async — poll `jobs.get(jobId)` for the result\n * (`output_data.videoUrl` + `output_data.audioUrl` in video mode).\n */\n change(input: {\n voiceId: string\n audioUrl?: string\n videoUrl?: string\n /** Speech-to-speech model id. Defaults to the server-configured default when omitted. */\n model?: string\n stability?: number\n similarityBoost?: number\n /** Style exaggeration (0–1). Default 0; >0 amplifies delivery at the cost of latency/stability. */\n style?: number\n /** ElevenLabs speaker boost — sharpens fidelity to the target speaker (small latency cost). */\n useSpeakerBoost?: boolean\n /** Deterministic speech-to-speech seed (integer 0–4294967295) for reproducible output. Omit for random. */\n seed?: number\n removeBackgroundNoise?: boolean\n }): Promise<{ jobId: string }> {\n return this.client.request<{ jobId: string }>(\"POST\", \"/v1/voice-changer\", { body: input })\n }\n\n /**\n * Recast each detected speaker in a multi-speaker recording to a different\n * voice (`POST /v1/voice-changer-pro`). `orderedVoices` maps speaker positions to\n * voices in detection order — speaker 0 → `orderedVoices[0]`, speaker 1 →\n * `orderedVoices[1]`, etc. Speakers beyond the end of `orderedVoices` keep\n * their original voice. Each entry is EITHER a bare voice id (premade name or\n * ElevenLabs UUID) OR a {@link VoiceChangerProVoice} object carrying per-voice\n * ElevenLabs speech-to-speech settings (stability / similarityBoost / style /\n * useSpeakerBoost / `seed`) plus a loudness `volumeMode` (and a manual\n * `volume`) — OR `null`, meaning keep this speaker's original voice (requires\n * a platform running cloud-plugins with keep-slot support). At least one\n * entry must be non-null. A per-voice `seed` makes that speaker's recast\n * reproducible.\n *\n * Pass `audioUrl` for audio-only recast or `videoUrl` to recast the audio\n * track of a video clip (the server demuxes, recasts, and remuxes).\n *\n * Voice and music are ALWAYS separated first — before recasting, the source\n * is split into an isolated vocal stem and a music/SFX stem.\n * `preserveBackground` (default `true`) only controls whether that\n * music/instrumental stem is mixed back under the new voices; set it `false`\n * for a clean voice-only result. `separationQuality` selects the quality of\n * the voice/music separation: `\"fast\"` (default, quicker — preserves more of\n * the voice) or `\"best\"` (finer voice/music separation).\n * `removeBackgroundNoise` additionally denoises the result.\n * `musicVolumeMode` sets the level of that preserved background (only relevant\n * when `preserveBackground` is on): `\"match\"` (default) keeps the original\n * level, `\"normalize\"` loudnorms it, `\"manual\"` uses `musicVolume`%.\n * `voiceFx` applies a reverb/echo to the COMBINED recast voices BEFORE the\n * background is mixed back in (effect sits on the voices, not the music bed).\n *\n * Cloud-only — costs credits and runs async; poll `jobs.get(jobId)` for the\n * result (`output_data.videoUrl` + `output_data.audioUrl` in video mode).\n */\n recast(input: VoiceChangerProInput): Promise<{ jobId: string }> {\n return this.client.request<{ jobId: string }>(\"POST\", \"/v1/voice-changer-pro\", { body: input })\n }\n\n /**\n * Detect the speakers in a clip WITHOUT recasting yet\n * (`POST /v1/voice-changer-pro/analyze`) — the first step of the interactive\n * flow. Separates voice from music once and diarizes the vocals, returning the\n * speaker list so a user (or agent) can choose a voice per speaker before\n * committing to a paid recast. Poll `jobs.get(jobId)`: the completed job's\n * `output_data` carries the separated stem urls + the detected `speakers`\n * (each with `id`, time `segments`, `firstStartSec`, `wordCount`, `snippet`)\n * and the detected language — reshape it into a {@link VcpAnalysis} and pass it\n * as `recast({ ..., analysis })` to skip re-detection. With `suggestTitle`,\n * `output_data.suggestedTitle` also carries an LLM-proposed title.\n *\n * Cloud-only; costs credits and runs async.\n */\n analyze(input: VcpAnalyzeInput): Promise<{ jobId: string }> {\n return this.client.request<{ jobId: string }>(\"POST\", \"/v1/voice-changer-pro/analyze\", { body: input })\n }\n\n /**\n * Render a final video from a mixed set of stems\n * (`POST /v1/voice-changer-pro/export`) — the last step of the interactive\n * flow. After `recast({ output: \"stems\" })` hands back the dry per-track stems\n * and the user has set levels / mutes / an effect in your editor, pass those\n * `tracks` (plus the source `videoUrl`) here to mix and remux into the finished\n * video. The video is stream-copied (never re-encoded), so the export is\n * bit-identical to your preview. At least one track must be un-muted (all-muted\n * is a 400); `voiceFx` is applied to the voice tracks at render time.\n *\n * Cloud-only; costs credits and runs async — poll `jobs.get(jobId)` for the\n * result (`output_data.videoUrl`).\n */\n exportMix(input: VcpExportInput): Promise<{ jobId: string }> {\n return this.client.request<{ jobId: string }>(\"POST\", \"/v1/voice-changer-pro/export\", { body: input })\n }\n\n /**\n * Design a brand-new synthetic voice from a text description\n * (`POST /v1/voice-design`) — ElevenLabs text-to-voice. `text` (100–1000 chars)\n * is a preview line spoken in the designed voice; `voiceDescription` describes\n * the voice to create. Costs credits and runs async — poll `jobs.get(jobId)`\n * for the preview + the reusable voice id.\n */\n design(input: VoiceDesignInput): Promise<{ jobId: string }> {\n return this.client.request<{ jobId: string }>(\"POST\", \"/v1/voice-design\", { body: input })\n }\n\n /**\n * Generate speech in a voice described in natural language, without cloning\n * (`POST /v1/voice-remix`). `text` (1–5000 chars) is spoken in a voice matching\n * `voiceDescription`. Costs credits and runs async — poll `jobs.get(jobId)`.\n */\n remix(input: VoiceRemixInput): Promise<{ jobId: string }> {\n return this.client.request<{ jobId: string }>(\"POST\", \"/v1/voice-remix\", { body: input })\n }\n\n /**\n * Dub an audio clip into another language while preserving each speaker's voice\n * (`POST /v1/dubbing`). `targetLanguage` is an ISO code (e.g. `\"es\"`, `\"fr\"`);\n * `sourceLanguage` is auto-detected when omitted. Costs credits and runs async\n * — poll `jobs.get(jobId)`.\n */\n dub(input: DubbingInput): Promise<{ jobId: string }> {\n return this.client.request<{ jobId: string }>(\"POST\", \"/v1/dubbing\", { body: input })\n }\n\n /**\n * Clone a voice from an audio FILE you hold in memory\n * (`POST /v1/voice-clones`, multipart) — the counterpart to\n * {@link VoicesResource.createClone}, which clones from an already-uploaded\n * URL. Pass the raw audio `file` (a `Blob`/`File` in the browser, or a\n * `Uint8Array`/`Buffer` in Node) plus a `name`. Costs credits. Returns the new\n * {@link VoiceClone} (`elevenlabsVoiceId` is the id to recast/synthesize with).\n */\n createCloneFromFile(input: {\n name: string\n file: Blob | Uint8Array | ArrayBuffer\n /** File name for the upload part (default `sample`). */\n filename?: string\n /** MIME type when `file` is a raw buffer (default `audio/mpeg`). */\n contentType?: string\n }): Promise<VoiceClone> {\n const form = new FormData()\n // Append the name field BEFORE the file so the route sees it in `fields`\n // (fastify-multipart's `req.file()` exposes fields parsed up to the file).\n form.append(\"name\", input.name)\n const blob =\n input.file instanceof Blob\n ? input.file\n : new Blob([input.file as BlobPart], { type: input.contentType ?? \"audio/mpeg\" })\n form.append(\"file\", blob, input.filename ?? \"sample\")\n return this.client.request<VoiceClone>(\"POST\", \"/v1/voice-clones\", { body: form })\n }\n}\n\n/**\n * One entry in {@link VoiceChangerProInput.orderedVoices}. Either a bare voice id\n * (premade name like `\"Rachel\"` or an ElevenLabs UUID for a custom clone), or\n * an object pinning per-voice speech-to-speech settings and the recast's\n * loudness behaviour for that speaker.\n */\nexport type VoiceChangerProVoice =\n | string\n | {\n /** Target voice — premade name (`\"Rachel\"`, `\"Aria\"`, …) or an ElevenLabs UUID for a custom clone. */\n voiceId: string\n /** ElevenLabs stability (0–1). Higher = steadier, lower = more expressive. */\n stability?: number\n /** ElevenLabs similarity boost (0–1) — how closely the output hugs the target voice's timbre. */\n similarityBoost?: number\n /** Style exaggeration (0–1). Default 0; >0 amplifies delivery at the cost of latency / stability. */\n style?: number\n /** ElevenLabs speaker boost — sharpens fidelity to the target speaker. */\n useSpeakerBoost?: boolean\n /**\n * Deterministic speech-to-speech seed (integer 0–4294967295) for\n * reproducible per-voice output — the same source + settings + seed\n * recast this speaker identically across runs. Omit for a random seed.\n */\n seed?: number\n /**\n * Loudness handling for this recast voice. `\"match\"` (default) matches the\n * original speaker's loudness; `\"normalize\"` applies loudness\n * normalization; `\"manual\"` uses `volume` as a percentage.\n */\n volumeMode?: \"match\" | \"normalize\" | \"manual\"\n /** Manual output volume as a percentage (0–200). Consulted only when `volumeMode === \"manual\"`. */\n volume?: number\n }\n\n/** Input for {@link VoicesResource.recast}. */\nexport interface VoiceChangerProInput {\n /** URL of an audio file to recast (audio → audio). Exactly one of `audioUrl` / `videoUrl` is required. */\n audioUrl?: string\n /** URL of a video file to recast (the audio track is recast and remuxed). Exactly one of `audioUrl` / `videoUrl` is required. */\n videoUrl?: string\n /**\n * Voices in speaker-detection order. Speaker N is mapped to `orderedVoices[N]`;\n * speakers beyond the array keep their original voice. Each entry is a bare\n * voice id OR a {@link VoiceChangerProVoice} object with per-voice settings —\n * OR `null`, meaning keep this speaker's original voice (requires a platform\n * running cloud-plugins with keep-slot support). At least one entry must be\n * non-null.\n */\n orderedVoices: Array<VoiceChangerProVoice | null>\n /** Model to use for speech-to-speech. Defaults to the server-configured default when omitted. */\n model?: string\n /**\n * Mix the separated music / SFX stem back under the recast voices. Default\n * `true`. The voice is ALWAYS split out before recasting regardless of this\n * flag — `false` simply drops the music for a clean voice-only result.\n */\n preserveBackground?: boolean\n /**\n * Quality of the voice/music separation. `\"fast\"` (default, quicker —\n * preserves more of the voice) or `\"best\"` (finer voice/music separation).\n */\n separationQuality?: \"fast\" | \"best\"\n /** Strip background noise for a clean voice-only result. */\n removeBackgroundNoise?: boolean\n /**\n * Level of the preserved background music / SFX stem in the final mix. Only\n * relevant when `preserveBackground` is on (otherwise there is no background to\n * level). `\"match\"` (default) leaves the separated instrumental at its original\n * level; `\"normalize\"` applies loudness normalization; `\"manual\"` sets its\n * level to `musicVolume`%.\n */\n musicVolumeMode?: \"match\" | \"normalize\" | \"manual\"\n /** Background music level as a percentage (0–200). Consulted only when `musicVolumeMode === \"manual\"`. */\n musicVolume?: number\n /**\n * Node-level reverb/echo applied to the COMBINED recast voices **before** the\n * background is mixed back in (so the effect sits on the voices only, not the\n * music/SFX bed). Reverb presets (`room`, `hall`, `church`, …) use\n * `wetDryMix`; the `echo` / `custom` presets use `delayMs` + `decay`. Omit for\n * no effect.\n */\n voiceFx?: {\n /** Effect preset — reverb space, `telephone`, `megaphone`, `echo`, or `custom`. */\n preset: AudioFxPreset\n /** Reverb wet/dry mix as a percentage (0–100). Higher = wetter (more reverb). */\n wetDryMix?: number\n /** Echo delay in milliseconds (20–2000). Used by the `echo` / `custom` presets. */\n delayMs?: number\n /** Echo decay / feedback (0–1). Higher = more repeats. Used by the `echo` / `custom` presets. */\n decay?: number\n }\n /**\n * Output mode. `\"video\"` (default) mixes the recast voices with the preserved\n * background and returns a finished merged video. `\"stems\"` returns the dry,\n * unleveled per-track stems instead (rendering nothing) so you can drive an\n * INTERACTIVE mix — adjust levels/mutes/effect in your own UI, then render the\n * final video with {@link VoicesResource.exportMix}. This is how an app builds\n * a full editor around VCP rather than a one-shot recast.\n */\n output?: \"video\" | \"stems\"\n /**\n * A prior {@link VoicesResource.analyze} result. Pass it to SKIP re-detection:\n * the recast reuses the already-separated stems and speaker segments instead of\n * running separation + diarization again. This is the fast-path for the\n * detect → pick voices → recast interactive flow (analyze once, recast N times\n * as the user tweaks voice assignments). Omit to auto-detect from the source.\n */\n analysis?: VcpAnalysis\n}\n\n/** One detected speaker in a {@link VcpAnalysis} (from `analyze`). */\nexport interface VcpAnalysisSpeaker {\n /** Stable speaker id (first-appearance order). */\n id: string\n /** The speaker's spoken time ranges (seconds). */\n segments: Array<{ start: number; end: number }>\n /** When the speaker first speaks (seconds). */\n firstStartSec?: number\n /** Rough word count across the clip — a proxy for how much this speaker says. */\n wordCount?: number\n /** The first few transcribed words, to help a user tell speakers apart. */\n snippet?: string\n}\n\n/**\n * The result of {@link VoicesResource.analyze}, reshaped to pass back into\n * {@link VoiceChangerProInput.analysis}. Read a completed analyze job's\n * `output_data` into this shape (it carries the separated stem urls + the\n * detected speakers) and thread it into `recast` to skip re-detection.\n */\nexport interface VcpAnalysis {\n /** URL of the isolated vocal stem. */\n vocalsUrl: string\n /** URL of the separated music/SFX stem (absent when the source had none). */\n backgroundUrl?: string\n /** The detected speakers, in first-appearance order. */\n speakers: VcpAnalysisSpeaker[]\n /** Scribe's detected language code, round-tripped so the recast auto-selects the STS model. */\n languageCode?: string\n /** Confidence (0–1) of {@link VcpAnalysis.languageCode}. */\n languageProbability?: number\n}\n\n/** Input for {@link VoicesResource.analyze}. */\nexport interface VcpAnalyzeInput {\n /** URL of an audio file to analyze. Exactly one of `audioUrl` / `videoUrl` is required. */\n audioUrl?: string\n /** URL of a video file to analyze (its audio track is used). Exactly one of `audioUrl` / `videoUrl` is required. */\n videoUrl?: string\n /** Quality of the voice/music separation run before diarization: `\"fast\"` (default) or `\"best\"`. */\n separationQuality?: \"fast\" | \"best\"\n /** Also suggest a conversion title from the transcript (returned on the job's `output_data.suggestedTitle`). */\n suggestTitle?: boolean\n}\n\n/** One track in a {@link VcpExportInput} mix. */\nexport interface VcpExportTrack {\n /** URL of the stem for this lane (a recast voice stem or the background stem). */\n url: string\n /** Fader position as a percentage: 0 = silent, 100 = unity, 200 = +6dB. */\n gain: number\n /** Whether this lane is muted in the mix. */\n muted: boolean\n /**\n * Which bucket the track is in, and so whether `voiceFx` lands on it. Defaults\n * to `\"voice\"`. Set `\"background\"` for the music/SFX lane (the effect never\n * touches it).\n */\n kind?: \"voice\" | \"background\"\n}\n\n/** Input for {@link VoicesResource.exportMix}. */\nexport interface VcpExportInput {\n /** The source video to remux the mixed audio onto (stream-copied — never re-encoded). */\n videoUrl: string\n /** The mix: one entry per lane. At least one must be un-muted (all-muted is a 400). Max 16 tracks. */\n tracks: VcpExportTrack[]\n /**\n * A reverb/echo applied to the VOICE tracks only (not `\"background\"` lanes)\n * at render time — so iterating the effect in your editor is free until you\n * export. Same shape as {@link VoiceChangerProInput.voiceFx}.\n */\n voiceFx?: VoiceChangerProInput[\"voiceFx\"]\n}\n\n/** Input for {@link VoicesResource.design}. */\nexport interface VoiceDesignInput {\n /** A preview line (100–1000 chars) spoken in the designed voice. */\n text: string\n /** Natural-language description of the voice to create. */\n voiceDescription: string\n /** Voice-design model id. Defaults to the server-configured default. */\n model?: string\n /** Output loudness (-1..1). */\n loudness?: number\n /** How strongly the description steers the design (0–100). */\n guidanceScale?: number\n /** Deterministic seed for a reproducible design. */\n seed?: number\n /** Design quality knob (provider-specific). */\n quality?: number\n /** Enhance the generated voice. */\n shouldEnhance?: boolean\n /** Optional extra prompt context (≤8000 chars). */\n userPrompt?: string\n}\n\n/** Input for {@link VoicesResource.remix}. */\nexport interface VoiceRemixInput {\n /** The text (1–5000 chars) to speak in the described voice. */\n text: string\n /** Natural-language description of the voice to speak in. */\n voiceDescription: string\n /** Optional extra prompt context (≤8000 chars). */\n userPrompt?: string\n}\n\n/** Input for {@link VoicesResource.dub}. */\nexport interface DubbingInput {\n /** URL of the audio to dub. */\n audioUrl: string\n /** Target language ISO code (2–10 chars), e.g. `\"es\"`, `\"pt-BR\"`. */\n targetLanguage: string\n /** Source language ISO code; auto-detected when omitted. */\n sourceLanguage?: string\n /** Expected number of speakers (1–20) — improves separation when known. */\n numSpeakers?: number\n /** Keep the original voices instead of cloning them into the target language. */\n disableVoiceCloning?: boolean\n /** Drop the background/music bed from the dubbed output. */\n dropBackgroundAudio?: boolean\n}\n","import type { NodaroClient } from \"../client.js\"\nimport { NodaroError, throwFromResponse } from \"../errors.js\"\n\n/**\n * Media ingestion + trimming — the source-preparation steps a Voice Changer Pro\n * flow (or any pipeline) needs before it has a clip to work on: pull a social\n * video into storage, copy a remote URL into storage, trim a video/audio to a\n * range, and probe a video's metadata. Each generation-style op returns a job id\n * to poll (`jobs.get(jobId)`); `videoMetadata` is a direct read.\n */\nexport class MediaResource {\n constructor(private client: NodaroClient) {}\n\n /**\n * Download a social video (YouTube / TikTok / Instagram / X / Facebook) into\n * your storage (`POST /v1/download-video`). `maxHeight` caps the resolution\n * (default \"best\"); `sectionStartSec` + `sectionEndSec` (both-or-neither) fetch\n * ONLY that time range instead of the whole video. Returns a `downloadId`;\n * progress streams from `GET /v1/download-video/progress/:downloadId`\n * (server-sent events) and the finished file lands in your library.\n */\n downloadVideo(input: {\n url: string\n maxHeight?: number\n sectionStartSec?: number\n sectionEndSec?: number\n }): Promise<{ downloadId: string }> {\n return this.client.request<{ downloadId: string }>(\"POST\", \"/v1/download-video\", { body: input })\n }\n\n /**\n * Stream the live progress of a {@link MediaResource.downloadVideo} import\n * (`GET /v1/download-video/progress/:downloadId`, server-sent events) as an\n * async iterable. Yields a {@link DownloadVideoProgress} roughly every 500ms\n * until the download reaches `completed` (its event carries the stored\n * `videoUrl`) or `failed` (its event carries `error`), then ends. The progress\n * state expires server-side shortly after the download starts existing, so\n * start iterating promptly after `downloadVideo` returns.\n *\n * No request timeout is applied (a large import legitimately takes minutes) —\n * pass an `AbortSignal` to cancel from the caller.\n */\n async *downloadVideoProgress(\n downloadId: string,\n opts: { signal?: AbortSignal } = {},\n ): AsyncGenerator<DownloadVideoProgress, void, undefined> {\n const url = `${this.client.baseUrl}/v1/download-video/progress/${encodeURIComponent(downloadId)}`\n const token = await this.client.auth.getToken()\n const res = await this.client.fetch(url, {\n headers: token ? { Authorization: `Bearer ${token}` } : {},\n signal: opts.signal,\n })\n if (!res.ok) {\n let errBody: Record<string, unknown> = {}\n try {\n errBody = (await res.json()) as Record<string, unknown>\n } catch {\n // Empty/non-JSON body — fall through with empty errBody\n }\n throwFromResponse(res.status, errBody)\n }\n if (!res.body) {\n throw new NodaroError(\"progress stream has no response body\", \"empty_stream\", res.status)\n }\n\n // Minimal SSE parse: the route emits only `data: <json>\\n\\n` frames (no\n // event/id fields), so split on blank lines and JSON-parse the data lines.\n const reader = res.body.getReader()\n const decoder = new TextDecoder()\n let buffer = \"\"\n try {\n for (;;) {\n const { done, value } = await reader.read()\n if (done) break\n buffer += decoder.decode(value, { stream: true })\n let sep: number\n while ((sep = buffer.indexOf(\"\\n\\n\")) >= 0) {\n const frame = buffer.slice(0, sep)\n buffer = buffer.slice(sep + 2)\n for (const line of frame.split(\"\\n\")) {\n if (!line.startsWith(\"data:\")) continue\n try {\n yield JSON.parse(line.slice(5).trim()) as DownloadVideoProgress\n } catch {\n // Skip malformed frames — the next tick re-reports full state.\n }\n }\n }\n }\n } finally {\n // Ends the HTTP request when the consumer breaks out of the loop early.\n reader.releaseLock()\n await res.body.cancel().catch(() => {})\n }\n }\n\n /**\n * Copy an external media URL into your Nodaro storage (`POST /v1/save-to-storage`)\n * — a server-side fetch, so nothing round-trips through the client. Poll\n * `jobs.get(jobId)`.\n */\n saveToStorage(input: { mediaUrl: string; filename?: string; mediaType?: \"image\" | \"video\" | \"audio\" }): Promise<{ jobId: string }> {\n return this.client.request<{ jobId: string }>(\"POST\", \"/v1/save-to-storage\", { body: input })\n }\n\n /**\n * Trim a video to a range (`POST /v1/trim-video`). Give the range in whichever\n * unit fits: `startTime`/`endTime` seconds, `trim*Frames`, `trim*Seconds`, or\n * `keepFirst`/`keepLastSeconds`. Poll `jobs.get(jobId)`.\n */\n trimVideo(input: {\n videoUrl: string\n startTime?: number\n endTime?: number\n trimStartFrames?: number\n trimEndFrames?: number\n trimStartSeconds?: number\n trimEndSeconds?: number\n keepFirstSeconds?: number\n keepLastSeconds?: number\n }): Promise<{ jobId: string }> {\n return this.client.request<{ jobId: string }>(\"POST\", \"/v1/trim-video\", { body: input })\n }\n\n /**\n * Trim (and extract) audio from a video or audio source\n * (`POST /v1/trim-audio`) to `[startTime, endTime]` seconds, in `audioFormat`\n * (`mp3` default / `wav` / `aac`). Poll `jobs.get(jobId)`.\n */\n trimAudio(input: {\n videoUrl?: string\n audioUrl?: string\n audioFormat?: \"mp3\" | \"wav\" | \"aac\"\n startTime?: number\n endTime?: number\n }): Promise<{ jobId: string }> {\n return this.client.request<{ jobId: string }>(\"POST\", \"/v1/trim-audio\", { body: input })\n }\n\n /**\n * Probe a social video's metadata (`POST /v1/video-metadata`) — duration,\n * dimensions, title, live status — WITHOUT downloading it. A direct read, not a\n * job. Use it to decide whether to trim before importing.\n */\n videoMetadata(input: { url: string }): Promise<VideoMetadata> {\n return this.client.request<VideoMetadata>(\"POST\", \"/v1/video-metadata\", { body: input })\n }\n}\n\n/**\n * One event from {@link MediaResource.downloadVideoProgress}. The stream ends\n * after a `completed` event (which carries the stored `videoUrl` + an optional\n * `thumbnailUrl`) or a `failed` event (which carries `error`).\n */\nexport interface DownloadVideoProgress {\n phase: \"downloading\" | \"processing\" | \"uploading\" | \"completed\" | \"failed\"\n /** Download percent (0–100). Section fetches report jumpy percents — display, don't sum. */\n percent: number\n /** The imported video's storage URL — set on the `completed` event. */\n videoUrl?: string\n /** Thumbnail storage URL — set on the `completed` event when one was captured. */\n thumbnailUrl?: string\n /** What went wrong — set on the `failed` event. */\n error?: string\n}\n\n/** Result of {@link MediaResource.videoMetadata}. Fields are best-effort — a probe may omit some. */\nexport interface VideoMetadata {\n durationSec?: number | null\n width?: number | null\n height?: number | null\n title?: string | null\n isLive?: boolean\n [key: string]: unknown\n}\n","import type { AudioFxPreset } from \"@nodaro/shared\"\nimport type { NodaroClient } from \"../client.js\"\n\n/**\n * Audio primitives — the building blocks Voice Changer Pro composes internally\n * (separation, isolation, effect, mix, level), exposed standalone so a consumer\n * can run any single step or assemble its own pipeline. Each returns a job id to\n * poll (`jobs.get(jobId)`).\n */\nexport class AudioResource {\n constructor(private client: NodaroClient) {}\n\n /**\n * Separate an audio track into stems (`POST /v1/audio-separation`, Demucs).\n * `mode` `\"vocal_instrumental\"` (default) splits voice from music/SFX;\n * `\"stems\"` returns the full drums/bass/other/… breakdown. `quality`\n * `auto` (default) / `fast` / `best`.\n */\n separate(input: { audioUrl: string; mode?: \"vocal_instrumental\" | \"stems\"; quality?: \"auto\" | \"fast\" | \"best\" }): Promise<{ jobId: string }> {\n return this.client.request<{ jobId: string }>(\"POST\", \"/v1/audio-separation\", { body: input })\n }\n\n /** Isolate the primary voice and strip background noise (`POST /v1/audio-isolation`, ElevenLabs). */\n isolate(input: { audioUrl: string }): Promise<{ jobId: string }> {\n return this.client.request<{ jobId: string }>(\"POST\", \"/v1/audio-isolation\", { body: input })\n }\n\n /**\n * Apply a reverb / echo / telephone / megaphone effect to an audio track\n * (`POST /v1/audio-fx`) — the same presets VCP's `voiceFx` uses, standalone.\n * `mix` (0–100) is the reverb wet/dry; `delayMs` + `decay` drive `echo`/`custom`;\n * `eqLow`/`eqHigh` (dB) shape telephone/megaphone.\n */\n applyFx(input: {\n audioUrl: string\n preset?: AudioFxPreset\n mix?: number\n delayMs?: number\n decay?: number\n eqLow?: number\n eqHigh?: number\n }): Promise<{ jobId: string }> {\n return this.client.request<{ jobId: string }>(\"POST\", \"/v1/audio-fx\", { body: input })\n }\n\n /**\n * Layer multiple audio tracks into one (`POST /v1/mix-audio`). `audioUrls`\n * (2–20) are summed; optional `trackVolumes` (0–200% each, positionally) set\n * per-track level.\n */\n mix(input: { audioUrls: string[]; trackVolumes?: number[] }): Promise<{ jobId: string }> {\n return this.client.request<{ jobId: string }>(\"POST\", \"/v1/mix-audio\", { body: input })\n }\n\n /**\n * Adjust an audio (or a video's audio) level (`POST /v1/adjust-volume`):\n * `volume` % (default 100), `normalize` to loudnorm, and `fadeIn`/`fadeOut`\n * seconds. Provide `audioUrl` or `videoUrl`.\n */\n adjustVolume(input: {\n audioUrl?: string\n videoUrl?: string\n volume?: number\n normalize?: boolean\n fadeIn?: number\n fadeOut?: number\n }): Promise<{ jobId: string }> {\n return this.client.request<{ jobId: string }>(\"POST\", \"/v1/adjust-volume\", { body: input })\n }\n\n /**\n * Concatenate audio segments end-to-end (`POST /v1/combine-audio`). Each\n * segment is a `url` with an optional `[startTime, endTime]` sub-range.\n */\n combine(input: { segments: Array<{ url: string; startTime?: number; endTime?: number }> }): Promise<{ jobId: string }> {\n return this.client.request<{ jobId: string }>(\"POST\", \"/v1/combine-audio\", { body: input })\n }\n}\n","import type { NodaroClient } from \"../client.js\"\n\n/**\n * Authenticated user's credit balance — the shape of `GET /v1/user/credits`'s\n * `data` field. Mirrors the canonical `UserBalance` from the backend billing\n * module (`backend/src/ee/billing/credits.ts`), the source of truth: keep this\n * in sync if that interface changes.\n */\nexport interface UserBalance {\n total: number\n subscription: number\n topup: number\n dailySpent: number\n dailyLimit: number | null\n monthlyAllocation: number\n tier: string\n features: Record<string, unknown>\n periodEnd: string | null\n /** Credits earned for app usage (free tier only — earned by running flows). */\n appCreditsAllowance: number\n}\n\n/**\n * Result of `POST /v1/credits/model-costs` — a batch cost lookup for editor\n * cost previews. `data` maps each priced identifier → its credit cost.\n *\n * Per-model fault isolation (the route runs the lookups under\n * `Promise.allSettled`): identifiers with no pricing row are reported in\n * `missing` (undisplayable until an operator seeds a price) and lookup\n * failures in `errors`, instead of failing the whole batch. Callers typically\n * render `'—'` for any identifier that lands in `missing`. The hard-fail\n * policy still triggers at reservation time when the user actually runs the\n * node — this preview lookup is intentionally lenient.\n */\nexport interface ModelCostsResult {\n data: Record<string, number>\n missing: string[]\n errors: string[]\n}\n\n/** Max identifiers per `model-costs` batch — the route caps the request at 50. */\nconst MODEL_COSTS_LIMIT = 50\n\nexport class CreditsResource {\n constructor(private client: NodaroClient) {}\n\n /**\n * `GET /v1/user/credits` → the authenticated user's credit balance and tier\n * info. Throws `UnauthorizedError` (401) when signed out, and the SDK's\n * other typed errors on the usual statuses.\n */\n async balance(): Promise<UserBalance> {\n const res = await this.client.request<{ data: UserBalance }>(\n \"GET\",\n \"/v1/user/credits\",\n )\n return res.data\n }\n\n /**\n * `POST /v1/credits/model-costs` → per-identifier credit cost, for editor\n * cost previews. Capped at the first {@link MODEL_COSTS_LIMIT} identifiers\n * (the route's request limit). Preserves the `{ data, missing, errors }`\n * fault-isolation shape verbatim (see {@link ModelCostsResult}).\n */\n modelCosts(ids: string[]): Promise<ModelCostsResult> {\n return this.client.request<ModelCostsResult>(\"POST\", \"/v1/credits/model-costs\", {\n body: { models: ids.slice(0, MODEL_COSTS_LIMIT) },\n })\n }\n\n // NOTE: no `estimate(...)` helper. The backend exposes\n // `POST /v1/credits/estimate-workflow` (body `{ nodes }` → `{ data: {\n // totalCredits, nodeCount } }`), but no consumer has a settled shape for it\n // yet (studio pre-checks `balance >= Σ modelCosts` client-side rather than\n // calling an estimate endpoint). Adding it now would be inventing an API\n // surface ahead of a real caller, so it's deliberately omitted — add it when\n // a consumer needs it, shaped to that need.\n}\n","import type { NodaroClient } from \"../client.js\"\n\n/**\n * Result of a successful `POST /v1/upload`. Mirrors the route's `data` envelope\n * (`backend/src/routes/upload.ts`), the source of truth — keep in sync if that\n * response changes. The route also returns loosely-typed extracted `metadata`;\n * it is omitted here until a consumer needs a typed shape (add it shaped to\n * that need rather than inventing `Record<string, unknown>` surface now).\n */\nexport interface UploadResult {\n /** Public R2 URL of the stored asset (always present on success). */\n readonly url: string\n /** Storage row id; `null` when no asset row was written (e.g. unauthenticated). */\n readonly assetId: string | null\n /** Generated thumbnail URL (images/video); `null` for audio or on failure. */\n readonly thumbnailUrl: string | null\n /** Server-classified asset category (e.g. \"image\" | \"video\" | \"audio\"). */\n readonly category: string\n /** Display filename (server override or the original). */\n readonly filename: string\n /** Final MIME type after server normalization. */\n readonly mimeType: string\n /** Stored byte size. */\n readonly sizeBytes: number\n /** R2 object key. */\n readonly r2Key: string\n}\n\nexport class UploadsResource {\n constructor(private client: NodaroClient) {}\n\n /**\n * Upload one file (`POST /v1/upload`, multipart — the file rides the `file`\n * field). The SDK's `request` detects the `FormData` body and lets the\n * runtime set the multipart boundary. Returns the persisted asset's public\n * URL + storage metadata (unwraps the `{ data }` envelope). Throws\n * `StorageExceededError` (413) over the storage cap and the SDK's other typed\n * errors on the usual statuses.\n */\n async upload(file: File): Promise<UploadResult> {\n const form = new FormData()\n form.append(\"file\", file)\n const res = await this.client.request<{ data: UploadResult }>(\n \"POST\",\n \"/v1/upload\",\n { body: form },\n )\n return res.data\n }\n}\n","import type { NodaroClient } from \"../client.js\"\n\n/**\n * A media asset in the caller's library (`GET /v1/library`). Mirrors the route's\n * camelCase row shape (`backend/src/routes/library.ts`), the source of truth —\n * keep in sync if that response changes. Covers BOTH uploaded files and saved\n * generations; `type` discriminates the media kind.\n */\nexport interface LibraryAsset {\n readonly id: string\n /** Media kind — filter the list with the same values via `type`. */\n readonly type: \"image\" | \"video\" | \"audio\"\n /** Display filename (server override or the original); `null` if unknown. */\n readonly filename: string | null\n readonly mimeType: string | null\n readonly sizeBytes: number | null\n /** Public R2 URL of the asset. */\n readonly url: string\n /** Generated thumbnail URL (images/video); `null` for audio or when absent. */\n readonly thumbnailUrl: string | null\n readonly metadata: Record<string, unknown>\n /** True when promoted to the shared (admin) library. */\n readonly isLibraryItem: boolean\n /** How the asset entered storage (e.g. \"manual_upload\" | \"generated\"). */\n readonly uploadSource: string\n readonly createdAt: string\n}\n\nexport interface ListLibraryParams {\n /** Filter by media kind; `\"all\"` (default) returns every kind. */\n readonly type?: \"all\" | \"image\" | \"video\" | \"audio\"\n /** Case-insensitive filename substring filter. */\n readonly search?: string\n /** Page size, 1–100 (default 40). */\n readonly limit?: number\n /** Opaque cursor from a prior page's `nextCursor` (fetches the next page). */\n readonly cursor?: string\n /**\n * `true` → EVERY asset the caller owns (the \"Storage\" view: uploads +\n * generations, regardless of the in-library flag). `false` (default) → only\n * assets explicitly saved to the library plus shared items (the in-editor\n * Media Library picker).\n */\n readonly owned?: boolean\n}\n\nexport interface ListLibraryResult {\n readonly data: LibraryAsset[]\n /** Pass back as `cursor` for the next page; `null` when there are no more. */\n readonly nextCursor: string | null\n /** Exact total (first page only — omitted on cursor-paged requests). */\n readonly totalCount?: number\n}\n\n/**\n * Library — the caller's media assets (uploaded files + saved generations).\n *\n * Read surface over `GET /v1/library`: a cursor-paginated, type-filterable,\n * filename-searchable list. This is the \"bring from your media\" source for\n * pickers that also offer upload + in-production stills. Writes (saving a\n * generation, promoting/removing) stay on their dedicated routes — not exposed\n * here until a consumer needs them.\n */\nexport class LibraryResource {\n constructor(private client: NodaroClient) {}\n\n /**\n * `GET /v1/library` → a page of the caller's media assets (newest first) plus\n * a `nextCursor`. Pass the returned `nextCursor` back as `cursor` for the next\n * page. Filter by `type` and a filename `search`; `owned: true` returns the\n * full Storage set (uploads + generations), the default only library-saved +\n * shared items.\n */\n list(params: ListLibraryParams = {}): Promise<ListLibraryResult> {\n const qs = new URLSearchParams()\n if (params.type) qs.set(\"type\", params.type)\n if (params.search) qs.set(\"search\", params.search)\n if (params.limit !== undefined) qs.set(\"limit\", String(params.limit))\n if (params.cursor) qs.set(\"cursor\", params.cursor)\n if (params.owned !== undefined) qs.set(\"owned\", String(params.owned))\n const query = qs.toString()\n return this.client.request(\"GET\", `/v1/library${query ? `?${query}` : \"\"}`)\n }\n}\n","import type { NodaroClient } from \"../client.js\"\nimport type { FactoryPreset } from \"@nodaro/prompts\"\n\n/**\n * A user's saved custom preset (`GET /v1/node-presets`). Mirrors the backend's\n * camelCase row shape. `data` is captured node config — merge it into a node's\n * data when building/running a workflow to \"apply\" the preset.\n */\nexport interface NodePreset {\n id: string\n nodeType: string\n name: string\n description?: string\n data: Record<string, unknown>\n groupId?: string\n tags: string[]\n sortOrder: number\n createdAt: string\n updatedAt: string\n}\n\n/** A user's preset folder/section (`GET /v1/node-preset-groups`). */\nexport interface NodePresetGroup {\n id: string\n nodeType: string\n name: string\n kind: \"folder\" | \"section\"\n sortOrder: number\n createdAt: string\n updatedAt: string\n}\n\n/**\n * Result of `GET /v1/node-presets/factory` — the built-in (factory) catalog for\n * a node type.\n */\nexport interface FactoryPresetsResult {\n data: FactoryPreset[]\n}\n\n/**\n * Node presets — reusable, named node configurations.\n *\n * Read-only over the API today: list your own custom presets and their folders,\n * and list the built-in factory catalog. To *use* a preset, take its `data` and\n * merge it into a node's config when you create/update a workflow. (Creating and\n * editing presets remains in the editor for now.)\n */\nexport class PresetsResource {\n constructor(private client: NodaroClient) {}\n\n /**\n * `GET /v1/node-presets` → your custom presets, newest first. Pass `nodeType`\n * (e.g. `\"generate-image\"`) to filter to one node type.\n */\n async list(nodeType?: string): Promise<NodePreset[]> {\n const qs = nodeType ? `?nodeType=${encodeURIComponent(nodeType)}` : \"\"\n const res = await this.client.request<{ data: NodePreset[] }>(\"GET\", `/v1/node-presets${qs}`)\n return res.data\n }\n\n /**\n * `GET /v1/node-preset-groups` → your preset folders/sections, in display\n * order. Pass `nodeType` to filter to one node type.\n */\n async listGroups(nodeType?: string): Promise<NodePresetGroup[]> {\n const qs = nodeType ? `?nodeType=${encodeURIComponent(nodeType)}` : \"\"\n const res = await this.client.request<{ data: NodePresetGroup[] }>(\"GET\", `/v1/node-preset-groups${qs}`)\n return res.data\n }\n\n /**\n * `GET /v1/node-presets/factory` → the built-in catalog for `nodeType`. These\n * ship with the app (no account needed to exist), so they're a good starting\n * point for \"what configs are available\".\n */\n listFactory(nodeType: string): Promise<FactoryPresetsResult> {\n return this.client.request<FactoryPresetsResult>(\n \"GET\",\n `/v1/node-presets/factory?nodeType=${encodeURIComponent(nodeType)}`,\n )\n }\n}\n","import type { NodaroClient } from \"../client.js\"\n\n/**\n * Picker-catalog types. Mirrors `@nodaro/shared`'s `ProjectedPickerCatalog` /\n * `PickerCatalogSummary` so the SDK stays dependency-free (same convention as\n * `NodeDescriptor` mirroring node-registry).\n */\nexport interface PickerOption {\n id: string\n label: string\n description?: string\n category?: string\n /** The prompt fragment this id injects downstream. Present only when detail=\"full\". */\n promptHint?: string\n icon?: string\n}\n\nexport interface PickerDimension {\n field: string\n label: string\n options: PickerOption[]\n}\n\nexport interface PickerCatalog {\n nodeType: string\n label: string\n catalogId: string\n kind: \"single\" | \"multi\"\n /** single only — the node-data field the chosen id writes to. */\n valueField?: string\n defaultValue?: string\n categoryOrder?: string[]\n categoryLabels?: Record<string, string>\n /** single-dim catalogs. */\n options?: PickerOption[]\n /** multi-dim catalogs. */\n fields?: string[]\n dimensions?: PickerDimension[]\n detail?: \"compact\" | \"full\"\n}\n\nexport interface PickerCatalogSummary {\n nodeType: string\n label: string\n catalogId: string\n kind: \"single\" | \"multi\"\n valueField?: string\n fields?: string[]\n optionCount: number\n}\n\nexport interface GetPickerCatalogOptions {\n /** \"compact\" (default) = id, label, category, icon; \"full\" additionally includes description + promptHint. */\n detail?: \"compact\" | \"full\"\n /** single-dim: filter to one category. */\n category?: string\n /** multi-dim: only this dimension field. */\n field?: string\n}\n\nexport class PickerCatalogsResource {\n constructor(private client: NodaroClient) {}\n\n /** List every parameter-picker node type + its option count. Cached publicly 5 min. */\n list(): Promise<{ data: PickerCatalogSummary[] }> {\n return this.client.request(\"GET\", \"/v1/picker-catalogs\")\n }\n\n /** Get one picker's catalog of valid values. */\n get(nodeType: string, opts: GetPickerCatalogOptions = {}): Promise<{ data: PickerCatalog }> {\n const qs = new URLSearchParams()\n if (opts.detail) qs.set(\"detail\", opts.detail)\n if (opts.category) qs.set(\"category\", opts.category)\n if (opts.field) qs.set(\"field\", opts.field)\n const query = qs.toString()\n return this.client.request(\n \"GET\",\n `/v1/picker-catalogs/${encodeURIComponent(nodeType)}${query ? `?${query}` : \"\"}`,\n )\n }\n}\n","import type { CommunityEntityType, CommunitySort, CommunityReportReason, CommunityCard, CommunityFullDetail, BrowseCommunityParams, BrowseCommunityResult, CloneListingResult, FavoriteListingResult, ReportListingResult, PublishListingParams, PublishListingResult, SharedListing } from \"@nodaro/shared\"\nexport type { CommunityEntityType, CommunitySort, CommunityReportReason, CommunityCard, CommunityFullDetail, BrowseCommunityParams, BrowseCommunityResult, CloneListingResult, FavoriteListingResult, ReportListingResult, PublishListingParams, PublishListingResult, SharedListing } from \"@nodaro/shared\"\nimport type { NodaroClient } from \"../client.js\"\n\n/**\n * The community-listing types are the single source of truth in\n * `@nodaro/shared` (re-used by the backend, frontend, and CLI). Re-export them\n * here so SDK consumers don't have to add `@nodaro/shared` as a second\n * dependency just to typecheck `browse`/`clone`/`favorite`/`report`.\n */\n\n/**\n * Community — browse, favorite, clone, and report shared characters,\n * locations, and objects.\n *\n * Publishing is intentionally NOT exposed here: it is admin-only via the\n * editor, and the publish route rejects personal access tokens (which is what\n * the SDK uses).\n */\nexport class CommunityResource {\n constructor(private client: NodaroClient) {}\n\n /**\n * `GET /v1/community/browse` → a page of public listings plus a `nextCursor`.\n * Pass the returned `nextCursor` back as `cursor` to fetch the next page.\n */\n browse(params: BrowseCommunityParams = {}): Promise<BrowseCommunityResult> {\n const qs = new URLSearchParams()\n if (params.entityType) qs.set(\"entityType\", params.entityType)\n if (params.q) qs.set(\"q\", params.q)\n if (params.category) qs.set(\"category\", params.category)\n if (params.sort) qs.set(\"sort\", params.sort)\n if (params.cursor) qs.set(\"cursor\", params.cursor)\n if (params.limit !== undefined) qs.set(\"limit\", String(params.limit))\n const query = qs.toString()\n return this.client.request(\n \"GET\",\n `/v1/community/browse${query ? `?${query}` : \"\"}`,\n )\n }\n\n /** `GET /v1/community/detail/:slug` → a single listing by its slug. */\n get(slug: string): Promise<{ data: CommunityCard }> {\n return this.client.request(\n \"GET\",\n `/v1/community/detail/${encodeURIComponent(slug)}`,\n )\n }\n\n /**\n * `GET /v1/community/detail/:slug/full` → the full read-only detail (card\n * identity + the stored public snapshot). Like {@link get}, but includes the\n * snapshot asset/voice/text blob needed to render the full cross-user view.\n */\n getFull(slug: string): Promise<{ data: CommunityFullDetail }> {\n return this.client.request(\n \"GET\",\n `/v1/community/detail/${encodeURIComponent(slug)}/full`,\n )\n }\n\n /** `GET /v1/community/favorites` → the listings you've favorited. */\n favorites(): Promise<{ data: CommunityCard[] }> {\n return this.client.request(\"GET\", \"/v1/community/favorites\")\n }\n\n /**\n * `POST /v1/community/listings/:id/clone` → copy a listing into your library.\n * Returns the new asset's `entityType` and `id`. Requires the `assets:write`\n * scope when called with an OAuth app token.\n */\n clone(id: string, entityType: CommunityEntityType): Promise<CloneListingResult> {\n return this.client.request(\n \"POST\",\n `/v1/community/listings/${encodeURIComponent(id)}/clone`,\n { body: { entityType } },\n )\n }\n\n /**\n * `POST /v1/community/listings/:id/favorite` → toggle a favorite. Returns the\n * resulting `favorited` state (`true` after adding, `false` after removing).\n */\n favorite(id: string): Promise<FavoriteListingResult> {\n return this.client.request(\n \"POST\",\n `/v1/community/listings/${encodeURIComponent(id)}/favorite`,\n )\n }\n\n /**\n * `POST /v1/community/listings/:id/report` → flag a listing for moderation.\n * `reason` must be one of {@link CommunityReportReason}.\n */\n report(id: string, reason: CommunityReportReason): Promise<ReportListingResult> {\n return this.client.request(\n \"POST\",\n `/v1/community/listings/${encodeURIComponent(id)}/report`,\n { body: { reason } },\n )\n }\n\n /**\n * `POST /v1/admin/community/:entityType/:id/publish` → share one of YOUR\n * entities to the community, returning the new listing's `slug` + `id`.\n *\n * **Requires an admin token** (the route is `requireAdmin`) AND the caller\n * must own the source entity. Personal/OAuth tokens without admin role get a\n * 401. For `character` listings, `params.likenessAttestation` must be `true`.\n */\n publish(\n entityType: CommunityEntityType,\n entityId: string,\n params: PublishListingParams,\n ): Promise<PublishListingResult> {\n return this.client.request(\n \"POST\",\n `/v1/admin/community/${encodeURIComponent(entityType)}/${encodeURIComponent(entityId)}/publish`,\n { body: params },\n )\n }\n\n /**\n * `DELETE /v1/admin/community/listings/:id` → unshare (deactivate) a listing\n * you published. **Requires an admin token** (the route is `requireAdmin`).\n */\n unpublish(listingId: string): Promise<{ ok: boolean }> {\n return this.client.request(\n \"DELETE\",\n `/v1/admin/community/listings/${encodeURIComponent(listingId)}`,\n )\n }\n\n /**\n * `GET /v1/admin/community/by-source/:entityType/:sourceId` → look up YOUR\n * existing listing (if any) for a source entity. Returns `{ data: null }`\n * when the entity hasn't been shared. **Requires an admin token** (the route\n * is `requireAdmin`); only returns listings created by the caller.\n */\n sharedListing(\n entityType: CommunityEntityType,\n sourceId: string,\n ): Promise<{ data: SharedListing | null }> {\n return this.client.request(\n \"GET\",\n `/v1/admin/community/by-source/${encodeURIComponent(entityType)}/${encodeURIComponent(sourceId)}`,\n )\n }\n}\n","import { throwFromResponse } from \"./errors.js\"\nimport type { Auth } from \"./auth.js\"\nimport { WorkflowsResource } from \"./resources/workflows.js\"\nimport { ProjectsResource } from \"./resources/projects.js\"\nimport { JobsResource } from \"./resources/jobs.js\"\nimport { ExecutionsResource } from \"./resources/executions.js\"\nimport { NodesResource } from \"./resources/nodes.js\"\nimport { DeveloperAppsResource } from \"./resources/developer-apps.js\"\nimport { OAuthResource } from \"./resources/oauth.js\"\nimport { AppsResource } from \"./resources/apps.js\"\nimport { CharactersResource } from \"./resources/characters.js\"\nimport { LocationsResource } from \"./resources/locations.js\"\nimport { ObjectsResource } from \"./resources/objects.js\"\nimport { CreaturesResource } from \"./resources/creatures.js\"\nimport { PipelinesResource } from \"./resources/pipelines.js\"\nimport { ReduceResource } from \"./resources/reduce.js\"\nimport { PromptHelperResource } from \"./resources/prompt-helper.js\"\nimport { VoicesResource } from \"./resources/voices.js\"\nimport { MediaResource } from \"./resources/media.js\"\nimport { AudioResource } from \"./resources/audio.js\"\nimport { CreditsResource } from \"./resources/credits.js\"\nimport { UploadsResource } from \"./resources/uploads.js\"\nimport { LibraryResource } from \"./resources/library.js\"\nimport { PresetsResource } from \"./resources/node-presets.js\"\nimport { PickerCatalogsResource } from \"./resources/picker-catalogs.js\"\nimport { CommunityResource } from \"./resources/community.js\"\n\nexport interface ClientOptions {\n /** Backend base URL, e.g. \"https://nodaro.example.com\" or empty string for same-origin. */\n baseUrl: string\n /** Auth provider. Use StaticTokenAuth, supabaseAuth, or CallbackAuth. */\n auth: Auth\n /** Optional fetch override (for tests or custom transports). */\n fetch?: typeof fetch\n /** Default request timeout in ms. Default 60s. */\n timeoutMs?: number\n}\n\ninterface RequestOptions {\n body?: unknown\n query?: Record<string, string | number | boolean | undefined>\n headers?: Record<string, string>\n signal?: AbortSignal\n}\n\n/**\n * The authenticated user's canonical identity (`GET /v1/me`). A token-\n * introspection primitive: any valid bearer token (first-party Supabase JWT or\n * a developer-app OAuth token) resolves to its owner's identity. Mirrors the\n * `profiles` identity columns server-side — the route is the source of truth.\n */\nexport interface UserIdentity {\n /** Nodaro user id (= the Supabase auth user id). */\n readonly id: string\n readonly email: string\n /** Human-readable display name (from `profiles.full_name`); `null` if unset. */\n readonly displayName: string | null\n /** Avatar URL; `null` if unset. */\n readonly avatarUrl: string | null\n /** Subscription tier (e.g. \"free\", \"pro\"). */\n readonly tier: string\n}\n\nexport class NodaroClient {\n readonly baseUrl: string\n readonly auth: Auth\n readonly timeoutMs: number\n private readonly fetchOverride: typeof fetch | undefined\n\n /**\n * Resolved lazily so consumers can swap `globalThis.fetch` after the\n * client has been constructed (e.g. test mocks). Always rebound to the\n * global object — native fetch throws \"Illegal invocation\" when its\n * `this` is anything else.\n */\n get fetch(): typeof fetch {\n return this.fetchOverride ?? globalThis.fetch.bind(globalThis)\n }\n\n readonly workflows: WorkflowsResource\n readonly projects: ProjectsResource\n readonly jobs: JobsResource\n readonly executions: ExecutionsResource\n readonly nodes: NodesResource\n readonly developerApps: DeveloperAppsResource\n readonly oauth: OAuthResource\n readonly apps: AppsResource\n readonly characters: CharactersResource\n readonly locations: LocationsResource\n readonly objects: ObjectsResource\n readonly creatures: CreaturesResource\n readonly pipelines: PipelinesResource\n readonly reduce: ReduceResource\n readonly promptHelper: PromptHelperResource\n readonly voices: VoicesResource\n readonly media: MediaResource\n readonly audio: AudioResource\n readonly credits: CreditsResource\n readonly uploads: UploadsResource\n readonly library: LibraryResource\n readonly presets: PresetsResource\n readonly pickerCatalogs: PickerCatalogsResource\n readonly community: CommunityResource\n\n constructor(opts: ClientOptions) {\n this.baseUrl = opts.baseUrl.replace(/\\/$/, \"\") // strip trailing slash\n this.auth = opts.auth\n this.fetchOverride = opts.fetch\n this.timeoutMs = opts.timeoutMs ?? 60_000\n\n this.workflows = new WorkflowsResource(this)\n this.projects = new ProjectsResource(this)\n this.jobs = new JobsResource(this)\n this.executions = new ExecutionsResource(this)\n this.nodes = new NodesResource(this)\n this.developerApps = new DeveloperAppsResource(this)\n this.oauth = new OAuthResource(this)\n this.apps = new AppsResource(this)\n this.characters = new CharactersResource(this)\n this.locations = new LocationsResource(this)\n this.objects = new ObjectsResource(this)\n this.creatures = new CreaturesResource(this)\n this.pipelines = new PipelinesResource(this)\n this.reduce = new ReduceResource(this)\n this.promptHelper = new PromptHelperResource(this)\n this.voices = new VoicesResource(this)\n this.media = new MediaResource(this)\n this.audio = new AudioResource(this)\n this.credits = new CreditsResource(this)\n this.uploads = new UploadsResource(this)\n this.library = new LibraryResource(this)\n this.presets = new PresetsResource(this)\n this.pickerCatalogs = new PickerCatalogsResource(this)\n this.community = new CommunityResource(this)\n }\n\n async request<T>(method: string, path: string, options: RequestOptions = {}): Promise<T> {\n const url = this.buildUrl(path, options.query)\n\n const token = await this.auth.getToken()\n // A `FormData` body is a multipart upload: let the runtime set\n // `Content-Type: multipart/form-data; boundary=…` itself (a manual JSON\n // content-type corrupts the boundary), and send the body as-is rather than\n // JSON-stringifying it. Every other body stays JSON, exactly as before.\n const isFormData =\n typeof FormData !== \"undefined\" && options.body instanceof FormData\n const headers: Record<string, string> = {\n ...(isFormData ? {} : { \"Content-Type\": \"application/json\" }),\n ...(options.headers ?? {}),\n }\n if (token) headers[\"Authorization\"] = `Bearer ${token}`\n\n const ac = new AbortController()\n const timeoutId = setTimeout(() => ac.abort(), this.timeoutMs)\n if (options.signal) {\n options.signal.addEventListener(\"abort\", () => ac.abort(), { once: true })\n }\n\n try {\n const res = await this.fetch(url, {\n method,\n headers,\n body:\n options.body === undefined\n ? undefined\n : isFormData\n ? (options.body as FormData)\n : JSON.stringify(options.body),\n signal: ac.signal,\n })\n\n if (!res.ok) {\n let errBody: Record<string, unknown> = {}\n try {\n errBody = await res.json() as Record<string, unknown>\n } catch {\n // Empty/non-JSON body — fall through with empty errBody\n }\n throwFromResponse(res.status, errBody)\n }\n\n // 204 No Content\n if (res.status === 204) return undefined as T\n return await res.json() as T\n } finally {\n clearTimeout(timeoutId)\n }\n }\n\n /**\n * `GET /v1/me` → the authenticated user's identity (see {@link UserIdentity}).\n * Unwraps the `{ data }` envelope. Throws `UnauthorizedError` (401) when the\n * token is missing/invalid, and the SDK's other typed errors as usual.\n */\n async me(): Promise<UserIdentity> {\n const res = await this.request<{ data: UserIdentity }>(\"GET\", \"/v1/me\")\n return res.data\n }\n\n private buildUrl(path: string, query?: Record<string, string | number | boolean | undefined>): string {\n const base = this.baseUrl || (typeof window !== \"undefined\" ? window.location.origin : \"http://placeholder\")\n const url = new URL(path, base)\n // If baseUrl was empty, strip the placeholder origin\n const fullUrl = this.baseUrl ? url.toString() : url.pathname + url.search\n if (query) {\n const u = new URL(this.baseUrl ? fullUrl : fullUrl, base)\n for (const [k, v] of Object.entries(query)) {\n if (v !== undefined) u.searchParams.set(k, String(v))\n }\n return this.baseUrl ? u.toString() : u.pathname + u.search\n }\n return fullUrl\n }\n}\n\n/** Factory function — preferred entry point. */\nexport function createClient(opts: ClientOptions): NodaroClient {\n return new NodaroClient(opts)\n}\n","/**\n * Auth provides the token used for `Authorization: Bearer <token>` on each request.\n * Implementations:\n * - StaticTokenAuth — fixed string (server-side, OAuth access token, API token)\n * - CallbackAuth — caller-supplied async function (BYO logic)\n * - supabaseAuth — pulls JWT from a Supabase client live (browser frontends)\n */\nexport interface Auth {\n /** Returns the current Bearer token, or null if not authenticated. */\n getToken(): Promise<string | null>\n}\n\nexport class StaticTokenAuth implements Auth {\n constructor(private token: string) {}\n async getToken() {\n return this.token\n }\n}\n\nexport class CallbackAuth implements Auth {\n constructor(private fn: () => string | null | Promise<string | null>) {}\n async getToken() {\n return this.fn()\n }\n}\n\ninterface SupabaseLikeClient {\n auth: {\n getSession(): Promise<{ data: { session: { access_token: string } | null } }>\n }\n}\n\n/** Pulls a JWT from a Supabase v2 client. Caller supplies their own supabase. */\nexport function supabaseAuth(supabase: SupabaseLikeClient): Auth {\n return {\n async getToken() {\n const { data } = await supabase.auth.getSession()\n return data.session?.access_token ?? null\n },\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/resources/workflows.ts","../src/resources/projects.ts","../src/resources/jobs.ts","../src/resources/executions.ts","../src/resources/nodes.ts","../src/resources/developer-apps.ts","../src/resources/oauth.ts","../src/resources/apps.ts","../src/resources/characters.ts","../src/resources/locations.ts","../src/resources/objects.ts","../src/resources/creatures.ts","../src/resources/pipelines.ts","../src/resources/reduce.ts","../src/resources/prompt-helper.ts","../src/resources/voices.ts","../src/resources/media.ts","../src/resources/audio.ts","../src/resources/credits.ts","../src/resources/uploads.ts","../src/resources/library.ts","../src/resources/node-presets.ts","../src/resources/picker-catalogs.ts","../src/resources/community.ts","../src/client.ts","../src/auth.ts"],"names":["buildPersonHints"],"mappings":"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","file":"index.cjs","sourcesContent":["export class NodaroError extends Error {\n constructor(\n message: string,\n public readonly code: string,\n public readonly status: number,\n ) {\n super(message)\n this.name = \"NodaroError\"\n }\n}\n\nexport class UnauthorizedError extends NodaroError {\n constructor(message = \"Authentication required\") {\n super(message, \"unauthorized\", 401)\n this.name = \"UnauthorizedError\"\n }\n}\n\nexport class ForbiddenError extends NodaroError {\n constructor(message = \"Forbidden\", public readonly missingScope?: string) {\n super(message, \"forbidden\", 403)\n this.name = \"ForbiddenError\"\n }\n}\n\nexport class NotFoundError extends NodaroError {\n constructor(message = \"Not found\") {\n super(message, \"not_found\", 404)\n this.name = \"NotFoundError\"\n }\n}\n\nexport class RateLimitedError extends NodaroError {\n constructor(message = \"Rate limited\") {\n super(message, \"rate_limited\", 429)\n this.name = \"RateLimitedError\"\n }\n}\n\nexport class InsufficientCreditsError extends NodaroError {\n constructor(\n message = \"Insufficient credits\",\n public readonly required?: number,\n public readonly available?: number,\n ) {\n super(message, \"insufficient_credits\", 402)\n this.name = \"InsufficientCreditsError\"\n }\n}\n\nexport class StorageExceededError extends NodaroError {\n constructor(message = \"Storage exceeded\", public readonly limitBytes?: number) {\n super(message, \"storage_exceeded\", 413)\n this.name = \"StorageExceededError\"\n }\n}\n\n/**\n * A job reached a terminal `failed`/`cancelled` status while being awaited by\n * `nodes.runAndWait` / `nodes.runMany`. Not an HTTP-level error (the polls\n * themselves succeeded), so `status` is 0 — distinguish it by type/`code`.\n * Carries the job's own `error_message` (as the message) and `jobId`.\n */\nexport class JobFailedError extends NodaroError {\n constructor(\n message: string,\n public readonly jobId: string,\n /** The terminal status that triggered the failure (`failed` | `cancelled`). */\n public readonly jobStatus: \"failed\" | \"cancelled\" = \"failed\",\n ) {\n super(message, \"job_failed\", 0)\n this.name = \"JobFailedError\"\n }\n}\n\n/**\n * `nodes.runAndWait` polled past its `maxMs` deadline without the job reaching\n * a terminal status. Not an HTTP error — `status` is 0; catch by type/`code`.\n */\nexport class JobTimeoutError extends NodaroError {\n constructor(\n message: string,\n public readonly jobId: string,\n /** The wall-clock deadline (ms) that was exceeded. */\n public readonly timeoutMs: number,\n ) {\n super(message, \"job_timeout\", 0)\n this.name = \"JobTimeoutError\"\n }\n}\n\n/**\n * The caller's `AbortSignal` fired while `nodes.runAndWait` was polling (or it\n * was already aborted on entry). Polling stops and this rejects. Not an HTTP\n * error — `status` is 0; catch by type/`code`.\n */\nexport class JobAbortedError extends NodaroError {\n constructor(message = \"Aborted\", public readonly jobId?: string) {\n super(message, \"job_aborted\", 0)\n this.name = \"JobAbortedError\"\n }\n}\n\ninterface ApiErrorBody {\n error?: { code?: string; message?: string; missingScope?: string; required?: number; available?: number; limitBytes?: number; [key: string]: unknown }\n}\n\nexport function throwFromResponse(status: number, body: ApiErrorBody): never {\n const code = body.error?.code ?? \"internal_error\"\n const message = body.error?.message ?? \"Request failed\"\n if (status === 401) throw new UnauthorizedError(message)\n if (status === 403 && code === \"insufficient_scope\") {\n throw new ForbiddenError(message, body.error?.missingScope)\n }\n if (status === 403) throw new ForbiddenError(message)\n if (status === 404) throw new NotFoundError(message)\n if (status === 429) throw new RateLimitedError(message)\n if (status === 402) {\n throw new InsufficientCreditsError(message, body.error?.required, body.error?.available)\n }\n if (status === 413) throw new StorageExceededError(message, body.error?.limitBytes)\n throw new NodaroError(message, code, status)\n}\n","import type { NodaroClient } from \"../client.js\"\nimport type { GenericNode, GenericEdge, WorkflowExport } from \"@nodaro/shared\"\n\n/**\n * Workflow metadata + (when fetched as a single record) full nodes/edges/settings.\n *\n * The list endpoint returns metadata only; `get`, `create`, and `update` return the\n * full record. `nodes`, `edges`, `settings`, and `sourcePrompt` are present only on\n * full records and omitted in list responses.\n */\nexport interface Workflow {\n id: string\n projectId: string | null\n userId: string\n name: string\n description?: string | null\n folderId?: string | null\n isTemplate?: boolean\n version?: number\n thumbnailUrl?: string | null\n nodes?: GenericNode[]\n edges?: GenericEdge[]\n settings?: Record<string, unknown>\n sourcePrompt?: string | null\n createdAt: string\n updatedAt: string\n}\n\nexport interface ListWorkflowsParams {\n /** Required — list endpoint is `/v1/projects/:projectId/workflows`. */\n projectId: string\n}\n\nexport interface CreateWorkflowInput {\n /** Required — workflow is created under this project. */\n projectId: string\n name: string\n description?: string\n folderId?: string | null\n nodes?: GenericNode[]\n edges?: GenericEdge[]\n settings?: Record<string, unknown>\n sourcePrompt?: string\n}\n\nexport interface UpdateWorkflowInput {\n name?: string\n description?: string\n folderId?: string | null\n nodes?: GenericNode[]\n edges?: GenericEdge[]\n settings?: Record<string, unknown>\n sourcePrompt?: string\n thumbnailUrl?: string | null\n}\n\nexport interface RunWorkflowParams {\n /** Optional subset of node IDs to execute. Omit to run the full workflow. */\n nodeIds?: string[]\n}\n\nexport interface RunWorkflowResult {\n executionId: string\n status: \"pending\" | \"running\"\n}\n\nexport class WorkflowsResource {\n constructor(private client: NodaroClient) {}\n\n /** List workflows for a project. Returns metadata only — `nodes`/`edges` are not included. */\n list(params: ListWorkflowsParams): Promise<{ data: Workflow[] }> {\n return this.client.request(\n \"GET\",\n `/v1/projects/${encodeURIComponent(params.projectId)}/workflows`,\n )\n }\n\n /** Get a workflow including its full nodes/edges/settings. */\n get(id: string): Promise<{ data: Workflow }> {\n return this.client.request(\"GET\", `/v1/workflows/${encodeURIComponent(id)}`)\n }\n\n /**\n * Get a PUBLICLY-SHARED workflow by id (`GET /v1/public/workflows/:id`) — the\n * unauthenticated share-by-link read. Returns the workflow's nodes/edges/\n * settings ONLY when it's opted into sharing server-side (`settings.studio.shared\n * === true`); otherwise the route 404s (→ `NotFoundError`). No auth required —\n * a share viewer has no session; the SDK omits the bearer when no token exists.\n */\n getPublic(id: string): Promise<{ data: Workflow }> {\n return this.client.request(\"GET\", `/v1/public/workflows/${encodeURIComponent(id)}`)\n }\n\n /**\n * Create a workflow under a project. Returns the full record.\n * NOTE: server route is `POST /v1/projects/:projectId/workflows`.\n */\n create(input: CreateWorkflowInput): Promise<{ data: Workflow }> {\n const { projectId, ...body } = input\n return this.client.request(\n \"POST\",\n `/v1/projects/${encodeURIComponent(projectId)}/workflows`,\n { body },\n )\n }\n\n /** Patch a workflow. Returns the full updated record. */\n update(id: string, input: UpdateWorkflowInput): Promise<{ data: Workflow }> {\n return this.client.request(\n \"PATCH\",\n `/v1/workflows/${encodeURIComponent(id)}`,\n { body: input },\n )\n }\n\n /** Delete a workflow. Returns `{ success: true }`. */\n delete(id: string): Promise<{ success: true }> {\n return this.client.request(\"DELETE\", `/v1/workflows/${encodeURIComponent(id)}`)\n }\n\n /**\n * Run a workflow. Returns the executionId for polling via\n * `client.executions.get(executionId)`.\n */\n run(id: string, params: RunWorkflowParams = {}): Promise<RunWorkflowResult> {\n return this.client.request(\n \"POST\",\n `/v1/workflows/${encodeURIComponent(id)}/run`,\n { body: params },\n )\n }\n\n /**\n * Export a workflow as a portable JSON bundle.\n * Pass `opts.assets = true` to include character/object/location entity data.\n */\n export(\n workflowId: string,\n opts?: { assets?: boolean },\n ): Promise<{ data: WorkflowExport }> {\n return this.client.request(\n \"GET\",\n `/v1/workflows/${encodeURIComponent(workflowId)}/export`,\n { query: { assets: opts?.assets ?? false } },\n )\n }\n\n /**\n * Import a `WorkflowExport` bundle into the specified project.\n * Re-creates any bundled assets (characters, objects, locations) under your account.\n */\n import(input: WorkflowExport & { projectId: string }): Promise<{ data: Workflow }> {\n const { projectId, ...workflowJson } = input\n return this.client.request(\"POST\", \"/v1/workflows/import\", {\n body: { projectId, workflow_json: workflowJson },\n })\n }\n}\n","import type { NodaroClient } from \"../client.js\"\n\nexport interface Project {\n id: string\n userId: string\n name: string\n description?: string | null\n settings?: Record<string, unknown>\n createdAt: string\n updatedAt: string\n}\n\nexport interface CreateProjectInput {\n name: string\n description?: string\n settings?: Record<string, unknown>\n}\n\nexport interface UpdateProjectInput {\n name?: string\n description?: string\n settings?: Record<string, unknown>\n}\n\nexport class ProjectsResource {\n constructor(private client: NodaroClient) {}\n\n /** List the authenticated user's projects. */\n list(): Promise<{ data: Project[] }> {\n return this.client.request(\"GET\", \"/v1/projects\")\n }\n\n /** Get a project by ID. */\n get(id: string): Promise<{ data: Project }> {\n return this.client.request(\"GET\", `/v1/projects/${encodeURIComponent(id)}`)\n }\n\n /** Create a new project. */\n create(input: CreateProjectInput): Promise<{ data: Project }> {\n return this.client.request(\"POST\", \"/v1/projects\", { body: input })\n }\n\n /** Update a project. At least one field must be provided. */\n update(id: string, input: UpdateProjectInput): Promise<{ data: Project }> {\n return this.client.request(\n \"PATCH\",\n `/v1/projects/${encodeURIComponent(id)}`,\n { body: input },\n )\n }\n\n /** Delete a project. Returns `{ success: true }`. */\n delete(id: string): Promise<{ success: true }> {\n return this.client.request(\"DELETE\", `/v1/projects/${encodeURIComponent(id)}`)\n }\n}\n","import type { NodaroClient } from \"../client.js\"\n\nexport type JobStatus =\n | \"pending\"\n | \"queued\"\n | \"processing\"\n | \"completed\"\n | \"failed\"\n | \"cancelled\"\n\n/**\n * Job record returned to non-admin SDK consumers. Field names are snake_case\n * to match the wire format (per the OpenAPI fix in Phase 1).\n *\n * Sensitive fields stripped server-side for non-admin callers:\n * `provider`, `provider_cost`, `display_cost`, `credits_actual`. USD\n * pricing is admin-only across api/sdk/mcp — non-admin consumers see\n * only the `credits` abstraction.\n */\nexport interface Job {\n id: string\n status: JobStatus\n progress: number\n user_id: string\n input_data: unknown\n output_data: unknown\n error_message: string | null\n credits: number | null\n job_type: string | null\n created_at: string\n started_at: string | null\n completed_at: string | null\n}\n\nexport interface CancelJobResult {\n success: true\n cancelled: number\n}\n\n/**\n * Lean job status returned by `GET /v1/jobs/:id/status`. Skips the\n * `input_data` JSONB, cost/timestamp columns, and the public sanitize pass —\n * intended for poll loops that only need progress/output/error.\n */\nexport interface JobStatusResult {\n id: string\n status: JobStatus\n progress?: number\n output_data?: unknown\n error_message?: string | null\n}\n\nexport class JobsResource {\n constructor(private client: NodaroClient) {}\n\n /** Get a single job by ID. */\n get(id: string): Promise<{ data: Job }> {\n return this.client.request(\"GET\", `/v1/jobs/${encodeURIComponent(id)}`)\n }\n\n /**\n * Get the lean status of a single job (poll-loop friendly).\n * Hits `GET /v1/jobs/:id/status` — returns only id/status/progress/\n * output_data/error_message, with far less wire/CPU cost than `get()`.\n * Same auth + ownership semantics as {@link get}.\n */\n getStatus(id: string): Promise<{ data: JobStatusResult }> {\n return this.client.request(\n \"GET\",\n `/v1/jobs/${encodeURIComponent(id)}/status`,\n )\n }\n\n /**\n * Cancel a job. Server route is `POST /v1/jobs/:jobId/cancel`.\n * Refunds any reserved credit holds.\n */\n cancel(id: string): Promise<CancelJobResult> {\n return this.client.request(\n \"POST\",\n `/v1/jobs/${encodeURIComponent(id)}/cancel`,\n )\n }\n}\n","import type { NodaroClient } from \"../client.js\"\n\nexport type ExecutionStatus =\n | \"pending\"\n | \"running\"\n | \"completed\"\n | \"failed\"\n | \"cancelled\"\n | \"stopping\"\n | \"timed_out\"\n | \"discarded\"\n\nexport type ExecutionTriggerType =\n | \"manual\"\n | \"webhook\"\n | \"schedule\"\n | \"app_run\"\n | \"single-node\"\n\n/**\n * Per-node state inside an execution's `nodeStates` map. Keys are node IDs.\n *\n * Shape mirrors `services/workflow-engine/types.ts` plus the synthetic\n * single-node-job shape from `routes/workflow-execution.ts`.\n */\nexport interface NodeExecutionState {\n status: string\n nodeType?: string\n jobId?: string | null\n creditsUsed?: number\n error?: string | null\n startedAt?: string | null\n completedAt?: string | null\n [key: string]: unknown\n}\n\n/**\n * Workflow execution record. Returned by `get()` and `cancel()` (the cancel\n * endpoint returns `{ success: true }`, not the execution itself).\n */\nexport interface WorkflowExecution {\n id: string\n workflowId: string | null\n userId: string\n status: ExecutionStatus\n triggerType: ExecutionTriggerType\n triggerData?: unknown\n nodeStates: Record<string, NodeExecutionState>\n totalNodes: number\n completedNodes: number\n failedNodes: number\n totalCreditsUsed: number\n errorMessage: string | null\n startedAt: string | null\n completedAt: string | null\n createdAt: string\n updatedAt: string\n}\n\n/** Summary returned by `listForWorkflow()`. Excludes per-row `triggerData`/`updatedAt`. */\nexport interface WorkflowExecutionSummary {\n id: string\n status: ExecutionStatus\n triggerType: ExecutionTriggerType\n nodeStates: Record<string, NodeExecutionState>\n totalNodes: number\n completedNodes: number\n failedNodes: number\n totalCreditsUsed: number\n errorMessage: string | null\n startedAt: string | null\n completedAt: string | null\n createdAt: string\n}\n\nexport interface ListExecutionsForWorkflowParams {\n limit?: number\n cursor?: string\n /** Comma-separated list of statuses, e.g. \"pending,running\". */\n status?: string\n /** \"editor\" excludes app_run / component / webhook / schedule executions. */\n source?: \"editor\" | \"all\"\n}\n\nexport interface ListExecutionsPage<T> {\n data: T[]\n nextCursor?: string\n}\n\nexport interface CancelExecutionParams {\n /**\n * \"after_current\" sets the execution to \"stopping\" (let in-flight nodes\n * finish, then stop). Default behavior cancels immediately.\n */\n mode?: \"after_current\" | \"discard\"\n}\n\nexport class ExecutionsResource {\n constructor(private client: NodaroClient) {}\n\n /** Get an execution by ID. Falls back to standalone single-node jobs server-side. */\n get(id: string): Promise<{ data: WorkflowExecution }> {\n return this.client.request(\n \"GET\",\n `/v1/workflow-executions/${encodeURIComponent(id)}`,\n )\n }\n\n /** List executions for a workflow. Merges workflow_executions + standalone single-node jobs. */\n listForWorkflow(\n workflowId: string,\n params: ListExecutionsForWorkflowParams = {},\n ): Promise<ListExecutionsPage<WorkflowExecutionSummary>> {\n return this.client.request(\n \"GET\",\n `/v1/workflows/${encodeURIComponent(workflowId)}/executions`,\n {\n query: {\n limit: params.limit,\n cursor: params.cursor,\n status: params.status,\n source: params.source,\n },\n },\n )\n }\n\n /** Cancel an execution. Returns `{ success: true }`. */\n cancel(id: string, params: CancelExecutionParams = {}): Promise<{ success: true }> {\n return this.client.request(\n \"POST\",\n `/v1/workflow-executions/${encodeURIComponent(id)}/cancel`,\n { body: params },\n )\n }\n}\n","import type { NodaroClient } from \"../client.js\"\nimport type { JobStatusResult } from \"./jobs.js\"\nimport { JobAbortedError, JobFailedError, JobTimeoutError } from \"../errors.js\"\nimport type { ConnectedReference } from \"@nodaro/shared\"\n\nexport type NodeCategory =\n | \"input\"\n | \"parameter\"\n | \"ai-image\"\n | \"ai-video\"\n | \"ai-audio\"\n | \"ai-text\"\n | \"processing\"\n | \"composition\"\n | \"trigger\"\n | \"output\"\n | \"control\"\n | \"entity\"\n | \"utility\"\n\nexport type OutputType = \"text\" | \"image\" | \"video\" | \"audio\" | \"data\" | \"none\"\n\n/**\n * Field shape inside a node's `inputSchema.fields[]`. Mirrors\n * `backend/src/lib/node-registry.ts`.\n */\nexport interface NodeInputField {\n key: string\n type: string\n required?: boolean\n options?: string[]\n}\n\nexport interface NodeInputSchema {\n fields: NodeInputField[]\n}\n\n/**\n * Node descriptor returned by `GET /v1/nodes` and `GET /v1/nodes/:type`.\n * Mirrors `backend/src/lib/node-registry.ts#NodeDescriptor`.\n */\nexport interface NodeDescriptor {\n type: string\n label: string\n category: NodeCategory\n description: string\n outputType: OutputType\n /** Credit cost. Number when fixed, string range like \"1-8\" when model-dependent, undefined if free. */\n creditCost?: number | string\n /** Input fields the node exposes for user override (subset of full config). */\n inputSchema?: NodeInputSchema\n /** For AI nodes: list of provider IDs supported. */\n providers?: string[]\n /** Capability flags such as \"supports-reference-image\" or \"supports-end-frame\". */\n capabilities?: string[]\n}\n\n/**\n * Result of a direct node execution. Most node types return `{ jobId }` and\n * are processed asynchronously by a worker — the caller polls\n * `client.jobs.get(jobId)` until status is `completed`/`failed`.\n *\n * A small subset (combine-text, split-text, composite — the \"inline\"\n * orchestrator categories) execute synchronously and return their full\n * result body. The shape is route-specific; consumers should branch on the\n * presence of `jobId`.\n */\nexport type RunNodeResult =\n | { jobId: string; usageLogId?: string; [k: string]: unknown }\n | Record<string, unknown>\n\n/**\n * Structured references — the editor's wired-reference shape — shared by\n * `generate-image` and `generate-video`. The route assembles them server-side\n * into per-ref `@image_N` directives and resolves `{image:N}` prompt tokens, so\n * a direct SDK run binds inline references exactly like the canvas. Pass the\n * same `ConnectedReference[]` the editor builds; the route dedupes + caps them\n * to the provider's image-reference limit.\n */\nexport interface StructuredReferenceParams {\n /** Wired references assembled server-side (deduped + capped per provider). */\n connectedReferences?: ConnectedReference[]\n /** Reorder the assembled reference list by stable ref ids; renumbers `@image_N`. */\n referenceOrder?: string[]\n}\n\n/**\n * Typed request body for `nodes.run(\"generate-image\", …)` / `runAndWait`.\n * Common fields are typed; any other route field passes through via the index\n * signature (the route Zod-validates the full body).\n */\nexport interface GenerateImageParams extends StructuredReferenceParams {\n prompt?: string\n provider?: string\n /** Flat reference image URLs — appended after `connectedReferences`. */\n referenceImageUrls?: string[]\n negativePrompt?: string\n [k: string]: unknown\n}\n\n/**\n * Typed request body for `nodes.run(\"generate-video\", …)` / `runAndWait`.\n * Common fields are typed; any other route field passes through.\n */\nexport interface GenerateVideoParams extends StructuredReferenceParams {\n prompt?: string\n provider?: string\n /** Start-frame image (image-to-video). */\n imageUrl?: string\n referenceImageUrls?: string[]\n referenceVideoUrls?: string[]\n referenceAudioUrls?: string[]\n [k: string]: unknown\n}\n\n/**\n * Typed request body for `nodes.run(\"assemble-narrated-video\", …)` / `runAndWait`.\n * Assembles blocks of video with audio narration into a single composed video.\n */\nexport interface AssembleNarratedVideoParams {\n blocks: { videoUrl: string; audioUrl?: string }[]\n voiceVolume?: number\n clipAudioVolume?: number\n maxSlowdown?: number\n trimStartFrames?: number\n trimEndFrames?: number\n [k: string]: unknown\n}\n\n/**\n * The `output_data` shape a finalized generation job writes. Every async\n * generation node persists one (or more) of these media URLs to\n * `jobs.output_data` on completion — `generate-image` → `imageUrl`,\n * `generate-video` / `combine-videos` / `merge-video-audio` / `video-upscale`\n * → `videoUrl` (+ `thumbnailUrl`), `text-to-speech` / `generate-music` →\n * `audioUrl`. Resolved by {@link NodesResource.runAndWait}. Extra fields may be\n * present, so the index signature is open.\n */\nexport interface NodeJobOutput {\n /** `text-to-speech` / `generate-music` / audio nodes write here. For\n * `audio-separation` this is the primary stem (vocals). */\n readonly audioUrl?: string\n /** `audio-separation` (Demucs) per-stem URLs. `vocalUrl`/`instrumentalUrl`\n * in vocal/instrumental mode; the rest in full-stems mode. */\n readonly vocalUrl?: string\n readonly instrumentalUrl?: string\n readonly drumsUrl?: string\n readonly bassUrl?: string\n readonly otherUrl?: string\n readonly guitarUrl?: string\n readonly pianoUrl?: string\n /** `generate-video` / `combine-videos` / `merge-video-audio` / `video-upscale` write here. */\n readonly videoUrl?: string\n /** `generate-image` / `edit-image` / `extract-frame` write here. */\n readonly imageUrl?: string\n /** Poster frame for video outputs. */\n readonly thumbnailUrl?: string\n readonly [k: string]: unknown\n}\n\n/** Options for {@link NodesResource.runAndWait} and {@link NodesResource.runMany}. */\nexport interface RunAndWaitOptions {\n /**\n * Abort the run/poll loop. Aborting (or passing an already-aborted signal)\n * stops polling and rejects with {@link JobAbortedError}.\n */\n readonly signal?: AbortSignal\n /** Called with each lean status the poll loop observes (running → terminal). */\n readonly onProgress?: (status: JobStatusResult) => void\n /** Poll interval in ms. Default 2000. */\n readonly pollMs?: number\n /** Wall-clock cap before giving up, in ms. Default ~15 min (900_000). */\n readonly maxMs?: number\n}\n\n/** One settled result from {@link NodesResource.runMany}. */\nexport interface RunManyResult {\n readonly jobId: string\n readonly output: NodeJobOutput\n}\n\n// Terminal statuses (poll loop stops on these) are handled explicitly in\n// `pollJob`: `completed` resolves output_data, `failed`/`cancelled` throw a\n// JobFailedError — matching studio's `completed | failed | cancelled` set.\nconst DEFAULT_POLL_MS = 2000\n/** Safety cap so a stuck job can't poll forever (~15 min at 2s). */\nconst DEFAULT_MAX_MS = 15 * 60 * 1000\n\nconst sleep = (ms: number, signal?: AbortSignal): Promise<void> =>\n new Promise<void>((resolve, reject) => {\n if (signal?.aborted) {\n reject(new JobAbortedError())\n return\n }\n const timer = setTimeout(() => {\n signal?.removeEventListener(\"abort\", onAbort)\n resolve()\n }, ms)\n function onAbort() {\n clearTimeout(timer)\n reject(new JobAbortedError())\n }\n signal?.addEventListener(\"abort\", onAbort, { once: true })\n })\n\n/** Pull a `{ jobId }` off a run result, or throw a labelled JobFailedError. */\nfunction extractJobId(result: RunNodeResult, label: string): string {\n if (result && typeof result === \"object\" && \"jobId\" in result) {\n const jobId = (result as { jobId: unknown }).jobId\n if (typeof jobId === \"string\") return jobId\n }\n // No jobId means the node ran inline/synchronously (combine-text, etc.) or\n // the route shape changed — runAndWait is for async jobs, so this is an error.\n throw new JobFailedError(`${label} did not return a jobId`, \"\")\n}\n\nexport class NodesResource {\n constructor(private client: NodaroClient) {}\n\n /** List all known node descriptors. Server caches publicly for 5 minutes. */\n list(): Promise<{ data: NodeDescriptor[] }> {\n return this.client.request(\"GET\", \"/v1/nodes\")\n }\n\n /** Get a single node descriptor by type slug (e.g. \"generate-image\"). */\n get(type: string): Promise<{ data: NodeDescriptor }> {\n return this.client.request(\"GET\", `/v1/nodes/${encodeURIComponent(type)}`)\n }\n\n /**\n * Run a single node directly without wrapping it in a workflow. Posts\n * `params` as the request body to `POST /v1/<type>` (the route convention\n * every generation node follows: `generate-image`, `image-to-video`,\n * `text-to-speech`, etc.).\n *\n * This is the SDK equivalent of the MCP server's verb tools — and the\n * path the Nodaro CLI uses for `nodaro nodes run <type>`.\n *\n * Most node types are async: the response includes `{ jobId }` and the\n * actual generation runs on a worker. Poll `client.jobs.get(jobId)` until\n * completed. Inline node types (combine-text, etc.) return their full\n * result synchronously without a `jobId` field.\n *\n * @param type Node type slug — must match an entry in the registry\n * returned by `list()` (e.g. \"generate-image\").\n * @param params Request body. Field names must match the node's\n * `inputSchema` (see `get(type).inputSchema`).\n */\n run(type: \"generate-image\", params?: GenerateImageParams): Promise<RunNodeResult>\n run(type: \"generate-video\", params?: GenerateVideoParams): Promise<RunNodeResult>\n run(type: \"assemble-narrated-video\", params?: AssembleNarratedVideoParams): Promise<RunNodeResult>\n run(type: string, params?: Record<string, unknown>): Promise<RunNodeResult>\n run(type: string, params: Record<string, unknown> = {}): Promise<RunNodeResult> {\n return this.client.request(\"POST\", `/v1/${encodeURIComponent(type)}`, { body: params })\n }\n\n /**\n * Run a single async node to completion: {@link run} it, extract the\n * `{ jobId }`, then client-poll `jobs.getStatus(jobId)` every `pollMs`\n * (default 2000) until a terminal status, up to `maxMs` (default ~15 min).\n *\n * Resolves the job's typed `output_data` ({@link NodeJobOutput}) on\n * `completed`. Throws (all typed, catchable by `instanceof`):\n * - {@link InsufficientCreditsError} / {@link StorageExceededError} etc. —\n * surfaced by the underlying {@link run} on 402/413/… before any poll.\n * - {@link JobFailedError} — terminal `failed`/`cancelled` (carries the\n * job's `error_message` + `jobId`).\n * - {@link JobTimeoutError} — `maxMs` deadline exceeded before terminal.\n * - {@link JobAbortedError} — `signal` fired (or was already aborted);\n * polling stops immediately.\n *\n * Polling is fully client-side (no server function blocks) — the same model\n * thin clients use, lifted out of their hand-rolled run→poll loops.\n *\n * @param type Node type slug (e.g. \"generate-video\"). See {@link run}.\n * @param params Request body — field names match the node's `inputSchema`.\n * @param opts `signal` / `onProgress` / `pollMs` / `maxMs`.\n */\n runAndWait(type: \"generate-image\", params?: GenerateImageParams, opts?: RunAndWaitOptions): Promise<NodeJobOutput>\n runAndWait(type: \"generate-video\", params?: GenerateVideoParams, opts?: RunAndWaitOptions): Promise<NodeJobOutput>\n runAndWait(type: \"assemble-narrated-video\", params?: AssembleNarratedVideoParams, opts?: RunAndWaitOptions): Promise<NodeJobOutput>\n runAndWait(type: string, params?: Record<string, unknown>, opts?: RunAndWaitOptions): Promise<NodeJobOutput>\n async runAndWait(\n type: string,\n params: Record<string, unknown> = {},\n opts: RunAndWaitOptions = {},\n ): Promise<NodeJobOutput> {\n if (opts.signal?.aborted) throw new JobAbortedError()\n const result = await this.run(type, params)\n const jobId = extractJobId(result, type)\n return this.pollJob(jobId, type, opts)\n }\n\n /**\n * Fan out N async runs of the same node `type` to completion concurrently —\n * the candidate-grid path (generate N stills/clips in parallel). Each runs\n * via {@link runAndWait}; resolves once ALL settle, to an array of\n * `{ jobId, output }` in input order. Rejects (and the rejection wins) if any\n * single run rejects — same typed errors as {@link runAndWait}. A shared\n * `signal` aborts the whole batch.\n *\n * @param type Node type slug, applied to every entry.\n * @param paramsList One request body per candidate.\n * @param opts Shared `signal` / `onProgress` / `pollMs` / `maxMs`.\n */\n async runMany(\n type: string,\n paramsList: Record<string, unknown>[],\n opts: RunAndWaitOptions = {},\n ): Promise<RunManyResult[]> {\n if (opts.signal?.aborted) throw new JobAbortedError()\n return Promise.all(\n paramsList.map(async (params) => {\n if (opts.signal?.aborted) throw new JobAbortedError()\n const result = await this.run(type, params)\n const jobId = extractJobId(result, type)\n const output = await this.pollJob(jobId, type, opts)\n return { jobId, output }\n }),\n )\n }\n\n /** Poll an already-kicked job id until terminal; resolve output_data or throw. */\n private async pollJob(\n jobId: string,\n label: string,\n opts: RunAndWaitOptions,\n ): Promise<NodeJobOutput> {\n const pollMs = opts.pollMs ?? DEFAULT_POLL_MS\n const maxMs = opts.maxMs ?? DEFAULT_MAX_MS\n const deadline = Date.now() + maxMs\n for (;;) {\n if (opts.signal?.aborted) throw new JobAbortedError(undefined, jobId)\n const { data } = await this.client.jobs.getStatus(jobId)\n opts.onProgress?.(data)\n if (data.status === \"completed\") {\n return (data.output_data ?? {}) as NodeJobOutput\n }\n if (data.status === \"failed\" || data.status === \"cancelled\") {\n throw new JobFailedError(\n data.error_message ?? `${label} ${data.status}`,\n jobId,\n data.status,\n )\n }\n if (Date.now() > deadline) {\n throw new JobTimeoutError(`${label} timed out`, jobId, maxMs)\n }\n await sleep(pollMs, opts.signal)\n }\n }\n}\n","import type { NodaroClient } from \"../client.js\"\n\n/**\n * OAuth scopes that a developer app may request. Mirrors\n * `backend/src/lib/scopes.ts#ALL_SCOPES`.\n */\nexport type DeveloperAppScope =\n | \"workflows:read\"\n | \"workflows:write\"\n | \"workflows:execute\"\n | \"jobs:read\"\n | \"assets:read\"\n | \"assets:write\"\n | \"credits:read\"\n | \"apps:read\"\n | \"pipelines:read\"\n | \"pipelines:execute\"\n | \"pipelines:approve\"\n\nexport type DeveloperAppStatus = \"active\" | \"suspended\" | \"pending_review\"\n\nexport interface DeveloperApp {\n id: string\n name: string\n description: string | null\n logoUrl: string | null\n homepageUrl: string | null\n redirectUris: string[]\n allowedOrigins: string[]\n scopesRequested: DeveloperAppScope[]\n clientId: string\n status: DeveloperAppStatus\n createdAt: string\n updatedAt: string\n}\n\n/**\n * One-shot create response — `clientSecret` is returned exactly ONCE here.\n * Store it securely; subsequent `get`/`list` calls will not include it.\n */\nexport interface CreateDeveloperAppResult extends DeveloperApp {\n clientSecret: string\n}\n\nexport interface CreateDeveloperAppInput {\n name: string\n description?: string\n homepageUrl?: string\n logoUrl?: string\n /** At least 1, at most 10 redirect URIs. Each must be https or http://localhost. */\n redirectUris: string[]\n /** Up to 5 bare origins (no path/query/hash), e.g. \"https://example.com\". */\n allowedOrigins?: string[]\n /** At least 1 scope required. */\n scopesRequested: DeveloperAppScope[]\n}\n\nexport interface UpdateDeveloperAppInput {\n name?: string\n description?: string\n homepageUrl?: string\n logoUrl?: string\n redirectUris?: string[]\n allowedOrigins?: string[]\n scopesRequested?: DeveloperAppScope[]\n}\n\nexport interface RotateSecretResult {\n /** New client secret. Returned exactly once — old secret is invalidated. */\n clientSecret: string\n}\n\nexport class DeveloperAppsResource {\n constructor(private client: NodaroClient) {}\n\n /** List the authenticated user's developer apps. */\n list(): Promise<{ data: DeveloperApp[] }> {\n return this.client.request(\"GET\", \"/v1/developer-apps\")\n }\n\n /** Get a developer app by ID. */\n get(id: string): Promise<{ data: DeveloperApp }> {\n return this.client.request(\"GET\", `/v1/developer-apps/${encodeURIComponent(id)}`)\n }\n\n /**\n * Create a new developer app. Returns the app PLUS a one-time `clientSecret`\n * — store it now, the secret hash is the only copy kept server-side.\n */\n create(input: CreateDeveloperAppInput): Promise<{ data: CreateDeveloperAppResult }> {\n return this.client.request(\"POST\", \"/v1/developer-apps\", { body: input })\n }\n\n /** Update a developer app's metadata, redirect URIs, origins, or requested scopes. */\n update(id: string, input: UpdateDeveloperAppInput): Promise<{ data: DeveloperApp }> {\n return this.client.request(\n \"PATCH\",\n `/v1/developer-apps/${encodeURIComponent(id)}`,\n { body: input },\n )\n }\n\n /** Delete a developer app. Returns `{ success: true }`. */\n delete(id: string): Promise<{ success: true }> {\n return this.client.request(\n \"DELETE\",\n `/v1/developer-apps/${encodeURIComponent(id)}`,\n )\n }\n\n /**\n * Generate a new `clientSecret`. The previous secret is invalidated.\n * Server returns ONLY the new secret, not the full app record.\n */\n rotateSecret(id: string): Promise<RotateSecretResult> {\n return this.client.request(\n \"POST\",\n `/v1/developer-apps/${encodeURIComponent(id)}/rotate-secret`,\n )\n }\n}\n","import type { NodaroClient } from \"../client.js\"\nimport type { DeveloperAppScope } from \"./developer-apps.js\"\n\n/**\n * Server-side authorization-code exchange payload. Field names are snake_case\n * per OAuth 2.0 (RFC 6749).\n */\nexport interface ExchangeCodeInput {\n client_id: string\n client_secret: string\n /** Authorization code received from the consent redirect. */\n code: string\n /** Must match the redirect_uri used when issuing the code. */\n redirect_uri: string\n}\n\n/**\n * `POST /v1/oauth/token` response — snake_case per RFC 6749. `expires_in` is\n * the token's lifetime in seconds.\n */\nexport interface AccessTokenResponse {\n access_token: string\n token_type: \"Bearer\"\n /** Space-separated list of granted scopes. */\n scope: string\n expires_in: number\n}\n\n/**\n * Public app metadata for consent screens. Only safe-to-display fields —\n * no secret, no full origin list, no owner_user_id.\n */\nexport interface OAuthAppInfo {\n name: string\n description: string | null\n logoUrl: string | null\n homepageUrl: string | null\n scopesRequested: DeveloperAppScope[]\n}\n\nexport class OAuthResource {\n constructor(private client: NodaroClient) {}\n\n /**\n * Server-side authorization-code exchange. Sends the standard OAuth 2.0\n * `application/json` body to `POST /v1/oauth/token`.\n *\n * NEVER call this from a browser — `client_secret` must stay on the server.\n */\n exchangeCode(input: ExchangeCodeInput): Promise<AccessTokenResponse> {\n return this.client.request(\"POST\", \"/v1/oauth/token\", {\n body: { grant_type: \"authorization_code\", ...input },\n })\n }\n\n /**\n * Revoke an access token (RFC 7009). Always returns `{ success: true }`,\n * even for unknown tokens — the spec forbids leaking token validity.\n */\n revoke(token: string): Promise<{ success: true }> {\n return this.client.request(\"POST\", \"/v1/oauth/revoke\", { body: { token } })\n }\n\n /**\n * Get public app metadata for a consent screen.\n * `GET /v1/oauth/app-info?client_id=<id>`. Public route — no auth needed.\n */\n getAppInfo(clientId: string): Promise<OAuthAppInfo> {\n return this.client.request(\"GET\", \"/v1/oauth/app-info\", {\n query: { client_id: clientId },\n })\n }\n}\n","import type { NodaroClient } from \"../client.js\"\n\n/**\n * A published app — a workflow wrapped in a curated input/output presentation.\n * Returned by the public `/v1/apps/browse` endpoint.\n */\nexport interface PublishedApp {\n id: string\n slug: string\n name: string\n description?: string | null\n creatorId: string\n creatorName?: string | null\n thumbnailUrl?: string | null\n category?: string | null\n isFeatured?: boolean\n runCount?: number\n createdAt: string\n updatedAt: string\n}\n\nexport interface ListAppsParams {\n /** Substring search across app name + description. */\n search?: string\n /** Page size; backend caps at 50. */\n limit?: number\n /** Cursor token returned by the previous page. */\n cursor?: string\n /** Filter to a single category slug. */\n category?: string\n}\n\nexport interface ListAppsResult {\n data: PublishedApp[]\n nextCursor?: string | null\n}\n\n/**\n * App detail — includes the input schema (required + optional fields end users\n * fill in) and the output mapping (which workflow nodes produce which display\n * cards).\n */\nexport interface PublishedAppDetail extends PublishedApp {\n inputSchema: Record<string, unknown>\n outputs: Array<{ nodeId: string; label: string; type: string }>\n}\n\nexport interface AppRunResult {\n /** The execution-id that was started — poll via client.executions.get(). */\n executionId: string\n status: \"pending\" | \"running\"\n /** App-run id (distinct from executionId — used by listRuns/getRun). */\n runId?: string\n}\n\nexport interface AppRun {\n id: string\n appSlug: string\n executionId: string\n status: \"pending\" | \"running\" | \"completed\" | \"failed\" | \"cancelled\"\n inputs: Record<string, unknown>\n outputs?: Array<{ nodeId: string; type: string; url?: string; text?: string }>\n startedAt: string\n finishedAt?: string | null\n}\n\nexport interface ListAppRunsParams {\n limit?: number\n cursor?: string\n}\n\n/**\n * Result of a soft-delete (archive) operation. The run is moved to the user's\n * archive in the Nodaro UI; restoration and permanent deletion are UI-only by\n * design — SDK / MCP / API delete callers can't accidentally destroy data.\n */\nexport interface DeleteAppRunResult {\n success: true\n archived: true\n}\n\nexport class AppsResource {\n constructor(private client: NodaroClient) {}\n\n /** List published apps. Public — no auth required for community apps. */\n list(params: ListAppsParams = {}): Promise<ListAppsResult> {\n const qs = new URLSearchParams()\n if (params.search) qs.set(\"search\", params.search)\n if (params.limit !== undefined) qs.set(\"limit\", String(params.limit))\n if (params.cursor) qs.set(\"cursor\", params.cursor)\n if (params.category) qs.set(\"category\", params.category)\n const query = qs.toString()\n return this.client.request(\"GET\", `/v1/apps/browse${query ? `?${query}` : \"\"}`)\n }\n\n /** Get one app's metadata + input schema by slug. */\n get(slug: string): Promise<{ data: PublishedAppDetail }> {\n return this.client.request(\"GET\", `/v1/app/${encodeURIComponent(slug)}`)\n }\n\n /**\n * Trigger an app run with the given input values. The keys in `inputs` must\n * match the app's input-schema field names (see `get(slug).inputSchema`).\n * Returns the execution-id for status polling via client.executions.get().\n */\n run(slug: string, inputs: Record<string, unknown> = {}): Promise<AppRunResult> {\n return this.client.request(\"POST\", `/v1/app/${encodeURIComponent(slug)}/run`, {\n body: { inputs },\n })\n }\n\n /** List past runs for an app (the caller must own the app or the runs). */\n listRuns(slug: string, params: ListAppRunsParams = {}): Promise<{ data: AppRun[]; nextCursor?: string | null }> {\n const qs = new URLSearchParams()\n if (params.limit !== undefined) qs.set(\"limit\", String(params.limit))\n if (params.cursor) qs.set(\"cursor\", params.cursor)\n const query = qs.toString()\n return this.client.request(\n \"GET\",\n `/v1/app/${encodeURIComponent(slug)}/runs${query ? `?${query}` : \"\"}`,\n )\n }\n\n /** Get one app-run by id. */\n getRun(slug: string, runId: string): Promise<{ data: AppRun }> {\n return this.client.request(\n \"GET\",\n `/v1/app/${encodeURIComponent(slug)}/runs/${encodeURIComponent(runId)}`,\n )\n }\n\n /**\n * Archive (soft-delete) a published-app run. The run is hidden from the\n * default run list and can be restored or permanently deleted from the\n * archive view at https://app.nodaro.ai/archived-runs.\n *\n * @param slug The published app's slug (the last path segment of its URL).\n * @param runId The run's UUID.\n */\n deleteRun(slug: string, runId: string): Promise<DeleteAppRunResult> {\n return this.client.request(\n \"DELETE\",\n `/v1/app/${encodeURIComponent(slug)}/runs/${encodeURIComponent(runId)}`,\n )\n }\n}\n","import { buildPersonHints, PEOPLE, PERSON_DIMENSION_ORDER, PERSON_DIMENSION_LABELS, type PersonValue } from \"@nodaro/prompts\"\nimport type { CharacterAspectRatio, TtsProvider, CharacterAttachColumn, EntityStyle } from \"@nodaro/shared\"\nexport type { EntityStyle, CharacterAspectRatio } from \"@nodaro/shared\"\nexport { CHARACTER_STYLES, CHARACTER_ASPECT_OPTIONS, CHARACTER_ASPECT_DEFAULTS } from \"@nodaro/shared\"\nimport type { NodaroClient } from \"../client.js\"\n\n/**\n * Re-export the shared `EntityStyle` union (realistic | anime | 3d-pixar |\n * illustration) and `CHARACTER_STYLES` runtime tuple so SDK consumers don't\n * have to add `@nodaro/shared` as a second dependency just to typecheck the\n * `style` field. Single source of truth lives in `@nodaro/shared/entity-prompts`.\n */\n\n/**\n * Re-export the 4-value aspect-ratio union accepted by the generate-character*\n * routes. Single source of truth lives in `@nodaro/shared`. See\n * `CHARACTER_ASPECT_DEFAULTS` for the per-asset-type defaults.\n */\n\n/**\n * Structured Person composer, re-exported from `@nodaro/shared` so SDK\n * consumers can build a detailed person description — Identity, Body, and the\n * Face facial-geometry layer (cheekbones, canthal tilt, eyelid type, lip\n * fullness/shape, nose tip, etc.) — and feed it as `seedPrompt` / `description`\n * to `generate()` / `upsert()` without adding `@nodaro/shared` as a second dep.\n *\n * `buildPersonSeedPrompt` collapses a `PersonValue` into the same comma-joined\n * fragment the editor's Person picker produces (e.g. \"almond-shaped eyes,\n * sharply sculpted high cheekbones, full plump lips\"). Returns \"\" when empty.\n */\n\n\n/**\n * A character record returned by Nodaro's REST API. Mirrors the camelCase\n * shape produced by `backend/src/routes/characters.ts::toCamel()`.\n *\n * `expressions`, `poses`, `motions`, `angles`, `bodyAngles`,\n * `lightingVariations` are independent buckets keyed by a human-readable\n * variant name (e.g. `\"smile\"`, `\"standing\"`, `\"3/4 left\"`). Each entry's\n * `url` points at an R2-hosted asset.\n *\n * Identity-foundation fields:\n * - `referencePhotos` — caller-supplied real-life refs (max one per\n * non-`other` kind; cap 20 total). Drive the i2v / i2i path when a\n * provider supports multi-image conditioning.\n * - `realLifeRefsByVariant` — per-variant reference URLs (cap 20 keys,\n * 5 URLs per key). Keys are lowercased+trimmed.\n * - `referenceVideosByVariant` — per-label user-uploaded reference VIDEO\n * URLs (cap 20 keys, 5 URLs per key, lowercased+trimmed keys). Mirrors\n * `realLifeRefsByVariant` for video clips (e.g. emotion takes). Read the\n * chosen URLs off the row to drive generate-video's `referenceVideoUrls`.\n * - `seedPrompt` — short prompt fragment that scaffolds portrait gen.\n * - `canonicalDescription` — ~80–120-word LLM-authored visual caption,\n * populated by `approvePortrait()` / `recaption()`.\n */\nexport interface Character {\n id: string\n userId: string\n /** Canvas node linkage. `null` only on legacy clone rows that predate the\n * clone-side node_id fix (new clones mint one; creates always had one). */\n nodeId: string | null\n projectId: string | null\n name: string\n description: string | null\n gender: string | null\n style: string | null\n baseOutfit: string | null\n sourceImageUrl: string | null\n /** MODEL_CATALOG image-model id the main image was generated with (or `null`).\n * Set on create (the provider you generated with) + editable via `upsert`. */\n imageProvider: string | null\n expressions: Array<{ name: string; url: string }> | null\n poses: Array<{ name: string; url: string }> | null\n lightingVariations: Array<{ name: string; url: string }> | null\n angles: Array<{ name: string; url: string }> | null\n bodyAngles: Array<{ name: string; url: string }> | null\n motions: Array<{ name: string; url: string }> | null\n /** Named Character Boards — dense reference sheets, one per persona/look\n * (the `generate-image/character-board` factory preset rendered from the\n * character's images). A first-class bucket: community publish snapshots it\n * and clone hands the consumer their own copy to extend. Defaults to `[]`.\n * `type` marks an image-collage `\"identity\"` sheet vs a plain `\"looks\"`\n * board; `sourceImages` are the R2 URLs it was collaged from. Both optional\n * + backward-compatible (legacy boards have neither). */\n boards?: Array<{ name: string; url: string; type?: \"looks\" | \"identity\"; sourceImages?: string[] }> | null\n /** Per-label user-uploaded reference VIDEO URLs (R2), keyed by a\n * caller-owned label (lowercased+trimmed server-side). Mirrors\n * `realLifeRefsByVariant` for video clips; read the chosen URLs off the row\n * to feed generate-video's `referenceVideoUrls`. Defaults to `{}`. */\n referenceVideosByVariant?: Record<string, string[]> | null\n /** The user's chosen DEFAULT asset take per variant (Studio version history).\n * OPAQUE map: key `\"<bucket>:<variant>\"` (e.g. `\"bodyAngles:front\"`,\n * `\"expressions:smile\"`) → the chosen asset URL (one already present in that\n * bucket). Stored verbatim — keys are NOT normalized; soft-capped server-side\n * at 200 keys / 2048-char values (overflow dropped silently). Defaults to `{}`. */\n selectedAssetByVariant?: Record<string, string> | null\n /** `voiceType` records the selected voice's KIND (premade voices are\n * addressed by name; library/custom voices by id at text-to-speech time).\n * `previewUrl` is a playable audio sample (the voice's `preview_url` / clone\n * sample) the studio plays in an `<audio>` element — persisted so Voice Library\n * voices (which have no by-id lookup) stay previewable after reload. Client-\n * played only; the server never fetches it. `ttsProvider` is the library\n * voice's verified TTS provider (see `SharedVoice.recommendedProvider`) —\n * send it as the text-to-speech `provider` so the voice renders on a model\n * it's verified for. All optional — a character may have no voice, or a\n * legacy voice predating these fields. */\n voice: { voiceId: string; voiceName: string; traits: string; voiceType?: \"premade\" | \"library\" | \"custom\"; previewUrl?: string; ttsProvider?: TtsProvider } | null\n personality: {\n mood: string\n speechStyle: string\n movementStyle: string\n behavioralNotes: string\n } | null\n /** ~80–120-word LLM-authored visual caption (approve-portrait / recaption).\n * Optional on the read surface so existing literal consumers don't break;\n * the route always returns it (string | null). */\n canonicalDescription?: string | null\n /** Identity-lock strength for Character Studio asset generation. */\n identityLock?: \"off\" | \"soft\" | \"strict\"\n deletedAt: string | null\n createdAt: string\n updatedAt: string\n}\n\n/**\n * GET /v1/characters/:id appends three live-progress buckets the studio uses\n * to rehydrate spinners after a reload. Optional in the SDK surface — they\n * don't appear on `list()` rows.\n */\nexport interface CharacterDetail extends Character {\n pendingJobs?: Array<{\n jobId: string\n assetType: \"expressions\" | \"poses\" | \"angles\" | \"bodyAngles\" | \"lighting\" | \"motions\"\n name: string\n }>\n portraitCandidates?: Array<{\n jobId: string\n url: string | undefined\n progress: number\n status: string\n }>\n previousCandidates?: Array<{\n jobId: string\n url: string\n createdAt: string\n }>\n}\n\nexport type ReferencePhotoKind =\n | \"frontFace\"\n | \"sideLeft\"\n | \"sideRight\"\n | \"threeQuarterLeft\"\n | \"threeQuarterRight\"\n | \"frontBody\"\n | \"other\"\n\nexport interface ReferencePhoto {\n url: string\n kind: ReferencePhotoKind\n}\n\n/**\n * Body for `client.characters.upsert()`. Mirrors `upsertCharacterBody` in\n * `backend/src/routes/characters.ts`. Omitting `id` triggers an INSERT;\n * supplying it triggers an UPDATE that only writes the fields you pass —\n * undefined keys are NOT touched on the row.\n *\n * `name` is optional at the type level. The route requires `name` on INSERT\n * (id absent) and rejects with `validation_error` otherwise; on UPDATE the\n * route just ignores `name` when omitted, which lets partial updates like\n * `update(id, { gender: \"female\" })` succeed without re-sending the same\n * name the caller already has.\n */\nexport interface UpsertCharacterInput {\n /** UUID of the character row; omit to create. */\n id?: string\n /** Canvas node id the character belongs to. REQUIRED on create (the route\n * 400s without it, like `name`); optional on update — the update branch\n * never touches node_id, so partial updates needn't round-trip it. */\n nodeId?: string\n workflowId?: string\n projectId?: string\n name?: string\n description?: string\n gender?: string\n style?: string\n baseOutfit?: string\n sourceImageUrl?: string\n /** Persistent image-model id (a MODEL_CATALOG image model). Validated\n * server-side — unknown / non-image / \"\" is stored as `null`. */\n imageProvider?: string | null\n expressions?: Array<{ name: string; url: string }>\n poses?: Array<{ name: string; url: string }>\n lightingVariations?: Array<{ name: string; url: string }>\n angles?: Array<{ name: string; url: string }>\n bodyAngles?: Array<{ name: string; url: string }>\n motions?: Array<{ name: string; url: string }>\n /** Named Character Boards (see `Character.boards`) — whole-array replace,\n * like the asset buckets. Server caps: 24 boards, 200-char names, 30\n * sourceImages per board. */\n boards?: Array<{ name: string; url: string; type?: \"looks\" | \"identity\"; sourceImages?: string[] }>\n /** See `Character.voice` — persisted alongside the voice so TTS can resolve a\n * library/custom voice by id, `previewUrl` keeps the sample playable after\n * reload, and `ttsProvider` keeps generation on a model the voice is\n * verified for. All optional. */\n voice?: { voiceId: string; voiceName: string; traits: string; voiceType?: \"premade\" | \"library\" | \"custom\"; previewUrl?: string; ttsProvider?: TtsProvider } | null\n personality?: {\n mood: string\n speechStyle: string\n movementStyle: string\n behavioralNotes: string\n } | null\n seedPrompt?: string\n canonicalDescription?: string\n /** Identity-lock strength for Character Studio asset generation (off/soft/strict). */\n identityLock?: \"off\" | \"soft\" | \"strict\"\n referencePhotos?: ReferencePhoto[]\n /** Per-variant real-life reference URLs. Keys are lowercased+trimmed server-side. */\n realLifeRefsByVariant?: Record<string, string[]>\n /** Per-label user-uploaded reference VIDEO URLs (e.g. emotion takes). Keys\n * are lowercased+trimmed server-side; max 20 keys, 5 URLs each. Stored R2\n * URLs are read back off the row to drive generate-video's\n * `referenceVideoUrls`. */\n referenceVideosByVariant?: Record<string, string[]>\n /** The user's chosen DEFAULT asset take per variant. OPAQUE\n * `\"<bucket>:<variant>\"` → chosen-URL map; a write REPLACES the whole map\n * (the studio sends the full map each save). Omit to leave the row untouched.\n * Keys stored verbatim; soft-capped server-side at 200 keys / 2048-char values. */\n selectedAssetByVariant?: Record<string, string>\n}\n\nexport interface UpsertCharacterResult {\n id: string\n name?: string\n}\n\nexport interface ListCharactersParams {\n /** Restrict to a single project. */\n projectId?: string\n /** When true, return archived characters instead of active ones. */\n archived?: boolean\n /**\n * Max rows to return. Server defaults to 100, caps at 500. Omit to take the\n * server default — passing it just narrows further.\n */\n limit?: number\n}\n\nexport interface DuplicateCharacterInput {\n /** Optional canvas node id to bind the new row to. */\n nodeId?: string\n /** Optional project to drop the new row into. */\n projectId?: string\n}\n\nexport interface CharacterUsage {\n workflowCount: number\n workflows: Array<{ id: string; name: string }>\n}\n\n/**\n * Input for `client.characters.generate()` — fires the\n * `POST /v1/generate-character` route. Produces 1–10 portrait candidates;\n * each lands as one `jobs` row in `pending` state and is then enqueued for\n * the worker.\n *\n * Provide at least one of `seedPrompt`, `referencePhotos`, or `description`\n * (the backend's refinement rejects empty input with `validation_error`).\n *\n * When `attachToCharacterId` is set, the worker writes the resulting URL\n * directly to `characters.source_image_url` on completion — caller doesn't\n * need a separate `approvePortrait` call for single-candidate runs.\n */\nexport interface GenerateCharacterInput {\n name: string\n description?: string\n userPrompt?: string\n gender?: string\n style?: EntityStyle\n baseOutfit?: string\n sourceImageUrl?: string\n provider?: string\n /** Originating client app slug ('person', 'studio', …) — diagnostic\n * attribution only (the platform's app_reports); never affects output. */\n origin?: string\n /** Auto-attach the result to this character row. */\n attachToCharacterId?: string\n seedPrompt?: string\n referencePhotos?: ReferencePhoto[]\n /** Number of candidate images to generate (1–10; server-validated). */\n count?: number\n /**\n * Explicit aspect ratio. Highest precedence — overrides both the character\n * node toggle and the per-asset-type default (portraits default to `3:4`).\n * Must be one of the 4-value `CharacterAspectRatio` union.\n */\n aspectRatio?: CharacterAspectRatio\n /**\n * Character node toggle (per-canvas-node `defaultAssetAspectRatio`). Wins\n * against the per-asset-type default, loses to `aspectRatio`.\n */\n characterNodeAspectRatio?: CharacterAspectRatio\n /**\n * Credit-affecting quality tier (e.g. `\"high\"` for gpt-image). Priced like\n * generate-image (composite ids such as `gpt-image:high`); values the chosen\n * model doesn't support are ignored server-side, never rejected.\n */\n quality?: string\n /**\n * Credit-affecting output resolution (e.g. `\"2K\"` / `\"4K\"` / `\"2 MP\"`).\n * Priced like generate-image (composite ids such as `nano-banana-pro:4K`);\n * values the chosen model doesn't support are ignored server-side, never\n * rejected.\n */\n resolution?: string\n}\n\nexport interface GenerateCharacterResult {\n /** First job-id; convenience alias for `jobIds[0]`. */\n jobId: string\n /** All job-ids when `count > 1`. */\n jobIds: string[]\n}\n\nexport interface GenerateAssetInput {\n assetType:\n | \"expressions\"\n | \"poses\"\n | \"lighting\"\n | \"angles\"\n | \"headAngles\"\n | \"bodyAngles\"\n | \"custom\"\n /** The named variant (e.g. `\"smile\"`, `\"standing\"`, `\"3/4 left\"`). */\n variant: string\n /** Display name of the character; appears in the prompt. */\n name: string\n description?: string\n userPrompt?: string\n gender?: string\n style?: EntityStyle\n baseOutfit?: string\n sourceImageUrl?: string\n /** Real-life reference URLs (cap 5). */\n realLifeRefs?: string[]\n provider?: string\n /** Originating client app slug ('person', 'studio', …) — diagnostic\n * attribution only (the platform's app_reports); never affects output. */\n origin?: string\n /** Auto-attach to character row + asset bucket on completion. */\n attachToCharacterId?: string\n /** Shared type — auto-includes new buckets (sheets/detail_closeups/outfit_variations); mirrors objects.ts/locations.ts. */\n attachToColumn?: CharacterAttachColumn\n attachName?: string\n /**\n * Explicit aspect ratio. Highest precedence — overrides both the character\n * node toggle and the per-asset-type default (expressions=1:1, poses=9:16,\n * headAngles=3:4, bodyAngles=9:16, lighting=3:4, angles=3:4, custom=3:4).\n */\n aspectRatio?: CharacterAspectRatio\n /**\n * Character node toggle (per-canvas-node `defaultAssetAspectRatio`). Wins\n * against the per-asset-type default, loses to `aspectRatio`.\n */\n characterNodeAspectRatio?: CharacterAspectRatio\n /**\n * Credit-affecting quality tier (e.g. `\"high\"` for gpt-image). Values the\n * chosen model doesn't support are ignored server-side, never rejected.\n */\n quality?: string\n /**\n * Credit-affecting output resolution (e.g. `\"2K\"` / `\"4K\"` / `\"2 MP\"`).\n * Values the chosen model doesn't support are ignored server-side, never\n * rejected.\n */\n resolution?: string\n}\n\nexport interface GenerateMotionInput {\n motionPrompt: string\n /** Optional when `attachToCharacterId` is set — falls back to the row's portrait. */\n sourceImageUrl?: string\n provider?: string\n name: string\n description?: string\n motionDescription?: string\n gender?: string\n style?: EntityStyle\n baseOutfit?: string\n realLifeRefs?: string[]\n attachToCharacterId?: string\n attachName?: string\n /**\n * Explicit aspect ratio. Highest precedence — overrides both the character\n * node toggle and the motions default (`9:16`).\n */\n aspectRatio?: CharacterAspectRatio\n /**\n * Character node toggle (per-canvas-node `defaultAssetAspectRatio`). Wins\n * against the motions default, loses to `aspectRatio`.\n */\n characterNodeAspectRatio?: CharacterAspectRatio\n}\n\nexport interface ApprovePortraitResult {\n portraitUrl: string\n /**\n * LLM-authored caption. `null` when the LLM call failed during the approval\n * — the portrait is still set; call `recaption()` to retry.\n */\n canonicalDescription: string | null\n}\n\nexport interface RecaptionResult {\n canonicalDescription: string\n}\n\nexport class CharactersResource {\n constructor(private client: NodaroClient) {}\n\n /**\n * List the caller's characters. By default returns active characters only;\n * pass `archived: true` to fetch soft-deleted rows for an \"archive\" view.\n * When `projectId` is set, only characters belonging to that project are\n * returned.\n */\n list(params: ListCharactersParams = {}): Promise<{ characters: Character[] }> {\n const query: Record<string, string | undefined> = {}\n if (params.projectId) query.projectId = params.projectId\n if (params.archived) query.archived = \"true\"\n if (params.limit !== undefined) query.limit = String(params.limit)\n return this.client.request(\"GET\", \"/v1/characters\", { query })\n }\n\n /**\n * Fetch a single character including in-flight portrait / asset job state.\n * Soft-deleted (archived) rows are returned by id intentionally so canvas\n * nodes that hold a stale `characterDbId` keep loading.\n */\n get(id: string): Promise<CharacterDetail> {\n return this.client.request(\"GET\", `/v1/characters/${encodeURIComponent(id)}`)\n }\n\n /**\n * Create or update a character. Omit `id` to create; supply it to update\n * (only the fields you pass get written — undefined keys are untouched).\n *\n * If the caller-supplied `name` collides with an existing active character\n * for this user, the request returns 409 `name_taken`. To auto-number a\n * placeholder, pass the placeholder name from `@nodaro/shared` and the\n * server will derive \"Untitled character 2\", \"Untitled character 3\", etc.\n */\n upsert(input: UpsertCharacterInput): Promise<UpsertCharacterResult> {\n return this.client.request(\"POST\", \"/v1/characters\", { body: input })\n }\n\n /**\n * Convenience wrapper around `upsert()` for creating new characters.\n * Equivalent to `upsert({ ...input, id: undefined })`. `name` is REQUIRED\n * on create — the route 400s on INSERT-without-name; we narrow the type\n * here so callers fail at compile-time rather than runtime.\n */\n create(\n input: Omit<UpsertCharacterInput, \"id\"> & { name: string },\n ): Promise<UpsertCharacterResult> {\n return this.upsert(input)\n }\n\n /**\n * Convenience wrapper around `upsert()` for updating an existing character.\n * Equivalent to `upsert({ ...input, id })`.\n */\n update(\n id: string,\n input: Omit<UpsertCharacterInput, \"id\">,\n ): Promise<UpsertCharacterResult> {\n return this.upsert({ ...input, id })\n }\n\n /**\n * Soft-delete (archive) a character. The row is hidden from `list()` by\n * default but still loadable via `get(id)` so canvas nodes pointing at it\n * keep working. Restore with `restore(id)`.\n */\n delete(id: string): Promise<{ success: true; archived: true }> {\n return this.client.request(\"DELETE\", `/v1/characters/${encodeURIComponent(id)}`)\n }\n\n /**\n * Un-archive a character. If the original name now collides with an\n * active row, the server auto-suffixes \"(restored)\" and returns the\n * effective name.\n */\n restore(id: string): Promise<{ id: string; name: string }> {\n return this.client.request(\"POST\", `/v1/characters/${encodeURIComponent(id)}/restore`)\n }\n\n /**\n * Duplicate (fork) a character to a new row with a `\"(copy)\"` suffix.\n * Asset URLs are shared by reference — the new row can diverge by\n * regenerating any of them.\n */\n duplicate(id: string, input: DuplicateCharacterInput = {}): Promise<{ id: string; name: string }> {\n return this.client.request(\n \"POST\",\n `/v1/characters/${encodeURIComponent(id)}/duplicate`,\n { body: input },\n )\n }\n\n /**\n * Count of the caller's workflows that reference this character. Powers the\n * library \"Archive\" confirmation modal in the editor.\n */\n usage(id: string): Promise<CharacterUsage> {\n return this.client.request(\"GET\", `/v1/characters/${encodeURIComponent(id)}/usage`)\n }\n\n /**\n * Fire `POST /v1/generate-character` to produce one or more portrait\n * candidates. With `count > 1`, all jobs are reserved up-front before any\n * is enqueued — mid-batch failures roll back atomically.\n *\n * When `attachToCharacterId` is set, the worker writes the result directly\n * to the row's `source_image_url`; otherwise you must call\n * `approvePortrait()` after picking a candidate.\n */\n generate(input: GenerateCharacterInput): Promise<GenerateCharacterResult> {\n return this.client.request(\"POST\", \"/v1/generate-character\", { body: input })\n }\n\n /**\n * Fire `POST /v1/generate-character-asset` to produce a single\n * expression / pose / angle / lighting variant. When the studio path is\n * set (`attachToCharacterId` + `attachToColumn` + `attachName`), the\n * worker appends `{ name: attachName, url: <result> }` to the named\n * JSONB array column on completion.\n */\n generateAsset(input: GenerateAssetInput): Promise<{ jobId: string }> {\n return this.client.request(\"POST\", \"/v1/generate-character-asset\", { body: input })\n }\n\n /**\n * Fire `POST /v1/generate-character-motion` to animate the character's\n * portrait into a motion clip. The result is appended to the character's\n * `motions[]` bucket when `attachToCharacterId` is set.\n */\n generateMotion(input: GenerateMotionInput): Promise<{ jobId: string }> {\n return this.client.request(\"POST\", \"/v1/generate-character-motion\", { body: input })\n }\n\n /**\n * Approve a completed `generate-character` job as the character's portrait.\n * Sets `source_image_url` and fires the LLM caption (Claude Sonnet vision)\n * inline. Returns the new portrait URL plus the caption — `canonicalDescription`\n * is `null` if the LLM call sub-failed (portrait still set; retry via `recaption()`).\n */\n approvePortrait(id: string, candidateJobId: string): Promise<ApprovePortraitResult> {\n return this.client.request(\n \"POST\",\n `/v1/characters/${encodeURIComponent(id)}/approve-portrait`,\n { body: { candidateJobId } },\n )\n }\n\n /**\n * Re-fire the LLM caption against the character's current portrait. 502s on\n * LLM failure; returns 400 `no_portrait` if no portrait is set yet.\n */\n recaption(id: string): Promise<RecaptionResult> {\n return this.client.request(\n \"POST\",\n `/v1/characters/${encodeURIComponent(id)}/llm-caption`,\n )\n }\n}\n\n/** Person catalog + hint helpers, re-exported from @nodaro/prompts (FSL-licensed). */\nexport { buildPersonHints, PEOPLE, PERSON_DIMENSION_ORDER, PERSON_DIMENSION_LABELS }\nexport type { PersonValue }\n\n/**\n * Collapse a PersonValue into the comma-joined seed-prompt fragment used by\n * `characters.generate({ seedPrompt })` — same composition the Nodaro editor\n * performs. Powered by @nodaro/prompts.\n */\nexport function buildPersonSeedPrompt(value: PersonValue): string {\n return buildPersonHints(value as Record<string, unknown> & PersonValue).join(\", \")\n}\n","import type { CharacterAspectRatio, SurroundDirection, LocationAssetType, LocationAttachColumn } from \"@nodaro/shared\"\nexport type { LocationAssetType, LocationAttachColumn, CharacterAspectRatio, SurroundDirection } from \"@nodaro/shared\"\nexport { LOCATION_ASSET_TYPES, LOCATION_ATTACH_COLUMNS, SURROUND_DIRECTIONS } from \"@nodaro/shared\"\nimport type { NodaroClient } from \"../client.js\"\n\n/**\n * Re-export the shared `LocationAssetType` / `LocationAttachColumn` unions and\n * their runtime tuples so SDK consumers don't have to add `@nodaro/shared` as a\n * second dependency just to typecheck the `assetType` / `attachToColumn`\n * fields. Single source of truth lives in `@nodaro/shared/entity-prompts`.\n *\n * `CharacterAspectRatio` is re-exported alongside them — `generateMotion`'s\n * `aspectRatio` field reuses the same 4-value enum (1:1 / 3:4 / 16:9 / 9:16)\n * as characters; the route enforces this with `z.enum(CHARACTER_ASPECT_OPTIONS)`.\n */\n\n/**\n * Reference-photo kind discriminator — the mood-board roles a user can attach\n * to a location. Mirrors the `reference_photos.kind` Zod enum in\n * `backend/src/routes/locations.ts`. `other` is the free-form bucket.\n */\nexport type LocationReferencePhotoKind =\n | \"wide\"\n | \"interior\"\n | \"exterior\"\n | \"detail\"\n | \"moodBoard\"\n | \"other\"\n\nexport interface LocationReferencePhoto {\n url: string\n kind: LocationReferencePhotoKind\n}\n\n/**\n * A location record returned by Nodaro's REST API. Mirrors the camelCase\n * shape produced by `backend/src/routes/locations.ts::toCamel()`.\n *\n * Asset buckets (`timeOfDay`, `weather`, `angles`, `lighting`, `seasons`,\n * `atmosphereMotions`) are independent JSONB arrays keyed by a human-readable\n * variant name (e.g. `\"dawn\"`, `\"clear\"`, `\"wide\"`). Each entry's `url` points\n * at an R2-hosted asset.\n *\n * Identity-foundation fields:\n * - `referencePhotos` — caller-supplied mood-board refs (cap 20).\n * - `canonicalDescription` — ~80–120-word LLM-authored visual caption,\n * populated by `approveMainImage()` / `recaption()`. The wire still sends\n * `\"\"` on caption sub-failure (the breaking wire change is deferred to a\n * major bump), but `get()` normalizes `\"\"` → `null` so consumers see the\n * same `string | null` semantics as characters.\n * - `styleLock` — whether asset gens should anchor to the canonical style\n * captured at approval time. Defaults to `true` on new rows.\n */\nexport interface Location {\n id: string\n userId: string\n nodeId: string\n projectId: string | null\n name: string\n description: string | null\n category: string | null\n style: string | null\n sourceImageUrl: string | null\n /** MODEL_CATALOG image-model id the main image was generated with (or `null`).\n * Set on create + editable via the update route. */\n imageProvider: string | null\n timeOfDay: Array<{ name: string; url: string }>\n weather: Array<{ name: string; url: string }>\n angles: Array<{ name: string; url: string }>\n lighting: Array<{ name: string; url: string }>\n seasons: Array<{ name: string; url: string }>\n atmosphereMotions: Array<{ name: string; url: string }>\n /** Named Location Boards — dense reference sheets, one per variant/mood\n * (the `generate-image/location-board` factory preset rendered from the\n * location's images). A first-class bucket: community publish snapshots it\n * and clone hands the consumer their own copy to extend. Defaults to `[]`.\n * `type` marks an image-collage `\"identity\"` sheet vs a plain `\"looks\"`\n * board; `sourceImages` are the R2 URLs it was collaged from. Both optional\n * + backward-compatible (legacy boards have neither). */\n boards?: Array<{ name: string; url: string; type?: \"looks\" | \"identity\"; sourceImages?: string[] }> | null\n referencePhotos: LocationReferencePhoto[]\n /** `null` when no caption is set (or the LLM caption sub-failed) — the wire\n * sends `\"\"`, normalized to `null` in `get()` to match character semantics. */\n canonicalDescription: string | null\n styleLock: boolean\n /** The user's chosen DEFAULT asset take per variant (Studio version history).\n * OPAQUE map: key `\"<bucket>:<variant>\"` (e.g. `\"timeOfDay:dawn\"`) → the chosen\n * asset URL (one already present in that bucket). Stored verbatim — keys are\n * NOT normalized; soft-capped server-side at 200 keys / 2048-char values\n * (overflow dropped silently). Defaults to `{}`. */\n selectedAssetByVariant?: Record<string, string> | null\n deletedAt: string | null\n createdAt: string\n updatedAt: string\n}\n\n/**\n * GET /v1/locations/:id appends a `pendingJobs` bucket the studio uses to\n * rehydrate spinners after a reload. Optional on the SDK surface — it doesn't\n * appear on `list()` rows.\n */\nexport interface LocationDetail extends Location {\n pendingJobs?: Array<{\n jobId: string\n assetType: string\n name: string\n status: string\n }>\n /**\n * Completed candidate main images for this location whose URL differs from\n * the current `sourceImageUrl`, newest first (max 5). Surfaced so a\n * \"pick from N\" UI can offer alternatives the user can promote via\n * `approveMainImage(id, jobId)`; until they pick, the current main image is\n * left untouched. Mirrors `CharacterDetail.previousCandidates`. Optional -\n * absent on `list()` rows, present (possibly empty) on `get()`.\n */\n previousCandidates?: Array<{\n jobId: string\n url: string\n createdAt: string\n }>\n}\n\n/**\n * Body for `client.locations.create()`. Mirrors the INSERT branch of\n * `upsertLocationBody` in `backend/src/routes/locations.ts`. `name` + `nodeId`\n * are required on create.\n */\nexport interface CreateLocationInput {\n nodeId: string\n name: string\n description?: string\n category?: string\n style?: string\n workflowId?: string\n projectId?: string\n sourceImageUrl?: string\n /** Persistent image-model id (a MODEL_CATALOG image model). Validated\n * server-side — unknown / non-image / \"\" is stored as `null`. */\n imageProvider?: string | null\n referencePhotos?: LocationReferencePhoto[]\n canonicalDescription?: string\n styleLock?: boolean\n /** The user's chosen DEFAULT asset take per variant. OPAQUE\n * `\"<bucket>:<variant>\"` → chosen-URL map; a write REPLACES the whole map.\n * Keys stored verbatim; soft-capped server-side at 200 keys / 2048-char values. */\n selectedAssetByVariant?: Record<string, string>\n}\n\n/**\n * Body for `client.locations.update()`. Mirrors the UPDATE branch of\n * `upsertLocationBody` in `backend/src/routes/locations.ts`.\n *\n * Worker-owned asset buckets (`timeOfDay`/`weather`/`angles`/`lighting`/\n * `seasons`/`atmosphereMotions`) are deliberately omitted — the route drops\n * them on UPDATE so a Studio auto-save with a stale snapshot cannot clobber\n * the worker's atomic `append_location_asset()` writes.\n *\n * `expectedUpdatedAt` is the optimistic-concurrency token: when present, the\n * UPDATE only succeeds if the row's `updated_at` still matches; on mismatch\n * the route returns 409 so the studio can re-fetch + merge.\n */\nexport interface UpdateLocationInput {\n name?: string\n description?: string\n category?: string\n style?: string\n sourceImageUrl?: string\n /** Persistent image-model id (a MODEL_CATALOG image model). Validated\n * server-side — unknown / non-image / \"\" is stored as `null`. */\n imageProvider?: string | null\n referencePhotos?: LocationReferencePhoto[]\n canonicalDescription?: string\n styleLock?: boolean\n /** The user's chosen DEFAULT asset take per variant. OPAQUE\n * `\"<bucket>:<variant>\"` → chosen-URL map; a write REPLACES the whole map\n * (omit to leave untouched). Keys stored verbatim; soft-capped server-side\n * at 200 keys / 2048-char values. */\n selectedAssetByVariant?: Record<string, string>\n /** Named Location Boards (see `Location.boards`) — whole-array replace,\n * USER-owned (unlike the worker-owned buckets it flows through UPDATE).\n * Server caps: 24 boards, 200-char names, 30 sourceImages per board. */\n boards?: Array<{ name: string; url: string; type?: \"looks\" | \"identity\"; sourceImages?: string[] }>\n /** ISO-8601 timestamp recording when PII consent was captured for this location. */\n piiConsentAt?: string\n expectedUpdatedAt?: string\n}\n\nexport interface UpdateLocationResult {\n id: string\n updatedAt: string\n}\n\nexport interface ListLocationsParams {\n /** When true, return archived locations instead of active ones. */\n archived?: boolean\n}\n\n/**\n * Input for `client.locations.generate()` — fires the\n * `POST /v1/generate-location` route. Produces 1–10 candidate\n * establishing shots; each lands as one `jobs` row in `pending` state and\n * is then enqueued for the worker.\n *\n * When `attachToLocationId` is set AND `count === 1`, the worker writes the\n * resulting URL directly to `locations.source_image_url` on completion —\n * caller doesn't need a separate `approveMainImage` call. Multi-candidate\n * batches MUST go through explicit approval so the user picks the winner.\n */\nexport interface GenerateLocationInput {\n name: string\n description?: string\n userPrompt?: string\n category?: \"indoor\" | \"outdoor\" | \"urban\" | \"nature\" | \"fantasy\" | \"sci-fi\" | \"historical\" | \"futuristic\" | \"other\"\n style?: \"realistic\" | \"anime\" | \"3d-pixar\" | \"illustration\"\n sourceImageUrl?: string\n provider?: string\n /** Number of candidate images to generate (1–10; server-validated). */\n count?: number\n /** Auto-attach the result to this location row (single-candidate only). */\n attachToLocationId?: string\n /**\n * Credit-affecting quality tier (e.g. `\"high\"` for gpt-image). Priced like\n * generate-image (composite ids such as `gpt-image:high`); values the chosen\n * model doesn't support are ignored server-side, never rejected.\n */\n quality?: string\n /**\n * Credit-affecting output resolution (e.g. `\"2K\"` / `\"4K\"` / `\"2 MP\"`).\n * Priced like generate-image (composite ids such as `nano-banana-pro:4K`);\n * values the chosen model doesn't support are ignored server-side, never\n * rejected.\n */\n resolution?: string\n}\n\n/**\n * `generate()` response — `jobIds` is ALWAYS present (the harmonized contract,\n * matching characters). `jobId` is a deprecated back-compat alias populated only\n * on `count === 1`; prefer `jobIds`. (Will be removed on the next major.)\n */\nexport interface GenerateLocationResult {\n jobIds: string[]\n /** @deprecated count===1 back-compat alias — use `jobIds`. */\n jobId?: string\n}\n\n/**\n * Input for `client.locations.generateAsset()` — fires the\n * `POST /v1/generate-location-asset` route. Produces a single\n * timeOfDay / weather / seasons / angles / lighting / custom variant.\n *\n * When all three studio-path fields are set (`attachToLocationId` +\n * `attachToColumn` + `attachName`), the worker appends\n * `{ name: attachName, url: <result> }` to the named JSONB array column on\n * the user's location row on completion. `attachToColumn` is REQUIRED for\n * `assetType === \"custom\"` — the worker can't infer the bucket from the\n * asset type.\n */\nexport interface GenerateLocationAssetInput {\n assetType: LocationAssetType\n variant: string\n name: string\n description?: string\n userPrompt?: string\n category?: string\n style?: \"realistic\" | \"anime\" | \"3d-pixar\" | \"illustration\"\n sourceImageUrl?: string\n provider?: string\n /** Optional framing override (the same 4-value enum as `generateMotion`).\n * Absent = the image model's default. The studio's 360° surround path pins\n * `\"16:9\"` so every ring view matches the establishing shot's frame. */\n aspectRatio?: CharacterAspectRatio\n attachToLocationId?: string\n attachToColumn?: LocationAttachColumn\n attachName?: string\n /**\n * Credit-affecting quality tier (e.g. `\"high\"` for gpt-image). Values the\n * chosen model doesn't support are ignored server-side, never rejected.\n */\n quality?: string\n /**\n * Credit-affecting output resolution (e.g. `\"2K\"` / `\"4K\"` / `\"2 MP\"`).\n * Values the chosen model doesn't support are ignored server-side, never\n * rejected.\n */\n resolution?: string\n}\n\n/**\n * Input for `client.locations.generateSurroundContinuation()` — fires the\n * `POST /v1/generate-surround-continuation` route. Generates one seamless 360°\n * ring view as an image-to-image continuation of `referenceImageUrl` (the\n * previous ring view, or the establishing shot for the first ring).\n *\n * The platform owns the whole pipeline: it builds the half-carry composite\n * server-side (carry the reference's trailing half into the new frame's leading\n * half per `direction`, gray the rest), paints the gray region, then\n * color-harmonizes the painted half to the carried half so there is no tonal\n * seam down the frame's center. The carried half stays pixel-exact, so a\n * panorama viewer stitching adjacent ring views stays geometrically seamless.\n *\n * When the studio path is set (`attachToLocationId` + `attachToColumn` +\n * `attachName`), the worker appends `{ name: attachName, url: <result> }` to the\n * location's bucket (studio uses `attachToColumn: \"angles\"`,\n * `attachName: \"Surround 45°\"`).\n */\nexport interface GenerateSurroundContinuationInput {\n /** The previous ring view to continue from (i2i anchor). */\n referenceImageUrl: string\n /** Carry/paint axis: turn right, turn left, tilt up, or tilt down. Tilts\n * render the sky/ground (a thin horizon strip), not a horizontal continuation. */\n direction: SurroundDirection\n /** Ring angle (45, 90, …) — stored on the result as metadata. */\n degrees?: number\n /** Fraction of the frame carried from the reference. Omitted ⇒ per-direction\n * default (0.5 for a pan, 0.12 thin strip for a tilt). */\n carriedFraction?: number\n /** Upscale/denoise the result before it's chained as the next reference, to\n * slow cumulative softening down a long ring chain. Default false. */\n refine?: boolean\n /** Refine model when `refine` is set. `recraft-upscale` (1 cr, default) or\n * `topaz-image-upscale` (3 cr). */\n refineProvider?: \"recraft-upscale\" | \"topaz-image-upscale\"\n /** Optional free-form scene hint woven into the fill prompt. */\n userPrompt?: string\n /** Image model. Studio pins `nano-banana-pro`; default `nano-banana`. */\n provider?: string\n /** Studio pins `\"16:9\"` so every ring view matches the establishing frame. */\n aspectRatio?: CharacterAspectRatio\n attachToLocationId?: string\n attachToColumn?: LocationAttachColumn\n attachName?: string\n}\n\n/**\n * Input for `client.locations.generateMotion()` — fires the\n * `POST /v1/generate-location-motion` route. Produces a single atmospheric\n * motion clip (drifting fog, snowfall, rolling waves, etc.) animated FROM a\n * static establishing-shot image.\n *\n * Mirrors `client.characters.generateMotion()` minus the character-specific\n * fields (gender / baseOutfit / realLifeRefs). The route hardcodes the attach\n * column to `atmosphere_motions` — callers supply `attachToLocationId` +\n * `attachName` only.\n *\n * `sourceImageUrl` is REQUIRED — image-to-video needs a source frame and the\n * route has no fallback (no `source_image_url` column to pull from on the\n * locations row; the studio path supplies the canonical establishing-shot URL\n * explicitly).\n *\n * When the studio path is set (`attachToLocationId` + `attachName`), the\n * worker appends `{ name: attachName, url: <result> }` to the location row's\n * `atmosphere_motions` JSONB column on completion.\n */\nexport interface GenerateLocationMotionInput {\n motionPrompt: string\n sourceImageUrl: string\n provider?: string\n name: string\n category?: string\n style?: \"realistic\" | \"anime\" | \"3d-pixar\" | \"illustration\"\n canonicalDescription?: string\n attachToLocationId?: string\n attachName?: string\n /**\n * Optional aspect ratio override. Defaults to 16:9 server-side via\n * `resolveLocationAspectRatio` (locations are cinematic establishing shots).\n * One of the 4-value `CharacterAspectRatio` union — locations reuse the\n * character aspect enum since the supported ratios are identical.\n */\n aspectRatio?: CharacterAspectRatio\n}\n\nexport interface ApproveMainImageResult {\n sourceImageUrl: string\n /**\n * LLM-authored caption. `null` when the LLM caption sub-failed — the wire\n * sends `\"\"`, normalized to `null` here so consumers see the same\n * `string | null` semantics as characters. The main image is still set; call\n * `recaption()` to retry.\n */\n canonicalDescription: string | null\n}\n\nexport interface RecaptionLocationResult {\n canonicalDescription: string\n}\n\nexport class LocationsResource {\n constructor(private client: NodaroClient) {}\n\n /**\n * List the caller's locations. By default returns active locations only;\n * pass `archived: true` to fetch soft-deleted rows for an \"archive\" view.\n */\n list(params: ListLocationsParams = {}): Promise<{ locations: Location[] }> {\n const query: Record<string, string | undefined> = {}\n if (params.archived) query.archived = \"true\"\n return this.client.request(\"GET\", \"/v1/locations\", { query })\n }\n\n /**\n * Convenience wrapper for `list({ archived: true })`. Returns soft-deleted\n * rows so callers can drive a UI \"Archived\" tab without re-encoding the\n * query param. Mirrors `ObjectsResource.listArchived`.\n *\n * `archived` is omitted from the param type — it's always set to `true` here.\n */\n listArchived(params: Omit<ListLocationsParams, \"archived\"> = {}): Promise<{ locations: Location[] }> {\n return this.list({ ...params, archived: true })\n }\n\n /**\n * Fetch a single location including in-flight asset job state. Soft-deleted\n * (archived) rows are returned by id intentionally so canvas nodes that\n * hold a stale `locationDbId` keep loading.\n */\n async get(id: string): Promise<LocationDetail> {\n const res = await this.client.request<LocationDetail>(\n \"GET\",\n `/v1/locations/${encodeURIComponent(id)}`,\n )\n // Normalize the wire `\"\"` caption (DB null / LLM sub-failure) → null so\n // consumers see the same `string | null` semantics as characters. New\n // object — never mutate the response.\n return { ...res, canonicalDescription: res.canonicalDescription || null }\n }\n\n /**\n * Create a new location. `name` + `nodeId` are required — the route 400s\n * otherwise. Returns the new row's id.\n *\n * Note: the underlying route is the same `POST /v1/locations` upsert that\n * powers `update()`. This convenience wrapper enforces the INSERT-required\n * fields at the type level and never sends an `id`.\n */\n create(data: CreateLocationInput): Promise<{ id: string }> {\n return this.client.request(\"POST\", \"/v1/locations\", { body: data })\n }\n\n /**\n * Update a location. Only the fields you pass are written — undefined keys\n * are NOT touched on the row. Worker-owned asset buckets are intentionally\n * not exposed on this surface (see `UpdateLocationInput` for the rationale).\n *\n * Optimistic-concurrency: pass `expectedUpdatedAt` to require the row's\n * `updated_at` still matches; on mismatch the route returns 409\n * `concurrent_modification`. The SDK surfaces that as a generic\n * `NodaroError` with the same code.\n */\n update(id: string, data: UpdateLocationInput): Promise<UpdateLocationResult> {\n return this.client.request(\"POST\", \"/v1/locations\", {\n body: { id, ...data },\n })\n }\n\n /**\n * Soft-delete (archive) a location. The row is hidden from `list()` by\n * default but still loadable via `get(id)` so canvas nodes pointing at it\n * keep working. Restore with `restore(id)`.\n */\n delete(id: string): Promise<{ success: true; archived: true }> {\n return this.client.request(\"DELETE\", `/v1/locations/${encodeURIComponent(id)}`)\n }\n\n /**\n * Un-archive a location. If the original name now collides (case-\n * insensitive) with an active row, the server auto-suffixes \"(restored)\"\n * and returns the effective name.\n */\n restore(id: string): Promise<{ id: string; name: string }> {\n return this.client.request(\"POST\", `/v1/locations/${encodeURIComponent(id)}/restore`)\n }\n\n /**\n * Fire `POST /v1/generate-location` to produce one or more candidate main\n * images. With `count > 1`, all jobs are reserved up-front before any\n * is enqueued — mid-batch failures roll back atomically.\n *\n * When `attachToLocationId` is set AND `count === 1`, the worker writes\n * the result directly to the row's `source_image_url`; otherwise you must\n * call `approveMainImage()` after picking a candidate.\n */\n async generate(data: GenerateLocationInput): Promise<GenerateLocationResult> {\n const res = await this.client.request<{ jobId?: string; jobIds?: string[] }>(\n \"POST\", \"/v1/generate-location\", { body: data },\n )\n // Tolerate the legacy `{ jobId }`-only shape (older server): synthesize jobIds.\n const jobIds = res.jobIds ?? (res.jobId ? [res.jobId] : [])\n return res.jobId ? { jobIds, jobId: res.jobId } : { jobIds }\n }\n\n /**\n * Fire `POST /v1/generate-location-asset` to produce a single variant.\n * When the studio path is set (`attachToLocationId` + `attachToColumn` +\n * `attachName`), the worker appends `{ name: attachName, url: <result> }`\n * to the named JSONB array column on completion.\n */\n generateAsset(data: GenerateLocationAssetInput): Promise<{ jobId: string }> {\n return this.client.request(\"POST\", \"/v1/generate-location-asset\", { body: data })\n }\n\n /**\n * Fire `POST /v1/generate-surround-continuation` to produce one seamless 360°\n * ring view as an i2i continuation of `referenceImageUrl`. The platform builds\n * the half-carry composite, paints the missing half, and color-harmonizes it\n * to the carried half (no tonal seam; carried half stays pixel-exact). When the\n * studio path is set, the worker appends the result to the location's bucket.\n */\n generateSurroundContinuation(data: GenerateSurroundContinuationInput): Promise<{ jobId: string }> {\n return this.client.request(\"POST\", \"/v1/generate-surround-continuation\", { body: data })\n }\n\n /**\n * Fire `POST /v1/generate-location-motion` to animate the location's\n * establishing shot into an atmospheric motion clip. Image-to-video, single\n * clip per call; the attach column is hardcoded to `atmosphere_motions`\n * server-side (locations have a single motion bucket so the caller doesn't\n * supply `attachToColumn`). When the studio path is set\n * (`attachToLocationId` + `attachName`), the worker appends\n * `{ name: attachName, url: <result> }` to the row's `atmosphere_motions`\n * column on completion.\n */\n generateMotion(data: GenerateLocationMotionInput): Promise<{ jobId: string }> {\n return this.client.request(\"POST\", \"/v1/generate-location-motion\", { body: data })\n }\n\n /**\n * Atomically remove ONE asset take (every entry matching `url`) from a\n * worker-owned bucket column — `POST /v1/locations/:id/remove-asset`. The\n * worker-owned buckets are deliberately not writable through `update()`\n * (a stale snapshot would race concurrent worker appends), so deleting a\n * take — e.g. a 360° surround view being regenerated — goes through this\n * single-statement server-side filter instead. 404s (`NotFoundError`) when\n * the url isn't in that bucket or the location isn't yours.\n */\n removeAsset(\n id: string,\n data: { column: LocationAttachColumn; url: string },\n ): Promise<{ removed: true }> {\n return this.client.request(\n \"POST\",\n `/v1/locations/${encodeURIComponent(id)}/remove-asset`,\n { body: data },\n )\n }\n\n /**\n * Approve a completed `generate-location` job as the location's main image.\n * Sets `source_image_url` and fires the LLM caption (Claude Sonnet vision)\n * inline. Returns the new main-image URL plus the caption.\n *\n * Caption-failure semantics: the route still sends `\"\"` on LLM sub-failure,\n * but the SDK normalizes `\"\"` → `null` here so `canonicalDescription` carries\n * the same `string | null` semantics as characters. The main image is still\n * set; call `recaption()` to retry.\n */\n async approveMainImage(id: string, candidateJobId: string): Promise<ApproveMainImageResult> {\n const res = await this.client.request<{ sourceImageUrl: string; canonicalDescription: string | null }>(\n \"POST\",\n `/v1/locations/${encodeURIComponent(id)}/approve-main-image`,\n { body: { candidateJobId } },\n )\n // Normalize the wire `\"\"` (LLM sub-failure) → null; build a new object\n // rather than mutating the response.\n return { sourceImageUrl: res.sourceImageUrl, canonicalDescription: res.canonicalDescription || null }\n }\n\n /**\n * Re-fire the LLM caption against the location's current main image. 502s\n * on LLM failure (unlike `approveMainImage` which preserves the side-effect\n * and returns \"\"); returns 400 `no_source_image` if no main image is set\n * yet.\n */\n recaption(id: string): Promise<RecaptionLocationResult> {\n return this.client.request(\n \"POST\",\n `/v1/locations/${encodeURIComponent(id)}/llm-caption`,\n )\n }\n}\n","import type { ObjectAspectRatio, ObjectAssetType, ObjectAttachColumn } from \"@nodaro/shared\"\nexport type { ObjectAssetType, ObjectAttachColumn, ObjectAspectRatio } from \"@nodaro/shared\"\nexport { OBJECT_ASSET_TYPES, OBJECT_ATTACH_COLUMNS, OBJECT_ASPECT_OPTIONS, OBJECT_ASPECT_DEFAULTS } from \"@nodaro/shared\"\nimport type { NodaroClient } from \"../client.js\"\n\n/**\n * Re-export the shared `ObjectAssetType` / `ObjectAttachColumn` unions and\n * their runtime tuples so SDK consumers don't have to add `@nodaro/shared` as\n * a second dependency just to typecheck the `assetType` / `attachToColumn`\n * fields. Single source of truth lives in `@nodaro/shared/entity-prompts`.\n *\n * `ObjectAspectRatio` is re-exported alongside them — `generateMotion`'s\n * `aspectRatio` field is the 5-value object enum (1:1 / 3:4 / 16:9 / 9:16 /\n * 4:3) from `@nodaro/shared/object-aspect-defaults`. Distinct from\n * `CharacterAspectRatio` because objects support an extra 4:3 framing for\n * product-showcase shots.\n */\n\n/**\n * Reference-photo kind discriminator — the mood-board roles a user can attach\n * to an object. Mirrors the `reference_photos.kind` field accepted by\n * `backend/src/routes/objects.ts` (the route accepts open strings; this SDK\n * type narrows to the 6 canonical roles surfaced by the Studio). `other` is\n * the free-form bucket.\n */\nexport type ObjectReferencePhotoKind =\n | \"front\"\n | \"side\"\n | \"detail\"\n | \"context\"\n | \"moodBoard\"\n | \"other\"\n\nexport interface ObjectReferencePhoto {\n url: string\n kind: ObjectReferencePhotoKind\n}\n\n/**\n * An object record returned by Nodaro's REST API. Mirrors the camelCase\n * shape produced by `backend/src/routes/objects.ts::toCamel()`.\n *\n * Asset buckets (`angles`, `materials`, `variations`, `motionClips`) are\n * independent JSONB arrays keyed by a human-readable variant name (e.g.\n * `\"front\"`, `\"wood\"`, `\"weathered\"`). Each entry's `url` points at an\n * R2-hosted asset.\n *\n * Identity-foundation fields:\n * - `referencePhotos` — caller-supplied mood-board refs (cap 20). Objects\n * do NOT carry a `piiConsentAt` field (location Phase 2 #7 only).\n * - `canonicalDescription` — ~80–120-word LLM-authored visual caption,\n * populated by `approveMainImage()` / `recaption()`. The wire still sends\n * `\"\"` on caption sub-failure (the breaking wire change is deferred to a\n * major bump), but `get()` normalizes `\"\"` → `null` so consumers see the\n * same `string | null` semantics as characters.\n * - `styleLock` — whether asset gens should anchor to the canonical style\n * captured at approval time. Defaults to `true` on new rows.\n *\n * `Object` shadows the JS global, which TypeScript handles cleanly via\n * local-scope resolution. Consumers who need both can alias as\n * `import type { Object as NodaroObject } from \"@nodaro/sdk\"`.\n */\nexport interface Object {\n id: string\n userId: string\n nodeId: string\n projectId: string | null\n name: string\n description: string | null\n category: string | null\n style: string | null\n sourceImageUrl: string | null\n /** MODEL_CATALOG image-model id the main image was generated with (or `null`).\n * Set on create + editable via the update route. */\n imageProvider: string | null\n angles: Array<{ name: string; url: string }>\n materials: Array<{ name: string; url: string }>\n variations: Array<{ name: string; url: string }>\n motionClips: Array<{ name: string; url: string }>\n /** Named Product Boards — dense reference sheets, one per variant/colorway\n * (the `generate-image/product-board` factory preset rendered from the\n * object's images). A first-class bucket: community publish snapshots it\n * and clone hands the consumer their own copy to extend. Defaults to `[]`.\n * `type` marks an image-collage `\"identity\"` sheet vs a plain `\"looks\"`\n * board; `sourceImages` are the R2 URLs it was collaged from. Both optional\n * + backward-compatible (legacy boards have neither). */\n boards?: Array<{ name: string; url: string; type?: \"looks\" | \"identity\"; sourceImages?: string[] }> | null\n referencePhotos: ObjectReferencePhoto[]\n /** `null` when no caption is set (or the LLM caption sub-failed) — the wire\n * sends `\"\"`, normalized to `null` in `get()` to match character semantics. */\n canonicalDescription: string | null\n styleLock: boolean\n /** The user's chosen DEFAULT asset take per variant (Studio version history).\n * OPAQUE map: key `\"<bucket>:<variant>\"` (e.g. `\"angles:front\"`) → the chosen\n * asset URL (one already present in that bucket). Stored verbatim — keys are\n * NOT normalized; soft-capped server-side at 200 keys / 2048-char values\n * (overflow dropped silently). Defaults to `{}`. */\n selectedAssetByVariant?: Record<string, string> | null\n deletedAt: string | null\n createdAt: string\n updatedAt: string\n}\n\n/**\n * GET /v1/objects/:id may append a `pendingJobs` bucket the studio uses to\n * rehydrate spinners after a reload. Optional on the SDK surface — it doesn't\n * appear on `list()` rows.\n */\nexport interface ObjectDetail extends Object {\n pendingJobs?: Array<{\n jobId: string\n assetType: string\n name: string\n status: string\n }>\n}\n\n/**\n * Body for `client.objects.create()`. Mirrors the INSERT branch of\n * `upsertObjectBody` in `backend/src/routes/objects.ts`. `name` + `nodeId`\n * are required on create.\n */\nexport interface CreateObjectInput {\n nodeId: string\n name: string\n description?: string\n category?: ObjectCategory\n style?: string\n workflowId?: string\n projectId?: string\n sourceImageUrl?: string\n /** Persistent image-model id (a MODEL_CATALOG image model). Validated\n * server-side — unknown / non-image / \"\" is stored as `null`. */\n imageProvider?: string | null\n referencePhotos?: ObjectReferencePhoto[]\n canonicalDescription?: string\n styleLock?: boolean\n /** The user's chosen DEFAULT asset take per variant. OPAQUE\n * `\"<bucket>:<variant>\"` → chosen-URL map; a write REPLACES the whole map.\n * Keys stored verbatim; soft-capped server-side at 200 keys / 2048-char values. */\n selectedAssetByVariant?: Record<string, string>\n}\n\n/**\n * The 10-value Object category enum. Mirrors the literal accepted by\n * `POST /v1/generate-object` and surfaced in the Object Studio category\n * picker. Distinct from location's geography-based set.\n */\nexport type ObjectCategory =\n | \"furniture\"\n | \"vehicle\"\n | \"weapon\"\n | \"food\"\n | \"clothing\"\n | \"electronics\"\n | \"nature\"\n | \"tool\"\n | \"animal\"\n | \"other\"\n\n/**\n * Body for `client.objects.update()`. Mirrors the UPDATE branch of\n * `upsertObjectBody` in `backend/src/routes/objects.ts`.\n *\n * Worker-owned asset buckets (`angles` / `materials` / `variations` /\n * `motionClips`) are deliberately omitted — the route drops them on UPDATE\n * so a Studio auto-save with a stale snapshot cannot clobber the worker's\n * atomic `append_object_asset()` writes.\n *\n * `expectedUpdatedAt` is the optimistic-concurrency token: when present, the\n * UPDATE only succeeds if the row's `updated_at` still matches; on mismatch\n * the route returns 409 so the studio can re-fetch + merge.\n */\nexport interface UpdateObjectInput {\n name?: string\n description?: string\n category?: ObjectCategory\n style?: string\n sourceImageUrl?: string\n /** Persistent image-model id (a MODEL_CATALOG image model). Validated\n * server-side — unknown / non-image / \"\" is stored as `null`. */\n imageProvider?: string | null\n referencePhotos?: ObjectReferencePhoto[]\n canonicalDescription?: string\n styleLock?: boolean\n /** The user's chosen DEFAULT asset take per variant. OPAQUE\n * `\"<bucket>:<variant>\"` → chosen-URL map; a write REPLACES the whole map\n * (omit to leave untouched). Keys stored verbatim; soft-capped server-side\n * at 200 keys / 2048-char values. */\n selectedAssetByVariant?: Record<string, string>\n /** Named Product Boards (see `Object.boards`) — whole-array replace,\n * USER-owned (unlike the worker-owned buckets it flows through UPDATE).\n * Server caps: 24 boards, 200-char names, 30 sourceImages per board. */\n boards?: Array<{ name: string; url: string; type?: \"looks\" | \"identity\"; sourceImages?: string[] }>\n expectedUpdatedAt?: string\n}\n\nexport interface UpdateObjectResult {\n id: string\n updatedAt: string\n}\n\n/**\n * Combined create + update body (parameter for both branches). Exported for\n * callers that want to drive a single `upsert` flow without picking between\n * `Create*` and `Update*`. Mirrors the `upsertObjectBody` Zod schema in\n * `backend/src/routes/objects.ts`. `nodeId` + `name` are required on INSERT;\n * `id` flips the route into UPDATE mode.\n */\nexport interface UpsertObjectInput extends CreateObjectInput {\n id?: string\n expectedUpdatedAt?: string\n}\n\nexport type UpsertObjectResult = { id: string } | UpdateObjectResult\n\nexport interface ListObjectsParams {\n /** When true, return archived objects instead of active ones. */\n archived?: boolean\n /** Optional project filter — server-scoped to the caller's user. */\n projectId?: string\n}\n\n/**\n * Input for `client.objects.generate()` — fires the\n * `POST /v1/generate-object` route. Produces 1–10 candidate\n * main images; each lands as one `jobs` row in `pending` state and is then\n * enqueued for the worker.\n *\n * When `attachToObjectId` is set AND `count === 1`, the worker writes the\n * resulting URL directly to `objects.source_image_url` on completion —\n * caller doesn't need a separate `approveMainImage` call. Multi-candidate\n * batches MUST go through explicit approval so the user picks the winner.\n *\n * `seedPromptHint` (Pass 7 F-77) flows the parameter-picker's prompt fragment\n * through to the worker so a catalog selection (e.g. \"antique brass lantern\")\n * gets appended to the generated prompt context.\n */\nexport interface GenerateObjectInput {\n name: string\n description?: string\n userPrompt?: string\n category?: ObjectCategory\n style?: \"realistic\" | \"anime\" | \"3d-pixar\" | \"illustration\"\n sourceImageUrl?: string\n provider?: string\n /** Number of candidate images to generate (1–10; server-validated). */\n count?: number\n /** Auto-attach the result to this object row (single-candidate only). */\n attachToObjectId?: string\n /** Parameter-picker prompt-fragment pass-through. */\n seedPromptHint?: string\n /** Optional name to set on the attached row alongside the main image. */\n attachName?: string\n /** Optimistic-concurrency token for the single-candidate auto-attach path. */\n expectedUpdatedAt?: string\n}\n\n/**\n * `generate()` response — `jobIds` is ALWAYS present (the harmonized contract,\n * matching characters). `jobId` is a deprecated back-compat alias populated only\n * on `count === 1`; prefer `jobIds`. (Will be removed on the next major.)\n */\nexport interface GenerateObjectResult {\n jobIds: string[]\n /** @deprecated count===1 back-compat alias — use `jobIds`. */\n jobId?: string\n}\n\n/**\n * Input for `client.objects.generateAsset()` — fires the\n * `POST /v1/generate-object-asset` route. Produces a single\n * angles / materials / variations / custom variant.\n *\n * When all three studio-path fields are set (`attachToObjectId` +\n * `attachToColumn` + `attachName`), the worker appends\n * `{ name: attachName, url: <result> }` to the named JSONB array column on\n * the user's object row on completion. `attachToColumn` is REQUIRED for\n * `assetType === \"custom\"` — the worker can't infer the bucket from the\n * asset type.\n */\nexport interface GenerateObjectAssetInput {\n assetType: ObjectAssetType\n variant: string\n name: string\n description?: string\n userPrompt?: string\n category?: string\n style?: \"realistic\" | \"anime\" | \"3d-pixar\" | \"illustration\"\n sourceImageUrl?: string\n provider?: string\n attachToObjectId?: string\n attachToColumn?: ObjectAttachColumn\n attachName?: string\n /** Parameter-picker prompt-fragment pass-through (Pass 7 F-77). */\n seedPromptHint?: string\n}\n\nexport interface GenerateObjectAssetResult {\n jobId: string\n}\n\n/**\n * Input for `client.objects.generateMotion()` — fires the\n * `POST /v1/generate-object-motion` route. Produces a single motion clip\n * (rotation, orbit, hover, drift, etc.) animated FROM a static product-shot\n * image.\n *\n * Mirrors `client.locations.generateMotion()` minus the location-specific\n * atmospheric fields. The route hardcodes the attach column to `motion_clips`\n * — callers supply `attachToObjectId` + `attachName` only.\n *\n * `sourceImageUrl` is REQUIRED — image-to-video needs a source frame and the\n * route has no fallback (no `source_image_url` column to pull from on the\n * objects row at this point in the flow; the studio path supplies the\n * canonical product-shot URL explicitly).\n *\n * Object-specific defaults vs location:\n * - `provider` defaults to `\"kling-turbo\"` (not location's `\"kling\"`)\n * - `aspectRatio` defaults to `\"1:1\"` server-side via\n * `resolveObjectAspectRatio({ assetType: \"motion\" })` — objects are\n * product-showcase framing, not cinematic establishing shots.\n *\n * When the studio path is set (`attachToObjectId` + `attachName`), the\n * worker appends `{ name: attachName, url: <result> }` to the object row's\n * `motion_clips` JSONB column on completion.\n */\nexport interface GenerateObjectMotionInput {\n motionPrompt: string\n sourceImageUrl: string\n provider?: string\n name: string\n /** Source clip URL — when set, worker routes to video-to-video refine. */\n refineFromVideoUrl?: string\n category?: string\n style?: \"realistic\" | \"anime\" | \"3d-pixar\" | \"illustration\"\n canonicalDescription?: string\n /** Parameter-picker prompt-fragment pass-through (Pass 7 F-77). */\n seedPromptHint?: string\n attachToObjectId?: string\n attachName?: string\n /**\n * Optional aspect ratio override. Defaults to 1:1 server-side. One of the\n * 5-value `ObjectAspectRatio` union (1:1 / 3:4 / 16:9 / 9:16 / 4:3) —\n * objects have their own enum (with 4:3 added) vs the character set.\n */\n aspectRatio?: ObjectAspectRatio\n /**\n * Optional clip duration in seconds. Validated server-side against the chosen\n * provider's allowed durations (e.g. kling 5/10, wan-i2v 5/10/15); omitted →\n * the model's own default (no behavior change). Mirrors generate-video's\n * per-model i2v duration lever.\n */\n duration?: number\n}\n\nexport interface GenerateObjectMotionResult {\n jobId: string\n}\n\nexport interface ApproveObjectMainImageResult {\n sourceImageUrl: string\n /**\n * LLM-authored caption. `null` when the LLM caption sub-failed — the wire\n * sends `\"\"`, normalized to `null` here so consumers see the same\n * `string | null` semantics as characters. The main image is still set; call\n * `recaption()` to retry.\n */\n canonicalDescription: string | null\n}\n\nexport interface RecaptionObjectResult {\n canonicalDescription: string\n}\n\nexport class ObjectsResource {\n constructor(private client: NodaroClient) {}\n\n /**\n * List the caller's objects. By default returns active objects only;\n * pass `archived: true` to fetch soft-deleted rows for an \"archive\" view.\n * Optional `projectId` scopes the result to a single project.\n */\n list(params: ListObjectsParams = {}): Promise<{ objects: Object[] }> {\n const query: Record<string, string | undefined> = {}\n if (params.archived) query.archived = \"true\"\n if (params.projectId) query.projectId = params.projectId\n return this.client.request(\"GET\", \"/v1/objects\", { query })\n }\n\n /**\n * Convenience wrapper for `list({ archived: true })`. Returns soft-deleted\n * rows so callers can drive a UI \"Archived\" tab without re-encoding the\n * query param.\n *\n * `archived` is omitted from the param type — it's always set to `true` here.\n */\n listArchived(params: Omit<ListObjectsParams, \"archived\"> = {}): Promise<{ objects: Object[] }> {\n return this.list({ ...params, archived: true })\n }\n\n /**\n * Fetch a single object including in-flight asset job state. Soft-deleted\n * (archived) rows are NOT returned by id — the route enforces\n * `deleted_at IS NULL` so archived objects 404 (uniform Pass 10 F-90b\n * \"not_found\" — does not leak the deleted vs non-existent distinction).\n */\n async get(id: string): Promise<ObjectDetail> {\n const res = await this.client.request<ObjectDetail>(\n \"GET\",\n `/v1/objects/${encodeURIComponent(id)}`,\n )\n // Normalize the wire `\"\"` caption (DB null / LLM sub-failure) → null so\n // consumers see the same `string | null` semantics as characters. New\n // object — never mutate the response.\n return { ...res, canonicalDescription: res.canonicalDescription || null }\n }\n\n /**\n * Create a new object. `name` + `nodeId` are required — the route 400s\n * otherwise. Returns the new row's id.\n *\n * Note: the underlying route is the same `POST /v1/objects` upsert that\n * powers `update()`. This convenience wrapper enforces the INSERT-required\n * fields at the type level and never sends an `id`.\n */\n create(data: CreateObjectInput): Promise<{ id: string }> {\n return this.client.request(\"POST\", \"/v1/objects\", { body: data })\n }\n\n /**\n * Update an object. Only the fields you pass are written — undefined keys\n * are NOT touched on the row. Worker-owned asset buckets are intentionally\n * not exposed on this surface (see `UpdateObjectInput` for the rationale).\n *\n * Optimistic-concurrency: pass `expectedUpdatedAt` to require the row's\n * `updated_at` still matches; on mismatch the route returns 409\n * `concurrent_modification` carrying the fresh `updatedAt`. The SDK\n * surfaces that as a generic `NodaroError` with the same code (per Phase\n * E1 calibration finding — error centralization in `throwApiError`).\n */\n update(id: string, data: UpdateObjectInput): Promise<UpdateObjectResult> {\n return this.client.request(\"POST\", \"/v1/objects\", {\n body: { id, ...data },\n })\n }\n\n /**\n * Soft-delete (archive) an object. The row is hidden from `list()` by\n * default but recoverable via `restore(id)` or visible under\n * `listArchived()`. Idempotent — repeating a delete on an already-archived\n * row is a no-op.\n */\n delete(id: string): Promise<{ success: true; archived: true }> {\n return this.client.request(\"DELETE\", `/v1/objects/${encodeURIComponent(id)}`)\n }\n\n /**\n * Hard-delete (permanent) an object — the row + every R2 asset it\n * references. Archived rows ONLY: active objects return 400 `not_archived`.\n * Call `delete()` first to archive, then `permanentDelete()` to destroy.\n *\n * Mirrors the `app_runs` permanent-delete pattern (archive-first) so a\n * stray SDK / curl caller cannot bypass the studio's archive-first UI\n * flow.\n */\n permanentDelete(id: string): Promise<{ success: true; permanent: true }> {\n return this.client.request(\"DELETE\", `/v1/objects/${encodeURIComponent(id)}`, {\n query: { permanent: \"true\" },\n })\n }\n\n /**\n * Un-archive an object. If the original name now collides (case-\n * insensitive) with an active row, the server auto-suffixes \"(restored)\"\n * and returns the effective name.\n */\n restore(id: string): Promise<{ id: string; name: string }> {\n return this.client.request(\"POST\", `/v1/objects/${encodeURIComponent(id)}/restore`)\n }\n\n /**\n * Fire `POST /v1/generate-object` to produce one or more candidate main\n * images. With `count > 1`, all jobs are reserved up-front before any\n * is enqueued — mid-batch failures roll back atomically.\n *\n * When `attachToObjectId` is set AND `count === 1`, the worker writes\n * the result directly to the row's `source_image_url`; otherwise you must\n * call `approveMainImage()` after picking a candidate.\n */\n async generate(data: GenerateObjectInput): Promise<GenerateObjectResult> {\n const res = await this.client.request<{ jobId?: string; jobIds?: string[] }>(\n \"POST\", \"/v1/generate-object\", { body: data },\n )\n // Tolerate the legacy `{ jobId }`-only shape (older server): synthesize jobIds.\n const jobIds = res.jobIds ?? (res.jobId ? [res.jobId] : [])\n return res.jobId ? { jobIds, jobId: res.jobId } : { jobIds }\n }\n\n /**\n * Fire `POST /v1/generate-object-asset` to produce a single variant.\n * When the studio path is set (`attachToObjectId` + `attachToColumn` +\n * `attachName`), the worker appends `{ name: attachName, url: <result> }`\n * to the named JSONB array column on completion.\n *\n * Note: `attachToColumn` is REQUIRED for `assetType === \"custom\"` — the\n * worker can't infer the bucket from the asset type. For canonical asset\n * types (`angles` / `materials` / `variations` / `motion`), the column is\n * derived automatically by the route.\n */\n generateAsset(\n data: GenerateObjectAssetInput,\n ): Promise<GenerateObjectAssetResult> {\n return this.client.request(\"POST\", \"/v1/generate-object-asset\", { body: data })\n }\n\n /**\n * Fire `POST /v1/generate-object-motion` to animate the object's main\n * image into a motion clip. Image-to-video, single clip per call; the\n * attach column is hardcoded to `motion_clips` server-side (objects have a\n * single motion bucket so the caller doesn't supply `attachToColumn`).\n * When the studio path is set (`attachToObjectId` + `attachName`), the\n * worker appends `{ name: attachName, url: <result> }` to the row's\n * `motion_clips` column on completion.\n *\n * Defaults: `provider` → `\"kling-turbo\"`, `aspectRatio` → `\"1:1\"` (set\n * server-side via `resolveObjectAspectRatio({ assetType: \"motion\" })`).\n */\n generateMotion(\n data: GenerateObjectMotionInput,\n ): Promise<GenerateObjectMotionResult> {\n return this.client.request(\"POST\", \"/v1/generate-object-motion\", { body: data })\n }\n\n /**\n * Approve a completed `generate-object` job as the object's main image.\n * Sets `source_image_url` and fires the LLM caption (Claude Sonnet vision)\n * inline. Returns the new main-image URL plus the caption.\n *\n * Caption-failure semantics: the route still sends `\"\"` on LLM sub-failure,\n * but the SDK normalizes `\"\"` → `null` here so `canonicalDescription` carries\n * the same `string | null` semantics as characters. The main image is still\n * set; call `recaption()` to retry.\n *\n * Optimistic-concurrency: pass `expectedUpdatedAt` to gate the update on\n * the row's current `updated_at`; on mismatch the route returns 409\n * `concurrent_modification` carrying the fresh token.\n */\n async approveMainImage(\n id: string,\n candidateJobId: string,\n expectedUpdatedAt?: string,\n ): Promise<ApproveObjectMainImageResult> {\n const res = await this.client.request<{ sourceImageUrl: string; canonicalDescription: string | null }>(\n \"POST\",\n `/v1/objects/${encodeURIComponent(id)}/approve-main-image`,\n { body: { candidateJobId, expectedUpdatedAt } },\n )\n // Normalize the wire `\"\"` (LLM sub-failure) → null; build a new object\n // rather than mutating the response.\n return { sourceImageUrl: res.sourceImageUrl, canonicalDescription: res.canonicalDescription || null }\n }\n\n /**\n * Re-fire the LLM caption against the object's current main image. 502s\n * on LLM failure (unlike `approveMainImage` which preserves the side-effect\n * and returns \"\"); returns 400 `main_image_required` if no main image is\n * set yet.\n *\n * The route is a pure idempotent retry — it does NOT accept an\n * `expectedUpdatedAt` token (per Phase E1 calibration finding: backend\n * route is idempotent retry, not gated on optimistic-concurrency).\n */\n recaption(id: string): Promise<RecaptionObjectResult> {\n return this.client.request(\n \"POST\",\n `/v1/objects/${encodeURIComponent(id)}/llm-caption`,\n )\n }\n}\n","import type { ObjectAspectRatio, TtsProvider, CreatureAttachColumn } from \"@nodaro/shared\"\nexport type { CreatureAttachColumn, ObjectAspectRatio as CreatureAspectRatio } from \"@nodaro/shared\"\nexport { CREATURE_ATTACH_COLUMNS, OBJECT_ASPECT_OPTIONS as CREATURE_ASPECT_OPTIONS, OBJECT_ASPECT_DEFAULTS as CREATURE_ASPECT_DEFAULTS } from \"@nodaro/shared\"\nimport type { NodaroClient } from \"../client.js\"\n\n/**\n * Creature voice — IDENTICAL shape + semantics to `Character[\"voice\"]` (the\n * \"talking creature\" stack reuses the character voice plumbing verbatim).\n * Render speech via `client.nodes.run(\"text-to-speech\", { text, voice:\n * voiceId, provider: ttsProvider, voiceType })`, then feed the audio + the\n * creature's `sourceImageUrl` to `client.nodes.run(\"lip-sync\", …)` (or\n * speech-to-video) for a talking-creature clip.\n */\nexport interface CreatureVoice {\n voiceId: string\n voiceName: string\n traits: string\n voiceType?: \"premade\" | \"library\" | \"custom\"\n /** Playable preview sample (client-played only). */\n previewUrl?: string\n /** Recommended TTS provider — send as `provider` on text-to-speech. */\n ttsProvider?: TtsProvider\n}\n\n/**\n * Re-export the shared `CreatureAttachColumn` union + its runtime tuple so SDK\n * consumers don't have to add `@nodaro/shared` as a second dependency just to\n * typecheck the `attachToColumn` field. Single source of truth lives in\n * `@nodaro/shared/entity-prompts` (`CREATURE_ATTACH_COLUMNS`).\n *\n * `CreatureAspectRatio` is re-exported as an alias of the shared\n * `ObjectAspectRatio` — `generateMotion`'s `aspectRatio` field is the 5-value\n * object enum (1:1 / 3:4 / 16:9 / 9:16 / 4:3). The creature motion route\n * deliberately REUSES `OBJECT_ASPECT_OPTIONS` server-side (a creature reference\n * clip is centered product-showcase framing, not cinematic 16:9), so the SDK\n * surfaces the same enum under a creature-friendly name. The runtime tuple is\n * re-exported as `CREATURE_ASPECT_OPTIONS` / `CREATURE_ASPECT_DEFAULTS` aliases.\n */\n\n/**\n * Creature asset-type enum — the kinds of variant a user can generate off a\n * creature's anchor main image. Mirrors the literal accepted by\n * `POST /v1/generate-creature-asset` (`backend/src/routes/generate-creature-asset.ts`).\n *\n * Delta vs `ObjectAssetType`: object's `materials` becomes `poses` (a creature\n * has poses, not materials), and there is NO `motion` value — creature motion\n * variants flow through the dedicated `/v1/generate-creature-motion` endpoint\n * (worker-side a different BullMQ job type). `custom` is the free-form bucket;\n * callers must supply `attachToColumn` explicitly since the worker can't infer\n * the destination from the asset type.\n *\n * The shared `@nodaro/shared` package does NOT export a `CREATURE_ASSET_TYPES`\n * tuple (the route validates an inline Zod enum), so the SDK defines its own\n * single-source-of-truth tuple here.\n */\nexport const CREATURE_ASSET_TYPES = [\"angles\", \"poses\", \"variations\", \"custom\"] as const\nexport type CreatureAssetType = (typeof CREATURE_ASSET_TYPES)[number]\n\n/**\n * Reference-photo kind discriminator — the mood-board roles a user can attach\n * to a creature. Mirrors the `reference_photos.kind` field accepted by\n * `backend/src/routes/creatures.ts` (the route accepts open strings; this SDK\n * type narrows to the 6 canonical roles surfaced by the Studio). `other` is\n * the free-form bucket.\n */\nexport type CreatureReferencePhotoKind =\n | \"front\"\n | \"side\"\n | \"detail\"\n | \"context\"\n | \"moodBoard\"\n | \"other\"\n\nexport interface CreatureReferencePhoto {\n url: string\n kind: CreatureReferencePhotoKind\n}\n\n/**\n * A creature record returned by Nodaro's REST API. Mirrors the camelCase\n * shape produced by `backend/src/routes/creatures.ts::toCamel()`.\n *\n * Asset buckets (`angles`, `poses`, `variations`, `motionClips`) are\n * independent JSONB arrays keyed by a human-readable variant name (e.g.\n * `\"front\"`, `\"walking\"`, `\"scarred\"`). Each entry's `url` points at an\n * R2-hosted asset.\n *\n * Creature delta vs object:\n * - `species` — free-text creature type (e.g. `\"dragon\"`, `\"wolf\"`). This is\n * the subject of the establishing-shot prompt and the primary creature\n * differentiator (object has no equivalent).\n * - `poses` (where object has `materials`) — the pose-variant asset bucket.\n * - `category` is free-text (NOT object's fixed 10-value enum) — a creature\n * can be any animal/type.\n *\n * Identity-foundation fields:\n * - `referencePhotos` — caller-supplied mood-board refs (cap 20).\n * - `canonicalDescription` — ~80–120-word LLM-authored visual caption,\n * populated by `approveMainImage()` / `recaption()`. The wire still sends\n * `\"\"` on caption sub-failure, but `get()` normalizes `\"\"` → `null` so\n * consumers see the same `string | null` semantics as characters.\n * - `styleLock` — whether asset gens should anchor to the canonical style\n * captured at approval time. Defaults to `true` on new rows.\n */\nexport interface Creature {\n id: string\n userId: string\n nodeId: string\n projectId: string | null\n name: string\n description: string | null\n /** Free-text creature type/species (e.g. \"dragon\", \"wolf\") — the creature\n * delta vs object. `null` when unset. */\n species: string | null\n category: string | null\n style: string | null\n sourceImageUrl: string | null\n /** MODEL_CATALOG image-model id the main image was generated with (or `null`).\n * Set on create + editable via the update route. */\n imageProvider: string | null\n angles: Array<{ name: string; url: string }>\n poses: Array<{ name: string; url: string }>\n variations: Array<{ name: string; url: string }>\n motionClips: Array<{ name: string; url: string }>\n /** Named Creature Boards — dense reference sheets, one per variant/mood\n * (the `generate-image/creature-board` factory preset rendered from the\n * creature's images). A first-class bucket: community publish snapshots it\n * and clone hands the consumer their own copy to extend. Defaults to `[]`.\n * `type` marks an image-collage `\"identity\"` sheet vs a plain `\"looks\"`\n * board; `sourceImages` are the R2 URLs it was collaged from. Both optional\n * + backward-compatible (legacy boards have neither). */\n boards?: Array<{ name: string; url: string; type?: \"looks\" | \"identity\"; sourceImages?: string[] }> | null\n /** The creature's voice (the \"talking creature\" stack) — same shape and\n * flow as `Character[\"voice\"]`. `null` when no voice is selected. */\n voice?: CreatureVoice | null\n referencePhotos: CreatureReferencePhoto[]\n /** `null` when no caption is set (or the LLM caption sub-failed) — the wire\n * sends `\"\"`, normalized to `null` in `get()` to match character semantics. */\n canonicalDescription: string | null\n styleLock: boolean\n /** The user's chosen DEFAULT asset take per variant (Studio version history).\n * OPAQUE map: key `\"<bucket>:<variant>\"` (e.g. `\"angles:front\"`) → the chosen\n * asset URL (one already present in that bucket). Stored verbatim — keys are\n * NOT normalized; soft-capped server-side at 200 keys / 2048-char values\n * (overflow dropped silently). Defaults to `{}`. */\n selectedAssetByVariant?: Record<string, string> | null\n deletedAt: string | null\n createdAt: string\n updatedAt: string\n}\n\n/**\n * GET /v1/creatures/:id may append a `pendingJobs` bucket the studio uses to\n * rehydrate spinners after a reload. Optional on the SDK surface — it doesn't\n * appear on `list()` rows.\n */\nexport interface CreatureDetail extends Creature {\n pendingJobs?: Array<{\n jobId: string\n assetType: string\n name: string\n status: string\n }>\n}\n\n/**\n * Body for `client.creatures.create()`. Mirrors the INSERT branch of\n * `upsertCreatureBody` in `backend/src/routes/creatures.ts`. `name` + `nodeId`\n * are required on create.\n */\nexport interface CreateCreatureInput {\n nodeId: string\n name: string\n description?: string\n /** Free-text creature type/species (the creature delta vs object). */\n species?: string\n /** Free-text category (NOT object's fixed enum — a creature can be anything). */\n category?: string\n style?: string\n workflowId?: string\n projectId?: string\n sourceImageUrl?: string\n /** Persistent image-model id (a MODEL_CATALOG image model). Validated\n * server-side — unknown / non-image / \"\" is stored as `null`. */\n imageProvider?: string | null\n referencePhotos?: CreatureReferencePhoto[]\n canonicalDescription?: string\n styleLock?: boolean\n /** The user's chosen DEFAULT asset take per variant. OPAQUE\n * `\"<bucket>:<variant>\"` → chosen-URL map; a write REPLACES the whole map.\n * Keys stored verbatim; soft-capped server-side at 200 keys / 2048-char values. */\n selectedAssetByVariant?: Record<string, string>\n /** Initial voice selection (see `CreatureVoice`). Omit for a voiceless creature. */\n voice?: CreatureVoice | null\n}\n\n/**\n * Body for `client.creatures.update()`. Mirrors the UPDATE branch of\n * `upsertCreatureBody` in `backend/src/routes/creatures.ts`.\n *\n * Worker-owned asset buckets (`angles` / `poses` / `variations` /\n * `motionClips`) are deliberately omitted — the route drops them on UPDATE\n * so a Studio auto-save with a stale snapshot cannot clobber the worker's\n * atomic `append_creature_asset()` writes.\n *\n * `expectedUpdatedAt` is the optimistic-concurrency token: when present, the\n * UPDATE only succeeds if the row's `updated_at` still matches; on mismatch\n * the route returns 409 so the studio can re-fetch + merge.\n */\nexport interface UpdateCreatureInput {\n name?: string\n description?: string\n /** Free-text creature type/species (the creature delta vs object). */\n species?: string\n category?: string\n style?: string\n sourceImageUrl?: string\n /** Persistent image-model id (a MODEL_CATALOG image model). Validated\n * server-side — unknown / non-image / \"\" is stored as `null`. */\n imageProvider?: string | null\n referencePhotos?: CreatureReferencePhoto[]\n canonicalDescription?: string\n styleLock?: boolean\n /** The user's chosen DEFAULT asset take per variant. OPAQUE\n * `\"<bucket>:<variant>\"` → chosen-URL map; a write REPLACES the whole map\n * (omit to leave untouched). Keys stored verbatim; soft-capped server-side\n * at 200 keys / 2048-char values. */\n selectedAssetByVariant?: Record<string, string>\n /** Named Creature Boards (see `Creature.boards`) — whole-array replace,\n * USER-owned (unlike the worker-owned buckets it flows through UPDATE).\n * Server caps: 24 boards, 200-char names, 30 sourceImages per board. */\n boards?: Array<{ name: string; url: string; type?: \"looks\" | \"identity\"; sourceImages?: string[] }>\n /** Voice selection (see `CreatureVoice`) — whole-object replace; pass\n * `null` to clear the voice, omit to leave untouched. */\n voice?: CreatureVoice | null\n expectedUpdatedAt?: string\n}\n\nexport interface UpdateCreatureResult {\n id: string\n updatedAt: string\n}\n\n/**\n * Combined create + update body (parameter for both branches). Exported for\n * callers that want to drive a single `upsert` flow without picking between\n * `Create*` and `Update*`. Mirrors the `upsertCreatureBody` Zod schema in\n * `backend/src/routes/creatures.ts`. `nodeId` + `name` are required on INSERT;\n * `id` flips the route into UPDATE mode.\n */\nexport interface UpsertCreatureInput extends CreateCreatureInput {\n id?: string\n expectedUpdatedAt?: string\n}\n\nexport type UpsertCreatureResult = { id: string } | UpdateCreatureResult\n\nexport interface ListCreaturesParams {\n /** When true, return archived creatures instead of active ones. */\n archived?: boolean\n /** Optional project filter — server-scoped to the caller's user. */\n projectId?: string\n}\n\n/**\n * Input for `client.creatures.generate()` — fires the\n * `POST /v1/generate-creature` route. Produces 1–10 candidate\n * main images; each lands as one `jobs` row in `pending` state and is then\n * enqueued for the worker.\n *\n * When `attachToCreatureId` is set AND `count === 1`, the worker writes the\n * resulting URL directly to `creatures.source_image_url` on completion —\n * caller doesn't need a separate `approveMainImage` call. Multi-candidate\n * batches MUST go through explicit approval so the user picks the winner.\n *\n * `seedPromptHint` flows the parameter-picker's prompt fragment through to the\n * worker so a catalog selection (e.g. \"armored frost dragon\") gets appended to\n * the generated prompt context.\n */\nexport interface GenerateCreatureInput {\n name: string\n description?: string\n userPrompt?: string\n /** Free-text creature type/species (the creature delta vs object). */\n species?: string\n category?: string\n style?: \"realistic\" | \"anime\" | \"3d-pixar\" | \"illustration\"\n sourceImageUrl?: string\n provider?: string\n /** Number of candidate images to generate (1–10; server-validated). */\n count?: number\n /** Auto-attach the result to this creature row (single-candidate only). */\n attachToCreatureId?: string\n /** Parameter-picker prompt-fragment pass-through. */\n seedPromptHint?: string\n /** Optional name to set on the attached row alongside the main image. */\n attachName?: string\n /** Optimistic-concurrency token for the single-candidate auto-attach path. */\n expectedUpdatedAt?: string\n}\n\n/**\n * `generate()` response — `jobIds` is ALWAYS present (the harmonized contract,\n * matching characters). `jobId` is a deprecated back-compat alias populated only\n * on `count === 1`; prefer `jobIds`. (Will be removed on the next major.)\n */\nexport interface GenerateCreatureResult {\n jobIds: string[]\n /** @deprecated count===1 back-compat alias — use `jobIds`. */\n jobId?: string\n}\n\n/**\n * Input for `client.creatures.generateAsset()` — fires the\n * `POST /v1/generate-creature-asset` route. Produces a single\n * angles / poses / variations / custom variant.\n *\n * When all three studio-path fields are set (`attachToCreatureId` +\n * `attachToColumn` + `attachName`), the worker appends\n * `{ name: attachName, url: <result> }` to the named JSONB array column on\n * the user's creature row on completion. `attachToColumn` is REQUIRED for\n * `assetType === \"custom\"` — the worker can't infer the bucket from the\n * asset type.\n */\nexport interface GenerateCreatureAssetInput {\n assetType: CreatureAssetType\n variant: string\n name: string\n description?: string\n userPrompt?: string\n category?: string\n style?: string\n sourceImageUrl?: string\n provider?: string\n attachToCreatureId?: string\n attachToColumn?: CreatureAttachColumn\n attachName?: string\n /** Parameter-picker prompt-fragment pass-through. */\n seedPromptHint?: string\n}\n\nexport interface GenerateCreatureAssetResult {\n jobId: string\n}\n\n/**\n * Input for `client.creatures.generateMotion()` — fires the\n * `POST /v1/generate-creature-motion` route. Produces a single motion clip\n * (idle, prowl, attack, etc.) animated FROM a static creature-shot image.\n *\n * Mirrors `client.objects.generateMotion()` — the creature motion route reuses\n * the entity-agnostic object motion helpers server-side. The route hardcodes\n * the attach column to `motion_clips` — callers supply `attachToCreatureId` +\n * `attachName` only.\n *\n * `sourceImageUrl` is REQUIRED — image-to-video needs a source frame and the\n * route has no fallback (the studio path supplies the canonical creature-shot\n * URL explicitly).\n *\n * Defaults vs location:\n * - `provider` defaults to `\"kling-turbo\"` (not location's `\"kling\"`)\n * - `aspectRatio` defaults to `\"1:1\"` server-side via\n * `resolveObjectAspectRatio({ assetType: \"motion\" })` — creatures use\n * centered reference framing, not cinematic establishing shots.\n *\n * When the studio path is set (`attachToCreatureId` + `attachName`), the\n * worker appends `{ name: attachName, url: <result> }` to the creature row's\n * `motion_clips` JSONB column on completion.\n */\nexport interface GenerateCreatureMotionInput {\n motionPrompt: string\n sourceImageUrl: string\n provider?: string\n name: string\n /** Source clip URL — when set, worker routes to video-to-video refine. */\n refineFromVideoUrl?: string\n category?: string\n style?: \"realistic\" | \"anime\" | \"3d-pixar\" | \"illustration\"\n canonicalDescription?: string\n /** Parameter-picker prompt-fragment pass-through. */\n seedPromptHint?: string\n attachToCreatureId?: string\n attachName?: string\n /**\n * Optional aspect ratio override. Defaults to 1:1 server-side. One of the\n * 5-value `CreatureAspectRatio` union (1:1 / 3:4 / 16:9 / 9:16 / 4:3) — the\n * creature route reuses the object aspect enum.\n */\n aspectRatio?: ObjectAspectRatio\n /**\n * Optional clip duration in seconds. Validated server-side against the chosen\n * provider's allowed durations (e.g. kling 5/10, wan-i2v 5/10/15); omitted →\n * the model's own default (no behavior change). Mirrors generate-video's\n * per-model i2v duration lever.\n */\n duration?: number\n}\n\nexport interface GenerateCreatureMotionResult {\n jobId: string\n}\n\nexport interface ApproveCreatureMainImageResult {\n sourceImageUrl: string\n /**\n * LLM-authored caption. `null` when the LLM caption sub-failed — the wire\n * sends `\"\"`, normalized to `null` here so consumers see the same\n * `string | null` semantics as characters. The main image is still set; call\n * `recaption()` to retry.\n */\n canonicalDescription: string | null\n}\n\nexport interface RecaptionCreatureResult {\n canonicalDescription: string\n}\n\nexport class CreaturesResource {\n constructor(private client: NodaroClient) {}\n\n /**\n * List the caller's creatures. By default returns active creatures only;\n * pass `archived: true` to fetch soft-deleted rows for an \"archive\" view.\n * Optional `projectId` scopes the result to a single project.\n */\n list(params: ListCreaturesParams = {}): Promise<{ creatures: Creature[] }> {\n const query: Record<string, string | undefined> = {}\n if (params.archived) query.archived = \"true\"\n if (params.projectId) query.projectId = params.projectId\n return this.client.request(\"GET\", \"/v1/creatures\", { query })\n }\n\n /**\n * Convenience wrapper for `list({ archived: true })`. Returns soft-deleted\n * rows so callers can drive a UI \"Archived\" tab without re-encoding the\n * query param.\n *\n * `archived` is omitted from the param type — it's always set to `true` here.\n */\n listArchived(params: Omit<ListCreaturesParams, \"archived\"> = {}): Promise<{ creatures: Creature[] }> {\n return this.list({ ...params, archived: true })\n }\n\n /**\n * Fetch a single creature including in-flight asset job state. Soft-deleted\n * (archived) rows are NOT returned by id — the route enforces\n * `deleted_at IS NULL` so archived creatures 404 (uniform \"not_found\" — does\n * not leak the deleted vs non-existent distinction).\n */\n async get(id: string): Promise<CreatureDetail> {\n const res = await this.client.request<CreatureDetail>(\n \"GET\",\n `/v1/creatures/${encodeURIComponent(id)}`,\n )\n // Normalize the wire `\"\"` caption (DB null / LLM sub-failure) → null so\n // consumers see the same `string | null` semantics as characters. New\n // object — never mutate the response.\n return { ...res, canonicalDescription: res.canonicalDescription || null }\n }\n\n /**\n * Create a new creature. `name` + `nodeId` are required — the route 400s\n * otherwise. Returns the new row's id.\n *\n * Note: the underlying route is the same `POST /v1/creatures` upsert that\n * powers `update()`. This convenience wrapper enforces the INSERT-required\n * fields at the type level and never sends an `id`.\n */\n create(data: CreateCreatureInput): Promise<{ id: string }> {\n return this.client.request(\"POST\", \"/v1/creatures\", { body: data })\n }\n\n /**\n * Update a creature. Only the fields you pass are written — undefined keys\n * are NOT touched on the row. Worker-owned asset buckets are intentionally\n * not exposed on this surface (see `UpdateCreatureInput` for the rationale).\n *\n * Optimistic-concurrency: pass `expectedUpdatedAt` to require the row's\n * `updated_at` still matches; on mismatch the route returns 409\n * `concurrent_modification` carrying the fresh `updatedAt`. The SDK\n * surfaces that as a generic `NodaroError` with the same code.\n */\n update(id: string, data: UpdateCreatureInput): Promise<UpdateCreatureResult> {\n return this.client.request(\"POST\", \"/v1/creatures\", {\n body: { id, ...data },\n })\n }\n\n /**\n * Soft-delete (archive) a creature. The row is hidden from `list()` by\n * default but recoverable via `restore(id)` or visible under\n * `listArchived()`. Idempotent — repeating a delete on an already-archived\n * row is a no-op.\n */\n delete(id: string): Promise<{ success: true; archived: true }> {\n return this.client.request(\"DELETE\", `/v1/creatures/${encodeURIComponent(id)}`)\n }\n\n /**\n * Hard-delete (permanent) a creature — the row + every R2 asset it\n * references. Archived rows ONLY: active creatures return 400 `not_archived`.\n * Call `delete()` first to archive, then `permanentDelete()` to destroy.\n *\n * Mirrors the `app_runs` permanent-delete pattern (archive-first) so a\n * stray SDK / curl caller cannot bypass the studio's archive-first UI\n * flow.\n */\n permanentDelete(id: string): Promise<{ success: true; permanent: true }> {\n return this.client.request(\"DELETE\", `/v1/creatures/${encodeURIComponent(id)}`, {\n query: { permanent: \"true\" },\n })\n }\n\n /**\n * Un-archive a creature. If the original name now collides (case-\n * insensitive) with an active row, the server auto-suffixes \"(restored)\"\n * and returns the effective name.\n */\n restore(id: string): Promise<{ id: string; name: string }> {\n return this.client.request(\"POST\", `/v1/creatures/${encodeURIComponent(id)}/restore`)\n }\n\n /**\n * Fire `POST /v1/generate-creature` to produce one or more candidate main\n * images. With `count > 1`, all jobs are reserved up-front before any\n * is enqueued — mid-batch failures roll back atomically.\n *\n * When `attachToCreatureId` is set AND `count === 1`, the worker writes\n * the result directly to the row's `source_image_url`; otherwise you must\n * call `approveMainImage()` after picking a candidate.\n */\n async generate(data: GenerateCreatureInput): Promise<GenerateCreatureResult> {\n const res = await this.client.request<{ jobId?: string; jobIds?: string[] }>(\n \"POST\", \"/v1/generate-creature\", { body: data },\n )\n // Tolerate the legacy `{ jobId }`-only shape (older server): synthesize jobIds.\n const jobIds = res.jobIds ?? (res.jobId ? [res.jobId] : [])\n return res.jobId ? { jobIds, jobId: res.jobId } : { jobIds }\n }\n\n /**\n * Fire `POST /v1/generate-creature-asset` to produce a single variant.\n * When the studio path is set (`attachToCreatureId` + `attachToColumn` +\n * `attachName`), the worker appends `{ name: attachName, url: <result> }`\n * to the named JSONB array column on completion.\n *\n * Note: `attachToColumn` is REQUIRED for `assetType === \"custom\"` — the\n * worker can't infer the bucket from the asset type. For canonical asset\n * types (`angles` / `poses` / `variations`), the column is derived\n * automatically by the route.\n */\n generateAsset(\n data: GenerateCreatureAssetInput,\n ): Promise<GenerateCreatureAssetResult> {\n return this.client.request(\"POST\", \"/v1/generate-creature-asset\", { body: data })\n }\n\n /**\n * Fire `POST /v1/generate-creature-motion` to animate the creature's main\n * image into a motion clip. Image-to-video, single clip per call; the\n * attach column is hardcoded to `motion_clips` server-side (creatures have a\n * single motion bucket so the caller doesn't supply `attachToColumn`).\n * When the studio path is set (`attachToCreatureId` + `attachName`), the\n * worker appends `{ name: attachName, url: <result> }` to the row's\n * `motion_clips` column on completion.\n *\n * Defaults: `provider` → `\"kling-turbo\"`, `aspectRatio` → `\"1:1\"` (set\n * server-side via `resolveObjectAspectRatio({ assetType: \"motion\" })`).\n */\n generateMotion(\n data: GenerateCreatureMotionInput,\n ): Promise<GenerateCreatureMotionResult> {\n return this.client.request(\"POST\", \"/v1/generate-creature-motion\", { body: data })\n }\n\n /**\n * Approve a completed `generate-creature` job as the creature's main image.\n * Sets `source_image_url` and fires the LLM caption (Claude Sonnet vision)\n * inline. Returns the new main-image URL plus the caption.\n *\n * Caption-failure semantics: the route still sends `\"\"` on LLM sub-failure,\n * but the SDK normalizes `\"\"` → `null` here so `canonicalDescription` carries\n * the same `string | null` semantics as characters. The main image is still\n * set; call `recaption()` to retry.\n *\n * Optimistic-concurrency: pass `expectedUpdatedAt` to gate the update on\n * the row's current `updated_at`; on mismatch the route returns 409\n * `concurrent_modification` carrying the fresh token.\n */\n async approveMainImage(\n id: string,\n candidateJobId: string,\n expectedUpdatedAt?: string,\n ): Promise<ApproveCreatureMainImageResult> {\n const res = await this.client.request<{ sourceImageUrl: string; canonicalDescription: string | null }>(\n \"POST\",\n `/v1/creatures/${encodeURIComponent(id)}/approve-main-image`,\n { body: { candidateJobId, expectedUpdatedAt } },\n )\n // Normalize the wire `\"\"` (LLM sub-failure) → null; build a new object\n // rather than mutating the response.\n return { sourceImageUrl: res.sourceImageUrl, canonicalDescription: res.canonicalDescription || null }\n }\n\n /**\n * Re-fire the LLM caption against the creature's current main image. 502s\n * on LLM failure (unlike `approveMainImage` which preserves the side-effect\n * and returns \"\"); returns 400 `main_image_required` if no main image is\n * set yet.\n *\n * The route is a pure idempotent retry — it does NOT accept an\n * `expectedUpdatedAt` token (backend route is idempotent retry, not gated on\n * optimistic-concurrency).\n */\n recaption(id: string): Promise<RecaptionCreatureResult> {\n return this.client.request(\n \"POST\",\n `/v1/creatures/${encodeURIComponent(id)}/llm-caption`,\n )\n }\n}\n","import type { NodaroClient } from \"../client.js\"\nimport type { PipelineInput, PipelineStageName, PipelineStatus, PipelineMode, SubGateName, ChatEnabledStage, ProposedChange } from \"@nodaro/shared\"\n\nexport type {\n PipelineInput,\n PipelineStageName,\n PipelineStatus,\n PipelineMode,\n SubGateName,\n ChatEnabledStage,\n ProposedChange,\n}\n\n/**\n * Owner-scoped pipeline record returned by `get` / `list` (the server strips\n * `user_id`). Mirrors the public field set of `GET /v1/pipelines/:id`.\n */\nexport interface PipelineRecord {\n id: string\n status: PipelineStatus\n current_stage: string | null\n spent_credits: number\n reserved_credits: number\n upfront_credit_estimate: number\n branched_from_pipeline_id: string | null\n branched_from_stage: string | null\n mode: PipelineMode | null\n failure_reason: string | null\n current_progress_message: string | null\n}\n\n/** One stage currently awaiting approval (from `pendingApprovals`). */\nexport interface PendingApproval {\n stage_name: PipelineStageName\n /** Stage output snapshot; shape varies by stage. */\n output: unknown\n}\n\n/**\n * Assembled timeline (`GET /v1/pipelines/:id/timeline`) — ordered scene\n * composites + their durations, plus optional music/narration and live\n * per-shot animate progress. The data the studio turns into a render.\n */\nexport interface PipelineTimeline {\n fps: number\n width: number\n height: number\n scenes: Array<{ compositeUrl: string; durationSeconds: number }>\n musicUrl?: string\n narrationUrl?: string\n animateProgress?: {\n totalShots: number\n shotsDone: number\n percent: number\n }\n}\n\nexport interface BranchPipelineInput {\n /** The stage to re-run from. Upstream stages are cloned as approved. */\n fromStage: PipelineStageName\n}\n\nexport interface BranchPipelineResult {\n /** The id of the newly created pipeline. */\n pipelineId: string\n /** Stage names that were cloned as 'approved' (stages before `fromStage`). */\n clonedStages: string[]\n /** Number of entity rows cloned into the new pipeline. */\n clonedEntities: number\n}\n\n/**\n * A single chat turn returned by `getStageChat`. Mirrors the\n * `pipeline_chat_turns` row shape selected by the GET handler.\n *\n * `@nodaro/shared` does not yet export a Zod schema for the full row; it only\n * exports `ChatTurnResponseSchema` (the LLM response shape) and the\n * `ProposedChange` discriminated union. Define the wire-format row locally so\n * callers get end-to-end typing today without re-shaping the backend payload.\n */\nexport interface ChatTurn {\n id: string\n turn_n: number\n role: \"user\" | \"assistant\"\n content: string\n proposed_change: ProposedChange | null\n llm_call_id: string | null\n applied_to_attempt_id: string | null\n created_at: string\n}\n\n/**\n * Result of `chatStage` — the assistant turn that was just persisted. The\n * route always echoes the assistant message back so callers can render the\n * reply without a follow-up GET (SSE is the secondary delivery channel).\n */\nexport interface ChatStageResult {\n turnId: string\n role: \"assistant\"\n content: string\n proposed_change: ProposedChange | null\n}\n\n/**\n * Result of `applyChatProposal`. Discriminated on `applied`:\n *\n * - `applied: true` — `applyStageEdit` validated + persisted a new attempt and\n * flipped the stage to approved. `newOutput` is the post-patch artifact.\n * - `applied: false` — recoverable failure (schema_invalid or\n * reference_integrity_failed); the backend has already inserted a follow-up\n * assistant turn with a human-readable hint so the user can iterate via\n * chat. Hard failures throw via the client's error pipeline (409).\n */\nexport type ApplyChatProposalResult =\n | { applied: true; attemptId: string; newOutput: unknown }\n | { applied: false; error: { code: string; detail?: unknown } }\n\nexport class PipelinesResource {\n constructor(private client: NodaroClient) {}\n\n /**\n * Start a new pipeline (headless film generation) — the programmatic\n * equivalent of the studio's \"Create film\". In Auto mode the engine\n * self-advances to completion; poll {@link get} for status and\n * {@link getTimeline} for the assembled output. In manual/guided mode, drive\n * it with {@link pendingApprovals} + {@link approveStage} /\n * {@link approveSubGate}.\n *\n * Requires `pipelines:execute` scope. Returns the new pipeline id.\n */\n create(input: PipelineInput): Promise<{ id: string }> {\n return this.client.request(\"POST\", \"/v1/pipelines\", { body: input })\n }\n\n /**\n * Fetch current pipeline state: `status`, `current_stage`, credit counters,\n * `mode`, and `failure_reason` (set when `status='failed'`). Poll this to\n * track a headless Auto run to completion. Requires `pipelines:read`.\n */\n get(id: string): Promise<PipelineRecord> {\n return this.client.request(\n \"GET\",\n `/v1/pipelines/${encodeURIComponent(id)}`,\n )\n }\n\n /** List the caller's pipelines (most recent first). Requires `pipelines:read`. */\n list(): Promise<PipelineRecord[]> {\n return this.client.request(\"GET\", \"/v1/pipelines\")\n }\n\n /**\n * Cancel a running pipeline. Unspent reserved credits refund. Idempotent on\n * an already-terminal pipeline. Requires `pipelines:execute`.\n */\n cancel(id: string): Promise<{ ok: true }> {\n return this.client.request(\n \"POST\",\n `/v1/pipelines/${encodeURIComponent(id)}/cancel`,\n { body: {} },\n )\n }\n\n /**\n * Stages currently `awaiting_approval`. Empty in a clean Auto run (the engine\n * self-approves); populated in manual/guided mode at each gate. Requires\n * `pipelines:read`.\n */\n pendingApprovals(id: string): Promise<PendingApproval[]> {\n return this.client.request(\n \"GET\",\n `/v1/pipelines/${encodeURIComponent(id)}/pending-approvals`,\n )\n }\n\n /**\n * Approve a stage so the engine advances to the next one. An optional `edits`\n * JSON-Patch is applied to the stage output before approval. Requires\n * `pipelines:approve`.\n */\n approveStage(\n id: string,\n stage: PipelineStageName,\n edits?: unknown,\n ): Promise<{ ok: true }> {\n return this.client.request(\n \"POST\",\n `/v1/pipelines/${encodeURIComponent(id)}/stages/${encodeURIComponent(stage)}/approve`,\n { body: edits ? { edits } : {} },\n )\n }\n\n /**\n * Reject a stage with feedback; the engine re-runs it incorporating the note.\n * Requires `pipelines:approve`.\n */\n rejectStage(\n id: string,\n stage: PipelineStageName,\n feedback: string,\n ): Promise<{ ok: true }> {\n return this.client.request(\n \"POST\",\n `/v1/pipelines/${encodeURIComponent(id)}/stages/${encodeURIComponent(stage)}/reject`,\n { body: { feedback } },\n )\n }\n\n /**\n * Approve a Stage-7 sub-gate (`dialogue_recheck` / `silent_cut`) so the\n * orchestrator resumes from the next sub-step. Requires `pipelines:approve`.\n */\n approveSubGate(\n id: string,\n gate: SubGateName,\n ): Promise<{ ok: true; gate: SubGateName; resumed_at: string }> {\n return this.client.request(\n \"POST\",\n `/v1/pipelines/${encodeURIComponent(id)}/sub-gates/${encodeURIComponent(gate)}/approve`,\n { body: {} },\n )\n }\n\n /**\n * Read a single stage's `status`, `output`, and `critic_feedback`. Useful for\n * inspecting the script/plan before approving. Requires `pipelines:read`.\n */\n getStage(\n id: string,\n stage: PipelineStageName,\n ): Promise<{ status: string; output: unknown; critic_feedback: unknown }> {\n return this.client.request(\n \"GET\",\n `/v1/pipelines/${encodeURIComponent(id)}/stages/${encodeURIComponent(stage)}`,\n )\n }\n\n /**\n * Assembled timeline — ordered scene composites + durations + audio URLs +\n * live animate progress. The output a headless caller renders or hands to a\n * downstream editor. Requires `pipelines:read`.\n */\n getTimeline(id: string): Promise<PipelineTimeline> {\n return this.client.request(\n \"GET\",\n `/v1/pipelines/${encodeURIComponent(id)}/timeline`,\n )\n }\n\n /**\n * Branch a completed pipeline into a new pipeline that re-runs from the\n * given stage. The original pipeline's upstream stages and entities are\n * cloned into the new pipeline; downstream stages are created by the\n * orchestrator as it advances.\n *\n * Requires `pipelines:execute` scope.\n * The source pipeline must have `status='completed'`.\n *\n * @returns 201 with `{ pipelineId, clonedStages, clonedEntities }`.\n */\n branch(id: string, input: BranchPipelineInput): Promise<BranchPipelineResult> {\n return this.client.request(\n \"POST\",\n `/v1/pipelines/${encodeURIComponent(id)}/branch`,\n { body: input },\n )\n }\n\n /**\n * Send a chat message to the Showrunner Refinement Director (Guided Mode).\n * Persists user + assistant turns; returns the assistant's reply and an\n * optional `proposed_change` the user can `applyChatProposal` to commit.\n *\n * Requires `pipelines:approve` scope. The pipeline must have\n * `mode='guided'` and the stage must be `awaiting_approval`.\n *\n * Only the Script stage ships a wired specialist in Phase 1D.2b — the other\n * chat-enabled stages (`shot_list`, `post_merge`) return 501 until 1D.2d.\n */\n chatStage(\n pipelineId: string,\n stage: ChatEnabledStage,\n message: string,\n ): Promise<ChatStageResult> {\n return this.client.request(\n \"POST\",\n `/v1/pipelines/${encodeURIComponent(pipelineId)}/stages/${encodeURIComponent(stage)}/chat`,\n { body: { message } },\n )\n }\n\n /**\n * Accept a proposed change from a prior assistant turn. Routes through\n * `applyStageEdit` (validates JSON Patch + per-stage schema +\n * reference-integrity, inserts a new pipeline_stage_attempts row, CAS-flips\n * the stage to approved, emits `chat:proposal_applied` SSE).\n *\n * Requires `pipelines:approve` scope.\n *\n * Returns `{ applied: true, attemptId, newOutput }` on success, or\n * `{ applied: false, error }` on recoverable failures (the backend already\n * inserted a follow-up assistant turn with a hint). Hard failures\n * (`patch_invalid`, `stage_not_awaiting`) throw via the standard error\n * pipeline (HTTP 409).\n */\n applyChatProposal(\n pipelineId: string,\n stage: ChatEnabledStage,\n turnId: string,\n ): Promise<ApplyChatProposalResult> {\n return this.client.request(\n \"POST\",\n `/v1/pipelines/${encodeURIComponent(pipelineId)}/stages/${encodeURIComponent(stage)}/chat/turns/${encodeURIComponent(turnId)}/apply`,\n { body: {} },\n )\n }\n\n /**\n * Fetch the chat history for a stage. Returns an empty array when no turns\n * exist yet (e.g., stage has not been started or the user hasn't sent any\n * messages). Used by the frontend chat panel on initial mount; subsequent\n * updates arrive via SSE (`chat:turn` events).\n *\n * Requires `pipelines:read` scope.\n */\n getStageChat(\n pipelineId: string,\n stage: ChatEnabledStage,\n ): Promise<{ turns: ChatTurn[] }> {\n return this.client.request(\n \"GET\",\n `/v1/pipelines/${encodeURIComponent(pipelineId)}/stages/${encodeURIComponent(stage)}/chat`,\n )\n }\n}\n","import type { NodaroClient } from \"../client.js\"\nimport type { ReduceStrategyId, ReduceMeta } from \"@nodaro/shared\"\n\n// Re-export the canonical types from `@nodaro/shared` (single source of\n// truth — the registry lives at `packages/shared/src/reduce-strategy-registry.ts`).\n// `@nodaro/shared` is already a hard dep of this package, so there's no\n// bundle-size cost to importing from it.\nexport type { ReduceStrategyId, ReduceMeta }\n\nexport interface ReduceInput {\n /** Which fan-in strategy to run. */\n strategyId: ReduceStrategyId\n /**\n * Strategy-specific config. Defaults to `{}` server-side, which uses every\n * strategy's `defaultConfig`. Schemas (from `@nodaro/shared`):\n * - `pick-best-llm`: `{ criteria: string, inputKind?: \"text\" | \"image-url\" }`\n * - `concat`: `{ separator?: string }`\n * - `vote`: `{ caseSensitive?: boolean }`\n * - `merge-json`: `{ strategy?: \"deep\" | \"shallow\" }`\n * - `first-non-empty`, `count`: `{}`\n */\n strategyConfig?: Record<string, unknown>\n /** Up to 1000 input strings (URLs, text fragments, etc.). */\n inputs: string[]\n /**\n * Optional — associates this reduce run with a workflow execution. The\n * server reads this from the body before Zod strips it (same path as\n * other job-creating routes).\n */\n workflowId?: string\n}\n\nexport interface ReduceResult {\n jobId: string\n /**\n * Stringified result — for `count` this is a numeric string, for\n * `merge-json` this is the JSON-encoded merged object, otherwise the\n * chosen / joined text.\n */\n output: string\n meta: ReduceMeta\n}\n\nexport class ReduceResource {\n constructor(private client: NodaroClient) {}\n\n /**\n * Run the Reduce (fan-in) node directly — useful for scripted batch\n * scoring, picking the best of N generations outside a workflow, or\n * one-shot programmatic merges.\n *\n * Throws `NodaroError` on 4xx/5xx responses (e.g. `code: \"no_valid_inputs\"`\n * with status 400 when every input is empty / whitespace; the underlying\n * `EmptyInputError` is mapped to a 400 server-side).\n */\n run(input: ReduceInput): Promise<ReduceResult> {\n return this.client.request(\"POST\", \"/v1/reduce\", {\n body: {\n strategyId: input.strategyId,\n strategyConfig: input.strategyConfig ?? {},\n inputs: input.inputs,\n ...(input.workflowId !== undefined\n ? { workflowId: input.workflowId }\n : {}),\n },\n })\n }\n}\n","import type { NodaroClient } from \"../client.js\"\nimport type { WizardQuestion, WizardSelection, RecommendedModel, WizardNodeContext } from \"@nodaro/prompts\"\nexport type { WizardQuestion, WizardOption, WizardSelection, RecommendedModel, WizardNodeContext } from \"@nodaro/prompts\"\n\n// Re-export the canonical wizard types (SSOT in @nodaro/shared).\n\ninterface CommonInput {\n nodeType: string\n provider?: string\n style?: string\n aspectRatio?: string\n duration?: number\n llmModel?: string\n /** Reasoning effort level (model-dependent). */\n reasoningEffort?: string\n nodeContext?: WizardNodeContext\n userPreference?: string\n /** Associates this call with a workflow execution. Read server-side before Zod. */\n workflowId?: string\n}\n\nexport interface AnalyzeInput extends CommonInput {\n /** The user's rough idea. Omit to build questions from scratch. */\n prompt?: string\n}\nexport interface AnalyzeResult {\n jobId: string\n questions: WizardQuestion[]\n}\n\nexport interface GenerateInput extends CommonInput {\n /** The chosen answers from analyze. */\n selections: WizardSelection[]\n /** The user's original rough idea, woven into the generated prompt. */\n originalPrompt?: string\n}\n\nexport interface EnhanceInput extends CommonInput {\n /** The rough idea to improve one-shot. Omit to build from scratch. */\n prompt?: string\n}\n\nexport interface PromptResult {\n jobId: string\n prompt: string\n recommendedModel?: RecommendedModel\n}\n\n/**\n * AI Prompt Wizard — help write/improve prompts for generation nodes.\n *\n * - `analyze` -> guided questions, `generate` -> prompt from selections (the\n * 2-step human flow), or `enhance` -> one-shot \"improve this prompt\".\n *\n * All three delegate to `POST /v1/prompt-helper/wizard`. Throws `NodaroError`\n * on 4xx/5xx (e.g. `validation_error`, `malformed_response`).\n */\nexport class PromptHelperResource {\n constructor(private client: NodaroClient) {}\n\n analyze(input: AnalyzeInput): Promise<AnalyzeResult> {\n return this.client.request(\"POST\", \"/v1/prompt-helper/wizard\", { body: { action: \"analyze\", ...input } })\n }\n\n generate(input: GenerateInput): Promise<PromptResult> {\n return this.client.request(\"POST\", \"/v1/prompt-helper/wizard\", { body: { action: \"generate\", ...input } })\n }\n\n enhance(input: EnhanceInput): Promise<PromptResult> {\n return this.client.request(\"POST\", \"/v1/prompt-helper/wizard\", { body: { action: \"enhance\", ...input } })\n }\n}\n","import type { Voice, VoiceClone, VoiceLibraryParams, VoiceLibraryResponse, AudioFxPreset } from \"@nodaro/shared\"\nexport type { Voice, SharedVoice, VoiceClone, VoiceLibraryParams, VoiceLibraryResponse, AudioFxPreset } from \"@nodaro/shared\"\nimport type { NodaroClient } from \"../client.js\"\n\n/**\n * Re-export the shared voice types so SDK consumers don't have to add\n * `@nodaro/shared` as a second dependency just to type a `Voice` row, a\n * `VoiceClone`, or a `searchLibrary` call. Single source of truth lives in\n * `@nodaro/shared`.\n */\n/** Audio-FX preset union (reverb spaces / telephone / megaphone / echo / custom) — used by {@link VoiceChangerProInput.voiceFx}. */\n\n/**\n * Read access to ElevenLabs voices: the premade catalog plus the shared\n * community Voice Library (both public GETs, no body), and the signed-in\n * user's own voice clones (list / create-from-url / delete).\n */\nexport class VoicesResource {\n constructor(private client: NodaroClient) {}\n\n /**\n * List the premade ElevenLabs voices (`GET /v1/voices`). Falls back to a\n * curated set server-side when no ElevenLabs API key is configured.\n */\n async list(): Promise<Voice[]> {\n const res = await this.client.request<{ voices: Voice[] }>(\"GET\", \"/v1/voices\")\n return res.voices\n }\n\n /**\n * Search the shared/community Voice Library (`GET /v1/voices/library`). All\n * params are optional and forwarded as a querystring; `undefined` / `null` /\n * empty-string values are omitted so the server defaults apply. `hasMore`\n * drives \"load more\" pagination.\n */\n searchLibrary(params: VoiceLibraryParams = {}): Promise<VoiceLibraryResponse> {\n const query: Record<string, string | number | boolean | undefined> = {}\n for (const [k, v] of Object.entries(params)) {\n if (v !== undefined && v !== null && v !== \"\") query[k] = v as string | number | boolean\n }\n return this.client.request(\"GET\", \"/v1/voices/library\", { query })\n }\n\n /**\n * List the signed-in user's voice clones (`GET /v1/voice-clones`). The route\n * wraps the rows in `{ voiceClones }`; we unwrap to the bare array.\n */\n async listClones(): Promise<VoiceClone[]> {\n const res = await this.client.request<{ voiceClones: VoiceClone[] }>(\"GET\", \"/v1/voice-clones\")\n return res.voiceClones\n }\n\n /**\n * Clone a voice from an already-uploaded audio URL\n * (`POST /v1/voice-clones/from-url`). Costs credits. Returns the create\n * subset of `VoiceClone` (`elevenlabsVoiceId` is the id to use at\n * text-to-speech time).\n */\n createClone(input: { name: string; audioUrl: string }): Promise<VoiceClone> {\n return this.client.request<VoiceClone>(\"POST\", \"/v1/voice-clones/from-url\", { body: input })\n }\n\n /** Delete one of the user's voice clones (`DELETE /v1/voice-clones/:id`). */\n async deleteClone(id: string): Promise<void> {\n await this.client.request<void>(\"DELETE\", `/v1/voice-clones/${encodeURIComponent(id)}`)\n }\n\n /**\n * Replace the voice in a recording — or in a whole talking video — with a\n * different voice (`POST /v1/voice-changer`). Pass `audioUrl` to revoice\n * audio→audio, or `videoUrl` to revoice an entire clip (the server demuxes\n * the audio, runs speech-to-speech, and remuxes onto the original video,\n * returning the video plus the new audio track). Exactly one of `audioUrl` /\n * `videoUrl` is required; when both are sent, video wins. `removeBackgroundNoise`\n * off keeps the music/SFX bed under the new voice; on yields a clean voice-only\n * result. Costs credits and runs async — poll `jobs.get(jobId)` for the result\n * (`output_data.videoUrl` + `output_data.audioUrl` in video mode).\n */\n change(input: {\n voiceId: string\n audioUrl?: string\n videoUrl?: string\n /** Speech-to-speech model id. Defaults to the server-configured default when omitted. */\n model?: string\n stability?: number\n similarityBoost?: number\n /** Style exaggeration (0–1). Default 0; >0 amplifies delivery at the cost of latency/stability. */\n style?: number\n /** ElevenLabs speaker boost — sharpens fidelity to the target speaker (small latency cost). */\n useSpeakerBoost?: boolean\n /** Deterministic speech-to-speech seed (integer 0–4294967295) for reproducible output. Omit for random. */\n seed?: number\n removeBackgroundNoise?: boolean\n }): Promise<{ jobId: string }> {\n return this.client.request<{ jobId: string }>(\"POST\", \"/v1/voice-changer\", { body: input })\n }\n\n /**\n * Recast each detected speaker in a multi-speaker recording to a different\n * voice (`POST /v1/voice-changer-pro`). `orderedVoices` maps speaker positions to\n * voices in detection order — speaker 0 → `orderedVoices[0]`, speaker 1 →\n * `orderedVoices[1]`, etc. Speakers beyond the end of `orderedVoices` keep\n * their original voice. Each entry is EITHER a bare voice id (premade name or\n * ElevenLabs UUID) OR a {@link VoiceChangerProVoice} object carrying per-voice\n * ElevenLabs speech-to-speech settings (stability / similarityBoost / style /\n * useSpeakerBoost / `seed`) plus a loudness `volumeMode` (and a manual\n * `volume`) — OR `null`, meaning keep this speaker's original voice (requires\n * a platform running cloud-plugins with keep-slot support). At least one\n * entry must be non-null. A per-voice `seed` makes that speaker's recast\n * reproducible.\n *\n * Pass `audioUrl` for audio-only recast or `videoUrl` to recast the audio\n * track of a video clip (the server demuxes, recasts, and remuxes).\n *\n * Voice and music are ALWAYS separated first — before recasting, the source\n * is split into an isolated vocal stem and a music/SFX stem.\n * `preserveBackground` (default `true`) only controls whether that\n * music/instrumental stem is mixed back under the new voices; set it `false`\n * for a clean voice-only result. `separationQuality` selects the quality of\n * the voice/music separation: `\"fast\"` (default, quicker — preserves more of\n * the voice) or `\"best\"` (finer voice/music separation).\n * `removeBackgroundNoise` additionally denoises the result.\n * `musicVolumeMode` sets the level of that preserved background (only relevant\n * when `preserveBackground` is on): `\"match\"` (default) keeps the original\n * level, `\"normalize\"` loudnorms it, `\"manual\"` uses `musicVolume`%.\n * `voiceFx` applies a reverb/echo to the COMBINED recast voices BEFORE the\n * background is mixed back in (effect sits on the voices, not the music bed).\n *\n * Cloud-only — costs credits and runs async; poll `jobs.get(jobId)` for the\n * result (`output_data.videoUrl` + `output_data.audioUrl` in video mode).\n */\n recast(input: VoiceChangerProInput): Promise<{ jobId: string }> {\n return this.client.request<{ jobId: string }>(\"POST\", \"/v1/voice-changer-pro\", { body: input })\n }\n\n /**\n * Detect the speakers in a clip WITHOUT recasting yet\n * (`POST /v1/voice-changer-pro/analyze`) — the first step of the interactive\n * flow. Separates voice from music once and diarizes the vocals, returning the\n * speaker list so a user (or agent) can choose a voice per speaker before\n * committing to a paid recast. Poll `jobs.get(jobId)`: the completed job's\n * `output_data` carries the separated stem urls + the detected `speakers`\n * (each with `id`, time `segments`, `firstStartSec`, `wordCount`, `snippet`)\n * and the detected language — reshape it into a {@link VcpAnalysis} and pass it\n * as `recast({ ..., analysis })` to skip re-detection. With `suggestTitle`,\n * `output_data.suggestedTitle` also carries an LLM-proposed title.\n *\n * Cloud-only; costs credits and runs async.\n */\n analyze(input: VcpAnalyzeInput): Promise<{ jobId: string }> {\n return this.client.request<{ jobId: string }>(\"POST\", \"/v1/voice-changer-pro/analyze\", { body: input })\n }\n\n /**\n * Render a final video from a mixed set of stems\n * (`POST /v1/voice-changer-pro/export`) — the last step of the interactive\n * flow. After `recast({ output: \"stems\" })` hands back the dry per-track stems\n * and the user has set levels / mutes / an effect in your editor, pass those\n * `tracks` (plus the source `videoUrl`) here to mix and remux into the finished\n * video. The video is stream-copied (never re-encoded), so the export is\n * bit-identical to your preview. At least one track must be un-muted (all-muted\n * is a 400); `voiceFx` is applied to the voice tracks at render time.\n *\n * Cloud-only; costs credits and runs async — poll `jobs.get(jobId)` for the\n * result (`output_data.videoUrl`).\n */\n exportMix(input: VcpExportInput): Promise<{ jobId: string }> {\n return this.client.request<{ jobId: string }>(\"POST\", \"/v1/voice-changer-pro/export\", { body: input })\n }\n\n /**\n * Design a brand-new synthetic voice from a text description\n * (`POST /v1/voice-design`) — ElevenLabs text-to-voice. `text` (100–1000 chars)\n * is a preview line spoken in the designed voice; `voiceDescription` describes\n * the voice to create. Costs credits and runs async — poll `jobs.get(jobId)`\n * for the preview + the reusable voice id.\n */\n design(input: VoiceDesignInput): Promise<{ jobId: string }> {\n return this.client.request<{ jobId: string }>(\"POST\", \"/v1/voice-design\", { body: input })\n }\n\n /**\n * Generate speech in a voice described in natural language, without cloning\n * (`POST /v1/voice-remix`). `text` (1–5000 chars) is spoken in a voice matching\n * `voiceDescription`. Costs credits and runs async — poll `jobs.get(jobId)`.\n */\n remix(input: VoiceRemixInput): Promise<{ jobId: string }> {\n return this.client.request<{ jobId: string }>(\"POST\", \"/v1/voice-remix\", { body: input })\n }\n\n /**\n * Dub an audio clip into another language while preserving each speaker's voice\n * (`POST /v1/dubbing`). `targetLanguage` is an ISO code (e.g. `\"es\"`, `\"fr\"`);\n * `sourceLanguage` is auto-detected when omitted. Costs credits and runs async\n * — poll `jobs.get(jobId)`.\n */\n dub(input: DubbingInput): Promise<{ jobId: string }> {\n return this.client.request<{ jobId: string }>(\"POST\", \"/v1/dubbing\", { body: input })\n }\n\n /**\n * Clone a voice from an audio FILE you hold in memory\n * (`POST /v1/voice-clones`, multipart) — the counterpart to\n * {@link VoicesResource.createClone}, which clones from an already-uploaded\n * URL. Pass the raw audio `file` (a `Blob`/`File` in the browser, or a\n * `Uint8Array`/`Buffer` in Node) plus a `name`. Costs credits. Returns the new\n * {@link VoiceClone} (`elevenlabsVoiceId` is the id to recast/synthesize with).\n */\n createCloneFromFile(input: {\n name: string\n file: Blob | Uint8Array | ArrayBuffer\n /** File name for the upload part (default `sample`). */\n filename?: string\n /** MIME type when `file` is a raw buffer (default `audio/mpeg`). */\n contentType?: string\n }): Promise<VoiceClone> {\n const form = new FormData()\n // Append the name field BEFORE the file so the route sees it in `fields`\n // (fastify-multipart's `req.file()` exposes fields parsed up to the file).\n form.append(\"name\", input.name)\n const blob =\n input.file instanceof Blob\n ? input.file\n : new Blob([input.file as BlobPart], { type: input.contentType ?? \"audio/mpeg\" })\n form.append(\"file\", blob, input.filename ?? \"sample\")\n return this.client.request<VoiceClone>(\"POST\", \"/v1/voice-clones\", { body: form })\n }\n}\n\n/**\n * One entry in {@link VoiceChangerProInput.orderedVoices}. Either a bare voice id\n * (premade name like `\"Rachel\"` or an ElevenLabs UUID for a custom clone), or\n * an object pinning per-voice speech-to-speech settings and the recast's\n * loudness behaviour for that speaker.\n */\nexport type VoiceChangerProVoice =\n | string\n | {\n /** Target voice — premade name (`\"Rachel\"`, `\"Aria\"`, …) or an ElevenLabs UUID for a custom clone. */\n voiceId: string\n /** ElevenLabs stability (0–1). Higher = steadier, lower = more expressive. */\n stability?: number\n /** ElevenLabs similarity boost (0–1) — how closely the output hugs the target voice's timbre. */\n similarityBoost?: number\n /** Style exaggeration (0–1). Default 0; >0 amplifies delivery at the cost of latency / stability. */\n style?: number\n /** ElevenLabs speaker boost — sharpens fidelity to the target speaker. */\n useSpeakerBoost?: boolean\n /**\n * Deterministic speech-to-speech seed (integer 0–4294967295) for\n * reproducible per-voice output — the same source + settings + seed\n * recast this speaker identically across runs. Omit for a random seed.\n */\n seed?: number\n /**\n * Loudness handling for this recast voice. `\"match\"` (default) matches the\n * original speaker's loudness; `\"normalize\"` applies loudness\n * normalization; `\"manual\"` uses `volume` as a percentage.\n */\n volumeMode?: \"match\" | \"normalize\" | \"manual\"\n /** Manual output volume as a percentage (0–200). Consulted only when `volumeMode === \"manual\"`. */\n volume?: number\n }\n\n/** Input for {@link VoicesResource.recast}. */\nexport interface VoiceChangerProInput {\n /** URL of an audio file to recast (audio → audio). Exactly one of `audioUrl` / `videoUrl` is required. */\n audioUrl?: string\n /** URL of a video file to recast (the audio track is recast and remuxed). Exactly one of `audioUrl` / `videoUrl` is required. */\n videoUrl?: string\n /**\n * Voices in speaker-detection order. Speaker N is mapped to `orderedVoices[N]`;\n * speakers beyond the array keep their original voice. Each entry is a bare\n * voice id OR a {@link VoiceChangerProVoice} object with per-voice settings —\n * OR `null`, meaning keep this speaker's original voice (requires a platform\n * running cloud-plugins with keep-slot support). At least one entry must be\n * non-null.\n */\n orderedVoices: Array<VoiceChangerProVoice | null>\n /** Model to use for speech-to-speech. Defaults to the server-configured default when omitted. */\n model?: string\n /**\n * Mix the separated music / SFX stem back under the recast voices. Default\n * `true`. The voice is ALWAYS split out before recasting regardless of this\n * flag — `false` simply drops the music for a clean voice-only result.\n */\n preserveBackground?: boolean\n /**\n * Quality of the voice/music separation. `\"fast\"` (default, quicker —\n * preserves more of the voice) or `\"best\"` (finer voice/music separation).\n */\n separationQuality?: \"fast\" | \"best\"\n /** Strip background noise for a clean voice-only result. */\n removeBackgroundNoise?: boolean\n /**\n * Level of the preserved background music / SFX stem in the final mix. Only\n * relevant when `preserveBackground` is on (otherwise there is no background to\n * level). `\"match\"` (default) leaves the separated instrumental at its original\n * level; `\"normalize\"` applies loudness normalization; `\"manual\"` sets its\n * level to `musicVolume`%.\n */\n musicVolumeMode?: \"match\" | \"normalize\" | \"manual\"\n /** Background music level as a percentage (0–200). Consulted only when `musicVolumeMode === \"manual\"`. */\n musicVolume?: number\n /**\n * Node-level reverb/echo applied to the COMBINED recast voices **before** the\n * background is mixed back in (so the effect sits on the voices only, not the\n * music/SFX bed). Reverb presets (`room`, `hall`, `church`, …) use\n * `wetDryMix`; the `echo` / `custom` presets use `delayMs` + `decay`. Omit for\n * no effect.\n */\n voiceFx?: {\n /** Effect preset — reverb space, `telephone`, `megaphone`, `echo`, or `custom`. */\n preset: AudioFxPreset\n /** Reverb wet/dry mix as a percentage (0–100). Higher = wetter (more reverb). */\n wetDryMix?: number\n /** Echo delay in milliseconds (20–2000). Used by the `echo` / `custom` presets. */\n delayMs?: number\n /** Echo decay / feedback (0–1). Higher = more repeats. Used by the `echo` / `custom` presets. */\n decay?: number\n }\n /**\n * Output mode. `\"video\"` (default) mixes the recast voices with the preserved\n * background and returns a finished merged video. `\"stems\"` returns the dry,\n * unleveled per-track stems instead (rendering nothing) so you can drive an\n * INTERACTIVE mix — adjust levels/mutes/effect in your own UI, then render the\n * final video with {@link VoicesResource.exportMix}. This is how an app builds\n * a full editor around VCP rather than a one-shot recast.\n */\n output?: \"video\" | \"stems\"\n /**\n * A prior {@link VoicesResource.analyze} result. Pass it to SKIP re-detection:\n * the recast reuses the already-separated stems and speaker segments instead of\n * running separation + diarization again. This is the fast-path for the\n * detect → pick voices → recast interactive flow (analyze once, recast N times\n * as the user tweaks voice assignments). Omit to auto-detect from the source.\n */\n analysis?: VcpAnalysis\n}\n\n/** One detected speaker in a {@link VcpAnalysis} (from `analyze`). */\nexport interface VcpAnalysisSpeaker {\n /** Stable speaker id (first-appearance order). */\n id: string\n /** The speaker's spoken time ranges (seconds). */\n segments: Array<{ start: number; end: number }>\n /** When the speaker first speaks (seconds). */\n firstStartSec?: number\n /** Rough word count across the clip — a proxy for how much this speaker says. */\n wordCount?: number\n /** The first few transcribed words, to help a user tell speakers apart. */\n snippet?: string\n}\n\n/**\n * The result of {@link VoicesResource.analyze}, reshaped to pass back into\n * {@link VoiceChangerProInput.analysis}. Read a completed analyze job's\n * `output_data` into this shape (it carries the separated stem urls + the\n * detected speakers) and thread it into `recast` to skip re-detection.\n */\nexport interface VcpAnalysis {\n /** URL of the isolated vocal stem. */\n vocalsUrl: string\n /** URL of the separated music/SFX stem (absent when the source had none). */\n backgroundUrl?: string\n /** The detected speakers, in first-appearance order. */\n speakers: VcpAnalysisSpeaker[]\n /** Scribe's detected language code, round-tripped so the recast auto-selects the STS model. */\n languageCode?: string\n /** Confidence (0–1) of {@link VcpAnalysis.languageCode}. */\n languageProbability?: number\n}\n\n/** Input for {@link VoicesResource.analyze}. */\nexport interface VcpAnalyzeInput {\n /** URL of an audio file to analyze. Exactly one of `audioUrl` / `videoUrl` is required. */\n audioUrl?: string\n /** URL of a video file to analyze (its audio track is used). Exactly one of `audioUrl` / `videoUrl` is required. */\n videoUrl?: string\n /** Quality of the voice/music separation run before diarization: `\"fast\"` (default) or `\"best\"`. */\n separationQuality?: \"fast\" | \"best\"\n /** Also suggest a conversion title from the transcript (returned on the job's `output_data.suggestedTitle`). */\n suggestTitle?: boolean\n}\n\n/** One track in a {@link VcpExportInput} mix. */\nexport interface VcpExportTrack {\n /** URL of the stem for this lane (a recast voice stem or the background stem). */\n url: string\n /** Fader position as a percentage: 0 = silent, 100 = unity, 200 = +6dB. */\n gain: number\n /** Whether this lane is muted in the mix. */\n muted: boolean\n /**\n * Which bucket the track is in, and so whether `voiceFx` lands on it. Defaults\n * to `\"voice\"`. Set `\"background\"` for the music/SFX lane (the effect never\n * touches it).\n */\n kind?: \"voice\" | \"background\"\n}\n\n/** Input for {@link VoicesResource.exportMix}. */\nexport interface VcpExportInput {\n /** The source video to remux the mixed audio onto (stream-copied — never re-encoded). */\n videoUrl: string\n /** The mix: one entry per lane. At least one must be un-muted (all-muted is a 400). Max 16 tracks. */\n tracks: VcpExportTrack[]\n /**\n * A reverb/echo applied to the VOICE tracks only (not `\"background\"` lanes)\n * at render time — so iterating the effect in your editor is free until you\n * export. Same shape as {@link VoiceChangerProInput.voiceFx}.\n */\n voiceFx?: VoiceChangerProInput[\"voiceFx\"]\n}\n\n/** Input for {@link VoicesResource.design}. */\nexport interface VoiceDesignInput {\n /** A preview line (100–1000 chars) spoken in the designed voice. */\n text: string\n /** Natural-language description of the voice to create. */\n voiceDescription: string\n /** Voice-design model id. Defaults to the server-configured default. */\n model?: string\n /** Output loudness (-1..1). */\n loudness?: number\n /** How strongly the description steers the design (0–100). */\n guidanceScale?: number\n /** Deterministic seed for a reproducible design. */\n seed?: number\n /** Design quality knob (provider-specific). */\n quality?: number\n /** Enhance the generated voice. */\n shouldEnhance?: boolean\n /** Optional extra prompt context (≤8000 chars). */\n userPrompt?: string\n}\n\n/** Input for {@link VoicesResource.remix}. */\nexport interface VoiceRemixInput {\n /** The text (1–5000 chars) to speak in the described voice. */\n text: string\n /** Natural-language description of the voice to speak in. */\n voiceDescription: string\n /** Optional extra prompt context (≤8000 chars). */\n userPrompt?: string\n}\n\n/** Input for {@link VoicesResource.dub}. */\nexport interface DubbingInput {\n /** URL of the audio to dub. */\n audioUrl: string\n /** Target language ISO code (2–10 chars), e.g. `\"es\"`, `\"pt-BR\"`. */\n targetLanguage: string\n /** Source language ISO code; auto-detected when omitted. */\n sourceLanguage?: string\n /** Expected number of speakers (1–20) — improves separation when known. */\n numSpeakers?: number\n /** Keep the original voices instead of cloning them into the target language. */\n disableVoiceCloning?: boolean\n /** Drop the background/music bed from the dubbed output. */\n dropBackgroundAudio?: boolean\n}\n","import type { NodaroClient } from \"../client.js\"\nimport { NodaroError, throwFromResponse } from \"../errors.js\"\n\n/**\n * Media ingestion + trimming — the source-preparation steps a Voice Changer Pro\n * flow (or any pipeline) needs before it has a clip to work on: pull a social\n * video into storage, copy a remote URL into storage, trim a video/audio to a\n * range, and probe a video's metadata. Each generation-style op returns a job id\n * to poll (`jobs.get(jobId)`); `videoMetadata` is a direct read.\n */\nexport class MediaResource {\n constructor(private client: NodaroClient) {}\n\n /**\n * Download a social video (YouTube / TikTok / Instagram / X / Facebook) into\n * your storage (`POST /v1/download-video`). `maxHeight` caps the resolution\n * (default \"best\"); `sectionStartSec` + `sectionEndSec` (both-or-neither) fetch\n * ONLY that time range instead of the whole video. Returns a `downloadId`;\n * progress streams from `GET /v1/download-video/progress/:downloadId`\n * (server-sent events) and the finished file lands in your library.\n */\n downloadVideo(input: {\n url: string\n maxHeight?: number\n sectionStartSec?: number\n sectionEndSec?: number\n }): Promise<{ downloadId: string }> {\n return this.client.request<{ downloadId: string }>(\"POST\", \"/v1/download-video\", { body: input })\n }\n\n /**\n * Stream the live progress of a {@link MediaResource.downloadVideo} import\n * (`GET /v1/download-video/progress/:downloadId`, server-sent events) as an\n * async iterable. Yields a {@link DownloadVideoProgress} roughly every 500ms\n * until the download reaches `completed` (its event carries the stored\n * `videoUrl`) or `failed` (its event carries `error`), then ends. The progress\n * state expires server-side shortly after the download starts existing, so\n * start iterating promptly after `downloadVideo` returns.\n *\n * No request timeout is applied (a large import legitimately takes minutes) —\n * pass an `AbortSignal` to cancel from the caller.\n */\n async *downloadVideoProgress(\n downloadId: string,\n opts: { signal?: AbortSignal } = {},\n ): AsyncGenerator<DownloadVideoProgress, void, undefined> {\n const url = `${this.client.baseUrl}/v1/download-video/progress/${encodeURIComponent(downloadId)}`\n const token = await this.client.auth.getToken()\n const res = await this.client.fetch(url, {\n headers: token ? { Authorization: `Bearer ${token}` } : {},\n signal: opts.signal,\n })\n if (!res.ok) {\n let errBody: Record<string, unknown> = {}\n try {\n errBody = (await res.json()) as Record<string, unknown>\n } catch {\n // Empty/non-JSON body — fall through with empty errBody\n }\n throwFromResponse(res.status, errBody)\n }\n if (!res.body) {\n throw new NodaroError(\"progress stream has no response body\", \"empty_stream\", res.status)\n }\n\n // Minimal SSE parse: the route emits only `data: <json>\\n\\n` frames (no\n // event/id fields), so split on blank lines and JSON-parse the data lines.\n const reader = res.body.getReader()\n const decoder = new TextDecoder()\n let buffer = \"\"\n try {\n for (;;) {\n const { done, value } = await reader.read()\n if (done) break\n buffer += decoder.decode(value, { stream: true })\n let sep: number\n while ((sep = buffer.indexOf(\"\\n\\n\")) >= 0) {\n const frame = buffer.slice(0, sep)\n buffer = buffer.slice(sep + 2)\n for (const line of frame.split(\"\\n\")) {\n if (!line.startsWith(\"data:\")) continue\n try {\n yield JSON.parse(line.slice(5).trim()) as DownloadVideoProgress\n } catch {\n // Skip malformed frames — the next tick re-reports full state.\n }\n }\n }\n }\n } finally {\n // Ends the HTTP request when the consumer breaks out of the loop early.\n reader.releaseLock()\n await res.body.cancel().catch(() => {})\n }\n }\n\n /**\n * Copy an external media URL into your Nodaro storage (`POST /v1/save-to-storage`)\n * — a server-side fetch, so nothing round-trips through the client. Poll\n * `jobs.get(jobId)`.\n */\n saveToStorage(input: { mediaUrl: string; filename?: string; mediaType?: \"image\" | \"video\" | \"audio\" }): Promise<{ jobId: string }> {\n return this.client.request<{ jobId: string }>(\"POST\", \"/v1/save-to-storage\", { body: input })\n }\n\n /**\n * Trim a video to a range (`POST /v1/trim-video`). Give the range in whichever\n * unit fits: `startTime`/`endTime` seconds, `trim*Frames`, `trim*Seconds`, or\n * `keepFirst`/`keepLastSeconds`. Poll `jobs.get(jobId)`.\n */\n trimVideo(input: {\n videoUrl: string\n startTime?: number\n endTime?: number\n trimStartFrames?: number\n trimEndFrames?: number\n trimStartSeconds?: number\n trimEndSeconds?: number\n keepFirstSeconds?: number\n keepLastSeconds?: number\n }): Promise<{ jobId: string }> {\n return this.client.request<{ jobId: string }>(\"POST\", \"/v1/trim-video\", { body: input })\n }\n\n /**\n * Trim (and extract) audio from a video or audio source\n * (`POST /v1/trim-audio`) to `[startTime, endTime]` seconds, in `audioFormat`\n * (`mp3` default / `wav` / `aac`). Poll `jobs.get(jobId)`.\n */\n trimAudio(input: {\n videoUrl?: string\n audioUrl?: string\n audioFormat?: \"mp3\" | \"wav\" | \"aac\"\n startTime?: number\n endTime?: number\n }): Promise<{ jobId: string }> {\n return this.client.request<{ jobId: string }>(\"POST\", \"/v1/trim-audio\", { body: input })\n }\n\n /**\n * Probe a social video's metadata (`POST /v1/video-metadata`) — duration,\n * dimensions, title, live status — WITHOUT downloading it. A direct read, not a\n * job. Use it to decide whether to trim before importing.\n */\n videoMetadata(input: { url: string }): Promise<VideoMetadata> {\n return this.client.request<VideoMetadata>(\"POST\", \"/v1/video-metadata\", { body: input })\n }\n}\n\n/**\n * One event from {@link MediaResource.downloadVideoProgress}. The stream ends\n * after a `completed` event (which carries the stored `videoUrl` + an optional\n * `thumbnailUrl`) or a `failed` event (which carries `error`).\n */\nexport interface DownloadVideoProgress {\n phase: \"downloading\" | \"processing\" | \"uploading\" | \"completed\" | \"failed\"\n /** Download percent (0–100). Section fetches report jumpy percents — display, don't sum. */\n percent: number\n /** The imported video's storage URL — set on the `completed` event. */\n videoUrl?: string\n /** Thumbnail storage URL — set on the `completed` event when one was captured. */\n thumbnailUrl?: string\n /** What went wrong — set on the `failed` event. */\n error?: string\n}\n\n/** Result of {@link MediaResource.videoMetadata}. Fields are best-effort — a probe may omit some. */\nexport interface VideoMetadata {\n durationSec?: number | null\n width?: number | null\n height?: number | null\n title?: string | null\n isLive?: boolean\n [key: string]: unknown\n}\n","import type { AudioFxPreset } from \"@nodaro/shared\"\nimport type { NodaroClient } from \"../client.js\"\n\n/**\n * Audio primitives — the building blocks Voice Changer Pro composes internally\n * (separation, isolation, effect, mix, level), exposed standalone so a consumer\n * can run any single step or assemble its own pipeline. Each returns a job id to\n * poll (`jobs.get(jobId)`).\n */\nexport class AudioResource {\n constructor(private client: NodaroClient) {}\n\n /**\n * Separate an audio track into stems (`POST /v1/audio-separation`, Demucs).\n * `mode` `\"vocal_instrumental\"` (default) splits voice from music/SFX;\n * `\"stems\"` returns the full drums/bass/other/… breakdown. `quality`\n * `auto` (default) / `fast` / `best`.\n */\n separate(input: { audioUrl: string; mode?: \"vocal_instrumental\" | \"stems\"; quality?: \"auto\" | \"fast\" | \"best\" }): Promise<{ jobId: string }> {\n return this.client.request<{ jobId: string }>(\"POST\", \"/v1/audio-separation\", { body: input })\n }\n\n /** Isolate the primary voice and strip background noise (`POST /v1/audio-isolation`, ElevenLabs). */\n isolate(input: { audioUrl: string }): Promise<{ jobId: string }> {\n return this.client.request<{ jobId: string }>(\"POST\", \"/v1/audio-isolation\", { body: input })\n }\n\n /**\n * Apply a reverb / echo / telephone / megaphone effect to an audio track\n * (`POST /v1/audio-fx`) — the same presets VCP's `voiceFx` uses, standalone.\n * `mix` (0–100) is the reverb wet/dry; `delayMs` + `decay` drive `echo`/`custom`;\n * `eqLow`/`eqHigh` (dB) shape telephone/megaphone.\n */\n applyFx(input: {\n audioUrl: string\n preset?: AudioFxPreset\n mix?: number\n delayMs?: number\n decay?: number\n eqLow?: number\n eqHigh?: number\n }): Promise<{ jobId: string }> {\n return this.client.request<{ jobId: string }>(\"POST\", \"/v1/audio-fx\", { body: input })\n }\n\n /**\n * Layer multiple audio tracks into one (`POST /v1/mix-audio`). `audioUrls`\n * (2–20) are summed; optional `trackVolumes` (0–200% each, positionally) set\n * per-track level.\n */\n mix(input: { audioUrls: string[]; trackVolumes?: number[] }): Promise<{ jobId: string }> {\n return this.client.request<{ jobId: string }>(\"POST\", \"/v1/mix-audio\", { body: input })\n }\n\n /**\n * Adjust an audio (or a video's audio) level (`POST /v1/adjust-volume`):\n * `volume` % (default 100), `normalize` to loudnorm, and `fadeIn`/`fadeOut`\n * seconds. Provide `audioUrl` or `videoUrl`.\n */\n adjustVolume(input: {\n audioUrl?: string\n videoUrl?: string\n volume?: number\n normalize?: boolean\n fadeIn?: number\n fadeOut?: number\n }): Promise<{ jobId: string }> {\n return this.client.request<{ jobId: string }>(\"POST\", \"/v1/adjust-volume\", { body: input })\n }\n\n /**\n * Concatenate audio segments end-to-end (`POST /v1/combine-audio`). Each\n * segment is a `url` with an optional `[startTime, endTime]` sub-range.\n */\n combine(input: { segments: Array<{ url: string; startTime?: number; endTime?: number }> }): Promise<{ jobId: string }> {\n return this.client.request<{ jobId: string }>(\"POST\", \"/v1/combine-audio\", { body: input })\n }\n}\n","import type { NodaroClient } from \"../client.js\"\n\n/**\n * Authenticated user's credit balance — the shape of `GET /v1/user/credits`'s\n * `data` field. Mirrors the canonical `UserBalance` from the backend billing\n * module (`backend/src/ee/billing/credits.ts`), the source of truth: keep this\n * in sync if that interface changes.\n */\nexport interface UserBalance {\n total: number\n subscription: number\n topup: number\n dailySpent: number\n dailyLimit: number | null\n monthlyAllocation: number\n tier: string\n features: Record<string, unknown>\n periodEnd: string | null\n /** Credits earned for app usage (free tier only — earned by running flows). */\n appCreditsAllowance: number\n}\n\n/**\n * Result of `POST /v1/credits/model-costs` — a batch cost lookup for editor\n * cost previews. `data` maps each priced identifier → its credit cost.\n *\n * Per-model fault isolation (the route runs the lookups under\n * `Promise.allSettled`): identifiers with no pricing row are reported in\n * `missing` (undisplayable until an operator seeds a price) and lookup\n * failures in `errors`, instead of failing the whole batch. Callers typically\n * render `'—'` for any identifier that lands in `missing`. The hard-fail\n * policy still triggers at reservation time when the user actually runs the\n * node — this preview lookup is intentionally lenient.\n */\nexport interface ModelCostsResult {\n data: Record<string, number>\n missing: string[]\n errors: string[]\n}\n\n/** Max identifiers per `model-costs` batch — the route caps the request at 50. */\nconst MODEL_COSTS_LIMIT = 50\n\nexport class CreditsResource {\n constructor(private client: NodaroClient) {}\n\n /**\n * `GET /v1/user/credits` → the authenticated user's credit balance and tier\n * info. Throws `UnauthorizedError` (401) when signed out, and the SDK's\n * other typed errors on the usual statuses.\n */\n async balance(): Promise<UserBalance> {\n const res = await this.client.request<{ data: UserBalance }>(\n \"GET\",\n \"/v1/user/credits\",\n )\n return res.data\n }\n\n /**\n * `POST /v1/credits/model-costs` → per-identifier credit cost, for editor\n * cost previews. Capped at the first {@link MODEL_COSTS_LIMIT} identifiers\n * (the route's request limit). Preserves the `{ data, missing, errors }`\n * fault-isolation shape verbatim (see {@link ModelCostsResult}).\n */\n modelCosts(ids: string[]): Promise<ModelCostsResult> {\n return this.client.request<ModelCostsResult>(\"POST\", \"/v1/credits/model-costs\", {\n body: { models: ids.slice(0, MODEL_COSTS_LIMIT) },\n })\n }\n\n // NOTE: no `estimate(...)` helper. The backend exposes\n // `POST /v1/credits/estimate-workflow` (body `{ nodes }` → `{ data: {\n // totalCredits, nodeCount } }`), but no consumer has a settled shape for it\n // yet (studio pre-checks `balance >= Σ modelCosts` client-side rather than\n // calling an estimate endpoint). Adding it now would be inventing an API\n // surface ahead of a real caller, so it's deliberately omitted — add it when\n // a consumer needs it, shaped to that need.\n}\n","import type { NodaroClient } from \"../client.js\"\n\n/**\n * Result of a successful `POST /v1/upload`. Mirrors the route's `data` envelope\n * (`backend/src/routes/upload.ts`), the source of truth — keep in sync if that\n * response changes. The route also returns loosely-typed extracted `metadata`;\n * it is omitted here until a consumer needs a typed shape (add it shaped to\n * that need rather than inventing `Record<string, unknown>` surface now).\n */\nexport interface UploadResult {\n /** Public R2 URL of the stored asset (always present on success). */\n readonly url: string\n /** Storage row id; `null` when no asset row was written (e.g. unauthenticated). */\n readonly assetId: string | null\n /** Generated thumbnail URL (images/video); `null` for audio or on failure. */\n readonly thumbnailUrl: string | null\n /** Server-classified asset category (e.g. \"image\" | \"video\" | \"audio\"). */\n readonly category: string\n /** Display filename (server override or the original). */\n readonly filename: string\n /** Final MIME type after server normalization. */\n readonly mimeType: string\n /** Stored byte size. */\n readonly sizeBytes: number\n /** R2 object key. */\n readonly r2Key: string\n}\n\nexport class UploadsResource {\n constructor(private client: NodaroClient) {}\n\n /**\n * Upload one file (`POST /v1/upload`, multipart — the file rides the `file`\n * field). The SDK's `request` detects the `FormData` body and lets the\n * runtime set the multipart boundary. Returns the persisted asset's public\n * URL + storage metadata (unwraps the `{ data }` envelope). Throws\n * `StorageExceededError` (413) over the storage cap and the SDK's other typed\n * errors on the usual statuses.\n */\n async upload(file: File): Promise<UploadResult> {\n const form = new FormData()\n form.append(\"file\", file)\n const res = await this.client.request<{ data: UploadResult }>(\n \"POST\",\n \"/v1/upload\",\n { body: form },\n )\n return res.data\n }\n}\n","import type { NodaroClient } from \"../client.js\"\n\n/**\n * A media asset in the caller's library (`GET /v1/library`). Mirrors the route's\n * camelCase row shape (`backend/src/routes/library.ts`), the source of truth —\n * keep in sync if that response changes. Covers BOTH uploaded files and saved\n * generations; `type` discriminates the media kind.\n */\nexport interface LibraryAsset {\n readonly id: string\n /** Media kind — filter the list with the same values via `type`. */\n readonly type: \"image\" | \"video\" | \"audio\"\n /** Display filename (server override or the original); `null` if unknown. */\n readonly filename: string | null\n readonly mimeType: string | null\n readonly sizeBytes: number | null\n /** Public R2 URL of the asset. */\n readonly url: string\n /** Generated thumbnail URL (images/video); `null` for audio or when absent. */\n readonly thumbnailUrl: string | null\n readonly metadata: Record<string, unknown>\n /** True when promoted to the shared (admin) library. */\n readonly isLibraryItem: boolean\n /** How the asset entered storage (e.g. \"manual_upload\" | \"generated\"). */\n readonly uploadSource: string\n readonly createdAt: string\n}\n\nexport interface ListLibraryParams {\n /** Filter by media kind; `\"all\"` (default) returns every kind. */\n readonly type?: \"all\" | \"image\" | \"video\" | \"audio\"\n /** Case-insensitive filename substring filter. */\n readonly search?: string\n /** Page size, 1–100 (default 40). */\n readonly limit?: number\n /** Opaque cursor from a prior page's `nextCursor` (fetches the next page). */\n readonly cursor?: string\n /**\n * `true` → EVERY asset the caller owns (the \"Storage\" view: uploads +\n * generations, regardless of the in-library flag). `false` (default) → only\n * assets explicitly saved to the library plus shared items (the in-editor\n * Media Library picker).\n */\n readonly owned?: boolean\n}\n\nexport interface ListLibraryResult {\n readonly data: LibraryAsset[]\n /** Pass back as `cursor` for the next page; `null` when there are no more. */\n readonly nextCursor: string | null\n /** Exact total (first page only — omitted on cursor-paged requests). */\n readonly totalCount?: number\n}\n\n/**\n * Library — the caller's media assets (uploaded files + saved generations).\n *\n * Read surface over `GET /v1/library`: a cursor-paginated, type-filterable,\n * filename-searchable list. This is the \"bring from your media\" source for\n * pickers that also offer upload + in-production stills. Writes (saving a\n * generation, promoting/removing) stay on their dedicated routes — not exposed\n * here until a consumer needs them.\n */\nexport class LibraryResource {\n constructor(private client: NodaroClient) {}\n\n /**\n * `GET /v1/library` → a page of the caller's media assets (newest first) plus\n * a `nextCursor`. Pass the returned `nextCursor` back as `cursor` for the next\n * page. Filter by `type` and a filename `search`; `owned: true` returns the\n * full Storage set (uploads + generations), the default only library-saved +\n * shared items.\n */\n list(params: ListLibraryParams = {}): Promise<ListLibraryResult> {\n const qs = new URLSearchParams()\n if (params.type) qs.set(\"type\", params.type)\n if (params.search) qs.set(\"search\", params.search)\n if (params.limit !== undefined) qs.set(\"limit\", String(params.limit))\n if (params.cursor) qs.set(\"cursor\", params.cursor)\n if (params.owned !== undefined) qs.set(\"owned\", String(params.owned))\n const query = qs.toString()\n return this.client.request(\"GET\", `/v1/library${query ? `?${query}` : \"\"}`)\n }\n}\n","import type { NodaroClient } from \"../client.js\"\nimport type { FactoryPreset } from \"@nodaro/prompts\"\n\n/**\n * A user's saved custom preset (`GET /v1/node-presets`). Mirrors the backend's\n * camelCase row shape. `data` is captured node config — merge it into a node's\n * data when building/running a workflow to \"apply\" the preset.\n */\nexport interface NodePreset {\n id: string\n nodeType: string\n name: string\n description?: string\n data: Record<string, unknown>\n groupId?: string\n tags: string[]\n sortOrder: number\n createdAt: string\n updatedAt: string\n}\n\n/** A user's preset folder/section (`GET /v1/node-preset-groups`). */\nexport interface NodePresetGroup {\n id: string\n nodeType: string\n name: string\n kind: \"folder\" | \"section\"\n sortOrder: number\n createdAt: string\n updatedAt: string\n}\n\n/**\n * Result of `GET /v1/node-presets/factory` — the built-in (factory) catalog for\n * a node type.\n */\nexport interface FactoryPresetsResult {\n data: FactoryPreset[]\n}\n\n/**\n * Node presets — reusable, named node configurations.\n *\n * Read-only over the API today: list your own custom presets and their folders,\n * and list the built-in factory catalog. To *use* a preset, take its `data` and\n * merge it into a node's config when you create/update a workflow. (Creating and\n * editing presets remains in the editor for now.)\n */\nexport class PresetsResource {\n constructor(private client: NodaroClient) {}\n\n /**\n * `GET /v1/node-presets` → your custom presets, newest first. Pass `nodeType`\n * (e.g. `\"generate-image\"`) to filter to one node type.\n */\n async list(nodeType?: string): Promise<NodePreset[]> {\n const qs = nodeType ? `?nodeType=${encodeURIComponent(nodeType)}` : \"\"\n const res = await this.client.request<{ data: NodePreset[] }>(\"GET\", `/v1/node-presets${qs}`)\n return res.data\n }\n\n /**\n * `GET /v1/node-preset-groups` → your preset folders/sections, in display\n * order. Pass `nodeType` to filter to one node type.\n */\n async listGroups(nodeType?: string): Promise<NodePresetGroup[]> {\n const qs = nodeType ? `?nodeType=${encodeURIComponent(nodeType)}` : \"\"\n const res = await this.client.request<{ data: NodePresetGroup[] }>(\"GET\", `/v1/node-preset-groups${qs}`)\n return res.data\n }\n\n /**\n * `GET /v1/node-presets/factory` → the built-in catalog for `nodeType`. These\n * ship with the app (no account needed to exist), so they're a good starting\n * point for \"what configs are available\".\n */\n listFactory(nodeType: string): Promise<FactoryPresetsResult> {\n return this.client.request<FactoryPresetsResult>(\n \"GET\",\n `/v1/node-presets/factory?nodeType=${encodeURIComponent(nodeType)}`,\n )\n }\n}\n","import type { NodaroClient } from \"../client.js\"\n\n/**\n * Picker-catalog types. Mirrors `@nodaro/shared`'s `ProjectedPickerCatalog` /\n * `PickerCatalogSummary` so the SDK stays dependency-free (same convention as\n * `NodeDescriptor` mirroring node-registry).\n */\nexport interface PickerOption {\n id: string\n label: string\n description?: string\n category?: string\n /** The prompt fragment this id injects downstream. Present only when detail=\"full\". */\n promptHint?: string\n icon?: string\n}\n\nexport interface PickerDimension {\n field: string\n label: string\n options: PickerOption[]\n}\n\nexport interface PickerCatalog {\n nodeType: string\n label: string\n catalogId: string\n kind: \"single\" | \"multi\"\n /** single only — the node-data field the chosen id writes to. */\n valueField?: string\n defaultValue?: string\n categoryOrder?: string[]\n categoryLabels?: Record<string, string>\n /** single-dim catalogs. */\n options?: PickerOption[]\n /** multi-dim catalogs. */\n fields?: string[]\n dimensions?: PickerDimension[]\n detail?: \"compact\" | \"full\"\n}\n\nexport interface PickerCatalogSummary {\n nodeType: string\n label: string\n catalogId: string\n kind: \"single\" | \"multi\"\n valueField?: string\n fields?: string[]\n optionCount: number\n}\n\nexport interface GetPickerCatalogOptions {\n /** \"compact\" (default) = id, label, category, icon; \"full\" additionally includes description + promptHint. */\n detail?: \"compact\" | \"full\"\n /** single-dim: filter to one category. */\n category?: string\n /** multi-dim: only this dimension field. */\n field?: string\n}\n\nexport class PickerCatalogsResource {\n constructor(private client: NodaroClient) {}\n\n /** List every parameter-picker node type + its option count. Cached publicly 5 min. */\n list(): Promise<{ data: PickerCatalogSummary[] }> {\n return this.client.request(\"GET\", \"/v1/picker-catalogs\")\n }\n\n /** Get one picker's catalog of valid values. */\n get(nodeType: string, opts: GetPickerCatalogOptions = {}): Promise<{ data: PickerCatalog }> {\n const qs = new URLSearchParams()\n if (opts.detail) qs.set(\"detail\", opts.detail)\n if (opts.category) qs.set(\"category\", opts.category)\n if (opts.field) qs.set(\"field\", opts.field)\n const query = qs.toString()\n return this.client.request(\n \"GET\",\n `/v1/picker-catalogs/${encodeURIComponent(nodeType)}${query ? `?${query}` : \"\"}`,\n )\n }\n}\n","import type { CommunityEntityType, CommunitySort, CommunityReportReason, CommunityCard, CommunityFullDetail, BrowseCommunityParams, BrowseCommunityResult, CloneListingResult, FavoriteListingResult, ReportListingResult, PublishListingParams, PublishListingResult, SharedListing } from \"@nodaro/shared\"\nexport type { CommunityEntityType, CommunitySort, CommunityReportReason, CommunityCard, CommunityFullDetail, BrowseCommunityParams, BrowseCommunityResult, CloneListingResult, FavoriteListingResult, ReportListingResult, PublishListingParams, PublishListingResult, SharedListing } from \"@nodaro/shared\"\nimport type { NodaroClient } from \"../client.js\"\n\n/**\n * The community-listing types are the single source of truth in\n * `@nodaro/shared` (re-used by the backend, frontend, and CLI). Re-export them\n * here so SDK consumers don't have to add `@nodaro/shared` as a second\n * dependency just to typecheck `browse`/`clone`/`favorite`/`report`.\n */\n\n/**\n * Community — browse, favorite, clone, and report shared characters,\n * locations, and objects.\n *\n * Publishing is intentionally NOT exposed here: it is admin-only via the\n * editor, and the publish route rejects personal access tokens (which is what\n * the SDK uses).\n */\nexport class CommunityResource {\n constructor(private client: NodaroClient) {}\n\n /**\n * `GET /v1/community/browse` → a page of public listings plus a `nextCursor`.\n * Pass the returned `nextCursor` back as `cursor` to fetch the next page.\n */\n browse(params: BrowseCommunityParams = {}): Promise<BrowseCommunityResult> {\n const qs = new URLSearchParams()\n if (params.entityType) qs.set(\"entityType\", params.entityType)\n if (params.q) qs.set(\"q\", params.q)\n if (params.category) qs.set(\"category\", params.category)\n if (params.sort) qs.set(\"sort\", params.sort)\n if (params.cursor) qs.set(\"cursor\", params.cursor)\n if (params.limit !== undefined) qs.set(\"limit\", String(params.limit))\n const query = qs.toString()\n return this.client.request(\n \"GET\",\n `/v1/community/browse${query ? `?${query}` : \"\"}`,\n )\n }\n\n /** `GET /v1/community/detail/:slug` → a single listing by its slug. */\n get(slug: string): Promise<{ data: CommunityCard }> {\n return this.client.request(\n \"GET\",\n `/v1/community/detail/${encodeURIComponent(slug)}`,\n )\n }\n\n /**\n * `GET /v1/community/detail/:slug/full` → the full read-only detail (card\n * identity + the stored public snapshot). Like {@link get}, but includes the\n * snapshot asset/voice/text blob needed to render the full cross-user view.\n */\n getFull(slug: string): Promise<{ data: CommunityFullDetail }> {\n return this.client.request(\n \"GET\",\n `/v1/community/detail/${encodeURIComponent(slug)}/full`,\n )\n }\n\n /** `GET /v1/community/favorites` → the listings you've favorited. */\n favorites(): Promise<{ data: CommunityCard[] }> {\n return this.client.request(\"GET\", \"/v1/community/favorites\")\n }\n\n /**\n * `POST /v1/community/listings/:id/clone` → copy a listing into your library.\n * Returns the new asset's `entityType` and `id`. Requires the `assets:write`\n * scope when called with an OAuth app token.\n */\n clone(id: string, entityType: CommunityEntityType): Promise<CloneListingResult> {\n return this.client.request(\n \"POST\",\n `/v1/community/listings/${encodeURIComponent(id)}/clone`,\n { body: { entityType } },\n )\n }\n\n /**\n * `POST /v1/community/listings/:id/favorite` → toggle a favorite. Returns the\n * resulting `favorited` state (`true` after adding, `false` after removing).\n */\n favorite(id: string): Promise<FavoriteListingResult> {\n return this.client.request(\n \"POST\",\n `/v1/community/listings/${encodeURIComponent(id)}/favorite`,\n )\n }\n\n /**\n * `POST /v1/community/listings/:id/report` → flag a listing for moderation.\n * `reason` must be one of {@link CommunityReportReason}.\n */\n report(id: string, reason: CommunityReportReason): Promise<ReportListingResult> {\n return this.client.request(\n \"POST\",\n `/v1/community/listings/${encodeURIComponent(id)}/report`,\n { body: { reason } },\n )\n }\n\n /**\n * `POST /v1/admin/community/:entityType/:id/publish` → share one of YOUR\n * entities to the community, returning the new listing's `slug` + `id`.\n *\n * **Requires an admin token** (the route is `requireAdmin`) AND the caller\n * must own the source entity. Personal/OAuth tokens without admin role get a\n * 401. For `character` listings, `params.likenessAttestation` must be `true`.\n */\n publish(\n entityType: CommunityEntityType,\n entityId: string,\n params: PublishListingParams,\n ): Promise<PublishListingResult> {\n return this.client.request(\n \"POST\",\n `/v1/admin/community/${encodeURIComponent(entityType)}/${encodeURIComponent(entityId)}/publish`,\n { body: params },\n )\n }\n\n /**\n * `DELETE /v1/admin/community/listings/:id` → unshare (deactivate) a listing\n * you published. **Requires an admin token** (the route is `requireAdmin`).\n */\n unpublish(listingId: string): Promise<{ ok: boolean }> {\n return this.client.request(\n \"DELETE\",\n `/v1/admin/community/listings/${encodeURIComponent(listingId)}`,\n )\n }\n\n /**\n * `GET /v1/admin/community/by-source/:entityType/:sourceId` → look up YOUR\n * existing listing (if any) for a source entity. Returns `{ data: null }`\n * when the entity hasn't been shared. **Requires an admin token** (the route\n * is `requireAdmin`); only returns listings created by the caller.\n */\n sharedListing(\n entityType: CommunityEntityType,\n sourceId: string,\n ): Promise<{ data: SharedListing | null }> {\n return this.client.request(\n \"GET\",\n `/v1/admin/community/by-source/${encodeURIComponent(entityType)}/${encodeURIComponent(sourceId)}`,\n )\n }\n}\n","import { throwFromResponse } from \"./errors.js\"\nimport type { Auth } from \"./auth.js\"\nimport { WorkflowsResource } from \"./resources/workflows.js\"\nimport { ProjectsResource } from \"./resources/projects.js\"\nimport { JobsResource } from \"./resources/jobs.js\"\nimport { ExecutionsResource } from \"./resources/executions.js\"\nimport { NodesResource } from \"./resources/nodes.js\"\nimport { DeveloperAppsResource } from \"./resources/developer-apps.js\"\nimport { OAuthResource } from \"./resources/oauth.js\"\nimport { AppsResource } from \"./resources/apps.js\"\nimport { CharactersResource } from \"./resources/characters.js\"\nimport { LocationsResource } from \"./resources/locations.js\"\nimport { ObjectsResource } from \"./resources/objects.js\"\nimport { CreaturesResource } from \"./resources/creatures.js\"\nimport { PipelinesResource } from \"./resources/pipelines.js\"\nimport { ReduceResource } from \"./resources/reduce.js\"\nimport { PromptHelperResource } from \"./resources/prompt-helper.js\"\nimport { VoicesResource } from \"./resources/voices.js\"\nimport { MediaResource } from \"./resources/media.js\"\nimport { AudioResource } from \"./resources/audio.js\"\nimport { CreditsResource } from \"./resources/credits.js\"\nimport { UploadsResource } from \"./resources/uploads.js\"\nimport { LibraryResource } from \"./resources/library.js\"\nimport { PresetsResource } from \"./resources/node-presets.js\"\nimport { PickerCatalogsResource } from \"./resources/picker-catalogs.js\"\nimport { CommunityResource } from \"./resources/community.js\"\n\nexport interface ClientOptions {\n /** Backend base URL, e.g. \"https://nodaro.example.com\" or empty string for same-origin. */\n baseUrl: string\n /** Auth provider. Use StaticTokenAuth, supabaseAuth, or CallbackAuth. */\n auth: Auth\n /** Optional fetch override (for tests or custom transports). */\n fetch?: typeof fetch\n /** Default request timeout in ms. Default 60s. */\n timeoutMs?: number\n}\n\ninterface RequestOptions {\n body?: unknown\n query?: Record<string, string | number | boolean | undefined>\n headers?: Record<string, string>\n signal?: AbortSignal\n}\n\n/**\n * The authenticated user's canonical identity (`GET /v1/me`). A token-\n * introspection primitive: any valid bearer token (first-party Supabase JWT or\n * a developer-app OAuth token) resolves to its owner's identity. Mirrors the\n * `profiles` identity columns server-side — the route is the source of truth.\n */\nexport interface UserIdentity {\n /** Nodaro user id (= the Supabase auth user id). */\n readonly id: string\n readonly email: string\n /** Human-readable display name (from `profiles.full_name`); `null` if unset. */\n readonly displayName: string | null\n /** Avatar URL; `null` if unset. */\n readonly avatarUrl: string | null\n /** Subscription tier (e.g. \"free\", \"pro\"). */\n readonly tier: string\n}\n\nexport class NodaroClient {\n readonly baseUrl: string\n readonly auth: Auth\n readonly timeoutMs: number\n private readonly fetchOverride: typeof fetch | undefined\n\n /**\n * Resolved lazily so consumers can swap `globalThis.fetch` after the\n * client has been constructed (e.g. test mocks). Always rebound to the\n * global object — native fetch throws \"Illegal invocation\" when its\n * `this` is anything else.\n */\n get fetch(): typeof fetch {\n return this.fetchOverride ?? globalThis.fetch.bind(globalThis)\n }\n\n readonly workflows: WorkflowsResource\n readonly projects: ProjectsResource\n readonly jobs: JobsResource\n readonly executions: ExecutionsResource\n readonly nodes: NodesResource\n readonly developerApps: DeveloperAppsResource\n readonly oauth: OAuthResource\n readonly apps: AppsResource\n readonly characters: CharactersResource\n readonly locations: LocationsResource\n readonly objects: ObjectsResource\n readonly creatures: CreaturesResource\n readonly pipelines: PipelinesResource\n readonly reduce: ReduceResource\n readonly promptHelper: PromptHelperResource\n readonly voices: VoicesResource\n readonly media: MediaResource\n readonly audio: AudioResource\n readonly credits: CreditsResource\n readonly uploads: UploadsResource\n readonly library: LibraryResource\n readonly presets: PresetsResource\n readonly pickerCatalogs: PickerCatalogsResource\n readonly community: CommunityResource\n\n constructor(opts: ClientOptions) {\n this.baseUrl = opts.baseUrl.replace(/\\/$/, \"\") // strip trailing slash\n this.auth = opts.auth\n this.fetchOverride = opts.fetch\n this.timeoutMs = opts.timeoutMs ?? 60_000\n\n this.workflows = new WorkflowsResource(this)\n this.projects = new ProjectsResource(this)\n this.jobs = new JobsResource(this)\n this.executions = new ExecutionsResource(this)\n this.nodes = new NodesResource(this)\n this.developerApps = new DeveloperAppsResource(this)\n this.oauth = new OAuthResource(this)\n this.apps = new AppsResource(this)\n this.characters = new CharactersResource(this)\n this.locations = new LocationsResource(this)\n this.objects = new ObjectsResource(this)\n this.creatures = new CreaturesResource(this)\n this.pipelines = new PipelinesResource(this)\n this.reduce = new ReduceResource(this)\n this.promptHelper = new PromptHelperResource(this)\n this.voices = new VoicesResource(this)\n this.media = new MediaResource(this)\n this.audio = new AudioResource(this)\n this.credits = new CreditsResource(this)\n this.uploads = new UploadsResource(this)\n this.library = new LibraryResource(this)\n this.presets = new PresetsResource(this)\n this.pickerCatalogs = new PickerCatalogsResource(this)\n this.community = new CommunityResource(this)\n }\n\n async request<T>(method: string, path: string, options: RequestOptions = {}): Promise<T> {\n const url = this.buildUrl(path, options.query)\n\n const token = await this.auth.getToken()\n // A `FormData` body is a multipart upload: let the runtime set\n // `Content-Type: multipart/form-data; boundary=…` itself (a manual JSON\n // content-type corrupts the boundary), and send the body as-is rather than\n // JSON-stringifying it. Every other body stays JSON, exactly as before.\n const isFormData =\n typeof FormData !== \"undefined\" && options.body instanceof FormData\n const headers: Record<string, string> = {\n ...(isFormData ? {} : { \"Content-Type\": \"application/json\" }),\n ...(options.headers ?? {}),\n }\n if (token) headers[\"Authorization\"] = `Bearer ${token}`\n\n const ac = new AbortController()\n const timeoutId = setTimeout(() => ac.abort(), this.timeoutMs)\n if (options.signal) {\n options.signal.addEventListener(\"abort\", () => ac.abort(), { once: true })\n }\n\n try {\n const res = await this.fetch(url, {\n method,\n headers,\n body:\n options.body === undefined\n ? undefined\n : isFormData\n ? (options.body as FormData)\n : JSON.stringify(options.body),\n signal: ac.signal,\n })\n\n if (!res.ok) {\n let errBody: Record<string, unknown> = {}\n try {\n errBody = await res.json() as Record<string, unknown>\n } catch {\n // Empty/non-JSON body — fall through with empty errBody\n }\n throwFromResponse(res.status, errBody)\n }\n\n // 204 No Content\n if (res.status === 204) return undefined as T\n return await res.json() as T\n } finally {\n clearTimeout(timeoutId)\n }\n }\n\n /**\n * `GET /v1/me` → the authenticated user's identity (see {@link UserIdentity}).\n * Unwraps the `{ data }` envelope. Throws `UnauthorizedError` (401) when the\n * token is missing/invalid, and the SDK's other typed errors as usual.\n */\n async me(): Promise<UserIdentity> {\n const res = await this.request<{ data: UserIdentity }>(\"GET\", \"/v1/me\")\n return res.data\n }\n\n private buildUrl(path: string, query?: Record<string, string | number | boolean | undefined>): string {\n const base = this.baseUrl || (typeof window !== \"undefined\" ? window.location.origin : \"http://placeholder\")\n const url = new URL(path, base)\n // If baseUrl was empty, strip the placeholder origin\n const fullUrl = this.baseUrl ? url.toString() : url.pathname + url.search\n if (query) {\n const u = new URL(this.baseUrl ? fullUrl : fullUrl, base)\n for (const [k, v] of Object.entries(query)) {\n if (v !== undefined) u.searchParams.set(k, String(v))\n }\n return this.baseUrl ? u.toString() : u.pathname + u.search\n }\n return fullUrl\n }\n}\n\n/** Factory function — preferred entry point. */\nexport function createClient(opts: ClientOptions): NodaroClient {\n return new NodaroClient(opts)\n}\n","/**\n * Auth provides the token used for `Authorization: Bearer <token>` on each request.\n * Implementations:\n * - StaticTokenAuth — fixed string (server-side, OAuth access token, API token)\n * - CallbackAuth — caller-supplied async function (BYO logic)\n * - supabaseAuth — pulls JWT from a Supabase client live (browser frontends)\n */\nexport interface Auth {\n /** Returns the current Bearer token, or null if not authenticated. */\n getToken(): Promise<string | null>\n}\n\nexport class StaticTokenAuth implements Auth {\n constructor(private token: string) {}\n async getToken() {\n return this.token\n }\n}\n\nexport class CallbackAuth implements Auth {\n constructor(private fn: () => string | null | Promise<string | null>) {}\n async getToken() {\n return this.fn()\n }\n}\n\ninterface SupabaseLikeClient {\n auth: {\n getSession(): Promise<{ data: { session: { access_token: string } | null } }>\n }\n}\n\n/** Pulls a JWT from a Supabase v2 client. Caller supplies their own supabase. */\nexport function supabaseAuth(supabase: SupabaseLikeClient): Auth {\n return {\n async getToken() {\n const { data } = await supabase.auth.getSession()\n return data.session?.access_token ?? null\n },\n }\n}\n"]}
|