@djangocfg/imgai 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,905 @@
1
+ 'use strict';
2
+
3
+ var fs = require('fs-extra');
4
+ var path2 = require('path');
5
+ var sharp = require('sharp');
6
+ var OpenAI = require('openai');
7
+ var Anthropic = require('@anthropic-ai/sdk');
8
+ var glob = require('glob');
9
+ var chalk = require('chalk');
10
+ var inquirer = require('inquirer');
11
+ var ora = require('ora');
12
+
13
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
14
+
15
+ var fs__default = /*#__PURE__*/_interopDefault(fs);
16
+ var path2__default = /*#__PURE__*/_interopDefault(path2);
17
+ var sharp__default = /*#__PURE__*/_interopDefault(sharp);
18
+ var OpenAI__default = /*#__PURE__*/_interopDefault(OpenAI);
19
+ var Anthropic__default = /*#__PURE__*/_interopDefault(Anthropic);
20
+ var chalk__default = /*#__PURE__*/_interopDefault(chalk);
21
+ var inquirer__default = /*#__PURE__*/_interopDefault(inquirer);
22
+ var ora__default = /*#__PURE__*/_interopDefault(ora);
23
+
24
+ // src/core/generator.ts
25
+ var ImageGenerator = class {
26
+ config;
27
+ openai;
28
+ anthropic;
29
+ constructor(config) {
30
+ this.config = config;
31
+ this.initializeProviders();
32
+ }
33
+ initializeProviders() {
34
+ const openaiKey = this.config.openaiApiKey || process.env.OPENAI_API_KEY;
35
+ const anthropicKey = this.config.anthropicApiKey || process.env.ANTHROPIC_API_KEY;
36
+ if (openaiKey) {
37
+ this.openai = new OpenAI__default.default({ apiKey: openaiKey });
38
+ }
39
+ if (anthropicKey) {
40
+ this.anthropic = new Anthropic__default.default({ apiKey: anthropicKey });
41
+ }
42
+ if (!this.openai && this.config.provider === "openai") {
43
+ throw new Error("OpenAI API key required. Set OPENAI_API_KEY or pass openaiApiKey in config.");
44
+ }
45
+ }
46
+ // ──────────────────────────────────────────────────────────────────────────
47
+ // SINGLE IMAGE GENERATION
48
+ // ──────────────────────────────────────────────────────────────────────────
49
+ async generate(options) {
50
+ const startTime = Date.now();
51
+ try {
52
+ const fullPrompt = this.buildPrompt(options.prompt);
53
+ const imageData = await this.generateWithOpenAI(fullPrompt);
54
+ const filename = options.filename || this.generateFilename(options.prompt);
55
+ const category = options.category || "general";
56
+ const outputDir = path2__default.default.join(
57
+ this.config.projectRoot,
58
+ this.config.outputDir,
59
+ category
60
+ );
61
+ await fs__default.default.ensureDir(outputDir);
62
+ const originalPath = path2__default.default.join(outputDir, `${filename}.png`);
63
+ await fs__default.default.writeFile(originalPath, Buffer.from(imageData, "base64"));
64
+ const resizeOptions = options.resize || this.config.resize;
65
+ let finalPath = originalPath;
66
+ if (resizeOptions) {
67
+ finalPath = await this.resizeImage(originalPath, resizeOptions);
68
+ if (finalPath !== originalPath) {
69
+ await fs__default.default.remove(originalPath);
70
+ }
71
+ }
72
+ const imageInfo = await this.getImageInfo(finalPath, category);
73
+ imageInfo.metadata = {
74
+ prompt: options.prompt,
75
+ caption: options.metadata?.caption,
76
+ tags: options.tags,
77
+ category,
78
+ generatedAt: /* @__PURE__ */ new Date(),
79
+ model: "dall-e-3"
80
+ };
81
+ const duration = Date.now() - startTime;
82
+ return {
83
+ success: true,
84
+ imagePath: finalPath,
85
+ imageUrl: imageInfo.url,
86
+ imageInfo,
87
+ duration
88
+ };
89
+ } catch (error) {
90
+ return {
91
+ success: false,
92
+ error: error instanceof Error ? error.message : "Unknown error",
93
+ duration: Date.now() - startTime
94
+ };
95
+ }
96
+ }
97
+ // ──────────────────────────────────────────────────────────────────────────
98
+ // BATCH GENERATION
99
+ // ──────────────────────────────────────────────────────────────────────────
100
+ async generateBatch(options) {
101
+ const startTime = Date.now();
102
+ const results = [];
103
+ const concurrency = options.concurrency || 2;
104
+ const delayBetween = options.delayBetween || 2e3;
105
+ for (let i = 0; i < options.items.length; i += concurrency) {
106
+ const batch = options.items.slice(i, i + concurrency);
107
+ const batchPromises = batch.map(async (item) => {
108
+ const result = await this.generate({
109
+ prompt: item.prompt,
110
+ filename: item.filename,
111
+ category: item.category
112
+ });
113
+ return result;
114
+ });
115
+ const batchResults = await Promise.all(batchPromises);
116
+ for (const result of batchResults) {
117
+ results.push(result);
118
+ if (options.onProgress) {
119
+ options.onProgress(results.length, options.items.length, result);
120
+ }
121
+ }
122
+ if (i + concurrency < options.items.length) {
123
+ await this.delay(delayBetween);
124
+ }
125
+ }
126
+ return {
127
+ total: results.length,
128
+ success: results.filter((r) => r.success).length,
129
+ failed: results.filter((r) => !r.success).length,
130
+ results,
131
+ duration: Date.now() - startTime
132
+ };
133
+ }
134
+ // ──────────────────────────────────────────────────────────────────────────
135
+ // PROMPT ENHANCEMENT (using Claude/GPT)
136
+ // ──────────────────────────────────────────────────────────────────────────
137
+ async enhancePrompt(basePrompt, context) {
138
+ const systemPrompt = `You are an expert at creating image generation prompts.
139
+ Given a description, create:
140
+ 1. A detailed visual prompt (max 150 words) for DALL-E 3
141
+ 2. A descriptive filename (lowercase, hyphens, max 30 chars)
142
+ 3. A short caption (max 50 words)
143
+
144
+ ${this.config.style ? `Style guide: ${this.config.style}` : ""}
145
+
146
+ Return ONLY valid JSON: {"prompt": "...", "filename": "...", "caption": "..."}`;
147
+ const userPrompt = context ? `Description: ${basePrompt}
148
+ Context: ${context}` : `Description: ${basePrompt}`;
149
+ if (this.anthropic && this.config.provider === "anthropic") {
150
+ const response = await this.anthropic.messages.create({
151
+ model: "claude-3-5-sonnet-20241022",
152
+ max_tokens: 500,
153
+ messages: [
154
+ { role: "user", content: `${systemPrompt}
155
+
156
+ ${userPrompt}` }
157
+ ]
158
+ });
159
+ const text = response.content[0].type === "text" ? response.content[0].text : "";
160
+ return JSON.parse(text);
161
+ }
162
+ if (this.openai) {
163
+ const response = await this.openai.chat.completions.create({
164
+ model: "gpt-4-turbo-preview",
165
+ messages: [
166
+ { role: "system", content: systemPrompt },
167
+ { role: "user", content: userPrompt }
168
+ ],
169
+ max_tokens: 500,
170
+ temperature: 0.7,
171
+ response_format: { type: "json_object" }
172
+ });
173
+ const text = response.choices[0]?.message?.content || "{}";
174
+ return JSON.parse(text);
175
+ }
176
+ throw new Error("No AI provider configured for prompt enhancement");
177
+ }
178
+ // ──────────────────────────────────────────────────────────────────────────
179
+ // PRIVATE METHODS
180
+ // ──────────────────────────────────────────────────────────────────────────
181
+ async generateWithOpenAI(prompt) {
182
+ if (!this.openai) {
183
+ throw new Error("OpenAI client not initialized");
184
+ }
185
+ const response = await this.openai.images.generate({
186
+ model: "dall-e-3",
187
+ prompt,
188
+ n: 1,
189
+ size: this.config.size,
190
+ quality: this.config.quality,
191
+ response_format: "b64_json"
192
+ });
193
+ const imageData = response.data?.[0]?.b64_json;
194
+ if (!imageData) {
195
+ throw new Error("No image data received from OpenAI");
196
+ }
197
+ return imageData;
198
+ }
199
+ async resizeImage(inputPath, options) {
200
+ const { width, height, quality = 85, format = "webp", fit = "inside" } = options;
201
+ let pipeline = sharp__default.default(inputPath);
202
+ if (width || height) {
203
+ pipeline = pipeline.resize(width, height, {
204
+ fit,
205
+ withoutEnlargement: true
206
+ });
207
+ }
208
+ const outputPath = inputPath.replace(/\.[^.]+$/, `.${format}`);
209
+ switch (format) {
210
+ case "webp":
211
+ pipeline = pipeline.webp({ quality });
212
+ break;
213
+ case "jpeg":
214
+ pipeline = pipeline.jpeg({ quality });
215
+ break;
216
+ case "png":
217
+ pipeline = pipeline.png({ quality });
218
+ break;
219
+ case "avif":
220
+ pipeline = pipeline.avif({ quality });
221
+ break;
222
+ }
223
+ await pipeline.toFile(outputPath);
224
+ return outputPath;
225
+ }
226
+ async getImageInfo(imagePath, category) {
227
+ const stats = await fs__default.default.stat(imagePath);
228
+ const metadata = await sharp__default.default(imagePath).metadata();
229
+ const filename = path2__default.default.basename(imagePath);
230
+ const extension = path2__default.default.extname(filename).slice(1);
231
+ const id = path2__default.default.basename(filename, path2__default.default.extname(filename));
232
+ const publicDir = path2__default.default.join(this.config.projectRoot, this.config.publicDir);
233
+ const relativePath = path2__default.default.relative(publicDir, imagePath);
234
+ const url = "/" + relativePath.replace(/\\/g, "/");
235
+ return {
236
+ id,
237
+ filename,
238
+ extension,
239
+ path: relativePath,
240
+ url,
241
+ size: stats.size,
242
+ width: metadata.width,
243
+ height: metadata.height,
244
+ createdAt: stats.birthtime,
245
+ modifiedAt: stats.mtime
246
+ };
247
+ }
248
+ buildPrompt(basePrompt) {
249
+ if (this.config.style) {
250
+ return `${this.config.style}. ${basePrompt}`;
251
+ }
252
+ return basePrompt;
253
+ }
254
+ generateFilename(prompt) {
255
+ return prompt.toLowerCase().replace(/[^a-z0-9\s]/g, "").replace(/\s+/g, "-").substring(0, 30);
256
+ }
257
+ delay(ms) {
258
+ return new Promise((resolve) => setTimeout(resolve, ms));
259
+ }
260
+ };
261
+ var DEFAULT_EXTENSIONS = ["png", "jpg", "jpeg", "webp", "avif", "gif", "svg"];
262
+ var DEFAULT_DIRECTORIES = ["static/images", "images", "assets"];
263
+ var ImageScanner = class {
264
+ config;
265
+ constructor(config) {
266
+ this.config = config;
267
+ }
268
+ // ──────────────────────────────────────────────────────────────────────────
269
+ // SCAN IMAGES
270
+ // ──────────────────────────────────────────────────────────────────────────
271
+ async scan(options = {}) {
272
+ const startTime = Date.now();
273
+ const {
274
+ directories = DEFAULT_DIRECTORIES,
275
+ extensions = DEFAULT_EXTENSIONS,
276
+ includeDimensions = true,
277
+ recursive = true
278
+ } = options;
279
+ const publicDir = path2__default.default.join(this.config.projectRoot, this.config.publicDir);
280
+ const images = [];
281
+ const byCategory = {};
282
+ const byExtension = {};
283
+ let totalSize = 0;
284
+ const patterns = directories.flatMap((dir) => {
285
+ const basePath = path2__default.default.join(publicDir, dir);
286
+ const extPattern = `*.{${extensions.join(",")}}`;
287
+ return recursive ? [path2__default.default.join(basePath, "**", extPattern)] : [path2__default.default.join(basePath, extPattern)];
288
+ });
289
+ for (const pattern of patterns) {
290
+ const files = await glob.glob(pattern, { nodir: true });
291
+ for (const filePath of files) {
292
+ try {
293
+ const imageInfo = await this.processImage(filePath, publicDir, includeDimensions);
294
+ images.push(imageInfo);
295
+ totalSize += imageInfo.size;
296
+ byExtension[imageInfo.extension] = (byExtension[imageInfo.extension] || 0) + 1;
297
+ const category = this.extractCategory(imageInfo.path);
298
+ if (!byCategory[category]) {
299
+ byCategory[category] = [];
300
+ }
301
+ byCategory[category].push(imageInfo);
302
+ } catch (error) {
303
+ console.warn(`Failed to process image: ${filePath}`, error);
304
+ }
305
+ }
306
+ }
307
+ images.sort((a, b) => b.modifiedAt.getTime() - a.modifiedAt.getTime());
308
+ return {
309
+ images,
310
+ total: images.length,
311
+ byCategory,
312
+ byExtension,
313
+ totalSize,
314
+ scanDuration: Date.now() - startTime
315
+ };
316
+ }
317
+ // ──────────────────────────────────────────────────────────────────────────
318
+ // BUILD CATALOG
319
+ // ──────────────────────────────────────────────────────────────────────────
320
+ async buildCatalog(options = {}) {
321
+ const scanResult = await this.scan(options);
322
+ const byId = {};
323
+ for (const image of scanResult.images) {
324
+ byId[image.id] = image;
325
+ }
326
+ return {
327
+ version: "1.0.0",
328
+ generatedAt: /* @__PURE__ */ new Date(),
329
+ count: scanResult.total,
330
+ categories: scanResult.byCategory,
331
+ images: scanResult.images,
332
+ byId
333
+ };
334
+ }
335
+ // ──────────────────────────────────────────────────────────────────────────
336
+ // FIND SPECIFIC IMAGES
337
+ // ──────────────────────────────────────────────────────────────────────────
338
+ async findByCategory(category) {
339
+ const scanResult = await this.scan();
340
+ return scanResult.byCategory[category] || [];
341
+ }
342
+ async findById(id) {
343
+ const scanResult = await this.scan();
344
+ return scanResult.images.find((img) => img.id === id);
345
+ }
346
+ async findByExtension(extension) {
347
+ const scanResult = await this.scan();
348
+ return scanResult.images.filter((img) => img.extension === extension);
349
+ }
350
+ // ──────────────────────────────────────────────────────────────────────────
351
+ // PRIVATE METHODS
352
+ // ──────────────────────────────────────────────────────────────────────────
353
+ async processImage(filePath, publicDir, includeDimensions) {
354
+ const stats = await fs__default.default.stat(filePath);
355
+ const filename = path2__default.default.basename(filePath);
356
+ const extension = path2__default.default.extname(filename).slice(1).toLowerCase();
357
+ const id = path2__default.default.basename(filename, path2__default.default.extname(filename));
358
+ const relativePath = path2__default.default.relative(publicDir, filePath).replace(/\\/g, "/");
359
+ const url = "/" + relativePath;
360
+ let width;
361
+ let height;
362
+ if (includeDimensions && extension !== "svg") {
363
+ try {
364
+ const metadata = await sharp__default.default(filePath).metadata();
365
+ width = metadata.width;
366
+ height = metadata.height;
367
+ } catch {
368
+ }
369
+ }
370
+ return {
371
+ id,
372
+ filename,
373
+ extension,
374
+ path: relativePath,
375
+ url,
376
+ size: stats.size,
377
+ width,
378
+ height,
379
+ createdAt: stats.birthtime,
380
+ modifiedAt: stats.mtime
381
+ };
382
+ }
383
+ extractCategory(imagePath) {
384
+ const parts = imagePath.split("/");
385
+ if (parts.length > 1) {
386
+ return parts[parts.length - 2];
387
+ }
388
+ return "root";
389
+ }
390
+ };
391
+ function formatBytes(bytes) {
392
+ if (bytes === 0) return "0 B";
393
+ const k = 1024;
394
+ const sizes = ["B", "KB", "MB", "GB"];
395
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
396
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
397
+ }
398
+ function formatDuration(ms) {
399
+ if (ms < 1e3) return `${ms}ms`;
400
+ return `${(ms / 1e3).toFixed(2)}s`;
401
+ }
402
+ var ConfigGenerator = class {
403
+ config;
404
+ scanner;
405
+ constructor(config) {
406
+ this.config = config;
407
+ this.scanner = new ImageScanner(config);
408
+ }
409
+ // ──────────────────────────────────────────────────────────────────────────
410
+ // GENERATE IMAGES.TS
411
+ // ──────────────────────────────────────────────────────────────────────────
412
+ async generate() {
413
+ const catalog = await this.scanner.buildCatalog();
414
+ const outputPath = path2__default.default.join(this.config.projectRoot, this.config.configOutputPath);
415
+ await fs__default.default.ensureDir(path2__default.default.dirname(outputPath));
416
+ const content = this.generateTypeScript(catalog);
417
+ await fs__default.default.writeFile(outputPath, content, "utf-8");
418
+ return outputPath;
419
+ }
420
+ // ──────────────────────────────────────────────────────────────────────────
421
+ // TYPESCRIPT GENERATION
422
+ // ──────────────────────────────────────────────────────────────────────────
423
+ generateTypeScript(catalog) {
424
+ const categories = Object.keys(catalog.categories).sort();
425
+ const allIds = catalog.images.map((img) => img.id).sort();
426
+ return `/**
427
+ * Auto-generated image catalog
428
+ * Generated: ${catalog.generatedAt.toISOString()}
429
+ * Total images: ${catalog.count}
430
+ *
431
+ * DO NOT EDIT - This file is auto-generated by @djangocfg/imgai
432
+ * Run \`imgai sync\` to regenerate
433
+ */
434
+
435
+ // ============================================================================
436
+ // TYPES
437
+ // ============================================================================
438
+
439
+ export interface ImageData {
440
+ id: string;
441
+ filename: string;
442
+ url: string;
443
+ path: string;
444
+ extension: string;
445
+ width?: number;
446
+ height?: number;
447
+ size: number;
448
+ }
449
+
450
+ export type ImageId = ${allIds.length > 0 ? allIds.map((id) => `'${id}'`).join(" | ") : "string"};
451
+ export type ImageCategory = ${categories.length > 0 ? categories.map((c) => `'${c}'`).join(" | ") : "string"};
452
+
453
+ // ============================================================================
454
+ // IMAGE CATALOG
455
+ // ============================================================================
456
+
457
+ export const IMAGES: Record<ImageId, ImageData> = {
458
+ ${catalog.images.map((img) => this.generateImageEntry(img)).join(",\n")}
459
+ } as const;
460
+
461
+ // ============================================================================
462
+ // BY CATEGORY
463
+ // ============================================================================
464
+
465
+ export const IMAGES_BY_CATEGORY: Record<ImageCategory, readonly ImageData[]> = {
466
+ ${categories.map((cat) => this.generateCategoryEntry(cat, catalog.categories[cat] || [])).join(",\n")}
467
+ } as const;
468
+
469
+ // ============================================================================
470
+ // HELPERS
471
+ // ============================================================================
472
+
473
+ /** Get image by ID */
474
+ export function getImage(id: ImageId): ImageData | undefined {
475
+ return IMAGES[id];
476
+ }
477
+
478
+ /** Get image URL by ID */
479
+ export function getImageUrl(id: ImageId): string | undefined {
480
+ return IMAGES[id]?.url;
481
+ }
482
+
483
+ /** Get all images in category */
484
+ export function getImagesByCategory(category: ImageCategory): readonly ImageData[] {
485
+ return IMAGES_BY_CATEGORY[category] || [];
486
+ }
487
+
488
+ /** Get all image IDs */
489
+ export function getAllImageIds(): ImageId[] {
490
+ return Object.keys(IMAGES) as ImageId[];
491
+ }
492
+
493
+ /** Get all categories */
494
+ export function getAllCategories(): ImageCategory[] {
495
+ return Object.keys(IMAGES_BY_CATEGORY) as ImageCategory[];
496
+ }
497
+
498
+ /** Check if image exists */
499
+ export function hasImage(id: string): id is ImageId {
500
+ return id in IMAGES;
501
+ }
502
+
503
+ // ============================================================================
504
+ // METADATA
505
+ // ============================================================================
506
+
507
+ export const IMAGE_CATALOG_META = {
508
+ version: '${catalog.version}',
509
+ generatedAt: '${catalog.generatedAt.toISOString()}',
510
+ totalImages: ${catalog.count},
511
+ categories: ${JSON.stringify(categories)},
512
+ } as const;
513
+ `;
514
+ }
515
+ generateImageEntry(img) {
516
+ const data = {
517
+ id: img.id,
518
+ filename: img.filename,
519
+ url: img.url,
520
+ path: img.path,
521
+ extension: img.extension,
522
+ size: img.size
523
+ };
524
+ if (img.width) data.width = img.width;
525
+ if (img.height) data.height = img.height;
526
+ return ` '${img.id}': ${JSON.stringify(data, null, 4).replace(/\n/g, "\n ").replace(/}$/, " }")}`;
527
+ }
528
+ generateCategoryEntry(category, images) {
529
+ const imageRefs = images.map((img) => `IMAGES['${img.id}']`);
530
+ return ` '${category}': [${imageRefs.join(", ")}]`;
531
+ }
532
+ };
533
+ async function generateImagesConfig(config) {
534
+ const generator = new ConfigGenerator(config);
535
+ return generator.generate();
536
+ }
537
+ var ImageAICLI = class {
538
+ config;
539
+ generator;
540
+ scanner;
541
+ configGenerator;
542
+ constructor(config) {
543
+ this.config = config;
544
+ this.generator = new ImageGenerator(config);
545
+ this.scanner = new ImageScanner(config);
546
+ this.configGenerator = new ConfigGenerator(config);
547
+ }
548
+ // ──────────────────────────────────────────────────────────────────────────
549
+ // INTERACTIVE MODE
550
+ // ──────────────────────────────────────────────────────────────────────────
551
+ async interactive() {
552
+ this.printHeader();
553
+ while (true) {
554
+ try {
555
+ const { action } = await inquirer__default.default.prompt([
556
+ {
557
+ type: "list",
558
+ name: "action",
559
+ message: chalk__default.default.cyan("What would you like to do?"),
560
+ choices: [
561
+ { name: `${chalk__default.default.green("\u{1F3A8}")} Generate image from prompt`, value: "generate" },
562
+ { name: `${chalk__default.default.blue("\u2728")} Generate with AI-enhanced prompt`, value: "enhance" },
563
+ { name: `${chalk__default.default.yellow("\u{1F4E6}")} Batch generate images`, value: "batch" },
564
+ new inquirer__default.default.Separator(),
565
+ { name: `${chalk__default.default.magenta("\u{1F50D}")} Scan & catalog images`, value: "scan" },
566
+ { name: `${chalk__default.default.cyan("\u26A1")} Sync images.ts config`, value: "sync" },
567
+ { name: `${chalk__default.default.white("\u{1F4CA}")} Show statistics`, value: "stats" },
568
+ new inquirer__default.default.Separator(),
569
+ { name: `${chalk__default.default.gray("\u2699\uFE0F")} Settings`, value: "settings" },
570
+ { name: `${chalk__default.default.red("\u{1F6AA}")} Exit`, value: "exit" }
571
+ ]
572
+ }
573
+ ]);
574
+ switch (action) {
575
+ case "generate":
576
+ await this.promptGenerate();
577
+ break;
578
+ case "enhance":
579
+ await this.promptEnhance();
580
+ break;
581
+ case "batch":
582
+ await this.promptBatch();
583
+ break;
584
+ case "scan":
585
+ await this.runScan();
586
+ break;
587
+ case "sync":
588
+ await this.runSync();
589
+ break;
590
+ case "stats":
591
+ await this.showStats();
592
+ break;
593
+ case "settings":
594
+ await this.showSettings();
595
+ break;
596
+ case "exit":
597
+ console.log(chalk__default.default.cyan("\n\u{1F44B} Goodbye!\n"));
598
+ return;
599
+ }
600
+ console.log("");
601
+ } catch (error) {
602
+ if (error instanceof Error && error.name === "ExitPromptError") {
603
+ console.log(chalk__default.default.cyan("\n\n\u{1F44B} Goodbye!\n"));
604
+ return;
605
+ }
606
+ throw error;
607
+ }
608
+ }
609
+ }
610
+ // ──────────────────────────────────────────────────────────────────────────
611
+ // GENERATE COMMANDS
612
+ // ──────────────────────────────────────────────────────────────────────────
613
+ async generate(options) {
614
+ const spinner = ora__default.default("Generating image...").start();
615
+ try {
616
+ const result = await this.generator.generate(options);
617
+ if (result.success) {
618
+ spinner.succeed(chalk__default.default.green("Image generated successfully!"));
619
+ console.log(chalk__default.default.gray(` Path: ${result.imagePath}`));
620
+ console.log(chalk__default.default.gray(` URL: ${result.imageUrl}`));
621
+ console.log(chalk__default.default.gray(` Time: ${formatDuration(result.duration || 0)}`));
622
+ } else {
623
+ spinner.fail(chalk__default.default.red(`Generation failed: ${result.error}`));
624
+ }
625
+ } catch (error) {
626
+ spinner.fail(chalk__default.default.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
627
+ }
628
+ }
629
+ async scan(options = {}) {
630
+ const spinner = ora__default.default("Scanning images...").start();
631
+ try {
632
+ const result = await this.scanner.scan(options);
633
+ spinner.succeed(chalk__default.default.green(`Found ${result.total} images`));
634
+ console.log(chalk__default.default.gray(` Total size: ${formatBytes(result.totalSize)}`));
635
+ console.log(chalk__default.default.gray(` Scan time: ${formatDuration(result.scanDuration)}`));
636
+ console.log(chalk__default.default.cyan("\n Categories:"));
637
+ for (const [category, images] of Object.entries(result.byCategory)) {
638
+ console.log(chalk__default.default.gray(` ${category}: ${images.length} images`));
639
+ }
640
+ console.log(chalk__default.default.cyan("\n Extensions:"));
641
+ for (const [ext, count] of Object.entries(result.byExtension)) {
642
+ console.log(chalk__default.default.gray(` .${ext}: ${count} files`));
643
+ }
644
+ } catch (error) {
645
+ spinner.fail(chalk__default.default.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
646
+ }
647
+ }
648
+ async sync() {
649
+ const spinner = ora__default.default("Syncing images.ts config...").start();
650
+ try {
651
+ const outputPath = await this.configGenerator.generate();
652
+ spinner.succeed(chalk__default.default.green("Config synced successfully!"));
653
+ console.log(chalk__default.default.gray(` Output: ${outputPath}`));
654
+ } catch (error) {
655
+ spinner.fail(chalk__default.default.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
656
+ }
657
+ }
658
+ // ──────────────────────────────────────────────────────────────────────────
659
+ // PRIVATE PROMPT METHODS
660
+ // ──────────────────────────────────────────────────────────────────────────
661
+ async promptGenerate() {
662
+ const answers = await inquirer__default.default.prompt([
663
+ {
664
+ type: "input",
665
+ name: "prompt",
666
+ message: "Enter image description:",
667
+ validate: (input) => input.trim() ? true : "Description is required"
668
+ },
669
+ {
670
+ type: "input",
671
+ name: "filename",
672
+ message: "Filename (optional, auto-generated if empty):"
673
+ },
674
+ {
675
+ type: "input",
676
+ name: "category",
677
+ message: "Category/folder (default: general):",
678
+ default: "general"
679
+ }
680
+ ]);
681
+ await this.generate({
682
+ prompt: answers.prompt,
683
+ filename: answers.filename || void 0,
684
+ category: answers.category
685
+ });
686
+ }
687
+ async promptEnhance() {
688
+ const answers = await inquirer__default.default.prompt([
689
+ {
690
+ type: "input",
691
+ name: "description",
692
+ message: "Describe what you want:",
693
+ validate: (input) => input.trim() ? true : "Description is required"
694
+ },
695
+ {
696
+ type: "input",
697
+ name: "context",
698
+ message: "Additional context (optional):"
699
+ },
700
+ {
701
+ type: "input",
702
+ name: "category",
703
+ message: "Category/folder (default: general):",
704
+ default: "general"
705
+ }
706
+ ]);
707
+ const spinner = ora__default.default("Enhancing prompt with AI...").start();
708
+ try {
709
+ const enhanced = await this.generator.enhancePrompt(
710
+ answers.description,
711
+ answers.context || void 0
712
+ );
713
+ spinner.succeed("Prompt enhanced!");
714
+ console.log(chalk__default.default.cyan("\n Enhanced prompt:"));
715
+ console.log(chalk__default.default.gray(` ${enhanced.prompt}
716
+ `));
717
+ console.log(chalk__default.default.gray(` Filename: ${enhanced.filename}`));
718
+ console.log(chalk__default.default.gray(` Caption: ${enhanced.caption}
719
+ `));
720
+ const { confirm } = await inquirer__default.default.prompt([
721
+ {
722
+ type: "confirm",
723
+ name: "confirm",
724
+ message: "Generate image with this prompt?",
725
+ default: true
726
+ }
727
+ ]);
728
+ if (confirm) {
729
+ await this.generate({
730
+ prompt: enhanced.prompt,
731
+ filename: enhanced.filename,
732
+ category: answers.category,
733
+ metadata: { caption: enhanced.caption }
734
+ });
735
+ }
736
+ } catch (error) {
737
+ spinner.fail(chalk__default.default.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
738
+ }
739
+ }
740
+ async promptBatch() {
741
+ console.log(chalk__default.default.cyan("\n Enter prompts (one per line, empty line to finish):"));
742
+ const prompts = [];
743
+ let lineNumber = 1;
744
+ while (true) {
745
+ const { prompt } = await inquirer__default.default.prompt([
746
+ {
747
+ type: "input",
748
+ name: "prompt",
749
+ message: ` ${lineNumber}.`
750
+ }
751
+ ]);
752
+ if (!prompt.trim()) break;
753
+ prompts.push(prompt);
754
+ lineNumber++;
755
+ }
756
+ if (prompts.length === 0) {
757
+ console.log(chalk__default.default.yellow(" No prompts entered."));
758
+ return;
759
+ }
760
+ const { category, concurrency } = await inquirer__default.default.prompt([
761
+ {
762
+ type: "input",
763
+ name: "category",
764
+ message: "Category for all images:",
765
+ default: "batch"
766
+ },
767
+ {
768
+ type: "list",
769
+ name: "concurrency",
770
+ message: "Concurrent workers:",
771
+ choices: [
772
+ { name: "1 (slow, safe)", value: 1 },
773
+ { name: "2 (balanced)", value: 2 },
774
+ { name: "3 (fast)", value: 3 }
775
+ ],
776
+ default: 2
777
+ }
778
+ ]);
779
+ console.log(chalk__default.default.cyan(`
780
+ Generating ${prompts.length} images...
781
+ `));
782
+ const result = await this.generator.generateBatch({
783
+ items: prompts.map((prompt, i) => ({
784
+ prompt,
785
+ category,
786
+ filename: `batch-${Date.now()}-${i + 1}`
787
+ })),
788
+ concurrency,
789
+ onProgress: (current, total, res) => {
790
+ const status = res.success ? chalk__default.default.green("\u2713") : chalk__default.default.red("\u2717");
791
+ console.log(` ${status} ${current}/${total}`);
792
+ }
793
+ });
794
+ console.log(chalk__default.default.cyan("\n Batch complete!"));
795
+ console.log(chalk__default.default.gray(` Success: ${result.success}`));
796
+ console.log(chalk__default.default.gray(` Failed: ${result.failed}`));
797
+ console.log(chalk__default.default.gray(` Time: ${formatDuration(result.duration)}`));
798
+ }
799
+ async runScan() {
800
+ await this.scan();
801
+ }
802
+ async runSync() {
803
+ await this.sync();
804
+ }
805
+ async showStats() {
806
+ const spinner = ora__default.default("Gathering statistics...").start();
807
+ try {
808
+ const result = await this.scanner.scan();
809
+ spinner.stop();
810
+ console.log(chalk__default.default.cyan("\n \u{1F4CA} Image Statistics\n"));
811
+ console.log(chalk__default.default.white(` Total images: ${result.total}`));
812
+ console.log(chalk__default.default.white(` Total size: ${formatBytes(result.totalSize)}`));
813
+ console.log(chalk__default.default.white(` Categories: ${Object.keys(result.byCategory).length}`));
814
+ console.log(chalk__default.default.cyan("\n By Category:"));
815
+ for (const [category, images] of Object.entries(result.byCategory).sort((a, b) => b[1].length - a[1].length)) {
816
+ const size = images.reduce((sum, img) => sum + img.size, 0);
817
+ console.log(chalk__default.default.gray(` ${category.padEnd(20)} ${images.length.toString().padStart(4)} images ${formatBytes(size).padStart(10)}`));
818
+ }
819
+ console.log(chalk__default.default.cyan("\n By Extension:"));
820
+ for (const [ext, count] of Object.entries(result.byExtension).sort((a, b) => b[1] - a[1])) {
821
+ console.log(chalk__default.default.gray(` .${ext.padEnd(6)} ${count} files`));
822
+ }
823
+ } catch (error) {
824
+ spinner.fail(chalk__default.default.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
825
+ }
826
+ }
827
+ async showSettings() {
828
+ console.log(chalk__default.default.cyan("\n \u2699\uFE0F Current Settings\n"));
829
+ console.log(chalk__default.default.gray(` Provider: ${this.config.provider}`));
830
+ console.log(chalk__default.default.gray(` Size: ${this.config.size}`));
831
+ console.log(chalk__default.default.gray(` Quality: ${this.config.quality}`));
832
+ console.log(chalk__default.default.gray(` Output dir: ${this.config.outputDir}`));
833
+ console.log(chalk__default.default.gray(` Config path: ${this.config.configOutputPath}`));
834
+ if (this.config.resize) {
835
+ console.log(chalk__default.default.cyan("\n Resize Settings:"));
836
+ console.log(chalk__default.default.gray(` Width: ${this.config.resize.width || "auto"}`));
837
+ console.log(chalk__default.default.gray(` Height: ${this.config.resize.height || "auto"}`));
838
+ console.log(chalk__default.default.gray(` Format: ${this.config.resize.format}`));
839
+ console.log(chalk__default.default.gray(` Quality: ${this.config.resize.quality}`));
840
+ }
841
+ if (this.config.style) {
842
+ console.log(chalk__default.default.cyan("\n Style:"));
843
+ console.log(chalk__default.default.gray(` ${this.config.style.substring(0, 100)}...`));
844
+ }
845
+ }
846
+ // ──────────────────────────────────────────────────────────────────────────
847
+ // UI HELPERS
848
+ // ──────────────────────────────────────────────────────────────────────────
849
+ printHeader() {
850
+ console.log("");
851
+ console.log(chalk__default.default.cyan(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
852
+ console.log(chalk__default.default.cyan(" \u2551") + chalk__default.default.white.bold(" \u{1F3A8} IMGAI - Image Generator ") + chalk__default.default.cyan("\u2551"));
853
+ console.log(chalk__default.default.cyan(" \u2551") + chalk__default.default.gray(" AI-powered image generation & sync ") + chalk__default.default.cyan("\u2551"));
854
+ console.log(chalk__default.default.cyan(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
855
+ console.log("");
856
+ }
857
+ };
858
+
859
+ // src/index.ts
860
+ function createImageAI(options) {
861
+ const { projectRoot, ...rest } = options;
862
+ const config = {
863
+ provider: rest.provider || "openai",
864
+ size: rest.size || "1792x1024",
865
+ quality: rest.quality || "standard",
866
+ outputDir: rest.outputDir || "public/static/images",
867
+ projectRoot,
868
+ srcDir: rest.srcDir || "src",
869
+ publicDir: rest.publicDir || "public",
870
+ configOutputPath: rest.configOutputPath || "src/core/images.ts",
871
+ ...rest
872
+ };
873
+ return {
874
+ generator: new ImageGenerator(config),
875
+ scanner: new ImageScanner(config),
876
+ configGenerator: new ConfigGenerator(config),
877
+ config
878
+ };
879
+ }
880
+ async function generateImage(prompt, options) {
881
+ const { generator } = createImageAI(options);
882
+ return generator.generate({ prompt });
883
+ }
884
+ async function scanImages(options) {
885
+ const { scanner } = createImageAI(options);
886
+ return scanner.scan();
887
+ }
888
+ async function syncImagesConfig(options) {
889
+ const { configGenerator } = createImageAI(options);
890
+ return configGenerator.generate();
891
+ }
892
+
893
+ exports.ConfigGenerator = ConfigGenerator;
894
+ exports.ImageAICLI = ImageAICLI;
895
+ exports.ImageGenerator = ImageGenerator;
896
+ exports.ImageScanner = ImageScanner;
897
+ exports.createImageAI = createImageAI;
898
+ exports.formatBytes = formatBytes;
899
+ exports.formatDuration = formatDuration;
900
+ exports.generateImage = generateImage;
901
+ exports.generateImagesConfig = generateImagesConfig;
902
+ exports.scanImages = scanImages;
903
+ exports.syncImagesConfig = syncImagesConfig;
904
+ //# sourceMappingURL=index.cjs.map
905
+ //# sourceMappingURL=index.cjs.map