@anraktech/sync 0.3.0 → 0.4.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.
- package/dist/cli.js +73 -219
- 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
|
|
6
|
-
import { stdin as
|
|
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
8
|
import { resolve as resolve2 } from "path";
|
|
9
|
-
import
|
|
9
|
+
import chalk3 from "chalk";
|
|
10
10
|
|
|
11
11
|
// src/config.ts
|
|
12
12
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
@@ -418,9 +418,6 @@ 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 as createInterface2 } from "readline/promises";
|
|
422
|
-
import { stdin as stdin2, stdout as stdout2 } from "process";
|
|
423
|
-
import chalk3 from "chalk";
|
|
424
421
|
|
|
425
422
|
// src/uploader.ts
|
|
426
423
|
import { stat as stat2 } from "fs/promises";
|
|
@@ -628,13 +625,13 @@ var TOOLS = [
|
|
|
628
625
|
type: "function",
|
|
629
626
|
function: {
|
|
630
627
|
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
|
|
628
|
+
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
629
|
parameters: {
|
|
633
630
|
type: "object",
|
|
634
631
|
properties: {
|
|
635
632
|
folderPath: {
|
|
636
633
|
type: "string",
|
|
637
|
-
description: "Absolute path to the folder
|
|
634
|
+
description: "Absolute path to the folder, e.g. /Users/name/Downloads"
|
|
638
635
|
}
|
|
639
636
|
},
|
|
640
637
|
required: ["folderPath"]
|
|
@@ -645,7 +642,7 @@ var TOOLS = [
|
|
|
645
642
|
type: "function",
|
|
646
643
|
function: {
|
|
647
644
|
name: "list_cases",
|
|
648
|
-
description: "List all legal cases on the
|
|
645
|
+
description: "List all legal cases on the server with case numbers, names, and document counts.",
|
|
649
646
|
parameters: { type: "object", properties: {} }
|
|
650
647
|
}
|
|
651
648
|
},
|
|
@@ -653,7 +650,7 @@ var TOOLS = [
|
|
|
653
650
|
type: "function",
|
|
654
651
|
function: {
|
|
655
652
|
name: "get_status",
|
|
656
|
-
description: "Show sync
|
|
653
|
+
description: "Show sync stats: files tracked, synced, pending, errors, queue size.",
|
|
657
654
|
parameters: { type: "object", properties: {} }
|
|
658
655
|
}
|
|
659
656
|
},
|
|
@@ -661,7 +658,7 @@ var TOOLS = [
|
|
|
661
658
|
type: "function",
|
|
662
659
|
function: {
|
|
663
660
|
name: "get_mappings",
|
|
664
|
-
description: "Show
|
|
661
|
+
description: "Show which local folders are mapped to which legal cases.",
|
|
665
662
|
parameters: { type: "object", properties: {} }
|
|
666
663
|
}
|
|
667
664
|
},
|
|
@@ -669,7 +666,7 @@ var TOOLS = [
|
|
|
669
666
|
type: "function",
|
|
670
667
|
function: {
|
|
671
668
|
name: "refresh_cases",
|
|
672
|
-
description: "Refresh the case list from the server
|
|
669
|
+
description: "Refresh the case list from the server.",
|
|
673
670
|
parameters: { type: "object", properties: {} }
|
|
674
671
|
}
|
|
675
672
|
},
|
|
@@ -677,37 +674,31 @@ var TOOLS = [
|
|
|
677
674
|
type: "function",
|
|
678
675
|
function: {
|
|
679
676
|
name: "rescan_watch_folder",
|
|
680
|
-
description: "Re-scan the
|
|
677
|
+
description: "Re-scan the watch folder for new or changed files and sync them.",
|
|
681
678
|
parameters: { type: "object", properties: {} }
|
|
682
679
|
}
|
|
683
680
|
}
|
|
684
681
|
];
|
|
685
682
|
function buildSystemPrompt(config) {
|
|
686
|
-
return `You are AnrakLegal Sync
|
|
687
|
-
You help lawyers sync local files to their AnrakLegal case management system.
|
|
683
|
+
return `You are AnrakLegal Sync, a terminal assistant that helps lawyers sync local files to their case management system.
|
|
688
684
|
|
|
689
|
-
|
|
685
|
+
Watch folder: ${config.watchFolder}
|
|
690
686
|
Server: ${config.apiUrl}
|
|
691
687
|
|
|
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).
|
|
688
|
+
You can scan folders, list cases, show sync status, and more using your tools.
|
|
702
689
|
|
|
703
|
-
|
|
690
|
+
Rules:
|
|
691
|
+
- Be concise. This is a terminal \u2014 1-3 lines unless showing a list.
|
|
692
|
+
- Do NOT use markdown. Plain text only.
|
|
693
|
+
- When user mentions a folder like "downloads" or "desktop", expand to full path (macOS: /Users/<name>/Downloads, Windows: C:\\Users\\<name>\\Downloads).
|
|
694
|
+
- Call tools to perform actions. Summarize results naturally after.
|
|
695
|
+
- Do NOT use thinking tags or reasoning tags in your output.`;
|
|
704
696
|
}
|
|
705
697
|
async function executeTool(name, args, ctx) {
|
|
706
698
|
switch (name) {
|
|
707
699
|
case "scan_folder": {
|
|
708
700
|
const folderPath = args.folderPath;
|
|
709
701
|
if (!folderPath) return JSON.stringify({ error: "Missing folderPath" });
|
|
710
|
-
log.dim(`Scanning ${folderPath}...`);
|
|
711
702
|
try {
|
|
712
703
|
await ctx.scanFolder(folderPath);
|
|
713
704
|
const stats = getStats();
|
|
@@ -720,9 +711,7 @@ async function executeTool(name, args, ctx) {
|
|
|
720
711
|
errors: stats.errors
|
|
721
712
|
});
|
|
722
713
|
} catch (err) {
|
|
723
|
-
return JSON.stringify({
|
|
724
|
-
error: err instanceof Error ? err.message : String(err)
|
|
725
|
-
});
|
|
714
|
+
return JSON.stringify({ error: err instanceof Error ? err.message : String(err) });
|
|
726
715
|
}
|
|
727
716
|
}
|
|
728
717
|
case "list_cases": {
|
|
@@ -753,13 +742,9 @@ async function executeTool(name, args, ctx) {
|
|
|
753
742
|
}
|
|
754
743
|
case "refresh_cases": {
|
|
755
744
|
await ctx.refreshCases();
|
|
756
|
-
return JSON.stringify({
|
|
757
|
-
success: true,
|
|
758
|
-
caseCount: ctx.getCases().length
|
|
759
|
-
});
|
|
745
|
+
return JSON.stringify({ success: true, caseCount: ctx.getCases().length });
|
|
760
746
|
}
|
|
761
747
|
case "rescan_watch_folder": {
|
|
762
|
-
log.dim(`Re-scanning ${ctx.config.watchFolder}...`);
|
|
763
748
|
await ctx.triggerScan();
|
|
764
749
|
const stats = getStats();
|
|
765
750
|
return JSON.stringify({
|
|
@@ -792,43 +777,41 @@ async function* parseSSE(response) {
|
|
|
792
777
|
const parsed = JSON.parse(data);
|
|
793
778
|
const choice = parsed.choices?.[0];
|
|
794
779
|
if (choice?.delta) {
|
|
795
|
-
yield {
|
|
796
|
-
delta: choice.delta,
|
|
797
|
-
finishReason: choice.finish_reason ?? null
|
|
798
|
-
};
|
|
780
|
+
yield { delta: choice.delta, finishReason: choice.finish_reason ?? null };
|
|
799
781
|
}
|
|
800
782
|
} catch {
|
|
801
783
|
}
|
|
802
784
|
}
|
|
803
785
|
}
|
|
804
786
|
}
|
|
805
|
-
var MODEL = "qwen/qwen3-8b";
|
|
806
787
|
async function agentTurn(messages, ctx) {
|
|
807
|
-
const
|
|
788
|
+
const token = await getAccessToken(ctx.config);
|
|
789
|
+
const response = await fetch(`${ctx.config.apiUrl}/api/sync/chat`, {
|
|
808
790
|
method: "POST",
|
|
809
791
|
headers: {
|
|
810
|
-
Authorization: `Bearer ${
|
|
811
|
-
"Content-Type": "application/json"
|
|
812
|
-
"HTTP-Referer": "https://anrak.legal",
|
|
813
|
-
"X-Title": "AnrakLegal Sync CLI"
|
|
792
|
+
Authorization: `Bearer ${token}`,
|
|
793
|
+
"Content-Type": "application/json"
|
|
814
794
|
},
|
|
815
|
-
body: JSON.stringify({
|
|
816
|
-
model: MODEL,
|
|
817
|
-
messages,
|
|
818
|
-
tools: TOOLS,
|
|
819
|
-
stream: true
|
|
820
|
-
})
|
|
795
|
+
body: JSON.stringify({ messages, tools: TOOLS })
|
|
821
796
|
});
|
|
822
797
|
if (!response.ok) {
|
|
823
798
|
const body = await response.text().catch(() => "");
|
|
824
|
-
throw new Error(`
|
|
799
|
+
throw new Error(`Server error (${response.status}): ${body.slice(0, 200)}`);
|
|
825
800
|
}
|
|
826
801
|
let textContent = "";
|
|
827
802
|
const toolCalls = /* @__PURE__ */ new Map();
|
|
803
|
+
let isFirstText = true;
|
|
828
804
|
for await (const { delta } of parseSSE(response)) {
|
|
829
805
|
if (delta.content) {
|
|
830
|
-
|
|
831
|
-
|
|
806
|
+
if (isFirstText) {
|
|
807
|
+
process.stdout.write(" ");
|
|
808
|
+
isFirstText = false;
|
|
809
|
+
}
|
|
810
|
+
const cleaned = delta.content.replace(/<\/?think>/g, "");
|
|
811
|
+
if (cleaned) {
|
|
812
|
+
process.stdout.write(cleaned);
|
|
813
|
+
textContent += cleaned;
|
|
814
|
+
}
|
|
832
815
|
}
|
|
833
816
|
if (delta.tool_calls) {
|
|
834
817
|
for (const tc of delta.tool_calls) {
|
|
@@ -862,29 +845,33 @@ async function agentTurn(messages, ctx) {
|
|
|
862
845
|
};
|
|
863
846
|
messages.push(assistantMsg);
|
|
864
847
|
for (const tc of toolCalls.values()) {
|
|
848
|
+
process.stdout.write(chalk2.dim(` \u27F3 ${tc.name}...
|
|
849
|
+
`));
|
|
865
850
|
let args = {};
|
|
866
851
|
try {
|
|
867
852
|
args = JSON.parse(tc.args || "{}");
|
|
868
853
|
} catch {
|
|
869
854
|
}
|
|
870
855
|
const result = await executeTool(tc.name, args, ctx);
|
|
871
|
-
messages.push({
|
|
872
|
-
role: "tool",
|
|
873
|
-
content: result,
|
|
874
|
-
tool_call_id: tc.id
|
|
875
|
-
});
|
|
856
|
+
messages.push({ role: "tool", content: result, tool_call_id: tc.id });
|
|
876
857
|
}
|
|
877
858
|
return agentTurn(messages, ctx);
|
|
878
859
|
}
|
|
879
860
|
function startAIAgent(ctx) {
|
|
880
|
-
const PROMPT = chalk2.blue("anrak> ");
|
|
881
861
|
const history = [
|
|
882
862
|
{ role: "system", content: buildSystemPrompt(ctx.config) }
|
|
883
863
|
];
|
|
884
864
|
const rl = createInterface({ input: stdin, output: stdout });
|
|
885
865
|
console.log("");
|
|
886
|
-
log.
|
|
887
|
-
log.dim(
|
|
866
|
+
console.log(chalk2.bold(" AnrakLegal Sync"));
|
|
867
|
+
console.log(chalk2.dim(` Watching ${ctx.config.watchFolder}`));
|
|
868
|
+
console.log(chalk2.dim(` Connected to ${ctx.config.apiUrl}`));
|
|
869
|
+
console.log("");
|
|
870
|
+
console.log(
|
|
871
|
+
chalk2.dim(" Ask me anything \u2014 scan folders, check cases, show status.")
|
|
872
|
+
);
|
|
873
|
+
console.log(chalk2.dim(' Type "quit" to exit.\n'));
|
|
874
|
+
const PROMPT = `${chalk2.bold.blue("\u276F")} `;
|
|
888
875
|
async function promptLoop() {
|
|
889
876
|
while (true) {
|
|
890
877
|
let input;
|
|
@@ -901,7 +888,7 @@ function startAIAgent(ctx) {
|
|
|
901
888
|
}
|
|
902
889
|
if (/^(clear|reset|new)$/i.test(trimmed)) {
|
|
903
890
|
history.length = 1;
|
|
904
|
-
log.
|
|
891
|
+
console.log(chalk2.dim(" Conversation cleared.\n"));
|
|
905
892
|
continue;
|
|
906
893
|
}
|
|
907
894
|
history.push({ role: "user", content: trimmed });
|
|
@@ -915,10 +902,7 @@ function startAIAgent(ctx) {
|
|
|
915
902
|
}
|
|
916
903
|
} catch (err) {
|
|
917
904
|
const msg = err instanceof Error ? err.message : String(err);
|
|
918
|
-
log.
|
|
919
|
-
if (msg.includes("401") || msg.includes("403")) {
|
|
920
|
-
log.warn("Check your OpenRouter API key (OPENROUTER_API_KEY env var)");
|
|
921
|
-
}
|
|
905
|
+
console.log(chalk2.red(` Error: ${msg}`));
|
|
922
906
|
}
|
|
923
907
|
console.log("");
|
|
924
908
|
}
|
|
@@ -1021,129 +1005,6 @@ async function scanExternalFolder(config, folderPath, cases) {
|
|
|
1021
1005
|
log.success("Everything up to date");
|
|
1022
1006
|
}
|
|
1023
1007
|
}
|
|
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
1008
|
async function startWatching(config) {
|
|
1148
1009
|
const folder = config.watchFolder;
|
|
1149
1010
|
log.info(`Scanning ${folder}...`);
|
|
@@ -1211,8 +1072,7 @@ async function startWatching(config) {
|
|
|
1211
1072
|
watcher.on("error", (err) => {
|
|
1212
1073
|
log.error(`Watcher error: ${err instanceof Error ? err.message : String(err)}`);
|
|
1213
1074
|
});
|
|
1214
|
-
|
|
1215
|
-
const agentContext = {
|
|
1075
|
+
startAIAgent({
|
|
1216
1076
|
config,
|
|
1217
1077
|
getCases: () => cases,
|
|
1218
1078
|
refreshCases: async () => {
|
|
@@ -1230,13 +1090,7 @@ async function startWatching(config) {
|
|
|
1230
1090
|
scanFolder: async (folderPath) => {
|
|
1231
1091
|
await scanExternalFolder(config, folderPath, cases);
|
|
1232
1092
|
}
|
|
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
|
-
}
|
|
1093
|
+
});
|
|
1240
1094
|
const shutdown = () => {
|
|
1241
1095
|
log.info("Shutting down...");
|
|
1242
1096
|
clearInterval(refreshInterval);
|
|
@@ -1260,11 +1114,11 @@ var pkg = JSON.parse(readFileSync2(join3(__dirname2, "..", "package.json"), "utf
|
|
|
1260
1114
|
var program = new Command();
|
|
1261
1115
|
program.name("anrak-sync").description("AnrakLegal desktop file sync \u2014 watches local folders, syncs to case management").version(pkg.version);
|
|
1262
1116
|
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 =
|
|
1117
|
+
const rl = createInterface2({ input: stdin2, output: stdout2 });
|
|
1264
1118
|
try {
|
|
1265
|
-
console.log(
|
|
1119
|
+
console.log(chalk3.bold.blue("\n AnrakLegal Sync \u2014 Setup\n"));
|
|
1266
1120
|
const apiUrl = await rl.question(
|
|
1267
|
-
` AnrakLegal URL ${
|
|
1121
|
+
` AnrakLegal URL ${chalk3.dim("(https://anrak.legal)")}: `
|
|
1268
1122
|
) || "https://anrak.legal";
|
|
1269
1123
|
log.info("Connecting to server...");
|
|
1270
1124
|
const serverConfig = await fetchServerConfig(apiUrl);
|
|
@@ -1292,10 +1146,10 @@ program.command("init").description("Set up AnrakLegal Sync (first-time configur
|
|
|
1292
1146
|
tokens = await browserLogin(apiUrl);
|
|
1293
1147
|
}
|
|
1294
1148
|
log.success("Authenticated");
|
|
1295
|
-
const rl2 =
|
|
1149
|
+
const rl2 = createInterface2({ input: stdin2, output: stdout2 });
|
|
1296
1150
|
const defaultFolder = process.platform === "win32" ? "C:\\Cases" : `${process.env.HOME}/Cases`;
|
|
1297
1151
|
const watchInput = await rl2.question(
|
|
1298
|
-
` Watch folder ${
|
|
1152
|
+
` Watch folder ${chalk3.dim(`(${defaultFolder})`)}: `
|
|
1299
1153
|
);
|
|
1300
1154
|
const watchFolder = resolve2(watchInput || defaultFolder);
|
|
1301
1155
|
rl2.close();
|
|
@@ -1321,7 +1175,7 @@ program.command("init").description("Set up AnrakLegal Sync (first-time configur
|
|
|
1321
1175
|
log.info(`Config saved to ${getConfigDir()}`);
|
|
1322
1176
|
log.info(`Watching: ${watchFolder}`);
|
|
1323
1177
|
console.log(
|
|
1324
|
-
|
|
1178
|
+
chalk3.dim("\n Run `anrak-sync start` to begin syncing\n")
|
|
1325
1179
|
);
|
|
1326
1180
|
} catch (err) {
|
|
1327
1181
|
log.error(err instanceof Error ? err.message : String(err));
|
|
@@ -1333,7 +1187,7 @@ program.command("login").description("Re-authenticate with AnrakLegal").option("
|
|
|
1333
1187
|
try {
|
|
1334
1188
|
let tokens;
|
|
1335
1189
|
if (opts.password) {
|
|
1336
|
-
const rl =
|
|
1190
|
+
const rl = createInterface2({ input: stdin2, output: stdout2 });
|
|
1337
1191
|
try {
|
|
1338
1192
|
const email = await rl.question(" Email: ");
|
|
1339
1193
|
const password = await rl.question(" Password: ");
|
|
@@ -1356,7 +1210,7 @@ program.command("login").description("Re-authenticate with AnrakLegal").option("
|
|
|
1356
1210
|
});
|
|
1357
1211
|
program.command("start").description("Start watching for file changes and syncing").action(async () => {
|
|
1358
1212
|
const config = requireConfig();
|
|
1359
|
-
console.log(
|
|
1213
|
+
console.log(chalk3.bold.blue("\n AnrakLegal Sync\n"));
|
|
1360
1214
|
log.info(`Watching: ${config.watchFolder}`);
|
|
1361
1215
|
log.info(`Server: ${config.apiUrl}`);
|
|
1362
1216
|
console.log("");
|
|
@@ -1369,7 +1223,7 @@ program.command("start").description("Start watching for file changes and syncin
|
|
|
1369
1223
|
});
|
|
1370
1224
|
program.command("push").description("One-time sync \u2014 upload all new/changed files, then exit").action(async () => {
|
|
1371
1225
|
const config = requireConfig();
|
|
1372
|
-
console.log(
|
|
1226
|
+
console.log(chalk3.bold.blue("\n AnrakLegal Sync \u2014 Push\n"));
|
|
1373
1227
|
log.info(`Folder: ${config.watchFolder}`);
|
|
1374
1228
|
log.info(`Server: ${config.apiUrl}`);
|
|
1375
1229
|
console.log("");
|
|
@@ -1383,24 +1237,24 @@ program.command("push").description("One-time sync \u2014 upload all new/changed
|
|
|
1383
1237
|
program.command("status").description("Show sync status").action(async () => {
|
|
1384
1238
|
const config = requireConfig();
|
|
1385
1239
|
const stats = getStats();
|
|
1386
|
-
console.log(
|
|
1240
|
+
console.log(chalk3.bold.blue("\n AnrakLegal Sync \u2014 Status\n"));
|
|
1387
1241
|
console.log(` Server: ${config.apiUrl}`);
|
|
1388
1242
|
console.log(` Watch folder: ${config.watchFolder}`);
|
|
1389
1243
|
console.log(` Config: ${getConfigDir()}`);
|
|
1390
1244
|
console.log("");
|
|
1391
1245
|
console.log(` Files tracked: ${stats.totalFiles}`);
|
|
1392
|
-
console.log(` Synced: ${
|
|
1393
|
-
console.log(` Pending: ${
|
|
1394
|
-
console.log(` Errors: ${
|
|
1246
|
+
console.log(` Synced: ${chalk3.green(stats.synced)}`);
|
|
1247
|
+
console.log(` Pending: ${chalk3.yellow(stats.pending)}`);
|
|
1248
|
+
console.log(` Errors: ${chalk3.red(stats.errors)}`);
|
|
1395
1249
|
console.log(` Mapped folders: ${stats.mappedFolders}`);
|
|
1396
1250
|
try {
|
|
1397
1251
|
const cases = await listCases(config);
|
|
1398
1252
|
console.log(`
|
|
1399
1253
|
Server cases: ${cases.length}`);
|
|
1400
|
-
console.log(` Auth: ${
|
|
1254
|
+
console.log(` Auth: ${chalk3.green("valid")}`);
|
|
1401
1255
|
} catch {
|
|
1402
1256
|
console.log(`
|
|
1403
|
-
Auth: ${
|
|
1257
|
+
Auth: ${chalk3.red("expired \u2014 run anrak-sync login")}`);
|
|
1404
1258
|
}
|
|
1405
1259
|
console.log("");
|
|
1406
1260
|
});
|
|
@@ -1408,13 +1262,13 @@ program.command("map").description("Show folder-to-case mappings").action(async
|
|
|
1408
1262
|
const config = requireConfig();
|
|
1409
1263
|
const mappings = getAllMappings();
|
|
1410
1264
|
const entries = Object.entries(mappings);
|
|
1411
|
-
console.log(
|
|
1265
|
+
console.log(chalk3.bold.blue("\n AnrakLegal Sync \u2014 Mappings\n"));
|
|
1412
1266
|
if (entries.length === 0) {
|
|
1413
1267
|
log.info("No mappings yet. Run `anrak-sync push` or `anrak-sync start` to create them.");
|
|
1414
1268
|
} else {
|
|
1415
1269
|
for (const [folder, mapping] of entries) {
|
|
1416
1270
|
console.log(
|
|
1417
|
-
` ${
|
|
1271
|
+
` ${chalk3.cyan(folder)} -> ${mapping.caseNumber} (${chalk3.dim(mapping.caseName)})`
|
|
1418
1272
|
);
|
|
1419
1273
|
}
|
|
1420
1274
|
}
|
|
@@ -1423,9 +1277,9 @@ program.command("map").description("Show folder-to-case mappings").action(async
|
|
|
1423
1277
|
const mappedIds = new Set(entries.map(([, m]) => m.caseId));
|
|
1424
1278
|
const unmapped = cases.filter((c) => !mappedIds.has(c.id));
|
|
1425
1279
|
if (unmapped.length > 0) {
|
|
1426
|
-
console.log(
|
|
1280
|
+
console.log(chalk3.dim("\n Unmapped server cases:"));
|
|
1427
1281
|
for (const c of unmapped) {
|
|
1428
|
-
console.log(` ${
|
|
1282
|
+
console.log(` ${chalk3.dim(c.caseNumber)} ${chalk3.dim(c.caseName)}`);
|
|
1429
1283
|
}
|
|
1430
1284
|
}
|
|
1431
1285
|
} catch {
|