filex-cli 0.1.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.
Files changed (42) hide show
  1. package/README.md +72 -0
  2. package/dist/bin/filex.d.ts +3 -0
  3. package/dist/bin/filex.d.ts.map +1 -0
  4. package/dist/bin/filex.js +5 -0
  5. package/dist/bin/filex.js.map +1 -0
  6. package/dist/src/actions/convertImages.d.ts +16 -0
  7. package/dist/src/actions/convertImages.d.ts.map +1 -0
  8. package/dist/src/actions/convertImages.js +101 -0
  9. package/dist/src/actions/convertImages.js.map +1 -0
  10. package/dist/src/actions/mergePdfs.d.ts +12 -0
  11. package/dist/src/actions/mergePdfs.d.ts.map +1 -0
  12. package/dist/src/actions/mergePdfs.js +132 -0
  13. package/dist/src/actions/mergePdfs.js.map +1 -0
  14. package/dist/src/actions/resizeImages.d.ts +11 -0
  15. package/dist/src/actions/resizeImages.d.ts.map +1 -0
  16. package/dist/src/actions/resizeImages.js +71 -0
  17. package/dist/src/actions/resizeImages.js.map +1 -0
  18. package/dist/src/ai/explain.d.ts +15 -0
  19. package/dist/src/ai/explain.d.ts.map +1 -0
  20. package/dist/src/ai/explain.js +75 -0
  21. package/dist/src/ai/explain.js.map +1 -0
  22. package/dist/src/ai/parseIntent.d.ts +18 -0
  23. package/dist/src/ai/parseIntent.d.ts.map +1 -0
  24. package/dist/src/ai/parseIntent.js +145 -0
  25. package/dist/src/ai/parseIntent.js.map +1 -0
  26. package/dist/src/cli.d.ts +2 -0
  27. package/dist/src/cli.d.ts.map +1 -0
  28. package/dist/src/cli.js +149 -0
  29. package/dist/src/cli.js.map +1 -0
  30. package/dist/src/confirm.d.ts +17 -0
  31. package/dist/src/confirm.d.ts.map +1 -0
  32. package/dist/src/confirm.js +69 -0
  33. package/dist/src/confirm.js.map +1 -0
  34. package/dist/src/scan.d.ts +27 -0
  35. package/dist/src/scan.d.ts.map +1 -0
  36. package/dist/src/scan.js +79 -0
  37. package/dist/src/scan.js.map +1 -0
  38. package/dist/src/schema.d.ts +224 -0
  39. package/dist/src/schema.d.ts.map +1 -0
  40. package/dist/src/schema.js +52 -0
  41. package/dist/src/schema.js.map +1 -0
  42. package/package.json +46 -0
package/README.md ADDED
@@ -0,0 +1,72 @@
1
+ # FileX
2
+
3
+ A terminal CLI for safe local file operations with optional AI assistance.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g filex
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### Manual Commands
14
+
15
+ ```bash
16
+ # Convert images to WebP
17
+ filex convert ./photos
18
+ filex convert ./photos --max-width 1600 --quality 90
19
+
20
+ # Resize images
21
+ filex resize ./photos --max-width 800
22
+
23
+ # Merge PDFs
24
+ filex merge-pdfs ./documents
25
+ filex merge-pdfs ./documents --output combined.pdf
26
+ ```
27
+
28
+ ### AI-Driven Usage
29
+
30
+ Set your Gemini API key:
31
+ ```bash
32
+ export GEMINI_API_KEY="your-api-key"
33
+ ```
34
+
35
+ Then use natural language:
36
+ ```bash
37
+ filex "convert all images here to webp and make them smaller for web"
38
+ filex "resize these photos to 1200px wide" --directory ./photos
39
+ filex "merge all the PDFs in this folder"
40
+ ```
41
+
42
+ ## Commands
43
+
44
+ | Command | Description |
45
+ |---------|-------------|
46
+ | `convert <dir>` | Convert PNG/JPG/JPEG to WebP |
47
+ | `resize <dir>` | Resize images to max width |
48
+ | `merge-pdfs <dir>` | Merge all PDFs alphabetically |
49
+
50
+ ## Options
51
+
52
+ ### convert
53
+ - `--max-width <px>` - Maximum width in pixels
54
+ - `--quality <1-100>` - Output quality (default: 80)
55
+
56
+ ### resize
57
+ - `--max-width <px>` - Maximum width in pixels (required)
58
+
59
+ ### merge-pdfs
60
+ - `-o, --output <file>` - Output filename (default: merged.pdf)
61
+
62
+ ## Safety Guarantees
63
+
64
+ - ✅ **Originals are never deleted** - All operations create new files
65
+ - ✅ **Confirmation before writes** - Preview planned actions and approve
66
+ - ✅ **AI is sandboxed** - AI only parses intent, never touches files
67
+ - ✅ **Graceful errors** - Clear messages, no crashes on edge cases
68
+ - ✅ **Works without AI** - Manual commands function independently
69
+
70
+ ## License
71
+
72
+ MIT
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import 'dotenv/config';
3
+ //# sourceMappingURL=filex.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filex.d.ts","sourceRoot":"","sources":["../../bin/filex.ts"],"names":[],"mappings":";AACA,OAAO,eAAe,CAAC"}
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ import 'dotenv/config';
3
+ import { runCli } from '../src/cli.js';
4
+ runCli();
5
+ //# sourceMappingURL=filex.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filex.js","sourceRoot":"","sources":["../../bin/filex.ts"],"names":[],"mappings":";AACA,OAAO,eAAe,CAAC;AACvB,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAEvC,MAAM,EAAE,CAAC"}
@@ -0,0 +1,16 @@
1
+ export declare const INPUT_FORMATS: readonly ["png", "jpg", "jpeg", "webp", "gif", "tiff", "avif"];
2
+ export declare const OUTPUT_FORMATS: readonly ["webp", "png", "jpg"];
3
+ export type InputFormat = typeof INPUT_FORMATS[number];
4
+ export type OutputFormat = typeof OUTPUT_FORMATS[number];
5
+ export interface ConvertImagesOptions {
6
+ from?: InputFormat | InputFormat[];
7
+ to?: OutputFormat;
8
+ quality?: number;
9
+ maxWidth?: number;
10
+ }
11
+ /**
12
+ * Convert images in a directory from one format to another.
13
+ * Originals are preserved. Output files are written alongside originals.
14
+ */
15
+ export declare function convertImages(dirPath: string, options?: ConvertImagesOptions): Promise<void>;
16
+ //# sourceMappingURL=convertImages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"convertImages.d.ts","sourceRoot":"","sources":["../../../src/actions/convertImages.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,aAAa,gEAAiE,CAAC;AAC5F,eAAO,MAAM,cAAc,iCAAkC,CAAC;AAE9D,MAAM,MAAM,WAAW,GAAG,OAAO,aAAa,CAAC,MAAM,CAAC,CAAC;AACvD,MAAM,MAAM,YAAY,GAAG,OAAO,cAAc,CAAC,MAAM,CAAC,CAAC;AAEzD,MAAM,WAAW,oBAAoB;IACjC,IAAI,CAAC,EAAE,WAAW,GAAG,WAAW,EAAE,CAAC;IACnC,EAAE,CAAC,EAAE,YAAY,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAsB,aAAa,CAC/B,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,oBAAyB,GACnC,OAAO,CAAC,IAAI,CAAC,CA0Gf"}
@@ -0,0 +1,101 @@
1
+ import * as path from 'node:path';
2
+ import sharp from 'sharp';
3
+ import ora from 'ora';
4
+ import chalk from 'chalk';
5
+ import { scanDirectory } from '../scan.js';
6
+ import { confirmAction } from '../confirm.js';
7
+ // Supported formats
8
+ export const INPUT_FORMATS = ['png', 'jpg', 'jpeg', 'webp', 'gif', 'tiff', 'avif'];
9
+ export const OUTPUT_FORMATS = ['webp', 'png', 'jpg'];
10
+ /**
11
+ * Convert images in a directory from one format to another.
12
+ * Originals are preserved. Output files are written alongside originals.
13
+ */
14
+ export async function convertImages(dirPath, options = {}) {
15
+ const { from, to = 'webp', quality = 80, maxWidth } = options;
16
+ // Determine which extensions to scan for
17
+ const inputExtensions = from
18
+ ? (Array.isArray(from) ? from : [from])
19
+ : [...INPUT_FORMATS];
20
+ // Filter out the target format from input (no point converting webp to webp)
21
+ const filteredExtensions = inputExtensions.filter(ext => {
22
+ // jpg and jpeg are the same
23
+ if (to === 'jpg' && (ext === 'jpg' || ext === 'jpeg'))
24
+ return false;
25
+ return ext !== to;
26
+ });
27
+ if (filteredExtensions.length === 0) {
28
+ console.log(chalk.yellow('No valid source formats to convert.'));
29
+ return;
30
+ }
31
+ // Scan for images with specified extensions
32
+ const scanResult = await scanDirectory(dirPath, filteredExtensions);
33
+ if (scanResult.count === 0) {
34
+ const extList = filteredExtensions.map(e => e.toUpperCase()).join(', ');
35
+ console.log(chalk.yellow(`No ${extList} images found in directory.`));
36
+ return;
37
+ }
38
+ // Build action descriptions
39
+ const fromLabel = from
40
+ ? (Array.isArray(from) ? from.join(', ') : from).toUpperCase()
41
+ : 'all supported formats';
42
+ const actions = [
43
+ `Convert ${fromLabel} → ${to.toUpperCase()}`,
44
+ ];
45
+ if (maxWidth) {
46
+ actions.push(`Resize to max width ${maxWidth}px`);
47
+ }
48
+ actions.push(`Quality: ${quality}%`);
49
+ // Confirm with user
50
+ const confirmed = await confirmAction({
51
+ fileCount: scanResult.count,
52
+ fileType: `${fromLabel} images`,
53
+ actions,
54
+ preserveOriginals: true,
55
+ });
56
+ if (!confirmed) {
57
+ return;
58
+ }
59
+ // Process images
60
+ const spinner = ora('Converting images...').start();
61
+ let successCount = 0;
62
+ let errorCount = 0;
63
+ for (const filePath of scanResult.files) {
64
+ try {
65
+ const dir = path.dirname(filePath);
66
+ const basename = path.basename(filePath, path.extname(filePath));
67
+ const outputExt = to === 'jpg' ? 'jpg' : to;
68
+ const outputPath = path.join(dir, `${basename}.${outputExt}`);
69
+ let pipeline = sharp(filePath);
70
+ if (maxWidth) {
71
+ pipeline = pipeline.resize({ width: maxWidth, withoutEnlargement: true });
72
+ }
73
+ // Convert to target format
74
+ switch (to) {
75
+ case 'webp':
76
+ await pipeline.webp({ quality }).toFile(outputPath);
77
+ break;
78
+ case 'png':
79
+ await pipeline.png({ quality }).toFile(outputPath);
80
+ break;
81
+ case 'jpg':
82
+ await pipeline.jpeg({ quality }).toFile(outputPath);
83
+ break;
84
+ }
85
+ successCount++;
86
+ spinner.text = `Converting images... (${successCount}/${scanResult.count})`;
87
+ }
88
+ catch (error) {
89
+ errorCount++;
90
+ spinner.warn(`Failed to convert: ${path.basename(filePath)}`);
91
+ spinner.start();
92
+ }
93
+ }
94
+ spinner.stop();
95
+ console.log();
96
+ console.log(chalk.green(`✓ Converted ${successCount} images to ${to.toUpperCase()}`));
97
+ if (errorCount > 0) {
98
+ console.log(chalk.red(`✗ Failed to convert ${errorCount} images`));
99
+ }
100
+ }
101
+ //# sourceMappingURL=convertImages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"convertImages.js","sourceRoot":"","sources":["../../../src/actions/convertImages.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ export declare const SORT_ORDERS: readonly ["name", "name-desc", "date", "date-desc", "random"];
2
+ export type SortOrder = typeof SORT_ORDERS[number];
3
+ export interface MergePdfsOptions {
4
+ outputFile?: string;
5
+ order?: SortOrder;
6
+ }
7
+ /**
8
+ * Merge all PDFs in a directory into a single output file.
9
+ * Originals are preserved.
10
+ */
11
+ export declare function mergePdfs(dirPath: string, options?: MergePdfsOptions): Promise<void>;
12
+ //# sourceMappingURL=mergePdfs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mergePdfs.d.ts","sourceRoot":"","sources":["../../../src/actions/mergePdfs.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,WAAW,+DAAgE,CAAC;AACzF,MAAM,MAAM,SAAS,GAAG,OAAO,WAAW,CAAC,MAAM,CAAC,CAAC;AAEnD,MAAM,WAAW,gBAAgB;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,SAAS,CAAC;CACrB;AAwED;;;GAGG;AACH,wBAAsB,SAAS,CAC3B,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,gBAAqB,GAC/B,OAAO,CAAC,IAAI,CAAC,CAyEf"}
@@ -0,0 +1,132 @@
1
+ import * as fs from 'node:fs/promises';
2
+ import * as path from 'node:path';
3
+ import { PDFDocument } from 'pdf-lib';
4
+ import ora from 'ora';
5
+ import chalk from 'chalk';
6
+ import { scanForPdfs } from '../scan.js';
7
+ import { confirmAction } from '../confirm.js';
8
+ export const SORT_ORDERS = ['name', 'name-desc', 'date', 'date-desc', 'random'];
9
+ /**
10
+ * Get file modification time.
11
+ */
12
+ async function getFileMtime(filePath) {
13
+ const stat = await fs.stat(filePath);
14
+ return stat.mtimeMs;
15
+ }
16
+ /**
17
+ * Sort files according to the specified order.
18
+ */
19
+ async function sortFiles(files, order) {
20
+ const sorted = [...files];
21
+ switch (order) {
22
+ case 'name':
23
+ // Already sorted alphabetically by scan.ts
24
+ return sorted;
25
+ case 'name-desc':
26
+ return sorted.reverse();
27
+ case 'date': {
28
+ const withTimes = await Promise.all(sorted.map(async (f) => ({ path: f, mtime: await getFileMtime(f) })));
29
+ withTimes.sort((a, b) => a.mtime - b.mtime);
30
+ return withTimes.map((f) => f.path);
31
+ }
32
+ case 'date-desc': {
33
+ const withTimes = await Promise.all(sorted.map(async (f) => ({ path: f, mtime: await getFileMtime(f) })));
34
+ withTimes.sort((a, b) => b.mtime - a.mtime);
35
+ return withTimes.map((f) => f.path);
36
+ }
37
+ case 'random':
38
+ for (let i = sorted.length - 1; i > 0; i--) {
39
+ const j = Math.floor(Math.random() * (i + 1));
40
+ [sorted[i], sorted[j]] = [sorted[j], sorted[i]];
41
+ }
42
+ return sorted;
43
+ default:
44
+ return sorted;
45
+ }
46
+ }
47
+ /**
48
+ * Get human-readable order description.
49
+ */
50
+ function getOrderDescription(order) {
51
+ switch (order) {
52
+ case 'name':
53
+ return 'alphabetical (A→Z)';
54
+ case 'name-desc':
55
+ return 'reverse alphabetical (Z→A)';
56
+ case 'date':
57
+ return 'oldest first';
58
+ case 'date-desc':
59
+ return 'newest first';
60
+ case 'random':
61
+ return 'random';
62
+ default:
63
+ return 'alphabetical';
64
+ }
65
+ }
66
+ /**
67
+ * Merge all PDFs in a directory into a single output file.
68
+ * Originals are preserved.
69
+ */
70
+ export async function mergePdfs(dirPath, options = {}) {
71
+ const { outputFile = 'merged.pdf', order = 'name' } = options;
72
+ // Scan for PDFs
73
+ const scanResult = await scanForPdfs(dirPath);
74
+ if (scanResult.count === 0) {
75
+ console.log(chalk.yellow('No PDF files found in directory.'));
76
+ return;
77
+ }
78
+ if (scanResult.count === 1) {
79
+ console.log(chalk.yellow('Only one PDF found. Nothing to merge.'));
80
+ return;
81
+ }
82
+ // Sort files according to order
83
+ const sortedFiles = await sortFiles(scanResult.files, order);
84
+ const outputPath = path.join(scanResult.directory, outputFile);
85
+ const orderDesc = getOrderDescription(order);
86
+ // Build action descriptions
87
+ const actions = [
88
+ `Merge ${scanResult.count} PDFs (${orderDesc})`,
89
+ `Output: ${outputFile}`,
90
+ ];
91
+ // Confirm with user
92
+ const confirmed = await confirmAction({
93
+ fileCount: scanResult.count,
94
+ fileType: 'PDF files',
95
+ actions,
96
+ preserveOriginals: true,
97
+ });
98
+ if (!confirmed) {
99
+ return;
100
+ }
101
+ // Merge PDFs
102
+ const spinner = ora('Merging PDFs...').start();
103
+ try {
104
+ const mergedPdf = await PDFDocument.create();
105
+ for (let i = 0; i < sortedFiles.length; i++) {
106
+ const filePath = sortedFiles[i];
107
+ spinner.text = `Merging PDFs... (${i + 1}/${scanResult.count})`;
108
+ try {
109
+ const pdfBytes = await fs.readFile(filePath);
110
+ const pdf = await PDFDocument.load(pdfBytes);
111
+ const pages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());
112
+ for (const page of pages) {
113
+ mergedPdf.addPage(page);
114
+ }
115
+ }
116
+ catch (error) {
117
+ spinner.warn(`Failed to read: ${path.basename(filePath)}`);
118
+ spinner.start();
119
+ }
120
+ }
121
+ const outputBytes = await mergedPdf.save();
122
+ await fs.writeFile(outputPath, outputBytes);
123
+ spinner.stop();
124
+ console.log();
125
+ console.log(chalk.green(`✓ Created ${outputFile} with ${mergedPdf.getPageCount()} pages`));
126
+ }
127
+ catch (error) {
128
+ spinner.stop();
129
+ console.log(chalk.red(`✗ Failed to merge PDFs: ${error.message}`));
130
+ }
131
+ }
132
+ //# sourceMappingURL=mergePdfs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mergePdfs.js","sourceRoot":"","sources":["../../../src/actions/mergePdfs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ export interface ResizeImagesOptions {
2
+ maxWidth: number;
3
+ quality?: number;
4
+ }
5
+ /**
6
+ * Resize images in a directory to a maximum width.
7
+ * Aspect ratio is preserved. Originals are preserved.
8
+ * Output files are written with _resized suffix.
9
+ */
10
+ export declare function resizeImages(dirPath: string, options: ResizeImagesOptions): Promise<void>;
11
+ //# sourceMappingURL=resizeImages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resizeImages.d.ts","sourceRoot":"","sources":["../../../src/actions/resizeImages.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,mBAAmB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAC9B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,mBAAmB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAsEf"}
@@ -0,0 +1,71 @@
1
+ import * as path from 'node:path';
2
+ import sharp from 'sharp';
3
+ import ora from 'ora';
4
+ import chalk from 'chalk';
5
+ import { scanForImages } from '../scan.js';
6
+ import { confirmAction } from '../confirm.js';
7
+ /**
8
+ * Resize images in a directory to a maximum width.
9
+ * Aspect ratio is preserved. Originals are preserved.
10
+ * Output files are written with _resized suffix.
11
+ */
12
+ export async function resizeImages(dirPath, options) {
13
+ const { maxWidth, quality = 80 } = options;
14
+ // Scan for images
15
+ const scanResult = await scanForImages(dirPath);
16
+ if (scanResult.count === 0) {
17
+ console.log(chalk.yellow('No images found in directory.'));
18
+ return;
19
+ }
20
+ // Build action descriptions
21
+ const actions = [
22
+ `Resize to max width ${maxWidth}px`,
23
+ 'Preserve aspect ratio',
24
+ ];
25
+ // Confirm with user
26
+ const confirmed = await confirmAction({
27
+ fileCount: scanResult.count,
28
+ fileType: 'images',
29
+ actions,
30
+ preserveOriginals: true,
31
+ });
32
+ if (!confirmed) {
33
+ return;
34
+ }
35
+ // Process images
36
+ const spinner = ora('Resizing images...').start();
37
+ let successCount = 0;
38
+ let errorCount = 0;
39
+ for (const filePath of scanResult.files) {
40
+ try {
41
+ const dir = path.dirname(filePath);
42
+ const ext = path.extname(filePath);
43
+ const basename = path.basename(filePath, ext);
44
+ const outputPath = path.join(dir, `${basename}_resized${ext}`);
45
+ const metadata = await sharp(filePath).metadata();
46
+ // Skip if already smaller than maxWidth
47
+ if (metadata.width && metadata.width <= maxWidth) {
48
+ spinner.text = `Skipping ${path.basename(filePath)} (already ${metadata.width}px wide)`;
49
+ successCount++;
50
+ continue;
51
+ }
52
+ await sharp(filePath)
53
+ .resize({ width: maxWidth, withoutEnlargement: true })
54
+ .toFile(outputPath);
55
+ successCount++;
56
+ spinner.text = `Resizing images... (${successCount}/${scanResult.count})`;
57
+ }
58
+ catch (error) {
59
+ errorCount++;
60
+ spinner.warn(`Failed to resize: ${path.basename(filePath)}`);
61
+ spinner.start();
62
+ }
63
+ }
64
+ spinner.stop();
65
+ console.log();
66
+ console.log(chalk.green(`✓ Resized ${successCount} images successfully`));
67
+ if (errorCount > 0) {
68
+ console.log(chalk.red(`✗ Failed to resize ${errorCount} images`));
69
+ }
70
+ }
71
+ //# sourceMappingURL=resizeImages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resizeImages.js","sourceRoot":"","sources":["../../../src/actions/resizeImages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAO9C;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAC9B,OAAe,EACf,OAA4B;IAE5B,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC;IAE3C,kBAAkB;IAClB,MAAM,UAAU,GAAG,MAAM,aAAa,CAAC,OAAO,CAAC,CAAC;IAEhD,IAAI,UAAU,CAAC,KAAK,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,+BAA+B,CAAC,CAAC,CAAC;QAC3D,OAAO;IACX,CAAC;IAED,4BAA4B;IAC5B,MAAM,OAAO,GAAa;QACtB,uBAAuB,QAAQ,IAAI;QACnC,uBAAuB;KAC1B,CAAC;IAEF,oBAAoB;IACpB,MAAM,SAAS,GAAG,MAAM,aAAa,CAAC;QAClC,SAAS,EAAE,UAAU,CAAC,KAAK;QAC3B,QAAQ,EAAE,QAAQ;QAClB,OAAO;QACP,iBAAiB,EAAE,IAAI;KAC1B,CAAC,CAAC;IAEH,IAAI,CAAC,SAAS,EAAE,CAAC;QACb,OAAO;IACX,CAAC;IAED,iBAAiB;IACjB,MAAM,OAAO,GAAG,GAAG,CAAC,oBAAoB,CAAC,CAAC,KAAK,EAAE,CAAC;IAClD,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,IAAI,UAAU,GAAG,CAAC,CAAC;IAEnB,KAAK,MAAM,QAAQ,IAAI,UAAU,CAAC,KAAK,EAAE,CAAC;QACtC,IAAI,CAAC;YACD,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;YACnC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;YACnC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;YAC9C,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,WAAW,GAAG,EAAE,CAAC,CAAC;YAE/D,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,QAAQ,CAAC,CAAC,QAAQ,EAAE,CAAC;YAElD,wCAAwC;YACxC,IAAI,QAAQ,CAAC,KAAK,IAAI,QAAQ,CAAC,KAAK,IAAI,QAAQ,EAAE,CAAC;gBAC/C,OAAO,CAAC,IAAI,GAAG,YAAY,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,aAAa,QAAQ,CAAC,KAAK,UAAU,CAAC;gBACxF,YAAY,EAAE,CAAC;gBACf,SAAS;YACb,CAAC;YAED,MAAM,KAAK,CAAC,QAAQ,CAAC;iBAChB,MAAM,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,kBAAkB,EAAE,IAAI,EAAE,CAAC;iBACrD,MAAM,CAAC,UAAU,CAAC,CAAC;YAExB,YAAY,EAAE,CAAC;YACf,OAAO,CAAC,IAAI,GAAG,uBAAuB,YAAY,IAAI,UAAU,CAAC,KAAK,GAAG,CAAC;QAC9E,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,UAAU,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CAAC,qBAAqB,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;YAC7D,OAAO,CAAC,KAAK,EAAE,CAAC;QACpB,CAAC;IACL,CAAC;IAED,OAAO,CAAC,IAAI,EAAE,CAAC;IAEf,OAAO,CAAC,GAAG,EAAE,CAAC;IACd,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,aAAa,YAAY,sBAAsB,CAAC,CAAC,CAAC;IAC1E,IAAI,UAAU,GAAG,CAAC,EAAE,CAAC;QACjB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,sBAAsB,UAAU,SAAS,CAAC,CAAC,CAAC;IACtE,CAAC;AACL,CAAC"}
@@ -0,0 +1,15 @@
1
+ import type { Action } from '../schema.js';
2
+ /**
3
+ * Get action name for display.
4
+ */
5
+ export declare function getActionName(action: Action): string;
6
+ /**
7
+ * Generate a human-friendly explanation from an Action.
8
+ * This is a pure function - no API calls needed.
9
+ */
10
+ export declare function explainAction(action: Action): string[];
11
+ /**
12
+ * Format explanation as a styled string for console output.
13
+ */
14
+ export declare function formatExplanation(action: Action): string;
15
+ //# sourceMappingURL=explain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"explain.d.ts","sourceRoot":"","sources":["../../../src/ai/explain.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3C;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAWpD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAkDtD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAKxD"}
@@ -0,0 +1,75 @@
1
+ import chalk from 'chalk';
2
+ /**
3
+ * Get action name for display.
4
+ */
5
+ export function getActionName(action) {
6
+ switch (action.action) {
7
+ case 'convert_images':
8
+ return 'Image Conversion';
9
+ case 'resize_images':
10
+ return 'Image Resize';
11
+ case 'merge_pdfs':
12
+ return 'PDF Merge';
13
+ default:
14
+ return 'Unknown';
15
+ }
16
+ }
17
+ /**
18
+ * Generate a human-friendly explanation from an Action.
19
+ * This is a pure function - no API calls needed.
20
+ */
21
+ export function explainAction(action) {
22
+ switch (action.action) {
23
+ case 'convert_images': {
24
+ const lines = [];
25
+ const from = action.options?.from;
26
+ const to = action.options?.to ?? action.options?.format ?? 'webp';
27
+ if (from) {
28
+ lines.push(`Convert: ${from.toUpperCase()} → ${to.toUpperCase()}`);
29
+ }
30
+ else {
31
+ lines.push(`Convert to: ${to.toUpperCase()}`);
32
+ }
33
+ if (action.options?.maxWidth) {
34
+ lines.push(`Max width: ${action.options.maxWidth}px`);
35
+ }
36
+ if (action.options?.quality) {
37
+ lines.push(`Quality: ${action.options.quality}%`);
38
+ }
39
+ return lines;
40
+ }
41
+ case 'resize_images': {
42
+ return [
43
+ `Max width: ${action.options.maxWidth}px`,
44
+ 'Aspect ratio preserved',
45
+ ];
46
+ }
47
+ case 'merge_pdfs': {
48
+ const outputFile = action.options?.outputFile ?? 'merged.pdf';
49
+ const order = action.options?.order ?? 'name';
50
+ const orderLabels = {
51
+ 'name': 'Alphabetical (A→Z)',
52
+ 'name-desc': 'Reverse (Z→A)',
53
+ 'date': 'Oldest first',
54
+ 'date-desc': 'Newest first',
55
+ 'random': 'Random',
56
+ };
57
+ return [
58
+ `Order: ${orderLabels[order] ?? 'Alphabetical'}`,
59
+ `Output: ${outputFile}`,
60
+ ];
61
+ }
62
+ default:
63
+ return ['Unknown action'];
64
+ }
65
+ }
66
+ /**
67
+ * Format explanation as a styled string for console output.
68
+ */
69
+ export function formatExplanation(action) {
70
+ const name = getActionName(action);
71
+ const lines = explainAction(action);
72
+ const formatted = lines.map(line => ` ${chalk.gray('›')} ${chalk.white(line)}`).join('\n');
73
+ return `${chalk.cyan.bold(`▸ ${name}`)}\n${formatted}`;
74
+ }
75
+ //# sourceMappingURL=explain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"explain.js","sourceRoot":"","sources":["../../../src/ai/explain.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import { type Action } from '../schema.js';
2
+ export type ParseIntentResult = {
3
+ success: true;
4
+ action: Action;
5
+ } | {
6
+ success: false;
7
+ error: string;
8
+ };
9
+ /**
10
+ * Parse natural language input into a structured Action using Gemini AI.
11
+ * Returns a validated Action or an error message.
12
+ */
13
+ export declare function parseIntent(input: string): Promise<ParseIntentResult>;
14
+ /**
15
+ * Display a helpful message when AI parsing fails.
16
+ */
17
+ export declare function showAiFailureMessage(error: string): void;
18
+ //# sourceMappingURL=parseIntent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseIntent.d.ts","sourceRoot":"","sources":["../../../src/ai/parseIntent.ts"],"names":[],"mappings":"AACA,OAAO,EAAmB,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AA+B5D,MAAM,MAAM,iBAAiB,GACvB;IACE,OAAO,EAAE,IAAI,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAClB,GACC;IACE,OAAO,EAAE,KAAK,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACjB,CAAC;AAEN;;;GAGG;AACH,wBAAsB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAwG3E;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAiBxD"}