@anraktech/sync 0.2.1 → 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 +368 -51
  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);
@@ -943,11 +1260,11 @@ var pkg = JSON.parse(readFileSync2(join3(__dirname2, "..", "package.json"), "utf
943
1260
  var program = new Command();
944
1261
  program.name("anrak-sync").description("AnrakLegal desktop file sync \u2014 watches local folders, syncs to case management").version(pkg.version);
945
1262
  program.command("init").description("Set up AnrakLegal Sync (first-time configuration)").option("--password", "Use email/password login instead of browser").action(async (opts) => {
946
- const rl = createInterface2({ input: stdin2, output: stdout2 });
1263
+ const rl = createInterface3({ input: stdin3, output: stdout3 });
947
1264
  try {
948
- console.log(chalk3.bold.blue("\n AnrakLegal Sync \u2014 Setup\n"));
1265
+ console.log(chalk4.bold.blue("\n AnrakLegal Sync \u2014 Setup\n"));
949
1266
  const apiUrl = await rl.question(
950
- ` AnrakLegal URL ${chalk3.dim("(https://anrak.legal)")}: `
1267
+ ` AnrakLegal URL ${chalk4.dim("(https://anrak.legal)")}: `
951
1268
  ) || "https://anrak.legal";
952
1269
  log.info("Connecting to server...");
953
1270
  const serverConfig = await fetchServerConfig(apiUrl);
@@ -975,10 +1292,10 @@ program.command("init").description("Set up AnrakLegal Sync (first-time configur
975
1292
  tokens = await browserLogin(apiUrl);
976
1293
  }
977
1294
  log.success("Authenticated");
978
- const rl2 = createInterface2({ input: stdin2, output: stdout2 });
1295
+ const rl2 = createInterface3({ input: stdin3, output: stdout3 });
979
1296
  const defaultFolder = process.platform === "win32" ? "C:\\Cases" : `${process.env.HOME}/Cases`;
980
1297
  const watchInput = await rl2.question(
981
- ` Watch folder ${chalk3.dim(`(${defaultFolder})`)}: `
1298
+ ` Watch folder ${chalk4.dim(`(${defaultFolder})`)}: `
982
1299
  );
983
1300
  const watchFolder = resolve2(watchInput || defaultFolder);
984
1301
  rl2.close();
@@ -1004,7 +1321,7 @@ program.command("init").description("Set up AnrakLegal Sync (first-time configur
1004
1321
  log.info(`Config saved to ${getConfigDir()}`);
1005
1322
  log.info(`Watching: ${watchFolder}`);
1006
1323
  console.log(
1007
- chalk3.dim("\n Run `anrak-sync start` to begin syncing\n")
1324
+ chalk4.dim("\n Run `anrak-sync start` to begin syncing\n")
1008
1325
  );
1009
1326
  } catch (err) {
1010
1327
  log.error(err instanceof Error ? err.message : String(err));
@@ -1016,7 +1333,7 @@ program.command("login").description("Re-authenticate with AnrakLegal").option("
1016
1333
  try {
1017
1334
  let tokens;
1018
1335
  if (opts.password) {
1019
- const rl = createInterface2({ input: stdin2, output: stdout2 });
1336
+ const rl = createInterface3({ input: stdin3, output: stdout3 });
1020
1337
  try {
1021
1338
  const email = await rl.question(" Email: ");
1022
1339
  const password = await rl.question(" Password: ");
@@ -1039,7 +1356,7 @@ program.command("login").description("Re-authenticate with AnrakLegal").option("
1039
1356
  });
1040
1357
  program.command("start").description("Start watching for file changes and syncing").action(async () => {
1041
1358
  const config = requireConfig();
1042
- console.log(chalk3.bold.blue("\n AnrakLegal Sync\n"));
1359
+ console.log(chalk4.bold.blue("\n AnrakLegal Sync\n"));
1043
1360
  log.info(`Watching: ${config.watchFolder}`);
1044
1361
  log.info(`Server: ${config.apiUrl}`);
1045
1362
  console.log("");
@@ -1052,7 +1369,7 @@ program.command("start").description("Start watching for file changes and syncin
1052
1369
  });
1053
1370
  program.command("push").description("One-time sync \u2014 upload all new/changed files, then exit").action(async () => {
1054
1371
  const config = requireConfig();
1055
- console.log(chalk3.bold.blue("\n AnrakLegal Sync \u2014 Push\n"));
1372
+ console.log(chalk4.bold.blue("\n AnrakLegal Sync \u2014 Push\n"));
1056
1373
  log.info(`Folder: ${config.watchFolder}`);
1057
1374
  log.info(`Server: ${config.apiUrl}`);
1058
1375
  console.log("");
@@ -1066,24 +1383,24 @@ program.command("push").description("One-time sync \u2014 upload all new/changed
1066
1383
  program.command("status").description("Show sync status").action(async () => {
1067
1384
  const config = requireConfig();
1068
1385
  const stats = getStats();
1069
- console.log(chalk3.bold.blue("\n AnrakLegal Sync \u2014 Status\n"));
1386
+ console.log(chalk4.bold.blue("\n AnrakLegal Sync \u2014 Status\n"));
1070
1387
  console.log(` Server: ${config.apiUrl}`);
1071
1388
  console.log(` Watch folder: ${config.watchFolder}`);
1072
1389
  console.log(` Config: ${getConfigDir()}`);
1073
1390
  console.log("");
1074
1391
  console.log(` Files tracked: ${stats.totalFiles}`);
1075
- console.log(` Synced: ${chalk3.green(stats.synced)}`);
1076
- console.log(` Pending: ${chalk3.yellow(stats.pending)}`);
1077
- 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)}`);
1078
1395
  console.log(` Mapped folders: ${stats.mappedFolders}`);
1079
1396
  try {
1080
1397
  const cases = await listCases(config);
1081
1398
  console.log(`
1082
1399
  Server cases: ${cases.length}`);
1083
- console.log(` Auth: ${chalk3.green("valid")}`);
1400
+ console.log(` Auth: ${chalk4.green("valid")}`);
1084
1401
  } catch {
1085
1402
  console.log(`
1086
- Auth: ${chalk3.red("expired \u2014 run anrak-sync login")}`);
1403
+ Auth: ${chalk4.red("expired \u2014 run anrak-sync login")}`);
1087
1404
  }
1088
1405
  console.log("");
1089
1406
  });
@@ -1091,13 +1408,13 @@ program.command("map").description("Show folder-to-case mappings").action(async
1091
1408
  const config = requireConfig();
1092
1409
  const mappings = getAllMappings();
1093
1410
  const entries = Object.entries(mappings);
1094
- console.log(chalk3.bold.blue("\n AnrakLegal Sync \u2014 Mappings\n"));
1411
+ console.log(chalk4.bold.blue("\n AnrakLegal Sync \u2014 Mappings\n"));
1095
1412
  if (entries.length === 0) {
1096
1413
  log.info("No mappings yet. Run `anrak-sync push` or `anrak-sync start` to create them.");
1097
1414
  } else {
1098
1415
  for (const [folder, mapping] of entries) {
1099
1416
  console.log(
1100
- ` ${chalk3.cyan(folder)} -> ${mapping.caseNumber} (${chalk3.dim(mapping.caseName)})`
1417
+ ` ${chalk4.cyan(folder)} -> ${mapping.caseNumber} (${chalk4.dim(mapping.caseName)})`
1101
1418
  );
1102
1419
  }
1103
1420
  }
@@ -1106,9 +1423,9 @@ program.command("map").description("Show folder-to-case mappings").action(async
1106
1423
  const mappedIds = new Set(entries.map(([, m]) => m.caseId));
1107
1424
  const unmapped = cases.filter((c) => !mappedIds.has(c.id));
1108
1425
  if (unmapped.length > 0) {
1109
- console.log(chalk3.dim("\n Unmapped server cases:"));
1426
+ console.log(chalk4.dim("\n Unmapped server cases:"));
1110
1427
  for (const c of unmapped) {
1111
- console.log(` ${chalk3.dim(c.caseNumber)} ${chalk3.dim(c.caseName)}`);
1428
+ console.log(` ${chalk4.dim(c.caseNumber)} ${chalk4.dim(c.caseName)}`);
1112
1429
  }
1113
1430
  }
1114
1431
  } catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anraktech/sync",
3
- "version": "0.2.1",
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": {