@anraktech/sync 0.5.0 → 0.6.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 +88 -13
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -4,7 +4,7 @@
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 { existsSync as existsSync3, statSync as statSync2 } from "fs";
7
+ import { existsSync as existsSync4, statSync as statSync3 } from "fs";
8
8
  import { resolve as resolve3 } from "path";
9
9
  import chalk3 from "chalk";
10
10
 
@@ -416,7 +416,7 @@ function resetCache() {
416
416
 
417
417
  // src/watcher.ts
418
418
  import { watch } from "chokidar";
419
- import { readdirSync, statSync, existsSync as existsSync2 } from "fs";
419
+ import { readdirSync as readdirSync2, statSync as statSync2, existsSync as existsSync3 } from "fs";
420
420
  import { join as join3, relative as relative2, basename as basename4, resolve as resolve2 } from "path";
421
421
 
422
422
  // src/uploader.ts
@@ -621,6 +621,7 @@ import { createInterface } from "readline/promises";
621
621
  import { stdin, stdout } from "process";
622
622
  import { homedir as homedir2, platform } from "os";
623
623
  import { resolve, join as join2 } from "path";
624
+ import { existsSync as existsSync2, readdirSync, statSync } from "fs";
624
625
  import chalk2 from "chalk";
625
626
  var HOME = homedir2();
626
627
  var IS_MAC = platform() === "darwin";
@@ -652,17 +653,39 @@ function normalizePath(folderPath) {
652
653
  return resolve(HOME, trimmed);
653
654
  }
654
655
  var TOOLS = [
656
+ {
657
+ type: "function",
658
+ function: {
659
+ name: "browse_folder",
660
+ description: "List files and subfolders in a local directory. Shows file names, sizes, types, and whether they are syncable. Use FIRST when user wants to see what's in a folder before syncing.",
661
+ parameters: {
662
+ type: "object",
663
+ properties: {
664
+ folderPath: {
665
+ type: "string",
666
+ description: "Absolute path to the folder to browse"
667
+ },
668
+ filter: {
669
+ type: "string",
670
+ enum: ["all", "syncable", "folders"],
671
+ description: "Filter: 'all' shows everything, 'syncable' shows only supported file types (PDF, DOCX, etc.), 'folders' shows only subfolders. Default: all"
672
+ }
673
+ },
674
+ required: ["folderPath"]
675
+ }
676
+ }
677
+ },
655
678
  {
656
679
  type: "function",
657
680
  function: {
658
681
  name: "scan_folder",
659
- 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.",
682
+ description: "Sync all supported files from a local folder to a matching legal case. Use when user explicitly asks to sync, upload, or push files. If unsure what's in the folder, use browse_folder first.",
660
683
  parameters: {
661
684
  type: "object",
662
685
  properties: {
663
686
  folderPath: {
664
687
  type: "string",
665
- description: "Absolute path to the folder, e.g. /Users/name/Downloads"
688
+ description: "Absolute path to the folder to sync"
666
689
  }
667
690
  },
668
691
  required: ["folderPath"]
@@ -718,7 +741,7 @@ Server: ${config.apiUrl}
718
741
  Home directory: ${HOME}
719
742
  Platform: ${IS_MAC ? "macOS" : platform()}
720
743
 
721
- You can scan folders, list cases, show sync status, and more using your tools.
744
+ You can browse local folders, scan & sync files, list cases, show sync status, and more.
722
745
 
723
746
  Rules:
724
747
  - Be concise. This is a terminal \u2014 1-3 lines unless showing a list.
@@ -728,12 +751,64 @@ Rules:
728
751
  "desktop" \u2192 ${join2(HOME, "Desktop")}
729
752
  "documents" \u2192 ${join2(HOME, "Documents")}
730
753
  "~/SomeFolder" \u2192 ${HOME}/SomeFolder
754
+ - When user says "look at" or "check" a folder, use browse_folder FIRST to show what's there.
755
+ - When user says "sync" or "upload", use scan_folder to actually sync.
756
+ - When browsing, highlight which files are syncable (PDF, DOCX, XLSX, etc.) vs not.
757
+ - Present file lists in a clean table/list format with names and sizes.
731
758
  - Call tools to perform actions. Summarize results naturally after.
732
759
  - If a tool returns an error, report it clearly to the user.
733
760
  - Do NOT use thinking tags or reasoning tags in your output.`;
734
761
  }
735
762
  async function executeTool(name, args, ctx) {
736
763
  switch (name) {
764
+ case "browse_folder": {
765
+ const rawPath = args.folderPath;
766
+ if (!rawPath) return JSON.stringify({ error: "Missing folderPath" });
767
+ const folderPath = normalizePath(rawPath);
768
+ if (!existsSync2(folderPath)) {
769
+ return JSON.stringify({ error: `Folder not found: ${folderPath}` });
770
+ }
771
+ try {
772
+ const s = statSync(folderPath);
773
+ if (!s.isDirectory()) {
774
+ return JSON.stringify({ error: `Not a directory: ${folderPath}` });
775
+ }
776
+ } catch {
777
+ return JSON.stringify({ error: `Cannot access: ${folderPath}` });
778
+ }
779
+ const filter = args.filter || "all";
780
+ const entries = readdirSync(folderPath);
781
+ const items = [];
782
+ for (const entry of entries) {
783
+ if (isIgnoredFile(entry)) continue;
784
+ const fullPath = join2(folderPath, entry);
785
+ let st;
786
+ try {
787
+ st = statSync(fullPath);
788
+ } catch {
789
+ continue;
790
+ }
791
+ if (st.isDirectory()) {
792
+ if (filter !== "syncable") {
793
+ items.push({ name: entry, type: "folder" });
794
+ }
795
+ } else if (st.isFile()) {
796
+ if (filter === "folders") continue;
797
+ const syncable = isSupportedFile(entry);
798
+ if (filter === "syncable" && !syncable) continue;
799
+ const sizeKB = Math.round(st.size / 1024);
800
+ const size = sizeKB > 1024 ? `${(sizeKB / 1024).toFixed(1)} MB` : `${sizeKB} KB`;
801
+ items.push({ name: entry, type: "file", size, syncable });
802
+ }
803
+ }
804
+ return JSON.stringify({
805
+ folder: folderPath,
806
+ totalItems: items.length,
807
+ items: items.slice(0, 50),
808
+ // Cap at 50 to avoid token explosion
809
+ truncated: items.length > 50
810
+ });
811
+ }
737
812
  case "scan_folder": {
738
813
  const rawPath = args.folderPath;
739
814
  if (!rawPath) return JSON.stringify({ error: "Missing folderPath" });
@@ -950,7 +1025,7 @@ async function scanFolder(config) {
950
1025
  function walk(dir) {
951
1026
  let entries;
952
1027
  try {
953
- entries = readdirSync(dir);
1028
+ entries = readdirSync2(dir);
954
1029
  } catch {
955
1030
  return;
956
1031
  }
@@ -959,7 +1034,7 @@ async function scanFolder(config) {
959
1034
  const fullPath = join3(dir, entry);
960
1035
  let s;
961
1036
  try {
962
- s = statSync(fullPath);
1037
+ s = statSync2(fullPath);
963
1038
  } catch {
964
1039
  continue;
965
1040
  }
@@ -992,11 +1067,11 @@ async function pushSync(config) {
992
1067
  }
993
1068
  async function scanExternalFolder(config, folderPath, cases) {
994
1069
  const absPath = resolve2(folderPath);
995
- if (!existsSync2(absPath)) {
1070
+ if (!existsSync3(absPath)) {
996
1071
  log.error(`Folder not found: ${absPath}`);
997
1072
  return;
998
1073
  }
999
- if (!statSync(absPath).isDirectory()) {
1074
+ if (!statSync2(absPath).isDirectory()) {
1000
1075
  log.error(`Not a directory: ${absPath}`);
1001
1076
  return;
1002
1077
  }
@@ -1005,7 +1080,7 @@ async function scanExternalFolder(config, folderPath, cases) {
1005
1080
  function walk(dir) {
1006
1081
  let entries;
1007
1082
  try {
1008
- entries = readdirSync(dir);
1083
+ entries = readdirSync2(dir);
1009
1084
  } catch {
1010
1085
  return;
1011
1086
  }
@@ -1014,7 +1089,7 @@ async function scanExternalFolder(config, folderPath, cases) {
1014
1089
  const fullPath = join3(dir, entry);
1015
1090
  let s;
1016
1091
  try {
1017
- s = statSync(fullPath);
1092
+ s = statSync2(fullPath);
1018
1093
  } catch {
1019
1094
  continue;
1020
1095
  }
@@ -1185,11 +1260,11 @@ program.command("init").description("Set up AnrakLegal Sync (first-time configur
1185
1260
  );
1186
1261
  const watchFolder = resolve3(watchInput || defaultFolder);
1187
1262
  rl2.close();
1188
- if (!existsSync3(watchFolder)) {
1263
+ if (!existsSync4(watchFolder)) {
1189
1264
  log.warn(
1190
1265
  `Folder ${watchFolder} does not exist \u2014 it will be created when you add files`
1191
1266
  );
1192
- } else if (!statSync2(watchFolder).isDirectory()) {
1267
+ } else if (!statSync3(watchFolder).isDirectory()) {
1193
1268
  log.error(`${watchFolder} is not a directory`);
1194
1269
  process.exit(1);
1195
1270
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anraktech/sync",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "AnrakLegal desktop file sync agent — watches local folders and syncs to case management",
5
5
  "type": "module",
6
6
  "bin": {