@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.
- package/extensions/add-dir-helpers.ts +4 -59
- package/extensions/add-dir.ts +4 -10
- package/package.json +2 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
93
|
-
dir,
|
|
89
|
+
return {
|
|
94
90
|
agentsMd: readContextFile(dir, "AGENTS.md"),
|
|
95
91
|
claudeMd: readContextFile(dir, "CLAUDE.md"),
|
|
96
|
-
|
|
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");
|
package/extensions/add-dir.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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": {
|