@oh-my-pi/pi-coding-agent 4.2.1 → 4.2.3

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.
Files changed (58) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/docs/sdk.md +5 -5
  3. package/examples/sdk/10-settings.ts +2 -2
  4. package/package.json +5 -5
  5. package/src/capability/fs.ts +90 -0
  6. package/src/capability/index.ts +41 -227
  7. package/src/capability/types.ts +1 -11
  8. package/src/cli/args.ts +4 -0
  9. package/src/core/agent-session.ts +4 -4
  10. package/src/core/agent-storage.ts +50 -0
  11. package/src/core/auth-storage.ts +112 -4
  12. package/src/core/bash-executor.ts +1 -1
  13. package/src/core/custom-tools/loader.ts +2 -2
  14. package/src/core/extensions/loader.ts +2 -2
  15. package/src/core/extensions/types.ts +1 -1
  16. package/src/core/hooks/loader.ts +2 -2
  17. package/src/core/mcp/config.ts +2 -2
  18. package/src/core/model-registry.ts +46 -0
  19. package/src/core/sdk.ts +37 -29
  20. package/src/core/settings-manager.ts +152 -135
  21. package/src/core/skills.ts +72 -51
  22. package/src/core/slash-commands.ts +3 -3
  23. package/src/core/system-prompt.ts +10 -10
  24. package/src/core/tools/edit.ts +7 -4
  25. package/src/core/tools/find.ts +2 -2
  26. package/src/core/tools/index.test.ts +16 -0
  27. package/src/core/tools/index.ts +21 -8
  28. package/src/core/tools/lsp/index.ts +4 -1
  29. package/src/core/tools/ssh.ts +6 -6
  30. package/src/core/tools/task/commands.ts +3 -5
  31. package/src/core/tools/task/executor.ts +88 -3
  32. package/src/core/tools/task/index.ts +4 -0
  33. package/src/core/tools/task/model-resolver.ts +10 -7
  34. package/src/core/tools/task/worker-protocol.ts +48 -2
  35. package/src/core/tools/task/worker.ts +152 -7
  36. package/src/core/tools/write.ts +7 -4
  37. package/src/discovery/agents-md.ts +13 -19
  38. package/src/discovery/builtin.ts +367 -247
  39. package/src/discovery/claude.ts +181 -290
  40. package/src/discovery/cline.ts +30 -10
  41. package/src/discovery/codex.ts +185 -244
  42. package/src/discovery/cursor.ts +106 -121
  43. package/src/discovery/gemini.ts +72 -97
  44. package/src/discovery/github.ts +7 -10
  45. package/src/discovery/helpers.ts +94 -88
  46. package/src/discovery/index.ts +1 -2
  47. package/src/discovery/mcp-json.ts +15 -18
  48. package/src/discovery/ssh.ts +9 -17
  49. package/src/discovery/vscode.ts +10 -5
  50. package/src/discovery/windsurf.ts +52 -86
  51. package/src/main.ts +5 -1
  52. package/src/modes/interactive/components/extensions/extension-dashboard.ts +24 -11
  53. package/src/modes/interactive/components/extensions/state-manager.ts +19 -15
  54. package/src/modes/interactive/controllers/selector-controller.ts +6 -2
  55. package/src/modes/interactive/interactive-mode.ts +19 -15
  56. package/src/prompts/agents/plan.md +107 -30
  57. package/src/utils/shell.ts +2 -2
  58. package/src/prompts/agents/planner.md +0 -112
@@ -5,9 +5,10 @@
5
5
  * Priority: 80 (tool-specific, below builtin but above shared standards)
6
6
  */
7
7
 
8
- import { dirname, join, sep } from "path";
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";
@@ -41,45 +42,49 @@ function getUserClaude(ctx: LoadContext): string {
41
42
  }
42
43
 
43
44
  /**
44
- * Get project-level .claude path (walks up from cwd).
45
+ * Get project-level .claude path (cwd only).
45
46
  */
46
- function getProjectClaude(ctx: LoadContext): string | null {
47
- return ctx.fs.walkUp(CONFIG_DIR, { dir: true });
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
- for (const [path, level] of [
64
- [userClaudeJson, "user"],
65
- [userMcpJson, "user"],
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
- const content = ctx.fs.readFile(path);
70
- if (!content) {
71
- warnings.push(`Failed to read ${path}`);
72
- continue;
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) continue;
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
- items.push({
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
- // Project-level: <project>/.mcp.json or <project>/mcp.json
97
- const projectBase = getProjectClaude(ctx);
98
- if (projectBase) {
99
- const projectMcpJson = join(projectBase, ".mcp.json");
100
- const projectMcpJsonAlt = join(projectBase, "mcp.json");
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
- if (ctx.fs.isFile(userClaudeMd)) {
149
- const content = ctx.fs.readFile(userClaudeMd);
150
- if (content !== null) {
151
- items.push({
152
- path: userClaudeMd,
153
- content,
154
- level: "user",
155
- _source: createSourceMeta(PROVIDER_ID, userClaudeMd, "user"),
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
- if (projectBase) {
165
- const projectClaudeMd = join(projectBase, "CLAUDE.md");
166
-
167
- if (ctx.fs.isFile(projectClaudeMd)) {
168
- const content = ctx.fs.readFile(projectClaudeMd);
169
- if (content !== null) {
170
- // Calculate depth (distance from cwd)
171
- const depth = calculateDepth(ctx.cwd, projectBase, sep);
172
-
173
- items.push({
174
- path: projectClaudeMd,
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,62 +159,52 @@ function loadContextFiles(ctx: LoadContext): LoadResult<ContextFile> {
214
159
  // Skills
215
160
  // =============================================================================
216
161
 
217
- function loadSkills(ctx: LoadContext): LoadResult<Skill> {
218
- const items: Skill[] = [];
219
- const warnings: string[] = [];
220
-
162
+ async function loadSkills(ctx: LoadContext): Promise<LoadResult<Skill>> {
221
163
  const userSkillsDir = join(getUserClaude(ctx), "skills");
222
- const userResult = loadSkillsFromDir(ctx, {
223
- dir: userSkillsDir,
224
- providerId: PROVIDER_ID,
225
- level: "user",
226
- });
227
- items.push(...userResult.items);
228
- if (userResult.warnings) warnings.push(...userResult.warnings);
164
+ const projectSkillsDir = join(getProjectClaude(ctx), "skills");
229
165
 
230
- const projectBase = getProjectClaude(ctx);
231
- if (projectBase) {
232
- const projectSkillsDir = join(projectBase, "skills");
233
- const projectResult = loadSkillsFromDir(ctx, {
234
- dir: projectSkillsDir,
235
- providerId: PROVIDER_ID,
236
- level: "project",
237
- });
238
- items.push(...projectResult.items);
239
- if (projectResult.warnings) warnings.push(...projectResult.warnings);
240
- }
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
+ ]);
241
170
 
242
- return { items, warnings };
171
+ return {
172
+ items: results.flatMap((r) => r.items),
173
+ warnings: results.flatMap((r) => r.warnings ?? []),
174
+ };
243
175
  }
244
176
 
245
177
  // =============================================================================
246
178
  // Extension Modules
247
179
  // =============================================================================
248
180
 
249
- function loadExtensionModules(ctx: LoadContext): LoadResult<ExtensionModule> {
181
+ async function loadExtensionModules(ctx: LoadContext): Promise<LoadResult<ExtensionModule>> {
250
182
  const items: ExtensionModule[] = [];
251
183
  const warnings: string[] = [];
252
184
 
253
185
  const userBase = getUserClaude(ctx);
254
186
  const userExtensionsDir = join(userBase, "extensions");
255
- for (const extPath of discoverExtensionModulePaths(ctx, userExtensionsDir)) {
256
- items.push({
257
- name: getExtensionNameFromPath(extPath),
258
- path: extPath,
259
- level: "user",
260
- _source: createSourceMeta(PROVIDER_ID, extPath, "user"),
261
- });
262
- }
263
-
264
- const projectBase = getProjectClaude(ctx);
265
- if (projectBase) {
266
- const projectExtensionsDir = join(projectBase, "extensions");
267
- for (const extPath of discoverExtensionModulePaths(ctx, projectExtensionsDir)) {
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) {
268
203
  items.push({
269
204
  name: getExtensionNameFromPath(extPath),
270
205
  path: extPath,
271
- level: "project",
272
- _source: createSourceMeta(PROVIDER_ID, extPath, "project"),
206
+ level,
207
+ _source: createSourceMeta(PROVIDER_ID, extPath, level),
273
208
  });
274
209
  }
275
210
  }
@@ -281,15 +216,14 @@ function loadExtensionModules(ctx: LoadContext): LoadResult<ExtensionModule> {
281
216
  // Slash Commands
282
217
  // =============================================================================
283
218
 
284
- function loadSlashCommands(ctx: LoadContext): LoadResult<SlashCommand> {
219
+ async function loadSlashCommands(ctx: LoadContext): Promise<LoadResult<SlashCommand>> {
285
220
  const items: SlashCommand[] = [];
286
221
  const warnings: string[] = [];
287
222
 
288
- // User-level: ~/.claude/commands/*.md
289
223
  const userBase = getUserClaude(ctx);
290
224
  const userCommandsDir = join(userBase, "commands");
291
225
 
292
- const userResult = loadFilesFromDir<SlashCommand>(ctx, userCommandsDir, PROVIDER_ID, "user", {
226
+ const userResult = await loadFilesFromDir<SlashCommand>(ctx, userCommandsDir, PROVIDER_ID, "user", {
293
227
  extensions: ["md"],
294
228
  transform: (name, content, path, source) => {
295
229
  const cmdName = name.replace(/\.md$/, "");
@@ -306,28 +240,24 @@ function loadSlashCommands(ctx: LoadContext): LoadResult<SlashCommand> {
306
240
  items.push(...userResult.items);
307
241
  if (userResult.warnings) warnings.push(...userResult.warnings);
308
242
 
309
- // Project-level: <project>/.claude/commands/*.md
310
- const projectBase = getProjectClaude(ctx);
311
- if (projectBase) {
312
- const projectCommandsDir = join(projectBase, "commands");
313
-
314
- const projectResult = loadFilesFromDir<SlashCommand>(ctx, projectCommandsDir, PROVIDER_ID, "project", {
315
- extensions: ["md"],
316
- transform: (name, content, path, source) => {
317
- const cmdName = name.replace(/\.md$/, "");
318
- return {
319
- name: cmdName,
320
- path,
321
- content,
322
- level: "project",
323
- _source: source,
324
- };
325
- },
326
- });
243
+ const projectCommandsDir = join(ctx.cwd, CONFIG_DIR, "commands");
327
244
 
328
- items.push(...projectResult.items);
329
- if (projectResult.warnings) warnings.push(...projectResult.warnings);
330
- }
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);
331
261
 
332
262
  return { items, warnings };
333
263
  }
@@ -336,63 +266,46 @@ function loadSlashCommands(ctx: LoadContext): LoadResult<SlashCommand> {
336
266
  // Hooks
337
267
  // =============================================================================
338
268
 
339
- function loadHooks(ctx: LoadContext): LoadResult<Hook> {
269
+ async function loadHooks(ctx: LoadContext): Promise<LoadResult<Hook>> {
340
270
  const items: Hook[] = [];
341
271
  const warnings: string[] = [];
342
272
 
343
- // User-level: ~/.claude/hooks/pre/* and ~/.claude/hooks/post/*
344
273
  const userBase = getUserClaude(ctx);
345
274
  const userHooksDir = join(userBase, "hooks");
275
+ const projectBase = getProjectClaude(ctx);
276
+ const projectHooksDir = join(projectBase, "hooks");
346
277
 
347
- for (const hookType of ["pre", "post"] as const) {
348
- const hooksTypeDir = join(userHooksDir, hookType);
349
-
350
- const result = loadFilesFromDir<Hook>(ctx, hooksTypeDir, PROVIDER_ID, "user", {
351
- transform: (name, _content, path, source) => {
352
- // Extract tool name from filename (e.g., "bash.sh" -> "bash", "*.sh" -> "*")
353
- const toolName = name.replace(/\.(sh|bash|zsh|fish)$/, "");
354
-
355
- return {
356
- name,
357
- path,
358
- type: hookType,
359
- tool: toolName,
360
- level: "user",
361
- _source: source,
362
- };
363
- },
364
- });
278
+ const hookTypes = ["pre", "post"] as const;
365
279
 
366
- items.push(...result.items);
367
- if (result.warnings) warnings.push(...result.warnings);
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" });
368
286
  }
369
287
 
370
- // Project-level: <project>/.claude/hooks/pre/* and <project>/.claude/hooks/post/*
371
- const projectBase = getProjectClaude(ctx);
372
- if (projectBase) {
373
- const projectHooksDir = join(projectBase, "hooks");
374
-
375
- for (const hookType of ["pre", "post"] as const) {
376
- const hooksTypeDir = join(projectHooksDir, hookType);
377
-
378
- 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, {
379
291
  transform: (name, _content, path, source) => {
380
292
  const toolName = name.replace(/\.(sh|bash|zsh|fish)$/, "");
381
-
382
293
  return {
383
294
  name,
384
295
  path,
385
296
  type: hookType,
386
297
  tool: toolName,
387
- level: "project",
298
+ level,
388
299
  _source: source,
389
300
  };
390
301
  },
391
- });
302
+ }),
303
+ ),
304
+ );
392
305
 
393
- items.push(...result.items);
394
- if (result.warnings) warnings.push(...result.warnings);
395
- }
306
+ for (const result of results) {
307
+ items.push(...result.items);
308
+ if (result.warnings) warnings.push(...result.warnings);
396
309
  }
397
310
 
398
311
  return { items, warnings };
@@ -402,15 +315,14 @@ function loadHooks(ctx: LoadContext): LoadResult<Hook> {
402
315
  // Custom Tools
403
316
  // =============================================================================
404
317
 
405
- function loadTools(ctx: LoadContext): LoadResult<CustomTool> {
318
+ async function loadTools(ctx: LoadContext): Promise<LoadResult<CustomTool>> {
406
319
  const items: CustomTool[] = [];
407
320
  const warnings: string[] = [];
408
321
 
409
- // User-level: ~/.claude/tools/*
410
322
  const userBase = getUserClaude(ctx);
411
323
  const userToolsDir = join(userBase, "tools");
412
324
 
413
- const userResult = loadFilesFromDir<CustomTool>(ctx, userToolsDir, PROVIDER_ID, "user", {
325
+ const userResult = await loadFilesFromDir<CustomTool>(ctx, userToolsDir, PROVIDER_ID, "user", {
414
326
  transform: (name, _content, path, source) => {
415
327
  const toolName = name.replace(/\.(ts|js|sh|bash|py)$/, "");
416
328
 
@@ -426,27 +338,24 @@ function loadTools(ctx: LoadContext): LoadResult<CustomTool> {
426
338
  items.push(...userResult.items);
427
339
  if (userResult.warnings) warnings.push(...userResult.warnings);
428
340
 
429
- // Project-level: <project>/.claude/tools/*
430
341
  const projectBase = getProjectClaude(ctx);
431
- if (projectBase) {
432
- const projectToolsDir = join(projectBase, "tools");
433
-
434
- const projectResult = loadFilesFromDir<CustomTool>(ctx, projectToolsDir, PROVIDER_ID, "project", {
435
- transform: (name, _content, path, source) => {
436
- const toolName = name.replace(/\.(ts|js|sh|bash|py)$/, "");
437
-
438
- return {
439
- name: toolName,
440
- path,
441
- level: "project",
442
- _source: source,
443
- };
444
- },
445
- });
342
+ const projectToolsDir = join(projectBase, "tools");
446
343
 
447
- items.push(...projectResult.items);
448
- if (projectResult.warnings) warnings.push(...projectResult.warnings);
449
- }
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);
450
359
 
451
360
  return { items, warnings };
452
361
  }
@@ -455,26 +364,21 @@ function loadTools(ctx: LoadContext): LoadResult<CustomTool> {
455
364
  // System Prompts
456
365
  // =============================================================================
457
366
 
458
- function loadSystemPrompts(ctx: LoadContext): LoadResult<SystemPrompt> {
367
+ async function loadSystemPrompts(ctx: LoadContext): Promise<LoadResult<SystemPrompt>> {
459
368
  const items: SystemPrompt[] = [];
460
369
  const warnings: string[] = [];
461
370
 
462
- // User-level: ~/.claude/SYSTEM.md
463
371
  const userBase = getUserClaude(ctx);
464
372
  const userSystemMd = join(userBase, "SYSTEM.md");
465
373
 
466
- if (ctx.fs.isFile(userSystemMd)) {
467
- const content = ctx.fs.readFile(userSystemMd);
468
- if (content !== null) {
469
- items.push({
470
- path: userSystemMd,
471
- content,
472
- level: "user",
473
- _source: createSourceMeta(PROVIDER_ID, userSystemMd, "user"),
474
- });
475
- } else {
476
- warnings.push(`Failed to read ${userSystemMd}`);
477
- }
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
+ });
478
382
  }
479
383
 
480
384
  return { items, warnings };
@@ -484,55 +388,42 @@ function loadSystemPrompts(ctx: LoadContext): LoadResult<SystemPrompt> {
484
388
  // Settings
485
389
  // =============================================================================
486
390
 
487
- function loadSettings(ctx: LoadContext): LoadResult<Settings> {
391
+ async function loadSettings(ctx: LoadContext): Promise<LoadResult<Settings>> {
488
392
  const items: Settings[] = [];
489
393
  const warnings: string[] = [];
490
394
 
491
- // User-level: ~/.claude/settings.json
492
395
  const userBase = getUserClaude(ctx);
493
396
  const userSettingsJson = join(userBase, "settings.json");
494
397
 
495
- if (ctx.fs.isFile(userSettingsJson)) {
496
- const content = ctx.fs.readFile(userSettingsJson);
497
- if (content) {
498
- const data = parseJSON<Record<string, unknown>>(content);
499
- if (data) {
500
- items.push({
501
- path: userSettingsJson,
502
- data,
503
- level: "user",
504
- _source: createSourceMeta(PROVIDER_ID, userSettingsJson, "user"),
505
- });
506
- } else {
507
- warnings.push(`Failed to parse JSON in ${userSettingsJson}`);
508
- }
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
+ });
509
408
  } else {
510
- warnings.push(`Failed to read ${userSettingsJson}`);
409
+ warnings.push(`Failed to parse JSON in ${userSettingsJson}`);
511
410
  }
512
411
  }
513
412
 
514
- // Project-level: <project>/.claude/settings.json
515
413
  const projectBase = getProjectClaude(ctx);
516
- if (projectBase) {
517
- const projectSettingsJson = join(projectBase, "settings.json");
518
-
519
- if (ctx.fs.isFile(projectSettingsJson)) {
520
- const content = ctx.fs.readFile(projectSettingsJson);
521
- if (content) {
522
- const data = parseJSON<Record<string, unknown>>(content);
523
- if (data) {
524
- items.push({
525
- path: projectSettingsJson,
526
- data,
527
- level: "project",
528
- _source: createSourceMeta(PROVIDER_ID, projectSettingsJson, "project"),
529
- });
530
- } else {
531
- warnings.push(`Failed to parse JSON in ${projectSettingsJson}`);
532
- }
533
- } else {
534
- warnings.push(`Failed to read ${projectSettingsJson}`);
535
- }
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}`);
536
427
  }
537
428
  }
538
429
 
@@ -554,7 +445,7 @@ registerProvider<MCPServer>(mcpCapability.id, {
554
445
  registerProvider<ContextFile>(contextFileCapability.id, {
555
446
  id: PROVIDER_ID,
556
447
  displayName: DISPLAY_NAME,
557
- description: "Load CLAUDE.md files from .claude/ directories and project root",
448
+ description: "Load CLAUDE.md files from .claude/ directories",
558
449
  priority: PRIORITY,
559
450
  load: loadContextFiles,
560
451
  });