awesome-agents 0.1.5 → 0.1.8

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "awesome-agents",
3
- "version": "0.1.5",
4
- "description": "Install reusable agent profiles into Codex, Claude Code, and OpenCode.",
3
+ "version": "0.1.8",
4
+ "description": "Install reusable agent profiles into Codex, Claude Code, OpenCode, Goose, and tenex-edge.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "awesome-agents": "bin/awesome-agents.js"
@@ -28,6 +28,7 @@
28
28
  "codex",
29
29
  "claude-code",
30
30
  "opencode",
31
+ "goose",
31
32
  "cli"
32
33
  ],
33
34
  "author": "Pablo Fernandez",
@@ -43,6 +44,7 @@
43
44
  "node": ">=22.12.0"
44
45
  },
45
46
  "dependencies": {
47
+ "@inquirer/checkbox": "^4.3.2",
46
48
  "commander": "^15.0.0",
47
49
  "yaml": "^2.9.0"
48
50
  }
@@ -28,10 +28,6 @@ async function main() {
28
28
  const nextVersion = resolveNextVersion(currentVersion, options.bump);
29
29
  const tag = `v${nextVersion}`;
30
30
 
31
- if (!options.dryRun) {
32
- assertCleanWorktree();
33
- }
34
-
35
31
  console.log(`Release plan: ${currentVersion} -> ${nextVersion}`);
36
32
 
37
33
  if (options.dryRun) {
package/src/cli.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import { Command } from "commander";
2
- import { PACKAGE_NAME, PACKAGE_VERSION, SUPPORTED_AGENTS } from "./constants.js";
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 OpenCode.")
20
+ .description("Install reusable agent profiles into Codex, Claude Code, OpenCode, Goose, 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", "Accepted for npx skills parity; prompts are not used")
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, options);
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
- console.log(` ${ui.profile(profile.slug)} ${profile.summary || profile.name}`);
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 instruction of runInstructions) {
229
- console.log(` ${ui.profile(instruction.profile)} via ${ui.command(instruction.harness)}: ${ui.command(instruction.command)}`);
230
- if (instruction.note) {
231
- console.log(` ${instruction.note}`);
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.5";
2
+ export const PACKAGE_VERSION = "0.1.8";
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", "goose", "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,12 +14,18 @@ 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
+ ["goose", "goose"],
19
+ ["tenex", "tenex-edge"],
20
+ ["tenex-edge", "tenex-edge"],
21
+ ["tenexedge", "tenex-edge"]
18
22
  ]);
19
23
 
20
24
  // The CLI binary used to detect whether a harness is available on PATH.
21
25
  export const HARNESS_COMMANDS = new Map([
22
26
  ["codex", "codex"],
23
27
  ["claude-code", "claude"],
24
- ["opencode", "opencode"]
28
+ ["opencode", "opencode"],
29
+ ["goose", "goose"],
30
+ ["tenex-edge", "tenex-edge"]
25
31
  ]);
package/src/help.js CHANGED
@@ -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 OpenCode")}`
191
+ ` ${ui.dim("Operational profiles for Codex, Claude Code, OpenCode, Goose, 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 OpenCode.",
197
+ description: "Install reusable operational agent profiles into Codex, Claude Code, OpenCode, Goose, 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 opencode` },
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" },
@@ -289,8 +289,8 @@ const commandHelp = {
289
289
  title: "Install Flow:",
290
290
  rows: [
291
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
- "3. Render Codex, Claude Code, or OpenCode files and record them in the registry."
292
+ "2. Install agent-owned scripts, references, and declared skills into ~/.agents/homes/<slug>/.",
293
+ "3. Render Codex, Claude Code, OpenCode, Goose, 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("(default)"), description: "Every harness detected on PATH (codex, claude, opencode); falls back to Codex if none are found" },
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 claude-code"), description: "Render for multiple harnesses" },
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,8 @@ 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("goose"), description: `${ui.path("~/.agents/agents/<profile>.md")} or ${ui.path(".agents/agents/<profile>.md")}` },
334
+ { term: ui.command("tenex-edge"), description: `${ui.path("~/.tenex-edge/agents/<profile>.json")} (project-local tenex-edge agents are not supported)` },
332
335
  { term: ui.command("registry"), description: `${ui.path("~/.awesome-agents/installed.json")} or ${ui.path(".awesome-agents/installed.json")}` }
333
336
  ]
334
337
  },
@@ -346,7 +349,7 @@ const commandHelp = {
346
349
  examples: [
347
350
  { command: `${PACKAGE_NAME} add ${exampleSource} --list` },
348
351
  { command: `${PACKAGE_NAME} add ${exampleSource} --agent ${exampleProfile}` },
349
- { command: `${PACKAGE_NAME} add ${exampleSource} --agent ${exampleProfile} --harness codex opencode` },
352
+ { command: `${PACKAGE_NAME} add ${exampleSource} --agent ${exampleProfile} --harness codex tenex-edge` },
350
353
  { command: `${PACKAGE_NAME} add ${exampleSource} --all --dry-run` }
351
354
  ],
352
355
  footer: `Generated files are marked with ${ui.profile("Generated by awesome-agents")}.`
@@ -449,14 +452,14 @@ const commandHelp = {
449
452
  function installOptions({ includeHome = false } = {}) {
450
453
  return compact([
451
454
  { 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" },
455
+ { term: ui.option("-p, --project"), description: "Install into the current project; not supported for Codex or tenex-edge profiles" },
453
456
  { term: ui.option("-a, --agent <profiles...>"), description: "Select agent profile slugs; harness names still work for compatibility" },
454
457
  { term: ui.option("--harness <harnesses...>"), description: `Select target harnesses: ${harnesses}, or *` },
455
458
  { term: ui.option("-s, --profile <profiles...>"), description: "Explicit profile selector; accepts *" },
456
459
  { term: ui.option("--skill <profiles...>"), description: "Compatibility alias for --profile" },
457
460
  { term: ui.option("-l, --list"), description: "List available source profiles without installing" },
458
461
  { term: ui.option("--all"), description: "Install all profiles to all supported harnesses" },
459
- { term: ui.option("-y, --yes"), description: "Accepted for npx skills parity; prompts are not used" },
462
+ { term: ui.option("-y, --yes"), description: "Accept detected profile and harness selections without opening selectors" },
460
463
  { term: ui.option("--dry-run"), description: "Print planned installs without writing files" },
461
464
  { term: ui.option("--force"), description: "Allow overwriting files without the generated marker" },
462
465
  { term: ui.option("--json"), description: "Output JSON without ANSI color" },
package/src/installer.js CHANGED
@@ -6,6 +6,7 @@ import { AGENT_ALIASES, HARNESS_COMMANDS, SUPPORTED_AGENTS } from "./constants.j
6
6
  import { contentHash, isGeneratedContent, normalizeAgentList, renderForAgent, resolveTargetPath } from "./renderers.js";
7
7
  import { expandHome, loadCatalog, materializeSource, splitSourceSpec } from "./source.js";
8
8
  import { readRegistry, registryPath, removeInstall, upsertInstall, writeRegistry } from "./registry.js";
9
+ import { installProfileSkills } from "./skills.js";
9
10
 
10
11
  export async function listAvailable(sourceInput, options = {}) {
11
12
  const { source } = splitSourceSpec(sourceInput);
@@ -22,16 +23,13 @@ export async function installFromSource(sourceSpec, options = {}) {
22
23
  const split = splitSourceSpec(sourceSpec);
23
24
  const source = split.source;
24
25
  const { selectors, harnessInput } = resolveInstallSelection(split, options);
25
- const harnesses = normalizeAgentList(harnessInput, {
26
- all: options.all,
27
- defaultAgent: detectAvailableHarnesses()
28
- });
26
+ const harnesses = await resolveHarnessesForInstall(harnessInput, options);
29
27
  const scope = resolveScope(options, harnesses);
30
28
 
31
29
  const materialized = await materializeSource(source, options);
32
30
  try {
33
31
  const catalog = await loadCatalog(materialized.path);
34
- const profiles = selectProfiles(catalog.profiles, selectors, options.all);
32
+ const profiles = await resolveProfilesForInstall(catalog.profiles, selectors, options.all, options.chooseProfiles, materialized.source);
35
33
  const registryOptions = { ...options, scope };
36
34
  const registry = await readRegistry(registryOptions);
37
35
  const operations = [];
@@ -39,9 +37,19 @@ export async function installFromSource(sourceSpec, options = {}) {
39
37
 
40
38
  for (const profile of profiles) {
41
39
  const supportTargets = await installProfileSupport(profile, options);
40
+ const installedSkills = await installProfileSkills(profile, materialized.path, options);
41
+ const renderProfile = {
42
+ ...profile,
43
+ installedSkills
44
+ };
42
45
  for (const harness of harnesses) {
43
- const content = renderForAgent(profile, harness, { source: materialized.source });
44
- const target = resolveTargetPath(profile, harness, { ...options, scope });
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
+ selectedHarnesses: harnesses
52
+ });
45
53
  await writeManagedFile(target, content, options);
46
54
 
47
55
  const install = {
@@ -56,6 +64,8 @@ export async function installFromSource(sourceSpec, options = {}) {
56
64
  installedAt,
57
65
  contentSha256: contentHash(content),
58
66
  supportTargets,
67
+ skillTargets: installedSkills.map((skill) => skill.path),
68
+ installedSkills,
59
69
  dryRun: Boolean(options.dryRun)
60
70
  };
61
71
 
@@ -91,9 +101,7 @@ export async function useFromSource(sourceSpec, options = {}) {
91
101
  throw new Error("Specify a profile with source@profile or --profile <profile>.");
92
102
  }
93
103
 
94
- const harness = normalizeAgentList(harnessInput, {
95
- defaultAgent: detectAvailableHarnesses()
96
- })[0];
104
+ const harness = resolveHarnessForUse(harnessInput, options);
97
105
  const materialized = await materializeSource(split.source, options);
98
106
  try {
99
107
  const catalog = await loadCatalog(materialized.path);
@@ -311,6 +319,8 @@ function runInstructionForOperation(operation, env) {
311
319
  }
312
320
  return {
313
321
  profile: operation.profile,
322
+ name: operation.name,
323
+ summary: operation.summary,
314
324
  harness: operation.harness,
315
325
  command: `codex --profile ${shellWord(operation.profile)}`,
316
326
  note: "Starts Codex with this installed profile."
@@ -323,6 +333,8 @@ function runInstructionForOperation(operation, env) {
323
333
  }
324
334
  return {
325
335
  profile: operation.profile,
336
+ name: operation.name,
337
+ summary: operation.summary,
326
338
  harness: operation.harness,
327
339
  command: `claude --agent ${shellWord(operation.profile)}`,
328
340
  note: "Starts Claude Code with this installed agent profile."
@@ -335,19 +347,91 @@ function runInstructionForOperation(operation, env) {
335
347
  }
336
348
  return {
337
349
  profile: operation.profile,
350
+ name: operation.name,
351
+ summary: operation.summary,
338
352
  harness: operation.harness,
339
353
  command: "opencode",
340
354
  note: `Start OpenCode, then invoke @${operation.profile} in the session.`
341
355
  };
342
356
  }
343
357
 
358
+ if (operation.harness === "goose") {
359
+ if (!commandExists("goose", env)) {
360
+ return undefined;
361
+ }
362
+ return {
363
+ profile: operation.profile,
364
+ name: operation.name,
365
+ summary: operation.summary,
366
+ harness: operation.harness,
367
+ command: "goose session",
368
+ note: `Start goose, then invoke @${operation.profile} in the session.`
369
+ };
370
+ }
371
+
372
+ if (operation.harness === "tenex-edge") {
373
+ if (!commandExists("tenex-edge", env)) {
374
+ return undefined;
375
+ }
376
+ return {
377
+ profile: operation.profile,
378
+ name: operation.name,
379
+ summary: operation.summary,
380
+ harness: operation.harness,
381
+ command: `tenex-edge launch ${shellWord(operation.profile)}`,
382
+ note: "Starts this profile as a tenex-edge-managed Claude Code agent."
383
+ };
384
+ }
385
+
344
386
  return undefined;
345
387
  }
346
388
 
389
+ async function resolveHarnessesForInstall(harnessInput, options = {}) {
390
+ if (options.all || harnessInput !== undefined) {
391
+ return normalizeAgentList(harnessInput, { all: options.all });
392
+ }
393
+
394
+ const detected = detectAvailableHarnesses(options.env ?? process.env);
395
+ if (detected.length === 0) {
396
+ throw new Error(noDetectedHarnessMessage());
397
+ }
398
+
399
+ if (detected.length > 1 && typeof options.chooseHarnesses === "function") {
400
+ const chosen = await options.chooseHarnesses(detected);
401
+ const harnesses = normalizeAgentList(chosen);
402
+ if (harnesses.length === 0) {
403
+ throw new Error("Select at least one harness to install, or pass --harness <harness>.");
404
+ }
405
+ return harnesses;
406
+ }
407
+
408
+ return detected;
409
+ }
410
+
411
+ function resolveHarnessForUse(harnessInput, options = {}) {
412
+ const explicit = normalizeAgentList(harnessInput);
413
+ if (explicit.length > 0) {
414
+ return explicit[0];
415
+ }
416
+
417
+ const detected = detectAvailableHarnesses(options.env ?? process.env);
418
+ if (detected.length === 0) {
419
+ throw new Error(noDetectedHarnessMessage());
420
+ }
421
+ if (detected.length > 1) {
422
+ throw new Error(`Multiple harnesses detected (${detected.join(", ")}). Pass --harness <harness> because use renders one target at a time.`);
423
+ }
424
+ return detected[0];
425
+ }
426
+
347
427
  function detectAvailableHarnesses(env = process.env) {
348
428
  return SUPPORTED_AGENTS.filter((harness) => commandExists(HARNESS_COMMANDS.get(harness), env));
349
429
  }
350
430
 
431
+ function noDetectedHarnessMessage() {
432
+ return `No supported harness CLI detected on PATH. Pass --harness <${SUPPORTED_AGENTS.join("|")}> to choose a target.`;
433
+ }
434
+
351
435
  function commandExists(command, env) {
352
436
  const pathValue = env.PATH ?? "";
353
437
  if (!pathValue) {
@@ -372,6 +456,15 @@ function shellWord(value) {
372
456
  return `'${text.replaceAll("'", "'\\''")}'`;
373
457
  }
374
458
 
459
+ async function resolveProfilesForInstall(profiles, selectors, all, chooseProfiles, source) {
460
+ if (!all && selectors.length === 0 && typeof chooseProfiles === "function") {
461
+ const chosen = await chooseProfiles(profiles.map((profile) => profileSummary(profile, source)));
462
+ return selectProfiles(profiles, normalizeProfileSelectors(chosen), false);
463
+ }
464
+
465
+ return selectProfiles(profiles, selectors, all);
466
+ }
467
+
375
468
  function selectProfiles(profiles, selectors, all = false) {
376
469
  if (all || selectors.length === 0 || selectors.includes("*")) {
377
470
  return profiles;
@@ -406,6 +499,13 @@ async function writeManagedFile(target, content, options = {}) {
406
499
  await fs.writeFile(target, content);
407
500
  }
408
501
 
502
+ async function readExistingContent(target) {
503
+ if (!existsSync(target)) {
504
+ return undefined;
505
+ }
506
+ return fs.readFile(target, "utf8");
507
+ }
508
+
409
509
  function resolveScope(options = {}, harnesses = []) {
410
510
  if (options.project && options.global) {
411
511
  throw new Error("Use either --global or --project, not both.");
@@ -413,13 +513,16 @@ function resolveScope(options = {}, harnesses = []) {
413
513
  if (options.project && harnesses.includes("codex")) {
414
514
  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
515
  }
516
+ if (options.project && harnesses.includes("tenex-edge")) {
517
+ 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.");
518
+ }
416
519
  if (options.global) {
417
520
  return "global";
418
521
  }
419
522
  if (options.project) {
420
523
  return "project";
421
524
  }
422
- if (harnesses.includes("codex")) {
525
+ if (harnesses.includes("codex") || harnesses.includes("tenex-edge")) {
423
526
  return "global";
424
527
  }
425
528
  return "project";