claude-setup
Advanced tools
@@ -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(``); | ||
| } |
+1
-1
| { | ||
| "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", |
+24
-0
@@ -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 @@ |
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
315604
3.12%6371
2.2%32
3.23%