@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.
- package/CHANGELOG.md +72 -34
- package/README.md +100 -100
- package/docs/compaction.md +8 -8
- package/docs/config-usage.md +113 -0
- package/docs/custom-tools.md +8 -8
- package/docs/extension-loading.md +58 -58
- package/docs/hooks.md +11 -11
- package/docs/rpc.md +4 -4
- package/docs/sdk.md +14 -14
- package/docs/session-tree-plan.md +1 -1
- package/docs/session.md +2 -2
- package/docs/skills.md +16 -16
- package/docs/theme.md +9 -9
- package/docs/tui.md +1 -1
- package/examples/README.md +1 -1
- package/examples/custom-tools/README.md +4 -4
- package/examples/custom-tools/subagent/README.md +13 -13
- package/examples/custom-tools/subagent/agents.ts +2 -2
- package/examples/custom-tools/subagent/index.ts +5 -5
- package/examples/hooks/README.md +3 -3
- package/examples/hooks/auto-commit-on-exit.ts +1 -1
- package/examples/hooks/custom-compaction.ts +1 -1
- package/examples/sdk/01-minimal.ts +1 -1
- package/examples/sdk/04-skills.ts +1 -1
- package/examples/sdk/05-tools.ts +1 -1
- package/examples/sdk/08-slash-commands.ts +1 -1
- package/examples/sdk/09-api-keys-and-oauth.ts +2 -2
- package/examples/sdk/README.md +2 -2
- package/package.json +13 -11
- package/src/capability/context-file.ts +40 -0
- package/src/capability/extension.ts +48 -0
- package/src/capability/hook.ts +40 -0
- package/src/capability/index.ts +616 -0
- package/src/capability/instruction.ts +37 -0
- package/src/capability/mcp.ts +52 -0
- package/src/capability/prompt.ts +35 -0
- package/src/capability/rule.ts +52 -0
- package/src/capability/settings.ts +35 -0
- package/src/capability/skill.ts +49 -0
- package/src/capability/slash-command.ts +40 -0
- package/src/capability/system-prompt.ts +35 -0
- package/src/capability/tool.ts +38 -0
- package/src/capability/types.ts +166 -0
- package/src/cli/args.ts +2 -2
- package/src/cli/plugin-cli.ts +24 -19
- package/src/cli/update-cli.ts +10 -10
- package/src/config.ts +290 -6
- package/src/core/auth-storage.ts +32 -9
- package/src/core/bash-executor.ts +1 -1
- package/src/core/custom-commands/loader.ts +44 -50
- package/src/core/custom-tools/index.ts +1 -0
- package/src/core/custom-tools/loader.ts +67 -69
- package/src/core/custom-tools/types.ts +10 -1
- package/src/core/hooks/loader.ts +13 -42
- package/src/core/index.ts +0 -1
- package/src/core/logger.ts +7 -7
- package/src/core/mcp/client.ts +1 -1
- package/src/core/mcp/config.ts +94 -146
- package/src/core/mcp/index.ts +0 -4
- package/src/core/mcp/loader.ts +26 -22
- package/src/core/mcp/manager.ts +18 -23
- package/src/core/mcp/tool-bridge.ts +9 -1
- package/src/core/mcp/types.ts +2 -0
- package/src/core/model-registry.ts +25 -8
- package/src/core/plugins/installer.ts +1 -1
- package/src/core/plugins/loader.ts +17 -11
- package/src/core/plugins/manager.ts +2 -2
- package/src/core/plugins/paths.ts +12 -7
- package/src/core/plugins/types.ts +3 -3
- package/src/core/sdk.ts +48 -27
- package/src/core/session-manager.ts +4 -4
- package/src/core/settings-manager.ts +45 -21
- package/src/core/skills.ts +208 -293
- package/src/core/slash-commands.ts +34 -165
- package/src/core/system-prompt.ts +58 -65
- package/src/core/timings.ts +2 -2
- package/src/core/tools/lsp/config.ts +38 -17
- package/src/core/tools/task/agents.ts +21 -0
- package/src/core/tools/task/artifacts.ts +1 -1
- package/src/core/tools/task/bundled-agents/reviewer.md +2 -1
- package/src/core/tools/task/bundled-agents/task.md +1 -0
- package/src/core/tools/task/commands.ts +30 -107
- package/src/core/tools/task/discovery.ts +75 -66
- package/src/core/tools/task/executor.ts +25 -10
- package/src/core/tools/task/index.ts +35 -10
- package/src/core/tools/task/model-resolver.ts +27 -25
- package/src/core/tools/task/types.ts +6 -2
- package/src/core/tools/web-fetch.ts +3 -3
- package/src/core/tools/web-search/auth.ts +40 -34
- package/src/core/tools/web-search/index.ts +1 -1
- package/src/core/tools/web-search/providers/anthropic.ts +1 -1
- package/src/discovery/agents-md.ts +75 -0
- package/src/discovery/builtin.ts +646 -0
- package/src/discovery/claude.ts +623 -0
- package/src/discovery/cline.ts +102 -0
- package/src/discovery/codex.ts +571 -0
- package/src/discovery/cursor.ts +264 -0
- package/src/discovery/gemini.ts +368 -0
- package/src/discovery/github.ts +120 -0
- package/src/discovery/helpers.test.ts +127 -0
- package/src/discovery/helpers.ts +249 -0
- package/src/discovery/index.ts +84 -0
- package/src/discovery/mcp-json.ts +127 -0
- package/src/discovery/vscode.ts +99 -0
- package/src/discovery/windsurf.ts +216 -0
- package/src/main.ts +14 -13
- package/src/migrations.ts +24 -3
- package/src/modes/interactive/components/hook-editor.ts +1 -1
- package/src/modes/interactive/components/plugin-settings.ts +1 -1
- package/src/modes/interactive/components/settings-defs.ts +38 -2
- package/src/modes/interactive/components/settings-selector.ts +1 -0
- package/src/modes/interactive/components/welcome.ts +2 -2
- package/src/modes/interactive/interactive-mode.ts +233 -16
- package/src/modes/interactive/theme/theme-schema.json +1 -1
- package/src/utils/clipboard.ts +1 -1
- package/src/utils/shell-snapshot.ts +2 -2
- 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
|
+
});
|