@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,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
|
+
});
|