awesome-agents 0.1.1 → 0.1.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/AGENTS.md +9 -3
- package/CHANGELOG.md +30 -0
- package/README.md +42 -26
- package/docs/cli.md +19 -7
- package/docs/product/command-model.md +16 -2
- package/docs/product/harness-targets.md +16 -8
- package/docs/product/open-questions.md +3 -2
- package/docs/product/product-scope.md +15 -10
- package/docs/product/profile-source-format.md +22 -16
- package/package.json +1 -1
- package/src/cli.js +50 -17
- package/src/constants.js +1 -2
- package/src/help.js +530 -0
- package/src/installer.js +129 -25
- package/src/renderers.js +5 -8
- package/src/source.js +200 -23
package/src/installer.js
CHANGED
|
@@ -21,11 +21,11 @@ export async function installFromSource(sourceSpec, options = {}) {
|
|
|
21
21
|
const split = splitSourceSpec(sourceSpec);
|
|
22
22
|
const source = split.source;
|
|
23
23
|
const { selectors, harnessInput } = resolveInstallSelection(split, options);
|
|
24
|
-
const scope = resolveScope(options);
|
|
25
24
|
const harnesses = normalizeAgentList(harnessInput, {
|
|
26
25
|
all: options.all,
|
|
27
26
|
defaultAgent: DEFAULT_AGENT
|
|
28
27
|
});
|
|
28
|
+
const scope = resolveScope(options, harnesses);
|
|
29
29
|
|
|
30
30
|
const materialized = await materializeSource(source, options);
|
|
31
31
|
try {
|
|
@@ -71,7 +71,11 @@ export async function installFromSource(sourceSpec, options = {}) {
|
|
|
71
71
|
await writeRegistry(registry, registryOptions);
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
return {
|
|
74
|
+
return {
|
|
75
|
+
operations,
|
|
76
|
+
registryPath: registryPath(registryOptions),
|
|
77
|
+
runInstructions: buildRunInstructions(operations)
|
|
78
|
+
};
|
|
75
79
|
} finally {
|
|
76
80
|
await materialized.cleanup();
|
|
77
81
|
}
|
|
@@ -104,10 +108,11 @@ export async function useFromSource(sourceSpec, options = {}) {
|
|
|
104
108
|
}
|
|
105
109
|
|
|
106
110
|
export async function listInstalled(options = {}) {
|
|
107
|
-
const scope = resolveScope(options);
|
|
108
|
-
const registry = await readRegistry({ ...options, scope });
|
|
109
111
|
const harnessInput = options.agents ?? options.agent;
|
|
110
|
-
const
|
|
112
|
+
const harnesses = harnessInput ? normalizeAgentList(harnessInput) : [];
|
|
113
|
+
const scope = resolveScope(options, harnesses);
|
|
114
|
+
const registry = await readRegistry({ ...options, scope });
|
|
115
|
+
const harnessFilter = harnesses.length > 0 ? new Set(harnesses) : undefined;
|
|
111
116
|
const installs = registry.installs
|
|
112
117
|
.filter((install) => !harnessFilter || harnessFilter.has(install.harness))
|
|
113
118
|
.map((install) => ({
|
|
@@ -123,7 +128,9 @@ export async function listInstalled(options = {}) {
|
|
|
123
128
|
}
|
|
124
129
|
|
|
125
130
|
export async function removeInstalled(profileArgs = [], options = {}) {
|
|
126
|
-
const
|
|
131
|
+
const harnessInput = options.agents ?? options.agent;
|
|
132
|
+
const harnesses = harnessInput ? normalizeAgentList(harnessInput) : [];
|
|
133
|
+
const scope = resolveScope(options, harnesses);
|
|
127
134
|
const registryOptions = { ...options, scope };
|
|
128
135
|
const registry = await readRegistry(registryOptions);
|
|
129
136
|
const selectors = normalizeProfileSelectors(profileArgs);
|
|
@@ -132,8 +139,7 @@ export async function removeInstalled(profileArgs = [], options = {}) {
|
|
|
132
139
|
throw new Error("Specify at least one profile to remove, or pass --all.");
|
|
133
140
|
}
|
|
134
141
|
|
|
135
|
-
const
|
|
136
|
-
const harnessFilter = harnessInput ? new Set(normalizeAgentList(harnessInput)) : undefined;
|
|
142
|
+
const harnessFilter = harnesses.length > 0 ? new Set(harnesses) : undefined;
|
|
137
143
|
const operations = [];
|
|
138
144
|
const matched = registry.installs.filter((install) => {
|
|
139
145
|
const profileMatch = removeAll || selectors.includes(install.profile);
|
|
@@ -172,12 +178,13 @@ export async function removeInstalled(profileArgs = [], options = {}) {
|
|
|
172
178
|
}
|
|
173
179
|
|
|
174
180
|
export async function updateInstalled(profileArgs = [], options = {}) {
|
|
175
|
-
const
|
|
181
|
+
const harnessInput = options.agents ?? options.agent;
|
|
182
|
+
const harnesses = harnessInput ? normalizeAgentList(harnessInput) : [];
|
|
183
|
+
const scope = resolveScope(options, harnesses);
|
|
176
184
|
const registryOptions = { ...options, scope };
|
|
177
185
|
const registry = await readRegistry(registryOptions);
|
|
178
186
|
const selectors = normalizeProfileSelectors(profileArgs);
|
|
179
|
-
const
|
|
180
|
-
const harnessFilter = harnessInput ? new Set(normalizeAgentList(harnessInput)) : undefined;
|
|
187
|
+
const harnessFilter = harnesses.length > 0 ? new Set(harnesses) : undefined;
|
|
181
188
|
const candidates = registry.installs.filter((install) => {
|
|
182
189
|
const profileMatch = selectors.length === 0 || selectors.includes(install.profile);
|
|
183
190
|
const harnessMatch = !harnessFilter || harnessFilter.has(install.harness);
|
|
@@ -185,6 +192,7 @@ export async function updateInstalled(profileArgs = [], options = {}) {
|
|
|
185
192
|
});
|
|
186
193
|
|
|
187
194
|
const operations = [];
|
|
195
|
+
const runInstructions = [];
|
|
188
196
|
for (const install of candidates) {
|
|
189
197
|
const result = await installFromSource(install.source, {
|
|
190
198
|
...options,
|
|
@@ -198,42 +206,126 @@ export async function updateInstalled(profileArgs = [], options = {}) {
|
|
|
198
206
|
...operation,
|
|
199
207
|
action: options.dryRun ? "would-update" : "updated"
|
|
200
208
|
})));
|
|
209
|
+
runInstructions.push(...result.runInstructions);
|
|
201
210
|
}
|
|
202
211
|
|
|
203
|
-
return { operations, registryPath: registryPath(registryOptions) };
|
|
212
|
+
return { operations, registryPath: registryPath(registryOptions), runInstructions };
|
|
204
213
|
}
|
|
205
214
|
|
|
206
215
|
export async function initProfile(name, options = {}) {
|
|
207
216
|
const slug = slugify(name || "new-agent-profile");
|
|
208
217
|
const root = options.cwd ?? process.cwd();
|
|
209
|
-
const profilePath = path.join(root, "agents", "profiles", `${slug}.
|
|
210
|
-
const adapterPath = path.join(root, "agents", "adapters", "codex", `${slug}.md`);
|
|
218
|
+
const profilePath = path.join(root, "agents", "profiles", `${slug}.agent.yaml`);
|
|
211
219
|
|
|
212
220
|
if (!options.force) {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
throw new Error(`${target} already exists. Pass --force to overwrite.`);
|
|
216
|
-
}
|
|
221
|
+
if (existsSync(profilePath)) {
|
|
222
|
+
throw new Error(`${profilePath} already exists. Pass --force to overwrite.`);
|
|
217
223
|
}
|
|
218
224
|
}
|
|
219
225
|
|
|
220
226
|
const title = titleCase(slug);
|
|
221
|
-
const profileContent =
|
|
222
|
-
const adapterContent = `---\nslug: ${slug}\nprofile: ../../profiles/${slug}.md\nharness: codex\nmodel: inherit\nreasoning_effort: medium\nrequired_skills: []\n---\n\n# Codex Adapter: ${title}\n\nLoad the canonical profile at \`agents/profiles/${slug}.md\`.\n`;
|
|
227
|
+
const profileContent = `schema: awesome-agents/v1\nid: ${slug}\nname: ${title}\ndescription: Describe when to use this agent profile.\nmodel: inherit\nreasoning_effort: medium\nhome_notes_template: "~/.agents/homes/${slug}/<project>/notes"\ninstructions: |\n Describe the agent's mission, boundaries, tools, coordination model, notes,\n and reporting style.\n`;
|
|
223
228
|
|
|
224
229
|
if (!options.dryRun) {
|
|
225
230
|
await fs.mkdir(path.dirname(profilePath), { recursive: true });
|
|
226
|
-
await fs.mkdir(path.dirname(adapterPath), { recursive: true });
|
|
227
231
|
await fs.writeFile(profilePath, profileContent);
|
|
228
|
-
await fs.writeFile(adapterPath, adapterContent);
|
|
229
232
|
}
|
|
230
233
|
|
|
231
234
|
return {
|
|
232
235
|
action: options.dryRun ? "would-init" : "initialized",
|
|
233
|
-
files: [profilePath
|
|
236
|
+
files: [profilePath]
|
|
234
237
|
};
|
|
235
238
|
}
|
|
236
239
|
|
|
240
|
+
function buildRunInstructions(operations, env = process.env) {
|
|
241
|
+
const instructions = [];
|
|
242
|
+
const seen = new Set();
|
|
243
|
+
|
|
244
|
+
for (const operation of operations) {
|
|
245
|
+
if (String(operation.action).startsWith("would-")) {
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const instruction = runInstructionForOperation(operation, env);
|
|
250
|
+
if (!instruction) {
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const key = `${instruction.harness}:${instruction.profile}:${instruction.command}`;
|
|
255
|
+
if (seen.has(key)) {
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
seen.add(key);
|
|
259
|
+
instructions.push(instruction);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return instructions;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function runInstructionForOperation(operation, env) {
|
|
266
|
+
if (operation.harness === "codex") {
|
|
267
|
+
if (!commandExists("codex", env)) {
|
|
268
|
+
return undefined;
|
|
269
|
+
}
|
|
270
|
+
return {
|
|
271
|
+
profile: operation.profile,
|
|
272
|
+
harness: operation.harness,
|
|
273
|
+
command: `codex --profile ${shellWord(operation.profile)}`,
|
|
274
|
+
note: "Starts Codex with this installed profile."
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (operation.harness === "claude-code") {
|
|
279
|
+
if (!commandExists("claude", env)) {
|
|
280
|
+
return undefined;
|
|
281
|
+
}
|
|
282
|
+
return {
|
|
283
|
+
profile: operation.profile,
|
|
284
|
+
harness: operation.harness,
|
|
285
|
+
command: `claude --agent ${shellWord(operation.profile)}`,
|
|
286
|
+
note: "Starts Claude Code with this installed agent profile."
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (operation.harness === "opencode") {
|
|
291
|
+
if (!commandExists("opencode", env)) {
|
|
292
|
+
return undefined;
|
|
293
|
+
}
|
|
294
|
+
return {
|
|
295
|
+
profile: operation.profile,
|
|
296
|
+
harness: operation.harness,
|
|
297
|
+
command: "opencode",
|
|
298
|
+
note: `Start OpenCode, then invoke @${operation.profile} in the session.`
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return undefined;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function commandExists(command, env) {
|
|
306
|
+
const pathValue = env.PATH ?? "";
|
|
307
|
+
if (!pathValue) {
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const extensions = process.platform === "win32"
|
|
312
|
+
? (env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";")
|
|
313
|
+
: [""];
|
|
314
|
+
|
|
315
|
+
return pathValue
|
|
316
|
+
.split(path.delimiter)
|
|
317
|
+
.filter(Boolean)
|
|
318
|
+
.some((directory) => extensions.some((extension) => existsSync(path.join(directory, `${command}${extension}`))));
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function shellWord(value) {
|
|
322
|
+
const text = String(value);
|
|
323
|
+
if (/^[A-Za-z0-9._/@:-]+$/.test(text)) {
|
|
324
|
+
return text;
|
|
325
|
+
}
|
|
326
|
+
return `'${text.replaceAll("'", "'\\''")}'`;
|
|
327
|
+
}
|
|
328
|
+
|
|
237
329
|
function selectProfiles(profiles, selectors, all = false) {
|
|
238
330
|
if (all || selectors.length === 0 || selectors.includes("*")) {
|
|
239
331
|
return profiles;
|
|
@@ -268,11 +360,23 @@ async function writeManagedFile(target, content, options = {}) {
|
|
|
268
360
|
await fs.writeFile(target, content);
|
|
269
361
|
}
|
|
270
362
|
|
|
271
|
-
function resolveScope(options = {}) {
|
|
363
|
+
function resolveScope(options = {}, harnesses = []) {
|
|
272
364
|
if (options.project && options.global) {
|
|
273
365
|
throw new Error("Use either --global or --project, not both.");
|
|
274
366
|
}
|
|
275
|
-
|
|
367
|
+
if (options.project && harnesses.includes("codex")) {
|
|
368
|
+
throw new Error("Codex profiles are loaded from $CODEX_HOME/<name>.config.toml and cannot be installed project-locally. Use --global or choose a different harness.");
|
|
369
|
+
}
|
|
370
|
+
if (options.global) {
|
|
371
|
+
return "global";
|
|
372
|
+
}
|
|
373
|
+
if (options.project) {
|
|
374
|
+
return "project";
|
|
375
|
+
}
|
|
376
|
+
if (harnesses.includes("codex")) {
|
|
377
|
+
return "global";
|
|
378
|
+
}
|
|
379
|
+
return "project";
|
|
276
380
|
}
|
|
277
381
|
|
|
278
382
|
function normalizeProfileSelectors(values) {
|
package/src/renderers.js
CHANGED
|
@@ -56,12 +56,7 @@ export function resolveTargetPath(profile, agent, options = {}) {
|
|
|
56
56
|
: process.env.CODEX_HOME
|
|
57
57
|
? path.resolve(expandHome(process.env.CODEX_HOME, home))
|
|
58
58
|
: path.join(home, ".codex");
|
|
59
|
-
|
|
60
|
-
if (scope === "project") {
|
|
61
|
-
return path.join(cwd, ".codex", "agents", `${profile.slug}.toml`);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return path.join(codexHome, "agents", `${profile.slug}.toml`);
|
|
59
|
+
return path.join(codexHome, `${profile.slug}.config.toml`);
|
|
65
60
|
}
|
|
66
61
|
|
|
67
62
|
if (normalized === "claude-code") {
|
|
@@ -170,9 +165,11 @@ function buildInstructionBody(profile, adapter, harness) {
|
|
|
170
165
|
"",
|
|
171
166
|
"## Installed Profile Context",
|
|
172
167
|
"",
|
|
173
|
-
`-
|
|
168
|
+
`- Installed identity: \`${profile.slug}\``,
|
|
169
|
+
`- Role/name: \`${profile.name}\``,
|
|
174
170
|
`- Installed for: \`${harness}\``,
|
|
175
|
-
"-
|
|
171
|
+
"- When asked who you are, what agent is running, or what role you are acting as, answer with this identity and role.",
|
|
172
|
+
"- This profile is a reusable operational agent profile, not a skill or local machine setup.",
|
|
176
173
|
"- The runtime agent manages any profile-specific home directory and notes at task time."
|
|
177
174
|
];
|
|
178
175
|
|
package/src/source.js
CHANGED
|
@@ -4,14 +4,24 @@ import fs from "node:fs/promises";
|
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
-
import
|
|
7
|
+
import YAML from "yaml";
|
|
8
8
|
import { parseFrontmatter } from "./frontmatter.js";
|
|
9
9
|
|
|
10
10
|
const GITHUB_SHORTHAND = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
|
|
11
|
+
const PROFILE_SUFFIXES = [
|
|
12
|
+
".agent.yaml",
|
|
13
|
+
".agent.yml",
|
|
14
|
+
".agf.yaml",
|
|
15
|
+
".agf.yml",
|
|
16
|
+
".yaml",
|
|
17
|
+
".yml",
|
|
18
|
+
".agent.md",
|
|
19
|
+
".md"
|
|
20
|
+
];
|
|
11
21
|
|
|
12
22
|
export function splitSourceSpec(spec) {
|
|
13
23
|
if (!spec) {
|
|
14
|
-
return { source:
|
|
24
|
+
return { source: undefined, profile: undefined };
|
|
15
25
|
}
|
|
16
26
|
|
|
17
27
|
const atIndex = spec.lastIndexOf("@");
|
|
@@ -57,13 +67,17 @@ export function normalizeGithubUrl(source) {
|
|
|
57
67
|
return undefined;
|
|
58
68
|
}
|
|
59
69
|
|
|
60
|
-
export async function materializeSource(sourceInput
|
|
70
|
+
export async function materializeSource(sourceInput, options = {}) {
|
|
61
71
|
const home = options.home ?? os.homedir();
|
|
62
|
-
const source = sourceInput
|
|
72
|
+
const source = sourceInput;
|
|
73
|
+
|
|
74
|
+
if (!source) {
|
|
75
|
+
throw new Error("Specify a source. Use a local path, owner/repo, or GitHub URL.");
|
|
76
|
+
}
|
|
63
77
|
|
|
64
78
|
if (isLocalSource(source, home)) {
|
|
65
79
|
const sourcePath = path.resolve(expandHome(source, home));
|
|
66
|
-
await
|
|
80
|
+
await assertAgentProfileLayout(sourcePath);
|
|
67
81
|
return {
|
|
68
82
|
kind: "local",
|
|
69
83
|
source: sourcePath,
|
|
@@ -89,7 +103,7 @@ export async function materializeSource(sourceInput = DEFAULT_SOURCE, options =
|
|
|
89
103
|
throw new Error(`Could not clone ${source}: ${detail}`);
|
|
90
104
|
}
|
|
91
105
|
|
|
92
|
-
await
|
|
106
|
+
await assertAgentProfileLayout(cloneDir);
|
|
93
107
|
|
|
94
108
|
return {
|
|
95
109
|
kind: "git",
|
|
@@ -102,7 +116,7 @@ export async function materializeSource(sourceInput = DEFAULT_SOURCE, options =
|
|
|
102
116
|
};
|
|
103
117
|
}
|
|
104
118
|
|
|
105
|
-
async function
|
|
119
|
+
async function assertAgentProfileLayout(sourcePath) {
|
|
106
120
|
const profilesDir = path.join(sourcePath, "agents", "profiles");
|
|
107
121
|
if (!existsSync(profilesDir)) {
|
|
108
122
|
throw new Error(`No agents/profiles directory found in ${sourcePath}`);
|
|
@@ -116,25 +130,15 @@ export async function loadCatalog(sourcePath) {
|
|
|
116
130
|
const profiles = [];
|
|
117
131
|
|
|
118
132
|
for (const file of files) {
|
|
119
|
-
if (!file.isFile() || !file.name
|
|
133
|
+
if (!file.isFile() || !isProfileFile(file.name)) {
|
|
120
134
|
continue;
|
|
121
135
|
}
|
|
122
136
|
|
|
123
137
|
const filePath = path.join(profilesDir, file.name);
|
|
124
|
-
const
|
|
125
|
-
const parsed = parseFrontmatter(raw, filePath);
|
|
126
|
-
const slug = parsed.attributes.slug ?? path.basename(file.name, ".md");
|
|
127
|
-
|
|
138
|
+
const profile = await loadProfileFile(filePath, sourcePath);
|
|
128
139
|
profiles.push({
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
summary: parsed.attributes.summary ?? "",
|
|
132
|
-
attributes: parsed.attributes,
|
|
133
|
-
body: parsed.body,
|
|
134
|
-
raw,
|
|
135
|
-
filePath,
|
|
136
|
-
relativePath: path.relative(sourcePath, filePath),
|
|
137
|
-
adapters: await loadAdapters(adapterRoot, slug, sourcePath)
|
|
140
|
+
...profile,
|
|
141
|
+
adapters: await loadAdapters(adapterRoot, profile.slug, sourcePath)
|
|
138
142
|
});
|
|
139
143
|
}
|
|
140
144
|
|
|
@@ -154,13 +158,13 @@ async function loadAdapters(adapterRoot, slug, sourcePath) {
|
|
|
154
158
|
continue;
|
|
155
159
|
}
|
|
156
160
|
|
|
157
|
-
const adapterPath = path.join(adapterRoot, harnessDir.name,
|
|
161
|
+
const adapterPath = await findProfileLikeFile(path.join(adapterRoot, harnessDir.name), slug);
|
|
158
162
|
if (!existsSync(adapterPath)) {
|
|
159
163
|
continue;
|
|
160
164
|
}
|
|
161
165
|
|
|
162
166
|
const raw = await fs.readFile(adapterPath, "utf8");
|
|
163
|
-
const parsed =
|
|
167
|
+
const parsed = await loadDefinitionFile(adapterPath, raw);
|
|
164
168
|
adapters[harnessDir.name] = {
|
|
165
169
|
harness: harnessDir.name,
|
|
166
170
|
attributes: parsed.attributes,
|
|
@@ -174,6 +178,179 @@ async function loadAdapters(adapterRoot, slug, sourcePath) {
|
|
|
174
178
|
return adapters;
|
|
175
179
|
}
|
|
176
180
|
|
|
181
|
+
async function loadProfileFile(filePath, sourcePath) {
|
|
182
|
+
const raw = await fs.readFile(filePath, "utf8");
|
|
183
|
+
const parsed = await loadDefinitionFile(filePath, raw);
|
|
184
|
+
const fallbackSlug = profileSlugFromFilename(path.basename(filePath));
|
|
185
|
+
const slug = parsed.attributes.slug ?? parsed.attributes.id ?? fallbackSlug;
|
|
186
|
+
const name = parsed.attributes.name ?? titleize(slug);
|
|
187
|
+
const summary = parsed.attributes.summary ?? parsed.attributes.description ?? "";
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
slug,
|
|
191
|
+
name,
|
|
192
|
+
summary,
|
|
193
|
+
attributes: {
|
|
194
|
+
...parsed.attributes,
|
|
195
|
+
slug,
|
|
196
|
+
name,
|
|
197
|
+
summary
|
|
198
|
+
},
|
|
199
|
+
body: parsed.body,
|
|
200
|
+
raw,
|
|
201
|
+
filePath,
|
|
202
|
+
relativePath: path.relative(sourcePath, filePath),
|
|
203
|
+
format: parsed.format
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async function loadDefinitionFile(filePath, rawInput) {
|
|
208
|
+
const raw = rawInput ?? await fs.readFile(filePath, "utf8");
|
|
209
|
+
if (isMarkdownProfile(filePath)) {
|
|
210
|
+
const parsed = parseFrontmatter(raw, filePath);
|
|
211
|
+
return {
|
|
212
|
+
attributes: normalizeFlatAttributes(parsed.attributes),
|
|
213
|
+
body: parsed.body,
|
|
214
|
+
format: "markdown-frontmatter"
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
let document;
|
|
219
|
+
try {
|
|
220
|
+
document = YAML.parse(raw) ?? {};
|
|
221
|
+
} catch (error) {
|
|
222
|
+
throw new Error(`Could not parse YAML profile in ${filePath}: ${error.message}`);
|
|
223
|
+
}
|
|
224
|
+
if (!isObject(document)) {
|
|
225
|
+
throw new Error(`YAML profile in ${filePath} must be a mapping/object.`);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const normalized = normalizeYamlProfile(document, filePath);
|
|
229
|
+
return {
|
|
230
|
+
attributes: normalized.attributes,
|
|
231
|
+
body: normalized.body,
|
|
232
|
+
format: normalized.format
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function normalizeYamlProfile(document, filePath) {
|
|
237
|
+
const metadata = isObject(document.metadata) ? document.metadata : {};
|
|
238
|
+
const executionPolicy = isObject(document.execution_policy) ? document.execution_policy : {};
|
|
239
|
+
const executionConfig = isObject(executionPolicy.config) ? executionPolicy.config : {};
|
|
240
|
+
|
|
241
|
+
const id = firstString(
|
|
242
|
+
document.slug,
|
|
243
|
+
document.id,
|
|
244
|
+
metadata.id,
|
|
245
|
+
profileSlugFromFilename(path.basename(filePath))
|
|
246
|
+
);
|
|
247
|
+
const name = firstString(document.name, metadata.name, titleize(id));
|
|
248
|
+
const description = firstString(document.summary, document.description, metadata.description, "");
|
|
249
|
+
const instructions = firstString(
|
|
250
|
+
document.instructions,
|
|
251
|
+
document.instruction,
|
|
252
|
+
document.prompt,
|
|
253
|
+
document.system_prompt,
|
|
254
|
+
executionConfig.instructions,
|
|
255
|
+
""
|
|
256
|
+
);
|
|
257
|
+
const model = firstString(document.recommended_model, document.model, executionConfig.model, "");
|
|
258
|
+
const reasoningEffort = firstString(
|
|
259
|
+
document.recommended_reasoning_effort,
|
|
260
|
+
document.reasoning_effort,
|
|
261
|
+
document.model_reasoning_effort,
|
|
262
|
+
executionConfig.reasoning_effort,
|
|
263
|
+
""
|
|
264
|
+
);
|
|
265
|
+
const recommendedModels = document.recommended_models ?? document.models ?? undefined;
|
|
266
|
+
|
|
267
|
+
const attributes = {
|
|
268
|
+
...document,
|
|
269
|
+
slug: id,
|
|
270
|
+
id,
|
|
271
|
+
name,
|
|
272
|
+
summary: description,
|
|
273
|
+
description,
|
|
274
|
+
kind: document.kind ?? document.type ?? "operational-agent-profile"
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
if (model && !attributes.recommended_model) {
|
|
278
|
+
attributes.recommended_model = model;
|
|
279
|
+
}
|
|
280
|
+
if (reasoningEffort && !attributes.recommended_reasoning_effort) {
|
|
281
|
+
attributes.recommended_reasoning_effort = reasoningEffort;
|
|
282
|
+
}
|
|
283
|
+
if (recommendedModels && !attributes.recommended_models) {
|
|
284
|
+
attributes.recommended_models = recommendedModels;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
attributes,
|
|
289
|
+
body: renderYamlProfileBody(name, description, instructions),
|
|
290
|
+
format: isAgentFormat(document) ? "agent-format-yaml" : "yaml"
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function normalizeFlatAttributes(attributes) {
|
|
295
|
+
return {
|
|
296
|
+
...attributes,
|
|
297
|
+
summary: attributes.summary ?? attributes.description ?? ""
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function renderYamlProfileBody(name, description, instructions) {
|
|
302
|
+
const body = instructions || description || "No instructions provided.";
|
|
303
|
+
return `# ${name}\n\n${body}`.trimEnd();
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async function findProfileLikeFile(directory, slug) {
|
|
307
|
+
for (const suffix of PROFILE_SUFFIXES) {
|
|
308
|
+
const candidate = path.join(directory, `${slug}${suffix}`);
|
|
309
|
+
if (existsSync(candidate)) {
|
|
310
|
+
return candidate;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return path.join(directory, `${slug}.md`);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function isProfileFile(filename) {
|
|
317
|
+
return PROFILE_SUFFIXES.some((suffix) => filename.endsWith(suffix));
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function isMarkdownProfile(filePath) {
|
|
321
|
+
return filePath.endsWith(".md");
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function profileSlugFromFilename(filename) {
|
|
325
|
+
const suffix = PROFILE_SUFFIXES.find((value) => filename.endsWith(value));
|
|
326
|
+
return suffix ? filename.slice(0, -suffix.length) : path.basename(filename, path.extname(filename));
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function isAgentFormat(document) {
|
|
330
|
+
return isObject(document.metadata) && isObject(document.execution_policy);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function isObject(value) {
|
|
334
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function firstString(...values) {
|
|
338
|
+
for (const value of values) {
|
|
339
|
+
if (typeof value === "string" && value.trim()) {
|
|
340
|
+
return value.trim();
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return "";
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function titleize(slug) {
|
|
347
|
+
return String(slug)
|
|
348
|
+
.split(/[-_]+/)
|
|
349
|
+
.filter(Boolean)
|
|
350
|
+
.map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`)
|
|
351
|
+
.join(" ");
|
|
352
|
+
}
|
|
353
|
+
|
|
177
354
|
export function resolvePackageRoot() {
|
|
178
355
|
return path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
179
356
|
}
|