flowsquire 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js ADDED
@@ -0,0 +1,926 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const index_js_1 = require("./db/index.js");
8
+ const file_watcher_js_1 = require("./watchers/file-watcher.js");
9
+ const index_js_2 = require("./db/index.js");
10
+ const crypto_1 = require("crypto");
11
+ const index_js_3 = require("./config/index.js");
12
+ const rule_engine_js_1 = require("./core/rule-engine.js");
13
+ const readline_1 = __importDefault(require("readline"));
14
+ const path_1 = __importDefault(require("path"));
15
+ const os_1 = __importDefault(require("os"));
16
+ const promises_1 = __importDefault(require("fs/promises"));
17
+ const args = process.argv.slice(2);
18
+ const command = args[0];
19
+ async function main() {
20
+ switch (command) {
21
+ case 'start':
22
+ await startAgent();
23
+ break;
24
+ case 'init':
25
+ await initWithTemplates();
26
+ break;
27
+ case 'rules':
28
+ await listRules();
29
+ break;
30
+ case 'run':
31
+ await runRule(args[1]);
32
+ break;
33
+ case 'config':
34
+ await handleConfigCommand(args.slice(1));
35
+ break;
36
+ default:
37
+ console.log(`
38
+ FlowSquire Agent - Local file automation
39
+
40
+ Usage:
41
+ flowsquire start Start the file watcher agent
42
+ flowsquire init Run interactive setup wizard
43
+ flowsquire rules List all rules
44
+ flowsquire config Show all configured paths and settings
45
+ flowsquire config --<key> <value> Set a config value (path or mode)
46
+ flowsquire config --<key> Get a config value
47
+
48
+ Config Keys:
49
+ Paths: --downloads, --documents, --screenshots, --pictures, --videos, --music
50
+ Modes: --downloads-mode <nested|system>, --screenshot-mode <metadata|by-app|by-date>
51
+
52
+ Options:
53
+ --dry-run Preview actions without executing (only with start)
54
+ `);
55
+ }
56
+ }
57
+ async function startAgent() {
58
+ console.log('šŸš€ FlowSquire Agent starting...\n');
59
+ // Initialize database
60
+ await (0, index_js_1.initDatabase)();
61
+ // Load enabled rules
62
+ const rules = await (0, index_js_2.getEnabledRules)();
63
+ if (rules.length === 0) {
64
+ console.log('āš ļø No enabled rules found. Run "flowsquire init" to create default rules.');
65
+ process.exit(1);
66
+ }
67
+ console.log(`šŸ“‹ Loaded ${rules.length} rule(s)\n`);
68
+ // Start file watcher
69
+ const dryRun = process.argv.includes('--dry-run');
70
+ const watcher = new file_watcher_js_1.FileWatcher({ rules, dryRun });
71
+ await watcher.start();
72
+ // Handle graceful shutdown
73
+ process.on('SIGINT', () => {
74
+ console.log('\n\nšŸ‘‹ Shutting down...');
75
+ watcher.stop();
76
+ process.exit(0);
77
+ });
78
+ process.on('SIGTERM', () => {
79
+ watcher.stop();
80
+ process.exit(0);
81
+ });
82
+ }
83
+ async function initWithTemplates() {
84
+ console.log('šŸ“ Initializing FlowSquire with default templates...\n');
85
+ await (0, index_js_1.initDatabase)();
86
+ // Load or create config
87
+ const config = await (0, index_js_3.loadConfig)();
88
+ // Check if this is first-time setup (no rules exist yet)
89
+ const existingRules = await (0, index_js_2.getEnabledRules)();
90
+ if (existingRules.length === 0) {
91
+ // Interactive setup for first-time users
92
+ await runInteractiveSetup(config);
93
+ }
94
+ // Reload config after potential changes
95
+ const finalConfig = await (0, index_js_3.loadConfig)();
96
+ // Create PDF Workflow Template Rules (in priority order)
97
+ // Determine PDF destinations based on downloadsMode setting
98
+ const isNestedMode = finalConfig.settings.downloadsMode === 'nested';
99
+ const pdfDestinations = {
100
+ compressed: isNestedMode ? '{downloads}/PDFs/Compressed' : '{documents}/PDFs/Compressed',
101
+ invoices: isNestedMode ? '{downloads}/PDFs/Invoices' : '{documents}/PDFs/Invoices',
102
+ finance: isNestedMode ? '{downloads}/PDFs/Finance' : '{documents}/PDFs/Finance',
103
+ study: isNestedMode ? '{downloads}/PDFs/Study' : '{documents}/PDFs/Study',
104
+ unsorted: isNestedMode ? '{downloads}/PDFs/Unsorted' : '{documents}/PDFs/Unsorted',
105
+ };
106
+ // Rule 1: Large PDF Compression (Priority 500 - highest, only for files > 8MB)
107
+ const largePdfCompressionRule = {
108
+ id: (0, crypto_1.randomUUID)(),
109
+ name: 'Large PDF Compression',
110
+ enabled: true,
111
+ priority: 500,
112
+ tags: ['pdf', 'compression', 'large-files'],
113
+ trigger: {
114
+ type: 'file_created',
115
+ config: { folder: '{downloads}' },
116
+ },
117
+ conditions: [
118
+ { type: 'extension', operator: 'equals', value: 'pdf' },
119
+ { type: 'size_greater_than_mb', operator: 'equals', value: 8 },
120
+ ],
121
+ actions: [
122
+ {
123
+ type: 'compress',
124
+ config: {
125
+ destination: pdfDestinations.compressed,
126
+ pattern: '{filename}_compressed',
127
+ createDirs: true,
128
+ compress: {
129
+ quality: 'medium',
130
+ archiveOriginal: true,
131
+ },
132
+ },
133
+ },
134
+ ],
135
+ createdAt: new Date(),
136
+ updatedAt: new Date(),
137
+ };
138
+ // Rule 2: Invoice PDFs (Priority 400)
139
+ const invoiceRule = {
140
+ id: (0, crypto_1.randomUUID)(),
141
+ name: 'PDF Invoice Organizer',
142
+ enabled: true,
143
+ priority: 400,
144
+ tags: ['pdf', 'invoice', 'finance'],
145
+ trigger: {
146
+ type: 'file_created',
147
+ config: { folder: '{downloads}' },
148
+ },
149
+ conditions: [
150
+ { type: 'extension', operator: 'equals', value: 'pdf' },
151
+ { type: 'name_contains', operator: 'equals', value: 'invoice' },
152
+ ],
153
+ actions: [
154
+ {
155
+ type: 'move',
156
+ config: {
157
+ destination: pdfDestinations.invoices,
158
+ pattern: '{filename}_{YYYY}-{MM}-{DD}',
159
+ createDirs: true,
160
+ },
161
+ },
162
+ ],
163
+ createdAt: new Date(),
164
+ updatedAt: new Date(),
165
+ };
166
+ // Rule 3: Bank/Statement PDFs (Priority 300)
167
+ const bankRule = {
168
+ id: (0, crypto_1.randomUUID)(),
169
+ name: 'PDF Bank Statement Organizer',
170
+ enabled: true,
171
+ priority: 300,
172
+ tags: ['pdf', 'bank', 'finance', 'statement'],
173
+ trigger: {
174
+ type: 'file_created',
175
+ config: { folder: '{downloads}' },
176
+ },
177
+ conditions: [
178
+ { type: 'extension', operator: 'equals', value: 'pdf' },
179
+ { type: 'name_contains', operator: 'equals', value: 'bank' },
180
+ ],
181
+ actions: [
182
+ {
183
+ type: 'move',
184
+ config: {
185
+ destination: pdfDestinations.finance,
186
+ pattern: '{filename}_{YYYY}-{MM}',
187
+ createDirs: true,
188
+ },
189
+ },
190
+ ],
191
+ createdAt: new Date(),
192
+ updatedAt: new Date(),
193
+ };
194
+ // Rule 4: Notes/College PDFs (Priority 200)
195
+ const notesRule = {
196
+ id: (0, crypto_1.randomUUID)(),
197
+ name: 'PDF Study Notes Organizer',
198
+ enabled: true,
199
+ priority: 200,
200
+ tags: ['pdf', 'study', 'notes', 'college'],
201
+ trigger: {
202
+ type: 'file_created',
203
+ config: { folder: '{downloads}' },
204
+ },
205
+ conditions: [
206
+ { type: 'extension', operator: 'equals', value: 'pdf' },
207
+ { type: 'name_contains', operator: 'equals', value: 'notes' },
208
+ ],
209
+ actions: [
210
+ {
211
+ type: 'move',
212
+ config: {
213
+ destination: pdfDestinations.study,
214
+ pattern: '{filename}_{YYYY}-{MM}-{DD}',
215
+ createDirs: true,
216
+ },
217
+ },
218
+ ],
219
+ createdAt: new Date(),
220
+ updatedAt: new Date(),
221
+ };
222
+ // Rule 5: Default PDF Organizer (Priority 100 - lowest)
223
+ const defaultPdfRule = {
224
+ id: (0, crypto_1.randomUUID)(),
225
+ name: 'PDF Default Organizer',
226
+ enabled: true,
227
+ priority: 100,
228
+ tags: ['pdf', 'default'],
229
+ trigger: {
230
+ type: 'file_created',
231
+ config: { folder: '{downloads}' },
232
+ },
233
+ conditions: [
234
+ { type: 'extension', operator: 'equals', value: 'pdf' },
235
+ ],
236
+ actions: [
237
+ {
238
+ type: 'move',
239
+ config: {
240
+ destination: pdfDestinations.unsorted,
241
+ createDirs: true,
242
+ },
243
+ },
244
+ ],
245
+ createdAt: new Date(),
246
+ updatedAt: new Date(),
247
+ };
248
+ // Save all rules
249
+ await (0, index_js_2.saveRule)(largePdfCompressionRule);
250
+ await (0, index_js_2.saveRule)(invoiceRule);
251
+ await (0, index_js_2.saveRule)(bankRule);
252
+ await (0, index_js_2.saveRule)(notesRule);
253
+ await (0, index_js_2.saveRule)(defaultPdfRule);
254
+ // Show appropriate messages based on mode
255
+ if (isNestedMode) {
256
+ console.log('āœ“ Created: Large PDF Compression (>8MB → Downloads/PDFs/Compressed)');
257
+ console.log('āœ“ Created: PDF Invoice Organizer (invoices → Downloads/PDFs/Invoices)');
258
+ console.log('āœ“ Created: PDF Bank Statement Organizer (bank → Downloads/PDFs/Finance)');
259
+ console.log('āœ“ Created: PDF Study Notes Organizer (notes → Downloads/PDFs/Study)');
260
+ console.log('āœ“ Created: PDF Default Organizer (other PDFs → Downloads/PDFs/Unsorted)');
261
+ }
262
+ else {
263
+ console.log('āœ“ Created: Large PDF Compression (>8MB → Documents/PDFs/Compressed)');
264
+ console.log('āœ“ Created: PDF Invoice Organizer (invoices → Documents/PDFs/Invoices)');
265
+ console.log('āœ“ Created: PDF Bank Statement Organizer (bank → Documents/PDFs/Finance)');
266
+ console.log('āœ“ Created: PDF Study Notes Organizer (notes → Documents/PDFs/Study)');
267
+ console.log('āœ“ Created: PDF Default Organizer (other PDFs → Documents/PDFs/Unsorted)');
268
+ }
269
+ // Create Downloads Organizer Template Rules (Priority 50 - lower than PDF rules)
270
+ // isNestedMode and destinations already defined above for PDF rules
271
+ const destinations = {
272
+ images: isNestedMode ? '{downloads}/Images' : '{pictures}/Downloads',
273
+ videos: isNestedMode ? '{downloads}/Videos' : '{videos}',
274
+ music: isNestedMode ? '{downloads}/Music' : '{music}',
275
+ archives: isNestedMode ? '{downloads}/Archives' : '{documents}/Archives',
276
+ documents: isNestedMode ? '{downloads}/Documents' : '{documents}/Documents',
277
+ installers: isNestedMode ? '{downloads}/Installers' : '{documents}/Installers',
278
+ code: isNestedMode ? '{downloads}/Code' : '{documents}/Code',
279
+ };
280
+ // Rule 6: Images Organizer (Priority 50)
281
+ const imagesRule = {
282
+ id: (0, crypto_1.randomUUID)(),
283
+ name: 'Downloads - Images Organizer',
284
+ enabled: true,
285
+ priority: 50,
286
+ tags: ['downloads', 'images', 'organizer'],
287
+ trigger: {
288
+ type: 'file_created',
289
+ config: { folder: '{downloads}' },
290
+ },
291
+ conditions: [
292
+ { type: 'extension', operator: 'in', value: ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'] },
293
+ ],
294
+ actions: [
295
+ {
296
+ type: 'move',
297
+ config: {
298
+ destination: destinations.images,
299
+ createDirs: true,
300
+ },
301
+ },
302
+ ],
303
+ createdAt: new Date(),
304
+ updatedAt: new Date(),
305
+ };
306
+ // Rule 7: Videos Organizer (Priority 50)
307
+ const videosRule = {
308
+ id: (0, crypto_1.randomUUID)(),
309
+ name: 'Downloads - Videos Organizer',
310
+ enabled: true,
311
+ priority: 50,
312
+ tags: ['downloads', 'videos', 'organizer'],
313
+ trigger: {
314
+ type: 'file_created',
315
+ config: { folder: '{downloads}' },
316
+ },
317
+ conditions: [
318
+ { type: 'extension', operator: 'in', value: ['mp4', 'mov', 'avi', 'mkv'] },
319
+ ],
320
+ actions: [
321
+ {
322
+ type: 'move',
323
+ config: {
324
+ destination: destinations.videos,
325
+ createDirs: true,
326
+ },
327
+ },
328
+ ],
329
+ createdAt: new Date(),
330
+ updatedAt: new Date(),
331
+ };
332
+ // Rule 8: Music Organizer (Priority 50)
333
+ const musicRule = {
334
+ id: (0, crypto_1.randomUUID)(),
335
+ name: 'Downloads - Music Organizer',
336
+ enabled: true,
337
+ priority: 50,
338
+ tags: ['downloads', 'music', 'organizer'],
339
+ trigger: {
340
+ type: 'file_created',
341
+ config: { folder: '{downloads}' },
342
+ },
343
+ conditions: [
344
+ { type: 'extension', operator: 'in', value: ['mp3', 'wav', 'flac', 'aac'] },
345
+ ],
346
+ actions: [
347
+ {
348
+ type: 'move',
349
+ config: {
350
+ destination: destinations.music,
351
+ createDirs: true,
352
+ },
353
+ },
354
+ ],
355
+ createdAt: new Date(),
356
+ updatedAt: new Date(),
357
+ };
358
+ // Rule 9: Archives Organizer (Priority 50)
359
+ const archivesRule = {
360
+ id: (0, crypto_1.randomUUID)(),
361
+ name: 'Downloads - Archives Organizer',
362
+ enabled: true,
363
+ priority: 50,
364
+ tags: ['downloads', 'archives', 'organizer'],
365
+ trigger: {
366
+ type: 'file_created',
367
+ config: { folder: '{downloads}' },
368
+ },
369
+ conditions: [
370
+ { type: 'extension', operator: 'in', value: ['zip', 'rar', '7z', 'tar', 'gz'] },
371
+ ],
372
+ actions: [
373
+ {
374
+ type: 'move',
375
+ config: {
376
+ destination: destinations.archives,
377
+ createDirs: true,
378
+ },
379
+ },
380
+ ],
381
+ createdAt: new Date(),
382
+ updatedAt: new Date(),
383
+ };
384
+ // Rule 10: Documents Organizer (Priority 50)
385
+ const documentsRule = {
386
+ id: (0, crypto_1.randomUUID)(),
387
+ name: 'Downloads - Documents Organizer',
388
+ enabled: true,
389
+ priority: 50,
390
+ tags: ['downloads', 'documents', 'organizer'],
391
+ trigger: {
392
+ type: 'file_created',
393
+ config: { folder: '{downloads}' },
394
+ },
395
+ conditions: [
396
+ { type: 'extension', operator: 'in', value: ['doc', 'docx', 'txt', 'rtf', 'xls', 'xlsx', 'ppt', 'pptx'] },
397
+ ],
398
+ actions: [
399
+ {
400
+ type: 'move',
401
+ config: {
402
+ destination: destinations.documents,
403
+ createDirs: true,
404
+ },
405
+ },
406
+ ],
407
+ createdAt: new Date(),
408
+ updatedAt: new Date(),
409
+ };
410
+ // Rule 11: Installers Organizer (Priority 50)
411
+ const installersRule = {
412
+ id: (0, crypto_1.randomUUID)(),
413
+ name: 'Downloads - Installers Organizer',
414
+ enabled: true,
415
+ priority: 50,
416
+ tags: ['downloads', 'installers', 'organizer'],
417
+ trigger: {
418
+ type: 'file_created',
419
+ config: { folder: '{downloads}' },
420
+ },
421
+ conditions: [
422
+ { type: 'extension', operator: 'in', value: ['dmg', 'pkg', 'exe', 'msi'] },
423
+ ],
424
+ actions: [
425
+ {
426
+ type: 'move',
427
+ config: {
428
+ destination: destinations.installers,
429
+ createDirs: true,
430
+ },
431
+ },
432
+ ],
433
+ createdAt: new Date(),
434
+ updatedAt: new Date(),
435
+ };
436
+ // Rule 12: Code Files Organizer (Priority 50)
437
+ const codeRule = {
438
+ id: (0, crypto_1.randomUUID)(),
439
+ name: 'Downloads - Code Files Organizer',
440
+ enabled: true,
441
+ priority: 50,
442
+ tags: ['downloads', 'code', 'organizer'],
443
+ trigger: {
444
+ type: 'file_created',
445
+ config: { folder: '{downloads}' },
446
+ },
447
+ conditions: [
448
+ { type: 'extension', operator: 'in', value: ['js', 'ts', 'jsx', 'tsx', 'py', 'rb', 'go', 'rs', 'java', 'cpp', 'c', 'h'] },
449
+ ],
450
+ actions: [
451
+ {
452
+ type: 'move',
453
+ config: {
454
+ destination: destinations.code,
455
+ createDirs: true,
456
+ },
457
+ },
458
+ ],
459
+ createdAt: new Date(),
460
+ updatedAt: new Date(),
461
+ };
462
+ // Save all Downloads Organizer rules
463
+ await (0, index_js_2.saveRule)(imagesRule);
464
+ await (0, index_js_2.saveRule)(videosRule);
465
+ await (0, index_js_2.saveRule)(musicRule);
466
+ await (0, index_js_2.saveRule)(archivesRule);
467
+ await (0, index_js_2.saveRule)(documentsRule);
468
+ await (0, index_js_2.saveRule)(installersRule);
469
+ await (0, index_js_2.saveRule)(codeRule);
470
+ // Show appropriate messages based on mode
471
+ if (isNestedMode) {
472
+ console.log('āœ“ Created: Images Organizer (jpg, png, gif, etc. → Downloads/Images)');
473
+ console.log('āœ“ Created: Videos Organizer (mp4, mov, etc. → Downloads/Videos)');
474
+ console.log('āœ“ Created: Music Organizer (mp3, wav, etc. → Downloads/Music)');
475
+ console.log('āœ“ Created: Archives Organizer (zip, rar, etc. → Downloads/Archives)');
476
+ console.log('āœ“ Created: Documents Organizer (doc, xls, etc. → Downloads/Documents)');
477
+ console.log('āœ“ Created: Installers Organizer (dmg, pkg, etc. → Downloads/Installers)');
478
+ console.log('āœ“ Created: Code Files Organizer (js, py, etc. → Downloads/Code)');
479
+ }
480
+ else {
481
+ console.log('āœ“ Created: Images Organizer (jpg, png, gif, etc. → Pictures/Downloads)');
482
+ console.log('āœ“ Created: Videos Organizer (mp4, mov, etc. → Movies)');
483
+ console.log('āœ“ Created: Music Organizer (mp3, wav, etc. → Music)');
484
+ console.log('āœ“ Created: Archives Organizer (zip, rar, etc. → Documents/Archives)');
485
+ console.log('āœ“ Created: Documents Organizer (doc, xls, etc. → Documents/Documents)');
486
+ console.log('āœ“ Created: Installers Organizer (dmg, pkg, etc. → Documents/Installers)');
487
+ console.log('āœ“ Created: Code Files Organizer (js, py, etc. → Documents/Code)');
488
+ }
489
+ console.log('\nāœ… Templates initialized! (PDF Workflow + Downloads Organizer)');
490
+ console.log(`Mode: ${finalConfig.settings.downloadsMode === 'nested' ? 'Organize inside Downloads' : 'Move to system folders'}`);
491
+ // Create Screenshot Organizer Template Rules
492
+ await createScreenshotOrganizerRules(finalConfig);
493
+ // Prompt to organize existing files (after rules are created)
494
+ if (process.stdin.isTTY) {
495
+ const rl = readline_1.default.createInterface({
496
+ input: process.stdin,
497
+ output: process.stdout,
498
+ });
499
+ try {
500
+ await promptOrganizeExistingFiles(rl, finalConfig);
501
+ }
502
+ finally {
503
+ rl.close();
504
+ }
505
+ }
506
+ console.log('\nRun "flowsquire start" to begin watching files.');
507
+ }
508
+ async function listRules() {
509
+ await (0, index_js_1.initDatabase)();
510
+ const rules = await (0, index_js_2.getEnabledRules)();
511
+ console.log('\nšŸ“‹ Rules:\n');
512
+ for (const rule of rules) {
513
+ const status = rule.enabled ? '🟢' : 'šŸ”“';
514
+ console.log(`${status} ${rule.name} (${rule.id})`);
515
+ console.log(` Trigger: ${rule.trigger.type}`);
516
+ console.log(` Actions: ${rule.actions.map((a) => a.type).join(', ')}`);
517
+ console.log('');
518
+ }
519
+ }
520
+ async function runRule(ruleId) {
521
+ console.log('āŒ The "run" command is not yet implemented.');
522
+ console.log(' Use "flowsquire start" to automatically process files based on rules.');
523
+ process.exit(1);
524
+ }
525
+ async function handleConfigCommand(args) {
526
+ const config = await (0, index_js_3.loadConfig)();
527
+ if (args.length === 0) {
528
+ // Show all config
529
+ (0, index_js_3.listConfigPaths)(config);
530
+ return;
531
+ }
532
+ // Parse arguments
533
+ const setArgs = {};
534
+ const getArgs = [];
535
+ for (let i = 0; i < args.length; i++) {
536
+ const arg = args[i];
537
+ if (arg.startsWith('--')) {
538
+ const key = arg.slice(2);
539
+ if (i + 1 < args.length && !args[i + 1].startsWith('--')) {
540
+ // Set value
541
+ setArgs[key] = args[i + 1];
542
+ i++; // Skip the value
543
+ }
544
+ else {
545
+ // Get value
546
+ getArgs.push(key);
547
+ }
548
+ }
549
+ }
550
+ // Handle get operations
551
+ for (const key of getArgs) {
552
+ if (key === 'downloadsMode') {
553
+ console.log(`{${key}}: ${config.settings.downloadsMode}`);
554
+ }
555
+ else {
556
+ const value = config.paths[key];
557
+ if (value !== undefined) {
558
+ console.log(`{${key}}: ${value}`);
559
+ }
560
+ else {
561
+ console.log(`{${key}}: (not set)`);
562
+ }
563
+ }
564
+ }
565
+ // Handle set operations
566
+ let modeChanged = false;
567
+ for (const [key, value] of Object.entries(setArgs)) {
568
+ await (0, index_js_3.setConfigValue)(key, value);
569
+ console.log(`āœ“ Set {${key}} to: ${value}`);
570
+ // Check if downloadsMode or screenshotMode was changed
571
+ if (key === 'downloadsMode' || key === 'downloads-mode' ||
572
+ key === 'screenshotMode' || key === 'screenshot-mode') {
573
+ modeChanged = true;
574
+ }
575
+ }
576
+ // Show hint if mode was changed
577
+ if (modeChanged) {
578
+ console.log('\nāš ļø Note: Run "rm .flowsquire/rules.json && flowsquire init" to regenerate rules with new mode');
579
+ }
580
+ // Show current modes in config listing
581
+ if (args.length === 0) {
582
+ console.log(`\nšŸ“Š Downloads organizer mode: ${config.settings.downloadsMode}`);
583
+ console.log(' (Change with: flowsquire config --downloads-mode nested|system)');
584
+ console.log(`\nšŸ“ø Screenshot organizer mode: ${config.settings.screenshotMode}`);
585
+ console.log(' (Change with: flowsquire config --screenshot-mode metadata|by-app|by-date)');
586
+ if (modeChanged) {
587
+ console.log(' āš ļø Mode changed? Delete rules and run "flowsquire init" to apply');
588
+ }
589
+ }
590
+ }
591
+ // Helper function to ask user a question via CLI
592
+ function askQuestion(rl, question) {
593
+ return new Promise((resolve) => {
594
+ rl.question(question, (answer) => {
595
+ resolve(answer.trim());
596
+ });
597
+ });
598
+ }
599
+ // Interactive setup for first-time users
600
+ async function runInteractiveSetup(config) {
601
+ // Check if stdin is a TTY (interactive terminal)
602
+ if (!process.stdin.isTTY) {
603
+ console.log('āš ļø Non-interactive mode detected. Using default settings.');
604
+ console.log(` Watch folder: ${config.paths.downloads}`);
605
+ console.log(` Organizer mode: ${config.settings.downloadsMode}`);
606
+ return;
607
+ }
608
+ const rl = readline_1.default.createInterface({
609
+ input: process.stdin,
610
+ output: process.stdout,
611
+ });
612
+ try {
613
+ console.log('šŸ‘‹ Welcome to FlowSquire!\n');
614
+ console.log('Let\'s set up your file organization preferences.\n');
615
+ // Question 1: Which folder to watch
616
+ const defaultDownloads = config.paths.downloads;
617
+ const downloadsAnswer = await askQuestion(rl, `Which folder should I watch for new files? [${defaultDownloads}]: `);
618
+ let downloadsPath = downloadsAnswer || defaultDownloads;
619
+ // Expand ~ to home directory
620
+ if (downloadsPath.startsWith('~/')) {
621
+ downloadsPath = path_1.default.join(os_1.default.homedir(), downloadsPath.slice(2));
622
+ }
623
+ // Resolve to absolute path if relative
624
+ const absoluteDownloads = path_1.default.isAbsolute(downloadsPath)
625
+ ? downloadsPath
626
+ : path_1.default.resolve(downloadsPath);
627
+ await (0, index_js_3.setConfigValue)('downloads', absoluteDownloads);
628
+ console.log(`āœ“ Watch folder set to: ${absoluteDownloads}\n`);
629
+ // Question 2: Documents folder (for PDFs, Archives, etc.)
630
+ const defaultDocuments = config.paths.documents;
631
+ const documentsAnswer = await askQuestion(rl, `Where should documents be organized? [${defaultDocuments}]: `);
632
+ let documentsPath = documentsAnswer || defaultDocuments;
633
+ // Expand ~ to home directory
634
+ if (documentsPath.startsWith('~/')) {
635
+ documentsPath = path_1.default.join(os_1.default.homedir(), documentsPath.slice(2));
636
+ }
637
+ // Resolve to absolute path if relative
638
+ const absoluteDocuments = path_1.default.isAbsolute(documentsPath)
639
+ ? documentsPath
640
+ : path_1.default.resolve(documentsPath);
641
+ await (0, index_js_3.setConfigValue)('documents', absoluteDocuments);
642
+ console.log(`āœ“ Documents folder set to: ${absoluteDocuments}\n`);
643
+ // Question 3: Screenshots folder (for Shottr or native screenshots)
644
+ const defaultScreenshots = config.paths.screenshots;
645
+ const screenshotsAnswer = await askQuestion(rl, `Where do your screenshots get saved? [${defaultScreenshots}]: `);
646
+ let screenshotsPath = screenshotsAnswer || defaultScreenshots;
647
+ // Expand ~ to home directory
648
+ if (screenshotsPath.startsWith('~/')) {
649
+ screenshotsPath = path_1.default.join(os_1.default.homedir(), screenshotsPath.slice(2));
650
+ }
651
+ // Resolve to absolute path if relative
652
+ const absoluteScreenshots = path_1.default.isAbsolute(screenshotsPath)
653
+ ? screenshotsPath
654
+ : path_1.default.resolve(screenshotsPath);
655
+ await (0, index_js_3.setConfigValue)('screenshots', absoluteScreenshots);
656
+ console.log(`āœ“ Screenshots folder set to: ${absoluteScreenshots}\n`);
657
+ // Question 4: Downloads organizer mode
658
+ console.log('How should I organize downloaded files?\n');
659
+ console.log('[1] Organize inside Downloads folder (default)');
660
+ console.log(' • Images → ~/Downloads/Images/');
661
+ console.log(' • Music → ~/Downloads/Music/');
662
+ console.log(' • Videos → ~/Downloads/Videos/');
663
+ console.log(' • etc.\n');
664
+ console.log('[2] Move to system folders');
665
+ console.log(' • Images → ~/Pictures/Downloads/');
666
+ console.log(' • Music → ~/Music/');
667
+ console.log(' • Videos → ~/Movies/');
668
+ console.log(' • etc.\n');
669
+ const modeAnswer = await askQuestion(rl, 'Select option [1/2]: ');
670
+ const downloadsMode = modeAnswer === '2' ? 'system' : 'nested';
671
+ await (0, index_js_3.setConfigValue)('downloadsMode', downloadsMode);
672
+ console.log(`āœ“ Downloads organizer mode set to: ${downloadsMode}\n`);
673
+ // Question 5: Screenshot organization mode
674
+ console.log('\nšŸ“ø How should screenshots be organized?\n');
675
+ console.log('[1] Full Metadata (recommended)');
676
+ console.log(' • Organizes by: App/Domain/{filename_date}');
677
+ console.log(' • Example: Google Chrome/aistudio.google.com/SCR-2026-02-01_16-41.png');
678
+ console.log(' • Requires: macOS with Accessibility permissions\n');
679
+ console.log('[2] By App Only');
680
+ console.log(' • Organizes by: App/{filename}');
681
+ console.log(' • Example: Google Chrome/SCR-20260201-ornd.png\n');
682
+ console.log('[3] By Date Only');
683
+ console.log(' • Organizes by: Date/{filename}');
684
+ console.log(' • Example: 2026/02/01/SCR-20260201-ornd.png');
685
+ console.log(' • Works on all platforms\n');
686
+ const screenshotModeAnswer = await askQuestion(rl, 'Select option [1/2/3]: ');
687
+ const screenshotMode = screenshotModeAnswer === '2' ? 'by-app' :
688
+ screenshotModeAnswer === '3' ? 'by-date' : 'metadata';
689
+ await (0, index_js_3.setConfigValue)('screenshotMode', screenshotMode);
690
+ console.log(`āœ“ Screenshot organizer mode set to: ${screenshotMode}\n`);
691
+ console.log('āœ… Configuration saved!\n');
692
+ }
693
+ finally {
694
+ rl.close();
695
+ }
696
+ }
697
+ // Prompt user to organize existing files
698
+ async function promptOrganizeExistingFiles(rl, config) {
699
+ const downloadsPath = config.paths.downloads;
700
+ // Count existing files
701
+ let existingFiles = [];
702
+ try {
703
+ const entries = await promises_1.default.readdir(downloadsPath, { withFileTypes: true });
704
+ existingFiles = entries
705
+ .filter(entry => entry.isFile() && !entry.name.startsWith('.'))
706
+ .map(entry => path_1.default.join(downloadsPath, entry.name));
707
+ }
708
+ catch {
709
+ console.log('āš ļø Could not read Downloads folder. Skipping existing file organization.');
710
+ return;
711
+ }
712
+ if (existingFiles.length === 0) {
713
+ console.log('šŸ“‚ Downloads folder is empty. No existing files to organize.');
714
+ return;
715
+ }
716
+ console.log(`šŸ“‚ Your Downloads folder has ${existingFiles.length} file(s).`);
717
+ console.log('Would you like to organize existing files now?\n');
718
+ console.log('[1] Yes, show me what will be moved first');
719
+ console.log('[2] No, only organize new files (default)\n');
720
+ const organizeAnswer = await askQuestion(rl, 'Select option [1/2]: ');
721
+ if (organizeAnswer !== '1') {
722
+ console.log('āœ“ Skipping existing file organization.\n');
723
+ return;
724
+ }
725
+ // Load rules and show preview
726
+ const rules = await (0, index_js_2.getEnabledRules)();
727
+ const preview = [];
728
+ for (const filePath of existingFiles) {
729
+ const matchingRules = (0, rule_engine_js_1.findMatchingRules)(rules, filePath);
730
+ if (matchingRules.length > 0) {
731
+ const rule = matchingRules[0];
732
+ // Calculate destination
733
+ const action = rule.actions[0];
734
+ if (action && (action.type === 'move' || action.type === 'copy' || action.type === 'compress')) {
735
+ const destTemplate = action.config.destination;
736
+ const fileName = path_1.default.basename(filePath);
737
+ // Simple destination preview (without pattern processing)
738
+ let destPath;
739
+ if (action.type === 'compress' && action.config.pattern) {
740
+ // For compression, show the compressed filename
741
+ const ext = path_1.default.extname(fileName);
742
+ const base = path_1.default.basename(fileName, ext);
743
+ destPath = path_1.default.join(destTemplate, `${base}_compressed${ext}`);
744
+ }
745
+ else {
746
+ destPath = path_1.default.join(destTemplate, fileName);
747
+ }
748
+ preview.push({
749
+ file: fileName,
750
+ rule: rule.name,
751
+ destination: destPath,
752
+ });
753
+ }
754
+ }
755
+ }
756
+ if (preview.length === 0) {
757
+ console.log('\nšŸ“‹ No files match the current rules.');
758
+ return;
759
+ }
760
+ console.log(`\nšŸ“‹ Preview of actions (${preview.length} files):`);
761
+ // Show first 10 files
762
+ preview.slice(0, 10).forEach(item => {
763
+ console.log(` • ${item.file} → ${item.destination}`);
764
+ });
765
+ if (preview.length > 10) {
766
+ console.log(` ... and ${preview.length - 10} more files`);
767
+ }
768
+ const confirmAnswer = await askQuestion(rl, '\nProceed with organization? [y/N]: ');
769
+ if (confirmAnswer.toLowerCase() !== 'y') {
770
+ console.log('āœ“ Organization cancelled.\n');
771
+ return;
772
+ }
773
+ // Execute organization
774
+ console.log('\nšŸ”„ Organizing files...\n');
775
+ let successCount = 0;
776
+ let failCount = 0;
777
+ for (const filePath of existingFiles) {
778
+ const matchingRules = (0, rule_engine_js_1.findMatchingRules)(rules, filePath);
779
+ if (matchingRules.length > 0) {
780
+ const rule = matchingRules[0];
781
+ try {
782
+ await (0, rule_engine_js_1.executeActions)(rule.actions, filePath, false);
783
+ successCount++;
784
+ console.log(` āœ“ ${path_1.default.basename(filePath)}`);
785
+ }
786
+ catch (error) {
787
+ failCount++;
788
+ console.log(` āœ— ${path_1.default.basename(filePath)} (error)`);
789
+ }
790
+ }
791
+ }
792
+ console.log(`\nāœ… Organization complete! ${successCount} files moved, ${failCount} failed.\n`);
793
+ }
794
+ // Create Screenshot Organizer Template Rules
795
+ async function createScreenshotOrganizerRules(config) {
796
+ console.log('\nšŸ“ø Creating Screenshot Organizer rules...\n');
797
+ // Determine screenshot destination based on downloadsMode
798
+ const isNestedMode = config.settings.downloadsMode === 'nested';
799
+ const screenshotMode = config.settings.screenshotMode || 'metadata';
800
+ const screenshotDestinations = {
801
+ organized: isNestedMode
802
+ ? '{screenshots}/Organized'
803
+ : '{pictures}/Screenshots/Organized',
804
+ byApp: isNestedMode
805
+ ? '{screenshots}/ByApp'
806
+ : '{pictures}/Screenshots/ByApp',
807
+ byDate: isNestedMode
808
+ ? '{screenshots}/ByDate'
809
+ : '{pictures}/Screenshots/ByDate',
810
+ };
811
+ // Create rules based on user's selected screenshot mode
812
+ if (screenshotMode === 'metadata') {
813
+ // Rule: Screenshot Organizer with Metadata (Priority 450)
814
+ const screenshotMetadataRule = {
815
+ id: (0, crypto_1.randomUUID)(),
816
+ name: 'Screenshot Organizer with Metadata',
817
+ enabled: true,
818
+ priority: 450,
819
+ tags: ['screenshot', 'metadata', 'organizer'],
820
+ trigger: {
821
+ type: 'file_created',
822
+ config: { folder: '{screenshots}' },
823
+ },
824
+ conditions: [
825
+ { type: 'extension', operator: 'in', value: ['png', 'jpg', 'jpeg', 'gif', 'webp'] },
826
+ ],
827
+ actions: [
828
+ {
829
+ type: 'move',
830
+ config: {
831
+ destination: `${screenshotDestinations.organized}/{app}/{domain}`,
832
+ pattern: '{filename}_{YYYY}-{MM}-{DD}_{HH}-{mm}',
833
+ createDirs: true,
834
+ },
835
+ },
836
+ ],
837
+ createdAt: new Date(),
838
+ updatedAt: new Date(),
839
+ };
840
+ await (0, index_js_2.saveRule)(screenshotMetadataRule);
841
+ if (isNestedMode) {
842
+ console.log('āœ“ Created: Screenshot Organizer with Metadata (→ Screenshots/Organized/{app}/{domain})');
843
+ }
844
+ else {
845
+ console.log('āœ“ Created: Screenshot Organizer with Metadata (→ Pictures/Screenshots/Organized/{app}/{domain})');
846
+ }
847
+ console.log(' Example: Google Chrome/aistudio.google.com/SCR-2026-02-01_16-41.png');
848
+ }
849
+ else if (screenshotMode === 'by-app') {
850
+ // Rule: Screenshot by App (Priority 450)
851
+ const screenshotByAppRule = {
852
+ id: (0, crypto_1.randomUUID)(),
853
+ name: 'Screenshot - Organize by App',
854
+ enabled: true,
855
+ priority: 450,
856
+ tags: ['screenshot', 'by-app', 'organizer'],
857
+ trigger: {
858
+ type: 'file_created',
859
+ config: { folder: '{screenshots}' },
860
+ },
861
+ conditions: [
862
+ { type: 'extension', operator: 'in', value: ['png', 'jpg', 'jpeg', 'gif', 'webp'] },
863
+ ],
864
+ actions: [
865
+ {
866
+ type: 'move',
867
+ config: {
868
+ destination: `${screenshotDestinations.byApp}/{app}`,
869
+ createDirs: true,
870
+ },
871
+ },
872
+ ],
873
+ createdAt: new Date(),
874
+ updatedAt: new Date(),
875
+ };
876
+ await (0, index_js_2.saveRule)(screenshotByAppRule);
877
+ if (isNestedMode) {
878
+ console.log('āœ“ Created: Screenshot by App (→ Screenshots/ByApp/{app})');
879
+ }
880
+ else {
881
+ console.log('āœ“ Created: Screenshot by App (→ Pictures/Screenshots/ByApp/{app})');
882
+ }
883
+ console.log(' Example: Google Chrome/SCR-20260201-ornd.png');
884
+ }
885
+ else if (screenshotMode === 'by-date') {
886
+ // Rule: Screenshot by Date (Priority 450)
887
+ const screenshotByDateRule = {
888
+ id: (0, crypto_1.randomUUID)(),
889
+ name: 'Screenshot - Organize by Date',
890
+ enabled: true,
891
+ priority: 450,
892
+ tags: ['screenshot', 'by-date', 'organizer'],
893
+ trigger: {
894
+ type: 'file_created',
895
+ config: { folder: '{screenshots}' },
896
+ },
897
+ conditions: [
898
+ { type: 'extension', operator: 'in', value: ['png', 'jpg', 'jpeg', 'gif', 'webp'] },
899
+ ],
900
+ actions: [
901
+ {
902
+ type: 'move',
903
+ config: {
904
+ destination: screenshotDestinations.byDate,
905
+ pattern: '{YYYY}/{Month}/{filename}',
906
+ createDirs: true,
907
+ },
908
+ },
909
+ ],
910
+ createdAt: new Date(),
911
+ updatedAt: new Date(),
912
+ };
913
+ await (0, index_js_2.saveRule)(screenshotByDateRule);
914
+ if (isNestedMode) {
915
+ console.log('āœ“ Created: Screenshot by Date (→ Screenshots/ByDate/{YYYY}/{MM})');
916
+ }
917
+ else {
918
+ console.log('āœ“ Created: Screenshot by Date (→ Pictures/Screenshots/ByDate/{YYYY}/{MM})');
919
+ }
920
+ console.log(' Example: 2026/02/01/SCR-20260201-ornd.png');
921
+ }
922
+ console.log('\n Note: Screenshot metadata capture requires macOS Accessibility permissions.');
923
+ console.log(' On non-macOS platforms, screenshots will be organized by date/filename.\n');
924
+ }
925
+ main().catch(console.error);
926
+ //# sourceMappingURL=cli.js.map