awesome-agents 0.1.3 → 0.1.6

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/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) {
@@ -70,6 +90,7 @@ export function normalizeGithubUrl(source) {
70
90
  export async function materializeSource(sourceInput, options = {}) {
71
91
  const home = options.home ?? os.homedir();
72
92
  const source = sourceInput;
93
+ const requireAgentProfileLayout = options.requireAgentProfileLayout ?? true;
73
94
 
74
95
  if (!source) {
75
96
  throw new Error("Specify a source. Use a local path, owner/repo, or GitHub URL.");
@@ -77,7 +98,9 @@ export async function materializeSource(sourceInput, options = {}) {
77
98
 
78
99
  if (isLocalSource(source, home)) {
79
100
  const sourcePath = path.resolve(expandHome(source, home));
80
- await assertAgentProfileLayout(sourcePath);
101
+ if (requireAgentProfileLayout) {
102
+ await assertAgentProfileLayout(sourcePath);
103
+ }
81
104
  return {
82
105
  kind: "local",
83
106
  source: sourcePath,
@@ -86,14 +109,20 @@ export async function materializeSource(sourceInput, options = {}) {
86
109
  };
87
110
  }
88
111
 
89
- const gitUrl = normalizeGithubUrl(source);
112
+ const { sourceWithoutRef, ref } = splitGitRef(source);
113
+ const gitUrl = normalizeGithubUrl(sourceWithoutRef);
90
114
  if (!gitUrl) {
91
115
  throw new Error(`Unsupported source "${source}". Use a local path, owner/repo, or GitHub URL.`);
92
116
  }
93
117
 
94
118
  const tmpRoot = options.tmpRoot ?? os.tmpdir();
95
119
  const cloneDir = await fs.mkdtemp(path.join(tmpRoot, "awesome-agents-source-"));
96
- const clone = spawnSync("git", ["clone", "--depth=1", gitUrl, cloneDir], {
120
+ const cloneArgs = ["clone", "--depth=1"];
121
+ if (ref) {
122
+ cloneArgs.push("--branch", ref);
123
+ }
124
+ cloneArgs.push(gitUrl, cloneDir);
125
+ const clone = spawnSync("git", cloneArgs, {
97
126
  encoding: "utf8"
98
127
  });
99
128
 
@@ -103,7 +132,9 @@ export async function materializeSource(sourceInput, options = {}) {
103
132
  throw new Error(`Could not clone ${source}: ${detail}`);
104
133
  }
105
134
 
106
- await assertAgentProfileLayout(cloneDir);
135
+ if (requireAgentProfileLayout) {
136
+ await assertAgentProfileLayout(cloneDir);
137
+ }
107
138
 
108
139
  return {
109
140
  kind: "git",
@@ -116,29 +147,42 @@ export async function materializeSource(sourceInput, options = {}) {
116
147
  };
117
148
  }
118
149
 
150
+ function splitGitRef(source) {
151
+ const hashIndex = source.lastIndexOf("#");
152
+ if (hashIndex <= 0 || hashIndex === source.length - 1) {
153
+ return { sourceWithoutRef: source, ref: undefined };
154
+ }
155
+ return {
156
+ sourceWithoutRef: source.slice(0, hashIndex),
157
+ ref: source.slice(hashIndex + 1)
158
+ };
159
+ }
160
+
119
161
  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}`);
162
+ const entries = await discoverProfileEntries(sourcePath);
163
+ if (entries.length === 0) {
164
+ throw new Error(`No agent profiles found in ${sourcePath}. Expected agents/<slug>/agent.yaml.`);
123
165
  }
124
166
  }
125
167
 
126
168
  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 });
169
+ const entries = await discoverProfileEntries(sourcePath);
130
170
  const profiles = [];
171
+ const seen = new Set();
131
172
 
132
- for (const file of files) {
133
- if (!file.isFile() || !isProfileFile(file.name)) {
173
+ for (const entry of entries) {
174
+ const profile = await loadProfileFile(entry.filePath, sourcePath, {
175
+ slugHint: entry.slugHint,
176
+ agentRoot: entry.agentRoot
177
+ });
178
+ if (seen.has(profile.slug)) {
134
179
  continue;
135
180
  }
136
-
137
- const filePath = path.join(profilesDir, file.name);
138
- const profile = await loadProfileFile(filePath, sourcePath);
181
+ seen.add(profile.slug);
139
182
  profiles.push({
140
183
  ...profile,
141
- adapters: await loadAdapters(adapterRoot, profile.slug, sourcePath)
184
+ adapters: await loadProfileAdapters(entry, profile.slug, sourcePath),
185
+ supportDirs: await loadSupportDirs(entry.agentRoot)
142
186
  });
143
187
  }
144
188
 
@@ -146,8 +190,104 @@ export async function loadCatalog(sourcePath) {
146
190
  return { sourcePath, profiles };
147
191
  }
148
192
 
193
+ async function discoverProfileEntries(sourcePath) {
194
+ return [
195
+ ...await discoverAgentDirectoryProfiles(sourcePath),
196
+ ...await discoverLegacyProfileDirectory(sourcePath)
197
+ ];
198
+ }
199
+
200
+ async function discoverAgentDirectoryProfiles(sourcePath) {
201
+ const agentsRoot = path.join(sourcePath, "agents");
202
+ if (!existsSync(agentsRoot)) {
203
+ return [];
204
+ }
205
+
206
+ const entries = [];
207
+ const files = await fs.readdir(agentsRoot, { withFileTypes: true });
208
+ for (const file of files) {
209
+ if (!file.isDirectory() || RESERVED_AGENT_DIRS.has(file.name)) {
210
+ continue;
211
+ }
212
+ const agentRoot = path.join(agentsRoot, file.name);
213
+ const filePath = await findAgentDefinitionFile(agentRoot, file.name);
214
+ if (!filePath) {
215
+ continue;
216
+ }
217
+ entries.push({
218
+ layout: "agent-directory",
219
+ filePath,
220
+ slugHint: file.name,
221
+ agentRoot,
222
+ adapterRoot: path.join(agentRoot, "adapters")
223
+ });
224
+ }
225
+ return entries;
226
+ }
227
+
228
+ async function discoverLegacyProfileDirectory(sourcePath) {
229
+ const profilesDir = path.join(sourcePath, "agents", "profiles");
230
+ if (!existsSync(profilesDir)) {
231
+ return [];
232
+ }
233
+
234
+ const entries = [];
235
+ const files = await fs.readdir(profilesDir, { withFileTypes: true });
236
+ for (const file of files) {
237
+ if (!file.isFile() || !isProfileFile(file.name)) {
238
+ continue;
239
+ }
240
+ entries.push({
241
+ layout: "legacy-profiles-directory",
242
+ filePath: path.join(profilesDir, file.name),
243
+ slugHint: profileSlugFromFilename(file.name),
244
+ adapterRoot: path.join(sourcePath, "agents", "adapters")
245
+ });
246
+ }
247
+ return entries;
248
+ }
249
+
250
+ async function findAgentDefinitionFile(agentRoot, slug) {
251
+ for (const name of AGENT_DEFINITION_NAMES) {
252
+ const candidate = path.join(agentRoot, name);
253
+ if (existsSync(candidate)) {
254
+ return candidate;
255
+ }
256
+ }
257
+ for (const suffix of PROFILE_SUFFIXES) {
258
+ const candidate = path.join(agentRoot, `${slug}${suffix}`);
259
+ if (existsSync(candidate)) {
260
+ return candidate;
261
+ }
262
+ }
263
+ return undefined;
264
+ }
265
+
266
+ async function loadProfileAdapters(entry, slug, sourcePath) {
267
+ const adapters = {};
268
+ Object.assign(adapters, await loadAdapters(entry.adapterRoot, slug, sourcePath));
269
+ if (entry.layout === "agent-directory") {
270
+ Object.assign(adapters, await loadAdapters(path.join(sourcePath, "agents", "adapters"), slug, sourcePath));
271
+ }
272
+ return adapters;
273
+ }
274
+
275
+ async function loadSupportDirs(agentRoot) {
276
+ if (!agentRoot) {
277
+ return [];
278
+ }
279
+ const dirs = [];
280
+ for (const kind of SUPPORT_DIRS) {
281
+ const sourcePath = path.join(agentRoot, kind);
282
+ if (existsSync(sourcePath)) {
283
+ dirs.push({ kind, sourcePath });
284
+ }
285
+ }
286
+ return dirs;
287
+ }
288
+
149
289
  async function loadAdapters(adapterRoot, slug, sourcePath) {
150
- if (!existsSync(adapterRoot)) {
290
+ if (!adapterRoot || !existsSync(adapterRoot)) {
151
291
  return {};
152
292
  }
153
293
 
@@ -178,10 +318,10 @@ async function loadAdapters(adapterRoot, slug, sourcePath) {
178
318
  return adapters;
179
319
  }
180
320
 
181
- async function loadProfileFile(filePath, sourcePath) {
321
+ async function loadProfileFile(filePath, sourcePath, options = {}) {
182
322
  const raw = await fs.readFile(filePath, "utf8");
183
323
  const parsed = await loadDefinitionFile(filePath, raw);
184
- const fallbackSlug = profileSlugFromFilename(path.basename(filePath));
324
+ const fallbackSlug = options.slugHint ?? profileSlugFromFilename(path.basename(filePath));
185
325
  const slug = parsed.attributes.slug ?? parsed.attributes.id ?? fallbackSlug;
186
326
  const name = parsed.attributes.name ?? titleize(slug);
187
327
  const summary = parsed.attributes.summary ?? parsed.attributes.description ?? "";
@@ -200,7 +340,9 @@ async function loadProfileFile(filePath, sourcePath) {
200
340
  raw,
201
341
  filePath,
202
342
  relativePath: path.relative(sourcePath, filePath),
203
- format: parsed.format
343
+ format: parsed.format,
344
+ agentRoot: options.agentRoot,
345
+ layout: options.agentRoot ? "agent-directory" : "legacy-profiles-directory"
204
346
  };
205
347
  }
206
348