@henryqw/pi-add-dir 0.1.11 → 0.1.13

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
@@ -23,7 +23,7 @@ pi install npm:@henryqw/pi-add-dir
23
23
  | `add_directory` | tool | Add a directory. |
24
24
  | `search_external_files` | tool | Glob-search added directories. |
25
25
 
26
- Added directories return root `AGENTS.md`, `CLAUDE.md`, `.pi/AGENTS.md`, and `.pi/CLAUDE.md`, and inject them into later prompts. Skills load from `.pi/skills`, `.agents/skills`, and `.claude/skills`. `/dir-add` reloads when it finds skills; `add_directory` reports when a reload is needed.
26
+ Added directories return root `AGENTS.md`, `CLAUDE.md`, `.pi/AGENTS.md`, and `.pi/CLAUDE.md`, and inject them into later prompts. Skills load from `.pi/skills`, `.agents/skills`, and `.claude/skills`. Their files also appear in the editor's `@` autocomplete with absolute paths. `/dir-add` reloads when it finds skills; `add_directory` reports when a reload is needed.
27
27
 
28
28
  Search uses Node filesystem traversal, skips `.git` and `node_modules`, supports basename and relative-path globs, and caps results at 1,000 per call.
29
29
 
@@ -1,5 +1,11 @@
1
- import { basename, isAbsolute, relative, sep } from "node:path";
2
- import { Text, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
1
+ import { basename, isAbsolute, join, relative, sep } from "node:path";
2
+ import {
3
+ Text,
4
+ truncateToWidth,
5
+ visibleWidth,
6
+ type AutocompleteProvider,
7
+ type AutocompleteSuggestions,
8
+ } from "@earendil-works/pi-tui";
3
9
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
4
10
  import { Type } from "typebox";
5
11
  import {
@@ -85,6 +91,67 @@ function contextSummary(dirCtx: DirContext): string {
85
91
  return found.length > 0 ? ` Found: ${found.join(", ")}.` : " No context files found.";
86
92
  }
87
93
 
94
+ function extractAtToken(textBeforeCursor: string): string | undefined {
95
+ return textBeforeCursor.match(/(?:^|[ \t])(@(?:"[^"]*|[^\s"]*))$/)?.[1];
96
+ }
97
+
98
+ export function createExternalAutocompleteProvider(
99
+ current: AutocompleteProvider,
100
+ getAddedDirs: () => AddedDir[],
101
+ ): AutocompleteProvider {
102
+ return {
103
+ async getSuggestions(lines, cursorLine, cursorCol, options): Promise<AutocompleteSuggestions | null> {
104
+ const line = lines[cursorLine] ?? "";
105
+ const token = extractAtToken(line.slice(0, cursorCol));
106
+ if (!token) return current.getSuggestions(lines, cursorLine, cursorCol, options);
107
+
108
+ const quoted = token.startsWith('@"');
109
+ const query = token.slice(quoted ? 2 : 1);
110
+ if (isAbsolute(query) || query.startsWith("~/") || query.startsWith("./") || query.startsWith("../")) {
111
+ return current.getSuggestions(lines, cursorLine, cursorCol, options);
112
+ }
113
+
114
+ const tokenStart = cursorCol - token.length;
115
+ const sources = await Promise.all([
116
+ current.getSuggestions(lines, cursorLine, cursorCol, options),
117
+ ...getAddedDirs().map(async (dir) => {
118
+ const externalPath = join(dir.absolutePath, query);
119
+ const externalToken = `${quoted || externalPath.includes(" ") ? '@"' : "@"}${externalPath}`;
120
+ const externalLines = [...lines];
121
+ externalLines[cursorLine] = `${line.slice(0, tokenStart)}${externalToken}${line.slice(cursorCol)}`;
122
+ const suggestions = await current.getSuggestions(
123
+ externalLines,
124
+ cursorLine,
125
+ tokenStart + externalToken.length,
126
+ options,
127
+ );
128
+ return suggestions
129
+ ? {
130
+ ...suggestions,
131
+ items: suggestions.items.map((item) => ({
132
+ ...item,
133
+ description: `${dir.label}: ${item.description ?? item.value}`,
134
+ })),
135
+ }
136
+ : null;
137
+ }),
138
+ ]);
139
+
140
+ const itemCount = Math.max(...sources.map((source) => source?.items.length ?? 0));
141
+ const items = Array.from({ length: itemCount }, (_, index) =>
142
+ sources.flatMap((source) => (source?.items[index] ? [source.items[index]] : [])),
143
+ ).flat();
144
+ return items.length > 0 ? { prefix: token, items } : null;
145
+ },
146
+ applyCompletion(lines, cursorLine, cursorCol, item, prefix) {
147
+ return current.applyCompletion(lines, cursorLine, cursorCol, item, prefix);
148
+ },
149
+ shouldTriggerFileCompletion(lines, cursorLine, cursorCol) {
150
+ return current.shouldTriggerFileCompletion?.(lines, cursorLine, cursorCol) ?? true;
151
+ },
152
+ };
153
+ }
154
+
88
155
  export default function addDirExtension(pi: ExtensionAPI): void {
89
156
  let addedDirs: AddedDir[] = [];
90
157
  let currentCwd = "";
@@ -212,6 +279,7 @@ export default function addDirExtension(pi: ExtensionAPI): void {
212
279
 
213
280
  pi.on("session_start", async (_event, ctx) => {
214
281
  reconstructState(ctx);
282
+ ctx.ui.addAutocompleteProvider((current) => createExternalAutocompleteProvider(current, () => addedDirs));
215
283
  });
216
284
  pi.on("session_tree", async (_event, ctx) => {
217
285
  if (reconstructState(ctx)) pi.sendUserMessage("/dir-reload", { expandPromptTemplates: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@henryqw/pi-add-dir",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "Add external directories to a Pi session with context, skills, and file search.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -25,8 +25,8 @@
25
25
  "pack:check": "npm pack --dry-run"
26
26
  },
27
27
  "peerDependencies": {
28
- "@earendil-works/pi-coding-agent": ">=0.84.1",
29
- "@earendil-works/pi-tui": ">=0.84.1",
28
+ "@earendil-works/pi-coding-agent": "^0.84.4",
29
+ "@earendil-works/pi-tui": "^0.84.4",
30
30
  "typebox": "^1.3.15"
31
31
  },
32
32
  "repository": {