@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.
- package/dist/cli.js +368 -51
- 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);
|
|
@@ -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 =
|
|
1263
|
+
const rl = createInterface3({ input: stdin3, output: stdout3 });
|
|
947
1264
|
try {
|
|
948
|
-
console.log(
|
|
1265
|
+
console.log(chalk4.bold.blue("\n AnrakLegal Sync \u2014 Setup\n"));
|
|
949
1266
|
const apiUrl = await rl.question(
|
|
950
|
-
` AnrakLegal URL ${
|
|
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 =
|
|
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 ${
|
|
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
|
-
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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: ${
|
|
1076
|
-
console.log(` Pending: ${
|
|
1077
|
-
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)}`);
|
|
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: ${
|
|
1400
|
+
console.log(` Auth: ${chalk4.green("valid")}`);
|
|
1084
1401
|
} catch {
|
|
1085
1402
|
console.log(`
|
|
1086
|
-
Auth: ${
|
|
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(
|
|
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
|
-
` ${
|
|
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(
|
|
1426
|
+
console.log(chalk4.dim("\n Unmapped server cases:"));
|
|
1110
1427
|
for (const c of unmapped) {
|
|
1111
|
-
console.log(` ${
|
|
1428
|
+
console.log(` ${chalk4.dim(c.caseNumber)} ${chalk4.dim(c.caseName)}`);
|
|
1112
1429
|
}
|
|
1113
1430
|
}
|
|
1114
1431
|
} catch {
|