@oh-my-pi/pi-coding-agent 4.2.0 → 4.2.2
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/CHANGELOG.md +46 -0
- package/docs/sdk.md +5 -5
- package/examples/sdk/10-settings.ts +2 -2
- package/package.json +5 -5
- package/src/capability/fs.ts +90 -0
- package/src/capability/index.ts +41 -227
- package/src/capability/types.ts +1 -11
- package/src/cli/args.ts +4 -0
- package/src/core/agent-session.ts +7 -7
- package/src/core/agent-storage.ts +50 -0
- package/src/core/auth-storage.ts +102 -3
- package/src/core/bash-executor.ts +1 -1
- package/src/core/custom-tools/loader.ts +2 -2
- package/src/core/export-html/index.ts +1 -33
- package/src/core/extensions/loader.ts +2 -2
- package/src/core/extensions/types.ts +1 -1
- package/src/core/hooks/loader.ts +2 -2
- package/src/core/mcp/config.ts +2 -2
- package/src/core/model-registry.ts +46 -0
- package/src/core/sdk.ts +37 -29
- package/src/core/settings-manager.ts +152 -135
- package/src/core/skills.ts +72 -51
- package/src/core/slash-commands.ts +3 -3
- package/src/core/system-prompt.ts +52 -10
- package/src/core/tools/complete.ts +5 -2
- package/src/core/tools/edit.ts +7 -4
- package/src/core/tools/index.test.ts +16 -0
- package/src/core/tools/index.ts +21 -8
- package/src/core/tools/lsp/index.ts +4 -1
- package/src/core/tools/ssh.ts +6 -6
- package/src/core/tools/task/commands.ts +3 -9
- package/src/core/tools/task/executor.ts +88 -3
- package/src/core/tools/task/index.ts +4 -0
- package/src/core/tools/task/model-resolver.ts +10 -7
- package/src/core/tools/task/worker-protocol.ts +48 -2
- package/src/core/tools/task/worker.ts +152 -7
- package/src/core/tools/write.ts +7 -4
- package/src/discovery/agents-md.ts +13 -19
- package/src/discovery/builtin.ts +368 -293
- package/src/discovery/claude.ts +183 -345
- package/src/discovery/cline.ts +30 -10
- package/src/discovery/codex.ts +188 -272
- package/src/discovery/cursor.ts +106 -121
- package/src/discovery/gemini.ts +72 -97
- package/src/discovery/github.ts +7 -10
- package/src/discovery/helpers.ts +114 -57
- package/src/discovery/index.ts +1 -2
- package/src/discovery/mcp-json.ts +15 -18
- package/src/discovery/ssh.ts +9 -17
- package/src/discovery/vscode.ts +10 -5
- package/src/discovery/windsurf.ts +52 -86
- package/src/main.ts +5 -1
- package/src/modes/interactive/components/extensions/extension-dashboard.ts +24 -11
- package/src/modes/interactive/components/extensions/state-manager.ts +19 -15
- package/src/modes/interactive/controllers/selector-controller.ts +9 -5
- package/src/modes/interactive/interactive-mode.ts +22 -15
- package/src/prompts/agents/plan.md +107 -30
- package/src/prompts/agents/task.md +5 -4
- package/src/prompts/system/system-prompt.md +5 -0
- package/src/prompts/tools/task.md +25 -19
- package/src/utils/shell.ts +2 -2
- package/src/prompts/agents/architect-plan.md +0 -10
- package/src/prompts/agents/implement-with-critic.md +0 -11
- package/src/prompts/agents/implement.md +0 -11
package/src/discovery/claude.ts
CHANGED
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
* Priority: 80 (tool-specific, below builtin but above shared standards)
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { join, sep } from "node:path";
|
|
9
9
|
import { type ContextFile, contextFileCapability } from "../capability/context-file";
|
|
10
10
|
import { type ExtensionModule, extensionModuleCapability } from "../capability/extension-module";
|
|
11
|
+
import { readFile } from "../capability/fs";
|
|
11
12
|
import { type Hook, hookCapability } from "../capability/hook";
|
|
12
13
|
import { registerProvider } from "../capability/index";
|
|
13
14
|
import { type MCPServer, mcpCapability } from "../capability/mcp";
|
|
@@ -24,7 +25,7 @@ import {
|
|
|
24
25
|
expandEnvVarsDeep,
|
|
25
26
|
getExtensionNameFromPath,
|
|
26
27
|
loadFilesFromDir,
|
|
27
|
-
|
|
28
|
+
loadSkillsFromDir,
|
|
28
29
|
parseJSON,
|
|
29
30
|
} from "./helpers";
|
|
30
31
|
|
|
@@ -41,45 +42,49 @@ function getUserClaude(ctx: LoadContext): string {
|
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
/**
|
|
44
|
-
* Get project-level .claude path (
|
|
45
|
+
* Get project-level .claude path (cwd only).
|
|
45
46
|
*/
|
|
46
|
-
function getProjectClaude(ctx: LoadContext): string
|
|
47
|
-
return ctx.
|
|
47
|
+
function getProjectClaude(ctx: LoadContext): string {
|
|
48
|
+
return join(ctx.cwd, CONFIG_DIR);
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
// =============================================================================
|
|
51
52
|
// MCP Servers
|
|
52
53
|
// =============================================================================
|
|
53
54
|
|
|
54
|
-
function loadMCPServers(ctx: LoadContext): LoadResult<MCPServer
|
|
55
|
+
async function loadMCPServers(ctx: LoadContext): Promise<LoadResult<MCPServer>> {
|
|
55
56
|
const items: MCPServer[] = [];
|
|
56
57
|
const warnings: string[] = [];
|
|
57
58
|
|
|
58
|
-
// User-level: ~/.claude.json or ~/.claude/mcp.json
|
|
59
59
|
const userBase = getUserClaude(ctx);
|
|
60
60
|
const userClaudeJson = join(ctx.home, ".claude.json");
|
|
61
61
|
const userMcpJson = join(userBase, "mcp.json");
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
] as const) {
|
|
67
|
-
if (!ctx.fs.isFile(path)) continue;
|
|
63
|
+
const projectBase = join(ctx.cwd, CONFIG_DIR);
|
|
64
|
+
const projectMcpJson = join(projectBase, ".mcp.json");
|
|
65
|
+
const projectMcpJsonAlt = join(projectBase, "mcp.json");
|
|
68
66
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
67
|
+
const userPaths = [
|
|
68
|
+
{ path: userClaudeJson, level: "user" as const },
|
|
69
|
+
{ path: userMcpJson, level: "user" as const },
|
|
70
|
+
];
|
|
71
|
+
const projectPaths = [
|
|
72
|
+
{ path: projectMcpJson, level: "project" as const },
|
|
73
|
+
{ path: projectMcpJsonAlt, level: "project" as const },
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
const allPaths = [...userPaths, ...projectPaths];
|
|
77
|
+
const contents = await Promise.all(allPaths.map(({ path }) => readFile(path)));
|
|
74
78
|
|
|
79
|
+
const parseMcpServers = (content: string | null, path: string, level: "user" | "project"): MCPServer[] => {
|
|
80
|
+
if (!content) return [];
|
|
75
81
|
const json = parseJSON<{ mcpServers?: Record<string, unknown> }>(content);
|
|
76
|
-
if (!json?.mcpServers)
|
|
82
|
+
if (!json?.mcpServers) return [];
|
|
77
83
|
|
|
78
84
|
const mcpServers = expandEnvVarsDeep(json.mcpServers);
|
|
79
|
-
|
|
80
|
-
for (const [name, config] of Object.entries(mcpServers)) {
|
|
85
|
+
return Object.entries(mcpServers).map(([name, config]) => {
|
|
81
86
|
const serverConfig = config as Record<string, unknown>;
|
|
82
|
-
|
|
87
|
+
return {
|
|
83
88
|
name,
|
|
84
89
|
command: serverConfig.command as string | undefined,
|
|
85
90
|
args: serverConfig.args as string[] | undefined,
|
|
@@ -88,45 +93,24 @@ function loadMCPServers(ctx: LoadContext): LoadResult<MCPServer> {
|
|
|
88
93
|
headers: serverConfig.headers as Record<string, string> | undefined,
|
|
89
94
|
transport: serverConfig.type as "stdio" | "sse" | "http" | undefined,
|
|
90
95
|
_source: createSourceMeta(PROVIDER_ID, path, level),
|
|
91
|
-
}
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
for (let i = 0; i < userPaths.length; i++) {
|
|
101
|
+
const servers = parseMcpServers(contents[i], userPaths[i].path, userPaths[i].level);
|
|
102
|
+
if (servers.length > 0) {
|
|
103
|
+
items.push(...servers);
|
|
104
|
+
break;
|
|
92
105
|
}
|
|
93
|
-
break; // First existing file wins
|
|
94
106
|
}
|
|
95
107
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
for (const path of [projectMcpJson, projectMcpJsonAlt]) {
|
|
103
|
-
if (!ctx.fs.isFile(path)) continue;
|
|
104
|
-
|
|
105
|
-
const content = ctx.fs.readFile(path);
|
|
106
|
-
if (!content) {
|
|
107
|
-
warnings.push(`Failed to read ${path}`);
|
|
108
|
-
continue;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const json = parseJSON<{ mcpServers?: Record<string, unknown> }>(content);
|
|
112
|
-
if (!json?.mcpServers) continue;
|
|
113
|
-
|
|
114
|
-
const mcpServers = expandEnvVarsDeep(json.mcpServers);
|
|
115
|
-
|
|
116
|
-
for (const [name, config] of Object.entries(mcpServers)) {
|
|
117
|
-
const serverConfig = config as Record<string, unknown>;
|
|
118
|
-
items.push({
|
|
119
|
-
name,
|
|
120
|
-
command: serverConfig.command as string | undefined,
|
|
121
|
-
args: serverConfig.args as string[] | undefined,
|
|
122
|
-
env: serverConfig.env as Record<string, string> | undefined,
|
|
123
|
-
url: serverConfig.url as string | undefined,
|
|
124
|
-
headers: serverConfig.headers as Record<string, string> | undefined,
|
|
125
|
-
transport: serverConfig.type as "stdio" | "sse" | "http" | undefined,
|
|
126
|
-
_source: createSourceMeta(PROVIDER_ID, path, "project"),
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
break; // First existing file wins
|
|
108
|
+
const projectOffset = userPaths.length;
|
|
109
|
+
for (let i = 0; i < projectPaths.length; i++) {
|
|
110
|
+
const servers = parseMcpServers(contents[projectOffset + i], projectPaths[i].path, projectPaths[i].level);
|
|
111
|
+
if (servers.length > 0) {
|
|
112
|
+
items.push(...servers);
|
|
113
|
+
break;
|
|
130
114
|
}
|
|
131
115
|
}
|
|
132
116
|
|
|
@@ -137,74 +121,35 @@ function loadMCPServers(ctx: LoadContext): LoadResult<MCPServer> {
|
|
|
137
121
|
// Context Files (CLAUDE.md)
|
|
138
122
|
// =============================================================================
|
|
139
123
|
|
|
140
|
-
function loadContextFiles(ctx: LoadContext): LoadResult<ContextFile
|
|
124
|
+
async function loadContextFiles(ctx: LoadContext): Promise<LoadResult<ContextFile>> {
|
|
141
125
|
const items: ContextFile[] = [];
|
|
142
126
|
const warnings: string[] = [];
|
|
143
127
|
|
|
144
|
-
// User-level: ~/.claude/CLAUDE.md
|
|
145
128
|
const userBase = getUserClaude(ctx);
|
|
146
129
|
const userClaudeMd = join(userBase, "CLAUDE.md");
|
|
147
130
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
});
|
|
157
|
-
} else {
|
|
158
|
-
warnings.push(`Failed to read ${userClaudeMd}`);
|
|
159
|
-
}
|
|
131
|
+
const userContent = await readFile(userClaudeMd);
|
|
132
|
+
if (userContent !== null) {
|
|
133
|
+
items.push({
|
|
134
|
+
path: userClaudeMd,
|
|
135
|
+
content: userContent,
|
|
136
|
+
level: "user",
|
|
137
|
+
_source: createSourceMeta(PROVIDER_ID, userClaudeMd, "user"),
|
|
138
|
+
});
|
|
160
139
|
}
|
|
161
140
|
|
|
162
|
-
// Project-level: walk up looking for .claude/CLAUDE.md
|
|
163
141
|
const projectBase = getProjectClaude(ctx);
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
content,
|
|
176
|
-
level: "project",
|
|
177
|
-
depth,
|
|
178
|
-
_source: createSourceMeta(PROVIDER_ID, projectClaudeMd, "project"),
|
|
179
|
-
});
|
|
180
|
-
} else {
|
|
181
|
-
warnings.push(`Failed to read ${projectClaudeMd}`);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Also check for CLAUDE.md in project root (without .claude directory)
|
|
187
|
-
const rootClaudeMd = ctx.fs.walkUp("CLAUDE.md", { file: true });
|
|
188
|
-
if (rootClaudeMd) {
|
|
189
|
-
const content = ctx.fs.readFile(rootClaudeMd);
|
|
190
|
-
if (content !== null) {
|
|
191
|
-
// Only add if not already added from .claude/CLAUDE.md
|
|
192
|
-
const alreadyAdded = items.some((item) => item.path === rootClaudeMd);
|
|
193
|
-
if (!alreadyAdded) {
|
|
194
|
-
const fileDir = dirname(rootClaudeMd);
|
|
195
|
-
const depth = calculateDepth(ctx.cwd, fileDir, sep);
|
|
196
|
-
|
|
197
|
-
items.push({
|
|
198
|
-
path: rootClaudeMd,
|
|
199
|
-
content,
|
|
200
|
-
level: "project",
|
|
201
|
-
depth,
|
|
202
|
-
_source: createSourceMeta(PROVIDER_ID, rootClaudeMd, "project"),
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
|
-
} else {
|
|
206
|
-
warnings.push(`Failed to read ${rootClaudeMd}`);
|
|
207
|
-
}
|
|
142
|
+
const projectClaudeMd = join(projectBase, "CLAUDE.md");
|
|
143
|
+
const projectContent = await readFile(projectClaudeMd);
|
|
144
|
+
if (projectContent !== null) {
|
|
145
|
+
const depth = calculateDepth(ctx.cwd, projectBase, sep);
|
|
146
|
+
items.push({
|
|
147
|
+
path: projectClaudeMd,
|
|
148
|
+
content: projectContent,
|
|
149
|
+
level: "project",
|
|
150
|
+
depth,
|
|
151
|
+
_source: createSourceMeta(PROVIDER_ID, projectClaudeMd, "project"),
|
|
152
|
+
});
|
|
208
153
|
}
|
|
209
154
|
|
|
210
155
|
return { items, warnings };
|
|
@@ -214,115 +159,52 @@ function loadContextFiles(ctx: LoadContext): LoadResult<ContextFile> {
|
|
|
214
159
|
// Skills
|
|
215
160
|
// =============================================================================
|
|
216
161
|
|
|
217
|
-
function loadSkills(ctx: LoadContext): LoadResult<Skill
|
|
218
|
-
const
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
// User-level: ~/.claude/skills/*/SKILL.md
|
|
222
|
-
const userBase = getUserClaude(ctx);
|
|
223
|
-
const userSkillsDir = join(userBase, "skills");
|
|
224
|
-
|
|
225
|
-
if (ctx.fs.isDir(userSkillsDir)) {
|
|
226
|
-
const skillDirs = ctx.fs.readDir(userSkillsDir);
|
|
227
|
-
|
|
228
|
-
for (const dirName of skillDirs) {
|
|
229
|
-
if (dirName.startsWith(".")) continue;
|
|
230
|
-
|
|
231
|
-
const skillDir = join(userSkillsDir, dirName);
|
|
232
|
-
if (!ctx.fs.isDir(skillDir)) continue;
|
|
233
|
-
|
|
234
|
-
const skillFile = join(skillDir, "SKILL.md");
|
|
235
|
-
if (!ctx.fs.isFile(skillFile)) continue;
|
|
236
|
-
|
|
237
|
-
const content = ctx.fs.readFile(skillFile);
|
|
238
|
-
if (!content) {
|
|
239
|
-
warnings.push(`Failed to read ${skillFile}`);
|
|
240
|
-
continue;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
const { frontmatter, body } = parseFrontmatter(content);
|
|
244
|
-
const name = (frontmatter.name as string) || dirName;
|
|
245
|
-
|
|
246
|
-
items.push({
|
|
247
|
-
name,
|
|
248
|
-
path: skillFile,
|
|
249
|
-
content: body,
|
|
250
|
-
frontmatter,
|
|
251
|
-
level: "user",
|
|
252
|
-
_source: createSourceMeta(PROVIDER_ID, skillFile, "user"),
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
}
|
|
162
|
+
async function loadSkills(ctx: LoadContext): Promise<LoadResult<Skill>> {
|
|
163
|
+
const userSkillsDir = join(getUserClaude(ctx), "skills");
|
|
164
|
+
const projectSkillsDir = join(getProjectClaude(ctx), "skills");
|
|
256
165
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
if (ctx.fs.isDir(projectSkillsDir)) {
|
|
263
|
-
const skillDirs = ctx.fs.readDir(projectSkillsDir);
|
|
264
|
-
|
|
265
|
-
for (const dirName of skillDirs) {
|
|
266
|
-
if (dirName.startsWith(".")) continue;
|
|
267
|
-
|
|
268
|
-
const skillDir = join(projectSkillsDir, dirName);
|
|
269
|
-
if (!ctx.fs.isDir(skillDir)) continue;
|
|
270
|
-
|
|
271
|
-
const skillFile = join(skillDir, "SKILL.md");
|
|
272
|
-
if (!ctx.fs.isFile(skillFile)) continue;
|
|
273
|
-
|
|
274
|
-
const content = ctx.fs.readFile(skillFile);
|
|
275
|
-
if (!content) {
|
|
276
|
-
warnings.push(`Failed to read ${skillFile}`);
|
|
277
|
-
continue;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
const { frontmatter, body } = parseFrontmatter(content);
|
|
281
|
-
const name = (frontmatter.name as string) || dirName;
|
|
282
|
-
|
|
283
|
-
items.push({
|
|
284
|
-
name,
|
|
285
|
-
path: skillFile,
|
|
286
|
-
content: body,
|
|
287
|
-
frontmatter,
|
|
288
|
-
level: "project",
|
|
289
|
-
_source: createSourceMeta(PROVIDER_ID, skillFile, "project"),
|
|
290
|
-
});
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
}
|
|
166
|
+
const results = await Promise.all([
|
|
167
|
+
loadSkillsFromDir(ctx, { dir: userSkillsDir, providerId: PROVIDER_ID, level: "user" }),
|
|
168
|
+
loadSkillsFromDir(ctx, { dir: projectSkillsDir, providerId: PROVIDER_ID, level: "project" }),
|
|
169
|
+
]);
|
|
294
170
|
|
|
295
|
-
return {
|
|
171
|
+
return {
|
|
172
|
+
items: results.flatMap((r) => r.items),
|
|
173
|
+
warnings: results.flatMap((r) => r.warnings ?? []),
|
|
174
|
+
};
|
|
296
175
|
}
|
|
297
176
|
|
|
298
177
|
// =============================================================================
|
|
299
178
|
// Extension Modules
|
|
300
179
|
// =============================================================================
|
|
301
180
|
|
|
302
|
-
function loadExtensionModules(ctx: LoadContext): LoadResult<ExtensionModule
|
|
181
|
+
async function loadExtensionModules(ctx: LoadContext): Promise<LoadResult<ExtensionModule>> {
|
|
303
182
|
const items: ExtensionModule[] = [];
|
|
304
183
|
const warnings: string[] = [];
|
|
305
184
|
|
|
306
185
|
const userBase = getUserClaude(ctx);
|
|
307
186
|
const userExtensionsDir = join(userBase, "extensions");
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
187
|
+
const projectExtensionsDir = join(ctx.cwd, CONFIG_DIR, "extensions");
|
|
188
|
+
|
|
189
|
+
const dirsToDiscover: { dir: string; level: "user" | "project" }[] = [
|
|
190
|
+
{ dir: userExtensionsDir, level: "user" },
|
|
191
|
+
{ dir: projectExtensionsDir, level: "project" },
|
|
192
|
+
];
|
|
193
|
+
|
|
194
|
+
const pathsByLevel = await Promise.all(
|
|
195
|
+
dirsToDiscover.map(async ({ dir, level }) => {
|
|
196
|
+
const paths = await discoverExtensionModulePaths(ctx, dir);
|
|
197
|
+
return paths.map((extPath) => ({ extPath, level }));
|
|
198
|
+
}),
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
for (const extensions of pathsByLevel) {
|
|
202
|
+
for (const { extPath, level } of extensions) {
|
|
321
203
|
items.push({
|
|
322
204
|
name: getExtensionNameFromPath(extPath),
|
|
323
205
|
path: extPath,
|
|
324
|
-
level
|
|
325
|
-
_source: createSourceMeta(PROVIDER_ID, extPath,
|
|
206
|
+
level,
|
|
207
|
+
_source: createSourceMeta(PROVIDER_ID, extPath, level),
|
|
326
208
|
});
|
|
327
209
|
}
|
|
328
210
|
}
|
|
@@ -334,15 +216,14 @@ function loadExtensionModules(ctx: LoadContext): LoadResult<ExtensionModule> {
|
|
|
334
216
|
// Slash Commands
|
|
335
217
|
// =============================================================================
|
|
336
218
|
|
|
337
|
-
function loadSlashCommands(ctx: LoadContext): LoadResult<SlashCommand
|
|
219
|
+
async function loadSlashCommands(ctx: LoadContext): Promise<LoadResult<SlashCommand>> {
|
|
338
220
|
const items: SlashCommand[] = [];
|
|
339
221
|
const warnings: string[] = [];
|
|
340
222
|
|
|
341
|
-
// User-level: ~/.claude/commands/*.md
|
|
342
223
|
const userBase = getUserClaude(ctx);
|
|
343
224
|
const userCommandsDir = join(userBase, "commands");
|
|
344
225
|
|
|
345
|
-
const userResult = loadFilesFromDir<SlashCommand>(ctx, userCommandsDir, PROVIDER_ID, "user", {
|
|
226
|
+
const userResult = await loadFilesFromDir<SlashCommand>(ctx, userCommandsDir, PROVIDER_ID, "user", {
|
|
346
227
|
extensions: ["md"],
|
|
347
228
|
transform: (name, content, path, source) => {
|
|
348
229
|
const cmdName = name.replace(/\.md$/, "");
|
|
@@ -359,28 +240,24 @@ function loadSlashCommands(ctx: LoadContext): LoadResult<SlashCommand> {
|
|
|
359
240
|
items.push(...userResult.items);
|
|
360
241
|
if (userResult.warnings) warnings.push(...userResult.warnings);
|
|
361
242
|
|
|
362
|
-
|
|
363
|
-
const projectBase = getProjectClaude(ctx);
|
|
364
|
-
if (projectBase) {
|
|
365
|
-
const projectCommandsDir = join(projectBase, "commands");
|
|
366
|
-
|
|
367
|
-
const projectResult = loadFilesFromDir<SlashCommand>(ctx, projectCommandsDir, PROVIDER_ID, "project", {
|
|
368
|
-
extensions: ["md"],
|
|
369
|
-
transform: (name, content, path, source) => {
|
|
370
|
-
const cmdName = name.replace(/\.md$/, "");
|
|
371
|
-
return {
|
|
372
|
-
name: cmdName,
|
|
373
|
-
path,
|
|
374
|
-
content,
|
|
375
|
-
level: "project",
|
|
376
|
-
_source: source,
|
|
377
|
-
};
|
|
378
|
-
},
|
|
379
|
-
});
|
|
243
|
+
const projectCommandsDir = join(ctx.cwd, CONFIG_DIR, "commands");
|
|
380
244
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
245
|
+
const projectResult = await loadFilesFromDir<SlashCommand>(ctx, projectCommandsDir, PROVIDER_ID, "project", {
|
|
246
|
+
extensions: ["md"],
|
|
247
|
+
transform: (name, content, path, source) => {
|
|
248
|
+
const cmdName = name.replace(/\.md$/, "");
|
|
249
|
+
return {
|
|
250
|
+
name: cmdName,
|
|
251
|
+
path,
|
|
252
|
+
content,
|
|
253
|
+
level: "project",
|
|
254
|
+
_source: source,
|
|
255
|
+
};
|
|
256
|
+
},
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
items.push(...projectResult.items);
|
|
260
|
+
if (projectResult.warnings) warnings.push(...projectResult.warnings);
|
|
384
261
|
|
|
385
262
|
return { items, warnings };
|
|
386
263
|
}
|
|
@@ -389,63 +266,46 @@ function loadSlashCommands(ctx: LoadContext): LoadResult<SlashCommand> {
|
|
|
389
266
|
// Hooks
|
|
390
267
|
// =============================================================================
|
|
391
268
|
|
|
392
|
-
function loadHooks(ctx: LoadContext): LoadResult<Hook
|
|
269
|
+
async function loadHooks(ctx: LoadContext): Promise<LoadResult<Hook>> {
|
|
393
270
|
const items: Hook[] = [];
|
|
394
271
|
const warnings: string[] = [];
|
|
395
272
|
|
|
396
|
-
// User-level: ~/.claude/hooks/pre/* and ~/.claude/hooks/post/*
|
|
397
273
|
const userBase = getUserClaude(ctx);
|
|
398
274
|
const userHooksDir = join(userBase, "hooks");
|
|
275
|
+
const projectBase = getProjectClaude(ctx);
|
|
276
|
+
const projectHooksDir = join(projectBase, "hooks");
|
|
399
277
|
|
|
400
|
-
|
|
401
|
-
const hooksTypeDir = join(userHooksDir, hookType);
|
|
402
|
-
|
|
403
|
-
const result = loadFilesFromDir<Hook>(ctx, hooksTypeDir, PROVIDER_ID, "user", {
|
|
404
|
-
transform: (name, _content, path, source) => {
|
|
405
|
-
// Extract tool name from filename (e.g., "bash.sh" -> "bash", "*.sh" -> "*")
|
|
406
|
-
const toolName = name.replace(/\.(sh|bash|zsh|fish)$/, "");
|
|
407
|
-
|
|
408
|
-
return {
|
|
409
|
-
name,
|
|
410
|
-
path,
|
|
411
|
-
type: hookType,
|
|
412
|
-
tool: toolName,
|
|
413
|
-
level: "user",
|
|
414
|
-
_source: source,
|
|
415
|
-
};
|
|
416
|
-
},
|
|
417
|
-
});
|
|
278
|
+
const hookTypes = ["pre", "post"] as const;
|
|
418
279
|
|
|
419
|
-
|
|
420
|
-
|
|
280
|
+
const loadTasks: { dir: string; hookType: "pre" | "post"; level: "user" | "project" }[] = [];
|
|
281
|
+
for (const hookType of hookTypes) {
|
|
282
|
+
loadTasks.push({ dir: join(userHooksDir, hookType), hookType, level: "user" });
|
|
283
|
+
}
|
|
284
|
+
for (const hookType of hookTypes) {
|
|
285
|
+
loadTasks.push({ dir: join(projectHooksDir, hookType), hookType, level: "project" });
|
|
421
286
|
}
|
|
422
287
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
const projectHooksDir = join(projectBase, "hooks");
|
|
427
|
-
|
|
428
|
-
for (const hookType of ["pre", "post"] as const) {
|
|
429
|
-
const hooksTypeDir = join(projectHooksDir, hookType);
|
|
430
|
-
|
|
431
|
-
const result = loadFilesFromDir<Hook>(ctx, hooksTypeDir, PROVIDER_ID, "project", {
|
|
288
|
+
const results = await Promise.all(
|
|
289
|
+
loadTasks.map(({ dir, hookType, level }) =>
|
|
290
|
+
loadFilesFromDir<Hook>(ctx, dir, PROVIDER_ID, level, {
|
|
432
291
|
transform: (name, _content, path, source) => {
|
|
433
292
|
const toolName = name.replace(/\.(sh|bash|zsh|fish)$/, "");
|
|
434
|
-
|
|
435
293
|
return {
|
|
436
294
|
name,
|
|
437
295
|
path,
|
|
438
296
|
type: hookType,
|
|
439
297
|
tool: toolName,
|
|
440
|
-
level
|
|
298
|
+
level,
|
|
441
299
|
_source: source,
|
|
442
300
|
};
|
|
443
301
|
},
|
|
444
|
-
})
|
|
302
|
+
}),
|
|
303
|
+
),
|
|
304
|
+
);
|
|
445
305
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
306
|
+
for (const result of results) {
|
|
307
|
+
items.push(...result.items);
|
|
308
|
+
if (result.warnings) warnings.push(...result.warnings);
|
|
449
309
|
}
|
|
450
310
|
|
|
451
311
|
return { items, warnings };
|
|
@@ -455,15 +315,14 @@ function loadHooks(ctx: LoadContext): LoadResult<Hook> {
|
|
|
455
315
|
// Custom Tools
|
|
456
316
|
// =============================================================================
|
|
457
317
|
|
|
458
|
-
function loadTools(ctx: LoadContext): LoadResult<CustomTool
|
|
318
|
+
async function loadTools(ctx: LoadContext): Promise<LoadResult<CustomTool>> {
|
|
459
319
|
const items: CustomTool[] = [];
|
|
460
320
|
const warnings: string[] = [];
|
|
461
321
|
|
|
462
|
-
// User-level: ~/.claude/tools/*
|
|
463
322
|
const userBase = getUserClaude(ctx);
|
|
464
323
|
const userToolsDir = join(userBase, "tools");
|
|
465
324
|
|
|
466
|
-
const userResult = loadFilesFromDir<CustomTool>(ctx, userToolsDir, PROVIDER_ID, "user", {
|
|
325
|
+
const userResult = await loadFilesFromDir<CustomTool>(ctx, userToolsDir, PROVIDER_ID, "user", {
|
|
467
326
|
transform: (name, _content, path, source) => {
|
|
468
327
|
const toolName = name.replace(/\.(ts|js|sh|bash|py)$/, "");
|
|
469
328
|
|
|
@@ -479,27 +338,24 @@ function loadTools(ctx: LoadContext): LoadResult<CustomTool> {
|
|
|
479
338
|
items.push(...userResult.items);
|
|
480
339
|
if (userResult.warnings) warnings.push(...userResult.warnings);
|
|
481
340
|
|
|
482
|
-
// Project-level: <project>/.claude/tools/*
|
|
483
341
|
const projectBase = getProjectClaude(ctx);
|
|
484
|
-
|
|
485
|
-
const projectToolsDir = join(projectBase, "tools");
|
|
486
|
-
|
|
487
|
-
const projectResult = loadFilesFromDir<CustomTool>(ctx, projectToolsDir, PROVIDER_ID, "project", {
|
|
488
|
-
transform: (name, _content, path, source) => {
|
|
489
|
-
const toolName = name.replace(/\.(ts|js|sh|bash|py)$/, "");
|
|
490
|
-
|
|
491
|
-
return {
|
|
492
|
-
name: toolName,
|
|
493
|
-
path,
|
|
494
|
-
level: "project",
|
|
495
|
-
_source: source,
|
|
496
|
-
};
|
|
497
|
-
},
|
|
498
|
-
});
|
|
342
|
+
const projectToolsDir = join(projectBase, "tools");
|
|
499
343
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
344
|
+
const projectResult = await loadFilesFromDir<CustomTool>(ctx, projectToolsDir, PROVIDER_ID, "project", {
|
|
345
|
+
transform: (name, _content, path, source) => {
|
|
346
|
+
const toolName = name.replace(/\.(ts|js|sh|bash|py)$/, "");
|
|
347
|
+
|
|
348
|
+
return {
|
|
349
|
+
name: toolName,
|
|
350
|
+
path,
|
|
351
|
+
level: "project",
|
|
352
|
+
_source: source,
|
|
353
|
+
};
|
|
354
|
+
},
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
items.push(...projectResult.items);
|
|
358
|
+
if (projectResult.warnings) warnings.push(...projectResult.warnings);
|
|
503
359
|
|
|
504
360
|
return { items, warnings };
|
|
505
361
|
}
|
|
@@ -508,26 +364,21 @@ function loadTools(ctx: LoadContext): LoadResult<CustomTool> {
|
|
|
508
364
|
// System Prompts
|
|
509
365
|
// =============================================================================
|
|
510
366
|
|
|
511
|
-
function loadSystemPrompts(ctx: LoadContext): LoadResult<SystemPrompt
|
|
367
|
+
async function loadSystemPrompts(ctx: LoadContext): Promise<LoadResult<SystemPrompt>> {
|
|
512
368
|
const items: SystemPrompt[] = [];
|
|
513
369
|
const warnings: string[] = [];
|
|
514
370
|
|
|
515
|
-
// User-level: ~/.claude/SYSTEM.md
|
|
516
371
|
const userBase = getUserClaude(ctx);
|
|
517
372
|
const userSystemMd = join(userBase, "SYSTEM.md");
|
|
518
373
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
});
|
|
528
|
-
} else {
|
|
529
|
-
warnings.push(`Failed to read ${userSystemMd}`);
|
|
530
|
-
}
|
|
374
|
+
const content = await readFile(userSystemMd);
|
|
375
|
+
if (content !== null) {
|
|
376
|
+
items.push({
|
|
377
|
+
path: userSystemMd,
|
|
378
|
+
content,
|
|
379
|
+
level: "user",
|
|
380
|
+
_source: createSourceMeta(PROVIDER_ID, userSystemMd, "user"),
|
|
381
|
+
});
|
|
531
382
|
}
|
|
532
383
|
|
|
533
384
|
return { items, warnings };
|
|
@@ -537,55 +388,42 @@ function loadSystemPrompts(ctx: LoadContext): LoadResult<SystemPrompt> {
|
|
|
537
388
|
// Settings
|
|
538
389
|
// =============================================================================
|
|
539
390
|
|
|
540
|
-
function loadSettings(ctx: LoadContext): LoadResult<Settings
|
|
391
|
+
async function loadSettings(ctx: LoadContext): Promise<LoadResult<Settings>> {
|
|
541
392
|
const items: Settings[] = [];
|
|
542
393
|
const warnings: string[] = [];
|
|
543
394
|
|
|
544
|
-
// User-level: ~/.claude/settings.json
|
|
545
395
|
const userBase = getUserClaude(ctx);
|
|
546
396
|
const userSettingsJson = join(userBase, "settings.json");
|
|
547
397
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
});
|
|
559
|
-
} else {
|
|
560
|
-
warnings.push(`Failed to parse JSON in ${userSettingsJson}`);
|
|
561
|
-
}
|
|
398
|
+
const userContent = await readFile(userSettingsJson);
|
|
399
|
+
if (userContent) {
|
|
400
|
+
const data = parseJSON<Record<string, unknown>>(userContent);
|
|
401
|
+
if (data) {
|
|
402
|
+
items.push({
|
|
403
|
+
path: userSettingsJson,
|
|
404
|
+
data,
|
|
405
|
+
level: "user",
|
|
406
|
+
_source: createSourceMeta(PROVIDER_ID, userSettingsJson, "user"),
|
|
407
|
+
});
|
|
562
408
|
} else {
|
|
563
|
-
warnings.push(`Failed to
|
|
409
|
+
warnings.push(`Failed to parse JSON in ${userSettingsJson}`);
|
|
564
410
|
}
|
|
565
411
|
}
|
|
566
412
|
|
|
567
|
-
// Project-level: <project>/.claude/settings.json
|
|
568
413
|
const projectBase = getProjectClaude(ctx);
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
});
|
|
583
|
-
} else {
|
|
584
|
-
warnings.push(`Failed to parse JSON in ${projectSettingsJson}`);
|
|
585
|
-
}
|
|
586
|
-
} else {
|
|
587
|
-
warnings.push(`Failed to read ${projectSettingsJson}`);
|
|
588
|
-
}
|
|
414
|
+
const projectSettingsJson = join(projectBase, "settings.json");
|
|
415
|
+
const projectContent = await readFile(projectSettingsJson);
|
|
416
|
+
if (projectContent) {
|
|
417
|
+
const data = parseJSON<Record<string, unknown>>(projectContent);
|
|
418
|
+
if (data) {
|
|
419
|
+
items.push({
|
|
420
|
+
path: projectSettingsJson,
|
|
421
|
+
data,
|
|
422
|
+
level: "project",
|
|
423
|
+
_source: createSourceMeta(PROVIDER_ID, projectSettingsJson, "project"),
|
|
424
|
+
});
|
|
425
|
+
} else {
|
|
426
|
+
warnings.push(`Failed to parse JSON in ${projectSettingsJson}`);
|
|
589
427
|
}
|
|
590
428
|
}
|
|
591
429
|
|
|
@@ -607,7 +445,7 @@ registerProvider<MCPServer>(mcpCapability.id, {
|
|
|
607
445
|
registerProvider<ContextFile>(contextFileCapability.id, {
|
|
608
446
|
id: PROVIDER_ID,
|
|
609
447
|
displayName: DISPLAY_NAME,
|
|
610
|
-
description: "Load CLAUDE.md files from .claude/ directories
|
|
448
|
+
description: "Load CLAUDE.md files from .claude/ directories",
|
|
611
449
|
priority: PRIORITY,
|
|
612
450
|
load: loadContextFiles,
|
|
613
451
|
});
|