@oh-my-pi/pi-coding-agent 13.7.6 → 13.9.1
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 +43 -0
- package/package.json +7 -7
- package/scripts/generate-docs-index.ts +3 -3
- package/src/capability/context-file.ts +6 -3
- package/src/capability/fs.ts +18 -0
- package/src/capability/index.ts +3 -2
- package/src/capability/rule.ts +0 -4
- package/src/capability/types.ts +2 -0
- package/src/cli/agents-cli.ts +1 -1
- package/src/cli/args.ts +7 -12
- package/src/commands/launch.ts +3 -2
- package/src/config/model-resolver.ts +118 -33
- package/src/config/settings-schema.ts +14 -2
- package/src/config/settings.ts +1 -17
- package/src/discovery/agents-md.ts +3 -4
- package/src/discovery/agents.ts +104 -84
- package/src/discovery/builtin.ts +28 -15
- package/src/discovery/claude.ts +27 -9
- package/src/discovery/helpers.ts +10 -17
- package/src/extensibility/extensions/loader.ts +1 -2
- package/src/extensibility/extensions/types.ts +2 -1
- package/src/extensibility/skills.ts +2 -2
- package/src/internal-urls/docs-index.generated.ts +1 -1
- package/src/main.ts +21 -10
- package/src/modes/components/agent-dashboard.ts +12 -13
- package/src/modes/components/model-selector.ts +157 -59
- package/src/modes/components/read-tool-group.ts +36 -2
- package/src/modes/components/settings-defs.ts +11 -8
- package/src/modes/components/settings-selector.ts +1 -1
- package/src/modes/components/thinking-selector.ts +3 -15
- package/src/modes/controllers/selector-controller.ts +6 -4
- package/src/modes/rpc/rpc-client.ts +2 -2
- package/src/modes/rpc/rpc-types.ts +2 -2
- package/src/modes/theme/theme.ts +2 -1
- package/src/patch/hashline.ts +113 -0
- package/src/patch/index.ts +27 -18
- package/src/prompts/tools/hashline.md +9 -10
- package/src/prompts/tools/read.md +2 -2
- package/src/sdk.ts +21 -25
- package/src/session/agent-session.ts +54 -59
- package/src/task/executor.ts +10 -8
- package/src/task/types.ts +1 -2
- package/src/tools/fetch.ts +152 -4
- package/src/tools/read.ts +88 -264
- package/src/utils/frontmatter.ts +25 -4
- package/src/web/scrapers/choosealicense.ts +1 -1
package/src/discovery/agents.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Agents (standard) Provider
|
|
3
3
|
*
|
|
4
|
-
* Loads
|
|
4
|
+
* Loads skills, rules, prompts, commands, context files, and system prompts
|
|
5
|
+
* from .agent/ and .agents/ directories at both user (~/) and project levels.
|
|
6
|
+
* Project-level discovery walks up from cwd to repoRoot.
|
|
5
7
|
*/
|
|
6
8
|
import * as path from "node:path";
|
|
7
9
|
import { registerProvider } from "../capability";
|
|
@@ -13,76 +15,97 @@ import { type Skill, skillCapability } from "../capability/skill";
|
|
|
13
15
|
import { type SlashCommand, slashCommandCapability } from "../capability/slash-command";
|
|
14
16
|
import { type SystemPrompt, systemPromptCapability } from "../capability/system-prompt";
|
|
15
17
|
import type { LoadContext, LoadResult } from "../capability/types";
|
|
16
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
buildRuleFromMarkdown,
|
|
20
|
+
calculateDepth,
|
|
21
|
+
createSourceMeta,
|
|
22
|
+
loadFilesFromDir,
|
|
23
|
+
scanSkillsFromDir,
|
|
24
|
+
} from "./helpers";
|
|
17
25
|
|
|
18
26
|
const PROVIDER_ID = "agents";
|
|
19
27
|
const DISPLAY_NAME = "Agents (standard)";
|
|
20
28
|
const PRIORITY = 70;
|
|
21
|
-
const
|
|
29
|
+
const AGENT_DIR_CANDIDATES = [".agent", ".agents"] as const;
|
|
22
30
|
|
|
23
|
-
|
|
24
|
-
|
|
31
|
+
/** User-level paths: ~/.agent/<segments> and ~/.agents/<segments>. */
|
|
32
|
+
function getUserPathCandidates(ctx: LoadContext, ...segments: string[]): string[] {
|
|
33
|
+
return AGENT_DIR_CANDIDATES.map(baseDir => path.join(ctx.home, baseDir, ...segments));
|
|
25
34
|
}
|
|
26
35
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
/** Project-level paths: walk up from cwd to repoRoot, returning .agent/<segments> and .agents/<segments> at each level. */
|
|
37
|
+
function getProjectPathCandidates(ctx: LoadContext, ...segments: string[]): string[] {
|
|
38
|
+
const paths: string[] = [];
|
|
39
|
+
let current = ctx.cwd;
|
|
40
|
+
while (true) {
|
|
41
|
+
for (const baseDir of AGENT_DIR_CANDIDATES) {
|
|
42
|
+
paths.push(path.join(current, baseDir, ...segments));
|
|
43
|
+
}
|
|
44
|
+
if (current === (ctx.repoRoot ?? ctx.home)) break;
|
|
45
|
+
const parent = path.dirname(current);
|
|
46
|
+
if (parent === current) break;
|
|
47
|
+
current = parent;
|
|
38
48
|
}
|
|
49
|
+
return paths;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Skills
|
|
53
|
+
async function loadSkills(ctx: LoadContext): Promise<LoadResult<Skill>> {
|
|
54
|
+
const projectScans = getProjectPathCandidates(ctx, "skills").map(dir =>
|
|
55
|
+
scanSkillsFromDir(ctx, { dir, providerId: PROVIDER_ID, level: "project" }),
|
|
56
|
+
);
|
|
57
|
+
const userScans = getUserPathCandidates(ctx, "skills").map(dir =>
|
|
58
|
+
scanSkillsFromDir(ctx, { dir, providerId: PROVIDER_ID, level: "user" }),
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const results = await Promise.all([...projectScans, ...userScans]);
|
|
62
|
+
|
|
39
63
|
return {
|
|
40
|
-
items,
|
|
41
|
-
warnings,
|
|
64
|
+
items: results.flatMap(r => r.items),
|
|
65
|
+
warnings: results.flatMap(r => r.warnings ?? []),
|
|
42
66
|
};
|
|
43
67
|
}
|
|
44
68
|
|
|
45
69
|
registerProvider<Skill>(skillCapability.id, {
|
|
46
70
|
id: PROVIDER_ID,
|
|
47
71
|
displayName: DISPLAY_NAME,
|
|
48
|
-
description: "Load skills from
|
|
72
|
+
description: "Load skills from .agent/skills and .agents/skills (project walk-up + user home)",
|
|
49
73
|
priority: PRIORITY,
|
|
50
74
|
load: loadSkills,
|
|
51
75
|
});
|
|
52
76
|
|
|
53
77
|
// Rules
|
|
54
78
|
async function loadRules(ctx: LoadContext): Promise<LoadResult<Rule>> {
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
for (const userRulesDir of getUserAgentPathCandidates(ctx, "rules")) {
|
|
58
|
-
const result = await loadFilesFromDir<Rule>(ctx, userRulesDir, PROVIDER_ID, "user", {
|
|
79
|
+
const load = (dir: string, level: "user" | "project") =>
|
|
80
|
+
loadFilesFromDir<Rule>(ctx, dir, PROVIDER_ID, level, {
|
|
59
81
|
extensions: ["md", "mdc"],
|
|
60
82
|
transform: (name, content, filePath, source) =>
|
|
61
83
|
buildRuleFromMarkdown(name, content, filePath, source, { stripNamePattern: /\.(md|mdc)$/ }),
|
|
62
84
|
});
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
85
|
+
|
|
86
|
+
const results = await Promise.all([
|
|
87
|
+
...getProjectPathCandidates(ctx, "rules").map(dir => load(dir, "project")),
|
|
88
|
+
...getUserPathCandidates(ctx, "rules").map(dir => load(dir, "user")),
|
|
89
|
+
]);
|
|
90
|
+
|
|
66
91
|
return {
|
|
67
|
-
items,
|
|
68
|
-
warnings,
|
|
92
|
+
items: results.flatMap(r => r.items),
|
|
93
|
+
warnings: results.flatMap(r => r.warnings ?? []),
|
|
69
94
|
};
|
|
70
95
|
}
|
|
71
96
|
|
|
72
97
|
registerProvider<Rule>(ruleCapability.id, {
|
|
73
98
|
id: PROVIDER_ID,
|
|
74
99
|
displayName: DISPLAY_NAME,
|
|
75
|
-
description: "Load rules from
|
|
100
|
+
description: "Load rules from .agent/rules and .agents/rules (project walk-up + user home)",
|
|
76
101
|
priority: PRIORITY,
|
|
77
102
|
load: loadRules,
|
|
78
103
|
});
|
|
79
104
|
|
|
80
105
|
// Prompts
|
|
81
106
|
async function loadPrompts(ctx: LoadContext): Promise<LoadResult<Prompt>> {
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
for (const userPromptsDir of getUserAgentPathCandidates(ctx, "prompts")) {
|
|
85
|
-
const result = await loadFilesFromDir<Prompt>(ctx, userPromptsDir, PROVIDER_ID, "user", {
|
|
107
|
+
const load = (dir: string, level: "user" | "project") =>
|
|
108
|
+
loadFilesFromDir<Prompt>(ctx, dir, PROVIDER_ID, level, {
|
|
86
109
|
extensions: ["md"],
|
|
87
110
|
transform: (name, content, filePath, source) => ({
|
|
88
111
|
name: name.replace(/\.md$/, ""),
|
|
@@ -91,109 +114,106 @@ async function loadPrompts(ctx: LoadContext): Promise<LoadResult<Prompt>> {
|
|
|
91
114
|
_source: source,
|
|
92
115
|
}),
|
|
93
116
|
});
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
117
|
+
|
|
118
|
+
const results = await Promise.all([
|
|
119
|
+
...getProjectPathCandidates(ctx, "prompts").map(dir => load(dir, "project")),
|
|
120
|
+
...getUserPathCandidates(ctx, "prompts").map(dir => load(dir, "user")),
|
|
121
|
+
]);
|
|
122
|
+
|
|
97
123
|
return {
|
|
98
|
-
items,
|
|
99
|
-
warnings,
|
|
124
|
+
items: results.flatMap(r => r.items),
|
|
125
|
+
warnings: results.flatMap(r => r.warnings ?? []),
|
|
100
126
|
};
|
|
101
127
|
}
|
|
102
128
|
|
|
103
129
|
registerProvider<Prompt>(promptCapability.id, {
|
|
104
130
|
id: PROVIDER_ID,
|
|
105
131
|
displayName: DISPLAY_NAME,
|
|
106
|
-
description: "Load prompts from
|
|
132
|
+
description: "Load prompts from .agent/prompts and .agents/prompts (project walk-up + user home)",
|
|
107
133
|
priority: PRIORITY,
|
|
108
134
|
load: loadPrompts,
|
|
109
135
|
});
|
|
110
136
|
|
|
111
137
|
// Slash Commands
|
|
112
138
|
async function loadSlashCommands(ctx: LoadContext): Promise<LoadResult<SlashCommand>> {
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
for (const userCommandsDir of getUserAgentPathCandidates(ctx, "commands")) {
|
|
116
|
-
const result = await loadFilesFromDir<SlashCommand>(ctx, userCommandsDir, PROVIDER_ID, "user", {
|
|
139
|
+
const load = (dir: string, level: "user" | "project") =>
|
|
140
|
+
loadFilesFromDir<SlashCommand>(ctx, dir, PROVIDER_ID, level, {
|
|
117
141
|
extensions: ["md"],
|
|
118
142
|
transform: (name, content, filePath, source) => ({
|
|
119
143
|
name: name.replace(/\.md$/, ""),
|
|
120
144
|
path: filePath,
|
|
121
145
|
content,
|
|
122
|
-
level
|
|
146
|
+
level,
|
|
123
147
|
_source: source,
|
|
124
148
|
}),
|
|
125
149
|
});
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
150
|
+
|
|
151
|
+
const results = await Promise.all([
|
|
152
|
+
...getProjectPathCandidates(ctx, "commands").map(dir => load(dir, "project")),
|
|
153
|
+
...getUserPathCandidates(ctx, "commands").map(dir => load(dir, "user")),
|
|
154
|
+
]);
|
|
155
|
+
|
|
129
156
|
return {
|
|
130
|
-
items,
|
|
131
|
-
warnings,
|
|
157
|
+
items: results.flatMap(r => r.items),
|
|
158
|
+
warnings: results.flatMap(r => r.warnings ?? []),
|
|
132
159
|
};
|
|
133
160
|
}
|
|
134
161
|
|
|
135
162
|
registerProvider<SlashCommand>(slashCommandCapability.id, {
|
|
136
163
|
id: PROVIDER_ID,
|
|
137
164
|
displayName: DISPLAY_NAME,
|
|
138
|
-
description: "Load commands from
|
|
165
|
+
description: "Load commands from .agent/commands and .agents/commands (project walk-up + user home)",
|
|
139
166
|
priority: PRIORITY,
|
|
140
167
|
load: loadSlashCommands,
|
|
141
168
|
});
|
|
142
169
|
|
|
143
170
|
// Context Files (AGENTS.md)
|
|
144
171
|
async function loadContextFiles(ctx: LoadContext): Promise<LoadResult<ContextFile>> {
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
path: agentsPath,
|
|
153
|
-
content,
|
|
154
|
-
level: "user",
|
|
155
|
-
_source: createSourceMeta(PROVIDER_ID, agentsPath, "user"),
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
return {
|
|
159
|
-
items,
|
|
160
|
-
warnings: [],
|
|
172
|
+
const load = async (filePath: string, level: "user" | "project"): Promise<ContextFile | null> => {
|
|
173
|
+
const content = await readFile(filePath);
|
|
174
|
+
if (!content) return null;
|
|
175
|
+
// filePath is <ancestor>/.agent(s)/AGENTS.md — go up past the config dir to the ancestor
|
|
176
|
+
const ancestorDir = path.dirname(path.dirname(filePath));
|
|
177
|
+
const depth = level === "project" ? calculateDepth(ctx.cwd, ancestorDir, path.sep) : undefined;
|
|
178
|
+
return { path: filePath, content, level, depth, _source: createSourceMeta(PROVIDER_ID, filePath, level) };
|
|
161
179
|
};
|
|
180
|
+
|
|
181
|
+
const results = await Promise.all([
|
|
182
|
+
...getProjectPathCandidates(ctx, "AGENTS.md").map(p => load(p, "project")),
|
|
183
|
+
...getUserPathCandidates(ctx, "AGENTS.md").map(p => load(p, "user")),
|
|
184
|
+
]);
|
|
185
|
+
|
|
186
|
+
return { items: results.filter((r): r is ContextFile => r !== null), warnings: [] };
|
|
162
187
|
}
|
|
163
188
|
|
|
164
189
|
registerProvider<ContextFile>(contextFileCapability.id, {
|
|
165
190
|
id: PROVIDER_ID,
|
|
166
191
|
displayName: DISPLAY_NAME,
|
|
167
|
-
description: "Load AGENTS.md from
|
|
192
|
+
description: "Load AGENTS.md from .agent and .agents (project walk-up + user home)",
|
|
168
193
|
priority: PRIORITY,
|
|
169
194
|
load: loadContextFiles,
|
|
170
195
|
});
|
|
171
196
|
|
|
172
197
|
// System Prompt (SYSTEM.md)
|
|
173
198
|
async function loadSystemPrompt(ctx: LoadContext): Promise<LoadResult<SystemPrompt>> {
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
continue;
|
|
179
|
-
}
|
|
180
|
-
items.push({
|
|
181
|
-
path: systemPath,
|
|
182
|
-
content,
|
|
183
|
-
level: "user",
|
|
184
|
-
_source: createSourceMeta(PROVIDER_ID, systemPath, "user"),
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
return {
|
|
188
|
-
items,
|
|
189
|
-
warnings: [],
|
|
199
|
+
const load = async (filePath: string, level: "user" | "project"): Promise<SystemPrompt | null> => {
|
|
200
|
+
const content = await readFile(filePath);
|
|
201
|
+
if (!content) return null;
|
|
202
|
+
return { path: filePath, content, level, _source: createSourceMeta(PROVIDER_ID, filePath, level) };
|
|
190
203
|
};
|
|
204
|
+
|
|
205
|
+
const results = await Promise.all([
|
|
206
|
+
...getProjectPathCandidates(ctx, "SYSTEM.md").map(p => load(p, "project")),
|
|
207
|
+
...getUserPathCandidates(ctx, "SYSTEM.md").map(p => load(p, "user")),
|
|
208
|
+
]);
|
|
209
|
+
|
|
210
|
+
return { items: results.filter((r): r is SystemPrompt => r !== null), warnings: [] };
|
|
191
211
|
}
|
|
192
212
|
|
|
193
213
|
registerProvider<SystemPrompt>(systemPromptCapability.id, {
|
|
194
214
|
id: PROVIDER_ID,
|
|
195
215
|
displayName: DISPLAY_NAME,
|
|
196
|
-
description: "Load SYSTEM.md from
|
|
216
|
+
description: "Load SYSTEM.md from .agent and .agents (project walk-up + user home)",
|
|
197
217
|
priority: PRIORITY,
|
|
198
218
|
load: loadSystemPrompt,
|
|
199
219
|
});
|
package/src/discovery/builtin.ts
CHANGED
|
@@ -68,12 +68,13 @@ async function getConfigDirs(ctx: LoadContext): Promise<Array<{ dir: string; lev
|
|
|
68
68
|
return result;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
function getAncestorDirs(cwd: string): Array<{ dir: string; depth: number }> {
|
|
71
|
+
function getAncestorDirs(cwd: string, stopAt?: string | null): Array<{ dir: string; depth: number }> {
|
|
72
72
|
const ancestors: Array<{ dir: string; depth: number }> = [];
|
|
73
73
|
let current = cwd;
|
|
74
74
|
let depth = 0;
|
|
75
75
|
while (true) {
|
|
76
76
|
ancestors.push({ dir: current, depth });
|
|
77
|
+
if (stopAt && current === stopAt) break;
|
|
77
78
|
const parent = path.dirname(current);
|
|
78
79
|
if (parent === current) break;
|
|
79
80
|
current = parent;
|
|
@@ -82,8 +83,11 @@ function getAncestorDirs(cwd: string): Array<{ dir: string; depth: number }> {
|
|
|
82
83
|
return ancestors;
|
|
83
84
|
}
|
|
84
85
|
|
|
85
|
-
async function findNearestProjectConfigDir(
|
|
86
|
-
|
|
86
|
+
async function findNearestProjectConfigDir(
|
|
87
|
+
cwd: string,
|
|
88
|
+
repoRoot?: string | null,
|
|
89
|
+
): Promise<{ dir: string; depth: number } | null> {
|
|
90
|
+
for (const ancestor of getAncestorDirs(cwd, repoRoot)) {
|
|
87
91
|
const configDir = await ifNonEmptyDir(ancestor.dir, PATHS.projectDir);
|
|
88
92
|
if (configDir) return { dir: configDir, depth: ancestor.depth };
|
|
89
93
|
}
|
|
@@ -215,7 +219,7 @@ async function loadSystemPrompt(ctx: LoadContext): Promise<LoadResult<SystemProm
|
|
|
215
219
|
});
|
|
216
220
|
}
|
|
217
221
|
|
|
218
|
-
const nearestProjectConfigDir = await findNearestProjectConfigDir(ctx.cwd);
|
|
222
|
+
const nearestProjectConfigDir = await findNearestProjectConfigDir(ctx.cwd, ctx.repoRoot);
|
|
219
223
|
if (nearestProjectConfigDir) {
|
|
220
224
|
const projectPath = path.join(nearestProjectConfigDir.dir, "SYSTEM.md");
|
|
221
225
|
const projectContent = await readFile(projectPath);
|
|
@@ -242,18 +246,27 @@ registerProvider<SystemPrompt>(systemPromptCapability.id, {
|
|
|
242
246
|
|
|
243
247
|
// Skills
|
|
244
248
|
async function loadSkills(ctx: LoadContext): Promise<LoadResult<Skill>> {
|
|
245
|
-
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
),
|
|
249
|
+
// Walk up from cwd finding .omp/skills/ in ancestors (closest first)
|
|
250
|
+
const ancestors = getAncestorDirs(ctx.cwd, ctx.repoRoot ?? ctx.home);
|
|
251
|
+
const projectScans = ancestors.map(({ dir }) =>
|
|
252
|
+
scanSkillsFromDir(ctx, {
|
|
253
|
+
dir: path.join(dir, PATHS.projectDir, "skills"),
|
|
254
|
+
providerId: PROVIDER_ID,
|
|
255
|
+
level: "project",
|
|
256
|
+
requireDescription: true,
|
|
257
|
+
}),
|
|
255
258
|
);
|
|
256
259
|
|
|
260
|
+
// User-level scan from ~/.omp/agent/skills/
|
|
261
|
+
const userScan = scanSkillsFromDir(ctx, {
|
|
262
|
+
dir: path.join(ctx.home, PATHS.userAgent, "skills"),
|
|
263
|
+
providerId: PROVIDER_ID,
|
|
264
|
+
level: "user",
|
|
265
|
+
requireDescription: true,
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
const results = await Promise.all([...projectScans, userScan]);
|
|
269
|
+
|
|
257
270
|
return {
|
|
258
271
|
items: results.flatMap(r => r.items),
|
|
259
272
|
warnings: results.flatMap(r => r.warnings ?? []),
|
|
@@ -795,7 +808,7 @@ async function loadContextFiles(ctx: LoadContext): Promise<LoadResult<ContextFil
|
|
|
795
808
|
});
|
|
796
809
|
}
|
|
797
810
|
|
|
798
|
-
const nearestProjectConfigDir = await findNearestProjectConfigDir(ctx.cwd);
|
|
811
|
+
const nearestProjectConfigDir = await findNearestProjectConfigDir(ctx.cwd, ctx.repoRoot);
|
|
799
812
|
if (nearestProjectConfigDir) {
|
|
800
813
|
const projectPath = path.join(nearestProjectConfigDir.dir, "AGENTS.md");
|
|
801
814
|
const projectContent = await readFile(projectPath);
|
package/src/discovery/claude.ts
CHANGED
|
@@ -145,7 +145,7 @@ async function loadContextFiles(ctx: LoadContext): Promise<LoadResult<ContextFil
|
|
|
145
145
|
const projectClaudeMd = path.join(projectBase, "CLAUDE.md");
|
|
146
146
|
const projectContent = await readFile(projectClaudeMd);
|
|
147
147
|
if (projectContent !== null) {
|
|
148
|
-
const depth = calculateDepth(ctx.cwd, projectBase, path.sep);
|
|
148
|
+
const depth = calculateDepth(ctx.cwd, path.dirname(projectBase), path.sep);
|
|
149
149
|
items.push({
|
|
150
150
|
path: projectClaudeMd,
|
|
151
151
|
content: projectContent,
|
|
@@ -164,11 +164,27 @@ async function loadContextFiles(ctx: LoadContext): Promise<LoadResult<ContextFil
|
|
|
164
164
|
|
|
165
165
|
async function loadSkills(ctx: LoadContext): Promise<LoadResult<Skill>> {
|
|
166
166
|
const userSkillsDir = path.join(getUserClaude(ctx), "skills");
|
|
167
|
-
const projectSkillsDir = path.join(getProjectClaude(ctx), "skills");
|
|
168
167
|
|
|
169
|
-
|
|
168
|
+
// Walk up from cwd finding .claude/skills/ in ancestors
|
|
169
|
+
const projectScans: Promise<LoadResult<Skill>>[] = [];
|
|
170
|
+
let current = ctx.cwd;
|
|
171
|
+
while (true) {
|
|
172
|
+
projectScans.push(
|
|
173
|
+
scanSkillsFromDir(ctx, {
|
|
174
|
+
dir: path.join(current, CONFIG_DIR, "skills"),
|
|
175
|
+
providerId: PROVIDER_ID,
|
|
176
|
+
level: "project",
|
|
177
|
+
}),
|
|
178
|
+
);
|
|
179
|
+
if (current === (ctx.repoRoot ?? ctx.home)) break;
|
|
180
|
+
const parent = path.dirname(current);
|
|
181
|
+
if (parent === current) break; // filesystem root
|
|
182
|
+
current = parent;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const [userResult, ...projectResults] = await Promise.allSettled([
|
|
170
186
|
scanSkillsFromDir(ctx, { dir: userSkillsDir, providerId: PROVIDER_ID, level: "user" }),
|
|
171
|
-
|
|
187
|
+
...projectScans,
|
|
172
188
|
]);
|
|
173
189
|
|
|
174
190
|
const items: Skill[] = [];
|
|
@@ -181,11 +197,13 @@ async function loadSkills(ctx: LoadContext): Promise<LoadResult<Skill>> {
|
|
|
181
197
|
warnings.push(`Failed to scan Claude user skills in ${userSkillsDir}: ${String(userResult.reason)}`);
|
|
182
198
|
}
|
|
183
199
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
200
|
+
for (const projectResult of projectResults) {
|
|
201
|
+
if (projectResult.status === "fulfilled") {
|
|
202
|
+
items.push(...projectResult.value.items);
|
|
203
|
+
warnings.push(...(projectResult.value.warnings ?? []));
|
|
204
|
+
} else if (!isMissingDirectoryError(projectResult.reason)) {
|
|
205
|
+
warnings.push(`Failed to scan Claude project skills: ${String(projectResult.reason)}`);
|
|
206
|
+
}
|
|
189
207
|
}
|
|
190
208
|
|
|
191
209
|
return { items, warnings };
|
package/src/discovery/helpers.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import type { ThinkingLevel } from "@oh-my-pi/pi-
|
|
3
|
+
import type { ThinkingLevel } from "@oh-my-pi/pi-ai";
|
|
4
|
+
import { parseThinkingLevel } from "@oh-my-pi/pi-ai";
|
|
4
5
|
import { FileType, glob } from "@oh-my-pi/pi-natives";
|
|
5
6
|
import { CONFIG_DIR_NAME, tryParseJson } from "@oh-my-pi/pi-utils";
|
|
6
7
|
import { readFile } from "../capability/fs";
|
|
@@ -9,8 +10,6 @@ import type { Skill, SkillFrontmatter } from "../capability/skill";
|
|
|
9
10
|
import type { LoadContext, LoadResult, SourceMeta } from "../capability/types";
|
|
10
11
|
import { parseFrontmatter } from "../utils/frontmatter";
|
|
11
12
|
|
|
12
|
-
const VALID_THINKING_LEVELS: readonly string[] = ["off", "minimal", "low", "medium", "high", "xhigh"];
|
|
13
|
-
|
|
14
13
|
/**
|
|
15
14
|
* Standard paths for each config source.
|
|
16
15
|
*/
|
|
@@ -100,18 +99,6 @@ export function createSourceMeta(provider: string, filePath: string, level: "use
|
|
|
100
99
|
};
|
|
101
100
|
}
|
|
102
101
|
|
|
103
|
-
/**
|
|
104
|
-
* Parse thinking level from frontmatter.
|
|
105
|
-
* Supports keys: thinkingLevel, thinking-level, thinking
|
|
106
|
-
*/
|
|
107
|
-
export function parseThinkingLevel(frontmatter: Record<string, unknown>): ThinkingLevel | undefined {
|
|
108
|
-
const raw = frontmatter.thinkingLevel ?? frontmatter["thinking-level"] ?? frontmatter.thinking;
|
|
109
|
-
if (typeof raw === "string" && VALID_THINKING_LEVELS.includes(raw)) {
|
|
110
|
-
return raw as ThinkingLevel;
|
|
111
|
-
}
|
|
112
|
-
return undefined;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
102
|
export function parseBoolean(value: unknown): boolean | undefined {
|
|
116
103
|
if (typeof value === "boolean") return value;
|
|
117
104
|
if (typeof value === "string") {
|
|
@@ -247,10 +234,16 @@ export function parseAgentFields(frontmatter: Record<string, unknown>): ParsedAg
|
|
|
247
234
|
}
|
|
248
235
|
|
|
249
236
|
const output = frontmatter.output !== undefined ? frontmatter.output : undefined;
|
|
237
|
+
const rawThinkingLevel =
|
|
238
|
+
typeof frontmatter.thinkingLevel === "string"
|
|
239
|
+
? frontmatter.thinkingLevel
|
|
240
|
+
: typeof frontmatter.thinking === "string"
|
|
241
|
+
? frontmatter.thinking
|
|
242
|
+
: undefined;
|
|
243
|
+
|
|
244
|
+
const thinkingLevel = parseThinkingLevel(rawThinkingLevel);
|
|
250
245
|
const model = parseModelList(frontmatter.model);
|
|
251
|
-
const thinkingLevel = parseThinkingLevel(frontmatter);
|
|
252
246
|
const blocking = parseBoolean(frontmatter.blocking);
|
|
253
|
-
|
|
254
247
|
return { name, description, tools, spawns, model, output, thinkingLevel, blocking };
|
|
255
248
|
}
|
|
256
249
|
|
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
import type * as fs1 from "node:fs";
|
|
5
5
|
import * as fs from "node:fs/promises";
|
|
6
6
|
import * as path from "node:path";
|
|
7
|
-
import type { ThinkingLevel } from "@oh-my-pi/pi-
|
|
8
|
-
import type { ImageContent, Model, TextContent } from "@oh-my-pi/pi-ai";
|
|
7
|
+
import type { ImageContent, Model, TextContent, ThinkingLevel } from "@oh-my-pi/pi-ai";
|
|
9
8
|
import * as piCodingAgent from "@oh-my-pi/pi-coding-agent";
|
|
10
9
|
import type { KeyId } from "@oh-my-pi/pi-tui";
|
|
11
10
|
import { hasFsCode, isEacces, isEnoent, logger } from "@oh-my-pi/pi-utils";
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* - Register commands, keyboard shortcuts, and CLI flags
|
|
8
8
|
* - Interact with the user via UI primitives
|
|
9
9
|
*/
|
|
10
|
-
import type { AgentMessage, AgentToolResult, AgentToolUpdateCallback
|
|
10
|
+
import type { AgentMessage, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
11
11
|
import type {
|
|
12
12
|
Api,
|
|
13
13
|
AssistantMessageEvent,
|
|
@@ -19,6 +19,7 @@ import type {
|
|
|
19
19
|
OAuthLoginCallbacks,
|
|
20
20
|
SimpleStreamOptions,
|
|
21
21
|
TextContent,
|
|
22
|
+
ThinkingLevel,
|
|
22
23
|
ToolResultMessage,
|
|
23
24
|
} from "@oh-my-pi/pi-ai";
|
|
24
25
|
import type * as piCodingAgent from "@oh-my-pi/pi-coding-agent";
|
|
@@ -40,7 +40,7 @@ export async function loadSkillsFromDir(options: LoadSkillsFromDirOptions): Prom
|
|
|
40
40
|
const providerId = rawProviderId || "custom";
|
|
41
41
|
const level: "user" | "project" = rawLevel === "project" ? "project" : "user";
|
|
42
42
|
const result = await scanSkillsFromDir(
|
|
43
|
-
{ cwd: getProjectDir(), home: os.homedir() },
|
|
43
|
+
{ cwd: getProjectDir(), home: os.homedir(), repoRoot: null },
|
|
44
44
|
{
|
|
45
45
|
dir: options.dir,
|
|
46
46
|
providerId,
|
|
@@ -175,7 +175,7 @@ export async function loadSkills(options: LoadSkillsOptions = {}): Promise<LoadS
|
|
|
175
175
|
customDirectories.map(async dir => {
|
|
176
176
|
const expandedDir = expandTilde(dir);
|
|
177
177
|
const scanResult = await scanSkillsFromDir(
|
|
178
|
-
{ cwd, home: os.homedir() },
|
|
178
|
+
{ cwd, home: os.homedir(), repoRoot: null },
|
|
179
179
|
{
|
|
180
180
|
dir: expandedDir,
|
|
181
181
|
providerId: "custom",
|