🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

claude-setup

Package Overview
Dependencies
Maintainers
1
Versions
12
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

claude-setup - npm Package Compare versions

Comparing version
1.1.9
to
2.0.0
+2
-0
dist/commands/add.js

@@ -9,2 +9,3 @@ import { writeFileSync, mkdirSync, existsSync } from "fs";

import { c } from "../output.js";
import { installMarketplaceFetcher } from "./init.js";
function ensureDir(dir) {

@@ -50,2 +51,3 @@ if (!existsSync(dir))

ensureDir(".claude/commands");
installMarketplaceFetcher();
writeFileSync(".claude/commands/stack-add.md", content, "utf8");

@@ -52,0 +54,0 @@ await updateManifest("add", collected, {

@@ -0,1 +1,3 @@

/** Install the marketplace-fetcher subagent so /stack-add can spawn it */
export declare function installMarketplaceFetcher(cwd?: string): void;
export declare function runInit(opts?: {

@@ -2,0 +4,0 @@ dryRun?: boolean;

@@ -74,2 +74,46 @@ import { writeFileSync, mkdirSync, existsSync, readFileSync } from "fs";

}
/** Install the marketplace-fetcher subagent so /stack-add can spawn it */
export function installMarketplaceFetcher(cwd = process.cwd()) {
const agentsDir = join(cwd, ".claude", "agents");
ensureDir(agentsDir);
const filepath = join(agentsDir, "marketplace-fetcher.md");
// Always overwrite — this is a system agent, not user-authored
writeFileSync(filepath, `---
name: marketplace-fetcher
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.
tools: Bash
model: haiku
---
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.
## Your task
You will receive a single install request (e.g., "Stripe integration", "orchestration agent"). Execute the full pipeline below. Return exactly ONE line when done.
## Pipeline
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:
1. **Classify** the request (agent vs skill, categories, SaaS matches)
2. **Search catalogs in order** — stop at the first quality match
3. **Download** the matched file to \`.claude/agents/\` (agents) or \`.claude/skills/<name>/\` (skills)
4. **Verify** the file has real content (not empty, not a stub)
5. **Return** exactly one line:
- \`INSTALLED .claude/agents/<file> <bytes>b\` or
- \`INSTALLED .claude/skills/<dir>/SKILL.md <bytes>b\` or
- \`CREATED .claude/agents/<file> <bytes>b\` (if custom-created after all catalogs exhausted) or
- \`CREATED .claude/skills/<dir>/SKILL.md <bytes>b\` or
- \`FAILED no match in any catalog and custom creation not possible\`
## Rules
- Execute every curl yourself. Never ask for confirmation.
- Use relative paths only. Never \`cd\` to an absolute path.
- Pipe all curl output through a node parser — never return raw JSON/README to context.
- A fetch failure is a routing signal to the next catalog, not a stop condition.
- If a step produces an ambiguous result, document why it failed in one line, then continue.
- When creating custom skills/agents (last resort), make them production-valid — no stubs.
`, "utf8");
}
function installTokenHook(cwd = process.cwd()) {

@@ -143,2 +187,3 @@ // Write the hook script

installBootstrapCommands(".claude/commands");
installMarketplaceFetcher();
await updateManifest("init", collected, { estimatedTokens: tokens, estimatedCost: cost });

@@ -186,2 +231,3 @@ installTokenHook();

installBootstrapCommands(".claude/commands");
installMarketplaceFetcher();
await updateManifest("init", collected, { estimatedTokens: tokens, estimatedCost: cost });

@@ -188,0 +234,0 @@ installTokenHook();

+131
-36

@@ -51,2 +51,29 @@ /**

];
// ── Adjacent category map (for fallback when primary SKIP) ─────────────
const ADJACENT_CATEGORIES = {
"01-core-development": ["02-language-specialists", "06-developer-experience"],
"02-language-specialists": ["01-core-development", "06-developer-experience"],
"03-infrastructure": ["04-quality-security", "09-meta-orchestration"],
"04-quality-security": ["03-infrastructure", "01-core-development"],
"05-data-ai": ["10-research-analysis", "07-specialized-domains"],
"06-developer-experience": ["01-core-development", "02-language-specialists"],
"07-specialized-domains": ["05-data-ai", "08-business-product"],
"08-business-product": ["07-specialized-domains", "09-meta-orchestration"],
"09-meta-orchestration": ["03-infrastructure", "04-quality-security"],
"10-research-analysis": ["05-data-ai", "07-specialized-domains"],
};
/** Expand target categories with their adjacent neighbors, deduplicating */
function expandWithAdjacent(targets) {
const seen = new Set(targets);
const adjacent = [];
for (const cat of targets) {
for (const adj of (ADJACENT_CATEGORIES[cat] ?? [])) {
if (!seen.has(adj)) {
seen.add(adj);
adjacent.push(adj);
}
}
}
return adjacent;
}
// ── Agent detection keywords ────────────────────────────────────────────

@@ -274,2 +301,3 @@ const AGENT_KEYWORDS = [

// ── STEP 1: VoltAgent/awesome-claude-code-subagents ──────────────
const adjacentCategories = expandWithAdjacent(targetCategories);
lines.push(`---`);

@@ -282,21 +310,35 @@ lines.push(``);

lines.push(``);
lines.push(`**1a. List agent files in the matched categories:**`);
lines.push(`If any curl below fails, skip to the next category or STEP 2 immediately.`);
lines.push(`**1a. List agent files in the primary categories:**`);
lines.push(``);
for (const cat of targetCategories) {
lines.push(`\`\`\`bash`);
lines.push(`# Category: ${cat}`);
lines.push(`# Primary category: ${cat}`);
lines.push(`curl -sf \${GITHUB_TOKEN:+-H "Authorization: token \$GITHUB_TOKEN"} "${VOLTAGENT_SUBAGENTS_API}/${cat}" \\`);
lines.push(` | node -e "const d=JSON.parse(require('fs').readFileSync(0,'utf8'));` +
`if(!Array.isArray(d)){console.log('SKIP');process.exit(0)}` +
`d.filter(x=>x.name.endsWith('.md')&&x.name!=='README.md').forEach(x=>console.log(x.name))"`);
`if(Array.isArray(d)===false){console.log('SKIP');process.exit(0)}` +
`d.filter(x=>x.name.endsWith('.md')&&(x.name==='README.md')===false).forEach(x=>console.log(x.name))"`);
lines.push(`\`\`\``);
lines.push(``);
}
lines.push(`**1b. From the file list above, pick the BEST match for "${input}".**`);
if (adjacentCategories.length > 0) {
lines.push(`**1b. If ALL primary categories above returned SKIP, try adjacent categories:**`);
lines.push(`Do NOT skip this step. A SKIP in the primary category does NOT mean VoltAgent has no match.`);
lines.push(``);
for (const cat of adjacentCategories) {
lines.push(`\`\`\`bash`);
lines.push(`# Adjacent category: ${cat}`);
lines.push(`curl -sf \${GITHUB_TOKEN:+-H "Authorization: token \$GITHUB_TOKEN"} "${VOLTAGENT_SUBAGENTS_API}/${cat}" \\`);
lines.push(` | node -e "const d=JSON.parse(require('fs').readFileSync(0,'utf8'));` +
`if(Array.isArray(d)===false){console.log('SKIP');process.exit(0)}` +
`d.filter(x=>x.name.endsWith('.md')&&(x.name==='README.md')===false).forEach(x=>console.log(x.name))"`);
lines.push(`\`\`\``);
lines.push(``);
}
}
lines.push(`**1c. From ALL file lists above (primary + adjacent), pick the BEST match for "${input}".**`);
lines.push(`Match by name and relevance. If multiple candidates exist, pick the closest one.`);
lines.push(``);
lines.push(`**1c. Download the matched agent file (3-stage resolution — this is stage 3):**`);
lines.push(`**1d. Download the matched agent file:**`);
lines.push(`\`\`\`bash`);
lines.push(`# Replace CATEGORY and AGENT_FILE with actual values from 1a/1b`);
lines.push(`# Replace CATEGORY and AGENT_FILE with actual values from 1a-1c`);
lines.push(`CATEGORY="${targetCategories[0]}"`);

@@ -309,16 +351,32 @@ lines.push(`AGENT_FILE="matched-agent.md"`);

lines.push(``);
lines.push(`**1d. Verify the file has real content (Rule 7 — must not be empty):**`);
lines.push(`**1e. Verify the file has real content (rule 7 — must not be empty):**`);
lines.push(`\`\`\`bash`);
lines.push(`# File must have frontmatter (---) and body content`);
lines.push(`head -3 ".claude/agents/\${AGENT_FILE}"`);
lines.push(`wc -l ".claude/agents/\${AGENT_FILE}"`);
lines.push(`\`\`\``);
lines.push(`If the file is empty or just frontmatter with no body: delete it, log the failure, continue to STEP 2.`);
lines.push(`If the file is empty or just frontmatter with no body: delete it, log the failure.`);
lines.push(`If the file has real content: agent is installed. Skip to "Install result format" below.`);
lines.push(``);
lines.push(`**README-driven fallback:** If a category listing returns a README.md instead of agent files,`);
lines.push(`parse the README for entries with links. Entries may point to external repos — follow them`);
lines.push(`using the universal URL resolution rules (extract {owner}/{repo} dynamically from the link).`);
lines.push(`Navigate the foreign repo to locate the agent .md file and download it.`);
lines.push(`**1f. README-driven fallback — if ALL category listings (primary + adjacent) returned SKIP:**`);
lines.push(`You MUST try this before moving to STEP 2. The README contains the full agent listing.`);
lines.push(``);
lines.push(`\`\`\`bash`);
lines.push(`# Step 1: Fetch the VoltAgent subagents README`);
lines.push(`curl -sf "https://raw.githubusercontent.com/${VOLTAGENT_SUBAGENTS_REPO}/main/README.md" \\`);
lines.push(` | node -e "const t=require('fs').readFileSync(0,'utf8');` +
`const re=/\\[([^\\]]+)\\]\\(([^)]+)\\)/g;let m;const r=[];` +
`while((m=re.exec(t))!==null){if(m[2].includes('.md')||m[2].includes('github.com'))r.push(m[1]+' | '+m[2])}` +
`r.slice(0,15).forEach(x=>console.log(x))"`);
lines.push(`\`\`\``);
lines.push(``);
lines.push(`\`\`\`bash`);
lines.push(`# Step 2: Pick the best entry for "${input}" from the list above.`);
lines.push(`# Extract the URL, convert github.com URLs to raw.githubusercontent.com (rule 2).`);
lines.push(`# Step 3: Download the resolved file`);
lines.push(`RESOLVED_URL="raw-url-from-step-2"`);
lines.push(`mkdir -p ".claude/agents"`);
lines.push(`curl -sf "\${RESOLVED_URL}" -o ".claude/agents/matched-agent.md"`);
lines.push(`\`\`\``);
lines.push(`Verify content (rule 7). If empty: delete and continue to STEP 2.`);
lines.push(``);
// ── STEP 2: jeremylongshore community skills (fallback for agents) ─

@@ -329,3 +387,3 @@ lines.push(`---`);

lines.push(``);
lines.push(`Only reach here if STEP 1 found no match.`);
lines.push(`**Before continuing:** Document in one line why STEP 1 produced no result.`);
lines.push(`Search for agent-like skills in the community catalog.`);

@@ -340,3 +398,3 @@ lines.push(`If curl fails, skip to STEP 3.`);

lines.push(``);
lines.push(`Only reach here if STEP 1 and 2 found no match.`);
lines.push(`**Before continuing:** Document in one line why STEPs 1 and 2 produced no result.`);
lines.push(`Strong for API and SaaS automation. Skills live in per-directory SKILL.md files.`);

@@ -401,3 +459,3 @@ lines.push(`If curl fails, skip to STEP 4.`);

lines.push(``);
lines.push(`Only reach here if STEP 2 found no match.`);
lines.push(`**Before continuing:** Document in one line why STEP 2 produced no result.`);
lines.push(`Curated real-world skills from engineering teams. If curl fails, skip to STEP 4.`);

@@ -408,4 +466,4 @@ lines.push(``);

lines.push(` | node -e "const d=JSON.parse(require('fs').readFileSync(0,'utf8'));` +
`if(!Array.isArray(d)){console.log('SKIP');process.exit(0)}` +
`d.filter(x=>x.type==='dir'&&!x.name.startsWith('.')).forEach(x=>console.log(x.name))"`);
`if(Array.isArray(d)===false){console.log('SKIP');process.exit(0)}` +
`d.filter(x=>x.type==='dir'&&x.name.startsWith('.')===false).forEach(x=>console.log(x.name))"`);
lines.push(`\`\`\``);

@@ -421,7 +479,23 @@ lines.push(`If any directory name matches "${input}", fetch its SKILL.md:`);

lines.push(``);
lines.push(`**README-driven fallback:** If the API returns a single file (README.md) instead of a directory listing,`);
lines.push(`parse its markdown sections for entries matching "${input}". Entries may link to external repos owned by`);
lines.push(`different authors — follow those links using the universal URL resolution rules above (extract {owner}/{repo}`);
lines.push(`dynamically, never hardcode). Navigate the foreign repo to find the installable .md file and download it.`);
lines.push(`**README-driven fallback:** If the API listing above returned SKIP or no match, fetch the README:`);
lines.push(``);
lines.push(`\`\`\`bash`);
lines.push(`# Step 1: Fetch the VoltAgent skills README`);
lines.push(`curl -sf "https://raw.githubusercontent.com/${VOLTAGENT_SKILLS_REPO}/main/README.md" \\`);
lines.push(` | node -e "const t=require('fs').readFileSync(0,'utf8');` +
`const re=/\\[([^\\]]+)\\]\\(([^)]+)\\)/g;let m;const r=[];` +
`while((m=re.exec(t))!==null){if(m[2].includes('SKILL.md')||m[2].includes('github.com'))r.push(m[1]+' | '+m[2])}` +
`r.slice(0,10).forEach(x=>console.log(x))"`);
lines.push(`\`\`\``);
lines.push(``);
lines.push(`\`\`\`bash`);
lines.push(`# Step 2: Pick the entry best matching "${input}". Resolve the URL (rule 2).`);
lines.push(`# Step 3: Download the resolved skill file`);
lines.push(`RESOLVED_URL="raw-url-from-step-2"`);
lines.push(`SKILL_DIR="matched-skill"`);
lines.push(`mkdir -p ".claude/skills/\${SKILL_DIR}"`);
lines.push(`curl -sf "\${RESOLVED_URL}" -o ".claude/skills/\${SKILL_DIR}/SKILL.md"`);
lines.push(`\`\`\``);
lines.push(`Verify content (rule 7). If empty: delete and continue to next STEP.`);
lines.push(``);
// ── STEP 4: ComposioHQ service integrations ───────────────────────

@@ -432,3 +506,3 @@ lines.push(`---`);

lines.push(``);
lines.push(`Only reach here if STEPs 2+3 found no match.`);
lines.push(`**Before continuing:** Document in one line why STEPs 2 and 3 produced no result.`);
lines.push(`Strong for API/SaaS automation: Gmail, Slack, GitHub, Notion, Stripe, Shopify, etc.`);

@@ -495,3 +569,12 @@ lines.push(`If curl fails, skip to STEP 5.`);

lines.push(``);
lines.push(`**5. Content verification — never keep stubs:**`);
lines.push(`**5. No absolute paths — never \`cd\` before commands:**`);
lines.push(`Claude Code already runs in the project working directory. Do NOT prepend \`cd /absolute/path &&\` to any command.`);
lines.push(`Use relative paths only (e.g., \`.claude/agents/\`, \`.claude/skills/\`). Absolute paths with backslashes break bash on Windows.`);
lines.push(``);
lines.push(`**6. Output filtering — cap context cost:**`);
lines.push(`Every curl result MUST be piped through a node parser that returns at most 5–15 lines of structured data.`);
lines.push(`The raw JSON/README response must NEVER enter the context unfiltered.`);
lines.push(`Chain multiple sequential fetch decisions inside a single bash script so they execute as one tool call.`);
lines.push(``);
lines.push(`**7. Content verification — never keep stubs:**`);
lines.push(`After downloading any file, verify ALL of these before accepting:`);

@@ -523,3 +606,3 @@ lines.push(`- Not empty (0 bytes)`);

lines.push(` | node -e "const a=JSON.parse(require('fs').readFileSync(0,'utf8'));` +
`if(!Array.isArray(a)){console.log('NO_SKILLS_DIR');process.exit(0)}` +
`if(Array.isArray(a)===false){console.log('NO_SKILLS_DIR');process.exit(0)}` +
`a.forEach(x=>console.log(x.name))"`);

@@ -546,4 +629,4 @@ lines.push(`\`\`\``);

lines.push(` | node -e "const d=JSON.parse(require('fs').readFileSync(0,'utf8'));` +
`if(!Array.isArray(d)){console.log('SKIP');process.exit(0)}` +
`d.filter(x=>x.type==='dir'&&!x.name.startsWith('.')).forEach(x=>console.log(x.name))"`);
`if(Array.isArray(d)===false){console.log('SKIP');process.exit(0)}` +
`d.filter(x=>x.type==='dir'&&x.name.startsWith('.')===false).forEach(x=>console.log(x.name))"`);
lines.push(`\`\`\``);

@@ -561,13 +644,25 @@ lines.push(``);

lines.push(`\`\`\``);
lines.push(`Verify the file has real content (rule 5 above). If empty: delete and move on.`);
lines.push(`Verify the file has real content (rule 7 above). If empty: delete and move on.`);
lines.push(``);
lines.push(`**README-driven fallback:** If the directory listing above fails or returns unexpected content,`);
lines.push(`fetch the repo's README.md and parse it for sections matching "${input}":`);
lines.push(`**README-driven fallback:** If the directory listing above fails or returns unexpected content:`);
lines.push(``);
lines.push(`\`\`\`bash`);
lines.push(`# Derive owner/repo dynamically from the catalog source — never hardcode`);
lines.push(`curl -sf "https://raw.githubusercontent.com/${COMPOSIO_REPO}/master/README.md" | head -300`);
lines.push(`# Step 1: Fetch the ComposioHQ README and extract entries with links`);
lines.push(`curl -sf "https://raw.githubusercontent.com/${COMPOSIO_REPO}/master/README.md" \\`);
lines.push(` | node -e "const t=require('fs').readFileSync(0,'utf8');` +
`const re=/\\[([^\\]]+)\\]\\(([^)]+)\\)/g;let m;const r=[];` +
`while((m=re.exec(t))!==null){if(m[2].includes('SKILL.md')||m[2].includes('github.com'))r.push(m[1]+' | '+m[2])}` +
`r.slice(0,10).forEach(x=>console.log(x))"`);
lines.push(`\`\`\``);
lines.push(`Scan sections for entries with external links. Follow those links using universal URL resolution`);
lines.push(`(rule 2+3 above) to download from the linked repo, regardless of its owner.`);
lines.push(``);
lines.push(`\`\`\`bash`);
lines.push(`# Step 2: Pick the entry best matching "${input}". Resolve the URL (rule 2).`);
lines.push(`# Step 3: Download the resolved skill file`);
lines.push(`RESOLVED_URL="raw-url-from-step-2"`);
lines.push(`SKILL_DIR="matched-skill"`);
lines.push(`mkdir -p ".claude/skills/\${SKILL_DIR}"`);
lines.push(`curl -sf "\${RESOLVED_URL}" -o ".claude/skills/\${SKILL_DIR}/SKILL.md"`);
lines.push(`\`\`\``);
lines.push(`Verify content (rule 7). If empty: delete and continue to next STEP.`);
lines.push(``);
}
{
"name": "claude-setup",
"version": "1.1.9",
"version": "2.0.0",
"description": "Setup layer for Claude Code — reads your project, writes command files, Claude Code does the rest",

@@ -5,0 +5,0 @@ "type": "module",

@@ -33,4 +33,28 @@ <!-- claude-setup add {{DATE}} -->

## Marketplace search — AUTOMATICALLY spawn the marketplace-fetcher subagent
**MANDATORY:** For any request that involves installing a skill, agent, plugin, or integration,
you MUST use the Agent tool to spawn the `marketplace-fetcher` subagent. Do NOT run catalog
fetches in your own context. Do NOT ask the user whether to spawn — just do it.
**How to spawn:** Use the Agent tool with these parameters:
- `description`: "Fetch and install: {{USER_INPUT}}"
- `prompt`: Copy the ENTIRE marketplace instructions block below (between the `BEGIN` and `END` markers) as the subagent's task prompt.
The subagent runs in isolation on Haiku: it fetches catalogs, parses READMEs, downloads files to disk,
and returns exactly one confirmation line. Its context dies when it finishes — zero catalog bloat
enters your session. This cuts API calls from ~113 to ~6 and cache-read tokens from ~4.8M to ~180K.
**Only skip the subagent** if the request is purely a single-file edit (add one MCP server to .mcp.json,
add a hook to settings.json, or append to CLAUDE.md) with no catalog search needed.
--- BEGIN MARKETPLACE INSTRUCTIONS (pass this entire block to the subagent) ---
{{MARKETPLACE_INSTRUCTIONS}}
--- END MARKETPLACE INSTRUCTIONS ---
After the subagent returns its one-line result, continue to the post-install steps below.
If the subagent returned `FAILED`, create a production-quality custom skill/agent yourself.
---

@@ -37,0 +61,0 @@