pabal-resource-mcp 1.8.9 → 1.8.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/mcp-server.js +68 -134
- package/package.json +1 -1
package/dist/bin/mcp-server.js
CHANGED
|
@@ -5126,8 +5126,8 @@ Expected phone screenshots in: ${screenshotsDir2}/{locale}/phone/`
|
|
|
5126
5126
|
// src/tools/app-icon/generate-app-icons.ts
|
|
5127
5127
|
import { z as z10 } from "zod";
|
|
5128
5128
|
import { zodToJsonSchema as zodToJsonSchema10 } from "zod-to-json-schema";
|
|
5129
|
-
import
|
|
5130
|
-
import
|
|
5129
|
+
import fs16 from "fs";
|
|
5130
|
+
import path16 from "path";
|
|
5131
5131
|
import sharp6 from "sharp";
|
|
5132
5132
|
|
|
5133
5133
|
// src/tools/app-icon/utils/icon-specs.util.ts
|
|
@@ -5401,97 +5401,21 @@ function calculateFinalPosition(areaLeft, areaTop, areaWidth, areaHeight, logoWi
|
|
|
5401
5401
|
}
|
|
5402
5402
|
|
|
5403
5403
|
// src/tools/app-icon/utils/icon-masking.util.ts
|
|
5404
|
-
import fs16 from "fs";
|
|
5405
|
-
import path16 from "path";
|
|
5406
|
-
import sharp5 from "sharp";
|
|
5407
|
-
|
|
5408
|
-
// src/tools/app-icon/utils/gemini.util.ts
|
|
5409
5404
|
import fs15 from "fs";
|
|
5410
5405
|
import path15 from "path";
|
|
5411
|
-
import
|
|
5412
|
-
function
|
|
5413
|
-
const apiKey = getGeminiApiKey();
|
|
5414
|
-
return new GoogleGenAI3({ apiKey });
|
|
5415
|
-
}
|
|
5416
|
-
function readImageAsBase643(imagePath) {
|
|
5417
|
-
const buffer = fs15.readFileSync(imagePath);
|
|
5418
|
-
const base64 = buffer.toString("base64");
|
|
5419
|
-
const ext = path15.extname(imagePath).toLowerCase();
|
|
5420
|
-
let mimeType = "image/png";
|
|
5421
|
-
if (ext === ".jpg" || ext === ".jpeg") {
|
|
5422
|
-
mimeType = "image/jpeg";
|
|
5423
|
-
} else if (ext === ".webp") {
|
|
5424
|
-
mimeType = "image/webp";
|
|
5425
|
-
}
|
|
5426
|
-
return { data: base64, mimeType };
|
|
5427
|
-
}
|
|
5428
|
-
|
|
5429
|
-
// src/tools/app-icon/utils/icon-masking.util.ts
|
|
5430
|
-
async function applyWhiteMasking(inputPath, outputPath, targetSize, logoPosition) {
|
|
5406
|
+
import sharp5 from "sharp";
|
|
5407
|
+
async function applyWhiteMasking(inputPath, outputPath, targetSize) {
|
|
5431
5408
|
try {
|
|
5432
|
-
const
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
const
|
|
5437
|
-
|
|
5438
|
-
|
|
5439
|
-
|
|
5440
|
-
|
|
5441
|
-
|
|
5442
|
-
const chat = client.chats.create({
|
|
5443
|
-
model: "gemini-3-pro-image-preview",
|
|
5444
|
-
config: {
|
|
5445
|
-
responseModalities: ["TEXT", "IMAGE"]
|
|
5446
|
-
}
|
|
5447
|
-
});
|
|
5448
|
-
const response = await chat.sendMessage({
|
|
5449
|
-
message: [
|
|
5450
|
-
{ text: prompt },
|
|
5451
|
-
{
|
|
5452
|
-
inlineData: {
|
|
5453
|
-
mimeType,
|
|
5454
|
-
data: imageData
|
|
5455
|
-
}
|
|
5456
|
-
}
|
|
5457
|
-
],
|
|
5458
|
-
config: {
|
|
5459
|
-
responseModalities: ["TEXT", "IMAGE"]
|
|
5460
|
-
}
|
|
5461
|
-
});
|
|
5462
|
-
const candidates = response.candidates;
|
|
5463
|
-
if (!candidates || candidates.length === 0) {
|
|
5464
|
-
return {
|
|
5465
|
-
success: false,
|
|
5466
|
-
error: "No response from Gemini API"
|
|
5467
|
-
};
|
|
5468
|
-
}
|
|
5469
|
-
const parts = candidates[0].content?.parts;
|
|
5470
|
-
if (!parts) {
|
|
5471
|
-
return {
|
|
5472
|
-
success: false,
|
|
5473
|
-
error: "No content parts in response"
|
|
5474
|
-
};
|
|
5475
|
-
}
|
|
5476
|
-
for (const part of parts) {
|
|
5477
|
-
if (part.inlineData?.data) {
|
|
5478
|
-
const imageBuffer = Buffer.from(part.inlineData.data, "base64");
|
|
5479
|
-
const outputDir = path16.dirname(outputPath);
|
|
5480
|
-
if (!fs16.existsSync(outputDir)) {
|
|
5481
|
-
fs16.mkdirSync(outputDir, { recursive: true });
|
|
5482
|
-
}
|
|
5483
|
-
const processedBuffer = await ensureTransparentBackground(imageBuffer);
|
|
5484
|
-
await sharp5(processedBuffer).resize(targetSize, targetSize, {
|
|
5485
|
-
fit: "contain",
|
|
5486
|
-
background: { r: 0, g: 0, b: 0, alpha: 0 }
|
|
5487
|
-
}).png().toFile(outputPath);
|
|
5488
|
-
return { success: true };
|
|
5489
|
-
}
|
|
5490
|
-
}
|
|
5491
|
-
return {
|
|
5492
|
-
success: false,
|
|
5493
|
-
error: "No image data in Gemini response"
|
|
5494
|
-
};
|
|
5409
|
+
const outputDir = path15.dirname(outputPath);
|
|
5410
|
+
if (!fs15.existsSync(outputDir)) {
|
|
5411
|
+
fs15.mkdirSync(outputDir, { recursive: true });
|
|
5412
|
+
}
|
|
5413
|
+
const processedBuffer = await createWhiteMaskFromOriginal(inputPath);
|
|
5414
|
+
await sharp5(processedBuffer).resize(targetSize, targetSize, {
|
|
5415
|
+
fit: "contain",
|
|
5416
|
+
background: { r: 0, g: 0, b: 0, alpha: 0 }
|
|
5417
|
+
}).png().toFile(outputPath);
|
|
5418
|
+
return { success: true };
|
|
5495
5419
|
} catch (error) {
|
|
5496
5420
|
const message = error instanceof Error ? error.message : String(error);
|
|
5497
5421
|
return {
|
|
@@ -5500,16 +5424,12 @@ Requirements:
|
|
|
5500
5424
|
};
|
|
5501
5425
|
}
|
|
5502
5426
|
}
|
|
5503
|
-
async function
|
|
5504
|
-
const { data, info } = await sharp5(
|
|
5427
|
+
async function createWhiteMaskFromOriginal(inputPath) {
|
|
5428
|
+
const { data, info } = await sharp5(inputPath).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
5505
5429
|
const pixels = new Uint8Array(data.length);
|
|
5506
5430
|
for (let i = 0; i < data.length; i += 4) {
|
|
5507
|
-
const r = data[i];
|
|
5508
|
-
const g = data[i + 1];
|
|
5509
|
-
const b = data[i + 2];
|
|
5510
5431
|
const a = data[i + 3];
|
|
5511
|
-
|
|
5512
|
-
if (brightness > 200 && a > 10) {
|
|
5432
|
+
if (a > 10) {
|
|
5513
5433
|
pixels[i] = 255;
|
|
5514
5434
|
pixels[i + 1] = 255;
|
|
5515
5435
|
pixels[i + 2] = 255;
|
|
@@ -5565,11 +5485,8 @@ var generateAppIconsInputSchema = z10.object({
|
|
|
5565
5485
|
]).optional().describe(
|
|
5566
5486
|
"Logo alignment within the canvas (default: center or config default). Affects how the logo is positioned relative to the safe zone."
|
|
5567
5487
|
),
|
|
5568
|
-
|
|
5569
|
-
"Use
|
|
5570
|
-
),
|
|
5571
|
-
logoPosition: z10.string().optional().describe(
|
|
5572
|
-
"Additional prompt for logo positioning when using AI masking (e.g., 'centered', 'slightly above center'). Only used when useAiMasking is true."
|
|
5488
|
+
useAlphaMasking: z10.boolean().optional().default(false).describe(
|
|
5489
|
+
"Use alpha channel for white masking (default: false). When false, uses Sharp-based threshold conversion (converts bright pixels to white). When true, uses original image's alpha channel (preserves exact logo shape)."
|
|
5573
5490
|
),
|
|
5574
5491
|
skipExisting: z10.boolean().optional().default(false).describe("Skip generation if output file already exists (default: false)"),
|
|
5575
5492
|
dryRun: z10.boolean().optional().default(false).describe(
|
|
@@ -5598,20 +5515,20 @@ var generateAppIconsTool = {
|
|
|
5598
5515
|
- **Automatic Padding Removal**: Extracts actual logo from icon.png (removes surrounding padding)
|
|
5599
5516
|
- **Smart Safe Zone Positioning**: Logo automatically fits within platform-specific circles
|
|
5600
5517
|
- **Flexible Alignment**: Position logo center/left/right/top/bottom relative to safe zone
|
|
5601
|
-
- **White Masking**:
|
|
5518
|
+
- **White Masking**: Threshold-based (default) or alpha-based (preserves exact shape)
|
|
5602
5519
|
- **Custom Background**: Only applies to ios-light.png. Others (adaptive-icon, splash, notification) are transparent
|
|
5603
5520
|
- **Style Variants**: Generate themed icons (christmas, halloween, etc.) with style-specific defaults
|
|
5604
5521
|
- **Config Integration**: Uses config.json appIcon settings for default colors and alignment
|
|
5605
5522
|
|
|
5606
5523
|
**White Masking Options:**
|
|
5607
|
-
- **Default (
|
|
5608
|
-
- **
|
|
5524
|
+
- **Default (useAlphaMasking=false)**: Threshold conversion - converts bright pixels to white
|
|
5525
|
+
- **Alpha-based (useAlphaMasking=true)**: Uses original alpha channel - preserves exact logo shape
|
|
5609
5526
|
|
|
5610
5527
|
**\u26A0\uFE0F Important: After Generation**
|
|
5611
5528
|
After generating icons, always check **android-notification-icon.png**:
|
|
5612
|
-
- If white masking is not clean (unwanted artifacts
|
|
5613
|
-
- Regenerate with \`
|
|
5614
|
-
- Example: \`{ "appName": "my-app", "
|
|
5529
|
+
- If white masking is not clean (unwanted artifacts or shape issues)
|
|
5530
|
+
- Regenerate with \`useAlphaMasking: true\` for alpha-based masking
|
|
5531
|
+
- Example: \`{ "appName": "my-app", "useAlphaMasking": true, "iconTypes": ["android-notification-icon"] }\`
|
|
5615
5532
|
|
|
5616
5533
|
**Example:**
|
|
5617
5534
|
\`\`\`
|
|
@@ -5620,7 +5537,7 @@ INPUT: my-app/icons/icon.png (source logo with padding)
|
|
|
5620
5537
|
OUTPUT: my-app/icons/ios-light.png (logo centered in safe zone)
|
|
5621
5538
|
my-app/icons/adaptive-icon.png (logo aligned as specified)
|
|
5622
5539
|
my-app/icons/splash-icon-light.png
|
|
5623
|
-
my-app/icons/android-notification-icon.png (white mask
|
|
5540
|
+
my-app/icons/android-notification-icon.png (white mask)
|
|
5624
5541
|
|
|
5625
5542
|
With styleFolder='christmas':
|
|
5626
5543
|
INPUT: my-app/icons/christmas/icon.png
|
|
@@ -5638,11 +5555,11 @@ function validateApp4(appName) {
|
|
|
5638
5555
|
);
|
|
5639
5556
|
}
|
|
5640
5557
|
const productsDir = getProductsDir();
|
|
5641
|
-
const configPath =
|
|
5558
|
+
const configPath = path16.join(productsDir, app.slug, "config.json");
|
|
5642
5559
|
let config;
|
|
5643
|
-
if (
|
|
5560
|
+
if (fs16.existsSync(configPath)) {
|
|
5644
5561
|
try {
|
|
5645
|
-
const configData =
|
|
5562
|
+
const configData = fs16.readFileSync(configPath, "utf-8");
|
|
5646
5563
|
config = JSON.parse(configData);
|
|
5647
5564
|
} catch (error) {
|
|
5648
5565
|
console.warn(
|
|
@@ -5661,7 +5578,7 @@ function buildGenerationTasks(slug, iconTypes, skipExisting, styleFolder) {
|
|
|
5661
5578
|
const tasks = [];
|
|
5662
5579
|
const baseIconPath = getBaseIconPath(slug, styleFolder);
|
|
5663
5580
|
const iconsDir = getIconsDir(slug, styleFolder);
|
|
5664
|
-
if (!
|
|
5581
|
+
if (!fs16.existsSync(baseIconPath)) {
|
|
5665
5582
|
throw new Error(
|
|
5666
5583
|
`Base icon not found: ${baseIconPath}
|
|
5667
5584
|
|
|
@@ -5670,7 +5587,7 @@ Please place your base icon at this location first.`
|
|
|
5670
5587
|
}
|
|
5671
5588
|
for (const iconType of iconTypes) {
|
|
5672
5589
|
const outputPath = getIconOutputPath(slug, iconType, styleFolder);
|
|
5673
|
-
if (skipExisting &&
|
|
5590
|
+
if (skipExisting && fs16.existsSync(outputPath)) {
|
|
5674
5591
|
continue;
|
|
5675
5592
|
}
|
|
5676
5593
|
tasks.push({
|
|
@@ -5682,7 +5599,7 @@ Please place your base icon at this location first.`
|
|
|
5682
5599
|
}
|
|
5683
5600
|
return { tasks, baseIconPath };
|
|
5684
5601
|
}
|
|
5685
|
-
async function generateIcons(tasks, backgroundColor, logoAlignment,
|
|
5602
|
+
async function generateIcons(tasks, backgroundColor, logoAlignment, useAlphaMasking, onProgress) {
|
|
5686
5603
|
let generatedCount = 0;
|
|
5687
5604
|
const errors = [];
|
|
5688
5605
|
const total = tasks.length;
|
|
@@ -5696,17 +5613,16 @@ async function generateIcons(tasks, backgroundColor, logoAlignment, useAiMasking
|
|
|
5696
5613
|
};
|
|
5697
5614
|
onProgress?.(progress);
|
|
5698
5615
|
try {
|
|
5699
|
-
const outputDir =
|
|
5700
|
-
if (!
|
|
5701
|
-
|
|
5616
|
+
const outputDir = path16.dirname(task.outputPath);
|
|
5617
|
+
if (!fs16.existsSync(outputDir)) {
|
|
5618
|
+
fs16.mkdirSync(outputDir, { recursive: true });
|
|
5702
5619
|
}
|
|
5703
5620
|
if (task.iconType === "android-notification-icon") {
|
|
5704
|
-
if (
|
|
5621
|
+
if (useAlphaMasking) {
|
|
5705
5622
|
const result = await applyWhiteMasking(
|
|
5706
5623
|
task.inputPath,
|
|
5707
5624
|
task.outputPath,
|
|
5708
|
-
task.spec.size
|
|
5709
|
-
logoPosition
|
|
5625
|
+
task.spec.size
|
|
5710
5626
|
);
|
|
5711
5627
|
if (!result.success) {
|
|
5712
5628
|
throw new Error(result.error || "AI white masking failed");
|
|
@@ -5751,8 +5667,7 @@ async function handleGenerateAppIcons(input) {
|
|
|
5751
5667
|
styleFolder,
|
|
5752
5668
|
backgroundColor: bgColorInput,
|
|
5753
5669
|
logoAlignment: logoAlignmentInput,
|
|
5754
|
-
|
|
5755
|
-
logoPosition,
|
|
5670
|
+
useAlphaMasking = false,
|
|
5756
5671
|
skipExisting = false,
|
|
5757
5672
|
dryRun = false
|
|
5758
5673
|
} = input;
|
|
@@ -5800,13 +5715,10 @@ async function handleGenerateAppIcons(input) {
|
|
|
5800
5715
|
const iconTypes = requestedIconTypes || ALL_ICON_TYPES;
|
|
5801
5716
|
results.push(`\u{1F3AF} Icon types: ${iconTypes.join(", ")}`);
|
|
5802
5717
|
results.push(`\u{1F4D0} Logo alignment: ${logoAlignment}`);
|
|
5803
|
-
if (
|
|
5804
|
-
results.push(`\u{
|
|
5805
|
-
if (logoPosition) {
|
|
5806
|
-
results.push(`\u{1F4CD} Logo positioning: ${logoPosition}`);
|
|
5807
|
-
}
|
|
5718
|
+
if (useAlphaMasking) {
|
|
5719
|
+
results.push(`\u{1F3AD} White masking: alpha-based (preserves exact shape)`);
|
|
5808
5720
|
} else {
|
|
5809
|
-
results.push(`\
|
|
5721
|
+
results.push(`\u{1F3AD} White masking: threshold-based (converts bright pixels)`);
|
|
5810
5722
|
}
|
|
5811
5723
|
let tasks;
|
|
5812
5724
|
let baseIconPath;
|
|
@@ -5875,8 +5787,7 @@ async function handleGenerateAppIcons(input) {
|
|
|
5875
5787
|
tasks,
|
|
5876
5788
|
bgColor,
|
|
5877
5789
|
logoAlignment,
|
|
5878
|
-
|
|
5879
|
-
logoPosition,
|
|
5790
|
+
useAlphaMasking,
|
|
5880
5791
|
(progress) => {
|
|
5881
5792
|
const progressPrefix = `[${progress.current}/${progress.total}]`;
|
|
5882
5793
|
if (progress.status === "generating") {
|
|
@@ -5911,16 +5822,16 @@ async function handleGenerateAppIcons(input) {
|
|
|
5911
5822
|
const hasNotificationIcon = tasks.some((t) => t.iconType === "android-notification-icon") && !generationResult.errors.some(
|
|
5912
5823
|
(e) => e.iconType === "android-notification-icon"
|
|
5913
5824
|
);
|
|
5914
|
-
if (hasNotificationIcon && !
|
|
5825
|
+
if (hasNotificationIcon && !useAlphaMasking) {
|
|
5915
5826
|
results.push(
|
|
5916
5827
|
`
|
|
5917
5828
|
\u26A0\uFE0F IMPORTANT: Check android-notification-icon.png`
|
|
5918
5829
|
);
|
|
5919
5830
|
results.push(
|
|
5920
|
-
` If
|
|
5831
|
+
` If the shape is not correct, regenerate with alpha masking:`
|
|
5921
5832
|
);
|
|
5922
5833
|
results.push(
|
|
5923
|
-
`
|
|
5834
|
+
` { "appName": "${appInfo.slug}", "useAlphaMasking": true, "iconTypes": ["android-notification-icon"]${styleFolder ? `, "styleFolder": "${styleFolder}"` : ""} }`
|
|
5924
5835
|
);
|
|
5925
5836
|
}
|
|
5926
5837
|
return {
|
|
@@ -5939,6 +5850,29 @@ import { zodToJsonSchema as zodToJsonSchema11 } from "zod-to-json-schema";
|
|
|
5939
5850
|
import fs18 from "fs";
|
|
5940
5851
|
import path18 from "path";
|
|
5941
5852
|
import sharp7 from "sharp";
|
|
5853
|
+
|
|
5854
|
+
// src/tools/app-icon/utils/gemini.util.ts
|
|
5855
|
+
import fs17 from "fs";
|
|
5856
|
+
import path17 from "path";
|
|
5857
|
+
import { GoogleGenAI as GoogleGenAI3 } from "@google/genai";
|
|
5858
|
+
function getGeminiClient3() {
|
|
5859
|
+
const apiKey = getGeminiApiKey();
|
|
5860
|
+
return new GoogleGenAI3({ apiKey });
|
|
5861
|
+
}
|
|
5862
|
+
function readImageAsBase643(imagePath) {
|
|
5863
|
+
const buffer = fs17.readFileSync(imagePath);
|
|
5864
|
+
const base64 = buffer.toString("base64");
|
|
5865
|
+
const ext = path17.extname(imagePath).toLowerCase();
|
|
5866
|
+
let mimeType = "image/png";
|
|
5867
|
+
if (ext === ".jpg" || ext === ".jpeg") {
|
|
5868
|
+
mimeType = "image/jpeg";
|
|
5869
|
+
} else if (ext === ".webp") {
|
|
5870
|
+
mimeType = "image/webp";
|
|
5871
|
+
}
|
|
5872
|
+
return { data: base64, mimeType };
|
|
5873
|
+
}
|
|
5874
|
+
|
|
5875
|
+
// src/tools/app-icon/stylize-app-icon.ts
|
|
5942
5876
|
var TOOL_NAME7 = "stylize-app-icon";
|
|
5943
5877
|
var stylizeAppIconInputSchema = z11.object({
|
|
5944
5878
|
appName: z11.string().describe(
|