pixelmuse 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -5
- package/dist/{chunk-MZZY4JXW.js → chunk-M4N6UFDD.js} +1 -1
- package/dist/{chunk-7ARYEFAH.js → chunk-PSHBBAJV.js} +1 -0
- package/dist/{chunk-3BR2BNXV.js → chunk-Q5SB7WKR.js} +2 -2
- package/dist/{chunk-LOW6S5UO.js → chunk-STJDWR4Q.js} +5 -3
- package/dist/cli.js +289 -16
- package/dist/{config-RMVFR3GN.js → config-Z76UJLDX.js} +1 -1
- package/dist/{image-2H3GUQI6.js → image-GQSNVWNN.js} +2 -2
- package/dist/mcp/server.js +8 -5
- package/dist/tui.js +11 -7
- package/package.json +12 -20
package/README.md
CHANGED
|
@@ -31,15 +31,12 @@
|
|
|
31
31
|
|
|
32
32
|
## Get Started
|
|
33
33
|
|
|
34
|
-
The fastest way to start generating images is with npm — one install, one login, and you're creating:
|
|
35
|
-
|
|
36
34
|
```bash
|
|
37
35
|
npm install -g pixelmuse
|
|
38
|
-
pixelmuse
|
|
39
|
-
pixelmuse "a cat floating through space"
|
|
36
|
+
pixelmuse setup
|
|
40
37
|
```
|
|
41
38
|
|
|
42
|
-
|
|
39
|
+
The setup wizard creates your account (opens browser), configures your API key, and optionally sets up the MCP server for Claude Code, Cursor, or Windsurf. New accounts include **15 free credits**.
|
|
43
40
|
|
|
44
41
|
> Requires Node.js 20+. For terminal image previews, install [chafa](https://hpjansson.org/chafa/) (`brew install chafa` / `sudo apt install chafa`).
|
|
45
42
|
|
|
@@ -210,6 +207,7 @@ Pixelmuse ships four interfaces. Pick the one that fits your workflow — they a
|
|
|
210
207
|
|
|
211
208
|
| Command | Description |
|
|
212
209
|
|---------|-------------|
|
|
210
|
+
| `pixelmuse setup` | First-time setup wizard (account, MCP, defaults) |
|
|
213
211
|
| `pixelmuse "prompt"` | Generate an image (default command) |
|
|
214
212
|
| `pixelmuse models` | List available models with costs |
|
|
215
213
|
| `pixelmuse account` | Account balance and usage stats |
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
PATHS,
|
|
4
4
|
ensureDirs
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-PSHBBAJV.js";
|
|
6
6
|
|
|
7
7
|
// src/core/types.ts
|
|
8
8
|
var ApiError = class extends Error {
|
|
@@ -17,7 +17,7 @@ var ApiError = class extends Error {
|
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
// src/core/client.ts
|
|
20
|
-
var CLI_VERSION = "0.
|
|
20
|
+
var CLI_VERSION = "0.4.0";
|
|
21
21
|
var BASE_URL = "https://www.pixelmuse.studio/api/v1";
|
|
22
22
|
var CLIENT_HEADERS = {
|
|
23
23
|
"User-Agent": `pixelmuse-cli/${CLI_VERSION}`,
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
import {
|
|
3
3
|
pollGeneration,
|
|
4
4
|
slugify
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-Q5SB7WKR.js";
|
|
6
6
|
import {
|
|
7
7
|
autoSave,
|
|
8
8
|
imageToBuffer,
|
|
9
9
|
saveImage
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-M4N6UFDD.js";
|
|
11
11
|
|
|
12
12
|
// src/core/generate.ts
|
|
13
13
|
import { existsSync } from "fs";
|
|
@@ -29,6 +29,7 @@ async function generateImage(client, options) {
|
|
|
29
29
|
model = "nano-banana-2",
|
|
30
30
|
aspectRatio = "1:1",
|
|
31
31
|
style,
|
|
32
|
+
visibility,
|
|
32
33
|
output,
|
|
33
34
|
noSave = false,
|
|
34
35
|
onProgress
|
|
@@ -38,7 +39,8 @@ async function generateImage(client, options) {
|
|
|
38
39
|
prompt,
|
|
39
40
|
model,
|
|
40
41
|
aspect_ratio: aspectRatio,
|
|
41
|
-
style: style === "none" ? void 0 : style
|
|
42
|
+
style: style === "none" ? void 0 : style,
|
|
43
|
+
visibility
|
|
42
44
|
});
|
|
43
45
|
if (gen.status === "processing" || gen.status === "pending") {
|
|
44
46
|
gen = await pollGeneration(client, gen.id, { onProgress });
|
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
generateImage
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-STJDWR4Q.js";
|
|
5
5
|
import {
|
|
6
|
+
CLI_VERSION,
|
|
6
7
|
PixelmuseClient,
|
|
7
8
|
deleteApiKey,
|
|
8
9
|
extractVariables,
|
|
@@ -14,19 +15,20 @@ import {
|
|
|
14
15
|
saveApiKey,
|
|
15
16
|
saveTemplate,
|
|
16
17
|
slugify
|
|
17
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-Q5SB7WKR.js";
|
|
18
19
|
import {
|
|
19
20
|
renderImageDirect
|
|
20
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-M4N6UFDD.js";
|
|
21
22
|
import {
|
|
22
|
-
readSettings
|
|
23
|
-
|
|
23
|
+
readSettings,
|
|
24
|
+
writeSettings
|
|
25
|
+
} from "./chunk-PSHBBAJV.js";
|
|
24
26
|
|
|
25
27
|
// src/cli.ts
|
|
26
28
|
import meow from "meow";
|
|
27
29
|
import chalk from "chalk";
|
|
28
30
|
import ora from "ora";
|
|
29
|
-
import { readFileSync, watchFile, unwatchFile } from "fs";
|
|
31
|
+
import { readFileSync as readFileSync2, watchFile, unwatchFile } from "fs";
|
|
30
32
|
import { resolve } from "path";
|
|
31
33
|
|
|
32
34
|
// src/core/utils.ts
|
|
@@ -39,6 +41,128 @@ function timeAgo(date) {
|
|
|
39
41
|
return date.toLocaleDateString();
|
|
40
42
|
}
|
|
41
43
|
|
|
44
|
+
// src/core/device-auth.ts
|
|
45
|
+
var BASE_URL = "https://www.pixelmuse.studio/api/v1";
|
|
46
|
+
async function initiateDeviceAuth() {
|
|
47
|
+
const res = await fetch(`${BASE_URL}/auth/device`, {
|
|
48
|
+
method: "POST",
|
|
49
|
+
headers: {
|
|
50
|
+
"Content-Type": "application/json",
|
|
51
|
+
"User-Agent": `pixelmuse-cli/${CLI_VERSION}`
|
|
52
|
+
},
|
|
53
|
+
body: JSON.stringify({
|
|
54
|
+
client_info: {
|
|
55
|
+
platform: process.platform,
|
|
56
|
+
cli_version: CLI_VERSION
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
});
|
|
60
|
+
if (!res.ok) {
|
|
61
|
+
const body = await res.text().catch(() => "");
|
|
62
|
+
throw new Error(`Failed to initiate device auth (HTTP ${res.status}): ${body}`);
|
|
63
|
+
}
|
|
64
|
+
const data = await res.json();
|
|
65
|
+
return {
|
|
66
|
+
deviceCode: data.device_code,
|
|
67
|
+
userCode: data.user_code,
|
|
68
|
+
verificationUri: data.verification_uri,
|
|
69
|
+
verificationUriComplete: data.verification_uri_complete,
|
|
70
|
+
expiresIn: data.expires_in,
|
|
71
|
+
interval: data.interval
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
async function pollForToken(deviceCode, options) {
|
|
75
|
+
const deadline = Date.now() + options.expiresIn * 1e3;
|
|
76
|
+
let intervalMs = options.interval * 1e3;
|
|
77
|
+
while (Date.now() < deadline) {
|
|
78
|
+
await new Promise((r) => setTimeout(r, intervalMs));
|
|
79
|
+
options.onPoll?.();
|
|
80
|
+
try {
|
|
81
|
+
const res = await fetch(`${BASE_URL}/auth/device/token`, {
|
|
82
|
+
method: "POST",
|
|
83
|
+
headers: {
|
|
84
|
+
"Content-Type": "application/json",
|
|
85
|
+
"User-Agent": `pixelmuse-cli/${CLI_VERSION}`
|
|
86
|
+
},
|
|
87
|
+
body: JSON.stringify({
|
|
88
|
+
device_code: deviceCode,
|
|
89
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
90
|
+
})
|
|
91
|
+
});
|
|
92
|
+
const data = await res.json();
|
|
93
|
+
if (data.api_key) {
|
|
94
|
+
return {
|
|
95
|
+
apiKey: data.api_key,
|
|
96
|
+
keyPrefix: data.key_prefix
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
if (data.error === "slow_down") {
|
|
100
|
+
intervalMs = (data.interval ?? 10) * 1e3;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (data.error === "expired_token") {
|
|
104
|
+
throw new Error("Authorization timed out. Run `pixelmuse setup` to try again.");
|
|
105
|
+
}
|
|
106
|
+
if (data.error === "access_denied") {
|
|
107
|
+
throw new Error("Authorization was denied.");
|
|
108
|
+
}
|
|
109
|
+
} catch (err) {
|
|
110
|
+
if (err instanceof Error && (err.message.includes("timed out") || err.message.includes("denied"))) {
|
|
111
|
+
throw err;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
throw new Error("Authorization timed out. Run `pixelmuse setup` to try again.");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// src/core/mcp-config.ts
|
|
119
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
120
|
+
import { dirname, join } from "path";
|
|
121
|
+
import { homedir } from "os";
|
|
122
|
+
var HOME = homedir();
|
|
123
|
+
var EDITORS = [
|
|
124
|
+
{
|
|
125
|
+
name: "Claude Code",
|
|
126
|
+
id: "claude-code",
|
|
127
|
+
configPath: join(HOME, ".claude", "mcp.json")
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: "Cursor",
|
|
131
|
+
id: "cursor",
|
|
132
|
+
configPath: join(HOME, ".cursor", "mcp.json")
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
name: "Windsurf",
|
|
136
|
+
id: "windsurf",
|
|
137
|
+
configPath: join(HOME, ".codeium", "windsurf", "mcp_config.json")
|
|
138
|
+
}
|
|
139
|
+
];
|
|
140
|
+
function detectEditors() {
|
|
141
|
+
return EDITORS.map((editor) => ({
|
|
142
|
+
...editor,
|
|
143
|
+
installed: existsSync(dirname(editor.configPath))
|
|
144
|
+
}));
|
|
145
|
+
}
|
|
146
|
+
function configureMcp(editor, apiKey) {
|
|
147
|
+
const entry = {
|
|
148
|
+
command: "npx",
|
|
149
|
+
args: ["-y", "pixelmuse-mcp"],
|
|
150
|
+
env: { PIXELMUSE_API_KEY: apiKey }
|
|
151
|
+
};
|
|
152
|
+
let config = {};
|
|
153
|
+
if (existsSync(editor.configPath)) {
|
|
154
|
+
try {
|
|
155
|
+
config = JSON.parse(readFileSync(editor.configPath, "utf-8"));
|
|
156
|
+
} catch {
|
|
157
|
+
}
|
|
158
|
+
} else {
|
|
159
|
+
mkdirSync(dirname(editor.configPath), { recursive: true });
|
|
160
|
+
}
|
|
161
|
+
config.mcpServers = config.mcpServers ?? {};
|
|
162
|
+
config.mcpServers.pixelmuse = entry;
|
|
163
|
+
writeFileSync(editor.configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
164
|
+
}
|
|
165
|
+
|
|
42
166
|
// src/cli.ts
|
|
43
167
|
var cli = meow(
|
|
44
168
|
`
|
|
@@ -50,6 +174,7 @@ var cli = meow(
|
|
|
50
174
|
$ echo "prompt" | pixelmuse Pipe from stdin
|
|
51
175
|
|
|
52
176
|
${chalk.bold("Commands")}
|
|
177
|
+
pixelmuse setup First-time setup wizard
|
|
53
178
|
pixelmuse models List available models
|
|
54
179
|
pixelmuse account View balance & usage
|
|
55
180
|
pixelmuse history Recent generations
|
|
@@ -68,6 +193,7 @@ var cli = meow(
|
|
|
68
193
|
--no-preview Skip image preview
|
|
69
194
|
--open Open result in system viewer
|
|
70
195
|
--watch <file> Watch a prompt file and regenerate on save
|
|
196
|
+
--public Make image public (default: private)
|
|
71
197
|
--no-save Don't save image to disk
|
|
72
198
|
--clipboard Copy image to clipboard
|
|
73
199
|
-h, --help Show this help
|
|
@@ -90,6 +216,7 @@ var cli = meow(
|
|
|
90
216
|
preview: { type: "boolean", default: true },
|
|
91
217
|
open: { type: "boolean", default: false },
|
|
92
218
|
watch: { type: "string" },
|
|
219
|
+
public: { type: "boolean", default: false },
|
|
93
220
|
save: { type: "boolean", default: true },
|
|
94
221
|
clipboard: { type: "boolean", default: false },
|
|
95
222
|
var: { type: "string", isMultiple: true }
|
|
@@ -132,6 +259,7 @@ async function handleGenerate(prompt) {
|
|
|
132
259
|
const model = cli.flags.model ?? settings.defaultModel;
|
|
133
260
|
const aspectRatio = cli.flags.aspectRatio ?? settings.defaultAspectRatio;
|
|
134
261
|
const style = cli.flags.style ?? settings.defaultStyle;
|
|
262
|
+
const visibility = cli.flags.public ? "public" : settings.defaultVisibility;
|
|
135
263
|
let balance = null;
|
|
136
264
|
let creditCost = null;
|
|
137
265
|
try {
|
|
@@ -150,6 +278,7 @@ async function handleGenerate(prompt) {
|
|
|
150
278
|
model,
|
|
151
279
|
aspectRatio,
|
|
152
280
|
style,
|
|
281
|
+
visibility,
|
|
153
282
|
output: cli.flags.output,
|
|
154
283
|
noSave: !cli.flags.save
|
|
155
284
|
});
|
|
@@ -160,6 +289,7 @@ async function handleGenerate(prompt) {
|
|
|
160
289
|
model: result.generation.model,
|
|
161
290
|
prompt: result.generation.prompt,
|
|
162
291
|
credits_charged: result.generation.credits_charged,
|
|
292
|
+
visibility: result.generation.visibility,
|
|
163
293
|
elapsed_seconds: Math.round(result.elapsed * 10) / 10,
|
|
164
294
|
output_path: result.imagePath
|
|
165
295
|
})
|
|
@@ -179,6 +309,7 @@ async function handleGenerate(prompt) {
|
|
|
179
309
|
model,
|
|
180
310
|
aspectRatio,
|
|
181
311
|
style,
|
|
312
|
+
visibility,
|
|
182
313
|
output: cli.flags.output,
|
|
183
314
|
noSave: !cli.flags.save,
|
|
184
315
|
onProgress: (elapsed) => {
|
|
@@ -188,8 +319,9 @@ async function handleGenerate(prompt) {
|
|
|
188
319
|
const charged = result.generation.credits_charged;
|
|
189
320
|
const remaining = balance !== null ? balance - charged : null;
|
|
190
321
|
const remainStr = remaining !== null ? ` (remaining: ${remaining})` : "";
|
|
322
|
+
const visLabel = result.generation.visibility === "public" ? chalk.green("public") : chalk.gray("private");
|
|
191
323
|
spinner.succeed(
|
|
192
|
-
`Generated in ${result.elapsed.toFixed(1)}s \xB7 ${charged} credit${charged > 1 ? "s" : ""} charged${remainStr}`
|
|
324
|
+
`Generated in ${result.elapsed.toFixed(1)}s \xB7 ${charged} credit${charged > 1 ? "s" : ""} charged${remainStr} \xB7 ${visLabel}`
|
|
193
325
|
);
|
|
194
326
|
if (result.imagePath) {
|
|
195
327
|
console.log(chalk.gray(` Saved to ${result.imagePath}`));
|
|
@@ -220,7 +352,7 @@ async function handleWatch(filePath) {
|
|
|
220
352
|
const generate = async () => {
|
|
221
353
|
if (generating) return;
|
|
222
354
|
generating = true;
|
|
223
|
-
const prompt =
|
|
355
|
+
const prompt = readFileSync2(absPath, "utf-8").trim();
|
|
224
356
|
if (!prompt) {
|
|
225
357
|
generating = false;
|
|
226
358
|
return;
|
|
@@ -228,6 +360,7 @@ async function handleWatch(filePath) {
|
|
|
228
360
|
const model = cli.flags.model ?? settings.defaultModel;
|
|
229
361
|
const aspectRatio = cli.flags.aspectRatio ?? settings.defaultAspectRatio;
|
|
230
362
|
const style = cli.flags.style ?? settings.defaultStyle;
|
|
363
|
+
const watchVisibility = cli.flags.public ? "public" : settings.defaultVisibility;
|
|
231
364
|
const time = (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false });
|
|
232
365
|
const spinner = ora(`[${time}] Generating... (${model})`).start();
|
|
233
366
|
try {
|
|
@@ -236,6 +369,7 @@ async function handleWatch(filePath) {
|
|
|
236
369
|
model,
|
|
237
370
|
aspectRatio,
|
|
238
371
|
style,
|
|
372
|
+
visibility: watchVisibility,
|
|
239
373
|
output: cli.flags.output
|
|
240
374
|
});
|
|
241
375
|
spinner.succeed(`[${time}] Saved to ${result.imagePath} (${result.elapsed.toFixed(1)}s)`);
|
|
@@ -315,8 +449,9 @@ async function handleHistory() {
|
|
|
315
449
|
const prompt = gen.prompt.length > 30 ? gen.prompt.slice(0, 30) + "..." : gen.prompt;
|
|
316
450
|
const date = timeAgo(new Date(gen.created_at));
|
|
317
451
|
const status = gen.status === "succeeded" ? chalk.green("\u25CF") : gen.status === "failed" ? chalk.red("\u2717") : chalk.yellow("\u25CC");
|
|
452
|
+
const vis = gen.visibility === "private" ? chalk.gray(" \u{1F512}") : "";
|
|
318
453
|
console.log(
|
|
319
|
-
chalk.gray(id.padEnd(idW)) + `${status} ${gen.model}`.padEnd(modelW + status.length - 1) + prompt.padEnd(promptW) + chalk.gray(date)
|
|
454
|
+
chalk.gray(id.padEnd(idW)) + `${status} ${gen.model}${vis}`.padEnd(modelW + status.length - 1 + vis.length) + prompt.padEnd(promptW) + chalk.gray(date)
|
|
320
455
|
);
|
|
321
456
|
}
|
|
322
457
|
} catch (err) {
|
|
@@ -331,11 +466,11 @@ async function handleOpen(id) {
|
|
|
331
466
|
const gen = await client.getGeneration(id);
|
|
332
467
|
spinner.stop();
|
|
333
468
|
if (gen.output?.[0]) {
|
|
334
|
-
const { imageToBuffer: imageToBuffer2, saveImage: saveImage2 } = await import("./image-
|
|
335
|
-
const { PATHS: PATHS2 } = await import("./config-
|
|
336
|
-
const { join } = await import("path");
|
|
469
|
+
const { imageToBuffer: imageToBuffer2, saveImage: saveImage2 } = await import("./image-GQSNVWNN.js");
|
|
470
|
+
const { PATHS: PATHS2 } = await import("./config-Z76UJLDX.js");
|
|
471
|
+
const { join: join2 } = await import("path");
|
|
337
472
|
const buf = await imageToBuffer2(gen.output[0]);
|
|
338
|
-
const path =
|
|
473
|
+
const path = join2(PATHS2.generations, `${gen.id}.png`);
|
|
339
474
|
saveImage2(buf, path);
|
|
340
475
|
const { default: open } = await import("open");
|
|
341
476
|
await open(path);
|
|
@@ -349,10 +484,53 @@ async function handleOpen(id) {
|
|
|
349
484
|
}
|
|
350
485
|
}
|
|
351
486
|
async function handleLogin() {
|
|
487
|
+
console.log(chalk.bold("Pixelmuse Login"));
|
|
488
|
+
console.log();
|
|
489
|
+
const spinner = ora("Preparing login...").start();
|
|
490
|
+
try {
|
|
491
|
+
const init = await initiateDeviceAuth();
|
|
492
|
+
spinner.stop();
|
|
493
|
+
console.log(` Your code: ${chalk.bold.cyan(init.userCode)}`);
|
|
494
|
+
console.log();
|
|
495
|
+
console.log(` Opening ${chalk.underline(init.verificationUriComplete)}`);
|
|
496
|
+
console.log(` ${chalk.gray("Or visit")} ${chalk.underline(init.verificationUri)} ${chalk.gray("and enter the code manually")}`);
|
|
497
|
+
console.log();
|
|
498
|
+
const { default: open } = await import("open");
|
|
499
|
+
await open(init.verificationUriComplete);
|
|
500
|
+
const pollSpinner = ora("Waiting for authorization...").start();
|
|
501
|
+
let dots = 0;
|
|
502
|
+
const result = await pollForToken(init.deviceCode, {
|
|
503
|
+
interval: init.interval,
|
|
504
|
+
expiresIn: init.expiresIn,
|
|
505
|
+
onPoll: () => {
|
|
506
|
+
dots = (dots + 1) % 4;
|
|
507
|
+
pollSpinner.text = `Waiting for authorization${".".repeat(dots)}`;
|
|
508
|
+
}
|
|
509
|
+
});
|
|
510
|
+
await saveApiKey(result.apiKey);
|
|
511
|
+
pollSpinner.stop();
|
|
512
|
+
const client = new PixelmuseClient(result.apiKey);
|
|
513
|
+
const account = await client.getAccount();
|
|
514
|
+
console.log(chalk.green("\u2713") + ` Authenticated as ${chalk.bold(account.email)} (${chalk.green(`${account.credits.total} credits`)})`);
|
|
515
|
+
return result.apiKey;
|
|
516
|
+
} catch (err) {
|
|
517
|
+
spinner.stop();
|
|
518
|
+
if (err instanceof Error && !err.message.includes("timed out") && !err.message.includes("denied")) {
|
|
519
|
+
console.log(chalk.gray("Browser login unavailable. Enter your API key manually."));
|
|
520
|
+
} else if (err instanceof Error) {
|
|
521
|
+
console.log(chalk.yellow(err.message));
|
|
522
|
+
}
|
|
523
|
+
return handleManualLogin();
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
async function handleManualLogin() {
|
|
527
|
+
console.log();
|
|
528
|
+
console.log(` Get your key at: ${chalk.underline("https://www.pixelmuse.studio/settings/api-keys")}`);
|
|
529
|
+
console.log();
|
|
352
530
|
const { createInterface } = await import("readline");
|
|
353
531
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
354
532
|
const key = await new Promise((resolve2) => {
|
|
355
|
-
rl.question("API key
|
|
533
|
+
rl.question("API key: ", (answer) => {
|
|
356
534
|
rl.close();
|
|
357
535
|
resolve2(answer.trim());
|
|
358
536
|
});
|
|
@@ -367,11 +545,103 @@ async function handleLogin() {
|
|
|
367
545
|
const account = await client.getAccount();
|
|
368
546
|
await saveApiKey(key);
|
|
369
547
|
spinner.succeed(`Authenticated as ${chalk.bold(account.email)} (${chalk.green(`${account.credits.total} credits`)})`);
|
|
548
|
+
return key;
|
|
370
549
|
} catch (err) {
|
|
371
550
|
spinner.fail(err instanceof Error ? err.message : "Authentication failed");
|
|
372
551
|
process.exit(1);
|
|
373
552
|
}
|
|
374
553
|
}
|
|
554
|
+
async function handleSetup() {
|
|
555
|
+
console.log();
|
|
556
|
+
console.log(chalk.bold(" Pixelmuse Setup"));
|
|
557
|
+
console.log(chalk.gray(" AI image generation from the terminal"));
|
|
558
|
+
console.log();
|
|
559
|
+
const existingKey = await getApiKey();
|
|
560
|
+
let apiKey;
|
|
561
|
+
if (existingKey) {
|
|
562
|
+
const spinner = ora("Checking existing credentials...").start();
|
|
563
|
+
try {
|
|
564
|
+
const client = new PixelmuseClient(existingKey);
|
|
565
|
+
const account = await client.getAccount();
|
|
566
|
+
spinner.succeed(`Already authenticated as ${chalk.bold(account.email)} (${chalk.green(`${account.credits.total} credits`)})`);
|
|
567
|
+
apiKey = existingKey;
|
|
568
|
+
} catch {
|
|
569
|
+
spinner.warn("Existing credentials invalid. Re-authenticating...");
|
|
570
|
+
apiKey = await handleLogin();
|
|
571
|
+
}
|
|
572
|
+
} else {
|
|
573
|
+
console.log(chalk.bold("Step 1:") + " Authenticate");
|
|
574
|
+
console.log();
|
|
575
|
+
apiKey = await handleLogin();
|
|
576
|
+
}
|
|
577
|
+
console.log();
|
|
578
|
+
const editors = detectEditors();
|
|
579
|
+
const available = editors.filter((e) => e.installed);
|
|
580
|
+
if (available.length > 0) {
|
|
581
|
+
console.log(chalk.bold("Step 2:") + " MCP Server (AI agent integration)");
|
|
582
|
+
console.log();
|
|
583
|
+
const { createInterface } = await import("readline");
|
|
584
|
+
for (const editor of available) {
|
|
585
|
+
const rl2 = createInterface({ input: process.stdin, output: process.stdout });
|
|
586
|
+
const answer = await new Promise((resolve2) => {
|
|
587
|
+
rl2.question(` Configure ${chalk.bold(editor.name)}? ${chalk.gray("[Y/n]")} `, (a) => {
|
|
588
|
+
rl2.close();
|
|
589
|
+
resolve2(a.trim().toLowerCase());
|
|
590
|
+
});
|
|
591
|
+
});
|
|
592
|
+
if (answer === "" || answer === "y" || answer === "yes") {
|
|
593
|
+
configureMcp(editor, apiKey);
|
|
594
|
+
console.log(chalk.green(" \u2713") + ` ${editor.name} MCP configured`);
|
|
595
|
+
} else {
|
|
596
|
+
console.log(chalk.gray(` Skipped ${editor.name}`));
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
console.log();
|
|
600
|
+
}
|
|
601
|
+
console.log(chalk.bold(available.length > 0 ? "Step 3:" : "Step 2:") + " Defaults");
|
|
602
|
+
console.log();
|
|
603
|
+
const { createInterface: createRl } = await import("readline");
|
|
604
|
+
const rl = createRl({ input: process.stdin, output: process.stdout });
|
|
605
|
+
const customize = await new Promise((resolve2) => {
|
|
606
|
+
rl.question(` Customize default model and settings? ${chalk.gray("[y/N]")} `, (a) => {
|
|
607
|
+
rl.close();
|
|
608
|
+
resolve2(a.trim().toLowerCase());
|
|
609
|
+
});
|
|
610
|
+
});
|
|
611
|
+
if (customize === "y" || customize === "yes") {
|
|
612
|
+
const settings = readSettings();
|
|
613
|
+
const updated = {};
|
|
614
|
+
const models = ["nano-banana-2", "nano-banana-pro", "flux-schnell", "imagen-3", "recraft-v4", "recraft-v4-pro"];
|
|
615
|
+
const aspects = ["1:1", "16:9", "9:16", "4:3", "3:4", "21:9"];
|
|
616
|
+
const styles = ["none", "realistic", "anime", "artistic"];
|
|
617
|
+
const pick = async (label, options, current) => {
|
|
618
|
+
const { createInterface } = await import("readline");
|
|
619
|
+
const rl2 = createInterface({ input: process.stdin, output: process.stdout });
|
|
620
|
+
console.log(` ${label}: ${options.map((o) => o === current ? chalk.bold.cyan(o) : chalk.gray(o)).join(" | ")}`);
|
|
621
|
+
const answer = await new Promise((resolve2) => {
|
|
622
|
+
rl2.question(` Choice ${chalk.gray(`[${current}]`)}: `, (a) => {
|
|
623
|
+
rl2.close();
|
|
624
|
+
resolve2(a.trim() || current);
|
|
625
|
+
});
|
|
626
|
+
});
|
|
627
|
+
return options.includes(answer) ? answer : current;
|
|
628
|
+
};
|
|
629
|
+
updated.defaultModel = await pick("Model", models, settings.defaultModel);
|
|
630
|
+
updated.defaultAspectRatio = await pick("Aspect ratio", aspects, settings.defaultAspectRatio);
|
|
631
|
+
updated.defaultStyle = await pick("Style", styles, settings.defaultStyle);
|
|
632
|
+
writeSettings({ ...settings, ...updated });
|
|
633
|
+
console.log(chalk.green(" \u2713") + " Defaults saved");
|
|
634
|
+
} else {
|
|
635
|
+
console.log(chalk.gray(" Using defaults (nano-banana-2, 1:1, no style)"));
|
|
636
|
+
}
|
|
637
|
+
console.log();
|
|
638
|
+
console.log(chalk.green.bold(" Setup complete!"));
|
|
639
|
+
console.log();
|
|
640
|
+
console.log(` Try it: ${chalk.cyan('pixelmuse "a cat floating through space"')}`);
|
|
641
|
+
console.log(` TUI: ${chalk.cyan("pixelmuse ui")}`);
|
|
642
|
+
console.log(` Help: ${chalk.cyan("pixelmuse --help")}`);
|
|
643
|
+
console.log();
|
|
644
|
+
}
|
|
375
645
|
async function handleTemplate() {
|
|
376
646
|
const [, templateCmd, ...templateArgs] = cli.input;
|
|
377
647
|
switch (templateCmd) {
|
|
@@ -433,7 +703,7 @@ ${chalk.bold("Prompt:")} ${template.prompt}`);
|
|
|
433
703
|
tags: []
|
|
434
704
|
};
|
|
435
705
|
saveTemplate(template);
|
|
436
|
-
const { PATHS: PATHS2 } = await import("./config-
|
|
706
|
+
const { PATHS: PATHS2 } = await import("./config-Z76UJLDX.js");
|
|
437
707
|
const path = `${PATHS2.prompts}/${slugify(name)}.yaml`;
|
|
438
708
|
console.log(`Created template: ${chalk.bold(path)}`);
|
|
439
709
|
console.log(chalk.gray("Edit the YAML file to customize your template."));
|
|
@@ -490,8 +760,11 @@ async function main() {
|
|
|
490
760
|
process.exit(1);
|
|
491
761
|
}
|
|
492
762
|
return handleOpen(rest[0]);
|
|
763
|
+
case "setup":
|
|
764
|
+
return handleSetup();
|
|
493
765
|
case "login":
|
|
494
|
-
|
|
766
|
+
await handleLogin();
|
|
767
|
+
return;
|
|
495
768
|
case "logout":
|
|
496
769
|
await deleteApiKey();
|
|
497
770
|
console.log("Logged out.");
|
package/dist/mcp/server.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
generateImage
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-STJDWR4Q.js";
|
|
5
5
|
import {
|
|
6
6
|
CLI_VERSION,
|
|
7
7
|
PixelmuseClient,
|
|
8
8
|
getApiKey
|
|
9
|
-
} from "../chunk-
|
|
10
|
-
import "../chunk-
|
|
9
|
+
} from "../chunk-Q5SB7WKR.js";
|
|
10
|
+
import "../chunk-M4N6UFDD.js";
|
|
11
11
|
import {
|
|
12
12
|
readSettings
|
|
13
|
-
} from "../chunk-
|
|
13
|
+
} from "../chunk-PSHBBAJV.js";
|
|
14
14
|
|
|
15
15
|
// src/mcp/server.ts
|
|
16
16
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -43,7 +43,8 @@ server.tool(
|
|
|
43
43
|
model: z.enum(MODELS).optional().default("nano-banana-2").describe("Model to use"),
|
|
44
44
|
aspect_ratio: z.enum(ASPECT_RATIOS).optional().default("1:1").describe("Image aspect ratio"),
|
|
45
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)")
|
|
46
|
+
output_path: z.string().optional().describe("Where to save the image (default: current directory)"),
|
|
47
|
+
visibility: z.enum(["public", "private"]).optional().describe("Image visibility (default: private)")
|
|
47
48
|
},
|
|
48
49
|
async (args) => {
|
|
49
50
|
const client = await getClient();
|
|
@@ -53,6 +54,7 @@ server.tool(
|
|
|
53
54
|
model: args.model ?? settings.defaultModel,
|
|
54
55
|
aspectRatio: args.aspect_ratio ?? settings.defaultAspectRatio,
|
|
55
56
|
style: args.style ?? settings.defaultStyle,
|
|
57
|
+
visibility: args.visibility ?? settings.defaultVisibility,
|
|
56
58
|
output: args.output_path
|
|
57
59
|
});
|
|
58
60
|
return {
|
|
@@ -65,6 +67,7 @@ server.tool(
|
|
|
65
67
|
model: result.generation.model,
|
|
66
68
|
prompt: result.generation.prompt,
|
|
67
69
|
credits_charged: result.generation.credits_charged,
|
|
70
|
+
visibility: result.generation.visibility,
|
|
68
71
|
elapsed_seconds: Math.round(result.elapsed * 10) / 10,
|
|
69
72
|
output_path: result.imagePath
|
|
70
73
|
}, null, 2)
|
package/dist/tui.js
CHANGED
|
@@ -11,17 +11,17 @@ import {
|
|
|
11
11
|
pollGeneration,
|
|
12
12
|
saveApiKey,
|
|
13
13
|
saveTemplate
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-Q5SB7WKR.js";
|
|
15
15
|
import {
|
|
16
16
|
autoSave,
|
|
17
17
|
hasChafa,
|
|
18
18
|
imageToBuffer,
|
|
19
19
|
renderImageDirect
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-M4N6UFDD.js";
|
|
21
21
|
import {
|
|
22
22
|
readSettings,
|
|
23
23
|
writeSettings
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-PSHBBAJV.js";
|
|
25
25
|
|
|
26
26
|
// src/tui.tsx
|
|
27
27
|
import { render } from "ink";
|
|
@@ -330,7 +330,8 @@ function Generate({
|
|
|
330
330
|
initialStyle,
|
|
331
331
|
defaultModel = "nano-banana-2",
|
|
332
332
|
defaultAspectRatio = "1:1",
|
|
333
|
-
defaultStyle = "none"
|
|
333
|
+
defaultStyle = "none",
|
|
334
|
+
defaultVisibility = "private"
|
|
334
335
|
}) {
|
|
335
336
|
const { exit } = useApp();
|
|
336
337
|
const [step, setStep] = useState4(initialPrompt ? "model" : "prompt");
|
|
@@ -367,7 +368,8 @@ function Generate({
|
|
|
367
368
|
prompt,
|
|
368
369
|
model,
|
|
369
370
|
aspect_ratio: aspectRatio,
|
|
370
|
-
style: style === "none" ? void 0 : style
|
|
371
|
+
style: style === "none" ? void 0 : style,
|
|
372
|
+
visibility: defaultVisibility
|
|
371
373
|
});
|
|
372
374
|
if (gen.status === "processing" || gen.status === "pending") {
|
|
373
375
|
gen = await pollGeneration(client, gen.id, {
|
|
@@ -523,7 +525,8 @@ function ImageGrid({ generations, columns = 3, onSelect }) {
|
|
|
523
525
|
/* @__PURE__ */ jsxs7(Text7, { color: gen.status === "succeeded" ? "green" : gen.status === "failed" ? "red" : "yellow", children: [
|
|
524
526
|
gen.status === "succeeded" ? "\u25CF" : gen.status === "failed" ? "\u2717" : "\u25CC",
|
|
525
527
|
" ",
|
|
526
|
-
gen.model
|
|
528
|
+
gen.model,
|
|
529
|
+
gen.visibility === "private" ? " \u{1F512}" : ""
|
|
527
530
|
] }),
|
|
528
531
|
/* @__PURE__ */ jsx7(Text7, { color: "gray", wrap: "truncate", children: gen.prompt.slice(0, cellWidth - 4) }),
|
|
529
532
|
/* @__PURE__ */ jsx7(Text7, { color: "gray", dimColor: true, children: new Date(gen.created_at).toLocaleDateString() })
|
|
@@ -1154,7 +1157,8 @@ function App({ initialRoute }) {
|
|
|
1154
1157
|
initialStyle: route.style,
|
|
1155
1158
|
defaultModel: settings.defaultModel,
|
|
1156
1159
|
defaultAspectRatio: settings.defaultAspectRatio,
|
|
1157
|
-
defaultStyle: settings.defaultStyle
|
|
1160
|
+
defaultStyle: settings.defaultStyle,
|
|
1161
|
+
defaultVisibility: settings.defaultVisibility
|
|
1158
1162
|
}
|
|
1159
1163
|
);
|
|
1160
1164
|
case "gallery":
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pixelmuse",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "AI image generation from the terminal — CLI, TUI, and MCP server for text-to-image with Flux, Imagen, Recraft, and more",
|
|
5
5
|
"homepage": "https://pixelmuse.studio",
|
|
6
6
|
"repository": {
|
|
@@ -19,17 +19,6 @@
|
|
|
19
19
|
"files": [
|
|
20
20
|
"dist"
|
|
21
21
|
],
|
|
22
|
-
"scripts": {
|
|
23
|
-
"build": "tsup",
|
|
24
|
-
"dev": "tsup --watch",
|
|
25
|
-
"start": "node dist/cli.js",
|
|
26
|
-
"typecheck": "tsc --noEmit",
|
|
27
|
-
"lint": "eslint src/",
|
|
28
|
-
"lint:fix": "eslint src/ --fix",
|
|
29
|
-
"test": "vitest run",
|
|
30
|
-
"test:watch": "vitest",
|
|
31
|
-
"ci": "pnpm lint && pnpm typecheck && pnpm build && pnpm test"
|
|
32
|
-
},
|
|
33
22
|
"keywords": [
|
|
34
23
|
"cli",
|
|
35
24
|
"image-generation",
|
|
@@ -83,12 +72,15 @@
|
|
|
83
72
|
"engines": {
|
|
84
73
|
"node": ">=20"
|
|
85
74
|
},
|
|
86
|
-
"
|
|
87
|
-
"
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
"
|
|
91
|
-
|
|
92
|
-
|
|
75
|
+
"scripts": {
|
|
76
|
+
"build": "tsup",
|
|
77
|
+
"dev": "tsup --watch",
|
|
78
|
+
"start": "node dist/cli.js",
|
|
79
|
+
"typecheck": "tsc --noEmit",
|
|
80
|
+
"lint": "eslint src/",
|
|
81
|
+
"lint:fix": "eslint src/ --fix",
|
|
82
|
+
"test": "vitest run",
|
|
83
|
+
"test:watch": "vitest",
|
|
84
|
+
"ci": "pnpm lint && pnpm typecheck && pnpm build && pnpm test"
|
|
93
85
|
}
|
|
94
|
-
}
|
|
86
|
+
}
|