@anraktech/sync 0.12.0 → 0.13.1
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 +285 -54
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -31,6 +31,36 @@ var log = {
|
|
|
31
31
|
var CONFIG_DIR = join(homedir(), ".anrak-sync");
|
|
32
32
|
var CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
33
33
|
var CACHE_FILE = join(CONFIG_DIR, "cache.json");
|
|
34
|
+
function getWatchFolders(config) {
|
|
35
|
+
const folders = /* @__PURE__ */ new Set();
|
|
36
|
+
if (config.watchFolder) folders.add(config.watchFolder);
|
|
37
|
+
if (config.watchFolders) {
|
|
38
|
+
for (const f of config.watchFolders) folders.add(f);
|
|
39
|
+
}
|
|
40
|
+
return [...folders];
|
|
41
|
+
}
|
|
42
|
+
function addWatchFolder(config, folder) {
|
|
43
|
+
const existing = getWatchFolders(config);
|
|
44
|
+
if (existing.includes(folder)) return false;
|
|
45
|
+
if (!config.watchFolders) config.watchFolders = [];
|
|
46
|
+
config.watchFolders.push(folder);
|
|
47
|
+
saveConfig(config);
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
function removeWatchFolder(config, folder) {
|
|
51
|
+
const all = getWatchFolders(config);
|
|
52
|
+
if (all.length <= 1) return { removed: false, error: "Cannot remove the only watch folder." };
|
|
53
|
+
if (!all.includes(folder)) return { removed: false, error: `Folder not found: ${folder}` };
|
|
54
|
+
if (config.watchFolder === folder) {
|
|
55
|
+
const remaining = all.filter((f) => f !== folder);
|
|
56
|
+
config.watchFolder = remaining[0];
|
|
57
|
+
config.watchFolders = remaining.slice(1);
|
|
58
|
+
} else {
|
|
59
|
+
config.watchFolders = (config.watchFolders || []).filter((f) => f !== folder);
|
|
60
|
+
}
|
|
61
|
+
saveConfig(config);
|
|
62
|
+
return { removed: true };
|
|
63
|
+
}
|
|
34
64
|
function ensureDir() {
|
|
35
65
|
if (!existsSync(CONFIG_DIR)) {
|
|
36
66
|
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
@@ -791,21 +821,47 @@ var TOOLS = [
|
|
|
791
821
|
description: "Re-scan the watch folder for new or changed files and sync them.",
|
|
792
822
|
parameters: { type: "object", properties: {} }
|
|
793
823
|
}
|
|
824
|
+
},
|
|
825
|
+
{
|
|
826
|
+
type: "function",
|
|
827
|
+
function: {
|
|
828
|
+
name: "manage_watch_folders",
|
|
829
|
+
description: "Add, remove, or list watch folders. Use when user wants to watch a new folder, stop watching a folder, or see which folders are being watched.",
|
|
830
|
+
parameters: {
|
|
831
|
+
type: "object",
|
|
832
|
+
properties: {
|
|
833
|
+
action: {
|
|
834
|
+
type: "string",
|
|
835
|
+
enum: ["add", "remove", "list"],
|
|
836
|
+
description: "Action to perform: 'add' a new folder, 'remove' an existing one, or 'list' all watched folders."
|
|
837
|
+
},
|
|
838
|
+
folderPath: {
|
|
839
|
+
type: "string",
|
|
840
|
+
description: "Absolute path to the folder (required for add/remove). Use full absolute paths like /Users/name/Desktop."
|
|
841
|
+
}
|
|
842
|
+
},
|
|
843
|
+
required: ["action"]
|
|
844
|
+
}
|
|
845
|
+
}
|
|
794
846
|
}
|
|
795
847
|
];
|
|
796
848
|
function buildSystemPrompt(config) {
|
|
849
|
+
const folders = getWatchFolders(config);
|
|
850
|
+
const folderList = folders.map((f) => ` - ${f}`).join("\n");
|
|
797
851
|
return `You are AnrakLegal Sync, a terminal assistant that helps lawyers sync local files to their case management system.
|
|
798
852
|
|
|
799
|
-
Watch
|
|
853
|
+
Watch folders:
|
|
854
|
+
${folderList}
|
|
800
855
|
Server: ${config.apiUrl}
|
|
801
856
|
Home directory: ${HOME}
|
|
802
857
|
Platform: ${IS_MAC ? "macOS" : platform()}
|
|
803
858
|
|
|
804
|
-
You can browse local folders, scan & sync files, list cases, show sync status, and more.
|
|
859
|
+
You can browse local folders, scan & sync files, list cases, show sync status, manage watch folders, and more.
|
|
805
860
|
|
|
806
861
|
Rules:
|
|
807
862
|
- Be concise. This is a terminal \u2014 1-3 lines unless showing a list.
|
|
808
863
|
- Do NOT use markdown. Plain text only.
|
|
864
|
+
- WATCH FOLDERS: When user asks to "watch", "add", or "also sync" a folder, use manage_watch_folders with action "add". When they say "stop watching" or "remove" a folder, use action "remove".
|
|
809
865
|
- IMPORTANT: When user mentions a folder like "downloads" or "desktop", ALWAYS use the full absolute path. Examples:
|
|
810
866
|
"downloads" or "my downloads" \u2192 ${join2(HOME, "Downloads")}
|
|
811
867
|
"desktop" \u2192 ${join2(HOME, "Desktop")}
|
|
@@ -1043,6 +1099,46 @@ async function executeTool(name, args, ctx) {
|
|
|
1043
1099
|
pending: stats.pending
|
|
1044
1100
|
});
|
|
1045
1101
|
}
|
|
1102
|
+
case "manage_watch_folders": {
|
|
1103
|
+
const action = args.action;
|
|
1104
|
+
const rawFolder = args.folderPath;
|
|
1105
|
+
if (action === "list") {
|
|
1106
|
+
const folders = getWatchFolders(ctx.config);
|
|
1107
|
+
return JSON.stringify({
|
|
1108
|
+
folders: folders.map((f, i) => ({
|
|
1109
|
+
path: f,
|
|
1110
|
+
primary: f === ctx.config.watchFolder,
|
|
1111
|
+
index: i + 1
|
|
1112
|
+
})),
|
|
1113
|
+
total: folders.length
|
|
1114
|
+
});
|
|
1115
|
+
}
|
|
1116
|
+
if (action === "add") {
|
|
1117
|
+
if (!rawFolder) return JSON.stringify({ error: "Missing folderPath" });
|
|
1118
|
+
const folderPath = normalizePath(rawFolder);
|
|
1119
|
+
if (!ctx.addWatchFolder) {
|
|
1120
|
+
return JSON.stringify({ error: "Watch folder management not available" });
|
|
1121
|
+
}
|
|
1122
|
+
const result = await ctx.addWatchFolder(folderPath);
|
|
1123
|
+
if (result.added) {
|
|
1124
|
+
return JSON.stringify({ success: true, added: result.path || folderPath, total: getWatchFolders(ctx.config).length });
|
|
1125
|
+
}
|
|
1126
|
+
return JSON.stringify({ error: result.error || "Could not add folder" });
|
|
1127
|
+
}
|
|
1128
|
+
if (action === "remove") {
|
|
1129
|
+
if (!rawFolder) return JSON.stringify({ error: "Missing folderPath" });
|
|
1130
|
+
const folderPath = normalizePath(rawFolder);
|
|
1131
|
+
if (!ctx.removeWatchFolder) {
|
|
1132
|
+
return JSON.stringify({ error: "Watch folder management not available" });
|
|
1133
|
+
}
|
|
1134
|
+
const result = ctx.removeWatchFolder(folderPath);
|
|
1135
|
+
if (result.removed) {
|
|
1136
|
+
return JSON.stringify({ success: true, removed: folderPath, total: getWatchFolders(ctx.config).length });
|
|
1137
|
+
}
|
|
1138
|
+
return JSON.stringify({ error: result.error || "Could not remove folder" });
|
|
1139
|
+
}
|
|
1140
|
+
return JSON.stringify({ error: `Unknown action: ${action}. Use add, remove, or list.` });
|
|
1141
|
+
}
|
|
1046
1142
|
default:
|
|
1047
1143
|
return JSON.stringify({ error: `Unknown tool: ${name}` });
|
|
1048
1144
|
}
|
|
@@ -1146,7 +1242,10 @@ function startAIAgent(ctx) {
|
|
|
1146
1242
|
];
|
|
1147
1243
|
const rl = createInterface({ input: stdin, output: stdout });
|
|
1148
1244
|
const W = Math.min(process.stdout.columns || 60, 60);
|
|
1149
|
-
const
|
|
1245
|
+
const allFolders = getWatchFolders(ctx.config);
|
|
1246
|
+
const folderDisplays = allFolders.map(
|
|
1247
|
+
(f) => f.startsWith(HOME) ? "~" + f.slice(HOME.length) : f
|
|
1248
|
+
);
|
|
1150
1249
|
const stats = ctx.bootStats;
|
|
1151
1250
|
const pad = (s, len) => {
|
|
1152
1251
|
const visible = s.replace(/\x1b\[[0-9;]*m/g, "");
|
|
@@ -1178,14 +1277,20 @@ function startAIAgent(ctx) {
|
|
|
1178
1277
|
console.log("");
|
|
1179
1278
|
console.log(top);
|
|
1180
1279
|
console.log(empty);
|
|
1181
|
-
|
|
1280
|
+
if (folderDisplays.length === 1) {
|
|
1281
|
+
console.log(row(`${chalk2.dim("Folder")} ${folderDisplays[0]}`));
|
|
1282
|
+
} else {
|
|
1283
|
+
console.log(row(`${chalk2.dim("Folders")} ${chalk2.cyan(String(folderDisplays.length))} watched`));
|
|
1284
|
+
for (const fd of folderDisplays) {
|
|
1285
|
+
console.log(row(`${chalk2.dim(" \xB7")} ${fd}`));
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1182
1288
|
console.log(row(`${chalk2.dim("Server")} ${ctx.config.apiUrl}`));
|
|
1183
1289
|
if (statusLine) {
|
|
1184
1290
|
console.log(row(`${chalk2.dim("Status")} ${statusLine}`));
|
|
1185
1291
|
}
|
|
1186
1292
|
console.log(empty);
|
|
1187
|
-
console.log(row(chalk2.dim("Type naturally, or
|
|
1188
|
-
console.log(row(chalk2.dim("Type /help to see all commands.")));
|
|
1293
|
+
console.log(row(chalk2.dim("Type naturally, or press / for commands.")));
|
|
1189
1294
|
console.log(empty);
|
|
1190
1295
|
console.log(bot);
|
|
1191
1296
|
console.log("");
|
|
@@ -1246,10 +1351,80 @@ function startAIAgent(ctx) {
|
|
|
1246
1351
|
}
|
|
1247
1352
|
},
|
|
1248
1353
|
folder: {
|
|
1249
|
-
description: "
|
|
1250
|
-
handler: () => {
|
|
1251
|
-
|
|
1252
|
-
|
|
1354
|
+
description: "Manage watch folders (add/remove/list)",
|
|
1355
|
+
handler: async () => {
|
|
1356
|
+
const sub = currentSlashArgs.trim();
|
|
1357
|
+
if (!sub || sub === "list") {
|
|
1358
|
+
const folders = getWatchFolders(ctx.config);
|
|
1359
|
+
console.log("");
|
|
1360
|
+
console.log(chalk2.bold(` ${folders.length} watch folder${folders.length !== 1 ? "s" : ""}`));
|
|
1361
|
+
console.log(chalk2.dim(" " + "\u2500".repeat(40)));
|
|
1362
|
+
for (let i = 0; i < folders.length; i++) {
|
|
1363
|
+
const f = folders[i];
|
|
1364
|
+
const display = f.startsWith(HOME) ? "~" + f.slice(HOME.length) : f;
|
|
1365
|
+
const tag = f === ctx.config.watchFolder ? chalk2.cyan(" (primary)") : "";
|
|
1366
|
+
console.log(` ${chalk2.dim(`${i + 1}.`)} ${display}${tag}`);
|
|
1367
|
+
}
|
|
1368
|
+
console.log("");
|
|
1369
|
+
console.log(chalk2.dim(" /folder add <path> Add a new watch folder"));
|
|
1370
|
+
console.log(chalk2.dim(" /folder remove <path> Stop watching a folder"));
|
|
1371
|
+
console.log(chalk2.dim(" /folder set <path> Set primary watch folder"));
|
|
1372
|
+
return;
|
|
1373
|
+
}
|
|
1374
|
+
const parts = sub.split(/\s+/);
|
|
1375
|
+
const action = parts[0].toLowerCase();
|
|
1376
|
+
const folderArg = parts.slice(1).join(" ");
|
|
1377
|
+
if (action === "add") {
|
|
1378
|
+
if (!folderArg) {
|
|
1379
|
+
console.log(chalk2.red(" Usage: /folder add <path>"));
|
|
1380
|
+
return;
|
|
1381
|
+
}
|
|
1382
|
+
const absPath = normalizePath(folderArg);
|
|
1383
|
+
if (!ctx.addWatchFolder) {
|
|
1384
|
+
console.log(chalk2.red(" Watch folder management not available."));
|
|
1385
|
+
return;
|
|
1386
|
+
}
|
|
1387
|
+
const result = await ctx.addWatchFolder(absPath);
|
|
1388
|
+
if (result.added) {
|
|
1389
|
+
const display = (result.path || absPath).startsWith(HOME) ? "~" + (result.path || absPath).slice(HOME.length) : result.path || absPath;
|
|
1390
|
+
console.log(chalk2.green(` Added: ${display}`));
|
|
1391
|
+
} else {
|
|
1392
|
+
console.log(chalk2.yellow(` ${result.error || "Could not add folder"}`));
|
|
1393
|
+
}
|
|
1394
|
+
} else if (action === "remove" || action === "rm") {
|
|
1395
|
+
if (!folderArg) {
|
|
1396
|
+
console.log(chalk2.red(" Usage: /folder remove <path>"));
|
|
1397
|
+
return;
|
|
1398
|
+
}
|
|
1399
|
+
const absPath = normalizePath(folderArg);
|
|
1400
|
+
if (!ctx.removeWatchFolder) {
|
|
1401
|
+
console.log(chalk2.red(" Watch folder management not available."));
|
|
1402
|
+
return;
|
|
1403
|
+
}
|
|
1404
|
+
const result = ctx.removeWatchFolder(absPath);
|
|
1405
|
+
if (result.removed) {
|
|
1406
|
+
const display = absPath.startsWith(HOME) ? "~" + absPath.slice(HOME.length) : absPath;
|
|
1407
|
+
console.log(chalk2.green(` Removed: ${display}`));
|
|
1408
|
+
} else {
|
|
1409
|
+
console.log(chalk2.yellow(` ${result.error || "Could not remove folder"}`));
|
|
1410
|
+
}
|
|
1411
|
+
} else if (action === "set") {
|
|
1412
|
+
if (!folderArg) {
|
|
1413
|
+
console.log(chalk2.red(" Usage: /folder set <path>"));
|
|
1414
|
+
return;
|
|
1415
|
+
}
|
|
1416
|
+
const absPath = normalizePath(folderArg);
|
|
1417
|
+
if (!existsSync2(absPath) || !statSync(absPath).isDirectory()) {
|
|
1418
|
+
console.log(chalk2.red(` Not a valid directory: ${absPath}`));
|
|
1419
|
+
return;
|
|
1420
|
+
}
|
|
1421
|
+
ctx.config.watchFolder = absPath;
|
|
1422
|
+
saveConfig(ctx.config);
|
|
1423
|
+
const display = absPath.startsWith(HOME) ? "~" + absPath.slice(HOME.length) : absPath;
|
|
1424
|
+
console.log(chalk2.green(` Primary folder set to: ${display}`));
|
|
1425
|
+
} else {
|
|
1426
|
+
console.log(chalk2.dim(" Usage: /folder [list|add|remove|set] <path>"));
|
|
1427
|
+
}
|
|
1253
1428
|
}
|
|
1254
1429
|
},
|
|
1255
1430
|
mappings: {
|
|
@@ -1330,6 +1505,7 @@ function startAIAgent(ctx) {
|
|
|
1330
1505
|
}
|
|
1331
1506
|
}
|
|
1332
1507
|
};
|
|
1508
|
+
let currentSlashArgs = "";
|
|
1333
1509
|
async function promptLoop() {
|
|
1334
1510
|
while (true) {
|
|
1335
1511
|
let input;
|
|
@@ -1341,9 +1517,27 @@ function startAIAgent(ctx) {
|
|
|
1341
1517
|
const trimmed = input.trim();
|
|
1342
1518
|
if (!trimmed) continue;
|
|
1343
1519
|
if (trimmed.startsWith("/")) {
|
|
1344
|
-
const
|
|
1520
|
+
const withoutSlash = trimmed.slice(1);
|
|
1521
|
+
const parts = withoutSlash.split(/\s+/);
|
|
1522
|
+
const cmd = (parts[0] || "").toLowerCase();
|
|
1523
|
+
if (!cmd) {
|
|
1524
|
+
console.log("");
|
|
1525
|
+
console.log(chalk2.bold(" Commands"));
|
|
1526
|
+
console.log(chalk2.dim(" " + "\u2500".repeat(Math.min(process.stdout.columns || 60, 60) - 4)));
|
|
1527
|
+
for (const [name, { description }] of Object.entries(slashCommands)) {
|
|
1528
|
+
const label = chalk2.cyan(`/${name}`);
|
|
1529
|
+
const dots = chalk2.dim(".".repeat(Math.max(2, 18 - name.length)));
|
|
1530
|
+
console.log(` ${label} ${dots} ${chalk2.dim(description)}`);
|
|
1531
|
+
}
|
|
1532
|
+
console.log("");
|
|
1533
|
+
console.log(chalk2.dim(" Or just type naturally \u2014 the AI understands plain English."));
|
|
1534
|
+
console.log("");
|
|
1535
|
+
continue;
|
|
1536
|
+
}
|
|
1345
1537
|
if (slashCommands[cmd]) {
|
|
1538
|
+
currentSlashArgs = parts.slice(1).join(" ");
|
|
1346
1539
|
await slashCommands[cmd].handler();
|
|
1540
|
+
currentSlashArgs = "";
|
|
1347
1541
|
console.log("");
|
|
1348
1542
|
continue;
|
|
1349
1543
|
}
|
|
@@ -1351,7 +1545,7 @@ function startAIAgent(ctx) {
|
|
|
1351
1545
|
if (matches.length > 0) {
|
|
1352
1546
|
console.log(chalk2.dim(` Did you mean: ${matches.map((m) => chalk2.cyan(`/${m}`)).join(", ")}?`));
|
|
1353
1547
|
} else {
|
|
1354
|
-
console.log(chalk2.dim(` Unknown command. Type ${chalk2.cyan("/
|
|
1548
|
+
console.log(chalk2.dim(` Unknown command. Type ${chalk2.cyan("/")} for available commands.`));
|
|
1355
1549
|
}
|
|
1356
1550
|
console.log("");
|
|
1357
1551
|
continue;
|
|
@@ -1381,10 +1575,9 @@ function startAIAgent(ctx) {
|
|
|
1381
1575
|
|
|
1382
1576
|
// src/watcher.ts
|
|
1383
1577
|
async function scanFolder(config) {
|
|
1384
|
-
const
|
|
1578
|
+
const folders = getWatchFolders(config);
|
|
1385
1579
|
let scanned = 0;
|
|
1386
|
-
|
|
1387
|
-
function walk(dir) {
|
|
1580
|
+
function walk(dir, watchRoot) {
|
|
1388
1581
|
let entries;
|
|
1389
1582
|
try {
|
|
1390
1583
|
entries = readdirSync2(dir);
|
|
@@ -1401,14 +1594,18 @@ async function scanFolder(config) {
|
|
|
1401
1594
|
continue;
|
|
1402
1595
|
}
|
|
1403
1596
|
if (s.isDirectory()) {
|
|
1404
|
-
walk(fullPath);
|
|
1597
|
+
walk(fullPath, watchRoot);
|
|
1405
1598
|
} else if (s.isFile() && isSupportedFile(entry)) {
|
|
1406
1599
|
scanned++;
|
|
1407
|
-
void enqueue(fullPath,
|
|
1600
|
+
void enqueue(fullPath, watchRoot);
|
|
1408
1601
|
}
|
|
1409
1602
|
}
|
|
1410
1603
|
}
|
|
1411
|
-
|
|
1604
|
+
for (const folder of folders) {
|
|
1605
|
+
if (existsSync3(folder)) {
|
|
1606
|
+
walk(folder, folder);
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1412
1609
|
await new Promise((r) => setTimeout(r, 500));
|
|
1413
1610
|
return { scanned, queued: queueSize() };
|
|
1414
1611
|
}
|
|
@@ -1475,7 +1672,7 @@ async function scanExternalFolder(config, folderPath, cases) {
|
|
|
1475
1672
|
}
|
|
1476
1673
|
}
|
|
1477
1674
|
async function startWatching(config) {
|
|
1478
|
-
const
|
|
1675
|
+
const folders = getWatchFolders(config);
|
|
1479
1676
|
let cases = await listCases(config);
|
|
1480
1677
|
const { scanned, queued } = await scanFolder(config);
|
|
1481
1678
|
if (queued > 0) {
|
|
@@ -1490,19 +1687,7 @@ async function startWatching(config) {
|
|
|
1490
1687
|
} catch {
|
|
1491
1688
|
}
|
|
1492
1689
|
}, 5 * 60 * 1e3);
|
|
1493
|
-
const
|
|
1494
|
-
ignored: /(^|[\/\\])(\.|~\$|Thumbs\.db|desktop\.ini)/,
|
|
1495
|
-
persistent: true,
|
|
1496
|
-
ignoreInitial: true,
|
|
1497
|
-
// We already scanned
|
|
1498
|
-
awaitWriteFinish: {
|
|
1499
|
-
stabilityThreshold: 2e3,
|
|
1500
|
-
// Wait 2s after last change
|
|
1501
|
-
pollInterval: 500
|
|
1502
|
-
},
|
|
1503
|
-
depth: 5
|
|
1504
|
-
// Max folder depth
|
|
1505
|
-
});
|
|
1690
|
+
const watchers = /* @__PURE__ */ new Map();
|
|
1506
1691
|
let debounceTimer = null;
|
|
1507
1692
|
function scheduleProcess() {
|
|
1508
1693
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
@@ -1517,26 +1702,44 @@ async function startWatching(config) {
|
|
|
1517
1702
|
}
|
|
1518
1703
|
}, 3e3);
|
|
1519
1704
|
}
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1705
|
+
function createWatcher(folder) {
|
|
1706
|
+
if (watchers.has(folder)) return;
|
|
1707
|
+
const w = watch(folder, {
|
|
1708
|
+
ignored: /(^|[\/\\])(\.|~\$|Thumbs\.db|desktop\.ini)/,
|
|
1709
|
+
persistent: true,
|
|
1710
|
+
ignoreInitial: true,
|
|
1711
|
+
awaitWriteFinish: {
|
|
1712
|
+
stabilityThreshold: 2e3,
|
|
1713
|
+
pollInterval: 500
|
|
1714
|
+
},
|
|
1715
|
+
depth: 5
|
|
1716
|
+
});
|
|
1717
|
+
w.on("add", async (path) => {
|
|
1718
|
+
const filename = basename5(path);
|
|
1719
|
+
if (!isSupportedFile(filename) || isIgnoredFile(filename)) return;
|
|
1720
|
+
log.file("detected", relative2(folder, path));
|
|
1721
|
+
await enqueue(path, folder);
|
|
1722
|
+
scheduleProcess();
|
|
1723
|
+
});
|
|
1724
|
+
w.on("change", async (path) => {
|
|
1725
|
+
const filename = basename5(path);
|
|
1726
|
+
if (!isSupportedFile(filename) || isIgnoredFile(filename)) return;
|
|
1727
|
+
log.file("changed", relative2(folder, path));
|
|
1728
|
+
await enqueue(path, folder);
|
|
1729
|
+
scheduleProcess();
|
|
1730
|
+
});
|
|
1731
|
+
w.on("unlink", (path) => {
|
|
1732
|
+
log.file("deleted", relative2(folder, path));
|
|
1733
|
+
});
|
|
1734
|
+
w.on("error", (err) => {
|
|
1735
|
+
log.error(`Watcher error (${basename5(folder)}): ${err instanceof Error ? err.message : String(err)}`);
|
|
1736
|
+
});
|
|
1737
|
+
watchers.set(folder, w);
|
|
1738
|
+
log.debug(`Watching: ${folder}`);
|
|
1739
|
+
}
|
|
1740
|
+
for (const folder of folders) {
|
|
1741
|
+
createWatcher(folder);
|
|
1742
|
+
}
|
|
1540
1743
|
startAIAgent({
|
|
1541
1744
|
config,
|
|
1542
1745
|
bootStats: { cases: cases.length, scanned, queued },
|
|
@@ -1545,7 +1748,8 @@ async function startWatching(config) {
|
|
|
1545
1748
|
cases = await listCases(config);
|
|
1546
1749
|
},
|
|
1547
1750
|
triggerScan: async () => {
|
|
1548
|
-
|
|
1751
|
+
const allFolders = getWatchFolders(config);
|
|
1752
|
+
log.info(`Re-scanning ${allFolders.length} folder(s)...`);
|
|
1549
1753
|
const result = await scanFolder(config);
|
|
1550
1754
|
log.info(`Scanned ${result.scanned} files, ${result.queued} need syncing`);
|
|
1551
1755
|
if (result.queued > 0) {
|
|
@@ -1555,13 +1759,40 @@ async function startWatching(config) {
|
|
|
1555
1759
|
},
|
|
1556
1760
|
scanFolder: async (folderPath) => {
|
|
1557
1761
|
await scanExternalFolder(config, folderPath, cases);
|
|
1762
|
+
},
|
|
1763
|
+
addWatchFolder: async (folderPath) => {
|
|
1764
|
+
const absPath = resolve2(folderPath);
|
|
1765
|
+
if (!existsSync3(absPath) || !statSync2(absPath).isDirectory()) {
|
|
1766
|
+
return { added: false, error: `Not a valid directory: ${absPath}` };
|
|
1767
|
+
}
|
|
1768
|
+
const added = addWatchFolder(config, absPath);
|
|
1769
|
+
if (!added) {
|
|
1770
|
+
return { added: false, error: "Folder already being watched" };
|
|
1771
|
+
}
|
|
1772
|
+
createWatcher(absPath);
|
|
1773
|
+
log.success(`Now watching: ${absPath}`);
|
|
1774
|
+
return { added: true, path: absPath };
|
|
1775
|
+
},
|
|
1776
|
+
removeWatchFolder: (folderPath) => {
|
|
1777
|
+
const absPath = resolve2(folderPath);
|
|
1778
|
+
const result = removeWatchFolder(config, absPath);
|
|
1779
|
+
if (result.removed) {
|
|
1780
|
+
const w = watchers.get(absPath);
|
|
1781
|
+
if (w) {
|
|
1782
|
+
void w.close();
|
|
1783
|
+
watchers.delete(absPath);
|
|
1784
|
+
}
|
|
1785
|
+
log.success(`Stopped watching: ${absPath}`);
|
|
1786
|
+
}
|
|
1787
|
+
return result;
|
|
1558
1788
|
}
|
|
1559
1789
|
});
|
|
1560
1790
|
const shutdown = () => {
|
|
1561
1791
|
log.info("Shutting down...");
|
|
1562
1792
|
clearInterval(refreshInterval);
|
|
1563
1793
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
1564
|
-
|
|
1794
|
+
const closePromises = [...watchers.values()].map((w) => w.close());
|
|
1795
|
+
Promise.all(closePromises).then(() => {
|
|
1565
1796
|
log.success("Stopped");
|
|
1566
1797
|
process.exit(0);
|
|
1567
1798
|
});
|