filemayor 2.0.3 → 2.0.5
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/core/license.js +340 -0
- package/index.js +116 -4
- package/package.json +4 -3
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/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/7fdcc87f-0660-4c1c-b3db-99f94773b71a',
|
|
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/7fdcc87f-0660-4c1c-b3db-99f94773b71a',
|
|
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.5';
|
|
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,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "filemayor",
|
|
3
|
-
"version": "2.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.0.5",
|
|
4
|
+
"description": "FileMayor — Your Digital Life Organizer.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"filemayor": "./index.js"
|
|
@@ -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
|
},
|