awesome-agents 0.1.3 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/renderers.js CHANGED
@@ -20,7 +20,7 @@ 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
+ return arrayify(options.defaultAgent);
24
24
  }
25
25
 
26
26
  if (rawAgents.includes("*")) {
@@ -41,6 +41,9 @@ export function renderForAgent(profile, agent, context) {
41
41
  if (normalized === "opencode") {
42
42
  return renderOpenCode(profile, context);
43
43
  }
44
+ if (normalized === "tenex-edge") {
45
+ return renderTenexEdge(profile, context);
46
+ }
44
47
  throw new Error(`Unsupported agent "${agent}"`);
45
48
  }
46
49
 
@@ -85,6 +88,15 @@ export function resolveTargetPath(profile, agent, options = {}) {
85
88
  return path.join(opencodeHome, "agents", `${profile.slug}.md`);
86
89
  }
87
90
 
91
+ if (normalized === "tenex-edge") {
92
+ const tenexEdgeHome = options.tenexEdgeHome
93
+ ? path.resolve(expandHome(options.tenexEdgeHome, home))
94
+ : process.env.TENEX_EDGE_HOME
95
+ ? path.resolve(expandHome(process.env.TENEX_EDGE_HOME, home))
96
+ : path.join(home, ".tenex-edge");
97
+ return path.join(tenexEdgeHome, "agents", `${profile.slug}.json`);
98
+ }
99
+
88
100
  throw new Error(`Unsupported agent "${agent}"`);
89
101
  }
90
102
 
@@ -105,9 +117,9 @@ function renderCodex(profile, context) {
105
117
  `# ${GENERATED_MARKER}.`,
106
118
  `# Source: ${context.source}`,
107
119
  `# Profile: ${profile.slug}`,
108
- "",
109
- `name = ${tomlString(profile.slug)}`,
110
- `description = ${tomlString(profile.summary || profile.name)}`
120
+ `# Display name: ${tomlComment(profile.name)}`,
121
+ `# Summary: ${tomlComment(profile.summary || profile.name)}`,
122
+ ""
111
123
  ];
112
124
 
113
125
  if (model && model !== "inherit") {
@@ -159,6 +171,38 @@ function renderOpenCode(profile, context) {
159
171
  return stringifyFrontmatter(attributes, `${marker}\n\n${buildInstructionBody(profile, undefined, "opencode")}`);
160
172
  }
161
173
 
174
+ function renderTenexEdge(profile, context) {
175
+ const existing = tenexEdgeKeyMaterial(context.existingContent);
176
+ const keyMaterial = existing ?? generateNostrKeypair();
177
+ const agent = {
178
+ description: profile.summary || profile.name,
179
+ prompt: buildInstructionBody(profile, profile.adapters["tenex-edge"] ?? profile.adapters["claude-code"], "tenex-edge")
180
+ };
181
+ const model = chooseClaudeModel(profile);
182
+ const effort = profile.attributes.recommended_reasoning_effort;
183
+
184
+ if (model && model !== "inherit") {
185
+ agent.model = model;
186
+ }
187
+ if (effort && effort !== "inherit" && ["low", "medium", "high", "xhigh", "max"].includes(effort)) {
188
+ agent.effort = effort;
189
+ }
190
+
191
+ const stored = {
192
+ slug: profile.slug,
193
+ secret_key: keyMaterial.secret_key,
194
+ public_key: keyMaterial.public_key,
195
+ created_at: keyMaterial.created_at ?? Math.floor(Date.now() / 1000),
196
+ command: ["claude"],
197
+ agent,
198
+ byline: profile.summary || profile.name,
199
+ managed_by: GENERATED_MARKER,
200
+ source: context.source
201
+ };
202
+
203
+ return `${JSON.stringify(stored, null, 2)}\n`;
204
+ }
205
+
162
206
  function buildInstructionBody(profile, adapter, harness) {
163
207
  const parts = [
164
208
  profile.body.trimEnd(),
@@ -173,6 +217,20 @@ function buildInstructionBody(profile, adapter, harness) {
173
217
  "- The runtime agent manages any profile-specific home directory and notes at task time."
174
218
  ];
175
219
 
220
+ if (profile.installedSkills?.length) {
221
+ const skillBase = path.join(path.dirname(profile.installedSkills[0].path), "<skill>");
222
+ parts.push(
223
+ "",
224
+ "## Immediately Relevant Skills",
225
+ "",
226
+ `Immediately relevant skills; you should load these right away from \`${skillBase}\`.`,
227
+ ""
228
+ );
229
+ for (const skill of profile.installedSkills) {
230
+ parts.push(`- \`${skill.name}\`: \`${skill.path}\``);
231
+ }
232
+ }
233
+
176
234
  if (adapter?.body) {
177
235
  parts.push("", "## Harness Adapter", "", adapter.body.trimEnd());
178
236
  }
@@ -219,10 +277,65 @@ function chooseOpenCodeModel(profile) {
219
277
  return candidates.find((model) => model.includes("/"));
220
278
  }
221
279
 
280
+ function tenexEdgeKeyMaterial(content) {
281
+ if (!content) {
282
+ return undefined;
283
+ }
284
+
285
+ let parsed;
286
+ try {
287
+ parsed = JSON.parse(content);
288
+ } catch {
289
+ return undefined;
290
+ }
291
+
292
+ if (!isHex64(parsed.secret_key)) {
293
+ return undefined;
294
+ }
295
+
296
+ const derived = derivePublicKey(parsed.secret_key);
297
+ if (!derived) {
298
+ return undefined;
299
+ }
300
+
301
+ return {
302
+ secret_key: parsed.secret_key,
303
+ public_key: isHex64(parsed.public_key) ? parsed.public_key : derived,
304
+ created_at: Number.isInteger(parsed.created_at) ? parsed.created_at : undefined
305
+ };
306
+ }
307
+
308
+ function generateNostrKeypair() {
309
+ const ecdh = crypto.createECDH("secp256k1");
310
+ const publicKey = ecdh.generateKeys(undefined, "uncompressed");
311
+ return {
312
+ secret_key: ecdh.getPrivateKey("hex"),
313
+ public_key: publicKey.subarray(1, 33).toString("hex")
314
+ };
315
+ }
316
+
317
+ function derivePublicKey(secretKey) {
318
+ try {
319
+ const ecdh = crypto.createECDH("secp256k1");
320
+ ecdh.setPrivateKey(Buffer.from(secretKey, "hex"));
321
+ return ecdh.getPublicKey(undefined, "uncompressed").subarray(1, 33).toString("hex");
322
+ } catch {
323
+ return undefined;
324
+ }
325
+ }
326
+
327
+ function isHex64(value) {
328
+ return typeof value === "string" && /^[0-9a-fA-F]{64}$/.test(value);
329
+ }
330
+
222
331
  function tomlString(value) {
223
332
  return JSON.stringify(String(value));
224
333
  }
225
334
 
335
+ function tomlComment(value) {
336
+ return String(value).replace(/\s+/g, " ").trim();
337
+ }
338
+
226
339
  function tomlMultiline(value) {
227
340
  const text = String(value).trimEnd();
228
341
  if (!text.includes("'''")) {
package/src/skills.js ADDED
@@ -0,0 +1,397 @@
1
+ import { existsSync } from "node:fs";
2
+ import fs from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { parseFrontmatter } from "./frontmatter.js";
6
+ import { expandHome, materializeSource } from "./source.js";
7
+
8
+ const SKILL_CONTAINER_DIRS = [
9
+ "skills",
10
+ "skills/.curated",
11
+ "skills/.experimental",
12
+ "skills/.system",
13
+ ".agents/skills",
14
+ ".claude/skills",
15
+ ".codex/skills",
16
+ ".opencode/skills"
17
+ ];
18
+ const SKIP_DIRS = new Set([".git", "node_modules", "dist", "build", "__pycache__"]);
19
+
20
+ export async function installProfileSkills(profile, sourceRoot, options = {}) {
21
+ const dependencies = normalizeSkillDependencies(profile.attributes.skills);
22
+ if (dependencies.length === 0) {
23
+ return [];
24
+ }
25
+
26
+ const home = path.resolve(expandHome(options.home ?? os.homedir()));
27
+ const installed = [];
28
+
29
+ for (const dependency of dependencies) {
30
+ const skill = await resolveSkillDependency(profile, dependency, sourceRoot, home, options);
31
+ const target = path.join(home, ".agents", "homes", profile.slug, "skills", skill.installName);
32
+
33
+ if (!options.dryRun) {
34
+ await fs.rm(target, { recursive: true, force: true });
35
+ await fs.mkdir(path.dirname(target), { recursive: true });
36
+ await fs.cp(skill.path, target, {
37
+ recursive: true,
38
+ dereference: true,
39
+ preserveTimestamps: true
40
+ });
41
+ }
42
+
43
+ installed.push({
44
+ name: skill.installName,
45
+ declaredName: dependency.selector ?? dependency.source ?? skill.name,
46
+ source: skill.source,
47
+ sourceKind: skill.sourceKind,
48
+ path: target
49
+ });
50
+ }
51
+
52
+ return installed;
53
+ }
54
+
55
+ function normalizeSkillDependencies(value) {
56
+ if (!value) {
57
+ return [];
58
+ }
59
+
60
+ const entries = Array.isArray(value) ? value : [value];
61
+ return entries.map(parseSkillDependency);
62
+ }
63
+
64
+ function parseSkillDependency(value) {
65
+ if (typeof value === "string") {
66
+ const text = value.trim();
67
+ if (!text) {
68
+ throw new Error("Skill dependency entries must not be empty.");
69
+ }
70
+
71
+ const split = text.match(/^(\S+)\s+(.+)$/);
72
+ if (split) {
73
+ return { source: split[1], selector: split[2].trim(), raw: text };
74
+ }
75
+
76
+ const atSelector = splitAtSelector(text);
77
+ if (atSelector) {
78
+ return { source: atSelector.source, selector: atSelector.selector, raw: text };
79
+ }
80
+
81
+ if (looksLikeRemoteSource(text)) {
82
+ return { source: text, selector: undefined, raw: text };
83
+ }
84
+
85
+ return { selector: text, raw: text };
86
+ }
87
+
88
+ if (isObject(value)) {
89
+ const source = firstString(value.source, value.package, value.repository, value.repo);
90
+ const selector = firstString(value.skill, value.name, value.slug, value.id);
91
+ if (!source && !selector) {
92
+ throw new Error("Skill dependency objects must include a skill/name or source field.");
93
+ }
94
+ return { source, selector, raw: value };
95
+ }
96
+
97
+ throw new Error("Skill dependencies must be strings or objects.");
98
+ }
99
+
100
+ function splitAtSelector(value) {
101
+ if (value.startsWith("git@")) {
102
+ return undefined;
103
+ }
104
+ const atIndex = value.lastIndexOf("@");
105
+ if (atIndex <= 0 || atIndex === value.length - 1 || atIndex <= value.lastIndexOf("/")) {
106
+ return undefined;
107
+ }
108
+ return {
109
+ source: value.slice(0, atIndex),
110
+ selector: value.slice(atIndex + 1)
111
+ };
112
+ }
113
+
114
+ function looksLikeRemoteSource(value) {
115
+ return value.includes("/") || value.includes(":") || value.startsWith("~") || value.startsWith(".");
116
+ }
117
+
118
+ async function resolveSkillDependency(profile, dependency, sourceRoot, home, options) {
119
+ if (dependency.source) {
120
+ const materialized = await materializeSource(dependency.source, {
121
+ ...options,
122
+ requireAgentProfileLayout: false
123
+ });
124
+ try {
125
+ return await selectSkillFromRoot(materialized.path, dependency.selector, {
126
+ dependency,
127
+ source: materialized.source,
128
+ sourceKind: materialized.kind
129
+ });
130
+ } finally {
131
+ await materialized.cleanup();
132
+ }
133
+ }
134
+
135
+ const profileSkill = await findSkillInRoot(profile.agentRoot, dependency.selector, {
136
+ dependency,
137
+ source: profile.agentRoot,
138
+ sourceKind: "profile-local"
139
+ });
140
+ if (profileSkill) {
141
+ return profileSkill;
142
+ }
143
+
144
+ const sourceSkill = await findSkillInRoot(sourceRoot, dependency.selector, {
145
+ dependency,
146
+ source: sourceRoot,
147
+ sourceKind: "local-source"
148
+ });
149
+ if (sourceSkill) {
150
+ return sourceSkill;
151
+ }
152
+
153
+ const userSkillRoot = path.join(home, ".agents", "skills");
154
+ const userSkill = await findSkillInRoot(userSkillRoot, dependency.selector, {
155
+ dependency,
156
+ source: userSkillRoot,
157
+ sourceKind: "user-skills"
158
+ });
159
+ if (userSkill) {
160
+ return userSkill;
161
+ }
162
+
163
+ throw new Error(`Skill "${dependency.selector}" was not found in the profile source or ${userSkillRoot}.`);
164
+ }
165
+
166
+ async function findSkillInRoot(root, selector, context) {
167
+ if (!root || !existsSync(root)) {
168
+ return undefined;
169
+ }
170
+
171
+ const skills = await discoverSkills(root);
172
+ if (skills.length === 0) {
173
+ return undefined;
174
+ }
175
+
176
+ return findDiscoveredSkill(skills, selector, context);
177
+ }
178
+
179
+ async function selectSkillFromRoot(root, selector, context) {
180
+ const skills = await discoverSkills(root);
181
+ if (skills.length === 0) {
182
+ throw new Error(`No skills found in ${context.source}. Expected SKILL.md files in the source root or skills/<name>/.`);
183
+ }
184
+
185
+ return selectDiscoveredSkill(skills, selector, context);
186
+ }
187
+
188
+ function selectDiscoveredSkill(skills, selector, context) {
189
+ if (!selector) {
190
+ if (skills.length === 1) {
191
+ return withSource(skills[0], context);
192
+ }
193
+ throw new Error(`Skill source "${context.source}" contains multiple skills. Specify one of: ${skills.map((skill) => skill.name).join(", ")}`);
194
+ }
195
+
196
+ const normalized = sanitizeSkillName(selector);
197
+ const selected = skills.find((skill) => {
198
+ return sanitizeSkillName(skill.name) === normalized
199
+ || sanitizeSkillName(skill.directoryName) === normalized
200
+ || skill.installName === normalized;
201
+ });
202
+
203
+ if (!selected) {
204
+ throw new Error(`Skill "${selector}" was not found in ${context.source}. Available skills: ${skills.map((skill) => skill.name).join(", ")}`);
205
+ }
206
+
207
+ return withSource(selected, context);
208
+ }
209
+
210
+ function findDiscoveredSkill(skills, selector, context) {
211
+ if (!selector) {
212
+ return skills.length === 1 ? withSource(skills[0], context) : undefined;
213
+ }
214
+
215
+ const normalized = sanitizeSkillName(selector);
216
+ const selected = skills.find((skill) => {
217
+ return sanitizeSkillName(skill.name) === normalized
218
+ || sanitizeSkillName(skill.directoryName) === normalized
219
+ || skill.installName === normalized;
220
+ });
221
+
222
+ return selected ? withSource(selected, context) : undefined;
223
+ }
224
+
225
+ function withSource(skill, context) {
226
+ return {
227
+ ...skill,
228
+ source: context.source,
229
+ sourceKind: context.sourceKind
230
+ };
231
+ }
232
+
233
+ async function discoverSkills(root) {
234
+ const discovered = [];
235
+ const seenPaths = new Set();
236
+ const seenNames = new Set();
237
+
238
+ const addSkillAt = async (skillDir) => {
239
+ const skillPath = path.join(skillDir, "SKILL.md");
240
+ if (!existsSync(skillPath)) {
241
+ return false;
242
+ }
243
+
244
+ const resolved = path.resolve(skillDir);
245
+ if (seenPaths.has(resolved)) {
246
+ return true;
247
+ }
248
+
249
+ const skill = await readSkill(skillDir, root);
250
+ if (seenNames.has(skill.installName)) {
251
+ return true;
252
+ }
253
+
254
+ discovered.push(skill);
255
+ seenPaths.add(resolved);
256
+ seenNames.add(skill.installName);
257
+ return true;
258
+ };
259
+
260
+ await addSkillAt(root);
261
+ await discoverContainer(root, false, addSkillAt);
262
+ for (const container of SKILL_CONTAINER_DIRS) {
263
+ await discoverContainer(path.join(root, container), true, addSkillAt);
264
+ }
265
+
266
+ if (discovered.length === 0) {
267
+ for (const skillDir of await findSkillDirs(root)) {
268
+ await addSkillAt(skillDir);
269
+ }
270
+ }
271
+
272
+ return discovered.sort((a, b) => a.installName.localeCompare(b.installName));
273
+ }
274
+
275
+ async function discoverContainer(container, walkGrandchildren, addSkillAt) {
276
+ let entries;
277
+ try {
278
+ entries = await fs.readdir(container, { withFileTypes: true });
279
+ } catch {
280
+ return;
281
+ }
282
+
283
+ for (const entry of entries) {
284
+ if (SKIP_DIRS.has(entry.name)) {
285
+ continue;
286
+ }
287
+
288
+ const childDir = path.join(container, entry.name);
289
+ if (!await isDirectoryLike(entry, childDir)) {
290
+ continue;
291
+ }
292
+
293
+ const found = await addSkillAt(childDir);
294
+ if (found || !walkGrandchildren) {
295
+ continue;
296
+ }
297
+
298
+ let grandchildren;
299
+ try {
300
+ grandchildren = await fs.readdir(childDir, { withFileTypes: true });
301
+ } catch {
302
+ continue;
303
+ }
304
+ for (const grandchild of grandchildren) {
305
+ if (SKIP_DIRS.has(grandchild.name)) {
306
+ continue;
307
+ }
308
+ const grandchildDir = path.join(childDir, grandchild.name);
309
+ if (await isDirectoryLike(grandchild, grandchildDir)) {
310
+ await addSkillAt(grandchildDir);
311
+ }
312
+ }
313
+ }
314
+ }
315
+
316
+ async function findSkillDirs(root) {
317
+ const results = [];
318
+
319
+ async function visit(directory) {
320
+ let entries;
321
+ try {
322
+ entries = await fs.readdir(directory, { withFileTypes: true });
323
+ } catch {
324
+ return;
325
+ }
326
+
327
+ if (existsSync(path.join(directory, "SKILL.md"))) {
328
+ results.push(directory);
329
+ return;
330
+ }
331
+
332
+ for (const entry of entries) {
333
+ if (SKIP_DIRS.has(entry.name)) {
334
+ continue;
335
+ }
336
+ const entryPath = path.join(directory, entry.name);
337
+ if (await isDirectoryLike(entry, entryPath)) {
338
+ await visit(entryPath);
339
+ }
340
+ }
341
+ }
342
+
343
+ await visit(root);
344
+ return results;
345
+ }
346
+
347
+ async function readSkill(skillDir, root) {
348
+ const skillPath = path.join(skillDir, "SKILL.md");
349
+ const raw = await fs.readFile(skillPath, "utf8");
350
+ const parsed = parseFrontmatter(raw, skillPath);
351
+ const directoryName = path.basename(skillDir);
352
+ const name = firstString(parsed.attributes.name, directoryName);
353
+
354
+ return {
355
+ name,
356
+ installName: sanitizeSkillName(name),
357
+ directoryName,
358
+ path: skillDir,
359
+ relativePath: path.relative(root, skillDir)
360
+ };
361
+ }
362
+
363
+ async function isDirectoryLike(entry, entryPath) {
364
+ if (entry.isDirectory()) {
365
+ return true;
366
+ }
367
+ if (!entry.isSymbolicLink()) {
368
+ return false;
369
+ }
370
+
371
+ try {
372
+ return (await fs.stat(entryPath)).isDirectory();
373
+ } catch {
374
+ return false;
375
+ }
376
+ }
377
+
378
+ function sanitizeSkillName(name) {
379
+ return String(name)
380
+ .toLowerCase()
381
+ .replace(/[^a-z0-9._]+/g, "-")
382
+ .replace(/^[.-]+|[.-]+$/g, "")
383
+ .slice(0, 255) || "unnamed-skill";
384
+ }
385
+
386
+ function firstString(...values) {
387
+ for (const value of values) {
388
+ if (typeof value === "string" && value.trim()) {
389
+ return value.trim();
390
+ }
391
+ }
392
+ return "";
393
+ }
394
+
395
+ function isObject(value) {
396
+ return value !== null && typeof value === "object" && !Array.isArray(value);
397
+ }