awesome-agents 0.1.3 → 0.1.5
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 +7 -4
- package/CHANGELOG.md +26 -0
- package/README.md +14 -11
- package/docs/cli.md +1 -1
- package/docs/product/command-model.md +6 -2
- package/docs/product/harness-targets.md +3 -2
- package/docs/product/product-scope.md +6 -1
- package/docs/product/profile-source-format.md +11 -12
- package/package.json +1 -1
- package/src/constants.js +8 -1
- package/src/help.js +4 -4
- package/src/installer.js +51 -5
- package/src/renderers.js +10 -5
- package/src/source.js +136 -16
package/AGENTS.md
CHANGED
|
@@ -9,10 +9,11 @@ OpenCode.
|
|
|
9
9
|
|
|
10
10
|
The canonical source format is repo-neutral:
|
|
11
11
|
|
|
12
|
-
- `agents
|
|
13
|
-
- `agents
|
|
14
|
-
- `agents
|
|
15
|
-
- `agents
|
|
12
|
+
- `agents/<slug>/agent.yaml`: preferred YAML profile definition.
|
|
13
|
+
- `agents/<slug>/agent.agf.yaml`: Agent Format-style YAML profile definition.
|
|
14
|
+
- `agents/<slug>/agent.md`: Markdown profile definition with YAML frontmatter.
|
|
15
|
+
- `agents/<slug>/scripts/`: agent-owned scripts installed into the agent home.
|
|
16
|
+
- `agents/<slug>/references/`: agent-owned references installed into the agent home.
|
|
16
17
|
|
|
17
18
|
Never hard-code a particular source repository into runtime behavior, tests, or
|
|
18
19
|
examples. Use neutral fixture/source names unless a test is explicitly about
|
|
@@ -53,6 +54,8 @@ source resolution.
|
|
|
53
54
|
## Safety
|
|
54
55
|
|
|
55
56
|
- Never overwrite or delete unmanaged harness files by default.
|
|
57
|
+
- Install agent-owned support files into `~/.agents/homes/<slug>/`, not into
|
|
58
|
+
harness configuration directories.
|
|
56
59
|
- Keep install behavior deterministic and noninteractive. `--yes` exists for
|
|
57
60
|
parity with `npx skills`, but the CLI should not require prompts to complete.
|
|
58
61
|
- Do not mutate harness configuration beyond writing the selected generated
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
This file is maintained by `npm run changelog` and `npm run release`. Release entries are generated from git commit history.
|
|
4
4
|
|
|
5
|
+
## 0.1.5 - 2026-07-06
|
|
6
|
+
|
|
7
|
+
Changes since `v0.1.4`.
|
|
8
|
+
|
|
9
|
+
- No commits found for this release.
|
|
10
|
+
|
|
11
|
+
## 0.1.4 - 2026-07-06
|
|
12
|
+
|
|
13
|
+
Changes since `v0.1.3`.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- Add justfile for lint/test/release/publish workflows (7fe04ba)
|
|
18
|
+
- Add agent detail page and visual treatment explorations (7d29557)
|
|
19
|
+
- Add Claude Code plugin for browsing and installing agent profiles (5a11e6f)
|
|
20
|
+
- Add awesome-agents.com marketing/directory site (9ce50cd)
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- Link leaderboard rows to per-agent pages (6f2c471)
|
|
25
|
+
- Default install to every detected harness on PATH (b5af687)
|
|
26
|
+
- Render Codex profile metadata as comments (59395cc)
|
|
27
|
+
- Install agent-owned support files (3ede3ce)
|
|
28
|
+
|
|
5
29
|
## 0.1.3 - 2026-07-05
|
|
6
30
|
|
|
7
31
|
Initial tracked release.
|
|
@@ -31,6 +55,8 @@ Initial tracked release.
|
|
|
31
55
|
- Replace source-specific help and documentation examples with neutral `owner/repo` examples.
|
|
32
56
|
- Parse repo-neutral YAML profile files in addition to Markdown frontmatter files.
|
|
33
57
|
- Replace source-specific test fixtures with neutral profile-source fixtures.
|
|
58
|
+
- Prefer `agents/<slug>/agent.yaml` source directories and install colocated
|
|
59
|
+
`scripts/` and `references/` into the agent home.
|
|
34
60
|
|
|
35
61
|
## 0.1.1 - 2026-07-05
|
|
36
62
|
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ agent profile instead of a skill.
|
|
|
6
6
|
|
|
7
7
|
Supported sources are GitHub repos, Git URLs, or local checkouts that use the
|
|
8
8
|
agent-profile source layout. Profiles are read from YAML or Markdown files under
|
|
9
|
-
`agents
|
|
9
|
+
`agents/<slug>/`, adapted for the selected harness, and installed into the
|
|
10
10
|
right place for Codex, Claude Code, or OpenCode.
|
|
11
11
|
|
|
12
12
|
## Install And Run
|
|
@@ -43,7 +43,9 @@ Useful install options:
|
|
|
43
43
|
- `--agent <slug>` to select an agent profile, for example `--agent triage-agent`
|
|
44
44
|
- `--profile <slug>` or `--skill <slug>` as explicit profile aliases; `--skill`
|
|
45
45
|
is command-shape compatibility and does not mean the artifact is a skill
|
|
46
|
-
- `--harness codex|claude-code|opencode|*` to select target harnesses
|
|
46
|
+
- `--harness codex|claude-code|opencode|*` to select target harnesses; without
|
|
47
|
+
it, the CLI installs to every harness whose CLI it finds on `PATH`
|
|
48
|
+
(falling back to Codex if none are found)
|
|
47
49
|
- `--all` to install all profiles to all supported harnesses
|
|
48
50
|
- `--dry-run` to preview writes
|
|
49
51
|
- `--project` for project-level install where supported; Codex profiles install globally
|
|
@@ -89,19 +91,20 @@ An agent-profile source should look like:
|
|
|
89
91
|
|
|
90
92
|
```text
|
|
91
93
|
agents/
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
94
|
+
triage-agent/
|
|
95
|
+
agent.yaml
|
|
96
|
+
ops-agent/
|
|
97
|
+
agent.agf.yaml
|
|
98
|
+
scripts/
|
|
99
|
+
heartbeat.sh
|
|
100
|
+
references/
|
|
101
|
+
runbook.md
|
|
99
102
|
```
|
|
100
103
|
|
|
101
104
|
YAML profile files are preferred. The loader also accepts Markdown files with
|
|
102
105
|
YAML frontmatter for compatibility with tools that use `.agent.md`-style
|
|
103
|
-
profiles.
|
|
104
|
-
|
|
106
|
+
profiles. Agent-owned `scripts/` and `references/` are installed into
|
|
107
|
+
`~/.agents/homes/<slug>/scripts` and `~/.agents/homes/<slug>/references`.
|
|
105
108
|
|
|
106
109
|
## Examples
|
|
107
110
|
|
package/docs/cli.md
CHANGED
|
@@ -25,7 +25,7 @@ Supported source values:
|
|
|
25
25
|
- GitHub URL: `https://github.com/owner/repo`
|
|
26
26
|
|
|
27
27
|
For GitHub sources, the CLI clones a shallow temporary copy and reads
|
|
28
|
-
`agents
|
|
28
|
+
`agents/<slug>/agent.yaml` style definitions.
|
|
29
29
|
|
|
30
30
|
There is no default source. `add`, `install`, and `use` require the caller to
|
|
31
31
|
provide a source explicitly.
|
|
@@ -38,8 +38,12 @@ Accepted implementation decision:
|
|
|
38
38
|
- Codex is an exception because `codex --profile <name>` loads
|
|
39
39
|
`$CODEX_HOME/<name>.config.toml`. Codex profile installs should use that
|
|
40
40
|
user-level config-layer target.
|
|
41
|
-
-
|
|
42
|
-
|
|
41
|
+
- When no `--harness` or legacy harness-valued `--agent` is provided, the CLI
|
|
42
|
+
installs to every supported harness whose CLI is detected on `PATH` (checks
|
|
43
|
+
for `codex`, `claude`, `opencode`). If none are detected, it falls back to
|
|
44
|
+
Codex only.
|
|
45
|
+
- `--harness <harness>` (or a harness-valued `--agent`) narrows the install to
|
|
46
|
+
that single harness, overriding detection.
|
|
43
47
|
- The CLI is noninteractive in the initial scaffold.
|
|
44
48
|
- `--yes` is accepted for command-shape parity, but prompts are not currently required.
|
|
45
49
|
|
|
@@ -29,11 +29,12 @@ file from `CODEX_HOME`.
|
|
|
29
29
|
|
|
30
30
|
The generated TOML includes:
|
|
31
31
|
|
|
32
|
-
- `name`
|
|
33
|
-
- `description`
|
|
34
32
|
- optional model settings
|
|
35
33
|
- `developer_instructions`
|
|
36
34
|
|
|
35
|
+
Profile name and summary are emitted as comments, not TOML keys, because Codex
|
|
36
|
+
rejects unknown configuration fields under strict config parsing.
|
|
37
|
+
|
|
37
38
|
## Claude Code
|
|
38
39
|
|
|
39
40
|
Generated Claude Code subagents install to:
|
|
@@ -8,7 +8,7 @@ These notes capture what `awesome-agents` is and why it exists.
|
|
|
8
8
|
- It should be installable with `npx`.
|
|
9
9
|
- Its command structure should mirror `npx skills`, but for agent profiles.
|
|
10
10
|
- It should install profiles from any explicit source repository that follows
|
|
11
|
-
the `agents
|
|
11
|
+
the `agents/<slug>/agent.yaml` source layout.
|
|
12
12
|
- Initial harness targets are Codex, Claude Code, and OpenCode.
|
|
13
13
|
- The package should be published after the scaffold works.
|
|
14
14
|
|
|
@@ -40,3 +40,8 @@ Product correction:
|
|
|
40
40
|
the artifact shape and target harnesses.
|
|
41
41
|
- Examples and tests should use neutral source names unless they are testing
|
|
42
42
|
source resolution itself.
|
|
43
|
+
- Source repositories should use one agent definition format per agent directory:
|
|
44
|
+
`agents/<slug>/agent.yaml` plus colocated support material.
|
|
45
|
+
- `awesome-agents` installs that local support material into
|
|
46
|
+
`~/.agents/homes/<slug>/` and transcribes the agent definition to the target
|
|
47
|
+
harness.
|
|
@@ -8,25 +8,23 @@ The source format is intentionally repo-neutral:
|
|
|
8
8
|
|
|
9
9
|
```text
|
|
10
10
|
agents/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
adapters/
|
|
16
|
-
<harness>/
|
|
17
|
-
<profile>.md
|
|
11
|
+
<profile>/
|
|
12
|
+
agent.yaml
|
|
13
|
+
scripts/
|
|
14
|
+
references/
|
|
18
15
|
```
|
|
19
16
|
|
|
20
17
|
Canonical profiles live at:
|
|
21
18
|
|
|
22
19
|
```text
|
|
23
|
-
agents
|
|
20
|
+
agents/<profile>/{agent.yaml,agent.yml,agent.agf.yaml,agent.agf.yml,agent.md}
|
|
24
21
|
```
|
|
25
22
|
|
|
26
|
-
Optional
|
|
23
|
+
Optional agent-owned support material lives at:
|
|
27
24
|
|
|
28
25
|
```text
|
|
29
|
-
agents
|
|
26
|
+
agents/<profile>/scripts/*
|
|
27
|
+
agents/<profile>/references/*
|
|
30
28
|
```
|
|
31
29
|
|
|
32
30
|
## Profile Files
|
|
@@ -42,8 +40,9 @@ of emerging YAML agent-definition shapes:
|
|
|
42
40
|
|
|
43
41
|
The CLI should preserve canonical profile content and generate harness-specific install files. A profile is reusable product content, not local machine setup.
|
|
44
42
|
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
Agent-owned scripts and references should be installed into
|
|
44
|
+
`~/.agents/homes/<profile>/scripts` and
|
|
45
|
+
`~/.agents/homes/<profile>/references`.
|
|
47
46
|
|
|
48
47
|
Profile source files are intentionally under `agents/`, not `skills/`, because
|
|
49
48
|
the source format models agent profiles separately from loadable skills.
|
package/package.json
CHANGED
package/src/constants.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export const PACKAGE_NAME = "awesome-agents";
|
|
2
|
-
export const PACKAGE_VERSION = "0.1.
|
|
2
|
+
export const PACKAGE_VERSION = "0.1.5";
|
|
3
3
|
export const DEFAULT_AGENT = "codex";
|
|
4
4
|
export const SUPPORTED_AGENTS = ["codex", "claude-code", "opencode"];
|
|
5
5
|
export const REGISTRY_DIRNAME = ".awesome-agents";
|
|
@@ -16,3 +16,10 @@ export const AGENT_ALIASES = new Map([
|
|
|
16
16
|
["opencode", "opencode"],
|
|
17
17
|
["open-code", "opencode"]
|
|
18
18
|
]);
|
|
19
|
+
|
|
20
|
+
// The CLI binary used to detect whether a harness is available on PATH.
|
|
21
|
+
export const HARNESS_COMMANDS = new Map([
|
|
22
|
+
["codex", "codex"],
|
|
23
|
+
["claude-code", "claude"],
|
|
24
|
+
["opencode", "opencode"]
|
|
25
|
+
]);
|
package/src/help.js
CHANGED
|
@@ -173,7 +173,7 @@ export function formatMissingSourceError(commandName = "add") {
|
|
|
173
173
|
].join("\n");
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
-
const sourceHint = `${ui.profile("agents
|
|
176
|
+
const sourceHint = `${ui.profile("agents/<slug>/agent.yaml")} plus optional ${ui.profile("agents/<slug>/{scripts,references}")}`;
|
|
177
177
|
const harnesses = SUPPORTED_AGENTS.join("|");
|
|
178
178
|
const exampleSource = "owner/repo";
|
|
179
179
|
const exampleProfile = "triage-agent";
|
|
@@ -288,8 +288,8 @@ const commandHelp = {
|
|
|
288
288
|
{
|
|
289
289
|
title: "Install Flow:",
|
|
290
290
|
rows: [
|
|
291
|
-
"1. Read canonical
|
|
292
|
-
"2.
|
|
291
|
+
"1. Read canonical definitions from agents/<slug>/agent.yaml or compatible variants.",
|
|
292
|
+
"2. Install agent-owned scripts and references into ~/.agents/homes/<slug>/.",
|
|
293
293
|
"3. Render Codex, Claude Code, or OpenCode files and record them in the registry."
|
|
294
294
|
]
|
|
295
295
|
},
|
|
@@ -315,7 +315,7 @@ const commandHelp = {
|
|
|
315
315
|
{
|
|
316
316
|
title: "Choose Targets:",
|
|
317
317
|
rows: [
|
|
318
|
-
{ term: ui.option("(default)"), description: "
|
|
318
|
+
{ term: ui.option("(default)"), description: "Every harness detected on PATH (codex, claude, opencode); falls back to Codex if none are found" },
|
|
319
319
|
{ term: ui.option("--harness opencode"), description: "Render for one harness" },
|
|
320
320
|
{ term: ui.option("--harness codex claude-code"), description: "Render for multiple harnesses" },
|
|
321
321
|
{ term: ui.option("--harness *"), description: "Render for every supported harness" },
|
package/src/installer.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
3
4
|
import path from "node:path";
|
|
4
|
-
import { AGENT_ALIASES,
|
|
5
|
+
import { AGENT_ALIASES, HARNESS_COMMANDS, SUPPORTED_AGENTS } from "./constants.js";
|
|
5
6
|
import { contentHash, isGeneratedContent, normalizeAgentList, renderForAgent, resolveTargetPath } from "./renderers.js";
|
|
6
|
-
import { loadCatalog, materializeSource, splitSourceSpec } from "./source.js";
|
|
7
|
+
import { expandHome, loadCatalog, materializeSource, splitSourceSpec } from "./source.js";
|
|
7
8
|
import { readRegistry, registryPath, removeInstall, upsertInstall, writeRegistry } from "./registry.js";
|
|
8
9
|
|
|
9
10
|
export async function listAvailable(sourceInput, options = {}) {
|
|
@@ -23,7 +24,7 @@ export async function installFromSource(sourceSpec, options = {}) {
|
|
|
23
24
|
const { selectors, harnessInput } = resolveInstallSelection(split, options);
|
|
24
25
|
const harnesses = normalizeAgentList(harnessInput, {
|
|
25
26
|
all: options.all,
|
|
26
|
-
defaultAgent:
|
|
27
|
+
defaultAgent: detectAvailableHarnesses()
|
|
27
28
|
});
|
|
28
29
|
const scope = resolveScope(options, harnesses);
|
|
29
30
|
|
|
@@ -37,6 +38,7 @@ export async function installFromSource(sourceSpec, options = {}) {
|
|
|
37
38
|
const installedAt = new Date().toISOString();
|
|
38
39
|
|
|
39
40
|
for (const profile of profiles) {
|
|
41
|
+
const supportTargets = await installProfileSupport(profile, options);
|
|
40
42
|
for (const harness of harnesses) {
|
|
41
43
|
const content = renderForAgent(profile, harness, { source: materialized.source });
|
|
42
44
|
const target = resolveTargetPath(profile, harness, { ...options, scope });
|
|
@@ -53,6 +55,7 @@ export async function installFromSource(sourceSpec, options = {}) {
|
|
|
53
55
|
target,
|
|
54
56
|
installedAt,
|
|
55
57
|
contentSha256: contentHash(content),
|
|
58
|
+
supportTargets,
|
|
56
59
|
dryRun: Boolean(options.dryRun)
|
|
57
60
|
};
|
|
58
61
|
|
|
@@ -89,7 +92,7 @@ export async function useFromSource(sourceSpec, options = {}) {
|
|
|
89
92
|
}
|
|
90
93
|
|
|
91
94
|
const harness = normalizeAgentList(harnessInput, {
|
|
92
|
-
defaultAgent:
|
|
95
|
+
defaultAgent: detectAvailableHarnesses()
|
|
93
96
|
})[0];
|
|
94
97
|
const materialized = await materializeSource(split.source, options);
|
|
95
98
|
try {
|
|
@@ -215,7 +218,7 @@ export async function updateInstalled(profileArgs = [], options = {}) {
|
|
|
215
218
|
export async function initProfile(name, options = {}) {
|
|
216
219
|
const slug = slugify(name || "new-agent-profile");
|
|
217
220
|
const root = options.cwd ?? process.cwd();
|
|
218
|
-
const profilePath = path.join(root, "agents",
|
|
221
|
+
const profilePath = path.join(root, "agents", slug, "agent.yaml");
|
|
219
222
|
|
|
220
223
|
if (!options.force) {
|
|
221
224
|
if (existsSync(profilePath)) {
|
|
@@ -237,6 +240,45 @@ export async function initProfile(name, options = {}) {
|
|
|
237
240
|
};
|
|
238
241
|
}
|
|
239
242
|
|
|
243
|
+
async function installProfileSupport(profile, options = {}) {
|
|
244
|
+
if (!profile.supportDirs?.length) {
|
|
245
|
+
return [];
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const home = path.resolve(expandHome(options.home ?? os.homedir()));
|
|
249
|
+
const agentHome = path.join(home, ".agents", "homes", profile.slug);
|
|
250
|
+
const targets = [];
|
|
251
|
+
|
|
252
|
+
for (const supportDir of profile.supportDirs) {
|
|
253
|
+
const files = await listFilesRecursive(supportDir.sourcePath);
|
|
254
|
+
for (const source of files) {
|
|
255
|
+
const relative = path.relative(supportDir.sourcePath, source);
|
|
256
|
+
const target = path.join(agentHome, supportDir.kind, relative);
|
|
257
|
+
targets.push(target);
|
|
258
|
+
if (!options.dryRun) {
|
|
259
|
+
await fs.mkdir(path.dirname(target), { recursive: true });
|
|
260
|
+
await fs.copyFile(source, target);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return targets;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async function listFilesRecursive(root) {
|
|
269
|
+
const entries = await fs.readdir(root, { withFileTypes: true });
|
|
270
|
+
const files = [];
|
|
271
|
+
for (const entry of entries) {
|
|
272
|
+
const entryPath = path.join(root, entry.name);
|
|
273
|
+
if (entry.isDirectory()) {
|
|
274
|
+
files.push(...await listFilesRecursive(entryPath));
|
|
275
|
+
} else if (entry.isFile()) {
|
|
276
|
+
files.push(entryPath);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return files.sort();
|
|
280
|
+
}
|
|
281
|
+
|
|
240
282
|
function buildRunInstructions(operations, env = process.env) {
|
|
241
283
|
const instructions = [];
|
|
242
284
|
const seen = new Set();
|
|
@@ -302,6 +344,10 @@ function runInstructionForOperation(operation, env) {
|
|
|
302
344
|
return undefined;
|
|
303
345
|
}
|
|
304
346
|
|
|
347
|
+
function detectAvailableHarnesses(env = process.env) {
|
|
348
|
+
return SUPPORTED_AGENTS.filter((harness) => commandExists(HARNESS_COMMANDS.get(harness), env));
|
|
349
|
+
}
|
|
350
|
+
|
|
305
351
|
function commandExists(command, env) {
|
|
306
352
|
const pathValue = env.PATH ?? "";
|
|
307
353
|
if (!pathValue) {
|
package/src/renderers.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { AGENT_ALIASES, GENERATED_MARKER, SUPPORTED_AGENTS } from "./constants.js";
|
|
4
|
+
import { AGENT_ALIASES, DEFAULT_AGENT, GENERATED_MARKER, SUPPORTED_AGENTS } from "./constants.js";
|
|
5
5
|
import { stringifyFrontmatter } from "./frontmatter.js";
|
|
6
6
|
import { expandHome } from "./source.js";
|
|
7
7
|
|
|
@@ -20,7 +20,8 @@ export function normalizeAgentList(input, options = {}) {
|
|
|
20
20
|
|
|
21
21
|
const rawAgents = flattenValues(input);
|
|
22
22
|
if (rawAgents.length === 0) {
|
|
23
|
-
|
|
23
|
+
const fallback = arrayify(options.defaultAgent);
|
|
24
|
+
return fallback.length > 0 ? fallback : [DEFAULT_AGENT];
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
if (rawAgents.includes("*")) {
|
|
@@ -105,9 +106,9 @@ function renderCodex(profile, context) {
|
|
|
105
106
|
`# ${GENERATED_MARKER}.`,
|
|
106
107
|
`# Source: ${context.source}`,
|
|
107
108
|
`# Profile: ${profile.slug}`,
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
`# Display name: ${tomlComment(profile.name)}`,
|
|
110
|
+
`# Summary: ${tomlComment(profile.summary || profile.name)}`,
|
|
111
|
+
""
|
|
111
112
|
];
|
|
112
113
|
|
|
113
114
|
if (model && model !== "inherit") {
|
|
@@ -223,6 +224,10 @@ function tomlString(value) {
|
|
|
223
224
|
return JSON.stringify(String(value));
|
|
224
225
|
}
|
|
225
226
|
|
|
227
|
+
function tomlComment(value) {
|
|
228
|
+
return String(value).replace(/\s+/g, " ").trim();
|
|
229
|
+
}
|
|
230
|
+
|
|
226
231
|
function tomlMultiline(value) {
|
|
227
232
|
const text = String(value).trimEnd();
|
|
228
233
|
if (!text.includes("'''")) {
|
package/src/source.js
CHANGED
|
@@ -18,6 +18,26 @@ const PROFILE_SUFFIXES = [
|
|
|
18
18
|
".agent.md",
|
|
19
19
|
".md"
|
|
20
20
|
];
|
|
21
|
+
const AGENT_DEFINITION_NAMES = [
|
|
22
|
+
"agent.agent.yaml",
|
|
23
|
+
"agent.agent.yml",
|
|
24
|
+
"agent.agf.yaml",
|
|
25
|
+
"agent.agf.yml",
|
|
26
|
+
"agent.yaml",
|
|
27
|
+
"agent.yml",
|
|
28
|
+
"agent.agent.md",
|
|
29
|
+
"agent.md",
|
|
30
|
+
"profile.agent.yaml",
|
|
31
|
+
"profile.agent.yml",
|
|
32
|
+
"profile.agf.yaml",
|
|
33
|
+
"profile.agf.yml",
|
|
34
|
+
"profile.yaml",
|
|
35
|
+
"profile.yml",
|
|
36
|
+
"profile.agent.md",
|
|
37
|
+
"profile.md"
|
|
38
|
+
];
|
|
39
|
+
const RESERVED_AGENT_DIRS = new Set(["profiles", "adapters"]);
|
|
40
|
+
const SUPPORT_DIRS = ["references", "scripts"];
|
|
21
41
|
|
|
22
42
|
export function splitSourceSpec(spec) {
|
|
23
43
|
if (!spec) {
|
|
@@ -117,28 +137,30 @@ export async function materializeSource(sourceInput, options = {}) {
|
|
|
117
137
|
}
|
|
118
138
|
|
|
119
139
|
async function assertAgentProfileLayout(sourcePath) {
|
|
120
|
-
const
|
|
121
|
-
if (
|
|
122
|
-
throw new Error(`No
|
|
140
|
+
const entries = await discoverProfileEntries(sourcePath);
|
|
141
|
+
if (entries.length === 0) {
|
|
142
|
+
throw new Error(`No agent profiles found in ${sourcePath}. Expected agents/<slug>/agent.yaml.`);
|
|
123
143
|
}
|
|
124
144
|
}
|
|
125
145
|
|
|
126
146
|
export async function loadCatalog(sourcePath) {
|
|
127
|
-
const
|
|
128
|
-
const adapterRoot = path.join(sourcePath, "agents", "adapters");
|
|
129
|
-
const files = await fs.readdir(profilesDir, { withFileTypes: true });
|
|
147
|
+
const entries = await discoverProfileEntries(sourcePath);
|
|
130
148
|
const profiles = [];
|
|
149
|
+
const seen = new Set();
|
|
131
150
|
|
|
132
|
-
for (const
|
|
133
|
-
|
|
151
|
+
for (const entry of entries) {
|
|
152
|
+
const profile = await loadProfileFile(entry.filePath, sourcePath, {
|
|
153
|
+
slugHint: entry.slugHint,
|
|
154
|
+
agentRoot: entry.agentRoot
|
|
155
|
+
});
|
|
156
|
+
if (seen.has(profile.slug)) {
|
|
134
157
|
continue;
|
|
135
158
|
}
|
|
136
|
-
|
|
137
|
-
const filePath = path.join(profilesDir, file.name);
|
|
138
|
-
const profile = await loadProfileFile(filePath, sourcePath);
|
|
159
|
+
seen.add(profile.slug);
|
|
139
160
|
profiles.push({
|
|
140
161
|
...profile,
|
|
141
|
-
adapters: await
|
|
162
|
+
adapters: await loadProfileAdapters(entry, profile.slug, sourcePath),
|
|
163
|
+
supportDirs: await loadSupportDirs(entry.agentRoot)
|
|
142
164
|
});
|
|
143
165
|
}
|
|
144
166
|
|
|
@@ -146,8 +168,104 @@ export async function loadCatalog(sourcePath) {
|
|
|
146
168
|
return { sourcePath, profiles };
|
|
147
169
|
}
|
|
148
170
|
|
|
171
|
+
async function discoverProfileEntries(sourcePath) {
|
|
172
|
+
return [
|
|
173
|
+
...await discoverAgentDirectoryProfiles(sourcePath),
|
|
174
|
+
...await discoverLegacyProfileDirectory(sourcePath)
|
|
175
|
+
];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function discoverAgentDirectoryProfiles(sourcePath) {
|
|
179
|
+
const agentsRoot = path.join(sourcePath, "agents");
|
|
180
|
+
if (!existsSync(agentsRoot)) {
|
|
181
|
+
return [];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const entries = [];
|
|
185
|
+
const files = await fs.readdir(agentsRoot, { withFileTypes: true });
|
|
186
|
+
for (const file of files) {
|
|
187
|
+
if (!file.isDirectory() || RESERVED_AGENT_DIRS.has(file.name)) {
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
const agentRoot = path.join(agentsRoot, file.name);
|
|
191
|
+
const filePath = await findAgentDefinitionFile(agentRoot, file.name);
|
|
192
|
+
if (!filePath) {
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
entries.push({
|
|
196
|
+
layout: "agent-directory",
|
|
197
|
+
filePath,
|
|
198
|
+
slugHint: file.name,
|
|
199
|
+
agentRoot,
|
|
200
|
+
adapterRoot: path.join(agentRoot, "adapters")
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
return entries;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async function discoverLegacyProfileDirectory(sourcePath) {
|
|
207
|
+
const profilesDir = path.join(sourcePath, "agents", "profiles");
|
|
208
|
+
if (!existsSync(profilesDir)) {
|
|
209
|
+
return [];
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const entries = [];
|
|
213
|
+
const files = await fs.readdir(profilesDir, { withFileTypes: true });
|
|
214
|
+
for (const file of files) {
|
|
215
|
+
if (!file.isFile() || !isProfileFile(file.name)) {
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
entries.push({
|
|
219
|
+
layout: "legacy-profiles-directory",
|
|
220
|
+
filePath: path.join(profilesDir, file.name),
|
|
221
|
+
slugHint: profileSlugFromFilename(file.name),
|
|
222
|
+
adapterRoot: path.join(sourcePath, "agents", "adapters")
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
return entries;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async function findAgentDefinitionFile(agentRoot, slug) {
|
|
229
|
+
for (const name of AGENT_DEFINITION_NAMES) {
|
|
230
|
+
const candidate = path.join(agentRoot, name);
|
|
231
|
+
if (existsSync(candidate)) {
|
|
232
|
+
return candidate;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
for (const suffix of PROFILE_SUFFIXES) {
|
|
236
|
+
const candidate = path.join(agentRoot, `${slug}${suffix}`);
|
|
237
|
+
if (existsSync(candidate)) {
|
|
238
|
+
return candidate;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return undefined;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async function loadProfileAdapters(entry, slug, sourcePath) {
|
|
245
|
+
const adapters = {};
|
|
246
|
+
Object.assign(adapters, await loadAdapters(entry.adapterRoot, slug, sourcePath));
|
|
247
|
+
if (entry.layout === "agent-directory") {
|
|
248
|
+
Object.assign(adapters, await loadAdapters(path.join(sourcePath, "agents", "adapters"), slug, sourcePath));
|
|
249
|
+
}
|
|
250
|
+
return adapters;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async function loadSupportDirs(agentRoot) {
|
|
254
|
+
if (!agentRoot) {
|
|
255
|
+
return [];
|
|
256
|
+
}
|
|
257
|
+
const dirs = [];
|
|
258
|
+
for (const kind of SUPPORT_DIRS) {
|
|
259
|
+
const sourcePath = path.join(agentRoot, kind);
|
|
260
|
+
if (existsSync(sourcePath)) {
|
|
261
|
+
dirs.push({ kind, sourcePath });
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return dirs;
|
|
265
|
+
}
|
|
266
|
+
|
|
149
267
|
async function loadAdapters(adapterRoot, slug, sourcePath) {
|
|
150
|
-
if (!existsSync(adapterRoot)) {
|
|
268
|
+
if (!adapterRoot || !existsSync(adapterRoot)) {
|
|
151
269
|
return {};
|
|
152
270
|
}
|
|
153
271
|
|
|
@@ -178,10 +296,10 @@ async function loadAdapters(adapterRoot, slug, sourcePath) {
|
|
|
178
296
|
return adapters;
|
|
179
297
|
}
|
|
180
298
|
|
|
181
|
-
async function loadProfileFile(filePath, sourcePath) {
|
|
299
|
+
async function loadProfileFile(filePath, sourcePath, options = {}) {
|
|
182
300
|
const raw = await fs.readFile(filePath, "utf8");
|
|
183
301
|
const parsed = await loadDefinitionFile(filePath, raw);
|
|
184
|
-
const fallbackSlug = profileSlugFromFilename(path.basename(filePath));
|
|
302
|
+
const fallbackSlug = options.slugHint ?? profileSlugFromFilename(path.basename(filePath));
|
|
185
303
|
const slug = parsed.attributes.slug ?? parsed.attributes.id ?? fallbackSlug;
|
|
186
304
|
const name = parsed.attributes.name ?? titleize(slug);
|
|
187
305
|
const summary = parsed.attributes.summary ?? parsed.attributes.description ?? "";
|
|
@@ -200,7 +318,9 @@ async function loadProfileFile(filePath, sourcePath) {
|
|
|
200
318
|
raw,
|
|
201
319
|
filePath,
|
|
202
320
|
relativePath: path.relative(sourcePath, filePath),
|
|
203
|
-
format: parsed.format
|
|
321
|
+
format: parsed.format,
|
|
322
|
+
agentRoot: options.agentRoot,
|
|
323
|
+
layout: options.agentRoot ? "agent-directory" : "legacy-profiles-directory"
|
|
204
324
|
};
|
|
205
325
|
}
|
|
206
326
|
|