@puppetry.com/sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,372 @@
1
+ # @puppetry.com/sdk
2
+
3
+ Official TypeScript SDK for the [Puppetry API](https://www.puppetry.com) — create talking head videos from photos using AI.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @puppetry.com/sdk
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```typescript
14
+ import { Puppetry } from "@puppetry.com/sdk";
15
+
16
+ const client = new Puppetry({ apiKey: "pk_..." });
17
+
18
+ // Create a video from text
19
+ const job = await client.videos.createFromText({
20
+ text: "Welcome to our product demo!",
21
+ image_url: "https://example.com/photo.jpg",
22
+ voice: "puppetry-af_heart",
23
+ idempotencyKey: "demo-video-1",
24
+ });
25
+
26
+ // Wait for completion (polls automatically, honoring the create Retry-After hint)
27
+ const video = await job.waitForCompletion();
28
+ console.log(video.url);
29
+ ```
30
+
31
+ ## Features
32
+
33
+ - **Text-to-Video**: Provide text → get a talking head video with AI voice
34
+ - **Audio-to-Video**: Provide audio → lip-sync any portrait
35
+ - **500+ Voices**: Choose from built-in voices or clone your own
36
+ - **29 Languages**: Arabic, Chinese, English, French, German, Hindi, and more
37
+ - **Progress Events**: Stream job events through status polling that follows API retry hints
38
+ - **Voice Cloning**: Upload 6 seconds of audio to create a custom voice
39
+ - **TypeScript-first**: Full type safety, JSDoc, IntelliSense support
40
+
41
+ ## API Reference
42
+
43
+ ### Create a Client
44
+
45
+ ```typescript
46
+ const client = new Puppetry({
47
+ apiKey: "pk_...", // Required — get yours at puppetry.com/dashboard/settings/api-keys
48
+ baseUrl: "https://www.puppetry.com/api/v1", // Optional
49
+ timeout: 30000, // Optional, ms
50
+ maxRetries: 2, // Optional, retries GET 429/5xx and idempotent POST 429/5xx
51
+ retryDelayMs: 500, // Optional, exponential backoff base delay
52
+ onRetry: (event) => console.warn(event.code, event.retryAfter), // Optional
53
+ });
54
+ ```
55
+
56
+ The SDK only retries side-effecting `POST` calls when you pass an
57
+ `idempotencyKey`. Use one for video creates and signed upload URL requests so a
58
+ transient 429/5xx cannot create duplicate jobs or charges.
59
+ `onRetry` runs before the SDK sleeps for a safe/idempotent retry and includes
60
+ Developer API job, operation, request, retry-after, and status URL metadata when
61
+ the server returns it.
62
+
63
+ ### Videos
64
+
65
+ ```typescript
66
+ // From text (text-to-speech + lip sync)
67
+ const job = await client.videos.createFromText({
68
+ text: "Hello, world!",
69
+ image_url: "https://example.com/photo.jpg",
70
+ voice: "puppetry-af_heart",
71
+ language: "en",
72
+ expressiveness: 1.0,
73
+ idempotencyKey: "text-video-demo-1",
74
+ });
75
+ console.log(job.source); // "text"
76
+ console.log(`First poll at ${job.nextPollAt ?? "the Retry-After time"}`);
77
+ ```
78
+
79
+ Agents and launch checks can preflight video credits and active slots before
80
+ reserving uploads or queueing a job:
81
+
82
+ ```typescript
83
+ const readiness = await client.videos.getReadiness();
84
+ if (!readiness.can_create) {
85
+ console.log(readiness.reason, readiness.retryAfter);
86
+ for (const blocker of readiness.blockers ?? []) {
87
+ console.log(blocker.code, blocker.retryable);
88
+ }
89
+ }
90
+
91
+ // Throws PuppetryRateLimitError with retryAfter for active-slot blockers, or
92
+ // PuppetryError(402) when the API key is out of video credits.
93
+ await client.videos.ensureReady();
94
+ ```
95
+
96
+ `readiness.blockers` preserves the API's machine-readable recovery list. Agents
97
+ can treat `insufficient_video_credits` as a buy-credits path and
98
+ `concurrent_job_limit_reached` as a backoff path using `retryAfter` or
99
+ `retry_after_seconds`.
100
+
101
+ For hosted audio uploads that will immediately feed `videos.createFromAudio()`,
102
+ pass `requireVideoReadiness: true` so Puppetry fails before reserving upload
103
+ quota if video credits or active slots are currently blocked.
104
+
105
+ If an idempotent retry replays an already completed job, the SDK normalizes the
106
+ terminal URL onto `url`, `videoUrl`, `resultUrl`, `downloadUrl`, and
107
+ `outputUrl` while
108
+ preserving raw API fields such as `download_url` and `idempotent_replay`.
109
+ Video job responses also preserve and normalize the create source across
110
+ `source`, `request_source`, and `requestSource`, so agents can distinguish text
111
+ jobs from audio/lip-sync jobs during status polling or idempotent replay. The
112
+ seven-day lookup expiry is normalized across `expires_at` and `expiresAt`.
113
+ Retryable video job responses expose both relative retry seconds and absolute
114
+ poll timestamps as `next_poll_at` / `nextPollAt`.
115
+ SDK `waitForCompletion()` and `videos.stream()` polling honors both forms, so
116
+ agents can follow the server-paced poll schedule without custom sleep logic.
117
+ Video create, replay, and status responses also normalize the public operation
118
+ handle across `operation_id` / `operationId`, using the response operation-id
119
+ headers when the body omits those aliases.
120
+ `waitForCompletion()` also treats retryable status-poll errors as part of the
121
+ same job lifecycle: it uses the error's preserved job identity, status URL, and
122
+ retry timing to keep polling the original job until the caller timeout expires.
123
+ The polling URL is normalized across `status_url` / `statusUrl`; when a queued
124
+ create or replay response only exposes the URL in `Location` /
125
+ `Content-Location`, the SDK fills the same aliases from those headers. Job
126
+ helpers also derive their polling target from that canonical status URL when it
127
+ is present, so idempotent replays and sparse header-only responses keep polling
128
+ the accepted video job.
129
+
130
+ ```typescript
131
+ // From audio (lip sync only)
132
+ const job2 = await client.videos.createFromAudio({
133
+ audio_url: "https://example.com/speech.mp3",
134
+ image_url: "https://example.com/photo.jpg",
135
+ idempotencyKey: "audio-video-demo-1",
136
+ });
137
+
138
+ // Get job status
139
+ const status = await client.jobs.get(job.id);
140
+
141
+ // Wait for completion (with polling)
142
+ const completed = await job.waitForCompletion({
143
+ intervalMs: 2000, // Poll every 2s (default)
144
+ timeoutMs: 300000, // 5 min timeout (default)
145
+ // initialDelayMs defaults to the job's Retry-After hint when present
146
+ onPoll: (latest) => {
147
+ console.log(latest.status, latest.progress ?? 0);
148
+ },
149
+ });
150
+
151
+ // The explicit resource helper is still available when you only have an ID.
152
+ const completedAgain = await client.jobs.waitForCompletion(job.id, {
153
+ initialDelayMs: (job.retryAfter ?? 0) * 1000,
154
+ });
155
+
156
+ // Stream progress events through polling
157
+ for await (const event of client.videos.stream(job.id)) {
158
+ console.log(`${event.type}: ${event.progress}%`);
159
+ if (event.type === "completed") {
160
+ console.log(`Video ready: ${event.video_url}`);
161
+ }
162
+ }
163
+ ```
164
+
165
+ ### Voices
166
+
167
+ ```typescript
168
+ // List available voices
169
+ const { data: voices } = await client.voices.list({
170
+ language: "en",
171
+ gender: "female",
172
+ limit: 20,
173
+ });
174
+
175
+ // List built-in Puppetry voices for /tts/puppetry
176
+ const puppetryVoices = await client.voices.listPuppetry();
177
+ console.log(puppetryVoices.default_voice_id);
178
+ console.log(puppetryVoices.data[0]?.preview_text);
179
+ ```
180
+
181
+ ### Text to Speech
182
+
183
+ ```typescript
184
+ const voices = await client.voices.listPuppetry();
185
+
186
+ const audio = await client.tts.createPuppetry({
187
+ voice_id: voices.default_voice_id,
188
+ text: "Create a voiceover before turning it into video.",
189
+ speed: 1.0,
190
+ });
191
+
192
+ console.log(audio.audio_url);
193
+ ```
194
+
195
+ ### Uploads
196
+
197
+ ```typescript
198
+ const upload = await client.uploads.createAudioUrl({
199
+ contentType: "audio/mpeg",
200
+ fileSize: 12345,
201
+ idempotencyKey: "upload-demo-1",
202
+ });
203
+
204
+ await fetch(upload.uploadUrl, {
205
+ method: upload.method,
206
+ headers: upload.headers,
207
+ body: audioBytes,
208
+ });
209
+
210
+ console.log(upload.readUrl);
211
+
212
+ // Or let the SDK request the signed URL and upload bytes in one call.
213
+ const uploaded = await client.uploads.uploadAudio({
214
+ contentType: "audio/mpeg",
215
+ sizeBytes: audioBytes.byteLength,
216
+ body: audioBytes,
217
+ idempotencyKey: "upload-demo-1",
218
+ });
219
+
220
+ const job = await client.videos.createFromAudio({
221
+ audio_url: uploaded.readUrl,
222
+ image_url: "https://example.com/photo.jpg",
223
+ });
224
+ ```
225
+
226
+ The raw API fields (`upload_url`, `read_url`, expiry fields, and limits) stay
227
+ available too; the SDK also exposes camelCase aliases for TypeScript and agent
228
+ callers, including `idempotentReplay` when a retried upload reservation is
229
+ replayed.
230
+
231
+ ### Puppets
232
+
233
+ ```typescript
234
+ // Create a reusable puppet (portrait)
235
+ const puppet = await client.puppets.create({
236
+ imageUrl: "https://example.com/portrait.jpg",
237
+ name: "Marketing Avatar",
238
+ });
239
+
240
+ // List puppets
241
+ const { data: puppets } = await client.puppets.list({ limit: 10 });
242
+
243
+ // Delete a puppet when it should no longer appear in your reusable library
244
+ await client.puppets.delete(puppet.id);
245
+ ```
246
+
247
+ ### Usage
248
+
249
+ ```typescript
250
+ const usage = await client.usage.get();
251
+ console.log(
252
+ `${usage.videoCredits?.balance ?? usage.creditsRemaining} video credits remaining`,
253
+ );
254
+ console.log(usage.videoGeneration?.blockers ?? []);
255
+ console.log(
256
+ `${usage.videoCredits?.netUsed ?? usage.creditsUsed} video credits used this month`,
257
+ );
258
+
259
+ // Agent integrations can use the quota alias for a clearer tool name.
260
+ const quota = await client.quota.get();
261
+ console.log(`${quota.creditsRemaining} video credits remaining`);
262
+ ```
263
+
264
+ ### Agent Tool Manifest
265
+
266
+ ```typescript
267
+ import {
268
+ Puppetry,
269
+ PUPPETRY_AGENT_TOOLS,
270
+ callPuppetryAgentTool,
271
+ } from "@puppetry.com/sdk";
272
+
273
+ console.log(PUPPETRY_AGENT_TOOLS.map((tool) => tool.name));
274
+ // puppetry_create_video_from_text, puppetry_create_video_from_audio,
275
+ // puppetry_create_video_from_text_and_wait,
276
+ // puppetry_create_video_from_audio_and_wait, puppetry_create_audio_upload_url,
277
+ // puppetry_lipsync, puppetry_list_voices, puppetry_get_job_status,
278
+ // puppetry_wait_for_video, puppetry_get_video_readiness, puppetry_get_quota
279
+
280
+ const client = new Puppetry({ apiKey: process.env.PUPPETRY_API_KEY! });
281
+ const result = await callPuppetryAgentTool(
282
+ client,
283
+ "puppetry_create_video_from_text",
284
+ {
285
+ prompt: "Agent-generated launch update.",
286
+ photo_url: "https://example.com/avatar.jpg",
287
+ voice: "puppetry-af_heart",
288
+ preflightReadiness: true,
289
+ },
290
+ );
291
+
292
+ console.log(result.id);
293
+ console.log(result.readiness_checked, result.readiness?.credits_remaining);
294
+
295
+ await callPuppetryAgentTool(client, "puppetry_get_job_status", {
296
+ taskId: result.taskId ?? result.id,
297
+ });
298
+ ```
299
+
300
+ Direct video agent tools accept `preflightReadiness` / `preflight_readiness`
301
+ when the caller wants to check video credits and active slots before queueing a
302
+ credit-bearing job. Successful direct-create results include
303
+ `readiness_checked` / `readinessChecked` and, when preflight ran, the normalized
304
+ `readiness` object that was checked before the create POST. The create-and-wait
305
+ agent tools perform that preflight by default and expose
306
+ `preflightReadiness: false` only for callers that already checked readiness.
307
+ Both result shapes include snake_case and camelCase readiness aliases so agents
308
+ can read the same evidence regardless of their naming convention.
309
+
310
+ ### MCP stdio server
311
+
312
+ ```json
313
+ {
314
+ "mcpServers": {
315
+ "puppetry": {
316
+ "command": "npx",
317
+ "args": ["-y", "@puppetry.com/sdk"],
318
+ "env": { "PUPPETRY_API_KEY": "pk_live_xxx" }
319
+ }
320
+ }
321
+ }
322
+ ```
323
+
324
+ The `puppetry-mcp` bin serves the same tool manifest over MCP stdio and
325
+ dispatches calls through the SDK. By default it uses
326
+ `https://www.puppetry.com/api/v1`, the live Developer API route family. Set
327
+ `PUPPETRY_BASE_URL` only when pointing agents at a preview or staging API host.
328
+ When the live API returns a Puppetry error, MCP JSON-RPC errors include
329
+ structured `error.data` with the API `status`/`statusCode`, Puppetry `code`,
330
+ `message`, optional `details`, and optional `retry_after_seconds`/
331
+ `retryAfterSeconds`/`retryAfter` seconds so agents can back off instead of
332
+ retrying blindly. Retry hints are normalized to seconds whether the API sends a
333
+ numeric or HTTP-date `Retry-After` header, and fall back to top-level
334
+ `retry_after_seconds` / `retryAfterSeconds` / `retryAfter` body aliases or
335
+ matching `details.*` aliases if an intermediary strips the header. Video job
336
+ responses also include `next_poll_at` / `nextPollAt` when the API can provide an
337
+ absolute retry time, plus pollable video identity fields such as `object`,
338
+ `id`, `job_id` / `jobId`, `source`, `request_source` / `requestSource`,
339
+ `status_url` / `statusUrl`, and `expires_at` / `expiresAt` when the error
340
+ belongs to a video job.
341
+
342
+ ## Error Handling
343
+
344
+ ```typescript
345
+ import { PuppetryError, PuppetryAuthError, PuppetryRateLimitError, PuppetryNetworkError } from '@puppetry.com/sdk';
346
+
347
+ try {
348
+ await client.videos.createFromText({ ... });
349
+ } catch (err) {
350
+ if (err instanceof PuppetryAuthError) {
351
+ console.error('Invalid API key');
352
+ } else if (err instanceof PuppetryRateLimitError) {
353
+ console.error(`Rate limited. Retry after ${err.retryAfter}s`);
354
+ } else if (err instanceof PuppetryNetworkError) {
355
+ console.error(`Network error: ${err.message}`);
356
+ } else if (err instanceof PuppetryError) {
357
+ if (err.retryAfter) {
358
+ console.error(`Try again after ${err.retryAfter}s`);
359
+ }
360
+ console.error(`API error ${err.status}: ${err.message}`);
361
+ }
362
+ }
363
+ ```
364
+
365
+ ## Requirements
366
+
367
+ - Node.js 18+ (uses native `fetch`)
368
+ - For Node.js 16, pass a `fetch` implementation: `new Puppetry({ apiKey: '...', fetch: nodeFetch })`
369
+
370
+ ## License
371
+
372
+ MIT