@nogataka/imgen 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,4 +1,24 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ AzureChatClient,
4
+ AzureImageClient,
5
+ DEFAULT_CONFIG,
6
+ LANGUAGE_DESCRIPTIONS,
7
+ Logger,
8
+ deletePreset,
9
+ fileExists,
10
+ getAzureConfig,
11
+ getConfigPath,
12
+ getPreset,
13
+ getPresetsPath,
14
+ listAllPresets,
15
+ loadConfig,
16
+ loadContextFile,
17
+ readImageFile,
18
+ saveConfig,
19
+ saveFileWithUniqueNameIfExists,
20
+ savePreset
21
+ } from "./chunk-HQT7ZTCY.js";
2
22
 
3
23
  // src/commands/index.ts
4
24
  import { Command as Command8 } from "commander";
@@ -6,68 +26,6 @@ import { Command as Command8 } from "commander";
6
26
  // src/commands/configure.ts
7
27
  import { Command } from "commander";
8
28
  import inquirer from "inquirer";
9
-
10
- // src/lang.ts
11
- var LANGUAGE_DESCRIPTIONS = {
12
- ja: "\u65E5\u672C\u8A9E",
13
- en: "\u82F1\u8A9E",
14
- zh: "\u4E2D\u56FD\u8A9E",
15
- ko: "\u97D3\u56FD\u8A9E",
16
- es: "\u30B9\u30DA\u30A4\u30F3\u8A9E",
17
- fr: "\u30D5\u30E9\u30F3\u30B9\u8A9E",
18
- de: "\u30C9\u30A4\u30C4\u8A9E",
19
- it: "\u30A4\u30BF\u30EA\u30A2\u8A9E",
20
- ru: "\u30ED\u30B7\u30A2\u8A9E",
21
- vi: "\u30D9\u30C8\u30CA\u30E0\u8A9E"
22
- };
23
-
24
- // src/utils/config.ts
25
- import * as fs from "fs/promises";
26
- import * as os from "os";
27
- import * as path from "path";
28
- var DEFAULT_CONFIG = {
29
- defaultLanguage: "ja",
30
- defaultImageSize: "1024x1024",
31
- defaultImageQuality: "high",
32
- defaultImageFormat: "png",
33
- logLevel: "info"
34
- };
35
- function getConfigDir() {
36
- return path.join(os.homedir(), ".imgen");
37
- }
38
- function getConfigPath() {
39
- return path.join(getConfigDir(), "config.json");
40
- }
41
- async function loadConfig() {
42
- try {
43
- const text = await fs.readFile(getConfigPath(), "utf-8");
44
- return JSON.parse(text);
45
- } catch {
46
- return null;
47
- }
48
- }
49
- async function saveConfig(config) {
50
- const configPath = getConfigPath();
51
- await fs.mkdir(path.dirname(configPath), { recursive: true });
52
- await fs.writeFile(configPath, JSON.stringify(config, null, 2));
53
- }
54
- async function getAzureConfig() {
55
- const config = await loadConfig();
56
- const endpoint = process.env.AZURE_OPENAI_ENDPOINT || config?.azureEndpoint;
57
- const apiKey = process.env.AZURE_OPENAI_API_KEY || config?.azureApiKey;
58
- const deploymentName = process.env.AZURE_OPENAI_DEPLOYMENT_NAME || config?.azureDeploymentName;
59
- const imageDeploymentName = process.env.AZURE_OPENAI_DEPLOYMENT_NAME_IMAGE || config?.azureImageDeploymentName;
60
- const apiVersion = process.env.AZURE_OPENAI_API_VERSION || config?.azureApiVersion || "2024-02-15-preview";
61
- const imageApiVersion = process.env.AZURE_OPENAI_IMAGE_API_VERSION || config?.azureImageApiVersion || "2025-04-01-preview";
62
- if (!endpoint || !apiKey || !deploymentName || !imageDeploymentName) {
63
- throw new Error(
64
- "Azure OpenAI \u306E\u8A2D\u5B9A\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002`imgen configure` \u30B3\u30DE\u30F3\u30C9\u3067\u8A2D\u5B9A\u3059\u308B\u304B\u3001\u74B0\u5883\u5909\u6570\u3092\u8A2D\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
65
- );
66
- }
67
- return { endpoint, apiKey, deploymentName, imageDeploymentName, apiVersion, imageApiVersion };
68
- }
69
-
70
- // src/commands/configure.ts
71
29
  function configureCommand() {
72
30
  return new Command("configure").description("API\u8A2D\u5B9A\u3092\u884C\u3044\u307E\u3059").option("--show", "\u73FE\u5728\u306E\u8A2D\u5B9A\u3092\u8868\u793A").option("--reset", "\u8A2D\u5B9A\u3092\u30EA\u30BB\u30C3\u30C8").action(async (options) => {
73
31
  try {
@@ -291,419 +249,10 @@ async function configureDefaults() {
291
249
  import { Command as Command5 } from "commander";
292
250
 
293
251
  // src/commands/image/edit.ts
294
- import * as fs4 from "fs/promises";
295
- import * as path3 from "path";
252
+ import * as fs from "fs/promises";
253
+ import * as path from "path";
296
254
  import { Command as Command2, Option } from "commander";
297
255
 
298
- // src/utils/file.ts
299
- import * as fs2 from "fs/promises";
300
- async function generateUniqueFilePath(outputPath, maxRetries = 3) {
301
- let finalPath = outputPath;
302
- let retryCount = 0;
303
- while (retryCount < maxRetries) {
304
- try {
305
- await fs2.stat(finalPath);
306
- const baseName = finalPath.slice(0, finalPath.lastIndexOf("."));
307
- const ext = finalPath.slice(finalPath.lastIndexOf("."));
308
- const rand = Math.floor(Math.random() * 1e4).toString().padStart(4, "0");
309
- finalPath = `${baseName}-${rand}${ext}`;
310
- retryCount++;
311
- } catch (error) {
312
- if (error.code === "ENOENT") {
313
- return finalPath;
314
- }
315
- throw error;
316
- }
317
- }
318
- throw new Error(
319
- `\u30D5\u30A1\u30A4\u30EB\u540D\u306E\u751F\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002${maxRetries}\u56DE\u8A66\u884C\u3057\u307E\u3057\u305F\u304C\u3001\u3059\u3079\u3066\u65E2\u5B58\u306E\u30D5\u30A1\u30A4\u30EB\u540D\u3068\u885D\u7A81\u3057\u3066\u3044\u307E\u3059\u3002`
320
- );
321
- }
322
- async function saveFileWithUniqueNameIfExists(outputPath, data, maxRetries = 3) {
323
- const finalPath = await generateUniqueFilePath(outputPath, maxRetries);
324
- await fs2.writeFile(finalPath, data);
325
- return finalPath;
326
- }
327
- async function loadContextFile(contextPath) {
328
- if (!contextPath) return "";
329
- try {
330
- return await fs2.readFile(contextPath, "utf-8");
331
- } catch (error) {
332
- if (error.code === "ENOENT") {
333
- throw new Error(`\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u30D5\u30A1\u30A4\u30EB\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: ${contextPath}`);
334
- }
335
- if (error instanceof Error) {
336
- throw new Error(`\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u30D5\u30A1\u30A4\u30EB\u306E\u8AAD\u307F\u8FBC\u307F\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${error.message}`);
337
- }
338
- throw new Error(`\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u30D5\u30A1\u30A4\u30EB\u306E\u8AAD\u307F\u8FBC\u307F\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${String(error)}`);
339
- }
340
- }
341
- async function fileExists(filePath) {
342
- try {
343
- await fs2.access(filePath);
344
- return true;
345
- } catch {
346
- return false;
347
- }
348
- }
349
-
350
- // src/utils/azure-chat.ts
351
- import { AzureOpenAI } from "openai";
352
-
353
- // src/utils/logger.ts
354
- import * as fs3 from "fs/promises";
355
- import * as path2 from "path";
356
- import * as os2 from "os";
357
- import { format } from "date-fns";
358
- var Logger = class _Logger {
359
- constructor(name) {
360
- this.name = name;
361
- const home = os2.homedir();
362
- this.logDir = path2.join(home, ".imgen", "logs");
363
- this.currentLogFile = this.generateLogFileName();
364
- }
365
- static instances = /* @__PURE__ */ new Map();
366
- static globalConfig = {
367
- destination: "CONSOLE" /* CONSOLE */,
368
- minLevel: "INFO" /* INFO */
369
- };
370
- static currentContext = "default";
371
- logDir;
372
- currentLogFile;
373
- static setGlobalConfig(config) {
374
- _Logger.globalConfig = { ..._Logger.globalConfig, ...config };
375
- }
376
- static setContext(name) {
377
- _Logger.currentContext = name;
378
- }
379
- static getInstance(options) {
380
- const { name } = options;
381
- if (!_Logger.instances.has(name)) {
382
- _Logger.instances.set(name, new _Logger(name));
383
- }
384
- return _Logger.instances.get(name);
385
- }
386
- generateLogFileName() {
387
- return path2.join(this.logDir, `${this.name}-${format(/* @__PURE__ */ new Date(), "yyyy-MM-dd")}.log`);
388
- }
389
- async ensureLogDirectory() {
390
- await fs3.mkdir(this.logDir, { recursive: true });
391
- }
392
- formatLogEntry(level, message, data) {
393
- return { timestamp: (/* @__PURE__ */ new Date()).toISOString(), level, message, data };
394
- }
395
- shouldLog(level) {
396
- const priority = {
397
- ["DEBUG" /* DEBUG */]: 0,
398
- ["INFO" /* INFO */]: 1,
399
- ["WARN" /* WARN */]: 2,
400
- ["ERROR" /* ERROR */]: 3
401
- };
402
- return priority[level] >= priority[_Logger.globalConfig.minLevel];
403
- }
404
- async writeLog(entry) {
405
- if (!this.shouldLog(entry.level)) return;
406
- const { destination } = _Logger.globalConfig;
407
- if (destination === "CONSOLE" /* CONSOLE */ || destination === "BOTH" /* BOTH */) {
408
- this.writeToConsole(entry);
409
- }
410
- if (destination === "FILE" /* FILE */ || destination === "BOTH" /* BOTH */) {
411
- await this.writeToFile(entry);
412
- }
413
- }
414
- writeToConsole(entry) {
415
- const ts = entry.timestamp.replace("T", " ").replace(/\.\d+Z$/, "");
416
- const dataStr = entry.data ? ` ${JSON.stringify(entry.data)}` : "";
417
- const msg = `[${ts}] [${this.name}] [${entry.level}] ${entry.message}${dataStr}`;
418
- switch (entry.level) {
419
- case "DEBUG" /* DEBUG */:
420
- console.debug(msg);
421
- break;
422
- case "INFO" /* INFO */:
423
- console.info(msg);
424
- break;
425
- case "WARN" /* WARN */:
426
- console.warn(msg);
427
- break;
428
- case "ERROR" /* ERROR */:
429
- console.error(msg);
430
- break;
431
- }
432
- }
433
- async writeToFile(entry) {
434
- await this.ensureLogDirectory();
435
- try {
436
- await fs3.appendFile(this.currentLogFile, JSON.stringify(entry) + "\n");
437
- } catch (error) {
438
- console.error(
439
- `\u30ED\u30B0\u306E\u66F8\u304D\u8FBC\u307F\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${error instanceof Error ? error.message : String(error)}`
440
- );
441
- }
442
- }
443
- debug(message, data) {
444
- return this.writeLog(this.formatLogEntry("DEBUG" /* DEBUG */, message, data));
445
- }
446
- info(message, data) {
447
- return this.writeLog(this.formatLogEntry("INFO" /* INFO */, message, data));
448
- }
449
- warn(message, data) {
450
- return this.writeLog(this.formatLogEntry("WARN" /* WARN */, message, data));
451
- }
452
- error(message, data) {
453
- return this.writeLog(this.formatLogEntry("ERROR" /* ERROR */, message, data));
454
- }
455
- static debug(message, data) {
456
- return _Logger.getInstance({ name: _Logger.currentContext }).debug(message, data);
457
- }
458
- static info(message, data) {
459
- return _Logger.getInstance({ name: _Logger.currentContext }).info(message, data);
460
- }
461
- static warn(message, data) {
462
- return _Logger.getInstance({ name: _Logger.currentContext }).warn(message, data);
463
- }
464
- static error(message, data) {
465
- return _Logger.getInstance({ name: _Logger.currentContext }).error(message, data);
466
- }
467
- getLatestLogFilePath() {
468
- return this.currentLogFile;
469
- }
470
- async getLogEntries(minLevel = "INFO" /* INFO */, maxEntries = 100) {
471
- try {
472
- await this.ensureLogDirectory();
473
- const content = await fs3.readFile(this.currentLogFile, "utf-8");
474
- const lines = content.trim().split("\n");
475
- const priority = {
476
- ["DEBUG" /* DEBUG */]: 0,
477
- ["INFO" /* INFO */]: 1,
478
- ["WARN" /* WARN */]: 2,
479
- ["ERROR" /* ERROR */]: 3
480
- };
481
- const entries = [];
482
- for (let i = lines.length - 1; i >= 0 && entries.length < maxEntries; i--) {
483
- try {
484
- const entry = JSON.parse(lines[i]);
485
- if (priority[entry.level] >= priority[minLevel]) entries.unshift(entry);
486
- } catch {
487
- continue;
488
- }
489
- }
490
- return entries;
491
- } catch (error) {
492
- if (error.code === "ENOENT") return [];
493
- throw error;
494
- }
495
- }
496
- };
497
-
498
- // src/utils/azure-chat.ts
499
- var AzureChatClient = class {
500
- client;
501
- deploymentName;
502
- logger;
503
- constructor(config) {
504
- this.client = new AzureOpenAI({
505
- endpoint: config.endpoint,
506
- apiKey: config.apiKey,
507
- apiVersion: config.apiVersion,
508
- deployment: config.deploymentName
509
- });
510
- this.deploymentName = config.deploymentName;
511
- this.logger = Logger.getInstance({ name: "azure-chat" });
512
- }
513
- /**
514
- * Generates a detailed image-generation prompt from a short theme description.
515
- * Optionally accepts additional context to guide prompt generation.
516
- */
517
- async generatePrompt(theme, context = "") {
518
- if (!theme) throw new Error("\u30C6\u30FC\u30DE\u304C\u7A7A\u3067\u3059");
519
- const prompt = `
520
- Generate a detailed image generation prompt based on the following information.
521
-
522
- Theme: ${theme}
523
- ${context ? `Context:
524
- ${context}
525
- ` : ""}
526
- Please generate a prompt that meets the following criteria:
527
- 1. Include specific and detailed descriptions
528
- 2. Clearly specify the image style and atmosphere
529
- 3. Include all necessary elements
530
- 4. Output in English
531
- 5. Focus on visual elements and composition
532
- 6. Include lighting and color descriptions
533
- 7. Specify the mood and emotional tone
534
- 8. Limit the output to approximately 1500 characters
535
-
536
- Prompt:
537
- `;
538
- try {
539
- const response = await this.client.chat.completions.create({
540
- model: this.deploymentName,
541
- messages: [{ role: "user", content: prompt }]
542
- });
543
- return response.choices[0]?.message?.content ?? "";
544
- } catch (error) {
545
- this.logger.error("\u30D7\u30ED\u30F3\u30D7\u30C8\u751F\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F", { error });
546
- throw new Error("\u30D7\u30ED\u30F3\u30D7\u30C8\u306E\u751F\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F");
547
- }
548
- }
549
- /**
550
- * Generates a sanitized file name (lowercase alphanumeric + hyphens only) from a theme.
551
- */
552
- async generateFileName(theme, maxLength = 40) {
553
- if (!theme) throw new Error("\u30C6\u30FC\u30DE\u304C\u7A7A\u3067\u3059");
554
- try {
555
- const response = await this.client.chat.completions.create({
556
- model: this.deploymentName,
557
- messages: [
558
- {
559
- role: "user",
560
- content: `\u4EE5\u4E0B\u306E\u30C6\u30FC\u30DE\u304B\u3089\u753B\u50CF\u306E\u30D5\u30A1\u30A4\u30EB\u540D\u3092\u751F\u6210\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u82F1\u5C0F\u6587\u5B57\u3068\u30CF\u30A4\u30D5\u30F3\u306E\u307F\u3001${maxLength}\u6587\u5B57\u4EE5\u5185\u3002\u62E1\u5F35\u5B50\u306A\u3057\u3002
561
-
562
- \u30C6\u30FC\u30DE: ${theme}
563
-
564
- \u30D5\u30A1\u30A4\u30EB\u540D:`
565
- }
566
- ]
567
- });
568
- let fileName = (response.choices[0]?.message?.content ?? "").trim();
569
- fileName = fileName.toLowerCase().replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
570
- if (fileName.length > maxLength) fileName = fileName.substring(0, maxLength);
571
- return fileName || "image";
572
- } catch (error) {
573
- this.logger.error("\u30D5\u30A1\u30A4\u30EB\u540D\u751F\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F", { error });
574
- throw new Error("\u30D5\u30A1\u30A4\u30EB\u540D\u306E\u751F\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F");
575
- }
576
- }
577
- /**
578
- * Generates a detailed explanation of an image using multimodal (vision) input.
579
- * The explanation language is controlled by the `lang` parameter.
580
- */
581
- async generateExplanation(imageData, lang = "ja", context) {
582
- try {
583
- const response = await this.client.chat.completions.create({
584
- model: this.deploymentName,
585
- messages: [
586
- {
587
- role: "user",
588
- content: [
589
- {
590
- type: "image_url",
591
- image_url: { url: `data:${imageData.mimeType};base64,${imageData.data}` }
592
- },
593
- {
594
- type: "text",
595
- text: `\u3053\u306E\u753B\u50CF\u306B\u3064\u3044\u3066\u3001${lang}\u3067\u8A73\u7D30\u306A\u8AAC\u660E\u3092\u751F\u6210\u3057\u3066\u304F\u3060\u3055\u3044\u3002${context ? `
596
-
597
- \u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u60C5\u5831:
598
- ${context}` : ""}`
599
- }
600
- ]
601
- }
602
- ]
603
- });
604
- return response.choices[0]?.message?.content ?? "";
605
- } catch (error) {
606
- this.logger.error("\u753B\u50CF\u8AAC\u660E\u306E\u751F\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F", { error });
607
- throw new Error("\u753B\u50CF\u306E\u8AAC\u660E\u751F\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F");
608
- }
609
- }
610
- };
611
-
612
- // src/utils/azure-image.ts
613
- import { AzureOpenAI as AzureOpenAI2 } from "openai";
614
- var AzureImageClient = class {
615
- client;
616
- config;
617
- logger;
618
- constructor(config) {
619
- this.config = config;
620
- this.client = new AzureOpenAI2({
621
- endpoint: config.endpoint,
622
- apiKey: config.apiKey,
623
- apiVersion: config.imageApiVersion,
624
- deployment: config.imageDeploymentName
625
- });
626
- this.logger = Logger.getInstance({ name: "azure-image" });
627
- }
628
- /**
629
- * Generates an image from a text prompt using the Azure OpenAI SDK.
630
- * Returns raw image bytes as a Uint8Array.
631
- */
632
- async generateImage(prompt, options) {
633
- const { size = "1024x1024", quality = "high" } = options;
634
- this.logger.debug("\u753B\u50CF\u751F\u6210\u30EA\u30AF\u30A8\u30B9\u30C8", { prompt: prompt.substring(0, 100), size, quality });
635
- try {
636
- const response = await this.client.images.generate({
637
- model: this.config.imageDeploymentName,
638
- prompt,
639
- n: 1,
640
- size,
641
- quality,
642
- output_format: "png"
643
- });
644
- if (!response.data || response.data.length === 0 || !response.data[0].b64_json) {
645
- throw new Error("\u753B\u50CF\u30C7\u30FC\u30BF\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093");
646
- }
647
- const b64 = response.data[0].b64_json;
648
- const binary = atob(b64);
649
- const bytes = new Uint8Array(binary.length);
650
- for (let i = 0; i < binary.length; i++) {
651
- bytes[i] = binary.charCodeAt(i);
652
- }
653
- return bytes;
654
- } catch (error) {
655
- if (error instanceof Error && error.message === "\u753B\u50CF\u30C7\u30FC\u30BF\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093") throw error;
656
- this.logger.error("\u753B\u50CF\u751F\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F", { error });
657
- throw new Error(
658
- `\u753B\u50CF\u751F\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${error instanceof Error ? error.message : String(error)}`
659
- );
660
- }
661
- }
662
- /**
663
- * Edits an existing image using the Azure OpenAI REST API.
664
- * Uses fetch + FormData because the SDK's image editing support is unreliable.
665
- * Returns raw image bytes as a Uint8Array.
666
- */
667
- async editImage(imageBuffer, prompt, options = {}) {
668
- const { size = "1024x1024" } = options;
669
- this.logger.debug("\u753B\u50CF\u7DE8\u96C6\u30EA\u30AF\u30A8\u30B9\u30C8 (REST API)", { prompt: prompt.substring(0, 100), size });
670
- const url = `${this.config.endpoint}/openai/deployments/${this.config.imageDeploymentName}/images/edits?api-version=${this.config.imageApiVersion}`;
671
- const blob = new Blob([imageBuffer], { type: "image/png" });
672
- const formData = new FormData();
673
- formData.append("image", blob, "image.png");
674
- formData.append("prompt", prompt);
675
- formData.append("size", size);
676
- try {
677
- const response = await fetch(url, {
678
- method: "POST",
679
- headers: { "api-key": this.config.apiKey },
680
- body: formData
681
- });
682
- if (!response.ok) {
683
- const errorText = await response.text();
684
- throw new Error(`Azure API error (${response.status}): ${errorText}`);
685
- }
686
- const json = await response.json();
687
- if (!json.data || json.data.length === 0 || !json.data[0].b64_json) {
688
- throw new Error("\u753B\u50CF\u30C7\u30FC\u30BF\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093");
689
- }
690
- const b64 = json.data[0].b64_json;
691
- const binary = atob(b64);
692
- const bytes = new Uint8Array(binary.length);
693
- for (let i = 0; i < binary.length; i++) {
694
- bytes[i] = binary.charCodeAt(i);
695
- }
696
- return bytes;
697
- } catch (error) {
698
- if (error instanceof Error && error.message === "\u753B\u50CF\u30C7\u30FC\u30BF\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093") throw error;
699
- this.logger.error("\u753B\u50CF\u7DE8\u96C6\u306B\u5931\u6557\u3057\u307E\u3057\u305F", { error });
700
- throw new Error(
701
- `\u753B\u50CF\u7DE8\u96C6\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${error instanceof Error ? error.message : String(error)}`
702
- );
703
- }
704
- }
705
- };
706
-
707
256
  // src/utils/output.ts
708
257
  function createSuccessOutput(command, result) {
709
258
  return { success: true, command, result };
@@ -727,7 +276,7 @@ function imageEditCommand() {
727
276
  ).addOption(
728
277
  new Option("-s, --size <size>", "\u51FA\u529B\u30B5\u30A4\u30BA").choices(["1024x1024", "1536x1024", "1024x1536"]).default("1024x1024")
729
278
  ).option("--json", "JSON\u5F62\u5F0F\u3067\u51FA\u529B", false).option("--dry-run", "\u5B9F\u884C\u305B\u305A\u306B\u8A2D\u5B9A\u3092\u78BA\u8A8D", false).action(async (filePath, prompt, options) => {
730
- const ext = path3.extname(filePath).toLowerCase();
279
+ const ext = path.extname(filePath).toLowerCase();
731
280
  if (!SUPPORTED_EXTENSIONS.includes(ext)) {
732
281
  const msg = `\u30B5\u30DD\u30FC\u30C8\u3055\u308C\u3066\u3044\u306A\u3044\u30D5\u30A1\u30A4\u30EB\u5F62\u5F0F\u3067\u3059: ${ext}`;
733
282
  if (options.json) {
@@ -776,18 +325,18 @@ function imageEditCommand() {
776
325
  const azureConfig = await getAzureConfig();
777
326
  const chatClient = new AzureChatClient(azureConfig);
778
327
  const imageClient = new AzureImageClient(azureConfig);
779
- const imageBuffer = Buffer.from(await fs4.readFile(filePath));
328
+ const imageBuffer = Buffer.from(await fs.readFile(filePath));
780
329
  const editedData = await imageClient.editImage(imageBuffer, prompt, {
781
330
  size: effectiveSize
782
331
  });
783
- const baseName = path3.basename(filePath, ext);
332
+ const baseName = path.basename(filePath, ext);
784
333
  const fileName = await chatClient.generateFileName(`${baseName} edited`);
785
334
  let outputPath;
786
335
  if (options.output) {
787
336
  try {
788
- const stat4 = await fs4.stat(options.output);
789
- if (stat4.isDirectory()) {
790
- outputPath = path3.join(options.output, `${fileName}.${effectiveFormat}`);
337
+ const stat3 = await fs.stat(options.output);
338
+ if (stat3.isDirectory()) {
339
+ outputPath = path.join(options.output, `${fileName}.${effectiveFormat}`);
791
340
  } else {
792
341
  outputPath = options.output;
793
342
  }
@@ -837,41 +386,8 @@ function imageEditCommand() {
837
386
  }
838
387
 
839
388
  // src/commands/image/explain.ts
840
- import * as fs6 from "fs/promises";
389
+ import * as fs2 from "fs/promises";
841
390
  import { Command as Command3 } from "commander";
842
-
843
- // src/utils/image.ts
844
- import * as fs5 from "fs/promises";
845
- async function readImageFile(filePath) {
846
- try {
847
- const buffer = await fs5.readFile(filePath);
848
- return {
849
- data: buffer.toString("base64"),
850
- mimeType: getMimeType(filePath)
851
- };
852
- } catch (error) {
853
- if (error instanceof Error && error.message.startsWith("\u30B5\u30DD\u30FC\u30C8\u3055\u308C\u3066\u3044\u306A\u3044")) {
854
- throw error;
855
- }
856
- throw new Error(
857
- `\u753B\u50CF\u30D5\u30A1\u30A4\u30EB\u306E\u8AAD\u307F\u8FBC\u307F\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${error instanceof Error ? error.message : String(error)}`
858
- );
859
- }
860
- }
861
- function getMimeType(filePath) {
862
- const ext = filePath.toLowerCase().split(".").pop();
863
- const map = {
864
- jpg: "image/jpeg",
865
- jpeg: "image/jpeg",
866
- png: "image/png",
867
- gif: "image/gif",
868
- webp: "image/webp"
869
- };
870
- if (ext && map[ext]) return map[ext];
871
- throw new Error(`\u30B5\u30DD\u30FC\u30C8\u3055\u308C\u3066\u3044\u306A\u3044\u30D5\u30A1\u30A4\u30EB\u5F62\u5F0F\u3067\u3059: .${ext}`);
872
- }
873
-
874
- // src/commands/image/explain.ts
875
391
  function imageExplainCommand() {
876
392
  return new Command3("explain").description("\u753B\u50CF\u306E\u5185\u5BB9\u3092\u8AAC\u660E").argument("<file>", "\u753B\u50CF\u30D5\u30A1\u30A4\u30EB\u306E\u30D1\u30B9").option(
877
393
  "-l, --lang <lang>",
@@ -889,7 +405,7 @@ function imageExplainCommand() {
889
405
  let context;
890
406
  if (options.context) {
891
407
  if (options.context.endsWith(".md") || options.context.endsWith(".txt")) {
892
- context = await fs6.readFile(options.context, "utf-8");
408
+ context = await fs2.readFile(options.context, "utf-8");
893
409
  } else {
894
410
  context = options.context;
895
411
  }
@@ -906,7 +422,7 @@ function imageExplainCommand() {
906
422
  ${explanation}`;
907
423
  }
908
424
  if (options.output) {
909
- await fs6.writeFile(options.output, output, "utf-8");
425
+ await fs2.writeFile(options.output, output, "utf-8");
910
426
  console.log(`\u8AAC\u660E\u3092\u4FDD\u5B58\u3057\u307E\u3057\u305F: ${options.output}`);
911
427
  } else {
912
428
  console.log(output);
@@ -919,74 +435,9 @@ ${explanation}`;
919
435
  }
920
436
 
921
437
  // src/commands/image/gen.ts
922
- import * as fs8 from "fs/promises";
923
- import * as path5 from "path";
438
+ import * as fs3 from "fs/promises";
439
+ import * as path2 from "path";
924
440
  import { Command as Command4, Option as Option2 } from "commander";
925
-
926
- // src/utils/preset.ts
927
- import * as fs7 from "fs/promises";
928
- import * as path4 from "path";
929
- var BUILTIN_PRESETS = {
930
- "builtin:square": { size: "1024x1024", quality: "high" },
931
- "builtin:landscape": { size: "1536x1024", quality: "high" },
932
- "builtin:portrait": { size: "1024x1536", quality: "high" },
933
- "builtin:draft": { size: "1024x1024", quality: "low" },
934
- "builtin:photo": { size: "1536x1024", quality: "high" }
935
- };
936
- function getPresetsPath() {
937
- return path4.join(getConfigDir(), "presets.json");
938
- }
939
- async function loadPresets() {
940
- try {
941
- const text = await fs7.readFile(getPresetsPath(), "utf-8");
942
- return JSON.parse(text);
943
- } catch {
944
- return {};
945
- }
946
- }
947
- async function savePresets(presets) {
948
- const p = getPresetsPath();
949
- await fs7.mkdir(path4.dirname(p), { recursive: true });
950
- await fs7.writeFile(p, JSON.stringify(presets, null, 2));
951
- }
952
- async function getPreset(name) {
953
- if (name.startsWith("builtin:") && BUILTIN_PRESETS[name]) {
954
- return BUILTIN_PRESETS[name];
955
- }
956
- const presets = await loadPresets();
957
- return presets[name] || null;
958
- }
959
- async function savePreset(name, preset) {
960
- if (name.startsWith("builtin:")) {
961
- throw new Error("\u30D3\u30EB\u30C8\u30A4\u30F3\u30D7\u30EA\u30BB\u30C3\u30C8\u306F\u4E0A\u66F8\u304D\u3067\u304D\u307E\u305B\u3093");
962
- }
963
- const presets = await loadPresets();
964
- presets[name] = preset;
965
- await savePresets(presets);
966
- }
967
- async function deletePreset(name) {
968
- if (name.startsWith("builtin:")) {
969
- throw new Error("\u30D3\u30EB\u30C8\u30A4\u30F3\u30D7\u30EA\u30BB\u30C3\u30C8\u306F\u524A\u9664\u3067\u304D\u307E\u305B\u3093");
970
- }
971
- const presets = await loadPresets();
972
- if (!presets[name]) return false;
973
- delete presets[name];
974
- await savePresets(presets);
975
- return true;
976
- }
977
- async function listAllPresets() {
978
- const result = [];
979
- for (const [name, preset] of Object.entries(BUILTIN_PRESETS)) {
980
- result.push({ name, preset, builtin: true });
981
- }
982
- const presets = await loadPresets();
983
- for (const [name, preset] of Object.entries(presets)) {
984
- result.push({ name, preset, builtin: false });
985
- }
986
- return result;
987
- }
988
-
989
- // src/commands/image/gen.ts
990
441
  var VALID_FORMATS = ["png", "jpg", "webp"];
991
442
  function validateImageFormat(ext, specifiedFormat) {
992
443
  if (!VALID_FORMATS.includes(ext)) {
@@ -999,28 +450,28 @@ function validateImageFormat(ext, specifiedFormat) {
999
450
  }
1000
451
  }
1001
452
  async function resolveOutputPath(outputPath, defaultFormat, fileName) {
1002
- let format2 = defaultFormat;
453
+ let format = defaultFormat;
1003
454
  if (!outputPath) {
1004
- return { path: `${fileName}.${format2}`, format: format2 };
455
+ return { path: `${fileName}.${format}`, format };
1005
456
  }
1006
457
  try {
1007
- const stat4 = await fs8.stat(outputPath);
1008
- if (stat4.isDirectory()) {
1009
- return { path: path5.join(outputPath, `${fileName}.${format2}`), format: format2 };
458
+ const stat3 = await fs3.stat(outputPath);
459
+ if (stat3.isDirectory()) {
460
+ return { path: path2.join(outputPath, `${fileName}.${format}`), format };
1010
461
  }
1011
462
  } catch (error) {
1012
463
  if (error.code !== "ENOENT") {
1013
464
  throw error;
1014
465
  }
1015
466
  }
1016
- const ext = path5.extname(outputPath).toLowerCase().slice(1);
467
+ const ext = path2.extname(outputPath).toLowerCase().slice(1);
1017
468
  if (ext) {
1018
469
  validateImageFormat(ext, defaultFormat);
1019
- format2 = ext;
470
+ format = ext;
1020
471
  } else {
1021
- outputPath = `${outputPath}.${format2}`;
472
+ outputPath = `${outputPath}.${format}`;
1022
473
  }
1023
- return { path: outputPath, format: format2 };
474
+ return { path: outputPath, format };
1024
475
  }
1025
476
  function imageGenCommand() {
1026
477
  return new Command4("gen").description("\u30C6\u30AD\u30B9\u30C8\u304B\u3089\u753B\u50CF\u3092\u751F\u6210").argument("<theme>", "\u753B\u50CF\u751F\u6210\u306E\u30C6\u30FC\u30DE").option("-c, --context <file>", "\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u30D5\u30A1\u30A4\u30EB\u306E\u30D1\u30B9").option("-o, --output <path>", "\u51FA\u529B\u30D1\u30B9\uFF08\u30D5\u30A1\u30A4\u30EB\u307E\u305F\u306F\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\uFF09").addOption(
@@ -1101,7 +552,7 @@ imgen preset list \u3067\u4E00\u89A7\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u
1101
552
  quality: effectiveQuality
1102
553
  });
1103
554
  const fileName = await chatClient.generateFileName(theme);
1104
- const { path: outputPath, format: format2 } = await resolveOutputPath(
555
+ const { path: outputPath, format } = await resolveOutputPath(
1105
556
  options.output,
1106
557
  effectiveFormat,
1107
558
  fileName
@@ -1109,7 +560,7 @@ imgen preset list \u3067\u4E00\u89A7\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u
1109
560
  const finalOutputPath = await saveFileWithUniqueNameIfExists(outputPath, imageData);
1110
561
  await logger.info("\u753B\u50CF\u3092\u751F\u6210\u3057\u307E\u3057\u305F", {
1111
562
  path: finalOutputPath,
1112
- format: format2,
563
+ format,
1113
564
  size: effectiveSize,
1114
565
  quality: effectiveQuality,
1115
566
  ...options.preset ? { preset: options.preset } : {}
@@ -1118,7 +569,7 @@ imgen preset list \u3067\u4E00\u89A7\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u
1118
569
  printJson(
1119
570
  createSuccessOutput("image gen", {
1120
571
  path: finalOutputPath,
1121
- format: format2,
572
+ format,
1122
573
  size: effectiveSize,
1123
574
  quality: effectiveQuality,
1124
575
  ...options.preset ? { preset: options.preset } : {}