@spark-apps/piclet 1.0.3 → 1.0.4

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/cli.js CHANGED
@@ -1,4 +1,10 @@
1
1
  #!/usr/bin/env node
2
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
4
+ }) : x)(function(x) {
5
+ if (typeof require !== "undefined") return require.apply(this, arguments);
6
+ throw Error('Dynamic require of "' + x + '" is not supported');
7
+ });
2
8
 
3
9
  // src/cli.ts
4
10
  import chalk17 from "chalk";
@@ -340,6 +346,58 @@ function startGuiServer(options) {
340
346
  res.json({ success: false, error: err.message });
341
347
  }
342
348
  });
349
+ app.post("/api/simplify-gif", async (req, res) => {
350
+ if (!options.onSimplifyGif) {
351
+ res.json({ success: false, error: "GIF simplification not supported" });
352
+ return;
353
+ }
354
+ try {
355
+ const skipFactor = req.body.skipFactor ?? 2;
356
+ const result = await options.onSimplifyGif(skipFactor);
357
+ if (result.success) {
358
+ options.imageInfo.filePath = result.filePath;
359
+ options.imageInfo.fileName = result.fileName;
360
+ options.imageInfo.width = result.width;
361
+ options.imageInfo.height = result.height;
362
+ options.imageInfo.frameCount = result.frameCount;
363
+ }
364
+ res.json(result);
365
+ } catch (err) {
366
+ res.json({ success: false, error: err.message });
367
+ }
368
+ });
369
+ app.post("/api/delete-frame", async (req, res) => {
370
+ if (!options.onDeleteFrame) {
371
+ res.json({ success: false, error: "Frame deletion not supported" });
372
+ return;
373
+ }
374
+ try {
375
+ const frameIndex = req.body.frameIndex;
376
+ const result = await options.onDeleteFrame(frameIndex);
377
+ if (result.success) {
378
+ options.imageInfo.frameCount = result.frameCount;
379
+ }
380
+ res.json(result);
381
+ } catch (err) {
382
+ res.json({ success: false, error: err.message });
383
+ }
384
+ });
385
+ app.post("/api/replace-frame", async (req, res) => {
386
+ if (!options.onReplaceFrame) {
387
+ res.json({ success: false, error: "Frame replacement not supported" });
388
+ return;
389
+ }
390
+ try {
391
+ const { frameIndex, imageData } = req.body;
392
+ const result = await options.onReplaceFrame(frameIndex, imageData);
393
+ if (result.success) {
394
+ options.imageInfo.frameCount = result.frameCount;
395
+ }
396
+ res.json(result);
397
+ } catch (err) {
398
+ res.json({ success: false, error: err.message });
399
+ }
400
+ });
343
401
  app.post("/api/preview", async (req, res) => {
344
402
  if (!options.onPreview) {
345
403
  res.json({ success: false, error: "Preview not supported" });
@@ -359,6 +417,11 @@ function startGuiServer(options) {
359
417
  try {
360
418
  const result = await options.onProcess(req.body);
361
419
  processResult = result.success;
420
+ if (result.outputPath) {
421
+ options.lastOutputPath = result.outputPath;
422
+ } else {
423
+ options.lastOutputPath = void 0;
424
+ }
362
425
  res.json(result);
363
426
  } catch (err) {
364
427
  processResult = false;
@@ -436,8 +499,41 @@ function startGuiServer(options) {
436
499
  }).unref();
437
500
  res.json({ success: true });
438
501
  });
502
+ app.get("/api/output-preview", (_req, res) => {
503
+ const outputPath = options.lastOutputPath;
504
+ if (!outputPath) {
505
+ res.json({ success: false, error: "No output file" });
506
+ return;
507
+ }
508
+ try {
509
+ const fs = __require("fs");
510
+ const path = __require("path");
511
+ if (!fs.existsSync(outputPath)) {
512
+ res.json({ success: false, error: "Output file not found" });
513
+ return;
514
+ }
515
+ const buffer = fs.readFileSync(outputPath);
516
+ const ext = path.extname(outputPath).toLowerCase();
517
+ const mimeTypes = {
518
+ ".png": "image/png",
519
+ ".jpg": "image/jpeg",
520
+ ".jpeg": "image/jpeg",
521
+ ".gif": "image/gif",
522
+ ".ico": "image/x-icon"
523
+ };
524
+ const mimeType = mimeTypes[ext] || "image/png";
525
+ res.json({
526
+ success: true,
527
+ imageData: `data:${mimeType};base64,${buffer.toString("base64")}`,
528
+ isGif: ext === ".gif"
529
+ });
530
+ } catch (err) {
531
+ res.json({ success: false, error: err.message });
532
+ }
533
+ });
439
534
  app.post("/api/open-folder", (_req, res) => {
440
- const filePath = options.imageInfo.filePath;
535
+ const outputPath = options.lastOutputPath;
536
+ const filePath = outputPath || options.imageInfo.filePath;
441
537
  let winPath = filePath;
442
538
  const wslMatch = filePath.match(/^\/mnt\/([a-z])\/(.*)$/);
443
539
  if (wslMatch) {
@@ -445,8 +541,10 @@ function startGuiServer(options) {
445
541
  const rest = wslMatch[2].replace(/\//g, "\\");
446
542
  winPath = `${drive}:\\${rest}`;
447
543
  }
448
- const dir = winPath.substring(0, winPath.lastIndexOf("\\")) || winPath.substring(0, winPath.lastIndexOf("/"));
449
- spawn("powershell.exe", ["-WindowStyle", "Hidden", "-Command", `explorer.exe "${dir}"`], {
544
+ const lastSep = Math.max(winPath.lastIndexOf("\\"), winPath.lastIndexOf("/"));
545
+ const dir = lastSep > 0 ? winPath.substring(0, lastSep) : winPath;
546
+ const explorerCmd = outputPath ? `explorer.exe /select,"${winPath}"` : `explorer.exe "${dir}"`;
547
+ spawn("powershell.exe", ["-WindowStyle", "Hidden", "-Command", explorerCmd], {
450
548
  detached: true,
451
549
  stdio: "ignore",
452
550
  windowsHide: true
@@ -549,7 +647,7 @@ function isGif(imagePath) {
549
647
  return imagePath.toLowerCase().endsWith(".gif");
550
648
  }
551
649
  function getGifOutputSuffix(outputPath) {
552
- return isGif(outputPath) ? " -layers Optimize" : "";
650
+ return isGif(outputPath) ? " -dispose Background -layers OptimizePlus" : "";
553
651
  }
554
652
  function getCoalescePrefix(inputPath) {
555
653
  return isGif(inputPath) ? "-coalesce " : "";
@@ -688,6 +786,20 @@ async function removeBackgroundBorderOnly(inputPath, outputPath, color, fuzz) {
688
786
  return false;
689
787
  }
690
788
  }
789
+ async function removeBackgroundEdgeAware(inputPath, outputPath, color, fuzz, featherAmount = 50) {
790
+ if (isGif(inputPath)) {
791
+ return removeBackgroundBorderOnly(inputPath, outputPath, color, fuzz);
792
+ }
793
+ try {
794
+ const featherRadius = 0.5 + featherAmount / 100 * 2.5;
795
+ await execAsync(
796
+ `convert "${inputPath}" -bordercolor "${color}" -border 1x1 -fill none -fuzz ${fuzz}% -draw "matte 0,0 floodfill" -shave 1x1 \\( +clone -alpha extract -blur 0x${featherRadius} \\) -compose CopyOpacity -composite "${outputPath}"`
797
+ );
798
+ return true;
799
+ } catch {
800
+ return removeBackgroundBorderOnly(inputPath, outputPath, color, fuzz);
801
+ }
802
+ }
691
803
  async function createIco(inputPath, outputPath, sizes = [256, 128, 64, 48, 32, 16]) {
692
804
  try {
693
805
  const sizeStr = sizes.join(",");
@@ -736,7 +848,13 @@ async function extractFirstFrame(inputPath, outputPath, frameIndex = 0) {
736
848
  }
737
849
  try {
738
850
  if (lowerPath.endsWith(".gif")) {
739
- await execAsync(`convert "${inputPath}" -coalesce miff:- | convert "miff:-[${frameIndex}]" "${outputPath}"`);
851
+ if (frameIndex === 0) {
852
+ await execAsync(`convert "${inputPath}[0]" "${outputPath}"`);
853
+ } else {
854
+ await execAsync(
855
+ `convert "${inputPath}[0-${frameIndex}]" -coalesce -delete 0--2 "${outputPath}"`
856
+ );
857
+ }
740
858
  } else {
741
859
  await execAsync(`convert "${inputPath}[${frameIndex}]" "${outputPath}"`);
742
860
  }
@@ -764,7 +882,7 @@ async function extractAllFrames(inputPath, outputDir, baseName) {
764
882
  try {
765
883
  ensureDir(`${outputDir}/dummy`);
766
884
  await execAsync(
767
- `convert "${inputPath}" -coalesce "${outputDir}/${baseName}-%04d.png"`
885
+ `convert "${inputPath}" -coalesce +adjoin "${outputDir}/${baseName}-%04d.png"`
768
886
  );
769
887
  const { stdout } = await execAsync(`ls -1 "${outputDir}/${baseName}"-*.png 2>/dev/null || true`);
770
888
  const files = stdout.trim().split("\n").filter((f) => f.length > 0);
@@ -893,6 +1011,93 @@ async function replaceColor(inputPath, outputPath, fromColor, toColor, fuzz) {
893
1011
  return false;
894
1012
  }
895
1013
  }
1014
+ async function deleteGifFrame(inputPath, outputPath, frameIndex) {
1015
+ try {
1016
+ const originalCount = await getFrameCount(inputPath);
1017
+ if (originalCount <= 1) {
1018
+ return { success: false };
1019
+ }
1020
+ if (frameIndex < 0 || frameIndex >= originalCount) {
1021
+ return { success: false };
1022
+ }
1023
+ await execAsync(
1024
+ `convert "${inputPath}" -coalesce -delete ${frameIndex} -dispose Background -layers OptimizePlus "${outputPath}"`
1025
+ );
1026
+ const newCount = await getFrameCount(outputPath);
1027
+ return { success: true, frameCount: newCount };
1028
+ } catch {
1029
+ return { success: false };
1030
+ }
1031
+ }
1032
+ async function replaceGifFrame(inputPath, outputPath, frameIndex, replacementPath) {
1033
+ try {
1034
+ const frameCount = await getFrameCount(inputPath);
1035
+ if (frameIndex < 0 || frameIndex >= frameCount) {
1036
+ return { success: false };
1037
+ }
1038
+ const dims = await getDimensions(inputPath);
1039
+ if (!dims) return { success: false };
1040
+ const tempReplacement = `${outputPath}.tmp.png`;
1041
+ await execAsync(
1042
+ `convert "${replacementPath}" -resize ${dims[0]}x${dims[1]}^ -gravity center -extent ${dims[0]}x${dims[1]} "${tempReplacement}"`
1043
+ );
1044
+ if (frameIndex === 0) {
1045
+ await execAsync(
1046
+ `convert "${tempReplacement}" \\( "${inputPath}" -coalesce \\) -delete 1 -dispose Background -layers OptimizePlus "${outputPath}"`
1047
+ );
1048
+ } else if (frameIndex === frameCount - 1) {
1049
+ await execAsync(
1050
+ `convert \\( "${inputPath}" -coalesce -delete -1 \\) "${tempReplacement}" -dispose Background -layers OptimizePlus "${outputPath}"`
1051
+ );
1052
+ } else {
1053
+ await execAsync(
1054
+ `convert \\( "${inputPath}" -coalesce \\) -delete ${frameIndex} "${outputPath}.frames.gif" && convert "${outputPath}.frames.gif[0-${frameIndex - 1}]" "${tempReplacement}" "${outputPath}.frames.gif[${frameIndex}-]" -dispose Background -layers OptimizePlus "${outputPath}" && rm -f "${outputPath}.frames.gif"`
1055
+ );
1056
+ }
1057
+ try {
1058
+ unlinkSync(tempReplacement);
1059
+ } catch {
1060
+ }
1061
+ const newCount = await getFrameCount(outputPath);
1062
+ return { success: true, frameCount: newCount };
1063
+ } catch {
1064
+ return { success: false };
1065
+ }
1066
+ }
1067
+ async function simplifyGif(inputPath, outputPath, skipFactor) {
1068
+ if (skipFactor < 2) {
1069
+ if (inputPath !== outputPath) {
1070
+ try {
1071
+ copyFileSync(inputPath, outputPath);
1072
+ const count2 = await getFrameCount(outputPath);
1073
+ return { success: true, frameCount: count2 };
1074
+ } catch {
1075
+ return { success: false };
1076
+ }
1077
+ }
1078
+ const count = await getFrameCount(inputPath);
1079
+ return { success: true, frameCount: count };
1080
+ }
1081
+ try {
1082
+ const originalCount = await getFrameCount(inputPath);
1083
+ if (originalCount <= 1) {
1084
+ copyFileSync(inputPath, outputPath);
1085
+ return { success: true, frameCount: 1 };
1086
+ }
1087
+ const frameIndices = [];
1088
+ for (let i = 0; i < originalCount; i += skipFactor) {
1089
+ frameIndices.push(i);
1090
+ }
1091
+ const keepPattern = frameIndices.join(",");
1092
+ await execAsync(
1093
+ `convert "${inputPath}[${keepPattern}]" -coalesce -dispose Background -layers OptimizePlus "${outputPath}"`
1094
+ );
1095
+ const newCount = await getFrameCount(outputPath);
1096
+ return { success: true, frameCount: newCount };
1097
+ } catch {
1098
+ return { success: false };
1099
+ }
1100
+ }
896
1101
 
897
1102
  // src/lib/paths.ts
898
1103
  import { basename, dirname as dirname4, extname, resolve } from "path";
@@ -2161,7 +2366,9 @@ async function generateCombinedPreview(input, borderColor, opts) {
2161
2366
  const rbOpts = opts.removebg;
2162
2367
  const out = makeTempPath("removebg");
2163
2368
  let success2 = false;
2164
- if (rbOpts.preserveInner && borderColor) {
2369
+ if (rbOpts.edgeDetect && borderColor) {
2370
+ success2 = await removeBackgroundEdgeAware(current, out, borderColor, rbOpts.fuzz, rbOpts.edgeStrength);
2371
+ } else if (rbOpts.preserveInner && borderColor) {
2165
2372
  success2 = await removeBackgroundBorderOnly(current, out, borderColor, rbOpts.fuzz);
2166
2373
  }
2167
2374
  if (!success2 && borderColor) {
@@ -2245,6 +2452,7 @@ async function processCombined(input, borderColor, opts, logs) {
2245
2452
  const fileInfo = getFileInfo(input);
2246
2453
  const outputs = [];
2247
2454
  const temps = [];
2455
+ let singleFilePath;
2248
2456
  const outputExt = fileInfo.extension.toLowerCase() === ".gif" ? ".gif" : ".png";
2249
2457
  const makeTempPath = (suffix) => {
2250
2458
  const p = `${fileInfo.dirname}/${fileInfo.filename}_${suffix}${outputExt}`;
@@ -2260,7 +2468,13 @@ async function processCombined(input, borderColor, opts, logs) {
2260
2468
  const rbOpts = opts.removebg;
2261
2469
  const out = makeTempPath("nobg");
2262
2470
  let success2 = false;
2263
- if (rbOpts.preserveInner && borderColor) {
2471
+ if (rbOpts.edgeDetect && borderColor) {
2472
+ logs.push({ type: "info", message: "Using edge feathering..." });
2473
+ success2 = await removeBackgroundEdgeAware(current, out, borderColor, rbOpts.fuzz, rbOpts.edgeStrength);
2474
+ if (!success2) {
2475
+ logs.push({ type: "warn", message: "Edge feathering failed, trying standard removal" });
2476
+ }
2477
+ } else if (rbOpts.preserveInner && borderColor) {
2264
2478
  success2 = await removeBackgroundBorderOnly(current, out, borderColor, rbOpts.fuzz);
2265
2479
  if (!success2) {
2266
2480
  logs.push({ type: "warn", message: "Border-only failed, trying full removal" });
@@ -2272,7 +2486,7 @@ async function processCombined(input, borderColor, opts, logs) {
2272
2486
  if (!success2) {
2273
2487
  logs.push({ type: "error", message: "Background removal failed" });
2274
2488
  cleanup(...temps);
2275
- return [];
2489
+ return { outputs: [] };
2276
2490
  }
2277
2491
  logs.push({ type: "success", message: "Background removed" });
2278
2492
  if (rbOpts.trim) {
@@ -2294,6 +2508,7 @@ async function processCombined(input, borderColor, opts, logs) {
2294
2508
  renameSync(current, finalOut);
2295
2509
  temps.splice(temps.indexOf(current), 1);
2296
2510
  outputs.push(basename7(finalOut));
2511
+ singleFilePath = finalOut;
2297
2512
  }
2298
2513
  break;
2299
2514
  }
@@ -2311,7 +2526,7 @@ async function processCombined(input, borderColor, opts, logs) {
2311
2526
  if (!success2) {
2312
2527
  logs.push({ type: "error", message: "Scale failed" });
2313
2528
  cleanup(...temps);
2314
- return [];
2529
+ return { outputs: [] };
2315
2530
  }
2316
2531
  const dims = await getDimensions(out);
2317
2532
  logs.push({ type: "success", message: `Scaled to ${dims?.[0]}\xD7${dims?.[1]}` });
@@ -2325,6 +2540,7 @@ async function processCombined(input, borderColor, opts, logs) {
2325
2540
  renameSync(current, finalOut);
2326
2541
  temps.splice(temps.indexOf(current), 1);
2327
2542
  outputs.push(basename7(finalOut));
2543
+ singleFilePath = finalOut;
2328
2544
  }
2329
2545
  break;
2330
2546
  }
@@ -2333,7 +2549,7 @@ async function processCombined(input, borderColor, opts, logs) {
2333
2549
  const icOpts = opts.icons;
2334
2550
  if (!icOpts.ico && !icOpts.web && !icOpts.android && !icOpts.ios) {
2335
2551
  logs.push({ type: "error", message: "No output format selected" });
2336
- return [];
2552
+ return { outputs: [] };
2337
2553
  }
2338
2554
  let iconSource = current;
2339
2555
  if (icOpts.trim && current === input) {
@@ -2358,7 +2574,7 @@ async function processCombined(input, borderColor, opts, logs) {
2358
2574
  if (!await scaleToSize(iconSource, srcTemp, maxSize)) {
2359
2575
  logs.push({ type: "error", message: "Failed to prepare icon source" });
2360
2576
  cleanup(...temps);
2361
- return [];
2577
+ return { outputs: [] };
2362
2578
  }
2363
2579
  if (iconSource !== current && iconSource !== input) cleanup(iconSource);
2364
2580
  let totalCount = 0;
@@ -2443,7 +2659,7 @@ async function processCombined(input, borderColor, opts, logs) {
2443
2659
  const spOpts = opts.storepack;
2444
2660
  if (!spOpts.dimensions || spOpts.dimensions.length === 0) {
2445
2661
  logs.push({ type: "error", message: "No dimensions specified" });
2446
- return [];
2662
+ return { outputs: [] };
2447
2663
  }
2448
2664
  const folderName = spOpts.presetName || "assets";
2449
2665
  const outputDir = `${fileInfo.dirname}/${fileInfo.filename}_${folderName}`;
@@ -2472,7 +2688,7 @@ async function processCombined(input, borderColor, opts, logs) {
2472
2688
  }
2473
2689
  }
2474
2690
  cleanup(...temps);
2475
- return outputs;
2691
+ return { outputs, singleFilePath };
2476
2692
  }
2477
2693
  async function runGUI6(inputRaw) {
2478
2694
  let currentInput = normalizePath(inputRaw);
@@ -2647,11 +2863,12 @@ async function runGUI6(inputRaw) {
2647
2863
  logs: [{ type: "error", message: "ImageMagick not found. Install: sudo apt install imagemagick" }]
2648
2864
  };
2649
2865
  }
2650
- const outputs = await processCombined(currentInput, currentBorderColor, toolOpts, logs);
2651
- if (outputs.length > 0) {
2866
+ const result = await processCombined(currentInput, currentBorderColor, toolOpts, logs);
2867
+ if (result.outputs.length > 0) {
2652
2868
  return {
2653
2869
  success: true,
2654
- output: outputs.join("\n"),
2870
+ output: result.outputs.join("\n"),
2871
+ outputPath: result.singleFilePath,
2655
2872
  logs
2656
2873
  };
2657
2874
  }
@@ -2691,6 +2908,64 @@ async function runGUI6(inputRaw) {
2691
2908
  const toolOpts = opts;
2692
2909
  if (!toolOpts.tools) toolOpts.tools = [];
2693
2910
  return generateFramePreview2(frameIndex, toolOpts);
2911
+ },
2912
+ onSimplifyGif: async (skipFactor) => {
2913
+ try {
2914
+ const tempPath = join7(tmpdir5(), `piclet-simplified-${Date.now()}.gif`);
2915
+ const result = await simplifyGif(currentInput, tempPath, skipFactor);
2916
+ if (!result.success) {
2917
+ return { success: false, error: "Failed to simplify GIF" };
2918
+ }
2919
+ const newDims = await getDimensions(tempPath);
2920
+ if (!newDims) {
2921
+ cleanup(tempPath);
2922
+ return { success: false, error: "Failed to read simplified GIF dimensions" };
2923
+ }
2924
+ currentInput = tempPath;
2925
+ currentFrameCount = result.frameCount ?? 1;
2926
+ return {
2927
+ success: true,
2928
+ filePath: tempPath,
2929
+ fileName: basename7(currentInput),
2930
+ width: newDims[0],
2931
+ height: newDims[1],
2932
+ frameCount: currentFrameCount
2933
+ };
2934
+ } catch (err) {
2935
+ return { success: false, error: err.message };
2936
+ }
2937
+ },
2938
+ onDeleteFrame: async (frameIndex) => {
2939
+ try {
2940
+ const tempPath = join7(tmpdir5(), `piclet-edited-${Date.now()}.gif`);
2941
+ const result = await deleteGifFrame(currentInput, tempPath, frameIndex);
2942
+ if (!result.success) {
2943
+ return { success: false, error: "Failed to delete frame" };
2944
+ }
2945
+ currentInput = tempPath;
2946
+ currentFrameCount = result.frameCount ?? 1;
2947
+ return { success: true, frameCount: currentFrameCount };
2948
+ } catch (err) {
2949
+ return { success: false, error: err.message };
2950
+ }
2951
+ },
2952
+ onReplaceFrame: async (frameIndex, imageData) => {
2953
+ try {
2954
+ const buffer = Buffer.from(imageData, "base64");
2955
+ const tempImagePath = join7(tmpdir5(), `piclet-replace-${Date.now()}.png`);
2956
+ writeFileSync2(tempImagePath, buffer);
2957
+ const tempPath = join7(tmpdir5(), `piclet-edited-${Date.now()}.gif`);
2958
+ const result = await replaceGifFrame(currentInput, tempPath, frameIndex, tempImagePath);
2959
+ cleanup(tempImagePath);
2960
+ if (!result.success) {
2961
+ return { success: false, error: "Failed to replace frame" };
2962
+ }
2963
+ currentInput = tempPath;
2964
+ currentFrameCount = result.frameCount ?? currentFrameCount;
2965
+ return { success: true, frameCount: currentFrameCount };
2966
+ } catch (err) {
2967
+ return { success: false, error: err.message };
2968
+ }
2694
2969
  }
2695
2970
  });
2696
2971
  }
@@ -2717,9 +2992,9 @@ async function processGifExport(input, borderColor, opts, logs) {
2717
2992
  let outputFile = frameFile;
2718
2993
  if (opts.tools && opts.tools.length > 0) {
2719
2994
  const processedLogs = [];
2720
- const outputs = await processCombined(frameFile, borderColor, opts, processedLogs);
2995
+ const result = await processCombined(frameFile, borderColor, opts, processedLogs);
2721
2996
  logs.push(...processedLogs);
2722
- if (outputs.length === 0) {
2997
+ if (result.outputs.length === 0) {
2723
2998
  cleanup(frameFile);
2724
2999
  return { success: false, error: "Processing failed", logs };
2725
3000
  }
@@ -2753,9 +3028,9 @@ async function processGifExport(input, borderColor, opts, logs) {
2753
3028
  }
2754
3029
  case "gif": {
2755
3030
  logs.push({ type: "info", message: "Processing GIF..." });
2756
- const outputs = await processCombined(input, borderColor, opts, logs);
2757
- if (outputs.length > 0) {
2758
- return { success: true, output: outputs.join("\n"), logs };
3031
+ const result = await processCombined(input, borderColor, opts, logs);
3032
+ if (result.outputs.length > 0) {
3033
+ return { success: true, output: result.outputs.join("\n"), outputPath: result.singleFilePath, logs };
2759
3034
  }
2760
3035
  return { success: false, error: "Processing failed", logs };
2761
3036
  }
@@ -4458,9 +4733,22 @@ function registerInstallCommand(program2) {
4458
4733
  chalk7.yellow(`! Registered ${successCount}/${results.length} entries.`)
4459
4734
  );
4460
4735
  }
4461
- console.log(chalk7.bold("\nUsage:"));
4736
+ console.log(chalk7.bold("\nContext Menu Usage:"));
4462
4737
  console.log(" Right-click any supported image in Windows Explorer.");
4463
4738
  console.log(" Multi-select supported for batch processing.");
4739
+ console.log(chalk7.bold("\nCLI Usage:"));
4740
+ console.log(chalk7.cyan(" piclet <image>") + chalk7.dim(" Open GUI editor"));
4741
+ console.log(chalk7.cyan(" piclet makeicon <img>") + chalk7.dim(" Convert to .ico"));
4742
+ console.log(chalk7.cyan(" piclet remove-bg <img>") + chalk7.dim(" Remove background"));
4743
+ console.log(chalk7.cyan(" piclet scale <img>") + chalk7.dim(" Resize image"));
4744
+ console.log(chalk7.cyan(" piclet iconpack <img>") + chalk7.dim(" Generate icon pack"));
4745
+ console.log(chalk7.cyan(" piclet storepack <img>") + chalk7.dim(" Generate store assets"));
4746
+ console.log(chalk7.cyan(" piclet transform <img>") + chalk7.dim(" Rotate/flip image"));
4747
+ console.log(chalk7.cyan(" piclet filter <img>") + chalk7.dim(" Apply filters"));
4748
+ console.log(chalk7.cyan(" piclet border <img>") + chalk7.dim(" Add border"));
4749
+ console.log(chalk7.cyan(" piclet recolor <img>") + chalk7.dim(" Replace colors"));
4750
+ console.log(chalk7.cyan(" piclet extract-frames <gif>") + chalk7.dim(" Extract GIF frames"));
4751
+ console.log(chalk7.dim('\n Run "piclet --help" for full documentation.'));
4464
4752
  console.log();
4465
4753
  });
4466
4754
  }