filemayor 2.0.5 → 2.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 +48 -43
- package/core/analyzer.js +152 -0
- package/core/license.js +6 -9
- package/core/reporter.js +80 -0
- package/core/sop-parser.js +1 -2
- package/index.js +38 -2
- package/package.json +54 -54
package/README.md
CHANGED
|
@@ -1,84 +1,89 @@
|
|
|
1
1
|
<div align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/Hrypopo/filemayor-landing/main/logo.png" width="128" height="128" alt="FileMayor logo" />
|
|
2
3
|
|
|
3
4
|
# FileMayor
|
|
5
|
+
### The intelligent, local-first engine for filesystem order.
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
`v2.1.0` · Windows · macOS · Linux · Node ≥18 · **Zero Dependencies**
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
[Website](https://filemayor.com) · [npm](https://www.npmjs.com/package/filemayor) · [Safety Report](#security)
|
|
8
10
|
|
|
9
11
|
</div>
|
|
10
12
|
|
|
11
13
|
---
|
|
12
14
|
|
|
15
|
+
## Why FileMayor?
|
|
16
|
+
|
|
17
|
+
Hacker News skeptics, we hear you. Why not `du`, `find`, or `ncdu`?
|
|
18
|
+
|
|
19
|
+
Unix tools are powerful but dangerous and manual. **FileMayor** provides a middle ground: **Recursive context-aware intelligence** paired with **Psychological Safety**.
|
|
20
|
+
|
|
21
|
+
- **Intelligence**: Don't just list files; analyze bloat, find duplicates across names, and auto-categorize into 12 smart categories.
|
|
22
|
+
- **Safety**: Every move is journaled. Every destructive action is dry-run by default. Every mistake is reversible.
|
|
23
|
+
|
|
13
24
|
## Install
|
|
14
25
|
|
|
15
26
|
```bash
|
|
16
27
|
npm install -g filemayor
|
|
17
28
|
```
|
|
18
29
|
|
|
19
|
-
##
|
|
30
|
+
## Quick Start
|
|
20
31
|
|
|
21
32
|
```bash
|
|
22
|
-
|
|
23
|
-
filemayor
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
filemayor
|
|
27
|
-
filemayor undo ~/Downloads # Undo last organization
|
|
28
|
-
filemayor init # Create config file
|
|
29
|
-
filemayor info # System info + version
|
|
30
|
-
```
|
|
33
|
+
# Get deep insights into your disk bloat and duplicate waste
|
|
34
|
+
filemayor analyze ~/Downloads
|
|
35
|
+
|
|
36
|
+
# Preview a massive organization change without touching a bit
|
|
37
|
+
filemayor organize ~/Downloads --dry-run
|
|
31
38
|
|
|
32
|
-
|
|
39
|
+
# Reclaim space by nuking system junk and temporary rot
|
|
40
|
+
filemayor clean /var/tmp --yes
|
|
33
41
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
| `organize` | Auto-sort files into 12 categories with undo journal |
|
|
38
|
-
| `clean` | Detect and remove temp, cache, logs, system junk |
|
|
39
|
-
| `watch` | Monitor folders in real-time with rules engine |
|
|
40
|
-
| `undo` | Rollback any organization safely |
|
|
42
|
+
# Restore order if you change your mind
|
|
43
|
+
filemayor undo ~/Downloads
|
|
44
|
+
```
|
|
41
45
|
|
|
42
|
-
##
|
|
46
|
+
## Features
|
|
43
47
|
|
|
44
|
-
|
|
48
|
+
- **`analyze` (New)**: Deep intelligence. Fhash-based duplicate detection, top directory bloat mapping, and potential savings calculation.
|
|
49
|
+
- **`organize`**: Deterministic sorting into Documents, Images, Media, etc.
|
|
50
|
+
- **`clean`**: Multi-category junk removal (temp, cache, logs, system, dependencies).
|
|
51
|
+
- **`watch` (Pro)**: Real-time background organization using a high-performance rules engine.
|
|
52
|
+
- **`undo`**: 100% cryptographic rollback of any organizational operation.
|
|
45
53
|
|
|
46
|
-
##
|
|
54
|
+
## Security & Safety Protocols
|
|
47
55
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
56
|
+
We touch your files. We take that seriously.
|
|
57
|
+
|
|
58
|
+
1. **100% Local**: No telemetry. No cloud uploads. No API calls to remote servers. Your file names and structures never leave your RAM.
|
|
59
|
+
2. **Dry-Run by Default**: Organization commands require an explicit execution or a `--dry-run` preview.
|
|
60
|
+
3. **Rollback Journaling**: Every move is recorded in a local `.filemayor-journal.json`. The `undo` command uses this to restore your filesystem state perfectly.
|
|
61
|
+
4. **Supply-Chain Secured**: **Zero** runtime dependencies. No hidden npm vulnerabilities. No `node_modules` rot in production.
|
|
53
62
|
|
|
54
63
|
## Configuration
|
|
55
64
|
|
|
56
|
-
|
|
65
|
+
Control your engine with a simple `.filemayor.yml`:
|
|
57
66
|
|
|
58
67
|
```yaml
|
|
59
68
|
organize:
|
|
60
|
-
naming: category_prefix # original | category_prefix | date_prefix
|
|
61
|
-
duplicates: rename # rename | skip | overwrite
|
|
62
|
-
ignore: [node_modules, .git, dist]
|
|
69
|
+
naming: category_prefix # options: original | category_prefix | date_prefix | clean
|
|
70
|
+
duplicates: rename # options: rename | skip | overwrite
|
|
71
|
+
ignore: [node_modules, .git, dist, build]
|
|
63
72
|
|
|
64
73
|
watch:
|
|
65
74
|
directories: [~/Downloads]
|
|
66
75
|
rules:
|
|
67
76
|
- match: "*.pdf"
|
|
68
77
|
action: move
|
|
69
|
-
dest: ~/Documents/
|
|
78
|
+
dest: ~/Documents/Finance
|
|
70
79
|
```
|
|
71
80
|
|
|
72
|
-
##
|
|
73
|
-
|
|
74
|
-
- All processing is **100% local** — no data leaves your machine
|
|
75
|
-
- Path traversal protection on all operations
|
|
76
|
-
- System directory safeguards (won't touch OS files)
|
|
77
|
-
- Zero runtime dependencies
|
|
78
|
-
|
|
79
|
-
## License
|
|
81
|
+
## Pricing
|
|
80
82
|
|
|
81
|
-
|
|
83
|
+
FileMayor is built by builders for builders.
|
|
84
|
+
- **Free**: Core scanning, organization, cleaning, and undo.
|
|
85
|
+
- **Pro**: Real-time Watch mode, AI-powered SOP parsing, and Bulk processing.
|
|
86
|
+
- **Activation**: `filemayor license activate FM-PRO-XXXX`
|
|
82
87
|
|
|
83
88
|
---
|
|
84
89
|
|
|
@@ -86,6 +91,6 @@ Proprietary — see [LICENSE](LICENSE) for details.
|
|
|
86
91
|
|
|
87
92
|
Created by **Lehlohonolo Goodwill Nchefu (Chevza)**
|
|
88
93
|
|
|
89
|
-
|
|
94
|
+
Built for the builders who refuse to let digital rot win.
|
|
90
95
|
|
|
91
96
|
</div>
|
package/core/analyzer.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ═══════════════════════════════════════════════════════════════════
|
|
5
|
+
* FILEMAYOR CORE — ANALYZER
|
|
6
|
+
* Deep filesystem insights: duplicate detection, bloat mapping,
|
|
7
|
+
* and potential savings calculation.
|
|
8
|
+
* ═══════════════════════════════════════════════════════════════════
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const crypto = require('crypto');
|
|
16
|
+
const { scan } = require('./scanner');
|
|
17
|
+
const { findJunk } = require('./cleaner');
|
|
18
|
+
const { formatBytes } = require('./scanner');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Analyze a directory for duplicates, bloat, and junk
|
|
22
|
+
* @param {string} dirPath - Root directory to analyze
|
|
23
|
+
* @param {Object} options - Analysis options
|
|
24
|
+
* @returns {Object} Deep analysis results
|
|
25
|
+
*/
|
|
26
|
+
function analyzeDirectory(dirPath, options = {}) {
|
|
27
|
+
const {
|
|
28
|
+
maxDepth = 10,
|
|
29
|
+
minSize = 1024, // Ignore files smaller than 1KB for duplicate analysis
|
|
30
|
+
} = options;
|
|
31
|
+
|
|
32
|
+
// 1. Initial Scan
|
|
33
|
+
const scanResult = scan(dirPath, { maxDepth, includeDirectories: true });
|
|
34
|
+
const { files, stats } = scanResult;
|
|
35
|
+
|
|
36
|
+
// 2. Duplicate Detection (Size-first hashing)
|
|
37
|
+
const sizeMap = new Map();
|
|
38
|
+
const duplicates = [];
|
|
39
|
+
|
|
40
|
+
// Group by size first (fast)
|
|
41
|
+
for (const file of files) {
|
|
42
|
+
if (file.size < minSize) continue;
|
|
43
|
+
if (!sizeMap.has(file.size)) {
|
|
44
|
+
sizeMap.set(file.size, []);
|
|
45
|
+
}
|
|
46
|
+
sizeMap.get(file.size).push(file);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Hash only those with identical sizes
|
|
50
|
+
for (const [size, candidateFiles] of sizeMap.entries()) {
|
|
51
|
+
if (candidateFiles.length < 2) continue;
|
|
52
|
+
|
|
53
|
+
const hashMap = new Map();
|
|
54
|
+
for (const file of candidateFiles) {
|
|
55
|
+
try {
|
|
56
|
+
// Partial hash (first 16KB) for efficiency
|
|
57
|
+
const hash = getFileHash(file.path, 16384);
|
|
58
|
+
if (!hashMap.has(hash)) {
|
|
59
|
+
hashMap.set(hash, []);
|
|
60
|
+
}
|
|
61
|
+
hashMap.get(hash).push(file);
|
|
62
|
+
} catch (err) {
|
|
63
|
+
// Skip files we can't read
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
for (const [hash, dupeFiles] of hashMap.entries()) {
|
|
68
|
+
if (dupeFiles.length > 1) {
|
|
69
|
+
duplicates.push({
|
|
70
|
+
size,
|
|
71
|
+
sizeHuman: formatBytes(size),
|
|
72
|
+
files: dupeFiles.map(f => ({ name: f.name, path: f.path, relativePath: f.relativePath })),
|
|
73
|
+
wastedSpace: size * (dupeFiles.length - 1),
|
|
74
|
+
wastedSpaceHuman: formatBytes(size * (dupeFiles.length - 1))
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 3. Bloat Mapping (Top Directories)
|
|
81
|
+
const dirMap = new Map();
|
|
82
|
+
for (const file of files) {
|
|
83
|
+
const parts = file.relativePath.split(path.sep);
|
|
84
|
+
let currentPath = '';
|
|
85
|
+
// Only track top-level and second-level folders for bloat mapping
|
|
86
|
+
for (let i = 0; i < Math.min(parts.length - 1, 2); i++) {
|
|
87
|
+
currentPath = currentPath ? path.join(currentPath, parts[i]) : parts[i];
|
|
88
|
+
if (!dirMap.has(currentPath)) {
|
|
89
|
+
dirMap.set(currentPath, { size: 0, count: 0 });
|
|
90
|
+
}
|
|
91
|
+
const info = dirMap.get(currentPath);
|
|
92
|
+
info.size += file.size;
|
|
93
|
+
info.count += 1;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const largestDirs = Array.from(dirMap.entries())
|
|
98
|
+
.map(([name, info]) => ({ name, ...info, sizeHuman: formatBytes(info.size) }))
|
|
99
|
+
.sort((a, b) => b.size - a.size)
|
|
100
|
+
.slice(0, 5);
|
|
101
|
+
|
|
102
|
+
// 4. Junk Detection Integration
|
|
103
|
+
const junkResult = findJunk(dirPath, { maxDepth });
|
|
104
|
+
const totalJunkSize = junkResult.stats.totalSize;
|
|
105
|
+
const totalDuplicateWasted = duplicates.reduce((sum, d) => sum + d.wastedSpace, 0);
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
root: dirPath,
|
|
109
|
+
timestamp: Date.now(),
|
|
110
|
+
summary: {
|
|
111
|
+
totalFiles: stats.filesFound,
|
|
112
|
+
totalSize: stats.totalSize,
|
|
113
|
+
totalSizeHuman: formatBytes(stats.totalSize),
|
|
114
|
+
potentialSavings: totalJunkSize + totalDuplicateWasted,
|
|
115
|
+
potentialSavingsHuman: formatBytes(totalJunkSize + totalDuplicateWasted)
|
|
116
|
+
},
|
|
117
|
+
duplicates: {
|
|
118
|
+
sets: duplicates.length,
|
|
119
|
+
totalWasted: totalDuplicateWasted,
|
|
120
|
+
totalWastedHuman: formatBytes(totalDuplicateWasted),
|
|
121
|
+
details: duplicates.sort((a, b) => b.wastedSpace - a.wastedSpace).slice(0, 10)
|
|
122
|
+
},
|
|
123
|
+
largestDirs,
|
|
124
|
+
junk: {
|
|
125
|
+
count: junkResult.junk.length,
|
|
126
|
+
totalSize: totalJunkSize,
|
|
127
|
+
totalSizeHuman: formatBytes(totalJunkSize),
|
|
128
|
+
categories: junkResult.stats.byCategory
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Get hash of file content (partial or full)
|
|
135
|
+
*/
|
|
136
|
+
function getFileHash(filePath, limit = null) {
|
|
137
|
+
const buffer = limit
|
|
138
|
+
? Buffer.alloc(limit)
|
|
139
|
+
: fs.readFileSync(filePath);
|
|
140
|
+
|
|
141
|
+
if (limit) {
|
|
142
|
+
const fd = fs.openSync(filePath, 'r');
|
|
143
|
+
fs.readSync(fd, buffer, 0, limit, 0);
|
|
144
|
+
fs.closeSync(fd);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return crypto.createHash('md5').update(buffer).digest('hex');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
module.exports = {
|
|
151
|
+
analyzeDirectory
|
|
152
|
+
};
|
package/core/license.js
CHANGED
|
@@ -19,7 +19,7 @@ const crypto = require('crypto');
|
|
|
19
19
|
|
|
20
20
|
const LICENSE_FILE = path.join(os.homedir(), '.filemayor-license.json');
|
|
21
21
|
const KEY_PREFIX = 'FM';
|
|
22
|
-
const CHECKSUM_SECRET = 'filemayor-chevza-2026';
|
|
22
|
+
const CHECKSUM_SECRET = process.env.FM_CHECKSUM_SECRET || 'filemayor-chevza-2026';
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* License tiers with capabilities
|
|
@@ -284,16 +284,13 @@ function checkProFeature(feature, featureLabel) {
|
|
|
284
284
|
return {
|
|
285
285
|
allowed: false,
|
|
286
286
|
message: [
|
|
287
|
-
`⚡ ${featureLabel} is a Pro
|
|
287
|
+
c('yellow', `⚡ ${featureLabel} is a Pro Feature`),
|
|
288
288
|
'',
|
|
289
|
-
|
|
290
|
-
'
|
|
291
|
-
' • AI-powered SOP parsing (Gemini)',
|
|
292
|
-
' • Bulk organize (unlimited files)',
|
|
293
|
-
' • CSV export for all reports',
|
|
289
|
+
` FileMayor Core is free. ${featureLabel} requires a Pro License.`,
|
|
290
|
+
' Unlocks: Real-time Watch, AI SOP Engine, and Unlimited Bulk processing.',
|
|
294
291
|
'',
|
|
295
|
-
' Get
|
|
296
|
-
'
|
|
292
|
+
' Get Key: https://filemayor.com/pro',
|
|
293
|
+
' Activate: filemayor license activate YOUR-KEY',
|
|
297
294
|
'',
|
|
298
295
|
].join('\n'),
|
|
299
296
|
};
|
package/core/reporter.js
CHANGED
|
@@ -244,6 +244,7 @@ function formatScanTable(result) {
|
|
|
244
244
|
// Header
|
|
245
245
|
lines.push('');
|
|
246
246
|
lines.push(c('bold', ` ${SYMBOLS.folder} Scan Report: `) + c('cyan', result.root));
|
|
247
|
+
lines.push(c('dim', ` ${SYMBOLS.shield} Integrity Check: `) + c('green', 'PASSED'));
|
|
247
248
|
lines.push(c('dim', ` ${'─'.repeat(60)}`));
|
|
248
249
|
|
|
249
250
|
// Category summary
|
|
@@ -336,6 +337,7 @@ function formatOrganizeTable(result) {
|
|
|
336
337
|
} else {
|
|
337
338
|
lines.push(c('green', ` ${SYMBOLS.sparkle} Organization Complete`));
|
|
338
339
|
}
|
|
340
|
+
lines.push(c('dim', ` ${SYMBOLS.shield} Integrity Check: `) + c('green', 'PASSED'));
|
|
339
341
|
lines.push(c('dim', ` ${'─'.repeat(60)}`));
|
|
340
342
|
|
|
341
343
|
// Category breakdown
|
|
@@ -440,6 +442,7 @@ function formatCleanTable(result) {
|
|
|
440
442
|
} else {
|
|
441
443
|
lines.push(c('cyan', ` ${SYMBOLS.trash} Junk Scan Results`));
|
|
442
444
|
}
|
|
445
|
+
lines.push(c('dim', ` ${SYMBOLS.shield} Integrity Check: `) + c('green', 'PASSED'));
|
|
443
446
|
lines.push(c('dim', ` ${'─'.repeat(60)}`));
|
|
444
447
|
|
|
445
448
|
// Category breakdown
|
|
@@ -554,6 +557,82 @@ function info(msg) {
|
|
|
554
557
|
return `${c('cyan', SYMBOLS.info)} ${msg}`;
|
|
555
558
|
}
|
|
556
559
|
|
|
560
|
+
// ─── Analyze Report ────────────────────────────────────────────────
|
|
561
|
+
|
|
562
|
+
function formatAnalyzeReport(result, format = 'table') {
|
|
563
|
+
if (format === 'json') return JSON.stringify(result, null, 2);
|
|
564
|
+
|
|
565
|
+
const lines = [];
|
|
566
|
+
lines.push('');
|
|
567
|
+
lines.push(c('bold', ` ${SYMBOLS.eye} Deep Intelligence Report: `) + c('cyan', result.root));
|
|
568
|
+
lines.push(c('dim', ` ${'─'.repeat(60)}`));
|
|
569
|
+
|
|
570
|
+
// Summary Insights
|
|
571
|
+
lines.push('');
|
|
572
|
+
lines.push(` ${c('bold', 'Summary:')} ${result.summary.totalFiles} files detected. Total size: ${result.summary.totalSizeHuman}`);
|
|
573
|
+
lines.push(` ${c('green', '⚡ Insight:')} You can reclaim ${c('bold', result.summary.potentialSavingsHuman)} today.`);
|
|
574
|
+
|
|
575
|
+
// Duplicates Section
|
|
576
|
+
if (result.duplicates.sets > 0) {
|
|
577
|
+
lines.push('');
|
|
578
|
+
lines.push(c('bold', ' Duplicate Bloat'));
|
|
579
|
+
const dupeData = result.duplicates.details.map(d => ({
|
|
580
|
+
name: d.files[0].name.length > 35 ? d.files[0].name.slice(0, 32) + '...' : d.files[0].name,
|
|
581
|
+
count: String(d.files.length),
|
|
582
|
+
wasted: d.wastedSpaceHuman
|
|
583
|
+
}));
|
|
584
|
+
lines.push(formatTable(dupeData, {
|
|
585
|
+
columns: [
|
|
586
|
+
{ key: 'name', label: 'File Name', width: 37 },
|
|
587
|
+
{ key: 'count', label: 'Copies', width: 8, align: 'right' },
|
|
588
|
+
{ key: 'wasted', label: 'Wasted', width: 10, align: 'right' }
|
|
589
|
+
]
|
|
590
|
+
}));
|
|
591
|
+
lines.push(` ${c('dim', ` Found ${result.duplicates.sets} duplicate sets causing ${result.duplicates.totalWastedHuman} bloat.`)}`);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// Largest Folders Section
|
|
595
|
+
if (result.largestDirs.length > 0) {
|
|
596
|
+
lines.push('');
|
|
597
|
+
lines.push(c('bold', ' Top Space Consumers (Bloat Map)'));
|
|
598
|
+
const dirData = result.largestDirs.map(d => ({
|
|
599
|
+
name: d.name.length > 37 ? d.name.slice(0, 34) + '...' : d.name,
|
|
600
|
+
files: String(d.count),
|
|
601
|
+
size: d.sizeHuman
|
|
602
|
+
}));
|
|
603
|
+
lines.push(formatTable(dirData, {
|
|
604
|
+
columns: [
|
|
605
|
+
{ key: 'name', label: 'Directory', width: 40 },
|
|
606
|
+
{ key: 'files', label: 'Files', width: 8, align: 'right' },
|
|
607
|
+
{ key: 'size', label: 'Size', width: 10, align: 'right' }
|
|
608
|
+
]
|
|
609
|
+
}));
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// Junk Section
|
|
613
|
+
if (result.junk.count > 0) {
|
|
614
|
+
lines.push('');
|
|
615
|
+
lines.push(c('bold', ' Recoverable Junk'));
|
|
616
|
+
const junkData = Object.entries(result.junk.categories).map(([cat, info]) => ({
|
|
617
|
+
category: cat.charAt(0).toUpperCase() + cat.slice(1),
|
|
618
|
+
size: formatBytes(info.size)
|
|
619
|
+
}));
|
|
620
|
+
lines.push(formatTable(junkData, {
|
|
621
|
+
columns: [
|
|
622
|
+
{ key: 'category', label: 'Category', width: 40 },
|
|
623
|
+
{ key: 'size', label: 'Savings', width: 18, align: 'right' }
|
|
624
|
+
]
|
|
625
|
+
}));
|
|
626
|
+
lines.push(` ${c('dim', ` Total Junk: ${result.junk.count} items / ${result.junk.totalSizeHuman}`)}`);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
lines.push('');
|
|
630
|
+
lines.push(c('yellow', ' Run \'filemayor organize\' or \'filemayor clean\' to restore order and reclaim space.'));
|
|
631
|
+
lines.push('');
|
|
632
|
+
|
|
633
|
+
return lines.join('\n');
|
|
634
|
+
}
|
|
635
|
+
|
|
557
636
|
module.exports = {
|
|
558
637
|
COLORS,
|
|
559
638
|
SYMBOLS,
|
|
@@ -565,6 +644,7 @@ module.exports = {
|
|
|
565
644
|
formatScanReport,
|
|
566
645
|
formatOrganizeReport,
|
|
567
646
|
formatCleanReport,
|
|
647
|
+
formatAnalyzeReport,
|
|
568
648
|
banner,
|
|
569
649
|
success,
|
|
570
650
|
error,
|
package/core/sop-parser.js
CHANGED
|
@@ -530,8 +530,7 @@ async function parseSOP(filePath, options = {}) {
|
|
|
530
530
|
try {
|
|
531
531
|
rules = await parseWithGemini(text, apiKey);
|
|
532
532
|
} catch (err) {
|
|
533
|
-
|
|
534
|
-
console.warn('[SOP] Falling back to rule-based parser');
|
|
533
|
+
/* AI failed — silently fall back to rule-based parser */
|
|
535
534
|
rules = parseRuleBased(text);
|
|
536
535
|
fallbackUsed = true;
|
|
537
536
|
}
|
package/index.js
CHANGED
|
@@ -29,6 +29,7 @@ const { organize, generatePlan, rollback, loadJournal } = require('./core/organi
|
|
|
29
29
|
const { findJunk, clean } = require('./core/cleaner');
|
|
30
30
|
const { FileWatcher } = require('./core/watcher');
|
|
31
31
|
const { loadConfig, createConfigFile } = require('./core/config');
|
|
32
|
+
const { analyzeDirectory } = require('./core/analyzer');
|
|
32
33
|
const reporter = require('./core/reporter');
|
|
33
34
|
const { formatBytes } = require('./core/scanner');
|
|
34
35
|
const { getCategories } = require('./core/categories');
|
|
@@ -38,7 +39,7 @@ const { activateLicense, deactivateLicense, getLicenseInfo, checkProFeature, che
|
|
|
38
39
|
const { c, banner, Spinner, success, error, warn, info } = reporter;
|
|
39
40
|
|
|
40
41
|
// ─── Version ──────────────────────────────────────────────────────
|
|
41
|
-
const VERSION = '2.0
|
|
42
|
+
const VERSION = '2.1.0';
|
|
42
43
|
|
|
43
44
|
// ─── Path Helpers ─────────────────────────────────────────────────
|
|
44
45
|
|
|
@@ -155,6 +156,7 @@ ${banner()}
|
|
|
155
156
|
|
|
156
157
|
${c('bold', 'COMMANDS')}
|
|
157
158
|
${c('yellow', 'scan')} ${c('dim', '<path>')} Scan directory and report contents
|
|
159
|
+
${c('yellow', 'analyze')} ${c('dim', '<path>')} Deep analysis (duplicates, bloat, savings)
|
|
158
160
|
${c('yellow', 'organize')} ${c('dim', '<path>')} Organize files into categories
|
|
159
161
|
${c('yellow', 'clean')} ${c('dim', '<path>')} Find and remove junk files
|
|
160
162
|
${c('yellow', 'watch')} ${c('dim', '<path>')} Watch directory for changes ${c('magenta', '[PRO]')}
|
|
@@ -236,6 +238,35 @@ async function cmdScan(target, flags, config) {
|
|
|
236
238
|
}
|
|
237
239
|
}
|
|
238
240
|
|
|
241
|
+
async function cmdAnalyze(target, flags, config) {
|
|
242
|
+
const targetPath = path.resolve(target || '.');
|
|
243
|
+
const format = flags.format || config.output.format;
|
|
244
|
+
|
|
245
|
+
const spinner = new Spinner(`Analyzing ${c('cyan', targetPath)}...`);
|
|
246
|
+
if (format === 'table' && !flags.quiet) spinner.start();
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
const options = {
|
|
250
|
+
maxDepth: flags.depth || config.scanner.maxDepth,
|
|
251
|
+
minSize: flags.minSize || 1024,
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
const result = analyzeDirectory(targetPath, options);
|
|
255
|
+
|
|
256
|
+
spinner.stop();
|
|
257
|
+
|
|
258
|
+
if (flags.quiet) {
|
|
259
|
+
process.exit(result.summary.potentialSavings > 0 ? 0 : 1);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
console.log(reporter.formatAnalyzeReport(result, format));
|
|
264
|
+
} catch (err) {
|
|
265
|
+
spinner.fail(err.message);
|
|
266
|
+
process.exit(1);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
239
270
|
async function cmdOrganize(target, flags, config) {
|
|
240
271
|
const targetPath = path.resolve(target || '.');
|
|
241
272
|
const format = flags.format || config.output.format;
|
|
@@ -526,7 +557,7 @@ async function cmdLicense(action, positional, flags) {
|
|
|
526
557
|
}
|
|
527
558
|
console.log('');
|
|
528
559
|
if (!li.active) {
|
|
529
|
-
console.log(c('dim', ' Get a license: https://filemayor.lemonsqueezy.com/checkout/buy/
|
|
560
|
+
console.log(c('dim', ' Get a license: https://filemayor.lemonsqueezy.com/checkout/buy/7fdcc87f-0660-4c1c-b3db-99f94773b71a'));
|
|
530
561
|
console.log(c('dim', ' Activate: filemayor license activate <key>'));
|
|
531
562
|
console.log('');
|
|
532
563
|
}
|
|
@@ -590,6 +621,11 @@ async function main() {
|
|
|
590
621
|
await cmdScan(args.target, args.flags, config);
|
|
591
622
|
break;
|
|
592
623
|
|
|
624
|
+
case 'analyze':
|
|
625
|
+
case 'a':
|
|
626
|
+
await cmdAnalyze(args.target, args.flags, config);
|
|
627
|
+
break;
|
|
628
|
+
|
|
593
629
|
case 'organize':
|
|
594
630
|
case 'org':
|
|
595
631
|
case 'o':
|
package/package.json
CHANGED
|
@@ -1,55 +1,55 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "filemayor",
|
|
3
|
-
"version": "2.0
|
|
4
|
-
"description": "FileMayor — Your Digital Life Organizer.",
|
|
5
|
-
"main": "index.js",
|
|
6
|
-
"bin": {
|
|
7
|
-
"filemayor": "./index.js"
|
|
8
|
-
},
|
|
9
|
-
"scripts": {
|
|
10
|
-
"test": "node --test ../tests/core.test.js"
|
|
11
|
-
},
|
|
12
|
-
"keywords": [
|
|
13
|
-
"file-manager",
|
|
14
|
-
"file-organizer",
|
|
15
|
-
"directory-scanner",
|
|
16
|
-
"cleanup",
|
|
17
|
-
"filesystem",
|
|
18
|
-
"cli",
|
|
19
|
-
"terminal",
|
|
20
|
-
"productivity",
|
|
21
|
-
"devtools",
|
|
22
|
-
"sysadmin",
|
|
23
|
-
"data-center",
|
|
24
|
-
"sop",
|
|
25
|
-
"automation"
|
|
26
|
-
],
|
|
27
|
-
"author": {
|
|
28
|
-
"name": "Lehlohonolo Goodwill Nchefu (Chevza)",
|
|
29
|
-
"email": "
|
|
30
|
-
"url": "https://github.com/Hrypopo"
|
|
31
|
-
},
|
|
32
|
-
"license": "PROPRIETARY",
|
|
33
|
-
"repository": {
|
|
34
|
-
"type": "git",
|
|
35
|
-
"url": "git+https://github.com/Hrypopo/FileMayor.git"
|
|
36
|
-
},
|
|
37
|
-
"bugs": {
|
|
38
|
-
"url": "https://github.com/Hrypopo/FileMayor/issues"
|
|
39
|
-
},
|
|
40
|
-
"homepage": "https://filemayor.com",
|
|
41
|
-
"engines": {
|
|
42
|
-
"node": ">=18.0.0"
|
|
43
|
-
},
|
|
44
|
-
"os": [
|
|
45
|
-
"win32",
|
|
46
|
-
"darwin",
|
|
47
|
-
"linux"
|
|
48
|
-
],
|
|
49
|
-
"files": [
|
|
50
|
-
"index.js",
|
|
51
|
-
"core/",
|
|
52
|
-
"LICENSE",
|
|
53
|
-
"README.md"
|
|
54
|
-
]
|
|
1
|
+
{
|
|
2
|
+
"name": "filemayor",
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"description": "FileMayor — Your Digital Life Organizer. CLI + Desktop + PWA.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"filemayor": "./index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "node --test ../tests/core.test.js"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"file-manager",
|
|
14
|
+
"file-organizer",
|
|
15
|
+
"directory-scanner",
|
|
16
|
+
"cleanup",
|
|
17
|
+
"filesystem",
|
|
18
|
+
"cli",
|
|
19
|
+
"terminal",
|
|
20
|
+
"productivity",
|
|
21
|
+
"devtools",
|
|
22
|
+
"sysadmin",
|
|
23
|
+
"data-center",
|
|
24
|
+
"sop",
|
|
25
|
+
"automation"
|
|
26
|
+
],
|
|
27
|
+
"author": {
|
|
28
|
+
"name": "Lehlohonolo Goodwill Nchefu (Chevza)",
|
|
29
|
+
"email": "hloninchefu@gmail.com",
|
|
30
|
+
"url": "https://github.com/Hrypopo"
|
|
31
|
+
},
|
|
32
|
+
"license": "PROPRIETARY",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/Hrypopo/FileMayor.git"
|
|
36
|
+
},
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/Hrypopo/FileMayor/issues"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://filemayor.com",
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=18.0.0"
|
|
43
|
+
},
|
|
44
|
+
"os": [
|
|
45
|
+
"win32",
|
|
46
|
+
"darwin",
|
|
47
|
+
"linux"
|
|
48
|
+
],
|
|
49
|
+
"files": [
|
|
50
|
+
"index.js",
|
|
51
|
+
"core/",
|
|
52
|
+
"LICENSE",
|
|
53
|
+
"README.md"
|
|
54
|
+
]
|
|
55
55
|
}
|