filemayor 3.6.0 → 4.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 +90 -90
- package/README.md +96 -96
- package/core/analyzer.js +163 -163
- package/core/categories.js +235 -235
- package/core/cleaner.js +527 -527
- package/core/config.js +562 -562
- package/core/fs-abstraction.js +110 -38
- package/core/index.js +135 -135
- package/core/intent-interpreter.js +209 -66
- package/core/license.js +403 -339
- package/core/organizer.js +414 -414
- package/core/reporter.js +783 -783
- package/core/scanner.js +466 -466
- package/core/security.js +370 -370
- package/core/sop-parser.js +564 -564
- package/core/telemetry.js +74 -0
- package/core/vault.js +83 -69
- package/core/watcher.js +478 -478
- package/index.js +895 -877
- package/package.json +2 -2
package/index.js
CHANGED
|
@@ -1,877 +1,895 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* ═══════════════════════════════════════════════════════════════════
|
|
5
|
-
*
|
|
6
|
-
* ███████╗██╗██╗ ███████╗███╗ ███╗ █████╗ ██╗ ██╗ ██████╗ ██████╗
|
|
7
|
-
* ██╔════╝██║██║ ██╔════╝████╗ ████║██╔══██╗╚██╗ ██╔╝██╔═══██╗██╔══██╗
|
|
8
|
-
* █████╗ ██║██║ █████╗ ██╔████╔██║███████║ ╚████╔╝ ██║ ██║██████╔╝
|
|
9
|
-
* ██╔══╝ ██║██║ ██╔══╝ ██║╚██╔╝██║██╔══██║ ╚██╔╝ ██║ ██║██╔══██╗
|
|
10
|
-
* ██║ ██║███████╗███████╗██║ ╚═╝ ██║██║ ██║ ██║ ╚██████╔╝██║ ██║
|
|
11
|
-
* ╚═╝ ╚═╝╚══════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝
|
|
12
|
-
*
|
|
13
|
-
* Your Digital Life Organizer — CLI Edition
|
|
14
|
-
* Created by Lehlohonolo Goodwill Nchefu (Chevza)
|
|
15
|
-
* Works everywhere: terminals, servers, data centers, CI/CD
|
|
16
|
-
*
|
|
17
|
-
* ═══════════════════════════════════════════════════════════════════
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
'use strict';
|
|
21
|
-
|
|
22
|
-
// ─── Load .env (zero-dependency) ─────────────────────────────────
|
|
23
|
-
try {
|
|
24
|
-
const _fs = require('fs');
|
|
25
|
-
const _path = require('path');
|
|
26
|
-
const envPath = _path.join(__dirname, '..', '.env');
|
|
27
|
-
_fs.readFileSync(envPath, 'utf-8').split('\n').forEach(line => {
|
|
28
|
-
const trimmed = line.trim();
|
|
29
|
-
if (trimmed && !trimmed.startsWith('#')) {
|
|
30
|
-
const [key, ...vals] = trimmed.split('=');
|
|
31
|
-
if (key && vals.length && !process.env[key.trim()]) {
|
|
32
|
-
process.env[key.trim()] = vals.join('=').trim();
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
} catch { /* .env not found — that's fine, user may set env vars directly */ }
|
|
37
|
-
|
|
38
|
-
const path = require('path');
|
|
39
|
-
const os = require('os');
|
|
40
|
-
const fs = require('fs');
|
|
41
|
-
|
|
42
|
-
// Core modules
|
|
43
|
-
const { scan, scanByCategory, scanSummary } = require('./core/scanner');
|
|
44
|
-
const { organize, generatePlan, rollback, loadJournal } = require('./core/organizer');
|
|
45
|
-
const { findJunk, clean } = require('./core/cleaner');
|
|
46
|
-
const { FileWatcher } = require('./core/watcher');
|
|
47
|
-
const { loadConfig, createConfigFile } = require('./core/config');
|
|
48
|
-
const { analyzeDirectory } = require('./core/analyzer');
|
|
49
|
-
const reporter = require('./core/reporter');
|
|
50
|
-
const { formatBytes } = require('./core/scanner');
|
|
51
|
-
const { getCategories } = require('./core/categories');
|
|
52
|
-
const { checkPermissions } = require('./core/security');
|
|
53
|
-
const { activateLicense, deactivateLicense, getLicenseInfo, checkProFeature, checkBulkLimit } = require('./core/license');
|
|
54
|
-
const { ExplainEngine, CureEngine, ApplyEngine, DedupeEngine } = require('./core/index');
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
*
|
|
66
|
-
* On
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (p
|
|
71
|
-
if (p
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
const
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
${c('yellow', '
|
|
176
|
-
${c('yellow', '
|
|
177
|
-
${c('yellow', '
|
|
178
|
-
${c('yellow', '
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
${c('yellow', '
|
|
183
|
-
${c('yellow', '
|
|
184
|
-
${c('yellow', '
|
|
185
|
-
${c('yellow', '
|
|
186
|
-
${c('yellow', '
|
|
187
|
-
${c('yellow', '
|
|
188
|
-
${c('yellow', '
|
|
189
|
-
${c('yellow', '
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
${c('dim', '--
|
|
194
|
-
${c('dim', '--
|
|
195
|
-
${c('dim', '--
|
|
196
|
-
${c('dim', '--
|
|
197
|
-
${c('dim', '--
|
|
198
|
-
${c('dim', '--
|
|
199
|
-
${c('dim', '--
|
|
200
|
-
${c('dim', '--
|
|
201
|
-
${c('dim', '--
|
|
202
|
-
${c('dim', '--
|
|
203
|
-
${c('dim', '--
|
|
204
|
-
${c('dim', '--
|
|
205
|
-
${c('dim', '--
|
|
206
|
-
${c('dim', '--
|
|
207
|
-
${c('dim', '--
|
|
208
|
-
${c('dim', '--
|
|
209
|
-
${c('dim', '--
|
|
210
|
-
${c('dim', '--
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
${c('dim', '$')} filemayor
|
|
215
|
-
${c('dim', '$')} filemayor organize ~/Downloads --
|
|
216
|
-
${c('dim', '$')} filemayor
|
|
217
|
-
${c('dim', '$')} filemayor
|
|
218
|
-
${c('dim', '$')} filemayor
|
|
219
|
-
${c('dim', '$')} filemayor
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
`)
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
const
|
|
296
|
-
const
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
console.log(
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
const
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
const
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
const
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
console.log(
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
console.log(` ${c('bold', '
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
console.log(` ${c('bold', '
|
|
647
|
-
|
|
648
|
-
//
|
|
649
|
-
const
|
|
650
|
-
const
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
console.
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
case '
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
console.log('');
|
|
707
|
-
console.log(c('bold', '
|
|
708
|
-
console.log(c('
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
{
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
{
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
case '
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
case '
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
case '
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
case '
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
case '
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ═══════════════════════════════════════════════════════════════════
|
|
5
|
+
*
|
|
6
|
+
* ███████╗██╗██╗ ███████╗███╗ ███╗ █████╗ ██╗ ██╗ ██████╗ ██████╗
|
|
7
|
+
* ██╔════╝██║██║ ██╔════╝████╗ ████║██╔══██╗╚██╗ ██╔╝██╔═══██╗██╔══██╗
|
|
8
|
+
* █████╗ ██║██║ █████╗ ██╔████╔██║███████║ ╚████╔╝ ██║ ██║██████╔╝
|
|
9
|
+
* ██╔══╝ ██║██║ ██╔══╝ ██║╚██╔╝██║██╔══██║ ╚██╔╝ ██║ ██║██╔══██╗
|
|
10
|
+
* ██║ ██║███████╗███████╗██║ ╚═╝ ██║██║ ██║ ██║ ╚██████╔╝██║ ██║
|
|
11
|
+
* ╚═╝ ╚═╝╚══════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝
|
|
12
|
+
*
|
|
13
|
+
* Your Digital Life Organizer — CLI Edition
|
|
14
|
+
* Created by Lehlohonolo Goodwill Nchefu (Chevza)
|
|
15
|
+
* Works everywhere: terminals, servers, data centers, CI/CD
|
|
16
|
+
*
|
|
17
|
+
* ═══════════════════════════════════════════════════════════════════
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
'use strict';
|
|
21
|
+
|
|
22
|
+
// ─── Load .env (zero-dependency) ─────────────────────────────────
|
|
23
|
+
try {
|
|
24
|
+
const _fs = require('fs');
|
|
25
|
+
const _path = require('path');
|
|
26
|
+
const envPath = _path.join(__dirname, '..', '.env');
|
|
27
|
+
_fs.readFileSync(envPath, 'utf-8').split('\n').forEach(line => {
|
|
28
|
+
const trimmed = line.trim();
|
|
29
|
+
if (trimmed && !trimmed.startsWith('#')) {
|
|
30
|
+
const [key, ...vals] = trimmed.split('=');
|
|
31
|
+
if (key && vals.length && !process.env[key.trim()]) {
|
|
32
|
+
process.env[key.trim()] = vals.join('=').trim();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
} catch { /* .env not found — that's fine, user may set env vars directly */ }
|
|
37
|
+
|
|
38
|
+
const path = require('path');
|
|
39
|
+
const os = require('os');
|
|
40
|
+
const fs = require('fs');
|
|
41
|
+
|
|
42
|
+
// Core modules
|
|
43
|
+
const { scan, scanByCategory, scanSummary } = require('./core/scanner');
|
|
44
|
+
const { organize, generatePlan, rollback, loadJournal } = require('./core/organizer');
|
|
45
|
+
const { findJunk, clean } = require('./core/cleaner');
|
|
46
|
+
const { FileWatcher } = require('./core/watcher');
|
|
47
|
+
const { loadConfig, createConfigFile } = require('./core/config');
|
|
48
|
+
const { analyzeDirectory } = require('./core/analyzer');
|
|
49
|
+
const reporter = require('./core/reporter');
|
|
50
|
+
const { formatBytes } = require('./core/scanner');
|
|
51
|
+
const { getCategories } = require('./core/categories');
|
|
52
|
+
const { checkPermissions } = require('./core/security');
|
|
53
|
+
const { activateLicense, deactivateLicense, getLicenseInfo, checkProFeature, checkBulkLimit } = require('./core/license');
|
|
54
|
+
const { ExplainEngine, CureEngine, ApplyEngine, DedupeEngine } = require('./core/index');
|
|
55
|
+
const { ping: telemetryPing } = require('./core/telemetry');
|
|
56
|
+
|
|
57
|
+
const { c, banner, Spinner, success, error, warn, info } = reporter;
|
|
58
|
+
|
|
59
|
+
// ─── Version ──────────────────────────────────────────────────────
|
|
60
|
+
const VERSION = '4.0.5';
|
|
61
|
+
|
|
62
|
+
// ─── Path Helpers ─────────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Expand ~ to user home directory (cross-platform).
|
|
66
|
+
* On Unix shells, ~ is expanded by the shell before reaching Node.
|
|
67
|
+
* On Windows PowerShell, it is NOT — so we handle it here.
|
|
68
|
+
*/
|
|
69
|
+
function expandTilde(p) {
|
|
70
|
+
if (!p) return p;
|
|
71
|
+
if (p === '~') return os.homedir();
|
|
72
|
+
if (p.startsWith('~/') || p.startsWith('~\\')) {
|
|
73
|
+
return path.join(os.homedir(), p.slice(2));
|
|
74
|
+
}
|
|
75
|
+
return p;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ─── Argument Parser ──────────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
function parseArgs(argv) {
|
|
81
|
+
const args = {
|
|
82
|
+
command: null,
|
|
83
|
+
target: null,
|
|
84
|
+
flags: {},
|
|
85
|
+
positional: []
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const raw = argv.slice(2);
|
|
89
|
+
let i = 0;
|
|
90
|
+
|
|
91
|
+
while (i < raw.length) {
|
|
92
|
+
const arg = raw[i];
|
|
93
|
+
|
|
94
|
+
if (arg === '--help' || arg === '-h') {
|
|
95
|
+
args.flags.help = true;
|
|
96
|
+
} else if (arg === '--version' || arg === '-V') {
|
|
97
|
+
args.flags.version = true;
|
|
98
|
+
} else if (arg === '--dry-run') {
|
|
99
|
+
args.flags.dryRun = true;
|
|
100
|
+
} else if (arg === '--json') {
|
|
101
|
+
args.flags.format = 'json';
|
|
102
|
+
} else if (arg === '--csv') {
|
|
103
|
+
args.flags.format = 'csv';
|
|
104
|
+
} else if (arg === '--minimal') {
|
|
105
|
+
args.flags.format = 'minimal';
|
|
106
|
+
} else if (arg === '--yes' || arg === '-y') {
|
|
107
|
+
args.flags.yes = true;
|
|
108
|
+
} else if (arg === '--verbose' || arg === '-v') {
|
|
109
|
+
args.flags.verbose = true;
|
|
110
|
+
} else if (arg === '--quiet' || arg === '-q') {
|
|
111
|
+
args.flags.quiet = true;
|
|
112
|
+
} else if (arg === '--no-color') {
|
|
113
|
+
args.flags.noColor = true;
|
|
114
|
+
} else if (arg === '--recursive' || arg === '-r') {
|
|
115
|
+
args.flags.recursive = true;
|
|
116
|
+
} else if (arg === '--depth' && raw[i + 1]) {
|
|
117
|
+
args.flags.depth = parseInt(raw[++i], 10);
|
|
118
|
+
} else if (arg === '--naming' && raw[i + 1]) {
|
|
119
|
+
args.flags.naming = raw[++i];
|
|
120
|
+
} else if (arg === '--output' && raw[i + 1]) {
|
|
121
|
+
args.flags.outputDir = raw[++i];
|
|
122
|
+
} else if (arg === '--config' && raw[i + 1]) {
|
|
123
|
+
args.flags.config = raw[++i];
|
|
124
|
+
} else if (arg === '--duplicates' && raw[i + 1]) {
|
|
125
|
+
args.flags.duplicates = raw[++i];
|
|
126
|
+
} else if (arg === '--extensions' && raw[i + 1]) {
|
|
127
|
+
args.flags.extensions = raw[++i].split(',');
|
|
128
|
+
} else if (arg === '--category' && raw[i + 1]) {
|
|
129
|
+
args.flags.category = raw[++i].split(',');
|
|
130
|
+
} else if (arg === '--min-size' && raw[i + 1]) {
|
|
131
|
+
args.flags.minSize = parseSizeArg(raw[++i]);
|
|
132
|
+
} else if (arg === '--max-size' && raw[i + 1]) {
|
|
133
|
+
args.flags.maxSize = parseSizeArg(raw[++i]);
|
|
134
|
+
} else if (arg.startsWith('--')) {
|
|
135
|
+
// Unknown flag with potential value
|
|
136
|
+
const key = arg.slice(2).replace(/-([a-z])/g, (_, l) => l.toUpperCase());
|
|
137
|
+
if (raw[i + 1] && !raw[i + 1].startsWith('-')) {
|
|
138
|
+
args.flags[key] = raw[++i];
|
|
139
|
+
} else {
|
|
140
|
+
args.flags[key] = true;
|
|
141
|
+
}
|
|
142
|
+
} else if (arg.startsWith('-') && arg.length === 2) {
|
|
143
|
+
args.flags[arg.slice(1)] = true;
|
|
144
|
+
} else if (!args.command) {
|
|
145
|
+
args.command = arg;
|
|
146
|
+
} else if (!args.target) {
|
|
147
|
+
args.target = expandTilde(arg);
|
|
148
|
+
} else {
|
|
149
|
+
args.positional.push(arg);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
i++;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return args;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function parseSizeArg(str) {
|
|
159
|
+
const match = str.match(/^(\d+(?:\.\d+)?)\s*(b|kb|mb|gb|tb)?$/i);
|
|
160
|
+
if (!match) return 0;
|
|
161
|
+
const num = parseFloat(match[1]);
|
|
162
|
+
const unit = (match[2] || 'b').toLowerCase();
|
|
163
|
+
const multipliers = { b: 1, kb: 1024, mb: 1024 ** 2, gb: 1024 ** 3, tb: 1024 ** 4 };
|
|
164
|
+
return Math.floor(num * (multipliers[unit] || 1));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ─── Help Text ────────────────────────────────────────────────────
|
|
168
|
+
|
|
169
|
+
function printHelp() {
|
|
170
|
+
console.log(`
|
|
171
|
+
${banner()}
|
|
172
|
+
${c('bold', 'USAGE')}
|
|
173
|
+
${c('cyan', 'filemayor')} ${c('yellow', '<command>')} ${c('dim', '[path]')} ${c('dim', '[options]')}
|
|
174
|
+
|
|
175
|
+
${c('yellow', 'explain')} ${c('dim', '<path>')} Diagnose folder health
|
|
176
|
+
${c('yellow', 'cure')} ${c('dim', '<path>')} Plan treatment using AI
|
|
177
|
+
${c('yellow', 'apply')} Execute the cure plan
|
|
178
|
+
${c('yellow', 'duplicates')} ${c('dim', '<path>')} Find duplicate files
|
|
179
|
+
${c('yellow', 'dedupe')} ${c('dim', '<path>')} Remove duplicate files
|
|
180
|
+
|
|
181
|
+
${c('bold', 'COMMANDS')}
|
|
182
|
+
${c('yellow', 'scan')} ${c('dim', '<path>')} Scan directory and report contents
|
|
183
|
+
${c('yellow', 'analyze')} ${c('dim', '<path>')} Deep analysis (duplicates, bloat, savings)
|
|
184
|
+
${c('yellow', 'organize')} ${c('dim', '<path>')} Organize files into categories
|
|
185
|
+
${c('yellow', 'clean')} ${c('dim', '<path>')} Find and remove junk files
|
|
186
|
+
${c('yellow', 'watch')} ${c('dim', '<path>')} Watch directory for changes
|
|
187
|
+
${c('yellow', 'init')} Create .filemayor.yml config
|
|
188
|
+
${c('yellow', 'undo')} ${c('dim', '<path>')} Undo last organization
|
|
189
|
+
${c('yellow', 'info')} System info and version
|
|
190
|
+
${c('yellow', 'license')} ${c('dim', '<action>')} Manage license (activate|status|deactivate)
|
|
191
|
+
|
|
192
|
+
${c('bold', 'OPTIONS')}
|
|
193
|
+
${c('dim', '--dry-run')} Preview changes without executing
|
|
194
|
+
${c('dim', '--json')} Output as JSON
|
|
195
|
+
${c('dim', '--csv')} Output as CSV
|
|
196
|
+
${c('dim', '--minimal')} Minimal output (paths only)
|
|
197
|
+
${c('dim', '--yes, -y')} Skip confirmation prompts
|
|
198
|
+
${c('dim', '--verbose, -v')} Detailed logging
|
|
199
|
+
${c('dim', '--quiet, -q')} Suppress output (exit code only)
|
|
200
|
+
${c('dim', '--no-color')} Disable colored output
|
|
201
|
+
${c('dim', '--config <path>')} Custom config file
|
|
202
|
+
${c('dim', '--depth <n>')} Max recursion depth
|
|
203
|
+
${c('dim', '--naming <type>')} Naming: original|category_prefix|date_prefix|clean
|
|
204
|
+
${c('dim', '--duplicates <s>')} Duplicates: rename|skip|overwrite
|
|
205
|
+
${c('dim', '--extensions <e>')} Filter by extensions (comma-separated)
|
|
206
|
+
${c('dim', '--output <path>')} Output directory for organized files
|
|
207
|
+
${c('dim', '--category <c>')} Filter by category (comma-separated)
|
|
208
|
+
${c('dim', '--min-size <s>')} Minimum file size (e.g., 1mb)
|
|
209
|
+
${c('dim', '--max-size <s>')} Maximum file size (e.g., 100mb)
|
|
210
|
+
${c('dim', '--help, -h')} Show this help
|
|
211
|
+
${c('dim', '--version, -V')} Show version
|
|
212
|
+
|
|
213
|
+
${c('bold', 'EXAMPLES')}
|
|
214
|
+
${c('dim', '$')} filemayor scan ~/Downloads
|
|
215
|
+
${c('dim', '$')} filemayor organize ~/Downloads --dry-run
|
|
216
|
+
${c('dim', '$')} filemayor organize ~/Downloads --naming category_prefix
|
|
217
|
+
${c('dim', '$')} filemayor clean /var/tmp --yes
|
|
218
|
+
${c('dim', '$')} filemayor scan . --json | jq '.files | length'
|
|
219
|
+
${c('dim', '$')} filemayor watch ~/Downloads
|
|
220
|
+
${c('dim', '$')} filemayor init
|
|
221
|
+
|
|
222
|
+
${c('bold', 'INSTALL')}
|
|
223
|
+
${c('dim', '$')} npm install -g filemayor
|
|
224
|
+
|
|
225
|
+
${c('dim', `FileMayor v${VERSION} — https://filemayor.com`)}
|
|
226
|
+
`);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ─── Commands ─────────────────────────────────────────────────────
|
|
230
|
+
|
|
231
|
+
async function cmdScan(target, flags, config) {
|
|
232
|
+
const targetPath = path.resolve(target || '.');
|
|
233
|
+
const format = flags.format || config.output.format;
|
|
234
|
+
|
|
235
|
+
const spinner = new Spinner(`Scanning ${c('cyan', targetPath)}...`);
|
|
236
|
+
if (format === 'table' && !flags.quiet) spinner.start();
|
|
237
|
+
|
|
238
|
+
try {
|
|
239
|
+
const options = {
|
|
240
|
+
maxDepth: flags.depth || config.scanner.maxDepth,
|
|
241
|
+
includeHidden: config.scanner.includeHidden,
|
|
242
|
+
ignore: config.organize.ignore,
|
|
243
|
+
extensions: flags.extensions || null,
|
|
244
|
+
minSize: flags.minSize || config.scanner.minSize,
|
|
245
|
+
maxSize: flags.maxSize || config.scanner.maxSize || Infinity,
|
|
246
|
+
followSymlinks: config.scanner.followSymlinks,
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
const result = scan(targetPath, options);
|
|
250
|
+
|
|
251
|
+
spinner.stop();
|
|
252
|
+
|
|
253
|
+
if (flags.quiet) {
|
|
254
|
+
process.exit(result.files.length > 0 ? 0 : 1);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
console.log(reporter.formatScanReport(result, format));
|
|
259
|
+
} catch (err) {
|
|
260
|
+
spinner.fail(err.message);
|
|
261
|
+
process.exit(1);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async function cmdAnalyze(target, flags, config) {
|
|
266
|
+
const targetPath = path.resolve(target || '.');
|
|
267
|
+
const format = flags.format || config.output.format;
|
|
268
|
+
|
|
269
|
+
const spinner = new Spinner(`Analyzing ${c('cyan', targetPath)}...`);
|
|
270
|
+
if (format === 'table' && !flags.quiet) spinner.start();
|
|
271
|
+
|
|
272
|
+
try {
|
|
273
|
+
const options = {
|
|
274
|
+
maxDepth: flags.depth || config.scanner.maxDepth,
|
|
275
|
+
minSize: flags.minSize || 1024,
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const result = analyzeDirectory(targetPath, options);
|
|
279
|
+
|
|
280
|
+
spinner.stop();
|
|
281
|
+
|
|
282
|
+
if (flags.quiet) {
|
|
283
|
+
process.exit(result.summary.potentialSavings > 0 ? 0 : 1);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
console.log(reporter.formatAnalyzeReport(result, format));
|
|
288
|
+
} catch (err) {
|
|
289
|
+
spinner.fail(err.message);
|
|
290
|
+
process.exit(1);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async function cmdOrganize(target, flags, config) {
|
|
295
|
+
const targetPath = path.resolve(target || '.');
|
|
296
|
+
const format = flags.format || config.output.format;
|
|
297
|
+
const dryRun = flags.dryRun || false;
|
|
298
|
+
|
|
299
|
+
const spinner = new Spinner(`${dryRun ? 'Planning' : 'Organizing'} ${c('cyan', targetPath)}...`);
|
|
300
|
+
if (format === 'table' && !flags.quiet) spinner.start();
|
|
301
|
+
|
|
302
|
+
try {
|
|
303
|
+
// Bulk limit check — generate plan first to count operations
|
|
304
|
+
if (!dryRun) {
|
|
305
|
+
const preview = await organize(targetPath, {
|
|
306
|
+
dryRun: true,
|
|
307
|
+
naming: flags.naming || config.organize.naming,
|
|
308
|
+
outputDir: flags.outputDir || null,
|
|
309
|
+
duplicateStrategy: flags.duplicates || config.organize.duplicates,
|
|
310
|
+
scanOptions: {
|
|
311
|
+
maxDepth: flags.depth || config.organize.maxDepth,
|
|
312
|
+
includeHidden: config.organize.includeHidden,
|
|
313
|
+
ignore: config.organize.ignore,
|
|
314
|
+
extensions: flags.extensions || null,
|
|
315
|
+
},
|
|
316
|
+
});
|
|
317
|
+
const opCount = preview?.plan?.length || 0;
|
|
318
|
+
const gate = checkBulkLimit(opCount);
|
|
319
|
+
if (!gate.allowed) {
|
|
320
|
+
spinner.stop();
|
|
321
|
+
console.log('\n' + gate.message);
|
|
322
|
+
process.exit(0);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const result = await organize(targetPath, {
|
|
327
|
+
dryRun,
|
|
328
|
+
naming: flags.naming || config.organize.naming,
|
|
329
|
+
outputDir: flags.outputDir || null,
|
|
330
|
+
duplicateStrategy: flags.duplicates || config.organize.duplicates,
|
|
331
|
+
scanOptions: {
|
|
332
|
+
maxDepth: flags.depth || config.organize.maxDepth,
|
|
333
|
+
includeHidden: config.organize.includeHidden,
|
|
334
|
+
ignore: config.organize.ignore,
|
|
335
|
+
extensions: flags.extensions || null,
|
|
336
|
+
},
|
|
337
|
+
onProgress: format === 'table' ? (p) => {
|
|
338
|
+
spinner.update(`${dryRun ? 'Planning' : 'Organizing'} ${p.percent}% — ${p.file}`);
|
|
339
|
+
} : null,
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
spinner.stop();
|
|
343
|
+
|
|
344
|
+
if (flags.quiet) {
|
|
345
|
+
process.exit(0);
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
console.log(reporter.formatOrganizeReport(result, format));
|
|
350
|
+
|
|
351
|
+
if (!dryRun && result.execSummary) {
|
|
352
|
+
const exec = result.execSummary;
|
|
353
|
+
if (exec.failed > 0) process.exit(1);
|
|
354
|
+
}
|
|
355
|
+
} catch (err) {
|
|
356
|
+
spinner.fail(err.message);
|
|
357
|
+
process.exit(1);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
async function cmdClean(target, flags, config) {
|
|
362
|
+
const targetPath = path.resolve(target || '.');
|
|
363
|
+
const format = flags.format || config.output.format;
|
|
364
|
+
const dryRun = flags.dryRun || !flags.yes;
|
|
365
|
+
|
|
366
|
+
// If --yes not passed and not --dry-run, force dry run
|
|
367
|
+
if (!flags.yes && !flags.dryRun) {
|
|
368
|
+
console.log(warn('Running in dry-run mode. Use --yes to actually delete files.'));
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const spinner = new Spinner(`Scanning for junk in ${c('cyan', targetPath)}...`);
|
|
372
|
+
if (format === 'table' && !flags.quiet) spinner.start();
|
|
373
|
+
|
|
374
|
+
try {
|
|
375
|
+
const result = clean(targetPath, {
|
|
376
|
+
dryRun,
|
|
377
|
+
categories: flags.category || config.clean.categories,
|
|
378
|
+
maxDepth: flags.depth || config.clean.maxDepth,
|
|
379
|
+
includeDirectories: config.clean.includeDirectories,
|
|
380
|
+
onProgress: format === 'table' ? (p) => {
|
|
381
|
+
spinner.update(`${p.phase === 'scanning' ? 'Scanning' : 'Deleting'} ${p.current || ''}...`);
|
|
382
|
+
} : null,
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
spinner.stop();
|
|
386
|
+
|
|
387
|
+
if (flags.quiet) {
|
|
388
|
+
process.exit(result.junk.length > 0 ? 0 : 1);
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
console.log(reporter.formatCleanReport(result, format));
|
|
393
|
+
} catch (err) {
|
|
394
|
+
spinner.fail(err.message);
|
|
395
|
+
process.exit(1);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
async function cmdWatch(target, flags, config) {
|
|
400
|
+
const targetPath = path.resolve(target || '.');
|
|
401
|
+
const format = flags.format || config.output.format;
|
|
402
|
+
|
|
403
|
+
console.log(banner());
|
|
404
|
+
console.log(info(`Watching ${c('cyan', targetPath)} for changes...`));
|
|
405
|
+
console.log(c('dim', ' Press Ctrl+C to stop\n'));
|
|
406
|
+
|
|
407
|
+
const watcher = new FileWatcher({
|
|
408
|
+
directories: [targetPath, ...(config.watch.directories || [])],
|
|
409
|
+
rules: config.watch.rules || [],
|
|
410
|
+
debounceMs: config.watch.debounceMs || 500,
|
|
411
|
+
recursive: flags.recursive !== false,
|
|
412
|
+
autoOrganize: config.watch.autoOrganize || false,
|
|
413
|
+
organizeOptions: {
|
|
414
|
+
naming: config.organize.naming,
|
|
415
|
+
duplicateStrategy: config.organize.duplicates,
|
|
416
|
+
},
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
watcher.on('watching', (data) => {
|
|
420
|
+
console.log(success(`Watching: ${data.directory}`));
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
watcher.on('change', (event) => {
|
|
424
|
+
if (format === 'json') {
|
|
425
|
+
console.log(JSON.stringify(event));
|
|
426
|
+
} else {
|
|
427
|
+
const action = event.exists
|
|
428
|
+
? c('green', event.type === 'rename' ? 'NEW' : 'CHG')
|
|
429
|
+
: c('red', 'DEL');
|
|
430
|
+
const cat = event.category ? c('dim', ` [${event.category}]`) : '';
|
|
431
|
+
console.log(` ${action} ${event.filename}${cat} ${c('dim', event.sizeHuman)}`);
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
watcher.on('action', (data) => {
|
|
436
|
+
console.log(` ${c('yellow', '→')} ${data.action}: ${path.basename(data.source)} → ${data.destination || ''}`);
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
watcher.on('error', (err) => {
|
|
440
|
+
console.error(error(err.error || err.message));
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
watcher.start();
|
|
444
|
+
|
|
445
|
+
// Handle shutdown
|
|
446
|
+
process.on('SIGINT', () => {
|
|
447
|
+
console.log('\n');
|
|
448
|
+
watcher.stop();
|
|
449
|
+
const stats = watcher.getStats();
|
|
450
|
+
console.log(info(`Processed ${stats.eventsProcessed} events, ${stats.filesActioned} actions`));
|
|
451
|
+
process.exit(0);
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
async function cmdInit(flags) {
|
|
456
|
+
try {
|
|
457
|
+
const configPath = createConfigFile(process.cwd());
|
|
458
|
+
console.log(success(`Created ${c('cyan', configPath)}`));
|
|
459
|
+
console.log(c('dim', ' Edit this file to customize FileMayor settings'));
|
|
460
|
+
} catch (err) {
|
|
461
|
+
console.error(error(err.message));
|
|
462
|
+
process.exit(1);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
async function cmdUndo(target, flags) {
|
|
467
|
+
const { JOURNAL_PATH, rollback: fsRollback } = require('./core/fs-abstraction');
|
|
468
|
+
|
|
469
|
+
if (!fs.existsSync(JOURNAL_PATH)) {
|
|
470
|
+
console.log(info('No operations to undo (journal is empty)'));
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const spinner = new Spinner('Undoing operations...');
|
|
475
|
+
spinner.start();
|
|
476
|
+
|
|
477
|
+
try {
|
|
478
|
+
const result = await fsRollback(JOURNAL_PATH);
|
|
479
|
+
spinner.stop();
|
|
480
|
+
if (result.count === 0) {
|
|
481
|
+
console.log(info('Nothing to undo — no completed operations found in the journal'));
|
|
482
|
+
} else {
|
|
483
|
+
console.log(success(`Undone ${result.count} operation${result.count === 1 ? '' : 's'}`));
|
|
484
|
+
}
|
|
485
|
+
} catch (err) {
|
|
486
|
+
spinner.fail(err.message);
|
|
487
|
+
process.exit(1);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// ==================== DIAGNOSTIC TRIAD (v3.5) ====================
|
|
492
|
+
|
|
493
|
+
let activePlanState = null;
|
|
494
|
+
|
|
495
|
+
async function cmdExplain(target, flags) {
|
|
496
|
+
const targetPath = path.resolve(target || '.');
|
|
497
|
+
const spinner = new Spinner(`Diagnosing ${c('cyan', targetPath)}...`);
|
|
498
|
+
spinner.start();
|
|
499
|
+
|
|
500
|
+
try {
|
|
501
|
+
const engine = new ExplainEngine();
|
|
502
|
+
const result = engine.run(targetPath);
|
|
503
|
+
spinner.stop();
|
|
504
|
+
console.log(reporter.formatExplainReport(result));
|
|
505
|
+
} catch (err) {
|
|
506
|
+
spinner.fail(err.message);
|
|
507
|
+
process.exit(1);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
async function cmdCure(target, flags) {
|
|
512
|
+
const targetPath = path.resolve(target || '.');
|
|
513
|
+
const prompt = flags.prompt || 'Clean up this folder and group similar files.';
|
|
514
|
+
|
|
515
|
+
if (!process.env.GEMINI_API_KEY) {
|
|
516
|
+
console.log(error('GEMINI_API_KEY environment variable is required for AI curative features.'));
|
|
517
|
+
process.exit(1);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const spinner = new Spinner(`Consulting AI for ${c('cyan', targetPath)}...`);
|
|
521
|
+
spinner.start();
|
|
522
|
+
|
|
523
|
+
try {
|
|
524
|
+
const engine = new CureEngine(targetPath, process.env.GEMINI_API_KEY);
|
|
525
|
+
activePlanState = await engine.generatePlan(prompt);
|
|
526
|
+
spinner.stop();
|
|
527
|
+
|
|
528
|
+
// Cache plan locally for 'apply' command
|
|
529
|
+
const cachePath = path.join(os.tmpdir(), 'filemayor-plan.json');
|
|
530
|
+
fs.writeFileSync(cachePath, JSON.stringify(activePlanState), 'utf8');
|
|
531
|
+
|
|
532
|
+
console.log(reporter.formatCureReport(activePlanState));
|
|
533
|
+
} catch (err) {
|
|
534
|
+
spinner.fail(err.message);
|
|
535
|
+
process.exit(1);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
async function cmdApply(flags) {
|
|
540
|
+
const cachePath = path.join(os.tmpdir(), 'filemayor-plan.json');
|
|
541
|
+
if (!fs.existsSync(cachePath)) {
|
|
542
|
+
console.log(error('No plan found. Run "filemayor cure" first.'));
|
|
543
|
+
process.exit(1);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
const plan = JSON.parse(fs.readFileSync(cachePath, 'utf8'));
|
|
547
|
+
|
|
548
|
+
// [Sprint B] Logic Guardrail: Check batch volume & destructive patterns
|
|
549
|
+
const LogicGuardrail = require('./core/guardrail');
|
|
550
|
+
const { FileMayorJailer } = require('./core/jailer');
|
|
551
|
+
const guardrail = new LogicGuardrail(50);
|
|
552
|
+
const jailer = new FileMayorJailer(process.cwd());
|
|
553
|
+
|
|
554
|
+
if (plan.plan && Array.isArray(plan.plan)) {
|
|
555
|
+
const approved = await guardrail.verifyBatch(plan.plan);
|
|
556
|
+
if (!approved) {
|
|
557
|
+
console.log(warn('Operation cancelled by user.'));
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// [Sprint B] Jailer: Validate every move before execution
|
|
562
|
+
const blocked = [];
|
|
563
|
+
for (const step of plan.plan) {
|
|
564
|
+
const check = jailer.validateMove(step.source, step.destination);
|
|
565
|
+
if (!check.safe) {
|
|
566
|
+
blocked.push({ file: path.basename(step.source), reason: check.error });
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
if (blocked.length > 0) {
|
|
570
|
+
console.log(error(`Jailer blocked ${blocked.length} unsafe operations:`));
|
|
571
|
+
for (const b of blocked) {
|
|
572
|
+
console.log(c('dim', ` ✗ ${b.file}: ${b.reason}`));
|
|
573
|
+
}
|
|
574
|
+
plan.plan = plan.plan.filter(step => jailer.validateMove(step.source, step.destination).safe);
|
|
575
|
+
if (plan.plan.length === 0) {
|
|
576
|
+
console.log(error('No safe operations remain. Aborting.'));
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
console.log(info(`Proceeding with ${plan.plan.length} safe operations.`));
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
const spinner = new Spinner(`Executing cure...`);
|
|
584
|
+
spinner.start();
|
|
585
|
+
|
|
586
|
+
try {
|
|
587
|
+
const engine = new ApplyEngine();
|
|
588
|
+
const result = await engine.apply(plan, (p) => {
|
|
589
|
+
spinner.update(`Relocating: ${p.index}/${p.total} — ${path.basename(p.current || '')}`);
|
|
590
|
+
});
|
|
591
|
+
spinner.succeed(`Cure applied! ${result.stats.success} files moved.`);
|
|
592
|
+
fs.unlinkSync(cachePath); // Clear plan
|
|
593
|
+
} catch (err) {
|
|
594
|
+
spinner.fail(err.message);
|
|
595
|
+
process.exit(1);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
async function cmdDuplicates(target, flags) {
|
|
600
|
+
const targetPath = path.resolve(target || '.');
|
|
601
|
+
const spinner = new Spinner(`Hunting duplicates in ${c('cyan', targetPath)}...`);
|
|
602
|
+
spinner.start();
|
|
603
|
+
|
|
604
|
+
try {
|
|
605
|
+
const engine = new DedupeEngine();
|
|
606
|
+
const result = engine.find(targetPath);
|
|
607
|
+
spinner.stop();
|
|
608
|
+
console.log(reporter.formatDedupeReport(result));
|
|
609
|
+
} catch (err) {
|
|
610
|
+
spinner.fail(err.message);
|
|
611
|
+
process.exit(1);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
async function cmdDedupe(target, flags) {
|
|
616
|
+
const targetPath = path.resolve(target || '.');
|
|
617
|
+
const spinner = new Spinner(`Deduplicating ${c('cyan', targetPath)}...`);
|
|
618
|
+
spinner.start();
|
|
619
|
+
|
|
620
|
+
try {
|
|
621
|
+
const engine = new DedupeEngine();
|
|
622
|
+
const report = engine.find(targetPath);
|
|
623
|
+
if (report.sets === 0) {
|
|
624
|
+
spinner.succeed('Total order restored: No duplicates found.');
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
const result = await engine.clean(report);
|
|
629
|
+
spinner.succeed(`Purged ${result.deleted} duplicates. Reclaimed ${result.freedHuman}.`);
|
|
630
|
+
} catch (err) {
|
|
631
|
+
spinner.fail(err.message);
|
|
632
|
+
process.exit(1);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
async function cmdInfo(config) {
|
|
637
|
+
console.log(banner());
|
|
638
|
+
console.log(` ${c('bold', 'Version')} ${VERSION}`);
|
|
639
|
+
console.log(` ${c('bold', 'Node')} ${process.version}`);
|
|
640
|
+
console.log(` ${c('bold', 'Platform')} ${os.platform()} ${os.arch()}`);
|
|
641
|
+
console.log(` ${c('bold', 'OS')} ${os.type()} ${os.release()}`);
|
|
642
|
+
console.log(` ${c('bold', 'Home')} ${os.homedir()}`);
|
|
643
|
+
console.log(` ${c('bold', 'CWD')} ${process.cwd()}`);
|
|
644
|
+
|
|
645
|
+
// Config info
|
|
646
|
+
console.log(` ${c('bold', 'Config')} ${config._source || 'defaults'}`);
|
|
647
|
+
|
|
648
|
+
// Categories
|
|
649
|
+
const cats = getCategories();
|
|
650
|
+
const catCount = Object.keys(cats).length;
|
|
651
|
+
const extCount = Object.values(cats).reduce((sum, cat) => sum + cat.extensions.length, 0);
|
|
652
|
+
console.log(` ${c('bold', 'Categories')} ${catCount} categories, ${extCount} extensions`);
|
|
653
|
+
|
|
654
|
+
// Permissions
|
|
655
|
+
const perms = checkPermissions(process.cwd());
|
|
656
|
+
const permStr = `${perms.read ? c('green', 'R') : c('red', '-')}${perms.write ? c('green', 'W') : c('red', '-')}${perms.execute ? c('green', 'X') : c('red', '-')}`;
|
|
657
|
+
console.log(` ${c('bold', 'Permissions')} ${permStr} (current directory)`);
|
|
658
|
+
|
|
659
|
+
// Telemetry disclosure
|
|
660
|
+
const telemetryOff = process.env.FILEMAYOR_NO_TELEMETRY === '1';
|
|
661
|
+
console.log(` ${c('bold', 'Telemetry')} ${telemetryOff ? c('dim', 'off (FILEMAYOR_NO_TELEMETRY=1)') : c('dim', 'on — anonymous version+OS+command ping once per version. Set FILEMAYOR_NO_TELEMETRY=1 to disable.')}`);
|
|
662
|
+
|
|
663
|
+
console.log('');
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// ─── License Command ──────────────────────────────────────────────
|
|
667
|
+
|
|
668
|
+
async function cmdLicense(action, positional, flags) {
|
|
669
|
+
const subAction = action || 'status';
|
|
670
|
+
|
|
671
|
+
switch (subAction) {
|
|
672
|
+
case 'activate': {
|
|
673
|
+
const key = positional[0] || flags.key;
|
|
674
|
+
if (!key) {
|
|
675
|
+
console.error(error('Usage: filemayor license activate <key>'));
|
|
676
|
+
console.log(c('dim', ' Example: filemayor license activate FM-PRO-XXXX-XXXX-XXXXX'));
|
|
677
|
+
process.exit(1);
|
|
678
|
+
}
|
|
679
|
+
const result = activateLicense(key);
|
|
680
|
+
if (result.success) {
|
|
681
|
+
console.log(success(result.message));
|
|
682
|
+
console.log(c('dim', ` Tier: ${result.tier}`));
|
|
683
|
+
} else {
|
|
684
|
+
console.error(error(result.message));
|
|
685
|
+
process.exit(1);
|
|
686
|
+
}
|
|
687
|
+
break;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
case 'deactivate':
|
|
691
|
+
case 'remove': {
|
|
692
|
+
const result = deactivateLicense();
|
|
693
|
+
if (result.success) {
|
|
694
|
+
console.log(success(result.message));
|
|
695
|
+
} else {
|
|
696
|
+
console.log(info(result.message));
|
|
697
|
+
}
|
|
698
|
+
break;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
case 'status':
|
|
702
|
+
default: {
|
|
703
|
+
const li = getLicenseInfo();
|
|
704
|
+
console.log(banner());
|
|
705
|
+
console.log(c('bold', ' License Status'));
|
|
706
|
+
console.log(c('dim', ` ${'─'.repeat(40)}`));
|
|
707
|
+
console.log(` ${c('bold', 'Tier:')} ${li.active ? c('green', li.name) : c('yellow', 'Free')}`);
|
|
708
|
+
console.log(` ${c('bold', 'Status:')} ${li.active ? c('green', '● Active') : c('dim', '○ No license')}`);
|
|
709
|
+
if (li.key) {
|
|
710
|
+
const masked = li.key.substring(0, 7) + '****' + li.key.substring(li.key.length - 5);
|
|
711
|
+
console.log(` ${c('bold', 'Key:')} ${c('dim', masked)}`);
|
|
712
|
+
}
|
|
713
|
+
if (li.activatedAt) {
|
|
714
|
+
console.log(` ${c('bold', 'Activated:')} ${c('dim', new Date(li.activatedAt).toLocaleDateString())}`);
|
|
715
|
+
}
|
|
716
|
+
console.log('');
|
|
717
|
+
console.log(c('bold', ' Features'));
|
|
718
|
+
console.log(c('dim', ` ${'─'.repeat(40)}`));
|
|
719
|
+
const allFeatures = [
|
|
720
|
+
{ id: 'scan', label: 'Directory Scanning', free: true },
|
|
721
|
+
{ id: 'organize', label: 'File Organization', free: true },
|
|
722
|
+
{ id: 'clean', label: 'Junk Cleanup', free: true },
|
|
723
|
+
{ id: 'undo', label: 'Undo Operations', free: true },
|
|
724
|
+
{ id: 'watch', label: 'Watch Mode', free: true },
|
|
725
|
+
{ id: 'sop-ai', label: 'AI SOP Parsing', free: true },
|
|
726
|
+
{ id: 'bulk-organize', label: 'Bulk Organize', free: true },
|
|
727
|
+
{ id: 'csv-export', label: 'CSV Export', free: true },
|
|
728
|
+
];
|
|
729
|
+
for (const feat of allFeatures) {
|
|
730
|
+
const has = li.features.includes('*') || li.features.includes(feat.id);
|
|
731
|
+
const icon = has ? c('green', '✓') : c('dim', '○');
|
|
732
|
+
const label = has ? feat.label : c('dim', feat.label);
|
|
733
|
+
const tag = !feat.free && !has ? c('magenta', ' [PRO]') : '';
|
|
734
|
+
console.log(` ${icon} ${label}${tag}`);
|
|
735
|
+
}
|
|
736
|
+
console.log('');
|
|
737
|
+
if (!li.active) {
|
|
738
|
+
console.log(c('dim', ' All features are free. License keys are optional: https://filemayor.com'));
|
|
739
|
+
console.log(c('dim', ' Activate: filemayor license activate <key>'));
|
|
740
|
+
console.log('');
|
|
741
|
+
}
|
|
742
|
+
break;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// ─── Main Entry Point ─────────────────────────────────────────────
|
|
748
|
+
|
|
749
|
+
async function main() {
|
|
750
|
+
const args = parseArgs(process.argv);
|
|
751
|
+
|
|
752
|
+
// Version
|
|
753
|
+
if (args.flags.version) {
|
|
754
|
+
console.log(VERSION);
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
// Colors
|
|
759
|
+
if (args.flags.noColor || process.env.NO_COLOR) {
|
|
760
|
+
reporter.setColors(false);
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// Load config
|
|
764
|
+
let config;
|
|
765
|
+
try {
|
|
766
|
+
const configResult = loadConfig({
|
|
767
|
+
configPath: args.flags.config || null,
|
|
768
|
+
cliOverrides: {},
|
|
769
|
+
});
|
|
770
|
+
config = configResult.config;
|
|
771
|
+
config._source = configResult.source;
|
|
772
|
+
|
|
773
|
+
// Show validation warnings
|
|
774
|
+
if (configResult.validation.warnings.length > 0 && args.flags.verbose) {
|
|
775
|
+
for (const w of configResult.validation.warnings) {
|
|
776
|
+
console.error(warn(`Config: ${w}`));
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
if (!configResult.validation.valid) {
|
|
780
|
+
for (const e of configResult.validation.errors) {
|
|
781
|
+
console.error(error(`Config: ${e}`));
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
} catch (err) {
|
|
785
|
+
console.error(error(`Config error: ${err.message}`));
|
|
786
|
+
process.exit(1);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// No command — show help
|
|
790
|
+
if (!args.command || args.flags.help) {
|
|
791
|
+
printHelp();
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// Anonymous first-run ping (fire-and-forget, opt-out with FILEMAYOR_NO_TELEMETRY=1)
|
|
796
|
+
telemetryPing(VERSION, args.command);
|
|
797
|
+
|
|
798
|
+
// Route commands
|
|
799
|
+
switch (args.command) {
|
|
800
|
+
case 'scan':
|
|
801
|
+
case 's':
|
|
802
|
+
await cmdScan(args.target, args.flags, config);
|
|
803
|
+
break;
|
|
804
|
+
|
|
805
|
+
case 'explain':
|
|
806
|
+
case 'dx':
|
|
807
|
+
await cmdExplain(args.target, args.flags);
|
|
808
|
+
break;
|
|
809
|
+
|
|
810
|
+
case 'cure':
|
|
811
|
+
case 'plan':
|
|
812
|
+
await cmdCure(args.target, args.flags);
|
|
813
|
+
break;
|
|
814
|
+
|
|
815
|
+
case 'apply':
|
|
816
|
+
case 'exec':
|
|
817
|
+
await cmdApply(args.flags);
|
|
818
|
+
break;
|
|
819
|
+
|
|
820
|
+
case 'duplicates':
|
|
821
|
+
case 'dupes':
|
|
822
|
+
await cmdDuplicates(args.target, args.flags);
|
|
823
|
+
break;
|
|
824
|
+
|
|
825
|
+
case 'dedupe':
|
|
826
|
+
await cmdDedupe(args.target, args.flags);
|
|
827
|
+
break;
|
|
828
|
+
|
|
829
|
+
case 'analyze':
|
|
830
|
+
case 'a':
|
|
831
|
+
await cmdAnalyze(args.target, args.flags, config);
|
|
832
|
+
break;
|
|
833
|
+
|
|
834
|
+
case 'organize':
|
|
835
|
+
case 'org':
|
|
836
|
+
case 'o':
|
|
837
|
+
await cmdOrganize(args.target, args.flags, config);
|
|
838
|
+
break;
|
|
839
|
+
|
|
840
|
+
case 'clean':
|
|
841
|
+
case 'cl':
|
|
842
|
+
case 'c':
|
|
843
|
+
await cmdClean(args.target, args.flags, config);
|
|
844
|
+
break;
|
|
845
|
+
|
|
846
|
+
case 'watch':
|
|
847
|
+
case 'w':
|
|
848
|
+
await cmdWatch(args.target, args.flags, config);
|
|
849
|
+
break;
|
|
850
|
+
|
|
851
|
+
case 'init':
|
|
852
|
+
case 'i':
|
|
853
|
+
await cmdInit(args.flags);
|
|
854
|
+
break;
|
|
855
|
+
|
|
856
|
+
case 'undo':
|
|
857
|
+
case 'u':
|
|
858
|
+
await cmdUndo(args.target, args.flags);
|
|
859
|
+
break;
|
|
860
|
+
|
|
861
|
+
case 'info':
|
|
862
|
+
await cmdInfo(config);
|
|
863
|
+
break;
|
|
864
|
+
|
|
865
|
+
case 'license':
|
|
866
|
+
case 'lic':
|
|
867
|
+
case 'l':
|
|
868
|
+
await cmdLicense(args.target, args.positional, args.flags);
|
|
869
|
+
break;
|
|
870
|
+
|
|
871
|
+
case 'help':
|
|
872
|
+
printHelp();
|
|
873
|
+
break;
|
|
874
|
+
|
|
875
|
+
case 'mcp':
|
|
876
|
+
// Start the MCP server (STDIO transport)
|
|
877
|
+
require('./mcp-server').startServer();
|
|
878
|
+
return; // Keep process alive — server runs until stdin closes
|
|
879
|
+
|
|
880
|
+
default:
|
|
881
|
+
console.error(error(`Unknown command: "${args.command}"`));
|
|
882
|
+
console.log(c('dim', 'Run "filemayor --help" for usage'));
|
|
883
|
+
process.exit(1);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// ─── Run ──────────────────────────────────────────────────────────
|
|
888
|
+
|
|
889
|
+
main().catch(err => {
|
|
890
|
+
console.error(error(err.message));
|
|
891
|
+
if (process.env.DEBUG) {
|
|
892
|
+
console.error(err.stack);
|
|
893
|
+
}
|
|
894
|
+
process.exit(1);
|
|
895
|
+
});
|