@oh-my-pi/pi-coding-agent 2.3.1337 → 3.1.1337

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