pabal-resource-mcp 1.8.9 → 1.8.11
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 +76 -241
- package/package.json +1 -1
package/dist/bin/mcp-server.js
CHANGED
|
@@ -5126,9 +5126,9 @@ 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
|
|
5131
|
-
import
|
|
5129
|
+
import fs15 from "fs";
|
|
5130
|
+
import path15 from "path";
|
|
5131
|
+
import sharp5 from "sharp";
|
|
5132
5132
|
|
|
5133
5133
|
// src/tools/app-icon/utils/icon-specs.util.ts
|
|
5134
5134
|
import path14 from "path";
|
|
@@ -5263,14 +5263,12 @@ async function trimIconPadding(inputPath, threshold = 10) {
|
|
|
5263
5263
|
const trimmed = await sharp4(inputPath).trim({ threshold }).toBuffer();
|
|
5264
5264
|
return trimmed;
|
|
5265
5265
|
}
|
|
5266
|
-
async function convertToWhiteMask(inputPath
|
|
5267
|
-
const
|
|
5268
|
-
const { data, info } = await sharp4(grayscale).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
5266
|
+
async function convertToWhiteMask(inputPath) {
|
|
5267
|
+
const { data, info } = await sharp4(inputPath).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
5269
5268
|
const pixels = new Uint8Array(data.length);
|
|
5270
5269
|
for (let i = 0; i < data.length; i += 4) {
|
|
5271
|
-
const gray = data[i];
|
|
5272
5270
|
const alpha = data[i + 3];
|
|
5273
|
-
if (alpha > 10
|
|
5271
|
+
if (alpha > 10) {
|
|
5274
5272
|
pixels[i] = 255;
|
|
5275
5273
|
pixels[i + 1] = 255;
|
|
5276
5274
|
pixels[i + 2] = 255;
|
|
@@ -5282,14 +5280,13 @@ async function convertToWhiteMask(inputPath, threshold = 128) {
|
|
|
5282
5280
|
pixels[i + 3] = 0;
|
|
5283
5281
|
}
|
|
5284
5282
|
}
|
|
5285
|
-
|
|
5283
|
+
return sharp4(pixels, {
|
|
5286
5284
|
raw: {
|
|
5287
5285
|
width: info.width,
|
|
5288
5286
|
height: info.height,
|
|
5289
5287
|
channels: 4
|
|
5290
5288
|
}
|
|
5291
5289
|
}).png().toBuffer();
|
|
5292
|
-
return whiteMask;
|
|
5293
5290
|
}
|
|
5294
5291
|
function calculateAlignmentArea(canvasSize, safeZoneLeft, safeZoneRight, safeZoneTop, safeZoneBottom, alignment) {
|
|
5295
5292
|
const safeZoneDiameter = safeZoneRight - safeZoneLeft;
|
|
@@ -5400,136 +5397,6 @@ function calculateFinalPosition(areaLeft, areaTop, areaWidth, areaHeight, logoWi
|
|
|
5400
5397
|
return { left, top };
|
|
5401
5398
|
}
|
|
5402
5399
|
|
|
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
|
-
import fs15 from "fs";
|
|
5410
|
-
import path15 from "path";
|
|
5411
|
-
import { GoogleGenAI as GoogleGenAI3 } from "@google/genai";
|
|
5412
|
-
function getGeminiClient3() {
|
|
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) {
|
|
5431
|
-
try {
|
|
5432
|
-
const client = getGeminiClient3();
|
|
5433
|
-
const { data: imageData, mimeType } = readImageAsBase643(inputPath);
|
|
5434
|
-
const positionInstruction = logoPosition ? `
|
|
5435
|
-
- Logo positioning hint: ${logoPosition}` : "";
|
|
5436
|
-
const prompt = `Convert this icon into a simple white Android notification icon.
|
|
5437
|
-
|
|
5438
|
-
Requirements:
|
|
5439
|
-
- White logo (#FFFFFF) on transparent background
|
|
5440
|
-
- Preserve the logo shape exactly
|
|
5441
|
-
- No gradients, solid white only${positionInstruction}`;
|
|
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
|
-
};
|
|
5495
|
-
} catch (error) {
|
|
5496
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
5497
|
-
return {
|
|
5498
|
-
success: false,
|
|
5499
|
-
error: message
|
|
5500
|
-
};
|
|
5501
|
-
}
|
|
5502
|
-
}
|
|
5503
|
-
async function ensureTransparentBackground(imageBuffer) {
|
|
5504
|
-
const { data, info } = await sharp5(imageBuffer).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
5505
|
-
const pixels = new Uint8Array(data.length);
|
|
5506
|
-
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
|
-
const a = data[i + 3];
|
|
5511
|
-
const brightness = (r + g + b) / 3;
|
|
5512
|
-
if (brightness > 200 && a > 10) {
|
|
5513
|
-
pixels[i] = 255;
|
|
5514
|
-
pixels[i + 1] = 255;
|
|
5515
|
-
pixels[i + 2] = 255;
|
|
5516
|
-
pixels[i + 3] = 255;
|
|
5517
|
-
} else {
|
|
5518
|
-
pixels[i] = 0;
|
|
5519
|
-
pixels[i + 1] = 0;
|
|
5520
|
-
pixels[i + 2] = 0;
|
|
5521
|
-
pixels[i + 3] = 0;
|
|
5522
|
-
}
|
|
5523
|
-
}
|
|
5524
|
-
return sharp5(pixels, {
|
|
5525
|
-
raw: {
|
|
5526
|
-
width: info.width,
|
|
5527
|
-
height: info.height,
|
|
5528
|
-
channels: 4
|
|
5529
|
-
}
|
|
5530
|
-
}).png().toBuffer();
|
|
5531
|
-
}
|
|
5532
|
-
|
|
5533
5400
|
// src/tools/app-icon/generate-app-icons.ts
|
|
5534
5401
|
var TOOL_NAME6 = "generate-app-icons";
|
|
5535
5402
|
var generateAppIconsInputSchema = z10.object({
|
|
@@ -5565,12 +5432,6 @@ var generateAppIconsInputSchema = z10.object({
|
|
|
5565
5432
|
]).optional().describe(
|
|
5566
5433
|
"Logo alignment within the canvas (default: center or config default). Affects how the logo is positioned relative to the safe zone."
|
|
5567
5434
|
),
|
|
5568
|
-
useAiMasking: z10.boolean().optional().default(false).describe(
|
|
5569
|
-
"Use Gemini API for intelligent white masking on notification icon (default: false). When false, uses Sharp-based threshold conversion (faster, free, no API key needed). When true, uses Gemini AI for more sophisticated logo extraction (requires GEMINI_API_KEY)."
|
|
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."
|
|
5573
|
-
),
|
|
5574
5435
|
skipExisting: z10.boolean().optional().default(false).describe("Skip generation if output file already exists (default: false)"),
|
|
5575
5436
|
dryRun: z10.boolean().optional().default(false).describe(
|
|
5576
5437
|
"Preview mode - shows what would be generated without actually generating"
|
|
@@ -5598,21 +5459,11 @@ var generateAppIconsTool = {
|
|
|
5598
5459
|
- **Automatic Padding Removal**: Extracts actual logo from icon.png (removes surrounding padding)
|
|
5599
5460
|
- **Smart Safe Zone Positioning**: Logo automatically fits within platform-specific circles
|
|
5600
5461
|
- **Flexible Alignment**: Position logo center/left/right/top/bottom relative to safe zone
|
|
5601
|
-
- **White Masking**:
|
|
5462
|
+
- **White Masking**: Threshold-based (default) or alpha-based (preserves exact shape)
|
|
5602
5463
|
- **Custom Background**: Only applies to ios-light.png. Others (adaptive-icon, splash, notification) are transparent
|
|
5603
5464
|
- **Style Variants**: Generate themed icons (christmas, halloween, etc.) with style-specific defaults
|
|
5604
5465
|
- **Config Integration**: Uses config.json appIcon settings for default colors and alignment
|
|
5605
5466
|
|
|
5606
|
-
**White Masking Options:**
|
|
5607
|
-
- **Default (useAiMasking=false)**: Sharp threshold conversion - fast, free, no API key needed
|
|
5608
|
-
- **AI-Powered (useAiMasking=true)**: Gemini API - more sophisticated, requires GEMINI_API_KEY
|
|
5609
|
-
|
|
5610
|
-
**\u26A0\uFE0F Important: After Generation**
|
|
5611
|
-
After generating icons, always check **android-notification-icon.png**:
|
|
5612
|
-
- If white masking is not clean (unwanted artifacts, incomplete conversion, or poor contrast)
|
|
5613
|
-
- Regenerate with \`useAiMasking: true\` for better AI-powered masking
|
|
5614
|
-
- Example: \`{ "appName": "my-app", "useAiMasking": true, "iconTypes": ["android-notification-icon"] }\`
|
|
5615
|
-
|
|
5616
5467
|
**Example:**
|
|
5617
5468
|
\`\`\`
|
|
5618
5469
|
INPUT: my-app/icons/icon.png (source logo with padding)
|
|
@@ -5620,7 +5471,7 @@ INPUT: my-app/icons/icon.png (source logo with padding)
|
|
|
5620
5471
|
OUTPUT: my-app/icons/ios-light.png (logo centered in safe zone)
|
|
5621
5472
|
my-app/icons/adaptive-icon.png (logo aligned as specified)
|
|
5622
5473
|
my-app/icons/splash-icon-light.png
|
|
5623
|
-
my-app/icons/android-notification-icon.png (white mask
|
|
5474
|
+
my-app/icons/android-notification-icon.png (white mask)
|
|
5624
5475
|
|
|
5625
5476
|
With styleFolder='christmas':
|
|
5626
5477
|
INPUT: my-app/icons/christmas/icon.png
|
|
@@ -5638,11 +5489,11 @@ function validateApp4(appName) {
|
|
|
5638
5489
|
);
|
|
5639
5490
|
}
|
|
5640
5491
|
const productsDir = getProductsDir();
|
|
5641
|
-
const configPath =
|
|
5492
|
+
const configPath = path15.join(productsDir, app.slug, "config.json");
|
|
5642
5493
|
let config;
|
|
5643
|
-
if (
|
|
5494
|
+
if (fs15.existsSync(configPath)) {
|
|
5644
5495
|
try {
|
|
5645
|
-
const configData =
|
|
5496
|
+
const configData = fs15.readFileSync(configPath, "utf-8");
|
|
5646
5497
|
config = JSON.parse(configData);
|
|
5647
5498
|
} catch (error) {
|
|
5648
5499
|
console.warn(
|
|
@@ -5661,7 +5512,7 @@ function buildGenerationTasks(slug, iconTypes, skipExisting, styleFolder) {
|
|
|
5661
5512
|
const tasks = [];
|
|
5662
5513
|
const baseIconPath = getBaseIconPath(slug, styleFolder);
|
|
5663
5514
|
const iconsDir = getIconsDir(slug, styleFolder);
|
|
5664
|
-
if (!
|
|
5515
|
+
if (!fs15.existsSync(baseIconPath)) {
|
|
5665
5516
|
throw new Error(
|
|
5666
5517
|
`Base icon not found: ${baseIconPath}
|
|
5667
5518
|
|
|
@@ -5670,7 +5521,7 @@ Please place your base icon at this location first.`
|
|
|
5670
5521
|
}
|
|
5671
5522
|
for (const iconType of iconTypes) {
|
|
5672
5523
|
const outputPath = getIconOutputPath(slug, iconType, styleFolder);
|
|
5673
|
-
if (skipExisting &&
|
|
5524
|
+
if (skipExisting && fs15.existsSync(outputPath)) {
|
|
5674
5525
|
continue;
|
|
5675
5526
|
}
|
|
5676
5527
|
tasks.push({
|
|
@@ -5682,7 +5533,7 @@ Please place your base icon at this location first.`
|
|
|
5682
5533
|
}
|
|
5683
5534
|
return { tasks, baseIconPath };
|
|
5684
5535
|
}
|
|
5685
|
-
async function generateIcons(tasks, backgroundColor, logoAlignment,
|
|
5536
|
+
async function generateIcons(tasks, backgroundColor, logoAlignment, onProgress) {
|
|
5686
5537
|
let generatedCount = 0;
|
|
5687
5538
|
const errors = [];
|
|
5688
5539
|
const total = tasks.length;
|
|
@@ -5696,28 +5547,16 @@ async function generateIcons(tasks, backgroundColor, logoAlignment, useAiMasking
|
|
|
5696
5547
|
};
|
|
5697
5548
|
onProgress?.(progress);
|
|
5698
5549
|
try {
|
|
5699
|
-
const outputDir =
|
|
5700
|
-
if (!
|
|
5701
|
-
|
|
5550
|
+
const outputDir = path15.dirname(task.outputPath);
|
|
5551
|
+
if (!fs15.existsSync(outputDir)) {
|
|
5552
|
+
fs15.mkdirSync(outputDir, { recursive: true });
|
|
5702
5553
|
}
|
|
5703
5554
|
if (task.iconType === "android-notification-icon") {
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
logoPosition
|
|
5710
|
-
);
|
|
5711
|
-
if (!result.success) {
|
|
5712
|
-
throw new Error(result.error || "AI white masking failed");
|
|
5713
|
-
}
|
|
5714
|
-
} else {
|
|
5715
|
-
const whiteMask = await convertToWhiteMask(task.inputPath);
|
|
5716
|
-
await sharp6(whiteMask).resize(task.spec.size, task.spec.size, {
|
|
5717
|
-
fit: "contain",
|
|
5718
|
-
background: { r: 0, g: 0, b: 0, alpha: 0 }
|
|
5719
|
-
}).png().toFile(task.outputPath);
|
|
5720
|
-
}
|
|
5555
|
+
const whiteMask = await convertToWhiteMask(task.inputPath);
|
|
5556
|
+
await sharp5(whiteMask).resize(task.spec.size, task.spec.size, {
|
|
5557
|
+
fit: "contain",
|
|
5558
|
+
background: { r: 0, g: 0, b: 0, alpha: 0 }
|
|
5559
|
+
}).png().toFile(task.outputPath);
|
|
5721
5560
|
} else {
|
|
5722
5561
|
const iconBgColor = task.iconType === "ios-light" ? backgroundColor : "transparent";
|
|
5723
5562
|
await resizeIconWithSafeZone(
|
|
@@ -5751,8 +5590,6 @@ async function handleGenerateAppIcons(input) {
|
|
|
5751
5590
|
styleFolder,
|
|
5752
5591
|
backgroundColor: bgColorInput,
|
|
5753
5592
|
logoAlignment: logoAlignmentInput,
|
|
5754
|
-
useAiMasking = false,
|
|
5755
|
-
logoPosition,
|
|
5756
5593
|
skipExisting = false,
|
|
5757
5594
|
dryRun = false
|
|
5758
5595
|
} = input;
|
|
@@ -5800,14 +5637,6 @@ async function handleGenerateAppIcons(input) {
|
|
|
5800
5637
|
const iconTypes = requestedIconTypes || ALL_ICON_TYPES;
|
|
5801
5638
|
results.push(`\u{1F3AF} Icon types: ${iconTypes.join(", ")}`);
|
|
5802
5639
|
results.push(`\u{1F4D0} Logo alignment: ${logoAlignment}`);
|
|
5803
|
-
if (useAiMasking) {
|
|
5804
|
-
results.push(`\u{1F916} AI masking: enabled (uses Gemini API)`);
|
|
5805
|
-
if (logoPosition) {
|
|
5806
|
-
results.push(`\u{1F4CD} Logo positioning: ${logoPosition}`);
|
|
5807
|
-
}
|
|
5808
|
-
} else {
|
|
5809
|
-
results.push(`\u26A1 AI masking: disabled (uses Sharp threshold)`);
|
|
5810
|
-
}
|
|
5811
5640
|
let tasks;
|
|
5812
5641
|
let baseIconPath;
|
|
5813
5642
|
try {
|
|
@@ -5875,8 +5704,6 @@ async function handleGenerateAppIcons(input) {
|
|
|
5875
5704
|
tasks,
|
|
5876
5705
|
bgColor,
|
|
5877
5706
|
logoAlignment,
|
|
5878
|
-
useAiMasking,
|
|
5879
|
-
logoPosition,
|
|
5880
5707
|
(progress) => {
|
|
5881
5708
|
const progressPrefix = `[${progress.current}/${progress.total}]`;
|
|
5882
5709
|
if (progress.status === "generating") {
|
|
@@ -5908,21 +5735,6 @@ async function handleGenerateAppIcons(input) {
|
|
|
5908
5735
|
\u{1F4C1} Output location: ${iconsDir}/`);
|
|
5909
5736
|
results.push(`
|
|
5910
5737
|
\u2705 Icon generation complete!`);
|
|
5911
|
-
const hasNotificationIcon = tasks.some((t) => t.iconType === "android-notification-icon") && !generationResult.errors.some(
|
|
5912
|
-
(e) => e.iconType === "android-notification-icon"
|
|
5913
|
-
);
|
|
5914
|
-
if (hasNotificationIcon && !useAiMasking) {
|
|
5915
|
-
results.push(
|
|
5916
|
-
`
|
|
5917
|
-
\u26A0\uFE0F IMPORTANT: Check android-notification-icon.png`
|
|
5918
|
-
);
|
|
5919
|
-
results.push(
|
|
5920
|
-
` If white masking looks incorrect (artifacts, incomplete conversion, poor contrast):`
|
|
5921
|
-
);
|
|
5922
|
-
results.push(
|
|
5923
|
-
` Regenerate with AI masking: { "appName": "${appInfo.slug}", "useAiMasking": true, "iconTypes": ["android-notification-icon"]${styleFolder ? `, "styleFolder": "${styleFolder}"` : ""} }`
|
|
5924
|
-
);
|
|
5925
|
-
}
|
|
5926
5738
|
return {
|
|
5927
5739
|
content: [
|
|
5928
5740
|
{
|
|
@@ -5936,9 +5748,32 @@ async function handleGenerateAppIcons(input) {
|
|
|
5936
5748
|
// src/tools/app-icon/stylize-app-icon.ts
|
|
5937
5749
|
import { z as z11 } from "zod";
|
|
5938
5750
|
import { zodToJsonSchema as zodToJsonSchema11 } from "zod-to-json-schema";
|
|
5939
|
-
import
|
|
5940
|
-
import
|
|
5941
|
-
import
|
|
5751
|
+
import fs17 from "fs";
|
|
5752
|
+
import path17 from "path";
|
|
5753
|
+
import sharp6 from "sharp";
|
|
5754
|
+
|
|
5755
|
+
// 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 };
|
|
5774
|
+
}
|
|
5775
|
+
|
|
5776
|
+
// src/tools/app-icon/stylize-app-icon.ts
|
|
5942
5777
|
var TOOL_NAME7 = "stylize-app-icon";
|
|
5943
5778
|
var stylizeAppIconInputSchema = z11.object({
|
|
5944
5779
|
appName: z11.string().describe(
|
|
@@ -6072,12 +5907,12 @@ Generate the stylized icon with transparent background now.`;
|
|
|
6072
5907
|
for (const part of parts) {
|
|
6073
5908
|
if (part.inlineData?.data) {
|
|
6074
5909
|
const imageBuffer = Buffer.from(part.inlineData.data, "base64");
|
|
6075
|
-
const outputDir =
|
|
6076
|
-
if (!
|
|
6077
|
-
|
|
5910
|
+
const outputDir = path17.dirname(outputPath);
|
|
5911
|
+
if (!fs17.existsSync(outputDir)) {
|
|
5912
|
+
fs17.mkdirSync(outputDir, { recursive: true });
|
|
6078
5913
|
}
|
|
6079
|
-
const processedImage = await
|
|
6080
|
-
await
|
|
5914
|
+
const processedImage = await sharp6(imageBuffer).ensureAlpha().png().toBuffer();
|
|
5915
|
+
await sharp6(processedImage).toFile(outputPath);
|
|
6081
5916
|
return { success: true };
|
|
6082
5917
|
}
|
|
6083
5918
|
}
|
|
@@ -6117,7 +5952,7 @@ async function handleStylizeAppIcon(input) {
|
|
|
6117
5952
|
};
|
|
6118
5953
|
}
|
|
6119
5954
|
const baseIconPath = getBaseIconPath(appInfo.slug);
|
|
6120
|
-
if (!
|
|
5955
|
+
if (!fs17.existsSync(baseIconPath)) {
|
|
6121
5956
|
return {
|
|
6122
5957
|
content: [
|
|
6123
5958
|
{
|
|
@@ -6211,13 +6046,13 @@ Next step: Run generate-app-icons with styleFolder='${styleFolder}' to create pl
|
|
|
6211
6046
|
}
|
|
6212
6047
|
|
|
6213
6048
|
// src/tools/apps/init.ts
|
|
6214
|
-
import
|
|
6215
|
-
import
|
|
6049
|
+
import fs18 from "fs";
|
|
6050
|
+
import path18 from "path";
|
|
6216
6051
|
import { z as z12 } from "zod";
|
|
6217
6052
|
import { zodToJsonSchema as zodToJsonSchema12 } from "zod-to-json-schema";
|
|
6218
6053
|
var listSlugDirs = (dir) => {
|
|
6219
|
-
if (!
|
|
6220
|
-
return
|
|
6054
|
+
if (!fs18.existsSync(dir)) return [];
|
|
6055
|
+
return fs18.readdirSync(dir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
|
|
6221
6056
|
};
|
|
6222
6057
|
var initProjectInputSchema = z12.object({
|
|
6223
6058
|
slug: z12.string().trim().optional().describe(
|
|
@@ -6242,7 +6077,7 @@ Steps:
|
|
|
6242
6077
|
inputSchema: inputSchema12
|
|
6243
6078
|
};
|
|
6244
6079
|
async function handleInitProject(input) {
|
|
6245
|
-
const pullDataDir =
|
|
6080
|
+
const pullDataDir = path18.join(getPullDataDir(), "products");
|
|
6246
6081
|
const publicDir = getProductsDir();
|
|
6247
6082
|
const pullDataSlugs = listSlugDirs(pullDataDir);
|
|
6248
6083
|
const publicSlugs = listSlugDirs(publicDir);
|
|
@@ -6325,14 +6160,14 @@ async function handleInitProject(input) {
|
|
|
6325
6160
|
}
|
|
6326
6161
|
|
|
6327
6162
|
// src/tools/content/create-blog-html.ts
|
|
6328
|
-
import
|
|
6329
|
-
import
|
|
6163
|
+
import fs20 from "fs";
|
|
6164
|
+
import path20 from "path";
|
|
6330
6165
|
import { z as z13 } from "zod";
|
|
6331
6166
|
import { zodToJsonSchema as zodToJsonSchema13 } from "zod-to-json-schema";
|
|
6332
6167
|
|
|
6333
6168
|
// src/utils/blog.util.ts
|
|
6334
|
-
import
|
|
6335
|
-
import
|
|
6169
|
+
import fs19 from "fs";
|
|
6170
|
+
import path19 from "path";
|
|
6336
6171
|
var DATE_REGEX = /^\d{4}-\d{2}-\d{2}$/;
|
|
6337
6172
|
var BLOG_ROOT = "blogs";
|
|
6338
6173
|
var removeDiacritics = (value) => value.normalize("NFKD").replace(/[\u0300-\u036f]/g, "");
|
|
@@ -6420,13 +6255,13 @@ function resolveTargetLocales(input) {
|
|
|
6420
6255
|
return fallback ? [fallback] : [];
|
|
6421
6256
|
}
|
|
6422
6257
|
function getBlogOutputPaths(options) {
|
|
6423
|
-
const baseDir =
|
|
6258
|
+
const baseDir = path19.join(
|
|
6424
6259
|
options.publicDir,
|
|
6425
6260
|
BLOG_ROOT,
|
|
6426
6261
|
options.appSlug,
|
|
6427
6262
|
options.slug
|
|
6428
6263
|
);
|
|
6429
|
-
const filePath =
|
|
6264
|
+
const filePath = path19.join(baseDir, `${options.locale}.html`);
|
|
6430
6265
|
const publicBasePath = toPublicBlogBase(options.appSlug, options.slug);
|
|
6431
6266
|
return { baseDir, filePath, publicBasePath };
|
|
6432
6267
|
}
|
|
@@ -6451,18 +6286,18 @@ function findExistingBlogPosts({
|
|
|
6451
6286
|
publicDir,
|
|
6452
6287
|
limit = 2
|
|
6453
6288
|
}) {
|
|
6454
|
-
const blogAppDir =
|
|
6455
|
-
if (!
|
|
6289
|
+
const blogAppDir = path19.join(publicDir, BLOG_ROOT, appSlug);
|
|
6290
|
+
if (!fs19.existsSync(blogAppDir)) {
|
|
6456
6291
|
return [];
|
|
6457
6292
|
}
|
|
6458
6293
|
const posts = [];
|
|
6459
|
-
const subdirs =
|
|
6294
|
+
const subdirs = fs19.readdirSync(blogAppDir, { withFileTypes: true });
|
|
6460
6295
|
for (const subdir of subdirs) {
|
|
6461
6296
|
if (!subdir.isDirectory()) continue;
|
|
6462
|
-
const localeFile =
|
|
6463
|
-
if (!
|
|
6297
|
+
const localeFile = path19.join(blogAppDir, subdir.name, `${locale}.html`);
|
|
6298
|
+
if (!fs19.existsSync(localeFile)) continue;
|
|
6464
6299
|
try {
|
|
6465
|
-
const htmlContent =
|
|
6300
|
+
const htmlContent = fs19.readFileSync(localeFile, "utf-8");
|
|
6466
6301
|
const { meta, body } = parseBlogHtml(htmlContent);
|
|
6467
6302
|
if (meta && meta.locale === locale) {
|
|
6468
6303
|
posts.push({
|
|
@@ -6605,7 +6440,7 @@ async function handleCreateBlogHtml(input) {
|
|
|
6605
6440
|
}
|
|
6606
6441
|
const output = {
|
|
6607
6442
|
slug,
|
|
6608
|
-
baseDir:
|
|
6443
|
+
baseDir: path20.join(publicDir, "blogs", appSlug, slug),
|
|
6609
6444
|
files: [],
|
|
6610
6445
|
coverImage: coverImage && coverImage.trim().length > 0 ? coverImage.trim() : `/products/${appSlug}/og-image.png`,
|
|
6611
6446
|
metaByLocale: {}
|
|
@@ -6619,7 +6454,7 @@ async function handleCreateBlogHtml(input) {
|
|
|
6619
6454
|
})
|
|
6620
6455
|
);
|
|
6621
6456
|
const existing = plannedFiles.filter(
|
|
6622
|
-
({ filePath }) =>
|
|
6457
|
+
({ filePath }) => fs20.existsSync(filePath)
|
|
6623
6458
|
);
|
|
6624
6459
|
if (existing.length > 0 && !overwrite) {
|
|
6625
6460
|
const existingList = existing.map((f) => f.filePath).join("\n- ");
|
|
@@ -6628,7 +6463,7 @@ async function handleCreateBlogHtml(input) {
|
|
|
6628
6463
|
- ${existingList}`
|
|
6629
6464
|
);
|
|
6630
6465
|
}
|
|
6631
|
-
|
|
6466
|
+
fs20.mkdirSync(output.baseDir, { recursive: true });
|
|
6632
6467
|
for (const locale of targetLocales) {
|
|
6633
6468
|
const { filePath } = getBlogOutputPaths({
|
|
6634
6469
|
appSlug,
|
|
@@ -6654,7 +6489,7 @@ async function handleCreateBlogHtml(input) {
|
|
|
6654
6489
|
meta,
|
|
6655
6490
|
content
|
|
6656
6491
|
});
|
|
6657
|
-
|
|
6492
|
+
fs20.writeFileSync(filePath, html, "utf-8");
|
|
6658
6493
|
output.files.push({ locale, path: filePath });
|
|
6659
6494
|
}
|
|
6660
6495
|
const summaryLines = [
|