@puntoycoma/bulki 3.0.1 → 3.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/bulki.js CHANGED
@@ -1,26 +1,33 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- VALID_FORMATS,
3
+ DEFAULTS,
4
+ PlanError,
5
+ UNSUPPORTED,
4
6
  buildJsonOutput,
5
- collectImageFiles,
7
+ defaultConcurrency,
6
8
  detectFormat,
9
+ displayPath,
10
+ encodeImage,
7
11
  formatFileResult,
8
12
  formatSummary,
9
- getFormatOptions,
10
13
  hookCommand,
11
- optimizeImage,
14
+ parseInteger,
15
+ parsePathInput,
12
16
  parseSize,
13
- processPool,
17
+ planJobs,
18
+ resolveOutputDir,
19
+ resolveUserPath,
20
+ runBatch,
21
+ validateEncodeSettings,
14
22
  validateOptions,
15
23
  watchCommand
16
- } from "../chunk-OMZC5IWI.js";
24
+ } from "../chunk-YUAKNSBH.js";
17
25
 
18
26
  // src/bin/bulki.ts
19
27
  import { createRequire as createRequire2 } from "module";
20
28
  import { Command as Command4 } from "commander";
21
29
 
22
30
  // src/commands/compress.ts
23
- import path from "path";
24
31
  import { Command } from "commander";
25
32
 
26
33
  // src/output/progress.ts
@@ -41,291 +48,208 @@ function createProgressBar(total) {
41
48
  }
42
49
 
43
50
  // src/commands/compress.ts
44
- var compressCommand = new Command("compress").description("Compress images in a file or directory").argument("<path>", "File or directory to process").option("-q, --quality <n>", "Output quality (1-100)", (v) => parseInt(v, 10), 70).option("-f, --format <fmt>", "Output format (jpeg, png, webp, gif, avif)").option("-r, --resize <width>", "Resize to width (px)", (v) => parseInt(v, 10)).option("-d, --destination <path>", "Destination directory", ".").option("-o, --output-folder <name>", "Output subfolder name", "bulki-output").option("--concurrency <n>", "Number of parallel operations", (v) => parseInt(v, 10)).option("--exclude <patterns...>", "Directory names to exclude").option("--dry-run", "Show what would be processed without writing files").option("--verbose", "Show per-file output").option("--json", "Output results as JSON").option("--max-size <size>", "Maximum file size budget (e.g., 200kb, 1mb)").action(async (inputPath, opts) => {
51
+ var compressCommand = new Command("compress").description(
52
+ "Compress images. Originals are never modified; optimized copies go to the output folder."
53
+ ).argument("<paths...>", "Images and/or folders to optimize (quote paths with spaces)").option("-o, --output <dir>", "Folder where optimized images are written", DEFAULTS.output).option("-q, --quality <n>", "Output quality (1-100)", parseInteger, DEFAULTS.quality).option("-f, --format <fmt>", "Convert to jpeg, png, webp, gif or avif (default: keep format)").option(
54
+ "-r, --resize <width>",
55
+ "Max width in px (smaller images are never enlarged)",
56
+ parseInteger
57
+ ).option("--concurrency <n>", "Images processed in parallel (1-32)", parseInteger).option("--exclude <names...>", "Folder names to skip").option("--dry-run", "Show what would be processed without writing files").option("--verbose", "Show one line per file").option("--json", "Output results as JSON").option("--max-size <size>", "Fail if any output is bigger than this (e.g. 200kb, 1mb)").option("-d, --destination <dir>", "Deprecated: base folder; output goes to <dir>/bulki-output").option("--output-folder <name>", "Deprecated: subfolder name inside --destination").addHelpText(
58
+ "after",
59
+ `
60
+ Examples:
61
+ bulki compress ./photos \u2192 ./bulki-output
62
+ bulki compress ./photos -o ./optimized -f webp
63
+ bulki compress "My Photos" -o "Web Ready" -r 1920
64
+ bulki compress *.png -f avif`
65
+ ).action(async (inputPaths, opts, cmd) => {
66
+ const outputFromCli = cmd.getOptionValueSource("output") === "cli";
45
67
  let options;
46
68
  try {
47
- options = validateOptions(opts);
69
+ options = validateOptions({ ...opts, output: outputFromCli ? opts.output : void 0 });
48
70
  } catch (err) {
49
- console.error(err.message);
50
- process.exit(1);
71
+ fail(err.message);
51
72
  }
52
- const resolvedPath = path.resolve(inputPath);
53
- let filePaths;
54
- let isDirectory;
73
+ const inputs = inputPaths.map((p) => resolveUserPath(p));
74
+ let plan;
55
75
  try {
56
- const collected = await collectImageFiles(resolvedPath, options.exclude);
57
- filePaths = collected.files;
58
- isDirectory = collected.isDirectory;
59
- } catch {
60
- console.error(`Path not found: ${resolvedPath}`);
61
- process.exit(1);
76
+ plan = await planJobs({
77
+ inputs,
78
+ output: options.output,
79
+ format: options.format,
80
+ exclude: options.exclude
81
+ });
82
+ } catch (err) {
83
+ if (err instanceof PlanError) fail(err.message);
84
+ throw err;
62
85
  }
63
- if (filePaths.length === 0) {
64
- console.log("No images found to process.");
86
+ const log = options.json ? () => {
87
+ } : console.log;
88
+ for (const s of plan.skipped) console.error(`Skipped ${s.file}: ${s.reason}`);
89
+ if (plan.jobs.length === 0) {
90
+ if (options.json) console.log(JSON.stringify(buildJsonOutput([], [], 0, options.output)));
91
+ else log("No images found to process.");
65
92
  return;
66
93
  }
67
94
  if (options.dryRun) {
68
- console.log(`Would process ${filePaths.length} image(s)`);
69
- for (const f of filePaths) console.log(` ${path.basename(f.path)}`);
95
+ log(`Would optimize ${plan.jobs.length} image(s) into ${displayPath(options.output)}`);
96
+ for (const job of plan.jobs) log(` ${displayPath(job.input)} \u2192 ${displayPath(job.output)}`);
70
97
  return;
71
98
  }
72
- const sourcePath = isDirectory ? resolvedPath : path.dirname(resolvedPath);
73
- const startTime = performance.now();
74
- const progress = !options.verbose && !options.json ? createProgressBar(filePaths.length) : null;
75
- const errors = [];
76
- const jsonErrors = [];
77
- const processedResults = await processPool(
78
- filePaths,
79
- async (file) => {
80
- try {
81
- const result = await optimizeImage(file.path, {
82
- ...options,
83
- sourcePath,
84
- detectedFormat: file.format
85
- });
86
- if (options.verbose && !options.json) {
87
- console.log(formatFileResult(result));
88
- }
89
- progress?.tick();
90
- return result;
91
- } catch (err) {
92
- errors.push(err);
93
- jsonErrors.push({ file: file.path, message: err.message });
94
- progress?.tick();
95
- return null;
96
- }
97
- },
98
- options.concurrency
99
- );
100
- const validResults = processedResults.filter((r) => r !== null);
101
- const totalDuration = performance.now() - startTime;
99
+ const showBar = !options.verbose && !options.json && process.stdout.isTTY;
100
+ const progress = showBar ? createProgressBar(plan.jobs.length) : null;
101
+ const batch = await runBatch(plan.jobs, options, options.concurrency, (job, result, error) => {
102
+ progress?.tick();
103
+ if (!options.verbose || options.json) return;
104
+ if (result) console.log(formatFileResult(result));
105
+ else console.error(`\u2717 ${displayPath(job.input)}: ${error?.message}`);
106
+ });
107
+ const maxSize = options.maxSize;
108
+ const exceeded = maxSize ? batch.results.filter((r) => r.newSize > maxSize) : [];
102
109
  if (options.json) {
103
- const output = buildJsonOutput(validResults, jsonErrors, totalDuration);
104
- if (options.maxSize) {
105
- for (const file of output.files) {
106
- if (file.newSize > options.maxSize) {
107
- file.exceeded = true;
108
- }
109
- }
110
+ const output = buildJsonOutput(batch.results, batch.errors, batch.duration, options.output);
111
+ if (maxSize) {
112
+ for (const f of output.files) if (f.newSize > maxSize) f.exceeded = true;
110
113
  }
111
114
  console.log(JSON.stringify(output));
112
115
  } else {
113
- console.log(formatSummary(validResults, errors, totalDuration));
114
- }
115
- if (validResults.length === 0 && errors.length > 0) {
116
- process.exit(1);
117
- }
118
- const maxSize = options.maxSize;
119
- if (maxSize) {
120
- const exceeded = validResults.filter((r) => r.newSize > maxSize);
121
- if (exceeded.length > 0) {
122
- if (!options.json) {
123
- for (const r of exceeded) {
124
- console.warn(`File exceeds size budget: ${r.outputPath}`);
125
- }
126
- }
127
- process.exit(1);
116
+ console.log(formatSummary(batch.results, batch.errors, batch.duration));
117
+ if (!options.verbose) {
118
+ for (const e of batch.errors) console.error(`\u2717 ${displayPath(e.file)}: ${e.message}`);
128
119
  }
120
+ if (batch.results.length > 0) console.log(`Saved to ${displayPath(options.output)}`);
121
+ for (const r of exceeded) console.warn(`File exceeds size budget: ${r.outputPath}`);
129
122
  }
123
+ if (batch.errors.length > 0 || exceeded.length > 0) process.exitCode = 1;
130
124
  });
125
+ function fail(message) {
126
+ console.error(`error: ${message}`);
127
+ process.exit(1);
128
+ }
131
129
 
132
130
  // src/commands/mcp.ts
133
131
  import { createRequire } from "module";
134
- import path2 from "path";
132
+ import path from "path";
135
133
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
136
134
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
137
135
  import { Command as Command2 } from "commander";
138
136
  import { z } from "zod";
139
137
  var require2 = createRequire(import.meta.url);
140
138
  var { version } = require2("../../package.json");
139
+ function text(payload) {
140
+ return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
141
+ }
141
142
  var mcpCommand = new Command2("mcp").description("Start MCP server for AI editor integration (Claude Code, Cursor, etc.)").action(async () => {
142
- const server = new McpServer({
143
- name: "bulki",
144
- version
145
- });
143
+ const server = new McpServer({ name: "bulki", version });
146
144
  server.tool(
147
145
  "compress",
148
- "Compress and optimize images. Supports JPEG, PNG, WebP, GIF, AVIF.",
146
+ "Compress and optimize images (JPEG, PNG, WebP, GIF, AVIF). Originals are never modified; optimized copies are written to `output`.",
149
147
  {
150
148
  path: z.string().describe("File or directory path to compress"),
151
- quality: z.number().min(1).max(100).default(70).describe("Output quality (1-100)"),
149
+ quality: z.number().int().min(1).max(100).default(70).describe("Output quality (1-100)"),
152
150
  format: z.enum(["jpeg", "png", "webp", "gif", "avif"]).optional().describe("Output format (omit to keep original)"),
153
- resize: z.number().positive().optional().describe("Max width in pixels"),
154
- destination: z.string().default(".").describe("Output directory"),
155
- outputFolder: z.string().default("bulki-output").describe("Output folder name"),
151
+ resize: z.number().int().positive().optional().describe("Max width in pixels (never enlarges)"),
152
+ output: z.string().optional().describe("Folder where optimized images are written (default: ./bulki-output)"),
153
+ destination: z.string().optional().describe("Deprecated: base folder, use `output`"),
154
+ outputFolder: z.string().optional().describe("Deprecated: subfolder name, use `output`"),
156
155
  maxSize: z.string().optional().describe("Max file size budget (e.g., 200kb, 1mb)")
157
156
  },
158
157
  async (params) => {
159
- if (params.outputFolder.includes("/") || params.outputFolder.includes("\\") || params.outputFolder.includes("..")) {
160
- return {
161
- content: [
162
- {
163
- type: "text",
164
- text: JSON.stringify({
165
- error: "Invalid outputFolder: must be a simple directory name"
166
- })
167
- }
168
- ]
169
- };
170
- }
171
- const resolvedPath = path2.resolve(params.path);
172
- let filePaths;
173
- let isDirectory;
174
158
  try {
175
- const collected = await collectImageFiles(resolvedPath, []);
176
- filePaths = collected.files;
177
- isDirectory = collected.isDirectory;
178
- } catch {
179
- return {
180
- content: [
181
- {
182
- type: "text",
183
- text: JSON.stringify({ error: `Path not found: ${resolvedPath}` })
184
- }
185
- ]
186
- };
187
- }
188
- if (filePaths.length === 0) {
189
- return {
190
- content: [
191
- {
192
- type: "text",
193
- text: JSON.stringify({ files: [], summary: { totalFiles: 0 } })
194
- }
195
- ]
196
- };
197
- }
198
- const sourcePath = isDirectory ? resolvedPath : path2.dirname(resolvedPath);
199
- const startTime = performance.now();
200
- const results = [];
201
- const errors = [];
202
- let maxSizeBytes;
203
- if (params.maxSize) {
204
- try {
205
- maxSizeBytes = parseSize(params.maxSize);
206
- } catch (err) {
207
- return {
208
- content: [
209
- {
210
- type: "text",
211
- text: JSON.stringify({ error: err.message })
212
- }
213
- ]
214
- };
215
- }
216
- }
217
- for (const file of filePaths) {
218
- try {
219
- const result = await optimizeImage(file.path, {
220
- quality: params.quality,
221
- format: params.format,
222
- resize: params.resize,
223
- destination: params.destination,
224
- outputFolder: params.outputFolder,
225
- concurrency: 1,
226
- exclude: [],
227
- dryRun: false,
228
- verbose: false,
229
- json: false,
230
- sourcePath,
231
- detectedFormat: file.format
232
- });
233
- results.push(result);
234
- } catch (err) {
235
- errors.push({ file: file.path, message: err.message });
236
- }
237
- }
238
- const totalDuration = performance.now() - startTime;
239
- const output = buildJsonOutput(results, errors, totalDuration);
240
- if (maxSizeBytes !== void 0) {
241
- for (const file of output.files) {
242
- if (file.newSize > maxSizeBytes) {
243
- file.exceeded = true;
244
- }
159
+ const maxSizeBytes = params.maxSize ? parseSize(params.maxSize) : void 0;
160
+ const output = resolveOutputDir(params);
161
+ const plan = await planJobs({
162
+ inputs: [resolveUserPath(params.path)],
163
+ output,
164
+ format: params.format
165
+ });
166
+ const settings = { quality: params.quality, format: params.format, resize: params.resize };
167
+ const batch = await runBatch(plan.jobs, settings, defaultConcurrency());
168
+ const result = buildJsonOutput(batch.results, batch.errors, batch.duration, output);
169
+ if (maxSizeBytes !== void 0) {
170
+ for (const file of result.files) if (file.newSize > maxSizeBytes) file.exceeded = true;
245
171
  }
172
+ return text(result);
173
+ } catch (err) {
174
+ return text({ error: err.message });
246
175
  }
247
- return { content: [{ type: "text", text: JSON.stringify(output, null, 2) }] };
248
176
  }
249
177
  );
250
178
  server.tool(
251
179
  "detect",
252
180
  "Detect if a file is an image and identify its format",
253
- {
254
- path: z.string().describe("Path to the file")
255
- },
181
+ { path: z.string().describe("Path to the file") },
256
182
  async (params) => {
257
- const resolvedPath = path2.resolve(params.path);
258
- const format = await detectFormat(resolvedPath);
259
- const result = format ? { format, isImage: true } : { format: null, isImage: false };
260
- return { content: [{ type: "text", text: JSON.stringify(result) }] };
183
+ const format = await detectFormat(path.resolve(resolveUserPath(params.path)));
184
+ if (format === UNSUPPORTED) return text({ format: "heic", isImage: true, supported: false });
185
+ return text(format ? { format, isImage: true } : { format: null, isImage: false });
261
186
  }
262
187
  );
263
- const transport = new StdioServerTransport();
264
- await server.connect(transport);
188
+ await server.connect(new StdioServerTransport());
265
189
  });
266
190
 
267
191
  // src/commands/pipe.ts
268
192
  import { Command as Command3 } from "commander";
269
- import sharp from "sharp";
270
- var pipeCommand = new Command3("pipe").description("Read image from stdin, write optimized image to stdout").requiredOption("-f, --format <fmt>", "Output format (jpeg, png, webp, gif, avif)").option("-q, --quality <n>", "Quality (1-100)", (v) => parseInt(v, 10), 70).option("-r, --resize <width>", "Resize to width (px)", (v) => parseInt(v, 10)).action(async (opts) => {
271
- const MAX_INPUT_SIZE = 100 * 1024 * 1024;
193
+ var MAX_INPUT_SIZE = 100 * 1024 * 1024;
194
+ var pipeCommand = new Command3("pipe").description("Read image from stdin, write optimized image to stdout").requiredOption("-f, --format <fmt>", "Output format (jpeg, png, webp, gif, avif)").option("-q, --quality <n>", "Quality (1-100)", parseInteger, DEFAULTS.quality).option("-r, --resize <width>", "Max width in px (never enlarges)", parseInteger).action(async (opts) => {
195
+ let settings;
196
+ try {
197
+ settings = validateEncodeSettings(opts);
198
+ } catch (err) {
199
+ return fail2(err.message);
200
+ }
272
201
  const chunks = [];
273
202
  let totalSize = 0;
274
203
  for await (const chunk of process.stdin) {
275
204
  totalSize += chunk.length;
276
- if (totalSize > MAX_INPUT_SIZE) {
277
- process.stderr.write("Error: input exceeds 100 MB limit\n");
278
- process.exit(1);
279
- }
205
+ if (totalSize > MAX_INPUT_SIZE) return fail2("input exceeds 100 MB limit");
280
206
  chunks.push(chunk);
281
207
  }
282
208
  const input = Buffer.concat(chunks);
283
- if (input.length === 0) {
284
- process.stderr.write("Error: no input received on stdin\n");
285
- process.exit(1);
286
- }
287
- const format = opts.format;
288
- const quality = opts.quality;
289
- const resize = opts.resize;
290
- if (!VALID_FORMATS.includes(format)) {
291
- process.stderr.write(`Error: invalid format "${format}"
292
- `);
293
- process.exit(1);
294
- }
295
- const outputFormat = format === "jpg" ? "jpeg" : format;
296
- const formatOptions = getFormatOptions(quality);
209
+ if (input.length === 0) return fail2("no input received on stdin");
297
210
  try {
298
- let pipeline = sharp(input);
299
- if (resize) {
300
- pipeline = pipeline.resize(resize);
301
- }
302
- pipeline = pipeline.toFormat(
303
- outputFormat,
304
- formatOptions[outputFormat] ?? {}
305
- );
306
- const outputBuffer = await pipeline.toBuffer();
307
- process.stdout.write(outputBuffer);
211
+ const detected = sniff(input);
212
+ const { data } = await encodeImage(input, detected, settings);
213
+ process.stdout.write(data);
308
214
  } catch (err) {
309
- process.stderr.write(`Error: ${err.message}
310
- `);
311
- process.exit(1);
215
+ fail2(err.message);
312
216
  }
313
217
  });
218
+ function sniff(buf) {
219
+ if (buf[0] === 255 && buf[1] === 216) return "jpeg";
220
+ if (buf[0] === 137 && buf.toString("latin1", 1, 4) === "PNG") return "png";
221
+ if (buf.toString("latin1", 0, 4) === "RIFF" && buf.toString("latin1", 8, 12) === "WEBP")
222
+ return "webp";
223
+ if (buf.toString("latin1", 0, 3) === "GIF") return "gif";
224
+ if (buf.toString("latin1", 4, 8) === "ftyp") {
225
+ const brand = buf.toString("latin1", 8, 12);
226
+ return brand === "avif" || brand === "avis" ? "avif" : UNSUPPORTED;
227
+ }
228
+ return "unknown";
229
+ }
230
+ function fail2(message) {
231
+ process.stderr.write(`Error: ${message}
232
+ `);
233
+ process.exit(1);
234
+ }
314
235
 
315
236
  // src/bin/bulki.ts
316
237
  var require3 = createRequire2(import.meta.url);
317
238
  var { version: version2 } = require3("../../package.json");
318
- if (process.argv.length <= 2) {
319
- const { runInteractive } = await import("../interactive-LGL325GZ.js");
320
- await runInteractive();
239
+ var args = process.argv.slice(2);
240
+ var COMMANDS = /* @__PURE__ */ new Set(["compress", "hook", "mcp", "pipe", "watch", "help"]);
241
+ var droppedPaths = args.length > 0 && !args[0].startsWith("-") && !COMMANDS.has(args[0]) ? parsePathInput(args.join(" ")) : null;
242
+ if (args.length === 0 || droppedPaths?.ok) {
243
+ const { runInteractive } = await import("../interactive-MTL4CMVQ.js");
244
+ await runInteractive(droppedPaths?.ok ? droppedPaths.paths : void 0);
321
245
  } else {
322
246
  const program = new Command4();
323
- program.name("bulki").description("Ultra-fast CLI image optimizer").version(version2);
247
+ program.name("bulki").description("Fast image optimizer. Run `bulki` with no arguments for the guided mode.").version(version2).showSuggestionAfterError();
324
248
  program.addCommand(compressCommand);
325
249
  program.addCommand(hookCommand);
326
250
  program.addCommand(mcpCommand);
327
251
  program.addCommand(pipeCommand);
328
252
  program.addCommand(watchCommand);
329
- program.parse();
253
+ await program.parseAsync();
330
254
  }
331
255
  //# sourceMappingURL=bulki.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/bin/bulki.ts","../../src/commands/compress.ts","../../src/output/progress.ts","../../src/commands/mcp.ts","../../src/commands/pipe.ts"],"sourcesContent":["import { createRequire } from 'node:module'\nimport { Command } from 'commander'\nimport { compressCommand } from '../commands/compress.js'\nimport { hookCommand } from '../commands/hook.js'\nimport { mcpCommand } from '../commands/mcp.js'\nimport { pipeCommand } from '../commands/pipe.js'\nimport { watchCommand } from '../commands/watch.js'\n\nconst require = createRequire(import.meta.url)\nconst { version } = require('../../package.json') as { version: string }\n\nif (process.argv.length <= 2) {\n\tconst { runInteractive } = await import('../interactive/index.js')\n\tawait runInteractive()\n} else {\n\tconst program = new Command()\n\tprogram.name('bulki').description('Ultra-fast CLI image optimizer').version(version)\n\n\tprogram.addCommand(compressCommand)\n\tprogram.addCommand(hookCommand)\n\tprogram.addCommand(mcpCommand)\n\tprogram.addCommand(pipeCommand)\n\tprogram.addCommand(watchCommand)\n\tprogram.parse()\n}\n","import path from 'node:path'\nimport { Command } from 'commander'\nimport { processPool } from '../concurrency/pool.js'\nimport { createProgressBar } from '../output/progress.js'\nimport { buildJsonOutput, formatFileResult, formatSummary } from '../output/results.js'\nimport { collectImageFiles } from '../pipeline/discover.js'\nimport { optimizeImage } from '../pipeline/optimize.js'\nimport type { CompressOptions, FileResult } from '../types/index.js'\nimport { type RawOptions, validateOptions } from '../validation/options.js'\n\nexport const compressCommand = new Command('compress')\n\t.description('Compress images in a file or directory')\n\t.argument('<path>', 'File or directory to process')\n\t.option('-q, --quality <n>', 'Output quality (1-100)', (v) => parseInt(v, 10), 70)\n\t.option('-f, --format <fmt>', 'Output format (jpeg, png, webp, gif, avif)')\n\t.option('-r, --resize <width>', 'Resize to width (px)', (v) => parseInt(v, 10))\n\t.option('-d, --destination <path>', 'Destination directory', '.')\n\t.option('-o, --output-folder <name>', 'Output subfolder name', 'bulki-output')\n\t.option('--concurrency <n>', 'Number of parallel operations', (v) => parseInt(v, 10))\n\t.option('--exclude <patterns...>', 'Directory names to exclude')\n\t.option('--dry-run', 'Show what would be processed without writing files')\n\t.option('--verbose', 'Show per-file output')\n\t.option('--json', 'Output results as JSON')\n\t.option('--max-size <size>', 'Maximum file size budget (e.g., 200kb, 1mb)')\n\t.action(async (inputPath: string, opts: Partial<RawOptions>) => {\n\t\tlet options: CompressOptions\n\t\ttry {\n\t\t\toptions = validateOptions(opts)\n\t\t} catch (err) {\n\t\t\tconsole.error((err as Error).message)\n\t\t\tprocess.exit(1)\n\t\t}\n\n\t\tconst resolvedPath = path.resolve(inputPath)\n\t\tlet filePaths: { path: string; format: string }[]\n\t\tlet isDirectory: boolean\n\t\ttry {\n\t\t\tconst collected = await collectImageFiles(resolvedPath, options.exclude)\n\t\t\tfilePaths = collected.files\n\t\t\tisDirectory = collected.isDirectory\n\t\t} catch {\n\t\t\tconsole.error(`Path not found: ${resolvedPath}`)\n\t\t\tprocess.exit(1)\n\t\t}\n\n\t\tif (filePaths.length === 0) {\n\t\t\tconsole.log('No images found to process.')\n\t\t\treturn\n\t\t}\n\n\t\tif (options.dryRun) {\n\t\t\tconsole.log(`Would process ${filePaths.length} image(s)`)\n\t\t\tfor (const f of filePaths) console.log(` ${path.basename(f.path)}`)\n\t\t\treturn\n\t\t}\n\n\t\tconst sourcePath = isDirectory ? resolvedPath : path.dirname(resolvedPath)\n\t\tconst startTime = performance.now()\n\n\t\tconst progress = !options.verbose && !options.json ? createProgressBar(filePaths.length) : null\n\n\t\tconst errors: Error[] = []\n\t\tconst jsonErrors: { file: string; message: string }[] = []\n\n\t\tconst processedResults = await processPool(\n\t\t\tfilePaths,\n\t\t\tasync (file) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await optimizeImage(file.path, {\n\t\t\t\t\t\t...options,\n\t\t\t\t\t\tsourcePath,\n\t\t\t\t\t\tdetectedFormat: file.format,\n\t\t\t\t\t})\n\t\t\t\t\tif (options.verbose && !options.json) {\n\t\t\t\t\t\tconsole.log(formatFileResult(result))\n\t\t\t\t\t}\n\t\t\t\t\tprogress?.tick()\n\t\t\t\t\treturn result\n\t\t\t\t} catch (err) {\n\t\t\t\t\terrors.push(err as Error)\n\t\t\t\t\tjsonErrors.push({ file: file.path, message: (err as Error).message })\n\t\t\t\t\tprogress?.tick()\n\t\t\t\t\treturn null\n\t\t\t\t}\n\t\t\t},\n\t\t\toptions.concurrency,\n\t\t)\n\n\t\tconst validResults = processedResults.filter((r): r is FileResult => r !== null)\n\t\tconst totalDuration = performance.now() - startTime\n\n\t\tif (options.json) {\n\t\t\tconst output = buildJsonOutput(validResults, jsonErrors, totalDuration)\n\t\t\tif (options.maxSize) {\n\t\t\t\tfor (const file of output.files) {\n\t\t\t\t\tif (file.newSize > options.maxSize) {\n\t\t\t\t\t\tfile.exceeded = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tconsole.log(JSON.stringify(output))\n\t\t} else {\n\t\t\tconsole.log(formatSummary(validResults, errors, totalDuration))\n\t\t}\n\n\t\tif (validResults.length === 0 && errors.length > 0) {\n\t\t\tprocess.exit(1)\n\t\t}\n\n\t\tconst maxSize = options.maxSize\n\t\tif (maxSize) {\n\t\t\tconst exceeded = validResults.filter((r) => r.newSize > maxSize)\n\t\t\tif (exceeded.length > 0) {\n\t\t\t\tif (!options.json) {\n\t\t\t\t\tfor (const r of exceeded) {\n\t\t\t\t\t\tconsole.warn(`File exceeds size budget: ${r.outputPath}`)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tprocess.exit(1)\n\t\t\t}\n\t\t}\n\t})\n","export function createProgressBar(total: number) {\n\tlet current = 0\n\n\treturn {\n\t\ttick() {\n\t\t\tcurrent++\n\t\t\tconst percent = Math.round((current / total) * 100)\n\t\t\tconst barWidth = Math.max(0, Math.min(process.stdout.columns || 40, 40) - 20)\n\t\t\tconst filled = Math.round((current / total) * barWidth)\n\t\t\tconst empty = barWidth - filled\n\t\t\tconst bar = `[${'='.repeat(filled)}${' '.repeat(empty)}] ${percent}% ${current}/${total}`\n\t\t\tprocess.stdout.write(`\\r${bar}`)\n\t\t\tif (current === total) process.stdout.write('\\n')\n\t\t},\n\t}\n}\n","import { createRequire } from 'node:module'\nimport path from 'node:path'\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport { Command } from 'commander'\nimport { z } from 'zod'\nimport { buildJsonOutput } from '../output/results.js'\nimport { collectImageFiles } from '../pipeline/discover.js'\nimport { detectFormat } from '../pipeline/filter.js'\nimport { optimizeImage } from '../pipeline/optimize.js'\nimport type { FileResult } from '../types/index.js'\nimport { parseSize } from '../validation/options.js'\n\nconst require = createRequire(import.meta.url)\nconst { version } = require('../../package.json') as { version: string }\n\nexport const mcpCommand = new Command('mcp')\n\t.description('Start MCP server for AI editor integration (Claude Code, Cursor, etc.)')\n\t.action(async () => {\n\t\tconst server = new McpServer({\n\t\t\tname: 'bulki',\n\t\t\tversion,\n\t\t})\n\n\t\tserver.tool(\n\t\t\t'compress',\n\t\t\t'Compress and optimize images. Supports JPEG, PNG, WebP, GIF, AVIF.',\n\t\t\t{\n\t\t\t\tpath: z.string().describe('File or directory path to compress'),\n\t\t\t\tquality: z.number().min(1).max(100).default(70).describe('Output quality (1-100)'),\n\t\t\t\tformat: z\n\t\t\t\t\t.enum(['jpeg', 'png', 'webp', 'gif', 'avif'])\n\t\t\t\t\t.optional()\n\t\t\t\t\t.describe('Output format (omit to keep original)'),\n\t\t\t\tresize: z.number().positive().optional().describe('Max width in pixels'),\n\t\t\t\tdestination: z.string().default('.').describe('Output directory'),\n\t\t\t\toutputFolder: z.string().default('bulki-output').describe('Output folder name'),\n\t\t\t\tmaxSize: z.string().optional().describe('Max file size budget (e.g., 200kb, 1mb)'),\n\t\t\t},\n\t\t\tasync (params) => {\n\t\t\t\tif (\n\t\t\t\t\tparams.outputFolder.includes('/') ||\n\t\t\t\t\tparams.outputFolder.includes('\\\\') ||\n\t\t\t\t\tparams.outputFolder.includes('..')\n\t\t\t\t) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttype: 'text' as const,\n\t\t\t\t\t\t\t\ttext: JSON.stringify({\n\t\t\t\t\t\t\t\t\terror: 'Invalid outputFolder: must be a simple directory name',\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst resolvedPath = path.resolve(params.path)\n\n\t\t\t\tlet filePaths: { path: string; format: string }[]\n\t\t\t\tlet isDirectory: boolean\n\t\t\t\ttry {\n\t\t\t\t\tconst collected = await collectImageFiles(resolvedPath, [])\n\t\t\t\t\tfilePaths = collected.files\n\t\t\t\t\tisDirectory = collected.isDirectory\n\t\t\t\t} catch {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttype: 'text' as const,\n\t\t\t\t\t\t\t\ttext: JSON.stringify({ error: `Path not found: ${resolvedPath}` }),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (filePaths.length === 0) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttype: 'text' as const,\n\t\t\t\t\t\t\t\ttext: JSON.stringify({ files: [], summary: { totalFiles: 0 } }),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst sourcePath = isDirectory ? resolvedPath : path.dirname(resolvedPath)\n\t\t\t\tconst startTime = performance.now()\n\t\t\t\tconst results: FileResult[] = []\n\t\t\t\tconst errors: { file: string; message: string }[] = []\n\n\t\t\t\tlet maxSizeBytes: number | undefined\n\t\t\t\tif (params.maxSize) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tmaxSizeBytes = parseSize(params.maxSize)\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttype: 'text' as const,\n\t\t\t\t\t\t\t\t\ttext: JSON.stringify({ error: (err as Error).message }),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tfor (const file of filePaths) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst result = await optimizeImage(file.path, {\n\t\t\t\t\t\t\tquality: params.quality,\n\t\t\t\t\t\t\tformat: params.format,\n\t\t\t\t\t\t\tresize: params.resize,\n\t\t\t\t\t\t\tdestination: params.destination,\n\t\t\t\t\t\t\toutputFolder: params.outputFolder,\n\t\t\t\t\t\t\tconcurrency: 1,\n\t\t\t\t\t\t\texclude: [],\n\t\t\t\t\t\t\tdryRun: false,\n\t\t\t\t\t\t\tverbose: false,\n\t\t\t\t\t\t\tjson: false,\n\t\t\t\t\t\t\tsourcePath,\n\t\t\t\t\t\t\tdetectedFormat: file.format,\n\t\t\t\t\t\t})\n\t\t\t\t\t\tresults.push(result)\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\terrors.push({ file: file.path, message: (err as Error).message })\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst totalDuration = performance.now() - startTime\n\t\t\t\tconst output = buildJsonOutput(results, errors, totalDuration)\n\t\t\t\tif (maxSizeBytes !== undefined) {\n\t\t\t\t\tfor (const file of output.files) {\n\t\t\t\t\t\tif (file.newSize > maxSizeBytes) {\n\t\t\t\t\t\t\tfile.exceeded = true\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn { content: [{ type: 'text' as const, text: JSON.stringify(output, null, 2) }] }\n\t\t\t},\n\t\t)\n\n\t\tserver.tool(\n\t\t\t'detect',\n\t\t\t'Detect if a file is an image and identify its format',\n\t\t\t{\n\t\t\t\tpath: z.string().describe('Path to the file'),\n\t\t\t},\n\t\t\tasync (params) => {\n\t\t\t\tconst resolvedPath = path.resolve(params.path)\n\t\t\t\tconst format = await detectFormat(resolvedPath)\n\t\t\t\tconst result = format ? { format, isImage: true } : { format: null, isImage: false }\n\t\t\t\treturn { content: [{ type: 'text' as const, text: JSON.stringify(result) }] }\n\t\t\t},\n\t\t)\n\n\t\tconst transport = new StdioServerTransport()\n\t\tawait server.connect(transport)\n\t})\n","import { Command } from 'commander'\nimport type { FormatEnum } from 'sharp'\nimport sharp from 'sharp'\nimport { getFormatOptions } from '../pipeline/optimize.js'\nimport { VALID_FORMATS } from '../validation/options.js'\n\nexport const pipeCommand = new Command('pipe')\n\t.description('Read image from stdin, write optimized image to stdout')\n\t.requiredOption('-f, --format <fmt>', 'Output format (jpeg, png, webp, gif, avif)')\n\t.option('-q, --quality <n>', 'Quality (1-100)', (v) => parseInt(v, 10), 70)\n\t.option('-r, --resize <width>', 'Resize to width (px)', (v) => parseInt(v, 10))\n\t.action(async (opts) => {\n\t\tconst MAX_INPUT_SIZE = 100 * 1024 * 1024 // 100 MB\n\t\tconst chunks: Buffer[] = []\n\t\tlet totalSize = 0\n\t\tfor await (const chunk of process.stdin) {\n\t\t\ttotalSize += chunk.length\n\t\t\tif (totalSize > MAX_INPUT_SIZE) {\n\t\t\t\tprocess.stderr.write('Error: input exceeds 100 MB limit\\n')\n\t\t\t\tprocess.exit(1)\n\t\t\t}\n\t\t\tchunks.push(chunk)\n\t\t}\n\t\tconst input = Buffer.concat(chunks)\n\n\t\tif (input.length === 0) {\n\t\t\tprocess.stderr.write('Error: no input received on stdin\\n')\n\t\t\tprocess.exit(1)\n\t\t}\n\n\t\tconst format = opts.format\n\t\tconst quality = opts.quality\n\t\tconst resize = opts.resize\n\n\t\tif (!VALID_FORMATS.includes(format)) {\n\t\t\tprocess.stderr.write(`Error: invalid format \"${format}\"\\n`)\n\t\t\tprocess.exit(1)\n\t\t}\n\n\t\tconst outputFormat = format === 'jpg' ? 'jpeg' : format\n\n\t\tconst formatOptions = getFormatOptions(quality)\n\n\t\ttry {\n\t\t\tlet pipeline = sharp(input)\n\n\t\t\tif (resize) {\n\t\t\t\tpipeline = pipeline.resize(resize)\n\t\t\t}\n\n\t\t\tpipeline = pipeline.toFormat(\n\t\t\t\toutputFormat as keyof FormatEnum,\n\t\t\t\tformatOptions[outputFormat] ?? {},\n\t\t\t)\n\n\t\t\tconst outputBuffer = await pipeline.toBuffer()\n\t\t\tprocess.stdout.write(outputBuffer)\n\t\t} catch (err) {\n\t\t\tprocess.stderr.write(`Error: ${(err as Error).message}\\n`)\n\t\t\tprocess.exit(1)\n\t\t}\n\t})\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA,SAAS,iBAAAA,sBAAqB;AAC9B,SAAS,WAAAC,gBAAe;;;ACDxB,OAAO,UAAU;AACjB,SAAS,eAAe;;;ACDjB,SAAS,kBAAkB,OAAe;AAChD,MAAI,UAAU;AAEd,SAAO;AAAA,IACN,OAAO;AACN;AACA,YAAM,UAAU,KAAK,MAAO,UAAU,QAAS,GAAG;AAClD,YAAM,WAAW,KAAK,IAAI,GAAG,KAAK,IAAI,QAAQ,OAAO,WAAW,IAAI,EAAE,IAAI,EAAE;AAC5E,YAAM,SAAS,KAAK,MAAO,UAAU,QAAS,QAAQ;AACtD,YAAM,QAAQ,WAAW;AACzB,YAAM,MAAM,IAAI,IAAI,OAAO,MAAM,CAAC,GAAG,IAAI,OAAO,KAAK,CAAC,KAAK,OAAO,KAAK,OAAO,IAAI,KAAK;AACvF,cAAQ,OAAO,MAAM,KAAK,GAAG,EAAE;AAC/B,UAAI,YAAY,MAAO,SAAQ,OAAO,MAAM,IAAI;AAAA,IACjD;AAAA,EACD;AACD;;;ADLO,IAAM,kBAAkB,IAAI,QAAQ,UAAU,EACnD,YAAY,wCAAwC,EACpD,SAAS,UAAU,8BAA8B,EACjD,OAAO,qBAAqB,0BAA0B,CAAC,MAAM,SAAS,GAAG,EAAE,GAAG,EAAE,EAChF,OAAO,sBAAsB,4CAA4C,EACzE,OAAO,wBAAwB,wBAAwB,CAAC,MAAM,SAAS,GAAG,EAAE,CAAC,EAC7E,OAAO,4BAA4B,yBAAyB,GAAG,EAC/D,OAAO,8BAA8B,yBAAyB,cAAc,EAC5E,OAAO,qBAAqB,iCAAiC,CAAC,MAAM,SAAS,GAAG,EAAE,CAAC,EACnF,OAAO,2BAA2B,4BAA4B,EAC9D,OAAO,aAAa,oDAAoD,EACxE,OAAO,aAAa,sBAAsB,EAC1C,OAAO,UAAU,wBAAwB,EACzC,OAAO,qBAAqB,6CAA6C,EACzE,OAAO,OAAO,WAAmB,SAA8B;AAC/D,MAAI;AACJ,MAAI;AACH,cAAU,gBAAgB,IAAI;AAAA,EAC/B,SAAS,KAAK;AACb,YAAQ,MAAO,IAAc,OAAO;AACpC,YAAQ,KAAK,CAAC;AAAA,EACf;AAEA,QAAM,eAAe,KAAK,QAAQ,SAAS;AAC3C,MAAI;AACJ,MAAI;AACJ,MAAI;AACH,UAAM,YAAY,MAAM,kBAAkB,cAAc,QAAQ,OAAO;AACvE,gBAAY,UAAU;AACtB,kBAAc,UAAU;AAAA,EACzB,QAAQ;AACP,YAAQ,MAAM,mBAAmB,YAAY,EAAE;AAC/C,YAAQ,KAAK,CAAC;AAAA,EACf;AAEA,MAAI,UAAU,WAAW,GAAG;AAC3B,YAAQ,IAAI,6BAA6B;AACzC;AAAA,EACD;AAEA,MAAI,QAAQ,QAAQ;AACnB,YAAQ,IAAI,iBAAiB,UAAU,MAAM,WAAW;AACxD,eAAW,KAAK,UAAW,SAAQ,IAAI,KAAK,KAAK,SAAS,EAAE,IAAI,CAAC,EAAE;AACnE;AAAA,EACD;AAEA,QAAM,aAAa,cAAc,eAAe,KAAK,QAAQ,YAAY;AACzE,QAAM,YAAY,YAAY,IAAI;AAElC,QAAM,WAAW,CAAC,QAAQ,WAAW,CAAC,QAAQ,OAAO,kBAAkB,UAAU,MAAM,IAAI;AAE3F,QAAM,SAAkB,CAAC;AACzB,QAAM,aAAkD,CAAC;AAEzD,QAAM,mBAAmB,MAAM;AAAA,IAC9B;AAAA,IACA,OAAO,SAAS;AACf,UAAI;AACH,cAAM,SAAS,MAAM,cAAc,KAAK,MAAM;AAAA,UAC7C,GAAG;AAAA,UACH;AAAA,UACA,gBAAgB,KAAK;AAAA,QACtB,CAAC;AACD,YAAI,QAAQ,WAAW,CAAC,QAAQ,MAAM;AACrC,kBAAQ,IAAI,iBAAiB,MAAM,CAAC;AAAA,QACrC;AACA,kBAAU,KAAK;AACf,eAAO;AAAA,MACR,SAAS,KAAK;AACb,eAAO,KAAK,GAAY;AACxB,mBAAW,KAAK,EAAE,MAAM,KAAK,MAAM,SAAU,IAAc,QAAQ,CAAC;AACpE,kBAAU,KAAK;AACf,eAAO;AAAA,MACR;AAAA,IACD;AAAA,IACA,QAAQ;AAAA,EACT;AAEA,QAAM,eAAe,iBAAiB,OAAO,CAAC,MAAuB,MAAM,IAAI;AAC/E,QAAM,gBAAgB,YAAY,IAAI,IAAI;AAE1C,MAAI,QAAQ,MAAM;AACjB,UAAM,SAAS,gBAAgB,cAAc,YAAY,aAAa;AACtE,QAAI,QAAQ,SAAS;AACpB,iBAAW,QAAQ,OAAO,OAAO;AAChC,YAAI,KAAK,UAAU,QAAQ,SAAS;AACnC,eAAK,WAAW;AAAA,QACjB;AAAA,MACD;AAAA,IACD;AACA,YAAQ,IAAI,KAAK,UAAU,MAAM,CAAC;AAAA,EACnC,OAAO;AACN,YAAQ,IAAI,cAAc,cAAc,QAAQ,aAAa,CAAC;AAAA,EAC/D;AAEA,MAAI,aAAa,WAAW,KAAK,OAAO,SAAS,GAAG;AACnD,YAAQ,KAAK,CAAC;AAAA,EACf;AAEA,QAAM,UAAU,QAAQ;AACxB,MAAI,SAAS;AACZ,UAAM,WAAW,aAAa,OAAO,CAAC,MAAM,EAAE,UAAU,OAAO;AAC/D,QAAI,SAAS,SAAS,GAAG;AACxB,UAAI,CAAC,QAAQ,MAAM;AAClB,mBAAW,KAAK,UAAU;AACzB,kBAAQ,KAAK,6BAA6B,EAAE,UAAU,EAAE;AAAA,QACzD;AAAA,MACD;AACA,cAAQ,KAAK,CAAC;AAAA,IACf;AAAA,EACD;AACD,CAAC;;;AEzHF,SAAS,qBAAqB;AAC9B,OAAOC,WAAU;AACjB,SAAS,iBAAiB;AAC1B,SAAS,4BAA4B;AACrC,SAAS,WAAAC,gBAAe;AACxB,SAAS,SAAS;AAQlB,IAAMC,WAAU,cAAc,YAAY,GAAG;AAC7C,IAAM,EAAE,QAAQ,IAAIA,SAAQ,oBAAoB;AAEzC,IAAM,aAAa,IAAIC,SAAQ,KAAK,EACzC,YAAY,wEAAwE,EACpF,OAAO,YAAY;AACnB,QAAM,SAAS,IAAI,UAAU;AAAA,IAC5B,MAAM;AAAA,IACN;AAAA,EACD,CAAC;AAED,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,MACC,MAAM,EAAE,OAAO,EAAE,SAAS,oCAAoC;AAAA,MAC9D,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,QAAQ,EAAE,EAAE,SAAS,wBAAwB;AAAA,MACjF,QAAQ,EACN,KAAK,CAAC,QAAQ,OAAO,QAAQ,OAAO,MAAM,CAAC,EAC3C,SAAS,EACT,SAAS,uCAAuC;AAAA,MAClD,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,qBAAqB;AAAA,MACvE,aAAa,EAAE,OAAO,EAAE,QAAQ,GAAG,EAAE,SAAS,kBAAkB;AAAA,MAChE,cAAc,EAAE,OAAO,EAAE,QAAQ,cAAc,EAAE,SAAS,oBAAoB;AAAA,MAC9E,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,yCAAyC;AAAA,IAClF;AAAA,IACA,OAAO,WAAW;AACjB,UACC,OAAO,aAAa,SAAS,GAAG,KAChC,OAAO,aAAa,SAAS,IAAI,KACjC,OAAO,aAAa,SAAS,IAAI,GAChC;AACD,eAAO;AAAA,UACN,SAAS;AAAA,YACR;AAAA,cACC,MAAM;AAAA,cACN,MAAM,KAAK,UAAU;AAAA,gBACpB,OAAO;AAAA,cACR,CAAC;AAAA,YACF;AAAA,UACD;AAAA,QACD;AAAA,MACD;AAEA,YAAM,eAAeC,MAAK,QAAQ,OAAO,IAAI;AAE7C,UAAI;AACJ,UAAI;AACJ,UAAI;AACH,cAAM,YAAY,MAAM,kBAAkB,cAAc,CAAC,CAAC;AAC1D,oBAAY,UAAU;AACtB,sBAAc,UAAU;AAAA,MACzB,QAAQ;AACP,eAAO;AAAA,UACN,SAAS;AAAA,YACR;AAAA,cACC,MAAM;AAAA,cACN,MAAM,KAAK,UAAU,EAAE,OAAO,mBAAmB,YAAY,GAAG,CAAC;AAAA,YAClE;AAAA,UACD;AAAA,QACD;AAAA,MACD;AAEA,UAAI,UAAU,WAAW,GAAG;AAC3B,eAAO;AAAA,UACN,SAAS;AAAA,YACR;AAAA,cACC,MAAM;AAAA,cACN,MAAM,KAAK,UAAU,EAAE,OAAO,CAAC,GAAG,SAAS,EAAE,YAAY,EAAE,EAAE,CAAC;AAAA,YAC/D;AAAA,UACD;AAAA,QACD;AAAA,MACD;AAEA,YAAM,aAAa,cAAc,eAAeA,MAAK,QAAQ,YAAY;AACzE,YAAM,YAAY,YAAY,IAAI;AAClC,YAAM,UAAwB,CAAC;AAC/B,YAAM,SAA8C,CAAC;AAErD,UAAI;AACJ,UAAI,OAAO,SAAS;AACnB,YAAI;AACH,yBAAe,UAAU,OAAO,OAAO;AAAA,QACxC,SAAS,KAAK;AACb,iBAAO;AAAA,YACN,SAAS;AAAA,cACR;AAAA,gBACC,MAAM;AAAA,gBACN,MAAM,KAAK,UAAU,EAAE,OAAQ,IAAc,QAAQ,CAAC;AAAA,cACvD;AAAA,YACD;AAAA,UACD;AAAA,QACD;AAAA,MACD;AAEA,iBAAW,QAAQ,WAAW;AAC7B,YAAI;AACH,gBAAM,SAAS,MAAM,cAAc,KAAK,MAAM;AAAA,YAC7C,SAAS,OAAO;AAAA,YAChB,QAAQ,OAAO;AAAA,YACf,QAAQ,OAAO;AAAA,YACf,aAAa,OAAO;AAAA,YACpB,cAAc,OAAO;AAAA,YACrB,aAAa;AAAA,YACb,SAAS,CAAC;AAAA,YACV,QAAQ;AAAA,YACR,SAAS;AAAA,YACT,MAAM;AAAA,YACN;AAAA,YACA,gBAAgB,KAAK;AAAA,UACtB,CAAC;AACD,kBAAQ,KAAK,MAAM;AAAA,QACpB,SAAS,KAAK;AACb,iBAAO,KAAK,EAAE,MAAM,KAAK,MAAM,SAAU,IAAc,QAAQ,CAAC;AAAA,QACjE;AAAA,MACD;AAEA,YAAM,gBAAgB,YAAY,IAAI,IAAI;AAC1C,YAAM,SAAS,gBAAgB,SAAS,QAAQ,aAAa;AAC7D,UAAI,iBAAiB,QAAW;AAC/B,mBAAW,QAAQ,OAAO,OAAO;AAChC,cAAI,KAAK,UAAU,cAAc;AAChC,iBAAK,WAAW;AAAA,UACjB;AAAA,QACD;AAAA,MACD;AAEA,aAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAiB,MAAM,KAAK,UAAU,QAAQ,MAAM,CAAC,EAAE,CAAC,EAAE;AAAA,IACtF;AAAA,EACD;AAEA,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,MACC,MAAM,EAAE,OAAO,EAAE,SAAS,kBAAkB;AAAA,IAC7C;AAAA,IACA,OAAO,WAAW;AACjB,YAAM,eAAeA,MAAK,QAAQ,OAAO,IAAI;AAC7C,YAAM,SAAS,MAAM,aAAa,YAAY;AAC9C,YAAM,SAAS,SAAS,EAAE,QAAQ,SAAS,KAAK,IAAI,EAAE,QAAQ,MAAM,SAAS,MAAM;AACnF,aAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAiB,MAAM,KAAK,UAAU,MAAM,EAAE,CAAC,EAAE;AAAA,IAC7E;AAAA,EACD;AAEA,QAAM,YAAY,IAAI,qBAAqB;AAC3C,QAAM,OAAO,QAAQ,SAAS;AAC/B,CAAC;;;AChKF,SAAS,WAAAC,gBAAe;AAExB,OAAO,WAAW;AAIX,IAAM,cAAc,IAAIC,SAAQ,MAAM,EAC3C,YAAY,wDAAwD,EACpE,eAAe,sBAAsB,4CAA4C,EACjF,OAAO,qBAAqB,mBAAmB,CAAC,MAAM,SAAS,GAAG,EAAE,GAAG,EAAE,EACzE,OAAO,wBAAwB,wBAAwB,CAAC,MAAM,SAAS,GAAG,EAAE,CAAC,EAC7E,OAAO,OAAO,SAAS;AACvB,QAAM,iBAAiB,MAAM,OAAO;AACpC,QAAM,SAAmB,CAAC;AAC1B,MAAI,YAAY;AAChB,mBAAiB,SAAS,QAAQ,OAAO;AACxC,iBAAa,MAAM;AACnB,QAAI,YAAY,gBAAgB;AAC/B,cAAQ,OAAO,MAAM,qCAAqC;AAC1D,cAAQ,KAAK,CAAC;AAAA,IACf;AACA,WAAO,KAAK,KAAK;AAAA,EAClB;AACA,QAAM,QAAQ,OAAO,OAAO,MAAM;AAElC,MAAI,MAAM,WAAW,GAAG;AACvB,YAAQ,OAAO,MAAM,qCAAqC;AAC1D,YAAQ,KAAK,CAAC;AAAA,EACf;AAEA,QAAM,SAAS,KAAK;AACpB,QAAM,UAAU,KAAK;AACrB,QAAM,SAAS,KAAK;AAEpB,MAAI,CAAC,cAAc,SAAS,MAAM,GAAG;AACpC,YAAQ,OAAO,MAAM,0BAA0B,MAAM;AAAA,CAAK;AAC1D,YAAQ,KAAK,CAAC;AAAA,EACf;AAEA,QAAM,eAAe,WAAW,QAAQ,SAAS;AAEjD,QAAM,gBAAgB,iBAAiB,OAAO;AAE9C,MAAI;AACH,QAAI,WAAW,MAAM,KAAK;AAE1B,QAAI,QAAQ;AACX,iBAAW,SAAS,OAAO,MAAM;AAAA,IAClC;AAEA,eAAW,SAAS;AAAA,MACnB;AAAA,MACA,cAAc,YAAY,KAAK,CAAC;AAAA,IACjC;AAEA,UAAM,eAAe,MAAM,SAAS,SAAS;AAC7C,YAAQ,OAAO,MAAM,YAAY;AAAA,EAClC,SAAS,KAAK;AACb,YAAQ,OAAO,MAAM,UAAW,IAAc,OAAO;AAAA,CAAI;AACzD,YAAQ,KAAK,CAAC;AAAA,EACf;AACD,CAAC;;;AJrDF,IAAMC,WAAUC,eAAc,YAAY,GAAG;AAC7C,IAAM,EAAE,SAAAC,SAAQ,IAAIF,SAAQ,oBAAoB;AAEhD,IAAI,QAAQ,KAAK,UAAU,GAAG;AAC7B,QAAM,EAAE,eAAe,IAAI,MAAM,OAAO,4BAAyB;AACjE,QAAM,eAAe;AACtB,OAAO;AACN,QAAM,UAAU,IAAIG,SAAQ;AAC5B,UAAQ,KAAK,OAAO,EAAE,YAAY,gCAAgC,EAAE,QAAQD,QAAO;AAEnF,UAAQ,WAAW,eAAe;AAClC,UAAQ,WAAW,WAAW;AAC9B,UAAQ,WAAW,UAAU;AAC7B,UAAQ,WAAW,WAAW;AAC9B,UAAQ,WAAW,YAAY;AAC/B,UAAQ,MAAM;AACf;","names":["createRequire","Command","path","Command","require","Command","path","Command","Command","require","createRequire","version","Command"]}
1
+ {"version":3,"sources":["../../src/bin/bulki.ts","../../src/commands/compress.ts","../../src/output/progress.ts","../../src/commands/mcp.ts","../../src/commands/pipe.ts"],"sourcesContent":["import { createRequire } from 'node:module'\nimport { Command } from 'commander'\nimport { compressCommand } from '../commands/compress.js'\nimport { hookCommand } from '../commands/hook.js'\nimport { mcpCommand } from '../commands/mcp.js'\nimport { pipeCommand } from '../commands/pipe.js'\nimport { watchCommand } from '../commands/watch.js'\nimport { parsePathInput } from '../pipeline/paths.js'\n\nconst require = createRequire(import.meta.url)\nconst { version } = require('../../package.json') as { version: string }\n\nconst args = process.argv.slice(2)\nconst COMMANDS = new Set(['compress', 'hook', 'mcp', 'pipe', 'watch', 'help'])\n\n// `bulki` alone, or `bulki <folder/images>` (e.g. dragged onto the terminal) → guided mode\nconst droppedPaths =\n\targs.length > 0 && !args[0].startsWith('-') && !COMMANDS.has(args[0])\n\t\t? parsePathInput(args.join(' '))\n\t\t: null\n\nif (args.length === 0 || droppedPaths?.ok) {\n\tconst { runInteractive } = await import('../interactive/index.js')\n\tawait runInteractive(droppedPaths?.ok ? droppedPaths.paths : undefined)\n} else {\n\tconst program = new Command()\n\tprogram\n\t\t.name('bulki')\n\t\t.description('Fast image optimizer. Run `bulki` with no arguments for the guided mode.')\n\t\t.version(version)\n\t\t.showSuggestionAfterError()\n\n\tprogram.addCommand(compressCommand)\n\tprogram.addCommand(hookCommand)\n\tprogram.addCommand(mcpCommand)\n\tprogram.addCommand(pipeCommand)\n\tprogram.addCommand(watchCommand)\n\tawait program.parseAsync()\n}\n","import { Command } from 'commander'\nimport { createProgressBar } from '../output/progress.js'\nimport { buildJsonOutput, formatFileResult, formatSummary } from '../output/results.js'\nimport { runBatch } from '../pipeline/optimize.js'\nimport { displayPath, resolveUserPath } from '../pipeline/paths.js'\nimport { PlanError, planJobs } from '../pipeline/plan.js'\nimport type { CompressOptions } from '../types/index.js'\nimport { DEFAULTS, parseInteger, type RawOptions, validateOptions } from '../validation/options.js'\n\nexport const compressCommand = new Command('compress')\n\t.description(\n\t\t'Compress images. Originals are never modified; optimized copies go to the output folder.',\n\t)\n\t.argument('<paths...>', 'Images and/or folders to optimize (quote paths with spaces)')\n\t.option('-o, --output <dir>', 'Folder where optimized images are written', DEFAULTS.output)\n\t.option('-q, --quality <n>', 'Output quality (1-100)', parseInteger, DEFAULTS.quality)\n\t.option('-f, --format <fmt>', 'Convert to jpeg, png, webp, gif or avif (default: keep format)')\n\t.option(\n\t\t'-r, --resize <width>',\n\t\t'Max width in px (smaller images are never enlarged)',\n\t\tparseInteger,\n\t)\n\t.option('--concurrency <n>', 'Images processed in parallel (1-32)', parseInteger)\n\t.option('--exclude <names...>', 'Folder names to skip')\n\t.option('--dry-run', 'Show what would be processed without writing files')\n\t.option('--verbose', 'Show one line per file')\n\t.option('--json', 'Output results as JSON')\n\t.option('--max-size <size>', 'Fail if any output is bigger than this (e.g. 200kb, 1mb)')\n\t// Legacy flags — kept so existing scripts and the GitHub Action keep working\n\t.option('-d, --destination <dir>', 'Deprecated: base folder; output goes to <dir>/bulki-output')\n\t.option('--output-folder <name>', 'Deprecated: subfolder name inside --destination')\n\t.addHelpText(\n\t\t'after',\n\t\t`\nExamples:\n bulki compress ./photos → ./bulki-output\n bulki compress ./photos -o ./optimized -f webp\n bulki compress \"My Photos\" -o \"Web Ready\" -r 1920\n bulki compress *.png -f avif`,\n\t)\n\t.action(async (inputPaths: string[], opts: Partial<RawOptions>, cmd: Command) => {\n\t\t// `-o` used to be the subfolder name; keep `-d x -o name` meaning `x/name`\n\t\tconst outputFromCli = cmd.getOptionValueSource('output') === 'cli'\n\t\tlet options: CompressOptions\n\t\ttry {\n\t\t\toptions = validateOptions({ ...opts, output: outputFromCli ? opts.output : undefined })\n\t\t} catch (err) {\n\t\t\tfail((err as Error).message)\n\t\t}\n\n\t\tconst inputs = inputPaths.map((p) => resolveUserPath(p))\n\t\tlet plan: Awaited<ReturnType<typeof planJobs>>\n\t\ttry {\n\t\t\tplan = await planJobs({\n\t\t\t\tinputs,\n\t\t\t\toutput: options.output,\n\t\t\t\tformat: options.format,\n\t\t\t\texclude: options.exclude,\n\t\t\t})\n\t\t} catch (err) {\n\t\t\tif (err instanceof PlanError) fail(err.message)\n\t\t\tthrow err\n\t\t}\n\n\t\tconst log = options.json ? () => {} : console.log\n\t\tfor (const s of plan.skipped) console.error(`Skipped ${s.file}: ${s.reason}`)\n\n\t\tif (plan.jobs.length === 0) {\n\t\t\tif (options.json) console.log(JSON.stringify(buildJsonOutput([], [], 0, options.output)))\n\t\t\telse log('No images found to process.')\n\t\t\treturn\n\t\t}\n\n\t\tif (options.dryRun) {\n\t\t\tlog(`Would optimize ${plan.jobs.length} image(s) into ${displayPath(options.output)}`)\n\t\t\tfor (const job of plan.jobs) log(` ${displayPath(job.input)} → ${displayPath(job.output)}`)\n\t\t\treturn\n\t\t}\n\n\t\tconst showBar = !options.verbose && !options.json && process.stdout.isTTY\n\t\tconst progress = showBar ? createProgressBar(plan.jobs.length) : null\n\n\t\tconst batch = await runBatch(plan.jobs, options, options.concurrency, (job, result, error) => {\n\t\t\tprogress?.tick()\n\t\t\tif (!options.verbose || options.json) return\n\t\t\tif (result) console.log(formatFileResult(result))\n\t\t\telse console.error(`✗ ${displayPath(job.input)}: ${error?.message}`)\n\t\t})\n\n\t\tconst maxSize = options.maxSize\n\t\tconst exceeded = maxSize ? batch.results.filter((r) => r.newSize > maxSize) : []\n\n\t\tif (options.json) {\n\t\t\tconst output = buildJsonOutput(batch.results, batch.errors, batch.duration, options.output)\n\t\t\tif (maxSize) for (const f of output.files) if (f.newSize > maxSize) f.exceeded = true\n\t\t\tconsole.log(JSON.stringify(output))\n\t\t} else {\n\t\t\tconsole.log(formatSummary(batch.results, batch.errors, batch.duration))\n\t\t\tif (!options.verbose) {\n\t\t\t\tfor (const e of batch.errors) console.error(`✗ ${displayPath(e.file)}: ${e.message}`)\n\t\t\t}\n\t\t\tif (batch.results.length > 0) console.log(`Saved to ${displayPath(options.output)}`)\n\t\t\tfor (const r of exceeded) console.warn(`File exceeds size budget: ${r.outputPath}`)\n\t\t}\n\n\t\tif (batch.errors.length > 0 || exceeded.length > 0) process.exitCode = 1\n\t})\n\nfunction fail(message: string): never {\n\tconsole.error(`error: ${message}`)\n\tprocess.exit(1)\n}\n","export function createProgressBar(total: number) {\n\tlet current = 0\n\n\treturn {\n\t\ttick() {\n\t\t\tcurrent++\n\t\t\tconst percent = Math.round((current / total) * 100)\n\t\t\tconst barWidth = Math.max(0, Math.min(process.stdout.columns || 40, 40) - 20)\n\t\t\tconst filled = Math.round((current / total) * barWidth)\n\t\t\tconst empty = barWidth - filled\n\t\t\tconst bar = `[${'='.repeat(filled)}${' '.repeat(empty)}] ${percent}% ${current}/${total}`\n\t\t\tprocess.stdout.write(`\\r${bar}`)\n\t\t\tif (current === total) process.stdout.write('\\n')\n\t\t},\n\t}\n}\n","import { createRequire } from 'node:module'\nimport path from 'node:path'\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport { Command } from 'commander'\nimport { z } from 'zod'\nimport { buildJsonOutput } from '../output/results.js'\nimport { detectFormat, UNSUPPORTED } from '../pipeline/filter.js'\nimport { runBatch } from '../pipeline/optimize.js'\nimport { resolveUserPath } from '../pipeline/paths.js'\nimport { planJobs } from '../pipeline/plan.js'\nimport { defaultConcurrency, parseSize, resolveOutputDir } from '../validation/options.js'\n\nconst require = createRequire(import.meta.url)\nconst { version } = require('../../package.json') as { version: string }\n\nfunction text(payload: unknown) {\n\treturn { content: [{ type: 'text' as const, text: JSON.stringify(payload, null, 2) }] }\n}\n\nexport const mcpCommand = new Command('mcp')\n\t.description('Start MCP server for AI editor integration (Claude Code, Cursor, etc.)')\n\t.action(async () => {\n\t\tconst server = new McpServer({ name: 'bulki', version })\n\n\t\tserver.tool(\n\t\t\t'compress',\n\t\t\t'Compress and optimize images (JPEG, PNG, WebP, GIF, AVIF). Originals are never modified; optimized copies are written to `output`.',\n\t\t\t{\n\t\t\t\tpath: z.string().describe('File or directory path to compress'),\n\t\t\t\tquality: z.number().int().min(1).max(100).default(70).describe('Output quality (1-100)'),\n\t\t\t\tformat: z\n\t\t\t\t\t.enum(['jpeg', 'png', 'webp', 'gif', 'avif'])\n\t\t\t\t\t.optional()\n\t\t\t\t\t.describe('Output format (omit to keep original)'),\n\t\t\t\tresize: z\n\t\t\t\t\t.number()\n\t\t\t\t\t.int()\n\t\t\t\t\t.positive()\n\t\t\t\t\t.optional()\n\t\t\t\t\t.describe('Max width in pixels (never enlarges)'),\n\t\t\t\toutput: z\n\t\t\t\t\t.string()\n\t\t\t\t\t.optional()\n\t\t\t\t\t.describe('Folder where optimized images are written (default: ./bulki-output)'),\n\t\t\t\tdestination: z.string().optional().describe('Deprecated: base folder, use `output`'),\n\t\t\t\toutputFolder: z.string().optional().describe('Deprecated: subfolder name, use `output`'),\n\t\t\t\tmaxSize: z.string().optional().describe('Max file size budget (e.g., 200kb, 1mb)'),\n\t\t\t},\n\t\t\tasync (params) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst maxSizeBytes = params.maxSize ? parseSize(params.maxSize) : undefined\n\t\t\t\t\tconst output = resolveOutputDir(params)\n\t\t\t\t\tconst plan = await planJobs({\n\t\t\t\t\t\tinputs: [resolveUserPath(params.path)],\n\t\t\t\t\t\toutput,\n\t\t\t\t\t\tformat: params.format,\n\t\t\t\t\t})\n\t\t\t\t\tconst settings = { quality: params.quality, format: params.format, resize: params.resize }\n\t\t\t\t\tconst batch = await runBatch(plan.jobs, settings, defaultConcurrency())\n\t\t\t\t\tconst result = buildJsonOutput(batch.results, batch.errors, batch.duration, output)\n\t\t\t\t\tif (maxSizeBytes !== undefined) {\n\t\t\t\t\t\tfor (const file of result.files) if (file.newSize > maxSizeBytes) file.exceeded = true\n\t\t\t\t\t}\n\t\t\t\t\treturn text(result)\n\t\t\t\t} catch (err) {\n\t\t\t\t\treturn text({ error: (err as Error).message })\n\t\t\t\t}\n\t\t\t},\n\t\t)\n\n\t\tserver.tool(\n\t\t\t'detect',\n\t\t\t'Detect if a file is an image and identify its format',\n\t\t\t{ path: z.string().describe('Path to the file') },\n\t\t\tasync (params) => {\n\t\t\t\tconst format = await detectFormat(path.resolve(resolveUserPath(params.path)))\n\t\t\t\tif (format === UNSUPPORTED) return text({ format: 'heic', isImage: true, supported: false })\n\t\t\t\treturn text(format ? { format, isImage: true } : { format: null, isImage: false })\n\t\t\t},\n\t\t)\n\n\t\tawait server.connect(new StdioServerTransport())\n\t})\n","import { Command } from 'commander'\nimport { encodeImage } from '../pipeline/encode.js'\nimport { UNSUPPORTED } from '../pipeline/filter.js'\nimport { DEFAULTS, parseInteger, validateEncodeSettings } from '../validation/options.js'\n\nconst MAX_INPUT_SIZE = 100 * 1024 * 1024 // 100 MB\n\nexport const pipeCommand = new Command('pipe')\n\t.description('Read image from stdin, write optimized image to stdout')\n\t.requiredOption('-f, --format <fmt>', 'Output format (jpeg, png, webp, gif, avif)')\n\t.option('-q, --quality <n>', 'Quality (1-100)', parseInteger, DEFAULTS.quality)\n\t.option('-r, --resize <width>', 'Max width in px (never enlarges)', parseInteger)\n\t.action(async (opts) => {\n\t\tlet settings: ReturnType<typeof validateEncodeSettings>\n\t\ttry {\n\t\t\tsettings = validateEncodeSettings(opts)\n\t\t} catch (err) {\n\t\t\treturn fail((err as Error).message)\n\t\t}\n\n\t\tconst chunks: Buffer[] = []\n\t\tlet totalSize = 0\n\t\tfor await (const chunk of process.stdin) {\n\t\t\ttotalSize += chunk.length\n\t\t\tif (totalSize > MAX_INPUT_SIZE) return fail('input exceeds 100 MB limit')\n\t\t\tchunks.push(chunk)\n\t\t}\n\t\tconst input = Buffer.concat(chunks)\n\t\tif (input.length === 0) return fail('no input received on stdin')\n\n\t\ttry {\n\t\t\tconst detected = sniff(input)\n\t\t\tconst { data } = await encodeImage(input, detected, settings)\n\t\t\tprocess.stdout.write(data)\n\t\t} catch (err) {\n\t\t\tfail((err as Error).message)\n\t\t}\n\t})\n\n/** Same magic-byte rules as detectFormat, for an in-memory buffer. */\nfunction sniff(buf: Buffer): string {\n\tif (buf[0] === 0xff && buf[1] === 0xd8) return 'jpeg'\n\tif (buf[0] === 0x89 && buf.toString('latin1', 1, 4) === 'PNG') return 'png'\n\tif (buf.toString('latin1', 0, 4) === 'RIFF' && buf.toString('latin1', 8, 12) === 'WEBP')\n\t\treturn 'webp'\n\tif (buf.toString('latin1', 0, 3) === 'GIF') return 'gif'\n\tif (buf.toString('latin1', 4, 8) === 'ftyp') {\n\t\tconst brand = buf.toString('latin1', 8, 12)\n\t\treturn brand === 'avif' || brand === 'avis' ? 'avif' : UNSUPPORTED\n\t}\n\treturn 'unknown'\n}\n\nfunction fail(message: string): never {\n\tprocess.stderr.write(`Error: ${message}\\n`)\n\tprocess.exit(1)\n}\n"],"mappings":"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","names":["createRequire","Command","Command","require","Command","Command","Command","fail","require","createRequire","version","Command"]}