@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.
- package/dist/cli.js +375 -52
- 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 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
|
|
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
|
|
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 =
|
|
719
|
-
const rl =
|
|
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(
|
|
1029
|
+
console.log(chalk3.bold(" Available commands:"));
|
|
723
1030
|
console.log("");
|
|
724
|
-
console.log(` ${
|
|
725
|
-
console.log(` ${
|
|
726
|
-
console.log(` ${
|
|
727
|
-
console.log(` ${
|
|
728
|
-
console.log(` ${
|
|
729
|
-
console.log(` ${
|
|
730
|
-
console.log(` ${
|
|
731
|
-
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`);
|
|
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
|
-
` ${
|
|
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(` ${
|
|
773
|
-
console.log(` ${
|
|
774
|
-
console.log(` ${
|
|
775
|
-
console.log(` ${
|
|
776
|
-
console.log(` ${
|
|
777
|
-
console.log(` ${
|
|
778
|
-
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()}`);
|
|
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
|
-
` ${
|
|
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 ${
|
|
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
|
-
|
|
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(
|
|
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 =
|
|
1263
|
+
const rl = createInterface3({ input: stdin3, output: stdout3 });
|
|
941
1264
|
try {
|
|
942
|
-
console.log(
|
|
1265
|
+
console.log(chalk4.bold.blue("\n AnrakLegal Sync \u2014 Setup\n"));
|
|
943
1266
|
const apiUrl = await rl.question(
|
|
944
|
-
` AnrakLegal URL ${
|
|
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 =
|
|
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 ${
|
|
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
|
-
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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: ${
|
|
1070
|
-
console.log(` Pending: ${
|
|
1071
|
-
console.log(` 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: ${
|
|
1400
|
+
console.log(` Auth: ${chalk4.green("valid")}`);
|
|
1078
1401
|
} catch {
|
|
1079
1402
|
console.log(`
|
|
1080
|
-
Auth: ${
|
|
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(
|
|
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
|
-
` ${
|
|
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(
|
|
1426
|
+
console.log(chalk4.dim("\n Unmapped server cases:"));
|
|
1104
1427
|
for (const c of unmapped) {
|
|
1105
|
-
console.log(` ${
|
|
1428
|
+
console.log(` ${chalk4.dim(c.caseNumber)} ${chalk4.dim(c.caseName)}`);
|
|
1106
1429
|
}
|
|
1107
1430
|
}
|
|
1108
1431
|
} catch {
|