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 CHANGED
@@ -9,10 +9,11 @@ OpenCode.
9
9
 
10
10
  The canonical source format is repo-neutral:
11
11
 
12
- - `agents/profiles/*.agent.yaml`: preferred YAML profile definitions.
13
- - `agents/profiles/*.agf.yaml`: Agent Format-style YAML profile definitions.
14
- - `agents/profiles/*.md`: Markdown profile definitions with YAML frontmatter.
15
- - `agents/adapters/<harness>/*`: optional harness-specific metadata and notes.
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/profiles/`, adapted for the selected harness, and installed into the
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
- profiles/
93
- triage-agent.agent.yaml
94
- ops-agent.agf.yaml
95
- legacy-agent.md
96
- adapters/
97
- codex/
98
- optional-agent.md
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. Adapters are optional and can provide harness-specific metadata, but a
104
- profile should stand on its own without requiring an adapter.
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/profiles`.
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
- - Codex is the default harness when no `--harness` or legacy harness-valued
42
- `--agent` is provided.
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/profiles` source layout.
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
- profiles/
12
- <profile>.agent.yaml
13
- <profile>.agf.yaml
14
- <profile>.md
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/profiles/*.{agent.yaml,agent.yml,agf.yaml,agf.yml,yaml,yml,agent.md,md}
20
+ agents/<profile>/{agent.yaml,agent.yml,agent.agf.yaml,agent.agf.yml,agent.md}
24
21
  ```
25
22
 
26
- Optional harness adapters live at:
23
+ Optional agent-owned support material lives at:
27
24
 
28
25
  ```text
29
- agents/adapters/<harness>/*
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
- Adapters are optional. A profile should be useful without a harness adapter; an
46
- adapter is only an override for harness-specific metadata or instructions.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "awesome-agents",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Install reusable agent profiles into Codex, Claude Code, and OpenCode.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/constants.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export const PACKAGE_NAME = "awesome-agents";
2
- export const PACKAGE_VERSION = "0.1.3";
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/profiles/*.{agent.yaml,agf.yaml,md}")} plus optional ${ui.profile("agents/adapters/<harness>/*")}`;
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 profiles from agents/profiles/*.{agent.yaml,agf.yaml,md}.",
292
- "2. Merge optional harness notes from agents/adapters/<harness>/*.",
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: "Codex, installed globally as a config profile" },
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, DEFAULT_AGENT, SUPPORTED_AGENTS } from "./constants.js";
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: DEFAULT_AGENT
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: DEFAULT_AGENT
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", "profiles", `${slug}.agent.yaml`);
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
- return [options.defaultAgent ?? "codex"];
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
- `name = ${tomlString(profile.slug)}`,
110
- `description = ${tomlString(profile.summary || profile.name)}`
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 profilesDir = path.join(sourcePath, "agents", "profiles");
121
- if (!existsSync(profilesDir)) {
122
- throw new Error(`No agents/profiles directory found in ${sourcePath}`);
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 profilesDir = path.join(sourcePath, "agents", "profiles");
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 file of files) {
133
- if (!file.isFile() || !isProfileFile(file.name)) {
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 loadAdapters(adapterRoot, profile.slug, sourcePath)
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