claude-setup 2.0.0 → 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.
@@ -84,35 +84,56 @@ tools: Bash
84
84
  model: haiku
85
85
  ---
86
86
 
87
- You are a marketplace fetch agent. Your ONLY job is to search the 4 curated catalogs, download the best matching file, and confirm the install. You run in isolation your context dies when you finish, keeping the main session clean.
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
88
 
89
- ## Your task
89
+ ## CRITICAL RULES — read before doing anything
90
90
 
91
- You will receive a single install request (e.g., "Stripe integration", "orchestration agent"). Execute the full pipeline below. Return exactly ONE line when done.
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.
92
98
 
93
- ## Pipeline
99
+ ## How to execute the instructions
94
100
 
95
- The main session has already generated a complete marketplace instruction block. It will be passed to you as your task prompt. Follow every step in that instruction block exactly:
101
+ Your prompt contains numbered STEPs with bash code blocks. For each STEP:
96
102
 
97
- 1. **Classify** the request (agent vs skill, categories, SaaS matches)
98
- 2. **Search catalogs in order** stop at the first quality match
99
- 3. **Download** the matched file to \`.claude/agents/\` (agents) or \`.claude/skills/<name>/\` (skills)
100
- 4. **Verify** the file has real content (not empty, not a stub)
101
- 5. **Return** exactly one line:
102
- - \`INSTALLED .claude/agents/<file> <bytes>b\` or
103
- - \`INSTALLED .claude/skills/<dir>/SKILL.md <bytes>b\` or
104
- - \`CREATED .claude/agents/<file> <bytes>b\` (if custom-created after all catalogs exhausted) or
105
- - \`CREATED .claude/skills/<dir>/SKILL.md <bytes>b\` or
106
- - \`FAILED no match in any catalog and custom creation not possible\`
103
+ 1. Run the first bash command (the catalog listing/search)
104
+ 2. Read the outputit 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.
107
110
 
108
- ## Rules
111
+ ## Return format
109
112
 
110
- - Execute every curl yourself. Never ask for confirmation.
111
- - Use relative paths only. Never \`cd\` to an absolute path.
112
- - Pipe all curl output through a node parser — never return raw JSON/README to context.
113
- - A fetch failure is a routing signal to the next catalog, not a stop condition.
114
- - If a step produces an ambiguous result, document why it failed in one line, then continue.
115
- - When creating custom skills/agents (last resort), make them production-valid no stubs.
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.
116
137
  `, "utf8");
117
138
  }
118
139
  function installTokenHook(cwd = process.cwd()) {
@@ -235,6 +235,49 @@ export function classifyRequest(input) {
235
235
  agentCategories: matchAgentCategories(input),
236
236
  };
237
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
+ }
238
281
  // ── Marketplace instruction builder ─────────────────────────────────────
239
282
  // Implements Rule 6 (4-catalog exhaustion) and Rule 7 (3-stage fetch).
240
283
  // Agent requests route to VoltAgent subagents first.
@@ -293,6 +336,7 @@ export function buildMarketplaceInstructions(input) {
293
336
  // ── Agent pipeline (VoltAgent subagents → skills fallback) ──────────
294
337
  function buildAgentPipeline(lines, input, agentCategories, categoryFilter) {
295
338
  const safeName = input.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
339
+ const queryRegex = buildQueryRegex(input);
296
340
  const targetCategories = agentCategories.length > 0
297
341
  ? agentCategories
298
342
  : ["09-meta-orchestration"];
@@ -333,47 +377,39 @@ function buildAgentPipeline(lines, input, agentCategories, categoryFilter) {
333
377
  }
334
378
  }
335
379
  lines.push(`**1c. From ALL file lists above (primary + adjacent), pick the BEST match for "${input}".**`);
336
- lines.push(`Match by name and relevance. If multiple candidates exist, pick the closest one.`);
380
+ lines.push(`Pick the filename closest to the request. If multiple candidates: pick the first relevant one.`);
337
381
  lines.push(``);
338
- lines.push(`**1d. Download the matched agent file:**`);
382
+ lines.push(`**1d. Download and verify the matched agent file (single step):**`);
339
383
  lines.push(`\`\`\`bash`);
340
384
  lines.push(`# Replace CATEGORY and AGENT_FILE with actual values from 1a-1c`);
341
385
  lines.push(`CATEGORY="${targetCategories[0]}"`);
342
386
  lines.push(`AGENT_FILE="matched-agent.md"`);
343
387
  lines.push(`mkdir -p ".claude/agents"`);
344
388
  lines.push(`curl -sf "${VOLTAGENT_SUBAGENTS_RAW}/\${CATEGORY}/\${AGENT_FILE}" \\`);
345
- lines.push(` -o ".claude/agents/\${AGENT_FILE}"`);
346
- lines.push(`\`\`\``);
347
- lines.push(``);
348
- lines.push(`**1e. Verify the file has real content (rule 7 — must not be empty):**`);
349
- lines.push(`\`\`\`bash`);
350
- lines.push(`head -3 ".claude/agents/\${AGENT_FILE}"`);
351
- lines.push(`wc -l ".claude/agents/\${AGENT_FILE}"`);
389
+ lines.push(` -o ".claude/agents/\${AGENT_FILE}" && wc -c ".claude/agents/\${AGENT_FILE}"`);
352
390
  lines.push(`\`\`\``);
353
- lines.push(`If the file is empty or just frontmatter with no body: delete it, log the failure.`);
354
- lines.push(`If the file has real content: agent is installed. Skip to "Install result format" below.`);
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.`);
355
393
  lines.push(``);
356
394
  lines.push(`**1f. README-driven fallback — if ALL category listings (primary + adjacent) returned SKIP:**`);
357
- lines.push(`You MUST try this before moving to STEP 2. The README contains the full agent listing.`);
395
+ lines.push(`You MUST run this before moving to STEP 2. The README contains the full agent listing.`);
358
396
  lines.push(``);
359
397
  lines.push(`\`\`\`bash`);
360
- lines.push(`# Step 1: Fetch the VoltAgent subagents README`);
398
+ lines.push(`# Fetch README parser finds sections matching your query, extracts all links`);
361
399
  lines.push(`curl -sf "https://raw.githubusercontent.com/${VOLTAGENT_SUBAGENTS_REPO}/main/README.md" \\`);
362
- lines.push(` | node -e "const t=require('fs').readFileSync(0,'utf8');` +
363
- `const re=/\\[([^\\]]+)\\]\\(([^)]+)\\)/g;let m;const r=[];` +
364
- `while((m=re.exec(t))!==null){if(m[2].includes('.md')||m[2].includes('github.com'))r.push(m[1]+' | '+m[2])}` +
365
- `r.slice(0,15).forEach(x=>console.log(x))"`);
400
+ lines.push(` | node -e "${buildSectionAwareParser(queryRegex)}"`);
366
401
  lines.push(`\`\`\``);
367
402
  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:`);
368
408
  lines.push(`\`\`\`bash`);
369
- lines.push(`# Step 2: Pick the best entry for "${input}" from the list above.`);
370
- lines.push(`# Extract the URL, convert github.com URLs to raw.githubusercontent.com (rule 2).`);
371
- lines.push(`# Step 3: Download the resolved file`);
372
- lines.push(`RESOLVED_URL="raw-url-from-step-2"`);
373
409
  lines.push(`mkdir -p ".claude/agents"`);
374
- lines.push(`curl -sf "\${RESOLVED_URL}" -o ".claude/agents/matched-agent.md"`);
410
+ lines.push(`curl -sf "RESOLVED_URL" -o ".claude/agents/AGENT_FILE.md" && wc -c ".claude/agents/AGENT_FILE.md"`);
375
411
  lines.push(`\`\`\``);
376
- lines.push(`Verify content (rule 7). If empty: delete and continue to STEP 2.`);
412
+ lines.push(`If >50 bytes: installed. If empty or NO_README_MATCHES: continue to STEP 2.`);
377
413
  lines.push(``);
378
414
  // ── STEP 2: jeremylongshore community skills (fallback for agents) ─
379
415
  lines.push(`---`);
@@ -423,6 +459,7 @@ function buildAgentPipeline(lines, input, agentCategories, categoryFilter) {
423
459
  // ── Skill pipeline (community → VoltAgent skills → ComposioHQ) ──────
424
460
  function buildSkillPipeline(lines, input, categoryFilter, saasMatches) {
425
461
  const safeName = input.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
462
+ const queryRegex = buildQueryRegex(input);
426
463
  // ── STEP 1: Official Anthropic plugins ────────────────────────────
427
464
  lines.push(`---`);
428
465
  lines.push(``);
@@ -457,37 +494,41 @@ function buildSkillPipeline(lines, input, categoryFilter, saasMatches) {
457
494
  lines.push(`curl -sf \${GITHUB_TOKEN:+-H "Authorization: token \$GITHUB_TOKEN"} "${VOLTAGENT_SKILLS_API}" \\`);
458
495
  lines.push(` | node -e "const d=JSON.parse(require('fs').readFileSync(0,'utf8'));` +
459
496
  `if(Array.isArray(d)===false){console.log('SKIP');process.exit(0)}` +
460
- `d.filter(x=>x.type==='dir'&&x.name.startsWith('.')===false).forEach(x=>console.log(x.name))"`);
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))"`);
461
502
  lines.push(`\`\`\``);
462
- lines.push(`If any directory name matches "${input}", fetch its SKILL.md:`);
503
+ lines.push(`Pick the FIRST directory from output that matches "${input}" and download:`);
463
504
  lines.push(`\`\`\`bash`);
505
+ lines.push(`# Replace SKILL_DIR with the actual directory name from the listing above`);
464
506
  lines.push(`SKILL_DIR="matched-skill"`);
465
507
  lines.push(`mkdir -p ".claude/skills/\${SKILL_DIR}"`);
466
508
  lines.push(`curl -sf "https://raw.githubusercontent.com/${VOLTAGENT_SKILLS_REPO}/main/\${SKILL_DIR}/SKILL.md" \\`);
467
509
  lines.push(` -o ".claude/skills/\${SKILL_DIR}/SKILL.md"`);
510
+ lines.push(`wc -c ".claude/skills/\${SKILL_DIR}/SKILL.md"`);
468
511
  lines.push(`\`\`\``);
469
- lines.push(`Verify content is real (not empty). If empty, delete and continue.`);
512
+ lines.push(`If >50 bytes: installed. If empty: delete and try README fallback below.`);
470
513
  lines.push(``);
471
- lines.push(`**README-driven fallback:** If the API listing above returned SKIP or no match, fetch the README:`);
514
+ lines.push(`**README-driven fallback:** If the API listing above returned SKIP or no match:`);
472
515
  lines.push(``);
473
516
  lines.push(`\`\`\`bash`);
474
- lines.push(`# Step 1: Fetch the VoltAgent skills README`);
517
+ lines.push(`# Fetch README parser finds sections matching your query, extracts all links`);
475
518
  lines.push(`curl -sf "https://raw.githubusercontent.com/${VOLTAGENT_SKILLS_REPO}/main/README.md" \\`);
476
- lines.push(` | node -e "const t=require('fs').readFileSync(0,'utf8');` +
477
- `const re=/\\[([^\\]]+)\\]\\(([^)]+)\\)/g;let m;const r=[];` +
478
- `while((m=re.exec(t))!==null){if(m[2].includes('SKILL.md')||m[2].includes('github.com'))r.push(m[1]+' | '+m[2])}` +
479
- `r.slice(0,10).forEach(x=>console.log(x))"`);
519
+ lines.push(` | node -e "${buildSectionAwareParser(queryRegex)}"`);
480
520
  lines.push(`\`\`\``);
481
521
  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`);
482
526
  lines.push(`\`\`\`bash`);
483
- lines.push(`# Step 2: Pick the entry best matching "${input}". Resolve the URL (rule 2).`);
484
- lines.push(`# Step 3: Download the resolved skill file`);
485
- lines.push(`RESOLVED_URL="raw-url-from-step-2"`);
486
527
  lines.push(`SKILL_DIR="matched-skill"`);
487
528
  lines.push(`mkdir -p ".claude/skills/\${SKILL_DIR}"`);
488
- lines.push(`curl -sf "\${RESOLVED_URL}" -o ".claude/skills/\${SKILL_DIR}/SKILL.md"`);
529
+ lines.push(`curl -sf "RESOLVED_URL" -o ".claude/skills/\${SKILL_DIR}/SKILL.md" && wc -c ".claude/skills/\${SKILL_DIR}/SKILL.md"`);
489
530
  lines.push(`\`\`\``);
490
- lines.push(`Verify content (rule 7). If empty: delete and continue to next STEP.`);
531
+ lines.push(`If >50 bytes: installed. If empty or NO_README_MATCHES: continue to next STEP.`);
491
532
  lines.push(``);
492
533
  // ── STEP 4: ComposioHQ service integrations ───────────────────────
493
534
  lines.push(`---`);
@@ -575,18 +616,18 @@ function buildUniversalRulesBlock(lines) {
575
616
  }
576
617
  // ── Shared fetch blocks ─────────────────────────────────────────────────
577
618
  function buildCommunitySkillsFetchBlock(lines, categoryFilter) {
578
- lines.push(`**Stage 1 — Fetch catalog and find matching plugin:**`);
619
+ lines.push(`**Stage 1 — Fetch catalog and find matching plugins (compact output — one line per match):**`);
579
620
  lines.push(`\`\`\`bash`);
580
621
  lines.push(`curl -sf "${MARKETPLACE_CATALOG_URL}" \\`);
581
622
  lines.push(` | node -e "const d=JSON.parse(require('fs').readFileSync(0,'utf8'));` +
582
- `const q='${categoryFilter}';` +
583
- `const r=d.plugins.filter(p=>(q===''||p.category.includes(q))&&p.name&&p.source).slice(0,10)` +
584
- `.map(p=>({name:p.name,source:p.source,desc:p.description}));` +
585
- `console.log(JSON.stringify(r,null,2));"`);
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')"`);
586
627
  lines.push(`\`\`\``);
587
- lines.push(`If this returns an error or empty array, skip to the next STEP.`);
628
+ lines.push(`If NO_PLUGINS or error: skip to next STEP.`);
588
629
  lines.push(``);
589
- lines.push(`**Stage 2 — Score candidates and pick the best match (see rule 4: Relevance > Scope > Uniqueness):**`);
630
+ lines.push(`**Stage 2 — Pick the FIRST match from output. Extract the source path (after " | "). List its skills:**`);
590
631
  lines.push(`\`\`\`bash`);
591
632
  lines.push(`# Replace PLUGIN_SOURCE_PATH with the "source" value from Stage 1`);
592
633
  lines.push(`PLUGIN_SOURCE_PATH="plugins/category/plugin-name"`);
@@ -595,60 +636,63 @@ function buildCommunitySkillsFetchBlock(lines, categoryFilter) {
595
636
  `if(Array.isArray(a)===false){console.log('NO_SKILLS_DIR');process.exit(0)}` +
596
637
  `a.forEach(x=>console.log(x.name))"`);
597
638
  lines.push(`\`\`\``);
598
- lines.push(`If this fails or shows NO_SKILLS_DIR, the plugin has no installable skills skip.`);
639
+ lines.push(`If NO_SKILLS_DIR: try the next plugin from Stage 1, or skip to next STEP.`);
599
640
  lines.push(``);
600
- lines.push(`**Stage 3 — Download each skill file and install:**`);
641
+ lines.push(`**Stage 3 — Download the skill file:**`);
601
642
  lines.push(`\`\`\`bash`);
602
- 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`);
603
644
  lines.push(`PLUGIN_SOURCE_PATH="plugins/category/plugin-name"`);
604
645
  lines.push(`SKILL_NAME="skill-directory-name"`);
605
646
  lines.push(`mkdir -p ".claude/skills/\${SKILL_NAME}"`);
606
647
  lines.push(`curl -sf "https://raw.githubusercontent.com/${MARKETPLACE_REPO}/main/\${PLUGIN_SOURCE_PATH}/skills/\${SKILL_NAME}/SKILL.md" \\`);
607
648
  lines.push(` -o ".claude/skills/\${SKILL_NAME}/SKILL.md"`);
649
+ lines.push(`wc -c ".claude/skills/\${SKILL_NAME}/SKILL.md"`);
608
650
  lines.push(`\`\`\``);
609
- lines.push(`Verify the file has real content (not empty, not just frontmatter). If empty: delete and move on.`);
651
+ lines.push(`If >50 bytes: installed. If empty: delete and move on to next plugin or next STEP.`);
610
652
  lines.push(``);
611
653
  }
612
654
  function buildComposioFetchBlock(lines, input) {
613
- lines.push(`**Stage 1 List available skill directories:**`);
655
+ const queryRegex = buildQueryRegex(input);
656
+ lines.push(`**Stage 1 — List available skill directories and filter by query:**`);
614
657
  lines.push(`\`\`\`bash`);
615
658
  lines.push(`curl -sf \${GITHUB_TOKEN:+-H "Authorization: token \$GITHUB_TOKEN"} "${COMPOSIO_API}" \\`);
616
659
  lines.push(` | node -e "const d=JSON.parse(require('fs').readFileSync(0,'utf8'));` +
617
660
  `if(Array.isArray(d)===false){console.log('SKIP');process.exit(0)}` +
618
- `d.filter(x=>x.type==='dir'&&x.name.startsWith('.')===false).forEach(x=>console.log(x.name))"`);
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))"`);
619
666
  lines.push(`\`\`\``);
620
667
  lines.push(``);
621
- lines.push(`**Stage 2 — Pick the directory that best matches "${input}":**`);
622
- lines.push(`Match by name similarity. If no directory name is relevant, skip.`);
623
- lines.push(``);
624
- 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:**`);
625
669
  lines.push(`\`\`\`bash`);
670
+ lines.push(`# Replace SKILL_DIR with the actual directory name from Stage 1 output`);
626
671
  lines.push(`SKILL_DIR="matched-directory"`);
627
672
  lines.push(`mkdir -p ".claude/skills/\${SKILL_DIR}"`);
628
673
  lines.push(`curl -sf "${COMPOSIO_RAW}/\${SKILL_DIR}/SKILL.md" \\`);
629
674
  lines.push(` -o ".claude/skills/\${SKILL_DIR}/SKILL.md"`);
675
+ lines.push(`wc -c ".claude/skills/\${SKILL_DIR}/SKILL.md"`);
630
676
  lines.push(`\`\`\``);
631
- lines.push(`Verify the file has real content (rule 7 above). If empty: delete and move on.`);
677
+ lines.push(`If >50 bytes: installed. If empty: delete and try README fallback below.`);
632
678
  lines.push(``);
633
- lines.push(`**README-driven fallback:** If the directory listing above fails or returns unexpected content:`);
679
+ lines.push(`**README-driven fallback:** If the directory listing returned SKIP or no match:`);
634
680
  lines.push(``);
635
681
  lines.push(`\`\`\`bash`);
636
- lines.push(`# Step 1: Fetch the ComposioHQ README and extract entries with links`);
682
+ lines.push(`# Fetch README parser finds sections matching your query, extracts all links`);
637
683
  lines.push(`curl -sf "https://raw.githubusercontent.com/${COMPOSIO_REPO}/master/README.md" \\`);
638
- lines.push(` | node -e "const t=require('fs').readFileSync(0,'utf8');` +
639
- `const re=/\\[([^\\]]+)\\]\\(([^)]+)\\)/g;let m;const r=[];` +
640
- `while((m=re.exec(t))!==null){if(m[2].includes('SKILL.md')||m[2].includes('github.com'))r.push(m[1]+' | '+m[2])}` +
641
- `r.slice(0,10).forEach(x=>console.log(x))"`);
684
+ lines.push(` | node -e "${buildSectionAwareParser(queryRegex)}"`);
642
685
  lines.push(`\`\`\``);
643
686
  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`);
644
691
  lines.push(`\`\`\`bash`);
645
- lines.push(`# Step 2: Pick the entry best matching "${input}". Resolve the URL (rule 2).`);
646
- lines.push(`# Step 3: Download the resolved skill file`);
647
- lines.push(`RESOLVED_URL="raw-url-from-step-2"`);
648
692
  lines.push(`SKILL_DIR="matched-skill"`);
649
693
  lines.push(`mkdir -p ".claude/skills/\${SKILL_DIR}"`);
650
- lines.push(`curl -sf "\${RESOLVED_URL}" -o ".claude/skills/\${SKILL_DIR}/SKILL.md"`);
694
+ lines.push(`curl -sf "RESOLVED_URL" -o ".claude/skills/\${SKILL_DIR}/SKILL.md" && wc -c ".claude/skills/\${SKILL_DIR}/SKILL.md"`);
651
695
  lines.push(`\`\`\``);
652
- lines.push(`Verify content (rule 7). If empty: delete and continue to next STEP.`);
696
+ lines.push(`If >50 bytes: installed. If empty or NO_README_MATCHES: continue to next STEP.`);
653
697
  lines.push(``);
654
698
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-setup",
3
- "version": "2.0.0",
3
+ "version": "2.0.3",
4
4
  "description": "Setup layer for Claude Code — reads your project, writes command files, Claude Code does the rest",
5
5
  "type": "module",
6
6
  "bin": {