@spark-apps/piclet 1.0.0

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 ADDED
@@ -0,0 +1,2989 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import { extname as extname3 } from "path";
5
+ import { dirname as dirname10, join as join9 } from "path";
6
+ import { fileURLToPath as fileURLToPath3 } from "url";
7
+ import chalk from "chalk";
8
+ import { Command } from "commander";
9
+
10
+ // src/lib/banner.ts
11
+ import figlet from "figlet";
12
+ import gradient from "gradient-string";
13
+ var GRADIENT_COLORS = ["#22c55e", "#84cc16", "#eab308", "#fcd34d"];
14
+ function renderLogo() {
15
+ const ascii = figlet.textSync("PicLet", {
16
+ font: "Slant",
17
+ horizontalLayout: "default"
18
+ });
19
+ return gradient(GRADIENT_COLORS)(ascii);
20
+ }
21
+ function showBanner(subtitle = "Image manipulation utility toolkit with Windows shell integration") {
22
+ try {
23
+ console.log(`
24
+ ${renderLogo()}`);
25
+ if (subtitle) {
26
+ const subtleGradient = gradient(["#a8a8a8", "#d4d4d4"]);
27
+ console.log(subtleGradient(` ${subtitle}
28
+ `));
29
+ }
30
+ } catch {
31
+ console.log("\n\x1B[1mPicLet\x1B[0m");
32
+ if (subtitle) {
33
+ console.log(`\x1B[2m ${subtitle}\x1B[0m
34
+ `);
35
+ }
36
+ }
37
+ }
38
+
39
+ // src/lib/config.ts
40
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
41
+ import { dirname, join } from "path";
42
+ import { homedir } from "os";
43
+ var DEFAULT_CONFIG = {
44
+ removeBg: {
45
+ fuzz: 10,
46
+ trim: true,
47
+ preserveInner: false,
48
+ makeSquare: false
49
+ },
50
+ rescale: {
51
+ defaultScale: 50,
52
+ makeSquare: false
53
+ },
54
+ iconpack: {
55
+ platforms: ["web", "android", "ios"]
56
+ }
57
+ };
58
+ function getConfigDir() {
59
+ return join(homedir(), ".config", "piclet");
60
+ }
61
+ function getConfigPath() {
62
+ return join(getConfigDir(), "config.json");
63
+ }
64
+ function loadConfig() {
65
+ const configPath = getConfigPath();
66
+ if (!existsSync(configPath)) {
67
+ return { ...DEFAULT_CONFIG };
68
+ }
69
+ try {
70
+ const content = readFileSync(configPath, "utf-8");
71
+ const loaded = JSON.parse(content);
72
+ return {
73
+ removeBg: { ...DEFAULT_CONFIG.removeBg, ...loaded.removeBg },
74
+ rescale: { ...DEFAULT_CONFIG.rescale, ...loaded.rescale },
75
+ iconpack: { ...DEFAULT_CONFIG.iconpack, ...loaded.iconpack }
76
+ };
77
+ } catch {
78
+ return { ...DEFAULT_CONFIG };
79
+ }
80
+ }
81
+ function resetConfig() {
82
+ const configPath = getConfigPath();
83
+ const configDir = dirname(configPath);
84
+ if (!existsSync(configDir)) {
85
+ mkdirSync(configDir, { recursive: true });
86
+ }
87
+ writeFileSync(configPath, JSON.stringify(DEFAULT_CONFIG, null, 2));
88
+ }
89
+
90
+ // src/lib/paths.ts
91
+ import { basename, dirname as dirname2, extname, resolve } from "path";
92
+ function windowsToWsl(winPath) {
93
+ if (winPath.startsWith("/mnt/")) {
94
+ return winPath;
95
+ }
96
+ const match = winPath.match(/^([A-Za-z]):[/\\](.*)$/);
97
+ if (match) {
98
+ const drive = match[1].toLowerCase();
99
+ const rest = match[2].replace(/\\/g, "/");
100
+ return `/mnt/${drive}/${rest}`;
101
+ }
102
+ return winPath;
103
+ }
104
+ function wslToWindows(wslPath) {
105
+ const match = wslPath.match(/^\/mnt\/([a-z])\/(.*)$/i);
106
+ if (match) {
107
+ const drive = match[1].toUpperCase();
108
+ const rest = match[2].replace(/\//g, "\\");
109
+ return `${drive}:\\${rest}`;
110
+ }
111
+ return wslPath;
112
+ }
113
+ function normalizePath(inputPath) {
114
+ if (/^[A-Za-z]:[/\\]/.test(inputPath)) {
115
+ return windowsToWsl(inputPath);
116
+ }
117
+ return resolve(inputPath);
118
+ }
119
+ function getFileInfo(filePath) {
120
+ const dir = dirname2(filePath);
121
+ const base = basename(filePath);
122
+ const ext = extname(filePath);
123
+ const name = base.slice(0, -ext.length);
124
+ return {
125
+ dirname: dir,
126
+ basename: base,
127
+ filename: name,
128
+ extension: ext
129
+ };
130
+ }
131
+
132
+ // src/lib/prompts.ts
133
+ import prompts from "prompts";
134
+ var useDefaults = false;
135
+ var overrides = {};
136
+ function setUseDefaults(value) {
137
+ useDefaults = value;
138
+ }
139
+ function setOverrides(values) {
140
+ Object.assign(overrides, values);
141
+ }
142
+ function clearOverrides() {
143
+ for (const key of Object.keys(overrides)) {
144
+ delete overrides[key];
145
+ }
146
+ }
147
+ function getOverride(message) {
148
+ const msgLower = message.toLowerCase();
149
+ for (const [key, value] of Object.entries(overrides)) {
150
+ if (msgLower.includes(key.toLowerCase())) {
151
+ return value;
152
+ }
153
+ }
154
+ return void 0;
155
+ }
156
+ async function number(message, defaultValue, min, max) {
157
+ const override = getOverride(message);
158
+ if (override !== void 0) return Number(override);
159
+ if (useDefaults) return defaultValue ?? 0;
160
+ const response = await prompts({
161
+ type: "number",
162
+ name: "value",
163
+ message,
164
+ initial: defaultValue,
165
+ min,
166
+ max
167
+ });
168
+ return response.value ?? defaultValue ?? 0;
169
+ }
170
+ async function confirm(message, defaultValue = true) {
171
+ const override = getOverride(message);
172
+ if (override !== void 0) return Boolean(override);
173
+ if (useDefaults) return defaultValue;
174
+ const response = await prompts({
175
+ type: "confirm",
176
+ name: "value",
177
+ message,
178
+ initial: defaultValue
179
+ });
180
+ return response.value ?? defaultValue;
181
+ }
182
+ async function select(message, options, defaultValue) {
183
+ if (useDefaults) return defaultValue ?? options[0]?.value ?? null;
184
+ const response = await prompts({
185
+ type: "select",
186
+ name: "value",
187
+ message,
188
+ choices: options.map((opt) => ({
189
+ title: opt.title,
190
+ value: opt.value,
191
+ description: opt.description
192
+ }))
193
+ });
194
+ return response.value ?? null;
195
+ }
196
+ async function multiSelect(message, options, defaultValues) {
197
+ if (useDefaults) return defaultValues ?? [];
198
+ const response = await prompts({
199
+ type: "multiselect",
200
+ name: "value",
201
+ message,
202
+ choices: options.map((opt) => ({
203
+ title: opt.title,
204
+ value: opt.value,
205
+ description: opt.description
206
+ })),
207
+ instructions: false,
208
+ hint: "- Space to select, Enter to confirm"
209
+ });
210
+ return response.value ?? [];
211
+ }
212
+ prompts.override({});
213
+ async function pauseOnError(message = "Press Enter to close...") {
214
+ if (!useDefaults) return;
215
+ console.log();
216
+ await prompts({
217
+ type: "text",
218
+ name: "value",
219
+ message
220
+ });
221
+ }
222
+
223
+ // src/lib/registry.ts
224
+ import { exec } from "child_process";
225
+ import { dirname as dirname3 } from "path";
226
+ import { fileURLToPath } from "url";
227
+ import { promisify } from "util";
228
+ var execAsync = promisify(exec);
229
+ function isWSL() {
230
+ return process.platform === "linux" && (process.env.WSL_DISTRO_NAME !== void 0 || process.env.WSLENV !== void 0);
231
+ }
232
+ async function addRegistryKey(keyPath, valueName, value, type = "REG_SZ") {
233
+ const valueArg = valueName ? `/v "${valueName}"` : "/ve";
234
+ const escapedValue = value.replace(/"/g, '\\"');
235
+ const cmd = `reg.exe add "${keyPath}" ${valueArg} /t ${type} /d "${escapedValue}" /f`;
236
+ try {
237
+ await execAsync(cmd);
238
+ return true;
239
+ } catch (error2) {
240
+ const message = error2.message;
241
+ const ignoredErrors = ["Exec format error", "not found"];
242
+ const shouldIgnore = ignoredErrors.some(
243
+ (e) => message.toLowerCase().includes(e.toLowerCase())
244
+ );
245
+ if (!shouldIgnore) {
246
+ console.error(`Failed to add registry key: ${keyPath}`);
247
+ console.error(message);
248
+ }
249
+ return false;
250
+ }
251
+ }
252
+ async function deleteRegistryKey(keyPath) {
253
+ const cmd = `reg.exe delete "${keyPath}" /f`;
254
+ try {
255
+ await execAsync(cmd);
256
+ return true;
257
+ } catch (error2) {
258
+ const message = error2.message;
259
+ const ignoredErrors = ["unable to find", "Exec format error", "not found"];
260
+ const shouldIgnore = ignoredErrors.some(
261
+ (e) => message.toLowerCase().includes(e.toLowerCase())
262
+ );
263
+ if (!shouldIgnore) {
264
+ console.error(`Failed to delete registry key: ${keyPath}`);
265
+ }
266
+ return false;
267
+ }
268
+ }
269
+
270
+ // src/tools/iconpack.ts
271
+ import { existsSync as existsSync4, mkdirSync as mkdirSync4 } from "fs";
272
+ import { basename as basename2, dirname as dirname7 } from "path";
273
+
274
+ // src/lib/gui-server.ts
275
+ import { spawn } from "child_process";
276
+ import { createServer } from "http";
277
+ import { dirname as dirname5, join as join3 } from "path";
278
+ import { fileURLToPath as fileURLToPath2 } from "url";
279
+ import express from "express";
280
+
281
+ // src/lib/presets.ts
282
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
283
+ import { dirname as dirname4, join as join2 } from "path";
284
+ import { homedir as homedir2 } from "os";
285
+ var BUILT_IN_PRESETS = [
286
+ // ── Game Asset Stores ──
287
+ {
288
+ id: "unity-asset",
289
+ name: "Unity Asset Store",
290
+ description: "Unity Asset Store package images",
291
+ icons: [
292
+ { filename: "icon-160.png", width: 160, height: 160 },
293
+ { filename: "card-420x280.png", width: 420, height: 280 },
294
+ { filename: "cover-1200x630.png", width: 1200, height: 630 },
295
+ { filename: "screenshot-1920x1080.png", width: 1920, height: 1080 }
296
+ ]
297
+ },
298
+ {
299
+ id: "unreal-fab",
300
+ name: "Unreal / Fab",
301
+ description: "Unreal Marketplace & Fab assets",
302
+ icons: [
303
+ { filename: "icon-256.png", width: 256, height: 256 },
304
+ { filename: "thumbnail-284.png", width: 284, height: 284 },
305
+ { filename: "featured-894x488.png", width: 894, height: 488 },
306
+ { filename: "gallery-1920x1080.png", width: 1920, height: 1080 }
307
+ ]
308
+ },
309
+ {
310
+ id: "godot-asset",
311
+ name: "Godot Asset Library",
312
+ description: "Godot Asset Library images",
313
+ icons: [
314
+ { filename: "icon-64.png", width: 64, height: 64 },
315
+ { filename: "icon-128.png", width: 128, height: 128 },
316
+ { filename: "screenshot-1280x720.png", width: 1280, height: 720 },
317
+ { filename: "screenshot-1920x1080.png", width: 1920, height: 1080 }
318
+ ]
319
+ },
320
+ {
321
+ id: "blender-market",
322
+ name: "Blender Market",
323
+ description: "Blender Market product images",
324
+ icons: [
325
+ { filename: "icon-128.png", width: 128, height: 128 },
326
+ { filename: "thumbnail-256.png", width: 256, height: 256 },
327
+ { filename: "preview-1920x1080.png", width: 1920, height: 1080 }
328
+ ]
329
+ },
330
+ {
331
+ id: "steam",
332
+ name: "Steam",
333
+ description: "Steam store assets",
334
+ icons: [
335
+ { filename: "capsule-small-231x87.png", width: 231, height: 87 },
336
+ { filename: "capsule-main-616x353.png", width: 616, height: 353 },
337
+ { filename: "header-460x215.png", width: 460, height: 215 },
338
+ { filename: "hero-1920x620.png", width: 1920, height: 620 },
339
+ { filename: "library-capsule-600x900.png", width: 600, height: 900 },
340
+ { filename: "library-hero-3840x1240.png", width: 3840, height: 1240 }
341
+ ]
342
+ },
343
+ {
344
+ id: "itch-io",
345
+ name: "itch.io",
346
+ description: "itch.io game page assets",
347
+ icons: [
348
+ { filename: "cover-630x500.png", width: 630, height: 500 },
349
+ { filename: "thumbnail-315x250.png", width: 315, height: 250 },
350
+ { filename: "banner-960x540.png", width: 960, height: 540 }
351
+ ]
352
+ },
353
+ // ── Extensions & Packages ──
354
+ {
355
+ id: "chrome-extension",
356
+ name: "Chrome Extension",
357
+ description: "Chrome Web Store extension icons",
358
+ icons: [
359
+ { filename: "icon-16.png", width: 16, height: 16 },
360
+ { filename: "icon-32.png", width: 32, height: 32 },
361
+ { filename: "icon-48.png", width: 48, height: 48 },
362
+ { filename: "icon-128.png", width: 128, height: 128 },
363
+ { filename: "promo-440x280.png", width: 440, height: 280 },
364
+ { filename: "promo-1400x560.png", width: 1400, height: 560 }
365
+ ]
366
+ },
367
+ {
368
+ id: "firefox-addon",
369
+ name: "Firefox Add-on",
370
+ description: "Firefox Add-ons icons",
371
+ icons: [
372
+ { filename: "icon-48.png", width: 48, height: 48 },
373
+ { filename: "icon-96.png", width: 96, height: 96 },
374
+ { filename: "icon-128.png", width: 128, height: 128 }
375
+ ]
376
+ },
377
+ {
378
+ id: "vscode-extension",
379
+ name: "VS Code Extension",
380
+ description: "VS Code Marketplace icons",
381
+ icons: [
382
+ { filename: "icon-128.png", width: 128, height: 128 },
383
+ { filename: "icon-256.png", width: 256, height: 256 }
384
+ ]
385
+ },
386
+ {
387
+ id: "npm-package",
388
+ name: "npm Package",
389
+ description: "npm package icons",
390
+ icons: [
391
+ { filename: "logo-64.png", width: 64, height: 64 },
392
+ { filename: "logo-128.png", width: 128, height: 128 },
393
+ { filename: "logo-256.png", width: 256, height: 256 },
394
+ { filename: "banner-1200x600.png", width: 1200, height: 600 }
395
+ ]
396
+ },
397
+ {
398
+ id: "windows-store",
399
+ name: "Windows Store",
400
+ description: "Microsoft Store app icons",
401
+ icons: [
402
+ { filename: "Square44x44Logo.png", width: 44, height: 44 },
403
+ { filename: "Square150x150Logo.png", width: 150, height: 150 },
404
+ { filename: "Square310x310Logo.png", width: 310, height: 310 },
405
+ { filename: "Wide310x150Logo.png", width: 310, height: 150 },
406
+ { filename: "StoreLogo-50.png", width: 50, height: 50 },
407
+ { filename: "SplashScreen-620x300.png", width: 620, height: 300 }
408
+ ]
409
+ }
410
+ ];
411
+ function getPresetsPath() {
412
+ const picletDir = join2(homedir2(), ".piclet");
413
+ return join2(picletDir, "presets.json");
414
+ }
415
+ function ensurePresetsDir() {
416
+ const presetsPath = getPresetsPath();
417
+ const dir = dirname4(presetsPath);
418
+ if (!existsSync2(dir)) {
419
+ mkdirSync2(dir, { recursive: true });
420
+ }
421
+ }
422
+ function loadPresets() {
423
+ const presetsPath = getPresetsPath();
424
+ let userPresets = [];
425
+ if (existsSync2(presetsPath)) {
426
+ try {
427
+ const data = readFileSync2(presetsPath, "utf-8");
428
+ const config7 = JSON.parse(data);
429
+ userPresets = config7.presets || [];
430
+ } catch {
431
+ }
432
+ }
433
+ const presetMap = /* @__PURE__ */ new Map();
434
+ for (const preset of BUILT_IN_PRESETS) {
435
+ presetMap.set(preset.id, preset);
436
+ }
437
+ for (const preset of userPresets) {
438
+ presetMap.set(preset.id, preset);
439
+ }
440
+ return Array.from(presetMap.values());
441
+ }
442
+ function savePresets(presets) {
443
+ ensurePresetsDir();
444
+ const config7 = {
445
+ version: 1,
446
+ presets
447
+ };
448
+ writeFileSync2(getPresetsPath(), JSON.stringify(config7, null, 2));
449
+ }
450
+ function savePreset(preset) {
451
+ const presetsPath = getPresetsPath();
452
+ let userPresets = [];
453
+ if (existsSync2(presetsPath)) {
454
+ try {
455
+ const data = readFileSync2(presetsPath, "utf-8");
456
+ const config7 = JSON.parse(data);
457
+ userPresets = config7.presets || [];
458
+ } catch {
459
+ }
460
+ }
461
+ const idx = userPresets.findIndex((p) => p.id === preset.id);
462
+ if (idx >= 0) {
463
+ userPresets[idx] = preset;
464
+ } else {
465
+ userPresets.push(preset);
466
+ }
467
+ savePresets(userPresets);
468
+ }
469
+ function deletePreset(id) {
470
+ if (BUILT_IN_PRESETS.some((p) => p.id === id)) {
471
+ return { success: false, error: "Cannot delete built-in presets" };
472
+ }
473
+ const presetsPath = getPresetsPath();
474
+ if (!existsSync2(presetsPath)) {
475
+ return { success: false, error: "Preset not found" };
476
+ }
477
+ try {
478
+ const data = readFileSync2(presetsPath, "utf-8");
479
+ const config7 = JSON.parse(data);
480
+ const userPresets = config7.presets || [];
481
+ const idx = userPresets.findIndex((p) => p.id === id);
482
+ if (idx < 0) {
483
+ return { success: false, error: "Preset not found" };
484
+ }
485
+ userPresets.splice(idx, 1);
486
+ savePresets(userPresets);
487
+ return { success: true };
488
+ } catch {
489
+ return { success: false, error: "Failed to delete preset" };
490
+ }
491
+ }
492
+
493
+ // src/lib/gui-server.ts
494
+ var __dirname2 = dirname5(fileURLToPath2(import.meta.url));
495
+ function signalReady() {
496
+ spawn("cmd.exe", ["/c", "echo.>", "%TEMP%\\piclet-ready.tmp"], {
497
+ stdio: "ignore",
498
+ windowsHide: true
499
+ });
500
+ }
501
+ function openAppWindow(url) {
502
+ spawn("cmd.exe", ["/c", "start", '""', "msedge", `--app=${url}`], {
503
+ detached: true,
504
+ stdio: "ignore",
505
+ windowsHide: true
506
+ }).unref();
507
+ setTimeout(signalReady, 500);
508
+ }
509
+ function startGuiServer(options) {
510
+ return new Promise((resolve2) => {
511
+ const app = express();
512
+ app.use(express.json({ limit: "50mb" }));
513
+ app.use((err, _req, res, next) => {
514
+ if (err instanceof SyntaxError && "body" in err) {
515
+ res.status(400).json({ success: false, error: "Invalid JSON: " + err.message });
516
+ return;
517
+ }
518
+ next(err);
519
+ });
520
+ let processResult = null;
521
+ let server = null;
522
+ const guiDir = join3(__dirname2, "gui");
523
+ const iconsDir = join3(__dirname2, "icons");
524
+ app.use(express.static(guiDir));
525
+ app.use("/icons", express.static(iconsDir));
526
+ app.get("/favicon.ico", (_req, res) => {
527
+ res.sendFile(join3(iconsDir, "banana.ico"));
528
+ });
529
+ app.get("/api/info", (_req, res) => {
530
+ res.json({
531
+ fileName: options.imageInfo.fileName,
532
+ width: options.imageInfo.width,
533
+ height: options.imageInfo.height,
534
+ borderColor: options.imageInfo.borderColor,
535
+ defaults: options.defaults
536
+ });
537
+ });
538
+ app.post("/api/preview", async (req, res) => {
539
+ if (!options.onPreview) {
540
+ res.json({ success: false, error: "Preview not supported" });
541
+ return;
542
+ }
543
+ try {
544
+ const result = await options.onPreview(req.body);
545
+ res.json(result);
546
+ } catch (err) {
547
+ res.json({
548
+ success: false,
549
+ error: err.message
550
+ });
551
+ }
552
+ });
553
+ app.post("/api/process", async (req, res) => {
554
+ try {
555
+ const result = await options.onProcess(req.body);
556
+ processResult = result.success;
557
+ res.json(result);
558
+ } catch (err) {
559
+ processResult = false;
560
+ res.json({
561
+ success: false,
562
+ error: err.message,
563
+ logs: [{ type: "error", message: err.message }]
564
+ });
565
+ }
566
+ });
567
+ app.post("/api/load", async (req, res) => {
568
+ if (!options.onLoadImage) {
569
+ res.json({ success: false, error: "Load not supported" });
570
+ return;
571
+ }
572
+ try {
573
+ const result = await options.onLoadImage(req.body);
574
+ if (result.success) {
575
+ options.imageInfo.filePath = result.filePath;
576
+ options.imageInfo.fileName = result.fileName;
577
+ options.imageInfo.width = result.width;
578
+ options.imageInfo.height = result.height;
579
+ options.imageInfo.borderColor = result.borderColor ?? null;
580
+ }
581
+ res.json(result);
582
+ } catch (err) {
583
+ res.json({ success: false, error: err.message });
584
+ }
585
+ });
586
+ app.post("/api/cancel", (_req, res) => {
587
+ processResult = false;
588
+ res.json({ ok: true });
589
+ shutdown();
590
+ });
591
+ app.post("/api/close", (_req, res) => {
592
+ res.json({ ok: true });
593
+ shutdown();
594
+ });
595
+ app.post("/api/save-preset", (req, res) => {
596
+ try {
597
+ const preset = req.body;
598
+ if (!preset.id || !preset.name || !preset.icons?.length) {
599
+ res.json({ success: false, error: "Invalid preset data" });
600
+ return;
601
+ }
602
+ savePreset(preset);
603
+ res.json({ success: true });
604
+ } catch (err) {
605
+ res.json({ success: false, error: err.message });
606
+ }
607
+ });
608
+ app.post("/api/delete-preset", (req, res) => {
609
+ try {
610
+ const { id } = req.body;
611
+ if (!id) {
612
+ res.json({ success: false, error: "Missing preset ID" });
613
+ return;
614
+ }
615
+ const result = deletePreset(id);
616
+ res.json(result);
617
+ } catch (err) {
618
+ res.json({ success: false, error: err.message });
619
+ }
620
+ });
621
+ function shutdown() {
622
+ setTimeout(() => {
623
+ server?.close();
624
+ resolve2(processResult ?? false);
625
+ }, 100);
626
+ }
627
+ server = createServer(app);
628
+ server.listen(0, "127.0.0.1", () => {
629
+ const addr = server.address();
630
+ if (typeof addr === "object" && addr) {
631
+ const port = addr.port;
632
+ const url = `http://127.0.0.1:${port}/${options.htmlFile}`;
633
+ openAppWindow(url);
634
+ setTimeout(() => {
635
+ if (processResult === null) {
636
+ shutdown();
637
+ }
638
+ }, 5 * 60 * 1e3);
639
+ }
640
+ });
641
+ server.on("error", (err) => {
642
+ console.error("GUI server error:", err.message);
643
+ resolve2(false);
644
+ });
645
+ });
646
+ }
647
+
648
+ // src/lib/logger.ts
649
+ var RED = "\x1B[31m";
650
+ var GREEN = "\x1B[32m";
651
+ var YELLOW = "\x1B[33m";
652
+ var BLUE = "\x1B[34m";
653
+ var CYAN = "\x1B[36m";
654
+ var BOLD = "\x1B[1m";
655
+ var DIM = "\x1B[2m";
656
+ var RESET = "\x1B[0m";
657
+ var CHECK = "\u2713";
658
+ var CROSS = "\u2717";
659
+ var ARROW = "\u2192";
660
+ var BULLET = "\u2022";
661
+ var SPINNER = "\u280B";
662
+ function success(message) {
663
+ console.log(`${GREEN}${CHECK}${RESET} ${message}`);
664
+ }
665
+ function error(message) {
666
+ console.log(`${RED}${CROSS}${RESET} ${message}`);
667
+ }
668
+ function warn(message) {
669
+ console.log(`${YELLOW}${BULLET}${RESET} ${message}`);
670
+ }
671
+ function info(message) {
672
+ console.log(`${BLUE}${ARROW}${RESET} ${message}`);
673
+ }
674
+ function step(message) {
675
+ console.log(`${CYAN}${BULLET}${RESET} ${message}`);
676
+ }
677
+ function header(title) {
678
+ console.log("");
679
+ console.log(`${BOLD}${title}${RESET}`);
680
+ console.log(`${DIM}${"\u2500".repeat(40)}${RESET}`);
681
+ }
682
+ function separator() {
683
+ console.log(`${DIM}${"\u2500".repeat(40)}${RESET}`);
684
+ }
685
+ function wip(message) {
686
+ process.stdout.write(`${YELLOW}${SPINNER}${RESET} ${message}`);
687
+ }
688
+ function wipDone(isSuccess, message) {
689
+ process.stdout.write("\r\x1B[K");
690
+ if (isSuccess) {
691
+ success(message);
692
+ } else {
693
+ error(message);
694
+ }
695
+ }
696
+ function clearLine() {
697
+ process.stdout.write("\r\x1B[K");
698
+ }
699
+
700
+ // src/lib/magick.ts
701
+ import { exec as exec2 } from "child_process";
702
+ import { copyFileSync, existsSync as existsSync3, mkdirSync as mkdirSync3, unlinkSync } from "fs";
703
+ import { dirname as dirname6 } from "path";
704
+ import { promisify as promisify2 } from "util";
705
+ var execAsync2 = promisify2(exec2);
706
+ function getInputSelector(imagePath) {
707
+ const lowerPath = imagePath.toLowerCase();
708
+ if (lowerPath.endsWith(".ico")) {
709
+ return `"${imagePath}[0]"`;
710
+ }
711
+ return `"${imagePath}"`;
712
+ }
713
+ async function checkImageMagick() {
714
+ try {
715
+ await execAsync2("command -v convert");
716
+ return true;
717
+ } catch {
718
+ return false;
719
+ }
720
+ }
721
+ async function getDimensions(imagePath) {
722
+ try {
723
+ const input = getInputSelector(imagePath);
724
+ const { stdout } = await execAsync2(
725
+ `convert ${input} -ping -format "%w %h" info:`
726
+ );
727
+ const [w, h] = stdout.trim().split(" ").map(Number);
728
+ if (Number.isNaN(w) || Number.isNaN(h)) return null;
729
+ return [w, h];
730
+ } catch {
731
+ return null;
732
+ }
733
+ }
734
+ async function getBorderColor(imagePath) {
735
+ try {
736
+ const input = getInputSelector(imagePath);
737
+ const { stdout } = await execAsync2(
738
+ `convert ${input} -format "%[pixel:u.p{0,0}]" info:`
739
+ );
740
+ return stdout.trim();
741
+ } catch {
742
+ return null;
743
+ }
744
+ }
745
+ async function trim(inputPath, outputPath) {
746
+ try {
747
+ const input = getInputSelector(inputPath);
748
+ await execAsync2(`convert ${input} -trim +repage "${outputPath}"`);
749
+ return true;
750
+ } catch {
751
+ return false;
752
+ }
753
+ }
754
+ async function squarify2(inputPath, outputPath) {
755
+ const dims = await getDimensions(inputPath);
756
+ if (!dims) return false;
757
+ const [width, height] = dims;
758
+ if (width === height) {
759
+ if (inputPath !== outputPath) {
760
+ copyFileSync(inputPath, outputPath);
761
+ }
762
+ return true;
763
+ }
764
+ const size = Math.max(width, height);
765
+ const input = getInputSelector(inputPath);
766
+ try {
767
+ await execAsync2(
768
+ `convert ${input} -background none -gravity center -extent ${size}x${size} "${outputPath}"`
769
+ );
770
+ return true;
771
+ } catch {
772
+ return false;
773
+ }
774
+ }
775
+ async function scaleToSize(inputPath, outputPath, size) {
776
+ try {
777
+ await execAsync2(
778
+ `convert "${inputPath}" -resize ${size}x${size} -background none -gravity center -extent ${size}x${size} "${outputPath}"`
779
+ );
780
+ return true;
781
+ } catch {
782
+ return false;
783
+ }
784
+ }
785
+ async function scaleWithPadding(inputPath, outputPath, width, height) {
786
+ try {
787
+ await execAsync2(
788
+ `convert "${inputPath}" -resize ${width}x${height} -background none -gravity center -extent ${width}x${height} "${outputPath}"`
789
+ );
790
+ return true;
791
+ } catch {
792
+ return false;
793
+ }
794
+ }
795
+ async function resize(inputPath, outputPath, width, height) {
796
+ try {
797
+ await execAsync2(
798
+ `convert "${inputPath}" -resize ${width}x${height}! "${outputPath}"`
799
+ );
800
+ return true;
801
+ } catch {
802
+ return false;
803
+ }
804
+ }
805
+ async function scaleFillCrop(inputPath, outputPath, width, height) {
806
+ try {
807
+ await execAsync2(
808
+ `convert "${inputPath}" -resize ${width}x${height}^ -background none -gravity center -extent ${width}x${height} "${outputPath}"`
809
+ );
810
+ return true;
811
+ } catch {
812
+ return false;
813
+ }
814
+ }
815
+ async function removeBackground(inputPath, outputPath, color, fuzz) {
816
+ try {
817
+ const input = getInputSelector(inputPath);
818
+ await execAsync2(
819
+ `convert ${input} -fuzz ${fuzz}% -transparent "${color}" "${outputPath}"`
820
+ );
821
+ return true;
822
+ } catch {
823
+ return false;
824
+ }
825
+ }
826
+ async function removeBackgroundBorderOnly(inputPath, outputPath, color, fuzz) {
827
+ try {
828
+ const input = getInputSelector(inputPath);
829
+ await execAsync2(
830
+ `convert ${input} -bordercolor "${color}" -border 1x1 -fill none -fuzz ${fuzz}% -draw "matte 0,0 floodfill" -shave 1x1 "${outputPath}"`
831
+ );
832
+ return true;
833
+ } catch {
834
+ return false;
835
+ }
836
+ }
837
+ async function createIco(inputPath, outputPath, sizes = [256, 128, 64, 48, 32, 16]) {
838
+ try {
839
+ const sizeStr = sizes.join(",");
840
+ await execAsync2(
841
+ `convert "${inputPath}" -define icon:auto-resize=${sizeStr} "${outputPath}"`
842
+ );
843
+ return true;
844
+ } catch {
845
+ return false;
846
+ }
847
+ }
848
+ async function createIcoFromMultiple(pngPaths, outputPath) {
849
+ try {
850
+ const inputs = pngPaths.map((p) => `"${p}"`).join(" ");
851
+ await execAsync2(`convert ${inputs} "${outputPath}"`);
852
+ return true;
853
+ } catch {
854
+ return false;
855
+ }
856
+ }
857
+ function cleanup(...files) {
858
+ for (const file of files) {
859
+ try {
860
+ if (existsSync3(file)) {
861
+ unlinkSync(file);
862
+ }
863
+ } catch {
864
+ }
865
+ }
866
+ }
867
+
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
900
+ { filename: "AppIcon-60@2x.png", size: 120 },
901
+ { filename: "AppIcon-60@3x.png", size: 180 },
902
+ // 76pt - iPad App
903
+ { filename: "AppIcon-76.png", size: 76 },
904
+ { filename: "AppIcon-76@2x.png", size: 152 },
905
+ // 83.5pt - iPad Pro
906
+ { filename: "AppIcon-83.5@2x.png", size: 167 },
907
+ // App Store
908
+ { filename: "AppIcon-1024.png", size: 1024 }
909
+ ];
910
+ async function generateIcons(outputDir, sourceImg, icons) {
911
+ const total = icons.length;
912
+ let current = 0;
913
+ let failed = 0;
914
+ for (const icon of icons) {
915
+ current++;
916
+ const outputPath = `${outputDir}/${icon.filename}`;
917
+ const subdir = dirname7(outputPath);
918
+ if (!existsSync4(subdir)) {
919
+ mkdirSync4(subdir, { recursive: true });
920
+ }
921
+ clearLine();
922
+ wip(`[${current}/${total}] Generating ${icon.filename}...`);
923
+ if (await scaleToSize(sourceImg, outputPath, icon.size)) {
924
+ clearLine();
925
+ success(
926
+ `[${current}/${total}] ${icon.filename} (${icon.size}x${icon.size})`
927
+ );
928
+ } else {
929
+ clearLine();
930
+ error(`[${current}/${total}] Failed: ${icon.filename}`);
931
+ failed++;
932
+ }
933
+ }
934
+ return failed;
935
+ }
936
+ async function generateFavicon(outputDir, sourceImg) {
937
+ wip("Generating favicon.ico...");
938
+ const temp16 = `${outputDir}/.temp_16.png`;
939
+ const temp32 = `${outputDir}/.temp_32.png`;
940
+ const temp48 = `${outputDir}/.temp_48.png`;
941
+ await scaleToSize(sourceImg, temp16, 16);
942
+ await scaleToSize(sourceImg, temp32, 32);
943
+ await scaleToSize(sourceImg, temp48, 48);
944
+ const result = await createIcoFromMultiple(
945
+ [temp16, temp32, temp48],
946
+ `${outputDir}/favicon.ico`
947
+ );
948
+ cleanup(temp16, temp32, temp48);
949
+ if (result) {
950
+ wipDone(true, "favicon.ico (16, 32, 48)");
951
+ return true;
952
+ }
953
+ wipDone(false, "favicon.ico failed");
954
+ return false;
955
+ }
956
+ async function run(inputRaw) {
957
+ if (!await checkImageMagick()) {
958
+ error("ImageMagick not found. Please install it:");
959
+ console.log(" sudo apt update && sudo apt install imagemagick");
960
+ await pauseOnError();
961
+ return false;
962
+ }
963
+ const input = normalizePath(inputRaw);
964
+ if (!existsSync4(input)) {
965
+ error(`File not found: ${input}`);
966
+ await pauseOnError();
967
+ return false;
968
+ }
969
+ const fileInfo = getFileInfo(input);
970
+ header("PicLet Icon Pack Generator");
971
+ wip("Analyzing source image...");
972
+ const dims = await getDimensions(input);
973
+ if (!dims) {
974
+ wipDone(false, "Failed to read image");
975
+ return false;
976
+ }
977
+ const [origW, origH] = dims;
978
+ wipDone(true, `Source: ${origW}x${origH}`);
979
+ if (origW < 1024 || origH < 1024) {
980
+ warn(
981
+ "Source image is smaller than 1024px. Larger images produce better quality."
982
+ );
983
+ }
984
+ if (origW !== origH) {
985
+ warn("Image is not square. Will add transparent padding.");
986
+ }
987
+ console.log("");
988
+ const platforms = await multiSelect(
989
+ "Select target platforms:",
990
+ [
991
+ { title: "Web (PWA, favicon, apple-touch-icon)", value: "web" },
992
+ { title: "Android (mipmap icons, Play Store)", value: "android" },
993
+ { title: "iOS (App icons, App Store)", value: "ios" }
994
+ ],
995
+ ["web", "android", "ios"]
996
+ // Default: all platforms
997
+ );
998
+ if (platforms.length === 0) {
999
+ error("No platforms selected");
1000
+ return false;
1001
+ }
1002
+ const doWeb = platforms.includes("web");
1003
+ const doAndroid = platforms.includes("android");
1004
+ const doIos = platforms.includes("ios");
1005
+ const outputDir = `${fileInfo.dirname}/${fileInfo.filename}_icons`;
1006
+ mkdirSync4(outputDir, { recursive: true });
1007
+ info(`Output directory: ${outputDir}`);
1008
+ console.log("");
1009
+ wip("Preparing source image...");
1010
+ const tempSource = `${outputDir}/.source_1024.png`;
1011
+ const tempSquare = `${outputDir}/.temp_square.png`;
1012
+ if (!await squarify2(input, tempSquare)) {
1013
+ wipDone(false, "Failed to prepare source");
1014
+ return false;
1015
+ }
1016
+ if (!await scaleToSize(tempSquare, tempSource, 1024)) {
1017
+ wipDone(false, "Failed to prepare source");
1018
+ cleanup(tempSquare);
1019
+ return false;
1020
+ }
1021
+ cleanup(tempSquare);
1022
+ wipDone(true, "Prepared 1024x1024 source");
1023
+ let totalFailed = 0;
1024
+ if (doWeb) {
1025
+ console.log("");
1026
+ header("Web Icons");
1027
+ const webDir = `${outputDir}/web`;
1028
+ mkdirSync4(webDir, { recursive: true });
1029
+ if (!await generateFavicon(webDir, tempSource)) {
1030
+ totalFailed++;
1031
+ }
1032
+ totalFailed += await generateIcons(webDir, tempSource, WEB_ICONS);
1033
+ }
1034
+ if (doAndroid) {
1035
+ console.log("");
1036
+ header("Android Icons");
1037
+ const androidDir = `${outputDir}/android`;
1038
+ mkdirSync4(androidDir, { recursive: true });
1039
+ totalFailed += await generateIcons(androidDir, tempSource, ANDROID_ICONS);
1040
+ }
1041
+ if (doIos) {
1042
+ console.log("");
1043
+ header("iOS Icons");
1044
+ const iosDir = `${outputDir}/ios`;
1045
+ mkdirSync4(iosDir, { recursive: true });
1046
+ totalFailed += await generateIcons(iosDir, tempSource, IOS_ICONS);
1047
+ }
1048
+ cleanup(tempSource);
1049
+ console.log("");
1050
+ separator();
1051
+ if (totalFailed === 0) {
1052
+ success("All icons generated successfully!");
1053
+ } else {
1054
+ warn(`${totalFailed} icon(s) failed to generate`);
1055
+ }
1056
+ console.log("");
1057
+ info(`Icons saved to: ${outputDir}`);
1058
+ if (doWeb) {
1059
+ step("web/ - Favicon, PWA icons, Apple touch icon");
1060
+ }
1061
+ if (doAndroid) {
1062
+ step("android/ - mipmap folders, Play Store icon");
1063
+ }
1064
+ if (doIos) {
1065
+ step("ios/ - All iOS app icon sizes");
1066
+ }
1067
+ return totalFailed === 0;
1068
+ }
1069
+ async function runGUI(inputRaw) {
1070
+ const input = normalizePath(inputRaw);
1071
+ if (!existsSync4(input)) {
1072
+ error(`File not found: ${input}`);
1073
+ return false;
1074
+ }
1075
+ const dims = await getDimensions(input);
1076
+ if (!dims) {
1077
+ error("Failed to read image dimensions");
1078
+ return false;
1079
+ }
1080
+ const fileInfo = getFileInfo(input);
1081
+ return startGuiServer({
1082
+ htmlFile: "iconpack.html",
1083
+ title: "PicLet - Icon Pack",
1084
+ imageInfo: {
1085
+ filePath: input,
1086
+ fileName: basename2(input),
1087
+ width: dims[0],
1088
+ height: dims[1],
1089
+ borderColor: null
1090
+ },
1091
+ defaults: {
1092
+ web: true,
1093
+ android: true,
1094
+ ios: true
1095
+ },
1096
+ onProcess: async (opts) => {
1097
+ const logs = [];
1098
+ if (!await checkImageMagick()) {
1099
+ return {
1100
+ success: false,
1101
+ error: "ImageMagick not found",
1102
+ logs: [{ type: "error", message: "ImageMagick not found" }]
1103
+ };
1104
+ }
1105
+ const doWeb = opts.web ?? true;
1106
+ const doAndroid = opts.android ?? true;
1107
+ const doIos = opts.ios ?? true;
1108
+ if (!doWeb && !doAndroid && !doIos) {
1109
+ return {
1110
+ success: false,
1111
+ error: "No platforms selected",
1112
+ logs: [{ type: "error", message: "No platforms selected" }]
1113
+ };
1114
+ }
1115
+ const outputDir = `${fileInfo.dirname}/${fileInfo.filename}_icons`;
1116
+ mkdirSync4(outputDir, { recursive: true });
1117
+ logs.push({ type: "info", message: `Output: ${outputDir}` });
1118
+ logs.push({ type: "info", message: "Preparing source image..." });
1119
+ const tempSource = `${outputDir}/.source_1024.png`;
1120
+ const tempSquare = `${outputDir}/.temp_square.png`;
1121
+ if (!await squarify2(input, tempSquare)) {
1122
+ return { success: false, error: "Failed to prepare source", logs };
1123
+ }
1124
+ if (!await scaleToSize(tempSquare, tempSource, 1024)) {
1125
+ cleanup(tempSquare);
1126
+ return { success: false, error: "Failed to prepare source", logs };
1127
+ }
1128
+ cleanup(tempSquare);
1129
+ logs.push({ type: "success", message: "Prepared 1024x1024 source" });
1130
+ let totalFailed = 0;
1131
+ if (doWeb) {
1132
+ logs.push({ type: "info", message: "Generating Web icons..." });
1133
+ const webDir = `${outputDir}/web`;
1134
+ mkdirSync4(webDir, { recursive: true });
1135
+ if (!await generateFaviconSilent(webDir, tempSource)) {
1136
+ totalFailed++;
1137
+ }
1138
+ totalFailed += await generateIconsSilent(webDir, tempSource, WEB_ICONS, logs);
1139
+ logs.push({ type: "success", message: `Web: ${WEB_ICONS.length + 1} icons` });
1140
+ }
1141
+ if (doAndroid) {
1142
+ logs.push({ type: "info", message: "Generating Android icons..." });
1143
+ const androidDir = `${outputDir}/android`;
1144
+ mkdirSync4(androidDir, { recursive: true });
1145
+ totalFailed += await generateIconsSilent(androidDir, tempSource, ANDROID_ICONS, logs);
1146
+ logs.push({ type: "success", message: `Android: ${ANDROID_ICONS.length} icons` });
1147
+ }
1148
+ if (doIos) {
1149
+ logs.push({ type: "info", message: "Generating iOS icons..." });
1150
+ const iosDir = `${outputDir}/ios`;
1151
+ mkdirSync4(iosDir, { recursive: true });
1152
+ totalFailed += await generateIconsSilent(iosDir, tempSource, IOS_ICONS, logs);
1153
+ logs.push({ type: "success", message: `iOS: ${IOS_ICONS.length} icons` });
1154
+ }
1155
+ cleanup(tempSource);
1156
+ if (totalFailed === 0) {
1157
+ return {
1158
+ success: true,
1159
+ output: `Icons saved to ${fileInfo.filename}_icons/`,
1160
+ logs
1161
+ };
1162
+ }
1163
+ return {
1164
+ success: false,
1165
+ error: `${totalFailed} icon(s) failed`,
1166
+ logs
1167
+ };
1168
+ }
1169
+ });
1170
+ }
1171
+ async function generateIconsSilent(outputDir, sourceImg, icons, _logs) {
1172
+ let failed = 0;
1173
+ for (const icon of icons) {
1174
+ const outputPath = `${outputDir}/${icon.filename}`;
1175
+ const subdir = dirname7(outputPath);
1176
+ if (!existsSync4(subdir)) {
1177
+ mkdirSync4(subdir, { recursive: true });
1178
+ }
1179
+ if (!await scaleToSize(sourceImg, outputPath, icon.size)) {
1180
+ failed++;
1181
+ }
1182
+ }
1183
+ return failed;
1184
+ }
1185
+ async function generateFaviconSilent(outputDir, sourceImg) {
1186
+ const temp16 = `${outputDir}/.temp_16.png`;
1187
+ const temp32 = `${outputDir}/.temp_32.png`;
1188
+ const temp48 = `${outputDir}/.temp_48.png`;
1189
+ await scaleToSize(sourceImg, temp16, 16);
1190
+ await scaleToSize(sourceImg, temp32, 32);
1191
+ await scaleToSize(sourceImg, temp48, 48);
1192
+ const result = await createIcoFromMultiple(
1193
+ [temp16, temp32, temp48],
1194
+ `${outputDir}/favicon.ico`
1195
+ );
1196
+ cleanup(temp16, temp32, temp48);
1197
+ return result;
1198
+ }
1199
+ var config = {
1200
+ id: "iconpack",
1201
+ name: "Icon Pack",
1202
+ icon: "iconpack.ico",
1203
+ extensions: [".png", ".jpg", ".jpeg"]
1204
+ };
1205
+
1206
+ // src/tools/makeicon.ts
1207
+ import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
1208
+ import { tmpdir } from "os";
1209
+ import { basename as basename3, join as join4 } from "path";
1210
+ async function run2(inputRaw) {
1211
+ if (!await checkImageMagick()) {
1212
+ error("ImageMagick not found. Please install it:");
1213
+ console.log(" sudo apt update && sudo apt install imagemagick");
1214
+ await pauseOnError();
1215
+ return false;
1216
+ }
1217
+ const input = normalizePath(inputRaw);
1218
+ if (!existsSync5(input)) {
1219
+ error(`File not found: ${input}`);
1220
+ await pauseOnError();
1221
+ return false;
1222
+ }
1223
+ const fileInfo = getFileInfo(input);
1224
+ const output = `${fileInfo.dirname}/${fileInfo.filename}.ico`;
1225
+ const tempTrimmed = `${fileInfo.dirname}/${fileInfo.filename}_trimmed.png`;
1226
+ const tempSquare = `${fileInfo.dirname}/${fileInfo.filename}_square.png`;
1227
+ const tempScaled = `${fileInfo.dirname}/${fileInfo.filename}_scaled.png`;
1228
+ header("PicLet Make Icon");
1229
+ wip("Analyzing image...");
1230
+ const dims = await getDimensions(input);
1231
+ if (!dims) {
1232
+ wipDone(false, "Failed to read image");
1233
+ return false;
1234
+ }
1235
+ wipDone(true, `Original size: ${dims[0]}x${dims[1]}`);
1236
+ wip("Trimming transparent areas...");
1237
+ if (!await trim(input, tempTrimmed)) {
1238
+ wipDone(false, "Trim failed");
1239
+ cleanup(tempTrimmed);
1240
+ return false;
1241
+ }
1242
+ wipDone(true, "Trimmed");
1243
+ wip("Making square...");
1244
+ if (!await squarify2(tempTrimmed, tempSquare)) {
1245
+ wipDone(false, "Square padding failed");
1246
+ cleanup(tempTrimmed, tempSquare);
1247
+ return false;
1248
+ }
1249
+ cleanup(tempTrimmed);
1250
+ wipDone(true, "Made square");
1251
+ wip("Scaling to 512px...");
1252
+ if (!await scaleToSize(tempSquare, tempScaled, 512)) {
1253
+ wipDone(false, "Scaling failed");
1254
+ cleanup(tempSquare, tempScaled);
1255
+ return false;
1256
+ }
1257
+ cleanup(tempSquare);
1258
+ wipDone(true, "Scaled to 512x512");
1259
+ wip("Creating icon (256, 128, 64, 48, 32, 16)...");
1260
+ if (!await createIco(tempScaled, output)) {
1261
+ wipDone(false, "Icon creation failed");
1262
+ cleanup(tempScaled);
1263
+ return false;
1264
+ }
1265
+ cleanup(tempScaled);
1266
+ wipDone(true, "Icon created");
1267
+ console.log("");
1268
+ success(`Output: ${output}`);
1269
+ return true;
1270
+ }
1271
+ async function processForIcon(input, output, options, logs) {
1272
+ const fileInfo = getFileInfo(input);
1273
+ const tempTrimmed = `${fileInfo.dirname}/${fileInfo.filename}_trimmed.png`;
1274
+ const tempSquare = `${fileInfo.dirname}/${fileInfo.filename}_square.png`;
1275
+ const tempScaled = `${fileInfo.dirname}/${fileInfo.filename}_scaled.png`;
1276
+ let currentInput = input;
1277
+ if (options.trim) {
1278
+ logs?.push({ type: "info", message: "Trimming transparent areas..." });
1279
+ if (!await trim(currentInput, tempTrimmed)) {
1280
+ cleanup(tempTrimmed);
1281
+ return false;
1282
+ }
1283
+ currentInput = tempTrimmed;
1284
+ logs?.push({ type: "success", message: "Trimmed" });
1285
+ }
1286
+ if (options.makeSquare) {
1287
+ logs?.push({ type: "info", message: "Making square..." });
1288
+ if (!await squarify2(currentInput, tempSquare)) {
1289
+ cleanup(tempTrimmed, tempSquare);
1290
+ return false;
1291
+ }
1292
+ if (currentInput === tempTrimmed) cleanup(tempTrimmed);
1293
+ currentInput = tempSquare;
1294
+ logs?.push({ type: "success", message: "Made square" });
1295
+ }
1296
+ logs?.push({ type: "info", message: "Scaling to 512px..." });
1297
+ if (!await scaleToSize(currentInput, tempScaled, 512)) {
1298
+ cleanup(tempTrimmed, tempSquare, tempScaled);
1299
+ return false;
1300
+ }
1301
+ if (currentInput === tempSquare) cleanup(tempSquare);
1302
+ else if (currentInput === tempTrimmed) cleanup(tempTrimmed);
1303
+ logs?.push({ type: "success", message: "Scaled to 512x512" });
1304
+ logs?.push({ type: "info", message: "Creating icon (256, 128, 64, 48, 32, 16)..." });
1305
+ if (!await createIco(tempScaled, output)) {
1306
+ cleanup(tempScaled);
1307
+ return false;
1308
+ }
1309
+ cleanup(tempScaled);
1310
+ logs?.push({ type: "success", message: "Icon created" });
1311
+ return true;
1312
+ }
1313
+ async function generatePreview(input, options) {
1314
+ const tempDir = tmpdir();
1315
+ const timestamp = Date.now();
1316
+ const tempTrimmed = join4(tempDir, `piclet-preview-trimmed-${timestamp}.png`);
1317
+ const tempSquare = join4(tempDir, `piclet-preview-square-${timestamp}.png`);
1318
+ const tempOutput = join4(tempDir, `piclet-preview-${timestamp}.png`);
1319
+ try {
1320
+ let currentInput = input;
1321
+ if (options.trim) {
1322
+ if (!await trim(currentInput, tempTrimmed)) {
1323
+ cleanup(tempTrimmed);
1324
+ return { success: false, error: "Trim failed" };
1325
+ }
1326
+ currentInput = tempTrimmed;
1327
+ }
1328
+ if (options.makeSquare) {
1329
+ if (!await squarify2(currentInput, tempSquare)) {
1330
+ cleanup(tempTrimmed, tempSquare);
1331
+ return { success: false, error: "Square failed" };
1332
+ }
1333
+ if (currentInput === tempTrimmed) cleanup(tempTrimmed);
1334
+ currentInput = tempSquare;
1335
+ }
1336
+ if (!await scaleToSize(currentInput, tempOutput, 256)) {
1337
+ cleanup(tempTrimmed, tempSquare, tempOutput);
1338
+ return { success: false, error: "Scale failed" };
1339
+ }
1340
+ if (currentInput === tempSquare) cleanup(tempSquare);
1341
+ else if (currentInput === tempTrimmed) cleanup(tempTrimmed);
1342
+ const buffer = readFileSync3(tempOutput);
1343
+ const base64 = buffer.toString("base64");
1344
+ const imageData = `data:image/png;base64,${base64}`;
1345
+ const dims = await getDimensions(tempOutput);
1346
+ cleanup(tempOutput);
1347
+ return {
1348
+ success: true,
1349
+ imageData,
1350
+ width: dims?.[0],
1351
+ height: dims?.[1]
1352
+ };
1353
+ } catch (err) {
1354
+ cleanup(tempTrimmed, tempSquare, tempOutput);
1355
+ return { success: false, error: err.message };
1356
+ }
1357
+ }
1358
+ async function runGUI2(inputRaw) {
1359
+ const input = normalizePath(inputRaw);
1360
+ if (!existsSync5(input)) {
1361
+ error(`File not found: ${input}`);
1362
+ return false;
1363
+ }
1364
+ const dims = await getDimensions(input);
1365
+ if (!dims) {
1366
+ error("Failed to read image dimensions");
1367
+ return false;
1368
+ }
1369
+ const fileInfo = getFileInfo(input);
1370
+ return startGuiServer({
1371
+ htmlFile: "makeicon.html",
1372
+ title: "PicLet - Make Icon",
1373
+ imageInfo: {
1374
+ filePath: input,
1375
+ fileName: basename3(input),
1376
+ width: dims[0],
1377
+ height: dims[1],
1378
+ borderColor: null
1379
+ },
1380
+ defaults: {
1381
+ trim: true,
1382
+ makeSquare: true
1383
+ },
1384
+ onPreview: async (opts) => {
1385
+ const options = {
1386
+ trim: opts.trim ?? true,
1387
+ makeSquare: opts.makeSquare ?? true
1388
+ };
1389
+ return generatePreview(input, options);
1390
+ },
1391
+ onProcess: async (opts) => {
1392
+ const logs = [];
1393
+ if (!await checkImageMagick()) {
1394
+ return {
1395
+ success: false,
1396
+ error: "ImageMagick not found",
1397
+ logs: [{ type: "error", message: "ImageMagick not found" }]
1398
+ };
1399
+ }
1400
+ const options = {
1401
+ trim: opts.trim ?? true,
1402
+ makeSquare: opts.makeSquare ?? true
1403
+ };
1404
+ const output = `${fileInfo.dirname}/${fileInfo.filename}.ico`;
1405
+ const success2 = await processForIcon(input, output, options, logs);
1406
+ if (success2) {
1407
+ return {
1408
+ success: true,
1409
+ output: `${fileInfo.filename}.ico`,
1410
+ logs
1411
+ };
1412
+ }
1413
+ return { success: false, error: "Icon creation failed", logs };
1414
+ }
1415
+ });
1416
+ }
1417
+ var config2 = {
1418
+ id: "makeicon",
1419
+ name: "Make Icon",
1420
+ icon: "makeicon.ico",
1421
+ extensions: [".png"]
1422
+ };
1423
+
1424
+ // src/tools/piclet-main.ts
1425
+ import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync4, renameSync, writeFileSync as writeFileSync3 } from "fs";
1426
+ import { tmpdir as tmpdir2 } from "os";
1427
+ import { basename as basename4, dirname as dirname8, extname as extname2, join as join5 } from "path";
1428
+ var TOOL_ORDER = ["removebg", "scale", "icons", "storepack"];
1429
+ async function generateCombinedPreview(input, borderColor, opts) {
1430
+ const tempDir = tmpdir2();
1431
+ const ts = Date.now();
1432
+ const temps = [];
1433
+ const makeTempPath = (suffix) => {
1434
+ const p = join5(tempDir, `piclet-${ts}-${suffix}.png`);
1435
+ temps.push(p);
1436
+ return p;
1437
+ };
1438
+ try {
1439
+ let current = input;
1440
+ if (opts.original || opts.tools.length === 0) {
1441
+ const dims2 = await getDimensions(input);
1442
+ let previewPath2 = input;
1443
+ if (dims2 && (dims2[0] > 512 || dims2[1] > 512)) {
1444
+ const scaled = makeTempPath("orig-preview");
1445
+ const targetSize = Math.min(512, Math.max(dims2[0], dims2[1]));
1446
+ if (await scaleToSize(input, scaled, targetSize)) {
1447
+ previewPath2 = scaled;
1448
+ }
1449
+ }
1450
+ const buffer2 = readFileSync4(previewPath2);
1451
+ const finalDims2 = await getDimensions(previewPath2);
1452
+ cleanup(...temps);
1453
+ return {
1454
+ success: true,
1455
+ imageData: `data:image/png;base64,${buffer2.toString("base64")}`,
1456
+ width: finalDims2?.[0] ?? dims2?.[0],
1457
+ height: finalDims2?.[1] ?? dims2?.[1]
1458
+ };
1459
+ }
1460
+ const activeTools = TOOL_ORDER.filter((t) => opts.tools.includes(t));
1461
+ for (const tool of activeTools) {
1462
+ switch (tool) {
1463
+ case "removebg": {
1464
+ const rbOpts = opts.removebg;
1465
+ const out = makeTempPath("removebg");
1466
+ let success2 = false;
1467
+ if (rbOpts.preserveInner && borderColor) {
1468
+ success2 = await removeBackgroundBorderOnly(current, out, borderColor, rbOpts.fuzz);
1469
+ }
1470
+ if (!success2 && borderColor) {
1471
+ success2 = await removeBackground(current, out, borderColor, rbOpts.fuzz);
1472
+ }
1473
+ if (!success2) {
1474
+ cleanup(...temps);
1475
+ return { success: false, error: "Background removal failed" };
1476
+ }
1477
+ if (rbOpts.trim) {
1478
+ const trimOut = makeTempPath("trim");
1479
+ if (await trim(out, trimOut)) {
1480
+ current = trimOut;
1481
+ } else {
1482
+ current = out;
1483
+ }
1484
+ } else {
1485
+ current = out;
1486
+ }
1487
+ break;
1488
+ }
1489
+ case "scale": {
1490
+ const scOpts = opts.scale;
1491
+ const out = makeTempPath("scale");
1492
+ let success2 = false;
1493
+ if (scOpts.makeSquare) {
1494
+ const max = Math.max(scOpts.width, scOpts.height);
1495
+ success2 = await scaleWithPadding(current, out, max, max);
1496
+ } else {
1497
+ success2 = await resize(current, out, scOpts.width, scOpts.height);
1498
+ }
1499
+ if (!success2) {
1500
+ cleanup(...temps);
1501
+ return { success: false, error: "Scale failed" };
1502
+ }
1503
+ current = out;
1504
+ break;
1505
+ }
1506
+ case "icons": {
1507
+ const icOpts = opts.icons;
1508
+ if (icOpts.trim && current === input) {
1509
+ const trimOut = makeTempPath("ic-trim");
1510
+ if (await trim(current, trimOut)) {
1511
+ current = trimOut;
1512
+ }
1513
+ }
1514
+ if (icOpts.makeSquare) {
1515
+ const sqOut = makeTempPath("ic-sq");
1516
+ if (await squarify2(current, sqOut)) {
1517
+ current = sqOut;
1518
+ }
1519
+ }
1520
+ break;
1521
+ }
1522
+ }
1523
+ }
1524
+ const dims = await getDimensions(current);
1525
+ let previewPath = current;
1526
+ if (dims && (dims[0] > 512 || dims[1] > 512)) {
1527
+ const scaled = makeTempPath("preview");
1528
+ const targetSize = Math.min(512, Math.max(dims[0], dims[1]));
1529
+ if (await scaleToSize(current, scaled, targetSize)) {
1530
+ previewPath = scaled;
1531
+ }
1532
+ }
1533
+ const buffer = readFileSync4(previewPath);
1534
+ const finalDims = await getDimensions(previewPath);
1535
+ cleanup(...temps);
1536
+ return {
1537
+ success: true,
1538
+ imageData: `data:image/png;base64,${buffer.toString("base64")}`,
1539
+ width: finalDims?.[0],
1540
+ height: finalDims?.[1]
1541
+ };
1542
+ } catch (err) {
1543
+ cleanup(...temps);
1544
+ return { success: false, error: err.message };
1545
+ }
1546
+ }
1547
+ async function processCombined(input, borderColor, opts, logs) {
1548
+ const fileInfo = getFileInfo(input);
1549
+ const outputs = [];
1550
+ const temps = [];
1551
+ const makeTempPath = (suffix) => {
1552
+ const p = `${fileInfo.dirname}/${fileInfo.filename}_${suffix}.png`;
1553
+ temps.push(p);
1554
+ return p;
1555
+ };
1556
+ let current = input;
1557
+ const activeTools = TOOL_ORDER.filter((t) => opts.tools.includes(t));
1558
+ for (const tool of activeTools) {
1559
+ switch (tool) {
1560
+ case "removebg": {
1561
+ logs.push({ type: "info", message: "Removing background..." });
1562
+ const rbOpts = opts.removebg;
1563
+ const out = makeTempPath("nobg");
1564
+ let success2 = false;
1565
+ if (rbOpts.preserveInner && borderColor) {
1566
+ success2 = await removeBackgroundBorderOnly(current, out, borderColor, rbOpts.fuzz);
1567
+ if (!success2) {
1568
+ logs.push({ type: "warn", message: "Border-only failed, trying full removal" });
1569
+ }
1570
+ }
1571
+ if (!success2 && borderColor) {
1572
+ success2 = await removeBackground(current, out, borderColor, rbOpts.fuzz);
1573
+ }
1574
+ if (!success2) {
1575
+ logs.push({ type: "error", message: "Background removal failed" });
1576
+ cleanup(...temps);
1577
+ return [];
1578
+ }
1579
+ logs.push({ type: "success", message: "Background removed" });
1580
+ if (rbOpts.trim) {
1581
+ logs.push({ type: "info", message: "Trimming edges..." });
1582
+ const trimOut = makeTempPath("trimmed");
1583
+ if (await trim(out, trimOut)) {
1584
+ cleanup(out);
1585
+ temps.splice(temps.indexOf(out), 1);
1586
+ current = trimOut;
1587
+ logs.push({ type: "success", message: "Trimmed" });
1588
+ } else {
1589
+ current = out;
1590
+ }
1591
+ } else {
1592
+ current = out;
1593
+ }
1594
+ if (activeTools.indexOf(tool) === activeTools.length - 1) {
1595
+ const finalOut = `${fileInfo.dirname}/${fileInfo.filename}_nobg.png`;
1596
+ renameSync(current, finalOut);
1597
+ temps.splice(temps.indexOf(current), 1);
1598
+ outputs.push(basename4(finalOut));
1599
+ }
1600
+ break;
1601
+ }
1602
+ case "scale": {
1603
+ logs.push({ type: "info", message: "Scaling image..." });
1604
+ const scOpts = opts.scale;
1605
+ const out = makeTempPath("scaled");
1606
+ let success2 = false;
1607
+ if (scOpts.makeSquare) {
1608
+ const max = Math.max(scOpts.width, scOpts.height);
1609
+ success2 = await scaleWithPadding(current, out, max, max);
1610
+ } else {
1611
+ success2 = await resize(current, out, scOpts.width, scOpts.height);
1612
+ }
1613
+ if (!success2) {
1614
+ logs.push({ type: "error", message: "Scale failed" });
1615
+ cleanup(...temps);
1616
+ return [];
1617
+ }
1618
+ const dims = await getDimensions(out);
1619
+ logs.push({ type: "success", message: `Scaled to ${dims?.[0]}\xD7${dims?.[1]}` });
1620
+ if (current !== input && temps.includes(current)) {
1621
+ cleanup(current);
1622
+ temps.splice(temps.indexOf(current), 1);
1623
+ }
1624
+ current = out;
1625
+ if (activeTools.indexOf(tool) === activeTools.length - 1) {
1626
+ const finalOut = `${fileInfo.dirname}/${fileInfo.filename}_scaled${fileInfo.extension}`;
1627
+ renameSync(current, finalOut);
1628
+ temps.splice(temps.indexOf(current), 1);
1629
+ outputs.push(basename4(finalOut));
1630
+ }
1631
+ break;
1632
+ }
1633
+ case "icons": {
1634
+ logs.push({ type: "info", message: "Generating icons..." });
1635
+ const icOpts = opts.icons;
1636
+ if (!icOpts.ico && !icOpts.web && !icOpts.android && !icOpts.ios) {
1637
+ logs.push({ type: "error", message: "No output format selected" });
1638
+ return [];
1639
+ }
1640
+ let iconSource = current;
1641
+ if (icOpts.trim && current === input) {
1642
+ logs.push({ type: "info", message: "Trimming edges..." });
1643
+ const trimOut = makeTempPath("ic-trim");
1644
+ if (await trim(current, trimOut)) {
1645
+ iconSource = trimOut;
1646
+ logs.push({ type: "success", message: "Trimmed" });
1647
+ }
1648
+ }
1649
+ if (icOpts.makeSquare) {
1650
+ logs.push({ type: "info", message: "Making square..." });
1651
+ const sqOut = makeTempPath("ic-sq");
1652
+ if (await squarify2(iconSource, sqOut)) {
1653
+ if (iconSource !== current && iconSource !== input) cleanup(iconSource);
1654
+ iconSource = sqOut;
1655
+ logs.push({ type: "success", message: "Made square" });
1656
+ }
1657
+ }
1658
+ const maxSize = icOpts.web || icOpts.android || icOpts.ios ? 1024 : 512;
1659
+ const srcTemp = makeTempPath("ic-src");
1660
+ if (!await scaleToSize(iconSource, srcTemp, maxSize)) {
1661
+ logs.push({ type: "error", message: "Failed to prepare icon source" });
1662
+ cleanup(...temps);
1663
+ return [];
1664
+ }
1665
+ if (iconSource !== current && iconSource !== input) cleanup(iconSource);
1666
+ let totalCount = 0;
1667
+ if (icOpts.ico) {
1668
+ logs.push({ type: "info", message: "Creating ICO file..." });
1669
+ const icoOut = `${fileInfo.dirname}/${fileInfo.filename}.ico`;
1670
+ if (await createIco(srcTemp, icoOut)) {
1671
+ logs.push({ type: "success", message: "ICO: 6 sizes (256, 128, 64, 48, 32, 16)" });
1672
+ outputs.push(basename4(icoOut));
1673
+ totalCount += 6;
1674
+ } else {
1675
+ logs.push({ type: "warn", message: "ICO creation failed" });
1676
+ }
1677
+ }
1678
+ const needsPacks = icOpts.web || icOpts.android || icOpts.ios;
1679
+ if (needsPacks) {
1680
+ const outputDir = `${fileInfo.dirname}/${fileInfo.filename}_icons`;
1681
+ mkdirSync5(outputDir, { recursive: true });
1682
+ if (icOpts.web) {
1683
+ logs.push({ type: "info", message: "Generating Web icons..." });
1684
+ const webDir = `${outputDir}/web`;
1685
+ mkdirSync5(webDir, { recursive: true });
1686
+ const t16 = `${webDir}/.t16.png`, t32 = `${webDir}/.t32.png`, t48 = `${webDir}/.t48.png`;
1687
+ await scaleToSize(srcTemp, t16, 16);
1688
+ await scaleToSize(srcTemp, t32, 32);
1689
+ await scaleToSize(srcTemp, t48, 48);
1690
+ await createIcoFromMultiple([t16, t32, t48], `${webDir}/favicon.ico`);
1691
+ cleanup(t16, t32, t48);
1692
+ totalCount++;
1693
+ const webIcons = [
1694
+ { name: "favicon-16x16.png", size: 16 },
1695
+ { name: "favicon-32x32.png", size: 32 },
1696
+ { name: "apple-touch-icon.png", size: 180 },
1697
+ { name: "android-chrome-192x192.png", size: 192 },
1698
+ { name: "android-chrome-512x512.png", size: 512 },
1699
+ { name: "mstile-150x150.png", size: 150 }
1700
+ ];
1701
+ for (const i of webIcons) {
1702
+ await scaleToSize(srcTemp, `${webDir}/${i.name}`, i.size);
1703
+ totalCount++;
1704
+ }
1705
+ logs.push({ type: "success", message: "Web: 7 icons" });
1706
+ }
1707
+ if (icOpts.android) {
1708
+ logs.push({ type: "info", message: "Generating Android icons..." });
1709
+ const androidDir = `${outputDir}/android`;
1710
+ const androidIcons = [
1711
+ { name: "mipmap-mdpi/ic_launcher.png", size: 48 },
1712
+ { name: "mipmap-hdpi/ic_launcher.png", size: 72 },
1713
+ { name: "mipmap-xhdpi/ic_launcher.png", size: 96 },
1714
+ { name: "mipmap-xxhdpi/ic_launcher.png", size: 144 },
1715
+ { name: "mipmap-xxxhdpi/ic_launcher.png", size: 192 },
1716
+ { name: "playstore-icon.png", size: 512 }
1717
+ ];
1718
+ for (const i of androidIcons) {
1719
+ const p = `${androidDir}/${i.name}`;
1720
+ mkdirSync5(dirname8(p), { recursive: true });
1721
+ await scaleToSize(srcTemp, p, i.size);
1722
+ totalCount++;
1723
+ }
1724
+ logs.push({ type: "success", message: "Android: 6 icons" });
1725
+ }
1726
+ if (icOpts.ios) {
1727
+ logs.push({ type: "info", message: "Generating iOS icons..." });
1728
+ const iosDir = `${outputDir}/ios`;
1729
+ mkdirSync5(iosDir, { recursive: true });
1730
+ const iosSizes = [20, 29, 40, 58, 60, 76, 80, 87, 120, 152, 167, 180, 1024];
1731
+ for (const s of iosSizes) {
1732
+ await scaleToSize(srcTemp, `${iosDir}/AppIcon-${s}.png`, s);
1733
+ totalCount++;
1734
+ }
1735
+ logs.push({ type: "success", message: `iOS: ${iosSizes.length} icons` });
1736
+ }
1737
+ outputs.push(`${totalCount} icons \u2192 ${fileInfo.filename}_icons/`);
1738
+ }
1739
+ cleanup(srcTemp);
1740
+ logs.push({ type: "success", message: `Generated ${totalCount} total icons` });
1741
+ break;
1742
+ }
1743
+ case "storepack": {
1744
+ logs.push({ type: "info", message: "Generating store assets..." });
1745
+ const spOpts = opts.storepack;
1746
+ if (!spOpts.dimensions || spOpts.dimensions.length === 0) {
1747
+ logs.push({ type: "error", message: "No dimensions specified" });
1748
+ return [];
1749
+ }
1750
+ const folderName = spOpts.presetName || "assets";
1751
+ const outputDir = `${fileInfo.dirname}/${fileInfo.filename}_${folderName}`;
1752
+ mkdirSync5(outputDir, { recursive: true });
1753
+ let count = 0;
1754
+ for (const dim of spOpts.dimensions) {
1755
+ const filename = dim.filename || `${dim.width}x${dim.height}.png`;
1756
+ const out = join5(outputDir, filename);
1757
+ let success2 = false;
1758
+ switch (spOpts.scaleMode) {
1759
+ case "fill":
1760
+ success2 = await scaleFillCrop(current, out, dim.width, dim.height);
1761
+ break;
1762
+ case "stretch":
1763
+ success2 = await resize(current, out, dim.width, dim.height);
1764
+ break;
1765
+ default:
1766
+ success2 = await scaleWithPadding(current, out, dim.width, dim.height);
1767
+ }
1768
+ if (success2) count++;
1769
+ }
1770
+ logs.push({ type: "success", message: `Generated ${count}/${spOpts.dimensions.length} images` });
1771
+ outputs.push(`${count} images \u2192 ${fileInfo.filename}_${folderName}/`);
1772
+ break;
1773
+ }
1774
+ }
1775
+ }
1776
+ cleanup(...temps);
1777
+ return outputs;
1778
+ }
1779
+ async function runGUI3(inputRaw) {
1780
+ let currentInput = normalizePath(inputRaw);
1781
+ if (!existsSync6(currentInput)) {
1782
+ error(`File not found: ${currentInput}`);
1783
+ return false;
1784
+ }
1785
+ const dims = await getDimensions(currentInput);
1786
+ if (!dims) {
1787
+ error("Failed to read image dimensions");
1788
+ return false;
1789
+ }
1790
+ let currentBorderColor = await getBorderColor(currentInput);
1791
+ const presets = loadPresets();
1792
+ return startGuiServer({
1793
+ htmlFile: "piclet.html",
1794
+ title: "PicLet",
1795
+ imageInfo: {
1796
+ filePath: currentInput,
1797
+ fileName: basename4(currentInput),
1798
+ width: dims[0],
1799
+ height: dims[1],
1800
+ borderColor: currentBorderColor
1801
+ },
1802
+ defaults: {
1803
+ // Return full preset data for the UI
1804
+ presets: presets.map((p) => ({
1805
+ id: p.id,
1806
+ name: p.name,
1807
+ description: p.description,
1808
+ icons: p.icons
1809
+ }))
1810
+ },
1811
+ onPreview: async (opts) => {
1812
+ const toolOpts = opts;
1813
+ if (!toolOpts.tools) {
1814
+ toolOpts.tools = [];
1815
+ }
1816
+ return generateCombinedPreview(currentInput, currentBorderColor, toolOpts);
1817
+ },
1818
+ onProcess: async (opts) => {
1819
+ const logs = [];
1820
+ const toolOpts = opts;
1821
+ if (!toolOpts.tools || toolOpts.tools.length === 0) {
1822
+ return { success: false, error: "No tools selected", logs };
1823
+ }
1824
+ if (!await checkImageMagick()) {
1825
+ return {
1826
+ success: false,
1827
+ error: "ImageMagick not found",
1828
+ logs: [{ type: "error", message: "ImageMagick not found. Install: sudo apt install imagemagick" }]
1829
+ };
1830
+ }
1831
+ const outputs = await processCombined(currentInput, currentBorderColor, toolOpts, logs);
1832
+ if (outputs.length > 0) {
1833
+ return {
1834
+ success: true,
1835
+ output: outputs.join("\n"),
1836
+ logs
1837
+ };
1838
+ }
1839
+ return { success: false, error: "Processing failed", logs };
1840
+ },
1841
+ onLoadImage: async (data) => {
1842
+ try {
1843
+ const ext = extname2(data.fileName) || ".png";
1844
+ const tempPath = join5(tmpdir2(), `piclet-load-${Date.now()}${ext}`);
1845
+ const buffer = Buffer.from(data.data, "base64");
1846
+ writeFileSync3(tempPath, buffer);
1847
+ const newDims = await getDimensions(tempPath);
1848
+ if (!newDims) {
1849
+ cleanup(tempPath);
1850
+ return { success: false, error: "Failed to read image dimensions" };
1851
+ }
1852
+ const newBorderColor = await getBorderColor(tempPath);
1853
+ currentInput = tempPath;
1854
+ currentBorderColor = newBorderColor;
1855
+ return {
1856
+ success: true,
1857
+ filePath: tempPath,
1858
+ fileName: data.fileName,
1859
+ width: newDims[0],
1860
+ height: newDims[1],
1861
+ borderColor: newBorderColor
1862
+ };
1863
+ } catch (err) {
1864
+ return { success: false, error: err.message };
1865
+ }
1866
+ }
1867
+ });
1868
+ }
1869
+ var config3 = {
1870
+ id: "piclet",
1871
+ name: "PicLet",
1872
+ icon: "banana.ico",
1873
+ extensions: [".png", ".jpg", ".jpeg", ".gif", ".bmp", ".ico"]
1874
+ };
1875
+
1876
+ // src/tools/remove-bg.ts
1877
+ import { existsSync as existsSync7, readFileSync as readFileSync5, renameSync as renameSync2 } from "fs";
1878
+ import { tmpdir as tmpdir3 } from "os";
1879
+ import { basename as basename5, join as join6 } from "path";
1880
+ async function processImage(input, borderColor, options) {
1881
+ const fileInfo = getFileInfo(input);
1882
+ const output = `${fileInfo.dirname}/${fileInfo.filename}_nobg.png`;
1883
+ const tempFile = `${fileInfo.dirname}/${fileInfo.filename}_temp.png`;
1884
+ wip("Removing background...");
1885
+ let bgRemoved = false;
1886
+ if (options.preserveInner && borderColor) {
1887
+ bgRemoved = await removeBackgroundBorderOnly(
1888
+ input,
1889
+ tempFile,
1890
+ borderColor,
1891
+ options.fuzz
1892
+ );
1893
+ if (!bgRemoved) {
1894
+ warn("Border-only removal failed, using standard method");
1895
+ }
1896
+ }
1897
+ if (!bgRemoved && borderColor) {
1898
+ bgRemoved = await removeBackground(input, tempFile, borderColor, options.fuzz);
1899
+ }
1900
+ if (!bgRemoved) {
1901
+ wipDone(false, "Background removal failed");
1902
+ cleanup(tempFile);
1903
+ return false;
1904
+ }
1905
+ wipDone(true, "Background removed");
1906
+ if (options.doTrim) {
1907
+ wip("Trimming transparent edges...");
1908
+ if (await trim(tempFile, output)) {
1909
+ wipDone(true, "Trimmed");
1910
+ cleanup(tempFile);
1911
+ } else {
1912
+ wipDone(false, "Trim failed, keeping untrimmed");
1913
+ renameSync2(tempFile, output);
1914
+ }
1915
+ } else {
1916
+ renameSync2(tempFile, output);
1917
+ }
1918
+ if (options.makeSquare) {
1919
+ wip("Making square...");
1920
+ const tempSquare = `${fileInfo.dirname}/${fileInfo.filename}_square_temp.png`;
1921
+ if (await squarify2(output, tempSquare)) {
1922
+ renameSync2(tempSquare, output);
1923
+ wipDone(true, "Made square");
1924
+ } else {
1925
+ wipDone(false, "Square padding failed");
1926
+ cleanup(tempSquare);
1927
+ }
1928
+ }
1929
+ const finalDims = await getDimensions(output);
1930
+ console.log("");
1931
+ if (finalDims) {
1932
+ success(`Output: ${output} (${finalDims[0]}x${finalDims[1]})`);
1933
+ } else {
1934
+ success(`Output: ${output}`);
1935
+ }
1936
+ return true;
1937
+ }
1938
+ async function collectOptionsCLI() {
1939
+ console.log("");
1940
+ console.log(`${BOLD}Fuzz Value:${RESET} Controls color matching strictness`);
1941
+ console.log(` ${DIM}0-10% = Only exact or very similar colors${RESET}`);
1942
+ console.log(` ${DIM}10-30% = Somewhat similar colors${RESET}`);
1943
+ console.log(` ${DIM}30-70% = Aggressive removal${RESET}`);
1944
+ console.log(` ${DIM}70-100% = May affect non-background areas${RESET}`);
1945
+ console.log("");
1946
+ let fuzz = await number("Fuzz value (0-100)", 10, 0, 100);
1947
+ if (fuzz < 0 || fuzz > 100) {
1948
+ warn("Invalid fuzz value, using 10");
1949
+ fuzz = 10;
1950
+ }
1951
+ console.log("");
1952
+ const doTrim = await confirm(
1953
+ "Trim transparent edges after removal?",
1954
+ true
1955
+ );
1956
+ console.log("");
1957
+ const preserveInner = await confirm(
1958
+ "Preserve inner areas of same color? (border-only removal)",
1959
+ false
1960
+ );
1961
+ console.log("");
1962
+ const makeSquare = await confirm(
1963
+ "Make output square with transparent padding?",
1964
+ false
1965
+ );
1966
+ return { fuzz, doTrim, preserveInner, makeSquare };
1967
+ }
1968
+ async function run3(inputRaw) {
1969
+ if (!await checkImageMagick()) {
1970
+ error("ImageMagick not found. Please install it:");
1971
+ console.log(" sudo apt update && sudo apt install imagemagick");
1972
+ await pauseOnError();
1973
+ return false;
1974
+ }
1975
+ const input = normalizePath(inputRaw);
1976
+ if (!existsSync7(input)) {
1977
+ error(`File not found: ${input}`);
1978
+ await pauseOnError();
1979
+ return false;
1980
+ }
1981
+ header("PicLet Remove Background");
1982
+ wip("Analyzing image...");
1983
+ const dims = await getDimensions(input);
1984
+ if (!dims) {
1985
+ wipDone(false, "Failed to read image");
1986
+ return false;
1987
+ }
1988
+ const borderColor = await getBorderColor(input);
1989
+ wipDone(true, `Size: ${dims[0]}x${dims[1]}`);
1990
+ if (borderColor) {
1991
+ info(`Detected border color: ${borderColor}`);
1992
+ }
1993
+ const options = await collectOptionsCLI();
1994
+ console.log("");
1995
+ return processImage(input, borderColor, options);
1996
+ }
1997
+ async function runGUI4(inputRaw) {
1998
+ const input = normalizePath(inputRaw);
1999
+ if (!existsSync7(input)) {
2000
+ error(`File not found: ${input}`);
2001
+ return false;
2002
+ }
2003
+ const dims = await getDimensions(input);
2004
+ if (!dims) {
2005
+ error("Failed to read image dimensions");
2006
+ return false;
2007
+ }
2008
+ const borderColor = await getBorderColor(input);
2009
+ const config7 = loadConfig();
2010
+ const defaults = config7.removeBg;
2011
+ return startGuiServer({
2012
+ htmlFile: "remove-bg.html",
2013
+ title: "PicLet - Remove Background",
2014
+ imageInfo: {
2015
+ filePath: input,
2016
+ fileName: basename5(input),
2017
+ width: dims[0],
2018
+ height: dims[1],
2019
+ borderColor
2020
+ },
2021
+ defaults: {
2022
+ fuzz: defaults.fuzz,
2023
+ trim: defaults.trim,
2024
+ preserveInner: defaults.preserveInner,
2025
+ makeSquare: defaults.makeSquare
2026
+ },
2027
+ onPreview: async (opts) => {
2028
+ const options = {
2029
+ fuzz: opts.fuzz ?? defaults.fuzz,
2030
+ doTrim: opts.trim ?? defaults.trim,
2031
+ preserveInner: opts.preserveInner ?? defaults.preserveInner,
2032
+ makeSquare: opts.makeSquare ?? defaults.makeSquare
2033
+ };
2034
+ return generatePreview2(input, borderColor, options);
2035
+ },
2036
+ onProcess: async (opts) => {
2037
+ const logs = [];
2038
+ if (!await checkImageMagick()) {
2039
+ return {
2040
+ success: false,
2041
+ error: "ImageMagick not found",
2042
+ logs: [{ type: "error", message: "ImageMagick not found. Install with: sudo apt install imagemagick" }]
2043
+ };
2044
+ }
2045
+ logs.push({ type: "info", message: `Processing ${basename5(input)}...` });
2046
+ const options = {
2047
+ fuzz: opts.fuzz ?? defaults.fuzz,
2048
+ doTrim: opts.trim ?? defaults.trim,
2049
+ preserveInner: opts.preserveInner ?? defaults.preserveInner,
2050
+ makeSquare: opts.makeSquare ?? defaults.makeSquare
2051
+ };
2052
+ const fileInfo = getFileInfo(input);
2053
+ const output = `${fileInfo.dirname}/${fileInfo.filename}_nobg.png`;
2054
+ logs.push({ type: "info", message: "Removing background..." });
2055
+ const success2 = await processImageSilent(input, borderColor, options, logs);
2056
+ if (success2) {
2057
+ const finalDims = await getDimensions(output);
2058
+ const sizeStr = finalDims ? ` (${finalDims[0]}x${finalDims[1]})` : "";
2059
+ return {
2060
+ success: true,
2061
+ output: `${basename5(output)}${sizeStr}`,
2062
+ logs
2063
+ };
2064
+ }
2065
+ return {
2066
+ success: false,
2067
+ error: "Processing failed",
2068
+ logs
2069
+ };
2070
+ }
2071
+ });
2072
+ }
2073
+ async function processImageSilent(input, borderColor, options, logs) {
2074
+ const fileInfo = getFileInfo(input);
2075
+ const output = `${fileInfo.dirname}/${fileInfo.filename}_nobg.png`;
2076
+ const tempFile = `${fileInfo.dirname}/${fileInfo.filename}_temp.png`;
2077
+ let bgRemoved = false;
2078
+ if (options.preserveInner && borderColor) {
2079
+ bgRemoved = await removeBackgroundBorderOnly(
2080
+ input,
2081
+ tempFile,
2082
+ borderColor,
2083
+ options.fuzz
2084
+ );
2085
+ if (!bgRemoved) {
2086
+ logs.push({ type: "warn", message: "Border-only removal failed, using standard method" });
2087
+ }
2088
+ }
2089
+ if (!bgRemoved && borderColor) {
2090
+ bgRemoved = await removeBackground(input, tempFile, borderColor, options.fuzz);
2091
+ }
2092
+ if (!bgRemoved) {
2093
+ logs.push({ type: "error", message: "Background removal failed" });
2094
+ cleanup(tempFile);
2095
+ return false;
2096
+ }
2097
+ logs.push({ type: "success", message: "Background removed" });
2098
+ if (options.doTrim) {
2099
+ logs.push({ type: "info", message: "Trimming transparent edges..." });
2100
+ if (await trim(tempFile, output)) {
2101
+ logs.push({ type: "success", message: "Trimmed" });
2102
+ cleanup(tempFile);
2103
+ } else {
2104
+ logs.push({ type: "warn", message: "Trim failed, keeping untrimmed" });
2105
+ renameSync2(tempFile, output);
2106
+ }
2107
+ } else {
2108
+ renameSync2(tempFile, output);
2109
+ }
2110
+ if (options.makeSquare) {
2111
+ logs.push({ type: "info", message: "Making square..." });
2112
+ const tempSquare = `${fileInfo.dirname}/${fileInfo.filename}_square_temp.png`;
2113
+ if (await squarify2(output, tempSquare)) {
2114
+ renameSync2(tempSquare, output);
2115
+ logs.push({ type: "success", message: "Made square" });
2116
+ } else {
2117
+ logs.push({ type: "warn", message: "Square padding failed" });
2118
+ cleanup(tempSquare);
2119
+ }
2120
+ }
2121
+ return true;
2122
+ }
2123
+ async function generatePreview2(input, borderColor, options) {
2124
+ const tempDir = tmpdir3();
2125
+ const timestamp = Date.now();
2126
+ const tempFile = join6(tempDir, `piclet-preview-${timestamp}.png`);
2127
+ const tempOutput = join6(tempDir, `piclet-preview-${timestamp}-out.png`);
2128
+ try {
2129
+ let bgRemoved = false;
2130
+ if (options.preserveInner && borderColor) {
2131
+ bgRemoved = await removeBackgroundBorderOnly(input, tempFile, borderColor, options.fuzz);
2132
+ }
2133
+ if (!bgRemoved && borderColor) {
2134
+ bgRemoved = await removeBackground(input, tempFile, borderColor, options.fuzz);
2135
+ }
2136
+ if (!bgRemoved) {
2137
+ return { success: false, error: "Background removal failed" };
2138
+ }
2139
+ let currentFile = tempFile;
2140
+ if (options.doTrim) {
2141
+ if (await trim(tempFile, tempOutput)) {
2142
+ cleanup(tempFile);
2143
+ currentFile = tempOutput;
2144
+ }
2145
+ }
2146
+ if (options.makeSquare) {
2147
+ const squareFile = join6(tempDir, `piclet-preview-${timestamp}-sq.png`);
2148
+ if (await squarify2(currentFile, squareFile)) {
2149
+ cleanup(currentFile);
2150
+ currentFile = squareFile;
2151
+ }
2152
+ }
2153
+ const buffer = readFileSync5(currentFile);
2154
+ const base64 = buffer.toString("base64");
2155
+ const imageData = `data:image/png;base64,${base64}`;
2156
+ const dims = await getDimensions(currentFile);
2157
+ cleanup(currentFile, tempFile, tempOutput);
2158
+ return {
2159
+ success: true,
2160
+ imageData,
2161
+ width: dims?.[0],
2162
+ height: dims?.[1]
2163
+ };
2164
+ } catch (err) {
2165
+ cleanup(tempFile, tempOutput);
2166
+ return { success: false, error: err.message };
2167
+ }
2168
+ }
2169
+ var config4 = {
2170
+ id: "remove-bg",
2171
+ name: "Remove Background",
2172
+ icon: "removebg.ico",
2173
+ extensions: [".png", ".jpg", ".jpeg", ".ico"]
2174
+ };
2175
+
2176
+ // src/tools/rescale.ts
2177
+ import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
2178
+ import { tmpdir as tmpdir4 } from "os";
2179
+ import { basename as basename6, join as join7 } from "path";
2180
+ async function run4(inputRaw) {
2181
+ if (!await checkImageMagick()) {
2182
+ error("ImageMagick not found. Please install it:");
2183
+ console.log(" sudo apt update && sudo apt install imagemagick");
2184
+ await pauseOnError();
2185
+ return false;
2186
+ }
2187
+ const input = normalizePath(inputRaw);
2188
+ if (!existsSync8(input)) {
2189
+ error(`File not found: ${input}`);
2190
+ await pauseOnError();
2191
+ return false;
2192
+ }
2193
+ const fileInfo = getFileInfo(input);
2194
+ const output = `${fileInfo.dirname}/${fileInfo.filename}_scaled${fileInfo.extension}`;
2195
+ header("PicLet Scale Image");
2196
+ wip("Reading image dimensions...");
2197
+ const dims = await getDimensions(input);
2198
+ if (!dims) {
2199
+ wipDone(false, "Failed to read image");
2200
+ return false;
2201
+ }
2202
+ const [origW, origH] = dims;
2203
+ wipDone(true, `Original size: ${origW}x${origH}`);
2204
+ if (origW !== origH) {
2205
+ warn(`Image is not square (${origW}x${origH})`);
2206
+ }
2207
+ console.log("");
2208
+ console.log(`${BOLD}Scaling Options:${RESET}`);
2209
+ console.log(
2210
+ ` ${DIM}Enter 0 for auto-calculate based on other dimension${RESET}`
2211
+ );
2212
+ console.log(
2213
+ ` ${DIM}Enter single number for both to make square output${RESET}`
2214
+ );
2215
+ console.log("");
2216
+ let targetW = await number("Width (0 for auto)", 0, 0);
2217
+ let targetH = await number("Height (0 for auto)", 0, 0);
2218
+ let makeSquare = false;
2219
+ if (targetW === 0 && targetH === 0) {
2220
+ targetW = Math.floor(origW / 2);
2221
+ targetH = Math.floor(origH / 2);
2222
+ info(`Using default: 50% scale (${targetW}x${targetH})`);
2223
+ } else if (targetW > 0 && targetH === 0) {
2224
+ targetH = Math.floor(targetW * origH / origW);
2225
+ info(`Calculated height: ${targetH}`);
2226
+ } else if (targetW === 0 && targetH > 0) {
2227
+ targetW = Math.floor(targetH * origW / origH);
2228
+ info(`Calculated width: ${targetW}`);
2229
+ }
2230
+ console.log("");
2231
+ makeSquare = await confirm(
2232
+ "Make output square with transparent padding?",
2233
+ false
2234
+ );
2235
+ if (makeSquare) {
2236
+ const maxDim = Math.max(targetW, targetH);
2237
+ targetW = maxDim;
2238
+ targetH = maxDim;
2239
+ info(`Output will be ${targetW}x${targetH} (square with padding)`);
2240
+ }
2241
+ console.log("");
2242
+ wip("Scaling image...");
2243
+ let scaled = false;
2244
+ if (makeSquare) {
2245
+ scaled = await scaleWithPadding(input, output, targetW, targetH);
2246
+ } else {
2247
+ scaled = await resize(input, output, targetW, targetH);
2248
+ }
2249
+ if (!scaled || !existsSync8(output)) {
2250
+ wipDone(false, "Scaling failed");
2251
+ return false;
2252
+ }
2253
+ const finalDims = await getDimensions(output);
2254
+ if (finalDims) {
2255
+ wipDone(true, `Scaled to ${finalDims[0]}x${finalDims[1]}`);
2256
+ } else {
2257
+ wipDone(true, "Scaled");
2258
+ }
2259
+ console.log("");
2260
+ success(`Output: ${output}`);
2261
+ return true;
2262
+ }
2263
+ async function runGUI5(inputRaw) {
2264
+ const input = normalizePath(inputRaw);
2265
+ if (!existsSync8(input)) {
2266
+ error(`File not found: ${input}`);
2267
+ return false;
2268
+ }
2269
+ const dims = await getDimensions(input);
2270
+ if (!dims) {
2271
+ error("Failed to read image dimensions");
2272
+ return false;
2273
+ }
2274
+ const fileInfo = getFileInfo(input);
2275
+ return startGuiServer({
2276
+ htmlFile: "rescale.html",
2277
+ title: "PicLet - Scale Image",
2278
+ imageInfo: {
2279
+ filePath: input,
2280
+ fileName: basename6(input),
2281
+ width: dims[0],
2282
+ height: dims[1],
2283
+ borderColor: null
2284
+ },
2285
+ defaults: {
2286
+ makeSquare: false
2287
+ },
2288
+ onPreview: async (opts) => {
2289
+ const options = {
2290
+ width: opts.width ?? Math.round(dims[0] / 2),
2291
+ height: opts.height ?? Math.round(dims[1] / 2),
2292
+ makeSquare: opts.makeSquare ?? false
2293
+ };
2294
+ return generatePreview3(input, options);
2295
+ },
2296
+ onProcess: async (opts) => {
2297
+ const logs = [];
2298
+ if (!await checkImageMagick()) {
2299
+ return {
2300
+ success: false,
2301
+ error: "ImageMagick not found",
2302
+ logs: [{ type: "error", message: "ImageMagick not found" }]
2303
+ };
2304
+ }
2305
+ const options = {
2306
+ width: opts.width ?? Math.round(dims[0] / 2),
2307
+ height: opts.height ?? Math.round(dims[1] / 2),
2308
+ makeSquare: opts.makeSquare ?? false
2309
+ };
2310
+ logs.push({ type: "info", message: `Scaling to ${options.width}x${options.height}...` });
2311
+ const output = `${fileInfo.dirname}/${fileInfo.filename}_scaled${fileInfo.extension}`;
2312
+ let scaled = false;
2313
+ if (options.makeSquare) {
2314
+ const maxDim = Math.max(options.width, options.height);
2315
+ scaled = await scaleWithPadding(input, output, maxDim, maxDim);
2316
+ } else {
2317
+ scaled = await resize(input, output, options.width, options.height);
2318
+ }
2319
+ if (scaled && existsSync8(output)) {
2320
+ const finalDims = await getDimensions(output);
2321
+ const sizeStr = finalDims ? ` (${finalDims[0]}x${finalDims[1]})` : "";
2322
+ logs.push({ type: "success", message: "Scaled successfully" });
2323
+ return {
2324
+ success: true,
2325
+ output: `${basename6(output)}${sizeStr}`,
2326
+ logs
2327
+ };
2328
+ }
2329
+ logs.push({ type: "error", message: "Scaling failed" });
2330
+ return { success: false, error: "Scaling failed", logs };
2331
+ }
2332
+ });
2333
+ }
2334
+ async function generatePreview3(input, options) {
2335
+ const tempDir = tmpdir4();
2336
+ const timestamp = Date.now();
2337
+ const tempOutput = join7(tempDir, `piclet-preview-${timestamp}.png`);
2338
+ try {
2339
+ let scaled = false;
2340
+ let targetW = options.width;
2341
+ let targetH = options.height;
2342
+ if (options.makeSquare) {
2343
+ const maxDim = Math.max(targetW, targetH);
2344
+ targetW = maxDim;
2345
+ targetH = maxDim;
2346
+ scaled = await scaleWithPadding(input, tempOutput, targetW, targetH);
2347
+ } else {
2348
+ scaled = await resize(input, tempOutput, targetW, targetH);
2349
+ }
2350
+ if (!scaled || !existsSync8(tempOutput)) {
2351
+ return { success: false, error: "Scaling failed" };
2352
+ }
2353
+ const buffer = readFileSync6(tempOutput);
2354
+ const base64 = buffer.toString("base64");
2355
+ const imageData = `data:image/png;base64,${base64}`;
2356
+ const dims = await getDimensions(tempOutput);
2357
+ cleanup(tempOutput);
2358
+ return {
2359
+ success: true,
2360
+ imageData,
2361
+ width: dims?.[0],
2362
+ height: dims?.[1]
2363
+ };
2364
+ } catch (err) {
2365
+ cleanup(tempOutput);
2366
+ return { success: false, error: err.message };
2367
+ }
2368
+ }
2369
+ var config5 = {
2370
+ id: "rescale",
2371
+ name: "Scale Image",
2372
+ icon: "rescale.ico",
2373
+ extensions: [".png", ".jpg", ".jpeg", ".gif", ".bmp"]
2374
+ };
2375
+
2376
+ // src/tools/storepack.ts
2377
+ import { existsSync as existsSync9, mkdirSync as mkdirSync6 } from "fs";
2378
+ import { basename as basename7, join as join8 } from "path";
2379
+ async function scaleImage(input, output, width, height, mode) {
2380
+ switch (mode) {
2381
+ case "fill":
2382
+ return scaleFillCrop(input, output, width, height);
2383
+ case "stretch":
2384
+ return resize(input, output, width, height);
2385
+ case "fit":
2386
+ default:
2387
+ return scaleWithPadding(input, output, width, height);
2388
+ }
2389
+ }
2390
+ async function generatePresetImages(sourceImg, outputDir, preset, scaleMode = "fit", logs) {
2391
+ let failed = 0;
2392
+ const total = preset.icons.length;
2393
+ for (let i = 0; i < total; i++) {
2394
+ const icon = preset.icons[i];
2395
+ const outputPath = join8(outputDir, icon.filename);
2396
+ if (logs) {
2397
+ logs.push({ type: "info", message: `[${i + 1}/${total}] ${icon.filename}` });
2398
+ } else {
2399
+ wip(`[${i + 1}/${total}] Generating ${icon.filename}...`);
2400
+ }
2401
+ const scaled = await scaleImage(
2402
+ sourceImg,
2403
+ outputPath,
2404
+ icon.width,
2405
+ icon.height,
2406
+ scaleMode
2407
+ );
2408
+ if (scaled) {
2409
+ if (!logs) {
2410
+ wipDone(true, `[${i + 1}/${total}] ${icon.filename} (${icon.width}x${icon.height})`);
2411
+ }
2412
+ } else {
2413
+ if (logs) {
2414
+ logs.push({ type: "error", message: `Failed: ${icon.filename}` });
2415
+ } else {
2416
+ wipDone(false, `[${i + 1}/${total}] Failed: ${icon.filename}`);
2417
+ }
2418
+ failed++;
2419
+ }
2420
+ }
2421
+ return failed;
2422
+ }
2423
+ async function run5(inputRaw) {
2424
+ if (!await checkImageMagick()) {
2425
+ error("ImageMagick not found. Please install it:");
2426
+ console.log(" sudo apt update && sudo apt install imagemagick");
2427
+ await pauseOnError();
2428
+ return false;
2429
+ }
2430
+ const input = normalizePath(inputRaw);
2431
+ if (!existsSync9(input)) {
2432
+ error(`File not found: ${input}`);
2433
+ await pauseOnError();
2434
+ return false;
2435
+ }
2436
+ const fileInfo = getFileInfo(input);
2437
+ header("PicLet Store Pack");
2438
+ wip("Analyzing source image...");
2439
+ const dims = await getDimensions(input);
2440
+ if (!dims) {
2441
+ wipDone(false, "Failed to read image");
2442
+ return false;
2443
+ }
2444
+ wipDone(true, `Source: ${dims[0]}x${dims[1]}`);
2445
+ const presets = loadPresets();
2446
+ const choices = presets.map((p) => ({
2447
+ title: p.name,
2448
+ value: p.id,
2449
+ description: p.description
2450
+ }));
2451
+ console.log("");
2452
+ const selectedId = await select("Select target store:", choices, presets[0].id);
2453
+ const preset = presets.find((p) => p.id === selectedId);
2454
+ if (!preset) {
2455
+ error("Preset not found");
2456
+ return false;
2457
+ }
2458
+ const outputDir = `${fileInfo.dirname}/${fileInfo.filename}_${preset.id}`;
2459
+ mkdirSync6(outputDir, { recursive: true });
2460
+ info(`Output: ${outputDir}`);
2461
+ console.log("");
2462
+ wip("Preparing source...");
2463
+ const tempSource = join8(outputDir, ".source.png");
2464
+ if (!await squarify(input, tempSource)) {
2465
+ wipDone(false, "Failed to prepare source");
2466
+ return false;
2467
+ }
2468
+ wipDone(true, "Source prepared");
2469
+ console.log("");
2470
+ header(`Generating ${preset.name} Images`);
2471
+ const failed = await generatePresetImages(tempSource, outputDir, preset);
2472
+ cleanup(tempSource);
2473
+ console.log("");
2474
+ if (failed === 0) {
2475
+ success(`All ${preset.icons.length} images generated!`);
2476
+ } else {
2477
+ error(`${failed}/${preset.icons.length} images failed`);
2478
+ }
2479
+ info(`Output: ${outputDir}`);
2480
+ return failed === 0;
2481
+ }
2482
+ async function runGUI6(inputRaw) {
2483
+ const input = normalizePath(inputRaw);
2484
+ if (!existsSync9(input)) {
2485
+ error(`File not found: ${input}`);
2486
+ return false;
2487
+ }
2488
+ const dims = await getDimensions(input);
2489
+ if (!dims) {
2490
+ error("Failed to read image dimensions");
2491
+ return false;
2492
+ }
2493
+ const fileInfo = getFileInfo(input);
2494
+ const presets = loadPresets();
2495
+ return startGuiServer({
2496
+ htmlFile: "storepack.html",
2497
+ title: "PicLet - Store Pack",
2498
+ imageInfo: {
2499
+ filePath: input,
2500
+ fileName: basename7(input),
2501
+ width: dims[0],
2502
+ height: dims[1],
2503
+ borderColor: null
2504
+ },
2505
+ defaults: {
2506
+ presets: presets.map((p) => ({
2507
+ id: p.id,
2508
+ name: p.name,
2509
+ description: p.description,
2510
+ icons: p.icons
2511
+ }))
2512
+ },
2513
+ onProcess: async (opts) => {
2514
+ const logs = [];
2515
+ if (!await checkImageMagick()) {
2516
+ return {
2517
+ success: false,
2518
+ error: "ImageMagick not found",
2519
+ logs: [{ type: "error", message: "ImageMagick not found" }]
2520
+ };
2521
+ }
2522
+ const presetId = opts.preset;
2523
+ const scaleMode = opts.scaleMode || "fit";
2524
+ const preset = presets.find((p) => p.id === presetId);
2525
+ if (!preset) {
2526
+ return {
2527
+ success: false,
2528
+ error: "Preset not found",
2529
+ logs: [{ type: "error", message: "No preset selected" }]
2530
+ };
2531
+ }
2532
+ const outputDir = `${fileInfo.dirname}/${fileInfo.filename}_${preset.id}`;
2533
+ mkdirSync6(outputDir, { recursive: true });
2534
+ logs.push({ type: "info", message: `Output: ${outputDir}` });
2535
+ logs.push({ type: "info", message: `Scale mode: ${scaleMode}` });
2536
+ logs.push({ type: "info", message: `Generating ${preset.icons.length} images...` });
2537
+ const failed = await generatePresetImages(input, outputDir, preset, scaleMode, logs);
2538
+ if (failed === 0) {
2539
+ logs.push({ type: "success", message: `All ${preset.icons.length} images generated` });
2540
+ return {
2541
+ success: true,
2542
+ output: `${preset.icons.length} images saved to ${fileInfo.filename}_${preset.id}/`,
2543
+ logs
2544
+ };
2545
+ }
2546
+ return {
2547
+ success: false,
2548
+ error: `${failed} image(s) failed`,
2549
+ logs
2550
+ };
2551
+ }
2552
+ });
2553
+ }
2554
+ var config6 = {
2555
+ id: "storepack",
2556
+ name: "Store Pack",
2557
+ icon: "storepack.ico",
2558
+ extensions: [".png", ".jpg", ".jpeg"]
2559
+ };
2560
+
2561
+ // src/cli.ts
2562
+ var tools = [
2563
+ { config: config2, run: run2 },
2564
+ { config: config4, run: run3 },
2565
+ { config: config5, run: run4 },
2566
+ { config, run },
2567
+ { config: config6, run: run5 }
2568
+ ];
2569
+ var picletTool = { config: config3, runGUI: runGUI3 };
2570
+ var program = new Command();
2571
+ function getDistDir() {
2572
+ const currentFile = fileURLToPath3(import.meta.url);
2573
+ return dirname10(currentFile);
2574
+ }
2575
+ function getMenuBasePath(extension) {
2576
+ return `HKCU\\Software\\Classes\\SystemFileAssociations\\${extension}\\shell\\PicLet`;
2577
+ }
2578
+ function getAllExtensions() {
2579
+ const extensions = /* @__PURE__ */ new Set();
2580
+ for (const { config: config7 } of tools) {
2581
+ for (const ext of config7.extensions) {
2582
+ extensions.add(ext);
2583
+ }
2584
+ }
2585
+ return Array.from(extensions);
2586
+ }
2587
+ function getToolsForExtension(extension) {
2588
+ return tools.filter((t) => t.config.extensions.includes(extension));
2589
+ }
2590
+ async function registerUnifiedMenu(extension, iconsDir, launcherPath) {
2591
+ const basePath = `HKCU\\Software\\Classes\\SystemFileAssociations\\${extension}\\shell\\PicLet`;
2592
+ const iconsDirWin = wslToWindows(iconsDir);
2593
+ const launcherWin = wslToWindows(launcherPath);
2594
+ const menuSuccess = await addRegistryKey(basePath, "MUIVerb", "PicLet");
2595
+ const iconSuccess = await addRegistryKey(basePath, "Icon", `${iconsDirWin}\\banana.ico`);
2596
+ await addRegistryKey(basePath, "MultiSelectModel", "Player");
2597
+ const commandValue = `wscript.exe //B "${launcherWin}" piclet "%1" -g`;
2598
+ const cmdSuccess = await addRegistryKey(`${basePath}\\command`, "", commandValue);
2599
+ return {
2600
+ extension,
2601
+ toolName: "PicLet",
2602
+ success: menuSuccess && iconSuccess && cmdSuccess
2603
+ };
2604
+ }
2605
+ async function unregisterMenuForExtension(extension) {
2606
+ const results = [];
2607
+ const basePath = getMenuBasePath(extension);
2608
+ const extensionTools = getToolsForExtension(extension);
2609
+ for (const { config: config7 } of extensionTools) {
2610
+ const toolPath = `${basePath}\\shell\\${config7.id}`;
2611
+ await deleteRegistryKey(`${toolPath}\\command`);
2612
+ const success2 = await deleteRegistryKey(toolPath);
2613
+ results.push({
2614
+ extension,
2615
+ toolName: config7.name,
2616
+ success: success2
2617
+ });
2618
+ }
2619
+ await deleteRegistryKey(`${basePath}\\shell`);
2620
+ await deleteRegistryKey(basePath);
2621
+ return results;
2622
+ }
2623
+ async function registerAllTools(distDir) {
2624
+ const iconsDir = join9(distDir, "icons");
2625
+ const launcherPath = join9(distDir, "launcher.vbs");
2626
+ const results = [];
2627
+ for (const extension of picletTool.config.extensions) {
2628
+ const result = await registerUnifiedMenu(extension, iconsDir, launcherPath);
2629
+ results.push(result);
2630
+ }
2631
+ return results;
2632
+ }
2633
+ async function unregisterAllTools() {
2634
+ const results = [];
2635
+ const allExts = /* @__PURE__ */ new Set([...getAllExtensions(), ...picletTool.config.extensions]);
2636
+ for (const extension of allExts) {
2637
+ const basePath = getMenuBasePath(extension);
2638
+ const extResults = await unregisterMenuForExtension(extension);
2639
+ results.push(...extResults);
2640
+ await deleteRegistryKey(`${basePath}\\command`);
2641
+ await deleteRegistryKey(basePath);
2642
+ }
2643
+ return results;
2644
+ }
2645
+ function getTool(id) {
2646
+ return tools.find((t) => t.config.id === id);
2647
+ }
2648
+ function validateExtensions(files, allowedExtensions) {
2649
+ const valid = [];
2650
+ const invalid = [];
2651
+ for (const file of files) {
2652
+ const ext = extname3(file).toLowerCase();
2653
+ if (allowedExtensions.includes(ext)) {
2654
+ valid.push(file);
2655
+ } else {
2656
+ invalid.push(file);
2657
+ }
2658
+ }
2659
+ return { valid, invalid };
2660
+ }
2661
+ async function runToolOnFiles(toolId, files, useYes) {
2662
+ const tool = getTool(toolId);
2663
+ if (!tool) {
2664
+ console.error(chalk.red(`Tool not found: ${toolId}`));
2665
+ return false;
2666
+ }
2667
+ const { valid, invalid } = validateExtensions(files, tool.config.extensions);
2668
+ if (invalid.length > 0) {
2669
+ console.error(chalk.red("Invalid file types:"));
2670
+ for (const file of invalid) {
2671
+ console.error(chalk.red(` \u2717 ${file}`));
2672
+ }
2673
+ console.error(
2674
+ chalk.yellow(
2675
+ `
2676
+ Supported extensions: ${tool.config.extensions.join(", ")}`
2677
+ )
2678
+ );
2679
+ if (valid.length === 0) return false;
2680
+ console.log();
2681
+ }
2682
+ if (useYes || valid.length > 1) {
2683
+ setUseDefaults(true);
2684
+ }
2685
+ let allSuccess = true;
2686
+ for (let i = 0; i < valid.length; i++) {
2687
+ const file = valid[i];
2688
+ if (valid.length > 1) {
2689
+ console.log(chalk.cyan(`
2690
+ [${i + 1}/${valid.length}] ${file}`));
2691
+ }
2692
+ const success2 = await tool.run(file);
2693
+ if (!success2) allSuccess = false;
2694
+ }
2695
+ return allSuccess;
2696
+ }
2697
+ function showHelp() {
2698
+ showBanner();
2699
+ const dim = chalk.gray;
2700
+ const cmd = chalk.cyan;
2701
+ const arg = chalk.yellow;
2702
+ const opt = chalk.green;
2703
+ const head = chalk.white.bold;
2704
+ console.log(
2705
+ ` ${head("Usage:")} piclet ${cmd("<command>")} ${arg("<file>")} ${opt("[options]")}`
2706
+ );
2707
+ console.log();
2708
+ console.log(head(" Unified"));
2709
+ console.log(
2710
+ ` ${cmd("piclet")} ${arg("<file>")} Open all tools in one window`
2711
+ );
2712
+ console.log();
2713
+ console.log(head(" Individual Tools"));
2714
+ console.log(
2715
+ ` ${cmd("makeicon")} ${arg("<file>")} Convert PNG to multi-resolution ICO`
2716
+ );
2717
+ console.log(
2718
+ ` ${cmd("remove-bg")} ${arg("<file>")} Remove solid background from image`
2719
+ );
2720
+ console.log(
2721
+ ` ${cmd("scale")} ${arg("<file>")} Resize image with optional padding`
2722
+ );
2723
+ console.log(
2724
+ ` ${cmd("iconpack")} ${arg("<file>")} Generate icon sets for Web/Android/iOS`
2725
+ );
2726
+ console.log(
2727
+ ` ${cmd("storepack")} ${arg("<file>")} Generate assets for app stores`
2728
+ );
2729
+ console.log();
2730
+ console.log(head(" Setup"));
2731
+ console.log(
2732
+ ` ${cmd("install")} Add Windows right-click menu`
2733
+ );
2734
+ console.log(` ${cmd("uninstall")} Remove right-click menu`);
2735
+ console.log();
2736
+ console.log(head(" Config"));
2737
+ console.log(` ${cmd("config")} Display current settings`);
2738
+ console.log(` ${cmd("config reset")} Restore defaults`);
2739
+ console.log();
2740
+ console.log(head(" Examples"));
2741
+ console.log(` ${dim("$")} piclet ${cmd("piclet")} ${arg("image.png")} ${dim("# All tools in one window")}`);
2742
+ console.log(` ${dim("$")} piclet ${cmd("makeicon")} ${arg("logo.png")} ${dim("# Interactive")}`);
2743
+ console.log(` ${dim("$")} piclet ${cmd("makeicon")} ${arg("*.png")} ${opt("-y")} ${dim("# Batch with defaults")}`);
2744
+ console.log(` ${dim("$")} piclet ${cmd("remove-bg")} ${arg("photo.png")} ${dim("# Interactive prompts")}`);
2745
+ console.log(` ${dim("$")} piclet ${cmd("scale")} ${arg("image.jpg")} ${dim("# Interactive resize")}`);
2746
+ console.log(` ${dim("$")} piclet ${cmd("iconpack")} ${arg("icon.png")} ${opt("-y")} ${dim("# All platforms")}`);
2747
+ console.log();
2748
+ console.log(head(" Requirements"));
2749
+ console.log(" - WSL (Windows Subsystem for Linux)");
2750
+ console.log(" - ImageMagick: sudo apt install imagemagick");
2751
+ console.log();
2752
+ }
2753
+ program.helpInformation = () => "";
2754
+ program.on("--help", () => {
2755
+ });
2756
+ program.name("piclet").description("Image manipulation utility toolkit with Windows shell integration").version("1.0.0").action(() => {
2757
+ showHelp();
2758
+ });
2759
+ program.command("help").description("Show help").action(() => {
2760
+ showHelp();
2761
+ });
2762
+ program.command("install").description("Install Windows shell context menu integration").action(async () => {
2763
+ showBanner();
2764
+ console.log(chalk.bold("Installing...\n"));
2765
+ if (!isWSL()) {
2766
+ console.log(
2767
+ chalk.yellow("! Not running in WSL. Registry integration skipped.")
2768
+ );
2769
+ console.log(
2770
+ chalk.yellow('! Run "piclet install" from WSL to add context menu.')
2771
+ );
2772
+ return;
2773
+ }
2774
+ console.log(chalk.dim("Removing old entries..."));
2775
+ await unregisterAllTools();
2776
+ console.log();
2777
+ const results = await registerAllTools(getDistDir());
2778
+ const successCount = results.filter((r) => r.success).length;
2779
+ for (const result of results) {
2780
+ if (result.success) {
2781
+ console.log(
2782
+ `${chalk.green("\u2713")} ${result.extension} \u2192 ${result.toolName}`
2783
+ );
2784
+ } else {
2785
+ console.log(
2786
+ `${chalk.red("\u2717")} ${result.extension} \u2192 ${result.toolName} (failed)`
2787
+ );
2788
+ }
2789
+ }
2790
+ console.log();
2791
+ if (successCount === results.length) {
2792
+ console.log(
2793
+ chalk.green(`\u2713 Registered ${successCount} context menu entries.`)
2794
+ );
2795
+ } else {
2796
+ console.log(
2797
+ chalk.yellow(`! Registered ${successCount}/${results.length} entries.`)
2798
+ );
2799
+ }
2800
+ console.log(chalk.bold("\nUsage:"));
2801
+ console.log(" Right-click any supported image in Windows Explorer.");
2802
+ console.log(" Multi-select supported for batch processing.");
2803
+ console.log();
2804
+ });
2805
+ program.command("uninstall").description("Remove Windows shell context menu integration").action(async () => {
2806
+ showBanner();
2807
+ console.log(chalk.bold("Uninstalling...\n"));
2808
+ if (!isWSL()) {
2809
+ console.log(
2810
+ chalk.yellow("! Not running in WSL. Registry cleanup skipped.")
2811
+ );
2812
+ console.log(
2813
+ chalk.yellow(
2814
+ '! Run "piclet uninstall" from WSL to remove context menu.'
2815
+ )
2816
+ );
2817
+ return;
2818
+ }
2819
+ const results = await unregisterAllTools();
2820
+ const removedCount = results.filter((r) => r.success).length;
2821
+ for (const result of results) {
2822
+ if (result.success) {
2823
+ console.log(
2824
+ `${chalk.green("\u2713")} Removed: ${result.extension} \u2192 ${result.toolName}`
2825
+ );
2826
+ } else {
2827
+ console.log(
2828
+ `${chalk.gray("-")} Skipped: ${result.extension} \u2192 ${result.toolName}`
2829
+ );
2830
+ }
2831
+ }
2832
+ console.log();
2833
+ console.log(
2834
+ chalk.green(
2835
+ `\u2713 Cleanup complete. Removed ${removedCount}/${results.length} entries.`
2836
+ )
2837
+ );
2838
+ console.log(chalk.dim("\nThanks for using PicLet!\n"));
2839
+ });
2840
+ program.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) => {
2841
+ if (options.gui) {
2842
+ const { valid, invalid } = validateExtensions(files, config2.extensions);
2843
+ if (invalid.length > 0) {
2844
+ console.error(chalk.red("Invalid file types:"));
2845
+ for (const file of invalid) {
2846
+ console.error(chalk.red(` - ${file}`));
2847
+ }
2848
+ }
2849
+ if (valid.length === 0) {
2850
+ process.exit(1);
2851
+ }
2852
+ const result = await runGUI2(valid[0]);
2853
+ process.exit(result ? 0 : 1);
2854
+ }
2855
+ const success2 = await runToolOnFiles(
2856
+ "makeicon",
2857
+ files,
2858
+ options.yes ?? false
2859
+ );
2860
+ process.exit(success2 ? 0 : 1);
2861
+ });
2862
+ program.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) => {
2863
+ if (options.gui) {
2864
+ const { valid, invalid } = validateExtensions(files, config4.extensions);
2865
+ if (invalid.length > 0) {
2866
+ console.error(chalk.red("Invalid file types:"));
2867
+ for (const file of invalid) {
2868
+ console.error(chalk.red(` - ${file}`));
2869
+ }
2870
+ }
2871
+ if (valid.length === 0) {
2872
+ process.exit(1);
2873
+ }
2874
+ const result = await runGUI4(valid[0]);
2875
+ process.exit(result ? 0 : 1);
2876
+ }
2877
+ if (options.fuzz !== void 0) {
2878
+ setOverrides({ "fuzz": Number(options.fuzz) });
2879
+ }
2880
+ if (options.trim !== void 0) {
2881
+ setOverrides({ "trim": options.trim });
2882
+ }
2883
+ if (options.preserveInner) {
2884
+ setOverrides({ "preserve inner": true });
2885
+ }
2886
+ if (options.square) {
2887
+ setOverrides({ "square": true });
2888
+ }
2889
+ const success2 = await runToolOnFiles(
2890
+ "remove-bg",
2891
+ files,
2892
+ options.yes ?? false
2893
+ );
2894
+ clearOverrides();
2895
+ process.exit(success2 ? 0 : 1);
2896
+ });
2897
+ program.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) => {
2898
+ if (options.gui) {
2899
+ const { valid, invalid } = validateExtensions(files, config5.extensions);
2900
+ if (invalid.length > 0) {
2901
+ console.error(chalk.red("Invalid file types:"));
2902
+ for (const file of invalid) {
2903
+ console.error(chalk.red(` - ${file}`));
2904
+ }
2905
+ }
2906
+ if (valid.length === 0) {
2907
+ process.exit(1);
2908
+ }
2909
+ const result = await runGUI5(valid[0]);
2910
+ process.exit(result ? 0 : 1);
2911
+ }
2912
+ const success2 = await runToolOnFiles(
2913
+ "rescale",
2914
+ files,
2915
+ options.yes ?? false
2916
+ );
2917
+ process.exit(success2 ? 0 : 1);
2918
+ });
2919
+ program.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) => {
2920
+ if (options.gui) {
2921
+ const { valid, invalid } = validateExtensions(files, config.extensions);
2922
+ if (invalid.length > 0) {
2923
+ console.error(chalk.red("Invalid file types:"));
2924
+ for (const file of invalid) {
2925
+ console.error(chalk.red(` - ${file}`));
2926
+ }
2927
+ }
2928
+ if (valid.length === 0) {
2929
+ process.exit(1);
2930
+ }
2931
+ const result = await runGUI(valid[0]);
2932
+ process.exit(result ? 0 : 1);
2933
+ }
2934
+ const success2 = await runToolOnFiles(
2935
+ "iconpack",
2936
+ files,
2937
+ options.yes ?? false
2938
+ );
2939
+ process.exit(success2 ? 0 : 1);
2940
+ });
2941
+ program.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) => {
2942
+ if (options.gui) {
2943
+ const { valid, invalid } = validateExtensions(files, config6.extensions);
2944
+ if (invalid.length > 0) {
2945
+ console.error(chalk.red("Invalid file types:"));
2946
+ for (const file of invalid) {
2947
+ console.error(chalk.red(` - ${file}`));
2948
+ }
2949
+ }
2950
+ if (valid.length === 0) {
2951
+ process.exit(1);
2952
+ }
2953
+ const result = await runGUI6(valid[0]);
2954
+ process.exit(result ? 0 : 1);
2955
+ }
2956
+ const success2 = await runToolOnFiles(
2957
+ "storepack",
2958
+ files,
2959
+ options.yes ?? false
2960
+ );
2961
+ process.exit(success2 ? 0 : 1);
2962
+ });
2963
+ program.command("piclet <file>").description("Open unified PicLet window with all tools").option("-g, --gui", "Use GUI (default)").action(async (file) => {
2964
+ const { valid, invalid } = validateExtensions([file], picletTool.config.extensions);
2965
+ if (invalid.length > 0) {
2966
+ console.error(chalk.red(`Invalid file type: ${file}`));
2967
+ console.error(chalk.yellow(`Supported: ${picletTool.config.extensions.join(", ")}`));
2968
+ process.exit(1);
2969
+ }
2970
+ const result = await picletTool.runGUI(valid[0]);
2971
+ process.exit(result ? 0 : 1);
2972
+ });
2973
+ var configCmd = program.command("config").description("Display current settings").action(() => {
2974
+ const config7 = loadConfig();
2975
+ console.log(chalk.white.bold("\n PicLet Configuration"));
2976
+ console.log(chalk.gray(` ${getConfigPath()}
2977
+ `));
2978
+ console.log(JSON.stringify(config7, null, 2));
2979
+ console.log();
2980
+ });
2981
+ configCmd.command("reset").description("Restore defaults").action(() => {
2982
+ resetConfig();
2983
+ console.log(chalk.green("Configuration reset to defaults."));
2984
+ });
2985
+ program.parseAsync(process.argv).catch((error2) => {
2986
+ console.error(chalk.red(`Error: ${error2.message}`));
2987
+ process.exit(1);
2988
+ });
2989
+ //# sourceMappingURL=cli.js.map