@spark-apps/piclet 1.0.1 → 1.0.3

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,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import chalk12 from "chalk";
4
+ import chalk17 from "chalk";
5
5
 
6
6
  // src/cli/index.ts
7
- import chalk11 from "chalk";
7
+ import chalk16 from "chalk";
8
8
  import { Command } from "commander";
9
9
 
10
10
  // src/lib/banner.ts
@@ -36,101 +36,25 @@ ${renderLogo()}`);
36
36
  }
37
37
  }
38
38
 
39
- // src/cli/commands/config.ts
40
- import chalk from "chalk";
41
-
42
- // src/lib/config.ts
43
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
44
- import { dirname, join } from "path";
45
- import { homedir } from "os";
46
- var DEFAULT_CONFIG = {
47
- removeBg: {
48
- fuzz: 10,
49
- trim: true,
50
- preserveInner: false,
51
- makeSquare: false
52
- },
53
- rescale: {
54
- defaultScale: 50,
55
- makeSquare: false
56
- },
57
- iconpack: {
58
- platforms: ["web", "android", "ios"]
59
- }
60
- };
61
- function getConfigDir() {
62
- return join(homedir(), ".config", "piclet");
63
- }
64
- function getConfigPath() {
65
- return join(getConfigDir(), "config.json");
66
- }
67
- function loadConfig() {
68
- const configPath = getConfigPath();
69
- if (!existsSync(configPath)) {
70
- return { ...DEFAULT_CONFIG };
71
- }
72
- try {
73
- const content = readFileSync(configPath, "utf-8");
74
- const loaded = JSON.parse(content);
75
- return {
76
- removeBg: { ...DEFAULT_CONFIG.removeBg, ...loaded.removeBg },
77
- rescale: { ...DEFAULT_CONFIG.rescale, ...loaded.rescale },
78
- iconpack: { ...DEFAULT_CONFIG.iconpack, ...loaded.iconpack }
79
- };
80
- } catch {
81
- return { ...DEFAULT_CONFIG };
82
- }
83
- }
84
- function resetConfig() {
85
- const configPath = getConfigPath();
86
- const configDir = dirname(configPath);
87
- if (!existsSync(configDir)) {
88
- mkdirSync(configDir, { recursive: true });
89
- }
90
- writeFileSync(configPath, JSON.stringify(DEFAULT_CONFIG, null, 2));
91
- }
92
-
93
- // src/cli/commands/config.ts
94
- function registerConfigCommand(program2) {
95
- const configCmd = program2.command("config").description("Display current settings").action(() => {
96
- const config7 = loadConfig();
97
- console.log(chalk.white.bold("\n PicLet Configuration"));
98
- console.log(chalk.gray(` ${getConfigPath()}
99
- `));
100
- console.log(JSON.stringify(config7, null, 2));
101
- console.log();
102
- });
103
- configCmd.command("reset").description("Restore defaults").action(() => {
104
- resetConfig();
105
- console.log(chalk.green("Configuration reset to defaults."));
106
- });
107
- }
108
-
109
- // src/cli/commands/help.ts
110
- function registerHelpCommand(program2) {
111
- program2.command("help").description("Show help").action(() => {
112
- showHelp();
113
- });
114
- }
115
-
116
- // src/cli/commands/iconpack.ts
117
- import chalk3 from "chalk";
39
+ // src/cli/commands/border.ts
40
+ import chalk2 from "chalk";
118
41
 
119
- // src/tools/iconpack.ts
120
- import { existsSync as existsSync4, mkdirSync as mkdirSync4 } from "fs";
121
- import { basename as basename2, dirname as dirname6 } from "path";
42
+ // src/tools/border.ts
43
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
44
+ import { tmpdir } from "os";
45
+ import { basename as basename2, join as join3 } from "path";
122
46
 
123
47
  // src/lib/gui-server.ts
124
48
  import { spawn } from "child_process";
125
49
  import { createServer } from "http";
126
- import { dirname as dirname3, join as join3 } from "path";
50
+ import { dirname as dirname2, join as join2 } from "path";
127
51
  import { fileURLToPath } from "url";
128
52
  import express from "express";
129
53
 
130
54
  // src/lib/presets.ts
131
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
132
- import { dirname as dirname2, join as join2 } from "path";
133
- import { homedir as homedir2 } from "os";
55
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
56
+ import { dirname, join } from "path";
57
+ import { homedir } from "os";
134
58
  var BUILT_IN_PRESETS = [
135
59
  // ── Game Asset Stores ──
136
60
  {
@@ -258,24 +182,24 @@ var BUILT_IN_PRESETS = [
258
182
  }
259
183
  ];
260
184
  function getPresetsPath() {
261
- const picletDir = join2(homedir2(), ".piclet");
262
- return join2(picletDir, "presets.json");
185
+ const picletDir = join(homedir(), ".piclet");
186
+ return join(picletDir, "presets.json");
263
187
  }
264
188
  function ensurePresetsDir() {
265
189
  const presetsPath = getPresetsPath();
266
- const dir = dirname2(presetsPath);
267
- if (!existsSync2(dir)) {
268
- mkdirSync2(dir, { recursive: true });
190
+ const dir = dirname(presetsPath);
191
+ if (!existsSync(dir)) {
192
+ mkdirSync(dir, { recursive: true });
269
193
  }
270
194
  }
271
195
  function loadPresets() {
272
196
  const presetsPath = getPresetsPath();
273
197
  let userPresets = [];
274
- if (existsSync2(presetsPath)) {
198
+ if (existsSync(presetsPath)) {
275
199
  try {
276
- const data = readFileSync2(presetsPath, "utf-8");
277
- const config7 = JSON.parse(data);
278
- userPresets = config7.presets || [];
200
+ const data = readFileSync(presetsPath, "utf-8");
201
+ const config12 = JSON.parse(data);
202
+ userPresets = config12.presets || [];
279
203
  } catch {
280
204
  }
281
205
  }
@@ -290,20 +214,20 @@ function loadPresets() {
290
214
  }
291
215
  function savePresets(presets) {
292
216
  ensurePresetsDir();
293
- const config7 = {
217
+ const config12 = {
294
218
  version: 1,
295
219
  presets
296
220
  };
297
- writeFileSync2(getPresetsPath(), JSON.stringify(config7, null, 2));
221
+ writeFileSync(getPresetsPath(), JSON.stringify(config12, null, 2));
298
222
  }
299
223
  function savePreset(preset) {
300
224
  const presetsPath = getPresetsPath();
301
225
  let userPresets = [];
302
- if (existsSync2(presetsPath)) {
226
+ if (existsSync(presetsPath)) {
303
227
  try {
304
- const data = readFileSync2(presetsPath, "utf-8");
305
- const config7 = JSON.parse(data);
306
- userPresets = config7.presets || [];
228
+ const data = readFileSync(presetsPath, "utf-8");
229
+ const config12 = JSON.parse(data);
230
+ userPresets = config12.presets || [];
307
231
  } catch {
308
232
  }
309
233
  }
@@ -320,13 +244,13 @@ function deletePreset(id) {
320
244
  return { success: false, error: "Cannot delete built-in presets" };
321
245
  }
322
246
  const presetsPath = getPresetsPath();
323
- if (!existsSync2(presetsPath)) {
247
+ if (!existsSync(presetsPath)) {
324
248
  return { success: false, error: "Preset not found" };
325
249
  }
326
250
  try {
327
- const data = readFileSync2(presetsPath, "utf-8");
328
- const config7 = JSON.parse(data);
329
- const userPresets = config7.presets || [];
251
+ const data = readFileSync(presetsPath, "utf-8");
252
+ const config12 = JSON.parse(data);
253
+ const userPresets = config12.presets || [];
330
254
  const idx = userPresets.findIndex((p) => p.id === id);
331
255
  if (idx < 0) {
332
256
  return { success: false, error: "Preset not found" };
@@ -340,7 +264,7 @@ function deletePreset(id) {
340
264
  }
341
265
 
342
266
  // src/lib/gui-server.ts
343
- var __dirname2 = dirname3(fileURLToPath(import.meta.url));
267
+ var __dirname2 = dirname2(fileURLToPath(import.meta.url));
344
268
  function signalReady() {
345
269
  spawn("powershell.exe", ["-WindowStyle", "Hidden", "-Command", "New-Item -Path $env:TEMP\\piclet-ready.tmp -ItemType File -Force | Out-Null"], {
346
270
  stdio: "ignore",
@@ -358,7 +282,7 @@ function openAppWindow(url) {
358
282
  stdio: "ignore",
359
283
  windowsHide: true
360
284
  }).unref();
361
- setTimeout(signalReady, 500);
285
+ signalReady();
362
286
  }
363
287
  function startGuiServer(options) {
364
288
  return new Promise((resolve2) => {
@@ -373,12 +297,12 @@ function startGuiServer(options) {
373
297
  });
374
298
  let processResult = null;
375
299
  let server = null;
376
- const guiDir = join3(__dirname2, "gui");
377
- const iconsDir = join3(__dirname2, "icons");
300
+ const guiDir = join2(__dirname2, "gui");
301
+ const iconsDir = join2(__dirname2, "icons");
378
302
  app.use(express.static(guiDir));
379
303
  app.use("/icons", express.static(iconsDir));
380
304
  app.get("/favicon.ico", (_req, res) => {
381
- res.sendFile(join3(iconsDir, "banana.ico"));
305
+ res.sendFile(join2(iconsDir, "banana.ico"));
382
306
  });
383
307
  app.get("/api/info", (_req, res) => {
384
308
  res.json({
@@ -386,9 +310,36 @@ function startGuiServer(options) {
386
310
  width: options.imageInfo.width,
387
311
  height: options.imageInfo.height,
388
312
  borderColor: options.imageInfo.borderColor,
313
+ frameCount: options.imageInfo.frameCount || 1,
389
314
  defaults: options.defaults
390
315
  });
391
316
  });
317
+ app.post("/api/frame-thumbnail", async (req, res) => {
318
+ if (!options.onFrameThumbnail) {
319
+ res.json({ success: false, error: "Frame thumbnails not supported" });
320
+ return;
321
+ }
322
+ try {
323
+ const frameIndex = req.body.frameIndex ?? 0;
324
+ const result = await options.onFrameThumbnail(frameIndex);
325
+ res.json(result);
326
+ } catch (err) {
327
+ res.json({ success: false, error: err.message });
328
+ }
329
+ });
330
+ app.post("/api/frame-preview", async (req, res) => {
331
+ if (!options.onFramePreview) {
332
+ res.json({ success: false, error: "Frame preview not supported" });
333
+ return;
334
+ }
335
+ try {
336
+ const { frameIndex, ...opts } = req.body;
337
+ const result = await options.onFramePreview(frameIndex ?? 0, opts);
338
+ res.json(result);
339
+ } catch (err) {
340
+ res.json({ success: false, error: err.message });
341
+ }
342
+ });
392
343
  app.post("/api/preview", async (req, res) => {
393
344
  if (!options.onPreview) {
394
345
  res.json({ success: false, error: "Preview not supported" });
@@ -485,6 +436,23 @@ function startGuiServer(options) {
485
436
  }).unref();
486
437
  res.json({ success: true });
487
438
  });
439
+ app.post("/api/open-folder", (_req, res) => {
440
+ const filePath = options.imageInfo.filePath;
441
+ let winPath = filePath;
442
+ const wslMatch = filePath.match(/^\/mnt\/([a-z])\/(.*)$/);
443
+ if (wslMatch) {
444
+ const drive = wslMatch[1].toUpperCase();
445
+ const rest = wslMatch[2].replace(/\//g, "\\");
446
+ winPath = `${drive}:\\${rest}`;
447
+ }
448
+ const dir = winPath.substring(0, winPath.lastIndexOf("\\")) || winPath.substring(0, winPath.lastIndexOf("/"));
449
+ spawn("powershell.exe", ["-WindowStyle", "Hidden", "-Command", `explorer.exe "${dir}"`], {
450
+ detached: true,
451
+ stdio: "ignore",
452
+ windowsHide: true
453
+ }).unref();
454
+ res.json({ success: true });
455
+ });
488
456
  function shutdown() {
489
457
  setTimeout(() => {
490
458
  server?.close();
@@ -566,17 +534,26 @@ function clearLine() {
566
534
 
567
535
  // src/lib/magick.ts
568
536
  import { exec } from "child_process";
569
- import { copyFileSync, existsSync as existsSync3, mkdirSync as mkdirSync3, unlinkSync } from "fs";
570
- import { dirname as dirname4 } from "path";
537
+ import { copyFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, unlinkSync } from "fs";
538
+ import { dirname as dirname3 } from "path";
571
539
  import { promisify } from "util";
572
540
  var execAsync = promisify(exec);
573
- function getInputSelector(imagePath) {
541
+ function getPreviewInputSelector(imagePath) {
574
542
  const lowerPath = imagePath.toLowerCase();
575
- if (lowerPath.endsWith(".ico")) {
543
+ if (lowerPath.endsWith(".ico") || lowerPath.endsWith(".gif")) {
576
544
  return `"${imagePath}[0]"`;
577
545
  }
578
546
  return `"${imagePath}"`;
579
547
  }
548
+ function isGif(imagePath) {
549
+ return imagePath.toLowerCase().endsWith(".gif");
550
+ }
551
+ function getGifOutputSuffix(outputPath) {
552
+ return isGif(outputPath) ? " -layers Optimize" : "";
553
+ }
554
+ function getCoalescePrefix(inputPath) {
555
+ return isGif(inputPath) ? "-coalesce " : "";
556
+ }
580
557
  async function checkImageMagick() {
581
558
  try {
582
559
  await execAsync("command -v convert");
@@ -587,7 +564,7 @@ async function checkImageMagick() {
587
564
  }
588
565
  async function getDimensions(imagePath) {
589
566
  try {
590
- const input = getInputSelector(imagePath);
567
+ const input = getPreviewInputSelector(imagePath);
591
568
  const { stdout } = await execAsync(
592
569
  `convert ${input} -ping -format "%w %h" info:`
593
570
  );
@@ -600,7 +577,7 @@ async function getDimensions(imagePath) {
600
577
  }
601
578
  async function getBorderColor(imagePath) {
602
579
  try {
603
- const input = getInputSelector(imagePath);
580
+ const input = getPreviewInputSelector(imagePath);
604
581
  const { stdout } = await execAsync(
605
582
  `convert ${input} -format "%[pixel:u.p{0,0}]" info:`
606
583
  );
@@ -611,14 +588,15 @@ async function getBorderColor(imagePath) {
611
588
  }
612
589
  async function trim(inputPath, outputPath) {
613
590
  try {
614
- const input = getInputSelector(inputPath);
615
- await execAsync(`convert ${input} -trim +repage "${outputPath}"`);
591
+ const coalesce = getCoalescePrefix(inputPath);
592
+ const gifSuffix = getGifOutputSuffix(outputPath);
593
+ await execAsync(`convert "${inputPath}" ${coalesce}-trim +repage${gifSuffix} "${outputPath}"`);
616
594
  return true;
617
595
  } catch {
618
596
  return false;
619
597
  }
620
598
  }
621
- async function squarify2(inputPath, outputPath) {
599
+ async function squarify(inputPath, outputPath) {
622
600
  const dims = await getDimensions(inputPath);
623
601
  if (!dims) return false;
624
602
  const [width, height] = dims;
@@ -629,10 +607,11 @@ async function squarify2(inputPath, outputPath) {
629
607
  return true;
630
608
  }
631
609
  const size = Math.max(width, height);
632
- const input = getInputSelector(inputPath);
610
+ const coalesce = getCoalescePrefix(inputPath);
611
+ const gifSuffix = getGifOutputSuffix(outputPath);
633
612
  try {
634
613
  await execAsync(
635
- `convert ${input} -background none -gravity center -extent ${size}x${size} "${outputPath}"`
614
+ `convert "${inputPath}" ${coalesce}-background none -gravity center -extent ${size}x${size}${gifSuffix} "${outputPath}"`
636
615
  );
637
616
  return true;
638
617
  } catch {
@@ -651,8 +630,10 @@ async function scaleToSize(inputPath, outputPath, size) {
651
630
  }
652
631
  async function scaleWithPadding(inputPath, outputPath, width, height) {
653
632
  try {
633
+ const coalesce = getCoalescePrefix(inputPath);
634
+ const gifSuffix = getGifOutputSuffix(outputPath);
654
635
  await execAsync(
655
- `convert "${inputPath}" -resize ${width}x${height} -background none -gravity center -extent ${width}x${height} "${outputPath}"`
636
+ `convert "${inputPath}" ${coalesce}-resize ${width}x${height} -background none -gravity center -extent ${width}x${height}${gifSuffix} "${outputPath}"`
656
637
  );
657
638
  return true;
658
639
  } catch {
@@ -661,8 +642,10 @@ async function scaleWithPadding(inputPath, outputPath, width, height) {
661
642
  }
662
643
  async function resize(inputPath, outputPath, width, height) {
663
644
  try {
645
+ const coalesce = getCoalescePrefix(inputPath);
646
+ const gifSuffix = getGifOutputSuffix(outputPath);
664
647
  await execAsync(
665
- `convert "${inputPath}" -resize ${width}x${height}! "${outputPath}"`
648
+ `convert "${inputPath}" ${coalesce}-resize ${width}x${height}!${gifSuffix} "${outputPath}"`
666
649
  );
667
650
  return true;
668
651
  } catch {
@@ -671,8 +654,10 @@ async function resize(inputPath, outputPath, width, height) {
671
654
  }
672
655
  async function scaleFillCrop(inputPath, outputPath, width, height) {
673
656
  try {
657
+ const coalesce = getCoalescePrefix(inputPath);
658
+ const gifSuffix = getGifOutputSuffix(outputPath);
674
659
  await execAsync(
675
- `convert "${inputPath}" -resize ${width}x${height}^ -background none -gravity center -extent ${width}x${height} "${outputPath}"`
660
+ `convert "${inputPath}" ${coalesce}-resize ${width}x${height}^ -background none -gravity center -extent ${width}x${height}${gifSuffix} "${outputPath}"`
676
661
  );
677
662
  return true;
678
663
  } catch {
@@ -681,9 +666,10 @@ async function scaleFillCrop(inputPath, outputPath, width, height) {
681
666
  }
682
667
  async function removeBackground(inputPath, outputPath, color, fuzz) {
683
668
  try {
684
- const input = getInputSelector(inputPath);
669
+ const coalesce = getCoalescePrefix(inputPath);
670
+ const gifSuffix = getGifOutputSuffix(outputPath);
685
671
  await execAsync(
686
- `convert ${input} -fuzz ${fuzz}% -transparent "${color}" "${outputPath}"`
672
+ `convert "${inputPath}" ${coalesce}-fuzz ${fuzz}% -transparent "${color}"${gifSuffix} "${outputPath}"`
687
673
  );
688
674
  return true;
689
675
  } catch {
@@ -692,9 +678,10 @@ async function removeBackground(inputPath, outputPath, color, fuzz) {
692
678
  }
693
679
  async function removeBackgroundBorderOnly(inputPath, outputPath, color, fuzz) {
694
680
  try {
695
- const input = getInputSelector(inputPath);
681
+ const coalesce = getCoalescePrefix(inputPath);
682
+ const gifSuffix = getGifOutputSuffix(outputPath);
696
683
  await execAsync(
697
- `convert ${input} -bordercolor "${color}" -border 1x1 -fill none -fuzz ${fuzz}% -draw "matte 0,0 floodfill" -shave 1x1 "${outputPath}"`
684
+ `convert "${inputPath}" ${coalesce}-bordercolor "${color}" -border 1x1 -fill none -fuzz ${fuzz}% -draw "matte 0,0 floodfill" -shave 1x1${gifSuffix} "${outputPath}"`
698
685
  );
699
686
  return true;
700
687
  } catch {
@@ -721,19 +708,194 @@ async function createIcoFromMultiple(pngPaths, outputPath) {
721
708
  return false;
722
709
  }
723
710
  }
711
+ function ensureDir(filePath) {
712
+ const dir = dirname3(filePath);
713
+ if (!existsSync2(dir)) {
714
+ mkdirSync2(dir, { recursive: true });
715
+ }
716
+ }
724
717
  function cleanup(...files) {
725
718
  for (const file of files) {
726
719
  try {
727
- if (existsSync3(file)) {
720
+ if (existsSync2(file)) {
728
721
  unlinkSync(file);
729
722
  }
730
723
  } catch {
731
724
  }
732
725
  }
733
726
  }
727
+ async function extractFirstFrame(inputPath, outputPath, frameIndex = 0) {
728
+ const lowerPath = inputPath.toLowerCase();
729
+ if (!lowerPath.endsWith(".gif") && !lowerPath.endsWith(".ico")) {
730
+ try {
731
+ copyFileSync(inputPath, outputPath);
732
+ return true;
733
+ } catch {
734
+ return false;
735
+ }
736
+ }
737
+ try {
738
+ if (lowerPath.endsWith(".gif")) {
739
+ await execAsync(`convert "${inputPath}" -coalesce miff:- | convert "miff:-[${frameIndex}]" "${outputPath}"`);
740
+ } else {
741
+ await execAsync(`convert "${inputPath}[${frameIndex}]" "${outputPath}"`);
742
+ }
743
+ return true;
744
+ } catch {
745
+ return false;
746
+ }
747
+ }
748
+ function isMultiFrame(imagePath) {
749
+ const lowerPath = imagePath.toLowerCase();
750
+ return lowerPath.endsWith(".gif") || lowerPath.endsWith(".ico");
751
+ }
752
+ async function getFrameCount(imagePath) {
753
+ try {
754
+ const { stdout } = await execAsync(
755
+ `identify -format "%n\\n" "${imagePath}" | head -1`
756
+ );
757
+ const count = parseInt(stdout.trim(), 10);
758
+ return Number.isNaN(count) ? 1 : count;
759
+ } catch {
760
+ return 1;
761
+ }
762
+ }
763
+ async function extractAllFrames(inputPath, outputDir, baseName) {
764
+ try {
765
+ ensureDir(`${outputDir}/dummy`);
766
+ await execAsync(
767
+ `convert "${inputPath}" -coalesce "${outputDir}/${baseName}-%04d.png"`
768
+ );
769
+ const { stdout } = await execAsync(`ls -1 "${outputDir}/${baseName}"-*.png 2>/dev/null || true`);
770
+ const files = stdout.trim().split("\n").filter((f) => f.length > 0);
771
+ return files;
772
+ } catch {
773
+ return [];
774
+ }
775
+ }
776
+ async function flipHorizontal(inputPath, outputPath) {
777
+ try {
778
+ const coalesce = getCoalescePrefix(inputPath);
779
+ const gifSuffix = getGifOutputSuffix(outputPath);
780
+ await execAsync(
781
+ `convert "${inputPath}" ${coalesce}-flop${gifSuffix} "${outputPath}"`
782
+ );
783
+ return true;
784
+ } catch {
785
+ return false;
786
+ }
787
+ }
788
+ async function flipVertical(inputPath, outputPath) {
789
+ try {
790
+ const coalesce = getCoalescePrefix(inputPath);
791
+ const gifSuffix = getGifOutputSuffix(outputPath);
792
+ await execAsync(
793
+ `convert "${inputPath}" ${coalesce}-flip${gifSuffix} "${outputPath}"`
794
+ );
795
+ return true;
796
+ } catch {
797
+ return false;
798
+ }
799
+ }
800
+ async function rotate(inputPath, outputPath, degrees) {
801
+ try {
802
+ const coalesce = getCoalescePrefix(inputPath);
803
+ const gifSuffix = getGifOutputSuffix(outputPath);
804
+ await execAsync(
805
+ `convert "${inputPath}" ${coalesce}-rotate ${degrees} -background none${gifSuffix} "${outputPath}"`
806
+ );
807
+ return true;
808
+ } catch {
809
+ return false;
810
+ }
811
+ }
812
+ async function filterGrayscale(inputPath, outputPath) {
813
+ try {
814
+ const coalesce = getCoalescePrefix(inputPath);
815
+ const gifSuffix = getGifOutputSuffix(outputPath);
816
+ await execAsync(
817
+ `convert "${inputPath}" ${coalesce}-colorspace Gray${gifSuffix} "${outputPath}"`
818
+ );
819
+ return true;
820
+ } catch {
821
+ return false;
822
+ }
823
+ }
824
+ async function filterSepia(inputPath, outputPath, intensity = 80) {
825
+ try {
826
+ const coalesce = getCoalescePrefix(inputPath);
827
+ const gifSuffix = getGifOutputSuffix(outputPath);
828
+ await execAsync(
829
+ `convert "${inputPath}" ${coalesce}-sepia-tone ${intensity}%${gifSuffix} "${outputPath}"`
830
+ );
831
+ return true;
832
+ } catch {
833
+ return false;
834
+ }
835
+ }
836
+ async function filterInvert(inputPath, outputPath) {
837
+ try {
838
+ const coalesce = getCoalescePrefix(inputPath);
839
+ const gifSuffix = getGifOutputSuffix(outputPath);
840
+ await execAsync(
841
+ `convert "${inputPath}" ${coalesce}-negate${gifSuffix} "${outputPath}"`
842
+ );
843
+ return true;
844
+ } catch {
845
+ return false;
846
+ }
847
+ }
848
+ async function filterVintage(inputPath, outputPath) {
849
+ try {
850
+ const coalesce = getCoalescePrefix(inputPath);
851
+ const gifSuffix = getGifOutputSuffix(outputPath);
852
+ await execAsync(
853
+ `convert "${inputPath}" ${coalesce}-modulate 100,70,100 -fill "#704214" -colorize 15%${gifSuffix} "${outputPath}"`
854
+ );
855
+ return true;
856
+ } catch {
857
+ return false;
858
+ }
859
+ }
860
+ async function filterVivid(inputPath, outputPath) {
861
+ try {
862
+ const coalesce = getCoalescePrefix(inputPath);
863
+ const gifSuffix = getGifOutputSuffix(outputPath);
864
+ await execAsync(
865
+ `convert "${inputPath}" ${coalesce}-modulate 100,130,100${gifSuffix} "${outputPath}"`
866
+ );
867
+ return true;
868
+ } catch {
869
+ return false;
870
+ }
871
+ }
872
+ async function addBorder(inputPath, outputPath, width, color) {
873
+ try {
874
+ const coalesce = getCoalescePrefix(inputPath);
875
+ const gifSuffix = getGifOutputSuffix(outputPath);
876
+ await execAsync(
877
+ `convert "${inputPath}" ${coalesce}-bordercolor "${color}" -border ${width}${gifSuffix} "${outputPath}"`
878
+ );
879
+ return true;
880
+ } catch {
881
+ return false;
882
+ }
883
+ }
884
+ async function replaceColor(inputPath, outputPath, fromColor, toColor, fuzz) {
885
+ try {
886
+ const coalesce = getCoalescePrefix(inputPath);
887
+ const gifSuffix = getGifOutputSuffix(outputPath);
888
+ await execAsync(
889
+ `convert "${inputPath}" ${coalesce}-fuzz ${fuzz}% -fill "${toColor}" -opaque "${fromColor}"${gifSuffix} "${outputPath}"`
890
+ );
891
+ return true;
892
+ } catch {
893
+ return false;
894
+ }
895
+ }
734
896
 
735
897
  // src/lib/paths.ts
736
- import { basename, dirname as dirname5, extname, resolve } from "path";
898
+ import { basename, dirname as dirname4, extname, resolve } from "path";
737
899
  function windowsToWsl(winPath) {
738
900
  if (winPath.startsWith("/mnt/")) {
739
901
  return winPath;
@@ -762,7 +924,7 @@ function normalizePath(inputPath) {
762
924
  return resolve(inputPath);
763
925
  }
764
926
  function getFileInfo(filePath) {
765
- const dir = dirname5(filePath);
927
+ const dir = dirname4(filePath);
766
928
  const base = basename(filePath);
767
929
  const ext = extname(filePath);
768
930
  const name = base.slice(0, -ext.length);
@@ -798,6 +960,16 @@ function getOverride(message) {
798
960
  }
799
961
  return void 0;
800
962
  }
963
+ async function text(message, defaultValue) {
964
+ if (useDefaults) return defaultValue ?? "";
965
+ const response = await prompts({
966
+ type: "text",
967
+ name: "value",
968
+ message,
969
+ initial: defaultValue
970
+ });
971
+ return response.value ?? defaultValue ?? "";
972
+ }
801
973
  async function number(message, defaultValue, min, max) {
802
974
  const override = getOverride(message);
803
975
  if (override !== void 0) return Number(override);
@@ -865,38 +1037,557 @@ async function pauseOnError(message = "Press Enter to close...") {
865
1037
  });
866
1038
  }
867
1039
 
868
- // src/tools/iconpack.ts
869
- var WEB_ICONS = [
870
- { filename: "favicon-16x16.png", size: 16 },
871
- { filename: "favicon-32x32.png", size: 32 },
872
- { filename: "favicon-48x48.png", size: 48 },
873
- { filename: "apple-touch-icon.png", size: 180 },
874
- { filename: "android-chrome-192x192.png", size: 192 },
875
- { filename: "android-chrome-512x512.png", size: 512 },
876
- { filename: "mstile-150x150.png", size: 150 }
877
- ];
878
- var ANDROID_ICONS = [
879
- { filename: "mipmap-mdpi/ic_launcher.png", size: 48 },
880
- { filename: "mipmap-hdpi/ic_launcher.png", size: 72 },
881
- { filename: "mipmap-xhdpi/ic_launcher.png", size: 96 },
882
- { filename: "mipmap-xxhdpi/ic_launcher.png", size: 144 },
883
- { filename: "mipmap-xxxhdpi/ic_launcher.png", size: 192 },
884
- { filename: "playstore-icon.png", size: 512 }
885
- ];
886
- var IOS_ICONS = [
887
- // 20pt - Notifications
888
- { filename: "AppIcon-20.png", size: 20 },
889
- { filename: "AppIcon-20@2x.png", size: 40 },
890
- { filename: "AppIcon-20@3x.png", size: 60 },
891
- // 29pt - Settings
892
- { filename: "AppIcon-29.png", size: 29 },
893
- { filename: "AppIcon-29@2x.png", size: 58 },
894
- { filename: "AppIcon-29@3x.png", size: 87 },
895
- // 40pt - Spotlight
896
- { filename: "AppIcon-40.png", size: 40 },
897
- { filename: "AppIcon-40@2x.png", size: 80 },
898
- { filename: "AppIcon-40@3x.png", size: 120 },
899
- // 60pt - iPhone App
1040
+ // src/tools/border.ts
1041
+ async function processImage(input, options) {
1042
+ const fileInfo = getFileInfo(input);
1043
+ const outputExt = fileInfo.extension.toLowerCase() === ".gif" ? ".gif" : ".png";
1044
+ const output = `${fileInfo.dirname}/${fileInfo.filename}_border${outputExt}`;
1045
+ wip(`Adding ${options.width}px ${options.color} border...`);
1046
+ const success2 = await addBorder(input, output, options.width, options.color);
1047
+ if (!success2) {
1048
+ wipDone(false, "Border failed");
1049
+ return false;
1050
+ }
1051
+ wipDone(true, "Border added");
1052
+ const finalDims = await getDimensions(output);
1053
+ console.log("");
1054
+ if (finalDims) {
1055
+ console.log(`Output: ${output} (${finalDims[0]}x${finalDims[1]})`);
1056
+ } else {
1057
+ console.log(`Output: ${output}`);
1058
+ }
1059
+ return true;
1060
+ }
1061
+ async function collectOptionsCLI() {
1062
+ console.log("");
1063
+ console.log(`${BOLD}Border Settings:${RESET}`);
1064
+ console.log(` ${DIM}Width: How thick the border should be (in pixels)${RESET}`);
1065
+ console.log(` ${DIM}Color: Hex color (#fff), named color (white), or rgb(...)${RESET}`);
1066
+ console.log("");
1067
+ let width = await number("Border width (px)", 10, 1, 200);
1068
+ if (width < 1) {
1069
+ width = 10;
1070
+ }
1071
+ console.log("");
1072
+ let color = await text("Border color", "#ffffff");
1073
+ if (!color) {
1074
+ color = "#ffffff";
1075
+ }
1076
+ return { width, color };
1077
+ }
1078
+ async function run(inputRaw) {
1079
+ if (!await checkImageMagick()) {
1080
+ error("ImageMagick not found. Please install it:");
1081
+ console.log(" sudo apt update && sudo apt install imagemagick");
1082
+ await pauseOnError();
1083
+ return false;
1084
+ }
1085
+ const input = normalizePath(inputRaw);
1086
+ if (!existsSync3(input)) {
1087
+ error(`File not found: ${input}`);
1088
+ await pauseOnError();
1089
+ return false;
1090
+ }
1091
+ header("PicLet Border");
1092
+ wip("Analyzing image...");
1093
+ const dims = await getDimensions(input);
1094
+ if (!dims) {
1095
+ wipDone(false, "Failed to read image");
1096
+ return false;
1097
+ }
1098
+ wipDone(true, `Size: ${dims[0]}x${dims[1]}`);
1099
+ const options = await collectOptionsCLI();
1100
+ console.log("");
1101
+ return processImage(input, options);
1102
+ }
1103
+ async function runGUI(inputRaw) {
1104
+ const input = normalizePath(inputRaw);
1105
+ if (!existsSync3(input)) {
1106
+ error(`File not found: ${input}`);
1107
+ return false;
1108
+ }
1109
+ const dims = await getDimensions(input);
1110
+ if (!dims) {
1111
+ error("Failed to read image dimensions");
1112
+ return false;
1113
+ }
1114
+ return startGuiServer({
1115
+ htmlFile: "border.html",
1116
+ title: "PicLet - Border",
1117
+ imageInfo: {
1118
+ filePath: input,
1119
+ fileName: basename2(input),
1120
+ width: dims[0],
1121
+ height: dims[1],
1122
+ borderColor: null
1123
+ },
1124
+ defaults: {
1125
+ width: 10,
1126
+ color: "#ffffff"
1127
+ },
1128
+ onPreview: async (opts) => {
1129
+ const width = opts.width ?? 10;
1130
+ const color = opts.color ?? "#ffffff";
1131
+ return generatePreview(input, { width, color });
1132
+ },
1133
+ onProcess: async (opts) => {
1134
+ const logs = [];
1135
+ if (!await checkImageMagick()) {
1136
+ return {
1137
+ success: false,
1138
+ error: "ImageMagick not found",
1139
+ logs: [{ type: "error", message: "ImageMagick not found. Install with: sudo apt install imagemagick" }]
1140
+ };
1141
+ }
1142
+ logs.push({ type: "info", message: `Processing ${basename2(input)}...` });
1143
+ const width = opts.width ?? 10;
1144
+ const color = opts.color ?? "#ffffff";
1145
+ const options = { width, color };
1146
+ const fileInfo = getFileInfo(input);
1147
+ const outputExt = fileInfo.extension.toLowerCase() === ".gif" ? ".gif" : ".png";
1148
+ const output = `${fileInfo.dirname}/${fileInfo.filename}_border${outputExt}`;
1149
+ logs.push({ type: "info", message: `Adding ${width}px border...` });
1150
+ const success2 = await addBorder(input, output, width, color);
1151
+ if (success2) {
1152
+ logs.push({ type: "success", message: "Border added" });
1153
+ const finalDims = await getDimensions(output);
1154
+ const sizeStr = finalDims ? ` (${finalDims[0]}x${finalDims[1]})` : "";
1155
+ return {
1156
+ success: true,
1157
+ output: `${basename2(output)}${sizeStr}`,
1158
+ logs
1159
+ };
1160
+ }
1161
+ logs.push({ type: "error", message: "Border failed" });
1162
+ return {
1163
+ success: false,
1164
+ error: "Processing failed",
1165
+ logs
1166
+ };
1167
+ }
1168
+ });
1169
+ }
1170
+ async function generatePreview(input, options) {
1171
+ const tempDir = tmpdir();
1172
+ const timestamp = Date.now();
1173
+ const tempSource = join3(tempDir, `piclet-preview-${timestamp}-src.png`);
1174
+ const tempOutput = join3(tempDir, `piclet-preview-${timestamp}.png`);
1175
+ try {
1176
+ let previewInput = input;
1177
+ if (isMultiFrame(input)) {
1178
+ if (!await extractFirstFrame(input, tempSource)) {
1179
+ return { success: false, error: "Failed to extract frame" };
1180
+ }
1181
+ previewInput = tempSource;
1182
+ }
1183
+ const success2 = await addBorder(previewInput, tempOutput, options.width, options.color);
1184
+ if (!success2) {
1185
+ cleanup(tempSource, tempOutput);
1186
+ return { success: false, error: "Border failed" };
1187
+ }
1188
+ const buffer = readFileSync2(tempOutput);
1189
+ const base64 = buffer.toString("base64");
1190
+ const imageData = `data:image/png;base64,${base64}`;
1191
+ const dims = await getDimensions(tempOutput);
1192
+ cleanup(tempSource, tempOutput);
1193
+ return {
1194
+ success: true,
1195
+ imageData,
1196
+ width: dims?.[0],
1197
+ height: dims?.[1]
1198
+ };
1199
+ } catch (err) {
1200
+ cleanup(tempSource, tempOutput);
1201
+ return { success: false, error: err.message };
1202
+ }
1203
+ }
1204
+ var config = {
1205
+ id: "border",
1206
+ name: "Add Border",
1207
+ icon: "border.ico",
1208
+ extensions: [".png", ".jpg", ".jpeg", ".gif", ".bmp"]
1209
+ };
1210
+
1211
+ // src/cli/utils.ts
1212
+ import { extname as extname3 } from "path";
1213
+ import { dirname as dirname9 } from "path";
1214
+ import { fileURLToPath as fileURLToPath2 } from "url";
1215
+ import chalk from "chalk";
1216
+
1217
+ // src/tools/extract-frames.ts
1218
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3 } from "fs";
1219
+ import { tmpdir as tmpdir2 } from "os";
1220
+ import { basename as basename3, join as join4 } from "path";
1221
+ async function run2(inputRaw) {
1222
+ if (!await checkImageMagick()) {
1223
+ error("ImageMagick not found. Please install it:");
1224
+ console.log(" sudo apt update && sudo apt install imagemagick");
1225
+ await pauseOnError();
1226
+ return false;
1227
+ }
1228
+ const input = normalizePath(inputRaw);
1229
+ if (!existsSync4(input)) {
1230
+ error(`File not found: ${input}`);
1231
+ await pauseOnError();
1232
+ return false;
1233
+ }
1234
+ header("PicLet Extract Frames");
1235
+ wip("Analyzing GIF...");
1236
+ const dims = await getDimensions(input);
1237
+ if (!dims) {
1238
+ wipDone(false, "Failed to read image");
1239
+ return false;
1240
+ }
1241
+ const frameCount = await getFrameCount(input);
1242
+ wipDone(true, `Size: ${dims[0]}x${dims[1]}, ${frameCount} frames`);
1243
+ if (frameCount <= 1) {
1244
+ info("Image has only 1 frame, nothing to extract");
1245
+ return true;
1246
+ }
1247
+ console.log("");
1248
+ console.log(`${BOLD}Extract Frames:${RESET}`);
1249
+ console.log(` ${DIM}This will create ${frameCount} PNG files${RESET}`);
1250
+ console.log("");
1251
+ const proceed = await confirm(`Extract ${frameCount} frames?`, true);
1252
+ if (!proceed) {
1253
+ info("Cancelled");
1254
+ return false;
1255
+ }
1256
+ const fileInfo = getFileInfo(input);
1257
+ const outputDir = `${fileInfo.dirname}/${fileInfo.filename}_frames`;
1258
+ console.log("");
1259
+ wip("Extracting frames...");
1260
+ mkdirSync3(outputDir, { recursive: true });
1261
+ const frames = await extractAllFrames(input, outputDir, "frame");
1262
+ if (frames.length === 0) {
1263
+ wipDone(false, "Extraction failed");
1264
+ return false;
1265
+ }
1266
+ wipDone(true, `Extracted ${frames.length} frames`);
1267
+ console.log("");
1268
+ success(`Output: ${outputDir}/`);
1269
+ return true;
1270
+ }
1271
+ async function runGUI2(inputRaw) {
1272
+ const input = normalizePath(inputRaw);
1273
+ if (!existsSync4(input)) {
1274
+ error(`File not found: ${input}`);
1275
+ return false;
1276
+ }
1277
+ const dims = await getDimensions(input);
1278
+ if (!dims) {
1279
+ error("Failed to read image dimensions");
1280
+ return false;
1281
+ }
1282
+ const frameCount = await getFrameCount(input);
1283
+ const fileInfo = getFileInfo(input);
1284
+ return startGuiServer({
1285
+ htmlFile: "extract-frames.html",
1286
+ title: "PicLet - Extract Frames",
1287
+ imageInfo: {
1288
+ filePath: input,
1289
+ fileName: basename3(input),
1290
+ width: dims[0],
1291
+ height: dims[1],
1292
+ borderColor: null
1293
+ },
1294
+ defaults: {
1295
+ frameCount
1296
+ },
1297
+ onPreview: async (opts) => {
1298
+ const frameIndex = opts.frameIndex ?? 0;
1299
+ return generateFramePreview(input, frameIndex);
1300
+ },
1301
+ onProcess: async () => {
1302
+ const logs = [];
1303
+ if (!await checkImageMagick()) {
1304
+ return {
1305
+ success: false,
1306
+ error: "ImageMagick not found",
1307
+ logs: [{ type: "error", message: "ImageMagick not found. Install with: sudo apt install imagemagick" }]
1308
+ };
1309
+ }
1310
+ logs.push({ type: "info", message: `Extracting ${frameCount} frames...` });
1311
+ const outputDir = `${fileInfo.dirname}/${fileInfo.filename}_frames`;
1312
+ mkdirSync3(outputDir, { recursive: true });
1313
+ const frames = await extractAllFrames(input, outputDir, "frame");
1314
+ if (frames.length > 0) {
1315
+ logs.push({ type: "success", message: `Extracted ${frames.length} frames` });
1316
+ return {
1317
+ success: true,
1318
+ output: `${frames.length} frames -> ${fileInfo.filename}_frames/`,
1319
+ logs
1320
+ };
1321
+ }
1322
+ logs.push({ type: "error", message: "Extraction failed" });
1323
+ return { success: false, error: "Extraction failed", logs };
1324
+ }
1325
+ });
1326
+ }
1327
+ async function generateFramePreview(input, frameIndex) {
1328
+ const tempDir = tmpdir2();
1329
+ const timestamp = Date.now();
1330
+ const tempOutput = join4(tempDir, `piclet-frame-${timestamp}.png`);
1331
+ try {
1332
+ if (!await extractFirstFrame(input, tempOutput, frameIndex)) {
1333
+ return { success: false, error: "Failed to extract frame" };
1334
+ }
1335
+ const buffer = readFileSync3(tempOutput);
1336
+ const base64 = buffer.toString("base64");
1337
+ const imageData = `data:image/png;base64,${base64}`;
1338
+ const dims = await getDimensions(tempOutput);
1339
+ cleanup(tempOutput);
1340
+ return {
1341
+ success: true,
1342
+ imageData,
1343
+ width: dims?.[0],
1344
+ height: dims?.[1]
1345
+ };
1346
+ } catch (err) {
1347
+ cleanup(tempOutput);
1348
+ return { success: false, error: err.message };
1349
+ }
1350
+ }
1351
+ var config2 = {
1352
+ id: "extract-frames",
1353
+ name: "Extract Frames",
1354
+ icon: "extract.ico",
1355
+ extensions: [".gif"]
1356
+ };
1357
+
1358
+ // src/tools/filter.ts
1359
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
1360
+ import { tmpdir as tmpdir3 } from "os";
1361
+ import { basename as basename4, join as join5 } from "path";
1362
+ var FILTER_LABELS = {
1363
+ "grayscale": "Grayscale",
1364
+ "sepia": "Sepia",
1365
+ "invert": "Invert",
1366
+ "vintage": "Vintage",
1367
+ "vivid": "Vivid"
1368
+ };
1369
+ async function applyFilter(input, output, filter) {
1370
+ switch (filter) {
1371
+ case "grayscale":
1372
+ return filterGrayscale(input, output);
1373
+ case "sepia":
1374
+ return filterSepia(input, output);
1375
+ case "invert":
1376
+ return filterInvert(input, output);
1377
+ case "vintage":
1378
+ return filterVintage(input, output);
1379
+ case "vivid":
1380
+ return filterVivid(input, output);
1381
+ default:
1382
+ return false;
1383
+ }
1384
+ }
1385
+ async function processImage2(input, options) {
1386
+ const fileInfo = getFileInfo(input);
1387
+ const outputExt = fileInfo.extension.toLowerCase() === ".gif" ? ".gif" : ".png";
1388
+ const output = `${fileInfo.dirname}/${fileInfo.filename}_${options.filter}${outputExt}`;
1389
+ wip(`Applying ${FILTER_LABELS[options.filter]} filter...`);
1390
+ const success2 = await applyFilter(input, output, options.filter);
1391
+ if (!success2) {
1392
+ wipDone(false, "Filter failed");
1393
+ return false;
1394
+ }
1395
+ wipDone(true, FILTER_LABELS[options.filter]);
1396
+ const finalDims = await getDimensions(output);
1397
+ console.log("");
1398
+ if (finalDims) {
1399
+ console.log(`Output: ${output} (${finalDims[0]}x${finalDims[1]})`);
1400
+ } else {
1401
+ console.log(`Output: ${output}`);
1402
+ }
1403
+ return true;
1404
+ }
1405
+ async function collectOptionsCLI2() {
1406
+ console.log("");
1407
+ console.log(`${BOLD}Filter Options:${RESET}`);
1408
+ console.log(` ${DIM}1. Grayscale - Black and white${RESET}`);
1409
+ console.log(` ${DIM}2. Sepia - Warm brownish tint${RESET}`);
1410
+ console.log(` ${DIM}3. Invert - Negative colors${RESET}`);
1411
+ console.log(` ${DIM}4. Vintage - Desaturated warm tone${RESET}`);
1412
+ console.log(` ${DIM}5. Vivid - Increased saturation${RESET}`);
1413
+ console.log("");
1414
+ const filter = await select(
1415
+ "Select filter",
1416
+ [
1417
+ { value: "grayscale", title: "Grayscale" },
1418
+ { value: "sepia", title: "Sepia" },
1419
+ { value: "invert", title: "Invert" },
1420
+ { value: "vintage", title: "Vintage" },
1421
+ { value: "vivid", title: "Vivid" }
1422
+ ],
1423
+ "grayscale"
1424
+ );
1425
+ return { filter: filter ?? "grayscale" };
1426
+ }
1427
+ async function run3(inputRaw) {
1428
+ if (!await checkImageMagick()) {
1429
+ error("ImageMagick not found. Please install it:");
1430
+ console.log(" sudo apt update && sudo apt install imagemagick");
1431
+ await pauseOnError();
1432
+ return false;
1433
+ }
1434
+ const input = normalizePath(inputRaw);
1435
+ if (!existsSync5(input)) {
1436
+ error(`File not found: ${input}`);
1437
+ await pauseOnError();
1438
+ return false;
1439
+ }
1440
+ header("PicLet Filter");
1441
+ wip("Analyzing image...");
1442
+ const dims = await getDimensions(input);
1443
+ if (!dims) {
1444
+ wipDone(false, "Failed to read image");
1445
+ return false;
1446
+ }
1447
+ wipDone(true, `Size: ${dims[0]}x${dims[1]}`);
1448
+ const options = await collectOptionsCLI2();
1449
+ console.log("");
1450
+ return processImage2(input, options);
1451
+ }
1452
+ async function runGUI3(inputRaw) {
1453
+ const input = normalizePath(inputRaw);
1454
+ if (!existsSync5(input)) {
1455
+ error(`File not found: ${input}`);
1456
+ return false;
1457
+ }
1458
+ const dims = await getDimensions(input);
1459
+ if (!dims) {
1460
+ error("Failed to read image dimensions");
1461
+ return false;
1462
+ }
1463
+ return startGuiServer({
1464
+ htmlFile: "filter.html",
1465
+ title: "PicLet - Filter",
1466
+ imageInfo: {
1467
+ filePath: input,
1468
+ fileName: basename4(input),
1469
+ width: dims[0],
1470
+ height: dims[1],
1471
+ borderColor: null
1472
+ },
1473
+ defaults: {
1474
+ filter: "grayscale"
1475
+ },
1476
+ onPreview: async (opts) => {
1477
+ const filter = opts.filter ?? "grayscale";
1478
+ return generatePreview2(input, { filter });
1479
+ },
1480
+ onProcess: async (opts) => {
1481
+ const logs = [];
1482
+ if (!await checkImageMagick()) {
1483
+ return {
1484
+ success: false,
1485
+ error: "ImageMagick not found",
1486
+ logs: [{ type: "error", message: "ImageMagick not found. Install with: sudo apt install imagemagick" }]
1487
+ };
1488
+ }
1489
+ logs.push({ type: "info", message: `Processing ${basename4(input)}...` });
1490
+ const filter = opts.filter ?? "grayscale";
1491
+ const options = { filter };
1492
+ const fileInfo = getFileInfo(input);
1493
+ const outputExt = fileInfo.extension.toLowerCase() === ".gif" ? ".gif" : ".png";
1494
+ const output = `${fileInfo.dirname}/${fileInfo.filename}_${filter}${outputExt}`;
1495
+ logs.push({ type: "info", message: `Applying ${FILTER_LABELS[filter]} filter...` });
1496
+ const success2 = await applyFilter(input, output, filter);
1497
+ if (success2) {
1498
+ logs.push({ type: "success", message: "Filter applied" });
1499
+ const finalDims = await getDimensions(output);
1500
+ const sizeStr = finalDims ? ` (${finalDims[0]}x${finalDims[1]})` : "";
1501
+ return {
1502
+ success: true,
1503
+ output: `${basename4(output)}${sizeStr}`,
1504
+ logs
1505
+ };
1506
+ }
1507
+ logs.push({ type: "error", message: "Filter failed" });
1508
+ return {
1509
+ success: false,
1510
+ error: "Processing failed",
1511
+ logs
1512
+ };
1513
+ }
1514
+ });
1515
+ }
1516
+ async function generatePreview2(input, options) {
1517
+ const tempDir = tmpdir3();
1518
+ const timestamp = Date.now();
1519
+ const tempSource = join5(tempDir, `piclet-preview-${timestamp}-src.png`);
1520
+ const tempOutput = join5(tempDir, `piclet-preview-${timestamp}.png`);
1521
+ try {
1522
+ let previewInput = input;
1523
+ if (isMultiFrame(input)) {
1524
+ if (!await extractFirstFrame(input, tempSource)) {
1525
+ return { success: false, error: "Failed to extract frame" };
1526
+ }
1527
+ previewInput = tempSource;
1528
+ }
1529
+ const success2 = await applyFilter(previewInput, tempOutput, options.filter);
1530
+ if (!success2) {
1531
+ cleanup(tempSource, tempOutput);
1532
+ return { success: false, error: "Filter failed" };
1533
+ }
1534
+ const buffer = readFileSync4(tempOutput);
1535
+ const base64 = buffer.toString("base64");
1536
+ const imageData = `data:image/png;base64,${base64}`;
1537
+ const dims = await getDimensions(tempOutput);
1538
+ cleanup(tempSource, tempOutput);
1539
+ return {
1540
+ success: true,
1541
+ imageData,
1542
+ width: dims?.[0],
1543
+ height: dims?.[1]
1544
+ };
1545
+ } catch (err) {
1546
+ cleanup(tempSource, tempOutput);
1547
+ return { success: false, error: err.message };
1548
+ }
1549
+ }
1550
+ var config3 = {
1551
+ id: "filter",
1552
+ name: "Filter",
1553
+ icon: "filter.ico",
1554
+ extensions: [".png", ".jpg", ".jpeg", ".gif", ".bmp"]
1555
+ };
1556
+
1557
+ // src/tools/iconpack.ts
1558
+ import { existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
1559
+ import { basename as basename5, dirname as dirname5 } from "path";
1560
+ var WEB_ICONS = [
1561
+ { filename: "favicon-16x16.png", size: 16 },
1562
+ { filename: "favicon-32x32.png", size: 32 },
1563
+ { filename: "favicon-48x48.png", size: 48 },
1564
+ { filename: "apple-touch-icon.png", size: 180 },
1565
+ { filename: "android-chrome-192x192.png", size: 192 },
1566
+ { filename: "android-chrome-512x512.png", size: 512 },
1567
+ { filename: "mstile-150x150.png", size: 150 }
1568
+ ];
1569
+ var ANDROID_ICONS = [
1570
+ { filename: "mipmap-mdpi/ic_launcher.png", size: 48 },
1571
+ { filename: "mipmap-hdpi/ic_launcher.png", size: 72 },
1572
+ { filename: "mipmap-xhdpi/ic_launcher.png", size: 96 },
1573
+ { filename: "mipmap-xxhdpi/ic_launcher.png", size: 144 },
1574
+ { filename: "mipmap-xxxhdpi/ic_launcher.png", size: 192 },
1575
+ { filename: "playstore-icon.png", size: 512 }
1576
+ ];
1577
+ var IOS_ICONS = [
1578
+ // 20pt - Notifications
1579
+ { filename: "AppIcon-20.png", size: 20 },
1580
+ { filename: "AppIcon-20@2x.png", size: 40 },
1581
+ { filename: "AppIcon-20@3x.png", size: 60 },
1582
+ // 29pt - Settings
1583
+ { filename: "AppIcon-29.png", size: 29 },
1584
+ { filename: "AppIcon-29@2x.png", size: 58 },
1585
+ { filename: "AppIcon-29@3x.png", size: 87 },
1586
+ // 40pt - Spotlight
1587
+ { filename: "AppIcon-40.png", size: 40 },
1588
+ { filename: "AppIcon-40@2x.png", size: 80 },
1589
+ { filename: "AppIcon-40@3x.png", size: 120 },
1590
+ // 60pt - iPhone App
900
1591
  { filename: "AppIcon-60@2x.png", size: 120 },
901
1592
  { filename: "AppIcon-60@3x.png", size: 180 },
902
1593
  // 76pt - iPad App
@@ -914,8 +1605,8 @@ async function generateIcons(outputDir, sourceImg, icons) {
914
1605
  for (const icon of icons) {
915
1606
  current++;
916
1607
  const outputPath = `${outputDir}/${icon.filename}`;
917
- const subdir = dirname6(outputPath);
918
- if (!existsSync4(subdir)) {
1608
+ const subdir = dirname5(outputPath);
1609
+ if (!existsSync6(subdir)) {
919
1610
  mkdirSync4(subdir, { recursive: true });
920
1611
  }
921
1612
  clearLine();
@@ -953,7 +1644,7 @@ async function generateFavicon(outputDir, sourceImg) {
953
1644
  wipDone(false, "favicon.ico failed");
954
1645
  return false;
955
1646
  }
956
- async function run(inputRaw) {
1647
+ async function run4(inputRaw) {
957
1648
  if (!await checkImageMagick()) {
958
1649
  error("ImageMagick not found. Please install it:");
959
1650
  console.log(" sudo apt update && sudo apt install imagemagick");
@@ -961,7 +1652,7 @@ async function run(inputRaw) {
961
1652
  return false;
962
1653
  }
963
1654
  const input = normalizePath(inputRaw);
964
- if (!existsSync4(input)) {
1655
+ if (!existsSync6(input)) {
965
1656
  error(`File not found: ${input}`);
966
1657
  await pauseOnError();
967
1658
  return false;
@@ -1009,7 +1700,7 @@ async function run(inputRaw) {
1009
1700
  wip("Preparing source image...");
1010
1701
  const tempSource = `${outputDir}/.source_1024.png`;
1011
1702
  const tempSquare = `${outputDir}/.temp_square.png`;
1012
- if (!await squarify2(input, tempSquare)) {
1703
+ if (!await squarify(input, tempSquare)) {
1013
1704
  wipDone(false, "Failed to prepare source");
1014
1705
  return false;
1015
1706
  }
@@ -1066,9 +1757,9 @@ async function run(inputRaw) {
1066
1757
  }
1067
1758
  return totalFailed === 0;
1068
1759
  }
1069
- async function runGUI(inputRaw) {
1760
+ async function runGUI4(inputRaw) {
1070
1761
  const input = normalizePath(inputRaw);
1071
- if (!existsSync4(input)) {
1762
+ if (!existsSync6(input)) {
1072
1763
  error(`File not found: ${input}`);
1073
1764
  return false;
1074
1765
  }
@@ -1083,7 +1774,7 @@ async function runGUI(inputRaw) {
1083
1774
  title: "PicLet - Icon Pack",
1084
1775
  imageInfo: {
1085
1776
  filePath: input,
1086
- fileName: basename2(input),
1777
+ fileName: basename5(input),
1087
1778
  width: dims[0],
1088
1779
  height: dims[1],
1089
1780
  borderColor: null
@@ -1118,7 +1809,7 @@ async function runGUI(inputRaw) {
1118
1809
  logs.push({ type: "info", message: "Preparing source image..." });
1119
1810
  const tempSource = `${outputDir}/.source_1024.png`;
1120
1811
  const tempSquare = `${outputDir}/.temp_square.png`;
1121
- if (!await squarify2(input, tempSquare)) {
1812
+ if (!await squarify(input, tempSquare)) {
1122
1813
  return { success: false, error: "Failed to prepare source", logs };
1123
1814
  }
1124
1815
  if (!await scaleToSize(tempSquare, tempSource, 1024)) {
@@ -1172,8 +1863,8 @@ async function generateIconsSilent(outputDir, sourceImg, icons, _logs) {
1172
1863
  let failed = 0;
1173
1864
  for (const icon of icons) {
1174
1865
  const outputPath = `${outputDir}/${icon.filename}`;
1175
- const subdir = dirname6(outputPath);
1176
- if (!existsSync4(subdir)) {
1866
+ const subdir = dirname5(outputPath);
1867
+ if (!existsSync6(subdir)) {
1177
1868
  mkdirSync4(subdir, { recursive: true });
1178
1869
  }
1179
1870
  if (!await scaleToSize(sourceImg, outputPath, icon.size)) {
@@ -1196,24 +1887,18 @@ async function generateFaviconSilent(outputDir, sourceImg) {
1196
1887
  cleanup(temp16, temp32, temp48);
1197
1888
  return result;
1198
1889
  }
1199
- var config = {
1890
+ var config4 = {
1200
1891
  id: "iconpack",
1201
1892
  name: "Icon Pack",
1202
1893
  icon: "iconpack.ico",
1203
- extensions: [".png", ".jpg", ".jpeg"]
1894
+ extensions: [".png", ".jpg", ".jpeg", ".gif"]
1204
1895
  };
1205
1896
 
1206
- // src/cli/utils.ts
1207
- import { extname as extname3 } from "path";
1208
- import { dirname as dirname9 } from "path";
1209
- import { fileURLToPath as fileURLToPath2 } from "url";
1210
- import chalk2 from "chalk";
1211
-
1212
1897
  // src/tools/makeicon.ts
1213
- import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
1214
- import { tmpdir } from "os";
1215
- import { basename as basename3, join as join4 } from "path";
1216
- async function run2(inputRaw) {
1898
+ import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
1899
+ import { tmpdir as tmpdir4 } from "os";
1900
+ import { basename as basename6, join as join6 } from "path";
1901
+ async function run5(inputRaw) {
1217
1902
  if (!await checkImageMagick()) {
1218
1903
  error("ImageMagick not found. Please install it:");
1219
1904
  console.log(" sudo apt update && sudo apt install imagemagick");
@@ -1221,7 +1906,7 @@ async function run2(inputRaw) {
1221
1906
  return false;
1222
1907
  }
1223
1908
  const input = normalizePath(inputRaw);
1224
- if (!existsSync5(input)) {
1909
+ if (!existsSync7(input)) {
1225
1910
  error(`File not found: ${input}`);
1226
1911
  await pauseOnError();
1227
1912
  return false;
@@ -1247,7 +1932,7 @@ async function run2(inputRaw) {
1247
1932
  }
1248
1933
  wipDone(true, "Trimmed");
1249
1934
  wip("Making square...");
1250
- if (!await squarify2(tempTrimmed, tempSquare)) {
1935
+ if (!await squarify(tempTrimmed, tempSquare)) {
1251
1936
  wipDone(false, "Square padding failed");
1252
1937
  cleanup(tempTrimmed, tempSquare);
1253
1938
  return false;
@@ -1291,7 +1976,7 @@ async function processForIcon(input, output, options, logs) {
1291
1976
  }
1292
1977
  if (options.makeSquare) {
1293
1978
  logs?.push({ type: "info", message: "Making square..." });
1294
- if (!await squarify2(currentInput, tempSquare)) {
1979
+ if (!await squarify(currentInput, tempSquare)) {
1295
1980
  cleanup(tempTrimmed, tempSquare);
1296
1981
  return false;
1297
1982
  }
@@ -1316,12 +2001,12 @@ async function processForIcon(input, output, options, logs) {
1316
2001
  logs?.push({ type: "success", message: "Icon created" });
1317
2002
  return true;
1318
2003
  }
1319
- async function generatePreview(input, options) {
1320
- const tempDir = tmpdir();
2004
+ async function generatePreview3(input, options) {
2005
+ const tempDir = tmpdir4();
1321
2006
  const timestamp = Date.now();
1322
- const tempTrimmed = join4(tempDir, `piclet-preview-trimmed-${timestamp}.png`);
1323
- const tempSquare = join4(tempDir, `piclet-preview-square-${timestamp}.png`);
1324
- const tempOutput = join4(tempDir, `piclet-preview-${timestamp}.png`);
2007
+ const tempTrimmed = join6(tempDir, `piclet-preview-trimmed-${timestamp}.png`);
2008
+ const tempSquare = join6(tempDir, `piclet-preview-square-${timestamp}.png`);
2009
+ const tempOutput = join6(tempDir, `piclet-preview-${timestamp}.png`);
1325
2010
  try {
1326
2011
  let currentInput = input;
1327
2012
  if (options.trim) {
@@ -1332,7 +2017,7 @@ async function generatePreview(input, options) {
1332
2017
  currentInput = tempTrimmed;
1333
2018
  }
1334
2019
  if (options.makeSquare) {
1335
- if (!await squarify2(currentInput, tempSquare)) {
2020
+ if (!await squarify(currentInput, tempSquare)) {
1336
2021
  cleanup(tempTrimmed, tempSquare);
1337
2022
  return { success: false, error: "Square failed" };
1338
2023
  }
@@ -1345,7 +2030,7 @@ async function generatePreview(input, options) {
1345
2030
  }
1346
2031
  if (currentInput === tempSquare) cleanup(tempSquare);
1347
2032
  else if (currentInput === tempTrimmed) cleanup(tempTrimmed);
1348
- const buffer = readFileSync3(tempOutput);
2033
+ const buffer = readFileSync5(tempOutput);
1349
2034
  const base64 = buffer.toString("base64");
1350
2035
  const imageData = `data:image/png;base64,${base64}`;
1351
2036
  const dims = await getDimensions(tempOutput);
@@ -1361,9 +2046,9 @@ async function generatePreview(input, options) {
1361
2046
  return { success: false, error: err.message };
1362
2047
  }
1363
2048
  }
1364
- async function runGUI2(inputRaw) {
2049
+ async function runGUI5(inputRaw) {
1365
2050
  const input = normalizePath(inputRaw);
1366
- if (!existsSync5(input)) {
2051
+ if (!existsSync7(input)) {
1367
2052
  error(`File not found: ${input}`);
1368
2053
  return false;
1369
2054
  }
@@ -1378,7 +2063,7 @@ async function runGUI2(inputRaw) {
1378
2063
  title: "PicLet - Make Icon",
1379
2064
  imageInfo: {
1380
2065
  filePath: input,
1381
- fileName: basename3(input),
2066
+ fileName: basename6(input),
1382
2067
  width: dims[0],
1383
2068
  height: dims[1],
1384
2069
  borderColor: null
@@ -1392,7 +2077,7 @@ async function runGUI2(inputRaw) {
1392
2077
  trim: opts.trim ?? true,
1393
2078
  makeSquare: opts.makeSquare ?? true
1394
2079
  };
1395
- return generatePreview(input, options);
2080
+ return generatePreview3(input, options);
1396
2081
  },
1397
2082
  onProcess: async (opts) => {
1398
2083
  const logs = [];
@@ -1420,40 +2105,46 @@ async function runGUI2(inputRaw) {
1420
2105
  }
1421
2106
  });
1422
2107
  }
1423
- var config2 = {
2108
+ var config5 = {
1424
2109
  id: "makeicon",
1425
2110
  name: "Make Icon",
1426
2111
  icon: "makeicon.ico",
1427
- extensions: [".png"]
2112
+ extensions: [".png", ".gif"]
1428
2113
  };
1429
2114
 
1430
2115
  // src/tools/piclet-main.ts
1431
- import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync4, renameSync, writeFileSync as writeFileSync3 } from "fs";
1432
- import { tmpdir as tmpdir2 } from "os";
1433
- import { basename as basename4, dirname as dirname7, extname as extname2, join as join5 } from "path";
2116
+ import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync6, renameSync, writeFileSync as writeFileSync2 } from "fs";
2117
+ import { tmpdir as tmpdir5 } from "os";
2118
+ import { basename as basename7, dirname as dirname6, extname as extname2, join as join7 } from "path";
1434
2119
  var TOOL_ORDER = ["removebg", "scale", "icons", "storepack"];
1435
2120
  async function generateCombinedPreview(input, borderColor, opts) {
1436
- const tempDir = tmpdir2();
2121
+ const tempDir = tmpdir5();
1437
2122
  const ts = Date.now();
1438
2123
  const temps = [];
1439
2124
  const makeTempPath = (suffix) => {
1440
- const p = join5(tempDir, `piclet-${ts}-${suffix}.png`);
2125
+ const p = join7(tempDir, `piclet-${ts}-${suffix}.png`);
1441
2126
  temps.push(p);
1442
2127
  return p;
1443
2128
  };
1444
2129
  try {
1445
2130
  let current = input;
2131
+ if (isMultiFrame(input)) {
2132
+ const frameTemp = makeTempPath("frame");
2133
+ if (await extractFirstFrame(input, frameTemp)) {
2134
+ current = frameTemp;
2135
+ }
2136
+ }
1446
2137
  if (opts.original || opts.tools.length === 0) {
1447
- const dims2 = await getDimensions(input);
1448
- let previewPath2 = input;
2138
+ const dims2 = await getDimensions(current);
2139
+ let previewPath2 = current;
1449
2140
  if (dims2 && (dims2[0] > 512 || dims2[1] > 512)) {
1450
2141
  const scaled = makeTempPath("orig-preview");
1451
2142
  const targetSize = Math.min(512, Math.max(dims2[0], dims2[1]));
1452
- if (await scaleToSize(input, scaled, targetSize)) {
2143
+ if (await scaleToSize(current, scaled, targetSize)) {
1453
2144
  previewPath2 = scaled;
1454
2145
  }
1455
2146
  }
1456
- const buffer2 = readFileSync4(previewPath2);
2147
+ const buffer2 = readFileSync6(previewPath2);
1457
2148
  const finalDims2 = await getDimensions(previewPath2);
1458
2149
  cleanup(...temps);
1459
2150
  return {
@@ -1519,7 +2210,7 @@ async function generateCombinedPreview(input, borderColor, opts) {
1519
2210
  }
1520
2211
  if (icOpts.makeSquare) {
1521
2212
  const sqOut = makeTempPath("ic-sq");
1522
- if (await squarify2(current, sqOut)) {
2213
+ if (await squarify(current, sqOut)) {
1523
2214
  current = sqOut;
1524
2215
  }
1525
2216
  }
@@ -1536,7 +2227,7 @@ async function generateCombinedPreview(input, borderColor, opts) {
1536
2227
  previewPath = scaled;
1537
2228
  }
1538
2229
  }
1539
- const buffer = readFileSync4(previewPath);
2230
+ const buffer = readFileSync6(previewPath);
1540
2231
  const finalDims = await getDimensions(previewPath);
1541
2232
  cleanup(...temps);
1542
2233
  return {
@@ -1554,8 +2245,9 @@ async function processCombined(input, borderColor, opts, logs) {
1554
2245
  const fileInfo = getFileInfo(input);
1555
2246
  const outputs = [];
1556
2247
  const temps = [];
2248
+ const outputExt = fileInfo.extension.toLowerCase() === ".gif" ? ".gif" : ".png";
1557
2249
  const makeTempPath = (suffix) => {
1558
- const p = `${fileInfo.dirname}/${fileInfo.filename}_${suffix}.png`;
2250
+ const p = `${fileInfo.dirname}/${fileInfo.filename}_${suffix}${outputExt}`;
1559
2251
  temps.push(p);
1560
2252
  return p;
1561
2253
  };
@@ -1598,10 +2290,10 @@ async function processCombined(input, borderColor, opts, logs) {
1598
2290
  current = out;
1599
2291
  }
1600
2292
  if (activeTools.indexOf(tool) === activeTools.length - 1) {
1601
- const finalOut = `${fileInfo.dirname}/${fileInfo.filename}_nobg.png`;
2293
+ const finalOut = `${fileInfo.dirname}/${fileInfo.filename}_nobg${outputExt}`;
1602
2294
  renameSync(current, finalOut);
1603
2295
  temps.splice(temps.indexOf(current), 1);
1604
- outputs.push(basename4(finalOut));
2296
+ outputs.push(basename7(finalOut));
1605
2297
  }
1606
2298
  break;
1607
2299
  }
@@ -1629,10 +2321,10 @@ async function processCombined(input, borderColor, opts, logs) {
1629
2321
  }
1630
2322
  current = out;
1631
2323
  if (activeTools.indexOf(tool) === activeTools.length - 1) {
1632
- const finalOut = `${fileInfo.dirname}/${fileInfo.filename}_scaled${fileInfo.extension}`;
2324
+ const finalOut = `${fileInfo.dirname}/${fileInfo.filename}_scaled${outputExt}`;
1633
2325
  renameSync(current, finalOut);
1634
2326
  temps.splice(temps.indexOf(current), 1);
1635
- outputs.push(basename4(finalOut));
2327
+ outputs.push(basename7(finalOut));
1636
2328
  }
1637
2329
  break;
1638
2330
  }
@@ -1655,7 +2347,7 @@ async function processCombined(input, borderColor, opts, logs) {
1655
2347
  if (icOpts.makeSquare) {
1656
2348
  logs.push({ type: "info", message: "Making square..." });
1657
2349
  const sqOut = makeTempPath("ic-sq");
1658
- if (await squarify2(iconSource, sqOut)) {
2350
+ if (await squarify(iconSource, sqOut)) {
1659
2351
  if (iconSource !== current && iconSource !== input) cleanup(iconSource);
1660
2352
  iconSource = sqOut;
1661
2353
  logs.push({ type: "success", message: "Made square" });
@@ -1675,7 +2367,7 @@ async function processCombined(input, borderColor, opts, logs) {
1675
2367
  const icoOut = `${fileInfo.dirname}/${fileInfo.filename}.ico`;
1676
2368
  if (await createIco(srcTemp, icoOut)) {
1677
2369
  logs.push({ type: "success", message: "ICO: 6 sizes (256, 128, 64, 48, 32, 16)" });
1678
- outputs.push(basename4(icoOut));
2370
+ outputs.push(basename7(icoOut));
1679
2371
  totalCount += 6;
1680
2372
  } else {
1681
2373
  logs.push({ type: "warn", message: "ICO creation failed" });
@@ -1723,7 +2415,7 @@ async function processCombined(input, borderColor, opts, logs) {
1723
2415
  ];
1724
2416
  for (const i of androidIcons) {
1725
2417
  const p = `${androidDir}/${i.name}`;
1726
- mkdirSync5(dirname7(p), { recursive: true });
2418
+ mkdirSync5(dirname6(p), { recursive: true });
1727
2419
  await scaleToSize(srcTemp, p, i.size);
1728
2420
  totalCount++;
1729
2421
  }
@@ -1759,7 +2451,7 @@ async function processCombined(input, borderColor, opts, logs) {
1759
2451
  let count = 0;
1760
2452
  for (const dim of spOpts.dimensions) {
1761
2453
  const filename = dim.filename || `${dim.width}x${dim.height}.png`;
1762
- const out = join5(outputDir, filename);
2454
+ const out = join7(outputDir, filename);
1763
2455
  let success2 = false;
1764
2456
  switch (spOpts.scaleMode) {
1765
2457
  case "fill":
@@ -1782,9 +2474,9 @@ async function processCombined(input, borderColor, opts, logs) {
1782
2474
  cleanup(...temps);
1783
2475
  return outputs;
1784
2476
  }
1785
- async function runGUI3(inputRaw) {
2477
+ async function runGUI6(inputRaw) {
1786
2478
  let currentInput = normalizePath(inputRaw);
1787
- if (!existsSync6(currentInput)) {
2479
+ if (!existsSync8(currentInput)) {
1788
2480
  error(`File not found: ${currentInput}`);
1789
2481
  return false;
1790
2482
  }
@@ -1794,99 +2486,557 @@ async function runGUI3(inputRaw) {
1794
2486
  return false;
1795
2487
  }
1796
2488
  let currentBorderColor = await getBorderColor(currentInput);
2489
+ let currentFrameCount = isMultiFrame(currentInput) ? await getFrameCount(currentInput) : 1;
1797
2490
  const presets = loadPresets();
2491
+ async function generateFrameThumbnail(frameIndex) {
2492
+ const tempDir = tmpdir5();
2493
+ const tempOutput = join7(tempDir, `piclet-frame-${Date.now()}-${frameIndex}.png`);
2494
+ try {
2495
+ if (!await extractFirstFrame(currentInput, tempOutput, frameIndex)) {
2496
+ return { success: false, error: "Failed to extract frame" };
2497
+ }
2498
+ const thumbOutput = join7(tempDir, `piclet-thumb-${Date.now()}-${frameIndex}.png`);
2499
+ await scaleToSize(tempOutput, thumbOutput, 96);
2500
+ const buffer = readFileSync6(thumbOutput);
2501
+ cleanup(tempOutput, thumbOutput);
2502
+ return {
2503
+ success: true,
2504
+ imageData: `data:image/png;base64,${buffer.toString("base64")}`
2505
+ };
2506
+ } catch (err) {
2507
+ cleanup(tempOutput);
2508
+ return { success: false, error: err.message };
2509
+ }
2510
+ }
2511
+ async function generateFramePreview2(frameIndex, opts) {
2512
+ const tempDir = tmpdir5();
2513
+ const ts = Date.now();
2514
+ const frameFile = join7(tempDir, `piclet-fp-${ts}-${frameIndex}.png`);
2515
+ const temps = [frameFile];
2516
+ try {
2517
+ if (!await extractFirstFrame(currentInput, frameFile, frameIndex)) {
2518
+ return { success: false, error: "Failed to extract frame" };
2519
+ }
2520
+ let current = frameFile;
2521
+ const activeTools = ["removebg", "scale", "icons"].filter((t) => opts.tools.includes(t));
2522
+ for (const tool of activeTools) {
2523
+ const tempOut = join7(tempDir, `piclet-fp-${ts}-${frameIndex}-${tool}.png`);
2524
+ temps.push(tempOut);
2525
+ switch (tool) {
2526
+ case "removebg": {
2527
+ const rbOpts = opts.removebg;
2528
+ let success2 = false;
2529
+ if (rbOpts.preserveInner && currentBorderColor) {
2530
+ success2 = await removeBackgroundBorderOnly(current, tempOut, currentBorderColor, rbOpts.fuzz);
2531
+ }
2532
+ if (!success2 && currentBorderColor) {
2533
+ success2 = await removeBackground(current, tempOut, currentBorderColor, rbOpts.fuzz);
2534
+ }
2535
+ if (success2) {
2536
+ if (rbOpts.trim) {
2537
+ const trimOut = join7(tempDir, `piclet-fp-${ts}-${frameIndex}-trim.png`);
2538
+ temps.push(trimOut);
2539
+ if (await trim(tempOut, trimOut)) {
2540
+ current = trimOut;
2541
+ } else {
2542
+ current = tempOut;
2543
+ }
2544
+ } else {
2545
+ current = tempOut;
2546
+ }
2547
+ }
2548
+ break;
2549
+ }
2550
+ case "scale": {
2551
+ const scOpts = opts.scale;
2552
+ if (scOpts.makeSquare) {
2553
+ const max = Math.max(scOpts.width, scOpts.height);
2554
+ if (await scaleWithPadding(current, tempOut, max, max)) {
2555
+ current = tempOut;
2556
+ }
2557
+ } else {
2558
+ if (await resize(current, tempOut, scOpts.width, scOpts.height)) {
2559
+ current = tempOut;
2560
+ }
2561
+ }
2562
+ break;
2563
+ }
2564
+ case "icons": {
2565
+ const icOpts = opts.icons;
2566
+ if (icOpts.trim) {
2567
+ const trimOut = join7(tempDir, `piclet-fp-${ts}-${frameIndex}-ictrim.png`);
2568
+ temps.push(trimOut);
2569
+ if (await trim(current, trimOut)) {
2570
+ current = trimOut;
2571
+ }
2572
+ }
2573
+ if (icOpts.makeSquare) {
2574
+ if (await squarify(current, tempOut)) {
2575
+ current = tempOut;
2576
+ }
2577
+ }
2578
+ break;
2579
+ }
2580
+ }
2581
+ }
2582
+ const thumbOut = join7(tempDir, `piclet-fp-${ts}-${frameIndex}-thumb.png`);
2583
+ temps.push(thumbOut);
2584
+ await scaleToSize(current, thumbOut, 96);
2585
+ const buffer = readFileSync6(thumbOut);
2586
+ cleanup(...temps);
2587
+ return {
2588
+ success: true,
2589
+ imageData: `data:image/png;base64,${buffer.toString("base64")}`
2590
+ };
2591
+ } catch (err) {
2592
+ cleanup(...temps);
2593
+ return { success: false, error: err.message };
2594
+ }
2595
+ }
1798
2596
  return startGuiServer({
1799
2597
  htmlFile: "piclet.html",
1800
2598
  title: "PicLet",
1801
2599
  imageInfo: {
1802
- filePath: currentInput,
1803
- fileName: basename4(currentInput),
2600
+ filePath: currentInput,
2601
+ fileName: basename7(currentInput),
2602
+ width: dims[0],
2603
+ height: dims[1],
2604
+ borderColor: currentBorderColor,
2605
+ frameCount: currentFrameCount
2606
+ },
2607
+ defaults: {
2608
+ // Return full preset data for the UI
2609
+ presets: presets.map((p) => ({
2610
+ id: p.id,
2611
+ name: p.name,
2612
+ description: p.description,
2613
+ icons: p.icons
2614
+ }))
2615
+ },
2616
+ onPreview: async (opts) => {
2617
+ const toolOpts = opts;
2618
+ if (!toolOpts.tools) {
2619
+ toolOpts.tools = [];
2620
+ }
2621
+ if (isMultiFrame(currentInput) && typeof toolOpts.frameIndex === "number") {
2622
+ const tempDir = tmpdir5();
2623
+ const ts = Date.now();
2624
+ const frameFile = join7(tempDir, `piclet-prev-${ts}.png`);
2625
+ if (!await extractFirstFrame(currentInput, frameFile, toolOpts.frameIndex)) {
2626
+ return { success: false, error: "Failed to extract frame" };
2627
+ }
2628
+ const result = await generateCombinedPreview(frameFile, currentBorderColor, toolOpts);
2629
+ cleanup(frameFile);
2630
+ return result;
2631
+ }
2632
+ return generateCombinedPreview(currentInput, currentBorderColor, toolOpts);
2633
+ },
2634
+ onProcess: async (opts) => {
2635
+ const logs = [];
2636
+ const toolOpts = opts;
2637
+ if (toolOpts.exportMode && isMultiFrame(currentInput)) {
2638
+ return processGifExport(currentInput, currentBorderColor, toolOpts, logs);
2639
+ }
2640
+ if (!toolOpts.tools || toolOpts.tools.length === 0) {
2641
+ return { success: false, error: "No tools selected", logs };
2642
+ }
2643
+ if (!await checkImageMagick()) {
2644
+ return {
2645
+ success: false,
2646
+ error: "ImageMagick not found",
2647
+ logs: [{ type: "error", message: "ImageMagick not found. Install: sudo apt install imagemagick" }]
2648
+ };
2649
+ }
2650
+ const outputs = await processCombined(currentInput, currentBorderColor, toolOpts, logs);
2651
+ if (outputs.length > 0) {
2652
+ return {
2653
+ success: true,
2654
+ output: outputs.join("\n"),
2655
+ logs
2656
+ };
2657
+ }
2658
+ return { success: false, error: "Processing failed", logs };
2659
+ },
2660
+ onLoadImage: async (data) => {
2661
+ try {
2662
+ const ext = extname2(data.fileName) || ".png";
2663
+ const tempPath = join7(tmpdir5(), `piclet-load-${Date.now()}${ext}`);
2664
+ const buffer = Buffer.from(data.data, "base64");
2665
+ writeFileSync2(tempPath, buffer);
2666
+ const newDims = await getDimensions(tempPath);
2667
+ if (!newDims) {
2668
+ cleanup(tempPath);
2669
+ return { success: false, error: "Failed to read image dimensions" };
2670
+ }
2671
+ const newBorderColor = await getBorderColor(tempPath);
2672
+ const newFrameCount = isMultiFrame(tempPath) ? await getFrameCount(tempPath) : 1;
2673
+ currentInput = tempPath;
2674
+ currentBorderColor = newBorderColor;
2675
+ currentFrameCount = newFrameCount;
2676
+ return {
2677
+ success: true,
2678
+ filePath: tempPath,
2679
+ fileName: data.fileName,
2680
+ width: newDims[0],
2681
+ height: newDims[1],
2682
+ borderColor: newBorderColor,
2683
+ frameCount: newFrameCount
2684
+ };
2685
+ } catch (err) {
2686
+ return { success: false, error: err.message };
2687
+ }
2688
+ },
2689
+ onFrameThumbnail: generateFrameThumbnail,
2690
+ onFramePreview: async (frameIndex, opts) => {
2691
+ const toolOpts = opts;
2692
+ if (!toolOpts.tools) toolOpts.tools = [];
2693
+ return generateFramePreview2(frameIndex, toolOpts);
2694
+ }
2695
+ });
2696
+ }
2697
+ async function processGifExport(input, borderColor, opts, logs) {
2698
+ if (!await checkImageMagick()) {
2699
+ return {
2700
+ success: false,
2701
+ error: "ImageMagick not found",
2702
+ logs: [{ type: "error", message: "ImageMagick not found" }]
2703
+ };
2704
+ }
2705
+ const fileInfo = getFileInfo(input);
2706
+ const tempDir = tmpdir5();
2707
+ const ts = Date.now();
2708
+ switch (opts.exportMode) {
2709
+ case "frame": {
2710
+ const frameIndex = opts.frameIndex ?? 0;
2711
+ logs.push({ type: "info", message: `Exporting frame ${frameIndex + 1}...` });
2712
+ const frameFile = join7(tempDir, `piclet-export-${ts}.png`);
2713
+ if (!await extractFirstFrame(input, frameFile, frameIndex)) {
2714
+ logs.push({ type: "error", message: "Failed to extract frame" });
2715
+ return { success: false, error: "Failed to extract frame", logs };
2716
+ }
2717
+ let outputFile = frameFile;
2718
+ if (opts.tools && opts.tools.length > 0) {
2719
+ const processedLogs = [];
2720
+ const outputs = await processCombined(frameFile, borderColor, opts, processedLogs);
2721
+ logs.push(...processedLogs);
2722
+ if (outputs.length === 0) {
2723
+ cleanup(frameFile);
2724
+ return { success: false, error: "Processing failed", logs };
2725
+ }
2726
+ }
2727
+ const finalOutput = `${fileInfo.dirname}/${fileInfo.filename}_frame${frameIndex + 1}.png`;
2728
+ if (outputFile === frameFile) {
2729
+ renameSync(frameFile, finalOutput);
2730
+ }
2731
+ logs.push({ type: "success", message: `Exported frame ${frameIndex + 1}` });
2732
+ return { success: true, output: basename7(finalOutput), logs };
2733
+ }
2734
+ case "all-frames": {
2735
+ logs.push({ type: "info", message: "Extracting all frames..." });
2736
+ const outputDir = `${fileInfo.dirname}/${fileInfo.filename}_frames`;
2737
+ mkdirSync5(outputDir, { recursive: true });
2738
+ const frames = await extractAllFrames(input, outputDir, "frame");
2739
+ if (frames.length === 0) {
2740
+ logs.push({ type: "error", message: "Failed to extract frames" });
2741
+ return { success: false, error: "Failed to extract frames", logs };
2742
+ }
2743
+ if (opts.tools && opts.tools.length > 0) {
2744
+ logs.push({ type: "info", message: `Processing ${frames.length} frames...` });
2745
+ for (let i = 0; i < frames.length; i++) {
2746
+ const frameLogs = [];
2747
+ await processCombined(frames[i], borderColor, opts, frameLogs);
2748
+ }
2749
+ logs.push({ type: "success", message: `Processed ${frames.length} frames` });
2750
+ }
2751
+ logs.push({ type: "success", message: `Exported ${frames.length} frames` });
2752
+ return { success: true, output: `${frames.length} frames -> ${fileInfo.filename}_frames/`, logs };
2753
+ }
2754
+ case "gif": {
2755
+ 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 };
2759
+ }
2760
+ return { success: false, error: "Processing failed", logs };
2761
+ }
2762
+ default:
2763
+ return { success: false, error: "Unknown export mode", logs };
2764
+ }
2765
+ }
2766
+ var config6 = {
2767
+ id: "piclet",
2768
+ name: "PicLet",
2769
+ icon: "banana.ico",
2770
+ extensions: [".png", ".jpg", ".jpeg", ".gif", ".bmp", ".ico"]
2771
+ };
2772
+
2773
+ // src/tools/recolor.ts
2774
+ import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
2775
+ import { tmpdir as tmpdir6 } from "os";
2776
+ import { basename as basename8, join as join8 } from "path";
2777
+ async function processImage3(input, options) {
2778
+ const fileInfo = getFileInfo(input);
2779
+ const outputExt = fileInfo.extension.toLowerCase() === ".gif" ? ".gif" : ".png";
2780
+ const output = `${fileInfo.dirname}/${fileInfo.filename}_recolor${outputExt}`;
2781
+ wip(`Replacing ${options.fromColor} with ${options.toColor}...`);
2782
+ const success2 = await replaceColor(
2783
+ input,
2784
+ output,
2785
+ options.fromColor,
2786
+ options.toColor,
2787
+ options.fuzz
2788
+ );
2789
+ if (!success2) {
2790
+ wipDone(false, "Color replacement failed");
2791
+ return false;
2792
+ }
2793
+ wipDone(true, "Color replaced");
2794
+ const finalDims = await getDimensions(output);
2795
+ console.log("");
2796
+ if (finalDims) {
2797
+ console.log(`Output: ${output} (${finalDims[0]}x${finalDims[1]})`);
2798
+ } else {
2799
+ console.log(`Output: ${output}`);
2800
+ }
2801
+ return true;
2802
+ }
2803
+ async function collectOptionsCLI3(detectedColor) {
2804
+ console.log("");
2805
+ console.log(`${BOLD}Color Replacement:${RESET}`);
2806
+ console.log(` ${DIM}Replace one color with another${RESET}`);
2807
+ console.log(` ${DIM}Colors can be hex (#fff), named (white), or rgb(...)${RESET}`);
2808
+ console.log("");
2809
+ const defaultFrom = detectedColor || "#ffffff";
2810
+ let fromColor = await text("Color to replace", defaultFrom);
2811
+ if (!fromColor) {
2812
+ fromColor = defaultFrom;
2813
+ }
2814
+ console.log("");
2815
+ let toColor = await text("New color", "#000000");
2816
+ if (!toColor) {
2817
+ toColor = "#000000";
2818
+ }
2819
+ console.log("");
2820
+ console.log(`${BOLD}Fuzz Value:${RESET} Controls color matching sensitivity`);
2821
+ console.log(` ${DIM}0-10% = Exact match only${RESET}`);
2822
+ console.log(` ${DIM}10-30% = Similar colors${RESET}`);
2823
+ console.log(` ${DIM}30-50% = Wider range${RESET}`);
2824
+ console.log("");
2825
+ let fuzz = await number("Fuzz value (0-100)", 10, 0, 100);
2826
+ if (fuzz < 0 || fuzz > 100) {
2827
+ fuzz = 10;
2828
+ }
2829
+ return { fromColor, toColor, fuzz };
2830
+ }
2831
+ async function run6(inputRaw) {
2832
+ if (!await checkImageMagick()) {
2833
+ error("ImageMagick not found. Please install it:");
2834
+ console.log(" sudo apt update && sudo apt install imagemagick");
2835
+ await pauseOnError();
2836
+ return false;
2837
+ }
2838
+ const input = normalizePath(inputRaw);
2839
+ if (!existsSync9(input)) {
2840
+ error(`File not found: ${input}`);
2841
+ await pauseOnError();
2842
+ return false;
2843
+ }
2844
+ header("PicLet Recolor");
2845
+ wip("Analyzing image...");
2846
+ const dims = await getDimensions(input);
2847
+ if (!dims) {
2848
+ wipDone(false, "Failed to read image");
2849
+ return false;
2850
+ }
2851
+ const borderColor = await getBorderColor(input);
2852
+ wipDone(true, `Size: ${dims[0]}x${dims[1]}`);
2853
+ if (borderColor) {
2854
+ info(`Detected corner color: ${borderColor}`);
2855
+ }
2856
+ const options = await collectOptionsCLI3(borderColor);
2857
+ console.log("");
2858
+ return processImage3(input, options);
2859
+ }
2860
+ async function runGUI7(inputRaw) {
2861
+ const input = normalizePath(inputRaw);
2862
+ if (!existsSync9(input)) {
2863
+ error(`File not found: ${input}`);
2864
+ return false;
2865
+ }
2866
+ const dims = await getDimensions(input);
2867
+ if (!dims) {
2868
+ error("Failed to read image dimensions");
2869
+ return false;
2870
+ }
2871
+ const borderColor = await getBorderColor(input);
2872
+ return startGuiServer({
2873
+ htmlFile: "recolor.html",
2874
+ title: "PicLet - Recolor",
2875
+ imageInfo: {
2876
+ filePath: input,
2877
+ fileName: basename8(input),
1804
2878
  width: dims[0],
1805
2879
  height: dims[1],
1806
- borderColor: currentBorderColor
2880
+ borderColor
1807
2881
  },
1808
2882
  defaults: {
1809
- // Return full preset data for the UI
1810
- presets: presets.map((p) => ({
1811
- id: p.id,
1812
- name: p.name,
1813
- description: p.description,
1814
- icons: p.icons
1815
- }))
2883
+ fromColor: borderColor || "#ffffff",
2884
+ toColor: "#000000",
2885
+ fuzz: 10
1816
2886
  },
1817
2887
  onPreview: async (opts) => {
1818
- const toolOpts = opts;
1819
- if (!toolOpts.tools) {
1820
- toolOpts.tools = [];
1821
- }
1822
- return generateCombinedPreview(currentInput, currentBorderColor, toolOpts);
2888
+ const fromColor = opts.fromColor ?? "#ffffff";
2889
+ const toColor = opts.toColor ?? "#000000";
2890
+ const fuzz = opts.fuzz ?? 10;
2891
+ return generatePreview4(input, { fromColor, toColor, fuzz });
1823
2892
  },
1824
2893
  onProcess: async (opts) => {
1825
2894
  const logs = [];
1826
- const toolOpts = opts;
1827
- if (!toolOpts.tools || toolOpts.tools.length === 0) {
1828
- return { success: false, error: "No tools selected", logs };
1829
- }
1830
2895
  if (!await checkImageMagick()) {
1831
2896
  return {
1832
2897
  success: false,
1833
2898
  error: "ImageMagick not found",
1834
- logs: [{ type: "error", message: "ImageMagick not found. Install: sudo apt install imagemagick" }]
2899
+ logs: [{ type: "error", message: "ImageMagick not found. Install with: sudo apt install imagemagick" }]
1835
2900
  };
1836
2901
  }
1837
- const outputs = await processCombined(currentInput, currentBorderColor, toolOpts, logs);
1838
- if (outputs.length > 0) {
2902
+ logs.push({ type: "info", message: `Processing ${basename8(input)}...` });
2903
+ const fromColor = opts.fromColor ?? "#ffffff";
2904
+ const toColor = opts.toColor ?? "#000000";
2905
+ const fuzz = opts.fuzz ?? 10;
2906
+ const options = { fromColor, toColor, fuzz };
2907
+ const fileInfo = getFileInfo(input);
2908
+ const outputExt = fileInfo.extension.toLowerCase() === ".gif" ? ".gif" : ".png";
2909
+ const output = `${fileInfo.dirname}/${fileInfo.filename}_recolor${outputExt}`;
2910
+ logs.push({ type: "info", message: `Replacing ${fromColor} \u2192 ${toColor}...` });
2911
+ const success2 = await replaceColor(input, output, fromColor, toColor, fuzz);
2912
+ if (success2) {
2913
+ logs.push({ type: "success", message: "Color replaced" });
2914
+ const finalDims = await getDimensions(output);
2915
+ const sizeStr = finalDims ? ` (${finalDims[0]}x${finalDims[1]})` : "";
1839
2916
  return {
1840
2917
  success: true,
1841
- output: outputs.join("\n"),
2918
+ output: `${basename8(output)}${sizeStr}`,
1842
2919
  logs
1843
2920
  };
1844
2921
  }
1845
- return { success: false, error: "Processing failed", logs };
1846
- },
1847
- onLoadImage: async (data) => {
1848
- try {
1849
- const ext = extname2(data.fileName) || ".png";
1850
- const tempPath = join5(tmpdir2(), `piclet-load-${Date.now()}${ext}`);
1851
- const buffer = Buffer.from(data.data, "base64");
1852
- writeFileSync3(tempPath, buffer);
1853
- const newDims = await getDimensions(tempPath);
1854
- if (!newDims) {
1855
- cleanup(tempPath);
1856
- return { success: false, error: "Failed to read image dimensions" };
1857
- }
1858
- const newBorderColor = await getBorderColor(tempPath);
1859
- currentInput = tempPath;
1860
- currentBorderColor = newBorderColor;
1861
- return {
1862
- success: true,
1863
- filePath: tempPath,
1864
- fileName: data.fileName,
1865
- width: newDims[0],
1866
- height: newDims[1],
1867
- borderColor: newBorderColor
1868
- };
1869
- } catch (err) {
1870
- return { success: false, error: err.message };
1871
- }
2922
+ logs.push({ type: "error", message: "Color replacement failed" });
2923
+ return {
2924
+ success: false,
2925
+ error: "Processing failed",
2926
+ logs
2927
+ };
1872
2928
  }
1873
2929
  });
1874
2930
  }
1875
- var config3 = {
1876
- id: "piclet",
1877
- name: "PicLet",
1878
- icon: "banana.ico",
1879
- extensions: [".png", ".jpg", ".jpeg", ".gif", ".bmp", ".ico"]
2931
+ async function generatePreview4(input, options) {
2932
+ const tempDir = tmpdir6();
2933
+ const timestamp = Date.now();
2934
+ const tempSource = join8(tempDir, `piclet-preview-${timestamp}-src.png`);
2935
+ const tempOutput = join8(tempDir, `piclet-preview-${timestamp}.png`);
2936
+ try {
2937
+ let previewInput = input;
2938
+ if (isMultiFrame(input)) {
2939
+ if (!await extractFirstFrame(input, tempSource)) {
2940
+ return { success: false, error: "Failed to extract frame" };
2941
+ }
2942
+ previewInput = tempSource;
2943
+ }
2944
+ const success2 = await replaceColor(
2945
+ previewInput,
2946
+ tempOutput,
2947
+ options.fromColor,
2948
+ options.toColor,
2949
+ options.fuzz
2950
+ );
2951
+ if (!success2) {
2952
+ cleanup(tempSource, tempOutput);
2953
+ return { success: false, error: "Color replacement failed" };
2954
+ }
2955
+ const buffer = readFileSync7(tempOutput);
2956
+ const base64 = buffer.toString("base64");
2957
+ const imageData = `data:image/png;base64,${base64}`;
2958
+ const dims = await getDimensions(tempOutput);
2959
+ cleanup(tempSource, tempOutput);
2960
+ return {
2961
+ success: true,
2962
+ imageData,
2963
+ width: dims?.[0],
2964
+ height: dims?.[1]
2965
+ };
2966
+ } catch (err) {
2967
+ cleanup(tempSource, tempOutput);
2968
+ return { success: false, error: err.message };
2969
+ }
2970
+ }
2971
+ var config7 = {
2972
+ id: "recolor",
2973
+ name: "Recolor",
2974
+ icon: "recolor.ico",
2975
+ extensions: [".png", ".jpg", ".jpeg", ".gif", ".bmp"]
1880
2976
  };
1881
2977
 
1882
2978
  // src/tools/remove-bg.ts
1883
- import { existsSync as existsSync7, readFileSync as readFileSync5, renameSync as renameSync2 } from "fs";
1884
- import { tmpdir as tmpdir3 } from "os";
1885
- import { basename as basename5, join as join6 } from "path";
1886
- async function processImage(input, borderColor, options) {
2979
+ import { existsSync as existsSync11, readFileSync as readFileSync9, renameSync as renameSync2 } from "fs";
2980
+ import { tmpdir as tmpdir7 } from "os";
2981
+ import { basename as basename9, join as join10 } from "path";
2982
+
2983
+ // src/lib/config.ts
2984
+ import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "fs";
2985
+ import { dirname as dirname7, join as join9 } from "path";
2986
+ import { homedir as homedir2 } from "os";
2987
+ var DEFAULT_CONFIG = {
2988
+ removeBg: {
2989
+ fuzz: 10,
2990
+ trim: true,
2991
+ preserveInner: false,
2992
+ makeSquare: false
2993
+ },
2994
+ rescale: {
2995
+ defaultScale: 50,
2996
+ makeSquare: false
2997
+ },
2998
+ iconpack: {
2999
+ platforms: ["web", "android", "ios"]
3000
+ }
3001
+ };
3002
+ function getConfigDir() {
3003
+ return join9(homedir2(), ".config", "piclet");
3004
+ }
3005
+ function getConfigPath() {
3006
+ return join9(getConfigDir(), "config.json");
3007
+ }
3008
+ function loadConfig() {
3009
+ const configPath = getConfigPath();
3010
+ if (!existsSync10(configPath)) {
3011
+ return { ...DEFAULT_CONFIG };
3012
+ }
3013
+ try {
3014
+ const content = readFileSync8(configPath, "utf-8");
3015
+ const loaded = JSON.parse(content);
3016
+ return {
3017
+ removeBg: { ...DEFAULT_CONFIG.removeBg, ...loaded.removeBg },
3018
+ rescale: { ...DEFAULT_CONFIG.rescale, ...loaded.rescale },
3019
+ iconpack: { ...DEFAULT_CONFIG.iconpack, ...loaded.iconpack }
3020
+ };
3021
+ } catch {
3022
+ return { ...DEFAULT_CONFIG };
3023
+ }
3024
+ }
3025
+ function resetConfig() {
3026
+ const configPath = getConfigPath();
3027
+ const configDir = dirname7(configPath);
3028
+ if (!existsSync10(configDir)) {
3029
+ mkdirSync6(configDir, { recursive: true });
3030
+ }
3031
+ writeFileSync3(configPath, JSON.stringify(DEFAULT_CONFIG, null, 2));
3032
+ }
3033
+
3034
+ // src/tools/remove-bg.ts
3035
+ async function processImage4(input, borderColor, options) {
1887
3036
  const fileInfo = getFileInfo(input);
1888
- const output = `${fileInfo.dirname}/${fileInfo.filename}_nobg.png`;
1889
- const tempFile = `${fileInfo.dirname}/${fileInfo.filename}_temp.png`;
3037
+ const outputExt = fileInfo.extension.toLowerCase() === ".gif" ? ".gif" : ".png";
3038
+ const output = `${fileInfo.dirname}/${fileInfo.filename}_nobg${outputExt}`;
3039
+ const tempFile = `${fileInfo.dirname}/${fileInfo.filename}_temp${outputExt}`;
1890
3040
  wip("Removing background...");
1891
3041
  let bgRemoved = false;
1892
3042
  if (options.preserveInner && borderColor) {
@@ -1923,8 +3073,8 @@ async function processImage(input, borderColor, options) {
1923
3073
  }
1924
3074
  if (options.makeSquare) {
1925
3075
  wip("Making square...");
1926
- const tempSquare = `${fileInfo.dirname}/${fileInfo.filename}_square_temp.png`;
1927
- if (await squarify2(output, tempSquare)) {
3076
+ const tempSquare = `${fileInfo.dirname}/${fileInfo.filename}_square_temp${outputExt}`;
3077
+ if (await squarify(output, tempSquare)) {
1928
3078
  renameSync2(tempSquare, output);
1929
3079
  wipDone(true, "Made square");
1930
3080
  } else {
@@ -1941,7 +3091,7 @@ async function processImage(input, borderColor, options) {
1941
3091
  }
1942
3092
  return true;
1943
3093
  }
1944
- async function collectOptionsCLI() {
3094
+ async function collectOptionsCLI4() {
1945
3095
  console.log("");
1946
3096
  console.log(`${BOLD}Fuzz Value:${RESET} Controls color matching strictness`);
1947
3097
  console.log(` ${DIM}0-10% = Only exact or very similar colors${RESET}`);
@@ -1971,7 +3121,7 @@ async function collectOptionsCLI() {
1971
3121
  );
1972
3122
  return { fuzz, doTrim, preserveInner, makeSquare };
1973
3123
  }
1974
- async function run3(inputRaw) {
3124
+ async function run7(inputRaw) {
1975
3125
  if (!await checkImageMagick()) {
1976
3126
  error("ImageMagick not found. Please install it:");
1977
3127
  console.log(" sudo apt update && sudo apt install imagemagick");
@@ -1979,7 +3129,7 @@ async function run3(inputRaw) {
1979
3129
  return false;
1980
3130
  }
1981
3131
  const input = normalizePath(inputRaw);
1982
- if (!existsSync7(input)) {
3132
+ if (!existsSync11(input)) {
1983
3133
  error(`File not found: ${input}`);
1984
3134
  await pauseOnError();
1985
3135
  return false;
@@ -1996,13 +3146,13 @@ async function run3(inputRaw) {
1996
3146
  if (borderColor) {
1997
3147
  info(`Detected border color: ${borderColor}`);
1998
3148
  }
1999
- const options = await collectOptionsCLI();
3149
+ const options = await collectOptionsCLI4();
2000
3150
  console.log("");
2001
- return processImage(input, borderColor, options);
3151
+ return processImage4(input, borderColor, options);
2002
3152
  }
2003
- async function runGUI4(inputRaw) {
3153
+ async function runGUI8(inputRaw) {
2004
3154
  const input = normalizePath(inputRaw);
2005
- if (!existsSync7(input)) {
3155
+ if (!existsSync11(input)) {
2006
3156
  error(`File not found: ${input}`);
2007
3157
  return false;
2008
3158
  }
@@ -2012,14 +3162,14 @@ async function runGUI4(inputRaw) {
2012
3162
  return false;
2013
3163
  }
2014
3164
  const borderColor = await getBorderColor(input);
2015
- const config7 = loadConfig();
2016
- const defaults = config7.removeBg;
3165
+ const config12 = loadConfig();
3166
+ const defaults = config12.removeBg;
2017
3167
  return startGuiServer({
2018
3168
  htmlFile: "remove-bg.html",
2019
3169
  title: "PicLet - Remove Background",
2020
3170
  imageInfo: {
2021
3171
  filePath: input,
2022
- fileName: basename5(input),
3172
+ fileName: basename9(input),
2023
3173
  width: dims[0],
2024
3174
  height: dims[1],
2025
3175
  borderColor
@@ -2037,7 +3187,7 @@ async function runGUI4(inputRaw) {
2037
3187
  preserveInner: opts.preserveInner ?? defaults.preserveInner,
2038
3188
  makeSquare: opts.makeSquare ?? defaults.makeSquare
2039
3189
  };
2040
- return generatePreview2(input, borderColor, options);
3190
+ return generatePreview5(input, borderColor, options);
2041
3191
  },
2042
3192
  onProcess: async (opts) => {
2043
3193
  const logs = [];
@@ -2048,7 +3198,7 @@ async function runGUI4(inputRaw) {
2048
3198
  logs: [{ type: "error", message: "ImageMagick not found. Install with: sudo apt install imagemagick" }]
2049
3199
  };
2050
3200
  }
2051
- logs.push({ type: "info", message: `Processing ${basename5(input)}...` });
3201
+ logs.push({ type: "info", message: `Processing ${basename9(input)}...` });
2052
3202
  const options = {
2053
3203
  fuzz: opts.fuzz ?? defaults.fuzz,
2054
3204
  doTrim: opts.trim ?? defaults.trim,
@@ -2064,7 +3214,7 @@ async function runGUI4(inputRaw) {
2064
3214
  const sizeStr = finalDims ? ` (${finalDims[0]}x${finalDims[1]})` : "";
2065
3215
  return {
2066
3216
  success: true,
2067
- output: `${basename5(output)}${sizeStr}`,
3217
+ output: `${basename9(output)}${sizeStr}`,
2068
3218
  logs
2069
3219
  };
2070
3220
  }
@@ -2078,8 +3228,9 @@ async function runGUI4(inputRaw) {
2078
3228
  }
2079
3229
  async function processImageSilent(input, borderColor, options, logs) {
2080
3230
  const fileInfo = getFileInfo(input);
2081
- const output = `${fileInfo.dirname}/${fileInfo.filename}_nobg.png`;
2082
- const tempFile = `${fileInfo.dirname}/${fileInfo.filename}_temp.png`;
3231
+ const outputExt = fileInfo.extension.toLowerCase() === ".gif" ? ".gif" : ".png";
3232
+ const output = `${fileInfo.dirname}/${fileInfo.filename}_nobg${outputExt}`;
3233
+ const tempFile = `${fileInfo.dirname}/${fileInfo.filename}_temp${outputExt}`;
2083
3234
  let bgRemoved = false;
2084
3235
  if (options.preserveInner && borderColor) {
2085
3236
  bgRemoved = await removeBackgroundBorderOnly(
@@ -2115,8 +3266,8 @@ async function processImageSilent(input, borderColor, options, logs) {
2115
3266
  }
2116
3267
  if (options.makeSquare) {
2117
3268
  logs.push({ type: "info", message: "Making square..." });
2118
- const tempSquare = `${fileInfo.dirname}/${fileInfo.filename}_square_temp.png`;
2119
- if (await squarify2(output, tempSquare)) {
3269
+ const tempSquare = `${fileInfo.dirname}/${fileInfo.filename}_square_temp${outputExt}`;
3270
+ if (await squarify(output, tempSquare)) {
2120
3271
  renameSync2(tempSquare, output);
2121
3272
  logs.push({ type: "success", message: "Made square" });
2122
3273
  } else {
@@ -2126,20 +3277,29 @@ async function processImageSilent(input, borderColor, options, logs) {
2126
3277
  }
2127
3278
  return true;
2128
3279
  }
2129
- async function generatePreview2(input, borderColor, options) {
2130
- const tempDir = tmpdir3();
3280
+ async function generatePreview5(input, borderColor, options) {
3281
+ const tempDir = tmpdir7();
2131
3282
  const timestamp = Date.now();
2132
- const tempFile = join6(tempDir, `piclet-preview-${timestamp}.png`);
2133
- const tempOutput = join6(tempDir, `piclet-preview-${timestamp}-out.png`);
3283
+ const tempSource = join10(tempDir, `piclet-preview-${timestamp}-src.png`);
3284
+ const tempFile = join10(tempDir, `piclet-preview-${timestamp}.png`);
3285
+ const tempOutput = join10(tempDir, `piclet-preview-${timestamp}-out.png`);
2134
3286
  try {
3287
+ let previewInput = input;
3288
+ if (isMultiFrame(input)) {
3289
+ if (!await extractFirstFrame(input, tempSource)) {
3290
+ return { success: false, error: "Failed to extract frame" };
3291
+ }
3292
+ previewInput = tempSource;
3293
+ }
2135
3294
  let bgRemoved = false;
2136
3295
  if (options.preserveInner && borderColor) {
2137
- bgRemoved = await removeBackgroundBorderOnly(input, tempFile, borderColor, options.fuzz);
3296
+ bgRemoved = await removeBackgroundBorderOnly(previewInput, tempFile, borderColor, options.fuzz);
2138
3297
  }
2139
3298
  if (!bgRemoved && borderColor) {
2140
- bgRemoved = await removeBackground(input, tempFile, borderColor, options.fuzz);
3299
+ bgRemoved = await removeBackground(previewInput, tempFile, borderColor, options.fuzz);
2141
3300
  }
2142
3301
  if (!bgRemoved) {
3302
+ cleanup(tempSource);
2143
3303
  return { success: false, error: "Background removal failed" };
2144
3304
  }
2145
3305
  let currentFile = tempFile;
@@ -2150,17 +3310,17 @@ async function generatePreview2(input, borderColor, options) {
2150
3310
  }
2151
3311
  }
2152
3312
  if (options.makeSquare) {
2153
- const squareFile = join6(tempDir, `piclet-preview-${timestamp}-sq.png`);
2154
- if (await squarify2(currentFile, squareFile)) {
3313
+ const squareFile = join10(tempDir, `piclet-preview-${timestamp}-sq.png`);
3314
+ if (await squarify(currentFile, squareFile)) {
2155
3315
  cleanup(currentFile);
2156
3316
  currentFile = squareFile;
2157
3317
  }
2158
3318
  }
2159
- const buffer = readFileSync5(currentFile);
3319
+ const buffer = readFileSync9(currentFile);
2160
3320
  const base64 = buffer.toString("base64");
2161
3321
  const imageData = `data:image/png;base64,${base64}`;
2162
3322
  const dims = await getDimensions(currentFile);
2163
- cleanup(currentFile, tempFile, tempOutput);
3323
+ cleanup(currentFile, tempFile, tempOutput, tempSource);
2164
3324
  return {
2165
3325
  success: true,
2166
3326
  imageData,
@@ -2168,22 +3328,22 @@ async function generatePreview2(input, borderColor, options) {
2168
3328
  height: dims?.[1]
2169
3329
  };
2170
3330
  } catch (err) {
2171
- cleanup(tempFile, tempOutput);
3331
+ cleanup(tempFile, tempOutput, tempSource);
2172
3332
  return { success: false, error: err.message };
2173
3333
  }
2174
3334
  }
2175
- var config4 = {
3335
+ var config8 = {
2176
3336
  id: "remove-bg",
2177
3337
  name: "Remove Background",
2178
3338
  icon: "removebg.ico",
2179
- extensions: [".png", ".jpg", ".jpeg", ".ico"]
3339
+ extensions: [".png", ".jpg", ".jpeg", ".ico", ".gif"]
2180
3340
  };
2181
3341
 
2182
3342
  // src/tools/rescale.ts
2183
- import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
2184
- import { tmpdir as tmpdir4 } from "os";
2185
- import { basename as basename6, join as join7 } from "path";
2186
- async function run4(inputRaw) {
3343
+ import { existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
3344
+ import { tmpdir as tmpdir8 } from "os";
3345
+ import { basename as basename10, join as join11 } from "path";
3346
+ async function run8(inputRaw) {
2187
3347
  if (!await checkImageMagick()) {
2188
3348
  error("ImageMagick not found. Please install it:");
2189
3349
  console.log(" sudo apt update && sudo apt install imagemagick");
@@ -2191,7 +3351,7 @@ async function run4(inputRaw) {
2191
3351
  return false;
2192
3352
  }
2193
3353
  const input = normalizePath(inputRaw);
2194
- if (!existsSync8(input)) {
3354
+ if (!existsSync12(input)) {
2195
3355
  error(`File not found: ${input}`);
2196
3356
  await pauseOnError();
2197
3357
  return false;
@@ -2252,7 +3412,7 @@ async function run4(inputRaw) {
2252
3412
  } else {
2253
3413
  scaled = await resize(input, output, targetW, targetH);
2254
3414
  }
2255
- if (!scaled || !existsSync8(output)) {
3415
+ if (!scaled || !existsSync12(output)) {
2256
3416
  wipDone(false, "Scaling failed");
2257
3417
  return false;
2258
3418
  }
@@ -2266,9 +3426,9 @@ async function run4(inputRaw) {
2266
3426
  success(`Output: ${output}`);
2267
3427
  return true;
2268
3428
  }
2269
- async function runGUI5(inputRaw) {
3429
+ async function runGUI9(inputRaw) {
2270
3430
  const input = normalizePath(inputRaw);
2271
- if (!existsSync8(input)) {
3431
+ if (!existsSync12(input)) {
2272
3432
  error(`File not found: ${input}`);
2273
3433
  return false;
2274
3434
  }
@@ -2283,7 +3443,7 @@ async function runGUI5(inputRaw) {
2283
3443
  title: "PicLet - Scale Image",
2284
3444
  imageInfo: {
2285
3445
  filePath: input,
2286
- fileName: basename6(input),
3446
+ fileName: basename10(input),
2287
3447
  width: dims[0],
2288
3448
  height: dims[1],
2289
3449
  borderColor: null
@@ -2297,7 +3457,7 @@ async function runGUI5(inputRaw) {
2297
3457
  height: opts.height ?? Math.round(dims[1] / 2),
2298
3458
  makeSquare: opts.makeSquare ?? false
2299
3459
  };
2300
- return generatePreview3(input, options);
3460
+ return generatePreview6(input, options);
2301
3461
  },
2302
3462
  onProcess: async (opts) => {
2303
3463
  const logs = [];
@@ -2322,13 +3482,13 @@ async function runGUI5(inputRaw) {
2322
3482
  } else {
2323
3483
  scaled = await resize(input, output, options.width, options.height);
2324
3484
  }
2325
- if (scaled && existsSync8(output)) {
3485
+ if (scaled && existsSync12(output)) {
2326
3486
  const finalDims = await getDimensions(output);
2327
3487
  const sizeStr = finalDims ? ` (${finalDims[0]}x${finalDims[1]})` : "";
2328
3488
  logs.push({ type: "success", message: "Scaled successfully" });
2329
3489
  return {
2330
3490
  success: true,
2331
- output: `${basename6(output)}${sizeStr}`,
3491
+ output: `${basename10(output)}${sizeStr}`,
2332
3492
  logs
2333
3493
  };
2334
3494
  }
@@ -2337,11 +3497,19 @@ async function runGUI5(inputRaw) {
2337
3497
  }
2338
3498
  });
2339
3499
  }
2340
- async function generatePreview3(input, options) {
2341
- const tempDir = tmpdir4();
3500
+ async function generatePreview6(input, options) {
3501
+ const tempDir = tmpdir8();
2342
3502
  const timestamp = Date.now();
2343
- const tempOutput = join7(tempDir, `piclet-preview-${timestamp}.png`);
3503
+ const tempSource = join11(tempDir, `piclet-preview-${timestamp}-src.png`);
3504
+ const tempOutput = join11(tempDir, `piclet-preview-${timestamp}.png`);
2344
3505
  try {
3506
+ let previewInput = input;
3507
+ if (isMultiFrame(input)) {
3508
+ if (!await extractFirstFrame(input, tempSource)) {
3509
+ return { success: false, error: "Failed to extract frame" };
3510
+ }
3511
+ previewInput = tempSource;
3512
+ }
2345
3513
  let scaled = false;
2346
3514
  let targetW = options.width;
2347
3515
  let targetH = options.height;
@@ -2349,18 +3517,19 @@ async function generatePreview3(input, options) {
2349
3517
  const maxDim = Math.max(targetW, targetH);
2350
3518
  targetW = maxDim;
2351
3519
  targetH = maxDim;
2352
- scaled = await scaleWithPadding(input, tempOutput, targetW, targetH);
3520
+ scaled = await scaleWithPadding(previewInput, tempOutput, targetW, targetH);
2353
3521
  } else {
2354
- scaled = await resize(input, tempOutput, targetW, targetH);
3522
+ scaled = await resize(previewInput, tempOutput, targetW, targetH);
2355
3523
  }
2356
- if (!scaled || !existsSync8(tempOutput)) {
3524
+ if (!scaled || !existsSync12(tempOutput)) {
3525
+ cleanup(tempSource);
2357
3526
  return { success: false, error: "Scaling failed" };
2358
3527
  }
2359
- const buffer = readFileSync6(tempOutput);
3528
+ const buffer = readFileSync10(tempOutput);
2360
3529
  const base64 = buffer.toString("base64");
2361
3530
  const imageData = `data:image/png;base64,${base64}`;
2362
3531
  const dims = await getDimensions(tempOutput);
2363
- cleanup(tempOutput);
3532
+ cleanup(tempSource, tempOutput);
2364
3533
  return {
2365
3534
  success: true,
2366
3535
  imageData,
@@ -2368,11 +3537,11 @@ async function generatePreview3(input, options) {
2368
3537
  height: dims?.[1]
2369
3538
  };
2370
3539
  } catch (err) {
2371
- cleanup(tempOutput);
3540
+ cleanup(tempSource, tempOutput);
2372
3541
  return { success: false, error: err.message };
2373
3542
  }
2374
3543
  }
2375
- var config5 = {
3544
+ var config9 = {
2376
3545
  id: "rescale",
2377
3546
  name: "Scale Image",
2378
3547
  icon: "rescale.ico",
@@ -2380,8 +3549,8 @@ var config5 = {
2380
3549
  };
2381
3550
 
2382
3551
  // src/tools/storepack.ts
2383
- import { existsSync as existsSync9, mkdirSync as mkdirSync6 } from "fs";
2384
- import { basename as basename7, join as join8 } from "path";
3552
+ import { existsSync as existsSync13, mkdirSync as mkdirSync7 } from "fs";
3553
+ import { basename as basename11, join as join12 } from "path";
2385
3554
  async function scaleImage(input, output, width, height, mode) {
2386
3555
  switch (mode) {
2387
3556
  case "fill":
@@ -2398,7 +3567,7 @@ async function generatePresetImages(sourceImg, outputDir, preset, scaleMode = "f
2398
3567
  const total = preset.icons.length;
2399
3568
  for (let i = 0; i < total; i++) {
2400
3569
  const icon = preset.icons[i];
2401
- const outputPath = join8(outputDir, icon.filename);
3570
+ const outputPath = join12(outputDir, icon.filename);
2402
3571
  if (logs) {
2403
3572
  logs.push({ type: "info", message: `[${i + 1}/${total}] ${icon.filename}` });
2404
3573
  } else {
@@ -2426,7 +3595,7 @@ async function generatePresetImages(sourceImg, outputDir, preset, scaleMode = "f
2426
3595
  }
2427
3596
  return failed;
2428
3597
  }
2429
- async function run5(inputRaw) {
3598
+ async function run9(inputRaw) {
2430
3599
  if (!await checkImageMagick()) {
2431
3600
  error("ImageMagick not found. Please install it:");
2432
3601
  console.log(" sudo apt update && sudo apt install imagemagick");
@@ -2434,7 +3603,7 @@ async function run5(inputRaw) {
2434
3603
  return false;
2435
3604
  }
2436
3605
  const input = normalizePath(inputRaw);
2437
- if (!existsSync9(input)) {
3606
+ if (!existsSync13(input)) {
2438
3607
  error(`File not found: ${input}`);
2439
3608
  await pauseOnError();
2440
3609
  return false;
@@ -2462,11 +3631,11 @@ async function run5(inputRaw) {
2462
3631
  return false;
2463
3632
  }
2464
3633
  const outputDir = `${fileInfo.dirname}/${fileInfo.filename}_${preset.id}`;
2465
- mkdirSync6(outputDir, { recursive: true });
3634
+ mkdirSync7(outputDir, { recursive: true });
2466
3635
  info(`Output: ${outputDir}`);
2467
3636
  console.log("");
2468
3637
  wip("Preparing source...");
2469
- const tempSource = join8(outputDir, ".source.png");
3638
+ const tempSource = join12(outputDir, ".source.png");
2470
3639
  if (!await squarify(input, tempSource)) {
2471
3640
  wipDone(false, "Failed to prepare source");
2472
3641
  return false;
@@ -2482,12 +3651,187 @@ async function run5(inputRaw) {
2482
3651
  } else {
2483
3652
  error(`${failed}/${preset.icons.length} images failed`);
2484
3653
  }
2485
- info(`Output: ${outputDir}`);
2486
- return failed === 0;
3654
+ info(`Output: ${outputDir}`);
3655
+ return failed === 0;
3656
+ }
3657
+ async function runGUI10(inputRaw) {
3658
+ const input = normalizePath(inputRaw);
3659
+ if (!existsSync13(input)) {
3660
+ error(`File not found: ${input}`);
3661
+ return false;
3662
+ }
3663
+ const dims = await getDimensions(input);
3664
+ if (!dims) {
3665
+ error("Failed to read image dimensions");
3666
+ return false;
3667
+ }
3668
+ const fileInfo = getFileInfo(input);
3669
+ const presets = loadPresets();
3670
+ return startGuiServer({
3671
+ htmlFile: "storepack.html",
3672
+ title: "PicLet - Store Pack",
3673
+ imageInfo: {
3674
+ filePath: input,
3675
+ fileName: basename11(input),
3676
+ width: dims[0],
3677
+ height: dims[1],
3678
+ borderColor: null
3679
+ },
3680
+ defaults: {
3681
+ presets: presets.map((p) => ({
3682
+ id: p.id,
3683
+ name: p.name,
3684
+ description: p.description,
3685
+ icons: p.icons
3686
+ }))
3687
+ },
3688
+ onProcess: async (opts) => {
3689
+ const logs = [];
3690
+ if (!await checkImageMagick()) {
3691
+ return {
3692
+ success: false,
3693
+ error: "ImageMagick not found",
3694
+ logs: [{ type: "error", message: "ImageMagick not found" }]
3695
+ };
3696
+ }
3697
+ const presetId = opts.preset;
3698
+ const scaleMode = opts.scaleMode || "fit";
3699
+ const preset = presets.find((p) => p.id === presetId);
3700
+ if (!preset) {
3701
+ return {
3702
+ success: false,
3703
+ error: "Preset not found",
3704
+ logs: [{ type: "error", message: "No preset selected" }]
3705
+ };
3706
+ }
3707
+ const outputDir = `${fileInfo.dirname}/${fileInfo.filename}_${preset.id}`;
3708
+ mkdirSync7(outputDir, { recursive: true });
3709
+ logs.push({ type: "info", message: `Output: ${outputDir}` });
3710
+ logs.push({ type: "info", message: `Scale mode: ${scaleMode}` });
3711
+ logs.push({ type: "info", message: `Generating ${preset.icons.length} images...` });
3712
+ const failed = await generatePresetImages(input, outputDir, preset, scaleMode, logs);
3713
+ if (failed === 0) {
3714
+ logs.push({ type: "success", message: `All ${preset.icons.length} images generated` });
3715
+ return {
3716
+ success: true,
3717
+ output: `${preset.icons.length} images saved to ${fileInfo.filename}_${preset.id}/`,
3718
+ logs
3719
+ };
3720
+ }
3721
+ return {
3722
+ success: false,
3723
+ error: `${failed} image(s) failed`,
3724
+ logs
3725
+ };
3726
+ }
3727
+ });
3728
+ }
3729
+ var config10 = {
3730
+ id: "storepack",
3731
+ name: "Store Pack",
3732
+ icon: "storepack.ico",
3733
+ extensions: [".png", ".jpg", ".jpeg", ".gif"]
3734
+ };
3735
+
3736
+ // src/tools/transform.ts
3737
+ import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
3738
+ import { tmpdir as tmpdir9 } from "os";
3739
+ import { basename as basename12, join as join13 } from "path";
3740
+ var TRANSFORM_LABELS = {
3741
+ "flip-h": "Flip Horizontal",
3742
+ "flip-v": "Flip Vertical",
3743
+ "rotate-90": "Rotate 90\xB0",
3744
+ "rotate-180": "Rotate 180\xB0",
3745
+ "rotate-270": "Rotate 270\xB0"
3746
+ };
3747
+ async function processImage5(input, options) {
3748
+ const fileInfo = getFileInfo(input);
3749
+ const outputExt = fileInfo.extension.toLowerCase() === ".gif" ? ".gif" : ".png";
3750
+ const suffix = options.transform.replace("-", "");
3751
+ const output = `${fileInfo.dirname}/${fileInfo.filename}_${suffix}${outputExt}`;
3752
+ wip(`Applying ${TRANSFORM_LABELS[options.transform]}...`);
3753
+ let success2 = false;
3754
+ switch (options.transform) {
3755
+ case "flip-h":
3756
+ success2 = await flipHorizontal(input, output);
3757
+ break;
3758
+ case "flip-v":
3759
+ success2 = await flipVertical(input, output);
3760
+ break;
3761
+ case "rotate-90":
3762
+ success2 = await rotate(input, output, 90);
3763
+ break;
3764
+ case "rotate-180":
3765
+ success2 = await rotate(input, output, 180);
3766
+ break;
3767
+ case "rotate-270":
3768
+ success2 = await rotate(input, output, 270);
3769
+ break;
3770
+ }
3771
+ if (!success2) {
3772
+ wipDone(false, "Transform failed");
3773
+ return false;
3774
+ }
3775
+ wipDone(true, TRANSFORM_LABELS[options.transform]);
3776
+ const finalDims = await getDimensions(output);
3777
+ console.log("");
3778
+ if (finalDims) {
3779
+ console.log(`Output: ${output} (${finalDims[0]}x${finalDims[1]})`);
3780
+ } else {
3781
+ console.log(`Output: ${output}`);
3782
+ }
3783
+ return true;
3784
+ }
3785
+ async function collectOptionsCLI5() {
3786
+ console.log("");
3787
+ console.log(`${BOLD}Transform Options:${RESET}`);
3788
+ console.log(` ${DIM}1. Flip Horizontal (mirror)${RESET}`);
3789
+ console.log(` ${DIM}2. Flip Vertical${RESET}`);
3790
+ console.log(` ${DIM}3. Rotate 90\xB0 clockwise${RESET}`);
3791
+ console.log(` ${DIM}4. Rotate 180\xB0${RESET}`);
3792
+ console.log(` ${DIM}5. Rotate 270\xB0 clockwise${RESET}`);
3793
+ console.log("");
3794
+ const transform = await select(
3795
+ "Select transform",
3796
+ [
3797
+ { value: "flip-h", title: "Flip Horizontal" },
3798
+ { value: "flip-v", title: "Flip Vertical" },
3799
+ { value: "rotate-90", title: "Rotate 90\xB0" },
3800
+ { value: "rotate-180", title: "Rotate 180\xB0" },
3801
+ { value: "rotate-270", title: "Rotate 270\xB0" }
3802
+ ],
3803
+ "flip-h"
3804
+ );
3805
+ return { transform: transform ?? "flip-h" };
3806
+ }
3807
+ async function run10(inputRaw) {
3808
+ if (!await checkImageMagick()) {
3809
+ error("ImageMagick not found. Please install it:");
3810
+ console.log(" sudo apt update && sudo apt install imagemagick");
3811
+ await pauseOnError();
3812
+ return false;
3813
+ }
3814
+ const input = normalizePath(inputRaw);
3815
+ if (!existsSync14(input)) {
3816
+ error(`File not found: ${input}`);
3817
+ await pauseOnError();
3818
+ return false;
3819
+ }
3820
+ header("PicLet Transform");
3821
+ wip("Analyzing image...");
3822
+ const dims = await getDimensions(input);
3823
+ if (!dims) {
3824
+ wipDone(false, "Failed to read image");
3825
+ return false;
3826
+ }
3827
+ wipDone(true, `Size: ${dims[0]}x${dims[1]}`);
3828
+ const options = await collectOptionsCLI5();
3829
+ console.log("");
3830
+ return processImage5(input, options);
2487
3831
  }
2488
- async function runGUI6(inputRaw) {
3832
+ async function runGUI11(inputRaw) {
2489
3833
  const input = normalizePath(inputRaw);
2490
- if (!existsSync9(input)) {
3834
+ if (!existsSync14(input)) {
2491
3835
  error(`File not found: ${input}`);
2492
3836
  return false;
2493
3837
  }
@@ -2496,25 +3840,22 @@ async function runGUI6(inputRaw) {
2496
3840
  error("Failed to read image dimensions");
2497
3841
  return false;
2498
3842
  }
2499
- const fileInfo = getFileInfo(input);
2500
- const presets = loadPresets();
2501
3843
  return startGuiServer({
2502
- htmlFile: "storepack.html",
2503
- title: "PicLet - Store Pack",
3844
+ htmlFile: "transform.html",
3845
+ title: "PicLet - Transform",
2504
3846
  imageInfo: {
2505
3847
  filePath: input,
2506
- fileName: basename7(input),
3848
+ fileName: basename12(input),
2507
3849
  width: dims[0],
2508
3850
  height: dims[1],
2509
3851
  borderColor: null
2510
3852
  },
2511
3853
  defaults: {
2512
- presets: presets.map((p) => ({
2513
- id: p.id,
2514
- name: p.name,
2515
- description: p.description,
2516
- icons: p.icons
2517
- }))
3854
+ transform: "flip-h"
3855
+ },
3856
+ onPreview: async (opts) => {
3857
+ const transform = opts.transform ?? "flip-h";
3858
+ return generatePreview7(input, { transform });
2518
3859
  },
2519
3860
  onProcess: async (opts) => {
2520
3861
  const logs = [];
@@ -2522,67 +3863,136 @@ async function runGUI6(inputRaw) {
2522
3863
  return {
2523
3864
  success: false,
2524
3865
  error: "ImageMagick not found",
2525
- logs: [{ type: "error", message: "ImageMagick not found" }]
3866
+ logs: [{ type: "error", message: "ImageMagick not found. Install with: sudo apt install imagemagick" }]
2526
3867
  };
2527
3868
  }
2528
- const presetId = opts.preset;
2529
- const scaleMode = opts.scaleMode || "fit";
2530
- const preset = presets.find((p) => p.id === presetId);
2531
- if (!preset) {
2532
- return {
2533
- success: false,
2534
- error: "Preset not found",
2535
- logs: [{ type: "error", message: "No preset selected" }]
2536
- };
3869
+ logs.push({ type: "info", message: `Processing ${basename12(input)}...` });
3870
+ const transform = opts.transform ?? "flip-h";
3871
+ const options = { transform };
3872
+ const fileInfo = getFileInfo(input);
3873
+ const outputExt = fileInfo.extension.toLowerCase() === ".gif" ? ".gif" : ".png";
3874
+ const suffix = transform.replace("-", "");
3875
+ const output = `${fileInfo.dirname}/${fileInfo.filename}_${suffix}${outputExt}`;
3876
+ logs.push({ type: "info", message: `Applying ${TRANSFORM_LABELS[transform]}...` });
3877
+ let success2 = false;
3878
+ switch (transform) {
3879
+ case "flip-h":
3880
+ success2 = await flipHorizontal(input, output);
3881
+ break;
3882
+ case "flip-v":
3883
+ success2 = await flipVertical(input, output);
3884
+ break;
3885
+ case "rotate-90":
3886
+ success2 = await rotate(input, output, 90);
3887
+ break;
3888
+ case "rotate-180":
3889
+ success2 = await rotate(input, output, 180);
3890
+ break;
3891
+ case "rotate-270":
3892
+ success2 = await rotate(input, output, 270);
3893
+ break;
2537
3894
  }
2538
- const outputDir = `${fileInfo.dirname}/${fileInfo.filename}_${preset.id}`;
2539
- mkdirSync6(outputDir, { recursive: true });
2540
- logs.push({ type: "info", message: `Output: ${outputDir}` });
2541
- logs.push({ type: "info", message: `Scale mode: ${scaleMode}` });
2542
- logs.push({ type: "info", message: `Generating ${preset.icons.length} images...` });
2543
- const failed = await generatePresetImages(input, outputDir, preset, scaleMode, logs);
2544
- if (failed === 0) {
2545
- logs.push({ type: "success", message: `All ${preset.icons.length} images generated` });
3895
+ if (success2) {
3896
+ logs.push({ type: "success", message: "Transform applied" });
3897
+ const finalDims = await getDimensions(output);
3898
+ const sizeStr = finalDims ? ` (${finalDims[0]}x${finalDims[1]})` : "";
2546
3899
  return {
2547
3900
  success: true,
2548
- output: `${preset.icons.length} images saved to ${fileInfo.filename}_${preset.id}/`,
3901
+ output: `${basename12(output)}${sizeStr}`,
2549
3902
  logs
2550
3903
  };
2551
3904
  }
3905
+ logs.push({ type: "error", message: "Transform failed" });
2552
3906
  return {
2553
3907
  success: false,
2554
- error: `${failed} image(s) failed`,
3908
+ error: "Processing failed",
2555
3909
  logs
2556
3910
  };
2557
3911
  }
2558
3912
  });
2559
3913
  }
2560
- var config6 = {
2561
- id: "storepack",
2562
- name: "Store Pack",
2563
- icon: "storepack.ico",
2564
- extensions: [".png", ".jpg", ".jpeg"]
3914
+ async function generatePreview7(input, options) {
3915
+ const tempDir = tmpdir9();
3916
+ const timestamp = Date.now();
3917
+ const tempSource = join13(tempDir, `piclet-preview-${timestamp}-src.png`);
3918
+ const tempOutput = join13(tempDir, `piclet-preview-${timestamp}.png`);
3919
+ try {
3920
+ let previewInput = input;
3921
+ if (isMultiFrame(input)) {
3922
+ if (!await extractFirstFrame(input, tempSource)) {
3923
+ return { success: false, error: "Failed to extract frame" };
3924
+ }
3925
+ previewInput = tempSource;
3926
+ }
3927
+ let success2 = false;
3928
+ switch (options.transform) {
3929
+ case "flip-h":
3930
+ success2 = await flipHorizontal(previewInput, tempOutput);
3931
+ break;
3932
+ case "flip-v":
3933
+ success2 = await flipVertical(previewInput, tempOutput);
3934
+ break;
3935
+ case "rotate-90":
3936
+ success2 = await rotate(previewInput, tempOutput, 90);
3937
+ break;
3938
+ case "rotate-180":
3939
+ success2 = await rotate(previewInput, tempOutput, 180);
3940
+ break;
3941
+ case "rotate-270":
3942
+ success2 = await rotate(previewInput, tempOutput, 270);
3943
+ break;
3944
+ }
3945
+ if (!success2) {
3946
+ cleanup(tempSource, tempOutput);
3947
+ return { success: false, error: "Transform failed" };
3948
+ }
3949
+ const buffer = readFileSync11(tempOutput);
3950
+ const base64 = buffer.toString("base64");
3951
+ const imageData = `data:image/png;base64,${base64}`;
3952
+ const dims = await getDimensions(tempOutput);
3953
+ cleanup(tempSource, tempOutput);
3954
+ return {
3955
+ success: true,
3956
+ imageData,
3957
+ width: dims?.[0],
3958
+ height: dims?.[1]
3959
+ };
3960
+ } catch (err) {
3961
+ cleanup(tempSource, tempOutput);
3962
+ return { success: false, error: err.message };
3963
+ }
3964
+ }
3965
+ var config11 = {
3966
+ id: "transform",
3967
+ name: "Transform",
3968
+ icon: "transform.ico",
3969
+ extensions: [".png", ".jpg", ".jpeg", ".gif", ".bmp", ".ico"]
2565
3970
  };
2566
3971
 
2567
3972
  // src/cli/tools.ts
2568
3973
  var tools = [
2569
- { config: config2, run: run2, runGUI: runGUI2 },
2570
- { config: config4, run: run3, runGUI: runGUI4 },
2571
- { config: config5, run: run4, runGUI: runGUI5 },
3974
+ { config: config5, run: run5, runGUI: runGUI5 },
3975
+ { config: config8, run: run7, runGUI: runGUI8 },
3976
+ { config: config9, run: run8, runGUI: runGUI9 },
3977
+ { config: config4, run: run4, runGUI: runGUI4 },
3978
+ { config: config10, run: run9, runGUI: runGUI10 },
3979
+ { config: config11, run: run10, runGUI: runGUI11 },
3980
+ { config: config3, run: run3, runGUI: runGUI3 },
2572
3981
  { config, run, runGUI },
2573
- { config: config6, run: run5, runGUI: runGUI6 }
3982
+ { config: config7, run: run6, runGUI: runGUI7 },
3983
+ { config: config2, run: run2, runGUI: runGUI2 }
2574
3984
  ];
2575
3985
  var picletTool = {
2576
- config: config3,
2577
- runGUI: runGUI3
3986
+ config: config6,
3987
+ runGUI: runGUI6
2578
3988
  };
2579
3989
  function getTool(id) {
2580
3990
  return tools.find((t) => t.config.id === id);
2581
3991
  }
2582
3992
  function getAllExtensions() {
2583
3993
  const extensions = /* @__PURE__ */ new Set();
2584
- for (const { config: config7 } of tools) {
2585
- for (const ext of config7.extensions) {
3994
+ for (const { config: config12 } of tools) {
3995
+ for (const ext of config12.extensions) {
2586
3996
  extensions.add(ext);
2587
3997
  }
2588
3998
  }
@@ -2616,17 +4026,17 @@ function validateExtensions(files, allowedExtensions) {
2616
4026
  async function runToolOnFiles(toolId, files, useYes) {
2617
4027
  const tool = getTool(toolId);
2618
4028
  if (!tool) {
2619
- console.error(chalk2.red(`Tool not found: ${toolId}`));
4029
+ console.error(chalk.red(`Tool not found: ${toolId}`));
2620
4030
  return false;
2621
4031
  }
2622
4032
  const { valid, invalid } = validateExtensions(files, tool.config.extensions);
2623
4033
  if (invalid.length > 0) {
2624
- console.error(chalk2.red("Invalid file types:"));
4034
+ console.error(chalk.red("Invalid file types:"));
2625
4035
  for (const file of invalid) {
2626
- console.error(chalk2.red(` \u2717 ${file}`));
4036
+ console.error(chalk.red(` \u2717 ${file}`));
2627
4037
  }
2628
4038
  console.error(
2629
- chalk2.yellow(
4039
+ chalk.yellow(
2630
4040
  `
2631
4041
  Supported extensions: ${tool.config.extensions.join(", ")}`
2632
4042
  )
@@ -2641,7 +4051,7 @@ Supported extensions: ${tool.config.extensions.join(", ")}`
2641
4051
  for (let i = 0; i < valid.length; i++) {
2642
4052
  const file = valid[i];
2643
4053
  if (valid.length > 1) {
2644
- console.log(chalk2.cyan(`
4054
+ console.log(chalk.cyan(`
2645
4055
  [${i + 1}/${valid.length}] ${file}`));
2646
4056
  }
2647
4057
  const success2 = await tool.run(file);
@@ -2650,21 +4060,126 @@ Supported extensions: ${tool.config.extensions.join(", ")}`
2650
4060
  return allSuccess;
2651
4061
  }
2652
4062
 
4063
+ // src/cli/commands/border.ts
4064
+ function registerBorderCommand(program2) {
4065
+ program2.command("border <files...>").description("Add solid color border to images").option("-y, --yes", "Use defaults, skip prompts").option("-g, --gui", "Use GUI for options").action(async (files, options) => {
4066
+ if (options.gui) {
4067
+ const { valid, invalid } = validateExtensions(files, config.extensions);
4068
+ if (invalid.length > 0) {
4069
+ console.error(chalk2.red("Invalid file types:"));
4070
+ for (const file of invalid) {
4071
+ console.error(chalk2.red(` - ${file}`));
4072
+ }
4073
+ }
4074
+ if (valid.length === 0) {
4075
+ process.exit(1);
4076
+ }
4077
+ const result = await runGUI(valid[0]);
4078
+ process.exit(result ? 0 : 1);
4079
+ }
4080
+ const success2 = await runToolOnFiles(
4081
+ "border",
4082
+ files,
4083
+ options.yes ?? false
4084
+ );
4085
+ process.exit(success2 ? 0 : 1);
4086
+ });
4087
+ }
4088
+
4089
+ // src/cli/commands/config.ts
4090
+ import chalk3 from "chalk";
4091
+ function registerConfigCommand(program2) {
4092
+ const configCmd = program2.command("config").description("Display current settings").action(() => {
4093
+ const config12 = loadConfig();
4094
+ console.log(chalk3.white.bold("\n PicLet Configuration"));
4095
+ console.log(chalk3.gray(` ${getConfigPath()}
4096
+ `));
4097
+ console.log(JSON.stringify(config12, null, 2));
4098
+ console.log();
4099
+ });
4100
+ configCmd.command("reset").description("Restore defaults").action(() => {
4101
+ resetConfig();
4102
+ console.log(chalk3.green("Configuration reset to defaults."));
4103
+ });
4104
+ }
4105
+
4106
+ // src/cli/commands/extract-frames.ts
4107
+ import chalk4 from "chalk";
4108
+ function registerExtractFramesCommand(program2) {
4109
+ program2.command("extract-frames <files...>").alias("frames").description("Extract frames from animated GIF").option("-y, --yes", "Use defaults, skip prompts").option("-g, --gui", "Use GUI for options").action(async (files, options) => {
4110
+ if (options.gui) {
4111
+ const { valid, invalid } = validateExtensions(files, config2.extensions);
4112
+ if (invalid.length > 0) {
4113
+ console.error(chalk4.red("Invalid file types:"));
4114
+ for (const file of invalid) {
4115
+ console.error(chalk4.red(` - ${file}`));
4116
+ }
4117
+ }
4118
+ if (valid.length === 0) {
4119
+ process.exit(1);
4120
+ }
4121
+ const result = await runGUI2(valid[0]);
4122
+ process.exit(result ? 0 : 1);
4123
+ }
4124
+ const success2 = await runToolOnFiles(
4125
+ "extract-frames",
4126
+ files,
4127
+ options.yes ?? false
4128
+ );
4129
+ process.exit(success2 ? 0 : 1);
4130
+ });
4131
+ }
4132
+
4133
+ // src/cli/commands/filter.ts
4134
+ import chalk5 from "chalk";
4135
+ function registerFilterCommand(program2) {
4136
+ program2.command("filter <files...>").description("Apply color filters (grayscale, sepia, etc.)").option("-y, --yes", "Use defaults, skip prompts").option("-g, --gui", "Use GUI for options").action(async (files, options) => {
4137
+ if (options.gui) {
4138
+ const { valid, invalid } = validateExtensions(files, config3.extensions);
4139
+ if (invalid.length > 0) {
4140
+ console.error(chalk5.red("Invalid file types:"));
4141
+ for (const file of invalid) {
4142
+ console.error(chalk5.red(` - ${file}`));
4143
+ }
4144
+ }
4145
+ if (valid.length === 0) {
4146
+ process.exit(1);
4147
+ }
4148
+ const result = await runGUI3(valid[0]);
4149
+ process.exit(result ? 0 : 1);
4150
+ }
4151
+ const success2 = await runToolOnFiles(
4152
+ "filter",
4153
+ files,
4154
+ options.yes ?? false
4155
+ );
4156
+ process.exit(success2 ? 0 : 1);
4157
+ });
4158
+ }
4159
+
4160
+ // src/cli/commands/help.ts
4161
+ function registerHelpCommand(program2) {
4162
+ program2.command("help").description("Show help").action(() => {
4163
+ showHelp();
4164
+ });
4165
+ }
4166
+
2653
4167
  // src/cli/commands/iconpack.ts
4168
+ import chalk6 from "chalk";
2654
4169
  function registerIconpackCommand(program2) {
2655
4170
  program2.command("iconpack <files...>").description("Generate icon sets for Web, Android, iOS").option("-y, --yes", "Use defaults, skip prompts").option("-g, --gui", "Use GUI for options").action(async (files, options) => {
2656
4171
  if (options.gui) {
2657
- const { valid, invalid } = validateExtensions(files, config.extensions);
4172
+ const { valid, invalid } = validateExtensions(files, config4.extensions);
2658
4173
  if (invalid.length > 0) {
2659
- console.error(chalk3.red("Invalid file types:"));
4174
+ console.error(chalk6.red("Invalid file types:"));
2660
4175
  for (const file of invalid) {
2661
- console.error(chalk3.red(` - ${file}`));
4176
+ console.error(chalk6.red(` - ${file}`));
2662
4177
  }
2663
4178
  }
2664
4179
  if (valid.length === 0) {
2665
4180
  process.exit(1);
2666
4181
  }
2667
- const result = await runGUI(valid[0]);
4182
+ const result = await runGUI4(valid[0]);
2668
4183
  process.exit(result ? 0 : 1);
2669
4184
  }
2670
4185
  const success2 = await runToolOnFiles(
@@ -2677,11 +4192,11 @@ function registerIconpackCommand(program2) {
2677
4192
  }
2678
4193
 
2679
4194
  // src/cli/commands/install.ts
2680
- import chalk4 from "chalk";
4195
+ import chalk7 from "chalk";
2681
4196
 
2682
4197
  // src/lib/registry.ts
2683
4198
  import { exec as exec2 } from "child_process";
2684
- import { existsSync as existsSync10 } from "fs";
4199
+ import { existsSync as existsSync15 } from "fs";
2685
4200
  import { dirname as dirname10 } from "path";
2686
4201
  import { fileURLToPath as fileURLToPath3 } from "url";
2687
4202
  import { promisify as promisify2 } from "util";
@@ -2690,7 +4205,7 @@ function isWSL() {
2690
4205
  return process.platform === "linux" && (process.env.WSL_DISTRO_NAME !== void 0 || process.env.WSLENV !== void 0);
2691
4206
  }
2692
4207
  function isWSLInteropEnabled() {
2693
- return existsSync10("/proc/sys/fs/binfmt_misc/WSLInterop");
4208
+ return existsSync15("/proc/sys/fs/binfmt_misc/WSLInterop");
2694
4209
  }
2695
4210
  async function addRegistryKey(keyPath, valueName, value, type = "REG_SZ") {
2696
4211
  const valueArg = valueName ? `/v "${valueName}"` : "/ve";
@@ -2732,7 +4247,7 @@ async function deleteRegistryKey(keyPath) {
2732
4247
 
2733
4248
  // src/cli/registry.ts
2734
4249
  import { writeFile } from "fs/promises";
2735
- import { join as join9 } from "path";
4250
+ import { join as join14 } from "path";
2736
4251
  async function registerUnifiedMenu(extension, iconsDir, launcherPath) {
2737
4252
  const basePath = `HKCU\\Software\\Classes\\SystemFileAssociations\\${extension}\\shell\\PicLet`;
2738
4253
  const iconsDirWin = wslToWindows(iconsDir);
@@ -2752,13 +4267,13 @@ async function unregisterMenuForExtension(extension) {
2752
4267
  const results = [];
2753
4268
  const basePath = getMenuBasePath(extension);
2754
4269
  const extensionTools = getToolsForExtension(extension);
2755
- for (const { config: config7 } of extensionTools) {
2756
- const toolPath = `${basePath}\\shell\\${config7.id}`;
4270
+ for (const { config: config12 } of extensionTools) {
4271
+ const toolPath = `${basePath}\\shell\\${config12.id}`;
2757
4272
  await deleteRegistryKey(`${toolPath}\\command`);
2758
4273
  const success2 = await deleteRegistryKey(toolPath);
2759
4274
  results.push({
2760
4275
  extension,
2761
- toolName: config7.name,
4276
+ toolName: config12.name,
2762
4277
  success: success2
2763
4278
  });
2764
4279
  }
@@ -2768,8 +4283,8 @@ async function unregisterMenuForExtension(extension) {
2768
4283
  }
2769
4284
  async function registerAllTools() {
2770
4285
  const distDir = getDistDir();
2771
- const iconsDir = join9(distDir, "icons");
2772
- const launcherPath = join9(distDir, "launcher.vbs");
4286
+ const iconsDir = join14(distDir, "icons");
4287
+ const launcherPath = join14(distDir, "launcher.vbs");
2773
4288
  const results = [];
2774
4289
  for (const extension of picletTool.config.extensions) {
2775
4290
  const result = await registerUnifiedMenu(extension, iconsDir, launcherPath);
@@ -2777,6 +4292,57 @@ async function registerAllTools() {
2777
4292
  }
2778
4293
  return results;
2779
4294
  }
4295
+ var LEGACY_TOOL_NAMES = [
4296
+ // Old direct menu items (various naming conventions)
4297
+ "Scale Image",
4298
+ "Resize Image",
4299
+ "Eale Image",
4300
+ // Corrupted entry
4301
+ "Remove Background",
4302
+ "Remove BG",
4303
+ "RemoveBG",
4304
+ "Make Icon",
4305
+ "MakeIcon",
4306
+ "Icon Pack",
4307
+ "IconPack",
4308
+ "Store Pack",
4309
+ "StorePack",
4310
+ "PicLet - Scale",
4311
+ "PicLet - Remove BG",
4312
+ "PicLet - Make Icon",
4313
+ "PicLet - Icon Pack",
4314
+ "PicLet Scale",
4315
+ "PicLet RemoveBG",
4316
+ // Tool IDs that might have been used as menu names
4317
+ "makeicon",
4318
+ "remove-bg",
4319
+ "removebg",
4320
+ "rescale",
4321
+ "scale",
4322
+ "iconpack",
4323
+ "storepack",
4324
+ "transform",
4325
+ "filter",
4326
+ "border",
4327
+ "recolor"
4328
+ ];
4329
+ var ALL_IMAGE_EXTENSIONS = [".png", ".jpg", ".jpeg", ".gif", ".bmp", ".ico", ".webp", ".tiff", ".tif"];
4330
+ async function cleanupLegacyEntries() {
4331
+ const removed = [];
4332
+ const failed = [];
4333
+ for (const ext of ALL_IMAGE_EXTENSIONS) {
4334
+ const shellBase = `HKCU\\Software\\Classes\\SystemFileAssociations\\${ext}\\shell`;
4335
+ for (const legacyName of LEGACY_TOOL_NAMES) {
4336
+ const keyPath = `${shellBase}\\${legacyName}`;
4337
+ await deleteRegistryKey(`${keyPath}\\command`);
4338
+ const success2 = await deleteRegistryKey(keyPath);
4339
+ if (success2) {
4340
+ removed.push(`${ext} \u2192 ${legacyName}`);
4341
+ }
4342
+ }
4343
+ }
4344
+ return { removed, failed };
4345
+ }
2780
4346
  async function unregisterAllTools() {
2781
4347
  const results = [];
2782
4348
  const allExts = /* @__PURE__ */ new Set([...getAllExtensions(), ...picletTool.config.extensions]);
@@ -2794,8 +4360,8 @@ function escapeRegValue(value) {
2794
4360
  }
2795
4361
  function generateRegContent() {
2796
4362
  const distDir = getDistDir();
2797
- const iconsDir = join9(distDir, "icons");
2798
- const launcherPath = join9(distDir, "launcher.vbs");
4363
+ const iconsDir = join14(distDir, "icons");
4364
+ const launcherPath = join14(distDir, "launcher.vbs");
2799
4365
  const iconsDirWin = wslToWindows(iconsDir);
2800
4366
  const launcherWin = wslToWindows(launcherPath);
2801
4367
  const lines = ["Windows Registry Editor Version 5.00", ""];
@@ -2821,18 +4387,25 @@ function generateUninstallRegContent() {
2821
4387
  lines.push(`[-${basePath}]`);
2822
4388
  lines.push("");
2823
4389
  }
4390
+ for (const ext of ALL_IMAGE_EXTENSIONS) {
4391
+ for (const legacyName of LEGACY_TOOL_NAMES) {
4392
+ const keyPath = `HKEY_CURRENT_USER\\Software\\Classes\\SystemFileAssociations\\${ext}\\shell\\${legacyName}`;
4393
+ lines.push(`[-${keyPath}]`);
4394
+ }
4395
+ lines.push("");
4396
+ }
2824
4397
  return lines.join("\r\n");
2825
4398
  }
2826
4399
  async function generateRegFile() {
2827
4400
  const distDir = getDistDir();
2828
- const regPath = join9(distDir, "piclet-install.reg");
4401
+ const regPath = join14(distDir, "piclet-install.reg");
2829
4402
  const content = generateRegContent();
2830
4403
  await writeFile(regPath, content, "utf-8");
2831
4404
  return regPath;
2832
4405
  }
2833
4406
  async function generateUninstallRegFile() {
2834
4407
  const distDir = getDistDir();
2835
- const regPath = join9(distDir, "piclet-uninstall.reg");
4408
+ const regPath = join14(distDir, "piclet-uninstall.reg");
2836
4409
  const content = generateUninstallRegContent();
2837
4410
  await writeFile(regPath, content, "utf-8");
2838
4411
  return regPath;
@@ -2842,50 +4415,50 @@ async function generateUninstallRegFile() {
2842
4415
  function registerInstallCommand(program2) {
2843
4416
  program2.command("install").description("Install Windows shell context menu integration").action(async () => {
2844
4417
  showBanner();
2845
- console.log(chalk4.bold("Installing...\n"));
4418
+ console.log(chalk7.bold("Installing...\n"));
2846
4419
  if (!isWSL()) {
2847
4420
  console.log(
2848
- chalk4.yellow("! Not running in WSL. Registry integration skipped.")
4421
+ chalk7.yellow("! Not running in WSL. Registry integration skipped.")
2849
4422
  );
2850
4423
  console.log(
2851
- chalk4.yellow('! Run "piclet install" from WSL to add context menu.')
4424
+ chalk7.yellow('! Run "piclet install" from WSL to add context menu.')
2852
4425
  );
2853
4426
  return;
2854
4427
  }
2855
4428
  if (!isWSLInteropEnabled()) {
2856
- console.log(chalk4.yellow("WSL Interop not available. Generating registry file...\n"));
4429
+ console.log(chalk7.yellow("WSL Interop not available. Generating registry file...\n"));
2857
4430
  const regPath = await generateRegFile();
2858
4431
  const winPath = wslToWindows(regPath);
2859
- console.log(chalk4.green("\u2713 Generated registry file:"));
2860
- console.log(chalk4.cyan(` ${winPath}
4432
+ console.log(chalk7.green("\u2713 Generated registry file:"));
4433
+ console.log(chalk7.cyan(` ${winPath}
2861
4434
  `));
2862
- console.log(chalk4.bold("To install, either:"));
2863
- console.log(chalk4.dim(" 1. Double-click the .reg file in Windows Explorer"));
2864
- console.log(chalk4.dim(` 2. Run in elevated PowerShell: reg import "${winPath}"`));
4435
+ console.log(chalk7.bold("To install, either:"));
4436
+ console.log(chalk7.dim(" 1. Double-click the .reg file in Windows Explorer"));
4437
+ console.log(chalk7.dim(` 2. Run in elevated PowerShell: reg import "${winPath}"`));
2865
4438
  console.log();
2866
4439
  return;
2867
4440
  }
2868
- console.log(chalk4.dim("Removing old entries..."));
4441
+ console.log(chalk7.dim("Removing old entries..."));
2869
4442
  await unregisterAllTools();
2870
4443
  console.log();
2871
4444
  const results = await registerAllTools();
2872
4445
  const allSuccess = results.every((r) => r.success);
2873
- for (const { config: config7 } of tools) {
2874
- const extList = config7.extensions.join(", ");
2875
- console.log(`${chalk4.green("\u2713")} ${config7.name} ${chalk4.dim(`[${extList}]`)}`);
4446
+ for (const { config: config12 } of tools) {
4447
+ const extList = config12.extensions.join(", ");
4448
+ console.log(`${chalk7.green("\u2713")} ${config12.name} ${chalk7.dim(`[${extList}]`)}`);
2876
4449
  }
2877
4450
  console.log();
2878
4451
  if (allSuccess) {
2879
4452
  console.log(
2880
- chalk4.green(`\u2713 Registered ${tools.length} tools for context menu.`)
4453
+ chalk7.green(`\u2713 Registered ${tools.length} tools for context menu.`)
2881
4454
  );
2882
4455
  } else {
2883
4456
  const successCount = results.filter((r) => r.success).length;
2884
4457
  console.log(
2885
- chalk4.yellow(`! Registered ${successCount}/${results.length} entries.`)
4458
+ chalk7.yellow(`! Registered ${successCount}/${results.length} entries.`)
2886
4459
  );
2887
4460
  }
2888
- console.log(chalk4.bold("\nUsage:"));
4461
+ console.log(chalk7.bold("\nUsage:"));
2889
4462
  console.log(" Right-click any supported image in Windows Explorer.");
2890
4463
  console.log(" Multi-select supported for batch processing.");
2891
4464
  console.log();
@@ -2893,21 +4466,21 @@ function registerInstallCommand(program2) {
2893
4466
  }
2894
4467
 
2895
4468
  // src/cli/commands/makeicon.ts
2896
- import chalk5 from "chalk";
4469
+ import chalk8 from "chalk";
2897
4470
  function registerMakeiconCommand(program2) {
2898
4471
  program2.command("makeicon <files...>").description("Convert PNG to multi-resolution ICO file").option("-y, --yes", "Use defaults, skip prompts").option("-g, --gui", "Use GUI for confirmation").action(async (files, options) => {
2899
4472
  if (options.gui) {
2900
- const { valid, invalid } = validateExtensions(files, config2.extensions);
4473
+ const { valid, invalid } = validateExtensions(files, config5.extensions);
2901
4474
  if (invalid.length > 0) {
2902
- console.error(chalk5.red("Invalid file types:"));
4475
+ console.error(chalk8.red("Invalid file types:"));
2903
4476
  for (const file of invalid) {
2904
- console.error(chalk5.red(` - ${file}`));
4477
+ console.error(chalk8.red(` - ${file}`));
2905
4478
  }
2906
4479
  }
2907
4480
  if (valid.length === 0) {
2908
4481
  process.exit(1);
2909
4482
  }
2910
- const result = await runGUI2(valid[0]);
4483
+ const result = await runGUI5(valid[0]);
2911
4484
  process.exit(result ? 0 : 1);
2912
4485
  }
2913
4486
  const success2 = await runToolOnFiles(
@@ -2920,13 +4493,13 @@ function registerMakeiconCommand(program2) {
2920
4493
  }
2921
4494
 
2922
4495
  // src/cli/commands/piclet.ts
2923
- import chalk6 from "chalk";
4496
+ import chalk9 from "chalk";
2924
4497
  function registerPicletCommand(program2) {
2925
4498
  program2.command("piclet <file>").description("Open unified PicLet window with all tools").option("-g, --gui", "Use GUI (default)").action(async (file) => {
2926
4499
  const { valid, invalid } = validateExtensions([file], picletTool.config.extensions);
2927
4500
  if (invalid.length > 0) {
2928
- console.error(chalk6.red(`Invalid file type: ${file}`));
2929
- console.error(chalk6.yellow(`Supported: ${picletTool.config.extensions.join(", ")}`));
4501
+ console.error(chalk9.red(`Invalid file type: ${file}`));
4502
+ console.error(chalk9.yellow(`Supported: ${picletTool.config.extensions.join(", ")}`));
2930
4503
  process.exit(1);
2931
4504
  }
2932
4505
  const result = await picletTool.runGUI(valid[0]);
@@ -2934,22 +4507,49 @@ function registerPicletCommand(program2) {
2934
4507
  });
2935
4508
  }
2936
4509
 
4510
+ // src/cli/commands/recolor.ts
4511
+ import chalk10 from "chalk";
4512
+ function registerRecolorCommand(program2) {
4513
+ program2.command("recolor <files...>").alias("replace-color").description("Replace one color with another").option("-y, --yes", "Use defaults, skip prompts").option("-g, --gui", "Use GUI for options").action(async (files, options) => {
4514
+ if (options.gui) {
4515
+ const { valid, invalid } = validateExtensions(files, config7.extensions);
4516
+ if (invalid.length > 0) {
4517
+ console.error(chalk10.red("Invalid file types:"));
4518
+ for (const file of invalid) {
4519
+ console.error(chalk10.red(` - ${file}`));
4520
+ }
4521
+ }
4522
+ if (valid.length === 0) {
4523
+ process.exit(1);
4524
+ }
4525
+ const result = await runGUI7(valid[0]);
4526
+ process.exit(result ? 0 : 1);
4527
+ }
4528
+ const success2 = await runToolOnFiles(
4529
+ "recolor",
4530
+ files,
4531
+ options.yes ?? false
4532
+ );
4533
+ process.exit(success2 ? 0 : 1);
4534
+ });
4535
+ }
4536
+
2937
4537
  // src/cli/commands/remove-bg.ts
2938
- import chalk7 from "chalk";
4538
+ import chalk11 from "chalk";
2939
4539
  function registerRemoveBgCommand(program2) {
2940
4540
  program2.command("remove-bg <files...>").alias("removebg").description("Remove solid background from image").option("-y, --yes", "Use defaults, skip prompts").option("-g, --gui", "Use TUI (terminal GUI) for options").option("-f, --fuzz <percent>", "Fuzz tolerance 0-100 (default: 10)").option("-t, --trim", "Trim transparent edges (default: true)").option("--no-trim", "Do not trim transparent edges").option("-p, --preserve-inner", "Preserve inner areas of same color").option("-s, --square", "Make output square with padding").action(async (files, options) => {
2941
4541
  if (options.gui) {
2942
- const { valid, invalid } = validateExtensions(files, config4.extensions);
4542
+ const { valid, invalid } = validateExtensions(files, config8.extensions);
2943
4543
  if (invalid.length > 0) {
2944
- console.error(chalk7.red("Invalid file types:"));
4544
+ console.error(chalk11.red("Invalid file types:"));
2945
4545
  for (const file of invalid) {
2946
- console.error(chalk7.red(` - ${file}`));
4546
+ console.error(chalk11.red(` - ${file}`));
2947
4547
  }
2948
4548
  }
2949
4549
  if (valid.length === 0) {
2950
4550
  process.exit(1);
2951
4551
  }
2952
- const result = await runGUI4(valid[0]);
4552
+ const result = await runGUI8(valid[0]);
2953
4553
  process.exit(result ? 0 : 1);
2954
4554
  }
2955
4555
  if (options.fuzz !== void 0) {
@@ -2975,21 +4575,21 @@ function registerRemoveBgCommand(program2) {
2975
4575
  }
2976
4576
 
2977
4577
  // src/cli/commands/scale.ts
2978
- import chalk8 from "chalk";
4578
+ import chalk12 from "chalk";
2979
4579
  function registerScaleCommand(program2) {
2980
4580
  program2.command("scale <files...>").alias("rescale").description("Resize image with optional padding").option("-y, --yes", "Use defaults, skip prompts").option("-g, --gui", "Use GUI for options").action(async (files, options) => {
2981
4581
  if (options.gui) {
2982
- const { valid, invalid } = validateExtensions(files, config5.extensions);
4582
+ const { valid, invalid } = validateExtensions(files, config9.extensions);
2983
4583
  if (invalid.length > 0) {
2984
- console.error(chalk8.red("Invalid file types:"));
4584
+ console.error(chalk12.red("Invalid file types:"));
2985
4585
  for (const file of invalid) {
2986
- console.error(chalk8.red(` - ${file}`));
4586
+ console.error(chalk12.red(` - ${file}`));
2987
4587
  }
2988
4588
  }
2989
4589
  if (valid.length === 0) {
2990
4590
  process.exit(1);
2991
4591
  }
2992
- const result = await runGUI5(valid[0]);
4592
+ const result = await runGUI9(valid[0]);
2993
4593
  process.exit(result ? 0 : 1);
2994
4594
  }
2995
4595
  const success2 = await runToolOnFiles(
@@ -3002,21 +4602,21 @@ function registerScaleCommand(program2) {
3002
4602
  }
3003
4603
 
3004
4604
  // src/cli/commands/storepack.ts
3005
- import chalk9 from "chalk";
4605
+ import chalk13 from "chalk";
3006
4606
  function registerStorepackCommand(program2) {
3007
4607
  program2.command("storepack <files...>").description("Generate assets for app stores (Windows, Unity, Steam, etc.)").option("-y, --yes", "Use defaults, skip prompts").option("-g, --gui", "Use GUI for options").action(async (files, options) => {
3008
4608
  if (options.gui) {
3009
- const { valid, invalid } = validateExtensions(files, config6.extensions);
4609
+ const { valid, invalid } = validateExtensions(files, config10.extensions);
3010
4610
  if (invalid.length > 0) {
3011
- console.error(chalk9.red("Invalid file types:"));
4611
+ console.error(chalk13.red("Invalid file types:"));
3012
4612
  for (const file of invalid) {
3013
- console.error(chalk9.red(` - ${file}`));
4613
+ console.error(chalk13.red(` - ${file}`));
3014
4614
  }
3015
4615
  }
3016
4616
  if (valid.length === 0) {
3017
4617
  process.exit(1);
3018
4618
  }
3019
- const result = await runGUI6(valid[0]);
4619
+ const result = await runGUI10(valid[0]);
3020
4620
  process.exit(result ? 0 : 1);
3021
4621
  }
3022
4622
  const success2 = await runToolOnFiles(
@@ -3028,67 +4628,104 @@ function registerStorepackCommand(program2) {
3028
4628
  });
3029
4629
  }
3030
4630
 
4631
+ // src/cli/commands/transform.ts
4632
+ import chalk14 from "chalk";
4633
+ function registerTransformCommand(program2) {
4634
+ program2.command("transform <files...>").alias("flip").description("Flip or rotate images").option("-y, --yes", "Use defaults, skip prompts").option("-g, --gui", "Use GUI for options").action(async (files, options) => {
4635
+ if (options.gui) {
4636
+ const { valid, invalid } = validateExtensions(files, config11.extensions);
4637
+ if (invalid.length > 0) {
4638
+ console.error(chalk14.red("Invalid file types:"));
4639
+ for (const file of invalid) {
4640
+ console.error(chalk14.red(` - ${file}`));
4641
+ }
4642
+ }
4643
+ if (valid.length === 0) {
4644
+ process.exit(1);
4645
+ }
4646
+ const result = await runGUI11(valid[0]);
4647
+ process.exit(result ? 0 : 1);
4648
+ }
4649
+ const success2 = await runToolOnFiles(
4650
+ "transform",
4651
+ files,
4652
+ options.yes ?? false
4653
+ );
4654
+ process.exit(success2 ? 0 : 1);
4655
+ });
4656
+ }
4657
+
3031
4658
  // src/cli/commands/uninstall.ts
3032
- import chalk10 from "chalk";
4659
+ import chalk15 from "chalk";
3033
4660
  function registerUninstallCommand(program2) {
3034
4661
  program2.command("uninstall").description("Remove Windows shell context menu integration").action(async () => {
3035
4662
  showBanner();
3036
- console.log(chalk10.bold("Uninstalling...\n"));
4663
+ console.log(chalk15.bold("Uninstalling...\n"));
3037
4664
  if (!isWSL()) {
3038
4665
  console.log(
3039
- chalk10.yellow("! Not running in WSL. Registry cleanup skipped.")
4666
+ chalk15.yellow("! Not running in WSL. Registry cleanup skipped.")
3040
4667
  );
3041
4668
  console.log(
3042
- chalk10.yellow(
4669
+ chalk15.yellow(
3043
4670
  '! Run "piclet uninstall" from WSL to remove context menu.'
3044
4671
  )
3045
4672
  );
3046
4673
  return;
3047
4674
  }
3048
4675
  if (!isWSLInteropEnabled()) {
3049
- console.log(chalk10.yellow("WSL Interop not available. Generating registry file...\n"));
4676
+ console.log(chalk15.yellow("WSL Interop not available. Generating registry file...\n"));
3050
4677
  const regPath = await generateUninstallRegFile();
3051
4678
  const winPath = wslToWindows(regPath);
3052
- console.log(chalk10.green("\u2713 Generated uninstall registry file:"));
3053
- console.log(chalk10.cyan(` ${winPath}
4679
+ console.log(chalk15.green("\u2713 Generated uninstall registry file:"));
4680
+ console.log(chalk15.cyan(` ${winPath}
3054
4681
  `));
3055
- console.log(chalk10.bold("To uninstall, either:"));
3056
- console.log(chalk10.dim(" 1. Double-click the .reg file in Windows Explorer"));
3057
- console.log(chalk10.dim(` 2. Run in elevated PowerShell: reg import "${winPath}"`));
4682
+ console.log(chalk15.bold("To uninstall, either:"));
4683
+ console.log(chalk15.dim(" 1. Double-click the .reg file in Windows Explorer"));
4684
+ console.log(chalk15.dim(` 2. Run in elevated PowerShell: reg import "${winPath}"`));
3058
4685
  console.log();
3059
4686
  return;
3060
4687
  }
4688
+ console.log(chalk15.dim("Cleaning up legacy entries...\n"));
4689
+ const legacyResult = await cleanupLegacyEntries();
4690
+ if (legacyResult.removed.length > 0) {
4691
+ console.log(chalk15.yellow(`Removed ${legacyResult.removed.length} legacy entries:`));
4692
+ for (const entry of legacyResult.removed) {
4693
+ console.log(` ${chalk15.green("\u2713")} ${entry}`);
4694
+ }
4695
+ console.log();
4696
+ }
3061
4697
  const results = await unregisterAllTools();
3062
4698
  const removedCount = results.filter((r) => r.success).length;
3063
4699
  for (const result of results) {
3064
4700
  if (result.success) {
3065
4701
  console.log(
3066
- `${chalk10.green("\u2713")} Removed: ${result.extension} \u2192 ${result.toolName}`
4702
+ `${chalk15.green("\u2713")} Removed: ${result.extension} \u2192 ${result.toolName}`
3067
4703
  );
3068
4704
  } else {
3069
4705
  console.log(
3070
- `${chalk10.gray("-")} Skipped: ${result.extension} \u2192 ${result.toolName}`
4706
+ `${chalk15.gray("-")} Skipped: ${result.extension} \u2192 ${result.toolName}`
3071
4707
  );
3072
4708
  }
3073
4709
  }
4710
+ const totalRemoved = removedCount + legacyResult.removed.length;
3074
4711
  console.log();
3075
4712
  console.log(
3076
- chalk10.green(
3077
- `\u2713 Cleanup complete. Removed ${removedCount}/${results.length} entries.`
4713
+ chalk15.green(
4714
+ `\u2713 Cleanup complete. Removed ${totalRemoved} entries total.`
3078
4715
  )
3079
4716
  );
3080
- console.log(chalk10.dim("\nThanks for using PicLet!\n"));
4717
+ console.log(chalk15.dim("\nThanks for using PicLet!\n"));
3081
4718
  });
3082
4719
  }
3083
4720
 
3084
4721
  // src/cli/index.ts
3085
4722
  function showHelp() {
3086
4723
  showBanner();
3087
- const dim = chalk11.gray;
3088
- const cmd = chalk11.cyan;
3089
- const arg = chalk11.yellow;
3090
- const opt = chalk11.green;
3091
- const head = chalk11.white.bold;
4724
+ const dim = chalk16.gray;
4725
+ const cmd = chalk16.cyan;
4726
+ const arg = chalk16.yellow;
4727
+ const opt = chalk16.green;
4728
+ const head = chalk16.white.bold;
3092
4729
  console.log(
3093
4730
  ` ${head("Usage:")} piclet ${cmd("<command>")} ${arg("<file>")} ${opt("[options]")}`
3094
4731
  );
@@ -3108,6 +4745,21 @@ function showHelp() {
3108
4745
  console.log(
3109
4746
  ` ${cmd("scale")} ${arg("<file>")} Resize image with optional padding`
3110
4747
  );
4748
+ console.log(
4749
+ ` ${cmd("transform")} ${arg("<file>")} Flip or rotate images`
4750
+ );
4751
+ console.log(
4752
+ ` ${cmd("filter")} ${arg("<file>")} Apply color filters (grayscale, sepia)`
4753
+ );
4754
+ console.log(
4755
+ ` ${cmd("border")} ${arg("<file>")} Add solid color border`
4756
+ );
4757
+ console.log(
4758
+ ` ${cmd("recolor")} ${arg("<file>")} Replace one color with another`
4759
+ );
4760
+ console.log(
4761
+ ` ${cmd("frames")} ${arg("<file>")} Extract frames from animated GIF`
4762
+ );
3111
4763
  console.log(
3112
4764
  ` ${cmd("iconpack")} ${arg("<file>")} Generate icon sets for Web/Android/iOS`
3113
4765
  );
@@ -3152,6 +4804,11 @@ function createProgram() {
3152
4804
  registerMakeiconCommand(program2);
3153
4805
  registerRemoveBgCommand(program2);
3154
4806
  registerScaleCommand(program2);
4807
+ registerTransformCommand(program2);
4808
+ registerFilterCommand(program2);
4809
+ registerBorderCommand(program2);
4810
+ registerRecolorCommand(program2);
4811
+ registerExtractFramesCommand(program2);
3155
4812
  registerIconpackCommand(program2);
3156
4813
  registerStorepackCommand(program2);
3157
4814
  registerPicletCommand(program2);
@@ -3162,7 +4819,7 @@ function createProgram() {
3162
4819
  // src/cli.ts
3163
4820
  var program = createProgram();
3164
4821
  program.parseAsync(process.argv).catch((error2) => {
3165
- console.error(chalk12.red(`Error: ${error2.message}`));
4822
+ console.error(chalk17.red(`Error: ${error2.message}`));
3166
4823
  process.exit(1);
3167
4824
  });
3168
4825
  //# sourceMappingURL=cli.js.map