pabal-resource-mcp 1.8.12 → 1.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,7 +6,7 @@ Build synced websites from App Store Connect and Google Play Console data.
6
6
 
7
7
  > 💡 **Example**: [labs.quartz.best](https://labs.quartz.best/)
8
8
 
9
- [![Documentation](https://img.shields.io/badge/docs-English-blue)](https://pabal.quartz.best/docs/en-US/pabal-store-api-mcp/README) [![한국어](https://img.shields.io/badge/docs-한국어-green)](https://pabal.quartz.best/docs/ko-KR/pabal-store-api-mcp/README)
9
+ [![Documentation](https://img.shields.io/badge/docs-English-blue)](https://pabal.quartz.best/en-US/docs/pabal-resource-mcp/README) [![한국어](https://img.shields.io/badge/docs-한국어-green)](https://pabal.quartz.best/ko-KR/docs/pabal-resource-mcp/README)
10
10
 
11
11
  ## Installation
12
12
 
@@ -61,7 +61,7 @@ Set `dataDir` in `~/.config/pabal-mcp/config.json`:
61
61
  | App Icon | `generate-app-icons` |
62
62
  | Content | `create-blog-html` |
63
63
 
64
- See [documentation](./docs/en-US/README.md) for details.
64
+ See [documentation](https://pabal.quartz.best/en-US/docs/pabal-resource-mcp/README) for details.
65
65
 
66
66
  ## License
67
67
 
@@ -3398,8 +3398,8 @@ Context around ${pos}: ${context}`
3398
3398
  // src/tools/screenshots/translate-screenshots.ts
3399
3399
  import { z as z7 } from "zod";
3400
3400
  import { zodToJsonSchema as zodToJsonSchema7 } from "zod-to-json-schema";
3401
- import fs11 from "fs";
3402
- import path11 from "path";
3401
+ import fs12 from "fs";
3402
+ import path12 from "path";
3403
3403
 
3404
3404
  // src/tools/screenshots/utils/scan-screenshots.util.ts
3405
3405
  import fs9 from "fs";
@@ -3481,10 +3481,161 @@ function scanRawScreenshots(slug, locale) {
3481
3481
  }
3482
3482
 
3483
3483
  // src/tools/screenshots/utils/gemini-image-translator.util.ts
3484
+ import fs11 from "fs";
3485
+ import path11 from "path";
3486
+ import sharp from "sharp";
3487
+
3488
+ // src/utils/gemini-image-model.util.ts
3489
+ var GEMINI_IMAGE_MODEL_PRESETS = {
3490
+ flash: "gemini-3.1-flash-image-preview",
3491
+ pro: "gemini-3-pro-image-preview"
3492
+ };
3493
+ var GEMINI_IMAGE_MODEL_VALUES = Object.keys(
3494
+ GEMINI_IMAGE_MODEL_PRESETS
3495
+ );
3496
+ var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([429, 500, 503, 504]);
3497
+ function parseCsv(value) {
3498
+ if (!value) {
3499
+ return [];
3500
+ }
3501
+ return value.split(",").map((item) => item.trim()).filter(Boolean);
3502
+ }
3503
+ function getStatusCode(error) {
3504
+ if (typeof error !== "object" || error === null) {
3505
+ return void 0;
3506
+ }
3507
+ const status = error.status;
3508
+ return typeof status === "number" ? status : void 0;
3509
+ }
3510
+ function getDefaultModelOrder(preference) {
3511
+ if (preference === "pro") {
3512
+ return [
3513
+ GEMINI_IMAGE_MODEL_PRESETS.pro,
3514
+ GEMINI_IMAGE_MODEL_PRESETS.flash
3515
+ ];
3516
+ }
3517
+ return [
3518
+ GEMINI_IMAGE_MODEL_PRESETS.flash,
3519
+ GEMINI_IMAGE_MODEL_PRESETS.pro
3520
+ ];
3521
+ }
3522
+ function getGeminiImageModelCandidates(preference = "flash") {
3523
+ const defaultOrder = getDefaultModelOrder(preference);
3524
+ const preferredModel = defaultOrder[0];
3525
+ const envModelOverride = process.env.GEMINI_IMAGE_MODEL?.trim();
3526
+ const fallbackModels = parseCsv(process.env.GEMINI_IMAGE_FALLBACK_MODELS);
3527
+ const orderedModels = [
3528
+ preferredModel,
3529
+ ...envModelOverride ? [envModelOverride] : [],
3530
+ ...defaultOrder,
3531
+ ...fallbackModels
3532
+ ];
3533
+ return Array.from(
3534
+ new Set(orderedModels.filter((model) => Boolean(model)))
3535
+ );
3536
+ }
3537
+ function getGeminiErrorMessage(error) {
3538
+ const defaultMessage = error instanceof Error ? error.message : String(error);
3539
+ const normalizedMessage = defaultMessage.replace(/^exception\s*/i, "").trim();
3540
+ try {
3541
+ const parsed = JSON.parse(normalizedMessage);
3542
+ const apiMessage = parsed?.error?.message;
3543
+ if (typeof apiMessage === "string" && apiMessage.trim()) {
3544
+ return apiMessage.trim();
3545
+ }
3546
+ } catch {
3547
+ }
3548
+ return normalizedMessage || "Unknown Gemini API error";
3549
+ }
3550
+ function shouldTryNextGeminiImageModel(error) {
3551
+ const status = getStatusCode(error);
3552
+ if (status && RETRYABLE_STATUS_CODES.has(status)) {
3553
+ return true;
3554
+ }
3555
+ const message = getGeminiErrorMessage(error).toLowerCase();
3556
+ return message.includes("high demand") || message.includes("temporarily unavailable") || message.includes("try again later") || message.includes("overloaded") || message.includes("resource_exhausted") || message.includes("unavailable");
3557
+ }
3558
+
3559
+ // src/utils/gemini-client.util.ts
3484
3560
  import { GoogleGenAI } from "@google/genai";
3561
+ function createGeminiClient() {
3562
+ const apiKey = getGeminiApiKey();
3563
+ return new GoogleGenAI({ apiKey });
3564
+ }
3565
+
3566
+ // src/utils/image-file.util.ts
3485
3567
  import fs10 from "fs";
3486
3568
  import path10 from "path";
3487
- import sharp from "sharp";
3569
+ function readImageAsBase64(imagePath) {
3570
+ const buffer = fs10.readFileSync(imagePath);
3571
+ const base64 = buffer.toString("base64");
3572
+ const ext = path10.extname(imagePath).toLowerCase();
3573
+ let mimeType = "image/png";
3574
+ if (ext === ".jpg" || ext === ".jpeg") {
3575
+ mimeType = "image/jpeg";
3576
+ } else if (ext === ".webp") {
3577
+ mimeType = "image/webp";
3578
+ }
3579
+ return { data: base64, mimeType };
3580
+ }
3581
+
3582
+ // src/utils/gemini-image-generation.util.ts
3583
+ async function generateImageWithFallback(input) {
3584
+ const models = getGeminiImageModelCandidates(input.imageModel);
3585
+ let lastError;
3586
+ for (const model of models) {
3587
+ try {
3588
+ const chat = input.client.chats.create({
3589
+ model,
3590
+ config: {
3591
+ responseModalities: ["TEXT", "IMAGE"]
3592
+ }
3593
+ });
3594
+ const response = await chat.sendMessage({
3595
+ message: [
3596
+ { text: input.prompt },
3597
+ {
3598
+ inlineData: {
3599
+ mimeType: input.image.mimeType,
3600
+ data: input.image.data
3601
+ }
3602
+ }
3603
+ ],
3604
+ config: {
3605
+ responseModalities: ["TEXT", "IMAGE"],
3606
+ ...input.aspectRatio ? {
3607
+ imageConfig: {
3608
+ aspectRatio: input.aspectRatio
3609
+ }
3610
+ } : {}
3611
+ }
3612
+ });
3613
+ const parts = response.candidates?.[0]?.content?.parts;
3614
+ if (!parts) {
3615
+ throw new Error("No content parts in Gemini response");
3616
+ }
3617
+ for (const part of parts) {
3618
+ if (part.inlineData?.data) {
3619
+ return {
3620
+ model,
3621
+ imageBase64: part.inlineData.data
3622
+ };
3623
+ }
3624
+ }
3625
+ throw new Error("No image data in Gemini response");
3626
+ } catch (error) {
3627
+ lastError = error;
3628
+ if (!shouldTryNextGeminiImageModel(error)) {
3629
+ break;
3630
+ }
3631
+ }
3632
+ }
3633
+ throw new Error(
3634
+ `All Gemini image models failed (${models.join(", ")}): ${getGeminiErrorMessage(lastError)}`
3635
+ );
3636
+ }
3637
+
3638
+ // src/tools/screenshots/utils/gemini-image-translator.util.ts
3488
3639
  var GEMINI_ASPECT_RATIOS = {
3489
3640
  "1:1": { ratio: 1 / 1, width: 2048, height: 2048 },
3490
3641
  "2:3": { ratio: 2 / 3, width: 1696, height: 2528 },
@@ -3524,35 +3675,18 @@ function getLanguageName(locale) {
3524
3675
  }
3525
3676
  return locale;
3526
3677
  }
3527
- function getGeminiClient() {
3528
- const apiKey = getGeminiApiKey();
3529
- return new GoogleGenAI({ apiKey });
3530
- }
3531
- function readImageAsBase64(imagePath) {
3532
- const buffer = fs10.readFileSync(imagePath);
3533
- const base64 = buffer.toString("base64");
3534
- const ext = path10.extname(imagePath).toLowerCase();
3535
- let mimeType = "image/png";
3536
- if (ext === ".jpg" || ext === ".jpeg") {
3537
- mimeType = "image/jpeg";
3538
- } else if (ext === ".webp") {
3539
- mimeType = "image/webp";
3540
- }
3541
- return { data: base64, mimeType };
3542
- }
3543
3678
  function getAspectRatioForDevice(deviceType) {
3544
3679
  return DEVICE_ASPECT_RATIOS[deviceType];
3545
3680
  }
3546
- async function translateImage(sourcePath, sourceLocale, targetLocale, outputPaths, deviceType, preserveWords) {
3547
- try {
3548
- const client = getGeminiClient();
3549
- const sourceLanguage = getLanguageName(sourceLocale);
3550
- const targetLanguage = getLanguageName(targetLocale);
3551
- const aspectRatio = getAspectRatioForDevice(deviceType);
3552
- const { data: imageData, mimeType } = readImageAsBase64(sourcePath);
3553
- const preserveInstruction = preserveWords && preserveWords.length > 0 ? `
3681
+ async function translateImage(sourcePath, sourceLocale, targetLocale, outputPaths, deviceType, preserveWords, imageModel = "flash") {
3682
+ const client = createGeminiClient();
3683
+ const sourceLanguage = getLanguageName(sourceLocale);
3684
+ const targetLanguage = getLanguageName(targetLocale);
3685
+ const aspectRatio = getAspectRatioForDevice(deviceType);
3686
+ const { data: imageData, mimeType } = readImageAsBase64(sourcePath);
3687
+ const preserveInstruction = preserveWords && preserveWords.length > 0 ? `
3554
3688
  - Do NOT translate these words, keep them exactly as-is: ${preserveWords.join(", ")}` : "";
3555
- const prompt = `This is an app screenshot with text in ${sourceLanguage}.
3689
+ const prompt = `This is an app screenshot with text in ${sourceLanguage}.
3556
3690
  Please translate ONLY the text/words in this image to ${targetLanguage}.
3557
3691
 
3558
3692
  IMPORTANT INSTRUCTIONS:
@@ -3562,73 +3696,38 @@ IMPORTANT INSTRUCTIONS:
3562
3696
  - Do NOT add any new elements or remove existing design elements
3563
3697
  - The output should look identical except the text language is ${targetLanguage}
3564
3698
  - Preserve all icons, images, and graphical elements exactly as they are${preserveInstruction}`;
3565
- const chat = client.chats.create({
3566
- model: "gemini-3-pro-image-preview",
3567
- config: {
3568
- responseModalities: ["TEXT", "IMAGE"]
3569
- }
3570
- });
3571
- const response = await chat.sendMessage({
3572
- message: [
3573
- { text: prompt },
3574
- {
3575
- inlineData: {
3576
- mimeType,
3577
- data: imageData
3578
- }
3579
- }
3580
- ],
3581
- config: {
3582
- responseModalities: ["TEXT", "IMAGE"],
3583
- imageConfig: {
3584
- aspectRatio
3585
- }
3586
- }
3699
+ try {
3700
+ const generated = await generateImageWithFallback({
3701
+ client,
3702
+ prompt,
3703
+ image: {
3704
+ mimeType,
3705
+ data: imageData
3706
+ },
3707
+ aspectRatio,
3708
+ imageModel
3587
3709
  });
3588
- const candidates = response.candidates;
3589
- if (!candidates || candidates.length === 0) {
3590
- return {
3591
- success: false,
3592
- error: "No response from Gemini API"
3593
- };
3594
- }
3595
- const parts = candidates[0].content?.parts;
3596
- if (!parts) {
3597
- return {
3598
- success: false,
3599
- error: "No content parts in response"
3600
- };
3601
- }
3602
- for (const part of parts) {
3603
- if (part.inlineData?.data) {
3604
- const imageBuffer = Buffer.from(part.inlineData.data, "base64");
3605
- for (const outputPath of outputPaths) {
3606
- const outputDir = path10.dirname(outputPath);
3607
- if (!fs10.existsSync(outputDir)) {
3608
- fs10.mkdirSync(outputDir, { recursive: true });
3609
- }
3610
- await sharp(imageBuffer).png().toFile(outputPath);
3611
- }
3612
- return {
3613
- success: true,
3614
- outputPath: outputPaths[0]
3615
- // Return primary path
3616
- };
3710
+ const imageBuffer = Buffer.from(generated.imageBase64, "base64");
3711
+ for (const outputPath of outputPaths) {
3712
+ const outputDir = path11.dirname(outputPath);
3713
+ if (!fs11.existsSync(outputDir)) {
3714
+ fs11.mkdirSync(outputDir, { recursive: true });
3617
3715
  }
3716
+ await sharp(imageBuffer).png().toFile(outputPath);
3618
3717
  }
3619
3718
  return {
3620
- success: false,
3621
- error: "No image data in Gemini response"
3719
+ success: true,
3720
+ outputPath: outputPaths[0]
3721
+ // Return primary path
3622
3722
  };
3623
3723
  } catch (error) {
3624
- const message = error instanceof Error ? error.message : String(error);
3625
3724
  return {
3626
3725
  success: false,
3627
- error: message
3726
+ error: error instanceof Error ? error.message : String(error)
3628
3727
  };
3629
3728
  }
3630
3729
  }
3631
- async function translateImagesWithProgress(translations, onProgress, preserveWords) {
3730
+ async function translateImagesWithProgress(translations, onProgress, preserveWords, imageModel = "flash") {
3632
3731
  let successful = 0;
3633
3732
  let failed = 0;
3634
3733
  const errors = [];
@@ -3652,7 +3751,8 @@ async function translateImagesWithProgress(translations, onProgress, preserveWor
3652
3751
  translation.targetLocale,
3653
3752
  translation.outputPaths,
3654
3753
  translation.deviceType,
3655
- preserveWords
3754
+ preserveWords,
3755
+ imageModel
3656
3756
  );
3657
3757
  if (result.success) {
3658
3758
  successful++;
@@ -3671,6 +3771,9 @@ async function translateImagesWithProgress(translations, onProgress, preserveWor
3671
3771
  }
3672
3772
  return { successful, failed, errors };
3673
3773
  }
3774
+ function getImageModelLabel(imageModel) {
3775
+ return `${imageModel} (${GEMINI_IMAGE_MODEL_PRESETS[imageModel]})`;
3776
+ }
3674
3777
 
3675
3778
  // src/tools/screenshots/utils/locale-mapping.constants.ts
3676
3779
  var GEMINI_LOCALE_GROUPS = {
@@ -3773,6 +3876,9 @@ var translateScreenshotsInputSchema = z7.object({
3773
3876
  ),
3774
3877
  preserveWords: z7.array(z7.string()).optional().describe(
3775
3878
  'Words to keep untranslated (e.g., brand names, product names). Example: ["Pabal", "Pro", "AI"]'
3879
+ ),
3880
+ imageModel: z7.enum(GEMINI_IMAGE_MODEL_VALUES).optional().default("flash").describe(
3881
+ "Gemini image model preference. 'flash' (default) is faster/cheaper, 'pro' prioritizes quality."
3776
3882
  )
3777
3883
  });
3778
3884
  var jsonSchema7 = zodToJsonSchema7(translateScreenshotsInputSchema, {
@@ -3798,6 +3904,10 @@ Use \`resize-screenshots\` after this tool to resize images to final dimensions.
3798
3904
  - Screenshots must be in: public/products/{slug}/screenshots/{locale}/phone/ and /tablet/
3799
3905
  - Locale files must exist in: public/products/{slug}/locales/
3800
3906
 
3907
+ **Model Selection:**
3908
+ - \`imageModel: "flash"\` (default) for speed/cost
3909
+ - \`imageModel: "pro"\` for higher instruction fidelity
3910
+
3801
3911
  **Example output structure:**
3802
3912
  \`\`\`
3803
3913
  public/products/my-app/screenshots/
@@ -3873,14 +3983,14 @@ function buildTranslationTasks(slug, screenshots, primaryLocale, targetLocales,
3873
3983
  for (const screenshot of screenshots) {
3874
3984
  const outputPaths = [];
3875
3985
  for (const locale of outputLocales) {
3876
- const outputPath = path11.join(
3986
+ const outputPath = path12.join(
3877
3987
  screenshotsDir,
3878
3988
  locale,
3879
3989
  screenshot.type,
3880
3990
  "raw",
3881
3991
  screenshot.filename
3882
3992
  );
3883
- if (!skipExisting || !fs11.existsSync(outputPath)) {
3993
+ if (!skipExisting || !fs12.existsSync(outputPath)) {
3884
3994
  outputPaths.push(outputPath);
3885
3995
  }
3886
3996
  }
@@ -3907,7 +4017,8 @@ async function handleTranslateScreenshots(input) {
3907
4017
  dryRun = false,
3908
4018
  skipExisting = true,
3909
4019
  screenshotNumbers,
3910
- preserveWords
4020
+ preserveWords,
4021
+ imageModel = "flash"
3911
4022
  } = input;
3912
4023
  const results = [];
3913
4024
  let appInfo;
@@ -3960,6 +4071,7 @@ async function handleTranslateScreenshots(input) {
3960
4071
  };
3961
4072
  }
3962
4073
  results.push(`\u{1F3AF} Target locales to translate: ${targetLocales.join(", ")}`);
4074
+ results.push(`\u{1F9E0} Image model: ${getImageModelLabel(imageModel)}`);
3963
4075
  if (groupedLocales.length > 0) {
3964
4076
  results.push(
3965
4077
  `\u{1F4CB} Grouped locales (saved together): ${groupedLocales.join(", ")}`
@@ -4087,9 +4199,9 @@ ${screenshotsDir2}/${primaryLocale}/tablet/1.png, 2.png, ...`
4087
4199
  }
4088
4200
  for (const task of tasks) {
4089
4201
  for (const outputPath of task.outputPaths) {
4090
- const outputDir = path11.dirname(outputPath);
4091
- if (!fs11.existsSync(outputDir)) {
4092
- fs11.mkdirSync(outputDir, { recursive: true });
4202
+ const outputDir = path12.dirname(outputPath);
4203
+ if (!fs12.existsSync(outputDir)) {
4204
+ fs12.mkdirSync(outputDir, { recursive: true });
4093
4205
  }
4094
4206
  }
4095
4207
  }
@@ -4111,7 +4223,8 @@ ${screenshotsDir2}/${primaryLocale}/tablet/1.png, 2.png, ...`
4111
4223
  );
4112
4224
  }
4113
4225
  },
4114
- preserveWords
4226
+ preserveWords,
4227
+ imageModel
4115
4228
  );
4116
4229
  results.push(`
4117
4230
  \u{1F4CA} Translation Results:`);
@@ -4121,7 +4234,7 @@ ${screenshotsDir2}/${primaryLocale}/tablet/1.png, 2.png, ...`
4121
4234
  results.push(`
4122
4235
  \u26A0\uFE0F Errors:`);
4123
4236
  for (const err of translationResult.errors.slice(0, 5)) {
4124
- results.push(` - ${path11.basename(err.path)}: ${err.error}`);
4237
+ results.push(` - ${path12.basename(err.path)}: ${err.error}`);
4125
4238
  }
4126
4239
  if (translationResult.errors.length > 5) {
4127
4240
  results.push(
@@ -4149,12 +4262,12 @@ ${screenshotsDir2}/${primaryLocale}/tablet/1.png, 2.png, ...`
4149
4262
  // src/tools/screenshots/resize-screenshots.ts
4150
4263
  import { z as z8 } from "zod";
4151
4264
  import { zodToJsonSchema as zodToJsonSchema8 } from "zod-to-json-schema";
4152
- import fs13 from "fs";
4153
- import path12 from "path";
4265
+ import fs14 from "fs";
4266
+ import path13 from "path";
4154
4267
 
4155
4268
  // src/tools/screenshots/utils/image-resizer.util.ts
4156
4269
  import sharp2 from "sharp";
4157
- import fs12 from "fs";
4270
+ import fs13 from "fs";
4158
4271
  var SCREENSHOT_DIMENSIONS = {
4159
4272
  phone: { width: 1242, height: 2688 },
4160
4273
  tablet: { width: 2048, height: 2732 }
@@ -4253,7 +4366,7 @@ async function resizeImage(inputPath, outputPath, targetDimensions, bgColor) {
4253
4366
  kernel: "lanczos3"
4254
4367
  // High-quality downscaling algorithm
4255
4368
  }).flatten({ background: backgroundColor }).png().toFile(outputPath + ".tmp");
4256
- fs12.renameSync(outputPath + ".tmp", outputPath);
4369
+ fs13.renameSync(outputPath + ".tmp", outputPath);
4257
4370
  }
4258
4371
 
4259
4372
  // src/tools/screenshots/resize-screenshots.ts
@@ -4349,6 +4462,10 @@ function buildResizeTasks(slug, sourceScreenshots, rawLocales, deviceTypes, scre
4349
4462
  const key = `${screenshot.type}/${screenshot.filename}`;
4350
4463
  sourceRefMap.set(key, screenshot.fullPath);
4351
4464
  }
4465
+ const sourceHasByDevice = {
4466
+ phone: sourceScreenshots.some((s) => s.type === "phone"),
4467
+ tablet: sourceScreenshots.some((s) => s.type === "tablet")
4468
+ };
4352
4469
  for (const locale of rawLocales) {
4353
4470
  const rawScreenshots = scanRawScreenshots(slug, locale);
4354
4471
  let filteredScreenshots = rawScreenshots.filter(
@@ -4372,16 +4489,16 @@ function buildResizeTasks(slug, sourceScreenshots, rawLocales, deviceTypes, scre
4372
4489
  for (const screenshot of filteredScreenshots) {
4373
4490
  const key = `${screenshot.type}/${screenshot.filename}`;
4374
4491
  const sourceReferencePath = sourceRefMap.get(key);
4375
- if (!sourceReferencePath) {
4492
+ if (!sourceReferencePath && sourceHasByDevice[screenshot.type]) {
4376
4493
  continue;
4377
4494
  }
4378
- const outputPath = path12.join(
4495
+ const outputPath = path13.join(
4379
4496
  screenshotsDir,
4380
4497
  locale,
4381
4498
  screenshot.type,
4382
4499
  screenshot.filename
4383
4500
  );
4384
- if (skipExisting && fs13.existsSync(outputPath)) {
4501
+ if (skipExisting && fs14.existsSync(outputPath)) {
4385
4502
  continue;
4386
4503
  }
4387
4504
  tasks.push({
@@ -4413,7 +4530,7 @@ async function batchResizeFromRaw(tasks, bgColor, onProgress) {
4413
4530
  };
4414
4531
  onProgress?.(progress);
4415
4532
  try {
4416
- if (!fs13.existsSync(task.rawPath)) {
4533
+ if (!fs14.existsSync(task.rawPath)) {
4417
4534
  progress.status = "skipped";
4418
4535
  onProgress?.(progress);
4419
4536
  skippedCount++;
@@ -4421,9 +4538,9 @@ async function batchResizeFromRaw(tasks, bgColor, onProgress) {
4421
4538
  }
4422
4539
  const targetDimensions = SCREENSHOT_DIMENSIONS[task.deviceType];
4423
4540
  const rawDimensions = await getImageDimensions(task.rawPath);
4424
- const outputDir = path12.dirname(task.outputPath);
4425
- if (!fs13.existsSync(outputDir)) {
4426
- fs13.mkdirSync(outputDir, { recursive: true });
4541
+ const outputDir = path13.dirname(task.outputPath);
4542
+ if (!fs14.existsSync(outputDir)) {
4543
+ fs14.mkdirSync(outputDir, { recursive: true });
4427
4544
  }
4428
4545
  await resizeImage(task.rawPath, task.outputPath, targetDimensions, bgColor);
4429
4546
  progress.status = "completed";
@@ -4650,7 +4767,7 @@ Available locales with raw/: ${allRawLocales.join(", ")}`
4650
4767
  results.push(`
4651
4768
  \u26A0\uFE0F Errors:`);
4652
4769
  for (const err of resizeResult.errors.slice(0, 5)) {
4653
- results.push(` - ${path12.basename(err.path)}: ${err.error}`);
4770
+ results.push(` - ${path13.basename(err.path)}: ${err.error}`);
4654
4771
  }
4655
4772
  if (resizeResult.errors.length > 5) {
4656
4773
  results.push(` ... and ${resizeResult.errors.length - 5} more errors`);
@@ -4674,10 +4791,9 @@ Available locales with raw/: ${allRawLocales.join(", ")}`
4674
4791
  // src/tools/screenshots/phone-to-tablet.ts
4675
4792
  import { z as z9 } from "zod";
4676
4793
  import { zodToJsonSchema as zodToJsonSchema9 } from "zod-to-json-schema";
4677
- import fs14 from "fs";
4678
- import path13 from "path";
4794
+ import fs15 from "fs";
4795
+ import path14 from "path";
4679
4796
  import sharp3 from "sharp";
4680
- import { GoogleGenAI as GoogleGenAI2 } from "@google/genai";
4681
4797
  var TOOL_NAME5 = "phone-to-tablet";
4682
4798
  var TABLET_ASPECT_RATIO = "3:4";
4683
4799
  var phoneToTabletInputSchema = z9.object({
@@ -4698,6 +4814,9 @@ var phoneToTabletInputSchema = z9.object({
4698
4814
  ),
4699
4815
  preserveWords: z9.array(z9.string()).optional().describe(
4700
4816
  'Words to keep exactly as-is in the generated image (e.g., brand names). Example: ["Pabal", "Pro", "AI"]'
4817
+ ),
4818
+ imageModel: z9.enum(GEMINI_IMAGE_MODEL_VALUES).optional().default("flash").describe(
4819
+ "Gemini image model preference. 'flash' (default) is faster/cheaper, 'pro' prioritizes quality."
4701
4820
  )
4702
4821
  });
4703
4822
  var jsonSchema9 = zodToJsonSchema9(phoneToTabletInputSchema, {
@@ -4711,8 +4830,8 @@ var phoneToTabletTool = {
4711
4830
 
4712
4831
  **PURPOSE:** Generate tablet-sized screenshots from existing phone screenshots by:
4713
4832
  1. Reading phone screenshots from the source locale
4714
- 2. Using Gemini to regenerate the UI with a tablet-friendly wider layout
4715
- 3. Adjusting internal device frame and UI components for larger screen
4833
+ 2. Using Gemini to keep the same UI while adapting to a tablet canvas
4834
+ 3. Preserving original content/layout without creating new UI
4716
4835
 
4717
4836
  **OUTPUT:** Saves generated tablet images to raw/ folder: \`{locale}/tablet/raw/{filename}\`
4718
4837
 
@@ -4728,6 +4847,10 @@ Run \`resize-screenshots --deviceTypes tablet\` after this tool to resize images
4728
4847
  - Phone screenshots must exist in: public/products/{slug}/screenshots/{locale}/phone/
4729
4848
  - Locale files must exist in: public/products/{slug}/locales/
4730
4849
 
4850
+ **Model Selection:**
4851
+ - \`imageModel: "flash"\` (default) for speed/cost
4852
+ - \`imageModel: "pro"\` for higher instruction fidelity
4853
+
4731
4854
  **Example output structure:**
4732
4855
  \`\`\`
4733
4856
  public/products/my-app/screenshots/
@@ -4779,14 +4902,14 @@ function buildConversionTasks(slug, phoneScreenshots, locale, screenshotNumbers,
4779
4902
  });
4780
4903
  }
4781
4904
  for (const screenshot of filteredScreenshots) {
4782
- const tabletRawPath = path13.join(
4905
+ const tabletRawPath = path14.join(
4783
4906
  screenshotsDir,
4784
4907
  locale,
4785
4908
  "tablet",
4786
4909
  "raw",
4787
4910
  screenshot.filename
4788
4911
  );
4789
- if (skipExisting && fs14.existsSync(tabletRawPath)) {
4912
+ if (skipExisting && fs15.existsSync(tabletRawPath)) {
4790
4913
  continue;
4791
4914
  }
4792
4915
  tasks.push({
@@ -4798,103 +4921,51 @@ function buildConversionTasks(slug, phoneScreenshots, locale, screenshotNumbers,
4798
4921
  }
4799
4922
  return tasks;
4800
4923
  }
4801
- function getGeminiClient2() {
4802
- const apiKey = getGeminiApiKey();
4803
- return new GoogleGenAI2({ apiKey });
4804
- }
4805
- function readImageAsBase642(imagePath) {
4806
- const buffer = fs14.readFileSync(imagePath);
4807
- const base64 = buffer.toString("base64");
4808
- const ext = path13.extname(imagePath).toLowerCase();
4809
- let mimeType = "image/png";
4810
- if (ext === ".jpg" || ext === ".jpeg") {
4811
- mimeType = "image/jpeg";
4812
- } else if (ext === ".webp") {
4813
- mimeType = "image/webp";
4814
- }
4815
- return { data: base64, mimeType };
4816
- }
4817
- async function convertPhoneToTablet(phonePath, tabletRawPath, preserveWords) {
4818
- try {
4819
- const client = getGeminiClient2();
4820
- const { data: imageData, mimeType } = readImageAsBase642(phonePath);
4821
- const preserveInstruction = preserveWords && preserveWords.length > 0 ? `
4924
+ async function convertPhoneToTablet(phonePath, tabletRawPath, preserveWords, imageModel = "flash") {
4925
+ const client = createGeminiClient();
4926
+ const { data: imageData, mimeType } = readImageAsBase64(phonePath);
4927
+ const preserveInstruction = preserveWords && preserveWords.length > 0 ? `
4822
4928
  - Do NOT change these words, keep them exactly as-is: ${preserveWords.join(", ")}` : "";
4823
- const prompt = `This is a phone app screenshot. Please recreate this screenshot as a TABLET version.
4929
+ const prompt = `Convert this PHONE app screenshot into a TABLET screenshot.
4824
4930
 
4825
4931
  IMPORTANT INSTRUCTIONS:
4826
- - Convert this phone UI layout to a tablet-friendly WIDER layout
4827
- - The tablet screen has a 3:4 aspect ratio (wider than phone's 9:16)
4828
- - Expand the UI horizontally to take advantage of the wider screen
4829
- - If there's a device frame mockup, change it to a tablet device frame
4830
- - Maintain the same visual style, colors, and design language
4831
- - Keep all the same content and text, just adjust the layout
4832
- - Use tablet-appropriate spacing and element sizes
4833
- - If the phone shows a single column, consider using wider cards or side-by-side layouts
4834
- - Keep the same app functionality visible, just optimized for tablet${preserveInstruction}
4835
-
4836
- Generate a new tablet screenshot that represents the same app screen but optimized for tablet display.`;
4837
- const chat = client.chats.create({
4838
- model: "gemini-3-pro-image-preview",
4839
- config: {
4840
- responseModalities: ["TEXT", "IMAGE"]
4841
- }
4842
- });
4843
- const response = await chat.sendMessage({
4844
- message: [
4845
- { text: prompt },
4846
- {
4847
- inlineData: {
4848
- mimeType,
4849
- data: imageData
4850
- }
4851
- }
4852
- ],
4853
- config: {
4854
- responseModalities: ["TEXT", "IMAGE"],
4855
- imageConfig: {
4856
- aspectRatio: TABLET_ASPECT_RATIO
4857
- }
4858
- }
4932
+ - Preserve the original UI exactly: same components, text, icons, colors, and visual hierarchy
4933
+ - Do NOT redesign, recompose, or invent any new UI
4934
+ - Do NOT add/remove/reorder elements
4935
+ - Do NOT create side-by-side layouts, new panels, or alternative arrangements
4936
+ - Keep the same screen content and structure from the phone screenshot
4937
+ - Only adapt to tablet aspect ratio (3:4) by extending canvas width as needed
4938
+ - Keep the original content centered and unchanged as much as possible
4939
+ - Use matching background fill/empty space for extra horizontal area
4940
+ - If a device frame exists, keep the same frame style and avoid changing its design${preserveInstruction}
4941
+
4942
+ Output one tablet screenshot that looks like the original phone screenshot placed on a wider tablet canvas.`;
4943
+ try {
4944
+ const generated = await generateImageWithFallback({
4945
+ client,
4946
+ prompt,
4947
+ image: {
4948
+ mimeType,
4949
+ data: imageData
4950
+ },
4951
+ aspectRatio: TABLET_ASPECT_RATIO,
4952
+ imageModel
4859
4953
  });
4860
- const candidates = response.candidates;
4861
- if (!candidates || candidates.length === 0) {
4862
- return {
4863
- success: false,
4864
- error: "No response from Gemini API"
4865
- };
4866
- }
4867
- const parts = candidates[0].content?.parts;
4868
- if (!parts) {
4869
- return {
4870
- success: false,
4871
- error: "No content parts in response"
4872
- };
4954
+ const imageBuffer = Buffer.from(generated.imageBase64, "base64");
4955
+ const outputDir = path14.dirname(tabletRawPath);
4956
+ if (!fs15.existsSync(outputDir)) {
4957
+ fs15.mkdirSync(outputDir, { recursive: true });
4873
4958
  }
4874
- for (const part of parts) {
4875
- if (part.inlineData?.data) {
4876
- const imageBuffer = Buffer.from(part.inlineData.data, "base64");
4877
- const outputDir = path13.dirname(tabletRawPath);
4878
- if (!fs14.existsSync(outputDir)) {
4879
- fs14.mkdirSync(outputDir, { recursive: true });
4880
- }
4881
- await sharp3(imageBuffer).png().toFile(tabletRawPath);
4882
- return { success: true };
4883
- }
4884
- }
4885
- return {
4886
- success: false,
4887
- error: "No image data in Gemini response"
4888
- };
4959
+ await sharp3(imageBuffer).png().toFile(tabletRawPath);
4960
+ return { success: true };
4889
4961
  } catch (error) {
4890
- const message = error instanceof Error ? error.message : String(error);
4891
4962
  return {
4892
4963
  success: false,
4893
- error: message
4964
+ error: error instanceof Error ? error.message : String(error)
4894
4965
  };
4895
4966
  }
4896
4967
  }
4897
- async function convertWithProgress(tasks, onProgress, preserveWords) {
4968
+ async function convertWithProgress(tasks, onProgress, preserveWords, imageModel = "flash") {
4898
4969
  let successful = 0;
4899
4970
  let failed = 0;
4900
4971
  const errors = [];
@@ -4913,7 +4984,8 @@ async function convertWithProgress(tasks, onProgress, preserveWords) {
4913
4984
  const result = await convertPhoneToTablet(
4914
4985
  task.phonePath,
4915
4986
  task.tabletRawPath,
4916
- preserveWords
4987
+ preserveWords,
4988
+ imageModel
4917
4989
  );
4918
4990
  if (result.success) {
4919
4991
  successful++;
@@ -4939,7 +5011,8 @@ async function handlePhoneToTablet(input) {
4939
5011
  screenshotNumbers,
4940
5012
  dryRun = false,
4941
5013
  skipExisting = true,
4942
- preserveWords
5014
+ preserveWords,
5015
+ imageModel = "flash"
4943
5016
  } = input;
4944
5017
  const results = [];
4945
5018
  let appInfo;
@@ -4989,6 +5062,9 @@ async function handlePhoneToTablet(input) {
4989
5062
  }
4990
5063
  }
4991
5064
  results.push(`\u{1F3AF} Locales to process: ${localesToProcess.join(", ")}`);
5065
+ results.push(
5066
+ `\u{1F9E0} Image model: ${imageModel} (${GEMINI_IMAGE_MODEL_PRESETS[imageModel]})`
5067
+ );
4992
5068
  const allTasks = [];
4993
5069
  for (const locale of localesToProcess) {
4994
5070
  const phoneScreenshots = scanLocaleScreenshots(appInfo.slug, locale).filter(
@@ -5086,7 +5162,8 @@ Expected phone screenshots in: ${screenshotsDir2}/{locale}/phone/`
5086
5162
  );
5087
5163
  }
5088
5164
  },
5089
- preserveWords
5165
+ preserveWords,
5166
+ imageModel
5090
5167
  );
5091
5168
  results.push(`
5092
5169
  \u{1F4CA} Conversion Results:`);
@@ -5096,7 +5173,7 @@ Expected phone screenshots in: ${screenshotsDir2}/{locale}/phone/`
5096
5173
  results.push(`
5097
5174
  \u26A0\uFE0F Errors:`);
5098
5175
  for (const err of conversionResult.errors.slice(0, 5)) {
5099
- results.push(` - ${path13.basename(err.path)}: ${err.error}`);
5176
+ results.push(` - ${path14.basename(err.path)}: ${err.error}`);
5100
5177
  }
5101
5178
  if (conversionResult.errors.length > 5) {
5102
5179
  results.push(
@@ -5126,12 +5203,12 @@ Expected phone screenshots in: ${screenshotsDir2}/{locale}/phone/`
5126
5203
  // src/tools/app-icon/generate-app-icons.ts
5127
5204
  import { z as z10 } from "zod";
5128
5205
  import { zodToJsonSchema as zodToJsonSchema10 } from "zod-to-json-schema";
5129
- import fs15 from "fs";
5130
- import path15 from "path";
5206
+ import fs16 from "fs";
5207
+ import path16 from "path";
5131
5208
  import sharp5 from "sharp";
5132
5209
 
5133
5210
  // src/tools/app-icon/utils/icon-specs.util.ts
5134
- import path14 from "path";
5211
+ import path15 from "path";
5135
5212
  var ICON_FILENAMES = {
5136
5213
  BASE: "icon.png",
5137
5214
  IOS_LIGHT: "ios-light.png",
@@ -5172,14 +5249,14 @@ var ALL_ICON_TYPES = Object.keys(
5172
5249
  );
5173
5250
  function getIconsDir(slug, styleFolder) {
5174
5251
  const productsDir = getProductsDir();
5175
- const baseDir = path14.join(productsDir, slug, "icons");
5176
- return styleFolder ? path14.join(baseDir, styleFolder) : baseDir;
5252
+ const baseDir = path15.join(productsDir, slug, "icons");
5253
+ return styleFolder ? path15.join(baseDir, styleFolder) : baseDir;
5177
5254
  }
5178
5255
  function getBaseIconPath(slug, styleFolder) {
5179
- return path14.join(getIconsDir(slug, styleFolder), ICON_FILENAMES.BASE);
5256
+ return path15.join(getIconsDir(slug, styleFolder), ICON_FILENAMES.BASE);
5180
5257
  }
5181
5258
  function getIconOutputPath(slug, iconType, styleFolder) {
5182
- return path14.join(getIconsDir(slug, styleFolder), ICON_SPECS[iconType].filename);
5259
+ return path15.join(getIconsDir(slug, styleFolder), ICON_SPECS[iconType].filename);
5183
5260
  }
5184
5261
 
5185
5262
  // src/tools/app-icon/utils/icon-resizer.util.ts
@@ -5489,11 +5566,11 @@ function validateApp4(appName) {
5489
5566
  );
5490
5567
  }
5491
5568
  const productsDir = getProductsDir();
5492
- const configPath = path15.join(productsDir, app.slug, "config.json");
5569
+ const configPath = path16.join(productsDir, app.slug, "config.json");
5493
5570
  let config;
5494
- if (fs15.existsSync(configPath)) {
5571
+ if (fs16.existsSync(configPath)) {
5495
5572
  try {
5496
- const configData = fs15.readFileSync(configPath, "utf-8");
5573
+ const configData = fs16.readFileSync(configPath, "utf-8");
5497
5574
  config = JSON.parse(configData);
5498
5575
  } catch (error) {
5499
5576
  console.warn(
@@ -5512,7 +5589,7 @@ function buildGenerationTasks(slug, iconTypes, skipExisting, styleFolder) {
5512
5589
  const tasks = [];
5513
5590
  const baseIconPath = getBaseIconPath(slug, styleFolder);
5514
5591
  const iconsDir = getIconsDir(slug, styleFolder);
5515
- if (!fs15.existsSync(baseIconPath)) {
5592
+ if (!fs16.existsSync(baseIconPath)) {
5516
5593
  throw new Error(
5517
5594
  `Base icon not found: ${baseIconPath}
5518
5595
 
@@ -5521,7 +5598,7 @@ Please place your base icon at this location first.`
5521
5598
  }
5522
5599
  for (const iconType of iconTypes) {
5523
5600
  const outputPath = getIconOutputPath(slug, iconType, styleFolder);
5524
- if (skipExisting && fs15.existsSync(outputPath)) {
5601
+ if (skipExisting && fs16.existsSync(outputPath)) {
5525
5602
  continue;
5526
5603
  }
5527
5604
  tasks.push({
@@ -5547,9 +5624,9 @@ async function generateIcons(tasks, backgroundColor, logoAlignment, onProgress)
5547
5624
  };
5548
5625
  onProgress?.(progress);
5549
5626
  try {
5550
- const outputDir = path15.dirname(task.outputPath);
5551
- if (!fs15.existsSync(outputDir)) {
5552
- fs15.mkdirSync(outputDir, { recursive: true });
5627
+ const outputDir = path16.dirname(task.outputPath);
5628
+ if (!fs16.existsSync(outputDir)) {
5629
+ fs16.mkdirSync(outputDir, { recursive: true });
5553
5630
  }
5554
5631
  if (task.iconType === "android-notification-icon") {
5555
5632
  const whiteMask = await convertToWhiteMask(task.inputPath);
@@ -5753,24 +5830,8 @@ import path17 from "path";
5753
5830
  import sharp6 from "sharp";
5754
5831
 
5755
5832
  // src/tools/app-icon/utils/gemini.util.ts
5756
- import fs16 from "fs";
5757
- import path16 from "path";
5758
- import { GoogleGenAI as GoogleGenAI3 } from "@google/genai";
5759
- function getGeminiClient3() {
5760
- const apiKey = getGeminiApiKey();
5761
- return new GoogleGenAI3({ apiKey });
5762
- }
5763
- function readImageAsBase643(imagePath) {
5764
- const buffer = fs16.readFileSync(imagePath);
5765
- const base64 = buffer.toString("base64");
5766
- const ext = path16.extname(imagePath).toLowerCase();
5767
- let mimeType = "image/png";
5768
- if (ext === ".jpg" || ext === ".jpeg") {
5769
- mimeType = "image/jpeg";
5770
- } else if (ext === ".webp") {
5771
- mimeType = "image/webp";
5772
- }
5773
- return { data: base64, mimeType };
5833
+ function getGeminiClient() {
5834
+ return createGeminiClient();
5774
5835
  }
5775
5836
 
5776
5837
  // src/tools/app-icon/stylize-app-icon.ts
@@ -5788,6 +5849,9 @@ var stylizeAppIconInputSchema = z11.object({
5788
5849
  preserveShape: z11.boolean().optional().default(true).describe(
5789
5850
  "Preserve the original icon shape and structure (default: true). When true, only applies style elements without changing the core design."
5790
5851
  ),
5852
+ imageModel: z11.enum(GEMINI_IMAGE_MODEL_VALUES).optional().default("flash").describe(
5853
+ "Gemini image model preference. 'flash' (default) is faster/cheaper, 'pro' prioritizes quality."
5854
+ ),
5791
5855
  dryRun: z11.boolean().optional().default(false).describe(
5792
5856
  "Preview mode - shows what would be generated without actually calling API or saving files"
5793
5857
  )
@@ -5816,6 +5880,10 @@ var stylizeAppIconTool = {
5816
5880
  - GEMINI_API_KEY or GOOGLE_API_KEY environment variable
5817
5881
  - Base icon exists at {slug}/icons/icon.png
5818
5882
 
5883
+ **Model Selection:**
5884
+ - \`imageModel: "flash"\` (default) for speed/cost
5885
+ - \`imageModel: "pro"\` for higher instruction fidelity
5886
+
5819
5887
  **Example Flow:**
5820
5888
  \`\`\`
5821
5889
  Step 1: Stylize icon
@@ -5850,10 +5918,10 @@ function validateApp5(appName) {
5850
5918
  name: app.name || app.slug
5851
5919
  };
5852
5920
  }
5853
- async function stylizeIconWithAI(inputPath, outputPath, stylePrompt, preserveShape) {
5921
+ async function stylizeIconWithAI(inputPath, outputPath, stylePrompt, preserveShape, imageModel = "flash") {
5854
5922
  try {
5855
- const client = getGeminiClient3();
5856
- const { data: imageData, mimeType } = readImageAsBase643(inputPath);
5923
+ const client = getGeminiClient();
5924
+ const { data: imageData, mimeType } = readImageAsBase64(inputPath);
5857
5925
  const shapeInstruction = preserveShape ? "IMPORTANT: Preserve the original icon's shape, structure, and core design elements. Only add style-specific decorations and color adjustments." : "You can modify the icon structure as needed to achieve the style.";
5858
5926
  const fullPrompt = `You are an expert app icon designer. Transform this app icon with the following style:
5859
5927
 
@@ -5870,56 +5938,23 @@ Requirements:
5870
5938
  6. Ensure the result is visually appealing and brand-appropriate
5871
5939
 
5872
5940
  Generate the stylized icon with transparent background now.`;
5873
- const chat = client.chats.create({
5874
- model: "gemini-3-pro-image-preview",
5875
- config: {
5876
- responseModalities: ["TEXT", "IMAGE"]
5877
- }
5878
- });
5879
- const response = await chat.sendMessage({
5880
- message: [
5881
- { text: fullPrompt },
5882
- {
5883
- inlineData: {
5884
- mimeType,
5885
- data: imageData
5886
- }
5887
- }
5888
- ],
5889
- config: {
5890
- responseModalities: ["TEXT", "IMAGE"]
5891
- }
5941
+ const generated = await generateImageWithFallback({
5942
+ client,
5943
+ prompt: fullPrompt,
5944
+ image: {
5945
+ mimeType,
5946
+ data: imageData
5947
+ },
5948
+ imageModel
5892
5949
  });
5893
- const candidates = response.candidates;
5894
- if (!candidates || candidates.length === 0) {
5895
- return {
5896
- success: false,
5897
- error: "No response from Gemini API"
5898
- };
5899
- }
5900
- const parts = candidates[0].content?.parts;
5901
- if (!parts) {
5902
- return {
5903
- success: false,
5904
- error: "No content parts in response"
5905
- };
5906
- }
5907
- for (const part of parts) {
5908
- if (part.inlineData?.data) {
5909
- const imageBuffer = Buffer.from(part.inlineData.data, "base64");
5910
- const outputDir = path17.dirname(outputPath);
5911
- if (!fs17.existsSync(outputDir)) {
5912
- fs17.mkdirSync(outputDir, { recursive: true });
5913
- }
5914
- const processedImage = await sharp6(imageBuffer).ensureAlpha().png().toBuffer();
5915
- await sharp6(processedImage).toFile(outputPath);
5916
- return { success: true };
5917
- }
5918
- }
5919
- return {
5920
- success: false,
5921
- error: "No image data in Gemini response"
5922
- };
5950
+ const imageBuffer = Buffer.from(generated.imageBase64, "base64");
5951
+ const outputDir = path17.dirname(outputPath);
5952
+ if (!fs17.existsSync(outputDir)) {
5953
+ fs17.mkdirSync(outputDir, { recursive: true });
5954
+ }
5955
+ const processedImage = await sharp6(imageBuffer).ensureAlpha().png().toBuffer();
5956
+ await sharp6(processedImage).toFile(outputPath);
5957
+ return { success: true };
5923
5958
  } catch (error) {
5924
5959
  const message = error instanceof Error ? error.message : String(error);
5925
5960
  return {
@@ -5934,6 +5969,7 @@ async function handleStylizeAppIcon(input) {
5934
5969
  styleFolder,
5935
5970
  stylePrompt,
5936
5971
  preserveShape = true,
5972
+ imageModel = "flash",
5937
5973
  dryRun = false
5938
5974
  } = input;
5939
5975
  const results = [];
@@ -5975,6 +6011,9 @@ Please place your base icon at this location first.`
5975
6011
  results.push(
5976
6012
  `\u{1F527} Shape preservation: ${preserveShape ? "enabled" : "disabled"}`
5977
6013
  );
6014
+ results.push(
6015
+ `\u{1F9E0} Image model: ${imageModel} (${GEMINI_IMAGE_MODEL_PRESETS[imageModel]})`
6016
+ );
5978
6017
  if (dryRun) {
5979
6018
  results.push(`
5980
6019
  \u{1F50D} DRY RUN - No actual generation will be performed`);
@@ -5997,7 +6036,7 @@ Next step: Run generate-app-icons with styleFolder='${styleFolder}' to create pl
5997
6036
  };
5998
6037
  }
5999
6038
  try {
6000
- getGeminiClient3();
6039
+ getGeminiClient();
6001
6040
  } catch (error) {
6002
6041
  return {
6003
6042
  content: [
@@ -6014,7 +6053,8 @@ Next step: Run generate-app-icons with styleFolder='${styleFolder}' to create pl
6014
6053
  baseIconPath,
6015
6054
  outputPath,
6016
6055
  stylePrompt,
6017
- preserveShape
6056
+ preserveShape,
6057
+ imageModel
6018
6058
  );
6019
6059
  if (!stylizeResult.success) {
6020
6060
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pabal-resource-mcp",
3
- "version": "1.8.12",
3
+ "version": "1.9.1",
4
4
  "type": "module",
5
5
  "description": "MCP server for ASO data management with shared types and utilities",
6
6
  "author": "skyu",