@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.
- package/CHANGELOG.md +30 -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 +4 -4
- package/src/core/agent-storage.ts +50 -0
- package/src/core/auth-storage.ts +112 -4
- package/src/core/bash-executor.ts +1 -1
- package/src/core/custom-tools/loader.ts +2 -2
- 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 +10 -10
- package/src/core/tools/edit.ts +7 -4
- package/src/core/tools/find.ts +2 -2
- 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 -5
- 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 +367 -247
- package/src/discovery/claude.ts +181 -290
- package/src/discovery/cline.ts +30 -10
- package/src/discovery/codex.ts +185 -244
- 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 +94 -88
- 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 +6 -2
- package/src/modes/interactive/interactive-mode.ts +19 -15
- package/src/prompts/agents/plan.md +107 -30
- package/src/utils/shell.ts +2 -2
- package/src/prompts/agents/planner.md +0 -112
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";
|
|
@@ -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,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
|
|
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
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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 {
|
|
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
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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
|
|
272
|
-
_source: createSourceMeta(PROVIDER_ID, extPath,
|
|
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
|
-
|
|
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
|
-
|
|
329
|
-
|
|
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
|
-
|
|
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
|
-
|
|
367
|
-
|
|
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
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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
|
|
298
|
+
level,
|
|
388
299
|
_source: source,
|
|
389
300
|
};
|
|
390
301
|
},
|
|
391
|
-
})
|
|
302
|
+
}),
|
|
303
|
+
),
|
|
304
|
+
);
|
|
392
305
|
|
|
393
|
-
|
|
394
|
-
|
|
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
|
-
|
|
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
|
-
|
|
448
|
-
|
|
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
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
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
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
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
|
|
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
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
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
|
|
448
|
+
description: "Load CLAUDE.md files from .claude/ directories",
|
|
558
449
|
priority: PRIORITY,
|
|
559
450
|
load: loadContextFiles,
|
|
560
451
|
});
|