awesome-agents 0.1.0 → 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 +51 -0
- package/README.md +66 -29
- package/docs/cli.md +44 -4
- package/docs/product/README.md +13 -2
- package/docs/product/command-model.md +63 -0
- package/docs/product/harness-targets.md +59 -0
- package/docs/product/open-questions.md +21 -0
- package/docs/product/product-scope.md +42 -0
- package/docs/product/profile-source-format.md +64 -0
- package/docs/product/safety-and-publishing.md +64 -0
- package/package.json +7 -2
- package/scripts/changelog.js +227 -0
- package/scripts/release.js +219 -0
- package/src/cli.js +56 -19
- package/src/constants.js +1 -2
- package/src/help.js +530 -0
- package/src/installer.js +200 -31
- package/src/renderers.js +5 -8
- package/src/source.js +200 -23
- package/docs/product/awesome-agents-flow-notes.md +0 -45
package/src/installer.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { DEFAULT_AGENT, SUPPORTED_AGENTS } from "./constants.js";
|
|
4
|
+
import { AGENT_ALIASES, DEFAULT_AGENT, SUPPORTED_AGENTS } from "./constants.js";
|
|
5
5
|
import { contentHash, isGeneratedContent, normalizeAgentList, renderForAgent, resolveTargetPath } from "./renderers.js";
|
|
6
6
|
import { loadCatalog, materializeSource, splitSourceSpec } from "./source.js";
|
|
7
7
|
import { readRegistry, registryPath, removeInstall, upsertInstall, writeRegistry } from "./registry.js";
|
|
@@ -20,13 +20,12 @@ export async function listAvailable(sourceInput, options = {}) {
|
|
|
20
20
|
export async function installFromSource(sourceSpec, options = {}) {
|
|
21
21
|
const split = splitSourceSpec(sourceSpec);
|
|
22
22
|
const source = split.source;
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
const scope = resolveScope(options);
|
|
26
|
-
const harnesses = normalizeAgentList(options.agents ?? options.agent, {
|
|
23
|
+
const { selectors, harnessInput } = resolveInstallSelection(split, options);
|
|
24
|
+
const harnesses = normalizeAgentList(harnessInput, {
|
|
27
25
|
all: options.all,
|
|
28
26
|
defaultAgent: DEFAULT_AGENT
|
|
29
27
|
});
|
|
28
|
+
const scope = resolveScope(options, harnesses);
|
|
30
29
|
|
|
31
30
|
const materialized = await materializeSource(source, options);
|
|
32
31
|
try {
|
|
@@ -72,7 +71,11 @@ export async function installFromSource(sourceSpec, options = {}) {
|
|
|
72
71
|
await writeRegistry(registry, registryOptions);
|
|
73
72
|
}
|
|
74
73
|
|
|
75
|
-
return {
|
|
74
|
+
return {
|
|
75
|
+
operations,
|
|
76
|
+
registryPath: registryPath(registryOptions),
|
|
77
|
+
runInstructions: buildRunInstructions(operations)
|
|
78
|
+
};
|
|
76
79
|
} finally {
|
|
77
80
|
await materialized.cleanup();
|
|
78
81
|
}
|
|
@@ -80,12 +83,12 @@ export async function installFromSource(sourceSpec, options = {}) {
|
|
|
80
83
|
|
|
81
84
|
export async function useFromSource(sourceSpec, options = {}) {
|
|
82
85
|
const split = splitSourceSpec(sourceSpec);
|
|
83
|
-
const profileSelector =
|
|
86
|
+
const { profileSelector, harnessInput } = resolveUseSelection(split, options);
|
|
84
87
|
if (!profileSelector) {
|
|
85
88
|
throw new Error("Specify a profile with source@profile or --profile <profile>.");
|
|
86
89
|
}
|
|
87
90
|
|
|
88
|
-
const harness = normalizeAgentList(
|
|
91
|
+
const harness = normalizeAgentList(harnessInput, {
|
|
89
92
|
defaultAgent: DEFAULT_AGENT
|
|
90
93
|
})[0];
|
|
91
94
|
const materialized = await materializeSource(split.source, options);
|
|
@@ -105,10 +108,11 @@ export async function useFromSource(sourceSpec, options = {}) {
|
|
|
105
108
|
}
|
|
106
109
|
|
|
107
110
|
export async function listInstalled(options = {}) {
|
|
108
|
-
const scope = resolveScope(options);
|
|
109
|
-
const registry = await readRegistry({ ...options, scope });
|
|
110
111
|
const harnessInput = options.agents ?? options.agent;
|
|
111
|
-
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;
|
|
112
116
|
const installs = registry.installs
|
|
113
117
|
.filter((install) => !harnessFilter || harnessFilter.has(install.harness))
|
|
114
118
|
.map((install) => ({
|
|
@@ -124,7 +128,9 @@ export async function listInstalled(options = {}) {
|
|
|
124
128
|
}
|
|
125
129
|
|
|
126
130
|
export async function removeInstalled(profileArgs = [], options = {}) {
|
|
127
|
-
const
|
|
131
|
+
const harnessInput = options.agents ?? options.agent;
|
|
132
|
+
const harnesses = harnessInput ? normalizeAgentList(harnessInput) : [];
|
|
133
|
+
const scope = resolveScope(options, harnesses);
|
|
128
134
|
const registryOptions = { ...options, scope };
|
|
129
135
|
const registry = await readRegistry(registryOptions);
|
|
130
136
|
const selectors = normalizeProfileSelectors(profileArgs);
|
|
@@ -133,8 +139,7 @@ export async function removeInstalled(profileArgs = [], options = {}) {
|
|
|
133
139
|
throw new Error("Specify at least one profile to remove, or pass --all.");
|
|
134
140
|
}
|
|
135
141
|
|
|
136
|
-
const
|
|
137
|
-
const harnessFilter = harnessInput ? new Set(normalizeAgentList(harnessInput)) : undefined;
|
|
142
|
+
const harnessFilter = harnesses.length > 0 ? new Set(harnesses) : undefined;
|
|
138
143
|
const operations = [];
|
|
139
144
|
const matched = registry.installs.filter((install) => {
|
|
140
145
|
const profileMatch = removeAll || selectors.includes(install.profile);
|
|
@@ -173,12 +178,13 @@ export async function removeInstalled(profileArgs = [], options = {}) {
|
|
|
173
178
|
}
|
|
174
179
|
|
|
175
180
|
export async function updateInstalled(profileArgs = [], options = {}) {
|
|
176
|
-
const
|
|
181
|
+
const harnessInput = options.agents ?? options.agent;
|
|
182
|
+
const harnesses = harnessInput ? normalizeAgentList(harnessInput) : [];
|
|
183
|
+
const scope = resolveScope(options, harnesses);
|
|
177
184
|
const registryOptions = { ...options, scope };
|
|
178
185
|
const registry = await readRegistry(registryOptions);
|
|
179
186
|
const selectors = normalizeProfileSelectors(profileArgs);
|
|
180
|
-
const
|
|
181
|
-
const harnessFilter = harnessInput ? new Set(normalizeAgentList(harnessInput)) : undefined;
|
|
187
|
+
const harnessFilter = harnesses.length > 0 ? new Set(harnesses) : undefined;
|
|
182
188
|
const candidates = registry.installs.filter((install) => {
|
|
183
189
|
const profileMatch = selectors.length === 0 || selectors.includes(install.profile);
|
|
184
190
|
const harnessMatch = !harnessFilter || harnessFilter.has(install.harness);
|
|
@@ -186,6 +192,7 @@ export async function updateInstalled(profileArgs = [], options = {}) {
|
|
|
186
192
|
});
|
|
187
193
|
|
|
188
194
|
const operations = [];
|
|
195
|
+
const runInstructions = [];
|
|
189
196
|
for (const install of candidates) {
|
|
190
197
|
const result = await installFromSource(install.source, {
|
|
191
198
|
...options,
|
|
@@ -199,42 +206,126 @@ export async function updateInstalled(profileArgs = [], options = {}) {
|
|
|
199
206
|
...operation,
|
|
200
207
|
action: options.dryRun ? "would-update" : "updated"
|
|
201
208
|
})));
|
|
209
|
+
runInstructions.push(...result.runInstructions);
|
|
202
210
|
}
|
|
203
211
|
|
|
204
|
-
return { operations, registryPath: registryPath(registryOptions) };
|
|
212
|
+
return { operations, registryPath: registryPath(registryOptions), runInstructions };
|
|
205
213
|
}
|
|
206
214
|
|
|
207
215
|
export async function initProfile(name, options = {}) {
|
|
208
216
|
const slug = slugify(name || "new-agent-profile");
|
|
209
217
|
const root = options.cwd ?? process.cwd();
|
|
210
|
-
const profilePath = path.join(root, "agents", "profiles", `${slug}.
|
|
211
|
-
const adapterPath = path.join(root, "agents", "adapters", "codex", `${slug}.md`);
|
|
218
|
+
const profilePath = path.join(root, "agents", "profiles", `${slug}.agent.yaml`);
|
|
212
219
|
|
|
213
220
|
if (!options.force) {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
throw new Error(`${target} already exists. Pass --force to overwrite.`);
|
|
217
|
-
}
|
|
221
|
+
if (existsSync(profilePath)) {
|
|
222
|
+
throw new Error(`${profilePath} already exists. Pass --force to overwrite.`);
|
|
218
223
|
}
|
|
219
224
|
}
|
|
220
225
|
|
|
221
226
|
const title = titleCase(slug);
|
|
222
|
-
const profileContent =
|
|
223
|
-
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`;
|
|
224
228
|
|
|
225
229
|
if (!options.dryRun) {
|
|
226
230
|
await fs.mkdir(path.dirname(profilePath), { recursive: true });
|
|
227
|
-
await fs.mkdir(path.dirname(adapterPath), { recursive: true });
|
|
228
231
|
await fs.writeFile(profilePath, profileContent);
|
|
229
|
-
await fs.writeFile(adapterPath, adapterContent);
|
|
230
232
|
}
|
|
231
233
|
|
|
232
234
|
return {
|
|
233
235
|
action: options.dryRun ? "would-init" : "initialized",
|
|
234
|
-
files: [profilePath
|
|
236
|
+
files: [profilePath]
|
|
235
237
|
};
|
|
236
238
|
}
|
|
237
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
|
+
|
|
238
329
|
function selectProfiles(profiles, selectors, all = false) {
|
|
239
330
|
if (all || selectors.length === 0 || selectors.includes("*")) {
|
|
240
331
|
return profiles;
|
|
@@ -269,11 +360,23 @@ async function writeManagedFile(target, content, options = {}) {
|
|
|
269
360
|
await fs.writeFile(target, content);
|
|
270
361
|
}
|
|
271
362
|
|
|
272
|
-
function resolveScope(options = {}) {
|
|
363
|
+
function resolveScope(options = {}, harnesses = []) {
|
|
273
364
|
if (options.project && options.global) {
|
|
274
365
|
throw new Error("Use either --global or --project, not both.");
|
|
275
366
|
}
|
|
276
|
-
|
|
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";
|
|
277
380
|
}
|
|
278
381
|
|
|
279
382
|
function normalizeProfileSelectors(values) {
|
|
@@ -285,6 +388,72 @@ function normalizeProfileSelectors(values) {
|
|
|
285
388
|
.filter(Boolean);
|
|
286
389
|
}
|
|
287
390
|
|
|
391
|
+
function resolveInstallSelection(split, options = {}) {
|
|
392
|
+
const selectors = [
|
|
393
|
+
...normalizeProfileSelectors(options.profiles ?? options.profile ?? options.skills ?? options.skill)
|
|
394
|
+
];
|
|
395
|
+
const explicitHarnessInput = firstDefined(options.harnesses, options.harness, options.targets, options.target);
|
|
396
|
+
const agentValues = normalizeProfileSelectors(options.agents ?? options.agent);
|
|
397
|
+
const agentHarnesses = [];
|
|
398
|
+
|
|
399
|
+
for (const value of agentValues) {
|
|
400
|
+
if (isHarnessSelector(value)) {
|
|
401
|
+
agentHarnesses.push(value);
|
|
402
|
+
} else {
|
|
403
|
+
selectors.push(value);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (split.profile) {
|
|
408
|
+
selectors.push(split.profile);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return {
|
|
412
|
+
selectors,
|
|
413
|
+
harnessInput: explicitHarnessInput ?? (agentHarnesses.length > 0 ? agentHarnesses : undefined)
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function resolveUseSelection(split, options = {}) {
|
|
418
|
+
const selectors = [
|
|
419
|
+
...normalizeProfileSelectors(options.profiles ?? options.profile ?? options.skills ?? options.skill)
|
|
420
|
+
];
|
|
421
|
+
const explicitHarnessInput = firstDefined(options.harnesses, options.harness, options.targets, options.target);
|
|
422
|
+
const agentValues = normalizeProfileSelectors(options.agents ?? options.agent);
|
|
423
|
+
const agentHarnesses = [];
|
|
424
|
+
|
|
425
|
+
for (const value of agentValues) {
|
|
426
|
+
if (isHarnessSelector(value)) {
|
|
427
|
+
agentHarnesses.push(value);
|
|
428
|
+
} else {
|
|
429
|
+
selectors.push(value);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (split.profile) {
|
|
434
|
+
selectors.push(split.profile);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const uniqueSelectors = [...new Set(selectors)];
|
|
438
|
+
if (uniqueSelectors.length > 1) {
|
|
439
|
+
throw new Error(`Use renders one profile at a time. Received: ${uniqueSelectors.join(", ")}`);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
return {
|
|
443
|
+
profileSelector: uniqueSelectors[0],
|
|
444
|
+
harnessInput: explicitHarnessInput ?? (agentHarnesses.length > 0 ? agentHarnesses : undefined)
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function isHarnessSelector(value) {
|
|
449
|
+
const normalized = String(value).toLowerCase();
|
|
450
|
+
return normalized === "*" || AGENT_ALIASES.has(normalized);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function firstDefined(...values) {
|
|
454
|
+
return values.find((value) => value !== undefined);
|
|
455
|
+
}
|
|
456
|
+
|
|
288
457
|
function profileSummary(profile, source) {
|
|
289
458
|
return {
|
|
290
459
|
slug: profile.slug,
|
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
|
}
|