@anraktech/sync 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +61 -29
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -5,7 +5,7 @@ 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
7
  import { existsSync as existsSync3, statSync as statSync2 } from "fs";
8
- import { resolve as resolve2 } from "path";
8
+ import { resolve as resolve3 } from "path";
9
9
  import chalk3 from "chalk";
10
10
 
11
11
  // src/config.ts
@@ -86,7 +86,7 @@ function openBrowser(url) {
86
86
  exec(`${cmd} "${url}"`);
87
87
  }
88
88
  function findFreePort() {
89
- return new Promise((resolve3, reject) => {
89
+ return new Promise((resolve4, reject) => {
90
90
  const srv = createServer();
91
91
  srv.listen(0, () => {
92
92
  const addr = srv.address();
@@ -96,14 +96,14 @@ function findFreePort() {
96
96
  return;
97
97
  }
98
98
  const port = addr.port;
99
- srv.close(() => resolve3(port));
99
+ srv.close(() => resolve4(port));
100
100
  });
101
101
  });
102
102
  }
103
103
  async function browserLogin(apiUrl) {
104
104
  const port = await findFreePort();
105
105
  return new Promise(
106
- (resolve3, reject) => {
106
+ (resolve4, reject) => {
107
107
  let server;
108
108
  const timeout = setTimeout(() => {
109
109
  server?.close();
@@ -150,7 +150,7 @@ async function browserLogin(apiUrl) {
150
150
  const tokens = await resp.json();
151
151
  clearTimeout(timeout);
152
152
  server.close();
153
- resolve3(tokens);
153
+ resolve4(tokens);
154
154
  } catch (err) {
155
155
  clearTimeout(timeout);
156
156
  server.close();
@@ -338,11 +338,11 @@ function persist() {
338
338
  if (cache) saveCache(cache);
339
339
  }
340
340
  function hashFile(filePath) {
341
- return new Promise((resolve3, reject) => {
341
+ return new Promise((resolve4, reject) => {
342
342
  const hash = createHash("sha256");
343
343
  const stream = createReadStream(filePath);
344
344
  stream.on("data", (chunk) => hash.update(chunk));
345
- stream.on("end", () => resolve3(hash.digest("hex")));
345
+ stream.on("end", () => resolve4(hash.digest("hex")));
346
346
  stream.on("error", reject);
347
347
  });
348
348
  }
@@ -417,7 +417,7 @@ function resetCache() {
417
417
  // src/watcher.ts
418
418
  import { watch } from "chokidar";
419
419
  import { readdirSync, statSync, existsSync as existsSync2 } from "fs";
420
- import { join as join2, relative as relative2, basename as basename4, resolve } from "path";
420
+ import { join as join3, relative as relative2, basename as basename4, resolve as resolve2 } from "path";
421
421
 
422
422
  // src/uploader.ts
423
423
  import { stat as stat2 } from "fs/promises";
@@ -619,7 +619,38 @@ function sleep(ms) {
619
619
  // src/agent.ts
620
620
  import { createInterface } from "readline/promises";
621
621
  import { stdin, stdout } from "process";
622
+ import { homedir as homedir2, platform } from "os";
623
+ import { resolve, join as join2 } from "path";
622
624
  import chalk2 from "chalk";
625
+ var HOME = homedir2();
626
+ var IS_MAC = platform() === "darwin";
627
+ var FOLDER_SHORTCUTS = {
628
+ downloads: join2(HOME, "Downloads"),
629
+ download: join2(HOME, "Downloads"),
630
+ desktop: join2(HOME, "Desktop"),
631
+ documents: join2(HOME, "Documents"),
632
+ document: join2(HOME, "Documents"),
633
+ home: HOME,
634
+ "~": HOME
635
+ };
636
+ function normalizePath(folderPath) {
637
+ const trimmed = folderPath.trim();
638
+ if (trimmed.startsWith("~/") || trimmed === "~") {
639
+ return resolve(trimmed.replace(/^~/, HOME));
640
+ }
641
+ const lower = trimmed.toLowerCase();
642
+ if (FOLDER_SHORTCUTS[lower]) {
643
+ return FOLDER_SHORTCUTS[lower];
644
+ }
645
+ const withoutSlash = lower.replace(/^\//, "");
646
+ if (FOLDER_SHORTCUTS[withoutSlash]) {
647
+ return FOLDER_SHORTCUTS[withoutSlash];
648
+ }
649
+ if (trimmed.startsWith("/") || /^[A-Z]:\\/i.test(trimmed)) {
650
+ return resolve(trimmed);
651
+ }
652
+ return resolve(HOME, trimmed);
653
+ }
623
654
  var TOOLS = [
624
655
  {
625
656
  type: "function",
@@ -684,21 +715,29 @@ function buildSystemPrompt(config) {
684
715
 
685
716
  Watch folder: ${config.watchFolder}
686
717
  Server: ${config.apiUrl}
718
+ Home directory: ${HOME}
719
+ Platform: ${IS_MAC ? "macOS" : platform()}
687
720
 
688
721
  You can scan folders, list cases, show sync status, and more using your tools.
689
722
 
690
723
  Rules:
691
724
  - Be concise. This is a terminal \u2014 1-3 lines unless showing a list.
692
725
  - 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).
726
+ - IMPORTANT: When user mentions a folder like "downloads" or "desktop", ALWAYS use the full absolute path. Examples:
727
+ "downloads" or "my downloads" \u2192 ${join2(HOME, "Downloads")}
728
+ "desktop" \u2192 ${join2(HOME, "Desktop")}
729
+ "documents" \u2192 ${join2(HOME, "Documents")}
730
+ "~/SomeFolder" \u2192 ${HOME}/SomeFolder
694
731
  - Call tools to perform actions. Summarize results naturally after.
732
+ - If a tool returns an error, report it clearly to the user.
695
733
  - Do NOT use thinking tags or reasoning tags in your output.`;
696
734
  }
697
735
  async function executeTool(name, args, ctx) {
698
736
  switch (name) {
699
737
  case "scan_folder": {
700
- const folderPath = args.folderPath;
701
- if (!folderPath) return JSON.stringify({ error: "Missing folderPath" });
738
+ const rawPath = args.folderPath;
739
+ if (!rawPath) return JSON.stringify({ error: "Missing folderPath" });
740
+ const folderPath = normalizePath(rawPath);
702
741
  try {
703
742
  await ctx.scanFolder(folderPath);
704
743
  const stats = getStats();
@@ -800,18 +839,9 @@ async function agentTurn(messages, ctx) {
800
839
  }
801
840
  let textContent = "";
802
841
  const toolCalls = /* @__PURE__ */ new Map();
803
- let isFirstText = true;
804
842
  for await (const { delta } of parseSSE(response)) {
805
843
  if (delta.content) {
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
- }
844
+ textContent += delta.content;
815
845
  }
816
846
  if (delta.tool_calls) {
817
847
  for (const tc of delta.tool_calls) {
@@ -828,8 +858,10 @@ async function agentTurn(messages, ctx) {
828
858
  }
829
859
  }
830
860
  }
861
+ textContent = textContent.replace(/<think>[\s\S]*?<\/think>/g, "").trim();
831
862
  if (textContent) {
832
- process.stdout.write("\n");
863
+ process.stdout.write(` ${textContent}
864
+ `);
833
865
  }
834
866
  if (toolCalls.size === 0) {
835
867
  return textContent;
@@ -924,7 +956,7 @@ async function scanFolder(config) {
924
956
  }
925
957
  for (const entry of entries) {
926
958
  if (isIgnoredFile(entry)) continue;
927
- const fullPath = join2(dir, entry);
959
+ const fullPath = join3(dir, entry);
928
960
  let s;
929
961
  try {
930
962
  s = statSync(fullPath);
@@ -959,7 +991,7 @@ async function pushSync(config) {
959
991
  );
960
992
  }
961
993
  async function scanExternalFolder(config, folderPath, cases) {
962
- const absPath = resolve(folderPath);
994
+ const absPath = resolve2(folderPath);
963
995
  if (!existsSync2(absPath)) {
964
996
  log.error(`Folder not found: ${absPath}`);
965
997
  return;
@@ -979,7 +1011,7 @@ async function scanExternalFolder(config, folderPath, cases) {
979
1011
  }
980
1012
  for (const entry of entries) {
981
1013
  if (isIgnoredFile(entry)) continue;
982
- const fullPath = join2(dir, entry);
1014
+ const fullPath = join3(dir, entry);
983
1015
  let s;
984
1016
  try {
985
1017
  s = statSync(fullPath);
@@ -990,7 +1022,7 @@ async function scanExternalFolder(config, folderPath, cases) {
990
1022
  walk(fullPath);
991
1023
  } else if (s.isFile() && isSupportedFile(entry)) {
992
1024
  fileCount++;
993
- void enqueue(fullPath, join2(absPath, ".."));
1025
+ void enqueue(fullPath, join3(absPath, ".."));
994
1026
  }
995
1027
  }
996
1028
  }
@@ -1107,10 +1139,10 @@ async function startWatching(config) {
1107
1139
  // src/cli.ts
1108
1140
  import { readFileSync as readFileSync2 } from "fs";
1109
1141
  import { fileURLToPath } from "url";
1110
- import { dirname as dirname2, join as join3 } from "path";
1142
+ import { dirname as dirname2, join as join4 } from "path";
1111
1143
  var __filename2 = fileURLToPath(import.meta.url);
1112
1144
  var __dirname2 = dirname2(__filename2);
1113
- var pkg = JSON.parse(readFileSync2(join3(__dirname2, "..", "package.json"), "utf-8"));
1145
+ var pkg = JSON.parse(readFileSync2(join4(__dirname2, "..", "package.json"), "utf-8"));
1114
1146
  var program = new Command();
1115
1147
  program.name("anrak-sync").description("AnrakLegal desktop file sync \u2014 watches local folders, syncs to case management").version(pkg.version);
1116
1148
  program.command("init").description("Set up AnrakLegal Sync (first-time configuration)").option("--password", "Use email/password login instead of browser").action(async (opts) => {
@@ -1151,7 +1183,7 @@ program.command("init").description("Set up AnrakLegal Sync (first-time configur
1151
1183
  const watchInput = await rl2.question(
1152
1184
  ` Watch folder ${chalk3.dim(`(${defaultFolder})`)}: `
1153
1185
  );
1154
- const watchFolder = resolve2(watchInput || defaultFolder);
1186
+ const watchFolder = resolve3(watchInput || defaultFolder);
1155
1187
  rl2.close();
1156
1188
  if (!existsSync3(watchFolder)) {
1157
1189
  log.warn(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anraktech/sync",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "AnrakLegal desktop file sync agent — watches local folders and syncs to case management",
5
5
  "type": "module",
6
6
  "bin": {