@henryqw/pi-add-dir 0.1.5 → 0.1.7

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.
@@ -1,4 +1,4 @@
1
- import { closeSync, openSync, readdirSync, readFileSync, readSync, realpathSync, statSync } from "node:fs";
1
+ import { readdirSync, readFileSync, realpathSync, statSync } from "node:fs";
2
2
  import { readdir } from "node:fs/promises";
3
3
  import { homedir } from "node:os";
4
4
  import * as path from "node:path";
@@ -6,15 +6,12 @@ import * as path from "node:path";
6
6
  export interface AddedDir {
7
7
  absolutePath: string;
8
8
  label: string;
9
- addedAt: number;
10
9
  }
11
10
 
12
11
  export interface DirContext {
13
- dir: string;
14
12
  agentsMd: string | null;
15
13
  claudeMd: string | null;
16
- skillPaths: Map<string, string>;
17
- skills: Map<string, string>;
14
+ skills: Set<string>;
18
15
  }
19
16
 
20
17
  const CONTEXT_FILES = ["AGENTS.md", "CLAUDE.md"] as const;
@@ -89,22 +86,11 @@ function skillFiles(dir: string): Array<{ name: string; path: string }> {
89
86
  }
90
87
 
91
88
  export function scanDirContext(dir: string): DirContext {
92
- const ctx: DirContext = {
93
- dir,
89
+ return {
94
90
  agentsMd: readContextFile(dir, "AGENTS.md"),
95
91
  claudeMd: readContextFile(dir, "CLAUDE.md"),
96
- skillPaths: new Map(),
97
- skills: new Map(),
92
+ skills: new Set(skillFiles(dir).map(({ name }) => name)),
98
93
  };
99
-
100
- for (const skill of skillFiles(dir)) {
101
- const content = readFileSafe(skill.path);
102
- if (content === null) continue;
103
- ctx.skillPaths.set(skill.name, skill.path);
104
- ctx.skills.set(skill.name, content);
105
- }
106
-
107
- return ctx;
108
94
  }
109
95
 
110
96
  export function collectSkillPaths(dirs: AddedDir[]): string[] {
@@ -121,36 +107,6 @@ export function collectSkillPaths(dirs: AddedDir[]): string[] {
121
107
  return paths;
122
108
  }
123
109
 
124
- function skillDescription(content: string): string {
125
- const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/)?.[1];
126
- const value = frontmatter?.match(/^description:\s*(.*?)\s*$/m)?.[1];
127
- if (!value || value === ">" || value === "|") return "No description";
128
- return value.replace(/^("|')|("|')$/g, "").trim() || "No description";
129
- }
130
-
131
- // ponytail: 8KB head is enough for SKILL.md frontmatter descriptions; full-body reads stay in scanDirContext
132
- function readHead(filePath: string, bytes = 8192): string | null {
133
- let fd: number | undefined;
134
- try {
135
- fd = openSync(filePath, "r");
136
- const buffer = Buffer.alloc(bytes);
137
- const bytesRead = readSync(fd, buffer, 0, bytes, 0);
138
- return buffer.toString("utf8", 0, bytesRead);
139
- } catch {
140
- return null;
141
- } finally {
142
- if (fd !== undefined) closeSync(fd);
143
- }
144
- }
145
-
146
- // ponytail: 8KB head covers typical frontmatter; falls back to full read only when opener present but closer missing (rare)
147
- function readSkillFrontmatter(filePath: string): string | null {
148
- const head = readHead(filePath, 8192);
149
- if (head === null || !/^---\r?\n/.test(head)) return head;
150
- if (/^---\r?\n[\s\S]*\r?\n---(?:\r?\n|$)/.test(head)) return head;
151
- return readFileSafe(filePath);
152
- }
153
-
154
110
  export function buildContextInjection(dirs: AddedDir[]): string {
155
111
  if (dirs.length === 0) return "";
156
112
 
@@ -159,7 +115,6 @@ export function buildContextInjection(dirs: AddedDir[]): string {
159
115
  `\nThe following ${dirs.length} external director${dirs.length === 1 ? "y is" : "ies are"} included in this session. You can read, edit, and write files in these directories using absolute paths.\n`,
160
116
  ];
161
117
 
162
- const registeredSkills = new Set<string>();
163
118
  for (const dir of dirs) {
164
119
  sections.push(`### ${dir.label} - \`${dir.absolutePath}\``);
165
120
 
@@ -167,16 +122,6 @@ export function buildContextInjection(dirs: AddedDir[]): string {
167
122
  if (agentsMd) sections.push(`\n#### AGENTS.md (from ${dir.label})\n${agentsMd}`);
168
123
  const claudeMd = readContextFile(dir.absolutePath, "CLAUDE.md");
169
124
  if (claudeMd) sections.push(`\n#### CLAUDE.md (from ${dir.label})\n${claudeMd}`);
170
-
171
- const skills = skillFiles(dir.absolutePath).filter((skill) => !registeredSkills.has(skill.name));
172
- if (skills.length > 0) {
173
- sections.push(`\n#### Skills from ${dir.label} (registered as /skill:name commands):`);
174
- for (const skill of skills) {
175
- registeredSkills.add(skill.name);
176
- const description = skillDescription(readSkillFrontmatter(skill.path) ?? "");
177
- sections.push(`- **${skill.name}**: ${description} - use \`/skill:${skill.name}\` or read \`${skill.path}\``);
178
- }
179
- }
180
125
  }
181
126
 
182
127
  return sections.join("\n");
@@ -53,13 +53,7 @@ interface SearchDetails {
53
53
  function isAddedDir(value: unknown): value is AddedDir {
54
54
  if (!value || typeof value !== "object") return false;
55
55
  const dir = value as Partial<AddedDir>;
56
- return (
57
- typeof dir.absolutePath === "string" &&
58
- isAbsolute(dir.absolutePath) &&
59
- typeof dir.label === "string" &&
60
- typeof dir.addedAt === "number" &&
61
- Number.isFinite(dir.addedAt)
62
- );
56
+ return typeof dir.absolutePath === "string" && isAbsolute(dir.absolutePath) && typeof dir.label === "string";
63
57
  }
64
58
 
65
59
  function readState(data: unknown): AddedDir[] {
@@ -91,7 +85,7 @@ function contextDetails(dirCtx: DirContext, absolutePath: string): AddDirectoryD
91
85
  hasAgentsMd: dirCtx.agentsMd !== null,
92
86
  hasClaudeMd: dirCtx.claudeMd !== null,
93
87
  skillCount: dirCtx.skills.size,
94
- skillNames: [...dirCtx.skills.keys()],
88
+ skillNames: [...dirCtx.skills],
95
89
  };
96
90
  }
97
91
 
@@ -183,7 +177,7 @@ export default function addDirExtension(pi: ExtensionAPI): void {
183
177
 
184
178
  const context = scanDirContext(absolutePath);
185
179
  const label = basename(absolutePath) || absolutePath;
186
- addedDirs.push({ absolutePath, label, addedAt: Date.now() });
180
+ addedDirs.push({ absolutePath, label });
187
181
  persistState();
188
182
  updateWidget(ctx);
189
183
 
@@ -287,7 +281,7 @@ export default function addDirExtension(pi: ExtensionAPI): void {
287
281
  response.push(`\nCLAUDE.md instructions from ${absolutePath}:\n${context.claudeMd}`);
288
282
  }
289
283
  if (context.skills.size > 0) {
290
- response.push(`\nDiscovered skills: ${[...context.skills.keys()].join(", ")}`);
284
+ response.push(`\nDiscovered skills: ${[...context.skills].join(", ")}`);
291
285
  response.push("Run /reload to register skills as /skill:name commands.");
292
286
  }
293
287
  response.push(`\nYou can now access files at: ${absolutePath}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@henryqw/pi-add-dir",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Add external directories to a Pi session with context, skills, and file search.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -21,7 +21,7 @@
21
21
  ],
22
22
  "scripts": {
23
23
  "test": "node --test test/*.test.ts",
24
- "typecheck": "tsc --noEmit --allowImportingTsExtensions --target ES2022 --module NodeNext --moduleResolution NodeNext --skipLibCheck extensions/*.ts test/*.test.ts",
24
+ "typecheck": "tsc --noEmit --allowImportingTsExtensions --target ES2022 --module NodeNext --moduleResolution NodeNext --skipLibCheck extensions/*.ts",
25
25
  "pack:check": "npm pack --dry-run"
26
26
  },
27
27
  "peerDependencies": {