@anraktech/sync 0.14.0 → 0.15.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 +136 -74
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -4,14 +4,15 @@
4
4
  import { Command } from "commander";
5
5
  import { createInterface as createInterface2 } from "readline/promises";
6
6
  import { stdin as stdin2, stdout as stdout2 } from "process";
7
- import { existsSync as existsSync4, statSync as statSync3 } from "fs";
8
- import { resolve as resolve3 } from "path";
7
+ import { readFileSync as readFileSync3 } from "fs";
8
+ import { resolve as resolve4 } from "path";
9
+ import { homedir as homedir3 } from "os";
9
10
  import chalk4 from "chalk";
10
11
 
11
12
  // src/config.ts
12
13
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
13
14
  import { homedir } from "os";
14
- import { join } from "path";
15
+ import { join, resolve } from "path";
15
16
 
16
17
  // src/logger.ts
17
18
  import chalk from "chalk";
@@ -31,11 +32,29 @@ var log = {
31
32
  var CONFIG_DIR = join(homedir(), ".anrak-sync");
32
33
  var CONFIG_FILE = join(CONFIG_DIR, "config.json");
33
34
  var CACHE_FILE = join(CONFIG_DIR, "cache.json");
35
+ var HOME = homedir();
36
+ var SHORTCUTS = {
37
+ downloads: join(HOME, "Downloads"),
38
+ download: join(HOME, "Downloads"),
39
+ desktop: join(HOME, "Desktop"),
40
+ documents: join(HOME, "Documents"),
41
+ document: join(HOME, "Documents")
42
+ };
43
+ function normalizePath(p) {
44
+ if (!p) return p;
45
+ if (p.startsWith("~/") || p === "~") return resolve(p.replace(/^~/, HOME));
46
+ const lower = p.toLowerCase().replace(/^\//, "");
47
+ if (SHORTCUTS[lower]) return SHORTCUTS[lower];
48
+ if (p.startsWith("/") && !existsSync(p) && SHORTCUTS[p.slice(1).toLowerCase()]) {
49
+ return SHORTCUTS[p.slice(1).toLowerCase()];
50
+ }
51
+ return p;
52
+ }
34
53
  function getWatchFolders(config) {
35
54
  const folders = /* @__PURE__ */ new Set();
36
- if (config.watchFolder) folders.add(config.watchFolder);
55
+ if (config.watchFolder) folders.add(normalizePath(config.watchFolder));
37
56
  if (config.watchFolders) {
38
- for (const f of config.watchFolders) folders.add(f);
57
+ for (const f of config.watchFolders) folders.add(normalizePath(f));
39
58
  }
40
59
  return [...folders];
41
60
  }
@@ -115,7 +134,7 @@ function openBrowser(url) {
115
134
  exec(`${cmd} "${url}"`);
116
135
  }
117
136
  function findFreePort() {
118
- return new Promise((resolve4, reject) => {
137
+ return new Promise((resolve5, reject) => {
119
138
  const srv = createServer();
120
139
  srv.listen(0, () => {
121
140
  const addr = srv.address();
@@ -125,14 +144,14 @@ function findFreePort() {
125
144
  return;
126
145
  }
127
146
  const port = addr.port;
128
- srv.close(() => resolve4(port));
147
+ srv.close(() => resolve5(port));
129
148
  });
130
149
  });
131
150
  }
132
151
  async function browserLogin(apiUrl) {
133
152
  const port = await findFreePort();
134
153
  return new Promise(
135
- (resolve4, reject) => {
154
+ (resolve5, reject) => {
136
155
  let server;
137
156
  const timeout = setTimeout(() => {
138
157
  server?.close();
@@ -179,7 +198,7 @@ async function browserLogin(apiUrl) {
179
198
  const tokens = await resp.json();
180
199
  clearTimeout(timeout);
181
200
  server.close();
182
- resolve4(tokens);
201
+ resolve5(tokens);
183
202
  } catch (err) {
184
203
  clearTimeout(timeout);
185
204
  server.close();
@@ -367,11 +386,11 @@ function persist() {
367
386
  if (cache) saveCache(cache);
368
387
  }
369
388
  function hashFile(filePath) {
370
- return new Promise((resolve4, reject) => {
389
+ return new Promise((resolve5, reject) => {
371
390
  const hash = createHash("sha256");
372
391
  const stream = createReadStream(filePath);
373
392
  stream.on("data", (chunk) => hash.update(chunk));
374
- stream.on("end", () => resolve4(hash.digest("hex")));
393
+ stream.on("end", () => resolve5(hash.digest("hex")));
375
394
  stream.on("error", reject);
376
395
  });
377
396
  }
@@ -446,7 +465,7 @@ function resetCache() {
446
465
  // src/watcher.ts
447
466
  import { watch } from "chokidar";
448
467
  import { readdirSync as readdirSync2, statSync as statSync2, existsSync as existsSync3 } from "fs";
449
- import { join as join3, relative as relative2, basename as basename5, resolve as resolve2 } from "path";
468
+ import { join as join3, relative as relative2, basename as basename5, resolve as resolve3 } from "path";
450
469
 
451
470
  // src/uploader.ts
452
471
  import { stat as stat2 } from "fs/promises";
@@ -753,7 +772,7 @@ function sleep(ms) {
753
772
  import { createInterface } from "readline/promises";
754
773
  import { stdin, stdout } from "process";
755
774
  import { homedir as homedir2, platform } from "os";
756
- import { resolve, join as join2, dirname as dirname2, basename as basename4 } from "path";
775
+ import { resolve as resolve2, join as join2, dirname as dirname2, basename as basename4 } from "path";
757
776
  import { existsSync as existsSync2, readdirSync, statSync, readFileSync as readFileSync2 } from "fs";
758
777
  import { fileURLToPath } from "url";
759
778
  import chalk2 from "chalk";
@@ -767,21 +786,21 @@ var PKG_VERSION = (() => {
767
786
  return "0.0.0";
768
787
  }
769
788
  })();
770
- var HOME = homedir2();
789
+ var HOME2 = homedir2();
771
790
  var IS_MAC = platform() === "darwin";
772
791
  var FOLDER_SHORTCUTS = {
773
- downloads: join2(HOME, "Downloads"),
774
- download: join2(HOME, "Downloads"),
775
- desktop: join2(HOME, "Desktop"),
776
- documents: join2(HOME, "Documents"),
777
- document: join2(HOME, "Documents"),
778
- home: HOME,
779
- "~": HOME
792
+ downloads: join2(HOME2, "Downloads"),
793
+ download: join2(HOME2, "Downloads"),
794
+ desktop: join2(HOME2, "Desktop"),
795
+ documents: join2(HOME2, "Documents"),
796
+ document: join2(HOME2, "Documents"),
797
+ home: HOME2,
798
+ "~": HOME2
780
799
  };
781
- function normalizePath(folderPath) {
800
+ function normalizePath2(folderPath) {
782
801
  const trimmed = folderPath.trim();
783
802
  if (trimmed.startsWith("~/") || trimmed === "~") {
784
- return resolve(trimmed.replace(/^~/, HOME));
803
+ return resolve2(trimmed.replace(/^~/, HOME2));
785
804
  }
786
805
  const lower = trimmed.toLowerCase();
787
806
  if (FOLDER_SHORTCUTS[lower]) {
@@ -792,9 +811,9 @@ function normalizePath(folderPath) {
792
811
  return FOLDER_SHORTCUTS[withoutSlash];
793
812
  }
794
813
  if (trimmed.startsWith("/") || /^[A-Z]:\\/i.test(trimmed)) {
795
- return resolve(trimmed);
814
+ return resolve2(trimmed);
796
815
  }
797
- return resolve(HOME, trimmed);
816
+ return resolve2(HOME2, trimmed);
798
817
  }
799
818
  var TOOLS = [
800
819
  {
@@ -949,28 +968,42 @@ var TOOLS = [
949
968
  }
950
969
  }
951
970
  ];
952
- function buildSystemPrompt(config) {
971
+ function buildSystemPrompt(config, onboarding) {
953
972
  const folders = getWatchFolders(config);
954
973
  const folderList = folders.map((f) => ` - ${f}`).join("\n");
974
+ const onboardingBlock = onboarding ? `
975
+ ONBOARDING MODE \u2014 This is the user's first time using AnrakLegal Sync. You just finished setup.
976
+ Your FIRST response must:
977
+ 1. Welcome them warmly (1 line).
978
+ 2. Ask which folders on their computer contain legal files they'd like to sync. Give examples like:
979
+ - Downloads (${join2(HOME2, "Downloads")}) \u2014 where they download court orders, filings
980
+ - Desktop (${join2(HOME2, "Desktop")}) \u2014 quick-access files
981
+ - Documents (${join2(HOME2, "Documents")}) \u2014 organized case folders
982
+ - A custom folder path
983
+ 3. Tell them they can name multiple folders at once, e.g. "downloads and desktop"
984
+ 4. After the user responds, use manage_watch_folders to add each folder they mention.
985
+ 5. Once folders are set, confirm the setup and let them know you're now watching for new files.
986
+ Keep it conversational and simple \u2014 they are lawyers, not developers.
987
+ ` : "";
955
988
  return `You are AnrakLegal Sync, a terminal assistant that helps lawyers sync local files to their case management system.
956
989
 
957
990
  Watch folders:
958
991
  ${folderList}
959
992
  Server: ${config.apiUrl}
960
- Home directory: ${HOME}
993
+ Home directory: ${HOME2}
961
994
  Platform: ${IS_MAC ? "macOS" : platform()}
962
995
 
963
996
  You can browse local folders, scan & sync files, list cases, show sync status, manage watch folders, and more.
964
-
997
+ ${onboardingBlock}
965
998
  Rules:
966
999
  - Be concise. This is a terminal \u2014 1-3 lines unless showing a list.
967
1000
  - Do NOT use markdown. Plain text only.
968
1001
  - 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".
969
1002
  - IMPORTANT: When user mentions a folder like "downloads" or "desktop", ALWAYS use the full absolute path. Examples:
970
- "downloads" or "my downloads" \u2192 ${join2(HOME, "Downloads")}
971
- "desktop" \u2192 ${join2(HOME, "Desktop")}
972
- "documents" \u2192 ${join2(HOME, "Documents")}
973
- "~/SomeFolder" \u2192 ${HOME}/SomeFolder
1003
+ "downloads" or "my downloads" \u2192 ${join2(HOME2, "Downloads")}
1004
+ "desktop" \u2192 ${join2(HOME2, "Desktop")}
1005
+ "documents" \u2192 ${join2(HOME2, "Documents")}
1006
+ "~/SomeFolder" \u2192 ${HOME2}/SomeFolder
974
1007
  - FINDING FILES: When user asks to find or locate a specific file, ALWAYS use search_files. NEVER browse a folder hoping to find it.
975
1008
  - UPLOADING: When user wants to upload a specific file to a case, use upload_to_case (NOT scan_folder). scan_folder uploads EVERYTHING.
976
1009
  - When user says "look at" or "check" a folder, use browse_folder to show what's there.
@@ -1031,8 +1064,8 @@ async function executeTool(name, args, ctx) {
1031
1064
  var searchDir = searchDir2;
1032
1065
  const query = (args.query || "").trim();
1033
1066
  if (!query) return JSON.stringify({ error: "Missing search query" });
1034
- const rawSearch = args.searchPath || HOME;
1035
- const searchPath = normalizePath(rawSearch);
1067
+ const rawSearch = args.searchPath || HOME2;
1068
+ const searchPath = normalizePath2(rawSearch);
1036
1069
  const maxDepth = Math.min(args.maxDepth || 3, 6);
1037
1070
  if (!existsSync2(searchPath) || !statSync(searchPath).isDirectory()) {
1038
1071
  return JSON.stringify({ error: `Search path not found: ${searchPath}` });
@@ -1053,7 +1086,7 @@ async function executeTool(name, args, ctx) {
1053
1086
  const caseId = args.caseIdentifier;
1054
1087
  if (!rawPath) return JSON.stringify({ error: "Missing filePath" });
1055
1088
  if (!caseId) return JSON.stringify({ error: "Missing caseIdentifier" });
1056
- const filePath = normalizePath(rawPath);
1089
+ const filePath = normalizePath2(rawPath);
1057
1090
  if (!existsSync2(filePath)) {
1058
1091
  return JSON.stringify({ error: `File not found: ${filePath}` });
1059
1092
  }
@@ -1131,7 +1164,7 @@ async function executeTool(name, args, ctx) {
1131
1164
  case "browse_folder": {
1132
1165
  const rawPath = args.folderPath;
1133
1166
  if (!rawPath) return JSON.stringify({ error: "Missing folderPath" });
1134
- const folderPath = normalizePath(rawPath);
1167
+ const folderPath = normalizePath2(rawPath);
1135
1168
  if (!existsSync2(folderPath)) {
1136
1169
  return JSON.stringify({ error: `Folder not found: ${folderPath}` });
1137
1170
  }
@@ -1181,7 +1214,7 @@ async function executeTool(name, args, ctx) {
1181
1214
  case "scan_folder": {
1182
1215
  const rawPath = args.folderPath;
1183
1216
  if (!rawPath) return JSON.stringify({ error: "Missing folderPath" });
1184
- const folderPath = normalizePath(rawPath);
1217
+ const folderPath = normalizePath2(rawPath);
1185
1218
  try {
1186
1219
  await ctx.scanFolder(folderPath);
1187
1220
  const stats = getStats();
@@ -1253,7 +1286,7 @@ async function executeTool(name, args, ctx) {
1253
1286
  }
1254
1287
  if (action === "add") {
1255
1288
  if (!rawFolder) return JSON.stringify({ error: "Missing folderPath" });
1256
- const folderPath = normalizePath(rawFolder);
1289
+ const folderPath = normalizePath2(rawFolder);
1257
1290
  if (!ctx.addWatchFolder) {
1258
1291
  return JSON.stringify({ error: "Watch folder management not available" });
1259
1292
  }
@@ -1265,7 +1298,7 @@ async function executeTool(name, args, ctx) {
1265
1298
  }
1266
1299
  if (action === "remove") {
1267
1300
  if (!rawFolder) return JSON.stringify({ error: "Missing folderPath" });
1268
- const folderPath = normalizePath(rawFolder);
1301
+ const folderPath = normalizePath2(rawFolder);
1269
1302
  if (!ctx.removeWatchFolder) {
1270
1303
  return JSON.stringify({ error: "Watch folder management not available" });
1271
1304
  }
@@ -1376,13 +1409,13 @@ async function agentTurn(messages, ctx) {
1376
1409
  }
1377
1410
  function startAIAgent(ctx) {
1378
1411
  const history = [
1379
- { role: "system", content: buildSystemPrompt(ctx.config) }
1412
+ { role: "system", content: buildSystemPrompt(ctx.config, ctx.onboarding) }
1380
1413
  ];
1381
1414
  const rl = createInterface({ input: stdin, output: stdout });
1382
1415
  const W = Math.min(process.stdout.columns || 60, 60);
1383
1416
  const allFolders = getWatchFolders(ctx.config);
1384
1417
  const folderDisplays = allFolders.map(
1385
- (f) => f.startsWith(HOME) ? "~" + f.slice(HOME.length) : f
1418
+ (f) => f.startsWith(HOME2) ? "~" + f.slice(HOME2.length) : f
1386
1419
  );
1387
1420
  const stats = ctx.bootStats;
1388
1421
  const pad = (s, len) => {
@@ -1428,7 +1461,11 @@ function startAIAgent(ctx) {
1428
1461
  console.log(row(`${chalk2.dim("Status")} ${statusLine}`));
1429
1462
  }
1430
1463
  console.log(empty);
1431
- console.log(row(chalk2.dim("Type naturally, or press / for commands.")));
1464
+ if (ctx.onboarding) {
1465
+ console.log(row(chalk2.dim("First-time setup \u2014 the AI will guide you.")));
1466
+ } else {
1467
+ console.log(row(chalk2.dim("Type naturally, or press / for commands.")));
1468
+ }
1432
1469
  console.log(empty);
1433
1470
  console.log(bot);
1434
1471
  console.log("");
@@ -1499,7 +1536,7 @@ function startAIAgent(ctx) {
1499
1536
  console.log(chalk2.dim(" " + "\u2500".repeat(40)));
1500
1537
  for (let i = 0; i < folders.length; i++) {
1501
1538
  const f = folders[i];
1502
- const display = f.startsWith(HOME) ? "~" + f.slice(HOME.length) : f;
1539
+ const display = f.startsWith(HOME2) ? "~" + f.slice(HOME2.length) : f;
1503
1540
  const tag = f === ctx.config.watchFolder ? chalk2.cyan(" (primary)") : "";
1504
1541
  console.log(` ${chalk2.dim(`${i + 1}.`)} ${display}${tag}`);
1505
1542
  }
@@ -1517,14 +1554,14 @@ function startAIAgent(ctx) {
1517
1554
  console.log(chalk2.red(" Usage: /folder add <path>"));
1518
1555
  return;
1519
1556
  }
1520
- const absPath = normalizePath(folderArg);
1557
+ const absPath = normalizePath2(folderArg);
1521
1558
  if (!ctx.addWatchFolder) {
1522
1559
  console.log(chalk2.red(" Watch folder management not available."));
1523
1560
  return;
1524
1561
  }
1525
1562
  const result = await ctx.addWatchFolder(absPath);
1526
1563
  if (result.added) {
1527
- const display = (result.path || absPath).startsWith(HOME) ? "~" + (result.path || absPath).slice(HOME.length) : result.path || absPath;
1564
+ const display = (result.path || absPath).startsWith(HOME2) ? "~" + (result.path || absPath).slice(HOME2.length) : result.path || absPath;
1528
1565
  console.log(chalk2.green(` Added: ${display}`));
1529
1566
  } else {
1530
1567
  console.log(chalk2.yellow(` ${result.error || "Could not add folder"}`));
@@ -1534,14 +1571,14 @@ function startAIAgent(ctx) {
1534
1571
  console.log(chalk2.red(" Usage: /folder remove <path>"));
1535
1572
  return;
1536
1573
  }
1537
- const absPath = normalizePath(folderArg);
1574
+ const absPath = normalizePath2(folderArg);
1538
1575
  if (!ctx.removeWatchFolder) {
1539
1576
  console.log(chalk2.red(" Watch folder management not available."));
1540
1577
  return;
1541
1578
  }
1542
1579
  const result = ctx.removeWatchFolder(absPath);
1543
1580
  if (result.removed) {
1544
- const display = absPath.startsWith(HOME) ? "~" + absPath.slice(HOME.length) : absPath;
1581
+ const display = absPath.startsWith(HOME2) ? "~" + absPath.slice(HOME2.length) : absPath;
1545
1582
  console.log(chalk2.green(` Removed: ${display}`));
1546
1583
  } else {
1547
1584
  console.log(chalk2.yellow(` ${result.error || "Could not remove folder"}`));
@@ -1551,14 +1588,14 @@ function startAIAgent(ctx) {
1551
1588
  console.log(chalk2.red(" Usage: /folder set <path>"));
1552
1589
  return;
1553
1590
  }
1554
- const absPath = normalizePath(folderArg);
1591
+ const absPath = normalizePath2(folderArg);
1555
1592
  if (!existsSync2(absPath) || !statSync(absPath).isDirectory()) {
1556
1593
  console.log(chalk2.red(` Not a valid directory: ${absPath}`));
1557
1594
  return;
1558
1595
  }
1559
1596
  ctx.config.watchFolder = absPath;
1560
1597
  saveConfig(ctx.config);
1561
- const display = absPath.startsWith(HOME) ? "~" + absPath.slice(HOME.length) : absPath;
1598
+ const display = absPath.startsWith(HOME2) ? "~" + absPath.slice(HOME2.length) : absPath;
1562
1599
  console.log(chalk2.green(` Primary folder set to: ${display}`));
1563
1600
  } else {
1564
1601
  console.log(chalk2.dim(" Usage: /folder [list|add|remove|set] <path>"));
@@ -1645,7 +1682,22 @@ function startAIAgent(ctx) {
1645
1682
  };
1646
1683
  let currentSlashArgs = "";
1647
1684
  let lastPendingCount = 0;
1685
+ let onboardingDone = !ctx.onboarding;
1648
1686
  async function promptLoop() {
1687
+ if (!onboardingDone) {
1688
+ onboardingDone = true;
1689
+ history.push({ role: "user", content: "Hi, I just installed AnrakLegal Sync. Help me set up my watch folders." });
1690
+ try {
1691
+ const response = await agentTurn([...history], ctx);
1692
+ if (response) {
1693
+ history.push({ role: "assistant", content: response });
1694
+ }
1695
+ } catch (err) {
1696
+ const msg = err instanceof Error ? err.message : String(err);
1697
+ console.log(chalk2.red(` Error: ${msg}`));
1698
+ }
1699
+ console.log("");
1700
+ }
1649
1701
  while (true) {
1650
1702
  if (pendingFiles.length > 0 && pendingFiles.length !== lastPendingCount) {
1651
1703
  lastPendingCount = pendingFiles.length;
@@ -1714,7 +1766,7 @@ function startAIAgent(ctx) {
1714
1766
  while (history.length > 21) {
1715
1767
  history.splice(1, 1);
1716
1768
  }
1717
- history[0] = { role: "system", content: buildSystemPrompt(ctx.config) };
1769
+ history[0] = { role: "system", content: buildSystemPrompt(ctx.config, false) };
1718
1770
  try {
1719
1771
  const response = await agentTurn([...history], ctx);
1720
1772
  if (response) {
@@ -1868,7 +1920,7 @@ async function pushSync(config) {
1868
1920
  );
1869
1921
  }
1870
1922
  async function scanExternalFolder(config, folderPath, cases) {
1871
- const absPath = resolve2(folderPath);
1923
+ const absPath = resolve3(folderPath);
1872
1924
  if (!existsSync3(absPath)) {
1873
1925
  log.error(`Folder not found: ${absPath}`);
1874
1926
  return;
@@ -1914,7 +1966,7 @@ async function scanExternalFolder(config, folderPath, cases) {
1914
1966
  log.success("Everything up to date");
1915
1967
  }
1916
1968
  }
1917
- async function startWatching(config) {
1969
+ async function startWatching(config, opts) {
1918
1970
  const folders = getWatchFolders(config);
1919
1971
  const smartMapper = async (folderName, fileName, caseList) => smartMapFolder(config, folderName, fileName, caseList);
1920
1972
  let cases = await listCases(config);
@@ -1987,6 +2039,7 @@ async function startWatching(config) {
1987
2039
  startAIAgent({
1988
2040
  config,
1989
2041
  bootStats: { cases: cases.length, scanned, queued },
2042
+ onboarding: opts?.onboarding,
1990
2043
  getCases: () => cases,
1991
2044
  refreshCases: async () => {
1992
2045
  cases = await listCases(config);
@@ -2005,7 +2058,7 @@ async function startWatching(config) {
2005
2058
  await scanExternalFolder(config, folderPath, cases);
2006
2059
  },
2007
2060
  addWatchFolder: async (folderPath) => {
2008
- const absPath = resolve2(folderPath);
2061
+ const absPath = resolve3(folderPath);
2009
2062
  if (!existsSync3(absPath) || !statSync2(absPath).isDirectory()) {
2010
2063
  return { added: false, error: `Not a valid directory: ${absPath}` };
2011
2064
  }
@@ -2018,7 +2071,7 @@ async function startWatching(config) {
2018
2071
  return { added: true, path: absPath };
2019
2072
  },
2020
2073
  removeWatchFolder: (folderPath) => {
2021
- const absPath = resolve2(folderPath);
2074
+ const absPath = resolve3(folderPath);
2022
2075
  const result = removeWatchFolder(config, absPath);
2023
2076
  if (result.removed) {
2024
2077
  const w = watchers.get(absPath);
@@ -2119,13 +2172,34 @@ async function checkForUpdates(currentVersion) {
2119
2172
  }
2120
2173
 
2121
2174
  // src/cli.ts
2122
- import { readFileSync as readFileSync3 } from "fs";
2123
2175
  import { fileURLToPath as fileURLToPath2 } from "url";
2124
2176
  import { dirname as dirname3, join as join4 } from "path";
2125
2177
  var __filename3 = fileURLToPath2(import.meta.url);
2126
2178
  var __dirname3 = dirname3(__filename3);
2127
2179
  var pkg = JSON.parse(readFileSync3(join4(__dirname3, "..", "package.json"), "utf-8"));
2128
2180
  await checkForUpdates(pkg.version);
2181
+ var HOME3 = homedir3();
2182
+ var FOLDER_SHORTCUTS2 = {
2183
+ downloads: join4(HOME3, "Downloads"),
2184
+ download: join4(HOME3, "Downloads"),
2185
+ desktop: join4(HOME3, "Desktop"),
2186
+ documents: join4(HOME3, "Documents"),
2187
+ document: join4(HOME3, "Documents"),
2188
+ home: HOME3,
2189
+ "~": HOME3
2190
+ };
2191
+ function normalizeFolderPath(input) {
2192
+ const trimmed = input.trim();
2193
+ if (!trimmed) return trimmed;
2194
+ if (trimmed.startsWith("~/") || trimmed === "~") {
2195
+ return resolve4(trimmed.replace(/^~/, HOME3));
2196
+ }
2197
+ const lower = trimmed.toLowerCase();
2198
+ if (FOLDER_SHORTCUTS2[lower]) return FOLDER_SHORTCUTS2[lower];
2199
+ const withoutSlash = lower.replace(/^\//, "");
2200
+ if (FOLDER_SHORTCUTS2[withoutSlash]) return FOLDER_SHORTCUTS2[withoutSlash];
2201
+ return resolve4(trimmed);
2202
+ }
2129
2203
  var program = new Command();
2130
2204
  program.name("anrak-sync").description("AnrakLegal desktop file sync \u2014 watches local folders, syncs to case management").version(pkg.version);
2131
2205
  program.command("init").description("Set up AnrakLegal Sync (first-time configuration)").option("--password", "Use email/password login instead of browser").action(async (opts) => {
@@ -2163,37 +2237,25 @@ program.command("init").description("Set up AnrakLegal Sync (first-time configur
2163
2237
  tokens = await browserLogin(apiUrl);
2164
2238
  }
2165
2239
  log.success("Authenticated");
2166
- const rl2 = createInterface2({ input: stdin2, output: stdout2 });
2167
- const defaultFolder = process.platform === "win32" ? "C:\\Cases" : `${process.env.HOME}/Cases`;
2168
- const watchInput = await rl2.question(
2169
- ` Watch folder ${chalk4.dim(`(${defaultFolder})`)}: `
2170
- );
2171
- const watchFolder = resolve3(watchInput || defaultFolder);
2172
- rl2.close();
2173
- if (!existsSync4(watchFolder)) {
2174
- log.warn(
2175
- `Folder ${watchFolder} does not exist \u2014 it will be created when you add files`
2176
- );
2177
- } else if (!statSync3(watchFolder).isDirectory()) {
2178
- log.error(`${watchFolder} is not a directory`);
2179
- process.exit(1);
2240
+ try {
2241
+ rl.close();
2242
+ } catch {
2180
2243
  }
2244
+ const defaultFolder = normalizeFolderPath("downloads");
2181
2245
  const config = {
2182
2246
  apiUrl,
2183
2247
  supabaseUrl: serverConfig.supabaseUrl,
2184
2248
  supabaseAnonKey: serverConfig.supabaseAnonKey,
2185
2249
  accessToken: tokens.accessToken,
2186
2250
  refreshToken: tokens.refreshToken,
2187
- watchFolder
2251
+ watchFolder: defaultFolder
2188
2252
  };
2189
2253
  saveConfig(config);
2190
2254
  console.log("");
2191
2255
  log.success("Setup complete!");
2192
2256
  log.info(`Config saved to ${getConfigDir()}`);
2193
- log.info(`Watching: ${watchFolder}`);
2194
- console.log(
2195
- chalk4.dim("\n Run `anrak-sync start` to begin syncing\n")
2196
- );
2257
+ console.log("");
2258
+ await startWatching(config, { onboarding: true });
2197
2259
  } catch (err) {
2198
2260
  log.error(err instanceof Error ? err.message : String(err));
2199
2261
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anraktech/sync",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
4
4
  "description": "AnrakLegal desktop file sync agent — watches local folders and syncs to case management",
5
5
  "type": "module",
6
6
  "bin": {