@anraktech/sync 0.14.0 → 0.15.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 +136 -74
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -4,14 +4,15 @@
|
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
6
6
|
import { stdin as stdin2, stdout as stdout2 } from "process";
|
|
7
|
-
import {
|
|
8
|
-
import { resolve as
|
|
7
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
8
|
+
import { resolve as resolve4 } from "path";
|
|
9
|
+
import { homedir as homedir3 } from "os";
|
|
9
10
|
import chalk4 from "chalk";
|
|
10
11
|
|
|
11
12
|
// src/config.ts
|
|
12
13
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
13
14
|
import { homedir } from "os";
|
|
14
|
-
import { join } from "path";
|
|
15
|
+
import { join, resolve } from "path";
|
|
15
16
|
|
|
16
17
|
// src/logger.ts
|
|
17
18
|
import chalk from "chalk";
|
|
@@ -31,11 +32,29 @@ var log = {
|
|
|
31
32
|
var CONFIG_DIR = join(homedir(), ".anrak-sync");
|
|
32
33
|
var CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
33
34
|
var CACHE_FILE = join(CONFIG_DIR, "cache.json");
|
|
35
|
+
var HOME = homedir();
|
|
36
|
+
var SHORTCUTS = {
|
|
37
|
+
downloads: join(HOME, "Downloads"),
|
|
38
|
+
download: join(HOME, "Downloads"),
|
|
39
|
+
desktop: join(HOME, "Desktop"),
|
|
40
|
+
documents: join(HOME, "Documents"),
|
|
41
|
+
document: join(HOME, "Documents")
|
|
42
|
+
};
|
|
43
|
+
function normalizePath(p) {
|
|
44
|
+
if (!p) return p;
|
|
45
|
+
if (p.startsWith("~/") || p === "~") return resolve(p.replace(/^~/, HOME));
|
|
46
|
+
const lower = p.toLowerCase().replace(/^\//, "");
|
|
47
|
+
if (SHORTCUTS[lower]) return SHORTCUTS[lower];
|
|
48
|
+
if (p.startsWith("/") && !existsSync(p) && SHORTCUTS[p.slice(1).toLowerCase()]) {
|
|
49
|
+
return SHORTCUTS[p.slice(1).toLowerCase()];
|
|
50
|
+
}
|
|
51
|
+
return p;
|
|
52
|
+
}
|
|
34
53
|
function getWatchFolders(config) {
|
|
35
54
|
const folders = /* @__PURE__ */ new Set();
|
|
36
|
-
if (config.watchFolder) folders.add(config.watchFolder);
|
|
55
|
+
if (config.watchFolder) folders.add(normalizePath(config.watchFolder));
|
|
37
56
|
if (config.watchFolders) {
|
|
38
|
-
for (const f of config.watchFolders) folders.add(f);
|
|
57
|
+
for (const f of config.watchFolders) folders.add(normalizePath(f));
|
|
39
58
|
}
|
|
40
59
|
return [...folders];
|
|
41
60
|
}
|
|
@@ -115,7 +134,7 @@ function openBrowser(url) {
|
|
|
115
134
|
exec(`${cmd} "${url}"`);
|
|
116
135
|
}
|
|
117
136
|
function findFreePort() {
|
|
118
|
-
return new Promise((
|
|
137
|
+
return new Promise((resolve5, reject) => {
|
|
119
138
|
const srv = createServer();
|
|
120
139
|
srv.listen(0, () => {
|
|
121
140
|
const addr = srv.address();
|
|
@@ -125,14 +144,14 @@ function findFreePort() {
|
|
|
125
144
|
return;
|
|
126
145
|
}
|
|
127
146
|
const port = addr.port;
|
|
128
|
-
srv.close(() =>
|
|
147
|
+
srv.close(() => resolve5(port));
|
|
129
148
|
});
|
|
130
149
|
});
|
|
131
150
|
}
|
|
132
151
|
async function browserLogin(apiUrl) {
|
|
133
152
|
const port = await findFreePort();
|
|
134
153
|
return new Promise(
|
|
135
|
-
(
|
|
154
|
+
(resolve5, reject) => {
|
|
136
155
|
let server;
|
|
137
156
|
const timeout = setTimeout(() => {
|
|
138
157
|
server?.close();
|
|
@@ -179,7 +198,7 @@ async function browserLogin(apiUrl) {
|
|
|
179
198
|
const tokens = await resp.json();
|
|
180
199
|
clearTimeout(timeout);
|
|
181
200
|
server.close();
|
|
182
|
-
|
|
201
|
+
resolve5(tokens);
|
|
183
202
|
} catch (err) {
|
|
184
203
|
clearTimeout(timeout);
|
|
185
204
|
server.close();
|
|
@@ -367,11 +386,11 @@ function persist() {
|
|
|
367
386
|
if (cache) saveCache(cache);
|
|
368
387
|
}
|
|
369
388
|
function hashFile(filePath) {
|
|
370
|
-
return new Promise((
|
|
389
|
+
return new Promise((resolve5, reject) => {
|
|
371
390
|
const hash = createHash("sha256");
|
|
372
391
|
const stream = createReadStream(filePath);
|
|
373
392
|
stream.on("data", (chunk) => hash.update(chunk));
|
|
374
|
-
stream.on("end", () =>
|
|
393
|
+
stream.on("end", () => resolve5(hash.digest("hex")));
|
|
375
394
|
stream.on("error", reject);
|
|
376
395
|
});
|
|
377
396
|
}
|
|
@@ -446,7 +465,7 @@ function resetCache() {
|
|
|
446
465
|
// src/watcher.ts
|
|
447
466
|
import { watch } from "chokidar";
|
|
448
467
|
import { readdirSync as readdirSync2, statSync as statSync2, existsSync as existsSync3 } from "fs";
|
|
449
|
-
import { join as join3, relative as relative2, basename as basename5, resolve as
|
|
468
|
+
import { join as join3, relative as relative2, basename as basename5, resolve as resolve3 } from "path";
|
|
450
469
|
|
|
451
470
|
// src/uploader.ts
|
|
452
471
|
import { stat as stat2 } from "fs/promises";
|
|
@@ -753,7 +772,7 @@ function sleep(ms) {
|
|
|
753
772
|
import { createInterface } from "readline/promises";
|
|
754
773
|
import { stdin, stdout } from "process";
|
|
755
774
|
import { homedir as homedir2, platform } from "os";
|
|
756
|
-
import { resolve, join as join2, dirname as dirname2, basename as basename4 } from "path";
|
|
775
|
+
import { resolve as resolve2, join as join2, dirname as dirname2, basename as basename4 } from "path";
|
|
757
776
|
import { existsSync as existsSync2, readdirSync, statSync, readFileSync as readFileSync2 } from "fs";
|
|
758
777
|
import { fileURLToPath } from "url";
|
|
759
778
|
import chalk2 from "chalk";
|
|
@@ -767,21 +786,21 @@ var PKG_VERSION = (() => {
|
|
|
767
786
|
return "0.0.0";
|
|
768
787
|
}
|
|
769
788
|
})();
|
|
770
|
-
var
|
|
789
|
+
var HOME2 = homedir2();
|
|
771
790
|
var IS_MAC = platform() === "darwin";
|
|
772
791
|
var FOLDER_SHORTCUTS = {
|
|
773
|
-
downloads: join2(
|
|
774
|
-
download: join2(
|
|
775
|
-
desktop: join2(
|
|
776
|
-
documents: join2(
|
|
777
|
-
document: join2(
|
|
778
|
-
home:
|
|
779
|
-
"~":
|
|
792
|
+
downloads: join2(HOME2, "Downloads"),
|
|
793
|
+
download: join2(HOME2, "Downloads"),
|
|
794
|
+
desktop: join2(HOME2, "Desktop"),
|
|
795
|
+
documents: join2(HOME2, "Documents"),
|
|
796
|
+
document: join2(HOME2, "Documents"),
|
|
797
|
+
home: HOME2,
|
|
798
|
+
"~": HOME2
|
|
780
799
|
};
|
|
781
|
-
function
|
|
800
|
+
function normalizePath2(folderPath) {
|
|
782
801
|
const trimmed = folderPath.trim();
|
|
783
802
|
if (trimmed.startsWith("~/") || trimmed === "~") {
|
|
784
|
-
return
|
|
803
|
+
return resolve2(trimmed.replace(/^~/, HOME2));
|
|
785
804
|
}
|
|
786
805
|
const lower = trimmed.toLowerCase();
|
|
787
806
|
if (FOLDER_SHORTCUTS[lower]) {
|
|
@@ -792,9 +811,9 @@ function normalizePath(folderPath) {
|
|
|
792
811
|
return FOLDER_SHORTCUTS[withoutSlash];
|
|
793
812
|
}
|
|
794
813
|
if (trimmed.startsWith("/") || /^[A-Z]:\\/i.test(trimmed)) {
|
|
795
|
-
return
|
|
814
|
+
return resolve2(trimmed);
|
|
796
815
|
}
|
|
797
|
-
return
|
|
816
|
+
return resolve2(HOME2, trimmed);
|
|
798
817
|
}
|
|
799
818
|
var TOOLS = [
|
|
800
819
|
{
|
|
@@ -949,28 +968,42 @@ var TOOLS = [
|
|
|
949
968
|
}
|
|
950
969
|
}
|
|
951
970
|
];
|
|
952
|
-
function buildSystemPrompt(config) {
|
|
971
|
+
function buildSystemPrompt(config, onboarding) {
|
|
953
972
|
const folders = getWatchFolders(config);
|
|
954
973
|
const folderList = folders.map((f) => ` - ${f}`).join("\n");
|
|
974
|
+
const onboardingBlock = onboarding ? `
|
|
975
|
+
ONBOARDING MODE \u2014 This is the user's first time using AnrakLegal Sync. You just finished setup.
|
|
976
|
+
Your FIRST response must:
|
|
977
|
+
1. Welcome them warmly (1 line).
|
|
978
|
+
2. Ask which folders on their computer contain legal files they'd like to sync. Give examples like:
|
|
979
|
+
- Downloads (${join2(HOME2, "Downloads")}) \u2014 where they download court orders, filings
|
|
980
|
+
- Desktop (${join2(HOME2, "Desktop")}) \u2014 quick-access files
|
|
981
|
+
- Documents (${join2(HOME2, "Documents")}) \u2014 organized case folders
|
|
982
|
+
- A custom folder path
|
|
983
|
+
3. Tell them they can name multiple folders at once, e.g. "downloads and desktop"
|
|
984
|
+
4. After the user responds, use manage_watch_folders to add each folder they mention.
|
|
985
|
+
5. Once folders are set, confirm the setup and let them know you're now watching for new files.
|
|
986
|
+
Keep it conversational and simple \u2014 they are lawyers, not developers.
|
|
987
|
+
` : "";
|
|
955
988
|
return `You are AnrakLegal Sync, a terminal assistant that helps lawyers sync local files to their case management system.
|
|
956
989
|
|
|
957
990
|
Watch folders:
|
|
958
991
|
${folderList}
|
|
959
992
|
Server: ${config.apiUrl}
|
|
960
|
-
Home directory: ${
|
|
993
|
+
Home directory: ${HOME2}
|
|
961
994
|
Platform: ${IS_MAC ? "macOS" : platform()}
|
|
962
995
|
|
|
963
996
|
You can browse local folders, scan & sync files, list cases, show sync status, manage watch folders, and more.
|
|
964
|
-
|
|
997
|
+
${onboardingBlock}
|
|
965
998
|
Rules:
|
|
966
999
|
- Be concise. This is a terminal \u2014 1-3 lines unless showing a list.
|
|
967
1000
|
- Do NOT use markdown. Plain text only.
|
|
968
1001
|
- WATCH FOLDERS: When user asks to "watch", "add", or "also sync" a folder, use manage_watch_folders with action "add". When they say "stop watching" or "remove" a folder, use action "remove".
|
|
969
1002
|
- IMPORTANT: When user mentions a folder like "downloads" or "desktop", ALWAYS use the full absolute path. Examples:
|
|
970
|
-
"downloads" or "my downloads" \u2192 ${join2(
|
|
971
|
-
"desktop" \u2192 ${join2(
|
|
972
|
-
"documents" \u2192 ${join2(
|
|
973
|
-
"~/SomeFolder" \u2192 ${
|
|
1003
|
+
"downloads" or "my downloads" \u2192 ${join2(HOME2, "Downloads")}
|
|
1004
|
+
"desktop" \u2192 ${join2(HOME2, "Desktop")}
|
|
1005
|
+
"documents" \u2192 ${join2(HOME2, "Documents")}
|
|
1006
|
+
"~/SomeFolder" \u2192 ${HOME2}/SomeFolder
|
|
974
1007
|
- FINDING FILES: When user asks to find or locate a specific file, ALWAYS use search_files. NEVER browse a folder hoping to find it.
|
|
975
1008
|
- UPLOADING: When user wants to upload a specific file to a case, use upload_to_case (NOT scan_folder). scan_folder uploads EVERYTHING.
|
|
976
1009
|
- When user says "look at" or "check" a folder, use browse_folder to show what's there.
|
|
@@ -1031,8 +1064,8 @@ async function executeTool(name, args, ctx) {
|
|
|
1031
1064
|
var searchDir = searchDir2;
|
|
1032
1065
|
const query = (args.query || "").trim();
|
|
1033
1066
|
if (!query) return JSON.stringify({ error: "Missing search query" });
|
|
1034
|
-
const rawSearch = args.searchPath ||
|
|
1035
|
-
const searchPath =
|
|
1067
|
+
const rawSearch = args.searchPath || HOME2;
|
|
1068
|
+
const searchPath = normalizePath2(rawSearch);
|
|
1036
1069
|
const maxDepth = Math.min(args.maxDepth || 3, 6);
|
|
1037
1070
|
if (!existsSync2(searchPath) || !statSync(searchPath).isDirectory()) {
|
|
1038
1071
|
return JSON.stringify({ error: `Search path not found: ${searchPath}` });
|
|
@@ -1053,7 +1086,7 @@ async function executeTool(name, args, ctx) {
|
|
|
1053
1086
|
const caseId = args.caseIdentifier;
|
|
1054
1087
|
if (!rawPath) return JSON.stringify({ error: "Missing filePath" });
|
|
1055
1088
|
if (!caseId) return JSON.stringify({ error: "Missing caseIdentifier" });
|
|
1056
|
-
const filePath =
|
|
1089
|
+
const filePath = normalizePath2(rawPath);
|
|
1057
1090
|
if (!existsSync2(filePath)) {
|
|
1058
1091
|
return JSON.stringify({ error: `File not found: ${filePath}` });
|
|
1059
1092
|
}
|
|
@@ -1131,7 +1164,7 @@ async function executeTool(name, args, ctx) {
|
|
|
1131
1164
|
case "browse_folder": {
|
|
1132
1165
|
const rawPath = args.folderPath;
|
|
1133
1166
|
if (!rawPath) return JSON.stringify({ error: "Missing folderPath" });
|
|
1134
|
-
const folderPath =
|
|
1167
|
+
const folderPath = normalizePath2(rawPath);
|
|
1135
1168
|
if (!existsSync2(folderPath)) {
|
|
1136
1169
|
return JSON.stringify({ error: `Folder not found: ${folderPath}` });
|
|
1137
1170
|
}
|
|
@@ -1181,7 +1214,7 @@ async function executeTool(name, args, ctx) {
|
|
|
1181
1214
|
case "scan_folder": {
|
|
1182
1215
|
const rawPath = args.folderPath;
|
|
1183
1216
|
if (!rawPath) return JSON.stringify({ error: "Missing folderPath" });
|
|
1184
|
-
const folderPath =
|
|
1217
|
+
const folderPath = normalizePath2(rawPath);
|
|
1185
1218
|
try {
|
|
1186
1219
|
await ctx.scanFolder(folderPath);
|
|
1187
1220
|
const stats = getStats();
|
|
@@ -1253,7 +1286,7 @@ async function executeTool(name, args, ctx) {
|
|
|
1253
1286
|
}
|
|
1254
1287
|
if (action === "add") {
|
|
1255
1288
|
if (!rawFolder) return JSON.stringify({ error: "Missing folderPath" });
|
|
1256
|
-
const folderPath =
|
|
1289
|
+
const folderPath = normalizePath2(rawFolder);
|
|
1257
1290
|
if (!ctx.addWatchFolder) {
|
|
1258
1291
|
return JSON.stringify({ error: "Watch folder management not available" });
|
|
1259
1292
|
}
|
|
@@ -1265,7 +1298,7 @@ async function executeTool(name, args, ctx) {
|
|
|
1265
1298
|
}
|
|
1266
1299
|
if (action === "remove") {
|
|
1267
1300
|
if (!rawFolder) return JSON.stringify({ error: "Missing folderPath" });
|
|
1268
|
-
const folderPath =
|
|
1301
|
+
const folderPath = normalizePath2(rawFolder);
|
|
1269
1302
|
if (!ctx.removeWatchFolder) {
|
|
1270
1303
|
return JSON.stringify({ error: "Watch folder management not available" });
|
|
1271
1304
|
}
|
|
@@ -1376,13 +1409,13 @@ async function agentTurn(messages, ctx) {
|
|
|
1376
1409
|
}
|
|
1377
1410
|
function startAIAgent(ctx) {
|
|
1378
1411
|
const history = [
|
|
1379
|
-
{ role: "system", content: buildSystemPrompt(ctx.config) }
|
|
1412
|
+
{ role: "system", content: buildSystemPrompt(ctx.config, ctx.onboarding) }
|
|
1380
1413
|
];
|
|
1381
1414
|
const rl = createInterface({ input: stdin, output: stdout });
|
|
1382
1415
|
const W = Math.min(process.stdout.columns || 60, 60);
|
|
1383
1416
|
const allFolders = getWatchFolders(ctx.config);
|
|
1384
1417
|
const folderDisplays = allFolders.map(
|
|
1385
|
-
(f) => f.startsWith(
|
|
1418
|
+
(f) => f.startsWith(HOME2) ? "~" + f.slice(HOME2.length) : f
|
|
1386
1419
|
);
|
|
1387
1420
|
const stats = ctx.bootStats;
|
|
1388
1421
|
const pad = (s, len) => {
|
|
@@ -1428,7 +1461,11 @@ function startAIAgent(ctx) {
|
|
|
1428
1461
|
console.log(row(`${chalk2.dim("Status")} ${statusLine}`));
|
|
1429
1462
|
}
|
|
1430
1463
|
console.log(empty);
|
|
1431
|
-
|
|
1464
|
+
if (ctx.onboarding) {
|
|
1465
|
+
console.log(row(chalk2.dim("First-time setup \u2014 the AI will guide you.")));
|
|
1466
|
+
} else {
|
|
1467
|
+
console.log(row(chalk2.dim("Type naturally, or press / for commands.")));
|
|
1468
|
+
}
|
|
1432
1469
|
console.log(empty);
|
|
1433
1470
|
console.log(bot);
|
|
1434
1471
|
console.log("");
|
|
@@ -1499,7 +1536,7 @@ function startAIAgent(ctx) {
|
|
|
1499
1536
|
console.log(chalk2.dim(" " + "\u2500".repeat(40)));
|
|
1500
1537
|
for (let i = 0; i < folders.length; i++) {
|
|
1501
1538
|
const f = folders[i];
|
|
1502
|
-
const display = f.startsWith(
|
|
1539
|
+
const display = f.startsWith(HOME2) ? "~" + f.slice(HOME2.length) : f;
|
|
1503
1540
|
const tag = f === ctx.config.watchFolder ? chalk2.cyan(" (primary)") : "";
|
|
1504
1541
|
console.log(` ${chalk2.dim(`${i + 1}.`)} ${display}${tag}`);
|
|
1505
1542
|
}
|
|
@@ -1517,14 +1554,14 @@ function startAIAgent(ctx) {
|
|
|
1517
1554
|
console.log(chalk2.red(" Usage: /folder add <path>"));
|
|
1518
1555
|
return;
|
|
1519
1556
|
}
|
|
1520
|
-
const absPath =
|
|
1557
|
+
const absPath = normalizePath2(folderArg);
|
|
1521
1558
|
if (!ctx.addWatchFolder) {
|
|
1522
1559
|
console.log(chalk2.red(" Watch folder management not available."));
|
|
1523
1560
|
return;
|
|
1524
1561
|
}
|
|
1525
1562
|
const result = await ctx.addWatchFolder(absPath);
|
|
1526
1563
|
if (result.added) {
|
|
1527
|
-
const display = (result.path || absPath).startsWith(
|
|
1564
|
+
const display = (result.path || absPath).startsWith(HOME2) ? "~" + (result.path || absPath).slice(HOME2.length) : result.path || absPath;
|
|
1528
1565
|
console.log(chalk2.green(` Added: ${display}`));
|
|
1529
1566
|
} else {
|
|
1530
1567
|
console.log(chalk2.yellow(` ${result.error || "Could not add folder"}`));
|
|
@@ -1534,14 +1571,14 @@ function startAIAgent(ctx) {
|
|
|
1534
1571
|
console.log(chalk2.red(" Usage: /folder remove <path>"));
|
|
1535
1572
|
return;
|
|
1536
1573
|
}
|
|
1537
|
-
const absPath =
|
|
1574
|
+
const absPath = normalizePath2(folderArg);
|
|
1538
1575
|
if (!ctx.removeWatchFolder) {
|
|
1539
1576
|
console.log(chalk2.red(" Watch folder management not available."));
|
|
1540
1577
|
return;
|
|
1541
1578
|
}
|
|
1542
1579
|
const result = ctx.removeWatchFolder(absPath);
|
|
1543
1580
|
if (result.removed) {
|
|
1544
|
-
const display = absPath.startsWith(
|
|
1581
|
+
const display = absPath.startsWith(HOME2) ? "~" + absPath.slice(HOME2.length) : absPath;
|
|
1545
1582
|
console.log(chalk2.green(` Removed: ${display}`));
|
|
1546
1583
|
} else {
|
|
1547
1584
|
console.log(chalk2.yellow(` ${result.error || "Could not remove folder"}`));
|
|
@@ -1551,14 +1588,14 @@ function startAIAgent(ctx) {
|
|
|
1551
1588
|
console.log(chalk2.red(" Usage: /folder set <path>"));
|
|
1552
1589
|
return;
|
|
1553
1590
|
}
|
|
1554
|
-
const absPath =
|
|
1591
|
+
const absPath = normalizePath2(folderArg);
|
|
1555
1592
|
if (!existsSync2(absPath) || !statSync(absPath).isDirectory()) {
|
|
1556
1593
|
console.log(chalk2.red(` Not a valid directory: ${absPath}`));
|
|
1557
1594
|
return;
|
|
1558
1595
|
}
|
|
1559
1596
|
ctx.config.watchFolder = absPath;
|
|
1560
1597
|
saveConfig(ctx.config);
|
|
1561
|
-
const display = absPath.startsWith(
|
|
1598
|
+
const display = absPath.startsWith(HOME2) ? "~" + absPath.slice(HOME2.length) : absPath;
|
|
1562
1599
|
console.log(chalk2.green(` Primary folder set to: ${display}`));
|
|
1563
1600
|
} else {
|
|
1564
1601
|
console.log(chalk2.dim(" Usage: /folder [list|add|remove|set] <path>"));
|
|
@@ -1645,7 +1682,22 @@ function startAIAgent(ctx) {
|
|
|
1645
1682
|
};
|
|
1646
1683
|
let currentSlashArgs = "";
|
|
1647
1684
|
let lastPendingCount = 0;
|
|
1685
|
+
let onboardingDone = !ctx.onboarding;
|
|
1648
1686
|
async function promptLoop() {
|
|
1687
|
+
if (!onboardingDone) {
|
|
1688
|
+
onboardingDone = true;
|
|
1689
|
+
history.push({ role: "user", content: "Hi, I just installed AnrakLegal Sync. Help me set up my watch folders." });
|
|
1690
|
+
try {
|
|
1691
|
+
const response = await agentTurn([...history], ctx);
|
|
1692
|
+
if (response) {
|
|
1693
|
+
history.push({ role: "assistant", content: response });
|
|
1694
|
+
}
|
|
1695
|
+
} catch (err) {
|
|
1696
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1697
|
+
console.log(chalk2.red(` Error: ${msg}`));
|
|
1698
|
+
}
|
|
1699
|
+
console.log("");
|
|
1700
|
+
}
|
|
1649
1701
|
while (true) {
|
|
1650
1702
|
if (pendingFiles.length > 0 && pendingFiles.length !== lastPendingCount) {
|
|
1651
1703
|
lastPendingCount = pendingFiles.length;
|
|
@@ -1714,7 +1766,7 @@ function startAIAgent(ctx) {
|
|
|
1714
1766
|
while (history.length > 21) {
|
|
1715
1767
|
history.splice(1, 1);
|
|
1716
1768
|
}
|
|
1717
|
-
history[0] = { role: "system", content: buildSystemPrompt(ctx.config) };
|
|
1769
|
+
history[0] = { role: "system", content: buildSystemPrompt(ctx.config, false) };
|
|
1718
1770
|
try {
|
|
1719
1771
|
const response = await agentTurn([...history], ctx);
|
|
1720
1772
|
if (response) {
|
|
@@ -1868,7 +1920,7 @@ async function pushSync(config) {
|
|
|
1868
1920
|
);
|
|
1869
1921
|
}
|
|
1870
1922
|
async function scanExternalFolder(config, folderPath, cases) {
|
|
1871
|
-
const absPath =
|
|
1923
|
+
const absPath = resolve3(folderPath);
|
|
1872
1924
|
if (!existsSync3(absPath)) {
|
|
1873
1925
|
log.error(`Folder not found: ${absPath}`);
|
|
1874
1926
|
return;
|
|
@@ -1914,7 +1966,7 @@ async function scanExternalFolder(config, folderPath, cases) {
|
|
|
1914
1966
|
log.success("Everything up to date");
|
|
1915
1967
|
}
|
|
1916
1968
|
}
|
|
1917
|
-
async function startWatching(config) {
|
|
1969
|
+
async function startWatching(config, opts) {
|
|
1918
1970
|
const folders = getWatchFolders(config);
|
|
1919
1971
|
const smartMapper = async (folderName, fileName, caseList) => smartMapFolder(config, folderName, fileName, caseList);
|
|
1920
1972
|
let cases = await listCases(config);
|
|
@@ -1987,6 +2039,7 @@ async function startWatching(config) {
|
|
|
1987
2039
|
startAIAgent({
|
|
1988
2040
|
config,
|
|
1989
2041
|
bootStats: { cases: cases.length, scanned, queued },
|
|
2042
|
+
onboarding: opts?.onboarding,
|
|
1990
2043
|
getCases: () => cases,
|
|
1991
2044
|
refreshCases: async () => {
|
|
1992
2045
|
cases = await listCases(config);
|
|
@@ -2005,7 +2058,7 @@ async function startWatching(config) {
|
|
|
2005
2058
|
await scanExternalFolder(config, folderPath, cases);
|
|
2006
2059
|
},
|
|
2007
2060
|
addWatchFolder: async (folderPath) => {
|
|
2008
|
-
const absPath =
|
|
2061
|
+
const absPath = resolve3(folderPath);
|
|
2009
2062
|
if (!existsSync3(absPath) || !statSync2(absPath).isDirectory()) {
|
|
2010
2063
|
return { added: false, error: `Not a valid directory: ${absPath}` };
|
|
2011
2064
|
}
|
|
@@ -2018,7 +2071,7 @@ async function startWatching(config) {
|
|
|
2018
2071
|
return { added: true, path: absPath };
|
|
2019
2072
|
},
|
|
2020
2073
|
removeWatchFolder: (folderPath) => {
|
|
2021
|
-
const absPath =
|
|
2074
|
+
const absPath = resolve3(folderPath);
|
|
2022
2075
|
const result = removeWatchFolder(config, absPath);
|
|
2023
2076
|
if (result.removed) {
|
|
2024
2077
|
const w = watchers.get(absPath);
|
|
@@ -2119,13 +2172,34 @@ async function checkForUpdates(currentVersion) {
|
|
|
2119
2172
|
}
|
|
2120
2173
|
|
|
2121
2174
|
// src/cli.ts
|
|
2122
|
-
import { readFileSync as readFileSync3 } from "fs";
|
|
2123
2175
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2124
2176
|
import { dirname as dirname3, join as join4 } from "path";
|
|
2125
2177
|
var __filename3 = fileURLToPath2(import.meta.url);
|
|
2126
2178
|
var __dirname3 = dirname3(__filename3);
|
|
2127
2179
|
var pkg = JSON.parse(readFileSync3(join4(__dirname3, "..", "package.json"), "utf-8"));
|
|
2128
2180
|
await checkForUpdates(pkg.version);
|
|
2181
|
+
var HOME3 = homedir3();
|
|
2182
|
+
var FOLDER_SHORTCUTS2 = {
|
|
2183
|
+
downloads: join4(HOME3, "Downloads"),
|
|
2184
|
+
download: join4(HOME3, "Downloads"),
|
|
2185
|
+
desktop: join4(HOME3, "Desktop"),
|
|
2186
|
+
documents: join4(HOME3, "Documents"),
|
|
2187
|
+
document: join4(HOME3, "Documents"),
|
|
2188
|
+
home: HOME3,
|
|
2189
|
+
"~": HOME3
|
|
2190
|
+
};
|
|
2191
|
+
function normalizeFolderPath(input) {
|
|
2192
|
+
const trimmed = input.trim();
|
|
2193
|
+
if (!trimmed) return trimmed;
|
|
2194
|
+
if (trimmed.startsWith("~/") || trimmed === "~") {
|
|
2195
|
+
return resolve4(trimmed.replace(/^~/, HOME3));
|
|
2196
|
+
}
|
|
2197
|
+
const lower = trimmed.toLowerCase();
|
|
2198
|
+
if (FOLDER_SHORTCUTS2[lower]) return FOLDER_SHORTCUTS2[lower];
|
|
2199
|
+
const withoutSlash = lower.replace(/^\//, "");
|
|
2200
|
+
if (FOLDER_SHORTCUTS2[withoutSlash]) return FOLDER_SHORTCUTS2[withoutSlash];
|
|
2201
|
+
return resolve4(trimmed);
|
|
2202
|
+
}
|
|
2129
2203
|
var program = new Command();
|
|
2130
2204
|
program.name("anrak-sync").description("AnrakLegal desktop file sync \u2014 watches local folders, syncs to case management").version(pkg.version);
|
|
2131
2205
|
program.command("init").description("Set up AnrakLegal Sync (first-time configuration)").option("--password", "Use email/password login instead of browser").action(async (opts) => {
|
|
@@ -2163,37 +2237,25 @@ program.command("init").description("Set up AnrakLegal Sync (first-time configur
|
|
|
2163
2237
|
tokens = await browserLogin(apiUrl);
|
|
2164
2238
|
}
|
|
2165
2239
|
log.success("Authenticated");
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
` Watch folder ${chalk4.dim(`(${defaultFolder})`)}: `
|
|
2170
|
-
);
|
|
2171
|
-
const watchFolder = resolve3(watchInput || defaultFolder);
|
|
2172
|
-
rl2.close();
|
|
2173
|
-
if (!existsSync4(watchFolder)) {
|
|
2174
|
-
log.warn(
|
|
2175
|
-
`Folder ${watchFolder} does not exist \u2014 it will be created when you add files`
|
|
2176
|
-
);
|
|
2177
|
-
} else if (!statSync3(watchFolder).isDirectory()) {
|
|
2178
|
-
log.error(`${watchFolder} is not a directory`);
|
|
2179
|
-
process.exit(1);
|
|
2240
|
+
try {
|
|
2241
|
+
rl.close();
|
|
2242
|
+
} catch {
|
|
2180
2243
|
}
|
|
2244
|
+
const defaultFolder = normalizeFolderPath("downloads");
|
|
2181
2245
|
const config = {
|
|
2182
2246
|
apiUrl,
|
|
2183
2247
|
supabaseUrl: serverConfig.supabaseUrl,
|
|
2184
2248
|
supabaseAnonKey: serverConfig.supabaseAnonKey,
|
|
2185
2249
|
accessToken: tokens.accessToken,
|
|
2186
2250
|
refreshToken: tokens.refreshToken,
|
|
2187
|
-
watchFolder
|
|
2251
|
+
watchFolder: defaultFolder
|
|
2188
2252
|
};
|
|
2189
2253
|
saveConfig(config);
|
|
2190
2254
|
console.log("");
|
|
2191
2255
|
log.success("Setup complete!");
|
|
2192
2256
|
log.info(`Config saved to ${getConfigDir()}`);
|
|
2193
|
-
log
|
|
2194
|
-
|
|
2195
|
-
chalk4.dim("\n Run `anrak-sync start` to begin syncing\n")
|
|
2196
|
-
);
|
|
2257
|
+
console.log("");
|
|
2258
|
+
await startWatching(config, { onboarding: true });
|
|
2197
2259
|
} catch (err) {
|
|
2198
2260
|
log.error(err instanceof Error ? err.message : String(err));
|
|
2199
2261
|
process.exit(1);
|