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/AGENTS.md +12 -3
- package/CHANGELOG.md +56 -0
- package/README.md +49 -30
- package/docs/cli.md +20 -8
- package/docs/product/command-model.md +22 -4
- package/docs/product/harness-targets.md +18 -9
- package/docs/product/open-questions.md +3 -2
- package/docs/product/product-scope.md +23 -13
- package/docs/product/profile-source-format.md +26 -21
- package/package.json +1 -1
- package/src/cli.js +50 -17
- package/src/constants.js +8 -2
- package/src/help.js +530 -0
- package/src/installer.js +179 -29
- package/src/renderers.js +15 -13
- package/src/source.js +330 -33
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 = {}) {
|
|
@@ -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:
|
|
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 {
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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",
|
|
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
|
-
|
|
214
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
115
|
-
|
|
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
|
-
`-
|
|
169
|
+
`- Installed identity: \`${profile.slug}\``,
|
|
170
|
+
`- Role/name: \`${profile.name}\``,
|
|
174
171
|
`- Installed for: \`${harness}\``,
|
|
175
|
-
"-
|
|
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("'''")) {
|