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/AGENTS.md +7 -4
- package/CHANGELOG.md +35 -0
- package/README.md +45 -17
- package/docs/cli.md +29 -8
- package/docs/product/README.md +1 -1
- package/docs/product/command-model.md +14 -9
- package/docs/product/harness-targets.md +26 -3
- package/docs/product/product-scope.md +7 -2
- package/docs/product/profile-source-format.md +28 -12
- package/package.json +3 -2
- package/src/cli.js +140 -10
- package/src/constants.js +14 -3
- package/src/help.js +14 -12
- package/src/installer.js +148 -14
- package/src/renderers.js +117 -4
- package/src/skills.js +397 -0
- package/src/source.js +162 -20
package/src/cli.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import
|
|
2
|
+
import checkbox from "@inquirer/checkbox";
|
|
3
|
+
import { stripVTControlCharacters } from "node:util";
|
|
4
|
+
import { HARNESS_COMMANDS, PACKAGE_NAME, PACKAGE_VERSION, SUPPORTED_AGENTS } from "./constants.js";
|
|
3
5
|
import { configureHelp, formatMissingSourceError, ui } from "./help.js";
|
|
4
6
|
import {
|
|
5
7
|
initProfile,
|
|
@@ -15,7 +17,7 @@ export async function run(argv = process.argv) {
|
|
|
15
17
|
const program = new Command();
|
|
16
18
|
program
|
|
17
19
|
.name(PACKAGE_NAME)
|
|
18
|
-
.description("Install reusable agent profiles into Codex, Claude Code, and
|
|
20
|
+
.description("Install reusable agent profiles into Codex, Claude Code, OpenCode, and tenex-edge.")
|
|
19
21
|
.version(PACKAGE_VERSION, "-v, --version")
|
|
20
22
|
.helpOption("-h, --help", "Show this help message")
|
|
21
23
|
.showHelpAfterError();
|
|
@@ -140,14 +142,14 @@ function addInstallCommand(program, commandName) {
|
|
|
140
142
|
.argument("[source]", "Local path, GitHub owner/repo, or GitHub URL")
|
|
141
143
|
.description(commandName === "install" ? "Alias for add" : "Install agent profiles from a source")
|
|
142
144
|
.option("-g, --global", "Install globally")
|
|
143
|
-
.option("-p, --project", "Install into the current project; not supported for Codex profiles")
|
|
145
|
+
.option("-p, --project", "Install into the current project; not supported for Codex or tenex-edge profiles")
|
|
144
146
|
.option("-a, --agent <agents...>", "Agent profile slugs to install; accepts harness names for backward compatibility")
|
|
145
147
|
.option("--harness <harnesses...>", `Target harnesses (${SUPPORTED_AGENTS.join(", ")}, or *)`)
|
|
146
148
|
.option("--target <harnesses...>", "Compatibility alias for --harness")
|
|
147
149
|
.option("-s, --profile <profiles...>", "Profile slugs to install (or *)")
|
|
148
150
|
.option("--skill <profiles...>", "Compatibility alias for --profile")
|
|
149
151
|
.option("-l, --list", "List available profiles in the source without installing")
|
|
150
|
-
.option("-y, --yes", "
|
|
152
|
+
.option("-y, --yes", "Accept detected profile and harness selections without opening selectors")
|
|
151
153
|
.option("--all", "Install all profiles to all supported agents")
|
|
152
154
|
.option("--dry-run", "Print planned installs without writing files")
|
|
153
155
|
.option("--force", "Allow overwriting files without the generated marker")
|
|
@@ -170,7 +172,11 @@ function addInstallCommand(program, commandName) {
|
|
|
170
172
|
return;
|
|
171
173
|
}
|
|
172
174
|
|
|
173
|
-
const result = await installFromSource(source,
|
|
175
|
+
const result = await installFromSource(source, {
|
|
176
|
+
...options,
|
|
177
|
+
chooseProfiles: shouldPromptForProfiles(options) ? promptProfileSelection : undefined,
|
|
178
|
+
chooseHarnesses: shouldPromptForHarnesses(options) ? promptHarnessSelection : undefined
|
|
179
|
+
});
|
|
174
180
|
if (options.json) {
|
|
175
181
|
printJson(result);
|
|
176
182
|
} else {
|
|
@@ -186,7 +192,12 @@ function printAvailable(profiles) {
|
|
|
186
192
|
}
|
|
187
193
|
console.log(ui.bold("Available profiles:"));
|
|
188
194
|
for (const profile of profiles) {
|
|
189
|
-
|
|
195
|
+
const summary = profile.summary || profile.name;
|
|
196
|
+
console.log(` ${ui.profile(profile.slug)} ${ui.dim(shortText(summary, 100))}`);
|
|
197
|
+
const details = formatProfileDetails(profile);
|
|
198
|
+
if (details) {
|
|
199
|
+
console.log(` ${details}`);
|
|
200
|
+
}
|
|
190
201
|
}
|
|
191
202
|
}
|
|
192
203
|
|
|
@@ -216,6 +227,9 @@ function printOperations(operations, registryPath, runInstructions = []) {
|
|
|
216
227
|
if (registryPath) {
|
|
217
228
|
console.log(`${ui.bold("Registry:")} ${ui.path(registryPath)}`);
|
|
218
229
|
}
|
|
230
|
+
if (runInstructions.length > 0) {
|
|
231
|
+
console.log("");
|
|
232
|
+
}
|
|
219
233
|
printRunInstructions(runInstructions);
|
|
220
234
|
}
|
|
221
235
|
|
|
@@ -225,10 +239,11 @@ function printRunInstructions(runInstructions = []) {
|
|
|
225
239
|
}
|
|
226
240
|
|
|
227
241
|
console.log(ui.bold("Run installed profiles:"));
|
|
228
|
-
for (const
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
242
|
+
for (const group of groupRunInstructions(runInstructions)) {
|
|
243
|
+
const summary = group.summary ? ` -- ${ui.dim(shortText(group.summary, 120))}` : "";
|
|
244
|
+
console.log(` ${ui.profile(group.profile)}${summary}`);
|
|
245
|
+
for (const instruction of group.instructions) {
|
|
246
|
+
console.log(` ${ui.command(runHarnessLabel(instruction.harness))}: ${ui.command(instruction.command)}`);
|
|
232
247
|
}
|
|
233
248
|
}
|
|
234
249
|
}
|
|
@@ -243,3 +258,118 @@ function formatAction(action) {
|
|
|
243
258
|
}
|
|
244
259
|
return ui.success(action);
|
|
245
260
|
}
|
|
261
|
+
|
|
262
|
+
function shouldPromptForProfiles(options) {
|
|
263
|
+
return Boolean(process.stdin.isTTY && process.stdout.isTTY && !options.json && !options.yes);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function shouldPromptForHarnesses(options) {
|
|
267
|
+
return Boolean(process.stdin.isTTY && process.stdout.isTTY && !options.json && !options.yes);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async function promptProfileSelection(profiles) {
|
|
271
|
+
if (profiles.length === 0) {
|
|
272
|
+
return [];
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return checkbox({
|
|
276
|
+
message: "Select agent profiles to install",
|
|
277
|
+
choices: profiles.map((profile) => {
|
|
278
|
+
const name = formatPromptChoice(profile);
|
|
279
|
+
return {
|
|
280
|
+
value: profile.slug,
|
|
281
|
+
name,
|
|
282
|
+
checkedName: name,
|
|
283
|
+
short: profile.slug,
|
|
284
|
+
description: formatPromptDescription(profile),
|
|
285
|
+
checked: true
|
|
286
|
+
};
|
|
287
|
+
}),
|
|
288
|
+
pageSize: Math.min(Math.max(profiles.length, 7), 12),
|
|
289
|
+
required: true,
|
|
290
|
+
loop: false
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async function promptHarnessSelection(harnesses) {
|
|
295
|
+
return checkbox({
|
|
296
|
+
message: "Select target harnesses",
|
|
297
|
+
choices: harnesses.map((harness) => {
|
|
298
|
+
const name = formatHarnessChoice(harness);
|
|
299
|
+
return {
|
|
300
|
+
value: harness,
|
|
301
|
+
name,
|
|
302
|
+
checkedName: name,
|
|
303
|
+
short: harness,
|
|
304
|
+
checked: true
|
|
305
|
+
};
|
|
306
|
+
}),
|
|
307
|
+
pageSize: Math.min(Math.max(harnesses.length, 4), 8),
|
|
308
|
+
required: true,
|
|
309
|
+
loop: false
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function formatHarnessChoice(harness) {
|
|
314
|
+
const command = HARNESS_COMMANDS.get(harness);
|
|
315
|
+
return command ? `${ui.command(harness)} ${ui.dim(`detected: ${command}`)}` : ui.command(harness);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function formatPromptChoice(profile) {
|
|
319
|
+
const summary = profile.summary || profile.name;
|
|
320
|
+
return `${ui.profile(profile.slug)} ${ui.dim(shortText(summary, 88))}`;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function formatPromptDescription(profile) {
|
|
324
|
+
return [
|
|
325
|
+
profile.summary || profile.name,
|
|
326
|
+
formatProfileDetails(profile)
|
|
327
|
+
].filter(Boolean).join("\n");
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function formatProfileDetails(profile) {
|
|
331
|
+
const details = [];
|
|
332
|
+
if (profile.kind) {
|
|
333
|
+
details.push(`kind: ${profile.kind}`);
|
|
334
|
+
}
|
|
335
|
+
if (profile.adapters?.length) {
|
|
336
|
+
details.push(`custom adapters: ${profile.adapters.join(", ")}`);
|
|
337
|
+
}
|
|
338
|
+
return details.length > 0 ? ui.dim(details.join(" | ")) : "";
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function groupRunInstructions(runInstructions) {
|
|
342
|
+
const groups = new Map();
|
|
343
|
+
for (const instruction of runInstructions) {
|
|
344
|
+
const key = instruction.profile;
|
|
345
|
+
if (!groups.has(key)) {
|
|
346
|
+
groups.set(key, {
|
|
347
|
+
profile: instruction.profile,
|
|
348
|
+
name: instruction.name,
|
|
349
|
+
summary: instruction.summary,
|
|
350
|
+
instructions: []
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
groups.get(key).instructions.push(instruction);
|
|
354
|
+
}
|
|
355
|
+
return [...groups.values()];
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function runHarnessLabel(harness) {
|
|
359
|
+
if (harness === "claude-code") {
|
|
360
|
+
return "claude";
|
|
361
|
+
}
|
|
362
|
+
return harness;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function shortText(value, maxLength) {
|
|
366
|
+
const text = stripVTControlCharacters(String(value ?? ""))
|
|
367
|
+
.replace(/\s+/g, " ")
|
|
368
|
+
.trim();
|
|
369
|
+
|
|
370
|
+
if (text.length <= maxLength) {
|
|
371
|
+
return text;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return `${text.slice(0, Math.max(0, maxLength - 3)).trimEnd()}...`;
|
|
375
|
+
}
|
package/src/constants.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const PACKAGE_NAME = "awesome-agents";
|
|
2
|
-
export const PACKAGE_VERSION = "0.1.
|
|
2
|
+
export const PACKAGE_VERSION = "0.1.6";
|
|
3
3
|
export const DEFAULT_AGENT = "codex";
|
|
4
|
-
export const SUPPORTED_AGENTS = ["codex", "claude-code", "opencode"];
|
|
4
|
+
export const SUPPORTED_AGENTS = ["codex", "claude-code", "opencode", "tenex-edge"];
|
|
5
5
|
export const REGISTRY_DIRNAME = ".awesome-agents";
|
|
6
6
|
export const REGISTRY_FILENAME = "installed.json";
|
|
7
7
|
export const GENERATED_MARKER = "Generated by awesome-agents";
|
|
@@ -14,5 +14,16 @@ export const AGENT_ALIASES = new Map([
|
|
|
14
14
|
["claude-code", "claude-code"],
|
|
15
15
|
["claudecode", "claude-code"],
|
|
16
16
|
["opencode", "opencode"],
|
|
17
|
-
["open-code", "opencode"]
|
|
17
|
+
["open-code", "opencode"],
|
|
18
|
+
["tenex", "tenex-edge"],
|
|
19
|
+
["tenex-edge", "tenex-edge"],
|
|
20
|
+
["tenexedge", "tenex-edge"]
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
// The CLI binary used to detect whether a harness is available on PATH.
|
|
24
|
+
export const HARNESS_COMMANDS = new Map([
|
|
25
|
+
["codex", "codex"],
|
|
26
|
+
["claude-code", "claude"],
|
|
27
|
+
["opencode", "opencode"],
|
|
28
|
+
["tenex-edge", "tenex-edge"]
|
|
18
29
|
]);
|
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";
|
|
@@ -188,13 +188,13 @@ const banner = [
|
|
|
188
188
|
color(" | (_| | (_| | __/ | | | |_\\__ \\ ", codes.bannerB),
|
|
189
189
|
color(" \\__,_|\\__, |\\___|_| |_|\\__|___/ ", codes.bannerA),
|
|
190
190
|
color(" |___/ ", codes.bannerA),
|
|
191
|
-
` ${ui.dim("Operational profiles for Codex, Claude Code, and
|
|
191
|
+
` ${ui.dim("Operational profiles for Codex, Claude Code, OpenCode, and tenex-edge")}`
|
|
192
192
|
];
|
|
193
193
|
|
|
194
194
|
const mainHelp = {
|
|
195
195
|
banner,
|
|
196
196
|
usage: `${PACKAGE_NAME} <command> [options]`,
|
|
197
|
-
description: "Install reusable operational agent profiles into Codex, Claude Code, and
|
|
197
|
+
description: "Install reusable operational agent profiles into Codex, Claude Code, OpenCode, and tenex-edge.",
|
|
198
198
|
sections: [
|
|
199
199
|
{
|
|
200
200
|
title: "Manage Profiles:",
|
|
@@ -270,7 +270,7 @@ const mainHelp = {
|
|
|
270
270
|
examples: [
|
|
271
271
|
{ command: `${PACKAGE_NAME} add ${exampleSource} --list`, comment: "inspect source profiles" },
|
|
272
272
|
{ command: `${PACKAGE_NAME} add ${exampleSource} --agent ${exampleProfile}` },
|
|
273
|
-
{ command: `${PACKAGE_NAME} add ${exampleSource} --agent ${exampleProfile} --harness
|
|
273
|
+
{ command: `${PACKAGE_NAME} add ${exampleSource} --agent ${exampleProfile} --harness tenex-edge` },
|
|
274
274
|
{ command: `${PACKAGE_NAME} add ${exampleSource} --all --dry-run`, comment: "preview every write" },
|
|
275
275
|
{ command: `${PACKAGE_NAME} use ${exampleSource}@${exampleProfile} --harness claude-code` },
|
|
276
276
|
{ command: `${PACKAGE_NAME} list --json`, comment: "machine-readable output" },
|
|
@@ -288,9 +288,9 @@ const commandHelp = {
|
|
|
288
288
|
{
|
|
289
289
|
title: "Install Flow:",
|
|
290
290
|
rows: [
|
|
291
|
-
"1. Read canonical
|
|
292
|
-
"2.
|
|
293
|
-
"3. Render Codex, Claude Code, or
|
|
291
|
+
"1. Read canonical definitions from agents/<slug>/agent.yaml or compatible variants.",
|
|
292
|
+
"2. Install agent-owned scripts, references, and declared skills into ~/.agents/homes/<slug>/.",
|
|
293
|
+
"3. Render Codex, Claude Code, OpenCode, or tenex-edge files and record them in the registry."
|
|
294
294
|
]
|
|
295
295
|
},
|
|
296
296
|
{
|
|
@@ -308,6 +308,7 @@ const commandHelp = {
|
|
|
308
308
|
{ term: ui.option("--agent triage-agent"), description: "Preferred shorthand: install one profile slug" },
|
|
309
309
|
{ term: ui.option("--profile triage-agent"), description: "Explicit profile selector" },
|
|
310
310
|
{ term: ui.option("--skill triage-agent"), description: "Compatibility alias for --profile" },
|
|
311
|
+
{ term: ui.option("(interactive)"), description: "Without a profile selector, choose source profiles from a checkbox list" },
|
|
311
312
|
{ term: ui.option("--all"), description: "Install every source profile" },
|
|
312
313
|
{ term: ui.option("--list"), description: "Show available profiles and exit" }
|
|
313
314
|
]
|
|
@@ -315,9 +316,9 @@ const commandHelp = {
|
|
|
315
316
|
{
|
|
316
317
|
title: "Choose Targets:",
|
|
317
318
|
rows: [
|
|
318
|
-
{ term: ui.option("(
|
|
319
|
+
{ term: ui.option("(auto-detect)"), description: "Every harness detected on PATH; interactive multi-detect opens a checked selector; pass --harness when none are detected" },
|
|
319
320
|
{ term: ui.option("--harness opencode"), description: "Render for one harness" },
|
|
320
|
-
{ term: ui.option("--harness codex
|
|
321
|
+
{ term: ui.option("--harness codex tenex-edge"), description: "Render for multiple harnesses" },
|
|
321
322
|
{ term: ui.option("--harness *"), description: "Render for every supported harness" },
|
|
322
323
|
{ term: ui.option("--global"), description: "Install into the user-level harness directory" },
|
|
323
324
|
{ term: ui.option("--project"), description: "Install project-local files where the harness supports it" }
|
|
@@ -329,6 +330,7 @@ const commandHelp = {
|
|
|
329
330
|
{ term: ui.command("codex"), description: `${ui.path("~/.codex/<profile>.config.toml")} (project-local Codex profiles are not supported)` },
|
|
330
331
|
{ term: ui.command("claude-code"), description: `${ui.path("~/.claude/agents/<profile>.md")} or ${ui.path(".claude/agents/<profile>.md")}` },
|
|
331
332
|
{ term: ui.command("opencode"), description: `${ui.path("~/.config/opencode/agents/<profile>.md")} or ${ui.path(".opencode/agents/<profile>.md")}` },
|
|
333
|
+
{ term: ui.command("tenex-edge"), description: `${ui.path("~/.tenex-edge/agents/<profile>.json")} (project-local tenex-edge agents are not supported)` },
|
|
332
334
|
{ term: ui.command("registry"), description: `${ui.path("~/.awesome-agents/installed.json")} or ${ui.path(".awesome-agents/installed.json")}` }
|
|
333
335
|
]
|
|
334
336
|
},
|
|
@@ -346,7 +348,7 @@ const commandHelp = {
|
|
|
346
348
|
examples: [
|
|
347
349
|
{ command: `${PACKAGE_NAME} add ${exampleSource} --list` },
|
|
348
350
|
{ command: `${PACKAGE_NAME} add ${exampleSource} --agent ${exampleProfile}` },
|
|
349
|
-
{ command: `${PACKAGE_NAME} add ${exampleSource} --agent ${exampleProfile} --harness codex
|
|
351
|
+
{ command: `${PACKAGE_NAME} add ${exampleSource} --agent ${exampleProfile} --harness codex tenex-edge` },
|
|
350
352
|
{ command: `${PACKAGE_NAME} add ${exampleSource} --all --dry-run` }
|
|
351
353
|
],
|
|
352
354
|
footer: `Generated files are marked with ${ui.profile("Generated by awesome-agents")}.`
|
|
@@ -449,14 +451,14 @@ const commandHelp = {
|
|
|
449
451
|
function installOptions({ includeHome = false } = {}) {
|
|
450
452
|
return compact([
|
|
451
453
|
{ term: ui.option("-g, --global"), description: "Install globally instead of into the current project" },
|
|
452
|
-
{ term: ui.option("-p, --project"), description: "Install into the current project; not supported for Codex profiles" },
|
|
454
|
+
{ term: ui.option("-p, --project"), description: "Install into the current project; not supported for Codex or tenex-edge profiles" },
|
|
453
455
|
{ term: ui.option("-a, --agent <profiles...>"), description: "Select agent profile slugs; harness names still work for compatibility" },
|
|
454
456
|
{ term: ui.option("--harness <harnesses...>"), description: `Select target harnesses: ${harnesses}, or *` },
|
|
455
457
|
{ term: ui.option("-s, --profile <profiles...>"), description: "Explicit profile selector; accepts *" },
|
|
456
458
|
{ term: ui.option("--skill <profiles...>"), description: "Compatibility alias for --profile" },
|
|
457
459
|
{ term: ui.option("-l, --list"), description: "List available source profiles without installing" },
|
|
458
460
|
{ term: ui.option("--all"), description: "Install all profiles to all supported harnesses" },
|
|
459
|
-
{ term: ui.option("-y, --yes"), description: "
|
|
461
|
+
{ term: ui.option("-y, --yes"), description: "Accept detected profile and harness selections without opening selectors" },
|
|
460
462
|
{ term: ui.option("--dry-run"), description: "Print planned installs without writing files" },
|
|
461
463
|
{ term: ui.option("--force"), description: "Allow overwriting files without the generated marker" },
|
|
462
464
|
{ term: ui.option("--json"), description: "Output JSON without ANSI color" },
|
package/src/installer.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
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";
|
|
9
|
+
import { installProfileSkills } from "./skills.js";
|
|
8
10
|
|
|
9
11
|
export async function listAvailable(sourceInput, options = {}) {
|
|
10
12
|
const { source } = splitSourceSpec(sourceInput);
|
|
@@ -21,25 +23,32 @@ export async function installFromSource(sourceSpec, options = {}) {
|
|
|
21
23
|
const split = splitSourceSpec(sourceSpec);
|
|
22
24
|
const source = split.source;
|
|
23
25
|
const { selectors, harnessInput } = resolveInstallSelection(split, options);
|
|
24
|
-
const harnesses =
|
|
25
|
-
all: options.all,
|
|
26
|
-
defaultAgent: DEFAULT_AGENT
|
|
27
|
-
});
|
|
26
|
+
const harnesses = await resolveHarnessesForInstall(harnessInput, options);
|
|
28
27
|
const scope = resolveScope(options, harnesses);
|
|
29
28
|
|
|
30
29
|
const materialized = await materializeSource(source, options);
|
|
31
30
|
try {
|
|
32
31
|
const catalog = await loadCatalog(materialized.path);
|
|
33
|
-
const profiles =
|
|
32
|
+
const profiles = await resolveProfilesForInstall(catalog.profiles, selectors, options.all, options.chooseProfiles, materialized.source);
|
|
34
33
|
const registryOptions = { ...options, scope };
|
|
35
34
|
const registry = await readRegistry(registryOptions);
|
|
36
35
|
const operations = [];
|
|
37
36
|
const installedAt = new Date().toISOString();
|
|
38
37
|
|
|
39
38
|
for (const profile of profiles) {
|
|
39
|
+
const supportTargets = await installProfileSupport(profile, options);
|
|
40
|
+
const installedSkills = await installProfileSkills(profile, materialized.path, options);
|
|
41
|
+
const renderProfile = {
|
|
42
|
+
...profile,
|
|
43
|
+
installedSkills
|
|
44
|
+
};
|
|
40
45
|
for (const harness of harnesses) {
|
|
41
|
-
const
|
|
42
|
-
const
|
|
46
|
+
const target = resolveTargetPath(renderProfile, harness, { ...options, scope });
|
|
47
|
+
const existingContent = await readExistingContent(target);
|
|
48
|
+
const content = renderForAgent(renderProfile, harness, {
|
|
49
|
+
source: materialized.source,
|
|
50
|
+
existingContent
|
|
51
|
+
});
|
|
43
52
|
await writeManagedFile(target, content, options);
|
|
44
53
|
|
|
45
54
|
const install = {
|
|
@@ -53,6 +62,9 @@ export async function installFromSource(sourceSpec, options = {}) {
|
|
|
53
62
|
target,
|
|
54
63
|
installedAt,
|
|
55
64
|
contentSha256: contentHash(content),
|
|
65
|
+
supportTargets,
|
|
66
|
+
skillTargets: installedSkills.map((skill) => skill.path),
|
|
67
|
+
installedSkills,
|
|
56
68
|
dryRun: Boolean(options.dryRun)
|
|
57
69
|
};
|
|
58
70
|
|
|
@@ -88,9 +100,7 @@ export async function useFromSource(sourceSpec, options = {}) {
|
|
|
88
100
|
throw new Error("Specify a profile with source@profile or --profile <profile>.");
|
|
89
101
|
}
|
|
90
102
|
|
|
91
|
-
const harness =
|
|
92
|
-
defaultAgent: DEFAULT_AGENT
|
|
93
|
-
})[0];
|
|
103
|
+
const harness = resolveHarnessForUse(harnessInput, options);
|
|
94
104
|
const materialized = await materializeSource(split.source, options);
|
|
95
105
|
try {
|
|
96
106
|
const catalog = await loadCatalog(materialized.path);
|
|
@@ -215,7 +225,7 @@ export async function updateInstalled(profileArgs = [], options = {}) {
|
|
|
215
225
|
export async function initProfile(name, options = {}) {
|
|
216
226
|
const slug = slugify(name || "new-agent-profile");
|
|
217
227
|
const root = options.cwd ?? process.cwd();
|
|
218
|
-
const profilePath = path.join(root, "agents",
|
|
228
|
+
const profilePath = path.join(root, "agents", slug, "agent.yaml");
|
|
219
229
|
|
|
220
230
|
if (!options.force) {
|
|
221
231
|
if (existsSync(profilePath)) {
|
|
@@ -237,6 +247,45 @@ export async function initProfile(name, options = {}) {
|
|
|
237
247
|
};
|
|
238
248
|
}
|
|
239
249
|
|
|
250
|
+
async function installProfileSupport(profile, options = {}) {
|
|
251
|
+
if (!profile.supportDirs?.length) {
|
|
252
|
+
return [];
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const home = path.resolve(expandHome(options.home ?? os.homedir()));
|
|
256
|
+
const agentHome = path.join(home, ".agents", "homes", profile.slug);
|
|
257
|
+
const targets = [];
|
|
258
|
+
|
|
259
|
+
for (const supportDir of profile.supportDirs) {
|
|
260
|
+
const files = await listFilesRecursive(supportDir.sourcePath);
|
|
261
|
+
for (const source of files) {
|
|
262
|
+
const relative = path.relative(supportDir.sourcePath, source);
|
|
263
|
+
const target = path.join(agentHome, supportDir.kind, relative);
|
|
264
|
+
targets.push(target);
|
|
265
|
+
if (!options.dryRun) {
|
|
266
|
+
await fs.mkdir(path.dirname(target), { recursive: true });
|
|
267
|
+
await fs.copyFile(source, target);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return targets;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
async function listFilesRecursive(root) {
|
|
276
|
+
const entries = await fs.readdir(root, { withFileTypes: true });
|
|
277
|
+
const files = [];
|
|
278
|
+
for (const entry of entries) {
|
|
279
|
+
const entryPath = path.join(root, entry.name);
|
|
280
|
+
if (entry.isDirectory()) {
|
|
281
|
+
files.push(...await listFilesRecursive(entryPath));
|
|
282
|
+
} else if (entry.isFile()) {
|
|
283
|
+
files.push(entryPath);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return files.sort();
|
|
287
|
+
}
|
|
288
|
+
|
|
240
289
|
function buildRunInstructions(operations, env = process.env) {
|
|
241
290
|
const instructions = [];
|
|
242
291
|
const seen = new Set();
|
|
@@ -269,6 +318,8 @@ function runInstructionForOperation(operation, env) {
|
|
|
269
318
|
}
|
|
270
319
|
return {
|
|
271
320
|
profile: operation.profile,
|
|
321
|
+
name: operation.name,
|
|
322
|
+
summary: operation.summary,
|
|
272
323
|
harness: operation.harness,
|
|
273
324
|
command: `codex --profile ${shellWord(operation.profile)}`,
|
|
274
325
|
note: "Starts Codex with this installed profile."
|
|
@@ -281,6 +332,8 @@ function runInstructionForOperation(operation, env) {
|
|
|
281
332
|
}
|
|
282
333
|
return {
|
|
283
334
|
profile: operation.profile,
|
|
335
|
+
name: operation.name,
|
|
336
|
+
summary: operation.summary,
|
|
284
337
|
harness: operation.harness,
|
|
285
338
|
command: `claude --agent ${shellWord(operation.profile)}`,
|
|
286
339
|
note: "Starts Claude Code with this installed agent profile."
|
|
@@ -293,15 +346,77 @@ function runInstructionForOperation(operation, env) {
|
|
|
293
346
|
}
|
|
294
347
|
return {
|
|
295
348
|
profile: operation.profile,
|
|
349
|
+
name: operation.name,
|
|
350
|
+
summary: operation.summary,
|
|
296
351
|
harness: operation.harness,
|
|
297
352
|
command: "opencode",
|
|
298
353
|
note: `Start OpenCode, then invoke @${operation.profile} in the session.`
|
|
299
354
|
};
|
|
300
355
|
}
|
|
301
356
|
|
|
357
|
+
if (operation.harness === "tenex-edge") {
|
|
358
|
+
if (!commandExists("tenex-edge", env)) {
|
|
359
|
+
return undefined;
|
|
360
|
+
}
|
|
361
|
+
return {
|
|
362
|
+
profile: operation.profile,
|
|
363
|
+
name: operation.name,
|
|
364
|
+
summary: operation.summary,
|
|
365
|
+
harness: operation.harness,
|
|
366
|
+
command: `tenex-edge launch ${shellWord(operation.profile)}`,
|
|
367
|
+
note: "Starts this profile as a tenex-edge-managed Claude Code agent."
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
302
371
|
return undefined;
|
|
303
372
|
}
|
|
304
373
|
|
|
374
|
+
async function resolveHarnessesForInstall(harnessInput, options = {}) {
|
|
375
|
+
if (options.all || harnessInput !== undefined) {
|
|
376
|
+
return normalizeAgentList(harnessInput, { all: options.all });
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const detected = detectAvailableHarnesses(options.env ?? process.env);
|
|
380
|
+
if (detected.length === 0) {
|
|
381
|
+
throw new Error(noDetectedHarnessMessage());
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (detected.length > 1 && typeof options.chooseHarnesses === "function") {
|
|
385
|
+
const chosen = await options.chooseHarnesses(detected);
|
|
386
|
+
const harnesses = normalizeAgentList(chosen);
|
|
387
|
+
if (harnesses.length === 0) {
|
|
388
|
+
throw new Error("Select at least one harness to install, or pass --harness <harness>.");
|
|
389
|
+
}
|
|
390
|
+
return harnesses;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return detected;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function resolveHarnessForUse(harnessInput, options = {}) {
|
|
397
|
+
const explicit = normalizeAgentList(harnessInput);
|
|
398
|
+
if (explicit.length > 0) {
|
|
399
|
+
return explicit[0];
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const detected = detectAvailableHarnesses(options.env ?? process.env);
|
|
403
|
+
if (detected.length === 0) {
|
|
404
|
+
throw new Error(noDetectedHarnessMessage());
|
|
405
|
+
}
|
|
406
|
+
if (detected.length > 1) {
|
|
407
|
+
throw new Error(`Multiple harnesses detected (${detected.join(", ")}). Pass --harness <harness> because use renders one target at a time.`);
|
|
408
|
+
}
|
|
409
|
+
return detected[0];
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function detectAvailableHarnesses(env = process.env) {
|
|
413
|
+
return SUPPORTED_AGENTS.filter((harness) => commandExists(HARNESS_COMMANDS.get(harness), env));
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function noDetectedHarnessMessage() {
|
|
417
|
+
return `No supported harness CLI detected on PATH. Pass --harness <${SUPPORTED_AGENTS.join("|")}> to choose a target.`;
|
|
418
|
+
}
|
|
419
|
+
|
|
305
420
|
function commandExists(command, env) {
|
|
306
421
|
const pathValue = env.PATH ?? "";
|
|
307
422
|
if (!pathValue) {
|
|
@@ -326,6 +441,15 @@ function shellWord(value) {
|
|
|
326
441
|
return `'${text.replaceAll("'", "'\\''")}'`;
|
|
327
442
|
}
|
|
328
443
|
|
|
444
|
+
async function resolveProfilesForInstall(profiles, selectors, all, chooseProfiles, source) {
|
|
445
|
+
if (!all && selectors.length === 0 && typeof chooseProfiles === "function") {
|
|
446
|
+
const chosen = await chooseProfiles(profiles.map((profile) => profileSummary(profile, source)));
|
|
447
|
+
return selectProfiles(profiles, normalizeProfileSelectors(chosen), false);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
return selectProfiles(profiles, selectors, all);
|
|
451
|
+
}
|
|
452
|
+
|
|
329
453
|
function selectProfiles(profiles, selectors, all = false) {
|
|
330
454
|
if (all || selectors.length === 0 || selectors.includes("*")) {
|
|
331
455
|
return profiles;
|
|
@@ -360,6 +484,13 @@ async function writeManagedFile(target, content, options = {}) {
|
|
|
360
484
|
await fs.writeFile(target, content);
|
|
361
485
|
}
|
|
362
486
|
|
|
487
|
+
async function readExistingContent(target) {
|
|
488
|
+
if (!existsSync(target)) {
|
|
489
|
+
return undefined;
|
|
490
|
+
}
|
|
491
|
+
return fs.readFile(target, "utf8");
|
|
492
|
+
}
|
|
493
|
+
|
|
363
494
|
function resolveScope(options = {}, harnesses = []) {
|
|
364
495
|
if (options.project && options.global) {
|
|
365
496
|
throw new Error("Use either --global or --project, not both.");
|
|
@@ -367,13 +498,16 @@ function resolveScope(options = {}, harnesses = []) {
|
|
|
367
498
|
if (options.project && harnesses.includes("codex")) {
|
|
368
499
|
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.");
|
|
369
500
|
}
|
|
501
|
+
if (options.project && harnesses.includes("tenex-edge")) {
|
|
502
|
+
throw new Error("tenex-edge agents are machine-local under $TENEX_EDGE_HOME/agents or ~/.tenex-edge/agents and cannot be installed project-locally. Use --global or choose a different harness.");
|
|
503
|
+
}
|
|
370
504
|
if (options.global) {
|
|
371
505
|
return "global";
|
|
372
506
|
}
|
|
373
507
|
if (options.project) {
|
|
374
508
|
return "project";
|
|
375
509
|
}
|
|
376
|
-
if (harnesses.includes("codex")) {
|
|
510
|
+
if (harnesses.includes("codex") || harnesses.includes("tenex-edge")) {
|
|
377
511
|
return "global";
|
|
378
512
|
}
|
|
379
513
|
return "project";
|