@anraktech/sync 0.11.0 → 0.13.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 +371 -52
- 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 });
|
|
@@ -794,9 +824,12 @@ var TOOLS = [
|
|
|
794
824
|
}
|
|
795
825
|
];
|
|
796
826
|
function buildSystemPrompt(config) {
|
|
827
|
+
const folders = getWatchFolders(config);
|
|
828
|
+
const folderList = folders.map((f) => ` - ${f}`).join("\n");
|
|
797
829
|
return `You are AnrakLegal Sync, a terminal assistant that helps lawyers sync local files to their case management system.
|
|
798
830
|
|
|
799
|
-
Watch
|
|
831
|
+
Watch folders:
|
|
832
|
+
${folderList}
|
|
800
833
|
Server: ${config.apiUrl}
|
|
801
834
|
Home directory: ${HOME}
|
|
802
835
|
Platform: ${IS_MAC ? "macOS" : platform()}
|
|
@@ -1146,7 +1179,10 @@ function startAIAgent(ctx) {
|
|
|
1146
1179
|
];
|
|
1147
1180
|
const rl = createInterface({ input: stdin, output: stdout });
|
|
1148
1181
|
const W = Math.min(process.stdout.columns || 60, 60);
|
|
1149
|
-
const
|
|
1182
|
+
const allFolders = getWatchFolders(ctx.config);
|
|
1183
|
+
const folderDisplays = allFolders.map(
|
|
1184
|
+
(f) => f.startsWith(HOME) ? "~" + f.slice(HOME.length) : f
|
|
1185
|
+
);
|
|
1150
1186
|
const stats = ctx.bootStats;
|
|
1151
1187
|
const pad = (s, len) => {
|
|
1152
1188
|
const visible = s.replace(/\x1b\[[0-9;]*m/g, "");
|
|
@@ -1178,18 +1214,235 @@ function startAIAgent(ctx) {
|
|
|
1178
1214
|
console.log("");
|
|
1179
1215
|
console.log(top);
|
|
1180
1216
|
console.log(empty);
|
|
1181
|
-
|
|
1217
|
+
if (folderDisplays.length === 1) {
|
|
1218
|
+
console.log(row(`${chalk2.dim("Folder")} ${folderDisplays[0]}`));
|
|
1219
|
+
} else {
|
|
1220
|
+
console.log(row(`${chalk2.dim("Folders")} ${chalk2.cyan(String(folderDisplays.length))} watched`));
|
|
1221
|
+
for (const fd of folderDisplays) {
|
|
1222
|
+
console.log(row(`${chalk2.dim(" \xB7")} ${fd}`));
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1182
1225
|
console.log(row(`${chalk2.dim("Server")} ${ctx.config.apiUrl}`));
|
|
1183
1226
|
if (statusLine) {
|
|
1184
1227
|
console.log(row(`${chalk2.dim("Status")} ${statusLine}`));
|
|
1185
1228
|
}
|
|
1186
1229
|
console.log(empty);
|
|
1187
|
-
console.log(row(chalk2.dim("Type naturally
|
|
1188
|
-
console.log(row(chalk2.dim('"show my cases" \xB7 "look at downloads" \xB7 "sync"')));
|
|
1230
|
+
console.log(row(chalk2.dim("Type naturally, or press / for commands.")));
|
|
1189
1231
|
console.log(empty);
|
|
1190
1232
|
console.log(bot);
|
|
1191
1233
|
console.log("");
|
|
1192
1234
|
const PROMPT = `${chalk2.bold.blue("\u276F")} `;
|
|
1235
|
+
const slashCommands = {
|
|
1236
|
+
help: {
|
|
1237
|
+
description: "Show all commands",
|
|
1238
|
+
handler: () => {
|
|
1239
|
+
const W2 = Math.min(process.stdout.columns || 60, 60);
|
|
1240
|
+
console.log("");
|
|
1241
|
+
console.log(chalk2.bold(" Commands"));
|
|
1242
|
+
console.log(chalk2.dim(" " + "\u2500".repeat(W2 - 4)));
|
|
1243
|
+
for (const [cmd, { description }] of Object.entries(slashCommands)) {
|
|
1244
|
+
const label = chalk2.cyan(`/${cmd}`);
|
|
1245
|
+
const dots = chalk2.dim(".".repeat(Math.max(2, 18 - cmd.length)));
|
|
1246
|
+
console.log(` ${label} ${dots} ${chalk2.dim(description)}`);
|
|
1247
|
+
}
|
|
1248
|
+
console.log("");
|
|
1249
|
+
console.log(chalk2.dim(" Or just type naturally \u2014 the AI understands plain English."));
|
|
1250
|
+
}
|
|
1251
|
+
},
|
|
1252
|
+
cases: {
|
|
1253
|
+
description: "List your legal cases",
|
|
1254
|
+
handler: async () => {
|
|
1255
|
+
try {
|
|
1256
|
+
await ctx.refreshCases();
|
|
1257
|
+
const cases = ctx.getCases();
|
|
1258
|
+
if (cases.length === 0) {
|
|
1259
|
+
console.log(chalk2.dim(" No cases found."));
|
|
1260
|
+
return;
|
|
1261
|
+
}
|
|
1262
|
+
console.log("");
|
|
1263
|
+
console.log(chalk2.bold(` ${cases.length} case${cases.length !== 1 ? "s" : ""}`));
|
|
1264
|
+
console.log(chalk2.dim(" " + "\u2500".repeat(40)));
|
|
1265
|
+
for (const c of cases) {
|
|
1266
|
+
const docs = c.documents?.length ?? 0;
|
|
1267
|
+
console.log(` ${chalk2.cyan(c.caseNumber)} ${c.caseName} ${chalk2.dim(`${docs} docs`)}`);
|
|
1268
|
+
}
|
|
1269
|
+
} catch (err) {
|
|
1270
|
+
console.log(chalk2.red(` Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
},
|
|
1274
|
+
status: {
|
|
1275
|
+
description: "Show sync status",
|
|
1276
|
+
handler: () => {
|
|
1277
|
+
const s = getStats();
|
|
1278
|
+
const q = queueSize();
|
|
1279
|
+
console.log("");
|
|
1280
|
+
console.log(chalk2.bold(" Sync Status"));
|
|
1281
|
+
console.log(chalk2.dim(" " + "\u2500".repeat(30)));
|
|
1282
|
+
console.log(` ${chalk2.dim("Files tracked")} ${s.totalFiles}`);
|
|
1283
|
+
console.log(` ${chalk2.dim("Synced")} ${chalk2.green(s.synced)}`);
|
|
1284
|
+
console.log(` ${chalk2.dim("Pending")} ${chalk2.yellow(s.pending)}`);
|
|
1285
|
+
console.log(` ${chalk2.dim("Errors")} ${s.errors > 0 ? chalk2.red(s.errors) : s.errors}`);
|
|
1286
|
+
console.log(` ${chalk2.dim("Queue")} ${q}`);
|
|
1287
|
+
console.log(` ${chalk2.dim("Mapped folders")} ${s.mappedFolders}`);
|
|
1288
|
+
}
|
|
1289
|
+
},
|
|
1290
|
+
folder: {
|
|
1291
|
+
description: "Manage watch folders (add/remove/list)",
|
|
1292
|
+
handler: async () => {
|
|
1293
|
+
const sub = currentSlashArgs.trim();
|
|
1294
|
+
if (!sub || sub === "list") {
|
|
1295
|
+
const folders = getWatchFolders(ctx.config);
|
|
1296
|
+
console.log("");
|
|
1297
|
+
console.log(chalk2.bold(` ${folders.length} watch folder${folders.length !== 1 ? "s" : ""}`));
|
|
1298
|
+
console.log(chalk2.dim(" " + "\u2500".repeat(40)));
|
|
1299
|
+
for (let i = 0; i < folders.length; i++) {
|
|
1300
|
+
const f = folders[i];
|
|
1301
|
+
const display = f.startsWith(HOME) ? "~" + f.slice(HOME.length) : f;
|
|
1302
|
+
const tag = f === ctx.config.watchFolder ? chalk2.cyan(" (primary)") : "";
|
|
1303
|
+
console.log(` ${chalk2.dim(`${i + 1}.`)} ${display}${tag}`);
|
|
1304
|
+
}
|
|
1305
|
+
console.log("");
|
|
1306
|
+
console.log(chalk2.dim(" /folder add <path> Add a new watch folder"));
|
|
1307
|
+
console.log(chalk2.dim(" /folder remove <path> Stop watching a folder"));
|
|
1308
|
+
console.log(chalk2.dim(" /folder set <path> Set primary watch folder"));
|
|
1309
|
+
return;
|
|
1310
|
+
}
|
|
1311
|
+
const parts = sub.split(/\s+/);
|
|
1312
|
+
const action = parts[0].toLowerCase();
|
|
1313
|
+
const folderArg = parts.slice(1).join(" ");
|
|
1314
|
+
if (action === "add") {
|
|
1315
|
+
if (!folderArg) {
|
|
1316
|
+
console.log(chalk2.red(" Usage: /folder add <path>"));
|
|
1317
|
+
return;
|
|
1318
|
+
}
|
|
1319
|
+
const absPath = normalizePath(folderArg);
|
|
1320
|
+
if (!ctx.addWatchFolder) {
|
|
1321
|
+
console.log(chalk2.red(" Watch folder management not available."));
|
|
1322
|
+
return;
|
|
1323
|
+
}
|
|
1324
|
+
const result = await ctx.addWatchFolder(absPath);
|
|
1325
|
+
if (result.added) {
|
|
1326
|
+
const display = (result.path || absPath).startsWith(HOME) ? "~" + (result.path || absPath).slice(HOME.length) : result.path || absPath;
|
|
1327
|
+
console.log(chalk2.green(` Added: ${display}`));
|
|
1328
|
+
} else {
|
|
1329
|
+
console.log(chalk2.yellow(` ${result.error || "Could not add folder"}`));
|
|
1330
|
+
}
|
|
1331
|
+
} else if (action === "remove" || action === "rm") {
|
|
1332
|
+
if (!folderArg) {
|
|
1333
|
+
console.log(chalk2.red(" Usage: /folder remove <path>"));
|
|
1334
|
+
return;
|
|
1335
|
+
}
|
|
1336
|
+
const absPath = normalizePath(folderArg);
|
|
1337
|
+
if (!ctx.removeWatchFolder) {
|
|
1338
|
+
console.log(chalk2.red(" Watch folder management not available."));
|
|
1339
|
+
return;
|
|
1340
|
+
}
|
|
1341
|
+
const result = ctx.removeWatchFolder(absPath);
|
|
1342
|
+
if (result.removed) {
|
|
1343
|
+
const display = absPath.startsWith(HOME) ? "~" + absPath.slice(HOME.length) : absPath;
|
|
1344
|
+
console.log(chalk2.green(` Removed: ${display}`));
|
|
1345
|
+
} else {
|
|
1346
|
+
console.log(chalk2.yellow(` ${result.error || "Could not remove folder"}`));
|
|
1347
|
+
}
|
|
1348
|
+
} else if (action === "set") {
|
|
1349
|
+
if (!folderArg) {
|
|
1350
|
+
console.log(chalk2.red(" Usage: /folder set <path>"));
|
|
1351
|
+
return;
|
|
1352
|
+
}
|
|
1353
|
+
const absPath = normalizePath(folderArg);
|
|
1354
|
+
if (!existsSync2(absPath) || !statSync(absPath).isDirectory()) {
|
|
1355
|
+
console.log(chalk2.red(` Not a valid directory: ${absPath}`));
|
|
1356
|
+
return;
|
|
1357
|
+
}
|
|
1358
|
+
ctx.config.watchFolder = absPath;
|
|
1359
|
+
saveConfig(ctx.config);
|
|
1360
|
+
const display = absPath.startsWith(HOME) ? "~" + absPath.slice(HOME.length) : absPath;
|
|
1361
|
+
console.log(chalk2.green(` Primary folder set to: ${display}`));
|
|
1362
|
+
} else {
|
|
1363
|
+
console.log(chalk2.dim(" Usage: /folder [list|add|remove|set] <path>"));
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
},
|
|
1367
|
+
mappings: {
|
|
1368
|
+
description: "Show folder \u2192 case mappings",
|
|
1369
|
+
handler: () => {
|
|
1370
|
+
const mappings = getAllMappings();
|
|
1371
|
+
const entries = Object.entries(mappings);
|
|
1372
|
+
if (entries.length === 0) {
|
|
1373
|
+
console.log(chalk2.dim(" No mappings yet. Sync a folder to create one."));
|
|
1374
|
+
return;
|
|
1375
|
+
}
|
|
1376
|
+
console.log("");
|
|
1377
|
+
console.log(chalk2.bold(` ${entries.length} mapping${entries.length !== 1 ? "s" : ""}`));
|
|
1378
|
+
console.log(chalk2.dim(" " + "\u2500".repeat(40)));
|
|
1379
|
+
for (const [folder, m] of entries) {
|
|
1380
|
+
console.log(` ${chalk2.cyan(folder)} \u2192 ${m.caseNumber} ${chalk2.dim(`(${m.caseName})`)}`);
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
},
|
|
1384
|
+
login: {
|
|
1385
|
+
description: "Re-authenticate with AnrakLegal",
|
|
1386
|
+
handler: async () => {
|
|
1387
|
+
try {
|
|
1388
|
+
log.info("Opening browser for login...");
|
|
1389
|
+
const tokens = await browserLogin(ctx.config.apiUrl);
|
|
1390
|
+
ctx.config.accessToken = tokens.accessToken;
|
|
1391
|
+
ctx.config.refreshToken = tokens.refreshToken;
|
|
1392
|
+
saveConfig(ctx.config);
|
|
1393
|
+
log.success("Logged in successfully");
|
|
1394
|
+
} catch (err) {
|
|
1395
|
+
console.log(chalk2.red(` Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
},
|
|
1399
|
+
logout: {
|
|
1400
|
+
description: "Clear stored credentials",
|
|
1401
|
+
handler: () => {
|
|
1402
|
+
ctx.config.accessToken = "";
|
|
1403
|
+
ctx.config.refreshToken = "";
|
|
1404
|
+
saveConfig(ctx.config);
|
|
1405
|
+
log.success("Logged out. Run /login to re-authenticate.");
|
|
1406
|
+
}
|
|
1407
|
+
},
|
|
1408
|
+
sync: {
|
|
1409
|
+
description: "Re-scan watch folder and sync",
|
|
1410
|
+
handler: async () => {
|
|
1411
|
+
try {
|
|
1412
|
+
await ctx.triggerScan();
|
|
1413
|
+
const s = getStats();
|
|
1414
|
+
if (s.pending === 0) {
|
|
1415
|
+
log.success("Everything up to date");
|
|
1416
|
+
} else {
|
|
1417
|
+
log.info(`${s.pending} file${s.pending !== 1 ? "s" : ""} pending sync`);
|
|
1418
|
+
}
|
|
1419
|
+
} catch (err) {
|
|
1420
|
+
console.log(chalk2.red(` Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
},
|
|
1424
|
+
clear: {
|
|
1425
|
+
description: "Clear conversation history",
|
|
1426
|
+
handler: () => {
|
|
1427
|
+
history.length = 1;
|
|
1428
|
+
console.log(chalk2.dim(" Conversation cleared."));
|
|
1429
|
+
}
|
|
1430
|
+
},
|
|
1431
|
+
reset: {
|
|
1432
|
+
description: "Clear sync cache (re-uploads on next sync)",
|
|
1433
|
+
handler: () => {
|
|
1434
|
+
resetCache();
|
|
1435
|
+
log.success("Cache cleared. Run /sync to re-sync.");
|
|
1436
|
+
}
|
|
1437
|
+
},
|
|
1438
|
+
quit: {
|
|
1439
|
+
description: "Exit AnrakLegal Sync",
|
|
1440
|
+
handler: () => {
|
|
1441
|
+
process.emit("SIGINT");
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
};
|
|
1445
|
+
let currentSlashArgs = "";
|
|
1193
1446
|
async function promptLoop() {
|
|
1194
1447
|
while (true) {
|
|
1195
1448
|
let input;
|
|
@@ -1200,15 +1453,44 @@ function startAIAgent(ctx) {
|
|
|
1200
1453
|
}
|
|
1201
1454
|
const trimmed = input.trim();
|
|
1202
1455
|
if (!trimmed) continue;
|
|
1456
|
+
if (trimmed.startsWith("/")) {
|
|
1457
|
+
const withoutSlash = trimmed.slice(1);
|
|
1458
|
+
const parts = withoutSlash.split(/\s+/);
|
|
1459
|
+
const cmd = (parts[0] || "").toLowerCase();
|
|
1460
|
+
if (!cmd) {
|
|
1461
|
+
console.log("");
|
|
1462
|
+
console.log(chalk2.bold(" Commands"));
|
|
1463
|
+
console.log(chalk2.dim(" " + "\u2500".repeat(Math.min(process.stdout.columns || 60, 60) - 4)));
|
|
1464
|
+
for (const [name, { description }] of Object.entries(slashCommands)) {
|
|
1465
|
+
const label = chalk2.cyan(`/${name}`);
|
|
1466
|
+
const dots = chalk2.dim(".".repeat(Math.max(2, 18 - name.length)));
|
|
1467
|
+
console.log(` ${label} ${dots} ${chalk2.dim(description)}`);
|
|
1468
|
+
}
|
|
1469
|
+
console.log("");
|
|
1470
|
+
console.log(chalk2.dim(" Or just type naturally \u2014 the AI understands plain English."));
|
|
1471
|
+
console.log("");
|
|
1472
|
+
continue;
|
|
1473
|
+
}
|
|
1474
|
+
if (slashCommands[cmd]) {
|
|
1475
|
+
currentSlashArgs = parts.slice(1).join(" ");
|
|
1476
|
+
await slashCommands[cmd].handler();
|
|
1477
|
+
currentSlashArgs = "";
|
|
1478
|
+
console.log("");
|
|
1479
|
+
continue;
|
|
1480
|
+
}
|
|
1481
|
+
const matches = Object.keys(slashCommands).filter((k) => k.startsWith(cmd));
|
|
1482
|
+
if (matches.length > 0) {
|
|
1483
|
+
console.log(chalk2.dim(` Did you mean: ${matches.map((m) => chalk2.cyan(`/${m}`)).join(", ")}?`));
|
|
1484
|
+
} else {
|
|
1485
|
+
console.log(chalk2.dim(` Unknown command. Type ${chalk2.cyan("/")} for available commands.`));
|
|
1486
|
+
}
|
|
1487
|
+
console.log("");
|
|
1488
|
+
continue;
|
|
1489
|
+
}
|
|
1203
1490
|
if (/^(quit|exit|q)$/i.test(trimmed)) {
|
|
1204
1491
|
process.emit("SIGINT");
|
|
1205
1492
|
return;
|
|
1206
1493
|
}
|
|
1207
|
-
if (/^(clear|reset|new)$/i.test(trimmed)) {
|
|
1208
|
-
history.length = 1;
|
|
1209
|
-
console.log(chalk2.dim(" Conversation cleared.\n"));
|
|
1210
|
-
continue;
|
|
1211
|
-
}
|
|
1212
1494
|
history.push({ role: "user", content: trimmed });
|
|
1213
1495
|
while (history.length > 21) {
|
|
1214
1496
|
history.splice(1, 1);
|
|
@@ -1230,10 +1512,9 @@ function startAIAgent(ctx) {
|
|
|
1230
1512
|
|
|
1231
1513
|
// src/watcher.ts
|
|
1232
1514
|
async function scanFolder(config) {
|
|
1233
|
-
const
|
|
1515
|
+
const folders = getWatchFolders(config);
|
|
1234
1516
|
let scanned = 0;
|
|
1235
|
-
|
|
1236
|
-
function walk(dir) {
|
|
1517
|
+
function walk(dir, watchRoot) {
|
|
1237
1518
|
let entries;
|
|
1238
1519
|
try {
|
|
1239
1520
|
entries = readdirSync2(dir);
|
|
@@ -1250,14 +1531,18 @@ async function scanFolder(config) {
|
|
|
1250
1531
|
continue;
|
|
1251
1532
|
}
|
|
1252
1533
|
if (s.isDirectory()) {
|
|
1253
|
-
walk(fullPath);
|
|
1534
|
+
walk(fullPath, watchRoot);
|
|
1254
1535
|
} else if (s.isFile() && isSupportedFile(entry)) {
|
|
1255
1536
|
scanned++;
|
|
1256
|
-
void enqueue(fullPath,
|
|
1537
|
+
void enqueue(fullPath, watchRoot);
|
|
1257
1538
|
}
|
|
1258
1539
|
}
|
|
1259
1540
|
}
|
|
1260
|
-
|
|
1541
|
+
for (const folder of folders) {
|
|
1542
|
+
if (existsSync3(folder)) {
|
|
1543
|
+
walk(folder, folder);
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1261
1546
|
await new Promise((r) => setTimeout(r, 500));
|
|
1262
1547
|
return { scanned, queued: queueSize() };
|
|
1263
1548
|
}
|
|
@@ -1324,7 +1609,7 @@ async function scanExternalFolder(config, folderPath, cases) {
|
|
|
1324
1609
|
}
|
|
1325
1610
|
}
|
|
1326
1611
|
async function startWatching(config) {
|
|
1327
|
-
const
|
|
1612
|
+
const folders = getWatchFolders(config);
|
|
1328
1613
|
let cases = await listCases(config);
|
|
1329
1614
|
const { scanned, queued } = await scanFolder(config);
|
|
1330
1615
|
if (queued > 0) {
|
|
@@ -1339,19 +1624,7 @@ async function startWatching(config) {
|
|
|
1339
1624
|
} catch {
|
|
1340
1625
|
}
|
|
1341
1626
|
}, 5 * 60 * 1e3);
|
|
1342
|
-
const
|
|
1343
|
-
ignored: /(^|[\/\\])(\.|~\$|Thumbs\.db|desktop\.ini)/,
|
|
1344
|
-
persistent: true,
|
|
1345
|
-
ignoreInitial: true,
|
|
1346
|
-
// We already scanned
|
|
1347
|
-
awaitWriteFinish: {
|
|
1348
|
-
stabilityThreshold: 2e3,
|
|
1349
|
-
// Wait 2s after last change
|
|
1350
|
-
pollInterval: 500
|
|
1351
|
-
},
|
|
1352
|
-
depth: 5
|
|
1353
|
-
// Max folder depth
|
|
1354
|
-
});
|
|
1627
|
+
const watchers = /* @__PURE__ */ new Map();
|
|
1355
1628
|
let debounceTimer = null;
|
|
1356
1629
|
function scheduleProcess() {
|
|
1357
1630
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
@@ -1366,26 +1639,44 @@ async function startWatching(config) {
|
|
|
1366
1639
|
}
|
|
1367
1640
|
}, 3e3);
|
|
1368
1641
|
}
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1642
|
+
function createWatcher(folder) {
|
|
1643
|
+
if (watchers.has(folder)) return;
|
|
1644
|
+
const w = watch(folder, {
|
|
1645
|
+
ignored: /(^|[\/\\])(\.|~\$|Thumbs\.db|desktop\.ini)/,
|
|
1646
|
+
persistent: true,
|
|
1647
|
+
ignoreInitial: true,
|
|
1648
|
+
awaitWriteFinish: {
|
|
1649
|
+
stabilityThreshold: 2e3,
|
|
1650
|
+
pollInterval: 500
|
|
1651
|
+
},
|
|
1652
|
+
depth: 5
|
|
1653
|
+
});
|
|
1654
|
+
w.on("add", async (path) => {
|
|
1655
|
+
const filename = basename5(path);
|
|
1656
|
+
if (!isSupportedFile(filename) || isIgnoredFile(filename)) return;
|
|
1657
|
+
log.file("detected", relative2(folder, path));
|
|
1658
|
+
await enqueue(path, folder);
|
|
1659
|
+
scheduleProcess();
|
|
1660
|
+
});
|
|
1661
|
+
w.on("change", async (path) => {
|
|
1662
|
+
const filename = basename5(path);
|
|
1663
|
+
if (!isSupportedFile(filename) || isIgnoredFile(filename)) return;
|
|
1664
|
+
log.file("changed", relative2(folder, path));
|
|
1665
|
+
await enqueue(path, folder);
|
|
1666
|
+
scheduleProcess();
|
|
1667
|
+
});
|
|
1668
|
+
w.on("unlink", (path) => {
|
|
1669
|
+
log.file("deleted", relative2(folder, path));
|
|
1670
|
+
});
|
|
1671
|
+
w.on("error", (err) => {
|
|
1672
|
+
log.error(`Watcher error (${basename5(folder)}): ${err instanceof Error ? err.message : String(err)}`);
|
|
1673
|
+
});
|
|
1674
|
+
watchers.set(folder, w);
|
|
1675
|
+
log.debug(`Watching: ${folder}`);
|
|
1676
|
+
}
|
|
1677
|
+
for (const folder of folders) {
|
|
1678
|
+
createWatcher(folder);
|
|
1679
|
+
}
|
|
1389
1680
|
startAIAgent({
|
|
1390
1681
|
config,
|
|
1391
1682
|
bootStats: { cases: cases.length, scanned, queued },
|
|
@@ -1394,7 +1685,8 @@ async function startWatching(config) {
|
|
|
1394
1685
|
cases = await listCases(config);
|
|
1395
1686
|
},
|
|
1396
1687
|
triggerScan: async () => {
|
|
1397
|
-
|
|
1688
|
+
const allFolders = getWatchFolders(config);
|
|
1689
|
+
log.info(`Re-scanning ${allFolders.length} folder(s)...`);
|
|
1398
1690
|
const result = await scanFolder(config);
|
|
1399
1691
|
log.info(`Scanned ${result.scanned} files, ${result.queued} need syncing`);
|
|
1400
1692
|
if (result.queued > 0) {
|
|
@@ -1404,13 +1696,40 @@ async function startWatching(config) {
|
|
|
1404
1696
|
},
|
|
1405
1697
|
scanFolder: async (folderPath) => {
|
|
1406
1698
|
await scanExternalFolder(config, folderPath, cases);
|
|
1699
|
+
},
|
|
1700
|
+
addWatchFolder: async (folderPath) => {
|
|
1701
|
+
const absPath = resolve2(folderPath);
|
|
1702
|
+
if (!existsSync3(absPath) || !statSync2(absPath).isDirectory()) {
|
|
1703
|
+
return { added: false, error: `Not a valid directory: ${absPath}` };
|
|
1704
|
+
}
|
|
1705
|
+
const added = addWatchFolder(config, absPath);
|
|
1706
|
+
if (!added) {
|
|
1707
|
+
return { added: false, error: "Folder already being watched" };
|
|
1708
|
+
}
|
|
1709
|
+
createWatcher(absPath);
|
|
1710
|
+
log.success(`Now watching: ${absPath}`);
|
|
1711
|
+
return { added: true, path: absPath };
|
|
1712
|
+
},
|
|
1713
|
+
removeWatchFolder: (folderPath) => {
|
|
1714
|
+
const absPath = resolve2(folderPath);
|
|
1715
|
+
const result = removeWatchFolder(config, absPath);
|
|
1716
|
+
if (result.removed) {
|
|
1717
|
+
const w = watchers.get(absPath);
|
|
1718
|
+
if (w) {
|
|
1719
|
+
void w.close();
|
|
1720
|
+
watchers.delete(absPath);
|
|
1721
|
+
}
|
|
1722
|
+
log.success(`Stopped watching: ${absPath}`);
|
|
1723
|
+
}
|
|
1724
|
+
return result;
|
|
1407
1725
|
}
|
|
1408
1726
|
});
|
|
1409
1727
|
const shutdown = () => {
|
|
1410
1728
|
log.info("Shutting down...");
|
|
1411
1729
|
clearInterval(refreshInterval);
|
|
1412
1730
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
1413
|
-
|
|
1731
|
+
const closePromises = [...watchers.values()].map((w) => w.close());
|
|
1732
|
+
Promise.all(closePromises).then(() => {
|
|
1414
1733
|
log.success("Stopped");
|
|
1415
1734
|
process.exit(0);
|
|
1416
1735
|
});
|