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 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.app
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.app
90
- For support: support@filemayor.app
89
+ For licensing inquiries: licensing@filemayor.com
90
+ For support: support@filemayor.com
package/README.md CHANGED
@@ -1,188 +1,91 @@
1
- <![CDATA[<div align="center">
1
+ <div align="center">
2
2
 
3
- # FileMayor
3
+ # FileMayor
4
4
 
5
- ### Your Digital Life Organizer
5
+ **Organize, scan, clean, and watch your filesystem — from any terminal.**
6
6
 
7
- **Organize, scan, clean, and watch your filesystem from any terminal, desktop, or browser.**
7
+ `v2.0.1` · Windows · macOS · Linux · Node ≥18
8
8
 
9
- [![npm version](https://img.shields.io/npm/v/filemayor?color=d4af37&label=npm)](https://www.npmjs.com/package/filemayor)
10
- [![License: Proprietary](https://img.shields.io/badge/license-Proprietary-red.svg)](LICENSE)
11
- [![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-brightgreen)]()
12
- [![Node](https://img.shields.io/badge/node-%3E%3D18-green)]()
9
+ </div>
13
10
 
14
11
  ---
15
12
 
16
- **CLI** · **Desktop App** · **PWA** · **Data Center Ready**
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
- ## 📦 What It Does
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
- # Scan a directory
45
- filemayor scan ~/Downloads
46
-
47
- # Organize files (preview first)
48
- filemayor organize ~/Downloads --dry-run
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
- # Organize with naming convention
51
- filemayor organize ~/Downloads --naming category_prefix
32
+ ## What It Does
52
33
 
53
- # Clean junk files
54
- filemayor clean /var/tmp --yes
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
- # Watch for new files and auto-organize
57
- filemayor watch ~/Downloads
42
+ ## 12 File Categories · 180+ Extensions
58
43
 
59
- # Create config file
60
- filemayor init
44
+ Documents · Images · Audio · Video · Archives · Code · Config · Fonts · Data · Executables · Design · Books
61
45
 
62
- # Undo last organization
63
- filemayor undo ~/Downloads
46
+ ## Output Formats
64
47
 
65
- # Machine-readable output
66
- filemayor scan ~/Downloads --json | jq '.files | length'
67
- filemayor scan . --csv > report.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
- ## ⚙️ Configuration
54
+ ## Configuration
71
55
 
72
- Create a `.filemayor.yml` in your project or home directory:
56
+ Create `.filemayor.yml` in any directory:
73
57
 
74
58
  ```yaml
75
- version: 1
76
-
77
59
  organize:
78
- naming: category_prefix # original | category_prefix | date_prefix | clean
79
- duplicates: rename # rename | skip | overwrite
80
- maxDepth: 20
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
- ## 📂 Categories
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
- Custom categories can be added via `.filemayor.yml`.
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
- ## 🔒 Security
79
+ ## License
125
80
 
126
- - **Path traversal protection** blocks operations on system directories
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
+ Proprietarysee [LICENSE](LICENSE) for details.
157
82
 
158
- ## 🌍 Platforms
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
- ## 📄 License
85
+ <div align="center">
178
86
 
179
- Proprietary License see [LICENSE](LICENSE) for details.
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
- ]]>
@@ -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.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 (daemon)
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.app`)}
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.1",
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://github.com/Hrypopo/FileMayor#readme",
40
+ "homepage": "https://filemayor.com",
40
41
  "engines": {
41
42
  "node": ">=18.0.0"
42
43
  },