@oh-my-pi/pi-coding-agent 2.2.1337 → 3.0.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 (116) hide show
  1. package/CHANGELOG.md +64 -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 +16 -12
  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/export-html/index.ts +9 -9
  55. package/src/core/export-html/template.generated.ts +2 -0
  56. package/src/core/hooks/loader.ts +13 -42
  57. package/src/core/index.ts +0 -1
  58. package/src/core/logger.ts +7 -7
  59. package/src/core/mcp/client.ts +1 -1
  60. package/src/core/mcp/config.ts +94 -146
  61. package/src/core/mcp/index.ts +0 -4
  62. package/src/core/mcp/loader.ts +26 -22
  63. package/src/core/mcp/manager.ts +18 -23
  64. package/src/core/mcp/tool-bridge.ts +9 -1
  65. package/src/core/mcp/types.ts +2 -0
  66. package/src/core/model-registry.ts +25 -8
  67. package/src/core/plugins/installer.ts +1 -1
  68. package/src/core/plugins/loader.ts +17 -11
  69. package/src/core/plugins/manager.ts +2 -2
  70. package/src/core/plugins/paths.ts +12 -7
  71. package/src/core/plugins/types.ts +3 -3
  72. package/src/core/sdk.ts +48 -27
  73. package/src/core/session-manager.ts +4 -4
  74. package/src/core/settings-manager.ts +45 -21
  75. package/src/core/skills.ts +222 -293
  76. package/src/core/slash-commands.ts +34 -165
  77. package/src/core/system-prompt.ts +58 -65
  78. package/src/core/timings.ts +2 -2
  79. package/src/core/tools/lsp/config.ts +38 -17
  80. package/src/core/tools/task/artifacts.ts +1 -1
  81. package/src/core/tools/task/commands.ts +30 -107
  82. package/src/core/tools/task/discovery.ts +54 -66
  83. package/src/core/tools/task/executor.ts +9 -9
  84. package/src/core/tools/task/index.ts +10 -10
  85. package/src/core/tools/task/model-resolver.ts +27 -25
  86. package/src/core/tools/task/types.ts +2 -2
  87. package/src/core/tools/web-fetch.ts +3 -3
  88. package/src/core/tools/web-search/auth.ts +40 -34
  89. package/src/core/tools/web-search/index.ts +1 -1
  90. package/src/core/tools/web-search/providers/anthropic.ts +1 -1
  91. package/src/discovery/agents-md.ts +75 -0
  92. package/src/discovery/builtin.ts +646 -0
  93. package/src/discovery/claude.ts +623 -0
  94. package/src/discovery/cline.ts +102 -0
  95. package/src/discovery/codex.ts +571 -0
  96. package/src/discovery/cursor.ts +264 -0
  97. package/src/discovery/gemini.ts +368 -0
  98. package/src/discovery/github.ts +120 -0
  99. package/src/discovery/helpers.test.ts +127 -0
  100. package/src/discovery/helpers.ts +249 -0
  101. package/src/discovery/index.ts +84 -0
  102. package/src/discovery/mcp-json.ts +127 -0
  103. package/src/discovery/vscode.ts +99 -0
  104. package/src/discovery/windsurf.ts +216 -0
  105. package/src/main.ts +14 -13
  106. package/src/migrations.ts +24 -3
  107. package/src/modes/interactive/components/hook-editor.ts +1 -1
  108. package/src/modes/interactive/components/plugin-settings.ts +1 -1
  109. package/src/modes/interactive/components/settings-defs.ts +38 -2
  110. package/src/modes/interactive/components/settings-selector.ts +1 -0
  111. package/src/modes/interactive/components/welcome.ts +2 -2
  112. package/src/modes/interactive/interactive-mode.ts +211 -16
  113. package/src/modes/interactive/theme/theme-schema.json +1 -1
  114. package/src/utils/clipboard.ts +1 -1
  115. package/src/utils/shell-snapshot.ts +2 -2
  116. package/src/utils/shell.ts +7 -7
@@ -0,0 +1,646 @@
1
+ /**
2
+ * Builtin Provider (.omp / .pi)
3
+ *
4
+ * Primary provider for OMP native configs. Supports all capabilities.
5
+ * .pi is an alias for backwards compatibility.
6
+ */
7
+
8
+ import { basename, dirname, join } from "node:path";
9
+ import { type ContextFile, contextFileCapability } from "../capability/context-file";
10
+ import { type Extension, type ExtensionManifest, extensionCapability } from "../capability/extension";
11
+ import { type Hook, hookCapability } from "../capability/hook";
12
+ import { registerProvider } from "../capability/index";
13
+ import { type Instruction, instructionCapability } from "../capability/instruction";
14
+ import { type MCPServer, mcpCapability } from "../capability/mcp";
15
+ import { type Prompt, promptCapability } from "../capability/prompt";
16
+ import { type Rule, ruleCapability } from "../capability/rule";
17
+ import { type Settings, settingsCapability } from "../capability/settings";
18
+ import { type Skill, type SkillFrontmatter, skillCapability } from "../capability/skill";
19
+ import { type SlashCommand, slashCommandCapability } from "../capability/slash-command";
20
+ import { type SystemPrompt, systemPromptCapability } from "../capability/system-prompt";
21
+ import { type CustomTool, toolCapability } from "../capability/tool";
22
+ import type { LoadContext, LoadResult } from "../capability/types";
23
+ import {
24
+ createSourceMeta,
25
+ expandEnvVarsDeep,
26
+ loadFilesFromDir,
27
+ parseFrontmatter,
28
+ parseJSON,
29
+ SOURCE_PATHS,
30
+ } from "./helpers";
31
+
32
+ const PROVIDER_ID = "native";
33
+ const DISPLAY_NAME = "OMP";
34
+ const DESCRIPTION = "Native OMP configuration from ~/.omp and .omp/";
35
+ const PRIORITY = 100;
36
+
37
+ const PATHS = SOURCE_PATHS.native;
38
+ const PROJECT_DIRS = [PATHS.projectDir, ...PATHS.aliases];
39
+ const USER_DIRS = [PATHS.userBase, ...PATHS.aliases];
40
+
41
+ function getConfigDirs(ctx: LoadContext): Array<{ dir: string; level: "user" | "project" }> {
42
+ const result: Array<{ dir: string; level: "user" | "project" }> = [];
43
+
44
+ for (const name of PROJECT_DIRS) {
45
+ const projectDir = ctx.fs.walkUp(name, { dir: true });
46
+ if (projectDir) {
47
+ result.push({ dir: projectDir, level: "project" });
48
+ break;
49
+ }
50
+ }
51
+
52
+ for (const name of USER_DIRS) {
53
+ const userDir = join(ctx.home, name, PATHS.userAgent.replace(`${PATHS.userBase}/`, ""));
54
+ if (ctx.fs.isDir(userDir)) {
55
+ result.push({ dir: userDir, level: "user" });
56
+ break;
57
+ }
58
+ }
59
+
60
+ return result;
61
+ }
62
+
63
+ // MCP
64
+ function loadMCPServers(ctx: LoadContext): LoadResult<MCPServer> {
65
+ const items: MCPServer[] = [];
66
+ const warnings: string[] = [];
67
+
68
+ for (const name of PROJECT_DIRS) {
69
+ const projectDir = ctx.fs.walkUp(name, { dir: true });
70
+ if (!projectDir) continue;
71
+
72
+ for (const filename of ["mcp.json", ".mcp.json"]) {
73
+ const path = join(projectDir, filename);
74
+ const content = ctx.fs.readFile(path);
75
+ if (!content) continue;
76
+
77
+ const data = parseJSON<{ mcpServers?: Record<string, unknown> }>(content);
78
+ if (!data?.mcpServers) continue;
79
+
80
+ const expanded = expandEnvVarsDeep(data.mcpServers);
81
+ for (const [serverName, config] of Object.entries(expanded)) {
82
+ const serverConfig = config as Record<string, unknown>;
83
+ items.push({
84
+ name: serverName,
85
+ command: serverConfig.command as string | undefined,
86
+ args: serverConfig.args as string[] | undefined,
87
+ env: serverConfig.env as Record<string, string> | undefined,
88
+ url: serverConfig.url as string | undefined,
89
+ headers: serverConfig.headers as Record<string, string> | undefined,
90
+ transport: serverConfig.type as "stdio" | "sse" | "http" | undefined,
91
+ _source: createSourceMeta(PROVIDER_ID, path, "project"),
92
+ });
93
+ }
94
+ break;
95
+ }
96
+ break;
97
+ }
98
+
99
+ for (const name of USER_DIRS) {
100
+ const userPath = join(ctx.home, name, "mcp.json");
101
+ const content = ctx.fs.readFile(userPath);
102
+ if (!content) continue;
103
+
104
+ const data = parseJSON<{ mcpServers?: Record<string, unknown> }>(content);
105
+ if (!data?.mcpServers) continue;
106
+
107
+ const expanded = expandEnvVarsDeep(data.mcpServers);
108
+ for (const [serverName, config] of Object.entries(expanded)) {
109
+ const serverConfig = config as Record<string, unknown>;
110
+ items.push({
111
+ name: serverName,
112
+ command: serverConfig.command as string | undefined,
113
+ args: serverConfig.args as string[] | undefined,
114
+ env: serverConfig.env as Record<string, string> | undefined,
115
+ url: serverConfig.url as string | undefined,
116
+ headers: serverConfig.headers as Record<string, string> | undefined,
117
+ transport: serverConfig.type as "stdio" | "sse" | "http" | undefined,
118
+ _source: createSourceMeta(PROVIDER_ID, userPath, "user"),
119
+ });
120
+ }
121
+ break;
122
+ }
123
+
124
+ return { items, warnings };
125
+ }
126
+
127
+ registerProvider<MCPServer>(mcpCapability.id, {
128
+ id: PROVIDER_ID,
129
+ displayName: DISPLAY_NAME,
130
+ description: DESCRIPTION,
131
+ priority: PRIORITY,
132
+ load: loadMCPServers,
133
+ });
134
+
135
+ // System Prompt (SYSTEM.md)
136
+ function loadSystemPrompt(ctx: LoadContext): LoadResult<SystemPrompt> {
137
+ const items: SystemPrompt[] = [];
138
+
139
+ // User level: ~/.omp/agent/SYSTEM.md or ~/.pi/agent/SYSTEM.md
140
+ for (const name of USER_DIRS) {
141
+ const userPath = join(ctx.home, name, PATHS.userAgent.replace(`${PATHS.userBase}/`, ""), "SYSTEM.md");
142
+ const userContent = ctx.fs.readFile(userPath);
143
+ if (userContent) {
144
+ items.push({
145
+ path: userPath,
146
+ content: userContent,
147
+ level: "user",
148
+ _source: createSourceMeta(PROVIDER_ID, userPath, "user"),
149
+ });
150
+ break; // First match wins
151
+ }
152
+ }
153
+
154
+ // Project level: walk up looking for .omp/SYSTEM.md or .pi/SYSTEM.md
155
+ let current = ctx.cwd;
156
+ while (true) {
157
+ for (const name of PROJECT_DIRS) {
158
+ const configDir = join(current, name);
159
+ if (ctx.fs.isDir(configDir)) {
160
+ const projectPath = join(configDir, "SYSTEM.md");
161
+ const content = ctx.fs.readFile(projectPath);
162
+ if (content) {
163
+ items.push({
164
+ path: projectPath,
165
+ content,
166
+ level: "project",
167
+ _source: createSourceMeta(PROVIDER_ID, projectPath, "project"),
168
+ });
169
+ break; // First config dir in this directory wins
170
+ }
171
+ }
172
+ }
173
+ const parent = dirname(current);
174
+ if (parent === current) break;
175
+ current = parent;
176
+ }
177
+
178
+ return { items, warnings: [] };
179
+ }
180
+
181
+ registerProvider<SystemPrompt>(systemPromptCapability.id, {
182
+ id: PROVIDER_ID,
183
+ displayName: DISPLAY_NAME,
184
+ description: "Custom system prompt from SYSTEM.md",
185
+ priority: PRIORITY,
186
+ load: loadSystemPrompt,
187
+ });
188
+
189
+ // Skills
190
+ function loadSkillFromFile(ctx: LoadContext, path: string, level: "user" | "project"): Skill | null {
191
+ const content = ctx.fs.readFile(path);
192
+ if (!content) return null;
193
+
194
+ const { frontmatter, body } = parseFrontmatter(content);
195
+ const skillDir = dirname(path);
196
+ const parentDirName = basename(skillDir);
197
+ const name = (frontmatter.name as string) || parentDirName;
198
+
199
+ if (!frontmatter.description) return null;
200
+
201
+ return {
202
+ name,
203
+ path,
204
+ content: body,
205
+ frontmatter: frontmatter as SkillFrontmatter,
206
+ level,
207
+ _source: createSourceMeta(PROVIDER_ID, path, level),
208
+ };
209
+ }
210
+
211
+ function loadSkillsRecursive(ctx: LoadContext, dir: string, level: "user" | "project"): LoadResult<Skill> {
212
+ const items: Skill[] = [];
213
+ const warnings: string[] = [];
214
+
215
+ if (!ctx.fs.isDir(dir)) return { items, warnings };
216
+
217
+ for (const name of ctx.fs.readDir(dir)) {
218
+ if (name.startsWith(".") || name === "node_modules") continue;
219
+
220
+ const path = join(dir, name);
221
+
222
+ if (ctx.fs.isDir(path)) {
223
+ const skillFile = join(path, "SKILL.md");
224
+ if (ctx.fs.isFile(skillFile)) {
225
+ const skill = loadSkillFromFile(ctx, skillFile, level);
226
+ if (skill) items.push(skill);
227
+ }
228
+
229
+ const sub = loadSkillsRecursive(ctx, path, level);
230
+ items.push(...sub.items);
231
+ if (sub.warnings) warnings.push(...sub.warnings);
232
+ } else if (name === "SKILL.md") {
233
+ const skill = loadSkillFromFile(ctx, path, level);
234
+ if (skill) items.push(skill);
235
+ }
236
+ }
237
+
238
+ return { items, warnings };
239
+ }
240
+
241
+ function loadSkills(ctx: LoadContext): LoadResult<Skill> {
242
+ const items: Skill[] = [];
243
+ const warnings: string[] = [];
244
+
245
+ for (const { dir, level } of getConfigDirs(ctx)) {
246
+ const skillsDir = join(dir, "skills");
247
+ const result = loadSkillsRecursive(ctx, skillsDir, level);
248
+ items.push(...result.items);
249
+ if (result.warnings) warnings.push(...result.warnings);
250
+ }
251
+
252
+ return { items, warnings };
253
+ }
254
+
255
+ registerProvider<Skill>(skillCapability.id, {
256
+ id: PROVIDER_ID,
257
+ displayName: DISPLAY_NAME,
258
+ description: DESCRIPTION,
259
+ priority: PRIORITY,
260
+ load: loadSkills,
261
+ });
262
+
263
+ // Slash Commands
264
+ function loadSlashCommands(ctx: LoadContext): LoadResult<SlashCommand> {
265
+ const items: SlashCommand[] = [];
266
+ const warnings: string[] = [];
267
+
268
+ for (const { dir, level } of getConfigDirs(ctx)) {
269
+ const commandsDir = join(dir, "commands");
270
+ const result = loadFilesFromDir<SlashCommand>(ctx, commandsDir, PROVIDER_ID, level, {
271
+ extensions: ["md"],
272
+ transform: (name, content, path, source) => ({
273
+ name: name.replace(/\.md$/, ""),
274
+ path,
275
+ content,
276
+ level,
277
+ _source: source,
278
+ }),
279
+ });
280
+ items.push(...result.items);
281
+ if (result.warnings) warnings.push(...result.warnings);
282
+ }
283
+
284
+ return { items, warnings };
285
+ }
286
+
287
+ registerProvider<SlashCommand>(slashCommandCapability.id, {
288
+ id: PROVIDER_ID,
289
+ displayName: DISPLAY_NAME,
290
+ description: DESCRIPTION,
291
+ priority: PRIORITY,
292
+ load: loadSlashCommands,
293
+ });
294
+
295
+ // Rules
296
+ function loadRules(ctx: LoadContext): LoadResult<Rule> {
297
+ const items: Rule[] = [];
298
+ const warnings: string[] = [];
299
+
300
+ for (const { dir, level } of getConfigDirs(ctx)) {
301
+ const rulesDir = join(dir, "rules");
302
+ const result = loadFilesFromDir<Rule>(ctx, rulesDir, PROVIDER_ID, level, {
303
+ extensions: ["md", "mdc"],
304
+ transform: (name, content, path, source) => {
305
+ const { frontmatter, body } = parseFrontmatter(content);
306
+ return {
307
+ name: name.replace(/\.(md|mdc)$/, ""),
308
+ path,
309
+ content: body,
310
+ globs: frontmatter.globs as string[] | undefined,
311
+ alwaysApply: frontmatter.alwaysApply as boolean | undefined,
312
+ description: frontmatter.description as string | undefined,
313
+ _source: source,
314
+ };
315
+ },
316
+ });
317
+ items.push(...result.items);
318
+ if (result.warnings) warnings.push(...result.warnings);
319
+ }
320
+
321
+ return { items, warnings };
322
+ }
323
+
324
+ registerProvider<Rule>(ruleCapability.id, {
325
+ id: PROVIDER_ID,
326
+ displayName: DISPLAY_NAME,
327
+ description: DESCRIPTION,
328
+ priority: PRIORITY,
329
+ load: loadRules,
330
+ });
331
+
332
+ // Prompts
333
+ function loadPrompts(ctx: LoadContext): LoadResult<Prompt> {
334
+ const items: Prompt[] = [];
335
+ const warnings: string[] = [];
336
+
337
+ for (const { dir, level } of getConfigDirs(ctx)) {
338
+ const promptsDir = join(dir, "prompts");
339
+ const result = loadFilesFromDir<Prompt>(ctx, promptsDir, PROVIDER_ID, level, {
340
+ extensions: ["md"],
341
+ transform: (name, content, path, source) => ({
342
+ name: name.replace(/\.md$/, ""),
343
+ path,
344
+ content,
345
+ _source: source,
346
+ }),
347
+ });
348
+ items.push(...result.items);
349
+ if (result.warnings) warnings.push(...result.warnings);
350
+ }
351
+
352
+ return { items, warnings };
353
+ }
354
+
355
+ registerProvider<Prompt>(promptCapability.id, {
356
+ id: PROVIDER_ID,
357
+ displayName: DISPLAY_NAME,
358
+ description: DESCRIPTION,
359
+ priority: PRIORITY,
360
+ load: loadPrompts,
361
+ });
362
+
363
+ // Extensions
364
+ function loadExtensions(ctx: LoadContext): LoadResult<Extension> {
365
+ const items: Extension[] = [];
366
+ const warnings: string[] = [];
367
+
368
+ for (const { dir, level } of getConfigDirs(ctx)) {
369
+ const extensionsDir = join(dir, "extensions");
370
+ if (!ctx.fs.isDir(extensionsDir)) continue;
371
+
372
+ for (const name of ctx.fs.readDir(extensionsDir)) {
373
+ if (name.startsWith(".")) continue;
374
+
375
+ const extDir = join(extensionsDir, name);
376
+ if (!ctx.fs.isDir(extDir)) continue;
377
+
378
+ const manifestPath = join(extDir, "gemini-extension.json");
379
+ const content = ctx.fs.readFile(manifestPath);
380
+ if (!content) continue;
381
+
382
+ const manifest = parseJSON<ExtensionManifest>(content);
383
+ if (!manifest) {
384
+ warnings.push(`Failed to parse ${manifestPath}`);
385
+ continue;
386
+ }
387
+
388
+ items.push({
389
+ name: manifest.name || name,
390
+ path: extDir,
391
+ manifest,
392
+ level,
393
+ _source: createSourceMeta(PROVIDER_ID, manifestPath, level),
394
+ });
395
+ }
396
+ }
397
+
398
+ return { items, warnings };
399
+ }
400
+
401
+ registerProvider<Extension>(extensionCapability.id, {
402
+ id: PROVIDER_ID,
403
+ displayName: DISPLAY_NAME,
404
+ description: DESCRIPTION,
405
+ priority: PRIORITY,
406
+ load: loadExtensions,
407
+ });
408
+
409
+ // Instructions
410
+ function loadInstructions(ctx: LoadContext): LoadResult<Instruction> {
411
+ const items: Instruction[] = [];
412
+ const warnings: string[] = [];
413
+
414
+ for (const { dir, level } of getConfigDirs(ctx)) {
415
+ const instructionsDir = join(dir, "instructions");
416
+ const result = loadFilesFromDir<Instruction>(ctx, instructionsDir, PROVIDER_ID, level, {
417
+ extensions: ["md"],
418
+ transform: (name, content, path, source) => {
419
+ const { frontmatter, body } = parseFrontmatter(content);
420
+ return {
421
+ name: name.replace(/\.instructions\.md$/, "").replace(/\.md$/, ""),
422
+ path,
423
+ content: body,
424
+ applyTo: frontmatter.applyTo as string | undefined,
425
+ _source: source,
426
+ };
427
+ },
428
+ });
429
+ items.push(...result.items);
430
+ if (result.warnings) warnings.push(...result.warnings);
431
+ }
432
+
433
+ return { items, warnings };
434
+ }
435
+
436
+ registerProvider<Instruction>(instructionCapability.id, {
437
+ id: PROVIDER_ID,
438
+ displayName: DISPLAY_NAME,
439
+ description: DESCRIPTION,
440
+ priority: PRIORITY,
441
+ load: loadInstructions,
442
+ });
443
+
444
+ // Hooks
445
+ function loadHooks(ctx: LoadContext): LoadResult<Hook> {
446
+ const items: Hook[] = [];
447
+
448
+ for (const { dir, level } of getConfigDirs(ctx)) {
449
+ const hooksDir = join(dir, "hooks");
450
+ if (!ctx.fs.isDir(hooksDir)) continue;
451
+
452
+ for (const hookType of ["pre", "post"] as const) {
453
+ const typeDir = join(hooksDir, hookType);
454
+ if (!ctx.fs.isDir(typeDir)) continue;
455
+
456
+ for (const name of ctx.fs.readDir(typeDir)) {
457
+ if (name.startsWith(".")) continue;
458
+
459
+ const path = join(typeDir, name);
460
+ if (!ctx.fs.isFile(path)) continue;
461
+
462
+ const baseName = name.includes(".") ? name.slice(0, name.lastIndexOf(".")) : name;
463
+ const tool = baseName === "*" ? "*" : baseName;
464
+
465
+ items.push({
466
+ name,
467
+ path,
468
+ type: hookType,
469
+ tool,
470
+ level,
471
+ _source: createSourceMeta(PROVIDER_ID, path, level),
472
+ });
473
+ }
474
+ }
475
+ }
476
+
477
+ return { items, warnings: [] };
478
+ }
479
+
480
+ registerProvider<Hook>(hookCapability.id, {
481
+ id: PROVIDER_ID,
482
+ displayName: DISPLAY_NAME,
483
+ description: DESCRIPTION,
484
+ priority: PRIORITY,
485
+ load: loadHooks,
486
+ });
487
+
488
+ // Custom Tools
489
+ function loadTools(ctx: LoadContext): LoadResult<CustomTool> {
490
+ const items: CustomTool[] = [];
491
+ const warnings: string[] = [];
492
+
493
+ for (const { dir, level } of getConfigDirs(ctx)) {
494
+ const toolsDir = join(dir, "tools");
495
+ if (!ctx.fs.isDir(toolsDir)) continue;
496
+
497
+ // Load tool files (JSON and Markdown declarative tools)
498
+ const result = loadFilesFromDir<CustomTool>(ctx, toolsDir, PROVIDER_ID, level, {
499
+ extensions: ["json", "md"],
500
+ transform: (name, content, path, source) => {
501
+ if (name.endsWith(".json")) {
502
+ const data = parseJSON<{ name?: string; description?: string }>(content);
503
+ return {
504
+ name: data?.name || name.replace(/\.json$/, ""),
505
+ path,
506
+ description: data?.description,
507
+ level,
508
+ _source: source,
509
+ };
510
+ }
511
+ const { frontmatter } = parseFrontmatter(content);
512
+ return {
513
+ name: (frontmatter.name as string) || name.replace(/\.md$/, ""),
514
+ path,
515
+ description: frontmatter.description as string | undefined,
516
+ level,
517
+ _source: source,
518
+ };
519
+ },
520
+ });
521
+ items.push(...result.items);
522
+ if (result.warnings) warnings.push(...result.warnings);
523
+
524
+ // Load TypeScript tools from subdirectories (tools/mytool/index.ts pattern)
525
+ for (const name of ctx.fs.readDir(toolsDir)) {
526
+ if (name.startsWith(".")) continue;
527
+
528
+ const subDir = join(toolsDir, name);
529
+ if (!ctx.fs.isDir(subDir)) continue;
530
+
531
+ const indexPath = join(subDir, "index.ts");
532
+ if (ctx.fs.isFile(indexPath)) {
533
+ items.push({
534
+ name,
535
+ path: indexPath,
536
+ description: undefined,
537
+ level,
538
+ _source: createSourceMeta(PROVIDER_ID, indexPath, level),
539
+ });
540
+ }
541
+ }
542
+ }
543
+
544
+ return { items, warnings };
545
+ }
546
+
547
+ registerProvider<CustomTool>(toolCapability.id, {
548
+ id: PROVIDER_ID,
549
+ displayName: DISPLAY_NAME,
550
+ description: DESCRIPTION,
551
+ priority: PRIORITY,
552
+ load: loadTools,
553
+ });
554
+
555
+ // Settings
556
+ function loadSettings(ctx: LoadContext): LoadResult<Settings> {
557
+ const items: Settings[] = [];
558
+ const warnings: string[] = [];
559
+
560
+ for (const { dir, level } of getConfigDirs(ctx)) {
561
+ const settingsPath = join(dir, "settings.json");
562
+ const content = ctx.fs.readFile(settingsPath);
563
+ if (!content) continue;
564
+
565
+ const data = parseJSON<Record<string, unknown>>(content);
566
+ if (!data) {
567
+ warnings.push(`Failed to parse ${settingsPath}`);
568
+ continue;
569
+ }
570
+
571
+ items.push({
572
+ path: settingsPath,
573
+ data,
574
+ level,
575
+ _source: createSourceMeta(PROVIDER_ID, settingsPath, level),
576
+ });
577
+ }
578
+
579
+ return { items, warnings };
580
+ }
581
+
582
+ registerProvider<Settings>(settingsCapability.id, {
583
+ id: PROVIDER_ID,
584
+ displayName: DISPLAY_NAME,
585
+ description: DESCRIPTION,
586
+ priority: PRIORITY,
587
+ load: loadSettings,
588
+ });
589
+
590
+ // Context Files (AGENTS.md)
591
+ function loadContextFiles(ctx: LoadContext): LoadResult<ContextFile> {
592
+ const items: ContextFile[] = [];
593
+ const warnings: string[] = [];
594
+
595
+ // User level: ~/.omp/agent/AGENTS.md or ~/.pi/agent/AGENTS.md
596
+ for (const name of USER_DIRS) {
597
+ const userPath = join(ctx.home, name, PATHS.userAgent.replace(`${PATHS.userBase}/`, ""), "AGENTS.md");
598
+ const content = ctx.fs.readFile(userPath);
599
+ if (content) {
600
+ items.push({
601
+ path: userPath,
602
+ content,
603
+ level: "user",
604
+ _source: createSourceMeta(PROVIDER_ID, userPath, "user"),
605
+ });
606
+ break; // First match wins
607
+ }
608
+ }
609
+
610
+ // Project level: walk up looking for .omp/AGENTS.md or .pi/AGENTS.md
611
+ let current = ctx.cwd;
612
+ let depth = 0;
613
+ while (true) {
614
+ for (const name of PROJECT_DIRS) {
615
+ const configDir = join(current, name);
616
+ if (ctx.fs.isDir(configDir)) {
617
+ const projectPath = join(configDir, "AGENTS.md");
618
+ const content = ctx.fs.readFile(projectPath);
619
+ if (content) {
620
+ items.push({
621
+ path: projectPath,
622
+ content,
623
+ level: "project",
624
+ depth,
625
+ _source: createSourceMeta(PROVIDER_ID, projectPath, "project"),
626
+ });
627
+ return { items, warnings }; // First config dir wins
628
+ }
629
+ }
630
+ }
631
+ const parent = dirname(current);
632
+ if (parent === current) break;
633
+ current = parent;
634
+ depth++;
635
+ }
636
+
637
+ return { items, warnings };
638
+ }
639
+
640
+ registerProvider<ContextFile>(contextFileCapability.id, {
641
+ id: PROVIDER_ID,
642
+ displayName: DISPLAY_NAME,
643
+ description: "Load AGENTS.md from .omp/ directories",
644
+ priority: PRIORITY,
645
+ load: loadContextFiles,
646
+ });