climage 0.1.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 ADDED
@@ -0,0 +1,81 @@
1
+ # climage
2
+
3
+ Generate images from the terminal via multiple providers.
4
+
5
+ ## Install / run
6
+
7
+ ```bash
8
+ npx climage "make image of kitten"
9
+ ```
10
+
11
+ ## Providers
12
+
13
+ ### xAI (grok-imagine-image)
14
+
15
+ Set one of:
16
+
17
+ - `XAI_API_KEY` (preferred)
18
+ - `XAI_TOKEN`
19
+ - `GROK_API_KEY`
20
+
21
+ Example:
22
+
23
+ ```bash
24
+ XAI_API_KEY=... npx climage "A cat in a tree" --provider xai
25
+ ```
26
+
27
+ ### Google (Imagen)
28
+
29
+ Set one of:
30
+
31
+ - `GEMINI_API_KEY` (preferred)
32
+ - `GOOGLE_API_KEY`
33
+
34
+ Example:
35
+
36
+ ```bash
37
+ GEMINI_API_KEY=... npx climage "A cat in a tree" --provider google --model imagen-4.0-generate-001
38
+ ```
39
+
40
+ ### fal.ai
41
+
42
+ Set one of:
43
+
44
+ - `FAL_KEY` (preferred by fal docs)
45
+ - `FAL_API_KEY` (also common)
46
+
47
+ Example:
48
+
49
+ ```bash
50
+ FAL_KEY=... npx climage "A cat in a tree" --provider fal
51
+ ```
52
+
53
+ ## Options
54
+
55
+ - `--provider auto|xai|fal|google`
56
+ - `--model <id>`
57
+ - `--n <1..10>`
58
+ - `--format png|jpg|webp`
59
+ - `--out <path>` (only for single image)
60
+ - `--outDir <dir>` (default: current directory)
61
+ - `--name <text>` base name override
62
+ - `--aspect-ratio <w:h>` (xAI supports e.g. `4:3`)
63
+ - `--json`
64
+
65
+ ## Library API
66
+
67
+ ```ts
68
+ import { generateImage } from 'climage';
69
+
70
+ const images = await generateImage('make image of kitten', {
71
+ provider: 'xai',
72
+ n: 2,
73
+ });
74
+
75
+ console.log(images.map((i) => i.filePath));
76
+ ```
77
+
78
+ ## Notes
79
+
80
+ - v0.1 ships xAI provider first.
81
+ - `google` and `fal` providers are planned next.
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/cli.js ADDED
@@ -0,0 +1,396 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import process3 from "process";
5
+
6
+ // src/core/router.ts
7
+ import path3 from "path";
8
+
9
+ // src/core/env.ts
10
+ import fs from "fs";
11
+ import path from "path";
12
+ import process2 from "process";
13
+ import dotenv from "dotenv";
14
+ function loadEnv(cwd = process2.cwd()) {
15
+ const candidates = [".env", ".env.local"];
16
+ const loadedFiles = [];
17
+ for (const name of candidates) {
18
+ const p = path.join(cwd, name);
19
+ if (!fs.existsSync(p)) continue;
20
+ dotenv.config({ path: p, override: false });
21
+ loadedFiles.push(p);
22
+ }
23
+ return { env: process2.env, loadedFiles };
24
+ }
25
+
26
+ // src/core/output.ts
27
+ import fs2 from "fs/promises";
28
+ import path2 from "path";
29
+ function extensionForFormat(format) {
30
+ switch (format) {
31
+ case "jpg":
32
+ return "jpg";
33
+ case "png":
34
+ return "png";
35
+ case "webp":
36
+ return "webp";
37
+ }
38
+ }
39
+ function resolveOutDir(outDir) {
40
+ return path2.isAbsolute(outDir) ? outDir : path2.resolve(process.cwd(), outDir);
41
+ }
42
+ function makeOutputPath(req, index) {
43
+ const ext = extensionForFormat(req.format);
44
+ if (req.out) return path2.resolve(process.cwd(), req.out);
45
+ const base = `${req.nameBase}-${req.timestamp}`;
46
+ const suffix = req.n > 1 ? `-${String(index + 1).padStart(2, "0")}` : "";
47
+ const filename = `${base}${suffix}.${ext}`;
48
+ return path2.join(req.outDir, filename);
49
+ }
50
+ async function writeImageFile(filePath, bytes) {
51
+ await fs2.mkdir(path2.dirname(filePath), { recursive: true });
52
+ await fs2.writeFile(filePath, bytes);
53
+ }
54
+
55
+ // src/core/strings.ts
56
+ function slugify(input, maxLen = 60) {
57
+ const s = input.trim().toLowerCase().replace(/['"`]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
58
+ if (!s) return "image";
59
+ return s.length > maxLen ? s.slice(0, maxLen).replace(/-+$/g, "") : s;
60
+ }
61
+ function timestampLocalCompact(d = /* @__PURE__ */ new Date()) {
62
+ const pad = (n) => String(n).padStart(2, "0");
63
+ return d.getFullYear() + pad(d.getMonth() + 1) + pad(d.getDate()) + "-" + pad(d.getHours()) + pad(d.getMinutes()) + pad(d.getSeconds());
64
+ }
65
+
66
+ // src/providers/xai.ts
67
+ var XAI_API_BASE = "https://api.x.ai/v1";
68
+ function getXaiApiKey(env) {
69
+ return env.XAI_API_KEY || env.XAI_TOKEN || env.GROK_API_KEY;
70
+ }
71
+ async function downloadBytes(url) {
72
+ const res = await fetch(url);
73
+ if (!res.ok) throw new Error(`xAI image download failed (${res.status})`);
74
+ const ab = await res.arrayBuffer();
75
+ const ct = res.headers.get("content-type") || void 0;
76
+ return { bytes: new Uint8Array(ab), mimeType: ct };
77
+ }
78
+ var xaiProvider = {
79
+ id: "xai",
80
+ displayName: "xAI (grok-imagine-image)",
81
+ isAvailable(env) {
82
+ return Boolean(getXaiApiKey(env));
83
+ },
84
+ async generate(req, env) {
85
+ const apiKey = getXaiApiKey(env);
86
+ if (!apiKey) throw new Error("Missing xAI API key. Set XAI_API_KEY (or XAI_TOKEN).");
87
+ const model = req.model ?? "grok-imagine-image";
88
+ const body = {
89
+ model,
90
+ prompt: req.prompt,
91
+ n: req.n,
92
+ // xAI docs: endpoint supports aspect_ratio
93
+ ...req.aspectRatio ? { aspect_ratio: req.aspectRatio } : {},
94
+ // Use URL format to download + save.
95
+ response_format: "url"
96
+ };
97
+ const res = await fetch(`${XAI_API_BASE}/images/generations`, {
98
+ method: "POST",
99
+ headers: {
100
+ authorization: `Bearer ${apiKey}`,
101
+ "content-type": "application/json"
102
+ },
103
+ body: JSON.stringify(body)
104
+ });
105
+ if (!res.ok) {
106
+ const txt = await res.text().catch(() => "");
107
+ throw new Error(`xAI generations failed (${res.status}): ${txt.slice(0, 500)}`);
108
+ }
109
+ const json = await res.json();
110
+ if (!json.data?.length) throw new Error("xAI returned no images");
111
+ const results = [];
112
+ for (let i = 0; i < json.data.length; i++) {
113
+ const img = json.data[i];
114
+ if (img.url) {
115
+ const { bytes, mimeType } = await downloadBytes(img.url);
116
+ results.push({ provider: "xai", model, index: i, url: img.url, bytes, mimeType });
117
+ continue;
118
+ }
119
+ if (img.b64_json) {
120
+ const bytes = Uint8Array.from(Buffer.from(img.b64_json, "base64"));
121
+ results.push({ provider: "xai", model, index: i, bytes });
122
+ continue;
123
+ }
124
+ throw new Error("xAI returned image without url or b64_json");
125
+ }
126
+ return results;
127
+ }
128
+ };
129
+
130
+ // src/providers/fal.ts
131
+ import { fal } from "@fal-ai/client";
132
+ function getFalKey(env) {
133
+ return env.FAL_API_KEY || env.FAL_KEY;
134
+ }
135
+ async function downloadBytes2(url) {
136
+ const res = await fetch(url);
137
+ if (!res.ok) throw new Error(`fal image download failed (${res.status})`);
138
+ const ab = await res.arrayBuffer();
139
+ const ct = res.headers.get("content-type") || void 0;
140
+ return { bytes: new Uint8Array(ab), mimeType: ct };
141
+ }
142
+ var falProvider = {
143
+ id: "fal",
144
+ displayName: "fal.ai",
145
+ isAvailable(env) {
146
+ return Boolean(getFalKey(env));
147
+ },
148
+ async generate(req, env) {
149
+ const key = getFalKey(env);
150
+ if (!key) throw new Error("Missing fal API key. Set FAL_KEY (or FAL_API_KEY).");
151
+ fal.config({ credentials: key });
152
+ const model = req.model ?? "fal-ai/flux/dev";
153
+ let image_size = void 0;
154
+ if (req.aspectRatio) {
155
+ const ar = req.aspectRatio.trim();
156
+ if (ar === "1:1") image_size = "square";
157
+ else if (ar === "4:3") image_size = "landscape_4_3";
158
+ else if (ar === "16:9") image_size = "landscape_16_9";
159
+ else if (ar === "3:4") image_size = "portrait_4_3";
160
+ else if (ar === "9:16") image_size = "portrait_16_9";
161
+ }
162
+ const input = {
163
+ prompt: req.prompt,
164
+ ...image_size ? { image_size } : {},
165
+ // Some fal models support "num_images"; flux/dev returns images array length.
166
+ ...req.n ? { num_images: req.n } : {}
167
+ };
168
+ const result = await fal.subscribe(model, { input });
169
+ const images = result?.data?.images;
170
+ if (!images?.length) throw new Error("fal returned no images");
171
+ const out = [];
172
+ for (let i = 0; i < Math.min(images.length, req.n); i++) {
173
+ const img = images[i];
174
+ if (!img?.url) continue;
175
+ const { bytes, mimeType } = await downloadBytes2(img.url);
176
+ out.push({ provider: "fal", model, index: i, url: img.url, bytes, mimeType: img.content_type ?? mimeType });
177
+ }
178
+ if (!out.length) throw new Error("fal returned images but none were downloadable");
179
+ return out;
180
+ }
181
+ };
182
+
183
+ // src/providers/google.ts
184
+ import { GoogleGenAI } from "@google/genai";
185
+ function getGeminiApiKey(env) {
186
+ return env.GEMINI_API_KEY || env.GOOGLE_API_KEY || env.GOOGLE_GENAI_API_KEY;
187
+ }
188
+ function mimeForFormat(format) {
189
+ switch (format) {
190
+ case "jpg":
191
+ return "image/jpeg";
192
+ case "webp":
193
+ return "image/webp";
194
+ case "png":
195
+ default:
196
+ return "image/png";
197
+ }
198
+ }
199
+ var googleProvider = {
200
+ id: "google",
201
+ displayName: "Google (Gemini / Imagen)",
202
+ isAvailable(env) {
203
+ return Boolean(getGeminiApiKey(env));
204
+ },
205
+ async generate(req, env) {
206
+ const apiKey = getGeminiApiKey(env);
207
+ if (!apiKey) throw new Error("Missing Google API key. Set GEMINI_API_KEY (or GOOGLE_API_KEY).");
208
+ const ai = new GoogleGenAI({ apiKey });
209
+ const model = req.model ?? "imagen-4.0-generate-001";
210
+ const res = await ai.models.generateImages({
211
+ model,
212
+ prompt: req.prompt,
213
+ config: {
214
+ numberOfImages: req.n,
215
+ outputMimeType: mimeForFormat(req.format)
216
+ // Note: aspect ratio / size varies by model. Add later.
217
+ }
218
+ });
219
+ const imgs = res.generatedImages;
220
+ if (!imgs?.length) throw new Error("Google generateImages returned no images");
221
+ const out = [];
222
+ for (let i = 0; i < Math.min(imgs.length, req.n); i++) {
223
+ const img = imgs[i];
224
+ const rawBytes = img?.image?.imageBytes;
225
+ if (!rawBytes) continue;
226
+ const bytes = typeof rawBytes === "string" ? Uint8Array.from(Buffer.from(rawBytes, "base64")) : rawBytes;
227
+ out.push({ provider: "google", model, index: i, bytes, mimeType: mimeForFormat(req.format) });
228
+ }
229
+ if (!out.length) throw new Error("Google returned images but no bytes were present");
230
+ return out;
231
+ }
232
+ };
233
+
234
+ // src/core/router.ts
235
+ var providers = [googleProvider, xaiProvider, falProvider];
236
+ function listProviders() {
237
+ return [...providers];
238
+ }
239
+ function pickProvider(id, env) {
240
+ if (id !== "auto") {
241
+ const p2 = providers.find((p3) => p3.id === id);
242
+ if (!p2) throw new Error(`Unknown provider: ${id}`);
243
+ if (!p2.isAvailable(env)) throw new Error(`Provider ${id} is not available (missing API key)`);
244
+ return p2;
245
+ }
246
+ const p = providers.find((pp) => pp.isAvailable(env));
247
+ if (!p) throw new Error("No providers available. Set XAI_API_KEY (or other provider keys) in .env or environment.");
248
+ return p;
249
+ }
250
+ function normalizeOptions(prompt, opts) {
251
+ const nRaw = opts.n ?? 1;
252
+ const n = Math.max(1, Math.min(10, Math.floor(nRaw)));
253
+ const format = opts.format ?? "png";
254
+ const outDir = resolveOutDir(opts.outDir ?? ".");
255
+ const timestamp = timestampLocalCompact();
256
+ const nameBase = slugify(opts.name ?? prompt);
257
+ return {
258
+ prompt,
259
+ provider: opts.provider ?? "auto",
260
+ model: opts.model ?? void 0,
261
+ n,
262
+ aspectRatio: opts.aspectRatio ?? void 0,
263
+ format,
264
+ outDir,
265
+ out: opts.out ? path3.resolve(process.cwd(), opts.out) : void 0,
266
+ nameBase,
267
+ timestamp,
268
+ verbose: Boolean(opts.verbose)
269
+ };
270
+ }
271
+ async function generateImage(prompt, opts = {}) {
272
+ const { env } = loadEnv(process.cwd());
273
+ const req = normalizeOptions(prompt, opts);
274
+ const provider = pickProvider(req.provider, env);
275
+ const partials = await provider.generate(req, env);
276
+ const images = [];
277
+ for (let i = 0; i < partials.length; i++) {
278
+ const p = partials[i];
279
+ if (!p) continue;
280
+ const filePath = makeOutputPath(req, i);
281
+ await writeImageFile(filePath, p.bytes);
282
+ images.push({ ...p, filePath });
283
+ }
284
+ return images;
285
+ }
286
+
287
+ // src/cli.ts
288
+ function usage(code = 0) {
289
+ const providers2 = listProviders().map((p) => `${p.id}`).join(", ");
290
+ console.log(`climage
291
+
292
+ Usage:
293
+ climage "prompt"
294
+
295
+ Options:
296
+ --provider <auto|${providers2}> Provider (default: auto)
297
+ --model <id> Model id (provider-specific)
298
+ --n <1..10> Number of images (default: 1)
299
+ --format <png|jpg|webp> Output format (default: png)
300
+ --out <path> Output file path (only when n=1)
301
+ --outDir <dir> Output directory (default: .)
302
+ --name <text> Base name (slugified); default: prompt
303
+ --aspect-ratio <w:h> Aspect ratio (xAI supports e.g. 4:3)
304
+ --json Print machine-readable JSON
305
+ --verbose Verbose logging
306
+ -h, --help Show help
307
+
308
+ Env:
309
+ GEMINI_API_KEY (or GOOGLE_API_KEY)
310
+ XAI_API_KEY (or XAI_TOKEN, GROK_API_KEY)
311
+ FAL_KEY (or FAL_API_KEY)
312
+
313
+ Examples:
314
+ npx climage "make image of kitten"
315
+ npx climage "A cat in a tree" --provider xai --n 4
316
+ `);
317
+ process3.exit(code);
318
+ }
319
+ function parseArgs(argv) {
320
+ const args = [...argv];
321
+ const opts = {};
322
+ let json = false;
323
+ const take = (name) => {
324
+ const v = args.shift();
325
+ if (!v) throw new Error(`Missing value for ${name}`);
326
+ return v;
327
+ };
328
+ while (args.length) {
329
+ const a = args[0];
330
+ if (!a) break;
331
+ if (a === "-h" || a === "--help") usage(0);
332
+ if (a === "--json") {
333
+ json = true;
334
+ args.shift();
335
+ continue;
336
+ }
337
+ if (!a.startsWith("-")) break;
338
+ args.shift();
339
+ switch (a) {
340
+ case "--provider":
341
+ opts.provider = take(a);
342
+ break;
343
+ case "--model":
344
+ opts.model = take(a);
345
+ break;
346
+ case "--n":
347
+ opts.n = Number(take(a));
348
+ break;
349
+ case "--format":
350
+ opts.format = take(a);
351
+ break;
352
+ case "--out":
353
+ opts.out = take(a);
354
+ break;
355
+ case "--outDir":
356
+ opts.outDir = take(a);
357
+ break;
358
+ case "--name":
359
+ opts.name = take(a);
360
+ break;
361
+ case "--aspect-ratio":
362
+ opts.aspectRatio = take(a);
363
+ break;
364
+ case "--verbose":
365
+ opts.verbose = true;
366
+ break;
367
+ default:
368
+ throw new Error(`Unknown option: ${a}`);
369
+ }
370
+ }
371
+ const prompt = args.join(" ").trim();
372
+ if (!prompt) throw new Error("Missing prompt");
373
+ return { prompt, opts, json };
374
+ }
375
+ async function main() {
376
+ try {
377
+ const { prompt, opts, json } = parseArgs(process3.argv.slice(2));
378
+ const images = await generateImage(prompt, opts);
379
+ if (json) {
380
+ process3.stdout.write(JSON.stringify({ images }, null, 2) + "\n");
381
+ return;
382
+ }
383
+ for (const img of images) {
384
+ process3.stdout.write(img.filePath + "\n");
385
+ }
386
+ } catch (err) {
387
+ const msg = err instanceof Error ? err.message : String(err);
388
+ process3.stderr.write(`climage: ${msg}
389
+ `);
390
+ process3.stderr.write(`Run: climage --help
391
+ `);
392
+ process3.exit(1);
393
+ }
394
+ }
395
+ main();
396
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli.ts","../src/core/router.ts","../src/core/env.ts","../src/core/output.ts","../src/core/strings.ts","../src/providers/xai.ts","../src/providers/fal.ts","../src/providers/google.ts"],"sourcesContent":["#!/usr/bin/env node\nimport process from 'node:process'\n\nimport { generateImage, listProviders } from './index.js'\nimport type { GenerateOptions, ProviderId } from './core/types.js'\n\nfunction usage(code = 0) {\n const providers = listProviders()\n .map((p) => `${p.id}`)\n .join(', ')\n\n // eslint-disable-next-line no-console\n console.log(`climage\n\nUsage:\n climage \"prompt\"\n\nOptions:\n --provider <auto|${providers}> Provider (default: auto)\n --model <id> Model id (provider-specific)\n --n <1..10> Number of images (default: 1)\n --format <png|jpg|webp> Output format (default: png)\n --out <path> Output file path (only when n=1)\n --outDir <dir> Output directory (default: .)\n --name <text> Base name (slugified); default: prompt\n --aspect-ratio <w:h> Aspect ratio (xAI supports e.g. 4:3)\n --json Print machine-readable JSON\n --verbose Verbose logging\n -h, --help Show help\n\nEnv:\n GEMINI_API_KEY (or GOOGLE_API_KEY)\n XAI_API_KEY (or XAI_TOKEN, GROK_API_KEY)\n FAL_KEY (or FAL_API_KEY)\n\nExamples:\n npx climage \"make image of kitten\"\n npx climage \"A cat in a tree\" --provider xai --n 4\n`)\n process.exit(code)\n}\n\nfunction parseArgs(argv: string[]): { prompt: string; opts: GenerateOptions; json: boolean } {\n const args = [...argv]\n const opts: GenerateOptions = {}\n let json = false\n\n const take = (name: string): string => {\n const v = args.shift()\n if (!v) throw new Error(`Missing value for ${name}`)\n return v\n }\n\n while (args.length) {\n const a = args[0]\n if (!a) break\n if (a === '-h' || a === '--help') usage(0)\n if (a === '--json') {\n json = true\n args.shift()\n continue\n }\n if (!a.startsWith('-')) break\n\n args.shift()\n switch (a) {\n case '--provider':\n opts.provider = take(a) as ProviderId\n break\n case '--model':\n opts.model = take(a)\n break\n case '--n':\n opts.n = Number(take(a))\n break\n case '--format':\n opts.format = take(a) as any\n break\n case '--out':\n opts.out = take(a)\n break\n case '--outDir':\n opts.outDir = take(a)\n break\n case '--name':\n opts.name = take(a)\n break\n case '--aspect-ratio':\n opts.aspectRatio = take(a)\n break\n case '--verbose':\n opts.verbose = true\n break\n default:\n throw new Error(`Unknown option: ${a}`)\n }\n }\n\n const prompt = args.join(' ').trim()\n if (!prompt) throw new Error('Missing prompt')\n\n return { prompt, opts, json }\n}\n\nasync function main() {\n try {\n const { prompt, opts, json } = parseArgs(process.argv.slice(2))\n const images = await generateImage(prompt, opts)\n\n if (json) {\n process.stdout.write(JSON.stringify({ images }, null, 2) + '\\n')\n return\n }\n\n for (const img of images) {\n process.stdout.write(img.filePath + '\\n')\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n process.stderr.write(`climage: ${msg}\\n`)\n process.stderr.write(`Run: climage --help\\n`)\n process.exit(1)\n }\n}\n\n// eslint-disable-next-line @typescript-eslint/no-floating-promises\nmain()\n","import path from 'node:path'\n\nimport type { GenerateOptions, GenerateRequest, GeneratedImage, GeneratedImagePartial, Provider, ProviderEnv, ProviderId } from './types.js'\nimport { loadEnv } from './env.js'\nimport { makeOutputPath, resolveOutDir, writeImageFile } from './output.js'\nimport { slugify, timestampLocalCompact } from './strings.js'\nimport { xaiProvider } from '../providers/xai.js'\nimport { falProvider } from '../providers/fal.js'\nimport { googleProvider } from '../providers/google.js'\n\nconst providers: Provider[] = [googleProvider, xaiProvider, falProvider]\n\nexport function listProviders(): Provider[] {\n return [...providers]\n}\n\nexport function pickProvider(id: ProviderId, env: ProviderEnv): Provider {\n if (id !== 'auto') {\n const p = providers.find((p) => p.id === id)\n if (!p) throw new Error(`Unknown provider: ${id}`)\n if (!p.isAvailable(env)) throw new Error(`Provider ${id} is not available (missing API key)`)\n return p\n }\n\n const p = providers.find((pp) => pp.isAvailable(env))\n if (!p) throw new Error('No providers available. Set XAI_API_KEY (or other provider keys) in .env or environment.')\n return p\n}\n\nfunction normalizeOptions(prompt: string, opts: GenerateOptions): GenerateRequest {\n const nRaw = opts.n ?? 1\n const n = Math.max(1, Math.min(10, Math.floor(nRaw)))\n\n const format = opts.format ?? 'png'\n const outDir = resolveOutDir(opts.outDir ?? '.')\n const timestamp = timestampLocalCompact()\n\n const nameBase = slugify(opts.name ?? prompt)\n\n return {\n prompt,\n provider: opts.provider ?? 'auto',\n model: opts.model ?? undefined,\n n,\n aspectRatio: opts.aspectRatio ?? undefined,\n format,\n outDir,\n out: opts.out ? path.resolve(process.cwd(), opts.out) : undefined,\n nameBase,\n timestamp,\n verbose: Boolean(opts.verbose),\n }\n}\n\nexport async function generateImage(prompt: string, opts: GenerateOptions = {}): Promise<GeneratedImage[]> {\n const { env } = loadEnv(process.cwd())\n const req = normalizeOptions(prompt, opts)\n const provider = pickProvider(req.provider, env)\n\n const partials = await provider.generate(req, env)\n const images: GeneratedImage[] = []\n\n for (let i = 0; i < partials.length; i++) {\n const p: GeneratedImagePartial | undefined = partials[i]\n if (!p) continue\n const filePath = makeOutputPath(req, i)\n await writeImageFile(filePath, p.bytes)\n images.push({ ...p, filePath })\n }\n\n return images\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\nimport process from 'node:process'\nimport dotenv from 'dotenv'\n\nimport type { ProviderEnv } from './types.js'\n\nexport type EnvLoadResult = {\n env: ProviderEnv\n loadedFiles: string[]\n}\n\nexport function loadEnv(cwd = process.cwd()): EnvLoadResult {\n const candidates = ['.env', '.env.local']\n const loadedFiles: string[] = []\n\n for (const name of candidates) {\n const p = path.join(cwd, name)\n if (!fs.existsSync(p)) continue\n dotenv.config({ path: p, override: false })\n loadedFiles.push(p)\n }\n\n return { env: process.env as ProviderEnv, loadedFiles }\n}\n","import fs from 'node:fs/promises'\nimport path from 'node:path'\n\nimport type { GeneratedImage, GenerateRequest } from './types.js'\n\nexport function extensionForFormat(format: GenerateRequest['format']): string {\n switch (format) {\n case 'jpg':\n return 'jpg'\n case 'png':\n return 'png'\n case 'webp':\n return 'webp'\n }\n}\n\nexport function resolveOutDir(outDir: string): string {\n return path.isAbsolute(outDir) ? outDir : path.resolve(process.cwd(), outDir)\n}\n\nexport function makeOutputPath(req: GenerateRequest, index: number): string {\n const ext = extensionForFormat(req.format)\n if (req.out) return path.resolve(process.cwd(), req.out)\n\n const base = `${req.nameBase}-${req.timestamp}`\n const suffix = req.n > 1 ? `-${String(index + 1).padStart(2, '0')}` : ''\n const filename = `${base}${suffix}.${ext}`\n return path.join(req.outDir, filename)\n}\n\nexport async function writeImageFile(filePath: string, bytes: Uint8Array): Promise<void> {\n await fs.mkdir(path.dirname(filePath), { recursive: true })\n await fs.writeFile(filePath, bytes)\n}\n\nexport function redactUrl(url: string): string {\n try {\n const u = new URL(url)\n u.search = ''\n return u.toString()\n } catch {\n return url\n }\n}\n\nexport function toJsonResult(images: GeneratedImage[]) {\n return {\n images: images.map((img) => ({\n provider: img.provider,\n model: img.model,\n index: img.index,\n filePath: img.filePath,\n url: img.url,\n bytes: img.bytes.byteLength,\n mimeType: img.mimeType,\n })),\n }\n}\n","export function slugify(input: string, maxLen = 60): string {\n const s = input\n .trim()\n .toLowerCase()\n .replace(/['\"`]/g, '')\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n\n if (!s) return 'image'\n return s.length > maxLen ? s.slice(0, maxLen).replace(/-+$/g, '') : s\n}\n\nexport function timestampLocalCompact(d = new Date()): string {\n const pad = (n: number) => String(n).padStart(2, '0')\n return (\n d.getFullYear() +\n pad(d.getMonth() + 1) +\n pad(d.getDate()) +\n '-' +\n pad(d.getHours()) +\n pad(d.getMinutes()) +\n pad(d.getSeconds())\n )\n}\n","import type { GenerateRequest, Provider, ProviderEnv } from '../core/types.js'\n\nconst XAI_API_BASE = 'https://api.x.ai/v1'\n\nfunction getXaiApiKey(env: ProviderEnv): string | undefined {\n return env.XAI_API_KEY || env.XAI_TOKEN || env.GROK_API_KEY\n}\n\ntype XaiImage = {\n url?: string\n b64_json?: string\n}\n\ntype XaiImagesResponse = {\n created?: number\n data: XaiImage[]\n}\n\nasync function downloadBytes(url: string): Promise<{ bytes: Uint8Array; mimeType?: string }> {\n const res = await fetch(url)\n if (!res.ok) throw new Error(`xAI image download failed (${res.status})`)\n const ab = await res.arrayBuffer()\n const ct = res.headers.get('content-type') || undefined\n return { bytes: new Uint8Array(ab), mimeType: ct }\n}\n\nexport const xaiProvider: Provider = {\n id: 'xai',\n displayName: 'xAI (grok-imagine-image)',\n isAvailable(env) {\n return Boolean(getXaiApiKey(env))\n },\n async generate(req: GenerateRequest, env: ProviderEnv) {\n const apiKey = getXaiApiKey(env)\n if (!apiKey) throw new Error('Missing xAI API key. Set XAI_API_KEY (or XAI_TOKEN).')\n\n const model = req.model ?? 'grok-imagine-image'\n const body: Record<string, unknown> = {\n model,\n prompt: req.prompt,\n n: req.n,\n // xAI docs: endpoint supports aspect_ratio\n ...(req.aspectRatio ? { aspect_ratio: req.aspectRatio } : {}),\n // Use URL format to download + save.\n response_format: 'url',\n }\n\n const res = await fetch(`${XAI_API_BASE}/images/generations`, {\n method: 'POST',\n headers: {\n authorization: `Bearer ${apiKey}`,\n 'content-type': 'application/json',\n },\n body: JSON.stringify(body),\n })\n\n if (!res.ok) {\n const txt = await res.text().catch(() => '')\n throw new Error(`xAI generations failed (${res.status}): ${txt.slice(0, 500)}`)\n }\n\n const json = (await res.json()) as XaiImagesResponse\n if (!json.data?.length) throw new Error('xAI returned no images')\n\n const results = [] as Array<{ provider: 'xai'; model?: string; index: number; url?: string; bytes: Uint8Array; mimeType?: string }>\n\n for (let i = 0; i < json.data.length; i++) {\n const img = json.data[i]\n if (img.url) {\n const { bytes, mimeType } = await downloadBytes(img.url)\n results.push({ provider: 'xai', model, index: i, url: img.url, bytes, mimeType })\n continue\n }\n if (img.b64_json) {\n const bytes = Uint8Array.from(Buffer.from(img.b64_json, 'base64'))\n results.push({ provider: 'xai', model, index: i, bytes })\n continue\n }\n throw new Error('xAI returned image without url or b64_json')\n }\n\n return results\n },\n}\n","import { fal } from '@fal-ai/client'\n\nimport type { GenerateRequest, Provider, ProviderEnv } from '../core/types.js'\n\nfunction getFalKey(env: ProviderEnv): string | undefined {\n // community is split; support both\n return env.FAL_API_KEY || env.FAL_KEY\n}\n\ntype FalImage = {\n url: string\n content_type?: string\n}\n\ntype FalResult = {\n images?: FalImage[]\n}\n\nasync function downloadBytes(url: string): Promise<{ bytes: Uint8Array; mimeType?: string }> {\n const res = await fetch(url)\n if (!res.ok) throw new Error(`fal image download failed (${res.status})`)\n const ab = await res.arrayBuffer()\n const ct = res.headers.get('content-type') || undefined\n return { bytes: new Uint8Array(ab), mimeType: ct }\n}\n\nexport const falProvider: Provider = {\n id: 'fal',\n displayName: 'fal.ai',\n isAvailable(env) {\n return Boolean(getFalKey(env))\n },\n async generate(req: GenerateRequest, env: ProviderEnv) {\n const key = getFalKey(env)\n if (!key) throw new Error('Missing fal API key. Set FAL_KEY (or FAL_API_KEY).')\n\n // Configure credentials at runtime\n fal.config({ credentials: key })\n\n // Default model: Flux dev (fast + popular). Can be overridden via --model.\n const model = req.model ?? 'fal-ai/flux/dev'\n\n // Map common aspect ratios to fal enums when possible.\n // If user passes e.g. 4:3, use landscape_4_3.\n let image_size: any = undefined\n if (req.aspectRatio) {\n const ar = req.aspectRatio.trim()\n if (ar === '1:1') image_size = 'square'\n else if (ar === '4:3') image_size = 'landscape_4_3'\n else if (ar === '16:9') image_size = 'landscape_16_9'\n else if (ar === '3:4') image_size = 'portrait_4_3'\n else if (ar === '9:16') image_size = 'portrait_16_9'\n }\n\n const input: Record<string, unknown> = {\n prompt: req.prompt,\n ...(image_size ? { image_size } : {}),\n // Some fal models support \"num_images\"; flux/dev returns images array length.\n ...(req.n ? { num_images: req.n } : {}),\n }\n\n const result = (await fal.subscribe(model, { input })) as { data: FalResult }\n\n const images = result?.data?.images\n if (!images?.length) throw new Error('fal returned no images')\n\n const out = [] as Array<{ provider: 'fal'; model?: string; index: number; url?: string; bytes: Uint8Array; mimeType?: string }>\n\n for (let i = 0; i < Math.min(images.length, req.n); i++) {\n const img = images[i]\n if (!img?.url) continue\n const { bytes, mimeType } = await downloadBytes(img.url)\n out.push({ provider: 'fal', model, index: i, url: img.url, bytes, mimeType: img.content_type ?? mimeType })\n }\n\n if (!out.length) throw new Error('fal returned images but none were downloadable')\n\n return out\n },\n}\n","import { GoogleGenAI } from '@google/genai'\n\nimport type { GenerateRequest, Provider, ProviderEnv } from '../core/types.js'\n\nfunction getGeminiApiKey(env: ProviderEnv): string | undefined {\n // Standard names + common aliases\n return env.GEMINI_API_KEY || env.GOOGLE_API_KEY || env.GOOGLE_GENAI_API_KEY\n}\n\nfunction mimeForFormat(format: GenerateRequest['format']): string {\n switch (format) {\n case 'jpg':\n return 'image/jpeg'\n case 'webp':\n return 'image/webp'\n case 'png':\n default:\n return 'image/png'\n }\n}\n\nexport const googleProvider: Provider = {\n id: 'google',\n displayName: 'Google (Gemini / Imagen)',\n isAvailable(env) {\n return Boolean(getGeminiApiKey(env))\n },\n async generate(req: GenerateRequest, env: ProviderEnv) {\n const apiKey = getGeminiApiKey(env)\n if (!apiKey) throw new Error('Missing Google API key. Set GEMINI_API_KEY (or GOOGLE_API_KEY).')\n\n const ai = new GoogleGenAI({ apiKey })\n\n // Default to Imagen for pure text-to-image.\n const model = req.model ?? 'imagen-4.0-generate-001'\n\n const res = await ai.models.generateImages({\n model,\n prompt: req.prompt,\n config: {\n numberOfImages: req.n,\n outputMimeType: mimeForFormat(req.format),\n // Note: aspect ratio / size varies by model. Add later.\n },\n })\n\n const imgs = res.generatedImages\n if (!imgs?.length) throw new Error('Google generateImages returned no images')\n\n const out = [] as Array<{ provider: 'google'; model?: string; index: number; bytes: Uint8Array; mimeType?: string }>\n\n for (let i = 0; i < Math.min(imgs.length, req.n); i++) {\n const img = imgs[i]\n const rawBytes = img?.image?.imageBytes\n if (!rawBytes) continue\n // SDK returns base64 string, decode to binary\n const bytes = typeof rawBytes === 'string'\n ? Uint8Array.from(Buffer.from(rawBytes, 'base64'))\n : rawBytes\n out.push({ provider: 'google', model, index: i, bytes, mimeType: mimeForFormat(req.format) })\n }\n\n if (!out.length) throw new Error('Google returned images but no bytes were present')\n return out\n },\n}\n"],"mappings":"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","names":["process","path","process","fs","path","downloadBytes","p","path","providers","process"]}
@@ -0,0 +1,48 @@
1
+ type ProviderId = 'auto' | 'xai' | 'fal' | 'google';
2
+ type ImageFormat = 'png' | 'jpg' | 'webp';
3
+ type GenerateOptions = {
4
+ provider?: ProviderId;
5
+ model?: string;
6
+ n?: number;
7
+ aspectRatio?: string;
8
+ format?: ImageFormat;
9
+ out?: string;
10
+ outDir?: string;
11
+ name?: string;
12
+ verbose?: boolean;
13
+ };
14
+ type GenerateRequest = {
15
+ prompt: string;
16
+ provider: ProviderId;
17
+ model?: string | undefined;
18
+ n: number;
19
+ aspectRatio?: string | undefined;
20
+ format: ImageFormat;
21
+ outDir: string;
22
+ out?: string | undefined;
23
+ nameBase: string;
24
+ timestamp: string;
25
+ verbose: boolean;
26
+ };
27
+ type GeneratedImage = {
28
+ provider: Exclude<ProviderId, 'auto'>;
29
+ model?: string;
30
+ index: number;
31
+ url?: string;
32
+ bytes: Uint8Array;
33
+ mimeType?: string;
34
+ filePath: string;
35
+ };
36
+ type ProviderEnv = Record<string, string | undefined>;
37
+ type GeneratedImagePartial = Omit<GeneratedImage, 'filePath'>;
38
+ interface Provider {
39
+ id: Exclude<ProviderId, 'auto'>;
40
+ displayName: string;
41
+ isAvailable(env: ProviderEnv): boolean;
42
+ generate(req: GenerateRequest, env: ProviderEnv): Promise<GeneratedImagePartial[]>;
43
+ }
44
+
45
+ declare function listProviders(): Provider[];
46
+ declare function generateImage(prompt: string, opts?: GenerateOptions): Promise<GeneratedImage[]>;
47
+
48
+ export { type GenerateOptions, type GeneratedImage, type ImageFormat, type ProviderId, generateImage, listProviders };
package/dist/index.js ADDED
@@ -0,0 +1,285 @@
1
+ // src/core/router.ts
2
+ import path3 from "path";
3
+
4
+ // src/core/env.ts
5
+ import fs from "fs";
6
+ import path from "path";
7
+ import process2 from "process";
8
+ import dotenv from "dotenv";
9
+ function loadEnv(cwd = process2.cwd()) {
10
+ const candidates = [".env", ".env.local"];
11
+ const loadedFiles = [];
12
+ for (const name of candidates) {
13
+ const p = path.join(cwd, name);
14
+ if (!fs.existsSync(p)) continue;
15
+ dotenv.config({ path: p, override: false });
16
+ loadedFiles.push(p);
17
+ }
18
+ return { env: process2.env, loadedFiles };
19
+ }
20
+
21
+ // src/core/output.ts
22
+ import fs2 from "fs/promises";
23
+ import path2 from "path";
24
+ function extensionForFormat(format) {
25
+ switch (format) {
26
+ case "jpg":
27
+ return "jpg";
28
+ case "png":
29
+ return "png";
30
+ case "webp":
31
+ return "webp";
32
+ }
33
+ }
34
+ function resolveOutDir(outDir) {
35
+ return path2.isAbsolute(outDir) ? outDir : path2.resolve(process.cwd(), outDir);
36
+ }
37
+ function makeOutputPath(req, index) {
38
+ const ext = extensionForFormat(req.format);
39
+ if (req.out) return path2.resolve(process.cwd(), req.out);
40
+ const base = `${req.nameBase}-${req.timestamp}`;
41
+ const suffix = req.n > 1 ? `-${String(index + 1).padStart(2, "0")}` : "";
42
+ const filename = `${base}${suffix}.${ext}`;
43
+ return path2.join(req.outDir, filename);
44
+ }
45
+ async function writeImageFile(filePath, bytes) {
46
+ await fs2.mkdir(path2.dirname(filePath), { recursive: true });
47
+ await fs2.writeFile(filePath, bytes);
48
+ }
49
+
50
+ // src/core/strings.ts
51
+ function slugify(input, maxLen = 60) {
52
+ const s = input.trim().toLowerCase().replace(/['"`]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
53
+ if (!s) return "image";
54
+ return s.length > maxLen ? s.slice(0, maxLen).replace(/-+$/g, "") : s;
55
+ }
56
+ function timestampLocalCompact(d = /* @__PURE__ */ new Date()) {
57
+ const pad = (n) => String(n).padStart(2, "0");
58
+ return d.getFullYear() + pad(d.getMonth() + 1) + pad(d.getDate()) + "-" + pad(d.getHours()) + pad(d.getMinutes()) + pad(d.getSeconds());
59
+ }
60
+
61
+ // src/providers/xai.ts
62
+ var XAI_API_BASE = "https://api.x.ai/v1";
63
+ function getXaiApiKey(env) {
64
+ return env.XAI_API_KEY || env.XAI_TOKEN || env.GROK_API_KEY;
65
+ }
66
+ async function downloadBytes(url) {
67
+ const res = await fetch(url);
68
+ if (!res.ok) throw new Error(`xAI image download failed (${res.status})`);
69
+ const ab = await res.arrayBuffer();
70
+ const ct = res.headers.get("content-type") || void 0;
71
+ return { bytes: new Uint8Array(ab), mimeType: ct };
72
+ }
73
+ var xaiProvider = {
74
+ id: "xai",
75
+ displayName: "xAI (grok-imagine-image)",
76
+ isAvailable(env) {
77
+ return Boolean(getXaiApiKey(env));
78
+ },
79
+ async generate(req, env) {
80
+ const apiKey = getXaiApiKey(env);
81
+ if (!apiKey) throw new Error("Missing xAI API key. Set XAI_API_KEY (or XAI_TOKEN).");
82
+ const model = req.model ?? "grok-imagine-image";
83
+ const body = {
84
+ model,
85
+ prompt: req.prompt,
86
+ n: req.n,
87
+ // xAI docs: endpoint supports aspect_ratio
88
+ ...req.aspectRatio ? { aspect_ratio: req.aspectRatio } : {},
89
+ // Use URL format to download + save.
90
+ response_format: "url"
91
+ };
92
+ const res = await fetch(`${XAI_API_BASE}/images/generations`, {
93
+ method: "POST",
94
+ headers: {
95
+ authorization: `Bearer ${apiKey}`,
96
+ "content-type": "application/json"
97
+ },
98
+ body: JSON.stringify(body)
99
+ });
100
+ if (!res.ok) {
101
+ const txt = await res.text().catch(() => "");
102
+ throw new Error(`xAI generations failed (${res.status}): ${txt.slice(0, 500)}`);
103
+ }
104
+ const json = await res.json();
105
+ if (!json.data?.length) throw new Error("xAI returned no images");
106
+ const results = [];
107
+ for (let i = 0; i < json.data.length; i++) {
108
+ const img = json.data[i];
109
+ if (img.url) {
110
+ const { bytes, mimeType } = await downloadBytes(img.url);
111
+ results.push({ provider: "xai", model, index: i, url: img.url, bytes, mimeType });
112
+ continue;
113
+ }
114
+ if (img.b64_json) {
115
+ const bytes = Uint8Array.from(Buffer.from(img.b64_json, "base64"));
116
+ results.push({ provider: "xai", model, index: i, bytes });
117
+ continue;
118
+ }
119
+ throw new Error("xAI returned image without url or b64_json");
120
+ }
121
+ return results;
122
+ }
123
+ };
124
+
125
+ // src/providers/fal.ts
126
+ import { fal } from "@fal-ai/client";
127
+ function getFalKey(env) {
128
+ return env.FAL_API_KEY || env.FAL_KEY;
129
+ }
130
+ async function downloadBytes2(url) {
131
+ const res = await fetch(url);
132
+ if (!res.ok) throw new Error(`fal image download failed (${res.status})`);
133
+ const ab = await res.arrayBuffer();
134
+ const ct = res.headers.get("content-type") || void 0;
135
+ return { bytes: new Uint8Array(ab), mimeType: ct };
136
+ }
137
+ var falProvider = {
138
+ id: "fal",
139
+ displayName: "fal.ai",
140
+ isAvailable(env) {
141
+ return Boolean(getFalKey(env));
142
+ },
143
+ async generate(req, env) {
144
+ const key = getFalKey(env);
145
+ if (!key) throw new Error("Missing fal API key. Set FAL_KEY (or FAL_API_KEY).");
146
+ fal.config({ credentials: key });
147
+ const model = req.model ?? "fal-ai/flux/dev";
148
+ let image_size = void 0;
149
+ if (req.aspectRatio) {
150
+ const ar = req.aspectRatio.trim();
151
+ if (ar === "1:1") image_size = "square";
152
+ else if (ar === "4:3") image_size = "landscape_4_3";
153
+ else if (ar === "16:9") image_size = "landscape_16_9";
154
+ else if (ar === "3:4") image_size = "portrait_4_3";
155
+ else if (ar === "9:16") image_size = "portrait_16_9";
156
+ }
157
+ const input = {
158
+ prompt: req.prompt,
159
+ ...image_size ? { image_size } : {},
160
+ // Some fal models support "num_images"; flux/dev returns images array length.
161
+ ...req.n ? { num_images: req.n } : {}
162
+ };
163
+ const result = await fal.subscribe(model, { input });
164
+ const images = result?.data?.images;
165
+ if (!images?.length) throw new Error("fal returned no images");
166
+ const out = [];
167
+ for (let i = 0; i < Math.min(images.length, req.n); i++) {
168
+ const img = images[i];
169
+ if (!img?.url) continue;
170
+ const { bytes, mimeType } = await downloadBytes2(img.url);
171
+ out.push({ provider: "fal", model, index: i, url: img.url, bytes, mimeType: img.content_type ?? mimeType });
172
+ }
173
+ if (!out.length) throw new Error("fal returned images but none were downloadable");
174
+ return out;
175
+ }
176
+ };
177
+
178
+ // src/providers/google.ts
179
+ import { GoogleGenAI } from "@google/genai";
180
+ function getGeminiApiKey(env) {
181
+ return env.GEMINI_API_KEY || env.GOOGLE_API_KEY || env.GOOGLE_GENAI_API_KEY;
182
+ }
183
+ function mimeForFormat(format) {
184
+ switch (format) {
185
+ case "jpg":
186
+ return "image/jpeg";
187
+ case "webp":
188
+ return "image/webp";
189
+ case "png":
190
+ default:
191
+ return "image/png";
192
+ }
193
+ }
194
+ var googleProvider = {
195
+ id: "google",
196
+ displayName: "Google (Gemini / Imagen)",
197
+ isAvailable(env) {
198
+ return Boolean(getGeminiApiKey(env));
199
+ },
200
+ async generate(req, env) {
201
+ const apiKey = getGeminiApiKey(env);
202
+ if (!apiKey) throw new Error("Missing Google API key. Set GEMINI_API_KEY (or GOOGLE_API_KEY).");
203
+ const ai = new GoogleGenAI({ apiKey });
204
+ const model = req.model ?? "imagen-4.0-generate-001";
205
+ const res = await ai.models.generateImages({
206
+ model,
207
+ prompt: req.prompt,
208
+ config: {
209
+ numberOfImages: req.n,
210
+ outputMimeType: mimeForFormat(req.format)
211
+ // Note: aspect ratio / size varies by model. Add later.
212
+ }
213
+ });
214
+ const imgs = res.generatedImages;
215
+ if (!imgs?.length) throw new Error("Google generateImages returned no images");
216
+ const out = [];
217
+ for (let i = 0; i < Math.min(imgs.length, req.n); i++) {
218
+ const img = imgs[i];
219
+ const rawBytes = img?.image?.imageBytes;
220
+ if (!rawBytes) continue;
221
+ const bytes = typeof rawBytes === "string" ? Uint8Array.from(Buffer.from(rawBytes, "base64")) : rawBytes;
222
+ out.push({ provider: "google", model, index: i, bytes, mimeType: mimeForFormat(req.format) });
223
+ }
224
+ if (!out.length) throw new Error("Google returned images but no bytes were present");
225
+ return out;
226
+ }
227
+ };
228
+
229
+ // src/core/router.ts
230
+ var providers = [googleProvider, xaiProvider, falProvider];
231
+ function listProviders() {
232
+ return [...providers];
233
+ }
234
+ function pickProvider(id, env) {
235
+ if (id !== "auto") {
236
+ const p2 = providers.find((p3) => p3.id === id);
237
+ if (!p2) throw new Error(`Unknown provider: ${id}`);
238
+ if (!p2.isAvailable(env)) throw new Error(`Provider ${id} is not available (missing API key)`);
239
+ return p2;
240
+ }
241
+ const p = providers.find((pp) => pp.isAvailable(env));
242
+ if (!p) throw new Error("No providers available. Set XAI_API_KEY (or other provider keys) in .env or environment.");
243
+ return p;
244
+ }
245
+ function normalizeOptions(prompt, opts) {
246
+ const nRaw = opts.n ?? 1;
247
+ const n = Math.max(1, Math.min(10, Math.floor(nRaw)));
248
+ const format = opts.format ?? "png";
249
+ const outDir = resolveOutDir(opts.outDir ?? ".");
250
+ const timestamp = timestampLocalCompact();
251
+ const nameBase = slugify(opts.name ?? prompt);
252
+ return {
253
+ prompt,
254
+ provider: opts.provider ?? "auto",
255
+ model: opts.model ?? void 0,
256
+ n,
257
+ aspectRatio: opts.aspectRatio ?? void 0,
258
+ format,
259
+ outDir,
260
+ out: opts.out ? path3.resolve(process.cwd(), opts.out) : void 0,
261
+ nameBase,
262
+ timestamp,
263
+ verbose: Boolean(opts.verbose)
264
+ };
265
+ }
266
+ async function generateImage(prompt, opts = {}) {
267
+ const { env } = loadEnv(process.cwd());
268
+ const req = normalizeOptions(prompt, opts);
269
+ const provider = pickProvider(req.provider, env);
270
+ const partials = await provider.generate(req, env);
271
+ const images = [];
272
+ for (let i = 0; i < partials.length; i++) {
273
+ const p = partials[i];
274
+ if (!p) continue;
275
+ const filePath = makeOutputPath(req, i);
276
+ await writeImageFile(filePath, p.bytes);
277
+ images.push({ ...p, filePath });
278
+ }
279
+ return images;
280
+ }
281
+ export {
282
+ generateImage,
283
+ listProviders
284
+ };
285
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/router.ts","../src/core/env.ts","../src/core/output.ts","../src/core/strings.ts","../src/providers/xai.ts","../src/providers/fal.ts","../src/providers/google.ts"],"sourcesContent":["import path from 'node:path'\n\nimport type { GenerateOptions, GenerateRequest, GeneratedImage, GeneratedImagePartial, Provider, ProviderEnv, ProviderId } from './types.js'\nimport { loadEnv } from './env.js'\nimport { makeOutputPath, resolveOutDir, writeImageFile } from './output.js'\nimport { slugify, timestampLocalCompact } from './strings.js'\nimport { xaiProvider } from '../providers/xai.js'\nimport { falProvider } from '../providers/fal.js'\nimport { googleProvider } from '../providers/google.js'\n\nconst providers: Provider[] = [googleProvider, xaiProvider, falProvider]\n\nexport function listProviders(): Provider[] {\n return [...providers]\n}\n\nexport function pickProvider(id: ProviderId, env: ProviderEnv): Provider {\n if (id !== 'auto') {\n const p = providers.find((p) => p.id === id)\n if (!p) throw new Error(`Unknown provider: ${id}`)\n if (!p.isAvailable(env)) throw new Error(`Provider ${id} is not available (missing API key)`)\n return p\n }\n\n const p = providers.find((pp) => pp.isAvailable(env))\n if (!p) throw new Error('No providers available. Set XAI_API_KEY (or other provider keys) in .env or environment.')\n return p\n}\n\nfunction normalizeOptions(prompt: string, opts: GenerateOptions): GenerateRequest {\n const nRaw = opts.n ?? 1\n const n = Math.max(1, Math.min(10, Math.floor(nRaw)))\n\n const format = opts.format ?? 'png'\n const outDir = resolveOutDir(opts.outDir ?? '.')\n const timestamp = timestampLocalCompact()\n\n const nameBase = slugify(opts.name ?? prompt)\n\n return {\n prompt,\n provider: opts.provider ?? 'auto',\n model: opts.model ?? undefined,\n n,\n aspectRatio: opts.aspectRatio ?? undefined,\n format,\n outDir,\n out: opts.out ? path.resolve(process.cwd(), opts.out) : undefined,\n nameBase,\n timestamp,\n verbose: Boolean(opts.verbose),\n }\n}\n\nexport async function generateImage(prompt: string, opts: GenerateOptions = {}): Promise<GeneratedImage[]> {\n const { env } = loadEnv(process.cwd())\n const req = normalizeOptions(prompt, opts)\n const provider = pickProvider(req.provider, env)\n\n const partials = await provider.generate(req, env)\n const images: GeneratedImage[] = []\n\n for (let i = 0; i < partials.length; i++) {\n const p: GeneratedImagePartial | undefined = partials[i]\n if (!p) continue\n const filePath = makeOutputPath(req, i)\n await writeImageFile(filePath, p.bytes)\n images.push({ ...p, filePath })\n }\n\n return images\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\nimport process from 'node:process'\nimport dotenv from 'dotenv'\n\nimport type { ProviderEnv } from './types.js'\n\nexport type EnvLoadResult = {\n env: ProviderEnv\n loadedFiles: string[]\n}\n\nexport function loadEnv(cwd = process.cwd()): EnvLoadResult {\n const candidates = ['.env', '.env.local']\n const loadedFiles: string[] = []\n\n for (const name of candidates) {\n const p = path.join(cwd, name)\n if (!fs.existsSync(p)) continue\n dotenv.config({ path: p, override: false })\n loadedFiles.push(p)\n }\n\n return { env: process.env as ProviderEnv, loadedFiles }\n}\n","import fs from 'node:fs/promises'\nimport path from 'node:path'\n\nimport type { GeneratedImage, GenerateRequest } from './types.js'\n\nexport function extensionForFormat(format: GenerateRequest['format']): string {\n switch (format) {\n case 'jpg':\n return 'jpg'\n case 'png':\n return 'png'\n case 'webp':\n return 'webp'\n }\n}\n\nexport function resolveOutDir(outDir: string): string {\n return path.isAbsolute(outDir) ? outDir : path.resolve(process.cwd(), outDir)\n}\n\nexport function makeOutputPath(req: GenerateRequest, index: number): string {\n const ext = extensionForFormat(req.format)\n if (req.out) return path.resolve(process.cwd(), req.out)\n\n const base = `${req.nameBase}-${req.timestamp}`\n const suffix = req.n > 1 ? `-${String(index + 1).padStart(2, '0')}` : ''\n const filename = `${base}${suffix}.${ext}`\n return path.join(req.outDir, filename)\n}\n\nexport async function writeImageFile(filePath: string, bytes: Uint8Array): Promise<void> {\n await fs.mkdir(path.dirname(filePath), { recursive: true })\n await fs.writeFile(filePath, bytes)\n}\n\nexport function redactUrl(url: string): string {\n try {\n const u = new URL(url)\n u.search = ''\n return u.toString()\n } catch {\n return url\n }\n}\n\nexport function toJsonResult(images: GeneratedImage[]) {\n return {\n images: images.map((img) => ({\n provider: img.provider,\n model: img.model,\n index: img.index,\n filePath: img.filePath,\n url: img.url,\n bytes: img.bytes.byteLength,\n mimeType: img.mimeType,\n })),\n }\n}\n","export function slugify(input: string, maxLen = 60): string {\n const s = input\n .trim()\n .toLowerCase()\n .replace(/['\"`]/g, '')\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n\n if (!s) return 'image'\n return s.length > maxLen ? s.slice(0, maxLen).replace(/-+$/g, '') : s\n}\n\nexport function timestampLocalCompact(d = new Date()): string {\n const pad = (n: number) => String(n).padStart(2, '0')\n return (\n d.getFullYear() +\n pad(d.getMonth() + 1) +\n pad(d.getDate()) +\n '-' +\n pad(d.getHours()) +\n pad(d.getMinutes()) +\n pad(d.getSeconds())\n )\n}\n","import type { GenerateRequest, Provider, ProviderEnv } from '../core/types.js'\n\nconst XAI_API_BASE = 'https://api.x.ai/v1'\n\nfunction getXaiApiKey(env: ProviderEnv): string | undefined {\n return env.XAI_API_KEY || env.XAI_TOKEN || env.GROK_API_KEY\n}\n\ntype XaiImage = {\n url?: string\n b64_json?: string\n}\n\ntype XaiImagesResponse = {\n created?: number\n data: XaiImage[]\n}\n\nasync function downloadBytes(url: string): Promise<{ bytes: Uint8Array; mimeType?: string }> {\n const res = await fetch(url)\n if (!res.ok) throw new Error(`xAI image download failed (${res.status})`)\n const ab = await res.arrayBuffer()\n const ct = res.headers.get('content-type') || undefined\n return { bytes: new Uint8Array(ab), mimeType: ct }\n}\n\nexport const xaiProvider: Provider = {\n id: 'xai',\n displayName: 'xAI (grok-imagine-image)',\n isAvailable(env) {\n return Boolean(getXaiApiKey(env))\n },\n async generate(req: GenerateRequest, env: ProviderEnv) {\n const apiKey = getXaiApiKey(env)\n if (!apiKey) throw new Error('Missing xAI API key. Set XAI_API_KEY (or XAI_TOKEN).')\n\n const model = req.model ?? 'grok-imagine-image'\n const body: Record<string, unknown> = {\n model,\n prompt: req.prompt,\n n: req.n,\n // xAI docs: endpoint supports aspect_ratio\n ...(req.aspectRatio ? { aspect_ratio: req.aspectRatio } : {}),\n // Use URL format to download + save.\n response_format: 'url',\n }\n\n const res = await fetch(`${XAI_API_BASE}/images/generations`, {\n method: 'POST',\n headers: {\n authorization: `Bearer ${apiKey}`,\n 'content-type': 'application/json',\n },\n body: JSON.stringify(body),\n })\n\n if (!res.ok) {\n const txt = await res.text().catch(() => '')\n throw new Error(`xAI generations failed (${res.status}): ${txt.slice(0, 500)}`)\n }\n\n const json = (await res.json()) as XaiImagesResponse\n if (!json.data?.length) throw new Error('xAI returned no images')\n\n const results = [] as Array<{ provider: 'xai'; model?: string; index: number; url?: string; bytes: Uint8Array; mimeType?: string }>\n\n for (let i = 0; i < json.data.length; i++) {\n const img = json.data[i]\n if (img.url) {\n const { bytes, mimeType } = await downloadBytes(img.url)\n results.push({ provider: 'xai', model, index: i, url: img.url, bytes, mimeType })\n continue\n }\n if (img.b64_json) {\n const bytes = Uint8Array.from(Buffer.from(img.b64_json, 'base64'))\n results.push({ provider: 'xai', model, index: i, bytes })\n continue\n }\n throw new Error('xAI returned image without url or b64_json')\n }\n\n return results\n },\n}\n","import { fal } from '@fal-ai/client'\n\nimport type { GenerateRequest, Provider, ProviderEnv } from '../core/types.js'\n\nfunction getFalKey(env: ProviderEnv): string | undefined {\n // community is split; support both\n return env.FAL_API_KEY || env.FAL_KEY\n}\n\ntype FalImage = {\n url: string\n content_type?: string\n}\n\ntype FalResult = {\n images?: FalImage[]\n}\n\nasync function downloadBytes(url: string): Promise<{ bytes: Uint8Array; mimeType?: string }> {\n const res = await fetch(url)\n if (!res.ok) throw new Error(`fal image download failed (${res.status})`)\n const ab = await res.arrayBuffer()\n const ct = res.headers.get('content-type') || undefined\n return { bytes: new Uint8Array(ab), mimeType: ct }\n}\n\nexport const falProvider: Provider = {\n id: 'fal',\n displayName: 'fal.ai',\n isAvailable(env) {\n return Boolean(getFalKey(env))\n },\n async generate(req: GenerateRequest, env: ProviderEnv) {\n const key = getFalKey(env)\n if (!key) throw new Error('Missing fal API key. Set FAL_KEY (or FAL_API_KEY).')\n\n // Configure credentials at runtime\n fal.config({ credentials: key })\n\n // Default model: Flux dev (fast + popular). Can be overridden via --model.\n const model = req.model ?? 'fal-ai/flux/dev'\n\n // Map common aspect ratios to fal enums when possible.\n // If user passes e.g. 4:3, use landscape_4_3.\n let image_size: any = undefined\n if (req.aspectRatio) {\n const ar = req.aspectRatio.trim()\n if (ar === '1:1') image_size = 'square'\n else if (ar === '4:3') image_size = 'landscape_4_3'\n else if (ar === '16:9') image_size = 'landscape_16_9'\n else if (ar === '3:4') image_size = 'portrait_4_3'\n else if (ar === '9:16') image_size = 'portrait_16_9'\n }\n\n const input: Record<string, unknown> = {\n prompt: req.prompt,\n ...(image_size ? { image_size } : {}),\n // Some fal models support \"num_images\"; flux/dev returns images array length.\n ...(req.n ? { num_images: req.n } : {}),\n }\n\n const result = (await fal.subscribe(model, { input })) as { data: FalResult }\n\n const images = result?.data?.images\n if (!images?.length) throw new Error('fal returned no images')\n\n const out = [] as Array<{ provider: 'fal'; model?: string; index: number; url?: string; bytes: Uint8Array; mimeType?: string }>\n\n for (let i = 0; i < Math.min(images.length, req.n); i++) {\n const img = images[i]\n if (!img?.url) continue\n const { bytes, mimeType } = await downloadBytes(img.url)\n out.push({ provider: 'fal', model, index: i, url: img.url, bytes, mimeType: img.content_type ?? mimeType })\n }\n\n if (!out.length) throw new Error('fal returned images but none were downloadable')\n\n return out\n },\n}\n","import { GoogleGenAI } from '@google/genai'\n\nimport type { GenerateRequest, Provider, ProviderEnv } from '../core/types.js'\n\nfunction getGeminiApiKey(env: ProviderEnv): string | undefined {\n // Standard names + common aliases\n return env.GEMINI_API_KEY || env.GOOGLE_API_KEY || env.GOOGLE_GENAI_API_KEY\n}\n\nfunction mimeForFormat(format: GenerateRequest['format']): string {\n switch (format) {\n case 'jpg':\n return 'image/jpeg'\n case 'webp':\n return 'image/webp'\n case 'png':\n default:\n return 'image/png'\n }\n}\n\nexport const googleProvider: Provider = {\n id: 'google',\n displayName: 'Google (Gemini / Imagen)',\n isAvailable(env) {\n return Boolean(getGeminiApiKey(env))\n },\n async generate(req: GenerateRequest, env: ProviderEnv) {\n const apiKey = getGeminiApiKey(env)\n if (!apiKey) throw new Error('Missing Google API key. Set GEMINI_API_KEY (or GOOGLE_API_KEY).')\n\n const ai = new GoogleGenAI({ apiKey })\n\n // Default to Imagen for pure text-to-image.\n const model = req.model ?? 'imagen-4.0-generate-001'\n\n const res = await ai.models.generateImages({\n model,\n prompt: req.prompt,\n config: {\n numberOfImages: req.n,\n outputMimeType: mimeForFormat(req.format),\n // Note: aspect ratio / size varies by model. Add later.\n },\n })\n\n const imgs = res.generatedImages\n if (!imgs?.length) throw new Error('Google generateImages returned no images')\n\n const out = [] as Array<{ provider: 'google'; model?: string; index: number; bytes: Uint8Array; mimeType?: string }>\n\n for (let i = 0; i < Math.min(imgs.length, req.n); i++) {\n const img = imgs[i]\n const rawBytes = img?.image?.imageBytes\n if (!rawBytes) continue\n // SDK returns base64 string, decode to binary\n const bytes = typeof rawBytes === 'string'\n ? Uint8Array.from(Buffer.from(rawBytes, 'base64'))\n : rawBytes\n out.push({ provider: 'google', model, index: i, bytes, mimeType: mimeForFormat(req.format) })\n }\n\n if (!out.length) throw new Error('Google returned images but no bytes were present')\n return out\n },\n}\n"],"mappings":"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","names":["path","process","fs","path","downloadBytes","p","path"]}
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "climage",
3
+ "version": "0.1.0",
4
+ "description": "Generate images from the terminal via multiple providers (xAI, Google, fal)",
5
+ "license": "MIT",
6
+ "author": "",
7
+ "type": "module",
8
+ "bin": {
9
+ "climage": "./dist/cli.js"
10
+ },
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "default": "./dist/index.js"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "engines": {
21
+ "node": ">=18"
22
+ },
23
+ "scripts": {
24
+ "build": "tsup",
25
+ "dev": "node --enable-source-maps dist/cli.js",
26
+ "typecheck": "tsc -p tsconfig.json --noEmit",
27
+ "lint": "prettier --check .",
28
+ "format": "prettier --write .",
29
+ "format:check": "prettier --check .",
30
+ "prepare": "husky"
31
+ },
32
+ "lint-staged": {
33
+ "*.{js,ts,json,md}": "prettier --write"
34
+ },
35
+ "dependencies": {
36
+ "@fal-ai/client": "^1.8.4",
37
+ "@google/genai": "^1.38.0",
38
+ "dotenv": "^17.2.2"
39
+ },
40
+ "devDependencies": {
41
+ "@types/node": "^24.1.0",
42
+ "husky": "^9.1.7",
43
+ "lint-staged": "^16.2.7",
44
+ "prettier": "^3.8.1",
45
+ "tsup": "^8.5.0",
46
+ "typescript": "^5.9.2"
47
+ }
48
+ }