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 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 login
39
- pixelmuse "a cat floating through space"
36
+ pixelmuse setup
40
37
  ```
41
38
 
42
- Sign up at [pixelmuse.studio/sign-up](https://www.pixelmuse.studio/sign-up) new accounts include **15 free credits**.
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 |
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  PATHS
4
- } from "./chunk-7ARYEFAH.js";
4
+ } from "./chunk-PSHBBAJV.js";
5
5
 
6
6
  // src/core/image.ts
7
7
  import { execSync } from "child_process";
@@ -18,6 +18,7 @@ var DEFAULT_SETTINGS = {
18
18
  defaultModel: "nano-banana-2",
19
19
  defaultAspectRatio: "1:1",
20
20
  defaultStyle: "none",
21
+ defaultVisibility: "private",
21
22
  autoPreview: true,
22
23
  autoSave: true
23
24
  };
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  PATHS,
4
4
  ensureDirs
5
- } from "./chunk-7ARYEFAH.js";
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.2.1";
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-3BR2BNXV.js";
5
+ } from "./chunk-Q5SB7WKR.js";
6
6
  import {
7
7
  autoSave,
8
8
  imageToBuffer,
9
9
  saveImage
10
- } from "./chunk-MZZY4JXW.js";
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-LOW6S5UO.js";
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-3BR2BNXV.js";
18
+ } from "./chunk-Q5SB7WKR.js";
18
19
  import {
19
20
  renderImageDirect
20
- } from "./chunk-MZZY4JXW.js";
21
+ } from "./chunk-M4N6UFDD.js";
21
22
  import {
22
- readSettings
23
- } from "./chunk-7ARYEFAH.js";
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 = readFileSync(absPath, "utf-8").trim();
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-2H3GUQI6.js");
335
- const { PATHS: PATHS2 } = await import("./config-RMVFR3GN.js");
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 = join(PATHS2.generations, `${gen.id}.png`);
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 (from pixelmuse.studio/settings/api-keys): ", (answer) => {
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-RMVFR3GN.js");
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
- return handleLogin();
766
+ await handleLogin();
767
+ return;
495
768
  case "logout":
496
769
  await deleteApiKey();
497
770
  console.log("Logged out.");
@@ -4,7 +4,7 @@ import {
4
4
  ensureDirs,
5
5
  readSettings,
6
6
  writeSettings
7
- } from "./chunk-7ARYEFAH.js";
7
+ } from "./chunk-PSHBBAJV.js";
8
8
  export {
9
9
  PATHS,
10
10
  ensureDirs,
@@ -5,8 +5,8 @@ import {
5
5
  imageToBuffer,
6
6
  renderImageDirect,
7
7
  saveImage
8
- } from "./chunk-MZZY4JXW.js";
9
- import "./chunk-7ARYEFAH.js";
8
+ } from "./chunk-M4N6UFDD.js";
9
+ import "./chunk-PSHBBAJV.js";
10
10
  export {
11
11
  autoSave,
12
12
  hasChafa,
@@ -1,16 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  generateImage
4
- } from "../chunk-LOW6S5UO.js";
4
+ } from "../chunk-STJDWR4Q.js";
5
5
  import {
6
6
  CLI_VERSION,
7
7
  PixelmuseClient,
8
8
  getApiKey
9
- } from "../chunk-3BR2BNXV.js";
10
- import "../chunk-MZZY4JXW.js";
9
+ } from "../chunk-Q5SB7WKR.js";
10
+ import "../chunk-M4N6UFDD.js";
11
11
  import {
12
12
  readSettings
13
- } from "../chunk-7ARYEFAH.js";
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-3BR2BNXV.js";
14
+ } from "./chunk-Q5SB7WKR.js";
15
15
  import {
16
16
  autoSave,
17
17
  hasChafa,
18
18
  imageToBuffer,
19
19
  renderImageDirect
20
- } from "./chunk-MZZY4JXW.js";
20
+ } from "./chunk-M4N6UFDD.js";
21
21
  import {
22
22
  readSettings,
23
23
  writeSettings
24
- } from "./chunk-7ARYEFAH.js";
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.2.1",
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
- "pnpm": {
87
- "onlyBuiltDependencies": [
88
- "esbuild"
89
- ],
90
- "overrides": {
91
- "phin@<3.7.1": ">=3.7.1"
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
+ }