@nogataka/imgen 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 +157 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1365 -0
- package/package.json +51 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1365 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/commands/index.ts
|
|
4
|
+
import { Command as Command8 } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/commands/configure.ts
|
|
7
|
+
import { Command } from "commander";
|
|
8
|
+
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
|
+
function configureCommand() {
|
|
72
|
+
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
|
+
try {
|
|
74
|
+
if (options.show) {
|
|
75
|
+
await showConfig();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (options.reset) {
|
|
79
|
+
await resetConfig();
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
await interactiveConfig();
|
|
83
|
+
} catch (error) {
|
|
84
|
+
if (error instanceof Error) {
|
|
85
|
+
console.error("\u30A8\u30E9\u30FC:", error.message);
|
|
86
|
+
} else {
|
|
87
|
+
console.error("\u4E0D\u660E\u306A\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F");
|
|
88
|
+
}
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
async function showConfig() {
|
|
94
|
+
const config = await loadConfig();
|
|
95
|
+
const configPath = getConfigPath();
|
|
96
|
+
console.log(`
|
|
97
|
+
\u8A2D\u5B9A\u30D5\u30A1\u30A4\u30EB: ${configPath}
|
|
98
|
+
`);
|
|
99
|
+
if (!config) {
|
|
100
|
+
console.log("\u8A2D\u5B9A\u30D5\u30A1\u30A4\u30EB\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002");
|
|
101
|
+
console.log("\n\u30C7\u30D5\u30A9\u30EB\u30C8\u5024:");
|
|
102
|
+
console.log(JSON.stringify(DEFAULT_CONFIG, null, 2));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const displayConfig = { ...config };
|
|
106
|
+
if (displayConfig.azureApiKey) {
|
|
107
|
+
displayConfig.azureApiKey = "****" + displayConfig.azureApiKey.slice(-4);
|
|
108
|
+
}
|
|
109
|
+
console.log("\u73FE\u5728\u306E\u8A2D\u5B9A:");
|
|
110
|
+
console.log(JSON.stringify(displayConfig, null, 2));
|
|
111
|
+
}
|
|
112
|
+
async function resetConfig() {
|
|
113
|
+
const { confirm } = await inquirer.prompt([
|
|
114
|
+
{
|
|
115
|
+
type: "confirm",
|
|
116
|
+
name: "confirm",
|
|
117
|
+
message: "\u8A2D\u5B9A\u3092\u30EA\u30BB\u30C3\u30C8\u3057\u307E\u3059\u304B\uFF1F\uFF08API\u8A2D\u5B9A\u3082\u524A\u9664\u3055\u308C\u307E\u3059\uFF09",
|
|
118
|
+
default: false
|
|
119
|
+
}
|
|
120
|
+
]);
|
|
121
|
+
if (confirm) {
|
|
122
|
+
await saveConfig({});
|
|
123
|
+
console.log("\u8A2D\u5B9A\u3092\u30EA\u30BB\u30C3\u30C8\u3057\u307E\u3057\u305F\u3002");
|
|
124
|
+
} else {
|
|
125
|
+
console.log("\u30AD\u30E3\u30F3\u30BB\u30EB\u3057\u307E\u3057\u305F\u3002");
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
async function interactiveConfig() {
|
|
129
|
+
const { action } = await inquirer.prompt([
|
|
130
|
+
{
|
|
131
|
+
type: "list",
|
|
132
|
+
name: "action",
|
|
133
|
+
message: "\u8A2D\u5B9A\u9805\u76EE\u3092\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
134
|
+
choices: [
|
|
135
|
+
{ name: "Azure OpenAI\u63A5\u7D9A\u8A2D\u5B9A", value: "azure" },
|
|
136
|
+
{ name: "\u30C7\u30D5\u30A9\u30EB\u30C8\u5024\u3092\u8A2D\u5B9A", value: "defaults" },
|
|
137
|
+
{ name: "\u73FE\u5728\u306E\u8A2D\u5B9A\u3092\u8868\u793A", value: "show" },
|
|
138
|
+
{ name: "\u8A2D\u5B9A\u3092\u30EA\u30BB\u30C3\u30C8", value: "reset" },
|
|
139
|
+
{ name: "\u7D42\u4E86", value: "exit" }
|
|
140
|
+
]
|
|
141
|
+
}
|
|
142
|
+
]);
|
|
143
|
+
switch (action) {
|
|
144
|
+
case "azure":
|
|
145
|
+
await configureAzure();
|
|
146
|
+
break;
|
|
147
|
+
case "defaults":
|
|
148
|
+
await configureDefaults();
|
|
149
|
+
break;
|
|
150
|
+
case "show":
|
|
151
|
+
await showConfig();
|
|
152
|
+
break;
|
|
153
|
+
case "reset":
|
|
154
|
+
await resetConfig();
|
|
155
|
+
break;
|
|
156
|
+
case "exit":
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
async function configureAzure() {
|
|
161
|
+
const config = await loadConfig() || {};
|
|
162
|
+
const envEndpoint = process.env.AZURE_OPENAI_ENDPOINT;
|
|
163
|
+
const envKey = process.env.AZURE_OPENAI_API_KEY;
|
|
164
|
+
if (envEndpoint && envKey) {
|
|
165
|
+
console.log("\n\u74B0\u5883\u5909\u6570\u3067Azure OpenAI\u8A2D\u5B9A\u304C\u691C\u51FA\u3055\u308C\u307E\u3057\u305F\u3002");
|
|
166
|
+
const { useEnv } = await inquirer.prompt([
|
|
167
|
+
{
|
|
168
|
+
type: "confirm",
|
|
169
|
+
name: "useEnv",
|
|
170
|
+
message: "\u74B0\u5883\u5909\u6570\u306E\u5024\u3092\u8A2D\u5B9A\u30D5\u30A1\u30A4\u30EB\u306B\u4FDD\u5B58\u3057\u307E\u3059\u304B\uFF1F",
|
|
171
|
+
default: true
|
|
172
|
+
}
|
|
173
|
+
]);
|
|
174
|
+
if (useEnv) {
|
|
175
|
+
config.azureEndpoint = envEndpoint;
|
|
176
|
+
config.azureApiKey = envKey;
|
|
177
|
+
config.azureDeploymentName = process.env.AZURE_OPENAI_DEPLOYMENT_NAME || "gpt-5.1";
|
|
178
|
+
config.azureImageDeploymentName = process.env.AZURE_OPENAI_DEPLOYMENT_NAME_IMAGE || "gpt-image-1.5";
|
|
179
|
+
config.azureApiVersion = process.env.AZURE_OPENAI_API_VERSION || "2024-02-15-preview";
|
|
180
|
+
config.azureImageApiVersion = process.env.AZURE_OPENAI_IMAGE_API_VERSION || "2025-04-01-preview";
|
|
181
|
+
await saveConfig(config);
|
|
182
|
+
console.log("\u8A2D\u5B9A\u3092\u4FDD\u5B58\u3057\u307E\u3057\u305F\u3002");
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
const answers = await inquirer.prompt([
|
|
187
|
+
{
|
|
188
|
+
type: "input",
|
|
189
|
+
name: "endpoint",
|
|
190
|
+
message: "Azure OpenAI \u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8:",
|
|
191
|
+
default: config.azureEndpoint || "",
|
|
192
|
+
validate: (input) => input.startsWith("https://") || "https:// \u3067\u59CB\u307E\u308BURL\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044"
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
type: "password",
|
|
196
|
+
name: "apiKey",
|
|
197
|
+
message: "Azure OpenAI API\u30AD\u30FC:",
|
|
198
|
+
mask: "*",
|
|
199
|
+
validate: (input) => input.length > 0 || "API\u30AD\u30FC\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044"
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
type: "input",
|
|
203
|
+
name: "deploymentName",
|
|
204
|
+
message: "\u30C1\u30E3\u30C3\u30C8\u30E2\u30C7\u30EB\u30C7\u30D7\u30ED\u30A4\u540D:",
|
|
205
|
+
default: config.azureDeploymentName || "gpt-5.1"
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
type: "input",
|
|
209
|
+
name: "imageDeploymentName",
|
|
210
|
+
message: "\u753B\u50CF\u30E2\u30C7\u30EB\u30C7\u30D7\u30ED\u30A4\u540D:",
|
|
211
|
+
default: config.azureImageDeploymentName || "gpt-image-1.5"
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
type: "input",
|
|
215
|
+
name: "apiVersion",
|
|
216
|
+
message: "\u30C1\u30E3\u30C3\u30C8 API \u30D0\u30FC\u30B8\u30E7\u30F3:",
|
|
217
|
+
default: config.azureApiVersion || "2024-02-15-preview"
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
type: "input",
|
|
221
|
+
name: "imageApiVersion",
|
|
222
|
+
message: "\u753B\u50CF API \u30D0\u30FC\u30B8\u30E7\u30F3:",
|
|
223
|
+
default: config.azureImageApiVersion || "2025-04-01-preview"
|
|
224
|
+
}
|
|
225
|
+
]);
|
|
226
|
+
config.azureEndpoint = answers.endpoint;
|
|
227
|
+
config.azureApiKey = answers.apiKey;
|
|
228
|
+
config.azureDeploymentName = answers.deploymentName;
|
|
229
|
+
config.azureImageDeploymentName = answers.imageDeploymentName;
|
|
230
|
+
config.azureApiVersion = answers.apiVersion;
|
|
231
|
+
config.azureImageApiVersion = answers.imageApiVersion;
|
|
232
|
+
await saveConfig(config);
|
|
233
|
+
console.log("\nAzure OpenAI\u8A2D\u5B9A\u3092\u4FDD\u5B58\u3057\u307E\u3057\u305F\u3002");
|
|
234
|
+
}
|
|
235
|
+
async function configureDefaults() {
|
|
236
|
+
const config = await loadConfig() || {};
|
|
237
|
+
const answers = await inquirer.prompt([
|
|
238
|
+
{
|
|
239
|
+
type: "list",
|
|
240
|
+
name: "language",
|
|
241
|
+
message: "\u30C7\u30D5\u30A9\u30EB\u30C8\u8A00\u8A9E",
|
|
242
|
+
choices: Object.entries(LANGUAGE_DESCRIPTIONS).map(([key, value]) => ({
|
|
243
|
+
name: `${key}: ${value}`,
|
|
244
|
+
value: key
|
|
245
|
+
})),
|
|
246
|
+
default: config.defaultLanguage || DEFAULT_CONFIG.defaultLanguage
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
type: "list",
|
|
250
|
+
name: "imageSize",
|
|
251
|
+
message: "\u30C7\u30D5\u30A9\u30EB\u30C8\u753B\u50CF\u30B5\u30A4\u30BA",
|
|
252
|
+
choices: [
|
|
253
|
+
{ name: "1024x1024 (\u6B63\u65B9\u5F62)", value: "1024x1024" },
|
|
254
|
+
{ name: "1536x1024 (\u6A2A\u9577)", value: "1536x1024" },
|
|
255
|
+
{ name: "1024x1536 (\u7E26\u9577)", value: "1024x1536" }
|
|
256
|
+
],
|
|
257
|
+
default: config.defaultImageSize || DEFAULT_CONFIG.defaultImageSize
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
type: "list",
|
|
261
|
+
name: "imageQuality",
|
|
262
|
+
message: "\u30C7\u30D5\u30A9\u30EB\u30C8\u753B\u50CF\u54C1\u8CEA",
|
|
263
|
+
choices: [
|
|
264
|
+
{ name: "high (\u9AD8\u54C1\u8CEA)", value: "high" },
|
|
265
|
+
{ name: "medium (\u6A19\u6E96)", value: "medium" },
|
|
266
|
+
{ name: "low (\u9AD8\u901F)", value: "low" }
|
|
267
|
+
],
|
|
268
|
+
default: config.defaultImageQuality || DEFAULT_CONFIG.defaultImageQuality
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
type: "list",
|
|
272
|
+
name: "imageFormat",
|
|
273
|
+
message: "\u30C7\u30D5\u30A9\u30EB\u30C8\u753B\u50CF\u30D5\u30A9\u30FC\u30DE\u30C3\u30C8",
|
|
274
|
+
choices: [
|
|
275
|
+
{ name: "PNG", value: "png" },
|
|
276
|
+
{ name: "JPG", value: "jpg" },
|
|
277
|
+
{ name: "WebP", value: "webp" }
|
|
278
|
+
],
|
|
279
|
+
default: config.defaultImageFormat || DEFAULT_CONFIG.defaultImageFormat
|
|
280
|
+
}
|
|
281
|
+
]);
|
|
282
|
+
config.defaultLanguage = answers.language;
|
|
283
|
+
config.defaultImageSize = answers.imageSize;
|
|
284
|
+
config.defaultImageQuality = answers.imageQuality;
|
|
285
|
+
config.defaultImageFormat = answers.imageFormat;
|
|
286
|
+
await saveConfig(config);
|
|
287
|
+
console.log("\n\u30C7\u30D5\u30A9\u30EB\u30C8\u8A2D\u5B9A\u3092\u4FDD\u5B58\u3057\u307E\u3057\u305F\u3002");
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// src/commands/image/index.ts
|
|
291
|
+
import { Command as Command5 } from "commander";
|
|
292
|
+
|
|
293
|
+
// src/commands/image/edit.ts
|
|
294
|
+
import * as fs4 from "fs/promises";
|
|
295
|
+
import * as path3 from "path";
|
|
296
|
+
import { Command as Command2, Option } from "commander";
|
|
297
|
+
|
|
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
|
+
// src/utils/output.ts
|
|
708
|
+
function createSuccessOutput(command, result) {
|
|
709
|
+
return { success: true, command, result };
|
|
710
|
+
}
|
|
711
|
+
function createErrorOutput(command, message, code) {
|
|
712
|
+
return {
|
|
713
|
+
success: false,
|
|
714
|
+
command,
|
|
715
|
+
error: { message, ...code ? { code } : {} }
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
function printJson(output) {
|
|
719
|
+
console.log(JSON.stringify(output, null, 2));
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// src/commands/image/edit.ts
|
|
723
|
+
var SUPPORTED_EXTENSIONS = [".jpg", ".jpeg", ".png", ".gif", ".webp"];
|
|
724
|
+
function imageEditCommand() {
|
|
725
|
+
return new Command2("edit").description("\u753B\u50CF\u3092AI\u7DE8\u96C6").argument("<file>", "\u7DE8\u96C6\u3059\u308B\u753B\u50CF\u30D5\u30A1\u30A4\u30EB\u306E\u30D1\u30B9").argument("<prompt>", "\u7DE8\u96C6\u306E\u6307\u793A").option("-o, --output <path>", "\u51FA\u529B\u30D1\u30B9").addOption(
|
|
726
|
+
new Option("-f, --format <format>", "\u51FA\u529B\u30D5\u30A9\u30FC\u30DE\u30C3\u30C8").choices(["png", "jpg", "webp"]).default("png")
|
|
727
|
+
).addOption(
|
|
728
|
+
new Option("-s, --size <size>", "\u51FA\u529B\u30B5\u30A4\u30BA").choices(["1024x1024", "1536x1024", "1024x1536"]).default("1024x1024")
|
|
729
|
+
).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();
|
|
731
|
+
if (!SUPPORTED_EXTENSIONS.includes(ext)) {
|
|
732
|
+
const msg = `\u30B5\u30DD\u30FC\u30C8\u3055\u308C\u3066\u3044\u306A\u3044\u30D5\u30A1\u30A4\u30EB\u5F62\u5F0F\u3067\u3059: ${ext}`;
|
|
733
|
+
if (options.json) {
|
|
734
|
+
printJson(createErrorOutput("image edit", msg));
|
|
735
|
+
} else {
|
|
736
|
+
console.error("\u30A8\u30E9\u30FC:", msg);
|
|
737
|
+
}
|
|
738
|
+
process.exit(1);
|
|
739
|
+
}
|
|
740
|
+
if (!await fileExists(filePath)) {
|
|
741
|
+
const msg = `\u30D5\u30A1\u30A4\u30EB\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: ${filePath}`;
|
|
742
|
+
if (options.json) {
|
|
743
|
+
printJson(createErrorOutput("image edit", msg));
|
|
744
|
+
} else {
|
|
745
|
+
console.error("\u30A8\u30E9\u30FC:", msg);
|
|
746
|
+
}
|
|
747
|
+
process.exit(1);
|
|
748
|
+
}
|
|
749
|
+
const config = await loadConfig();
|
|
750
|
+
const effectiveSize = options.size !== "1024x1024" ? options.size : config?.defaultImageSize || options.size;
|
|
751
|
+
const effectiveFormat = options.format !== "png" ? options.format : config?.defaultImageFormat || options.format;
|
|
752
|
+
Logger.setGlobalConfig({ destination: "BOTH" /* BOTH */, minLevel: "INFO" /* INFO */ });
|
|
753
|
+
const logger = Logger.getInstance({ name: "image-edit" });
|
|
754
|
+
if (options.dryRun) {
|
|
755
|
+
const info = {
|
|
756
|
+
file: filePath,
|
|
757
|
+
prompt,
|
|
758
|
+
size: effectiveSize,
|
|
759
|
+
format: effectiveFormat,
|
|
760
|
+
output: options.output || "(\u81EA\u52D5\u751F\u6210)"
|
|
761
|
+
};
|
|
762
|
+
if (options.json) {
|
|
763
|
+
printJson(createSuccessOutput("image edit", { dryRun: true, ...info }));
|
|
764
|
+
} else {
|
|
765
|
+
console.log("\n[DRY-RUN] \u753B\u50CF\u7DE8\u96C6");
|
|
766
|
+
console.log(" \u5165\u529B:", filePath);
|
|
767
|
+
console.log(" \u30D7\u30ED\u30F3\u30D7\u30C8:", prompt);
|
|
768
|
+
console.log(" \u30B5\u30A4\u30BA:", effectiveSize);
|
|
769
|
+
console.log(" \u30D5\u30A9\u30FC\u30DE\u30C3\u30C8:", effectiveFormat);
|
|
770
|
+
console.log(" \u51FA\u529B\u5148:", options.output || "(\u81EA\u52D5\u751F\u6210)");
|
|
771
|
+
console.log("\nAPI\u306F\u547C\u3073\u51FA\u3055\u308C\u307E\u305B\u3093\u3002\u5B9F\u884C\u3059\u308B\u306B\u306F --dry-run \u3092\u5916\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
|
|
772
|
+
}
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
try {
|
|
776
|
+
const azureConfig = await getAzureConfig();
|
|
777
|
+
const chatClient = new AzureChatClient(azureConfig);
|
|
778
|
+
const imageClient = new AzureImageClient(azureConfig);
|
|
779
|
+
const imageBuffer = Buffer.from(await fs4.readFile(filePath));
|
|
780
|
+
const editedData = await imageClient.editImage(imageBuffer, prompt, {
|
|
781
|
+
size: effectiveSize
|
|
782
|
+
});
|
|
783
|
+
const baseName = path3.basename(filePath, ext);
|
|
784
|
+
const fileName = await chatClient.generateFileName(`${baseName} edited`);
|
|
785
|
+
let outputPath;
|
|
786
|
+
if (options.output) {
|
|
787
|
+
try {
|
|
788
|
+
const stat4 = await fs4.stat(options.output);
|
|
789
|
+
if (stat4.isDirectory()) {
|
|
790
|
+
outputPath = path3.join(options.output, `${fileName}.${effectiveFormat}`);
|
|
791
|
+
} else {
|
|
792
|
+
outputPath = options.output;
|
|
793
|
+
}
|
|
794
|
+
} catch {
|
|
795
|
+
outputPath = options.output;
|
|
796
|
+
}
|
|
797
|
+
} else {
|
|
798
|
+
outputPath = `${fileName}.${effectiveFormat}`;
|
|
799
|
+
}
|
|
800
|
+
const finalPath = await saveFileWithUniqueNameIfExists(outputPath, editedData);
|
|
801
|
+
await logger.info("\u753B\u50CF\u3092\u7DE8\u96C6\u3057\u307E\u3057\u305F", {
|
|
802
|
+
input: filePath,
|
|
803
|
+
output: finalPath,
|
|
804
|
+
size: effectiveSize
|
|
805
|
+
});
|
|
806
|
+
if (options.json) {
|
|
807
|
+
printJson(
|
|
808
|
+
createSuccessOutput("image edit", {
|
|
809
|
+
input: filePath,
|
|
810
|
+
path: finalPath,
|
|
811
|
+
format: effectiveFormat,
|
|
812
|
+
size: effectiveSize
|
|
813
|
+
})
|
|
814
|
+
);
|
|
815
|
+
} else {
|
|
816
|
+
console.log(`\u753B\u50CF\u3092\u7DE8\u96C6\u3057\u307E\u3057\u305F: ${finalPath}`);
|
|
817
|
+
}
|
|
818
|
+
} catch (error) {
|
|
819
|
+
if (error instanceof Error) {
|
|
820
|
+
await logger.error("\u753B\u50CF\u7DE8\u96C6\u306B\u5931\u6557\u3057\u307E\u3057\u305F", { error: error.message });
|
|
821
|
+
if (options.json) {
|
|
822
|
+
printJson(createErrorOutput("image edit", error.message));
|
|
823
|
+
} else {
|
|
824
|
+
console.error("\u30A8\u30E9\u30FC:", error.message);
|
|
825
|
+
}
|
|
826
|
+
} else {
|
|
827
|
+
await logger.error("\u4E0D\u660E\u306A\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F");
|
|
828
|
+
if (options.json) {
|
|
829
|
+
printJson(createErrorOutput("image edit", "\u4E0D\u660E\u306A\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"));
|
|
830
|
+
} else {
|
|
831
|
+
console.error("\u4E0D\u660E\u306A\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F");
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
process.exit(1);
|
|
835
|
+
}
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// src/commands/image/explain.ts
|
|
840
|
+
import * as fs6 from "fs/promises";
|
|
841
|
+
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
|
+
function imageExplainCommand() {
|
|
876
|
+
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
|
+
"-l, --lang <lang>",
|
|
878
|
+
`\u51FA\u529B\u8A00\u8A9E (${Object.keys(LANGUAGE_DESCRIPTIONS).join(", ")})`,
|
|
879
|
+
"ja"
|
|
880
|
+
).option("-f, --format <format>", "\u51FA\u529B\u30D5\u30A9\u30FC\u30DE\u30C3\u30C8 (markdown | json)", "markdown").option("-c, --context <context>", "\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u60C5\u5831").option("-o, --output <path>", "\u51FA\u529B\u30D5\u30A1\u30A4\u30EB\u30D1\u30B9").action(async (filePath, options) => {
|
|
881
|
+
try {
|
|
882
|
+
const config = await loadConfig();
|
|
883
|
+
const lang = options.lang === "ja" && config?.defaultLanguage ? config.defaultLanguage : options.lang;
|
|
884
|
+
const validLangs = Object.keys(LANGUAGE_DESCRIPTIONS);
|
|
885
|
+
const effectiveLang = validLangs.includes(lang) ? lang : "ja";
|
|
886
|
+
const azureConfig = await getAzureConfig();
|
|
887
|
+
const chatClient = new AzureChatClient(azureConfig);
|
|
888
|
+
const imageData = await readImageFile(filePath);
|
|
889
|
+
let context;
|
|
890
|
+
if (options.context) {
|
|
891
|
+
if (options.context.endsWith(".md") || options.context.endsWith(".txt")) {
|
|
892
|
+
context = await fs6.readFile(options.context, "utf-8");
|
|
893
|
+
} else {
|
|
894
|
+
context = options.context;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
const explanation = await chatClient.generateExplanation(imageData, effectiveLang, context);
|
|
898
|
+
let output;
|
|
899
|
+
if (options.format === "json") {
|
|
900
|
+
output = JSON.stringify({ file: filePath, type: "image", explanation }, null, 2);
|
|
901
|
+
} else {
|
|
902
|
+
output = `# ${filePath}
|
|
903
|
+
|
|
904
|
+
**\u7A2E\u985E:** \u753B\u50CF
|
|
905
|
+
|
|
906
|
+
${explanation}`;
|
|
907
|
+
}
|
|
908
|
+
if (options.output) {
|
|
909
|
+
await fs6.writeFile(options.output, output, "utf-8");
|
|
910
|
+
console.log(`\u8AAC\u660E\u3092\u4FDD\u5B58\u3057\u307E\u3057\u305F: ${options.output}`);
|
|
911
|
+
} else {
|
|
912
|
+
console.log(output);
|
|
913
|
+
}
|
|
914
|
+
} catch (error) {
|
|
915
|
+
console.error("\u30A8\u30E9\u30FC:", error instanceof Error ? error.message : "\u4E0D\u660E\u306A\u30A8\u30E9\u30FC");
|
|
916
|
+
process.exit(1);
|
|
917
|
+
}
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// src/commands/image/gen.ts
|
|
922
|
+
import * as fs8 from "fs/promises";
|
|
923
|
+
import * as path5 from "path";
|
|
924
|
+
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
|
+
var VALID_FORMATS = ["png", "jpg", "webp"];
|
|
991
|
+
function validateImageFormat(ext, specifiedFormat) {
|
|
992
|
+
if (!VALID_FORMATS.includes(ext)) {
|
|
993
|
+
throw new Error(`\u30B5\u30DD\u30FC\u30C8\u3055\u308C\u3066\u3044\u306A\u3044\u753B\u50CF\u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u3067\u3059: ${ext}`);
|
|
994
|
+
}
|
|
995
|
+
if (specifiedFormat && ext !== specifiedFormat) {
|
|
996
|
+
throw new Error(
|
|
997
|
+
`\u51FA\u529B\u30D5\u30A1\u30A4\u30EB\u306E\u62E1\u5F35\u5B50(${ext})\u3068\u6307\u5B9A\u3055\u308C\u305F\u30D5\u30A9\u30FC\u30DE\u30C3\u30C8(${specifiedFormat})\u304C\u4E00\u81F4\u3057\u307E\u305B\u3093`
|
|
998
|
+
);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
async function resolveOutputPath(outputPath, defaultFormat, fileName) {
|
|
1002
|
+
let format2 = defaultFormat;
|
|
1003
|
+
if (!outputPath) {
|
|
1004
|
+
return { path: `${fileName}.${format2}`, format: format2 };
|
|
1005
|
+
}
|
|
1006
|
+
try {
|
|
1007
|
+
const stat4 = await fs8.stat(outputPath);
|
|
1008
|
+
if (stat4.isDirectory()) {
|
|
1009
|
+
return { path: path5.join(outputPath, `${fileName}.${format2}`), format: format2 };
|
|
1010
|
+
}
|
|
1011
|
+
} catch (error) {
|
|
1012
|
+
if (error.code !== "ENOENT") {
|
|
1013
|
+
throw error;
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
const ext = path5.extname(outputPath).toLowerCase().slice(1);
|
|
1017
|
+
if (ext) {
|
|
1018
|
+
validateImageFormat(ext, defaultFormat);
|
|
1019
|
+
format2 = ext;
|
|
1020
|
+
} else {
|
|
1021
|
+
outputPath = `${outputPath}.${format2}`;
|
|
1022
|
+
}
|
|
1023
|
+
return { path: outputPath, format: format2 };
|
|
1024
|
+
}
|
|
1025
|
+
function imageGenCommand() {
|
|
1026
|
+
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(
|
|
1027
|
+
new Option2("-s, --size <size>", "\u753B\u50CF\u30B5\u30A4\u30BA").choices(["1024x1024", "1536x1024", "1024x1536"]).default("1024x1024")
|
|
1028
|
+
).addOption(
|
|
1029
|
+
new Option2("-q, --quality <quality>", "\u753B\u50CF\u54C1\u8CEA").choices(["low", "medium", "high"]).default("high")
|
|
1030
|
+
).addOption(
|
|
1031
|
+
new Option2("-f, --format <format>", "\u753B\u50CF\u30D5\u30A9\u30FC\u30DE\u30C3\u30C8").choices(["png", "jpg", "webp"]).default("png")
|
|
1032
|
+
).option("-d, --debug", "\u30C7\u30D0\u30C3\u30B0\u30E2\u30FC\u30C9", false).option("--json", "JSON\u5F62\u5F0F\u3067\u51FA\u529B", false).option("--dry-run", "\u5B9F\u884C\u305B\u305A\u306B\u8A2D\u5B9A\u3092\u78BA\u8A8D", false).option("-p, --preset <name>", "\u30D7\u30EA\u30BB\u30C3\u30C8\u540D").action(async (theme, options) => {
|
|
1033
|
+
if (!theme) {
|
|
1034
|
+
console.log("\u30C6\u30FC\u30DE\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044");
|
|
1035
|
+
process.exit(1);
|
|
1036
|
+
}
|
|
1037
|
+
let presetSize;
|
|
1038
|
+
let presetQuality;
|
|
1039
|
+
let presetFormat;
|
|
1040
|
+
if (options.preset) {
|
|
1041
|
+
const preset = await getPreset(options.preset);
|
|
1042
|
+
if (!preset) {
|
|
1043
|
+
const errorMsg = `\u30D7\u30EA\u30BB\u30C3\u30C8 '${options.preset}' \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002
|
|
1044
|
+
imgen preset list \u3067\u4E00\u89A7\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002`;
|
|
1045
|
+
if (options.json) {
|
|
1046
|
+
printJson(createErrorOutput("image gen", errorMsg, "PRESET_NOT_FOUND"));
|
|
1047
|
+
} else {
|
|
1048
|
+
console.error("\u30A8\u30E9\u30FC:", errorMsg);
|
|
1049
|
+
}
|
|
1050
|
+
process.exit(1);
|
|
1051
|
+
}
|
|
1052
|
+
presetSize = preset.size;
|
|
1053
|
+
presetQuality = preset.quality;
|
|
1054
|
+
presetFormat = preset.format;
|
|
1055
|
+
}
|
|
1056
|
+
const config = await loadConfig();
|
|
1057
|
+
const effectiveSize = options.size !== "1024x1024" ? options.size : presetSize || config?.defaultImageSize || options.size;
|
|
1058
|
+
const effectiveQuality = options.quality !== "high" ? options.quality : presetQuality || config?.defaultImageQuality || options.quality;
|
|
1059
|
+
const effectiveFormat = options.format !== "png" ? options.format : presetFormat || config?.defaultImageFormat || options.format;
|
|
1060
|
+
Logger.setGlobalConfig({
|
|
1061
|
+
destination: "BOTH" /* BOTH */,
|
|
1062
|
+
minLevel: options.debug ? "DEBUG" /* DEBUG */ : "INFO" /* INFO */
|
|
1063
|
+
});
|
|
1064
|
+
const logger = Logger.getInstance({ name: "image-gen" });
|
|
1065
|
+
if (options.dryRun) {
|
|
1066
|
+
const dryRunInfo = {
|
|
1067
|
+
theme,
|
|
1068
|
+
size: effectiveSize,
|
|
1069
|
+
quality: effectiveQuality,
|
|
1070
|
+
format: effectiveFormat,
|
|
1071
|
+
output: options.output || `(\u81EA\u52D5\u751F\u6210).${effectiveFormat}`,
|
|
1072
|
+
...options.preset ? { preset: options.preset } : {},
|
|
1073
|
+
...options.context ? { context: options.context } : {}
|
|
1074
|
+
};
|
|
1075
|
+
if (options.json) {
|
|
1076
|
+
printJson(createSuccessOutput("image gen", { dryRun: true, ...dryRunInfo }));
|
|
1077
|
+
} else {
|
|
1078
|
+
console.log("\n[DRY-RUN] \u753B\u50CF\u751F\u6210");
|
|
1079
|
+
console.log(" \u30C6\u30FC\u30DE:", theme);
|
|
1080
|
+
if (options.preset) console.log(" \u30D7\u30EA\u30BB\u30C3\u30C8:", options.preset);
|
|
1081
|
+
console.log(" \u30B5\u30A4\u30BA:", effectiveSize);
|
|
1082
|
+
console.log(" \u54C1\u8CEA:", effectiveQuality);
|
|
1083
|
+
console.log(" \u30D5\u30A9\u30FC\u30DE\u30C3\u30C8:", effectiveFormat);
|
|
1084
|
+
if (options.context) console.log(" \u30B3\u30F3\u30C6\u30AD\u30B9\u30C8:", options.context);
|
|
1085
|
+
console.log(" \u51FA\u529B\u5148:", options.output || `(\u81EA\u52D5\u751F\u6210).${effectiveFormat}`);
|
|
1086
|
+
console.log("\nAPI\u306F\u547C\u3073\u51FA\u3055\u308C\u307E\u305B\u3093\u3002\u5B9F\u884C\u3059\u308B\u306B\u306F --dry-run \u3092\u5916\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
|
|
1087
|
+
}
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
try {
|
|
1091
|
+
const azureConfig = await getAzureConfig();
|
|
1092
|
+
const chatClient = new AzureChatClient(azureConfig);
|
|
1093
|
+
const imageClient = new AzureImageClient(azureConfig);
|
|
1094
|
+
const context = await loadContextFile(options.context);
|
|
1095
|
+
const prompt = await chatClient.generatePrompt(theme, context);
|
|
1096
|
+
if (options.debug) {
|
|
1097
|
+
await logger.debug("\u751F\u6210\u3055\u308C\u305F\u30D7\u30ED\u30F3\u30D7\u30C8", { prompt, theme });
|
|
1098
|
+
}
|
|
1099
|
+
const imageData = await imageClient.generateImage(prompt, {
|
|
1100
|
+
size: effectiveSize,
|
|
1101
|
+
quality: effectiveQuality
|
|
1102
|
+
});
|
|
1103
|
+
const fileName = await chatClient.generateFileName(theme);
|
|
1104
|
+
const { path: outputPath, format: format2 } = await resolveOutputPath(
|
|
1105
|
+
options.output,
|
|
1106
|
+
effectiveFormat,
|
|
1107
|
+
fileName
|
|
1108
|
+
);
|
|
1109
|
+
const finalOutputPath = await saveFileWithUniqueNameIfExists(outputPath, imageData);
|
|
1110
|
+
await logger.info("\u753B\u50CF\u3092\u751F\u6210\u3057\u307E\u3057\u305F", {
|
|
1111
|
+
path: finalOutputPath,
|
|
1112
|
+
format: format2,
|
|
1113
|
+
size: effectiveSize,
|
|
1114
|
+
quality: effectiveQuality,
|
|
1115
|
+
...options.preset ? { preset: options.preset } : {}
|
|
1116
|
+
});
|
|
1117
|
+
if (options.json) {
|
|
1118
|
+
printJson(
|
|
1119
|
+
createSuccessOutput("image gen", {
|
|
1120
|
+
path: finalOutputPath,
|
|
1121
|
+
format: format2,
|
|
1122
|
+
size: effectiveSize,
|
|
1123
|
+
quality: effectiveQuality,
|
|
1124
|
+
...options.preset ? { preset: options.preset } : {}
|
|
1125
|
+
})
|
|
1126
|
+
);
|
|
1127
|
+
} else {
|
|
1128
|
+
console.log(`\u753B\u50CF\u3092\u751F\u6210\u3057\u307E\u3057\u305F: ${finalOutputPath}`);
|
|
1129
|
+
}
|
|
1130
|
+
} catch (error) {
|
|
1131
|
+
if (error instanceof Error) {
|
|
1132
|
+
await logger.error("\u753B\u50CF\u751F\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F", { error: error.message });
|
|
1133
|
+
if (options.json) {
|
|
1134
|
+
printJson(createErrorOutput("image gen", error.message));
|
|
1135
|
+
} else {
|
|
1136
|
+
console.error("\u30A8\u30E9\u30FC:", error.message);
|
|
1137
|
+
}
|
|
1138
|
+
} else {
|
|
1139
|
+
await logger.error("\u4E0D\u660E\u306A\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F");
|
|
1140
|
+
if (options.json) {
|
|
1141
|
+
printJson(createErrorOutput("image gen", "\u4E0D\u660E\u306A\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"));
|
|
1142
|
+
} else {
|
|
1143
|
+
console.error("\u4E0D\u660E\u306A\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F");
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
process.exit(1);
|
|
1147
|
+
}
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
// src/commands/image/index.ts
|
|
1152
|
+
function imageCommand() {
|
|
1153
|
+
const image = new Command5("image").description("\u753B\u50CF\u751F\u6210\u30FB\u7DE8\u96C6\u30FB\u8AAC\u660E (gpt-image-1.5 / gpt-5.1)");
|
|
1154
|
+
image.addCommand(imageGenCommand());
|
|
1155
|
+
image.addCommand(imageEditCommand());
|
|
1156
|
+
image.addCommand(imageExplainCommand());
|
|
1157
|
+
return image;
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
// src/commands/log.ts
|
|
1161
|
+
import chalk from "chalk";
|
|
1162
|
+
import { Command as Command6 } from "commander";
|
|
1163
|
+
function parseLogLevel(level) {
|
|
1164
|
+
switch (level.toLowerCase()) {
|
|
1165
|
+
case "debug":
|
|
1166
|
+
return "DEBUG" /* DEBUG */;
|
|
1167
|
+
case "info":
|
|
1168
|
+
return "INFO" /* INFO */;
|
|
1169
|
+
case "warn":
|
|
1170
|
+
return "WARN" /* WARN */;
|
|
1171
|
+
case "error":
|
|
1172
|
+
return "ERROR" /* ERROR */;
|
|
1173
|
+
default:
|
|
1174
|
+
return "INFO" /* INFO */;
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
function displayLogs(entries) {
|
|
1178
|
+
for (const entry of entries) {
|
|
1179
|
+
const timestamp = entry.timestamp.replace("T", " ").replace(/\.\d+Z$/, "");
|
|
1180
|
+
let levelColor;
|
|
1181
|
+
switch (entry.level) {
|
|
1182
|
+
case "DEBUG" /* DEBUG */:
|
|
1183
|
+
levelColor = chalk.gray;
|
|
1184
|
+
break;
|
|
1185
|
+
case "INFO" /* INFO */:
|
|
1186
|
+
levelColor = chalk.blue;
|
|
1187
|
+
break;
|
|
1188
|
+
case "WARN" /* WARN */:
|
|
1189
|
+
levelColor = chalk.yellow;
|
|
1190
|
+
break;
|
|
1191
|
+
case "ERROR" /* ERROR */:
|
|
1192
|
+
levelColor = chalk.red;
|
|
1193
|
+
break;
|
|
1194
|
+
default:
|
|
1195
|
+
levelColor = chalk.white;
|
|
1196
|
+
}
|
|
1197
|
+
const levelText = levelColor(entry.level);
|
|
1198
|
+
const dataText = entry.data ? ` ${JSON.stringify(entry.data)}` : "";
|
|
1199
|
+
console.log(`[${timestamp}] ${levelText} ${entry.message}${dataText}`);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
function logCommand() {
|
|
1203
|
+
return new Command6("log").description("\u30ED\u30B0\u3092\u8868\u793A\u3057\u307E\u3059").option("-n, --lines <lines>", "\u8868\u793A\u3059\u308B\u884C\u6570", "20").option("-l, --level <level>", "\u8868\u793A\u3059\u308B\u30ED\u30B0\u30EC\u30D9\u30EB (debug, info, warn, error)", "info").action(async (options) => {
|
|
1204
|
+
const lines = parseInt(options.lines, 10) || 20;
|
|
1205
|
+
const logLevel = parseLogLevel(options.level);
|
|
1206
|
+
const logger = Logger.getInstance({
|
|
1207
|
+
name: "imgen",
|
|
1208
|
+
config: { destination: "CONSOLE" /* CONSOLE */ }
|
|
1209
|
+
});
|
|
1210
|
+
try {
|
|
1211
|
+
const entries = await logger.getLogEntries(logLevel, lines);
|
|
1212
|
+
if (entries.length === 0) {
|
|
1213
|
+
console.log("\u30ED\u30B0\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3067\u3057\u305F");
|
|
1214
|
+
return;
|
|
1215
|
+
}
|
|
1216
|
+
displayLogs(entries);
|
|
1217
|
+
} catch (error) {
|
|
1218
|
+
console.error(
|
|
1219
|
+
`\u30ED\u30B0\u306E\u53D6\u5F97\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${error instanceof Error ? error.message : String(error)}`
|
|
1220
|
+
);
|
|
1221
|
+
}
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
// src/commands/preset.ts
|
|
1226
|
+
import { Command as Command7 } from "commander";
|
|
1227
|
+
function presetCommand() {
|
|
1228
|
+
const preset = new Command7("preset").description("\u753B\u50CF\u751F\u6210\u30D7\u30EA\u30BB\u30C3\u30C8\u3092\u7BA1\u7406\u3057\u307E\u3059");
|
|
1229
|
+
preset.command("list").description("\u30D7\u30EA\u30BB\u30C3\u30C8\u4E00\u89A7\u3092\u8868\u793A").option("--json", "JSON\u5F62\u5F0F\u3067\u51FA\u529B").action(async (options) => {
|
|
1230
|
+
try {
|
|
1231
|
+
const presets = await listAllPresets();
|
|
1232
|
+
if (options.json) {
|
|
1233
|
+
console.log(JSON.stringify(presets, null, 2));
|
|
1234
|
+
return;
|
|
1235
|
+
}
|
|
1236
|
+
console.log(`
|
|
1237
|
+
\u30D7\u30EA\u30BB\u30C3\u30C8\u4E00\u89A7 (${getPresetsPath()})
|
|
1238
|
+
`);
|
|
1239
|
+
if (presets.length === 0) {
|
|
1240
|
+
console.log("\u30D7\u30EA\u30BB\u30C3\u30C8\u304C\u3042\u308A\u307E\u305B\u3093\u3002");
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
const builtins = presets.filter((p) => p.builtin);
|
|
1244
|
+
if (builtins.length > 0) {
|
|
1245
|
+
console.log("\u30D3\u30EB\u30C8\u30A4\u30F3\u30D7\u30EA\u30BB\u30C3\u30C8:");
|
|
1246
|
+
for (const { name, preset: preset2 } of builtins) {
|
|
1247
|
+
console.log(` ${name}`);
|
|
1248
|
+
formatPreset(preset2, " ");
|
|
1249
|
+
}
|
|
1250
|
+
console.log();
|
|
1251
|
+
}
|
|
1252
|
+
const userDefined = presets.filter((p) => !p.builtin);
|
|
1253
|
+
if (userDefined.length > 0) {
|
|
1254
|
+
console.log("\u30E6\u30FC\u30B6\u30FC\u5B9A\u7FA9\u30D7\u30EA\u30BB\u30C3\u30C8:");
|
|
1255
|
+
for (const { name, preset: preset2 } of userDefined) {
|
|
1256
|
+
console.log(` ${name}`);
|
|
1257
|
+
formatPreset(preset2, " ");
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
} catch (error) {
|
|
1261
|
+
if (error instanceof Error) {
|
|
1262
|
+
console.error("\u30A8\u30E9\u30FC:", error.message);
|
|
1263
|
+
} else {
|
|
1264
|
+
console.error("\u4E0D\u660E\u306A\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F");
|
|
1265
|
+
}
|
|
1266
|
+
process.exit(1);
|
|
1267
|
+
}
|
|
1268
|
+
});
|
|
1269
|
+
preset.command("save <name>").description("\u65B0\u3057\u3044\u30D7\u30EA\u30BB\u30C3\u30C8\u3092\u4FDD\u5B58").option("-s, --size <size>", "\u753B\u50CF\u30B5\u30A4\u30BA (1024x1024, 1536x1024, 1024x1536)").option("-q, --quality <quality>", "\u753B\u50CF\u54C1\u8CEA (low, medium, high)").option("-f, --format <format>", "\u753B\u50CF\u30D5\u30A9\u30FC\u30DE\u30C3\u30C8 (png, jpg, webp)").action(
|
|
1270
|
+
async (name, options) => {
|
|
1271
|
+
try {
|
|
1272
|
+
const preset2 = {};
|
|
1273
|
+
if (options.size) preset2.size = options.size;
|
|
1274
|
+
if (options.quality) preset2.quality = options.quality;
|
|
1275
|
+
if (options.format) preset2.format = options.format;
|
|
1276
|
+
if (Object.keys(preset2).length === 0) {
|
|
1277
|
+
console.error("\u30A8\u30E9\u30FC: \u5C11\u306A\u304F\u3068\u30821\u3064\u306E\u30AA\u30D7\u30B7\u30E7\u30F3\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044");
|
|
1278
|
+
process.exit(1);
|
|
1279
|
+
}
|
|
1280
|
+
await savePreset(name, preset2);
|
|
1281
|
+
console.log(`\u30D7\u30EA\u30BB\u30C3\u30C8 '${name}' \u3092\u4FDD\u5B58\u3057\u307E\u3057\u305F\u3002`);
|
|
1282
|
+
} catch (error) {
|
|
1283
|
+
if (error instanceof Error) {
|
|
1284
|
+
console.error("\u30A8\u30E9\u30FC:", error.message);
|
|
1285
|
+
} else {
|
|
1286
|
+
console.error("\u4E0D\u660E\u306A\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F");
|
|
1287
|
+
}
|
|
1288
|
+
process.exit(1);
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
);
|
|
1292
|
+
preset.command("delete <name>").description("\u30D7\u30EA\u30BB\u30C3\u30C8\u3092\u524A\u9664").action(async (name) => {
|
|
1293
|
+
try {
|
|
1294
|
+
const deleted = await deletePreset(name);
|
|
1295
|
+
if (deleted) {
|
|
1296
|
+
console.log(`\u30D7\u30EA\u30BB\u30C3\u30C8 '${name}' \u3092\u524A\u9664\u3057\u307E\u3057\u305F\u3002`);
|
|
1297
|
+
} else {
|
|
1298
|
+
console.error(`\u30D7\u30EA\u30BB\u30C3\u30C8 '${name}' \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002`);
|
|
1299
|
+
process.exit(1);
|
|
1300
|
+
}
|
|
1301
|
+
} catch (error) {
|
|
1302
|
+
if (error instanceof Error) {
|
|
1303
|
+
console.error("\u30A8\u30E9\u30FC:", error.message);
|
|
1304
|
+
} else {
|
|
1305
|
+
console.error("\u4E0D\u660E\u306A\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F");
|
|
1306
|
+
}
|
|
1307
|
+
process.exit(1);
|
|
1308
|
+
}
|
|
1309
|
+
});
|
|
1310
|
+
return preset;
|
|
1311
|
+
}
|
|
1312
|
+
function formatPreset(preset, indent) {
|
|
1313
|
+
if (preset.size) console.log(`${indent}\u30B5\u30A4\u30BA: ${preset.size}`);
|
|
1314
|
+
if (preset.quality) console.log(`${indent}\u54C1\u8CEA: ${preset.quality}`);
|
|
1315
|
+
if (preset.format) console.log(`${indent}\u30D5\u30A9\u30FC\u30DE\u30C3\u30C8: ${preset.format}`);
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
// src/commands/index.ts
|
|
1319
|
+
function createMainCommand() {
|
|
1320
|
+
const program = new Command8().name("imgen").version("0.1.0").description("Azure OpenAI \u753B\u50CF\u751F\u6210\u30FB\u7DE8\u96C6\u30FB\u8AAC\u660E\u30C4\u30FC\u30EB - gpt-image-1.5 / gpt-5.1").addHelpText(
|
|
1321
|
+
"before",
|
|
1322
|
+
`
|
|
1323
|
+
\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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
|
|
1324
|
+
\u2551 \u2551
|
|
1325
|
+
\u2551 imgen - Azure OpenAI Image Generation CLI \u2551
|
|
1326
|
+
\u2551 \u2551
|
|
1327
|
+
\u2551 gpt-image-1.5 \u753B\u50CF\u751F\u6210\u30FB\u7DE8\u96C6 \u2551
|
|
1328
|
+
\u2551 gpt-5.1 \u30D7\u30ED\u30F3\u30D7\u30C8\u62E1\u5F35\u30FB\u753B\u50CF\u8AAC\u660E \u2551
|
|
1329
|
+
\u2551 \u2551
|
|
1330
|
+
\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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
|
|
1331
|
+
`
|
|
1332
|
+
).addHelpText(
|
|
1333
|
+
"after",
|
|
1334
|
+
`
|
|
1335
|
+
\u4F7F\u7528\u4F8B:
|
|
1336
|
+
$ imgen image gen "\u5915\u65E5\u306E\u6D77\u8FBA" -q high -t vivid
|
|
1337
|
+
$ imgen image gen "\u53EF\u611B\u3044\u732B" -s 1024x1024 -p builtin:square
|
|
1338
|
+
$ imgen image edit photo.png "\u80CC\u666F\u3092\u9752\u7A7A\u306B"
|
|
1339
|
+
$ imgen image explain screenshot.png -l en
|
|
1340
|
+
|
|
1341
|
+
\u30AF\u30A4\u30C3\u30AF\u30B9\u30BF\u30FC\u30C8:
|
|
1342
|
+
1. \u8A2D\u5B9A: imgen configure
|
|
1343
|
+
2. \u753B\u50CF\u751F\u6210: imgen image gen "\u30C6\u30FC\u30DE"
|
|
1344
|
+
`
|
|
1345
|
+
).action(() => {
|
|
1346
|
+
program.help();
|
|
1347
|
+
});
|
|
1348
|
+
program.addCommand(imageCommand());
|
|
1349
|
+
program.addCommand(presetCommand());
|
|
1350
|
+
program.addCommand(configureCommand());
|
|
1351
|
+
program.addCommand(logCommand());
|
|
1352
|
+
return program;
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
// src/index.ts
|
|
1356
|
+
async function runCli() {
|
|
1357
|
+
try {
|
|
1358
|
+
const program = createMainCommand();
|
|
1359
|
+
await program.parseAsync(process.argv);
|
|
1360
|
+
} catch (error) {
|
|
1361
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
1362
|
+
process.exit(1);
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
runCli();
|