@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 +372 -0
- package/dist/chunk-IP47SEN7.mjs +1638 -0
- package/dist/chunk-QK5VPBL7.mjs +1500 -0
- package/dist/index.d.mts +1105 -0
- package/dist/index.d.ts +1105 -0
- package/dist/index.js +3163 -0
- package/dist/index.mjs +28 -0
- package/dist/mcp-ChYQVbVe.d.mts +947 -0
- package/dist/mcp-ChYQVbVe.d.ts +947 -0
- package/dist/mcp-stdio.d.mts +1 -0
- package/dist/mcp-stdio.d.ts +1 -0
- package/dist/mcp-stdio.js +3174 -0
- package/dist/mcp-stdio.mjs +65 -0
- package/dist/mcp.d.mts +1 -0
- package/dist/mcp.d.ts +1 -0
- package/dist/mcp.js +1451 -0
- package/dist/mcp.mjs +6 -0
- package/package.json +61 -0
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
|