@anraktech/sync 0.2.0 → 0.3.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 +375 -52
  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 createInterface2 } from "readline/promises";
6
- import { stdin as stdin2, stdout as stdout2 } from "process";
5
+ import { createInterface as createInterface3 } from "readline/promises";
6
+ import { stdin as stdin3, stdout as stdout3 } from "process";
7
7
  import { existsSync as existsSync3, statSync as statSync2 } from "fs";
8
8
  import { resolve as resolve2 } from "path";
9
- import chalk3 from "chalk";
9
+ import chalk4 from "chalk";
10
10
 
11
11
  // src/config.ts
12
12
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
@@ -418,9 +418,9 @@ function resetCache() {
418
418
  import { watch } from "chokidar";
419
419
  import { readdirSync, statSync, existsSync as existsSync2 } from "fs";
420
420
  import { join as join2, relative as relative2, basename as basename4, resolve } from "path";
421
- import { createInterface } from "readline/promises";
422
- import { stdin, stdout } from "process";
423
- import chalk2 from "chalk";
421
+ import { createInterface as createInterface2 } from "readline/promises";
422
+ import { stdin as stdin2, stdout as stdout2 } from "process";
423
+ import chalk3 from "chalk";
424
424
 
425
425
  // src/uploader.ts
426
426
  import { stat as stat2 } from "fs/promises";
@@ -619,6 +619,313 @@ function sleep(ms) {
619
619
  return new Promise((r) => setTimeout(r, ms));
620
620
  }
621
621
 
622
+ // src/agent.ts
623
+ import { createInterface } from "readline/promises";
624
+ import { stdin, stdout } from "process";
625
+ import chalk2 from "chalk";
626
+ var TOOLS = [
627
+ {
628
+ type: "function",
629
+ function: {
630
+ 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.",
632
+ parameters: {
633
+ type: "object",
634
+ properties: {
635
+ folderPath: {
636
+ type: "string",
637
+ description: "Absolute path to the folder to scan, e.g. /Users/kapil/Downloads"
638
+ }
639
+ },
640
+ required: ["folderPath"]
641
+ }
642
+ }
643
+ },
644
+ {
645
+ type: "function",
646
+ function: {
647
+ name: "list_cases",
648
+ description: "List all legal cases on the AnrakLegal server. Shows case numbers, names, and document counts.",
649
+ parameters: { type: "object", properties: {} }
650
+ }
651
+ },
652
+ {
653
+ type: "function",
654
+ function: {
655
+ name: "get_status",
656
+ description: "Show sync statistics: files tracked, synced, pending, errors, queue size, and mapped folders.",
657
+ parameters: { type: "object", properties: {} }
658
+ }
659
+ },
660
+ {
661
+ type: "function",
662
+ function: {
663
+ name: "get_mappings",
664
+ description: "Show all folder-to-case mappings. Shows which local folders are mapped to which legal cases.",
665
+ parameters: { type: "object", properties: {} }
666
+ }
667
+ },
668
+ {
669
+ type: "function",
670
+ function: {
671
+ name: "refresh_cases",
672
+ description: "Refresh the case list from the server to pick up any newly created cases.",
673
+ parameters: { type: "object", properties: {} }
674
+ }
675
+ },
676
+ {
677
+ type: "function",
678
+ function: {
679
+ name: "rescan_watch_folder",
680
+ description: "Re-scan the configured watch folder for new or changed files and sync them.",
681
+ parameters: { type: "object", properties: {} }
682
+ }
683
+ }
684
+ ];
685
+ 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.
688
+
689
+ Current watch folder: ${config.watchFolder}
690
+ Server: ${config.apiUrl}
691
+
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.
700
+
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).`;
704
+ }
705
+ async function executeTool(name, args, ctx) {
706
+ switch (name) {
707
+ case "scan_folder": {
708
+ const folderPath = args.folderPath;
709
+ if (!folderPath) return JSON.stringify({ error: "Missing folderPath" });
710
+ log.dim(`Scanning ${folderPath}...`);
711
+ try {
712
+ await ctx.scanFolder(folderPath);
713
+ const stats = getStats();
714
+ return JSON.stringify({
715
+ success: true,
716
+ folderPath,
717
+ totalFiles: stats.totalFiles,
718
+ synced: stats.synced,
719
+ pending: stats.pending,
720
+ errors: stats.errors
721
+ });
722
+ } catch (err) {
723
+ return JSON.stringify({
724
+ error: err instanceof Error ? err.message : String(err)
725
+ });
726
+ }
727
+ }
728
+ case "list_cases": {
729
+ await ctx.refreshCases();
730
+ const cases = ctx.getCases();
731
+ return JSON.stringify(
732
+ cases.map((c) => ({
733
+ caseNumber: c.caseNumber,
734
+ caseName: c.caseName,
735
+ status: c.status,
736
+ documents: c.documents?.length ?? 0
737
+ }))
738
+ );
739
+ }
740
+ case "get_status": {
741
+ const stats = getStats();
742
+ return JSON.stringify({ ...stats, queueSize: queueSize() });
743
+ }
744
+ case "get_mappings": {
745
+ const mappings = getAllMappings();
746
+ return JSON.stringify(
747
+ Object.entries(mappings).map(([folder, m]) => ({
748
+ folder,
749
+ caseNumber: m.caseNumber,
750
+ caseName: m.caseName
751
+ }))
752
+ );
753
+ }
754
+ case "refresh_cases": {
755
+ await ctx.refreshCases();
756
+ return JSON.stringify({
757
+ success: true,
758
+ caseCount: ctx.getCases().length
759
+ });
760
+ }
761
+ case "rescan_watch_folder": {
762
+ log.dim(`Re-scanning ${ctx.config.watchFolder}...`);
763
+ await ctx.triggerScan();
764
+ const stats = getStats();
765
+ return JSON.stringify({
766
+ success: true,
767
+ totalFiles: stats.totalFiles,
768
+ synced: stats.synced,
769
+ pending: stats.pending
770
+ });
771
+ }
772
+ default:
773
+ return JSON.stringify({ error: `Unknown tool: ${name}` });
774
+ }
775
+ }
776
+ async function* parseSSE(response) {
777
+ const reader = response.body.getReader();
778
+ const decoder = new TextDecoder();
779
+ let buffer = "";
780
+ while (true) {
781
+ const { done, value } = await reader.read();
782
+ if (done) break;
783
+ buffer += decoder.decode(value, { stream: true });
784
+ const lines = buffer.split("\n");
785
+ buffer = lines.pop();
786
+ for (const line of lines) {
787
+ const trimmed = line.trim();
788
+ if (!trimmed || !trimmed.startsWith("data: ")) continue;
789
+ const data = trimmed.slice(6);
790
+ if (data === "[DONE]") return;
791
+ try {
792
+ const parsed = JSON.parse(data);
793
+ const choice = parsed.choices?.[0];
794
+ if (choice?.delta) {
795
+ yield {
796
+ delta: choice.delta,
797
+ finishReason: choice.finish_reason ?? null
798
+ };
799
+ }
800
+ } catch {
801
+ }
802
+ }
803
+ }
804
+ }
805
+ var MODEL = "qwen/qwen3-8b";
806
+ async function agentTurn(messages, ctx) {
807
+ const response = await fetch("https://openrouter.ai/api/v1/chat/completions", {
808
+ method: "POST",
809
+ headers: {
810
+ Authorization: `Bearer ${ctx.apiKey}`,
811
+ "Content-Type": "application/json",
812
+ "HTTP-Referer": "https://anrak.legal",
813
+ "X-Title": "AnrakLegal Sync CLI"
814
+ },
815
+ body: JSON.stringify({
816
+ model: MODEL,
817
+ messages,
818
+ tools: TOOLS,
819
+ stream: true
820
+ })
821
+ });
822
+ if (!response.ok) {
823
+ const body = await response.text().catch(() => "");
824
+ throw new Error(`API error (${response.status}): ${body.slice(0, 200)}`);
825
+ }
826
+ let textContent = "";
827
+ const toolCalls = /* @__PURE__ */ new Map();
828
+ for await (const { delta } of parseSSE(response)) {
829
+ if (delta.content) {
830
+ process.stdout.write(delta.content);
831
+ textContent += delta.content;
832
+ }
833
+ if (delta.tool_calls) {
834
+ for (const tc of delta.tool_calls) {
835
+ const existing = toolCalls.get(tc.index);
836
+ if (existing) {
837
+ if (tc.function?.arguments) existing.args += tc.function.arguments;
838
+ } else {
839
+ toolCalls.set(tc.index, {
840
+ id: tc.id ?? `call_${tc.index}`,
841
+ name: tc.function?.name ?? "",
842
+ args: tc.function?.arguments ?? ""
843
+ });
844
+ }
845
+ }
846
+ }
847
+ }
848
+ if (textContent) {
849
+ process.stdout.write("\n");
850
+ }
851
+ if (toolCalls.size === 0) {
852
+ return textContent;
853
+ }
854
+ const assistantMsg = {
855
+ role: "assistant",
856
+ content: textContent || null,
857
+ tool_calls: Array.from(toolCalls.values()).map((tc) => ({
858
+ id: tc.id,
859
+ type: "function",
860
+ function: { name: tc.name, arguments: tc.args }
861
+ }))
862
+ };
863
+ messages.push(assistantMsg);
864
+ for (const tc of toolCalls.values()) {
865
+ let args = {};
866
+ try {
867
+ args = JSON.parse(tc.args || "{}");
868
+ } catch {
869
+ }
870
+ const result = await executeTool(tc.name, args, ctx);
871
+ messages.push({
872
+ role: "tool",
873
+ content: result,
874
+ tool_call_id: tc.id
875
+ });
876
+ }
877
+ return agentTurn(messages, ctx);
878
+ }
879
+ function startAIAgent(ctx) {
880
+ const PROMPT = chalk2.blue("anrak> ");
881
+ const history = [
882
+ { role: "system", content: buildSystemPrompt(ctx.config) }
883
+ ];
884
+ const rl = createInterface({ input: stdin, output: stdout });
885
+ 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');
888
+ async function promptLoop() {
889
+ while (true) {
890
+ let input;
891
+ try {
892
+ input = await rl.question(PROMPT);
893
+ } catch {
894
+ break;
895
+ }
896
+ const trimmed = input.trim();
897
+ if (!trimmed) continue;
898
+ if (/^(quit|exit|q)$/i.test(trimmed)) {
899
+ process.emit("SIGINT");
900
+ return;
901
+ }
902
+ if (/^(clear|reset|new)$/i.test(trimmed)) {
903
+ history.length = 1;
904
+ log.success("Conversation cleared");
905
+ continue;
906
+ }
907
+ history.push({ role: "user", content: trimmed });
908
+ while (history.length > 21) {
909
+ history.splice(1, 1);
910
+ }
911
+ try {
912
+ const response = await agentTurn([...history], ctx);
913
+ if (response) {
914
+ history.push({ role: "assistant", content: response });
915
+ }
916
+ } catch (err) {
917
+ 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
+ }
922
+ }
923
+ console.log("");
924
+ }
925
+ }
926
+ void promptLoop();
927
+ }
928
+
622
929
  // src/watcher.ts
623
930
  async function scanFolder(config) {
624
931
  const folder = config.watchFolder;
@@ -715,20 +1022,20 @@ async function scanExternalFolder(config, folderPath, cases) {
715
1022
  }
716
1023
  }
717
1024
  function startInteractiveAgent(config, getCases, refreshCases, triggerScan) {
718
- const PROMPT = chalk2.blue("anrak> ");
719
- const rl = createInterface({ input: stdin, output: stdout });
1025
+ const PROMPT = chalk3.blue("anrak> ");
1026
+ const rl = createInterface2({ input: stdin2, output: stdout2 });
720
1027
  function showHelp() {
721
1028
  console.log("");
722
- console.log(chalk2.bold(" Available commands:"));
1029
+ console.log(chalk3.bold(" Available commands:"));
723
1030
  console.log("");
724
- console.log(` ${chalk2.cyan("scan <folder>")} Scan a folder and sync its files to a case`);
725
- console.log(` ${chalk2.cyan("rescan")} Re-scan the watch folder for new files`);
726
- console.log(` ${chalk2.cyan("cases")} List cases on the server`);
727
- console.log(` ${chalk2.cyan("status")} Show sync statistics`);
728
- console.log(` ${chalk2.cyan("mappings")} Show folder \u2192 case mappings`);
729
- console.log(` ${chalk2.cyan("refresh")} Refresh cases from server`);
730
- console.log(` ${chalk2.cyan("help")} Show this help`);
731
- console.log(` ${chalk2.cyan("quit")} Stop syncing and exit`);
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`);
732
1039
  console.log("");
733
1040
  }
734
1041
  async function handleCommand(input) {
@@ -759,7 +1066,7 @@ function startInteractiveAgent(config, getCases, refreshCases, triggerScan) {
759
1066
  for (const c of cases) {
760
1067
  const docCount = c.documents?.length ?? 0;
761
1068
  console.log(
762
- ` ${chalk2.cyan(c.caseNumber)} ${c.caseName} ${chalk2.dim(`(${docCount} docs)`)}`
1069
+ ` ${chalk3.cyan(c.caseNumber)} ${c.caseName} ${chalk3.dim(`(${docCount} docs)`)}`
763
1070
  );
764
1071
  }
765
1072
  console.log("");
@@ -769,13 +1076,13 @@ function startInteractiveAgent(config, getCases, refreshCases, triggerScan) {
769
1076
  case "status": {
770
1077
  const stats = getStats();
771
1078
  console.log("");
772
- console.log(` ${chalk2.bold("Watch folder:")} ${config.watchFolder}`);
773
- console.log(` ${chalk2.bold("Files tracked:")} ${stats.totalFiles}`);
774
- console.log(` ${chalk2.bold("Synced:")} ${chalk2.green(String(stats.synced))}`);
775
- console.log(` ${chalk2.bold("Pending:")} ${chalk2.yellow(String(stats.pending))}`);
776
- console.log(` ${chalk2.bold("Errors:")} ${chalk2.red(String(stats.errors))}`);
777
- console.log(` ${chalk2.bold("Mapped folders:")} ${stats.mappedFolders}`);
778
- console.log(` ${chalk2.bold("Queue:")} ${queueSize()}`);
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()}`);
779
1086
  console.log("");
780
1087
  break;
781
1088
  }
@@ -789,7 +1096,7 @@ function startInteractiveAgent(config, getCases, refreshCases, triggerScan) {
789
1096
  console.log("");
790
1097
  for (const [folder, m] of entries) {
791
1098
  console.log(
792
- ` ${chalk2.cyan(folder)} \u2192 ${m.caseNumber} ${chalk2.dim(`(${m.caseName})`)}`
1099
+ ` ${chalk3.cyan(folder)} \u2192 ${m.caseNumber} ${chalk3.dim(`(${m.caseName})`)}`
793
1100
  );
794
1101
  }
795
1102
  console.log("");
@@ -814,7 +1121,7 @@ function startInteractiveAgent(config, getCases, refreshCases, triggerScan) {
814
1121
  return;
815
1122
  }
816
1123
  default: {
817
- log.warn(`Unknown command: ${cmd}. Type ${chalk2.cyan("help")} for available commands.`);
1124
+ log.warn(`Unknown command: ${cmd}. Type ${chalk3.cyan("help")} for available commands.`);
818
1125
  break;
819
1126
  }
820
1127
  }
@@ -904,13 +1211,14 @@ async function startWatching(config) {
904
1211
  watcher.on("error", (err) => {
905
1212
  log.error(`Watcher error: ${err instanceof Error ? err.message : String(err)}`);
906
1213
  });
907
- startInteractiveAgent(
1214
+ const apiKey = process.env.OPENROUTER_API_KEY ?? config.openrouterApiKey;
1215
+ const agentContext = {
908
1216
  config,
909
- () => cases,
910
- async () => {
1217
+ getCases: () => cases,
1218
+ refreshCases: async () => {
911
1219
  cases = await listCases(config);
912
1220
  },
913
- async () => {
1221
+ triggerScan: async () => {
914
1222
  log.info(`Re-scanning ${folder}...`);
915
1223
  const result = await scanFolder(config);
916
1224
  log.info(`Scanned ${result.scanned} files, ${result.queued} need syncing`);
@@ -918,8 +1226,17 @@ async function startWatching(config) {
918
1226
  const syncResult = await processQueue(config, cases);
919
1227
  log.info(`Synced: ${syncResult.uploaded} uploaded, ${syncResult.failed} failed`);
920
1228
  }
1229
+ },
1230
+ scanFolder: async (folderPath) => {
1231
+ await scanExternalFolder(config, folderPath, cases);
921
1232
  }
922
- );
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
+ }
923
1240
  const shutdown = () => {
924
1241
  log.info("Shutting down...");
925
1242
  clearInterval(refreshInterval);
@@ -934,14 +1251,20 @@ async function startWatching(config) {
934
1251
  }
935
1252
 
936
1253
  // src/cli.ts
1254
+ import { readFileSync as readFileSync2 } from "fs";
1255
+ import { fileURLToPath } from "url";
1256
+ import { dirname as dirname2, join as join3 } from "path";
1257
+ var __filename2 = fileURLToPath(import.meta.url);
1258
+ var __dirname2 = dirname2(__filename2);
1259
+ var pkg = JSON.parse(readFileSync2(join3(__dirname2, "..", "package.json"), "utf-8"));
937
1260
  var program = new Command();
938
- program.name("anrak-sync").description("AnrakLegal desktop file sync \u2014 watches local folders, syncs to case management").version("0.1.0");
1261
+ program.name("anrak-sync").description("AnrakLegal desktop file sync \u2014 watches local folders, syncs to case management").version(pkg.version);
939
1262
  program.command("init").description("Set up AnrakLegal Sync (first-time configuration)").option("--password", "Use email/password login instead of browser").action(async (opts) => {
940
- const rl = createInterface2({ input: stdin2, output: stdout2 });
1263
+ const rl = createInterface3({ input: stdin3, output: stdout3 });
941
1264
  try {
942
- console.log(chalk3.bold.blue("\n AnrakLegal Sync \u2014 Setup\n"));
1265
+ console.log(chalk4.bold.blue("\n AnrakLegal Sync \u2014 Setup\n"));
943
1266
  const apiUrl = await rl.question(
944
- ` AnrakLegal URL ${chalk3.dim("(https://anrak.legal)")}: `
1267
+ ` AnrakLegal URL ${chalk4.dim("(https://anrak.legal)")}: `
945
1268
  ) || "https://anrak.legal";
946
1269
  log.info("Connecting to server...");
947
1270
  const serverConfig = await fetchServerConfig(apiUrl);
@@ -969,10 +1292,10 @@ program.command("init").description("Set up AnrakLegal Sync (first-time configur
969
1292
  tokens = await browserLogin(apiUrl);
970
1293
  }
971
1294
  log.success("Authenticated");
972
- const rl2 = createInterface2({ input: stdin2, output: stdout2 });
1295
+ const rl2 = createInterface3({ input: stdin3, output: stdout3 });
973
1296
  const defaultFolder = process.platform === "win32" ? "C:\\Cases" : `${process.env.HOME}/Cases`;
974
1297
  const watchInput = await rl2.question(
975
- ` Watch folder ${chalk3.dim(`(${defaultFolder})`)}: `
1298
+ ` Watch folder ${chalk4.dim(`(${defaultFolder})`)}: `
976
1299
  );
977
1300
  const watchFolder = resolve2(watchInput || defaultFolder);
978
1301
  rl2.close();
@@ -998,7 +1321,7 @@ program.command("init").description("Set up AnrakLegal Sync (first-time configur
998
1321
  log.info(`Config saved to ${getConfigDir()}`);
999
1322
  log.info(`Watching: ${watchFolder}`);
1000
1323
  console.log(
1001
- chalk3.dim("\n Run `anrak-sync start` to begin syncing\n")
1324
+ chalk4.dim("\n Run `anrak-sync start` to begin syncing\n")
1002
1325
  );
1003
1326
  } catch (err) {
1004
1327
  log.error(err instanceof Error ? err.message : String(err));
@@ -1010,7 +1333,7 @@ program.command("login").description("Re-authenticate with AnrakLegal").option("
1010
1333
  try {
1011
1334
  let tokens;
1012
1335
  if (opts.password) {
1013
- const rl = createInterface2({ input: stdin2, output: stdout2 });
1336
+ const rl = createInterface3({ input: stdin3, output: stdout3 });
1014
1337
  try {
1015
1338
  const email = await rl.question(" Email: ");
1016
1339
  const password = await rl.question(" Password: ");
@@ -1033,7 +1356,7 @@ program.command("login").description("Re-authenticate with AnrakLegal").option("
1033
1356
  });
1034
1357
  program.command("start").description("Start watching for file changes and syncing").action(async () => {
1035
1358
  const config = requireConfig();
1036
- console.log(chalk3.bold.blue("\n AnrakLegal Sync\n"));
1359
+ console.log(chalk4.bold.blue("\n AnrakLegal Sync\n"));
1037
1360
  log.info(`Watching: ${config.watchFolder}`);
1038
1361
  log.info(`Server: ${config.apiUrl}`);
1039
1362
  console.log("");
@@ -1046,7 +1369,7 @@ program.command("start").description("Start watching for file changes and syncin
1046
1369
  });
1047
1370
  program.command("push").description("One-time sync \u2014 upload all new/changed files, then exit").action(async () => {
1048
1371
  const config = requireConfig();
1049
- console.log(chalk3.bold.blue("\n AnrakLegal Sync \u2014 Push\n"));
1372
+ console.log(chalk4.bold.blue("\n AnrakLegal Sync \u2014 Push\n"));
1050
1373
  log.info(`Folder: ${config.watchFolder}`);
1051
1374
  log.info(`Server: ${config.apiUrl}`);
1052
1375
  console.log("");
@@ -1060,24 +1383,24 @@ program.command("push").description("One-time sync \u2014 upload all new/changed
1060
1383
  program.command("status").description("Show sync status").action(async () => {
1061
1384
  const config = requireConfig();
1062
1385
  const stats = getStats();
1063
- console.log(chalk3.bold.blue("\n AnrakLegal Sync \u2014 Status\n"));
1386
+ console.log(chalk4.bold.blue("\n AnrakLegal Sync \u2014 Status\n"));
1064
1387
  console.log(` Server: ${config.apiUrl}`);
1065
1388
  console.log(` Watch folder: ${config.watchFolder}`);
1066
1389
  console.log(` Config: ${getConfigDir()}`);
1067
1390
  console.log("");
1068
1391
  console.log(` Files tracked: ${stats.totalFiles}`);
1069
- console.log(` Synced: ${chalk3.green(stats.synced)}`);
1070
- console.log(` Pending: ${chalk3.yellow(stats.pending)}`);
1071
- console.log(` Errors: ${chalk3.red(stats.errors)}`);
1392
+ console.log(` Synced: ${chalk4.green(stats.synced)}`);
1393
+ console.log(` Pending: ${chalk4.yellow(stats.pending)}`);
1394
+ console.log(` Errors: ${chalk4.red(stats.errors)}`);
1072
1395
  console.log(` Mapped folders: ${stats.mappedFolders}`);
1073
1396
  try {
1074
1397
  const cases = await listCases(config);
1075
1398
  console.log(`
1076
1399
  Server cases: ${cases.length}`);
1077
- console.log(` Auth: ${chalk3.green("valid")}`);
1400
+ console.log(` Auth: ${chalk4.green("valid")}`);
1078
1401
  } catch {
1079
1402
  console.log(`
1080
- Auth: ${chalk3.red("expired \u2014 run anrak-sync login")}`);
1403
+ Auth: ${chalk4.red("expired \u2014 run anrak-sync login")}`);
1081
1404
  }
1082
1405
  console.log("");
1083
1406
  });
@@ -1085,13 +1408,13 @@ program.command("map").description("Show folder-to-case mappings").action(async
1085
1408
  const config = requireConfig();
1086
1409
  const mappings = getAllMappings();
1087
1410
  const entries = Object.entries(mappings);
1088
- console.log(chalk3.bold.blue("\n AnrakLegal Sync \u2014 Mappings\n"));
1411
+ console.log(chalk4.bold.blue("\n AnrakLegal Sync \u2014 Mappings\n"));
1089
1412
  if (entries.length === 0) {
1090
1413
  log.info("No mappings yet. Run `anrak-sync push` or `anrak-sync start` to create them.");
1091
1414
  } else {
1092
1415
  for (const [folder, mapping] of entries) {
1093
1416
  console.log(
1094
- ` ${chalk3.cyan(folder)} -> ${mapping.caseNumber} (${chalk3.dim(mapping.caseName)})`
1417
+ ` ${chalk4.cyan(folder)} -> ${mapping.caseNumber} (${chalk4.dim(mapping.caseName)})`
1095
1418
  );
1096
1419
  }
1097
1420
  }
@@ -1100,9 +1423,9 @@ program.command("map").description("Show folder-to-case mappings").action(async
1100
1423
  const mappedIds = new Set(entries.map(([, m]) => m.caseId));
1101
1424
  const unmapped = cases.filter((c) => !mappedIds.has(c.id));
1102
1425
  if (unmapped.length > 0) {
1103
- console.log(chalk3.dim("\n Unmapped server cases:"));
1426
+ console.log(chalk4.dim("\n Unmapped server cases:"));
1104
1427
  for (const c of unmapped) {
1105
- console.log(` ${chalk3.dim(c.caseNumber)} ${chalk3.dim(c.caseName)}`);
1428
+ console.log(` ${chalk4.dim(c.caseNumber)} ${chalk4.dim(c.caseName)}`);
1106
1429
  }
1107
1430
  }
1108
1431
  } catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anraktech/sync",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "AnrakLegal desktop file sync agent — watches local folders and syncs to case management",
5
5
  "type": "module",
6
6
  "bin": {