@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.
Files changed (2) hide show
  1. package/dist/cli.js +371 -52
  2. 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 folder: ${config.watchFolder}
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 watchDisplay = ctx.config.watchFolder.startsWith(HOME) ? "~" + ctx.config.watchFolder.slice(HOME.length) : ctx.config.watchFolder;
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
- console.log(row(`${chalk2.dim("Folder")} ${watchDisplay}`));
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 to manage your files.")));
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 folder = config.watchFolder;
1515
+ const folders = getWatchFolders(config);
1234
1516
  let scanned = 0;
1235
- let queued = 0;
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, folder).then(() => queued++);
1537
+ void enqueue(fullPath, watchRoot);
1257
1538
  }
1258
1539
  }
1259
1540
  }
1260
- walk(folder);
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 folder = config.watchFolder;
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 watcher = watch(folder, {
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
- watcher.on("add", async (path) => {
1370
- const filename = basename5(path);
1371
- if (!isSupportedFile(filename) || isIgnoredFile(filename)) return;
1372
- log.file("detected", relative2(folder, path));
1373
- await enqueue(path, folder);
1374
- scheduleProcess();
1375
- });
1376
- watcher.on("change", async (path) => {
1377
- const filename = basename5(path);
1378
- if (!isSupportedFile(filename) || isIgnoredFile(filename)) return;
1379
- log.file("changed", relative2(folder, path));
1380
- await enqueue(path, folder);
1381
- scheduleProcess();
1382
- });
1383
- watcher.on("unlink", (path) => {
1384
- log.file("deleted", relative2(folder, path));
1385
- });
1386
- watcher.on("error", (err) => {
1387
- log.error(`Watcher error: ${err instanceof Error ? err.message : String(err)}`);
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
- log.info(`Re-scanning ${folder}...`);
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
- watcher.close().then(() => {
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
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anraktech/sync",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "description": "AnrakLegal desktop file sync agent — watches local folders and syncs to case management",
5
5
  "type": "module",
6
6
  "bin": {