@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,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cline Provider
|
|
3
|
+
*
|
|
4
|
+
* Loads rules from .clinerules (can be single file or directory with *.md files).
|
|
5
|
+
* Project-only (no user-level config).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { registerProvider } from "../capability/index";
|
|
9
|
+
import type { Rule } from "../capability/rule";
|
|
10
|
+
import { ruleCapability } from "../capability/rule";
|
|
11
|
+
import type { LoadContext, LoadResult } from "../capability/types";
|
|
12
|
+
import { createSourceMeta, loadFilesFromDir, parseFrontmatter } from "./helpers";
|
|
13
|
+
|
|
14
|
+
const PROVIDER_ID = "cline";
|
|
15
|
+
const DISPLAY_NAME = "Cline";
|
|
16
|
+
const PRIORITY = 40;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Load rules from .clinerules
|
|
20
|
+
*/
|
|
21
|
+
function loadRules(ctx: LoadContext): LoadResult<Rule> {
|
|
22
|
+
const items: Rule[] = [];
|
|
23
|
+
const warnings: string[] = [];
|
|
24
|
+
|
|
25
|
+
// Project-level only (Cline uses root-level .clinerules)
|
|
26
|
+
const projectPath = ctx.fs.walkUp(".clinerules");
|
|
27
|
+
if (!projectPath) {
|
|
28
|
+
return { items, warnings };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Check if .clinerules is a directory or file
|
|
32
|
+
if (ctx.fs.isDir(projectPath)) {
|
|
33
|
+
// Directory format: load all *.md files
|
|
34
|
+
const result = loadFilesFromDir(ctx, projectPath, PROVIDER_ID, "project", {
|
|
35
|
+
extensions: ["md"],
|
|
36
|
+
transform: (name, content, path, source) => {
|
|
37
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
38
|
+
const ruleName = name.replace(/\.md$/, "");
|
|
39
|
+
|
|
40
|
+
// Parse globs (can be array or single string)
|
|
41
|
+
let globs: string[] | undefined;
|
|
42
|
+
if (Array.isArray(frontmatter.globs)) {
|
|
43
|
+
globs = frontmatter.globs.filter((g): g is string => typeof g === "string");
|
|
44
|
+
} else if (typeof frontmatter.globs === "string") {
|
|
45
|
+
globs = [frontmatter.globs];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
name: ruleName,
|
|
50
|
+
path,
|
|
51
|
+
content: body,
|
|
52
|
+
globs,
|
|
53
|
+
alwaysApply: typeof frontmatter.alwaysApply === "boolean" ? frontmatter.alwaysApply : undefined,
|
|
54
|
+
description: typeof frontmatter.description === "string" ? frontmatter.description : undefined,
|
|
55
|
+
_source: source,
|
|
56
|
+
};
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
items.push(...result.items);
|
|
61
|
+
if (result.warnings) warnings.push(...result.warnings);
|
|
62
|
+
} else if (ctx.fs.isFile(projectPath)) {
|
|
63
|
+
// Single file format
|
|
64
|
+
const content = ctx.fs.readFile(projectPath);
|
|
65
|
+
if (content === null) {
|
|
66
|
+
warnings.push(`Failed to read .clinerules at ${projectPath}`);
|
|
67
|
+
return { items, warnings };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
71
|
+
const source = createSourceMeta(PROVIDER_ID, projectPath, "project");
|
|
72
|
+
|
|
73
|
+
// Parse globs (can be array or single string)
|
|
74
|
+
let globs: string[] | undefined;
|
|
75
|
+
if (Array.isArray(frontmatter.globs)) {
|
|
76
|
+
globs = frontmatter.globs.filter((g): g is string => typeof g === "string");
|
|
77
|
+
} else if (typeof frontmatter.globs === "string") {
|
|
78
|
+
globs = [frontmatter.globs];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
items.push({
|
|
82
|
+
name: "clinerules",
|
|
83
|
+
path: projectPath,
|
|
84
|
+
content: body,
|
|
85
|
+
globs,
|
|
86
|
+
alwaysApply: typeof frontmatter.alwaysApply === "boolean" ? frontmatter.alwaysApply : undefined,
|
|
87
|
+
description: typeof frontmatter.description === "string" ? frontmatter.description : undefined,
|
|
88
|
+
_source: source,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return { items, warnings };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Register provider
|
|
96
|
+
registerProvider<Rule>(ruleCapability.id, {
|
|
97
|
+
id: PROVIDER_ID,
|
|
98
|
+
displayName: DISPLAY_NAME,
|
|
99
|
+
description: "Load rules from .clinerules (single file or directory)",
|
|
100
|
+
priority: PRIORITY,
|
|
101
|
+
load: loadRules,
|
|
102
|
+
});
|
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex Discovery Provider
|
|
3
|
+
*
|
|
4
|
+
* Loads configuration from OpenAI Codex format:
|
|
5
|
+
* - System Instructions: AGENTS.md (user-level only at ~/.codex/AGENTS.md)
|
|
6
|
+
*
|
|
7
|
+
* User directory: ~/.codex
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { parse as parseToml } from "smol-toml";
|
|
12
|
+
import type { ContextFile } from "../capability/context-file";
|
|
13
|
+
import { contextFileCapability } from "../capability/context-file";
|
|
14
|
+
import type { Hook } from "../capability/hook";
|
|
15
|
+
import { hookCapability } from "../capability/hook";
|
|
16
|
+
import { registerProvider } from "../capability/index";
|
|
17
|
+
import type { MCPServer } from "../capability/mcp";
|
|
18
|
+
import { mcpCapability } from "../capability/mcp";
|
|
19
|
+
import type { Prompt } from "../capability/prompt";
|
|
20
|
+
import { promptCapability } from "../capability/prompt";
|
|
21
|
+
import type { Settings } from "../capability/settings";
|
|
22
|
+
import { settingsCapability } from "../capability/settings";
|
|
23
|
+
import type { Skill } from "../capability/skill";
|
|
24
|
+
import { skillCapability } from "../capability/skill";
|
|
25
|
+
import type { SlashCommand } from "../capability/slash-command";
|
|
26
|
+
import { slashCommandCapability } from "../capability/slash-command";
|
|
27
|
+
import type { CustomTool } from "../capability/tool";
|
|
28
|
+
import { toolCapability } from "../capability/tool";
|
|
29
|
+
import type { LoadContext, LoadResult } from "../capability/types";
|
|
30
|
+
import { createSourceMeta, loadFilesFromDir, parseFrontmatter, SOURCE_PATHS } from "./helpers";
|
|
31
|
+
|
|
32
|
+
const PROVIDER_ID = "codex";
|
|
33
|
+
const DISPLAY_NAME = "OpenAI Codex";
|
|
34
|
+
const PRIORITY = 70;
|
|
35
|
+
|
|
36
|
+
// =============================================================================
|
|
37
|
+
// Context Files (AGENTS.md)
|
|
38
|
+
// =============================================================================
|
|
39
|
+
|
|
40
|
+
function loadContextFiles(ctx: LoadContext): LoadResult<ContextFile> {
|
|
41
|
+
const items: ContextFile[] = [];
|
|
42
|
+
const warnings: string[] = [];
|
|
43
|
+
|
|
44
|
+
// User level only: ~/.codex/AGENTS.md
|
|
45
|
+
const userBase = join(ctx.home, SOURCE_PATHS.codex.userBase);
|
|
46
|
+
if (ctx.fs.isDir(userBase)) {
|
|
47
|
+
const agentsMd = join(userBase, "AGENTS.md");
|
|
48
|
+
const agentsContent = ctx.fs.readFile(agentsMd);
|
|
49
|
+
if (agentsContent) {
|
|
50
|
+
items.push({
|
|
51
|
+
path: agentsMd,
|
|
52
|
+
content: agentsContent,
|
|
53
|
+
level: "user",
|
|
54
|
+
_source: createSourceMeta(PROVIDER_ID, agentsMd, "user"),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return { items, warnings };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// =============================================================================
|
|
63
|
+
// MCP Servers (config.toml)
|
|
64
|
+
// =============================================================================
|
|
65
|
+
|
|
66
|
+
function loadMCPServers(ctx: LoadContext): LoadResult<MCPServer> {
|
|
67
|
+
const items: MCPServer[] = [];
|
|
68
|
+
const warnings: string[] = [];
|
|
69
|
+
|
|
70
|
+
// User level: ~/.codex/config.toml
|
|
71
|
+
const userConfigPath = join(ctx.home, SOURCE_PATHS.codex.userBase, "config.toml");
|
|
72
|
+
const userConfig = loadTomlConfig(ctx, userConfigPath);
|
|
73
|
+
if (userConfig) {
|
|
74
|
+
const servers = extractMCPServersFromToml(userConfig);
|
|
75
|
+
for (const [name, config] of Object.entries(servers)) {
|
|
76
|
+
items.push({
|
|
77
|
+
name,
|
|
78
|
+
...config,
|
|
79
|
+
_source: createSourceMeta(PROVIDER_ID, userConfigPath, "user"),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Project level: .codex/config.toml
|
|
85
|
+
const codexDir = ctx.fs.walkUp(".codex", { dir: true });
|
|
86
|
+
if (codexDir) {
|
|
87
|
+
const projectConfigPath = join(codexDir, "config.toml");
|
|
88
|
+
const projectConfig = loadTomlConfig(ctx, projectConfigPath);
|
|
89
|
+
if (projectConfig) {
|
|
90
|
+
const servers = extractMCPServersFromToml(projectConfig);
|
|
91
|
+
for (const [name, config] of Object.entries(servers)) {
|
|
92
|
+
items.push({
|
|
93
|
+
name,
|
|
94
|
+
...config,
|
|
95
|
+
_source: createSourceMeta(PROVIDER_ID, projectConfigPath, "project"),
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return { items, warnings };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function loadTomlConfig(ctx: LoadContext, path: string): Record<string, unknown> | null {
|
|
105
|
+
const content = ctx.fs.readFile(path);
|
|
106
|
+
if (!content) return null;
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
return parseToml(content) as Record<string, unknown>;
|
|
110
|
+
} catch (_err) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Codex MCP server config format (from config.toml) */
|
|
116
|
+
interface CodexMCPConfig {
|
|
117
|
+
command?: string;
|
|
118
|
+
args?: string[];
|
|
119
|
+
env?: Record<string, string>;
|
|
120
|
+
env_vars?: string[]; // Environment variable names to forward from parent
|
|
121
|
+
url?: string;
|
|
122
|
+
http_headers?: Record<string, string>;
|
|
123
|
+
env_http_headers?: Record<string, string>; // Header name -> env var name
|
|
124
|
+
bearer_token_env_var?: string;
|
|
125
|
+
cwd?: string;
|
|
126
|
+
startup_timeout_sec?: number;
|
|
127
|
+
tool_timeout_sec?: number;
|
|
128
|
+
enabled_tools?: string[];
|
|
129
|
+
disabled_tools?: string[];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function extractMCPServersFromToml(toml: Record<string, unknown>): Record<string, Partial<MCPServer>> {
|
|
133
|
+
// Check for [mcp_servers.*] sections (Codex format)
|
|
134
|
+
if (!toml.mcp_servers || typeof toml.mcp_servers !== "object") {
|
|
135
|
+
return {};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const codexServers = toml.mcp_servers as Record<string, CodexMCPConfig>;
|
|
139
|
+
const result: Record<string, Partial<MCPServer>> = {};
|
|
140
|
+
|
|
141
|
+
for (const [name, config] of Object.entries(codexServers)) {
|
|
142
|
+
const server: Partial<MCPServer> = {
|
|
143
|
+
command: config.command,
|
|
144
|
+
args: config.args,
|
|
145
|
+
url: config.url,
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// Build env by merging explicit env and forwarded env_vars
|
|
149
|
+
const env: Record<string, string> = { ...config.env };
|
|
150
|
+
if (config.env_vars) {
|
|
151
|
+
for (const varName of config.env_vars) {
|
|
152
|
+
const value = process.env[varName];
|
|
153
|
+
if (value !== undefined) {
|
|
154
|
+
env[varName] = value;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (Object.keys(env).length > 0) {
|
|
159
|
+
server.env = env;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Build headers from http_headers, env_http_headers, and bearer_token_env_var
|
|
163
|
+
const headers: Record<string, string> = { ...config.http_headers };
|
|
164
|
+
if (config.env_http_headers) {
|
|
165
|
+
for (const [headerName, envVarName] of Object.entries(config.env_http_headers)) {
|
|
166
|
+
const value = process.env[envVarName];
|
|
167
|
+
if (value !== undefined) {
|
|
168
|
+
headers[headerName] = value;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (config.bearer_token_env_var) {
|
|
173
|
+
const token = process.env[config.bearer_token_env_var];
|
|
174
|
+
if (token) {
|
|
175
|
+
headers.Authorization = `Bearer ${token}`;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (Object.keys(headers).length > 0) {
|
|
179
|
+
server.headers = headers;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Determine transport type (infer from config if not explicit)
|
|
183
|
+
if (config.url) {
|
|
184
|
+
server.transport = "http";
|
|
185
|
+
} else if (config.command) {
|
|
186
|
+
server.transport = "stdio";
|
|
187
|
+
}
|
|
188
|
+
// Note: validation of transport vs endpoint is handled by mcpCapability.validate()
|
|
189
|
+
|
|
190
|
+
result[name] = server;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return result;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// =============================================================================
|
|
197
|
+
// Skills (skills/)
|
|
198
|
+
// =============================================================================
|
|
199
|
+
|
|
200
|
+
function loadSkills(ctx: LoadContext): LoadResult<Skill> {
|
|
201
|
+
const items: Skill[] = [];
|
|
202
|
+
const warnings: string[] = [];
|
|
203
|
+
|
|
204
|
+
// User level: ~/.codex/skills/
|
|
205
|
+
const userSkillsDir = join(ctx.home, SOURCE_PATHS.codex.userBase, "skills");
|
|
206
|
+
const userResult = loadFilesFromDir(ctx, userSkillsDir, PROVIDER_ID, "user", {
|
|
207
|
+
extensions: ["md"],
|
|
208
|
+
recursive: true,
|
|
209
|
+
transform: (name, content, path, source) => {
|
|
210
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
211
|
+
const skillName = frontmatter.name || name.replace(/\.md$/, "");
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
name: String(skillName),
|
|
215
|
+
path,
|
|
216
|
+
content: body,
|
|
217
|
+
frontmatter,
|
|
218
|
+
level: "user" as const,
|
|
219
|
+
_source: source,
|
|
220
|
+
};
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
items.push(...userResult.items);
|
|
224
|
+
warnings.push(...(userResult.warnings || []));
|
|
225
|
+
|
|
226
|
+
// Project level: .codex/skills/
|
|
227
|
+
const codexDir = ctx.fs.walkUp(".codex", { dir: true });
|
|
228
|
+
if (codexDir) {
|
|
229
|
+
const projectSkillsDir = join(codexDir, "skills");
|
|
230
|
+
const projectResult = loadFilesFromDir(ctx, projectSkillsDir, PROVIDER_ID, "project", {
|
|
231
|
+
extensions: ["md"],
|
|
232
|
+
recursive: true,
|
|
233
|
+
transform: (name, content, path, source) => {
|
|
234
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
235
|
+
const skillName = frontmatter.name || name.replace(/\.md$/, "");
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
name: String(skillName),
|
|
239
|
+
path,
|
|
240
|
+
content: body,
|
|
241
|
+
frontmatter,
|
|
242
|
+
level: "project" as const,
|
|
243
|
+
_source: source,
|
|
244
|
+
};
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
items.push(...projectResult.items);
|
|
248
|
+
warnings.push(...(projectResult.warnings || []));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return { items, warnings };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// =============================================================================
|
|
255
|
+
// Slash Commands (commands/)
|
|
256
|
+
// =============================================================================
|
|
257
|
+
|
|
258
|
+
function loadSlashCommands(ctx: LoadContext): LoadResult<SlashCommand> {
|
|
259
|
+
const items: SlashCommand[] = [];
|
|
260
|
+
const warnings: string[] = [];
|
|
261
|
+
|
|
262
|
+
// User level: ~/.codex/commands/
|
|
263
|
+
const userCommandsDir = join(ctx.home, SOURCE_PATHS.codex.userBase, "commands");
|
|
264
|
+
const userResult = loadFilesFromDir(ctx, userCommandsDir, PROVIDER_ID, "user", {
|
|
265
|
+
extensions: ["md"],
|
|
266
|
+
transform: (name, content, path, source) => {
|
|
267
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
268
|
+
const commandName = frontmatter.name || name.replace(/\.md$/, "");
|
|
269
|
+
|
|
270
|
+
return {
|
|
271
|
+
name: String(commandName),
|
|
272
|
+
path,
|
|
273
|
+
content: body,
|
|
274
|
+
level: "user" as const,
|
|
275
|
+
_source: source,
|
|
276
|
+
};
|
|
277
|
+
},
|
|
278
|
+
});
|
|
279
|
+
items.push(...userResult.items);
|
|
280
|
+
warnings.push(...(userResult.warnings || []));
|
|
281
|
+
|
|
282
|
+
// Project level: .codex/commands/
|
|
283
|
+
const codexDir = ctx.fs.walkUp(".codex", { dir: true });
|
|
284
|
+
if (codexDir) {
|
|
285
|
+
const projectCommandsDir = join(codexDir, "commands");
|
|
286
|
+
const projectResult = loadFilesFromDir(ctx, projectCommandsDir, PROVIDER_ID, "project", {
|
|
287
|
+
extensions: ["md"],
|
|
288
|
+
transform: (name, content, path, source) => {
|
|
289
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
290
|
+
const commandName = frontmatter.name || name.replace(/\.md$/, "");
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
name: String(commandName),
|
|
294
|
+
path,
|
|
295
|
+
content: body,
|
|
296
|
+
level: "project" as const,
|
|
297
|
+
_source: source,
|
|
298
|
+
};
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
items.push(...projectResult.items);
|
|
302
|
+
warnings.push(...(projectResult.warnings || []));
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return { items, warnings };
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// =============================================================================
|
|
309
|
+
// Prompts (prompts/*.md)
|
|
310
|
+
// =============================================================================
|
|
311
|
+
|
|
312
|
+
function loadPrompts(ctx: LoadContext): LoadResult<Prompt> {
|
|
313
|
+
const items: Prompt[] = [];
|
|
314
|
+
const warnings: string[] = [];
|
|
315
|
+
|
|
316
|
+
// User level: ~/.codex/prompts/
|
|
317
|
+
const userPromptsDir = join(ctx.home, SOURCE_PATHS.codex.userBase, "prompts");
|
|
318
|
+
const userResult = loadFilesFromDir(ctx, userPromptsDir, PROVIDER_ID, "user", {
|
|
319
|
+
extensions: ["md"],
|
|
320
|
+
transform: (name, content, path, source) => {
|
|
321
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
322
|
+
const promptName = frontmatter.name || name.replace(/\.md$/, "");
|
|
323
|
+
|
|
324
|
+
return {
|
|
325
|
+
name: String(promptName),
|
|
326
|
+
path,
|
|
327
|
+
content: body,
|
|
328
|
+
description: frontmatter.description ? String(frontmatter.description) : undefined,
|
|
329
|
+
_source: source,
|
|
330
|
+
};
|
|
331
|
+
},
|
|
332
|
+
});
|
|
333
|
+
items.push(...userResult.items);
|
|
334
|
+
warnings.push(...(userResult.warnings || []));
|
|
335
|
+
|
|
336
|
+
// Project level: .codex/prompts/
|
|
337
|
+
const codexDir = ctx.fs.walkUp(".codex", { dir: true });
|
|
338
|
+
if (codexDir) {
|
|
339
|
+
const projectPromptsDir = join(codexDir, "prompts");
|
|
340
|
+
const projectResult = loadFilesFromDir(ctx, projectPromptsDir, PROVIDER_ID, "project", {
|
|
341
|
+
extensions: ["md"],
|
|
342
|
+
transform: (name, content, path, source) => {
|
|
343
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
344
|
+
const promptName = frontmatter.name || name.replace(/\.md$/, "");
|
|
345
|
+
|
|
346
|
+
return {
|
|
347
|
+
name: String(promptName),
|
|
348
|
+
path,
|
|
349
|
+
content: body,
|
|
350
|
+
description: frontmatter.description ? String(frontmatter.description) : undefined,
|
|
351
|
+
_source: source,
|
|
352
|
+
};
|
|
353
|
+
},
|
|
354
|
+
});
|
|
355
|
+
items.push(...projectResult.items);
|
|
356
|
+
warnings.push(...(projectResult.warnings || []));
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return { items, warnings };
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// =============================================================================
|
|
363
|
+
// Hooks (hooks/)
|
|
364
|
+
// =============================================================================
|
|
365
|
+
|
|
366
|
+
function loadHooks(ctx: LoadContext): LoadResult<Hook> {
|
|
367
|
+
const items: Hook[] = [];
|
|
368
|
+
const warnings: string[] = [];
|
|
369
|
+
|
|
370
|
+
// User level: ~/.codex/hooks/
|
|
371
|
+
const userHooksDir = join(ctx.home, SOURCE_PATHS.codex.userBase, "hooks");
|
|
372
|
+
const userResult = loadFilesFromDir(ctx, userHooksDir, PROVIDER_ID, "user", {
|
|
373
|
+
extensions: ["ts", "js"],
|
|
374
|
+
transform: (name, _content, path, source) => {
|
|
375
|
+
// Extract hook type and tool from filename (e.g., pre-bash.ts -> type: pre, tool: bash)
|
|
376
|
+
const baseName = name.replace(/\.(ts|js)$/, "");
|
|
377
|
+
const match = baseName.match(/^(pre|post)-(.+)$/);
|
|
378
|
+
const hookType = (match?.[1] as "pre" | "post") || "pre";
|
|
379
|
+
const toolName = match?.[2] || baseName;
|
|
380
|
+
|
|
381
|
+
return {
|
|
382
|
+
name,
|
|
383
|
+
path,
|
|
384
|
+
type: hookType,
|
|
385
|
+
tool: toolName,
|
|
386
|
+
level: "user" as const,
|
|
387
|
+
_source: source,
|
|
388
|
+
};
|
|
389
|
+
},
|
|
390
|
+
});
|
|
391
|
+
items.push(...userResult.items);
|
|
392
|
+
warnings.push(...(userResult.warnings || []));
|
|
393
|
+
|
|
394
|
+
// Project level: .codex/hooks/
|
|
395
|
+
const codexDir = ctx.fs.walkUp(".codex", { dir: true });
|
|
396
|
+
if (codexDir) {
|
|
397
|
+
const projectHooksDir = join(codexDir, "hooks");
|
|
398
|
+
const projectResult = loadFilesFromDir(ctx, projectHooksDir, PROVIDER_ID, "project", {
|
|
399
|
+
extensions: ["ts", "js"],
|
|
400
|
+
transform: (name, _content, path, source) => {
|
|
401
|
+
const baseName = name.replace(/\.(ts|js)$/, "");
|
|
402
|
+
const match = baseName.match(/^(pre|post)-(.+)$/);
|
|
403
|
+
const hookType = (match?.[1] as "pre" | "post") || "pre";
|
|
404
|
+
const toolName = match?.[2] || baseName;
|
|
405
|
+
|
|
406
|
+
return {
|
|
407
|
+
name,
|
|
408
|
+
path,
|
|
409
|
+
type: hookType,
|
|
410
|
+
tool: toolName,
|
|
411
|
+
level: "project" as const,
|
|
412
|
+
_source: source,
|
|
413
|
+
};
|
|
414
|
+
},
|
|
415
|
+
});
|
|
416
|
+
items.push(...projectResult.items);
|
|
417
|
+
warnings.push(...(projectResult.warnings || []));
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return { items, warnings };
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// =============================================================================
|
|
424
|
+
// Tools (tools/)
|
|
425
|
+
// =============================================================================
|
|
426
|
+
|
|
427
|
+
function loadTools(ctx: LoadContext): LoadResult<CustomTool> {
|
|
428
|
+
const items: CustomTool[] = [];
|
|
429
|
+
const warnings: string[] = [];
|
|
430
|
+
|
|
431
|
+
// User level: ~/.codex/tools/
|
|
432
|
+
const userToolsDir = join(ctx.home, SOURCE_PATHS.codex.userBase, "tools");
|
|
433
|
+
const userResult = loadFilesFromDir(ctx, userToolsDir, PROVIDER_ID, "user", {
|
|
434
|
+
extensions: ["ts", "js"],
|
|
435
|
+
transform: (name, _content, path, source) => {
|
|
436
|
+
const toolName = name.replace(/\.(ts|js)$/, "");
|
|
437
|
+
return {
|
|
438
|
+
name: toolName,
|
|
439
|
+
path,
|
|
440
|
+
level: "user" as const,
|
|
441
|
+
_source: source,
|
|
442
|
+
} as CustomTool;
|
|
443
|
+
},
|
|
444
|
+
});
|
|
445
|
+
items.push(...userResult.items);
|
|
446
|
+
warnings.push(...(userResult.warnings || []));
|
|
447
|
+
|
|
448
|
+
// Project level: .codex/tools/
|
|
449
|
+
const codexDir = ctx.fs.walkUp(".codex", { dir: true });
|
|
450
|
+
if (codexDir) {
|
|
451
|
+
const projectToolsDir = join(codexDir, "tools");
|
|
452
|
+
const projectResult = loadFilesFromDir(ctx, projectToolsDir, PROVIDER_ID, "project", {
|
|
453
|
+
extensions: ["ts", "js"],
|
|
454
|
+
transform: (name, _content, path, source) => {
|
|
455
|
+
const toolName = name.replace(/\.(ts|js)$/, "");
|
|
456
|
+
return {
|
|
457
|
+
name: toolName,
|
|
458
|
+
path,
|
|
459
|
+
level: "project" as const,
|
|
460
|
+
_source: source,
|
|
461
|
+
} as CustomTool;
|
|
462
|
+
},
|
|
463
|
+
});
|
|
464
|
+
items.push(...projectResult.items);
|
|
465
|
+
warnings.push(...(projectResult.warnings || []));
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
return { items, warnings };
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// =============================================================================
|
|
472
|
+
// Settings (config.toml)
|
|
473
|
+
// =============================================================================
|
|
474
|
+
|
|
475
|
+
function loadSettings(ctx: LoadContext): LoadResult<Settings> {
|
|
476
|
+
const items: Settings[] = [];
|
|
477
|
+
const warnings: string[] = [];
|
|
478
|
+
|
|
479
|
+
// User level: ~/.codex/config.toml
|
|
480
|
+
const userConfigPath = join(ctx.home, SOURCE_PATHS.codex.userBase, "config.toml");
|
|
481
|
+
const userConfig = loadTomlConfig(ctx, userConfigPath);
|
|
482
|
+
if (userConfig) {
|
|
483
|
+
items.push({
|
|
484
|
+
...userConfig,
|
|
485
|
+
_source: createSourceMeta(PROVIDER_ID, userConfigPath, "user"),
|
|
486
|
+
} as Settings);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Project level: .codex/config.toml
|
|
490
|
+
const codexDir = ctx.fs.walkUp(".codex", { dir: true });
|
|
491
|
+
if (codexDir) {
|
|
492
|
+
const projectConfigPath = join(codexDir, "config.toml");
|
|
493
|
+
const projectConfig = loadTomlConfig(ctx, projectConfigPath);
|
|
494
|
+
if (projectConfig) {
|
|
495
|
+
items.push({
|
|
496
|
+
...projectConfig,
|
|
497
|
+
_source: createSourceMeta(PROVIDER_ID, projectConfigPath, "project"),
|
|
498
|
+
} as Settings);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
return { items, warnings };
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// =============================================================================
|
|
506
|
+
// Provider Registration (executes on module import)
|
|
507
|
+
// =============================================================================
|
|
508
|
+
|
|
509
|
+
registerProvider<ContextFile>(contextFileCapability.id, {
|
|
510
|
+
id: PROVIDER_ID,
|
|
511
|
+
displayName: DISPLAY_NAME,
|
|
512
|
+
description: "Load context files from ~/.codex/AGENTS.md (user-level only)",
|
|
513
|
+
priority: PRIORITY,
|
|
514
|
+
load: loadContextFiles,
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
registerProvider<MCPServer>(mcpCapability.id, {
|
|
518
|
+
id: PROVIDER_ID,
|
|
519
|
+
displayName: DISPLAY_NAME,
|
|
520
|
+
description: "Load MCP servers from config.toml [mcp_servers.*] sections",
|
|
521
|
+
priority: PRIORITY,
|
|
522
|
+
load: loadMCPServers,
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
registerProvider<Skill>(skillCapability.id, {
|
|
526
|
+
id: PROVIDER_ID,
|
|
527
|
+
displayName: DISPLAY_NAME,
|
|
528
|
+
description: "Load skills from ~/.codex/skills and .codex/skills/",
|
|
529
|
+
priority: PRIORITY,
|
|
530
|
+
load: loadSkills,
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
registerProvider<SlashCommand>(slashCommandCapability.id, {
|
|
534
|
+
id: PROVIDER_ID,
|
|
535
|
+
displayName: DISPLAY_NAME,
|
|
536
|
+
description: "Load slash commands from ~/.codex/commands and .codex/commands/",
|
|
537
|
+
priority: PRIORITY,
|
|
538
|
+
load: loadSlashCommands,
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
registerProvider<Prompt>(promptCapability.id, {
|
|
542
|
+
id: PROVIDER_ID,
|
|
543
|
+
displayName: DISPLAY_NAME,
|
|
544
|
+
description: "Load prompts from ~/.codex/prompts and .codex/prompts/",
|
|
545
|
+
priority: PRIORITY,
|
|
546
|
+
load: loadPrompts,
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
registerProvider<Hook>(hookCapability.id, {
|
|
550
|
+
id: PROVIDER_ID,
|
|
551
|
+
displayName: DISPLAY_NAME,
|
|
552
|
+
description: "Load hooks from ~/.codex/hooks and .codex/hooks/",
|
|
553
|
+
priority: PRIORITY,
|
|
554
|
+
load: loadHooks,
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
registerProvider<CustomTool>(toolCapability.id, {
|
|
558
|
+
id: PROVIDER_ID,
|
|
559
|
+
displayName: DISPLAY_NAME,
|
|
560
|
+
description: "Load custom tools from ~/.codex/tools and .codex/tools/",
|
|
561
|
+
priority: PRIORITY,
|
|
562
|
+
load: loadTools,
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
registerProvider<Settings>(settingsCapability.id, {
|
|
566
|
+
id: PROVIDER_ID,
|
|
567
|
+
displayName: DISPLAY_NAME,
|
|
568
|
+
description: "Load settings from config.toml",
|
|
569
|
+
priority: PRIORITY,
|
|
570
|
+
load: loadSettings,
|
|
571
|
+
});
|