awesome-agents 0.1.1 → 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/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 = {}) {
@@ -21,11 +22,11 @@ export async function installFromSource(sourceSpec, options = {}) {
21
22
  const split = splitSourceSpec(sourceSpec);
22
23
  const source = split.source;
23
24
  const { selectors, harnessInput } = resolveInstallSelection(split, options);
24
- const scope = resolveScope(options);
25
25
  const harnesses = normalizeAgentList(harnessInput, {
26
26
  all: options.all,
27
- defaultAgent: DEFAULT_AGENT
27
+ defaultAgent: detectAvailableHarnesses()
28
28
  });
29
+ const scope = resolveScope(options, harnesses);
29
30
 
30
31
  const materialized = await materializeSource(source, options);
31
32
  try {
@@ -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
 
@@ -71,7 +74,11 @@ export async function installFromSource(sourceSpec, options = {}) {
71
74
  await writeRegistry(registry, registryOptions);
72
75
  }
73
76
 
74
- return { operations, registryPath: registryPath(registryOptions) };
77
+ return {
78
+ operations,
79
+ registryPath: registryPath(registryOptions),
80
+ runInstructions: buildRunInstructions(operations)
81
+ };
75
82
  } finally {
76
83
  await materialized.cleanup();
77
84
  }
@@ -85,7 +92,7 @@ export async function useFromSource(sourceSpec, options = {}) {
85
92
  }
86
93
 
87
94
  const harness = normalizeAgentList(harnessInput, {
88
- defaultAgent: DEFAULT_AGENT
95
+ defaultAgent: detectAvailableHarnesses()
89
96
  })[0];
90
97
  const materialized = await materializeSource(split.source, options);
91
98
  try {
@@ -104,10 +111,11 @@ export async function useFromSource(sourceSpec, options = {}) {
104
111
  }
105
112
 
106
113
  export async function listInstalled(options = {}) {
107
- const scope = resolveScope(options);
108
- const registry = await readRegistry({ ...options, scope });
109
114
  const harnessInput = options.agents ?? options.agent;
110
- const harnessFilter = harnessInput ? new Set(normalizeAgentList(harnessInput)) : undefined;
115
+ const harnesses = harnessInput ? normalizeAgentList(harnessInput) : [];
116
+ const scope = resolveScope(options, harnesses);
117
+ const registry = await readRegistry({ ...options, scope });
118
+ const harnessFilter = harnesses.length > 0 ? new Set(harnesses) : undefined;
111
119
  const installs = registry.installs
112
120
  .filter((install) => !harnessFilter || harnessFilter.has(install.harness))
113
121
  .map((install) => ({
@@ -123,7 +131,9 @@ export async function listInstalled(options = {}) {
123
131
  }
124
132
 
125
133
  export async function removeInstalled(profileArgs = [], options = {}) {
126
- const scope = resolveScope(options);
134
+ const harnessInput = options.agents ?? options.agent;
135
+ const harnesses = harnessInput ? normalizeAgentList(harnessInput) : [];
136
+ const scope = resolveScope(options, harnesses);
127
137
  const registryOptions = { ...options, scope };
128
138
  const registry = await readRegistry(registryOptions);
129
139
  const selectors = normalizeProfileSelectors(profileArgs);
@@ -132,8 +142,7 @@ export async function removeInstalled(profileArgs = [], options = {}) {
132
142
  throw new Error("Specify at least one profile to remove, or pass --all.");
133
143
  }
134
144
 
135
- const harnessInput = options.agents ?? options.agent;
136
- const harnessFilter = harnessInput ? new Set(normalizeAgentList(harnessInput)) : undefined;
145
+ const harnessFilter = harnesses.length > 0 ? new Set(harnesses) : undefined;
137
146
  const operations = [];
138
147
  const matched = registry.installs.filter((install) => {
139
148
  const profileMatch = removeAll || selectors.includes(install.profile);
@@ -172,12 +181,13 @@ export async function removeInstalled(profileArgs = [], options = {}) {
172
181
  }
173
182
 
174
183
  export async function updateInstalled(profileArgs = [], options = {}) {
175
- const scope = resolveScope(options);
184
+ const harnessInput = options.agents ?? options.agent;
185
+ const harnesses = harnessInput ? normalizeAgentList(harnessInput) : [];
186
+ const scope = resolveScope(options, harnesses);
176
187
  const registryOptions = { ...options, scope };
177
188
  const registry = await readRegistry(registryOptions);
178
189
  const selectors = normalizeProfileSelectors(profileArgs);
179
- const harnessInput = options.agents ?? options.agent;
180
- const harnessFilter = harnessInput ? new Set(normalizeAgentList(harnessInput)) : undefined;
190
+ const harnessFilter = harnesses.length > 0 ? new Set(harnesses) : undefined;
181
191
  const candidates = registry.installs.filter((install) => {
182
192
  const profileMatch = selectors.length === 0 || selectors.includes(install.profile);
183
193
  const harnessMatch = !harnessFilter || harnessFilter.has(install.harness);
@@ -185,6 +195,7 @@ export async function updateInstalled(profileArgs = [], options = {}) {
185
195
  });
186
196
 
187
197
  const operations = [];
198
+ const runInstructions = [];
188
199
  for (const install of candidates) {
189
200
  const result = await installFromSource(install.source, {
190
201
  ...options,
@@ -198,42 +209,169 @@ export async function updateInstalled(profileArgs = [], options = {}) {
198
209
  ...operation,
199
210
  action: options.dryRun ? "would-update" : "updated"
200
211
  })));
212
+ runInstructions.push(...result.runInstructions);
201
213
  }
202
214
 
203
- return { operations, registryPath: registryPath(registryOptions) };
215
+ return { operations, registryPath: registryPath(registryOptions), runInstructions };
204
216
  }
205
217
 
206
218
  export async function initProfile(name, options = {}) {
207
219
  const slug = slugify(name || "new-agent-profile");
208
220
  const root = options.cwd ?? process.cwd();
209
- const profilePath = path.join(root, "agents", "profiles", `${slug}.md`);
210
- const adapterPath = path.join(root, "agents", "adapters", "codex", `${slug}.md`);
221
+ const profilePath = path.join(root, "agents", slug, "agent.yaml");
211
222
 
212
223
  if (!options.force) {
213
- for (const target of [profilePath, adapterPath]) {
214
- if (existsSync(target)) {
215
- throw new Error(`${target} already exists. Pass --force to overwrite.`);
216
- }
224
+ if (existsSync(profilePath)) {
225
+ throw new Error(`${profilePath} already exists. Pass --force to overwrite.`);
217
226
  }
218
227
  }
219
228
 
220
229
  const title = titleCase(slug);
221
- const profileContent = `---\nslug: ${slug}\nname: ${title}\nkind: operational-agent-profile\nsummary: Describe when to use this agent profile.\nrecommended_model: inherit\nrecommended_reasoning_effort: medium\nhome_notes_template: "~/.agents/homes/${slug}/<project>/notes"\n---\n\n# ${title}\n\nDescribe the agent's mission, boundaries, tools, coordination model, notes, and reporting style.\n`;
222
- const adapterContent = `---\nslug: ${slug}\nprofile: ../../profiles/${slug}.md\nharness: codex\nmodel: inherit\nreasoning_effort: medium\nrequired_skills: []\n---\n\n# Codex Adapter: ${title}\n\nLoad the canonical profile at \`agents/profiles/${slug}.md\`.\n`;
230
+ const profileContent = `schema: awesome-agents/v1\nid: ${slug}\nname: ${title}\ndescription: Describe when to use this agent profile.\nmodel: inherit\nreasoning_effort: medium\nhome_notes_template: "~/.agents/homes/${slug}/<project>/notes"\ninstructions: |\n Describe the agent's mission, boundaries, tools, coordination model, notes,\n and reporting style.\n`;
223
231
 
224
232
  if (!options.dryRun) {
225
233
  await fs.mkdir(path.dirname(profilePath), { recursive: true });
226
- await fs.mkdir(path.dirname(adapterPath), { recursive: true });
227
234
  await fs.writeFile(profilePath, profileContent);
228
- await fs.writeFile(adapterPath, adapterContent);
229
235
  }
230
236
 
231
237
  return {
232
238
  action: options.dryRun ? "would-init" : "initialized",
233
- files: [profilePath, adapterPath]
239
+ files: [profilePath]
234
240
  };
235
241
  }
236
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
+
282
+ function buildRunInstructions(operations, env = process.env) {
283
+ const instructions = [];
284
+ const seen = new Set();
285
+
286
+ for (const operation of operations) {
287
+ if (String(operation.action).startsWith("would-")) {
288
+ continue;
289
+ }
290
+
291
+ const instruction = runInstructionForOperation(operation, env);
292
+ if (!instruction) {
293
+ continue;
294
+ }
295
+
296
+ const key = `${instruction.harness}:${instruction.profile}:${instruction.command}`;
297
+ if (seen.has(key)) {
298
+ continue;
299
+ }
300
+ seen.add(key);
301
+ instructions.push(instruction);
302
+ }
303
+
304
+ return instructions;
305
+ }
306
+
307
+ function runInstructionForOperation(operation, env) {
308
+ if (operation.harness === "codex") {
309
+ if (!commandExists("codex", env)) {
310
+ return undefined;
311
+ }
312
+ return {
313
+ profile: operation.profile,
314
+ harness: operation.harness,
315
+ command: `codex --profile ${shellWord(operation.profile)}`,
316
+ note: "Starts Codex with this installed profile."
317
+ };
318
+ }
319
+
320
+ if (operation.harness === "claude-code") {
321
+ if (!commandExists("claude", env)) {
322
+ return undefined;
323
+ }
324
+ return {
325
+ profile: operation.profile,
326
+ harness: operation.harness,
327
+ command: `claude --agent ${shellWord(operation.profile)}`,
328
+ note: "Starts Claude Code with this installed agent profile."
329
+ };
330
+ }
331
+
332
+ if (operation.harness === "opencode") {
333
+ if (!commandExists("opencode", env)) {
334
+ return undefined;
335
+ }
336
+ return {
337
+ profile: operation.profile,
338
+ harness: operation.harness,
339
+ command: "opencode",
340
+ note: `Start OpenCode, then invoke @${operation.profile} in the session.`
341
+ };
342
+ }
343
+
344
+ return undefined;
345
+ }
346
+
347
+ function detectAvailableHarnesses(env = process.env) {
348
+ return SUPPORTED_AGENTS.filter((harness) => commandExists(HARNESS_COMMANDS.get(harness), env));
349
+ }
350
+
351
+ function commandExists(command, env) {
352
+ const pathValue = env.PATH ?? "";
353
+ if (!pathValue) {
354
+ return false;
355
+ }
356
+
357
+ const extensions = process.platform === "win32"
358
+ ? (env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";")
359
+ : [""];
360
+
361
+ return pathValue
362
+ .split(path.delimiter)
363
+ .filter(Boolean)
364
+ .some((directory) => extensions.some((extension) => existsSync(path.join(directory, `${command}${extension}`))));
365
+ }
366
+
367
+ function shellWord(value) {
368
+ const text = String(value);
369
+ if (/^[A-Za-z0-9._/@:-]+$/.test(text)) {
370
+ return text;
371
+ }
372
+ return `'${text.replaceAll("'", "'\\''")}'`;
373
+ }
374
+
237
375
  function selectProfiles(profiles, selectors, all = false) {
238
376
  if (all || selectors.length === 0 || selectors.includes("*")) {
239
377
  return profiles;
@@ -268,11 +406,23 @@ async function writeManagedFile(target, content, options = {}) {
268
406
  await fs.writeFile(target, content);
269
407
  }
270
408
 
271
- function resolveScope(options = {}) {
409
+ function resolveScope(options = {}, harnesses = []) {
272
410
  if (options.project && options.global) {
273
411
  throw new Error("Use either --global or --project, not both.");
274
412
  }
275
- return options.global ? "global" : "project";
413
+ if (options.project && harnesses.includes("codex")) {
414
+ 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.");
415
+ }
416
+ if (options.global) {
417
+ return "global";
418
+ }
419
+ if (options.project) {
420
+ return "project";
421
+ }
422
+ if (harnesses.includes("codex")) {
423
+ return "global";
424
+ }
425
+ return "project";
276
426
  }
277
427
 
278
428
  function normalizeProfileSelectors(values) {
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("*")) {
@@ -56,12 +57,7 @@ export function resolveTargetPath(profile, agent, options = {}) {
56
57
  : process.env.CODEX_HOME
57
58
  ? path.resolve(expandHome(process.env.CODEX_HOME, home))
58
59
  : 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`);
60
+ return path.join(codexHome, `${profile.slug}.config.toml`);
65
61
  }
66
62
 
67
63
  if (normalized === "claude-code") {
@@ -110,9 +106,9 @@ function renderCodex(profile, context) {
110
106
  `# ${GENERATED_MARKER}.`,
111
107
  `# Source: ${context.source}`,
112
108
  `# Profile: ${profile.slug}`,
113
- "",
114
- `name = ${tomlString(profile.slug)}`,
115
- `description = ${tomlString(profile.summary || profile.name)}`
109
+ `# Display name: ${tomlComment(profile.name)}`,
110
+ `# Summary: ${tomlComment(profile.summary || profile.name)}`,
111
+ ""
116
112
  ];
117
113
 
118
114
  if (model && model !== "inherit") {
@@ -170,9 +166,11 @@ function buildInstructionBody(profile, adapter, harness) {
170
166
  "",
171
167
  "## Installed Profile Context",
172
168
  "",
173
- `- Canonical profile slug: \`${profile.slug}\``,
169
+ `- Installed identity: \`${profile.slug}\``,
170
+ `- Role/name: \`${profile.name}\``,
174
171
  `- Installed for: \`${harness}\``,
175
- "- This profile is a reusable operational agent profile, not local machine setup.",
172
+ "- When asked who you are, what agent is running, or what role you are acting as, answer with this identity and role.",
173
+ "- This profile is a reusable operational agent profile, not a skill or local machine setup.",
176
174
  "- The runtime agent manages any profile-specific home directory and notes at task time."
177
175
  ];
178
176
 
@@ -226,6 +224,10 @@ function tomlString(value) {
226
224
  return JSON.stringify(String(value));
227
225
  }
228
226
 
227
+ function tomlComment(value) {
228
+ return String(value).replace(/\s+/g, " ").trim();
229
+ }
230
+
229
231
  function tomlMultiline(value) {
230
232
  const text = String(value).trimEnd();
231
233
  if (!text.includes("'''")) {