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/LICENSE +88 -0
- package/README.md +274 -0
- package/dist/chunk-743CKPHW.js +60 -0
- package/dist/chunk-7ARYEFAH.js +52 -0
- package/dist/chunk-MZZY4JXW.js +63 -0
- package/dist/chunk-ZVJQFWUI.js +272 -0
- package/dist/cli.js +531 -0
- package/dist/config-RMVFR3GN.js +13 -0
- package/dist/image-2H3GUQI6.js +16 -0
- package/dist/mcp/server.js +133 -0
- package/dist/tui.js +1274 -0
- package/package.json +94 -0
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,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
|
+
});
|