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/README.md +258 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +926 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/index.d.ts +36 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +154 -0
- package/dist/config/index.js.map +1 -0
- package/dist/core/rule-engine.d.ts +8 -0
- package/dist/core/rule-engine.d.ts.map +1 -0
- package/dist/core/rule-engine.js +289 -0
- package/dist/core/rule-engine.js.map +1 -0
- package/dist/core/screenshot-metadata.d.ts +24 -0
- package/dist/core/screenshot-metadata.d.ts.map +1 -0
- package/dist/core/screenshot-metadata.js +175 -0
- package/dist/core/screenshot-metadata.js.map +1 -0
- package/dist/db/index.d.ts +8 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +87 -0
- package/dist/db/index.js.map +1 -0
- package/dist/types/index.d.ts +62 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/dist/watchers/file-watcher.d.ts +21 -0
- package/dist/watchers/file-watcher.d.ts.map +1 -0
- package/dist/watchers/file-watcher.js +318 -0
- package/dist/watchers/file-watcher.js.map +1 -0
- package/package.json +56 -0
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
|