@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/cline.ts
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* Project-only (no user-level config).
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { dirname, resolve } from "node:path";
|
|
9
|
+
import { readDirEntries, readFile } from "../capability/fs";
|
|
8
10
|
import { registerProvider } from "../capability/index";
|
|
9
11
|
import type { Rule } from "../capability/rule";
|
|
10
12
|
import { ruleCapability } from "../capability/rule";
|
|
@@ -15,23 +17,41 @@ const PROVIDER_ID = "cline";
|
|
|
15
17
|
const DISPLAY_NAME = "Cline";
|
|
16
18
|
const PRIORITY = 40;
|
|
17
19
|
|
|
20
|
+
async function findClinerules(startDir: string): Promise<{ path: string; isDir: boolean } | null> {
|
|
21
|
+
let current = resolve(startDir);
|
|
22
|
+
|
|
23
|
+
while (true) {
|
|
24
|
+
const entries = await readDirEntries(current);
|
|
25
|
+
const entry = entries.find((e) => e.name === ".clinerules");
|
|
26
|
+
if (entry) {
|
|
27
|
+
return {
|
|
28
|
+
path: resolve(current, ".clinerules"),
|
|
29
|
+
isDir: entry.isDirectory(),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
const parent = dirname(current);
|
|
33
|
+
if (parent === current) return null;
|
|
34
|
+
current = parent;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
18
38
|
/**
|
|
19
39
|
* Load rules from .clinerules
|
|
20
40
|
*/
|
|
21
|
-
function loadRules(ctx: LoadContext): LoadResult<Rule
|
|
41
|
+
async function loadRules(ctx: LoadContext): Promise<LoadResult<Rule>> {
|
|
22
42
|
const items: Rule[] = [];
|
|
23
43
|
const warnings: string[] = [];
|
|
24
44
|
|
|
25
45
|
// Project-level only (Cline uses root-level .clinerules)
|
|
26
|
-
const
|
|
27
|
-
if (!
|
|
46
|
+
const found = await findClinerules(ctx.cwd);
|
|
47
|
+
if (!found) {
|
|
28
48
|
return { items, warnings };
|
|
29
49
|
}
|
|
30
50
|
|
|
31
51
|
// Check if .clinerules is a directory or file
|
|
32
|
-
if (
|
|
52
|
+
if (found.isDir) {
|
|
33
53
|
// Directory format: load all *.md files
|
|
34
|
-
const result = loadFilesFromDir(ctx,
|
|
54
|
+
const result = await loadFilesFromDir(ctx, found.path, PROVIDER_ID, "project", {
|
|
35
55
|
extensions: ["md"],
|
|
36
56
|
transform: (name, content, path, source) => {
|
|
37
57
|
const { frontmatter, body } = parseFrontmatter(content);
|
|
@@ -60,16 +80,16 @@ function loadRules(ctx: LoadContext): LoadResult<Rule> {
|
|
|
60
80
|
|
|
61
81
|
items.push(...result.items);
|
|
62
82
|
if (result.warnings) warnings.push(...result.warnings);
|
|
63
|
-
} else
|
|
83
|
+
} else {
|
|
64
84
|
// Single file format
|
|
65
|
-
const content =
|
|
85
|
+
const content = await readFile(found.path);
|
|
66
86
|
if (content === null) {
|
|
67
|
-
warnings.push(`Failed to read .clinerules at ${
|
|
87
|
+
warnings.push(`Failed to read .clinerules at ${found.path}`);
|
|
68
88
|
return { items, warnings };
|
|
69
89
|
}
|
|
70
90
|
|
|
71
91
|
const { frontmatter, body } = parseFrontmatter(content);
|
|
72
|
-
const source = createSourceMeta(PROVIDER_ID,
|
|
92
|
+
const source = createSourceMeta(PROVIDER_ID, found.path, "project");
|
|
73
93
|
|
|
74
94
|
// Parse globs (can be array or single string)
|
|
75
95
|
let globs: string[] | undefined;
|
|
@@ -81,7 +101,7 @@ function loadRules(ctx: LoadContext): LoadResult<Rule> {
|
|
|
81
101
|
|
|
82
102
|
items.push({
|
|
83
103
|
name: "clinerules",
|
|
84
|
-
path:
|
|
104
|
+
path: found.path,
|
|
85
105
|
content: body,
|
|
86
106
|
globs,
|
|
87
107
|
alwaysApply: typeof frontmatter.alwaysApply === "boolean" ? frontmatter.alwaysApply : undefined,
|
package/src/discovery/codex.ts
CHANGED
|
@@ -7,11 +7,12 @@
|
|
|
7
7
|
* User directory: ~/.codex
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { join } from "path";
|
|
10
|
+
import { join } from "node:path";
|
|
11
11
|
import { parse as parseToml } from "smol-toml";
|
|
12
12
|
import type { ContextFile } from "../capability/context-file";
|
|
13
13
|
import { contextFileCapability } from "../capability/context-file";
|
|
14
14
|
import { type ExtensionModule, extensionModuleCapability } from "../capability/extension-module";
|
|
15
|
+
import { readFile } from "../capability/fs";
|
|
15
16
|
import type { Hook } from "../capability/hook";
|
|
16
17
|
import { hookCapability } from "../capability/hook";
|
|
17
18
|
import { registerProvider } from "../capability/index";
|
|
@@ -42,27 +43,28 @@ const PROVIDER_ID = "codex";
|
|
|
42
43
|
const DISPLAY_NAME = "OpenAI Codex";
|
|
43
44
|
const PRIORITY = 70;
|
|
44
45
|
|
|
46
|
+
function getProjectCodexDir(ctx: LoadContext): string {
|
|
47
|
+
return join(ctx.cwd, ".codex");
|
|
48
|
+
}
|
|
49
|
+
|
|
45
50
|
// =============================================================================
|
|
46
51
|
// Context Files (AGENTS.md)
|
|
47
52
|
// =============================================================================
|
|
48
53
|
|
|
49
|
-
function loadContextFiles(ctx: LoadContext): LoadResult<ContextFile
|
|
54
|
+
async function loadContextFiles(ctx: LoadContext): Promise<LoadResult<ContextFile>> {
|
|
50
55
|
const items: ContextFile[] = [];
|
|
51
56
|
const warnings: string[] = [];
|
|
52
57
|
|
|
53
58
|
// User level only: ~/.codex/AGENTS.md
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
_source: createSourceMeta(PROVIDER_ID, agentsMd, "user"),
|
|
64
|
-
});
|
|
65
|
-
}
|
|
59
|
+
const agentsMd = join(ctx.home, SOURCE_PATHS.codex.userBase, "AGENTS.md");
|
|
60
|
+
const agentsContent = await readFile(agentsMd);
|
|
61
|
+
if (agentsContent) {
|
|
62
|
+
items.push({
|
|
63
|
+
path: agentsMd,
|
|
64
|
+
content: agentsContent,
|
|
65
|
+
level: "user",
|
|
66
|
+
_source: createSourceMeta(PROVIDER_ID, agentsMd, "user"),
|
|
67
|
+
});
|
|
66
68
|
}
|
|
67
69
|
|
|
68
70
|
return { items, warnings };
|
|
@@ -72,13 +74,19 @@ function loadContextFiles(ctx: LoadContext): LoadResult<ContextFile> {
|
|
|
72
74
|
// MCP Servers (config.toml)
|
|
73
75
|
// =============================================================================
|
|
74
76
|
|
|
75
|
-
function loadMCPServers(ctx: LoadContext): LoadResult<MCPServer
|
|
76
|
-
const items: MCPServer[] = [];
|
|
77
|
+
async function loadMCPServers(ctx: LoadContext): Promise<LoadResult<MCPServer>> {
|
|
77
78
|
const warnings: string[] = [];
|
|
78
79
|
|
|
79
|
-
// User level: ~/.codex/config.toml
|
|
80
80
|
const userConfigPath = join(ctx.home, SOURCE_PATHS.codex.userBase, "config.toml");
|
|
81
|
-
const
|
|
81
|
+
const codexDir = getProjectCodexDir(ctx);
|
|
82
|
+
const projectConfigPath = join(codexDir, "config.toml");
|
|
83
|
+
|
|
84
|
+
const [userConfig, projectConfig] = await Promise.all([
|
|
85
|
+
loadTomlConfig(ctx, userConfigPath),
|
|
86
|
+
loadTomlConfig(ctx, projectConfigPath),
|
|
87
|
+
]);
|
|
88
|
+
|
|
89
|
+
const items: MCPServer[] = [];
|
|
82
90
|
if (userConfig) {
|
|
83
91
|
const servers = extractMCPServersFromToml(userConfig);
|
|
84
92
|
for (const [name, config] of Object.entries(servers)) {
|
|
@@ -89,29 +97,22 @@ function loadMCPServers(ctx: LoadContext): LoadResult<MCPServer> {
|
|
|
89
97
|
});
|
|
90
98
|
}
|
|
91
99
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
for (const [name, config] of Object.entries(servers)) {
|
|
101
|
-
items.push({
|
|
102
|
-
name,
|
|
103
|
-
...config,
|
|
104
|
-
_source: createSourceMeta(PROVIDER_ID, projectConfigPath, "project"),
|
|
105
|
-
});
|
|
106
|
-
}
|
|
100
|
+
if (projectConfig) {
|
|
101
|
+
const servers = extractMCPServersFromToml(projectConfig);
|
|
102
|
+
for (const [name, config] of Object.entries(servers)) {
|
|
103
|
+
items.push({
|
|
104
|
+
name,
|
|
105
|
+
...config,
|
|
106
|
+
_source: createSourceMeta(PROVIDER_ID, projectConfigPath, "project"),
|
|
107
|
+
});
|
|
107
108
|
}
|
|
108
109
|
}
|
|
109
110
|
|
|
110
111
|
return { items, warnings };
|
|
111
112
|
}
|
|
112
113
|
|
|
113
|
-
function loadTomlConfig(
|
|
114
|
-
const content =
|
|
114
|
+
async function loadTomlConfig(_ctx: LoadContext, path: string): Promise<Record<string, unknown> | null> {
|
|
115
|
+
const content = await readFile(path);
|
|
115
116
|
if (!content) return null;
|
|
116
117
|
|
|
117
118
|
try {
|
|
@@ -206,30 +207,26 @@ function extractMCPServersFromToml(toml: Record<string, unknown>): Record<string
|
|
|
206
207
|
// Skills (skills/)
|
|
207
208
|
// =============================================================================
|
|
208
209
|
|
|
209
|
-
function loadSkills(ctx: LoadContext): LoadResult<Skill
|
|
210
|
-
const items: Skill[] = [];
|
|
211
|
-
const warnings: string[] = [];
|
|
212
|
-
|
|
210
|
+
async function loadSkills(ctx: LoadContext): Promise<LoadResult<Skill>> {
|
|
213
211
|
const userSkillsDir = join(ctx.home, SOURCE_PATHS.codex.userBase, "skills");
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
const projectSkillsDir = join(codexDir, "skills");
|
|
225
|
-
const projectResult = loadSkillsFromDir(ctx, {
|
|
212
|
+
const codexDir = getProjectCodexDir(ctx);
|
|
213
|
+
const projectSkillsDir = join(codexDir, "skills");
|
|
214
|
+
|
|
215
|
+
const results = await Promise.all([
|
|
216
|
+
loadSkillsFromDir(ctx, {
|
|
217
|
+
dir: userSkillsDir,
|
|
218
|
+
providerId: PROVIDER_ID,
|
|
219
|
+
level: "user",
|
|
220
|
+
}),
|
|
221
|
+
loadSkillsFromDir(ctx, {
|
|
226
222
|
dir: projectSkillsDir,
|
|
227
223
|
providerId: PROVIDER_ID,
|
|
228
224
|
level: "project",
|
|
229
|
-
})
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
225
|
+
}),
|
|
226
|
+
]);
|
|
227
|
+
|
|
228
|
+
const items = results.flatMap((r) => r.items);
|
|
229
|
+
const warnings = results.flatMap((r) => r.warnings || []);
|
|
233
230
|
|
|
234
231
|
return { items, warnings };
|
|
235
232
|
}
|
|
@@ -238,34 +235,32 @@ function loadSkills(ctx: LoadContext): LoadResult<Skill> {
|
|
|
238
235
|
// Extension Modules (extensions/)
|
|
239
236
|
// =============================================================================
|
|
240
237
|
|
|
241
|
-
function loadExtensionModules(ctx: LoadContext): LoadResult<ExtensionModule
|
|
242
|
-
const items: ExtensionModule[] = [];
|
|
238
|
+
async function loadExtensionModules(ctx: LoadContext): Promise<LoadResult<ExtensionModule>> {
|
|
243
239
|
const warnings: string[] = [];
|
|
244
240
|
|
|
245
|
-
// User level: ~/.codex/extensions/
|
|
246
241
|
const userExtensionsDir = join(ctx.home, SOURCE_PATHS.codex.userBase, "extensions");
|
|
247
|
-
|
|
248
|
-
|
|
242
|
+
const codexDir = getProjectCodexDir(ctx);
|
|
243
|
+
const projectExtensionsDir = join(codexDir, "extensions");
|
|
244
|
+
|
|
245
|
+
const [userPaths, projectPaths] = await Promise.all([
|
|
246
|
+
discoverExtensionModulePaths(ctx, userExtensionsDir),
|
|
247
|
+
discoverExtensionModulePaths(ctx, projectExtensionsDir),
|
|
248
|
+
]);
|
|
249
|
+
|
|
250
|
+
const items: ExtensionModule[] = [
|
|
251
|
+
...userPaths.map((extPath) => ({
|
|
249
252
|
name: getExtensionNameFromPath(extPath),
|
|
250
253
|
path: extPath,
|
|
251
|
-
level: "user",
|
|
254
|
+
level: "user" as const,
|
|
252
255
|
_source: createSourceMeta(PROVIDER_ID, extPath, "user"),
|
|
253
|
-
})
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
items.push({
|
|
262
|
-
name: getExtensionNameFromPath(extPath),
|
|
263
|
-
path: extPath,
|
|
264
|
-
level: "project",
|
|
265
|
-
_source: createSourceMeta(PROVIDER_ID, extPath, "project"),
|
|
266
|
-
});
|
|
267
|
-
}
|
|
268
|
-
}
|
|
256
|
+
})),
|
|
257
|
+
...projectPaths.map((extPath) => ({
|
|
258
|
+
name: getExtensionNameFromPath(extPath),
|
|
259
|
+
path: extPath,
|
|
260
|
+
level: "project" as const,
|
|
261
|
+
_source: createSourceMeta(PROVIDER_ID, extPath, "project"),
|
|
262
|
+
})),
|
|
263
|
+
];
|
|
269
264
|
|
|
270
265
|
return { items, warnings };
|
|
271
266
|
}
|
|
@@ -274,52 +269,38 @@ function loadExtensionModules(ctx: LoadContext): LoadResult<ExtensionModule> {
|
|
|
274
269
|
// Slash Commands (commands/)
|
|
275
270
|
// =============================================================================
|
|
276
271
|
|
|
277
|
-
function loadSlashCommands(ctx: LoadContext): LoadResult<SlashCommand
|
|
278
|
-
const items: SlashCommand[] = [];
|
|
279
|
-
const warnings: string[] = [];
|
|
280
|
-
|
|
281
|
-
// User level: ~/.codex/commands/
|
|
272
|
+
async function loadSlashCommands(ctx: LoadContext): Promise<LoadResult<SlashCommand>> {
|
|
282
273
|
const userCommandsDir = join(ctx.home, SOURCE_PATHS.codex.userBase, "commands");
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
274
|
+
const codexDir = getProjectCodexDir(ctx);
|
|
275
|
+
const projectCommandsDir = join(codexDir, "commands");
|
|
276
|
+
|
|
277
|
+
const transformCommand =
|
|
278
|
+
(level: "user" | "project") =>
|
|
279
|
+
(name: string, content: string, path: string, source: ReturnType<typeof createSourceMeta>) => {
|
|
286
280
|
const { frontmatter, body } = parseFrontmatter(content);
|
|
287
281
|
const commandName = frontmatter.name || name.replace(/\.md$/, "");
|
|
288
|
-
|
|
289
282
|
return {
|
|
290
283
|
name: String(commandName),
|
|
291
284
|
path,
|
|
292
285
|
content: body,
|
|
293
|
-
level
|
|
286
|
+
level,
|
|
294
287
|
_source: source,
|
|
295
288
|
};
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
// Project level: .codex/commands/
|
|
302
|
-
const codexDir = ctx.fs.walkUp(".codex", { dir: true });
|
|
303
|
-
if (codexDir) {
|
|
304
|
-
const projectCommandsDir = join(codexDir, "commands");
|
|
305
|
-
const projectResult = loadFilesFromDir(ctx, projectCommandsDir, PROVIDER_ID, "project", {
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
const results = await Promise.all([
|
|
292
|
+
loadFilesFromDir(ctx, userCommandsDir, PROVIDER_ID, "user", {
|
|
306
293
|
extensions: ["md"],
|
|
307
|
-
transform: (
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
};
|
|
318
|
-
},
|
|
319
|
-
});
|
|
320
|
-
items.push(...projectResult.items);
|
|
321
|
-
warnings.push(...(projectResult.warnings || []));
|
|
322
|
-
}
|
|
294
|
+
transform: transformCommand("user"),
|
|
295
|
+
}),
|
|
296
|
+
loadFilesFromDir(ctx, projectCommandsDir, PROVIDER_ID, "project", {
|
|
297
|
+
extensions: ["md"],
|
|
298
|
+
transform: transformCommand("project"),
|
|
299
|
+
}),
|
|
300
|
+
]);
|
|
301
|
+
|
|
302
|
+
const items = results.flatMap((r) => r.items);
|
|
303
|
+
const warnings = results.flatMap((r) => r.warnings || []);
|
|
323
304
|
|
|
324
305
|
return { items, warnings };
|
|
325
306
|
}
|
|
@@ -328,52 +309,41 @@ function loadSlashCommands(ctx: LoadContext): LoadResult<SlashCommand> {
|
|
|
328
309
|
// Prompts (prompts/*.md)
|
|
329
310
|
// =============================================================================
|
|
330
311
|
|
|
331
|
-
function loadPrompts(ctx: LoadContext): LoadResult<Prompt
|
|
332
|
-
const items: Prompt[] = [];
|
|
333
|
-
const warnings: string[] = [];
|
|
334
|
-
|
|
335
|
-
// User level: ~/.codex/prompts/
|
|
312
|
+
async function loadPrompts(ctx: LoadContext): Promise<LoadResult<Prompt>> {
|
|
336
313
|
const userPromptsDir = join(ctx.home, SOURCE_PATHS.codex.userBase, "prompts");
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
314
|
+
const codexDir = getProjectCodexDir(ctx);
|
|
315
|
+
const projectPromptsDir = join(codexDir, "prompts");
|
|
316
|
+
|
|
317
|
+
const transformPrompt = (
|
|
318
|
+
name: string,
|
|
319
|
+
content: string,
|
|
320
|
+
path: string,
|
|
321
|
+
source: ReturnType<typeof createSourceMeta>,
|
|
322
|
+
) => {
|
|
323
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
324
|
+
const promptName = frontmatter.name || name.replace(/\.md$/, "");
|
|
325
|
+
return {
|
|
326
|
+
name: String(promptName),
|
|
327
|
+
path,
|
|
328
|
+
content: body,
|
|
329
|
+
description: frontmatter.description ? String(frontmatter.description) : undefined,
|
|
330
|
+
_source: source,
|
|
331
|
+
};
|
|
332
|
+
};
|
|
342
333
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
path,
|
|
346
|
-
content: body,
|
|
347
|
-
description: frontmatter.description ? String(frontmatter.description) : undefined,
|
|
348
|
-
_source: source,
|
|
349
|
-
};
|
|
350
|
-
},
|
|
351
|
-
});
|
|
352
|
-
items.push(...userResult.items);
|
|
353
|
-
warnings.push(...(userResult.warnings || []));
|
|
354
|
-
|
|
355
|
-
// Project level: .codex/prompts/
|
|
356
|
-
const codexDir = ctx.fs.walkUp(".codex", { dir: true });
|
|
357
|
-
if (codexDir) {
|
|
358
|
-
const projectPromptsDir = join(codexDir, "prompts");
|
|
359
|
-
const projectResult = loadFilesFromDir(ctx, projectPromptsDir, PROVIDER_ID, "project", {
|
|
334
|
+
const results = await Promise.all([
|
|
335
|
+
loadFilesFromDir(ctx, userPromptsDir, PROVIDER_ID, "user", {
|
|
360
336
|
extensions: ["md"],
|
|
361
|
-
transform:
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
};
|
|
372
|
-
},
|
|
373
|
-
});
|
|
374
|
-
items.push(...projectResult.items);
|
|
375
|
-
warnings.push(...(projectResult.warnings || []));
|
|
376
|
-
}
|
|
337
|
+
transform: transformPrompt,
|
|
338
|
+
}),
|
|
339
|
+
loadFilesFromDir(ctx, projectPromptsDir, PROVIDER_ID, "project", {
|
|
340
|
+
extensions: ["md"],
|
|
341
|
+
transform: transformPrompt,
|
|
342
|
+
}),
|
|
343
|
+
]);
|
|
344
|
+
|
|
345
|
+
const items = results.flatMap((r) => r.items);
|
|
346
|
+
const warnings = results.flatMap((r) => r.warnings || []);
|
|
377
347
|
|
|
378
348
|
return { items, warnings };
|
|
379
349
|
}
|
|
@@ -382,59 +352,41 @@ function loadPrompts(ctx: LoadContext): LoadResult<Prompt> {
|
|
|
382
352
|
// Hooks (hooks/)
|
|
383
353
|
// =============================================================================
|
|
384
354
|
|
|
385
|
-
function loadHooks(ctx: LoadContext): LoadResult<Hook
|
|
386
|
-
const items: Hook[] = [];
|
|
387
|
-
const warnings: string[] = [];
|
|
388
|
-
|
|
389
|
-
// User level: ~/.codex/hooks/
|
|
355
|
+
async function loadHooks(ctx: LoadContext): Promise<LoadResult<Hook>> {
|
|
390
356
|
const userHooksDir = join(ctx.home, SOURCE_PATHS.codex.userBase, "hooks");
|
|
391
|
-
const
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
357
|
+
const codexDir = getProjectCodexDir(ctx);
|
|
358
|
+
const projectHooksDir = join(codexDir, "hooks");
|
|
359
|
+
|
|
360
|
+
const transformHook =
|
|
361
|
+
(level: "user" | "project") =>
|
|
362
|
+
(name: string, _content: string, path: string, source: ReturnType<typeof createSourceMeta>) => {
|
|
395
363
|
const baseName = name.replace(/\.(ts|js)$/, "");
|
|
396
364
|
const match = baseName.match(/^(pre|post)-(.+)$/);
|
|
397
365
|
const hookType = (match?.[1] as "pre" | "post") || "pre";
|
|
398
366
|
const toolName = match?.[2] || baseName;
|
|
399
|
-
|
|
400
367
|
return {
|
|
401
368
|
name,
|
|
402
369
|
path,
|
|
403
370
|
type: hookType,
|
|
404
371
|
tool: toolName,
|
|
405
|
-
level
|
|
372
|
+
level,
|
|
406
373
|
_source: source,
|
|
407
374
|
};
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
// Project level: .codex/hooks/
|
|
414
|
-
const codexDir = ctx.fs.walkUp(".codex", { dir: true });
|
|
415
|
-
if (codexDir) {
|
|
416
|
-
const projectHooksDir = join(codexDir, "hooks");
|
|
417
|
-
const projectResult = loadFilesFromDir(ctx, projectHooksDir, PROVIDER_ID, "project", {
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
const results = await Promise.all([
|
|
378
|
+
loadFilesFromDir(ctx, userHooksDir, PROVIDER_ID, "user", {
|
|
418
379
|
extensions: ["ts", "js"],
|
|
419
|
-
transform: (
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
tool: toolName,
|
|
430
|
-
level: "project" as const,
|
|
431
|
-
_source: source,
|
|
432
|
-
};
|
|
433
|
-
},
|
|
434
|
-
});
|
|
435
|
-
items.push(...projectResult.items);
|
|
436
|
-
warnings.push(...(projectResult.warnings || []));
|
|
437
|
-
}
|
|
380
|
+
transform: transformHook("user"),
|
|
381
|
+
}),
|
|
382
|
+
loadFilesFromDir(ctx, projectHooksDir, PROVIDER_ID, "project", {
|
|
383
|
+
extensions: ["ts", "js"],
|
|
384
|
+
transform: transformHook("project"),
|
|
385
|
+
}),
|
|
386
|
+
]);
|
|
387
|
+
|
|
388
|
+
const items = results.flatMap((r) => r.items);
|
|
389
|
+
const warnings = results.flatMap((r) => r.warnings || []);
|
|
438
390
|
|
|
439
391
|
return { items, warnings };
|
|
440
392
|
}
|
|
@@ -443,46 +395,36 @@ function loadHooks(ctx: LoadContext): LoadResult<Hook> {
|
|
|
443
395
|
// Tools (tools/)
|
|
444
396
|
// =============================================================================
|
|
445
397
|
|
|
446
|
-
function loadTools(ctx: LoadContext): LoadResult<CustomTool
|
|
447
|
-
const items: CustomTool[] = [];
|
|
448
|
-
const warnings: string[] = [];
|
|
449
|
-
|
|
450
|
-
// User level: ~/.codex/tools/
|
|
398
|
+
async function loadTools(ctx: LoadContext): Promise<LoadResult<CustomTool>> {
|
|
451
399
|
const userToolsDir = join(ctx.home, SOURCE_PATHS.codex.userBase, "tools");
|
|
452
|
-
const
|
|
453
|
-
|
|
454
|
-
|
|
400
|
+
const codexDir = getProjectCodexDir(ctx);
|
|
401
|
+
const projectToolsDir = join(codexDir, "tools");
|
|
402
|
+
|
|
403
|
+
const transformTool =
|
|
404
|
+
(level: "user" | "project") =>
|
|
405
|
+
(name: string, _content: string, path: string, source: ReturnType<typeof createSourceMeta>) => {
|
|
455
406
|
const toolName = name.replace(/\.(ts|js)$/, "");
|
|
456
407
|
return {
|
|
457
408
|
name: toolName,
|
|
458
409
|
path,
|
|
459
|
-
level
|
|
410
|
+
level,
|
|
460
411
|
_source: source,
|
|
461
412
|
} as CustomTool;
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
// Project level: .codex/tools/
|
|
468
|
-
const codexDir = ctx.fs.walkUp(".codex", { dir: true });
|
|
469
|
-
if (codexDir) {
|
|
470
|
-
const projectToolsDir = join(codexDir, "tools");
|
|
471
|
-
const projectResult = loadFilesFromDir(ctx, projectToolsDir, PROVIDER_ID, "project", {
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
const results = await Promise.all([
|
|
416
|
+
loadFilesFromDir(ctx, userToolsDir, PROVIDER_ID, "user", {
|
|
472
417
|
extensions: ["ts", "js"],
|
|
473
|
-
transform: (
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
items.push(...projectResult.items);
|
|
484
|
-
warnings.push(...(projectResult.warnings || []));
|
|
485
|
-
}
|
|
418
|
+
transform: transformTool("user"),
|
|
419
|
+
}),
|
|
420
|
+
loadFilesFromDir(ctx, projectToolsDir, PROVIDER_ID, "project", {
|
|
421
|
+
extensions: ["ts", "js"],
|
|
422
|
+
transform: transformTool("project"),
|
|
423
|
+
}),
|
|
424
|
+
]);
|
|
425
|
+
|
|
426
|
+
const items = results.flatMap((r) => r.items);
|
|
427
|
+
const warnings = results.flatMap((r) => r.warnings || []);
|
|
486
428
|
|
|
487
429
|
return { items, warnings };
|
|
488
430
|
}
|
|
@@ -491,31 +433,30 @@ function loadTools(ctx: LoadContext): LoadResult<CustomTool> {
|
|
|
491
433
|
// Settings (config.toml)
|
|
492
434
|
// =============================================================================
|
|
493
435
|
|
|
494
|
-
function loadSettings(ctx: LoadContext): LoadResult<Settings
|
|
495
|
-
const items: Settings[] = [];
|
|
436
|
+
async function loadSettings(ctx: LoadContext): Promise<LoadResult<Settings>> {
|
|
496
437
|
const warnings: string[] = [];
|
|
497
438
|
|
|
498
|
-
// User level: ~/.codex/config.toml
|
|
499
439
|
const userConfigPath = join(ctx.home, SOURCE_PATHS.codex.userBase, "config.toml");
|
|
500
|
-
const
|
|
440
|
+
const codexDir = getProjectCodexDir(ctx);
|
|
441
|
+
const projectConfigPath = join(codexDir, "config.toml");
|
|
442
|
+
|
|
443
|
+
const [userConfig, projectConfig] = await Promise.all([
|
|
444
|
+
loadTomlConfig(ctx, userConfigPath),
|
|
445
|
+
loadTomlConfig(ctx, projectConfigPath),
|
|
446
|
+
]);
|
|
447
|
+
|
|
448
|
+
const items: Settings[] = [];
|
|
501
449
|
if (userConfig) {
|
|
502
450
|
items.push({
|
|
503
451
|
...userConfig,
|
|
504
452
|
_source: createSourceMeta(PROVIDER_ID, userConfigPath, "user"),
|
|
505
453
|
} as Settings);
|
|
506
454
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
const projectConfig = loadTomlConfig(ctx, projectConfigPath);
|
|
513
|
-
if (projectConfig) {
|
|
514
|
-
items.push({
|
|
515
|
-
...projectConfig,
|
|
516
|
-
_source: createSourceMeta(PROVIDER_ID, projectConfigPath, "project"),
|
|
517
|
-
} as Settings);
|
|
518
|
-
}
|
|
455
|
+
if (projectConfig) {
|
|
456
|
+
items.push({
|
|
457
|
+
...projectConfig,
|
|
458
|
+
_source: createSourceMeta(PROVIDER_ID, projectConfigPath, "project"),
|
|
459
|
+
} as Settings);
|
|
519
460
|
}
|
|
520
461
|
|
|
521
462
|
return { items, warnings };
|