@anraktech/sync 0.3.0 → 0.5.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 +122 -236
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  // src/cli.ts
4
4
  import { Command } from "commander";
5
- import { createInterface as createInterface3 } from "readline/promises";
6
- import { stdin as stdin3, stdout as stdout3 } from "process";
5
+ import { createInterface as createInterface2 } from "readline/promises";
6
+ import { stdin as stdin2, stdout as stdout2 } from "process";
7
7
  import { existsSync as existsSync3, statSync as statSync2 } from "fs";
8
- import { resolve as resolve2 } from "path";
9
- import chalk4 from "chalk";
8
+ import { resolve as resolve3 } from "path";
9
+ import chalk3 from "chalk";
10
10
 
11
11
  // src/config.ts
12
12
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
@@ -86,7 +86,7 @@ function openBrowser(url) {
86
86
  exec(`${cmd} "${url}"`);
87
87
  }
88
88
  function findFreePort() {
89
- return new Promise((resolve3, reject) => {
89
+ return new Promise((resolve4, reject) => {
90
90
  const srv = createServer();
91
91
  srv.listen(0, () => {
92
92
  const addr = srv.address();
@@ -96,14 +96,14 @@ function findFreePort() {
96
96
  return;
97
97
  }
98
98
  const port = addr.port;
99
- srv.close(() => resolve3(port));
99
+ srv.close(() => resolve4(port));
100
100
  });
101
101
  });
102
102
  }
103
103
  async function browserLogin(apiUrl) {
104
104
  const port = await findFreePort();
105
105
  return new Promise(
106
- (resolve3, reject) => {
106
+ (resolve4, reject) => {
107
107
  let server;
108
108
  const timeout = setTimeout(() => {
109
109
  server?.close();
@@ -150,7 +150,7 @@ async function browserLogin(apiUrl) {
150
150
  const tokens = await resp.json();
151
151
  clearTimeout(timeout);
152
152
  server.close();
153
- resolve3(tokens);
153
+ resolve4(tokens);
154
154
  } catch (err) {
155
155
  clearTimeout(timeout);
156
156
  server.close();
@@ -338,11 +338,11 @@ function persist() {
338
338
  if (cache) saveCache(cache);
339
339
  }
340
340
  function hashFile(filePath) {
341
- return new Promise((resolve3, reject) => {
341
+ return new Promise((resolve4, reject) => {
342
342
  const hash = createHash("sha256");
343
343
  const stream = createReadStream(filePath);
344
344
  stream.on("data", (chunk) => hash.update(chunk));
345
- stream.on("end", () => resolve3(hash.digest("hex")));
345
+ stream.on("end", () => resolve4(hash.digest("hex")));
346
346
  stream.on("error", reject);
347
347
  });
348
348
  }
@@ -417,10 +417,7 @@ function resetCache() {
417
417
  // src/watcher.ts
418
418
  import { watch } from "chokidar";
419
419
  import { readdirSync, statSync, existsSync as existsSync2 } from "fs";
420
- import { join as join2, relative as relative2, basename as basename4, resolve } from "path";
421
- import { createInterface as createInterface2 } from "readline/promises";
422
- import { stdin as stdin2, stdout as stdout2 } from "process";
423
- import chalk3 from "chalk";
420
+ import { join as join3, relative as relative2, basename as basename4, resolve as resolve2 } from "path";
424
421
 
425
422
  // src/uploader.ts
426
423
  import { stat as stat2 } from "fs/promises";
@@ -622,19 +619,50 @@ function sleep(ms) {
622
619
  // src/agent.ts
623
620
  import { createInterface } from "readline/promises";
624
621
  import { stdin, stdout } from "process";
622
+ import { homedir as homedir2, platform } from "os";
623
+ import { resolve, join as join2 } from "path";
625
624
  import chalk2 from "chalk";
625
+ var HOME = homedir2();
626
+ var IS_MAC = platform() === "darwin";
627
+ var FOLDER_SHORTCUTS = {
628
+ downloads: join2(HOME, "Downloads"),
629
+ download: join2(HOME, "Downloads"),
630
+ desktop: join2(HOME, "Desktop"),
631
+ documents: join2(HOME, "Documents"),
632
+ document: join2(HOME, "Documents"),
633
+ home: HOME,
634
+ "~": HOME
635
+ };
636
+ function normalizePath(folderPath) {
637
+ const trimmed = folderPath.trim();
638
+ if (trimmed.startsWith("~/") || trimmed === "~") {
639
+ return resolve(trimmed.replace(/^~/, HOME));
640
+ }
641
+ const lower = trimmed.toLowerCase();
642
+ if (FOLDER_SHORTCUTS[lower]) {
643
+ return FOLDER_SHORTCUTS[lower];
644
+ }
645
+ const withoutSlash = lower.replace(/^\//, "");
646
+ if (FOLDER_SHORTCUTS[withoutSlash]) {
647
+ return FOLDER_SHORTCUTS[withoutSlash];
648
+ }
649
+ if (trimmed.startsWith("/") || /^[A-Z]:\\/i.test(trimmed)) {
650
+ return resolve(trimmed);
651
+ }
652
+ return resolve(HOME, trimmed);
653
+ }
626
654
  var TOOLS = [
627
655
  {
628
656
  type: "function",
629
657
  function: {
630
658
  name: "scan_folder",
631
- description: "Scan a local folder on the user's computer and sync all supported files (PDF, DOCX, etc.) to a matching legal case. Use when user asks to scan, sync, upload, or look at a specific folder or directory.",
659
+ description: "Scan a local folder on the user's computer and sync all supported files (PDF, DOCX, etc.) to a matching legal case. Use when user asks to scan, sync, upload, or look at a specific folder.",
632
660
  parameters: {
633
661
  type: "object",
634
662
  properties: {
635
663
  folderPath: {
636
664
  type: "string",
637
- description: "Absolute path to the folder to scan, e.g. /Users/kapil/Downloads"
665
+ description: "Absolute path to the folder, e.g. /Users/name/Downloads"
638
666
  }
639
667
  },
640
668
  required: ["folderPath"]
@@ -645,7 +673,7 @@ var TOOLS = [
645
673
  type: "function",
646
674
  function: {
647
675
  name: "list_cases",
648
- description: "List all legal cases on the AnrakLegal server. Shows case numbers, names, and document counts.",
676
+ description: "List all legal cases on the server with case numbers, names, and document counts.",
649
677
  parameters: { type: "object", properties: {} }
650
678
  }
651
679
  },
@@ -653,7 +681,7 @@ var TOOLS = [
653
681
  type: "function",
654
682
  function: {
655
683
  name: "get_status",
656
- description: "Show sync statistics: files tracked, synced, pending, errors, queue size, and mapped folders.",
684
+ description: "Show sync stats: files tracked, synced, pending, errors, queue size.",
657
685
  parameters: { type: "object", properties: {} }
658
686
  }
659
687
  },
@@ -661,7 +689,7 @@ var TOOLS = [
661
689
  type: "function",
662
690
  function: {
663
691
  name: "get_mappings",
664
- description: "Show all folder-to-case mappings. Shows which local folders are mapped to which legal cases.",
692
+ description: "Show which local folders are mapped to which legal cases.",
665
693
  parameters: { type: "object", properties: {} }
666
694
  }
667
695
  },
@@ -669,7 +697,7 @@ var TOOLS = [
669
697
  type: "function",
670
698
  function: {
671
699
  name: "refresh_cases",
672
- description: "Refresh the case list from the server to pick up any newly created cases.",
700
+ description: "Refresh the case list from the server.",
673
701
  parameters: { type: "object", properties: {} }
674
702
  }
675
703
  },
@@ -677,37 +705,39 @@ var TOOLS = [
677
705
  type: "function",
678
706
  function: {
679
707
  name: "rescan_watch_folder",
680
- description: "Re-scan the configured watch folder for new or changed files and sync them.",
708
+ description: "Re-scan the watch folder for new or changed files and sync them.",
681
709
  parameters: { type: "object", properties: {} }
682
710
  }
683
711
  }
684
712
  ];
685
713
  function buildSystemPrompt(config) {
686
- return `You are AnrakLegal Sync Assistant, running inside a terminal CLI tool.
687
- You help lawyers sync local files to their AnrakLegal case management system.
714
+ return `You are AnrakLegal Sync, a terminal assistant that helps lawyers sync local files to their case management system.
688
715
 
689
- Current watch folder: ${config.watchFolder}
716
+ Watch folder: ${config.watchFolder}
690
717
  Server: ${config.apiUrl}
718
+ Home directory: ${HOME}
719
+ Platform: ${IS_MAC ? "macOS" : platform()}
691
720
 
692
- You can:
693
- - Scan any folder on the user's computer and sync files to cases
694
- - List cases on the server
695
- - Show sync status and folder-to-case mappings
696
- - Re-scan the watch folder for changes
697
- - Refresh the case list
698
-
699
- When the user asks you to do something, call the appropriate tool. After getting tool results, summarize them conversationally and concisely \u2014 this is a terminal, keep it brief.
721
+ You can scan folders, list cases, show sync status, and more using your tools.
700
722
 
701
- If the user mentions a folder path, use scan_folder. If they say something like "my downloads" or "desktop", expand it to a likely absolute path based on common OS conventions (e.g. /Users/<username>/Downloads on macOS).
702
-
703
- Do NOT use markdown formatting. Use plain text only. Keep responses short (1-3 lines unless showing a list).`;
723
+ Rules:
724
+ - Be concise. This is a terminal \u2014 1-3 lines unless showing a list.
725
+ - Do NOT use markdown. Plain text only.
726
+ - IMPORTANT: When user mentions a folder like "downloads" or "desktop", ALWAYS use the full absolute path. Examples:
727
+ "downloads" or "my downloads" \u2192 ${join2(HOME, "Downloads")}
728
+ "desktop" \u2192 ${join2(HOME, "Desktop")}
729
+ "documents" \u2192 ${join2(HOME, "Documents")}
730
+ "~/SomeFolder" \u2192 ${HOME}/SomeFolder
731
+ - Call tools to perform actions. Summarize results naturally after.
732
+ - If a tool returns an error, report it clearly to the user.
733
+ - Do NOT use thinking tags or reasoning tags in your output.`;
704
734
  }
705
735
  async function executeTool(name, args, ctx) {
706
736
  switch (name) {
707
737
  case "scan_folder": {
708
- const folderPath = args.folderPath;
709
- if (!folderPath) return JSON.stringify({ error: "Missing folderPath" });
710
- log.dim(`Scanning ${folderPath}...`);
738
+ const rawPath = args.folderPath;
739
+ if (!rawPath) return JSON.stringify({ error: "Missing folderPath" });
740
+ const folderPath = normalizePath(rawPath);
711
741
  try {
712
742
  await ctx.scanFolder(folderPath);
713
743
  const stats = getStats();
@@ -720,9 +750,7 @@ async function executeTool(name, args, ctx) {
720
750
  errors: stats.errors
721
751
  });
722
752
  } catch (err) {
723
- return JSON.stringify({
724
- error: err instanceof Error ? err.message : String(err)
725
- });
753
+ return JSON.stringify({ error: err instanceof Error ? err.message : String(err) });
726
754
  }
727
755
  }
728
756
  case "list_cases": {
@@ -753,13 +781,9 @@ async function executeTool(name, args, ctx) {
753
781
  }
754
782
  case "refresh_cases": {
755
783
  await ctx.refreshCases();
756
- return JSON.stringify({
757
- success: true,
758
- caseCount: ctx.getCases().length
759
- });
784
+ return JSON.stringify({ success: true, caseCount: ctx.getCases().length });
760
785
  }
761
786
  case "rescan_watch_folder": {
762
- log.dim(`Re-scanning ${ctx.config.watchFolder}...`);
763
787
  await ctx.triggerScan();
764
788
  const stats = getStats();
765
789
  return JSON.stringify({
@@ -792,42 +816,31 @@ async function* parseSSE(response) {
792
816
  const parsed = JSON.parse(data);
793
817
  const choice = parsed.choices?.[0];
794
818
  if (choice?.delta) {
795
- yield {
796
- delta: choice.delta,
797
- finishReason: choice.finish_reason ?? null
798
- };
819
+ yield { delta: choice.delta, finishReason: choice.finish_reason ?? null };
799
820
  }
800
821
  } catch {
801
822
  }
802
823
  }
803
824
  }
804
825
  }
805
- var MODEL = "qwen/qwen3-8b";
806
826
  async function agentTurn(messages, ctx) {
807
- const response = await fetch("https://openrouter.ai/api/v1/chat/completions", {
827
+ const token = await getAccessToken(ctx.config);
828
+ const response = await fetch(`${ctx.config.apiUrl}/api/sync/chat`, {
808
829
  method: "POST",
809
830
  headers: {
810
- Authorization: `Bearer ${ctx.apiKey}`,
811
- "Content-Type": "application/json",
812
- "HTTP-Referer": "https://anrak.legal",
813
- "X-Title": "AnrakLegal Sync CLI"
831
+ Authorization: `Bearer ${token}`,
832
+ "Content-Type": "application/json"
814
833
  },
815
- body: JSON.stringify({
816
- model: MODEL,
817
- messages,
818
- tools: TOOLS,
819
- stream: true
820
- })
834
+ body: JSON.stringify({ messages, tools: TOOLS })
821
835
  });
822
836
  if (!response.ok) {
823
837
  const body = await response.text().catch(() => "");
824
- throw new Error(`API error (${response.status}): ${body.slice(0, 200)}`);
838
+ throw new Error(`Server error (${response.status}): ${body.slice(0, 200)}`);
825
839
  }
826
840
  let textContent = "";
827
841
  const toolCalls = /* @__PURE__ */ new Map();
828
842
  for await (const { delta } of parseSSE(response)) {
829
843
  if (delta.content) {
830
- process.stdout.write(delta.content);
831
844
  textContent += delta.content;
832
845
  }
833
846
  if (delta.tool_calls) {
@@ -845,8 +858,10 @@ async function agentTurn(messages, ctx) {
845
858
  }
846
859
  }
847
860
  }
861
+ textContent = textContent.replace(/<think>[\s\S]*?<\/think>/g, "").trim();
848
862
  if (textContent) {
849
- process.stdout.write("\n");
863
+ process.stdout.write(` ${textContent}
864
+ `);
850
865
  }
851
866
  if (toolCalls.size === 0) {
852
867
  return textContent;
@@ -862,29 +877,33 @@ async function agentTurn(messages, ctx) {
862
877
  };
863
878
  messages.push(assistantMsg);
864
879
  for (const tc of toolCalls.values()) {
880
+ process.stdout.write(chalk2.dim(` \u27F3 ${tc.name}...
881
+ `));
865
882
  let args = {};
866
883
  try {
867
884
  args = JSON.parse(tc.args || "{}");
868
885
  } catch {
869
886
  }
870
887
  const result = await executeTool(tc.name, args, ctx);
871
- messages.push({
872
- role: "tool",
873
- content: result,
874
- tool_call_id: tc.id
875
- });
888
+ messages.push({ role: "tool", content: result, tool_call_id: tc.id });
876
889
  }
877
890
  return agentTurn(messages, ctx);
878
891
  }
879
892
  function startAIAgent(ctx) {
880
- const PROMPT = chalk2.blue("anrak> ");
881
893
  const history = [
882
894
  { role: "system", content: buildSystemPrompt(ctx.config) }
883
895
  ];
884
896
  const rl = createInterface({ input: stdin, output: stdout });
885
897
  console.log("");
886
- log.info("AI assistant ready. Type naturally \u2014 ask me to scan folders, show cases, etc.");
887
- log.dim('Type "quit" to exit.\n');
898
+ console.log(chalk2.bold(" AnrakLegal Sync"));
899
+ console.log(chalk2.dim(` Watching ${ctx.config.watchFolder}`));
900
+ console.log(chalk2.dim(` Connected to ${ctx.config.apiUrl}`));
901
+ console.log("");
902
+ console.log(
903
+ chalk2.dim(" Ask me anything \u2014 scan folders, check cases, show status.")
904
+ );
905
+ console.log(chalk2.dim(' Type "quit" to exit.\n'));
906
+ const PROMPT = `${chalk2.bold.blue("\u276F")} `;
888
907
  async function promptLoop() {
889
908
  while (true) {
890
909
  let input;
@@ -901,7 +920,7 @@ function startAIAgent(ctx) {
901
920
  }
902
921
  if (/^(clear|reset|new)$/i.test(trimmed)) {
903
922
  history.length = 1;
904
- log.success("Conversation cleared");
923
+ console.log(chalk2.dim(" Conversation cleared.\n"));
905
924
  continue;
906
925
  }
907
926
  history.push({ role: "user", content: trimmed });
@@ -915,10 +934,7 @@ function startAIAgent(ctx) {
915
934
  }
916
935
  } catch (err) {
917
936
  const msg = err instanceof Error ? err.message : String(err);
918
- log.error(msg);
919
- if (msg.includes("401") || msg.includes("403")) {
920
- log.warn("Check your OpenRouter API key (OPENROUTER_API_KEY env var)");
921
- }
937
+ console.log(chalk2.red(` Error: ${msg}`));
922
938
  }
923
939
  console.log("");
924
940
  }
@@ -940,7 +956,7 @@ async function scanFolder(config) {
940
956
  }
941
957
  for (const entry of entries) {
942
958
  if (isIgnoredFile(entry)) continue;
943
- const fullPath = join2(dir, entry);
959
+ const fullPath = join3(dir, entry);
944
960
  let s;
945
961
  try {
946
962
  s = statSync(fullPath);
@@ -975,7 +991,7 @@ async function pushSync(config) {
975
991
  );
976
992
  }
977
993
  async function scanExternalFolder(config, folderPath, cases) {
978
- const absPath = resolve(folderPath);
994
+ const absPath = resolve2(folderPath);
979
995
  if (!existsSync2(absPath)) {
980
996
  log.error(`Folder not found: ${absPath}`);
981
997
  return;
@@ -995,7 +1011,7 @@ async function scanExternalFolder(config, folderPath, cases) {
995
1011
  }
996
1012
  for (const entry of entries) {
997
1013
  if (isIgnoredFile(entry)) continue;
998
- const fullPath = join2(dir, entry);
1014
+ const fullPath = join3(dir, entry);
999
1015
  let s;
1000
1016
  try {
1001
1017
  s = statSync(fullPath);
@@ -1006,7 +1022,7 @@ async function scanExternalFolder(config, folderPath, cases) {
1006
1022
  walk(fullPath);
1007
1023
  } else if (s.isFile() && isSupportedFile(entry)) {
1008
1024
  fileCount++;
1009
- void enqueue(fullPath, join2(absPath, ".."));
1025
+ void enqueue(fullPath, join3(absPath, ".."));
1010
1026
  }
1011
1027
  }
1012
1028
  }
@@ -1021,129 +1037,6 @@ async function scanExternalFolder(config, folderPath, cases) {
1021
1037
  log.success("Everything up to date");
1022
1038
  }
1023
1039
  }
1024
- function startInteractiveAgent(config, getCases, refreshCases, triggerScan) {
1025
- const PROMPT = chalk3.blue("anrak> ");
1026
- const rl = createInterface2({ input: stdin2, output: stdout2 });
1027
- function showHelp() {
1028
- console.log("");
1029
- console.log(chalk3.bold(" Available commands:"));
1030
- console.log("");
1031
- console.log(` ${chalk3.cyan("scan <folder>")} Scan a folder and sync its files to a case`);
1032
- console.log(` ${chalk3.cyan("rescan")} Re-scan the watch folder for new files`);
1033
- console.log(` ${chalk3.cyan("cases")} List cases on the server`);
1034
- console.log(` ${chalk3.cyan("status")} Show sync statistics`);
1035
- console.log(` ${chalk3.cyan("mappings")} Show folder \u2192 case mappings`);
1036
- console.log(` ${chalk3.cyan("refresh")} Refresh cases from server`);
1037
- console.log(` ${chalk3.cyan("help")} Show this help`);
1038
- console.log(` ${chalk3.cyan("quit")} Stop syncing and exit`);
1039
- console.log("");
1040
- }
1041
- async function handleCommand(input) {
1042
- const trimmed = input.trim();
1043
- if (!trimmed) return;
1044
- const [cmd, ...args] = trimmed.split(/\s+/);
1045
- const arg = args.join(" ");
1046
- switch (cmd.toLowerCase()) {
1047
- case "scan": {
1048
- if (!arg) {
1049
- log.warn("Usage: scan <folder path>");
1050
- break;
1051
- }
1052
- await scanExternalFolder(config, arg, getCases());
1053
- break;
1054
- }
1055
- case "rescan": {
1056
- await triggerScan();
1057
- break;
1058
- }
1059
- case "cases": {
1060
- await refreshCases();
1061
- const cases = getCases();
1062
- if (cases.length === 0) {
1063
- log.info("No cases found on server");
1064
- } else {
1065
- console.log("");
1066
- for (const c of cases) {
1067
- const docCount = c.documents?.length ?? 0;
1068
- console.log(
1069
- ` ${chalk3.cyan(c.caseNumber)} ${c.caseName} ${chalk3.dim(`(${docCount} docs)`)}`
1070
- );
1071
- }
1072
- console.log("");
1073
- }
1074
- break;
1075
- }
1076
- case "status": {
1077
- const stats = getStats();
1078
- console.log("");
1079
- console.log(` ${chalk3.bold("Watch folder:")} ${config.watchFolder}`);
1080
- console.log(` ${chalk3.bold("Files tracked:")} ${stats.totalFiles}`);
1081
- console.log(` ${chalk3.bold("Synced:")} ${chalk3.green(String(stats.synced))}`);
1082
- console.log(` ${chalk3.bold("Pending:")} ${chalk3.yellow(String(stats.pending))}`);
1083
- console.log(` ${chalk3.bold("Errors:")} ${chalk3.red(String(stats.errors))}`);
1084
- console.log(` ${chalk3.bold("Mapped folders:")} ${stats.mappedFolders}`);
1085
- console.log(` ${chalk3.bold("Queue:")} ${queueSize()}`);
1086
- console.log("");
1087
- break;
1088
- }
1089
- case "mappings":
1090
- case "map": {
1091
- const mappings = getAllMappings();
1092
- const entries = Object.entries(mappings);
1093
- if (entries.length === 0) {
1094
- log.info("No folder mappings yet");
1095
- } else {
1096
- console.log("");
1097
- for (const [folder, m] of entries) {
1098
- console.log(
1099
- ` ${chalk3.cyan(folder)} \u2192 ${m.caseNumber} ${chalk3.dim(`(${m.caseName})`)}`
1100
- );
1101
- }
1102
- console.log("");
1103
- }
1104
- break;
1105
- }
1106
- case "refresh": {
1107
- log.info("Refreshing cases from server...");
1108
- await refreshCases();
1109
- log.success(`Found ${getCases().length} case(s)`);
1110
- break;
1111
- }
1112
- case "help":
1113
- case "?": {
1114
- showHelp();
1115
- break;
1116
- }
1117
- case "quit":
1118
- case "exit":
1119
- case "q": {
1120
- process.emit("SIGINT");
1121
- return;
1122
- }
1123
- default: {
1124
- log.warn(`Unknown command: ${cmd}. Type ${chalk3.cyan("help")} for available commands.`);
1125
- break;
1126
- }
1127
- }
1128
- }
1129
- showHelp();
1130
- async function promptLoop() {
1131
- while (true) {
1132
- let input;
1133
- try {
1134
- input = await rl.question(PROMPT);
1135
- } catch {
1136
- break;
1137
- }
1138
- try {
1139
- await handleCommand(input);
1140
- } catch (err) {
1141
- log.error(err instanceof Error ? err.message : String(err));
1142
- }
1143
- }
1144
- }
1145
- void promptLoop();
1146
- }
1147
1040
  async function startWatching(config) {
1148
1041
  const folder = config.watchFolder;
1149
1042
  log.info(`Scanning ${folder}...`);
@@ -1211,8 +1104,7 @@ async function startWatching(config) {
1211
1104
  watcher.on("error", (err) => {
1212
1105
  log.error(`Watcher error: ${err instanceof Error ? err.message : String(err)}`);
1213
1106
  });
1214
- const apiKey = process.env.OPENROUTER_API_KEY ?? config.openrouterApiKey;
1215
- const agentContext = {
1107
+ startAIAgent({
1216
1108
  config,
1217
1109
  getCases: () => cases,
1218
1110
  refreshCases: async () => {
@@ -1230,13 +1122,7 @@ async function startWatching(config) {
1230
1122
  scanFolder: async (folderPath) => {
1231
1123
  await scanExternalFolder(config, folderPath, cases);
1232
1124
  }
1233
- };
1234
- if (apiKey) {
1235
- startAIAgent({ ...agentContext, apiKey });
1236
- } else {
1237
- log.dim("Tip: Set OPENROUTER_API_KEY for AI-powered natural language mode\n");
1238
- startInteractiveAgent(config, agentContext.getCases, agentContext.refreshCases, agentContext.triggerScan);
1239
- }
1125
+ });
1240
1126
  const shutdown = () => {
1241
1127
  log.info("Shutting down...");
1242
1128
  clearInterval(refreshInterval);
@@ -1253,18 +1139,18 @@ async function startWatching(config) {
1253
1139
  // src/cli.ts
1254
1140
  import { readFileSync as readFileSync2 } from "fs";
1255
1141
  import { fileURLToPath } from "url";
1256
- import { dirname as dirname2, join as join3 } from "path";
1142
+ import { dirname as dirname2, join as join4 } from "path";
1257
1143
  var __filename2 = fileURLToPath(import.meta.url);
1258
1144
  var __dirname2 = dirname2(__filename2);
1259
- var pkg = JSON.parse(readFileSync2(join3(__dirname2, "..", "package.json"), "utf-8"));
1145
+ var pkg = JSON.parse(readFileSync2(join4(__dirname2, "..", "package.json"), "utf-8"));
1260
1146
  var program = new Command();
1261
1147
  program.name("anrak-sync").description("AnrakLegal desktop file sync \u2014 watches local folders, syncs to case management").version(pkg.version);
1262
1148
  program.command("init").description("Set up AnrakLegal Sync (first-time configuration)").option("--password", "Use email/password login instead of browser").action(async (opts) => {
1263
- const rl = createInterface3({ input: stdin3, output: stdout3 });
1149
+ const rl = createInterface2({ input: stdin2, output: stdout2 });
1264
1150
  try {
1265
- console.log(chalk4.bold.blue("\n AnrakLegal Sync \u2014 Setup\n"));
1151
+ console.log(chalk3.bold.blue("\n AnrakLegal Sync \u2014 Setup\n"));
1266
1152
  const apiUrl = await rl.question(
1267
- ` AnrakLegal URL ${chalk4.dim("(https://anrak.legal)")}: `
1153
+ ` AnrakLegal URL ${chalk3.dim("(https://anrak.legal)")}: `
1268
1154
  ) || "https://anrak.legal";
1269
1155
  log.info("Connecting to server...");
1270
1156
  const serverConfig = await fetchServerConfig(apiUrl);
@@ -1292,12 +1178,12 @@ program.command("init").description("Set up AnrakLegal Sync (first-time configur
1292
1178
  tokens = await browserLogin(apiUrl);
1293
1179
  }
1294
1180
  log.success("Authenticated");
1295
- const rl2 = createInterface3({ input: stdin3, output: stdout3 });
1181
+ const rl2 = createInterface2({ input: stdin2, output: stdout2 });
1296
1182
  const defaultFolder = process.platform === "win32" ? "C:\\Cases" : `${process.env.HOME}/Cases`;
1297
1183
  const watchInput = await rl2.question(
1298
- ` Watch folder ${chalk4.dim(`(${defaultFolder})`)}: `
1184
+ ` Watch folder ${chalk3.dim(`(${defaultFolder})`)}: `
1299
1185
  );
1300
- const watchFolder = resolve2(watchInput || defaultFolder);
1186
+ const watchFolder = resolve3(watchInput || defaultFolder);
1301
1187
  rl2.close();
1302
1188
  if (!existsSync3(watchFolder)) {
1303
1189
  log.warn(
@@ -1321,7 +1207,7 @@ program.command("init").description("Set up AnrakLegal Sync (first-time configur
1321
1207
  log.info(`Config saved to ${getConfigDir()}`);
1322
1208
  log.info(`Watching: ${watchFolder}`);
1323
1209
  console.log(
1324
- chalk4.dim("\n Run `anrak-sync start` to begin syncing\n")
1210
+ chalk3.dim("\n Run `anrak-sync start` to begin syncing\n")
1325
1211
  );
1326
1212
  } catch (err) {
1327
1213
  log.error(err instanceof Error ? err.message : String(err));
@@ -1333,7 +1219,7 @@ program.command("login").description("Re-authenticate with AnrakLegal").option("
1333
1219
  try {
1334
1220
  let tokens;
1335
1221
  if (opts.password) {
1336
- const rl = createInterface3({ input: stdin3, output: stdout3 });
1222
+ const rl = createInterface2({ input: stdin2, output: stdout2 });
1337
1223
  try {
1338
1224
  const email = await rl.question(" Email: ");
1339
1225
  const password = await rl.question(" Password: ");
@@ -1356,7 +1242,7 @@ program.command("login").description("Re-authenticate with AnrakLegal").option("
1356
1242
  });
1357
1243
  program.command("start").description("Start watching for file changes and syncing").action(async () => {
1358
1244
  const config = requireConfig();
1359
- console.log(chalk4.bold.blue("\n AnrakLegal Sync\n"));
1245
+ console.log(chalk3.bold.blue("\n AnrakLegal Sync\n"));
1360
1246
  log.info(`Watching: ${config.watchFolder}`);
1361
1247
  log.info(`Server: ${config.apiUrl}`);
1362
1248
  console.log("");
@@ -1369,7 +1255,7 @@ program.command("start").description("Start watching for file changes and syncin
1369
1255
  });
1370
1256
  program.command("push").description("One-time sync \u2014 upload all new/changed files, then exit").action(async () => {
1371
1257
  const config = requireConfig();
1372
- console.log(chalk4.bold.blue("\n AnrakLegal Sync \u2014 Push\n"));
1258
+ console.log(chalk3.bold.blue("\n AnrakLegal Sync \u2014 Push\n"));
1373
1259
  log.info(`Folder: ${config.watchFolder}`);
1374
1260
  log.info(`Server: ${config.apiUrl}`);
1375
1261
  console.log("");
@@ -1383,24 +1269,24 @@ program.command("push").description("One-time sync \u2014 upload all new/changed
1383
1269
  program.command("status").description("Show sync status").action(async () => {
1384
1270
  const config = requireConfig();
1385
1271
  const stats = getStats();
1386
- console.log(chalk4.bold.blue("\n AnrakLegal Sync \u2014 Status\n"));
1272
+ console.log(chalk3.bold.blue("\n AnrakLegal Sync \u2014 Status\n"));
1387
1273
  console.log(` Server: ${config.apiUrl}`);
1388
1274
  console.log(` Watch folder: ${config.watchFolder}`);
1389
1275
  console.log(` Config: ${getConfigDir()}`);
1390
1276
  console.log("");
1391
1277
  console.log(` Files tracked: ${stats.totalFiles}`);
1392
- console.log(` Synced: ${chalk4.green(stats.synced)}`);
1393
- console.log(` Pending: ${chalk4.yellow(stats.pending)}`);
1394
- console.log(` Errors: ${chalk4.red(stats.errors)}`);
1278
+ console.log(` Synced: ${chalk3.green(stats.synced)}`);
1279
+ console.log(` Pending: ${chalk3.yellow(stats.pending)}`);
1280
+ console.log(` Errors: ${chalk3.red(stats.errors)}`);
1395
1281
  console.log(` Mapped folders: ${stats.mappedFolders}`);
1396
1282
  try {
1397
1283
  const cases = await listCases(config);
1398
1284
  console.log(`
1399
1285
  Server cases: ${cases.length}`);
1400
- console.log(` Auth: ${chalk4.green("valid")}`);
1286
+ console.log(` Auth: ${chalk3.green("valid")}`);
1401
1287
  } catch {
1402
1288
  console.log(`
1403
- Auth: ${chalk4.red("expired \u2014 run anrak-sync login")}`);
1289
+ Auth: ${chalk3.red("expired \u2014 run anrak-sync login")}`);
1404
1290
  }
1405
1291
  console.log("");
1406
1292
  });
@@ -1408,13 +1294,13 @@ program.command("map").description("Show folder-to-case mappings").action(async
1408
1294
  const config = requireConfig();
1409
1295
  const mappings = getAllMappings();
1410
1296
  const entries = Object.entries(mappings);
1411
- console.log(chalk4.bold.blue("\n AnrakLegal Sync \u2014 Mappings\n"));
1297
+ console.log(chalk3.bold.blue("\n AnrakLegal Sync \u2014 Mappings\n"));
1412
1298
  if (entries.length === 0) {
1413
1299
  log.info("No mappings yet. Run `anrak-sync push` or `anrak-sync start` to create them.");
1414
1300
  } else {
1415
1301
  for (const [folder, mapping] of entries) {
1416
1302
  console.log(
1417
- ` ${chalk4.cyan(folder)} -> ${mapping.caseNumber} (${chalk4.dim(mapping.caseName)})`
1303
+ ` ${chalk3.cyan(folder)} -> ${mapping.caseNumber} (${chalk3.dim(mapping.caseName)})`
1418
1304
  );
1419
1305
  }
1420
1306
  }
@@ -1423,9 +1309,9 @@ program.command("map").description("Show folder-to-case mappings").action(async
1423
1309
  const mappedIds = new Set(entries.map(([, m]) => m.caseId));
1424
1310
  const unmapped = cases.filter((c) => !mappedIds.has(c.id));
1425
1311
  if (unmapped.length > 0) {
1426
- console.log(chalk4.dim("\n Unmapped server cases:"));
1312
+ console.log(chalk3.dim("\n Unmapped server cases:"));
1427
1313
  for (const c of unmapped) {
1428
- console.log(` ${chalk4.dim(c.caseNumber)} ${chalk4.dim(c.caseName)}`);
1314
+ console.log(` ${chalk3.dim(c.caseNumber)} ${chalk3.dim(c.caseName)}`);
1429
1315
  }
1430
1316
  }
1431
1317
  } catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anraktech/sync",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "AnrakLegal desktop file sync agent — watches local folders and syncs to case management",
5
5
  "type": "module",
6
6
  "bin": {