clawbr 0.0.41 → 0.0.43
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 +52 -0
- package/dist/app.module.js +5 -1
- package/dist/app.module.js.map +1 -1
- package/dist/commands/delete-comment.command.js +139 -0
- package/dist/commands/delete-comment.command.js.map +1 -0
- package/dist/commands/delete-post.command.js +139 -0
- package/dist/commands/delete-post.command.js.map +1 -0
- package/dist/commands/generate.command.js +97 -27
- package/dist/commands/generate.command.js.map +1 -1
- package/dist/commands/tui.command.js +161 -28
- package/dist/commands/tui.command.js.map +1 -1
- package/dist/config/image-models.js +79 -29
- package/dist/config/image-models.js.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
|
@@ -22,7 +22,7 @@ import { getProviderModels, getModelById, isValidModel, getPrimaryModel, getFall
|
|
|
22
22
|
export class GenerateCommand extends CommandRunner {
|
|
23
23
|
async run(inputs, options) {
|
|
24
24
|
await requireOnboarding();
|
|
25
|
-
const { prompt, output, size = "1024x1024", sourceImage, model, json = false } = options;
|
|
25
|
+
const { prompt, output, size = "1024x1024", sourceImage, model, aspectRatio, imageSize, json = false } = options;
|
|
26
26
|
// ─────────────────────────────────────────────────────────────────────
|
|
27
27
|
// Validation
|
|
28
28
|
// ─────────────────────────────────────────────────────────────────────
|
|
@@ -86,30 +86,37 @@ export class GenerateCommand extends CommandRunner {
|
|
|
86
86
|
const spinner = json ? null : ora(sourceImageData ? "Generating image from source..." : "Generating image...").start();
|
|
87
87
|
try {
|
|
88
88
|
let imageBuffer;
|
|
89
|
-
|
|
89
|
+
let modelUsed;
|
|
90
|
+
// Determine models to try
|
|
90
91
|
const primaryModel = model || getPrimaryModel(aiProvider);
|
|
92
|
+
// If the user explicitly specified a model, disable fallbacks so we
|
|
93
|
+
// honour their choice strictly and fail fast if it doesn't work.
|
|
91
94
|
const fallbackModels = model ? [] : getFallbackModels(aiProvider);
|
|
95
|
+
// Pass aspect ratio and image size to generation
|
|
96
|
+
const imageConfig = {};
|
|
97
|
+
if (aspectRatio) imageConfig.aspectRatio = aspectRatio;
|
|
98
|
+
if (imageSize) imageConfig.imageSize = imageSize;
|
|
92
99
|
if (aiProvider === "openrouter") {
|
|
93
|
-
imageBuffer = await this.generateWithFallback(prompt, size, apiKey, "openrouter", {
|
|
100
|
+
({ buffer: imageBuffer, modelUsed } = await this.generateWithFallback(prompt, size, apiKey, "openrouter", {
|
|
94
101
|
primary: primaryModel,
|
|
95
102
|
fallbacks: fallbackModels
|
|
96
|
-
}, spinner, sourceImageData);
|
|
103
|
+
}, spinner, sourceImageData, imageConfig));
|
|
97
104
|
} else if (aiProvider === "openai") {
|
|
98
105
|
if (sourceImageData) {
|
|
99
106
|
throw new Error("OpenAI does not support image-to-image generation. Use OpenRouter with a model that supports reference images.");
|
|
100
107
|
}
|
|
101
|
-
imageBuffer = await this.generateWithFallback(prompt, size, apiKey, "openai", {
|
|
108
|
+
({ buffer: imageBuffer, modelUsed } = await this.generateWithFallback(prompt, size, apiKey, "openai", {
|
|
102
109
|
primary: primaryModel,
|
|
103
110
|
fallbacks: fallbackModels
|
|
104
|
-
}, spinner);
|
|
111
|
+
}, spinner));
|
|
105
112
|
} else if (aiProvider === "google") {
|
|
106
113
|
if (sourceImageData) {
|
|
107
114
|
throw new Error("Google Imagen does not support image-to-image generation. Use OpenRouter with a model that supports reference images.");
|
|
108
115
|
}
|
|
109
|
-
imageBuffer = await this.generateWithFallback(prompt, size, apiKey, "google", {
|
|
116
|
+
({ buffer: imageBuffer, modelUsed } = await this.generateWithFallback(prompt, size, apiKey, "google", {
|
|
110
117
|
primary: primaryModel,
|
|
111
118
|
fallbacks: fallbackModels
|
|
112
|
-
}, spinner);
|
|
119
|
+
}, spinner));
|
|
113
120
|
} else {
|
|
114
121
|
if (spinner) spinner.fail();
|
|
115
122
|
throw new Error(`Unsupported AI provider: ${aiProvider}`);
|
|
@@ -131,7 +138,8 @@ export class GenerateCommand extends CommandRunner {
|
|
|
131
138
|
prompt,
|
|
132
139
|
output: outputPath,
|
|
133
140
|
size,
|
|
134
|
-
provider: aiProvider
|
|
141
|
+
provider: aiProvider,
|
|
142
|
+
modelUsed
|
|
135
143
|
}, null, 2));
|
|
136
144
|
} else {
|
|
137
145
|
console.log("\n🎨 Image Generation Complete!");
|
|
@@ -158,7 +166,7 @@ export class GenerateCommand extends CommandRunner {
|
|
|
158
166
|
/**
|
|
159
167
|
* Generate image with smart fallback chain
|
|
160
168
|
* Tries primary model first, then falls back to alternatives if it fails
|
|
161
|
-
*/ async generateWithFallback(prompt, size, apiKey, provider, config, spinner,
|
|
169
|
+
*/ async generateWithFallback(prompt, size, apiKey, provider, config, spinner, sourceImageData, imageConfig) {
|
|
162
170
|
const modelsToTry = [
|
|
163
171
|
config.primary,
|
|
164
172
|
...config.fallbacks
|
|
@@ -171,12 +179,15 @@ export class GenerateCommand extends CommandRunner {
|
|
|
171
179
|
const modelName = model.split("/").pop() || model;
|
|
172
180
|
spinner.text = `Generating image with ${modelName}... (attempt ${i + 1}/${modelsToTry.length})`;
|
|
173
181
|
}
|
|
174
|
-
const imageBuffer = await this.generateWithModel(prompt, size, apiKey, provider, model,
|
|
182
|
+
const imageBuffer = await this.generateWithModel(prompt, size, apiKey, provider, model, sourceImageData, imageConfig);
|
|
175
183
|
if (spinner && i > 0) {
|
|
176
184
|
// Only show fallback message if we had to fall back
|
|
177
185
|
spinner.info(`Successfully generated with fallback model: ${model}`);
|
|
178
186
|
}
|
|
179
|
-
return
|
|
187
|
+
return {
|
|
188
|
+
buffer: imageBuffer,
|
|
189
|
+
modelUsed: model
|
|
190
|
+
};
|
|
180
191
|
} catch (error) {
|
|
181
192
|
lastError = error;
|
|
182
193
|
// If this wasn't the last model, log the failure and try the next one
|
|
@@ -211,22 +222,42 @@ export class GenerateCommand extends CommandRunner {
|
|
|
211
222
|
}
|
|
212
223
|
/**
|
|
213
224
|
* Generate image using a specific model
|
|
214
|
-
*/ async generateWithModel(prompt, size, apiKey, provider, model,
|
|
225
|
+
*/ async generateWithModel(prompt, size, apiKey, provider, model, sourceImageData, imageConfig) {
|
|
215
226
|
// ─────────────────────────────────────────────────────────────────────
|
|
216
227
|
// OPENROUTER (Via Fetch / Chat Completions)
|
|
217
228
|
// ─────────────────────────────────────────────────────────────────────
|
|
218
229
|
if (provider === "openrouter") {
|
|
219
|
-
//
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
230
|
+
// Calculate aspect ratio from size if not provided
|
|
231
|
+
let aspectRatio = imageConfig?.aspectRatio || "1:1";
|
|
232
|
+
if (!imageConfig?.aspectRatio) {
|
|
233
|
+
const [width, height] = size.split("x").map(Number);
|
|
234
|
+
if (width && height) {
|
|
235
|
+
const gcd = (a, b)=>b === 0 ? a : gcd(b, a % b);
|
|
236
|
+
const divisor = gcd(width, height);
|
|
237
|
+
const calculated = `${width / divisor}:${height / divisor}`;
|
|
238
|
+
// Map calculated ratio to supported OpenRouter ratios
|
|
239
|
+
const supportedRatios = {
|
|
240
|
+
"1:1": "1:1",
|
|
241
|
+
"2:3": "2:3",
|
|
242
|
+
"3:2": "3:2",
|
|
243
|
+
"3:4": "3:4",
|
|
244
|
+
"4:3": "4:3",
|
|
245
|
+
"4:5": "4:5",
|
|
246
|
+
"5:4": "5:4",
|
|
247
|
+
"9:16": "9:16",
|
|
248
|
+
"16:9": "16:9",
|
|
249
|
+
"21:9": "21:9",
|
|
250
|
+
// Common unsupported ratios mapped to closest supported
|
|
251
|
+
"7:4": "16:9",
|
|
252
|
+
"4:7": "9:16",
|
|
253
|
+
"64:27": "21:9"
|
|
254
|
+
};
|
|
255
|
+
aspectRatio = supportedRatios[calculated] || "1:1";
|
|
256
|
+
}
|
|
226
257
|
}
|
|
227
|
-
// Build
|
|
258
|
+
// Build messages array
|
|
228
259
|
let content;
|
|
229
|
-
if (
|
|
260
|
+
if (sourceImageData) {
|
|
230
261
|
// Image-to-image generation: include source image in content
|
|
231
262
|
content = [
|
|
232
263
|
{
|
|
@@ -236,7 +267,7 @@ export class GenerateCommand extends CommandRunner {
|
|
|
236
267
|
{
|
|
237
268
|
type: "image_url",
|
|
238
269
|
image_url: {
|
|
239
|
-
url:
|
|
270
|
+
url: sourceImageData
|
|
240
271
|
}
|
|
241
272
|
}
|
|
242
273
|
];
|
|
@@ -265,9 +296,16 @@ export class GenerateCommand extends CommandRunner {
|
|
|
265
296
|
"image",
|
|
266
297
|
"text"
|
|
267
298
|
],
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
299
|
+
...aspectRatio || imageConfig?.imageSize ? {
|
|
300
|
+
image_config: {
|
|
301
|
+
...aspectRatio ? {
|
|
302
|
+
aspect_ratio: aspectRatio
|
|
303
|
+
} : {},
|
|
304
|
+
...imageConfig?.imageSize ? {
|
|
305
|
+
image_size: imageConfig.imageSize
|
|
306
|
+
} : {}
|
|
307
|
+
}
|
|
308
|
+
} : {}
|
|
271
309
|
})
|
|
272
310
|
});
|
|
273
311
|
if (!response.ok) {
|
|
@@ -279,7 +317,11 @@ export class GenerateCommand extends CommandRunner {
|
|
|
279
317
|
const imageUrl = result.choices[0].message.images[0].image_url.url;
|
|
280
318
|
// If it's a URL, fetch it
|
|
281
319
|
if (imageUrl.startsWith("http")) {
|
|
282
|
-
const imgRes = await fetch(imageUrl
|
|
320
|
+
const imgRes = await fetch(imageUrl, {
|
|
321
|
+
headers: {
|
|
322
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"
|
|
323
|
+
}
|
|
324
|
+
});
|
|
283
325
|
const arrayBuffer = await imgRes.arrayBuffer();
|
|
284
326
|
return Buffer.from(arrayBuffer);
|
|
285
327
|
}
|
|
@@ -323,6 +365,12 @@ export class GenerateCommand extends CommandRunner {
|
|
|
323
365
|
parseModel(val) {
|
|
324
366
|
return val;
|
|
325
367
|
}
|
|
368
|
+
parseAspectRatio(val) {
|
|
369
|
+
return val;
|
|
370
|
+
}
|
|
371
|
+
parseImageSize(val) {
|
|
372
|
+
return val;
|
|
373
|
+
}
|
|
326
374
|
parseJson() {
|
|
327
375
|
return true;
|
|
328
376
|
}
|
|
@@ -382,6 +430,28 @@ _ts_decorate([
|
|
|
382
430
|
]),
|
|
383
431
|
_ts_metadata("design:returntype", String)
|
|
384
432
|
], GenerateCommand.prototype, "parseModel", null);
|
|
433
|
+
_ts_decorate([
|
|
434
|
+
Option({
|
|
435
|
+
flags: "--aspect-ratio <ratio>",
|
|
436
|
+
description: "Aspect ratio for generated image (OpenRouter only). Supported: 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9"
|
|
437
|
+
}),
|
|
438
|
+
_ts_metadata("design:type", Function),
|
|
439
|
+
_ts_metadata("design:paramtypes", [
|
|
440
|
+
String
|
|
441
|
+
]),
|
|
442
|
+
_ts_metadata("design:returntype", String)
|
|
443
|
+
], GenerateCommand.prototype, "parseAspectRatio", null);
|
|
444
|
+
_ts_decorate([
|
|
445
|
+
Option({
|
|
446
|
+
flags: "--image-size <size>",
|
|
447
|
+
description: "Image resolution size (OpenRouter only). Supported: 1K (standard), 2K (higher), 4K (highest)"
|
|
448
|
+
}),
|
|
449
|
+
_ts_metadata("design:type", Function),
|
|
450
|
+
_ts_metadata("design:paramtypes", [
|
|
451
|
+
String
|
|
452
|
+
]),
|
|
453
|
+
_ts_metadata("design:returntype", String)
|
|
454
|
+
], GenerateCommand.prototype, "parseImageSize", null);
|
|
385
455
|
_ts_decorate([
|
|
386
456
|
Option({
|
|
387
457
|
flags: "--json",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/commands/generate.command.ts"],"sourcesContent":["import { Command, CommandRunner, Option } from \"nest-commander\";\nimport { writeFileSync } from \"fs\";\nimport ora from \"ora\";\nimport fetch from \"node-fetch\";\nimport { resolve } from \"path\";\nimport { generateImage } from \"ai\";\nimport { createOpenAI } from \"@ai-sdk/openai\";\nimport { createGoogleGenerativeAI } from \"@ai-sdk/google\";\nimport { loadCredentials } from \"../utils/credentials.js\";\nimport { resolveImageToDataUri, validateImageInput } from \"../utils/image.js\";\nimport { requireOnboarding } from \"../utils/config.js\";\nimport {\n getProviderModels,\n getModelById,\n isValidModel,\n getPrimaryModel,\n getFallbackModels,\n supportsReferenceImage,\n formatModelList,\n} from \"../config/image-models.js\";\n\ninterface GenerateCommandOptions {\n prompt?: string;\n output?: string;\n size?: string;\n sourceImage?: string;\n model?: string;\n json?: boolean;\n}\n\n@Command({\n name: \"generate\",\n description: \"Generate an image using AI with smart model fallback\",\n arguments: \"\",\n options: { isDefault: false },\n})\nexport class GenerateCommand extends CommandRunner {\n async run(inputs: string[], options: GenerateCommandOptions): Promise<void> {\n await requireOnboarding();\n const { prompt, output, size = \"1024x1024\", sourceImage, model, json = false } = options;\n\n // ─────────────────────────────────────────────────────────────────────\n // Validation\n // ─────────────────────────────────────────────────────────────────────\n if (!prompt) {\n throw new Error(\n '--prompt is required. Example: clawbr generate --prompt \"a robot building software\" --output \"./robot.png\"'\n );\n }\n\n if (!output) {\n throw new Error(\n '--output is required. Example: clawbr generate --prompt \"...\" --output \"./image.png\"'\n );\n }\n\n // Validate source image if provided\n if (sourceImage) {\n const validation = validateImageInput(sourceImage);\n if (!validation.valid) {\n throw new Error(validation.error);\n }\n }\n\n // Validate size\n const validSizes = [\"256x256\", \"512x512\", \"1024x1024\", \"1792x1024\", \"1024x1792\"];\n if (!validSizes.includes(size)) {\n throw new Error(`Invalid size. Must be one of: ${validSizes.join(\", \")}`);\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // Load Credentials\n // ─────────────────────────────────────────────────────────────────────\n const credentials = loadCredentials();\n\n if (!credentials) {\n throw new Error(\"Credentials not found. Run 'clawbr onboard' first to set up your account.\");\n }\n\n const { aiProvider, apiKeys } = credentials;\n const apiKey = apiKeys[aiProvider as keyof typeof apiKeys];\n\n if (!apiKey) {\n throw new Error(\n `No API key found for provider '${aiProvider}'. Run 'clawbr onboard' to configure.`\n );\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // Validate model if provided\n // ─────────────────────────────────────────────────────────────────────\n if (model && !isValidModel(aiProvider, model)) {\n const availableModels = formatModelList(aiProvider);\n throw new Error(\n `Invalid model '${model}' for provider '${aiProvider}'.\\n\\nAvailable models:\\n${availableModels}`\n );\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // Check reference image support\n // ─────────────────────────────────────────────────────────────────────\n if (sourceImage && model && !supportsReferenceImage(aiProvider, model)) {\n const modelInfo = getModelById(aiProvider, model);\n throw new Error(\n `Model '${modelInfo?.name || model}' does not support reference images.\\n\\n` +\n `For reference image support with ${aiProvider}, use one of:\\n` +\n getProviderModels(aiProvider)\n .filter((m) => m.supportsReferenceImage)\n .map((m) => ` • ${m.id}`)\n .join(\"\\n\")\n );\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // Prepare source image if provided\n // ─────────────────────────────────────────────────────────────────────\n const sourceImageData = sourceImage ? await resolveImageToDataUri(sourceImage) : undefined;\n\n // ─────────────────────────────────────────────────────────────────────\n // Generate Image with Smart Fallback\n // ─────────────────────────────────────────────────────────────────────\n const spinner = json\n ? null\n : ora(sourceImageData ? \"Generating image from source...\" : \"Generating image...\").start();\n\n try {\n let imageBuffer: Buffer;\n\n // Determine models to use\n const primaryModel = model || getPrimaryModel(aiProvider);\n const fallbackModels = model ? [] : getFallbackModels(aiProvider);\n\n if (aiProvider === \"openrouter\") {\n imageBuffer = await this.generateWithFallback(\n prompt,\n size,\n apiKey,\n \"openrouter\",\n { primary: primaryModel, fallbacks: fallbackModels },\n spinner,\n sourceImageData\n );\n } else if (aiProvider === \"openai\") {\n if (sourceImageData) {\n throw new Error(\n \"OpenAI does not support image-to-image generation. Use OpenRouter with a model that supports reference images.\"\n );\n }\n imageBuffer = await this.generateWithFallback(\n prompt,\n size,\n apiKey,\n \"openai\",\n { primary: primaryModel, fallbacks: fallbackModels },\n spinner\n );\n } else if (aiProvider === \"google\") {\n if (sourceImageData) {\n throw new Error(\n \"Google Imagen does not support image-to-image generation. Use OpenRouter with a model that supports reference images.\"\n );\n }\n imageBuffer = await this.generateWithFallback(\n prompt,\n size,\n apiKey,\n \"google\",\n { primary: primaryModel, fallbacks: fallbackModels },\n spinner\n );\n } else {\n if (spinner) spinner.fail();\n throw new Error(`Unsupported AI provider: ${aiProvider}`);\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // Save Image\n // ─────────────────────────────────────────────────────────────────────\n const outputPath = resolve(output);\n writeFileSync(outputPath, imageBuffer);\n\n if (spinner) {\n spinner.succeed(`Image generated and saved to: ${outputPath}`);\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // Output\n // ─────────────────────────────────────────────────────────────────────\n if (json) {\n console.log(\n JSON.stringify(\n {\n success: true,\n prompt,\n output: outputPath,\n size,\n provider: aiProvider,\n },\n null,\n 2\n )\n );\n } else {\n console.log(\"\\n🎨 Image Generation Complete!\");\n console.log(\"─────────────────────────────────────\");\n console.log(`Prompt: ${prompt}`);\n console.log(`Size: ${size}`);\n if (sourceImageData) {\n console.log(`Source Image: ${sourceImage}`);\n }\n console.log(`Output: ${outputPath}`);\n console.log(`Provider: ${aiProvider}`);\n if (model) {\n console.log(`Model: ${model}`);\n }\n console.log(\"─────────────────────────────────────\\n\");\n }\n } catch (error) {\n if (spinner && spinner.isSpinning) {\n spinner.fail(\"Image generation failed\");\n }\n throw error;\n }\n }\n\n /**\n * Generate image with smart fallback chain\n * Tries primary model first, then falls back to alternatives if it fails\n */\n private async generateWithFallback(\n prompt: string,\n size: string,\n apiKey: string,\n provider: \"openrouter\" | \"openai\" | \"google\",\n config: { primary: string | null; fallbacks: string[] },\n spinner: {\n text: string;\n info: (msg: string) => void;\n warn: (msg: string) => void;\n isSpinning?: boolean;\n } | null,\n sourceImage?: string\n ): Promise<Buffer> {\n const modelsToTry = [config.primary, ...config.fallbacks].filter(\n (model): model is string => model !== null\n );\n\n let lastError: Error | null = null;\n\n for (let i = 0; i < modelsToTry.length; i++) {\n const model = modelsToTry[i];\n\n try {\n if (spinner) {\n const modelName = model.split(\"/\").pop() || model;\n spinner.text = `Generating image with ${modelName}... (attempt ${i + 1}/${modelsToTry.length})`;\n }\n\n const imageBuffer = await this.generateWithModel(\n prompt,\n size,\n apiKey,\n provider,\n model,\n sourceImage\n );\n\n if (spinner && i > 0) {\n // Only show fallback message if we had to fall back\n spinner.info(`Successfully generated with fallback model: ${model}`);\n }\n\n return imageBuffer;\n } catch (error) {\n lastError = error as Error;\n\n // If this wasn't the last model, log the failure and try the next one\n if (i < modelsToTry.length - 1) {\n if (spinner) {\n spinner.warn(`Model ${model} failed, trying fallback...`);\n } else {\n console.warn(`Model ${model} failed: ${lastError.message}`);\n }\n continue;\n }\n }\n }\n\n // If we get here, all models failed\n throw new Error(\n `All models failed to generate image. Last error: ${lastError?.message || \"Unknown error\"}`\n );\n }\n\n /**\n * get the model configuration for the AI SDK\n */\n private getImageModel(provider: string, apiKey: string, model: string) {\n if (provider === \"openai\") {\n const openai = createOpenAI({ apiKey });\n return openai.image(model);\n } else if (provider === \"google\") {\n const google = createGoogleGenerativeAI({ apiKey });\n return google.image(model);\n }\n throw new Error(`Provider ${provider} not supported via AI SDK`);\n }\n\n /**\n * Generate image using a specific model\n */\n private async generateWithModel(\n prompt: string,\n size: string,\n apiKey: string,\n provider: \"openrouter\" | \"openai\" | \"google\",\n model: string,\n sourceImage?: string\n ): Promise<Buffer> {\n // ─────────────────────────────────────────────────────────────────────\n // OPENROUTER (Via Fetch / Chat Completions)\n // ─────────────────────────────────────────────────────────────────────\n if (provider === \"openrouter\") {\n // Parse aspect ratio from size\n const [width, height] = size.split(\"x\").map(Number);\n let aspectRatio = \"1:1\";\n if (width && height) {\n const gcd = (a: number, b: number): number => (b === 0 ? a : gcd(b, a % b));\n const divisor = gcd(width, height);\n aspectRatio = `${width / divisor}:${height / divisor}`;\n }\n\n // Build content array based on whether we have a source image\n let content: any;\n if (sourceImage) {\n // Image-to-image generation: include source image in content\n content = [\n {\n type: \"text\",\n text: prompt,\n },\n {\n type: \"image_url\",\n image_url: {\n url: sourceImage,\n },\n },\n ];\n } else {\n // Text-to-image generation: just the prompt\n content = prompt;\n }\n\n const response = await fetch(\"https://openrouter.ai/api/v1/chat/completions\", {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n \"HTTP-Referer\": \"https://clawbr.bricks-studio.ai\",\n \"X-Title\": \"clawbr CLI\",\n },\n body: JSON.stringify({\n model: model,\n messages: [\n {\n role: \"user\",\n content: content,\n },\n ],\n // Specific to Gemini/OpenRouter multimodal\n modalities: [\"image\", \"text\"],\n image_config: {\n aspect_ratio: aspectRatio,\n // image_size: \"4K\", // Optional based on snippet, but maybe risky for all models\n },\n }),\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`OpenRouter API error: ${text}`);\n }\n\n const result = (await response.json()) as any;\n\n if (result.choices?.[0]?.message?.images?.[0]?.image_url?.url) {\n const imageUrl = result.choices[0].message.images[0].image_url.url;\n\n // If it's a URL, fetch it\n if (imageUrl.startsWith(\"http\")) {\n const imgRes = await fetch(imageUrl);\n const arrayBuffer = await imgRes.arrayBuffer();\n return Buffer.from(arrayBuffer);\n }\n\n // If it's base64 data URI\n if (imageUrl.startsWith(\"data:image\")) {\n const base64Data = imageUrl.split(\",\")[1];\n return Buffer.from(base64Data, \"base64\");\n }\n\n throw new Error(\"Unknown image URL format\");\n }\n\n throw new Error(\"No image generated from OpenRouter response\");\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // OPENAI / GOOGLE (Via AI SDK)\n // ─────────────────────────────────────────────────────────────────────\n const imageModel = this.getImageModel(provider, apiKey, model);\n\n // Pass size as string directly as per SDK requirements.\n // We cast to 'any' to avoid strict template literal validation errors\n // since we know validSizes allows specifically \"1024x1024\" etc.\n const { image } = await generateImage({\n model: imageModel,\n prompt,\n n: 1,\n size: size as any,\n });\n\n // The image object from 'ai' SDK contains the base64 string\n return Buffer.from(image.base64, \"base64\");\n }\n\n @Option({\n flags: \"-p, --prompt <text>\",\n description: \"Text description of the image to generate\",\n })\n parsePrompt(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"-o, --output <path>\",\n description: \"Path where the generated image will be saved\",\n })\n parseOutput(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"-s, --size <size>\",\n description: \"Image size (256x256, 512x512, 1024x1024, 1792x1024, 1024x1792)\",\n })\n parseSize(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"--source-image <path>\",\n description: \"Path to source image or URL (for image-to-image generation, OpenRouter only)\",\n })\n parseSourceImage(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"-m, --model <modelId>\",\n description:\n \"Specific model to use (provider-dependent). Use model ID from your provider's list. Note: Not all models support reference images (--source-image).\",\n })\n parseModel(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"--json\",\n description: \"Output result in JSON format\",\n })\n parseJson(): boolean {\n return true;\n }\n}\n"],"names":["Command","CommandRunner","Option","writeFileSync","ora","fetch","resolve","generateImage","createOpenAI","createGoogleGenerativeAI","loadCredentials","resolveImageToDataUri","validateImageInput","requireOnboarding","getProviderModels","getModelById","isValidModel","getPrimaryModel","getFallbackModels","supportsReferenceImage","formatModelList","GenerateCommand","run","inputs","options","prompt","output","size","sourceImage","model","json","Error","validation","valid","error","validSizes","includes","join","credentials","aiProvider","apiKeys","apiKey","availableModels","modelInfo","name","filter","m","map","id","sourceImageData","undefined","spinner","start","imageBuffer","primaryModel","fallbackModels","generateWithFallback","primary","fallbacks","fail","outputPath","succeed","console","log","JSON","stringify","success","provider","isSpinning","config","modelsToTry","lastError","i","length","modelName","split","pop","text","generateWithModel","info","warn","message","getImageModel","openai","image","google","width","height","Number","aspectRatio","gcd","a","b","divisor","content","type","image_url","url","response","method","headers","Authorization","body","messages","role","modalities","image_config","aspect_ratio","ok","result","choices","images","imageUrl","startsWith","imgRes","arrayBuffer","Buffer","from","base64Data","imageModel","n","base64","parsePrompt","val","parseOutput","parseSize","parseSourceImage","parseModel","parseJson","flags","description","arguments","isDefault"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/commands/generate.command.ts"],"sourcesContent":["import { Command, CommandRunner, Option } from \"nest-commander\";\nimport { writeFileSync } from \"fs\";\nimport ora from \"ora\";\nimport fetch from \"node-fetch\";\nimport { resolve } from \"path\";\nimport { generateImage } from \"ai\";\nimport { createOpenAI } from \"@ai-sdk/openai\";\nimport { createGoogleGenerativeAI } from \"@ai-sdk/google\";\nimport { loadCredentials } from \"../utils/credentials.js\";\nimport { resolveImageToDataUri, validateImageInput } from \"../utils/image.js\";\nimport { requireOnboarding } from \"../utils/config.js\";\nimport {\n getProviderModels,\n getModelById,\n isValidModel,\n getPrimaryModel,\n getFallbackModels,\n supportsReferenceImage,\n formatModelList,\n} from \"../config/image-models.js\";\n\ninterface GenerateCommandOptions {\n prompt?: string;\n output?: string;\n size?: string;\n sourceImage?: string;\n model?: string;\n aspectRatio?: string;\n imageSize?: string;\n json?: boolean;\n}\n\n@Command({\n name: \"generate\",\n description: \"Generate an image using AI with smart model fallback\",\n arguments: \"\",\n options: { isDefault: false },\n})\nexport class GenerateCommand extends CommandRunner {\n async run(inputs: string[], options: GenerateCommandOptions): Promise<void> {\n await requireOnboarding();\n const {\n prompt,\n output,\n size = \"1024x1024\",\n sourceImage,\n model,\n aspectRatio,\n imageSize,\n json = false,\n } = options;\n\n // ─────────────────────────────────────────────────────────────────────\n // Validation\n // ─────────────────────────────────────────────────────────────────────\n if (!prompt) {\n throw new Error(\n '--prompt is required. Example: clawbr generate --prompt \"a robot building software\" --output \"./robot.png\"'\n );\n }\n\n if (!output) {\n throw new Error(\n '--output is required. Example: clawbr generate --prompt \"...\" --output \"./image.png\"'\n );\n }\n\n // Validate source image if provided\n if (sourceImage) {\n const validation = validateImageInput(sourceImage);\n if (!validation.valid) {\n throw new Error(validation.error);\n }\n }\n\n // Validate size\n const validSizes = [\"256x256\", \"512x512\", \"1024x1024\", \"1792x1024\", \"1024x1792\"];\n if (!validSizes.includes(size)) {\n throw new Error(`Invalid size. Must be one of: ${validSizes.join(\", \")}`);\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // Load Credentials\n // ─────────────────────────────────────────────────────────────────────\n const credentials = loadCredentials();\n\n if (!credentials) {\n throw new Error(\"Credentials not found. Run 'clawbr onboard' first to set up your account.\");\n }\n\n const { aiProvider, apiKeys } = credentials;\n const apiKey = apiKeys[aiProvider as keyof typeof apiKeys];\n\n if (!apiKey) {\n throw new Error(\n `No API key found for provider '${aiProvider}'. Run 'clawbr onboard' to configure.`\n );\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // Validate model if provided\n // ─────────────────────────────────────────────────────────────────────\n if (model && !isValidModel(aiProvider, model)) {\n const availableModels = formatModelList(aiProvider);\n throw new Error(\n `Invalid model '${model}' for provider '${aiProvider}'.\\n\\nAvailable models:\\n${availableModels}`\n );\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // Check reference image support\n // ─────────────────────────────────────────────────────────────────────\n if (sourceImage && model && !supportsReferenceImage(aiProvider, model)) {\n const modelInfo = getModelById(aiProvider, model);\n throw new Error(\n `Model '${modelInfo?.name || model}' does not support reference images.\\n\\n` +\n `For reference image support with ${aiProvider}, use one of:\\n` +\n getProviderModels(aiProvider)\n .filter((m) => m.supportsReferenceImage)\n .map((m) => ` • ${m.id}`)\n .join(\"\\n\")\n );\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // Prepare source image if provided\n // ─────────────────────────────────────────────────────────────────────\n const sourceImageData = sourceImage ? await resolveImageToDataUri(sourceImage) : undefined;\n\n // ─────────────────────────────────────────────────────────────────────\n // Generate Image with Smart Fallback\n // ─────────────────────────────────────────────────────────────────────\n const spinner = json\n ? null\n : ora(sourceImageData ? \"Generating image from source...\" : \"Generating image...\").start();\n\n try {\n let imageBuffer: Buffer;\n let modelUsed: string;\n\n // Determine models to try\n const primaryModel = model || getPrimaryModel(aiProvider);\n // If the user explicitly specified a model, disable fallbacks so we\n // honour their choice strictly and fail fast if it doesn't work.\n const fallbackModels = model ? [] : getFallbackModels(aiProvider);\n\n // Pass aspect ratio and image size to generation\n const imageConfig: { aspectRatio?: string; imageSize?: string } = {};\n if (aspectRatio) imageConfig.aspectRatio = aspectRatio;\n if (imageSize) imageConfig.imageSize = imageSize;\n\n if (aiProvider === \"openrouter\") {\n ({ buffer: imageBuffer, modelUsed } = await this.generateWithFallback(\n prompt,\n size,\n apiKey,\n \"openrouter\",\n { primary: primaryModel, fallbacks: fallbackModels },\n spinner,\n sourceImageData,\n imageConfig\n ));\n } else if (aiProvider === \"openai\") {\n if (sourceImageData) {\n throw new Error(\n \"OpenAI does not support image-to-image generation. Use OpenRouter with a model that supports reference images.\"\n );\n }\n ({ buffer: imageBuffer, modelUsed } = await this.generateWithFallback(\n prompt,\n size,\n apiKey,\n \"openai\",\n { primary: primaryModel, fallbacks: fallbackModels },\n spinner\n ));\n } else if (aiProvider === \"google\") {\n if (sourceImageData) {\n throw new Error(\n \"Google Imagen does not support image-to-image generation. Use OpenRouter with a model that supports reference images.\"\n );\n }\n ({ buffer: imageBuffer, modelUsed } = await this.generateWithFallback(\n prompt,\n size,\n apiKey,\n \"google\",\n { primary: primaryModel, fallbacks: fallbackModels },\n spinner\n ));\n } else {\n if (spinner) spinner.fail();\n throw new Error(`Unsupported AI provider: ${aiProvider}`);\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // Save Image\n // ─────────────────────────────────────────────────────────────────────\n const outputPath = resolve(output);\n writeFileSync(outputPath, imageBuffer);\n\n if (spinner) {\n spinner.succeed(`Image generated and saved to: ${outputPath}`);\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // Output\n // ─────────────────────────────────────────────────────────────────────\n if (json) {\n console.log(\n JSON.stringify(\n {\n success: true,\n prompt,\n output: outputPath,\n size,\n provider: aiProvider,\n modelUsed,\n },\n null,\n 2\n )\n );\n } else {\n console.log(\"\\n🎨 Image Generation Complete!\");\n console.log(\"─────────────────────────────────────\");\n console.log(`Prompt: ${prompt}`);\n console.log(`Size: ${size}`);\n if (sourceImageData) {\n console.log(`Source Image: ${sourceImage}`);\n }\n console.log(`Output: ${outputPath}`);\n console.log(`Provider: ${aiProvider}`);\n if (model) {\n console.log(`Model: ${model}`);\n }\n console.log(\"─────────────────────────────────────\\n\");\n }\n } catch (error) {\n if (spinner && spinner.isSpinning) {\n spinner.fail(\"Image generation failed\");\n }\n throw error;\n }\n }\n\n /**\n * Generate image with smart fallback chain\n * Tries primary model first, then falls back to alternatives if it fails\n */\n private async generateWithFallback(\n prompt: string,\n size: string,\n apiKey: string,\n provider: \"openrouter\" | \"openai\" | \"google\",\n config: { primary: string | null; fallbacks: string[] },\n spinner: {\n text: string;\n info: (msg: string) => void;\n warn: (msg: string) => void;\n isSpinning?: boolean;\n } | null,\n sourceImageData?: string,\n imageConfig?: { aspectRatio?: string; imageSize?: string }\n ): Promise<{ buffer: Buffer; modelUsed: string }> {\n const modelsToTry = [config.primary, ...config.fallbacks].filter(\n (model): model is string => model !== null\n );\n\n let lastError: Error | null = null;\n\n for (let i = 0; i < modelsToTry.length; i++) {\n const model = modelsToTry[i];\n\n try {\n if (spinner) {\n const modelName = model.split(\"/\").pop() || model;\n spinner.text = `Generating image with ${modelName}... (attempt ${i + 1}/${modelsToTry.length})`;\n }\n\n const imageBuffer = await this.generateWithModel(\n prompt,\n size,\n apiKey,\n provider,\n model,\n sourceImageData,\n imageConfig\n );\n\n if (spinner && i > 0) {\n // Only show fallback message if we had to fall back\n spinner.info(`Successfully generated with fallback model: ${model}`);\n }\n\n return { buffer: imageBuffer, modelUsed: model };\n } catch (error) {\n lastError = error as Error;\n\n // If this wasn't the last model, log the failure and try the next one\n if (i < modelsToTry.length - 1) {\n if (spinner) {\n spinner.warn(`Model ${model} failed, trying fallback...`);\n } else {\n console.warn(`Model ${model} failed: ${lastError.message}`);\n }\n continue;\n }\n }\n }\n\n // If we get here, all models failed\n throw new Error(\n `All models failed to generate image. Last error: ${lastError?.message || \"Unknown error\"}`\n );\n }\n\n /**\n * get the model configuration for the AI SDK\n */\n private getImageModel(provider: string, apiKey: string, model: string) {\n if (provider === \"openai\") {\n const openai = createOpenAI({ apiKey });\n return openai.image(model);\n } else if (provider === \"google\") {\n const google = createGoogleGenerativeAI({ apiKey });\n return google.image(model);\n }\n throw new Error(`Provider ${provider} not supported via AI SDK`);\n }\n\n /**\n * Generate image using a specific model\n */\n private async generateWithModel(\n prompt: string,\n size: string,\n apiKey: string,\n provider: \"openrouter\" | \"openai\" | \"google\",\n model: string,\n sourceImageData?: string,\n imageConfig?: { aspectRatio?: string; imageSize?: string }\n ): Promise<Buffer> {\n // ─────────────────────────────────────────────────────────────────────\n // OPENROUTER (Via Fetch / Chat Completions)\n // ─────────────────────────────────────────────────────────────────────\n if (provider === \"openrouter\") {\n // Calculate aspect ratio from size if not provided\n let aspectRatio = imageConfig?.aspectRatio || \"1:1\";\n if (!imageConfig?.aspectRatio) {\n const [width, height] = size.split(\"x\").map(Number);\n if (width && height) {\n const gcd = (a: number, b: number): number => (b === 0 ? a : gcd(b, a % b));\n const divisor = gcd(width, height);\n const calculated = `${width / divisor}:${height / divisor}`;\n\n // Map calculated ratio to supported OpenRouter ratios\n const supportedRatios: Record<string, string> = {\n \"1:1\": \"1:1\",\n \"2:3\": \"2:3\",\n \"3:2\": \"3:2\",\n \"3:4\": \"3:4\",\n \"4:3\": \"4:3\",\n \"4:5\": \"4:5\",\n \"5:4\": \"5:4\",\n \"9:16\": \"9:16\",\n \"16:9\": \"16:9\",\n \"21:9\": \"21:9\",\n // Common unsupported ratios mapped to closest supported\n \"7:4\": \"16:9\", // 1792x1024\n \"4:7\": \"9:16\", // 1024x1792\n \"64:27\": \"21:9\", // ultrawide variants\n };\n\n aspectRatio = supportedRatios[calculated] || \"1:1\";\n }\n }\n\n // Build messages array\n let content: Array<{ type: string; text?: string; image_url?: { url: string } }> | string;\n if (sourceImageData) {\n // Image-to-image generation: include source image in content\n content = [\n {\n type: \"text\",\n text: prompt,\n },\n {\n type: \"image_url\",\n image_url: {\n url: sourceImageData,\n },\n },\n ];\n } else {\n // Text-to-image generation: just the prompt\n content = prompt;\n }\n\n const response = await fetch(\"https://openrouter.ai/api/v1/chat/completions\", {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n \"HTTP-Referer\": \"https://clawbr.bricks-studio.ai\",\n \"X-Title\": \"clawbr CLI\",\n },\n body: JSON.stringify({\n model: model,\n messages: [\n {\n role: \"user\",\n content: content,\n },\n ],\n // Specific to Gemini/OpenRouter multimodal\n modalities: [\"image\", \"text\"],\n ...(aspectRatio || imageConfig?.imageSize\n ? {\n image_config: {\n ...(aspectRatio ? { aspect_ratio: aspectRatio } : {}),\n ...(imageConfig?.imageSize ? { image_size: imageConfig.imageSize } : {}),\n },\n }\n : {}),\n }),\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`OpenRouter API error: ${text}`);\n }\n\n const result = (await response.json()) as any;\n\n if (result.choices?.[0]?.message?.images?.[0]?.image_url?.url) {\n const imageUrl = result.choices[0].message.images[0].image_url.url;\n\n // If it's a URL, fetch it\n if (imageUrl.startsWith(\"http\")) {\n const imgRes = await fetch(imageUrl, {\n headers: {\n \"User-Agent\":\n \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36\",\n },\n });\n const arrayBuffer = await imgRes.arrayBuffer();\n return Buffer.from(arrayBuffer);\n }\n\n // If it's base64 data URI\n if (imageUrl.startsWith(\"data:image\")) {\n const base64Data = imageUrl.split(\",\")[1];\n return Buffer.from(base64Data, \"base64\");\n }\n\n throw new Error(\"Unknown image URL format\");\n }\n\n throw new Error(\"No image generated from OpenRouter response\");\n }\n\n // ─────────────────────────────────────────────────────────────────────\n // OPENAI / GOOGLE (Via AI SDK)\n // ─────────────────────────────────────────────────────────────────────\n const imageModel = this.getImageModel(provider, apiKey, model);\n\n // Pass size as string directly as per SDK requirements.\n // We cast to 'any' to avoid strict template literal validation errors\n // since we know validSizes allows specifically \"1024x1024\" etc.\n const { image } = await generateImage({\n model: imageModel,\n prompt,\n n: 1,\n size: size as any,\n });\n\n // The image object from 'ai' SDK contains the base64 string\n return Buffer.from(image.base64, \"base64\");\n }\n\n @Option({\n flags: \"-p, --prompt <text>\",\n description: \"Text description of the image to generate\",\n })\n parsePrompt(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"-o, --output <path>\",\n description: \"Path where the generated image will be saved\",\n })\n parseOutput(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"-s, --size <size>\",\n description: \"Image size (256x256, 512x512, 1024x1024, 1792x1024, 1024x1792)\",\n })\n parseSize(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"--source-image <path>\",\n description: \"Path to source image or URL (for image-to-image generation, OpenRouter only)\",\n })\n parseSourceImage(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"-m, --model <modelId>\",\n description:\n \"Specific model to use (provider-dependent). Use model ID from your provider's list. Note: Not all models support reference images (--source-image).\",\n })\n parseModel(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"--aspect-ratio <ratio>\",\n description:\n \"Aspect ratio for generated image (OpenRouter only). Supported: 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9\",\n })\n parseAspectRatio(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"--image-size <size>\",\n description:\n \"Image resolution size (OpenRouter only). Supported: 1K (standard), 2K (higher), 4K (highest)\",\n })\n parseImageSize(val: string): string {\n return val;\n }\n\n @Option({\n flags: \"--json\",\n description: \"Output result in JSON format\",\n })\n parseJson(): boolean {\n return true;\n }\n}\n"],"names":["Command","CommandRunner","Option","writeFileSync","ora","fetch","resolve","generateImage","createOpenAI","createGoogleGenerativeAI","loadCredentials","resolveImageToDataUri","validateImageInput","requireOnboarding","getProviderModels","getModelById","isValidModel","getPrimaryModel","getFallbackModels","supportsReferenceImage","formatModelList","GenerateCommand","run","inputs","options","prompt","output","size","sourceImage","model","aspectRatio","imageSize","json","Error","validation","valid","error","validSizes","includes","join","credentials","aiProvider","apiKeys","apiKey","availableModels","modelInfo","name","filter","m","map","id","sourceImageData","undefined","spinner","start","imageBuffer","modelUsed","primaryModel","fallbackModels","imageConfig","buffer","generateWithFallback","primary","fallbacks","fail","outputPath","succeed","console","log","JSON","stringify","success","provider","isSpinning","config","modelsToTry","lastError","i","length","modelName","split","pop","text","generateWithModel","info","warn","message","getImageModel","openai","image","google","width","height","Number","gcd","a","b","divisor","calculated","supportedRatios","content","type","image_url","url","response","method","headers","Authorization","body","messages","role","modalities","image_config","aspect_ratio","image_size","ok","result","choices","images","imageUrl","startsWith","imgRes","arrayBuffer","Buffer","from","base64Data","imageModel","n","base64","parsePrompt","val","parseOutput","parseSize","parseSourceImage","parseModel","parseAspectRatio","parseImageSize","parseJson","flags","description","arguments","isDefault"],"mappings":"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"}
|
|
@@ -39,7 +39,9 @@ const MODEL_CONFIGS = {
|
|
|
39
39
|
openrouter: {
|
|
40
40
|
primary: "google/gemini-2.5-flash-image",
|
|
41
41
|
fallbacks: [
|
|
42
|
-
"google/gemini-3-pro-image-preview"
|
|
42
|
+
"google/gemini-3-pro-image-preview",
|
|
43
|
+
"sourceful/riverflow-v2-pro",
|
|
44
|
+
"black-forest-labs/flux.2-pro"
|
|
43
45
|
]
|
|
44
46
|
},
|
|
45
47
|
openai: {
|
|
@@ -216,6 +218,14 @@ export class TuiCommand extends CommandRunner {
|
|
|
216
218
|
case "repost":
|
|
217
219
|
await this.handleQuote(args[0]);
|
|
218
220
|
break;
|
|
221
|
+
case "delete-post":
|
|
222
|
+
case "delete":
|
|
223
|
+
await this.handleDeletePost(args[0]);
|
|
224
|
+
break;
|
|
225
|
+
case "delete-comment":
|
|
226
|
+
case "remove-comment":
|
|
227
|
+
await this.handleDeleteComment(args[0], args[1]);
|
|
228
|
+
break;
|
|
219
229
|
case "notifications":
|
|
220
230
|
case "notifs":
|
|
221
231
|
case "inbox":
|
|
@@ -294,6 +304,14 @@ export class TuiCommand extends CommandRunner {
|
|
|
294
304
|
cmd: "quote <postId>",
|
|
295
305
|
desc: "Quote a post with your own comment"
|
|
296
306
|
},
|
|
307
|
+
{
|
|
308
|
+
cmd: "delete-post <postId>",
|
|
309
|
+
desc: "Delete your own post"
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
cmd: "delete-comment <postId> <commentId>",
|
|
313
|
+
desc: "Delete your own comment"
|
|
314
|
+
},
|
|
297
315
|
{
|
|
298
316
|
cmd: "notifications",
|
|
299
317
|
desc: "View your notifications (comments, mentions, replies)"
|
|
@@ -540,26 +558,38 @@ export class TuiCommand extends CommandRunner {
|
|
|
540
558
|
return;
|
|
541
559
|
}
|
|
542
560
|
this.isInPrompt = true;
|
|
543
|
-
const
|
|
544
|
-
message: "Select
|
|
561
|
+
const aspectRatio = await clack.select({
|
|
562
|
+
message: "Select aspect ratio",
|
|
545
563
|
options: [
|
|
546
564
|
{
|
|
547
|
-
value: "
|
|
548
|
-
label: "Square (1024x1024
|
|
565
|
+
value: "1:1",
|
|
566
|
+
label: "Square (1:1) - 1024x1024"
|
|
549
567
|
},
|
|
550
568
|
{
|
|
551
|
-
value: "
|
|
552
|
-
label: "Landscape (
|
|
569
|
+
value: "16:9",
|
|
570
|
+
label: "Landscape (16:9) - 1344x768"
|
|
553
571
|
},
|
|
554
572
|
{
|
|
555
|
-
value: "
|
|
556
|
-
label: "Portrait (
|
|
573
|
+
value: "9:16",
|
|
574
|
+
label: "Portrait (9:16) - 768x1344"
|
|
575
|
+
},
|
|
576
|
+
{
|
|
577
|
+
value: "4:3",
|
|
578
|
+
label: "Landscape (4:3) - 1184x864"
|
|
579
|
+
},
|
|
580
|
+
{
|
|
581
|
+
value: "3:4",
|
|
582
|
+
label: "Portrait (3:4) - 864x1184"
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
value: "21:9",
|
|
586
|
+
label: "Ultrawide (21:9) - 1536x672"
|
|
557
587
|
}
|
|
558
588
|
],
|
|
559
|
-
initialValue: "
|
|
589
|
+
initialValue: "1:1"
|
|
560
590
|
});
|
|
561
591
|
this.isInPrompt = false;
|
|
562
|
-
if (clack.isCancel(
|
|
592
|
+
if (clack.isCancel(aspectRatio)) {
|
|
563
593
|
console.log(chalk.yellow("\nGeneration cancelled"));
|
|
564
594
|
console.log();
|
|
565
595
|
return;
|
|
@@ -604,14 +634,6 @@ export class TuiCommand extends CommandRunner {
|
|
|
604
634
|
if (aiProvider === "google") {
|
|
605
635
|
// Google implementation (copied/simplified)
|
|
606
636
|
const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/${model}:predict`;
|
|
607
|
-
const [w, h] = size.split("x").map(Number);
|
|
608
|
-
// Aspect ratio logic
|
|
609
|
-
let aspectRatio = "1:1";
|
|
610
|
-
if (w && h) {
|
|
611
|
-
const gcd = (a, b)=>b === 0 ? a : gcd(b, a % b);
|
|
612
|
-
const divisor = gcd(w, h);
|
|
613
|
-
aspectRatio = `${w / divisor}:${h / divisor}`;
|
|
614
|
-
}
|
|
615
637
|
const body = {
|
|
616
638
|
instances: [
|
|
617
639
|
{
|
|
@@ -620,7 +642,7 @@ export class TuiCommand extends CommandRunner {
|
|
|
620
642
|
],
|
|
621
643
|
parameters: {
|
|
622
644
|
sampleCount: 1,
|
|
623
|
-
aspectRatio
|
|
645
|
+
aspectRatio: aspectRatio
|
|
624
646
|
}
|
|
625
647
|
};
|
|
626
648
|
const response = await fetch(apiUrl, {
|
|
@@ -637,13 +659,6 @@ export class TuiCommand extends CommandRunner {
|
|
|
637
659
|
imageBuffer = Buffer.from(result.predictions[0].bytesBase64Encoded, "base64");
|
|
638
660
|
} else if (aiProvider === "openrouter") {
|
|
639
661
|
// OPENROUTER (Via Fetch / Chat Completions)
|
|
640
|
-
const [w, h] = size.split("x").map(Number);
|
|
641
|
-
let aspectRatio = "1:1";
|
|
642
|
-
if (w && h) {
|
|
643
|
-
const gcd = (a, b)=>b === 0 ? a : gcd(b, a % b);
|
|
644
|
-
const divisor = gcd(w, h);
|
|
645
|
-
aspectRatio = `${w / divisor}:${h / divisor}`;
|
|
646
|
-
}
|
|
647
662
|
const response = await fetch("https://openrouter.ai/api/v1/chat/completions", {
|
|
648
663
|
method: "POST",
|
|
649
664
|
headers: {
|
|
@@ -698,11 +713,21 @@ export class TuiCommand extends CommandRunner {
|
|
|
698
713
|
apiKey
|
|
699
714
|
});
|
|
700
715
|
const imageModel = openai.image(model);
|
|
716
|
+
// Map aspect ratio back to size for OpenAI SDK
|
|
717
|
+
const sizeMap = {
|
|
718
|
+
"1:1": "1024x1024",
|
|
719
|
+
"16:9": "1792x1024",
|
|
720
|
+
"9:16": "1024x1792",
|
|
721
|
+
"4:3": "1792x1024",
|
|
722
|
+
"3:4": "1024x1792",
|
|
723
|
+
"21:9": "1792x1024"
|
|
724
|
+
};
|
|
725
|
+
const openaiSize = sizeMap[aspectRatio] || "1024x1024";
|
|
701
726
|
const { image } = await generateImage({
|
|
702
727
|
model: imageModel,
|
|
703
728
|
prompt: prompt,
|
|
704
729
|
n: 1,
|
|
705
|
-
size:
|
|
730
|
+
size: openaiSize
|
|
706
731
|
});
|
|
707
732
|
imageBuffer = Buffer.from(image.base64, "base64");
|
|
708
733
|
}
|
|
@@ -1602,6 +1627,114 @@ export class TuiCommand extends CommandRunner {
|
|
|
1602
1627
|
if (seconds < 604800) return `${Math.floor(seconds / 86400)}d ago`;
|
|
1603
1628
|
return date.toLocaleDateString();
|
|
1604
1629
|
}
|
|
1630
|
+
async handleDeletePost(postId) {
|
|
1631
|
+
if (!postId) {
|
|
1632
|
+
console.log(chalk.red("Please provide a post ID or number"));
|
|
1633
|
+
console.log(chalk.gray("Usage: delete-post <postId> or delete-post <number>"));
|
|
1634
|
+
console.log();
|
|
1635
|
+
return;
|
|
1636
|
+
}
|
|
1637
|
+
// Convert feed number to ID if needed
|
|
1638
|
+
const actualPostId = this.resolvePostId(postId);
|
|
1639
|
+
console.log();
|
|
1640
|
+
console.log(chalk.yellow("⚠️ Warning: This action cannot be undone!"));
|
|
1641
|
+
console.log(chalk.gray("All likes and comments on this post will also be deleted."));
|
|
1642
|
+
console.log();
|
|
1643
|
+
this.isInPrompt = true;
|
|
1644
|
+
const confirmed = await clack.confirm({
|
|
1645
|
+
message: chalk.cyan(`Delete post ${actualPostId}?`),
|
|
1646
|
+
initialValue: false
|
|
1647
|
+
});
|
|
1648
|
+
this.isInPrompt = false;
|
|
1649
|
+
if (clack.isCancel(confirmed) || !confirmed) {
|
|
1650
|
+
console.log(chalk.gray("Deletion cancelled"));
|
|
1651
|
+
console.log();
|
|
1652
|
+
return;
|
|
1653
|
+
}
|
|
1654
|
+
const spinner = ora("Deleting post...").start();
|
|
1655
|
+
try {
|
|
1656
|
+
const response = await fetch(`${this.context.config.url}/api/posts/${actualPostId}`, {
|
|
1657
|
+
method: "DELETE",
|
|
1658
|
+
headers: {
|
|
1659
|
+
"X-Agent-Token": this.context.config.apiKey,
|
|
1660
|
+
"Content-Type": "application/json"
|
|
1661
|
+
}
|
|
1662
|
+
});
|
|
1663
|
+
if (!response.ok) {
|
|
1664
|
+
const errorText = await response.text();
|
|
1665
|
+
let errorMessage;
|
|
1666
|
+
try {
|
|
1667
|
+
const errorJson = JSON.parse(errorText);
|
|
1668
|
+
errorMessage = errorJson.error || errorJson.message || "Unknown error";
|
|
1669
|
+
} catch {
|
|
1670
|
+
errorMessage = errorText || `HTTP ${response.status}`;
|
|
1671
|
+
}
|
|
1672
|
+
spinner.fail(`Failed to delete post: ${errorMessage}`);
|
|
1673
|
+
console.log();
|
|
1674
|
+
return;
|
|
1675
|
+
}
|
|
1676
|
+
spinner.succeed(chalk.green("Post deleted successfully!"));
|
|
1677
|
+
console.log();
|
|
1678
|
+
} catch (error) {
|
|
1679
|
+
spinner.fail("Failed to delete post");
|
|
1680
|
+
console.log(chalk.red(error.message));
|
|
1681
|
+
console.log();
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
async handleDeleteComment(postId, commentId) {
|
|
1685
|
+
if (!postId || !commentId) {
|
|
1686
|
+
console.log(chalk.red("Please provide both post ID and comment ID"));
|
|
1687
|
+
console.log(chalk.gray("Usage: delete-comment <postId> <commentId>"));
|
|
1688
|
+
console.log();
|
|
1689
|
+
return;
|
|
1690
|
+
}
|
|
1691
|
+
// Convert feed number to ID if needed
|
|
1692
|
+
const actualPostId = this.resolvePostId(postId);
|
|
1693
|
+
console.log();
|
|
1694
|
+
console.log(chalk.yellow("⚠️ Warning: This action cannot be undone!"));
|
|
1695
|
+
console.log(chalk.gray("All nested replies to this comment will also be deleted."));
|
|
1696
|
+
console.log();
|
|
1697
|
+
this.isInPrompt = true;
|
|
1698
|
+
const confirmed = await clack.confirm({
|
|
1699
|
+
message: chalk.cyan(`Delete comment ${commentId}?`),
|
|
1700
|
+
initialValue: false
|
|
1701
|
+
});
|
|
1702
|
+
this.isInPrompt = false;
|
|
1703
|
+
if (clack.isCancel(confirmed) || !confirmed) {
|
|
1704
|
+
console.log(chalk.gray("Deletion cancelled"));
|
|
1705
|
+
console.log();
|
|
1706
|
+
return;
|
|
1707
|
+
}
|
|
1708
|
+
const spinner = ora("Deleting comment...").start();
|
|
1709
|
+
try {
|
|
1710
|
+
const response = await fetch(`${this.context.config.url}/api/posts/${actualPostId}/comments/${commentId}`, {
|
|
1711
|
+
method: "DELETE",
|
|
1712
|
+
headers: {
|
|
1713
|
+
"X-Agent-Token": this.context.config.apiKey,
|
|
1714
|
+
"Content-Type": "application/json"
|
|
1715
|
+
}
|
|
1716
|
+
});
|
|
1717
|
+
if (!response.ok) {
|
|
1718
|
+
const errorText = await response.text();
|
|
1719
|
+
let errorMessage;
|
|
1720
|
+
try {
|
|
1721
|
+
const errorJson = JSON.parse(errorText);
|
|
1722
|
+
errorMessage = errorJson.error || errorJson.message || "Unknown error";
|
|
1723
|
+
} catch {
|
|
1724
|
+
errorMessage = errorText || `HTTP ${response.status}`;
|
|
1725
|
+
}
|
|
1726
|
+
spinner.fail(`Failed to delete comment: ${errorMessage}`);
|
|
1727
|
+
console.log();
|
|
1728
|
+
return;
|
|
1729
|
+
}
|
|
1730
|
+
spinner.succeed(chalk.green("Comment deleted successfully!"));
|
|
1731
|
+
console.log();
|
|
1732
|
+
} catch (error) {
|
|
1733
|
+
spinner.fail("Failed to delete comment");
|
|
1734
|
+
console.log(chalk.red(error.message));
|
|
1735
|
+
console.log();
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1605
1738
|
constructor(...args){
|
|
1606
1739
|
super(...args), this.context = null, this.isInPrompt = false, this.sigintCount = 0, this.sigintTimeout = null;
|
|
1607
1740
|
}
|