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.
- package/README.md +72 -0
- package/dist/bin/filex.d.ts +3 -0
- package/dist/bin/filex.d.ts.map +1 -0
- package/dist/bin/filex.js +5 -0
- package/dist/bin/filex.js.map +1 -0
- package/dist/src/actions/convertImages.d.ts +16 -0
- package/dist/src/actions/convertImages.d.ts.map +1 -0
- package/dist/src/actions/convertImages.js +101 -0
- package/dist/src/actions/convertImages.js.map +1 -0
- package/dist/src/actions/mergePdfs.d.ts +12 -0
- package/dist/src/actions/mergePdfs.d.ts.map +1 -0
- package/dist/src/actions/mergePdfs.js +132 -0
- package/dist/src/actions/mergePdfs.js.map +1 -0
- package/dist/src/actions/resizeImages.d.ts +11 -0
- package/dist/src/actions/resizeImages.d.ts.map +1 -0
- package/dist/src/actions/resizeImages.js +71 -0
- package/dist/src/actions/resizeImages.js.map +1 -0
- package/dist/src/ai/explain.d.ts +15 -0
- package/dist/src/ai/explain.d.ts.map +1 -0
- package/dist/src/ai/explain.js +75 -0
- package/dist/src/ai/explain.js.map +1 -0
- package/dist/src/ai/parseIntent.d.ts +18 -0
- package/dist/src/ai/parseIntent.d.ts.map +1 -0
- package/dist/src/ai/parseIntent.js +145 -0
- package/dist/src/ai/parseIntent.js.map +1 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.d.ts.map +1 -0
- package/dist/src/cli.js +149 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/confirm.d.ts +17 -0
- package/dist/src/confirm.d.ts.map +1 -0
- package/dist/src/confirm.js +69 -0
- package/dist/src/confirm.js.map +1 -0
- package/dist/src/scan.d.ts +27 -0
- package/dist/src/scan.d.ts.map +1 -0
- package/dist/src/scan.js +79 -0
- package/dist/src/scan.js.map +1 -0
- package/dist/src/schema.d.ts +224 -0
- package/dist/src/schema.d.ts.map +1 -0
- package/dist/src/schema.js +52 -0
- package/dist/src/schema.js.map +1 -0
- 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 @@
|
|
|
1
|
+
{"version":3,"file":"filex.d.ts","sourceRoot":"","sources":["../../bin/filex.ts"],"names":[],"mappings":";AACA,OAAO,eAAe,CAAC"}
|
|
@@ -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":"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"}
|
|
@@ -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"}
|