claude-setup 1.1.9 → 2.0.3
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/dist/commands/add.js +2 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +67 -0
- package/dist/marketplace.js +198 -59
- package/package.json +1 -1
- package/templates/add.md +24 -0
package/dist/commands/add.js
CHANGED
|
@@ -6,6 +6,7 @@ import { updateManifest } from "../manifest.js";
|
|
|
6
6
|
import { buildAddCommand } from "../builder.js";
|
|
7
7
|
import { estimateTokens, estimateCost } from "../tokens.js";
|
|
8
8
|
import { c } from "../output.js";
|
|
9
|
+
import { installMarketplaceFetcher } from "./init.js";
|
|
9
10
|
function ensureDir(dir) {
|
|
10
11
|
if (!existsSync(dir))
|
|
11
12
|
mkdirSync(dir, { recursive: true });
|
|
@@ -46,6 +47,7 @@ capabilities that need documentation, MCP servers, skills, and hooks together.
|
|
|
46
47
|
const tokens = estimateTokens(content);
|
|
47
48
|
const cost = estimateCost(tokens);
|
|
48
49
|
ensureDir(".claude/commands");
|
|
50
|
+
installMarketplaceFetcher();
|
|
49
51
|
writeFileSync(".claude/commands/stack-add.md", content, "utf8");
|
|
50
52
|
await updateManifest("add", collected, {
|
|
51
53
|
input: userInput,
|
package/dist/commands/init.d.ts
CHANGED
package/dist/commands/init.js
CHANGED
|
@@ -71,6 +71,71 @@ No further action needed — the output IS the status.
|
|
|
71
71
|
writeFileSync(filepath, content, "utf8");
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
|
+
/** Install the marketplace-fetcher subagent so /stack-add can spawn it */
|
|
75
|
+
export function installMarketplaceFetcher(cwd = process.cwd()) {
|
|
76
|
+
const agentsDir = join(cwd, ".claude", "agents");
|
|
77
|
+
ensureDir(agentsDir);
|
|
78
|
+
const filepath = join(agentsDir, "marketplace-fetcher.md");
|
|
79
|
+
// Always overwrite — this is a system agent, not user-authored
|
|
80
|
+
writeFileSync(filepath, `---
|
|
81
|
+
name: marketplace-fetcher
|
|
82
|
+
description: Fetches skills and agents from all 4 marketplace catalogs. Spawned automatically by stack-add. Runs in isolation, writes to disk, returns only the install confirmation.
|
|
83
|
+
tools: Bash
|
|
84
|
+
model: haiku
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
You are a marketplace fetch agent. You receive a prompt containing marketplace instructions with bash code blocks. Your job is to RUN those bash commands, use the output to find matching files, download them, and confirm the install.
|
|
88
|
+
|
|
89
|
+
## CRITICAL RULES — read before doing anything
|
|
90
|
+
|
|
91
|
+
1. **You MUST run curl commands.** Your entire purpose is to fetch from remote catalogs. If you finish without running at least 3 curl commands, you have failed.
|
|
92
|
+
2. **NEVER skip to custom creation early.** You may only create custom content after you have attempted ALL catalog steps with real curl calls and each one returned empty or errored.
|
|
93
|
+
3. **Use relative paths only.** Never prepend \`cd /some/path &&\` to commands. You are already in the project directory.
|
|
94
|
+
4. **Pipe curl output through node parsers.** Never let raw JSON/README enter the context. Every curl MUST pipe through \`| node -e "..."\` to extract only the lines you need.
|
|
95
|
+
5. **A curl failure or empty result means: try the next step.** It does NOT mean stop. Log what failed in one line, then continue to the next step.
|
|
96
|
+
6. **Fill in template variables.** The instructions contain placeholder variables like \`CATEGORY="09-meta-orchestration"\` and \`SKILL_DIR="matched-directory"\`. Replace these with ACTUAL values from the previous step's output.
|
|
97
|
+
7. **Verify downloads.** After every curl -o, check the file exists and has >50 characters of content. If empty or stub: delete it, log failure, continue.
|
|
98
|
+
|
|
99
|
+
## How to execute the instructions
|
|
100
|
+
|
|
101
|
+
Your prompt contains numbered STEPs with bash code blocks. For each STEP:
|
|
102
|
+
|
|
103
|
+
1. Run the first bash command (the catalog listing/search)
|
|
104
|
+
2. Read the output — it shows available files/directories
|
|
105
|
+
3. Pick the entry that best matches the user's request
|
|
106
|
+
4. Substitute that entry name into the download command
|
|
107
|
+
5. Run the download command
|
|
108
|
+
6. Verify the downloaded file has real content
|
|
109
|
+
7. If it works → return the success line. If not → go to next STEP.
|
|
110
|
+
|
|
111
|
+
## Return format
|
|
112
|
+
|
|
113
|
+
Return exactly ONE line:
|
|
114
|
+
- \`INSTALLED .claude/agents/<file> <bytes>b\` or
|
|
115
|
+
- \`INSTALLED .claude/skills/<dir>/SKILL.md <bytes>b\` or
|
|
116
|
+
- \`CREATED .claude/agents/<file> <bytes>b\` (only after ALL catalogs exhausted) or
|
|
117
|
+
- \`CREATED .claude/skills/<dir>/SKILL.md <bytes>b\` or
|
|
118
|
+
- \`FAILED no match in any catalog and custom creation not possible\`
|
|
119
|
+
|
|
120
|
+
## Example of correct behavior
|
|
121
|
+
|
|
122
|
+
If the instructions say:
|
|
123
|
+
\`\`\`bash
|
|
124
|
+
curl -sf "https://api.github.com/repos/X/Y/contents/categories/03-infrastructure" | node -e "..."
|
|
125
|
+
\`\`\`
|
|
126
|
+
And the output shows: \`docker-compose-agent.md\`, \`kubernetes-agent.md\`, \`terraform-agent.md\`
|
|
127
|
+
|
|
128
|
+
Then for the download command that says \`AGENT_FILE="matched-agent.md"\`, you substitute:
|
|
129
|
+
\`\`\`bash
|
|
130
|
+
AGENT_FILE="kubernetes-agent.md"
|
|
131
|
+
curl -sf "https://raw.githubusercontent.com/X/Y/main/categories/03-infrastructure/kubernetes-agent.md" -o ".claude/agents/kubernetes-agent.md"
|
|
132
|
+
\`\`\`
|
|
133
|
+
|
|
134
|
+
Then verify: \`wc -c ".claude/agents/kubernetes-agent.md"\`
|
|
135
|
+
|
|
136
|
+
Now execute the instructions in your prompt. Start with STEP 1 immediately.
|
|
137
|
+
`, "utf8");
|
|
138
|
+
}
|
|
74
139
|
function installTokenHook(cwd = process.cwd()) {
|
|
75
140
|
// Write the hook script
|
|
76
141
|
const hooksDir = join(cwd, ".claude", "hooks");
|
|
@@ -139,6 +204,7 @@ export async function runInit(opts = {}) {
|
|
|
139
204
|
writeFileSync(".claude/commands/stack-init.md", content, "utf8");
|
|
140
205
|
writeFileSync(".claude/commands/stack-sync.md", buildBootstrapSync(), "utf8");
|
|
141
206
|
installBootstrapCommands(".claude/commands");
|
|
207
|
+
installMarketplaceFetcher();
|
|
142
208
|
await updateManifest("init", collected, { estimatedTokens: tokens, estimatedCost: cost });
|
|
143
209
|
installTokenHook();
|
|
144
210
|
// Create initial snapshot — collectFilesForSnapshot scans all .claude/ automatically
|
|
@@ -181,6 +247,7 @@ Claude Code will ask 3 questions, then set up your environment.
|
|
|
181
247
|
writeFileSync(".claude/commands/stack-init.md", orchestrator, "utf8");
|
|
182
248
|
writeFileSync(".claude/commands/stack-sync.md", buildBootstrapSync(), "utf8");
|
|
183
249
|
installBootstrapCommands(".claude/commands");
|
|
250
|
+
installMarketplaceFetcher();
|
|
184
251
|
await updateManifest("init", collected, { estimatedTokens: tokens, estimatedCost: cost });
|
|
185
252
|
installTokenHook();
|
|
186
253
|
// Create initial snapshot — collectFilesForSnapshot scans all .claude/ automatically
|
package/dist/marketplace.js
CHANGED
|
@@ -48,6 +48,33 @@ export const SAAS_PACKS = [
|
|
|
48
48
|
"Supabase", "Vercel", "OpenRouter", "GitHub", "Azure", "MongoDB",
|
|
49
49
|
"Playwright", "Tavily", "Stripe", "Slack", "Linear", "Notion",
|
|
50
50
|
];
|
|
51
|
+
// ── Adjacent category map (for fallback when primary SKIP) ─────────────
|
|
52
|
+
const ADJACENT_CATEGORIES = {
|
|
53
|
+
"01-core-development": ["02-language-specialists", "06-developer-experience"],
|
|
54
|
+
"02-language-specialists": ["01-core-development", "06-developer-experience"],
|
|
55
|
+
"03-infrastructure": ["04-quality-security", "09-meta-orchestration"],
|
|
56
|
+
"04-quality-security": ["03-infrastructure", "01-core-development"],
|
|
57
|
+
"05-data-ai": ["10-research-analysis", "07-specialized-domains"],
|
|
58
|
+
"06-developer-experience": ["01-core-development", "02-language-specialists"],
|
|
59
|
+
"07-specialized-domains": ["05-data-ai", "08-business-product"],
|
|
60
|
+
"08-business-product": ["07-specialized-domains", "09-meta-orchestration"],
|
|
61
|
+
"09-meta-orchestration": ["03-infrastructure", "04-quality-security"],
|
|
62
|
+
"10-research-analysis": ["05-data-ai", "07-specialized-domains"],
|
|
63
|
+
};
|
|
64
|
+
/** Expand target categories with their adjacent neighbors, deduplicating */
|
|
65
|
+
function expandWithAdjacent(targets) {
|
|
66
|
+
const seen = new Set(targets);
|
|
67
|
+
const adjacent = [];
|
|
68
|
+
for (const cat of targets) {
|
|
69
|
+
for (const adj of (ADJACENT_CATEGORIES[cat] ?? [])) {
|
|
70
|
+
if (!seen.has(adj)) {
|
|
71
|
+
seen.add(adj);
|
|
72
|
+
adjacent.push(adj);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return adjacent;
|
|
77
|
+
}
|
|
51
78
|
// ── Agent detection keywords ────────────────────────────────────────────
|
|
52
79
|
const AGENT_KEYWORDS = [
|
|
53
80
|
"agent", "subagent", "sub-agent", "orchestrat", "multi-agent",
|
|
@@ -208,6 +235,49 @@ export function classifyRequest(input) {
|
|
|
208
235
|
agentCategories: matchAgentCategories(input),
|
|
209
236
|
};
|
|
210
237
|
}
|
|
238
|
+
// ── Query keyword extraction (for inline filtering in node parsers) ─────
|
|
239
|
+
/** Extract meaningful search keywords from user input for inline node filtering */
|
|
240
|
+
function buildQueryRegex(input) {
|
|
241
|
+
const stopWords = new Set(["a", "an", "the", "and", "or", "for", "to", "in", "of", "with", "add", "install", "setup", "get", "find", "need", "want", "skill", "agent", "plugin", "tool"]);
|
|
242
|
+
const words = input.toLowerCase()
|
|
243
|
+
.replace(/[^a-z0-9\s-]/g, "")
|
|
244
|
+
.split(/\s+/)
|
|
245
|
+
.filter(w => w.length > 2 && !stopWords.has(w));
|
|
246
|
+
if (words.length === 0)
|
|
247
|
+
return "."; // match everything if no keywords
|
|
248
|
+
return words.join("|");
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Build a universal section-aware README parser as an inline node -e script.
|
|
252
|
+
* Rules (no hardcoding):
|
|
253
|
+
* 1. Split README into sections by any heading (## / ### / **Bold**)
|
|
254
|
+
* 2. Find sections whose heading matches the query keywords
|
|
255
|
+
* 3. Extract ALL links from matching sections (relative ./ AND absolute github.com)
|
|
256
|
+
* 4. If no section matches, fall back to all links in the entire README
|
|
257
|
+
* This works for any README structure — ComposioHQ, VoltAgent, or anything else.
|
|
258
|
+
*/
|
|
259
|
+
function buildSectionAwareParser(queryRegex) {
|
|
260
|
+
// Each part is carefully escaped for: TypeScript template → bash double-quotes → node -e
|
|
261
|
+
return (`const t=require('fs').readFileSync(0,'utf8');` +
|
|
262
|
+
`const q=/${queryRegex}/i;` +
|
|
263
|
+
// Split by ## headings, ### headings, and **Bold** subsection headers
|
|
264
|
+
`const secs=t.split(/\\n(?=#{2,3}\\s|\\*\\*[A-Z])/);` +
|
|
265
|
+
// 1st pass: sections whose heading matches query
|
|
266
|
+
`let hit=secs.filter(s=>q.test(s.split('\\n')[0]));` +
|
|
267
|
+
// 2nd pass: if no heading matched, find sections that have a link whose name matches
|
|
268
|
+
`if(hit.length===0)hit=secs.filter(s=>{const lk=/\\[([^\\]]+)\\]/g;let x;while((x=lk.exec(s))!=null){if(q.test(x[1]))return true}return false});` +
|
|
269
|
+
// Use matching sections, or fall back to entire README
|
|
270
|
+
`const src=hit.length>0?hit.join('\\n'):t;` +
|
|
271
|
+
// Extract all links — relative (./dir/) and absolute (github.com) and SKILL.md
|
|
272
|
+
`const re=/\\[([^\\]]+)\\]\\(([^)]+)\\)/g;let m;const r=[];` +
|
|
273
|
+
`while((m=re.exec(src))!=null){` +
|
|
274
|
+
`const u=m[2];` +
|
|
275
|
+
`if(u.startsWith('./')||u.includes('github.com')||u.includes('SKILL.md'))` +
|
|
276
|
+
`r.push(m[1]+' | '+u)` +
|
|
277
|
+
`}` +
|
|
278
|
+
`r.slice(0,10).forEach(x=>console.log(x));` +
|
|
279
|
+
`if(r.length===0)console.log('NO_README_MATCHES')`);
|
|
280
|
+
}
|
|
211
281
|
// ── Marketplace instruction builder ─────────────────────────────────────
|
|
212
282
|
// Implements Rule 6 (4-catalog exhaustion) and Rule 7 (3-stage fetch).
|
|
213
283
|
// Agent requests route to VoltAgent subagents first.
|
|
@@ -266,10 +336,12 @@ export function buildMarketplaceInstructions(input) {
|
|
|
266
336
|
// ── Agent pipeline (VoltAgent subagents → skills fallback) ──────────
|
|
267
337
|
function buildAgentPipeline(lines, input, agentCategories, categoryFilter) {
|
|
268
338
|
const safeName = input.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
339
|
+
const queryRegex = buildQueryRegex(input);
|
|
269
340
|
const targetCategories = agentCategories.length > 0
|
|
270
341
|
? agentCategories
|
|
271
342
|
: ["09-meta-orchestration"];
|
|
272
343
|
// ── STEP 1: VoltAgent/awesome-claude-code-subagents ──────────────
|
|
344
|
+
const adjacentCategories = expandWithAdjacent(targetCategories);
|
|
273
345
|
lines.push(`---`);
|
|
274
346
|
lines.push(``);
|
|
275
347
|
lines.push(`### STEP 1 — VoltAgent subagents (PRIMARY source for agents — 127+ specialized agents)`);
|
|
@@ -277,52 +349,74 @@ function buildAgentPipeline(lines, input, agentCategories, categoryFilter) {
|
|
|
277
349
|
lines.push(`This is the preferred source for anything agent-shaped. 10 categories, 127+ agents.`);
|
|
278
350
|
lines.push(`If a match is found here, install to \`.claude/agents/\` (NOT .claude/skills/).`);
|
|
279
351
|
lines.push(``);
|
|
280
|
-
lines.push(`**1a. List agent files in the
|
|
281
|
-
lines.push(`If any curl below fails, skip to the next category or STEP 2 immediately.`);
|
|
352
|
+
lines.push(`**1a. List agent files in the primary categories:**`);
|
|
282
353
|
lines.push(``);
|
|
283
354
|
for (const cat of targetCategories) {
|
|
284
355
|
lines.push(`\`\`\`bash`);
|
|
285
|
-
lines.push(`#
|
|
356
|
+
lines.push(`# Primary category: ${cat}`);
|
|
286
357
|
lines.push(`curl -sf \${GITHUB_TOKEN:+-H "Authorization: token \$GITHUB_TOKEN"} "${VOLTAGENT_SUBAGENTS_API}/${cat}" \\`);
|
|
287
358
|
lines.push(` | node -e "const d=JSON.parse(require('fs').readFileSync(0,'utf8'));` +
|
|
288
|
-
`if(
|
|
289
|
-
`d.filter(x=>x.name.endsWith('.md')&&x.name
|
|
359
|
+
`if(Array.isArray(d)===false){console.log('SKIP');process.exit(0)}` +
|
|
360
|
+
`d.filter(x=>x.name.endsWith('.md')&&(x.name==='README.md')===false).forEach(x=>console.log(x.name))"`);
|
|
290
361
|
lines.push(`\`\`\``);
|
|
291
362
|
lines.push(``);
|
|
292
363
|
}
|
|
293
|
-
|
|
294
|
-
|
|
364
|
+
if (adjacentCategories.length > 0) {
|
|
365
|
+
lines.push(`**1b. If ALL primary categories above returned SKIP, try adjacent categories:**`);
|
|
366
|
+
lines.push(`Do NOT skip this step. A SKIP in the primary category does NOT mean VoltAgent has no match.`);
|
|
367
|
+
lines.push(``);
|
|
368
|
+
for (const cat of adjacentCategories) {
|
|
369
|
+
lines.push(`\`\`\`bash`);
|
|
370
|
+
lines.push(`# Adjacent category: ${cat}`);
|
|
371
|
+
lines.push(`curl -sf \${GITHUB_TOKEN:+-H "Authorization: token \$GITHUB_TOKEN"} "${VOLTAGENT_SUBAGENTS_API}/${cat}" \\`);
|
|
372
|
+
lines.push(` | node -e "const d=JSON.parse(require('fs').readFileSync(0,'utf8'));` +
|
|
373
|
+
`if(Array.isArray(d)===false){console.log('SKIP');process.exit(0)}` +
|
|
374
|
+
`d.filter(x=>x.name.endsWith('.md')&&(x.name==='README.md')===false).forEach(x=>console.log(x.name))"`);
|
|
375
|
+
lines.push(`\`\`\``);
|
|
376
|
+
lines.push(``);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
lines.push(`**1c. From ALL file lists above (primary + adjacent), pick the BEST match for "${input}".**`);
|
|
380
|
+
lines.push(`Pick the filename closest to the request. If multiple candidates: pick the first relevant one.`);
|
|
295
381
|
lines.push(``);
|
|
296
|
-
lines.push(`**
|
|
382
|
+
lines.push(`**1d. Download and verify the matched agent file (single step):**`);
|
|
297
383
|
lines.push(`\`\`\`bash`);
|
|
298
|
-
lines.push(`# Replace CATEGORY and AGENT_FILE with actual values from 1a
|
|
384
|
+
lines.push(`# Replace CATEGORY and AGENT_FILE with actual values from 1a-1c`);
|
|
299
385
|
lines.push(`CATEGORY="${targetCategories[0]}"`);
|
|
300
386
|
lines.push(`AGENT_FILE="matched-agent.md"`);
|
|
301
387
|
lines.push(`mkdir -p ".claude/agents"`);
|
|
302
388
|
lines.push(`curl -sf "${VOLTAGENT_SUBAGENTS_RAW}/\${CATEGORY}/\${AGENT_FILE}" \\`);
|
|
303
|
-
lines.push(` -o ".claude/agents/\${AGENT_FILE}"`);
|
|
389
|
+
lines.push(` -o ".claude/agents/\${AGENT_FILE}" && wc -c ".claude/agents/\${AGENT_FILE}"`);
|
|
304
390
|
lines.push(`\`\`\``);
|
|
391
|
+
lines.push(`If >50 bytes: agent is installed. Return the success line.`);
|
|
392
|
+
lines.push(`If empty or 0 bytes: delete it, log the failure, try README fallback below.`);
|
|
393
|
+
lines.push(``);
|
|
394
|
+
lines.push(`**1f. README-driven fallback — if ALL category listings (primary + adjacent) returned SKIP:**`);
|
|
395
|
+
lines.push(`You MUST run this before moving to STEP 2. The README contains the full agent listing.`);
|
|
305
396
|
lines.push(``);
|
|
306
|
-
lines.push(`**1d. Verify the file has real content (Rule 7 — must not be empty):**`);
|
|
307
397
|
lines.push(`\`\`\`bash`);
|
|
308
|
-
lines.push(`#
|
|
309
|
-
lines.push(`
|
|
310
|
-
lines.push(`
|
|
398
|
+
lines.push(`# Fetch README — parser finds sections matching your query, extracts all links`);
|
|
399
|
+
lines.push(`curl -sf "https://raw.githubusercontent.com/${VOLTAGENT_SUBAGENTS_REPO}/main/README.md" \\`);
|
|
400
|
+
lines.push(` | node -e "${buildSectionAwareParser(queryRegex)}"`);
|
|
311
401
|
lines.push(`\`\`\``);
|
|
312
|
-
lines.push(`If the file is empty or just frontmatter with no body: delete it, log the failure, continue to STEP 2.`);
|
|
313
|
-
lines.push(`If the file has real content: agent is installed. Skip to "Install result format" below.`);
|
|
314
402
|
lines.push(``);
|
|
315
|
-
lines.push(
|
|
316
|
-
lines.push(
|
|
317
|
-
lines.push(
|
|
318
|
-
lines.push(
|
|
403
|
+
lines.push(`From the output, pick the FIRST entry. Extract the URL (after " | "). Resolve it:`);
|
|
404
|
+
lines.push(`- If URL starts with \`./\`: it is relative to the repo root. Prepend \`https://raw.githubusercontent.com/${VOLTAGENT_SUBAGENTS_REPO}/main/\``);
|
|
405
|
+
lines.push(`- If URL contains \`github.com/OWNER/REPO/blob/BRANCH/\`: replace with \`raw.githubusercontent.com/OWNER/REPO/BRANCH/\``);
|
|
406
|
+
lines.push(`- If URL contains \`github.com/OWNER/REPO/tree/BRANCH/\`: use API \`api.github.com/repos/OWNER/REPO/contents/PATH\` to list files, then download the .md file`);
|
|
407
|
+
lines.push(`Then download:`);
|
|
408
|
+
lines.push(`\`\`\`bash`);
|
|
409
|
+
lines.push(`mkdir -p ".claude/agents"`);
|
|
410
|
+
lines.push(`curl -sf "RESOLVED_URL" -o ".claude/agents/AGENT_FILE.md" && wc -c ".claude/agents/AGENT_FILE.md"`);
|
|
411
|
+
lines.push(`\`\`\``);
|
|
412
|
+
lines.push(`If >50 bytes: installed. If empty or NO_README_MATCHES: continue to STEP 2.`);
|
|
319
413
|
lines.push(``);
|
|
320
414
|
// ── STEP 2: jeremylongshore community skills (fallback for agents) ─
|
|
321
415
|
lines.push(`---`);
|
|
322
416
|
lines.push(``);
|
|
323
417
|
lines.push(`### STEP 2 — Community skills catalog (fallback — 416 plugins)`);
|
|
324
418
|
lines.push(``);
|
|
325
|
-
lines.push(
|
|
419
|
+
lines.push(`**Before continuing:** Document in one line why STEP 1 produced no result.`);
|
|
326
420
|
lines.push(`Search for agent-like skills in the community catalog.`);
|
|
327
421
|
lines.push(`If curl fails, skip to STEP 3.`);
|
|
328
422
|
lines.push(``);
|
|
@@ -332,7 +426,7 @@ function buildAgentPipeline(lines, input, agentCategories, categoryFilter) {
|
|
|
332
426
|
lines.push(``);
|
|
333
427
|
lines.push(`### STEP 3 — ComposioHQ service integrations (1000+ skills — fallback)`);
|
|
334
428
|
lines.push(``);
|
|
335
|
-
lines.push(
|
|
429
|
+
lines.push(`**Before continuing:** Document in one line why STEPs 1 and 2 produced no result.`);
|
|
336
430
|
lines.push(`Strong for API and SaaS automation. Skills live in per-directory SKILL.md files.`);
|
|
337
431
|
lines.push(`If curl fails, skip to STEP 4.`);
|
|
338
432
|
lines.push(``);
|
|
@@ -365,6 +459,7 @@ function buildAgentPipeline(lines, input, agentCategories, categoryFilter) {
|
|
|
365
459
|
// ── Skill pipeline (community → VoltAgent skills → ComposioHQ) ──────
|
|
366
460
|
function buildSkillPipeline(lines, input, categoryFilter, saasMatches) {
|
|
367
461
|
const safeName = input.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
462
|
+
const queryRegex = buildQueryRegex(input);
|
|
368
463
|
// ── STEP 1: Official Anthropic plugins ────────────────────────────
|
|
369
464
|
lines.push(`---`);
|
|
370
465
|
lines.push(``);
|
|
@@ -392,35 +487,55 @@ function buildSkillPipeline(lines, input, categoryFilter, saasMatches) {
|
|
|
392
487
|
lines.push(``);
|
|
393
488
|
lines.push(`### STEP 3 — VoltAgent curated agent skills (production-proven)`);
|
|
394
489
|
lines.push(``);
|
|
395
|
-
lines.push(
|
|
490
|
+
lines.push(`**Before continuing:** Document in one line why STEP 2 produced no result.`);
|
|
396
491
|
lines.push(`Curated real-world skills from engineering teams. If curl fails, skip to STEP 4.`);
|
|
397
492
|
lines.push(``);
|
|
398
493
|
lines.push(`\`\`\`bash`);
|
|
399
494
|
lines.push(`curl -sf \${GITHUB_TOKEN:+-H "Authorization: token \$GITHUB_TOKEN"} "${VOLTAGENT_SKILLS_API}" \\`);
|
|
400
495
|
lines.push(` | node -e "const d=JSON.parse(require('fs').readFileSync(0,'utf8'));` +
|
|
401
|
-
`if(
|
|
402
|
-
`
|
|
496
|
+
`if(Array.isArray(d)===false){console.log('SKIP');process.exit(0)}` +
|
|
497
|
+
`const q=/${queryRegex}/i;` +
|
|
498
|
+
`const all=d.filter(x=>x.type==='dir'&&(x.name[0]==='.')===false).map(x=>x.name);` +
|
|
499
|
+
`const matched=all.filter(n=>q.test(n));` +
|
|
500
|
+
`const out=matched.length>0?matched:all;` +
|
|
501
|
+
`out.slice(0,10).forEach(x=>console.log(x))"`);
|
|
403
502
|
lines.push(`\`\`\``);
|
|
404
|
-
lines.push(`
|
|
503
|
+
lines.push(`Pick the FIRST directory from output that matches "${input}" and download:`);
|
|
405
504
|
lines.push(`\`\`\`bash`);
|
|
505
|
+
lines.push(`# Replace SKILL_DIR with the actual directory name from the listing above`);
|
|
406
506
|
lines.push(`SKILL_DIR="matched-skill"`);
|
|
407
507
|
lines.push(`mkdir -p ".claude/skills/\${SKILL_DIR}"`);
|
|
408
508
|
lines.push(`curl -sf "https://raw.githubusercontent.com/${VOLTAGENT_SKILLS_REPO}/main/\${SKILL_DIR}/SKILL.md" \\`);
|
|
409
509
|
lines.push(` -o ".claude/skills/\${SKILL_DIR}/SKILL.md"`);
|
|
510
|
+
lines.push(`wc -c ".claude/skills/\${SKILL_DIR}/SKILL.md"`);
|
|
511
|
+
lines.push(`\`\`\``);
|
|
512
|
+
lines.push(`If >50 bytes: installed. If empty: delete and try README fallback below.`);
|
|
513
|
+
lines.push(``);
|
|
514
|
+
lines.push(`**README-driven fallback:** If the API listing above returned SKIP or no match:`);
|
|
515
|
+
lines.push(``);
|
|
516
|
+
lines.push(`\`\`\`bash`);
|
|
517
|
+
lines.push(`# Fetch README — parser finds sections matching your query, extracts all links`);
|
|
518
|
+
lines.push(`curl -sf "https://raw.githubusercontent.com/${VOLTAGENT_SKILLS_REPO}/main/README.md" \\`);
|
|
519
|
+
lines.push(` | node -e "${buildSectionAwareParser(queryRegex)}"`);
|
|
410
520
|
lines.push(`\`\`\``);
|
|
411
|
-
lines.push(`Verify content is real (not empty). If empty, delete and continue.`);
|
|
412
521
|
lines.push(``);
|
|
413
|
-
lines.push(
|
|
414
|
-
lines.push(
|
|
415
|
-
lines.push(
|
|
416
|
-
lines.push(
|
|
522
|
+
lines.push(`From the output, pick the FIRST entry. Extract the URL (after " | "). Resolve it:`);
|
|
523
|
+
lines.push(`- If URL starts with \`./\`: prepend \`https://raw.githubusercontent.com/${VOLTAGENT_SKILLS_REPO}/main/\``);
|
|
524
|
+
lines.push(`- If URL contains \`github.com/OWNER/REPO/blob/BRANCH/\`: replace with \`raw.githubusercontent.com/OWNER/REPO/BRANCH/\``);
|
|
525
|
+
lines.push(`- If URL contains \`github.com/OWNER/REPO/tree/BRANCH/\`: use API to list files, then download the SKILL.md`);
|
|
526
|
+
lines.push(`\`\`\`bash`);
|
|
527
|
+
lines.push(`SKILL_DIR="matched-skill"`);
|
|
528
|
+
lines.push(`mkdir -p ".claude/skills/\${SKILL_DIR}"`);
|
|
529
|
+
lines.push(`curl -sf "RESOLVED_URL" -o ".claude/skills/\${SKILL_DIR}/SKILL.md" && wc -c ".claude/skills/\${SKILL_DIR}/SKILL.md"`);
|
|
530
|
+
lines.push(`\`\`\``);
|
|
531
|
+
lines.push(`If >50 bytes: installed. If empty or NO_README_MATCHES: continue to next STEP.`);
|
|
417
532
|
lines.push(``);
|
|
418
533
|
// ── STEP 4: ComposioHQ service integrations ───────────────────────
|
|
419
534
|
lines.push(`---`);
|
|
420
535
|
lines.push(``);
|
|
421
536
|
lines.push(`### STEP 4 — ComposioHQ service integrations (1000+ skills)`);
|
|
422
537
|
lines.push(``);
|
|
423
|
-
lines.push(
|
|
538
|
+
lines.push(`**Before continuing:** Document in one line why STEPs 2 and 3 produced no result.`);
|
|
424
539
|
lines.push(`Strong for API/SaaS automation: Gmail, Slack, GitHub, Notion, Stripe, Shopify, etc.`);
|
|
425
540
|
lines.push(`If curl fails, skip to STEP 5.`);
|
|
426
541
|
lines.push(``);
|
|
@@ -482,7 +597,16 @@ function buildUniversalRulesBlock(lines) {
|
|
|
482
597
|
lines.push(`- **Uniqueness**: does it duplicate something already installed in .claude/skills/ or .claude/agents/? Deprioritize duplicates.`);
|
|
483
598
|
lines.push(`Install only the highest-scoring candidate. One high-quality tool per task.`);
|
|
484
599
|
lines.push(``);
|
|
485
|
-
lines.push(`**5.
|
|
600
|
+
lines.push(`**5. No absolute paths — never \`cd\` before commands:**`);
|
|
601
|
+
lines.push(`Claude Code already runs in the project working directory. Do NOT prepend \`cd /absolute/path &&\` to any command.`);
|
|
602
|
+
lines.push(`Use relative paths only (e.g., \`.claude/agents/\`, \`.claude/skills/\`). Absolute paths with backslashes break bash on Windows.`);
|
|
603
|
+
lines.push(``);
|
|
604
|
+
lines.push(`**6. Output filtering — cap context cost:**`);
|
|
605
|
+
lines.push(`Every curl result MUST be piped through a node parser that returns at most 5–15 lines of structured data.`);
|
|
606
|
+
lines.push(`The raw JSON/README response must NEVER enter the context unfiltered.`);
|
|
607
|
+
lines.push(`Chain multiple sequential fetch decisions inside a single bash script so they execute as one tool call.`);
|
|
608
|
+
lines.push(``);
|
|
609
|
+
lines.push(`**7. Content verification — never keep stubs:**`);
|
|
486
610
|
lines.push(`After downloading any file, verify ALL of these before accepting:`);
|
|
487
611
|
lines.push(`- Not empty (0 bytes)`);
|
|
488
612
|
lines.push(`- Not only YAML frontmatter with no body (just \`---\\nname: x\\n---\` and nothing after)`);
|
|
@@ -492,68 +616,83 @@ function buildUniversalRulesBlock(lines) {
|
|
|
492
616
|
}
|
|
493
617
|
// ── Shared fetch blocks ─────────────────────────────────────────────────
|
|
494
618
|
function buildCommunitySkillsFetchBlock(lines, categoryFilter) {
|
|
495
|
-
lines.push(`**Stage 1 — Fetch catalog and find matching
|
|
619
|
+
lines.push(`**Stage 1 — Fetch catalog and find matching plugins (compact output — one line per match):**`);
|
|
496
620
|
lines.push(`\`\`\`bash`);
|
|
497
621
|
lines.push(`curl -sf "${MARKETPLACE_CATALOG_URL}" \\`);
|
|
498
622
|
lines.push(` | node -e "const d=JSON.parse(require('fs').readFileSync(0,'utf8'));` +
|
|
499
|
-
`const q='${categoryFilter}';` +
|
|
500
|
-
`const r=d.plugins.filter(p=>(q===''||p.category.includes(q))&&p.name&&p.source).slice(0,10)
|
|
501
|
-
|
|
502
|
-
`console.log(
|
|
623
|
+
`const q='${categoryFilter}'.replace(/^\\\\d+-/,'');` +
|
|
624
|
+
`const r=d.plugins.filter(p=>(q===''||p.category.includes(q)||q.includes(p.category))&&p.name&&p.source).slice(0,10);` +
|
|
625
|
+
`r.forEach(p=>console.log(p.name+' | '+p.source));` +
|
|
626
|
+
`if(r.length===0)console.log('NO_PLUGINS')"`);
|
|
503
627
|
lines.push(`\`\`\``);
|
|
504
|
-
lines.push(`If
|
|
628
|
+
lines.push(`If NO_PLUGINS or error: skip to next STEP.`);
|
|
505
629
|
lines.push(``);
|
|
506
|
-
lines.push(`**Stage 2 —
|
|
630
|
+
lines.push(`**Stage 2 — Pick the FIRST match from output. Extract the source path (after " | "). List its skills:**`);
|
|
507
631
|
lines.push(`\`\`\`bash`);
|
|
508
632
|
lines.push(`# Replace PLUGIN_SOURCE_PATH with the "source" value from Stage 1`);
|
|
509
633
|
lines.push(`PLUGIN_SOURCE_PATH="plugins/category/plugin-name"`);
|
|
510
634
|
lines.push(`curl -sf \${GITHUB_TOKEN:+-H "Authorization: token \$GITHUB_TOKEN"} "https://api.github.com/repos/${MARKETPLACE_REPO}/contents/\${PLUGIN_SOURCE_PATH}/skills" \\`);
|
|
511
635
|
lines.push(` | node -e "const a=JSON.parse(require('fs').readFileSync(0,'utf8'));` +
|
|
512
|
-
`if(
|
|
636
|
+
`if(Array.isArray(a)===false){console.log('NO_SKILLS_DIR');process.exit(0)}` +
|
|
513
637
|
`a.forEach(x=>console.log(x.name))"`);
|
|
514
638
|
lines.push(`\`\`\``);
|
|
515
|
-
lines.push(`If
|
|
639
|
+
lines.push(`If NO_SKILLS_DIR: try the next plugin from Stage 1, or skip to next STEP.`);
|
|
516
640
|
lines.push(``);
|
|
517
|
-
lines.push(`**Stage 3 — Download
|
|
641
|
+
lines.push(`**Stage 3 — Download the skill file:**`);
|
|
518
642
|
lines.push(`\`\`\`bash`);
|
|
519
|
-
lines.push(`# Replace PLUGIN_SOURCE_PATH and SKILL_NAME with actual values`);
|
|
643
|
+
lines.push(`# Replace PLUGIN_SOURCE_PATH and SKILL_NAME with actual values from above`);
|
|
520
644
|
lines.push(`PLUGIN_SOURCE_PATH="plugins/category/plugin-name"`);
|
|
521
645
|
lines.push(`SKILL_NAME="skill-directory-name"`);
|
|
522
646
|
lines.push(`mkdir -p ".claude/skills/\${SKILL_NAME}"`);
|
|
523
647
|
lines.push(`curl -sf "https://raw.githubusercontent.com/${MARKETPLACE_REPO}/main/\${PLUGIN_SOURCE_PATH}/skills/\${SKILL_NAME}/SKILL.md" \\`);
|
|
524
648
|
lines.push(` -o ".claude/skills/\${SKILL_NAME}/SKILL.md"`);
|
|
649
|
+
lines.push(`wc -c ".claude/skills/\${SKILL_NAME}/SKILL.md"`);
|
|
525
650
|
lines.push(`\`\`\``);
|
|
526
|
-
lines.push(`
|
|
651
|
+
lines.push(`If >50 bytes: installed. If empty: delete and move on to next plugin or next STEP.`);
|
|
527
652
|
lines.push(``);
|
|
528
653
|
}
|
|
529
654
|
function buildComposioFetchBlock(lines, input) {
|
|
530
|
-
|
|
655
|
+
const queryRegex = buildQueryRegex(input);
|
|
656
|
+
lines.push(`**Stage 1 — List available skill directories and filter by query:**`);
|
|
531
657
|
lines.push(`\`\`\`bash`);
|
|
532
658
|
lines.push(`curl -sf \${GITHUB_TOKEN:+-H "Authorization: token \$GITHUB_TOKEN"} "${COMPOSIO_API}" \\`);
|
|
533
659
|
lines.push(` | node -e "const d=JSON.parse(require('fs').readFileSync(0,'utf8'));` +
|
|
534
|
-
`if(
|
|
535
|
-
`
|
|
660
|
+
`if(Array.isArray(d)===false){console.log('SKIP');process.exit(0)}` +
|
|
661
|
+
`const q=/${queryRegex}/i;` +
|
|
662
|
+
`const all=d.filter(x=>x.type==='dir'&&(x.name[0]==='.')===false).map(x=>x.name);` +
|
|
663
|
+
`const matched=all.filter(n=>q.test(n));` +
|
|
664
|
+
`const out=matched.length>0?matched:all;` +
|
|
665
|
+
`out.slice(0,10).forEach(x=>console.log(x))"`);
|
|
536
666
|
lines.push(`\`\`\``);
|
|
537
667
|
lines.push(``);
|
|
538
|
-
lines.push(`**Stage 2 — Pick the directory that
|
|
539
|
-
lines.push(`Match by name similarity. If no directory name is relevant, skip.`);
|
|
540
|
-
lines.push(``);
|
|
541
|
-
lines.push(`**Stage 3 — Download the SKILL.md from the matched directory:**`);
|
|
668
|
+
lines.push(`**Stage 2 — Pick the FIRST directory from the output that matches "${input}" and download:**`);
|
|
542
669
|
lines.push(`\`\`\`bash`);
|
|
670
|
+
lines.push(`# Replace SKILL_DIR with the actual directory name from Stage 1 output`);
|
|
543
671
|
lines.push(`SKILL_DIR="matched-directory"`);
|
|
544
672
|
lines.push(`mkdir -p ".claude/skills/\${SKILL_DIR}"`);
|
|
545
673
|
lines.push(`curl -sf "${COMPOSIO_RAW}/\${SKILL_DIR}/SKILL.md" \\`);
|
|
546
674
|
lines.push(` -o ".claude/skills/\${SKILL_DIR}/SKILL.md"`);
|
|
675
|
+
lines.push(`wc -c ".claude/skills/\${SKILL_DIR}/SKILL.md"`);
|
|
676
|
+
lines.push(`\`\`\``);
|
|
677
|
+
lines.push(`If >50 bytes: installed. If empty: delete and try README fallback below.`);
|
|
678
|
+
lines.push(``);
|
|
679
|
+
lines.push(`**README-driven fallback:** If the directory listing returned SKIP or no match:`);
|
|
680
|
+
lines.push(``);
|
|
681
|
+
lines.push(`\`\`\`bash`);
|
|
682
|
+
lines.push(`# Fetch README — parser finds sections matching your query, extracts all links`);
|
|
683
|
+
lines.push(`curl -sf "https://raw.githubusercontent.com/${COMPOSIO_REPO}/master/README.md" \\`);
|
|
684
|
+
lines.push(` | node -e "${buildSectionAwareParser(queryRegex)}"`);
|
|
547
685
|
lines.push(`\`\`\``);
|
|
548
|
-
lines.push(`Verify the file has real content (rule 5 above). If empty: delete and move on.`);
|
|
549
686
|
lines.push(``);
|
|
550
|
-
lines.push(
|
|
551
|
-
lines.push(
|
|
687
|
+
lines.push(`From the output, pick the FIRST entry. Extract the URL (after " | "). Resolve it:`);
|
|
688
|
+
lines.push(`- If URL starts with \`./\`: prepend \`https://raw.githubusercontent.com/${COMPOSIO_REPO}/master/\` and append \`SKILL.md\` if the URL is a directory`);
|
|
689
|
+
lines.push(`- If URL contains \`github.com/OWNER/REPO/blob/BRANCH/\`: replace with \`raw.githubusercontent.com/OWNER/REPO/BRANCH/\``);
|
|
690
|
+
lines.push(`- If URL contains \`github.com/OWNER/REPO/tree/BRANCH/\`: use API to list files, then download the SKILL.md`);
|
|
552
691
|
lines.push(`\`\`\`bash`);
|
|
553
|
-
lines.push(
|
|
554
|
-
lines.push(`
|
|
692
|
+
lines.push(`SKILL_DIR="matched-skill"`);
|
|
693
|
+
lines.push(`mkdir -p ".claude/skills/\${SKILL_DIR}"`);
|
|
694
|
+
lines.push(`curl -sf "RESOLVED_URL" -o ".claude/skills/\${SKILL_DIR}/SKILL.md" && wc -c ".claude/skills/\${SKILL_DIR}/SKILL.md"`);
|
|
555
695
|
lines.push(`\`\`\``);
|
|
556
|
-
lines.push(`
|
|
557
|
-
lines.push(`(rule 2+3 above) to download from the linked repo, regardless of its owner.`);
|
|
696
|
+
lines.push(`If >50 bytes: installed. If empty or NO_README_MATCHES: continue to next STEP.`);
|
|
558
697
|
lines.push(``);
|
|
559
698
|
}
|
package/package.json
CHANGED
package/templates/add.md
CHANGED
|
@@ -30,8 +30,32 @@ Skills: {{SKILLS_LIST}} | Commands: {{COMMANDS_LIST}}
|
|
|
30
30
|
|
|
31
31
|
---
|
|
32
32
|
|
|
33
|
+
## Marketplace search — AUTOMATICALLY spawn the marketplace-fetcher subagent
|
|
34
|
+
|
|
35
|
+
**MANDATORY:** For any request that involves installing a skill, agent, plugin, or integration,
|
|
36
|
+
you MUST use the Agent tool to spawn the `marketplace-fetcher` subagent. Do NOT run catalog
|
|
37
|
+
fetches in your own context. Do NOT ask the user whether to spawn — just do it.
|
|
38
|
+
|
|
39
|
+
**How to spawn:** Use the Agent tool with these parameters:
|
|
40
|
+
- `description`: "Fetch and install: {{USER_INPUT}}"
|
|
41
|
+
- `prompt`: Copy the ENTIRE marketplace instructions block below (between the `BEGIN` and `END` markers) as the subagent's task prompt.
|
|
42
|
+
|
|
43
|
+
The subagent runs in isolation on Haiku: it fetches catalogs, parses READMEs, downloads files to disk,
|
|
44
|
+
and returns exactly one confirmation line. Its context dies when it finishes — zero catalog bloat
|
|
45
|
+
enters your session. This cuts API calls from ~113 to ~6 and cache-read tokens from ~4.8M to ~180K.
|
|
46
|
+
|
|
47
|
+
**Only skip the subagent** if the request is purely a single-file edit (add one MCP server to .mcp.json,
|
|
48
|
+
add a hook to settings.json, or append to CLAUDE.md) with no catalog search needed.
|
|
49
|
+
|
|
50
|
+
--- BEGIN MARKETPLACE INSTRUCTIONS (pass this entire block to the subagent) ---
|
|
51
|
+
|
|
33
52
|
{{MARKETPLACE_INSTRUCTIONS}}
|
|
34
53
|
|
|
54
|
+
--- END MARKETPLACE INSTRUCTIONS ---
|
|
55
|
+
|
|
56
|
+
After the subagent returns its one-line result, continue to the post-install steps below.
|
|
57
|
+
If the subagent returned `FAILED`, create a production-quality custom skill/agent yourself.
|
|
58
|
+
|
|
35
59
|
---
|
|
36
60
|
|
|
37
61
|
## What you must actually do
|