@pixeldojo/mcp 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/dist/index.js ADDED
@@ -0,0 +1,587 @@
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
4
+ import { PixelDojoClient } from '@pixeldojo/sdk';
5
+ import { z } from 'zod';
6
+
7
+ // src/server.ts
8
+ function makeAttributedClient(options) {
9
+ const { mcpVersion, skillName, baseFetch, ...sdkOptions } = options;
10
+ const real = baseFetch ?? globalThis.fetch.bind(globalThis);
11
+ const wrappedFetch = (input, init) => {
12
+ const headers = new Headers(init?.headers);
13
+ headers.set("x-pixeldojo-source", `mcp/${mcpVersion}`);
14
+ if (skillName) headers.set("x-pixeldojo-skill", skillName);
15
+ return real(input, { ...init, headers });
16
+ };
17
+ return new PixelDojoClient({
18
+ ...sdkOptions,
19
+ fetch: wrappedFetch
20
+ });
21
+ }
22
+
23
+ // src/poll.ts
24
+ var DEFAULT_BACKOFF = [1e3, 2e3, 4e3, 8e3];
25
+ var DEFAULT_MAX_INTERVAL = 8e3;
26
+ var DEFAULT_SYNC_BUDGET_MS = 3e4;
27
+ function defaultSleep(ms, signal) {
28
+ return new Promise((resolve, reject) => {
29
+ if (signal?.aborted) {
30
+ reject(new DOMException("aborted", "AbortError"));
31
+ return;
32
+ }
33
+ const timer = setTimeout(() => {
34
+ signal?.removeEventListener("abort", onAbort);
35
+ resolve();
36
+ }, ms);
37
+ const onAbort = () => {
38
+ clearTimeout(timer);
39
+ reject(new DOMException("aborted", "AbortError"));
40
+ };
41
+ signal?.addEventListener("abort", onAbort, { once: true });
42
+ });
43
+ }
44
+ async function pollJobUntilDoneOrHandoff(jobId, fetchJob, buildStatusUrl, options = {}) {
45
+ const {
46
+ syncBudgetMs = DEFAULT_SYNC_BUDGET_MS,
47
+ backoffSequenceMs = DEFAULT_BACKOFF,
48
+ maxIntervalMs = DEFAULT_MAX_INTERVAL,
49
+ now = Date.now,
50
+ sleep = (ms) => defaultSleep(ms, options.signal),
51
+ signal
52
+ } = options;
53
+ const start = now();
54
+ let attempt = 0;
55
+ while (true) {
56
+ if (signal?.aborted) throw new DOMException("aborted", "AbortError");
57
+ const job = await fetchJob(jobId);
58
+ if (job.status === "completed") return { kind: "completed", job };
59
+ if (job.status === "failed") return { kind: "failed", job };
60
+ const elapsed = now() - start;
61
+ const remaining = syncBudgetMs - elapsed;
62
+ if (remaining <= 0) {
63
+ return {
64
+ kind: "handoff",
65
+ jobId,
66
+ statusUrl: buildStatusUrl(jobId)
67
+ };
68
+ }
69
+ const interval = attempt < backoffSequenceMs.length ? backoffSequenceMs[attempt] : maxIntervalMs;
70
+ const wait = Math.min(interval, Math.max(remaining, 0));
71
+ if (wait > 0) await sleep(wait);
72
+ attempt++;
73
+ }
74
+ }
75
+
76
+ // src/skills/types.ts
77
+ function pollResultToSkillResult(result, successText) {
78
+ if (result.kind === "completed") {
79
+ const urls = extractAssetUrls(result.job);
80
+ return {
81
+ kind: "assets",
82
+ text: successText(urls),
83
+ assetUrls: urls
84
+ };
85
+ }
86
+ if (result.kind === "failed") {
87
+ const errMsg = typeof result.job.error === "string" ? result.job.error : result.job.error?.message ?? "Unknown provider error";
88
+ return {
89
+ kind: "text",
90
+ text: `Job ${result.job.jobId} failed: ${errMsg}`
91
+ };
92
+ }
93
+ return {
94
+ kind: "handoff",
95
+ text: `Job ${result.jobId} is still running after the 30s sync window. Use pixeldojo:status with this jobId to check completion, or follow ${result.statusUrl}.`,
96
+ jobId: result.jobId,
97
+ statusUrl: result.statusUrl
98
+ };
99
+ }
100
+ function extractAssetUrls(job) {
101
+ if (job.assets && job.assets.length > 0) return job.assets.map((a) => a.url);
102
+ const output = job.output;
103
+ if (output?.images) return output.images;
104
+ if (output?.videos) return output.videos;
105
+ if (output?.image) return [output.image];
106
+ if (output?.video) return [output.video];
107
+ return [];
108
+ }
109
+
110
+ // src/skills/generate.ts
111
+ var InputSchema = z.object({
112
+ prompt: z.string().min(1).max(8e3).describe("Prompt describing what to generate."),
113
+ model: z.string().optional().describe(
114
+ 'Override the model. Any apiId from /api/v1/models (e.g. "flux", "google-nano-banana", "veo-3.1", "kling-video"). Omit to use the default.'
115
+ ),
116
+ aspect_ratio: z.string().optional().describe('Aspect ratio supported by the chosen model (e.g. "1:1", "16:9", "9:16").'),
117
+ duration: z.number().int().positive().optional().describe("Video duration in seconds, when applicable."),
118
+ image_url: z.string().url().optional().describe("Reference image URL for image-to-image / image-to-video tools.")
119
+ });
120
+ var DEFAULT_MODEL = "google-nano-banana";
121
+ var InputJsonSchema = {
122
+ type: "object",
123
+ properties: {
124
+ prompt: { type: "string", description: "Prompt describing what to generate." },
125
+ model: {
126
+ type: "string",
127
+ description: 'Override the model. Any apiId from /api/v1/models (e.g. "flux", "google-nano-banana", "veo-3.1", "kling-video"). Omit to use the default.'
128
+ },
129
+ aspect_ratio: { type: "string" },
130
+ duration: { type: "integer" },
131
+ image_url: { type: "string", format: "uri" }
132
+ },
133
+ required: ["prompt"]
134
+ };
135
+ async function run(input, ctx) {
136
+ const apiId = input.model ?? DEFAULT_MODEL;
137
+ const body = { prompt: input.prompt };
138
+ if (input.aspect_ratio) body.aspect_ratio = input.aspect_ratio;
139
+ if (input.duration !== void 0) body.duration = input.duration;
140
+ if (input.image_url) body.image_url = input.image_url;
141
+ const submitted = await ctx.client.run(apiId, body);
142
+ const result = await pollJobUntilDoneOrHandoff(
143
+ submitted.jobId,
144
+ (id) => ctx.client.getJob(id),
145
+ ctx.buildStatusUrl,
146
+ {
147
+ now: ctx.pollOverrides?.now,
148
+ sleep: ctx.pollOverrides?.sleep
149
+ }
150
+ );
151
+ return pollResultToSkillResult(
152
+ result,
153
+ (urls) => urls.length === 0 ? `Job ${submitted.jobId} completed but returned no assets.` : urls.length === 1 ? `Generated. Asset: ${urls[0]}` : `Generated ${urls.length} assets. URLs: ${urls.join(", ")}`
154
+ );
155
+ }
156
+ var generateSkill = {
157
+ name: "pixeldojo:generate",
158
+ description: "Generate any image or video from a prompt. Auto-routes to a sensible default model; pass `model` to pick a specific apiId from the catalog.",
159
+ inputZod: InputSchema,
160
+ inputJsonSchema: InputJsonSchema,
161
+ allowedModels: null,
162
+ defaultModel: DEFAULT_MODEL,
163
+ run
164
+ };
165
+ var ALLOWED = ["ideogram-character", "flux-edit", "gpt-image-2-edit"];
166
+ var DEFAULT_MODEL2 = "ideogram-character";
167
+ var InputSchema2 = z.object({
168
+ prompt: z.string().min(1).max(8e3),
169
+ model: z.string().optional().describe(`One of: ${ALLOWED.join(", ")}. Defaults to ${DEFAULT_MODEL2}.`),
170
+ reference_image_url: z.string().url().optional().describe(
171
+ "Reference image of the character. Required by flux-edit and gpt-image-2-edit; optional for ideogram-character."
172
+ ),
173
+ aspect_ratio: z.string().optional()
174
+ });
175
+ var InputJsonSchema2 = {
176
+ type: "object",
177
+ properties: {
178
+ prompt: { type: "string" },
179
+ model: {
180
+ type: "string",
181
+ enum: ALLOWED,
182
+ description: `One of: ${ALLOWED.join(", ")}. Defaults to ${DEFAULT_MODEL2}.`
183
+ },
184
+ reference_image_url: { type: "string", format: "uri" },
185
+ aspect_ratio: { type: "string" }
186
+ },
187
+ required: ["prompt"]
188
+ };
189
+ async function run2(input, ctx) {
190
+ const requested = input.model ?? DEFAULT_MODEL2;
191
+ if (!ALLOWED.includes(requested)) {
192
+ return {
193
+ kind: "text",
194
+ text: `Unknown model "${requested}" for pixeldojo:character. Allowed: ${ALLOWED.join(", ")}.`
195
+ };
196
+ }
197
+ const apiId = requested;
198
+ const body = { prompt: input.prompt };
199
+ if (input.aspect_ratio) body.aspect_ratio = input.aspect_ratio;
200
+ if (input.reference_image_url) {
201
+ body.image_url = input.reference_image_url;
202
+ body.reference_image_url = input.reference_image_url;
203
+ }
204
+ const submitted = await ctx.client.run(apiId, body);
205
+ const result = await pollJobUntilDoneOrHandoff(
206
+ submitted.jobId,
207
+ (id) => ctx.client.getJob(id),
208
+ ctx.buildStatusUrl,
209
+ {
210
+ now: ctx.pollOverrides?.now,
211
+ sleep: ctx.pollOverrides?.sleep
212
+ }
213
+ );
214
+ return pollResultToSkillResult(
215
+ result,
216
+ (urls) => urls.length === 0 ? `Job ${submitted.jobId} completed but returned no assets.` : `Character shot ready. Asset: ${urls[0]}`
217
+ );
218
+ }
219
+ var characterSkill = {
220
+ name: "pixeldojo:character",
221
+ description: "Generate or edit consistent characters across shots. Pass a reference_image_url for edit-style models.",
222
+ inputZod: InputSchema2,
223
+ inputJsonSchema: InputJsonSchema2,
224
+ allowedModels: [...ALLOWED],
225
+ defaultModel: DEFAULT_MODEL2,
226
+ run: run2
227
+ };
228
+ var ALLOWED2 = ["veo-3.1", "kling-video", "seedance-2"];
229
+ var DEFAULT_MODEL3 = "veo-3.1";
230
+ var InputSchema3 = z.object({
231
+ brief: z.string().min(1).max(8e3).describe("Storyboard brief: scene, shots, mood, action."),
232
+ model: z.string().optional().describe(`One of: ${ALLOWED2.join(", ")}. Defaults to ${DEFAULT_MODEL3}.`),
233
+ aspect_ratio: z.string().optional(),
234
+ duration: z.number().int().positive().optional()
235
+ });
236
+ var InputJsonSchema3 = {
237
+ type: "object",
238
+ properties: {
239
+ brief: {
240
+ type: "string",
241
+ description: "Storyboard brief: scene, shots, mood, action."
242
+ },
243
+ model: {
244
+ type: "string",
245
+ enum: ALLOWED2,
246
+ description: `One of: ${ALLOWED2.join(", ")}. Defaults to ${DEFAULT_MODEL3}.`
247
+ },
248
+ aspect_ratio: { type: "string" },
249
+ duration: { type: "integer" }
250
+ },
251
+ required: ["brief"]
252
+ };
253
+ async function run3(input, ctx) {
254
+ const requested = input.model ?? DEFAULT_MODEL3;
255
+ if (!ALLOWED2.includes(requested)) {
256
+ return {
257
+ kind: "text",
258
+ text: `Unknown model "${requested}" for pixeldojo:storyboard. Allowed: ${ALLOWED2.join(", ")}.`
259
+ };
260
+ }
261
+ const apiId = requested;
262
+ const body = { prompt: input.brief };
263
+ if (input.aspect_ratio) body.aspect_ratio = input.aspect_ratio;
264
+ if (input.duration !== void 0) body.duration = input.duration;
265
+ const submitted = await ctx.client.run(apiId, body);
266
+ const result = await pollJobUntilDoneOrHandoff(
267
+ submitted.jobId,
268
+ (id) => ctx.client.getJob(id),
269
+ ctx.buildStatusUrl,
270
+ {
271
+ now: ctx.pollOverrides?.now,
272
+ sleep: ctx.pollOverrides?.sleep
273
+ }
274
+ );
275
+ return pollResultToSkillResult(
276
+ result,
277
+ (urls) => urls.length === 0 ? `Job ${submitted.jobId} completed but returned no assets.` : `Storyboard preview ready. Asset: ${urls[0]}`
278
+ );
279
+ }
280
+ var storyboardSkill = {
281
+ name: "pixeldojo:storyboard",
282
+ description: "Generate a video storyboard preview from a single brief. v1: single-shot preview; multi-shot orchestration coming.",
283
+ inputZod: InputSchema3,
284
+ inputJsonSchema: InputJsonSchema3,
285
+ allowedModels: [...ALLOWED2],
286
+ defaultModel: DEFAULT_MODEL3,
287
+ run: run3
288
+ };
289
+ var ALLOWED3 = ["magnific-upscaler", "creative-upscale", "face-enhance"];
290
+ var DEFAULT_MODEL4 = "magnific-upscaler";
291
+ var InputSchema4 = z.object({
292
+ image_url: z.string().url().describe("URL of the source image to upscale."),
293
+ model: z.string().optional().describe(`One of: ${ALLOWED3.join(", ")}. Defaults to ${DEFAULT_MODEL4}.`),
294
+ scale_factor: z.number().int().min(1).max(4).optional().describe("Magnific only: 1, 2, or 4. Defaults to 2."),
295
+ creativity: z.number().min(0).max(1).optional().describe("Magnific only: 0 (preserve) \u2192 1 (creative). Defaults to 0.")
296
+ });
297
+ var InputJsonSchema4 = {
298
+ type: "object",
299
+ properties: {
300
+ image_url: { type: "string", format: "uri" },
301
+ model: { type: "string", enum: ALLOWED3 },
302
+ scale_factor: { type: "integer", minimum: 1, maximum: 4 },
303
+ creativity: { type: "number", minimum: 0, maximum: 1 }
304
+ },
305
+ required: ["image_url"]
306
+ };
307
+ async function run4(input, ctx) {
308
+ const requested = input.model ?? DEFAULT_MODEL4;
309
+ if (!ALLOWED3.includes(requested)) {
310
+ return {
311
+ kind: "text",
312
+ text: `Unknown model "${requested}" for pixeldojo:upscale. Allowed: ${ALLOWED3.join(", ")}.`
313
+ };
314
+ }
315
+ const apiId = requested;
316
+ const body = {
317
+ prompt: "",
318
+ image_url: input.image_url
319
+ };
320
+ if (apiId === "magnific-upscaler") {
321
+ body.scale_factor = input.scale_factor ?? 2;
322
+ if (input.creativity !== void 0) body.creativity = input.creativity;
323
+ }
324
+ const submitted = await ctx.client.run(apiId, body);
325
+ const result = await pollJobUntilDoneOrHandoff(
326
+ submitted.jobId,
327
+ (id) => ctx.client.getJob(id),
328
+ ctx.buildStatusUrl,
329
+ {
330
+ now: ctx.pollOverrides?.now,
331
+ sleep: ctx.pollOverrides?.sleep
332
+ }
333
+ );
334
+ return pollResultToSkillResult(
335
+ result,
336
+ (urls) => urls.length === 0 ? `Job ${submitted.jobId} completed but returned no assets.` : `Upscaled. Asset: ${urls[0]}`
337
+ );
338
+ }
339
+ var upscaleSkill = {
340
+ name: "pixeldojo:upscale",
341
+ description: "Enhance an image. Magnific (default) supports configurable scale_factor and creativity; face-enhance for portraits.",
342
+ inputZod: InputSchema4,
343
+ inputJsonSchema: InputJsonSchema4,
344
+ allowedModels: [...ALLOWED3],
345
+ defaultModel: DEFAULT_MODEL4,
346
+ run: run4
347
+ };
348
+ var InputSchema5 = z.object({
349
+ jobId: z.string().min(1).max(200).describe("Job id returned from a prior pixeldojo:* skill handoff.")
350
+ });
351
+ var InputJsonSchema5 = {
352
+ type: "object",
353
+ properties: {
354
+ jobId: {
355
+ type: "string",
356
+ description: "Job id returned from a prior pixeldojo:* skill handoff."
357
+ }
358
+ },
359
+ required: ["jobId"]
360
+ };
361
+ async function run5(input, ctx) {
362
+ const job = await ctx.client.getJob(input.jobId);
363
+ if (job.status === "completed") {
364
+ const urls = extractAssetUrls2(job);
365
+ return {
366
+ kind: "assets",
367
+ text: urls.length === 0 ? `Job ${input.jobId} completed but returned no assets.` : urls.length === 1 ? `Job ${input.jobId} completed. Asset: ${urls[0]}` : `Job ${input.jobId} completed. Assets: ${urls.join(", ")}`,
368
+ assetUrls: urls
369
+ };
370
+ }
371
+ if (job.status === "failed") {
372
+ const errMsg = typeof job.error === "string" ? job.error : job.error?.message ?? "Unknown provider error";
373
+ return {
374
+ kind: "text",
375
+ text: `Job ${input.jobId} failed: ${errMsg}`
376
+ };
377
+ }
378
+ return {
379
+ kind: "handoff",
380
+ text: `Job ${input.jobId} is still ${job.status}. Try again in a few seconds, or follow ${ctx.buildStatusUrl(input.jobId)}.`,
381
+ jobId: input.jobId,
382
+ statusUrl: ctx.buildStatusUrl(input.jobId)
383
+ };
384
+ }
385
+ function extractAssetUrls2(job) {
386
+ if (job.assets && job.assets.length > 0) return job.assets.map((a) => a.url);
387
+ const output = job.output;
388
+ if (output?.images) return output.images;
389
+ if (output?.videos) return output.videos;
390
+ if (output?.image) return [output.image];
391
+ if (output?.video) return [output.video];
392
+ return [];
393
+ }
394
+ var statusSkill = {
395
+ name: "pixeldojo:status",
396
+ description: "Check the status of a long-running job after a 30s sync handoff. Returns assets when ready, failure reason if failed, or current state if still running.",
397
+ inputZod: InputSchema5,
398
+ inputJsonSchema: InputJsonSchema5,
399
+ // status is special — it doesn't dispatch to a model
400
+ allowedModels: [],
401
+ defaultModel: "",
402
+ run: run5
403
+ };
404
+
405
+ // src/skills/index.ts
406
+ var ALL_SKILLS = [
407
+ generateSkill,
408
+ characterSkill,
409
+ storyboardSkill,
410
+ upscaleSkill,
411
+ statusSkill
412
+ ];
413
+ function findSkill(name) {
414
+ return ALL_SKILLS.find((s) => s.name === name);
415
+ }
416
+
417
+ // src/redact.ts
418
+ var PD_KEY_RE = /pd_[A-Za-z0-9_-]{8,}/g;
419
+ var REDACTED = "[redacted]";
420
+ function redactKeys(input) {
421
+ if (!input || typeof input !== "string") return input;
422
+ if (!input.includes("pd_")) return input;
423
+ return input.replace(PD_KEY_RE, REDACTED);
424
+ }
425
+ function redactValue(value, seen = /* @__PURE__ */ new WeakSet()) {
426
+ if (value == null) return value;
427
+ if (typeof value === "string") return redactKeys(value);
428
+ if (typeof value !== "object") return value;
429
+ if (seen.has(value)) return "[circular]";
430
+ seen.add(value);
431
+ if (Array.isArray(value)) {
432
+ return value.map((v) => redactValue(v, seen));
433
+ }
434
+ const out = {};
435
+ for (const [k, v] of Object.entries(value)) {
436
+ out[k] = redactValue(v, seen);
437
+ }
438
+ return out;
439
+ }
440
+ function makeRedactedLogger(target = console) {
441
+ return {
442
+ error(msg, ...args) {
443
+ const redactedArgs = args.map((a) => redactValue(a));
444
+ target.error(`[pixeldojo-mcp] ${redactKeys(msg)}`, ...redactedArgs);
445
+ }
446
+ };
447
+ }
448
+
449
+ // src/server.ts
450
+ var PACKAGE_VERSION = "0.1.0";
451
+ function buildServer(options) {
452
+ if (!options.apiKey) {
453
+ throw new Error(
454
+ "apiKey is required. Set PIXELDOJO_API_KEY env var or pass apiKey to buildServer()."
455
+ );
456
+ }
457
+ const version = options.packageVersion ?? PACKAGE_VERSION;
458
+ const baseUrl = options.baseUrl ?? "https://pixeldojo.ai";
459
+ const logger = makeRedactedLogger();
460
+ const server = new Server(
461
+ {
462
+ name: "pixeldojo",
463
+ version
464
+ },
465
+ {
466
+ capabilities: {
467
+ tools: {}
468
+ }
469
+ }
470
+ );
471
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
472
+ return {
473
+ tools: ALL_SKILLS.map((skill) => ({
474
+ name: skill.name,
475
+ description: skill.description,
476
+ inputSchema: skill.inputJsonSchema
477
+ }))
478
+ };
479
+ });
480
+ server.setRequestHandler(CallToolRequestSchema, async (req) => {
481
+ const skillName = req.params.name;
482
+ const skill = findSkill(skillName);
483
+ if (!skill) {
484
+ const available = ALL_SKILLS.map((s) => s.name).join(", ");
485
+ return {
486
+ isError: true,
487
+ content: [
488
+ {
489
+ type: "text",
490
+ text: `Unknown skill "${skillName}". Available: ${available}.`
491
+ }
492
+ ]
493
+ };
494
+ }
495
+ const parsed = skill.inputZod.safeParse(req.params.arguments ?? {});
496
+ if (!parsed.success) {
497
+ const issues = parsed.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
498
+ return {
499
+ isError: true,
500
+ content: [
501
+ {
502
+ type: "text",
503
+ text: `Invalid input for ${skillName}: ${issues}`
504
+ }
505
+ ]
506
+ };
507
+ }
508
+ const client = makeAttributedClient({
509
+ apiKey: options.apiKey,
510
+ baseUrl,
511
+ mcpVersion: version,
512
+ skillName,
513
+ baseFetch: options.baseFetch
514
+ });
515
+ const ctx = {
516
+ client,
517
+ mcpVersion: version,
518
+ logger,
519
+ buildStatusUrl: (jobId) => `${baseUrl}/api/v1/jobs/${encodeURIComponent(jobId)}`
520
+ };
521
+ try {
522
+ const result = await skill.run(parsed.data, ctx);
523
+ return skillResultToMcpResponse(result);
524
+ } catch (err) {
525
+ const message = errorToActionableMessage(err);
526
+ logger.error(`Skill ${skillName} failed`, message);
527
+ return {
528
+ isError: true,
529
+ content: [{ type: "text", text: message }]
530
+ };
531
+ }
532
+ });
533
+ return server;
534
+ }
535
+ function skillResultToMcpResponse(result) {
536
+ if (result.kind === "assets") {
537
+ return {
538
+ content: [
539
+ { type: "text", text: result.text },
540
+ // Each URL on its own line so an LLM can copy individually.
541
+ ...result.assetUrls.map((url) => ({ type: "text", text: url }))
542
+ ]
543
+ };
544
+ }
545
+ if (result.kind === "handoff") {
546
+ return {
547
+ content: [
548
+ { type: "text", text: result.text },
549
+ { type: "text", text: `jobId: ${result.jobId}` },
550
+ { type: "text", text: `statusUrl: ${result.statusUrl}` }
551
+ ]
552
+ };
553
+ }
554
+ return {
555
+ content: [{ type: "text", text: result.text }]
556
+ };
557
+ }
558
+ function errorToActionableMessage(err) {
559
+ if (err == null) return "Unknown error";
560
+ if (typeof err === "string") return err;
561
+ const e = err;
562
+ if (e.status === 401 || e.code === "auth_required" || e.name === "AuthenticationError") {
563
+ return "Authentication failed. Check PIXELDOJO_API_KEY. Get a key at https://pixeldojo.ai/api-platform/api-keys.";
564
+ }
565
+ if (e.status === 402 || e.code === "insufficient_credits") {
566
+ return "Insufficient credits. Top up at https://pixeldojo.ai/api-platform/buy-credits.";
567
+ }
568
+ if (e.status === 429 || e.code === "rate_limit") {
569
+ return "Rate limit hit. Try again in a few seconds.";
570
+ }
571
+ if (e.status === 404 || e.code === "not_found") {
572
+ return e.message ?? "Resource not found.";
573
+ }
574
+ if (e.name === "TimeoutError") {
575
+ return e.message ?? "Request timed out. Try again.";
576
+ }
577
+ return e.message ?? "Unknown error";
578
+ }
579
+ async function runStdio(options) {
580
+ const server = buildServer(options);
581
+ const transport = new StdioServerTransport();
582
+ await server.connect(transport);
583
+ }
584
+
585
+ export { ALL_SKILLS, PACKAGE_VERSION, buildServer, findSkill, makeAttributedClient, makeRedactedLogger, pollJobUntilDoneOrHandoff, redactKeys, redactValue, runStdio };
586
+ //# sourceMappingURL=index.js.map
587
+ //# sourceMappingURL=index.js.map