pixellab-forge-mcp 1.3.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/tools.js ADDED
@@ -0,0 +1,1072 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { getPendingJobs, getJobLog } from "./job-log.js";
4
+ // ── Schema helpers ──────────────────────────────────────────────────────
5
+ function imageSchema(description) {
6
+ return {
7
+ type: "object",
8
+ description,
9
+ properties: {
10
+ type: { type: "string", const: "base64", default: "base64", description: "Image data type (always \"base64\")" },
11
+ base64: { type: "string", description: "Base64-encoded PNG image data" },
12
+ format: { type: "string", default: "png", description: "Image format (default \"png\")" },
13
+ },
14
+ required: ["base64"],
15
+ };
16
+ }
17
+ function frameImageSchema(description) {
18
+ return {
19
+ type: "object",
20
+ description,
21
+ properties: {
22
+ image: imageSchema("Image data"),
23
+ width: { type: "number", description: "Image width in pixels" },
24
+ height: { type: "number", description: "Image height in pixels" },
25
+ },
26
+ required: ["image", "width", "height"],
27
+ };
28
+ }
29
+ function sizeSchema(description, required = true) {
30
+ return {
31
+ type: "object",
32
+ description,
33
+ properties: {
34
+ width: { type: "integer", description: "Width in pixels" },
35
+ height: { type: "integer", description: "Height in pixels" },
36
+ },
37
+ required: required ? ["width", "height"] : [],
38
+ };
39
+ }
40
+ // ── Reusable property fragments ─────────────────────────────────────────
41
+ const seed = { type: "number", description: "Seed for deterministic generation (default 0)" };
42
+ const negativeDescription = { type: "string", description: "What to avoid in generation" };
43
+ const initImageStrength = { type: "number", description: "Initial image influence strength (0-1000, default 300)" };
44
+ const isometric = { type: "boolean", description: "Generate in isometric view (default false)" };
45
+ const obliqueProjection = { type: "boolean", description: "Use oblique projection (default false)" };
46
+ const coveragePercentage = { type: "number", description: "Percentage of canvas to cover (0-100)" };
47
+ const noBackground = { type: "boolean", description: "Generate with transparent background", default: true };
48
+ const textGuidanceScale = { type: "number", description: "How closely to follow the text (1.0-20.0, default 8)", minimum: 1, maximum: 20 };
49
+ const forceColors = { type: "boolean", description: "Force use of colors from color_image (default false)" };
50
+ const colorImage = imageSchema("Color palette reference image");
51
+ const styleParams = {
52
+ outline: { type: "string", description: "Outline style" },
53
+ shading: { type: "string", description: "Shading style" },
54
+ detail: { type: "string", description: "Detail level" },
55
+ };
56
+ const viewEnum = {
57
+ type: "string",
58
+ enum: ["low top-down", "high top-down", "side"],
59
+ description: "Camera perspective",
60
+ };
61
+ const directionEnum = {
62
+ type: "string",
63
+ enum: ["south", "north", "east", "west", "south-east", "south-west", "north-east", "north-west"],
64
+ description: "Character facing direction",
65
+ };
66
+ const proportionsSchema = {
67
+ type: "object",
68
+ description: "Body proportions - preset (chibi, cartoon, stylized, realistic_male, realistic_female, heroic) or custom with head_size, arm_length, leg_length, shoulder_width, hip_width (0.5-2.0)",
69
+ properties: {
70
+ type: { type: "string", enum: ["preset", "custom"] },
71
+ name: { type: "string", description: "Preset name" },
72
+ head_size: { type: "number" }, arm_length: { type: "number" },
73
+ leg_length: { type: "number" }, shoulder_width: { type: "number" },
74
+ hip_width: { type: "number" },
75
+ },
76
+ };
77
+ const colorPaletteArray = {
78
+ type: "array",
79
+ items: { type: "string" },
80
+ description: "Forced color palette as hex strings (e.g. [\"#ff0000\", \"#00ff00\"])",
81
+ };
82
+ // ── Tools ───────────────────────────────────────────────────────────────
83
+ export const tools = [
84
+ // ═══════ ACCOUNT ═══════
85
+ {
86
+ name: "get_balance",
87
+ description: "Get your current PixelLab credit balance",
88
+ inputSchema: { type: "object", properties: {} },
89
+ handler: async (client) => client.get("/balance"),
90
+ },
91
+ {
92
+ name: "get_job_status",
93
+ description: "Check the status of a background job and retrieve its results when complete. All creation tools return a job_id immediately — use this tool to poll for completion and get the generated images/data.",
94
+ inputSchema: {
95
+ type: "object",
96
+ properties: {
97
+ job_id: { type: "string", description: "The background job ID" },
98
+ },
99
+ required: ["job_id"],
100
+ },
101
+ handler: async (client, args) => client.get(`/background-jobs/${args.job_id}`),
102
+ },
103
+ {
104
+ name: "list_pending_jobs",
105
+ description: "List background jobs that were started but haven't completed yet. Use this to recover jobs after a disconnection or timeout.",
106
+ inputSchema: { type: "object", properties: {} },
107
+ handler: async () => {
108
+ const pending = getPendingJobs();
109
+ if (pending.length === 0) {
110
+ return { message: "No pending jobs", jobs: [] };
111
+ }
112
+ return { jobs: pending };
113
+ },
114
+ },
115
+ {
116
+ name: "list_job_history",
117
+ description: "List recent job history (completed, failed, and pending). Jobs are pruned after 24 hours.",
118
+ inputSchema: { type: "object", properties: {} },
119
+ handler: async () => getJobLog(),
120
+ },
121
+ // ═══════ IMAGE GENERATION (Pro/v2) ═══════
122
+ {
123
+ name: "generate_image",
124
+ description: "Generate pixel art from a text description. The API auto-generates variants based on size: ≤42px → 64 images, 43-85px → 16, 86-170px → 4, >170px → 1. Use larger sizes for faster results with fewer variants. Supports reference images and style images for guidance.",
125
+ inputSchema: {
126
+ type: "object",
127
+ properties: {
128
+ description: { type: "string", description: "Text description of the pixel art to generate" },
129
+ image_size: sizeSchema("Output image dimensions"),
130
+ reference_images: {
131
+ type: "array",
132
+ description: "Up to 4 reference images for subject guidance",
133
+ items: imageSchema("Reference image"),
134
+ },
135
+ style_image: imageSchema("Style reference image for consistent pixel art style"),
136
+ style_options: {
137
+ type: "object",
138
+ description: "Options controlling what to copy from the style image",
139
+ properties: {
140
+ copy_outline: { type: "boolean", description: "Copy outline style" },
141
+ copy_shading: { type: "boolean", description: "Copy shading style" },
142
+ copy_detail: { type: "boolean", description: "Copy detail level" },
143
+ copy_colors: { type: "boolean", description: "Copy color palette" },
144
+ },
145
+ },
146
+ seed,
147
+ no_background: noBackground,
148
+ },
149
+ required: ["description", "image_size"],
150
+ },
151
+ handler: async (client, args) => client.post("/generate-image-v2", args),
152
+ },
153
+ {
154
+ name: "generate_with_style",
155
+ description: "Generate pixel art matching a specific visual style from 1-4 style reference images.",
156
+ inputSchema: {
157
+ type: "object",
158
+ properties: {
159
+ style_images: {
160
+ type: "array",
161
+ description: "1-4 style reference images",
162
+ items: imageSchema("Style image"),
163
+ },
164
+ description: { type: "string", description: "What to generate" },
165
+ style_description: { type: "string", description: "Fine-tune style matching details" },
166
+ image_size: sizeSchema("Output dimensions (square, 16-512px)"),
167
+ seed,
168
+ no_background: noBackground,
169
+ },
170
+ required: ["style_images", "description", "image_size"],
171
+ },
172
+ handler: async (client, args) => client.post("/generate-with-style-v2", args),
173
+ },
174
+ {
175
+ name: "generate_ui",
176
+ description: "Generate pixel art UI elements for games (buttons, panels, health bars, inventory slots, icons).",
177
+ inputSchema: {
178
+ type: "object",
179
+ properties: {
180
+ description: { type: "string", description: "UI element description (e.g. 'medieval stone button with gold trim')" },
181
+ image_size: sizeSchema("Output dimensions (min 16x16)"),
182
+ concept_image: imageSchema("Design guidance image"),
183
+ color_palette: { type: "string", description: "Color palette description (e.g. 'brown and gold')" },
184
+ seed,
185
+ no_background: noBackground,
186
+ },
187
+ required: ["description", "image_size"],
188
+ },
189
+ handler: async (client, args) => client.post("/generate-ui-v2", args),
190
+ },
191
+ // ═══════ IMAGE GENERATION (Legacy engines) ═══════
192
+ {
193
+ name: "create_image_pixflux",
194
+ description: "Generate pixel art using the Pixflux engine. Supports color reference images, transparent backgrounds, and style controls. Size 32x32 to 400x400.",
195
+ inputSchema: {
196
+ type: "object",
197
+ properties: {
198
+ description: { type: "string", description: "Image description" },
199
+ image_size: sizeSchema("32x32 to 400x400"),
200
+ negative_description: negativeDescription,
201
+ text_guidance_scale: { type: "number", description: "How closely to follow text (1.0-20.0, default 8.0)" },
202
+ ...styleParams,
203
+ view: viewEnum,
204
+ direction: directionEnum,
205
+ isometric,
206
+ no_background: noBackground,
207
+ coverage_percentage: coveragePercentage,
208
+ init_image: imageSchema("Starting image for img2img"),
209
+ init_image_strength: initImageStrength,
210
+ color_image: colorImage,
211
+ seed,
212
+ },
213
+ required: ["description", "image_size"],
214
+ },
215
+ handler: async (client, args) => client.post("/create-image-pixflux", args),
216
+ },
217
+ {
218
+ name: "create_image_bitforge",
219
+ description: "Generate pixel art using the Bitforge engine. Supports style images, inpainting, skeleton keypoints, and color reference. Max 200x200.",
220
+ inputSchema: {
221
+ type: "object",
222
+ properties: {
223
+ description: { type: "string", description: "Image description" },
224
+ image_size: sizeSchema("Max 200x200"),
225
+ negative_description: negativeDescription,
226
+ text_guidance_scale: { type: "number", description: "Text prompt adherence (1.0-20.0, default 3.0)" },
227
+ extra_guidance_scale: { type: "number", description: "Additional guidance (default 3.0)" },
228
+ style_strength: { type: "number", description: "Style image influence (default 0.0)" },
229
+ skeleton_guidance_scale: { type: "number", description: "Skeleton keypoint influence (default 1.0)" },
230
+ ...styleParams,
231
+ view: viewEnum,
232
+ direction: directionEnum,
233
+ isometric,
234
+ oblique_projection: obliqueProjection,
235
+ no_background: noBackground,
236
+ coverage_percentage: coveragePercentage,
237
+ init_image: imageSchema("Starting image"),
238
+ init_image_strength: initImageStrength,
239
+ style_image: imageSchema("Style reference"),
240
+ inpainting_image: imageSchema("Image to inpaint on"),
241
+ mask_image: imageSchema("Inpainting mask"),
242
+ skeleton_keypoints: { type: "array", description: "Body joint positions" },
243
+ color_image: colorImage,
244
+ seed,
245
+ },
246
+ required: ["description", "image_size"],
247
+ },
248
+ handler: async (client, args) => client.post("/create-image-bitforge", args),
249
+ },
250
+ // ═══════ IMAGE OPERATIONS ═══════
251
+ {
252
+ name: "image_to_pixelart",
253
+ description: "Convert a photograph or regular image into pixel art. Input max 1280x1280, output max 320x320.",
254
+ inputSchema: {
255
+ type: "object",
256
+ properties: {
257
+ image: imageSchema("Source image to convert"),
258
+ image_size: sizeSchema("Input image dimensions"),
259
+ output_size: sizeSchema("Target pixel art size (max 320x320)"),
260
+ text_guidance_scale: { type: "number", description: "Pixel art style adherence (default 8.0)" },
261
+ seed,
262
+ },
263
+ required: ["image", "image_size", "output_size"],
264
+ },
265
+ handler: async (client, args) => client.post("/image-to-pixelart", args),
266
+ },
267
+ {
268
+ name: "resize_image",
269
+ description: "AI-powered resize of a pixel art image to a different resolution while preserving quality.",
270
+ inputSchema: {
271
+ type: "object",
272
+ properties: {
273
+ description: { type: "string", description: "Description of the character/object" },
274
+ reference_image: imageSchema("Image to resize"),
275
+ reference_image_size: sizeSchema("Current image dimensions"),
276
+ target_size: sizeSchema("Target dimensions (16-200px)"),
277
+ view: viewEnum,
278
+ direction: directionEnum,
279
+ isometric,
280
+ oblique_projection: obliqueProjection,
281
+ no_background: noBackground,
282
+ color_image: colorImage,
283
+ init_image: imageSchema("Optional initialization image"),
284
+ init_image_strength: { type: "number", description: "Init image influence (default 150.0)" },
285
+ seed,
286
+ },
287
+ required: ["description", "reference_image", "reference_image_size", "target_size"],
288
+ },
289
+ handler: async (client, args) => client.post("/resize", args),
290
+ },
291
+ {
292
+ name: "remove_background",
293
+ description: "Remove the background from a pixel art image (max 400x400).",
294
+ inputSchema: {
295
+ type: "object",
296
+ properties: {
297
+ image: imageSchema("Source image"),
298
+ image_size: sizeSchema("Image dimensions"),
299
+ background_removal_task: {
300
+ type: "string",
301
+ enum: ["remove_simple_background", "remove_complex_background"],
302
+ description: "Type of background removal (default remove_simple_background)",
303
+ },
304
+ text: { type: "string", description: "Description of the foreground object to help removal" },
305
+ seed,
306
+ },
307
+ required: ["image", "image_size"],
308
+ },
309
+ handler: async (client, args) => client.post("/remove-background", args),
310
+ },
311
+ // ═══════ ANIMATION (Pro/v2) ═══════
312
+ {
313
+ name: "edit_animation",
314
+ description: "Edit an existing animation sequence (2-16 frames) using a text description.",
315
+ inputSchema: {
316
+ type: "object",
317
+ properties: {
318
+ description: { type: "string", description: "Edit description" },
319
+ frames: {
320
+ type: "array",
321
+ description: "Animation frames (2-16)",
322
+ items: imageSchema("Animation frame"),
323
+ },
324
+ image_size: sizeSchema("Frame dimensions (16-256px)"),
325
+ seed,
326
+ no_background: noBackground,
327
+ },
328
+ required: ["description", "frames", "image_size"],
329
+ },
330
+ handler: async (client, args) => client.post("/edit-animation-v2", args),
331
+ },
332
+ {
333
+ name: "interpolate_frames",
334
+ description: "Generate intermediate animation frames between a start and end keyframe. Size 16x16 to 128x128.",
335
+ inputSchema: {
336
+ type: "object",
337
+ properties: {
338
+ start_image: imageSchema("First keyframe"),
339
+ end_image: imageSchema("Last keyframe"),
340
+ action: { type: "string", description: "Animation action description" },
341
+ image_size: sizeSchema("Frame size (16x16 to 128x128)"),
342
+ seed,
343
+ no_background: noBackground,
344
+ },
345
+ required: ["start_image", "end_image", "action", "image_size"],
346
+ },
347
+ handler: async (client, args) => client.post("/interpolation-v2", args),
348
+ },
349
+ {
350
+ name: "transfer_outfit",
351
+ description: "Transfer an outfit from a reference image onto animation frames (2-16 frames).",
352
+ inputSchema: {
353
+ type: "object",
354
+ properties: {
355
+ reference_image: frameImageSchema("Outfit source image with dimensions"),
356
+ frames: {
357
+ type: "array",
358
+ description: "Animation frames (2-16) with dimensions",
359
+ items: frameImageSchema("Frame with dimensions"),
360
+ },
361
+ image_size: sizeSchema("Output frame dimensions"),
362
+ seed,
363
+ no_background: noBackground,
364
+ },
365
+ required: ["reference_image", "frames", "image_size"],
366
+ },
367
+ handler: async (client, args) => client.post("/transfer-outfit-v2", args),
368
+ },
369
+ // ═══════ ANIMATION (Legacy) ═══════
370
+ {
371
+ name: "animate_with_skeleton",
372
+ description: "Create animation using skeleton keypoints for precise pose control. Size 16x16 to 256x256.",
373
+ inputSchema: {
374
+ type: "object",
375
+ properties: {
376
+ image_size: sizeSchema("16x16 to 256x256"),
377
+ skeleton_keypoints: { type: "array", description: "Body joint positions per frame" },
378
+ view: viewEnum,
379
+ direction: directionEnum,
380
+ guidance_scale: { type: "number", description: "How closely to follow reference image and skeleton keypoints (1.0-20.0, default 4.0)", minimum: 1, maximum: 20 },
381
+ isometric,
382
+ oblique_projection: obliqueProjection,
383
+ reference_image: imageSchema("Character reference"),
384
+ init_images: { type: "array", items: imageSchema("Init image"), description: "Initialization images per frame" },
385
+ init_image_strength: initImageStrength,
386
+ inpainting_images: { type: "array", items: imageSchema("Inpainting image") },
387
+ mask_images: { type: "array", items: imageSchema("Mask image") },
388
+ color_image: colorImage,
389
+ seed,
390
+ },
391
+ required: ["image_size", "skeleton_keypoints", "view", "direction"],
392
+ },
393
+ handler: async (client, args) => client.post("/animate-with-skeleton", args),
394
+ },
395
+ {
396
+ name: "animate_with_text",
397
+ description: "Create a character animation from text description and action. Fixed 64x64 size.",
398
+ inputSchema: {
399
+ type: "object",
400
+ properties: {
401
+ description: { type: "string", description: "Character description" },
402
+ action: { type: "string", description: "Animation action (e.g. 'walking', 'attacking')" },
403
+ image_size: sizeSchema("Frame size"),
404
+ reference_image: imageSchema("Character reference"),
405
+ view: { ...viewEnum, description: "Camera angle (default 'side')" },
406
+ direction: { ...directionEnum, description: "Facing direction (default 'east')" },
407
+ negative_description: negativeDescription,
408
+ text_guidance_scale: { type: "number", description: "Text prompt influence (1.0-20.0, default 7.5)" },
409
+ image_guidance_scale: { type: "number", description: "Reference image influence (default 1.5)" },
410
+ n_frames: { type: "number", description: "Number of frames (default 4)" },
411
+ start_frame_index: { type: "number", description: "Starting frame index (default 0)" },
412
+ init_images: { type: "array", items: imageSchema("Init image"), description: "Initialization images per frame" },
413
+ init_image_strength: initImageStrength,
414
+ inpainting_images: { type: "array", items: imageSchema("Inpainting image") },
415
+ mask_images: { type: "array", items: imageSchema("Mask image") },
416
+ color_image: colorImage,
417
+ seed,
418
+ },
419
+ required: ["description", "action", "image_size", "reference_image"],
420
+ },
421
+ handler: async (client, args) => client.post("/animate-with-text", args),
422
+ },
423
+ {
424
+ name: "animate_with_text_v2",
425
+ description: "Animate an existing character image with text-described action. Size 32x32 to 256x256.",
426
+ inputSchema: {
427
+ type: "object",
428
+ properties: {
429
+ reference_image: frameImageSchema("Character image to animate with dimensions"),
430
+ reference_image_size: sizeSchema("Character image dimensions"),
431
+ action: { type: "string", description: "Action to animate (e.g. 'walk', 'cast spell')" },
432
+ image_size: sizeSchema("Output frame size (32x32 to 256x256)"),
433
+ view: {
434
+ type: "string",
435
+ enum: ["none", "low top-down", "high top-down", "side"],
436
+ description: "Camera perspective (default 'none')",
437
+ },
438
+ direction: {
439
+ type: "string",
440
+ enum: ["none", "south", "north", "east", "west", "south-east", "south-west", "north-east", "north-west"],
441
+ description: "Facing direction (default 'none')",
442
+ },
443
+ seed,
444
+ no_background: noBackground,
445
+ },
446
+ required: ["reference_image", "reference_image_size", "action", "image_size"],
447
+ },
448
+ handler: async (client, args) => client.post("/animate-with-text-v2", args),
449
+ },
450
+ {
451
+ name: "animate_with_text_v3",
452
+ description: "Animate from a first frame with optional last frame keyframe. 4-16 frames output.",
453
+ inputSchema: {
454
+ type: "object",
455
+ properties: {
456
+ first_frame: imageSchema("Starting frame image"),
457
+ action: { type: "string", description: "Action description" },
458
+ frame_count: { type: "integer", description: "Number of frames (4-16, default 8, must be even)" },
459
+ last_frame: imageSchema("Optional ending keyframe"),
460
+ seed,
461
+ no_background: noBackground,
462
+ },
463
+ required: ["first_frame", "action"],
464
+ },
465
+ handler: async (client, args) => client.post("/animate-with-text-v3", args),
466
+ },
467
+ {
468
+ name: "estimate_skeleton",
469
+ description: "Estimate skeleton keypoints from a character image.",
470
+ inputSchema: {
471
+ type: "object",
472
+ properties: {
473
+ image: imageSchema("Character image"),
474
+ },
475
+ required: ["image"],
476
+ },
477
+ handler: async (client, args) => client.post("/estimate-skeleton", args),
478
+ },
479
+ // ═══════ ROTATION ═══════
480
+ {
481
+ name: "generate_8_rotations",
482
+ description: "Generate 8 directional views of a character. Methods: rotate_character, create_with_style, create_from_concept. Size 32x32 to 168x168.",
483
+ inputSchema: {
484
+ type: "object",
485
+ properties: {
486
+ method: {
487
+ type: "string",
488
+ enum: ["rotate_character", "create_with_style", "create_from_concept"],
489
+ description: "Generation method",
490
+ },
491
+ image_size: sizeSchema("32x32 to 168x168"),
492
+ view: viewEnum,
493
+ reference_image: frameImageSchema("For rotate_character: character image with dimensions"),
494
+ description: { type: "string", description: "For create_with_style: character description" },
495
+ concept_image: imageSchema("For create_from_concept: concept art"),
496
+ style_description: { type: "string", description: "Style description for the character" },
497
+ no_background: noBackground,
498
+ seed,
499
+ },
500
+ required: ["method", "image_size"],
501
+ },
502
+ handler: async (client, args) => client.post("/generate-8-rotations-v2", args),
503
+ },
504
+ {
505
+ name: "rotate",
506
+ description: "Rotate a character from one view/direction to another. Size 16x16 to 128x128.",
507
+ inputSchema: {
508
+ type: "object",
509
+ properties: {
510
+ image_size: sizeSchema("16x16 to 128x128"),
511
+ from_image: imageSchema("Source image"),
512
+ from_view: viewEnum,
513
+ to_view: viewEnum,
514
+ from_direction: directionEnum,
515
+ to_direction: directionEnum,
516
+ view_change: { type: "number", description: "Relative view change (alternative to from/to_view)" },
517
+ direction_change: { type: "number", description: "Relative direction change (alternative to from/to_direction)" },
518
+ image_guidance_scale: { type: "number", description: "Source image influence (default 3.0)" },
519
+ isometric,
520
+ oblique_projection: obliqueProjection,
521
+ init_image: imageSchema("Initialization image"),
522
+ init_image_strength: initImageStrength,
523
+ mask_image: imageSchema("Mask image"),
524
+ color_image: colorImage,
525
+ seed,
526
+ },
527
+ required: ["image_size", "from_image"],
528
+ },
529
+ handler: async (client, args) => client.post("/rotate", args),
530
+ },
531
+ // ═══════ INPAINTING & EDITING ═══════
532
+ {
533
+ name: "inpaint_v3",
534
+ description: "Edit a specific region of a pixel art image using a mask. White mask = generate, black mask = preserve. Size 32-512px.",
535
+ inputSchema: {
536
+ type: "object",
537
+ properties: {
538
+ description: { type: "string", description: "What to generate in the masked area" },
539
+ inpainting_image: imageSchema("Image to edit"),
540
+ mask_image: imageSchema("Mask (white=generate, black=preserve)"),
541
+ context_image: imageSchema("Style guidance image (up to 1024x1024) (deprecated)"),
542
+ bounding_box: {
543
+ type: "object",
544
+ description: "Precise editing area within the image (deprecated)",
545
+ properties: {
546
+ x: { type: "number" }, y: { type: "number" },
547
+ width: { type: "number" }, height: { type: "number" },
548
+ },
549
+ },
550
+ seed,
551
+ no_background: noBackground,
552
+ crop_to_mask: { type: "boolean", description: "Whether to crop generated content to mask boundary (default true)" },
553
+ },
554
+ required: ["description", "inpainting_image", "mask_image"],
555
+ },
556
+ handler: async (client, args) => client.post("/inpaint-v3", args),
557
+ },
558
+ {
559
+ name: "inpaint",
560
+ description: "Inpaint a pixel art image using the legacy Bitforge engine. Max 200x200.",
561
+ inputSchema: {
562
+ type: "object",
563
+ properties: {
564
+ description: { type: "string", description: "What to generate" },
565
+ image_size: sizeSchema("Max 200x200"),
566
+ inpainting_image: imageSchema("Image to edit"),
567
+ mask_image: imageSchema("Mask image"),
568
+ negative_description: negativeDescription,
569
+ text_guidance_scale: { type: "number", description: "Text prompt influence (1.0-20.0, default 3.0)" },
570
+ extra_guidance_scale: { type: "number", description: "Additional guidance (default 3.0)" },
571
+ ...styleParams,
572
+ view: viewEnum,
573
+ direction: directionEnum,
574
+ isometric,
575
+ oblique_projection: obliqueProjection,
576
+ no_background: noBackground,
577
+ init_image: imageSchema("Initialization image"),
578
+ init_image_strength: initImageStrength,
579
+ color_image: colorImage,
580
+ seed,
581
+ },
582
+ required: ["description", "image_size", "inpainting_image", "mask_image"],
583
+ },
584
+ handler: async (client, args) => client.post("/inpaint", args),
585
+ },
586
+ {
587
+ name: "edit_images",
588
+ description: "Edit 1-16 images using text description or a reference image. Size 32x32 to 512x512.",
589
+ inputSchema: {
590
+ type: "object",
591
+ properties: {
592
+ method: {
593
+ type: "string",
594
+ enum: ["edit_with_text", "edit_with_reference"],
595
+ },
596
+ edit_images: {
597
+ type: "array",
598
+ description: "1-16 images to edit with dimensions",
599
+ items: frameImageSchema("Image with dimensions"),
600
+ },
601
+ image_size: sizeSchema("Output size 32x32 to 512x512"),
602
+ description: { type: "string", description: "Edit description (for edit_with_text)" },
603
+ reference_image: frameImageSchema("Style reference with dimensions (for edit_with_reference)"),
604
+ seed,
605
+ no_background: noBackground,
606
+ },
607
+ required: ["method", "edit_images", "image_size"],
608
+ },
609
+ handler: async (client, args) => client.post("/edit-images-v2", args),
610
+ },
611
+ {
612
+ name: "edit_image",
613
+ description: "Edit a single image using a text description. Size 16x16 to 400x400.",
614
+ inputSchema: {
615
+ type: "object",
616
+ properties: {
617
+ image: imageSchema("Image to edit"),
618
+ image_size: sizeSchema("Current image dimensions"),
619
+ description: { type: "string", description: "Edit description" },
620
+ width: { type: "number", description: "Target canvas width (16-400px)" },
621
+ height: { type: "number", description: "Target canvas height (16-400px)" },
622
+ seed,
623
+ no_background: noBackground,
624
+ text_guidance_scale: { type: "number", description: "How closely to follow text (1.0-10.0, default 8)" },
625
+ color_image: imageSchema("Color reference image"),
626
+ },
627
+ required: ["image", "image_size", "description", "width", "height"],
628
+ },
629
+ handler: async (client, args) => client.post("/edit-image", args),
630
+ },
631
+ // ═══════ TILESETS ═══════
632
+ {
633
+ name: "create_tileset",
634
+ description: "Create a top-down tileset with base terrain, elevated terrain, and transitions. Tile size 16x16 or 32x32.",
635
+ inputSchema: {
636
+ type: "object",
637
+ properties: {
638
+ lower_description: { type: "string", description: "Base terrain (e.g. 'deep blue ocean water')" },
639
+ upper_description: { type: "string", description: "Elevated terrain (e.g. 'golden sandy beach')" },
640
+ transition_description: { type: "string", description: "Transition terrain (e.g. 'wet sand with foam')" },
641
+ tile_size: sizeSchema("16x16 or 32x32"),
642
+ transition_size: { type: "number", description: "Elevation difference 0.25-1.0 (default 0.5)" },
643
+ view: {
644
+ type: "string",
645
+ enum: ["low top-down", "high top-down"],
646
+ description: "Camera perspective (default 'high top-down')",
647
+ },
648
+ ...styleParams,
649
+ lower_base_tile_id: { type: "string", description: "ID of existing lower base tile to use" },
650
+ upper_base_tile_id: { type: "string", description: "ID of existing upper base tile to use" },
651
+ text_guidance_scale: { type: "number", description: "How closely to follow text (1-20, default 8)", minimum: 1, maximum: 20 },
652
+ tile_strength: { type: "number", description: "Tile pattern strength (0.1-2, default 1)", minimum: 0.1, maximum: 2 },
653
+ tileset_adherence_freedom: { type: "number", description: "Freedom from tileset constraints (0-900, default 500)", minimum: 0, maximum: 900 },
654
+ tileset_adherence: { type: "number", description: "Adherence to tileset patterns (0-500, default 100)", minimum: 0, maximum: 500 },
655
+ lower_reference_image: imageSchema("Reference image for lower terrain style"),
656
+ upper_reference_image: imageSchema("Reference image for upper terrain style"),
657
+ transition_reference_image: imageSchema("Reference image for transition style"),
658
+ color_image: colorImage,
659
+ seed,
660
+ },
661
+ required: ["lower_description", "upper_description", "tile_size"],
662
+ },
663
+ handler: async (client, args) => client.post("/create-tileset", args),
664
+ },
665
+ {
666
+ name: "get_tileset",
667
+ description: "Get a previously created tileset by ID.",
668
+ inputSchema: {
669
+ type: "object",
670
+ properties: {
671
+ tileset_id: { type: "string", description: "Tileset ID" },
672
+ },
673
+ required: ["tileset_id"],
674
+ },
675
+ handler: async (client, args) => client.get(`/tilesets/${args.tileset_id}`),
676
+ },
677
+ {
678
+ name: "create_tileset_sidescroller",
679
+ description: "Create a sidescroller/platformer tileset with terrain and transitions.",
680
+ inputSchema: {
681
+ type: "object",
682
+ properties: {
683
+ lower_description: { type: "string", description: "Base terrain description" },
684
+ transition_description: { type: "string", description: "Transition description" },
685
+ tile_size: sizeSchema("Tile dimensions (16x16 or 32x32)"),
686
+ transition_size: { type: "number", description: "0.25-1.0 (default 0.5)" },
687
+ ...styleParams,
688
+ lower_base_tile_id: { type: "string", description: "ID of existing lower base tile to use" },
689
+ text_guidance_scale: { type: "number", description: "How closely to follow text (1-20, default 8)", minimum: 1, maximum: 20 },
690
+ tile_strength: { type: "number", description: "Tile pattern strength (0.1-2, default 1)", minimum: 0.1, maximum: 2 },
691
+ tileset_adherence_freedom: { type: "number", description: "Freedom from tileset constraints (0-900, default 500)", minimum: 0, maximum: 900 },
692
+ tileset_adherence: { type: "number", description: "Adherence to tileset patterns (0-500, default 100)", minimum: 0, maximum: 500 },
693
+ lower_reference_image: imageSchema("Reference image for lower terrain style"),
694
+ transition_reference_image: imageSchema("Reference image for transition style"),
695
+ color_image: colorImage,
696
+ seed,
697
+ },
698
+ required: ["lower_description", "tile_size"],
699
+ },
700
+ handler: async (client, args) => client.post("/create-tileset-sidescroller", args),
701
+ },
702
+ {
703
+ name: "create_isometric_tile",
704
+ description: "Create an isometric tile. Size 16x16 to 64x64.",
705
+ inputSchema: {
706
+ type: "object",
707
+ properties: {
708
+ description: { type: "string", description: "Tile description" },
709
+ image_size: sizeSchema("16x16 to 64x64"),
710
+ init_image: imageSchema("Optional starting image"),
711
+ color_image: colorImage,
712
+ seed,
713
+ text_guidance_scale: { type: "number", description: "How closely to follow text (1-20, default 8)", minimum: 1, maximum: 20 },
714
+ ...styleParams,
715
+ init_image_strength: { type: "number", description: "Initial image influence strength (1-999, default 300)", minimum: 1, maximum: 999 },
716
+ isometric_tile_size: { type: "number", description: "Isometric tile size in pixels (default 16)" },
717
+ isometric_tile_shape: {
718
+ type: "string",
719
+ enum: ["thick tile", "thin tile", "block"],
720
+ description: "Shape of the isometric tile (default 'block')",
721
+ },
722
+ },
723
+ required: ["description", "image_size"],
724
+ },
725
+ handler: async (client, args) => client.post("/create-isometric-tile", args),
726
+ },
727
+ {
728
+ name: "get_isometric_tile",
729
+ description: "Get a previously created isometric tile by ID.",
730
+ inputSchema: {
731
+ type: "object",
732
+ properties: {
733
+ tile_id: { type: "string", description: "Isometric tile ID" },
734
+ },
735
+ required: ["tile_id"],
736
+ },
737
+ handler: async (client, args) => client.get(`/isometric-tiles/${args.tile_id}`),
738
+ },
739
+ {
740
+ name: "create_tiles_pro",
741
+ description: "Create professional tiles. Types: hex, hex_pointy, isometric, octagon, square_topdown. Size 16-128px.",
742
+ inputSchema: {
743
+ type: "object",
744
+ properties: {
745
+ description: { type: "string", description: "Tile description" },
746
+ tile_type: {
747
+ type: "string",
748
+ enum: ["hex", "hex_pointy", "isometric", "octagon", "square_topdown"],
749
+ description: "Type of tile",
750
+ },
751
+ tile_size: { type: "integer", description: "Tile size in pixels (16-256, default 32)" },
752
+ n_tiles: { type: "number", description: "Number of tiles to generate" },
753
+ tile_height: { type: "number", description: "Tile height in pixels (16-256)" },
754
+ tile_view: {
755
+ type: "string",
756
+ enum: ["top-down", "high top-down", "low top-down", "side"],
757
+ description: "Camera perspective for tiles",
758
+ },
759
+ tile_view_angle: { type: "number", description: "View angle in degrees (0-90)" },
760
+ tile_depth_ratio: { type: "number", description: "Depth ratio (0-1)" },
761
+ seed,
762
+ style_images: { type: "string", description: "Style reference images (JSON string)" },
763
+ style_options: { type: "string", description: "Style options (JSON string)" },
764
+ },
765
+ required: ["description", "tile_type", "tile_size", "n_tiles"],
766
+ },
767
+ handler: async (client, args) => client.post("/create-tiles-pro", args),
768
+ },
769
+ {
770
+ name: "get_tiles_pro",
771
+ description: "Get previously created pro tiles by ID.",
772
+ inputSchema: {
773
+ type: "object",
774
+ properties: {
775
+ tile_id: { type: "string", description: "Tiles pro ID" },
776
+ },
777
+ required: ["tile_id"],
778
+ },
779
+ handler: async (client, args) => client.get(`/tiles-pro/${args.tile_id}`),
780
+ },
781
+ // ═══════ MAP OBJECTS ═══════
782
+ {
783
+ name: "create_map_object",
784
+ description: "Generate a map object with transparent background for game use.",
785
+ inputSchema: {
786
+ type: "object",
787
+ properties: {
788
+ description: { type: "string", description: "Object description" },
789
+ image_size: sizeSchema("Output dimensions"),
790
+ view: viewEnum,
791
+ ...styleParams,
792
+ color_image: colorImage,
793
+ seed,
794
+ text_guidance_scale: { type: "number", description: "How closely to follow text (1-20, default 8)", minimum: 1, maximum: 20 },
795
+ init_image: imageSchema("Optional starting image"),
796
+ init_image_strength: { type: "number", description: "Initial image influence strength (1-999, default 300)", minimum: 1, maximum: 999 },
797
+ background_image: imageSchema("Background image for context"),
798
+ inpainting: { type: "string", description: "Inpainting configuration (JSON string or object)" },
799
+ },
800
+ required: ["description", "image_size"],
801
+ },
802
+ handler: async (client, args) => client.post("/map-objects", args),
803
+ },
804
+ // ═══════ CHARACTERS ═══════
805
+ {
806
+ name: "create_character_4dir",
807
+ description: "Create a persistent character with 4 directional views (N/S/E/W). Size 32x32 to 168x168.",
808
+ inputSchema: {
809
+ type: "object",
810
+ properties: {
811
+ description: { type: "string", description: "Character description" },
812
+ image_size: sizeSchema("Character sprite dimensions (32x32 to 168x168)"),
813
+ view: viewEnum,
814
+ proportions: proportionsSchema,
815
+ text_guidance_scale: textGuidanceScale,
816
+ isometric: { type: "boolean", description: "Generate in isometric view (default false)" },
817
+ color_image: imageSchema("Color reference image"),
818
+ force_colors: forceColors,
819
+ template_id: { type: "string", description: "Template ID (e.g. 'mannequin' for humanoid, 'bear'/'cat'/'dog'/'horse'/'lion' for quadruped)" },
820
+ ...styleParams,
821
+ seed,
822
+ },
823
+ required: ["description", "image_size"],
824
+ },
825
+ handler: async (client, args) => client.post("/create-character-with-4-directions", args),
826
+ },
827
+ {
828
+ name: "create_character_8dir",
829
+ description: "Create a persistent character with 8 directional views (N/NE/E/SE/S/SW/W/NW). Size 32x32 to 168x168.",
830
+ inputSchema: {
831
+ type: "object",
832
+ properties: {
833
+ description: { type: "string", description: "Character description" },
834
+ image_size: sizeSchema("Character sprite dimensions (32x32 to 168x168)"),
835
+ view: viewEnum,
836
+ proportions: proportionsSchema,
837
+ text_guidance_scale: textGuidanceScale,
838
+ isometric: { type: "boolean", description: "Generate in isometric view (default false)" },
839
+ color_image: imageSchema("Color reference image"),
840
+ force_colors: forceColors,
841
+ template_id: { type: "string", description: "Template ID (e.g. 'mannequin' for humanoid, 'bear'/'cat'/'dog'/'horse'/'lion' for quadruped)" },
842
+ ...styleParams,
843
+ seed,
844
+ },
845
+ required: ["description", "image_size"],
846
+ },
847
+ handler: async (client, args) => client.post("/create-character-with-8-directions", args),
848
+ },
849
+ {
850
+ name: "animate_character",
851
+ description: "Animate an existing character by ID with a specific animation template.",
852
+ inputSchema: {
853
+ type: "object",
854
+ properties: {
855
+ character_id: { type: "string", description: "Character ID" },
856
+ template_animation_id: {
857
+ type: "string",
858
+ enum: [
859
+ "backflip", "breathing-idle", "cross-punch", "crouched-walking",
860
+ "crouching", "drinking", "falling-back-death", "fight-stance-idle-8-frames",
861
+ "fireball", "flying-kick", "front-flip", "getting-up",
862
+ "high-kick", "hurricane-kick", "jumping-1", "jumping-2",
863
+ "lead-jab", "leg-sweep", "picking-up", "pull-heavy-object",
864
+ "pushing", "roundhouse-kick", "running-4-frames", "running-6-frames",
865
+ "running-8-frames", "running-jump", "running-slide", "sad-walk",
866
+ "scary-walk", "surprise-uppercut", "taking-punch", "throw-object",
867
+ "two-footed-jump", "walk", "walk-1", "walk-2",
868
+ "walking", "walking-10", "walking-2", "walking-3",
869
+ "walking-4", "walking-4-frames", "walking-5", "walking-6",
870
+ "walking-6-frames", "walking-7", "walking-8", "walking-8-frames",
871
+ "walking-9",
872
+ ],
873
+ description: "Animation template ID",
874
+ },
875
+ animation_name: { type: "string", description: "Custom animation name" },
876
+ description: { type: "string", description: "Character description for context" },
877
+ action_description: { type: "string", description: "Action description for custom animations" },
878
+ directions: {
879
+ type: "array",
880
+ items: { type: "string" },
881
+ description: "Specific directions to animate, or omit for all",
882
+ },
883
+ text_guidance_scale: textGuidanceScale,
884
+ isometric: { type: "boolean", description: "Generate in isometric view" },
885
+ color_image: imageSchema("Color reference image"),
886
+ force_colors: forceColors,
887
+ ...styleParams,
888
+ seed,
889
+ },
890
+ required: ["character_id", "template_animation_id"],
891
+ },
892
+ handler: async (client, args) => client.post("/animate-character", args),
893
+ },
894
+ {
895
+ name: "list_characters",
896
+ description: "List your created characters with pagination.",
897
+ inputSchema: {
898
+ type: "object",
899
+ properties: {
900
+ limit: { type: "number", description: "Results per page (1-100, default 50)" },
901
+ offset: { type: "number", description: "Pagination offset" },
902
+ },
903
+ },
904
+ handler: async (client, args) => {
905
+ const params = new URLSearchParams();
906
+ if (args.limit)
907
+ params.set("limit", String(args.limit));
908
+ if (args.offset)
909
+ params.set("offset", String(args.offset));
910
+ const qs = params.toString();
911
+ return client.get(`/characters${qs ? `?${qs}` : ""}`);
912
+ },
913
+ },
914
+ {
915
+ name: "get_character",
916
+ description: "Get a character by ID including all directional views and animations.",
917
+ inputSchema: {
918
+ type: "object",
919
+ properties: {
920
+ character_id: { type: "string", description: "Character ID" },
921
+ },
922
+ required: ["character_id"],
923
+ },
924
+ handler: async (client, args) => client.get(`/characters/${args.character_id}`),
925
+ },
926
+ {
927
+ name: "delete_character",
928
+ description: "Delete a character by ID.",
929
+ inputSchema: {
930
+ type: "object",
931
+ properties: {
932
+ character_id: { type: "string", description: "Character ID" },
933
+ },
934
+ required: ["character_id"],
935
+ },
936
+ handler: async (client, args) => client.delete(`/characters/${args.character_id}`),
937
+ },
938
+ {
939
+ name: "download_character_zip",
940
+ description: "Download a character as a ZIP file with all sprites and metadata.",
941
+ inputSchema: {
942
+ type: "object",
943
+ properties: {
944
+ character_id: { type: "string", description: "Character ID" },
945
+ },
946
+ required: ["character_id"],
947
+ },
948
+ handler: async (client, args) => client.get(`/characters/${args.character_id}/zip`),
949
+ },
950
+ {
951
+ name: "update_character_tags",
952
+ description: "Update tags on a character (max 20 tags, 50 chars each).",
953
+ inputSchema: {
954
+ type: "object",
955
+ properties: {
956
+ character_id: { type: "string", description: "Character ID" },
957
+ tags: {
958
+ type: "array",
959
+ items: { type: "string" },
960
+ description: "Tags to set",
961
+ },
962
+ },
963
+ required: ["character_id", "tags"],
964
+ },
965
+ handler: async (client, args) => client.patch(`/characters/${args.character_id}/tags`, { tags: args.tags }),
966
+ },
967
+ // ═══════ OBJECTS ═══════
968
+ {
969
+ name: "create_object_4dir",
970
+ description: "Create an object with 4 directional views.",
971
+ inputSchema: {
972
+ type: "object",
973
+ properties: {
974
+ description: { type: "string", description: "Object description" },
975
+ image_size: sizeSchema("Object dimensions"),
976
+ view: viewEnum,
977
+ text_guidance_scale: textGuidanceScale,
978
+ color_image: imageSchema("Color reference image"),
979
+ force_colors: forceColors,
980
+ ...styleParams,
981
+ seed,
982
+ },
983
+ required: ["description", "image_size"],
984
+ },
985
+ handler: async (client, args) => client.post("/create-object-with-4-directions", args),
986
+ },
987
+ {
988
+ name: "list_objects",
989
+ description: "List your created objects with pagination.",
990
+ inputSchema: {
991
+ type: "object",
992
+ properties: {
993
+ limit: { type: "number", description: "1-100, default 50" },
994
+ offset: { type: "number" },
995
+ },
996
+ },
997
+ handler: async (client, args) => {
998
+ const params = new URLSearchParams();
999
+ if (args.limit)
1000
+ params.set("limit", String(args.limit));
1001
+ if (args.offset)
1002
+ params.set("offset", String(args.offset));
1003
+ const qs = params.toString();
1004
+ return client.get(`/objects${qs ? `?${qs}` : ""}`);
1005
+ },
1006
+ },
1007
+ {
1008
+ name: "get_object",
1009
+ description: "Get an object by ID.",
1010
+ inputSchema: {
1011
+ type: "object",
1012
+ properties: {
1013
+ object_id: { type: "string", description: "Object ID" },
1014
+ },
1015
+ required: ["object_id"],
1016
+ },
1017
+ handler: async (client, args) => client.get(`/objects/${args.object_id}`),
1018
+ },
1019
+ {
1020
+ name: "delete_object",
1021
+ description: "Delete an object by ID.",
1022
+ inputSchema: {
1023
+ type: "object",
1024
+ properties: {
1025
+ object_id: { type: "string", description: "Object ID" },
1026
+ },
1027
+ required: ["object_id"],
1028
+ },
1029
+ handler: async (client, args) => client.delete(`/objects/${args.object_id}`),
1030
+ },
1031
+ {
1032
+ name: "update_object_tags",
1033
+ description: "Update tags on an object.",
1034
+ inputSchema: {
1035
+ type: "object",
1036
+ properties: {
1037
+ object_id: { type: "string", description: "Object ID" },
1038
+ tags: {
1039
+ type: "array",
1040
+ items: { type: "string" },
1041
+ description: "Tags to set",
1042
+ },
1043
+ },
1044
+ required: ["object_id", "tags"],
1045
+ },
1046
+ handler: async (client, args) => client.patch(`/objects/${args.object_id}/tags`, { tags: args.tags }),
1047
+ },
1048
+ // ═══════ UTILITY ═══════
1049
+ {
1050
+ name: "read_image",
1051
+ description: "Read a previously saved image from disk and return it as a Base64Image object " +
1052
+ "that can be passed directly to other tools (e.g. edit_image, remove_background, " +
1053
+ "image_to_pixelart). Use the file paths shown in earlier tool responses.",
1054
+ inputSchema: {
1055
+ type: "object",
1056
+ properties: {
1057
+ file_path: {
1058
+ type: "string",
1059
+ description: "Absolute path to a saved PNG image (from a previous tool response)",
1060
+ },
1061
+ },
1062
+ required: ["file_path"],
1063
+ },
1064
+ handler: async (_client, args) => {
1065
+ const filePath = resolve(args.file_path);
1066
+ const buf = readFileSync(filePath);
1067
+ const base64 = buf.toString("base64");
1068
+ return { image: { type: "base64", base64, format: "png" } };
1069
+ },
1070
+ },
1071
+ ];
1072
+ //# sourceMappingURL=tools.js.map