pixelmuse 0.2.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/cli.js ADDED
@@ -0,0 +1,531 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ generateImage
4
+ } from "./chunk-743CKPHW.js";
5
+ import {
6
+ PixelmuseClient,
7
+ deleteApiKey,
8
+ extractVariables,
9
+ getApiKey,
10
+ getTemplate,
11
+ interpolate,
12
+ isValidKeyFormat,
13
+ listTemplates,
14
+ saveApiKey,
15
+ saveTemplate,
16
+ slugify
17
+ } from "./chunk-ZVJQFWUI.js";
18
+ import {
19
+ renderImageDirect
20
+ } from "./chunk-MZZY4JXW.js";
21
+ import {
22
+ readSettings
23
+ } from "./chunk-7ARYEFAH.js";
24
+
25
+ // src/cli.ts
26
+ import meow from "meow";
27
+ import chalk from "chalk";
28
+ import ora from "ora";
29
+ import { readFileSync, watchFile, unwatchFile } from "fs";
30
+ import { resolve } from "path";
31
+
32
+ // src/core/utils.ts
33
+ function timeAgo(date) {
34
+ const seconds = Math.floor((Date.now() - date.getTime()) / 1e3);
35
+ if (seconds < 60) return "just now";
36
+ if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`;
37
+ if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`;
38
+ if (seconds < 604800) return `${Math.floor(seconds / 86400)}d ago`;
39
+ return date.toLocaleDateString();
40
+ }
41
+
42
+ // src/cli.ts
43
+ var cli = meow(
44
+ `
45
+ ${chalk.bold("Usage")}
46
+ $ pixelmuse "prompt" Generate with defaults
47
+ $ pixelmuse "prompt" -o hero.png Save to specific path
48
+ $ pixelmuse "prompt" -m nano-banana-pro Choose model
49
+ $ pixelmuse --json "prompt" Machine-readable output
50
+ $ echo "prompt" | pixelmuse Pipe from stdin
51
+
52
+ ${chalk.bold("Commands")}
53
+ pixelmuse models List available models
54
+ pixelmuse account View balance & usage
55
+ pixelmuse history Recent generations
56
+ pixelmuse open <id> Open generation in viewer
57
+ pixelmuse login Authenticate
58
+ pixelmuse logout Remove credentials
59
+ pixelmuse ui Launch interactive TUI
60
+ pixelmuse template <list|use|init|show> Manage prompt templates
61
+
62
+ ${chalk.bold("Options")}
63
+ -m, --model Model ID (default: nano-banana-2)
64
+ -a, --aspect-ratio Aspect ratio (default: 1:1)
65
+ -s, --style Style: realistic, anime, artistic, none
66
+ -o, --output Output file path
67
+ --json Output JSON (for scripting)
68
+ --no-preview Skip image preview
69
+ --open Open result in system viewer
70
+ --watch <file> Watch a prompt file and regenerate on save
71
+ --no-save Don't save image to disk
72
+ --clipboard Copy image to clipboard
73
+ -h, --help Show this help
74
+ -v, --version Show version
75
+
76
+ ${chalk.bold("Examples")}
77
+ $ pixelmuse "astronaut riding a horse"
78
+ $ pixelmuse "app icon, minimal" -a 1:1 -o icon.png
79
+ $ pixelmuse --watch prompt.txt -o output.png
80
+ $ pixelmuse template use blog-thumbnail --var subject="React hooks"
81
+ `,
82
+ {
83
+ importMeta: import.meta,
84
+ flags: {
85
+ model: { type: "string", shortFlag: "m" },
86
+ aspectRatio: { type: "string", shortFlag: "a" },
87
+ style: { type: "string", shortFlag: "s" },
88
+ output: { type: "string", shortFlag: "o" },
89
+ json: { type: "boolean", default: false },
90
+ preview: { type: "boolean", default: true },
91
+ open: { type: "boolean", default: false },
92
+ watch: { type: "string" },
93
+ save: { type: "boolean", default: true },
94
+ clipboard: { type: "boolean", default: false },
95
+ var: { type: "string", isMultiple: true }
96
+ }
97
+ }
98
+ );
99
+ var [subcommand, ...rest] = cli.input;
100
+ async function readStdin() {
101
+ if (process.stdin.isTTY) return null;
102
+ const chunks = [];
103
+ for await (const chunk of process.stdin) {
104
+ chunks.push(chunk);
105
+ }
106
+ return Buffer.concat(chunks).toString("utf-8").trim() || null;
107
+ }
108
+ async function requireClient() {
109
+ const key = await getApiKey();
110
+ if (!key) {
111
+ console.error(chalk.red("Not authenticated. Run: pixelmuse login"));
112
+ process.exit(1);
113
+ }
114
+ return new PixelmuseClient(key);
115
+ }
116
+ async function copyToClipboard(imagePath) {
117
+ const { execSync } = await import("child_process");
118
+ try {
119
+ if (process.platform === "darwin") {
120
+ execSync(`osascript -e 'set the clipboard to (read (POSIX file "${imagePath}") as TIFF picture)'`);
121
+ } else {
122
+ execSync(`xclip -selection clipboard -t image/png -i "${imagePath}"`);
123
+ }
124
+ return true;
125
+ } catch {
126
+ return false;
127
+ }
128
+ }
129
+ async function handleGenerate(prompt) {
130
+ const client = await requireClient();
131
+ const settings = readSettings();
132
+ const model = cli.flags.model ?? settings.defaultModel;
133
+ const aspectRatio = cli.flags.aspectRatio ?? settings.defaultAspectRatio;
134
+ const style = cli.flags.style ?? settings.defaultStyle;
135
+ let balance = null;
136
+ let creditCost = null;
137
+ try {
138
+ const [account, models] = await Promise.all([
139
+ client.getAccount(),
140
+ PixelmuseClient.listModels()
141
+ ]);
142
+ balance = account.credits.total;
143
+ creditCost = models.find((m) => m.id === model)?.credit_cost ?? null;
144
+ } catch {
145
+ }
146
+ if (cli.flags.json) {
147
+ try {
148
+ const result = await generateImage(client, {
149
+ prompt,
150
+ model,
151
+ aspectRatio,
152
+ style,
153
+ output: cli.flags.output,
154
+ noSave: !cli.flags.save
155
+ });
156
+ console.log(
157
+ JSON.stringify({
158
+ id: result.generation.id,
159
+ status: result.generation.status,
160
+ model: result.generation.model,
161
+ prompt: result.generation.prompt,
162
+ credits_charged: result.generation.credits_charged,
163
+ elapsed_seconds: Math.round(result.elapsed * 10) / 10,
164
+ output_path: result.imagePath
165
+ })
166
+ );
167
+ } catch (err) {
168
+ console.error(JSON.stringify({ error: err instanceof Error ? err.message : "Generation failed" }));
169
+ process.exit(1);
170
+ }
171
+ return;
172
+ }
173
+ const costStr = creditCost !== null ? `${creditCost} credit${creditCost > 1 ? "s" : ""}` : "";
174
+ const balStr = balance !== null ? ` (balance: ${balance})` : "";
175
+ const spinner = ora(`${chalk.bold(model)} \xB7 ${costStr}${balStr} \u2014 generating...`).start();
176
+ try {
177
+ const result = await generateImage(client, {
178
+ prompt,
179
+ model,
180
+ aspectRatio,
181
+ style,
182
+ output: cli.flags.output,
183
+ noSave: !cli.flags.save,
184
+ onProgress: (elapsed) => {
185
+ spinner.text = `${chalk.bold(model)} \xB7 ${costStr}${balStr} \u2014 generating... ${chalk.gray(`${elapsed.toFixed(1)}s`)}`;
186
+ }
187
+ });
188
+ const charged = result.generation.credits_charged;
189
+ const remaining = balance !== null ? balance - charged : null;
190
+ const remainStr = remaining !== null ? ` (remaining: ${remaining})` : "";
191
+ spinner.succeed(
192
+ `Generated in ${result.elapsed.toFixed(1)}s \xB7 ${charged} credit${charged > 1 ? "s" : ""} charged${remainStr}`
193
+ );
194
+ if (result.imagePath) {
195
+ console.log(chalk.gray(` Saved to ${result.imagePath}`));
196
+ }
197
+ if (cli.flags.preview && result.imagePath) {
198
+ const previewed = renderImageDirect(result.imagePath);
199
+ if (!previewed) console.log(chalk.gray(" Install chafa for terminal image preview"));
200
+ }
201
+ if (cli.flags.clipboard && result.imagePath) {
202
+ const ok = await copyToClipboard(result.imagePath);
203
+ if (ok) console.log(chalk.gray(" Copied to clipboard"));
204
+ }
205
+ if (cli.flags.open && result.imagePath) {
206
+ const { default: open } = await import("open");
207
+ await open(result.imagePath);
208
+ }
209
+ } catch (err) {
210
+ spinner.fail(err instanceof Error ? err.message : "Generation failed");
211
+ process.exit(1);
212
+ }
213
+ }
214
+ async function handleWatch(filePath) {
215
+ const absPath = resolve(filePath);
216
+ const client = await requireClient();
217
+ const settings = readSettings();
218
+ console.log(chalk.cyan(`Watching ${absPath} for changes... (Ctrl+C to stop)`));
219
+ let generating = false;
220
+ const generate = async () => {
221
+ if (generating) return;
222
+ generating = true;
223
+ const prompt = readFileSync(absPath, "utf-8").trim();
224
+ if (!prompt) {
225
+ generating = false;
226
+ return;
227
+ }
228
+ const model = cli.flags.model ?? settings.defaultModel;
229
+ const aspectRatio = cli.flags.aspectRatio ?? settings.defaultAspectRatio;
230
+ const style = cli.flags.style ?? settings.defaultStyle;
231
+ const time = (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false });
232
+ const spinner = ora(`[${time}] Generating... (${model})`).start();
233
+ try {
234
+ const result = await generateImage(client, {
235
+ prompt,
236
+ model,
237
+ aspectRatio,
238
+ style,
239
+ output: cli.flags.output
240
+ });
241
+ spinner.succeed(`[${time}] Saved to ${result.imagePath} (${result.elapsed.toFixed(1)}s)`);
242
+ } catch (err) {
243
+ spinner.fail(`[${time}] ${err instanceof Error ? err.message : "Failed"}`);
244
+ }
245
+ generating = false;
246
+ };
247
+ await generate();
248
+ watchFile(absPath, { interval: 500 }, generate);
249
+ process.on("SIGINT", () => {
250
+ unwatchFile(absPath);
251
+ console.log(chalk.gray("\nStopped watching."));
252
+ process.exit(0);
253
+ });
254
+ await new Promise(() => {
255
+ });
256
+ }
257
+ async function handleModels() {
258
+ const spinner = ora("Loading models...").start();
259
+ try {
260
+ const models = await PixelmuseClient.listModels();
261
+ spinner.stop();
262
+ const nameW = 22;
263
+ const costW = 10;
264
+ console.log(
265
+ chalk.bold("Model".padEnd(nameW)) + chalk.bold("Credits".padEnd(costW)) + chalk.bold("Best For")
266
+ );
267
+ console.log(chalk.gray("\u2500".repeat(70)));
268
+ for (const m of models) {
269
+ const cost = m.credit_cost > 1 ? chalk.yellow(String(m.credit_cost)) : chalk.green(String(m.credit_cost));
270
+ console.log(
271
+ m.name.padEnd(nameW) + cost.padEnd(costW + (cost.length - String(m.credit_cost).length)) + chalk.gray(m.strengths.join(", "))
272
+ );
273
+ }
274
+ } catch (err) {
275
+ spinner.fail(err instanceof Error ? err.message : "Failed to load models");
276
+ process.exit(1);
277
+ }
278
+ }
279
+ async function handleAccount() {
280
+ const client = await requireClient();
281
+ const spinner = ora("Loading account...").start();
282
+ try {
283
+ const account = await client.getAccount();
284
+ spinner.stop();
285
+ console.log(`${chalk.bold("Email:")} ${account.email}`);
286
+ console.log(`${chalk.bold("Plan:")} ${chalk.cyan(account.plan)}`);
287
+ console.log(
288
+ `${chalk.bold("Credits:")} ${chalk.green(String(account.credits.total))} (${account.credits.subscription} subscription + ${account.credits.purchased} purchased)`
289
+ );
290
+ console.log(`${chalk.bold("Rate:")} ${account.rate_limit.requests_per_minute} req/min`);
291
+ } catch (err) {
292
+ spinner.fail(err instanceof Error ? err.message : "Failed to load account");
293
+ process.exit(1);
294
+ }
295
+ }
296
+ async function handleHistory() {
297
+ const client = await requireClient();
298
+ const spinner = ora("Loading history...").start();
299
+ try {
300
+ const result = await client.listGenerations({ limit: 20 });
301
+ spinner.stop();
302
+ if (result.data.length === 0) {
303
+ console.log("No generations yet.");
304
+ return;
305
+ }
306
+ const idW = 18;
307
+ const modelW = 22;
308
+ const promptW = 34;
309
+ console.log(
310
+ chalk.bold("ID".padEnd(idW)) + chalk.bold("Model".padEnd(modelW)) + chalk.bold("Prompt".padEnd(promptW)) + chalk.bold("Date")
311
+ );
312
+ console.log(chalk.gray("\u2500".repeat(90)));
313
+ for (const gen of result.data) {
314
+ const id = gen.id.slice(0, 15) + "...";
315
+ const prompt = gen.prompt.length > 30 ? gen.prompt.slice(0, 30) + "..." : gen.prompt;
316
+ const date = timeAgo(new Date(gen.created_at));
317
+ const status = gen.status === "succeeded" ? chalk.green("\u25CF") : gen.status === "failed" ? chalk.red("\u2717") : chalk.yellow("\u25CC");
318
+ console.log(
319
+ chalk.gray(id.padEnd(idW)) + `${status} ${gen.model}`.padEnd(modelW + status.length - 1) + prompt.padEnd(promptW) + chalk.gray(date)
320
+ );
321
+ }
322
+ } catch (err) {
323
+ spinner.fail(err instanceof Error ? err.message : "Failed to load history");
324
+ process.exit(1);
325
+ }
326
+ }
327
+ async function handleOpen(id) {
328
+ const client = await requireClient();
329
+ const spinner = ora("Loading generation...").start();
330
+ try {
331
+ const gen = await client.getGeneration(id);
332
+ spinner.stop();
333
+ if (gen.output?.[0]) {
334
+ const { imageToBuffer: imageToBuffer2, saveImage: saveImage2 } = await import("./image-2H3GUQI6.js");
335
+ const { PATHS: PATHS2 } = await import("./config-RMVFR3GN.js");
336
+ const { join } = await import("path");
337
+ const buf = await imageToBuffer2(gen.output[0]);
338
+ const path = join(PATHS2.generations, `${gen.id}.png`);
339
+ saveImage2(buf, path);
340
+ const { default: open } = await import("open");
341
+ await open(path);
342
+ console.log(`Opened ${path}`);
343
+ } else {
344
+ console.log(`Generation ${id} has no output (status: ${gen.status})`);
345
+ }
346
+ } catch (err) {
347
+ spinner.fail(err instanceof Error ? err.message : "Failed");
348
+ process.exit(1);
349
+ }
350
+ }
351
+ async function handleLogin() {
352
+ const { createInterface } = await import("readline");
353
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
354
+ const key = await new Promise((resolve2) => {
355
+ rl.question("API key (from pixelmuse.studio/settings/api-keys): ", (answer) => {
356
+ rl.close();
357
+ resolve2(answer.trim());
358
+ });
359
+ });
360
+ if (!isValidKeyFormat(key)) {
361
+ console.error(chalk.red("Invalid key format. Keys start with pm_live_ or pm_test_"));
362
+ process.exit(1);
363
+ }
364
+ const spinner = ora("Validating...").start();
365
+ try {
366
+ const client = new PixelmuseClient(key);
367
+ const account = await client.getAccount();
368
+ await saveApiKey(key);
369
+ spinner.succeed(`Authenticated as ${chalk.bold(account.email)} (${chalk.green(`${account.credits.total} credits`)})`);
370
+ } catch (err) {
371
+ spinner.fail(err instanceof Error ? err.message : "Authentication failed");
372
+ process.exit(1);
373
+ }
374
+ }
375
+ async function handleTemplate() {
376
+ const [, templateCmd, ...templateArgs] = cli.input;
377
+ switch (templateCmd) {
378
+ case "list": {
379
+ const templates = listTemplates();
380
+ if (templates.length === 0) {
381
+ console.log("No templates. Create one with: pixelmuse template init <name>");
382
+ return;
383
+ }
384
+ for (const t of templates) {
385
+ console.log(`${chalk.bold(t.name)} ${chalk.gray(`\u2014 ${t.description}`)}`);
386
+ if (t.tags.length > 0) console.log(chalk.gray(` tags: ${t.tags.join(", ")}`));
387
+ }
388
+ break;
389
+ }
390
+ case "show": {
391
+ const name = templateArgs.join(" ");
392
+ if (!name) {
393
+ console.error(chalk.red("Usage: pixelmuse template show <name>"));
394
+ process.exit(1);
395
+ }
396
+ const template = getTemplate(name);
397
+ if (!template) {
398
+ console.error(chalk.red(`Template "${name}" not found`));
399
+ process.exit(1);
400
+ }
401
+ console.log(chalk.bold(template.name));
402
+ console.log(chalk.gray(template.description));
403
+ console.log(`
404
+ ${chalk.bold("Prompt:")} ${template.prompt}`);
405
+ const vars = extractVariables(template.prompt);
406
+ if (vars.length > 0) {
407
+ console.log(`${chalk.bold("Variables:")} ${vars.join(", ")}`);
408
+ for (const [k, v] of Object.entries(template.variables)) {
409
+ console.log(chalk.gray(` ${k} = "${v}"`));
410
+ }
411
+ }
412
+ if (template.defaults.model) console.log(`${chalk.bold("Model:")} ${template.defaults.model}`);
413
+ if (template.defaults.aspect_ratio) console.log(`${chalk.bold("Aspect:")} ${template.defaults.aspect_ratio}`);
414
+ break;
415
+ }
416
+ case "init": {
417
+ const name = templateArgs.join(" ");
418
+ if (!name) {
419
+ console.error(chalk.red("Usage: pixelmuse template init <name>"));
420
+ process.exit(1);
421
+ }
422
+ const existing = getTemplate(name);
423
+ if (existing) {
424
+ console.error(chalk.red(`Template "${name}" already exists`));
425
+ process.exit(1);
426
+ }
427
+ const template = {
428
+ name,
429
+ description: `${name} template`,
430
+ prompt: "A {{subject}} with dramatic lighting",
431
+ defaults: { model: "nano-banana-2", aspect_ratio: "16:9" },
432
+ variables: { subject: "landscape" },
433
+ tags: []
434
+ };
435
+ saveTemplate(template);
436
+ const { PATHS: PATHS2 } = await import("./config-RMVFR3GN.js");
437
+ const path = `${PATHS2.prompts}/${slugify(name)}.yaml`;
438
+ console.log(`Created template: ${chalk.bold(path)}`);
439
+ console.log(chalk.gray("Edit the YAML file to customize your template."));
440
+ break;
441
+ }
442
+ case "use": {
443
+ const name = templateArgs.join(" ");
444
+ if (!name) {
445
+ console.error(chalk.red("Usage: pixelmuse template use <name> [--var key=value]"));
446
+ process.exit(1);
447
+ }
448
+ const template = getTemplate(name);
449
+ if (!template) {
450
+ console.error(chalk.red(`Template "${name}" not found`));
451
+ process.exit(1);
452
+ }
453
+ const vars = { ...template.variables };
454
+ for (const v of cli.flags.var ?? []) {
455
+ const eq = v.indexOf("=");
456
+ if (eq > 0) {
457
+ vars[v.slice(0, eq)] = v.slice(eq + 1);
458
+ }
459
+ }
460
+ const prompt = interpolate(template.prompt, vars);
461
+ if (template.defaults.model && !cli.flags.model) {
462
+ cli.flags.model = template.defaults.model;
463
+ }
464
+ if (template.defaults.aspect_ratio && !cli.flags.aspectRatio) {
465
+ cli.flags.aspectRatio = template.defaults.aspect_ratio;
466
+ }
467
+ if (template.defaults.style && !cli.flags.style) {
468
+ cli.flags.style = template.defaults.style;
469
+ }
470
+ await handleGenerate(prompt);
471
+ break;
472
+ }
473
+ default:
474
+ console.log(`Usage: pixelmuse template <list|show|init|use> [name]`);
475
+ break;
476
+ }
477
+ }
478
+ async function main() {
479
+ const stdinPrompt = await readStdin();
480
+ switch (subcommand) {
481
+ case "models":
482
+ return handleModels();
483
+ case "account":
484
+ return handleAccount();
485
+ case "history":
486
+ return handleHistory();
487
+ case "open":
488
+ if (!rest[0]) {
489
+ console.error(chalk.red("Usage: pixelmuse open <generation-id>"));
490
+ process.exit(1);
491
+ }
492
+ return handleOpen(rest[0]);
493
+ case "login":
494
+ return handleLogin();
495
+ case "logout":
496
+ await deleteApiKey();
497
+ console.log("Logged out.");
498
+ return;
499
+ case "template":
500
+ return handleTemplate();
501
+ case "ui": {
502
+ const { launchTui } = await import("./tui.js");
503
+ return launchTui();
504
+ }
505
+ // Explicit generate or default with prompt
506
+ case "generate": {
507
+ const prompt = rest.join(" ") || stdinPrompt;
508
+ if (!prompt) {
509
+ console.error(chalk.red('Usage: pixelmuse generate "your prompt"'));
510
+ process.exit(1);
511
+ }
512
+ if (cli.flags.watch) return handleWatch(cli.flags.watch);
513
+ return handleGenerate(prompt);
514
+ }
515
+ default: {
516
+ const prompt = cli.input.join(" ") || stdinPrompt;
517
+ if (!prompt) {
518
+ if (process.stdin.isTTY) {
519
+ cli.showHelp();
520
+ }
521
+ return;
522
+ }
523
+ if (cli.flags.watch) return handleWatch(cli.flags.watch);
524
+ return handleGenerate(prompt);
525
+ }
526
+ }
527
+ }
528
+ main().catch((err) => {
529
+ console.error(chalk.red(err instanceof Error ? err.message : "Fatal error"));
530
+ process.exit(1);
531
+ });
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ PATHS,
4
+ ensureDirs,
5
+ readSettings,
6
+ writeSettings
7
+ } from "./chunk-7ARYEFAH.js";
8
+ export {
9
+ PATHS,
10
+ ensureDirs,
11
+ readSettings,
12
+ writeSettings
13
+ };
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ autoSave,
4
+ hasChafa,
5
+ imageToBuffer,
6
+ renderImageDirect,
7
+ saveImage
8
+ } from "./chunk-MZZY4JXW.js";
9
+ import "./chunk-7ARYEFAH.js";
10
+ export {
11
+ autoSave,
12
+ hasChafa,
13
+ imageToBuffer,
14
+ renderImageDirect,
15
+ saveImage
16
+ };
@@ -0,0 +1,133 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ generateImage
4
+ } from "../chunk-743CKPHW.js";
5
+ import {
6
+ CLI_VERSION,
7
+ PixelmuseClient,
8
+ getApiKey
9
+ } from "../chunk-ZVJQFWUI.js";
10
+ import "../chunk-MZZY4JXW.js";
11
+ import {
12
+ readSettings
13
+ } from "../chunk-7ARYEFAH.js";
14
+
15
+ // src/mcp/server.ts
16
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
17
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
18
+ import { z } from "zod";
19
+ var MODELS = [
20
+ "nano-banana-2",
21
+ "nano-banana-pro",
22
+ "flux-schnell",
23
+ "imagen-3",
24
+ "recraft-v4",
25
+ "recraft-v4-pro"
26
+ ];
27
+ var ASPECT_RATIOS = ["1:1", "16:9", "9:16", "3:2", "2:3", "4:5", "5:4", "3:4", "4:3", "21:9", "9:21"];
28
+ var STYLES = ["none", "realistic", "anime", "artistic"];
29
+ async function getClient() {
30
+ const key = await getApiKey();
31
+ if (!key) throw new Error("No API key found. Set PIXELMUSE_API_KEY environment variable.");
32
+ return new PixelmuseClient(key);
33
+ }
34
+ var server = new McpServer({
35
+ name: "pixelmuse",
36
+ version: CLI_VERSION
37
+ });
38
+ server.tool(
39
+ "generate_image",
40
+ "Generate an AI image using Pixelmuse. Returns the saved file path and generation metadata.",
41
+ {
42
+ prompt: z.string().describe("Image description / prompt"),
43
+ model: z.enum(MODELS).optional().default("nano-banana-2").describe("Model to use"),
44
+ aspect_ratio: z.enum(ASPECT_RATIOS).optional().default("1:1").describe("Image aspect ratio"),
45
+ style: z.enum(STYLES).optional().default("none").describe("Visual style"),
46
+ output_path: z.string().optional().describe("Where to save the image (default: current directory)")
47
+ },
48
+ async (args) => {
49
+ const client = await getClient();
50
+ const settings = readSettings();
51
+ const result = await generateImage(client, {
52
+ prompt: args.prompt,
53
+ model: args.model ?? settings.defaultModel,
54
+ aspectRatio: args.aspect_ratio ?? settings.defaultAspectRatio,
55
+ style: args.style ?? settings.defaultStyle,
56
+ output: args.output_path
57
+ });
58
+ return {
59
+ content: [
60
+ {
61
+ type: "text",
62
+ text: JSON.stringify({
63
+ id: result.generation.id,
64
+ status: result.generation.status,
65
+ model: result.generation.model,
66
+ prompt: result.generation.prompt,
67
+ credits_charged: result.generation.credits_charged,
68
+ elapsed_seconds: Math.round(result.elapsed * 10) / 10,
69
+ output_path: result.imagePath
70
+ }, null, 2)
71
+ }
72
+ ]
73
+ };
74
+ }
75
+ );
76
+ server.tool(
77
+ "list_models",
78
+ "List all available Pixelmuse image generation models with their costs and capabilities.",
79
+ {},
80
+ async () => {
81
+ const models = await PixelmuseClient.listModels();
82
+ return {
83
+ content: [
84
+ {
85
+ type: "text",
86
+ text: JSON.stringify(
87
+ models.map((m) => ({
88
+ id: m.id,
89
+ name: m.name,
90
+ credit_cost: m.credit_cost,
91
+ strengths: m.strengths,
92
+ supported_aspect_ratios: m.supported_aspect_ratios
93
+ })),
94
+ null,
95
+ 2
96
+ )
97
+ }
98
+ ]
99
+ };
100
+ }
101
+ );
102
+ server.tool(
103
+ "check_balance",
104
+ "Check Pixelmuse account credit balance and plan info.",
105
+ {},
106
+ async () => {
107
+ const client = await getClient();
108
+ const account = await client.getAccount();
109
+ return {
110
+ content: [
111
+ {
112
+ type: "text",
113
+ text: JSON.stringify({
114
+ email: account.email,
115
+ plan: account.plan,
116
+ credits_total: account.credits.total,
117
+ credits_subscription: account.credits.subscription,
118
+ credits_purchased: account.credits.purchased,
119
+ rate_limit: account.rate_limit.requests_per_minute
120
+ }, null, 2)
121
+ }
122
+ ]
123
+ };
124
+ }
125
+ );
126
+ async function main() {
127
+ const transport = new StdioServerTransport();
128
+ await server.connect(transport);
129
+ }
130
+ main().catch((err) => {
131
+ console.error("MCP server error:", err);
132
+ process.exit(1);
133
+ });