@henryqw/pi-add-dir 2.1.6 → 2.2.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/README.md CHANGED
@@ -21,8 +21,8 @@ Use slash commands from the Pi prompt as a human; Pi agents use the tools.
21
21
  | `/dir-add [path]` | command | Add to this session by default; pass `--project` or `--global` before the path to persist for this Git repository's linked worktrees or every workspace. Omit the path to enter it when prompted; supports `~`. |
22
22
  | `/dir-ls` | command | List directories with their scope; select one to remove from that scope. |
23
23
  | `/dir-reload` | command | Reload external directory resources. |
24
- | `add_directory(path)` | tool | Add a directory to this session and return its context and instructions. |
25
- | `search_external_files(pattern, maxResults?)` | tool | Glob-search added directories; returns up to 50 results by default, configurable to 1,000. |
24
+ | `add_directory(path)` | tool | Add a directory to this session and return its context and instructions. Model-only: codemode scripts cannot call it. |
25
+ | `search_external_files(pattern, maxResults?)` | tool | Glob-search added directories; returns up to 50 results by default, configurable to 1,000. Returns a structured result to codemode scripts. |
26
26
  | `@` file autocomplete | ui | Find files in added directories and insert their absolute paths. |
27
27
  | External directory widget | ui | Show added directories and point to `/dir-ls` for management. |
28
28
 
@@ -34,6 +34,12 @@ Added directories give Pi these resources:
34
34
 
35
35
  `/dir-add` reloads only when the discovered external skill paths change, not merely because the directory contains skills. `add_directory` stays session-local; when its result asks for `/reload`, run it to update external skills.
36
36
 
37
+ ## Programmatic use
38
+
39
+ `search_external_files` declares an output schema. Programmatic callers such as Pi `codemode` scripts receive `{ pattern, maxResults, searchedDirectories, totalFound, truncated, directories }`. `directories` lists each directory with at least one match, with its label and absolute file paths. `searchedDirectories` counts only directories actually searched, including those with no matches; directories skipped after reaching the cap are not counted. `truncated` is true when the result cap stopped the search, so later directories or files may hold more matches. The model receives the same matches as text. Calling the tool with no added directories is a tool error, and scripts reject with the message.
40
+
41
+ `add_directory` is declared to the model only (Pi `model-only` exposure). Codemode scripts and other tools cannot call it, so another directory's `AGENTS.md`, `CLAUDE.md`, and skills enter the session only through a model-visible tool call or a user command. This package does not enable codemode.
42
+
37
43
  ## Config
38
44
 
39
45
  Package-owned: `~/.pi/agent/config/pi-add-dir/config.json`
@@ -66,6 +66,20 @@ const SearchExternalFilesParams = Type.Object({
66
66
  ),
67
67
  });
68
68
 
69
+ // Programmatic result of search_external_files. Codemode scripts receive it;
70
+ // the model receives the text listing.
71
+ const SearchExternalFilesOutput = Type.Object({
72
+ pattern: Type.String(),
73
+ maxResults: Type.Integer({ description: "Result cap applied to this call." }),
74
+ searchedDirectories: Type.Integer({ description: "Active external directories searched, in order." }),
75
+ totalFound: Type.Integer(),
76
+ truncated: Type.Boolean({ description: "True when the cap stopped the search; later directories or files may hold more matches." }),
77
+ directories: Type.Array(
78
+ Type.Object({ path: Type.String(), label: Type.String(), files: Type.Array(Type.String()) }),
79
+ { description: "Directories with at least one match, with absolute file paths." },
80
+ ),
81
+ });
82
+
69
83
  interface AddDirectoryDetails {
70
84
  directory: string;
71
85
  hasAgentsMd: boolean;
@@ -625,6 +639,10 @@ export default function addDirExtension(pi: ExtensionAPI, options: ExtensionOpti
625
639
  "After adding, you can read/edit/write files in the external directory using absolute paths.",
626
640
  ],
627
641
  parameters: AddDirectoryParams,
642
+ // Loading another directory's instructions must stay a model-visible call,
643
+ // never a nested call a codemode script can issue and filter.
644
+ exposure: "model-only",
645
+ annotations: { destructiveHint: false, idempotentHint: true, openWorldHint: false },
628
646
 
629
647
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
630
648
  const inputPath = params.path.trim();
@@ -693,6 +711,8 @@ export default function addDirExtension(pi: ExtensionAPI, options: ExtensionOpti
693
711
  "Returns matching file paths with their parent directory labels.",
694
712
  ],
695
713
  parameters: SearchExternalFilesParams,
714
+ outputSchema: SearchExternalFilesOutput,
715
+ annotations: { readOnlyHint: true, openWorldHint: false },
696
716
 
697
717
  async execute(_toolCallId, params, signal) {
698
718
  if (addedDirs.length === 0) {
@@ -701,22 +721,32 @@ export default function addDirExtension(pi: ExtensionAPI, options: ExtensionOpti
701
721
 
702
722
  const pattern = params.pattern.trim();
703
723
  const limit = params.maxResults ?? DEFAULT_MAX_RESULTS;
704
- const results: Array<{ dir: string; label: string; files: string[] }> = [];
724
+ const directories: Array<{ path: string; label: string; files: string[] }> = [];
705
725
  let totalFound = 0;
726
+ let searchedDirectories = 0;
706
727
 
707
728
  for (const dir of addedDirs) {
708
729
  signal?.throwIfAborted();
709
730
  const remaining = limit - totalFound;
710
731
  if (remaining <= 0) break;
711
732
 
733
+ searchedDirectories++;
712
734
  const files = await findFiles(dir.absolutePath, pattern, remaining, signal);
713
735
  if (files.length > 0) {
714
- results.push({ dir: dir.absolutePath, label: dir.label, files });
736
+ directories.push({ path: dir.absolutePath, label: dir.label, files });
715
737
  totalFound += files.length;
716
738
  }
717
739
  }
718
740
 
719
741
  signal?.throwIfAborted();
742
+ const structuredContent = {
743
+ pattern,
744
+ maxResults: limit,
745
+ searchedDirectories,
746
+ totalFound,
747
+ truncated: totalFound >= limit,
748
+ directories,
749
+ };
720
750
  if (totalFound === 0) {
721
751
  return {
722
752
  content: [
@@ -726,19 +756,21 @@ export default function addDirExtension(pi: ExtensionAPI, options: ExtensionOpti
726
756
  },
727
757
  ],
728
758
  details: { totalFound: 0, pattern, dirCount: 0 } satisfies SearchDetails,
759
+ structuredContent,
729
760
  };
730
761
  }
731
762
 
732
763
  const lines = [`Found ${totalFound} file(s) matching "${pattern}":\n`];
733
- for (const result of results) {
734
- lines.push(`📂 ${result.label} (${result.dir}):`);
735
- for (const file of result.files) lines.push(` ${file}`);
764
+ for (const directory of directories) {
765
+ lines.push(`📂 ${directory.label} (${directory.path}):`);
766
+ for (const file of directory.files) lines.push(` ${file}`);
736
767
  lines.push("");
737
768
  }
738
769
 
739
770
  return {
740
771
  content: [{ type: "text", text: lines.join("\n") }],
741
- details: { totalFound, pattern, dirCount: results.length } satisfies SearchDetails,
772
+ details: { totalFound, pattern, dirCount: directories.length } satisfies SearchDetails,
773
+ structuredContent,
742
774
  };
743
775
  },
744
776
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@henryqw/pi-add-dir",
3
- "version": "2.1.6",
3
+ "version": "2.2.0",
4
4
  "description": "Add external directories to a Pi session with context, skills, and file search.",
5
5
  "keywords": [
6
6
  "pi-package",