filemayor 2.0.1 → 2.0.4
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/LICENSE +3 -3
- package/README.md +47 -144
- package/core/license.js +340 -0
- package/index.js +116 -4
- package/package.json +3 -2
package/LICENSE
CHANGED
|
@@ -52,7 +52,7 @@ operating as FileMayor, and its contributors.
|
|
|
52
52
|
- Server / data center deployment
|
|
53
53
|
- SOP AI Engine features
|
|
54
54
|
|
|
55
|
-
requires a valid Paid Tier license. Contact licensing@filemayor.
|
|
55
|
+
requires a valid Paid Tier license. Contact licensing@filemayor.com
|
|
56
56
|
for commercial licensing inquiries.
|
|
57
57
|
|
|
58
58
|
5. AI FEATURES
|
|
@@ -86,5 +86,5 @@ operating as FileMayor, and its contributors.
|
|
|
86
86
|
|
|
87
87
|
This License shall be governed by the laws of the Republic of South Africa.
|
|
88
88
|
|
|
89
|
-
For licensing inquiries: licensing@filemayor.
|
|
90
|
-
For support: support@filemayor.
|
|
89
|
+
For licensing inquiries: licensing@filemayor.com
|
|
90
|
+
For support: support@filemayor.com
|
package/README.md
CHANGED
|
@@ -1,188 +1,91 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
#
|
|
3
|
+
# FileMayor
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**Organize, scan, clean, and watch your filesystem — from any terminal.**
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
`v2.0.1` · Windows · macOS · Linux · Node ≥18
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
[](LICENSE)
|
|
11
|
-
[]()
|
|
12
|
-
[]()
|
|
9
|
+
</div>
|
|
13
10
|
|
|
14
11
|
---
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
</div>
|
|
19
|
-
|
|
20
|
-
## 🚀 Install
|
|
13
|
+
## Install
|
|
21
14
|
|
|
22
15
|
```bash
|
|
23
|
-
# npm (recommended)
|
|
24
16
|
npm install -g filemayor
|
|
25
|
-
|
|
26
|
-
# Or one-liner
|
|
27
|
-
curl -fsSL https://raw.githubusercontent.com/filemayor/filemayor/main/install.sh | bash
|
|
28
17
|
```
|
|
29
18
|
|
|
30
|
-
##
|
|
31
|
-
|
|
32
|
-
| Feature | Description |
|
|
33
|
-
|---------|-------------|
|
|
34
|
-
| **Scan** | Recursively scan directories with size, category, and date filtering |
|
|
35
|
-
| **Organize** | Auto-categorize files into 12 categories with configurable naming |
|
|
36
|
-
| **Clean** | Detect and remove junk files (temp, cache, logs, system junk) |
|
|
37
|
-
| **Watch** | Real-time file watcher with rules engine and auto-organize |
|
|
38
|
-
| **Undo** | Full rollback journal — undo any organization safely |
|
|
39
|
-
| **Config** | `.filemayor.yml` config with hierarchical merge and env vars |
|
|
40
|
-
|
|
41
|
-
## 🖥 CLI Usage
|
|
19
|
+
## Commands
|
|
42
20
|
|
|
43
21
|
```bash
|
|
44
|
-
#
|
|
45
|
-
filemayor
|
|
46
|
-
|
|
47
|
-
#
|
|
48
|
-
filemayor
|
|
22
|
+
filemayor scan ~/Downloads # See what's in a folder
|
|
23
|
+
filemayor organize ~/Downloads # Sort files into categories
|
|
24
|
+
filemayor organize ~/Downloads --dry-run # Preview without moving
|
|
25
|
+
filemayor clean /var/tmp --yes # Delete junk files
|
|
26
|
+
filemayor watch ~/Downloads # Auto-organize in real-time
|
|
27
|
+
filemayor undo ~/Downloads # Undo last organization
|
|
28
|
+
filemayor init # Create config file
|
|
29
|
+
filemayor info # System info + version
|
|
30
|
+
```
|
|
49
31
|
|
|
50
|
-
|
|
51
|
-
filemayor organize ~/Downloads --naming category_prefix
|
|
32
|
+
## What It Does
|
|
52
33
|
|
|
53
|
-
|
|
54
|
-
|
|
34
|
+
| Command | Description |
|
|
35
|
+
|---------|-------------|
|
|
36
|
+
| `scan` | Recursively scan directories — size, type, date filtering |
|
|
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 |
|
|
55
41
|
|
|
56
|
-
|
|
57
|
-
filemayor watch ~/Downloads
|
|
42
|
+
## 12 File Categories · 180+ Extensions
|
|
58
43
|
|
|
59
|
-
|
|
60
|
-
filemayor init
|
|
44
|
+
Documents · Images · Audio · Video · Archives · Code · Config · Fonts · Data · Executables · Design · Books
|
|
61
45
|
|
|
62
|
-
|
|
63
|
-
filemayor undo ~/Downloads
|
|
46
|
+
## Output Formats
|
|
64
47
|
|
|
65
|
-
|
|
66
|
-
filemayor scan
|
|
67
|
-
filemayor scan . --csv >
|
|
48
|
+
```bash
|
|
49
|
+
filemayor scan . --json # Machine-readable JSON
|
|
50
|
+
filemayor scan . --csv > out.csv # Spreadsheet export
|
|
51
|
+
filemayor scan . --minimal # Paths only
|
|
68
52
|
```
|
|
69
53
|
|
|
70
|
-
##
|
|
54
|
+
## Configuration
|
|
71
55
|
|
|
72
|
-
Create
|
|
56
|
+
Create `.filemayor.yml` in any directory:
|
|
73
57
|
|
|
74
58
|
```yaml
|
|
75
|
-
version: 1
|
|
76
|
-
|
|
77
59
|
organize:
|
|
78
|
-
naming: category_prefix
|
|
79
|
-
duplicates: rename
|
|
80
|
-
|
|
81
|
-
ignore: [node_modules, .git, __pycache__, dist, build]
|
|
82
|
-
|
|
83
|
-
clean:
|
|
84
|
-
categories: [temp, cache, system, logs]
|
|
85
|
-
maxDepth: 10
|
|
60
|
+
naming: category_prefix # original | category_prefix | date_prefix
|
|
61
|
+
duplicates: rename # rename | skip | overwrite
|
|
62
|
+
ignore: [node_modules, .git, dist]
|
|
86
63
|
|
|
87
64
|
watch:
|
|
88
65
|
directories: [~/Downloads]
|
|
89
|
-
autoOrganize: false
|
|
90
66
|
rules:
|
|
91
67
|
- match: "*.pdf"
|
|
92
68
|
action: move
|
|
93
69
|
dest: ~/Documents/PDFs
|
|
94
|
-
- match: "*.{jpg,png,gif}"
|
|
95
|
-
action: move
|
|
96
|
-
dest: ~/Pictures
|
|
97
|
-
|
|
98
|
-
output:
|
|
99
|
-
format: table # table | json | csv | minimal
|
|
100
|
-
colors: true
|
|
101
70
|
```
|
|
102
71
|
|
|
103
|
-
##
|
|
104
|
-
|
|
105
|
-
FileMayor recognizes **12 file categories** with **180+ extensions**:
|
|
106
|
-
|
|
107
|
-
| Category | Extensions |
|
|
108
|
-
|----------|-----------|
|
|
109
|
-
| Documents | pdf, doc, docx, txt, md, epub... |
|
|
110
|
-
| Images | jpg, png, gif, svg, webp, psd... |
|
|
111
|
-
| Audio | mp3, wav, flac, aac, ogg... |
|
|
112
|
-
| Video | mp4, mkv, avi, mov, webm... |
|
|
113
|
-
| Archives | zip, rar, 7z, tar, gz... |
|
|
114
|
-
| Code | js, ts, py, java, cpp, go, rs... |
|
|
115
|
-
| Config | json, yaml, yml, toml, ini, env... |
|
|
116
|
-
| Fonts | ttf, otf, woff, woff2... |
|
|
117
|
-
| Data | db, sqlite, parquet, hdf5... |
|
|
118
|
-
| Executables | exe, msi, app, apk, deb... |
|
|
119
|
-
| Design | fig, sketch, blend, stl, dwg... |
|
|
120
|
-
| Books | epub, mobi, azw, djvu, cbz... |
|
|
72
|
+
## Security
|
|
121
73
|
|
|
122
|
-
|
|
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
|
|
123
78
|
|
|
124
|
-
##
|
|
79
|
+
## License
|
|
125
80
|
|
|
126
|
-
|
|
127
|
-
- **System file guards** — won't touch OS-critical files
|
|
128
|
-
- **Rate limiting** — prevents runaway operations
|
|
129
|
-
- **Rollback journal** — every move is logged for safe undo
|
|
130
|
-
- **Content Security Policy** — hardened Electron with CSP headers
|
|
131
|
-
- **No remote code execution** — fully local, no external API calls
|
|
132
|
-
|
|
133
|
-
## 🏗 Architecture
|
|
134
|
-
|
|
135
|
-
```
|
|
136
|
-
filemayor/
|
|
137
|
-
├── cli/ # CLI + Core Engine
|
|
138
|
-
│ ├── index.js # CLI entry point (7 subcommands)
|
|
139
|
-
│ └── core/ # Platform-agnostic engine
|
|
140
|
-
│ ├── scanner.js # Recursive directory scanner
|
|
141
|
-
│ ├── organizer.js # File organization + rollback
|
|
142
|
-
│ ├── cleaner.js # Junk detection + deletion
|
|
143
|
-
│ ├── watcher.js # Real-time file watcher
|
|
144
|
-
│ ├── config.js # YAML config parser
|
|
145
|
-
│ ├── reporter.js # Multi-format output
|
|
146
|
-
│ ├── categories.js # Extension → category mapping
|
|
147
|
-
│ └── security.js # Path guards + sanitization
|
|
148
|
-
├── electron/ # Desktop app (Electron)
|
|
149
|
-
│ ├── main.js # Main process
|
|
150
|
-
│ └── preload.js # IPC bridge
|
|
151
|
-
├── src/ # React frontend
|
|
152
|
-
│ ├── App.tsx # Main app
|
|
153
|
-
│ ├── views/ # UI views
|
|
154
|
-
│ └── locales/ # i18n (10 languages)
|
|
155
|
-
└── public/ # Static assets + PWA
|
|
156
|
-
```
|
|
81
|
+
Proprietary — see [LICENSE](LICENSE) for details.
|
|
157
82
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
| Platform | Method |
|
|
161
|
-
|----------|--------|
|
|
162
|
-
| **Terminal** | `npm install -g filemayor` |
|
|
163
|
-
| **Windows** | Download from GitHub Releases |
|
|
164
|
-
| **macOS** | Download .dmg from Releases |
|
|
165
|
-
| **Linux** | AppImage or .deb from Releases |
|
|
166
|
-
| **Browser** | PWA at filemayor.app |
|
|
167
|
-
| **Servers** | `npm install -g filemayor` + cron/systemd |
|
|
168
|
-
|
|
169
|
-
## 🤝 Contributing
|
|
170
|
-
|
|
171
|
-
1. Fork the repository
|
|
172
|
-
2. Create a feature branch: `git checkout -b feature/amazing-feature`
|
|
173
|
-
3. Commit changes: `git commit -m 'Add amazing feature'`
|
|
174
|
-
4. Push: `git push origin feature/amazing-feature`
|
|
175
|
-
5. Open a Pull Request
|
|
83
|
+
---
|
|
176
84
|
|
|
177
|
-
|
|
85
|
+
<div align="center">
|
|
178
86
|
|
|
179
|
-
|
|
87
|
+
Created by **Lehlohonolo Goodwill Nchefu (Chevza)**
|
|
180
88
|
|
|
181
|
-
|
|
89
|
+
[GitHub](https://github.com/Hrypopo) · [npm](https://www.npmjs.com/package/filemayor)
|
|
182
90
|
|
|
183
|
-
<div align="center">
|
|
184
|
-
<strong>Created by Lehlohonolo Goodwill Nchefu (Chevza)</strong>
|
|
185
|
-
<br>
|
|
186
|
-
<a href="https://github.com/Hrypopo">github.com/Hrypopo</a> · <a href="https://filemayor.app">filemayor.app</a>
|
|
187
91
|
</div>
|
|
188
|
-
]]>
|
package/core/license.js
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ═══════════════════════════════════════════════════════════════════
|
|
5
|
+
* FILEMAYOR CORE — LICENSE
|
|
6
|
+
* Offline license key validation, tier management, and feature gating.
|
|
7
|
+
* Created by Lehlohonolo Goodwill Nchefu (Chevza)
|
|
8
|
+
* ═══════════════════════════════════════════════════════════════════
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const os = require('os');
|
|
16
|
+
const crypto = require('crypto');
|
|
17
|
+
|
|
18
|
+
// ─── Constants ────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
const LICENSE_FILE = path.join(os.homedir(), '.filemayor-license.json');
|
|
21
|
+
const KEY_PREFIX = 'FM';
|
|
22
|
+
const CHECKSUM_SECRET = 'filemayor-chevza-2026';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* License tiers with capabilities
|
|
26
|
+
*/
|
|
27
|
+
const TIERS = {
|
|
28
|
+
free: {
|
|
29
|
+
name: 'Free',
|
|
30
|
+
features: ['scan', 'organize', 'clean', 'undo', 'init', 'info'],
|
|
31
|
+
limits: { bulkOrganize: 50 },
|
|
32
|
+
},
|
|
33
|
+
pro: {
|
|
34
|
+
name: 'Pro',
|
|
35
|
+
features: ['scan', 'organize', 'clean', 'undo', 'init', 'info',
|
|
36
|
+
'watch', 'sop-ai', 'bulk-organize', 'csv-export'],
|
|
37
|
+
limits: { bulkOrganize: Infinity },
|
|
38
|
+
},
|
|
39
|
+
enterprise: {
|
|
40
|
+
name: 'Enterprise',
|
|
41
|
+
features: ['scan', 'organize', 'clean', 'undo', 'init', 'info',
|
|
42
|
+
'watch', 'sop-ai', 'bulk-organize', 'csv-export',
|
|
43
|
+
'api-access', 'custom-categories', 'team-sharing'],
|
|
44
|
+
limits: { bulkOrganize: Infinity },
|
|
45
|
+
},
|
|
46
|
+
owner: {
|
|
47
|
+
name: 'Owner',
|
|
48
|
+
features: ['*'],
|
|
49
|
+
limits: { bulkOrganize: Infinity },
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Built-in keys (hardcoded, never expire)
|
|
55
|
+
*/
|
|
56
|
+
const BUILTIN_KEYS = {
|
|
57
|
+
'FM-OWN-CHEV-2026-PRMNT': { tier: 'owner', name: 'Owner — Chevza', expires: null },
|
|
58
|
+
'FM-TST-DEV0-TEST-00000': { tier: 'pro', name: 'Test — Development', expires: null },
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// ─── Key Generation & Validation ──────────────────────────────────
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Generate a checksum for a key body
|
|
65
|
+
* @param {string} body - Key body without checksum segment
|
|
66
|
+
* @returns {string} 5-char checksum
|
|
67
|
+
*/
|
|
68
|
+
function generateChecksum(body) {
|
|
69
|
+
const hash = crypto.createHmac('sha256', CHECKSUM_SECRET)
|
|
70
|
+
.update(body)
|
|
71
|
+
.digest('hex');
|
|
72
|
+
return hash.substring(0, 5).toUpperCase();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Validate key format: FM-XXX-XXXX-XXXX-XXXXX
|
|
77
|
+
* Last 5 chars are HMAC checksum of the rest
|
|
78
|
+
* @param {string} key - License key
|
|
79
|
+
* @returns {{ valid: boolean, tier: string|null, reason: string }}
|
|
80
|
+
*/
|
|
81
|
+
function validateKeyFormat(key) {
|
|
82
|
+
if (!key || typeof key !== 'string') {
|
|
83
|
+
return { valid: false, tier: null, reason: 'No key provided' };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const k = key.trim().toUpperCase();
|
|
87
|
+
|
|
88
|
+
// Check built-in keys first
|
|
89
|
+
if (BUILTIN_KEYS[k]) {
|
|
90
|
+
return { valid: true, tier: BUILTIN_KEYS[k].tier, reason: 'Built-in key' };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Format: FM-XXX-XXXX-XXXX-XXXXX
|
|
94
|
+
const pattern = /^FM-(PRO|ENT|OWN|TST)-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{5}$/;
|
|
95
|
+
if (!pattern.test(k)) {
|
|
96
|
+
return { valid: false, tier: null, reason: 'Invalid key format' };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Extract parts
|
|
100
|
+
const parts = k.split('-');
|
|
101
|
+
const tierCode = parts[1];
|
|
102
|
+
const body = parts.slice(0, 4).join('-'); // FM-XXX-XXXX-XXXX
|
|
103
|
+
const checksum = parts[4]; // XXXXX
|
|
104
|
+
|
|
105
|
+
// Verify checksum
|
|
106
|
+
const expectedChecksum = generateChecksum(body);
|
|
107
|
+
if (checksum !== expectedChecksum) {
|
|
108
|
+
return { valid: false, tier: null, reason: 'Invalid key (checksum failed)' };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Map tier code
|
|
112
|
+
const tierMap = { PRO: 'pro', ENT: 'enterprise', OWN: 'owner', TST: 'pro' };
|
|
113
|
+
const tier = tierMap[tierCode] || 'free';
|
|
114
|
+
|
|
115
|
+
return { valid: true, tier, reason: 'Valid' };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Generate a new license key
|
|
120
|
+
* @param {'pro'|'enterprise'|'owner'} tier
|
|
121
|
+
* @returns {string} Generated key
|
|
122
|
+
*/
|
|
123
|
+
function generateKey(tier = 'pro') {
|
|
124
|
+
const tierMap = { pro: 'PRO', enterprise: 'ENT', owner: 'OWN' };
|
|
125
|
+
const code = tierMap[tier] || 'PRO';
|
|
126
|
+
|
|
127
|
+
const seg1 = crypto.randomBytes(2).toString('hex').toUpperCase().substring(0, 4);
|
|
128
|
+
const seg2 = crypto.randomBytes(2).toString('hex').toUpperCase().substring(0, 4);
|
|
129
|
+
const body = `FM-${code}-${seg1}-${seg2}`;
|
|
130
|
+
const checksum = generateChecksum(body);
|
|
131
|
+
|
|
132
|
+
return `${body}-${checksum}`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ─── License Storage ──────────────────────────────────────────────
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Read stored license from disk
|
|
139
|
+
* @returns {{ key: string, tier: string, activatedAt: string, name: string }|null}
|
|
140
|
+
*/
|
|
141
|
+
function readLicense() {
|
|
142
|
+
try {
|
|
143
|
+
if (!fs.existsSync(LICENSE_FILE)) return null;
|
|
144
|
+
const data = JSON.parse(fs.readFileSync(LICENSE_FILE, 'utf8'));
|
|
145
|
+
return data;
|
|
146
|
+
} catch {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Write license to disk
|
|
153
|
+
* @param {Object} licenseData
|
|
154
|
+
*/
|
|
155
|
+
function writeLicense(licenseData) {
|
|
156
|
+
fs.writeFileSync(LICENSE_FILE, JSON.stringify(licenseData, null, 2), 'utf8');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Remove stored license
|
|
161
|
+
*/
|
|
162
|
+
function removeLicense() {
|
|
163
|
+
if (fs.existsSync(LICENSE_FILE)) {
|
|
164
|
+
fs.unlinkSync(LICENSE_FILE);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ─── License Management ──────────────────────────────────────────
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Activate a license key
|
|
172
|
+
* @param {string} key - License key to activate
|
|
173
|
+
* @returns {{ success: boolean, message: string, tier: string|null }}
|
|
174
|
+
*/
|
|
175
|
+
function activateLicense(key) {
|
|
176
|
+
const validation = validateKeyFormat(key);
|
|
177
|
+
|
|
178
|
+
if (!validation.valid) {
|
|
179
|
+
return { success: false, message: validation.reason, tier: null };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const k = key.trim().toUpperCase();
|
|
183
|
+
const builtin = BUILTIN_KEYS[k];
|
|
184
|
+
|
|
185
|
+
const licenseData = {
|
|
186
|
+
key: k,
|
|
187
|
+
tier: validation.tier,
|
|
188
|
+
name: builtin ? builtin.name : `${TIERS[validation.tier].name} License`,
|
|
189
|
+
activatedAt: new Date().toISOString(),
|
|
190
|
+
expires: builtin ? null : null, // LemonSqueezy keys can add expiry later
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
writeLicense(licenseData);
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
success: true,
|
|
197
|
+
message: `${TIERS[validation.tier].name} license activated successfully`,
|
|
198
|
+
tier: validation.tier,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Deactivate the current license
|
|
204
|
+
* @returns {{ success: boolean, message: string }}
|
|
205
|
+
*/
|
|
206
|
+
function deactivateLicense() {
|
|
207
|
+
const current = readLicense();
|
|
208
|
+
if (!current) {
|
|
209
|
+
return { success: false, message: 'No active license found' };
|
|
210
|
+
}
|
|
211
|
+
removeLicense();
|
|
212
|
+
return { success: true, message: 'License deactivated. Reverted to Free tier.' };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Get current license info
|
|
217
|
+
* @returns {{ tier: string, name: string, features: string[], limits: Object, key: string|null, active: boolean }}
|
|
218
|
+
*/
|
|
219
|
+
function getLicenseInfo() {
|
|
220
|
+
const stored = readLicense();
|
|
221
|
+
|
|
222
|
+
if (!stored) {
|
|
223
|
+
return {
|
|
224
|
+
tier: 'free',
|
|
225
|
+
name: 'Free',
|
|
226
|
+
features: TIERS.free.features,
|
|
227
|
+
limits: TIERS.free.limits,
|
|
228
|
+
key: null,
|
|
229
|
+
active: false,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Re-validate stored key
|
|
234
|
+
const validation = validateKeyFormat(stored.key);
|
|
235
|
+
if (!validation.valid) {
|
|
236
|
+
removeLicense();
|
|
237
|
+
return {
|
|
238
|
+
tier: 'free',
|
|
239
|
+
name: 'Free (invalid key removed)',
|
|
240
|
+
features: TIERS.free.features,
|
|
241
|
+
limits: TIERS.free.limits,
|
|
242
|
+
key: null,
|
|
243
|
+
active: false,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const tier = TIERS[stored.tier] || TIERS.free;
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
tier: stored.tier,
|
|
251
|
+
name: stored.name,
|
|
252
|
+
features: tier.features,
|
|
253
|
+
limits: tier.limits,
|
|
254
|
+
key: stored.key,
|
|
255
|
+
active: true,
|
|
256
|
+
activatedAt: stored.activatedAt,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// ─── Feature Gating ──────────────────────────────────────────────
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Check if a feature is available in the current license
|
|
264
|
+
* @param {string} feature - Feature identifier
|
|
265
|
+
* @returns {boolean}
|
|
266
|
+
*/
|
|
267
|
+
function hasFeature(feature) {
|
|
268
|
+
const info = getLicenseInfo();
|
|
269
|
+
if (info.features.includes('*')) return true; // Owner has everything
|
|
270
|
+
return info.features.includes(feature);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Check if a Pro feature is gated and return appropriate message
|
|
275
|
+
* @param {string} feature - Feature identifier
|
|
276
|
+
* @param {string} featureLabel - Human-readable feature name
|
|
277
|
+
* @returns {{ allowed: boolean, message: string|null }}
|
|
278
|
+
*/
|
|
279
|
+
function checkProFeature(feature, featureLabel) {
|
|
280
|
+
if (hasFeature(feature)) {
|
|
281
|
+
return { allowed: true, message: null };
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return {
|
|
285
|
+
allowed: false,
|
|
286
|
+
message: [
|
|
287
|
+
`⚡ ${featureLabel} is a Pro feature`,
|
|
288
|
+
'',
|
|
289
|
+
' Upgrade to Pro to unlock:',
|
|
290
|
+
' • Real-time file watching & auto-organize',
|
|
291
|
+
' • AI-powered SOP parsing (Gemini)',
|
|
292
|
+
' • Bulk organize (unlimited files)',
|
|
293
|
+
' • CSV export for all reports',
|
|
294
|
+
'',
|
|
295
|
+
' Get your license: https://filemayor.lemonsqueezy.com/checkout/buy/d2795526-eb05-4272-8084-98b6c7a118bb',
|
|
296
|
+
' Then run: filemayor license activate YOUR-KEY',
|
|
297
|
+
'',
|
|
298
|
+
].join('\n'),
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Check bulk organize limit
|
|
304
|
+
* @param {number} fileCount - Number of files to organize
|
|
305
|
+
* @returns {{ allowed: boolean, message: string|null }}
|
|
306
|
+
*/
|
|
307
|
+
function checkBulkLimit(fileCount) {
|
|
308
|
+
const info = getLicenseInfo();
|
|
309
|
+
if (info.limits.bulkOrganize >= fileCount) {
|
|
310
|
+
return { allowed: true, message: null };
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return {
|
|
314
|
+
allowed: false,
|
|
315
|
+
message: [
|
|
316
|
+
`⚡ Bulk organize (${fileCount} files) requires a Pro license`,
|
|
317
|
+
` Free tier is limited to ${info.limits.bulkOrganize} files per operation.`,
|
|
318
|
+
'',
|
|
319
|
+
' Upgrade: https://filemayor.lemonsqueezy.com/checkout/buy/d2795526-eb05-4272-8084-98b6c7a118bb',
|
|
320
|
+
' Then run: filemayor license activate YOUR-KEY',
|
|
321
|
+
].join('\n'),
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// ─── Exports ─────────────────────────────────────────────────────
|
|
326
|
+
|
|
327
|
+
module.exports = {
|
|
328
|
+
TIERS,
|
|
329
|
+
BUILTIN_KEYS,
|
|
330
|
+
validateKeyFormat,
|
|
331
|
+
generateKey,
|
|
332
|
+
readLicense,
|
|
333
|
+
activateLicense,
|
|
334
|
+
deactivateLicense,
|
|
335
|
+
getLicenseInfo,
|
|
336
|
+
hasFeature,
|
|
337
|
+
checkProFeature,
|
|
338
|
+
checkBulkLimit,
|
|
339
|
+
LICENSE_FILE,
|
|
340
|
+
};
|
package/index.js
CHANGED
|
@@ -33,11 +33,28 @@ const reporter = require('./core/reporter');
|
|
|
33
33
|
const { formatBytes } = require('./core/scanner');
|
|
34
34
|
const { getCategories } = require('./core/categories');
|
|
35
35
|
const { checkPermissions } = require('./core/security');
|
|
36
|
+
const { activateLicense, deactivateLicense, getLicenseInfo, checkProFeature, checkBulkLimit } = require('./core/license');
|
|
36
37
|
|
|
37
38
|
const { c, banner, Spinner, success, error, warn, info } = reporter;
|
|
38
39
|
|
|
39
40
|
// ─── Version ──────────────────────────────────────────────────────
|
|
40
|
-
const VERSION = '2.0.
|
|
41
|
+
const VERSION = '2.0.4';
|
|
42
|
+
|
|
43
|
+
// ─── Path Helpers ─────────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Expand ~ to user home directory (cross-platform).
|
|
47
|
+
* On Unix shells, ~ is expanded by the shell before reaching Node.
|
|
48
|
+
* On Windows PowerShell, it is NOT — so we handle it here.
|
|
49
|
+
*/
|
|
50
|
+
function expandTilde(p) {
|
|
51
|
+
if (!p) return p;
|
|
52
|
+
if (p === '~') return os.homedir();
|
|
53
|
+
if (p.startsWith('~/') || p.startsWith('~\\')) {
|
|
54
|
+
return path.join(os.homedir(), p.slice(2));
|
|
55
|
+
}
|
|
56
|
+
return p;
|
|
57
|
+
}
|
|
41
58
|
|
|
42
59
|
// ─── Argument Parser ──────────────────────────────────────────────
|
|
43
60
|
|
|
@@ -108,7 +125,7 @@ function parseArgs(argv) {
|
|
|
108
125
|
} else if (!args.command) {
|
|
109
126
|
args.command = arg;
|
|
110
127
|
} else if (!args.target) {
|
|
111
|
-
args.target = arg;
|
|
128
|
+
args.target = expandTilde(arg);
|
|
112
129
|
} else {
|
|
113
130
|
args.positional.push(arg);
|
|
114
131
|
}
|
|
@@ -140,10 +157,11 @@ ${banner()}
|
|
|
140
157
|
${c('yellow', 'scan')} ${c('dim', '<path>')} Scan directory and report contents
|
|
141
158
|
${c('yellow', 'organize')} ${c('dim', '<path>')} Organize files into categories
|
|
142
159
|
${c('yellow', 'clean')} ${c('dim', '<path>')} Find and remove junk files
|
|
143
|
-
${c('yellow', 'watch')} ${c('dim', '<path>')} Watch directory for changes (
|
|
160
|
+
${c('yellow', 'watch')} ${c('dim', '<path>')} Watch directory for changes ${c('magenta', '[PRO]')}
|
|
144
161
|
${c('yellow', 'init')} Create .filemayor.yml config
|
|
145
162
|
${c('yellow', 'undo')} ${c('dim', '<path>')} Undo last organization
|
|
146
163
|
${c('yellow', 'info')} System info and version
|
|
164
|
+
${c('yellow', 'license')} ${c('dim', '<action>')} Manage license (activate|status|deactivate)
|
|
147
165
|
|
|
148
166
|
${c('bold', 'OPTIONS')}
|
|
149
167
|
${c('dim', '--dry-run')} Preview changes without executing
|
|
@@ -178,7 +196,7 @@ ${banner()}
|
|
|
178
196
|
${c('bold', 'INSTALL')}
|
|
179
197
|
${c('dim', '$')} npm install -g filemayor
|
|
180
198
|
|
|
181
|
-
${c('dim', `FileMayor v${VERSION} — https://filemayor.
|
|
199
|
+
${c('dim', `FileMayor v${VERSION} — https://filemayor.com`)}
|
|
182
200
|
`);
|
|
183
201
|
}
|
|
184
202
|
|
|
@@ -301,6 +319,13 @@ async function cmdClean(target, flags, config) {
|
|
|
301
319
|
}
|
|
302
320
|
|
|
303
321
|
async function cmdWatch(target, flags, config) {
|
|
322
|
+
// Pro feature gate
|
|
323
|
+
const gate = checkProFeature('watch', 'Watch Mode');
|
|
324
|
+
if (!gate.allowed) {
|
|
325
|
+
console.log(gate.message);
|
|
326
|
+
process.exit(0);
|
|
327
|
+
}
|
|
328
|
+
|
|
304
329
|
const targetPath = path.resolve(target || '.');
|
|
305
330
|
const format = flags.format || config.output.format;
|
|
306
331
|
|
|
@@ -429,6 +454,87 @@ async function cmdInfo(config) {
|
|
|
429
454
|
console.log('');
|
|
430
455
|
}
|
|
431
456
|
|
|
457
|
+
// ─── License Command ──────────────────────────────────────────────
|
|
458
|
+
|
|
459
|
+
async function cmdLicense(action, positional, flags) {
|
|
460
|
+
const subAction = action || 'status';
|
|
461
|
+
|
|
462
|
+
switch (subAction) {
|
|
463
|
+
case 'activate': {
|
|
464
|
+
const key = positional[0] || flags.key;
|
|
465
|
+
if (!key) {
|
|
466
|
+
console.error(error('Usage: filemayor license activate <key>'));
|
|
467
|
+
console.log(c('dim', ' Example: filemayor license activate FM-PRO-XXXX-XXXX-XXXXX'));
|
|
468
|
+
process.exit(1);
|
|
469
|
+
}
|
|
470
|
+
const result = activateLicense(key);
|
|
471
|
+
if (result.success) {
|
|
472
|
+
console.log(success(result.message));
|
|
473
|
+
console.log(c('dim', ` Tier: ${result.tier}`));
|
|
474
|
+
} else {
|
|
475
|
+
console.error(error(result.message));
|
|
476
|
+
process.exit(1);
|
|
477
|
+
}
|
|
478
|
+
break;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
case 'deactivate':
|
|
482
|
+
case 'remove': {
|
|
483
|
+
const result = deactivateLicense();
|
|
484
|
+
if (result.success) {
|
|
485
|
+
console.log(success(result.message));
|
|
486
|
+
} else {
|
|
487
|
+
console.log(info(result.message));
|
|
488
|
+
}
|
|
489
|
+
break;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
case 'status':
|
|
493
|
+
default: {
|
|
494
|
+
const li = getLicenseInfo();
|
|
495
|
+
console.log(banner());
|
|
496
|
+
console.log(c('bold', ' License Status'));
|
|
497
|
+
console.log(c('dim', ` ${'─'.repeat(40)}`));
|
|
498
|
+
console.log(` ${c('bold', 'Tier:')} ${li.active ? c('green', li.name) : c('yellow', 'Free')}`);
|
|
499
|
+
console.log(` ${c('bold', 'Status:')} ${li.active ? c('green', '● Active') : c('dim', '○ No license')}`);
|
|
500
|
+
if (li.key) {
|
|
501
|
+
const masked = li.key.substring(0, 7) + '****' + li.key.substring(li.key.length - 5);
|
|
502
|
+
console.log(` ${c('bold', 'Key:')} ${c('dim', masked)}`);
|
|
503
|
+
}
|
|
504
|
+
if (li.activatedAt) {
|
|
505
|
+
console.log(` ${c('bold', 'Activated:')} ${c('dim', new Date(li.activatedAt).toLocaleDateString())}`);
|
|
506
|
+
}
|
|
507
|
+
console.log('');
|
|
508
|
+
console.log(c('bold', ' Features'));
|
|
509
|
+
console.log(c('dim', ` ${'─'.repeat(40)}`));
|
|
510
|
+
const allFeatures = [
|
|
511
|
+
{ id: 'scan', label: 'Directory Scanning', free: true },
|
|
512
|
+
{ id: 'organize', label: 'File Organization', free: true },
|
|
513
|
+
{ id: 'clean', label: 'Junk Cleanup', free: true },
|
|
514
|
+
{ id: 'undo', label: 'Undo Operations', free: true },
|
|
515
|
+
{ id: 'watch', label: 'Watch Mode', free: false },
|
|
516
|
+
{ id: 'sop-ai', label: 'AI SOP Parsing', free: false },
|
|
517
|
+
{ id: 'bulk-organize', label: 'Bulk Organize (50+ files)', free: false },
|
|
518
|
+
{ id: 'csv-export', label: 'CSV Export', free: false },
|
|
519
|
+
];
|
|
520
|
+
for (const feat of allFeatures) {
|
|
521
|
+
const has = li.features.includes('*') || li.features.includes(feat.id);
|
|
522
|
+
const icon = has ? c('green', '✓') : c('dim', '○');
|
|
523
|
+
const label = has ? feat.label : c('dim', feat.label);
|
|
524
|
+
const tag = !feat.free && !has ? c('magenta', ' [PRO]') : '';
|
|
525
|
+
console.log(` ${icon} ${label}${tag}`);
|
|
526
|
+
}
|
|
527
|
+
console.log('');
|
|
528
|
+
if (!li.active) {
|
|
529
|
+
console.log(c('dim', ' Get a license: https://filemayor.lemonsqueezy.com/checkout/buy/d2795526-eb05-4272-8084-98b6c7a118bb'));
|
|
530
|
+
console.log(c('dim', ' Activate: filemayor license activate <key>'));
|
|
531
|
+
console.log('');
|
|
532
|
+
}
|
|
533
|
+
break;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
432
538
|
// ─── Main Entry Point ─────────────────────────────────────────────
|
|
433
539
|
|
|
434
540
|
async function main() {
|
|
@@ -515,6 +621,12 @@ async function main() {
|
|
|
515
621
|
await cmdInfo(config);
|
|
516
622
|
break;
|
|
517
623
|
|
|
624
|
+
case 'license':
|
|
625
|
+
case 'lic':
|
|
626
|
+
case 'l':
|
|
627
|
+
await cmdLicense(args.target, args.positional, args.flags);
|
|
628
|
+
break;
|
|
629
|
+
|
|
518
630
|
case 'help':
|
|
519
631
|
printHelp();
|
|
520
632
|
break;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "filemayor",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.4",
|
|
4
4
|
"description": "Enterprise file management engine — scan, organize, clean, and watch your filesystem from any terminal",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
],
|
|
27
27
|
"author": {
|
|
28
28
|
"name": "Lehlohonolo Goodwill Nchefu (Chevza)",
|
|
29
|
+
"email": "nchefuh@gmail.com",
|
|
29
30
|
"url": "https://github.com/Hrypopo"
|
|
30
31
|
},
|
|
31
32
|
"license": "PROPRIETARY",
|
|
@@ -36,7 +37,7 @@
|
|
|
36
37
|
"bugs": {
|
|
37
38
|
"url": "https://github.com/Hrypopo/FileMayor/issues"
|
|
38
39
|
},
|
|
39
|
-
"homepage": "https://
|
|
40
|
+
"homepage": "https://filemayor.com",
|
|
40
41
|
"engines": {
|
|
41
42
|
"node": ">=18.0.0"
|
|
42
43
|
},
|