ctx7 0.3.6 → 0.3.8

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/index.js CHANGED
@@ -1,4 +1,11 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ appendTomlServer,
4
+ mergeServerEntry,
5
+ readJsonConfig,
6
+ resolveMcpPath,
7
+ writeJsonConfig
8
+ } from "./chunk-WKOIWR6Y.js";
2
9
 
3
10
  // src/index.ts
4
11
  import { Command } from "commander";
@@ -103,6 +110,10 @@ async function downloadSkillFromGitHub(skill) {
103
110
  }
104
111
  const content = await fileResponse.text();
105
112
  const relativePath = item.path.slice(skillPath.length + 1);
113
+ if (relativePath.includes("..")) {
114
+ console.warn(`Skipping file with unsafe path: ${item.path}`);
115
+ continue;
116
+ }
106
117
  files.push({
107
118
  path: relativePath,
108
119
  content
@@ -433,13 +444,18 @@ async function checkboxWithHover(config, options) {
433
444
  );
434
445
  const values = choices.map((c) => c.value);
435
446
  const totalItems = values.length;
436
- let cursorPosition = 0;
447
+ let cursorPosition = choices.findIndex((c) => !c.disabled);
448
+ if (cursorPosition < 0) cursorPosition = 0;
437
449
  const getName = options?.getName ?? ((v) => v.name);
438
450
  const keypressHandler = (_str, key) => {
439
- if (key.name === "up" && cursorPosition > 0) {
440
- cursorPosition--;
441
- } else if (key.name === "down" && cursorPosition < totalItems - 1) {
442
- cursorPosition++;
451
+ if (key.name === "up") {
452
+ let next = cursorPosition - 1;
453
+ while (next >= 0 && choices[next].disabled) next--;
454
+ if (next >= 0) cursorPosition = next;
455
+ } else if (key.name === "down") {
456
+ let next = cursorPosition + 1;
457
+ while (next < totalItems && choices[next].disabled) next++;
458
+ if (next < totalItems) cursorPosition = next;
443
459
  }
444
460
  };
445
461
  readline.emitKeypressEvents(process.stdin);
@@ -483,7 +499,7 @@ var IDE_GLOBAL_PATHS = {
483
499
  claude: ".claude/skills",
484
500
  cursor: ".cursor/skills",
485
501
  antigravity: ".agent/skills",
486
- universal: ".config/agents/skills"
502
+ universal: ".agents/skills"
487
503
  };
488
504
  var IDE_NAMES = {
489
505
  claude: "Claude Code",
@@ -492,7 +508,7 @@ var IDE_NAMES = {
492
508
  universal: "Universal"
493
509
  };
494
510
  var UNIVERSAL_SKILLS_PATH = ".agents/skills";
495
- var UNIVERSAL_SKILLS_GLOBAL_PATH = ".config/agents/skills";
511
+ var UNIVERSAL_SKILLS_GLOBAL_PATH = ".agents/skills";
496
512
  var UNIVERSAL_AGENTS_LABEL = "Amp, Codex, Gemini CLI, GitHub Copilot, OpenCode + more";
497
513
  var VENDOR_SPECIFIC_AGENTS = ["claude", "cursor", "antigravity"];
498
514
  var DEFAULT_CONFIG = {
@@ -715,12 +731,15 @@ function getTargetDirFromSelection(ide, scope) {
715
731
 
716
732
  // src/utils/installer.ts
717
733
  import { mkdir, writeFile, rm, symlink, lstat } from "fs/promises";
718
- import { join as join3 } from "path";
734
+ import { join as join3, resolve, dirname as dirname3 } from "path";
719
735
  async function installSkillFiles(skillName, files, targetDir) {
720
- const skillDir = join3(targetDir, skillName);
736
+ const skillDir = resolve(targetDir, skillName);
721
737
  for (const file of files) {
722
- const filePath = join3(skillDir, file.path);
723
- const fileDir = join3(filePath, "..");
738
+ const filePath = resolve(skillDir, file.path);
739
+ if (!filePath.startsWith(skillDir + "/") && filePath !== skillDir) {
740
+ throw new Error(`Skill file path "${file.path}" resolves outside the target directory`);
741
+ }
742
+ const fileDir = dirname3(filePath);
724
743
  await mkdir(fileDir, { recursive: true });
725
744
  await writeFile(filePath, file.content);
726
745
  }
@@ -850,11 +869,11 @@ function createCallbackServer(expectedState) {
850
869
  let resolveResult;
851
870
  let rejectResult;
852
871
  let serverInstance = null;
853
- const portPromise = new Promise((resolve) => {
854
- resolvePort = resolve;
872
+ const portPromise = new Promise((resolve2) => {
873
+ resolvePort = resolve2;
855
874
  });
856
- const resultPromise = new Promise((resolve, reject) => {
857
- resolveResult = resolve;
875
+ const resultPromise = new Promise((resolve2, reject) => {
876
+ resolveResult = resolve2;
858
877
  rejectResult = reject;
859
878
  });
860
879
  const server = http.createServer((req, res) => {
@@ -1317,7 +1336,7 @@ async function generateCommand(options) {
1317
1336
  log.blank();
1318
1337
  if (searchResult.searchFilterApplied) {
1319
1338
  log.warn(
1320
- "Your results only include libraries matching your access settings. To search across all public libraries, update your settings at https://context7.com/dashboard?tab=libraries"
1339
+ "Your results only include libraries matching your teamspace's library filters. To adjust quality thresholds or blocked libraries, update your filters at https://context7.com/dashboard?tab=policies"
1321
1340
  );
1322
1341
  log.blank();
1323
1342
  }
@@ -1542,12 +1561,11 @@ async function generateCommand(options) {
1542
1561
  previewFileWritten = true;
1543
1562
  }
1544
1563
  const editor = process.env.EDITOR || "open";
1545
- await new Promise((resolve) => {
1564
+ await new Promise((resolve2) => {
1546
1565
  const child = spawn(editor, [previewFile], {
1547
- stdio: "inherit",
1548
- shell: true
1566
+ stdio: "inherit"
1549
1567
  });
1550
- child.on("close", () => resolve());
1568
+ child.on("close", () => resolve2());
1551
1569
  });
1552
1570
  };
1553
1571
  const syncFromPreviewFile = async () => {
@@ -1556,7 +1574,7 @@ async function generateCommand(options) {
1556
1574
  }
1557
1575
  };
1558
1576
  showPreview();
1559
- await new Promise((resolve) => setTimeout(resolve, 100));
1577
+ await new Promise((resolve2) => setTimeout(resolve2, 100));
1560
1578
  try {
1561
1579
  let action;
1562
1580
  while (true) {
@@ -2415,7 +2433,7 @@ ${headerLine}`,
2415
2433
  import pc8 from "picocolors";
2416
2434
  import ora4 from "ora";
2417
2435
  import { select as select3 } from "@inquirer/prompts";
2418
- import { mkdir as mkdir4, writeFile as writeFile4 } from "fs/promises";
2436
+ import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
2419
2437
  import { dirname as dirname4, join as join9 } from "path";
2420
2438
  import { randomBytes as randomBytes2 } from "crypto";
2421
2439
 
@@ -2426,7 +2444,8 @@ import { homedir as homedir5 } from "os";
2426
2444
  var SETUP_AGENT_NAMES = {
2427
2445
  claude: "Claude Code",
2428
2446
  cursor: "Cursor",
2429
- opencode: "OpenCode"
2447
+ opencode: "OpenCode",
2448
+ codex: "Codex"
2430
2449
  };
2431
2450
  var AUTH_MODE_LABELS = {
2432
2451
  oauth: "OAuth",
@@ -2447,12 +2466,13 @@ var agents = {
2447
2466
  name: "claude",
2448
2467
  displayName: "Claude Code",
2449
2468
  mcp: {
2450
- projectPath: ".mcp.json",
2451
- globalPath: join8(homedir5(), ".claude.json"),
2469
+ projectPaths: [".mcp.json"],
2470
+ globalPaths: [join8(homedir5(), ".claude.json")],
2452
2471
  configKey: "mcpServers",
2453
2472
  buildEntry: (auth) => withHeaders({ type: "http", url: mcpUrl(auth) }, auth)
2454
2473
  },
2455
2474
  rule: {
2475
+ kind: "file",
2456
2476
  dir: (scope) => scope === "global" ? join8(homedir5(), ".claude", "rules") : join8(".claude", "rules"),
2457
2477
  filename: "context7.md"
2458
2478
  },
@@ -2469,12 +2489,13 @@ var agents = {
2469
2489
  name: "cursor",
2470
2490
  displayName: "Cursor",
2471
2491
  mcp: {
2472
- projectPath: join8(".cursor", "mcp.json"),
2473
- globalPath: join8(homedir5(), ".cursor", "mcp.json"),
2492
+ projectPaths: [join8(".cursor", "mcp.json")],
2493
+ globalPaths: [join8(homedir5(), ".cursor", "mcp.json")],
2474
2494
  configKey: "mcpServers",
2475
2495
  buildEntry: (auth) => withHeaders({ url: mcpUrl(auth) }, auth)
2476
2496
  },
2477
2497
  rule: {
2498
+ kind: "file",
2478
2499
  dir: (scope) => scope === "global" ? join8(homedir5(), ".cursor", "rules") : join8(".cursor", "rules"),
2479
2500
  filename: "context7.mdc"
2480
2501
  },
@@ -2491,24 +2512,58 @@ var agents = {
2491
2512
  name: "opencode",
2492
2513
  displayName: "OpenCode",
2493
2514
  mcp: {
2494
- projectPath: ".opencode.json",
2495
- globalPath: join8(homedir5(), ".config", "opencode", "opencode.json"),
2515
+ projectPaths: ["opencode.json", "opencode.jsonc", ".opencode.json", ".opencode.jsonc"],
2516
+ globalPaths: [
2517
+ join8(homedir5(), ".config", "opencode", "opencode.json"),
2518
+ join8(homedir5(), ".config", "opencode", "opencode.jsonc"),
2519
+ join8(homedir5(), ".config", "opencode", ".opencode.json"),
2520
+ join8(homedir5(), ".config", "opencode", ".opencode.jsonc")
2521
+ ],
2496
2522
  configKey: "mcp",
2497
2523
  buildEntry: (auth) => withHeaders({ type: "remote", url: mcpUrl(auth), enabled: true }, auth)
2498
2524
  },
2499
2525
  rule: {
2500
- dir: (scope) => scope === "global" ? join8(homedir5(), ".config", "opencode", "rules") : join8(".opencode", "rules"),
2501
- filename: "context7.md",
2502
- instructionsGlob: (scope) => scope === "global" ? join8(homedir5(), ".config", "opencode", "rules", "*.md") : ".opencode/rules/*.md"
2526
+ kind: "append",
2527
+ file: (scope) => scope === "global" ? join8(homedir5(), ".config", "opencode", "AGENTS.md") : "AGENTS.md",
2528
+ sectionMarker: "<!-- context7 -->"
2503
2529
  },
2504
2530
  skill: {
2505
2531
  name: "context7-mcp",
2506
2532
  dir: (scope) => scope === "global" ? join8(homedir5(), ".agents", "skills") : join8(".agents", "skills")
2507
2533
  },
2508
2534
  detect: {
2509
- projectPaths: [".opencode.json"],
2535
+ projectPaths: ["opencode.json", "opencode.jsonc", ".opencode.json", ".opencode.jsonc"],
2510
2536
  globalPaths: [join8(homedir5(), ".config", "opencode")]
2511
2537
  }
2538
+ },
2539
+ codex: {
2540
+ name: "codex",
2541
+ displayName: "Codex",
2542
+ mcp: {
2543
+ projectPaths: [join8(".codex", "config.toml")],
2544
+ globalPaths: [join8(homedir5(), ".codex", "config.toml")],
2545
+ configKey: "mcp_servers",
2546
+ buildEntry: (auth) => {
2547
+ const entry = { type: "http", url: mcpUrl(auth) };
2548
+ if (auth.mode === "api-key" && auth.apiKey) {
2549
+ entry.headers = { CONTEXT7_API_KEY: auth.apiKey };
2550
+ }
2551
+ return entry;
2552
+ }
2553
+ },
2554
+ rule: {
2555
+ kind: "append",
2556
+ file: (scope) => scope === "global" ? join8(homedir5(), ".codex", "AGENTS.md") : "AGENTS.md",
2557
+ sectionMarker: "<!-- context7 -->"
2558
+ },
2559
+ skill: {
2560
+ name: "context7-mcp",
2561
+ dir: (scope) => scope === "global" ? join8(homedir5(), ".agents", "skills") : join8(".agents", "skills")
2562
+ },
2563
+ detect: {
2564
+ projectPaths: [".codex"],
2565
+ globalPaths: [join8(homedir5(), ".codex")]
2566
+ }
2512
2567
  }
2513
2568
  };
2514
2569
  function getAgent(name) {
@@ -2539,58 +2594,58 @@ async function detectAgents(scope) {
2539
2594
  }
2540
2595
 
2541
2596
  // src/setup/templates.ts
2542
- var RULE_CONTENT = `---
2543
- alwaysApply: true
2544
- ---
2597
+ var GITHUB_RAW_URLS = [
2598
+ "https://raw.githubusercontent.com/upstash/context7/master/rules",
2599
+ "https://raw.githubusercontent.com/upstash/context7/main/rules"
2600
+ ];
2601
+ var FALLBACK_MCP = `Use Context7 MCP to fetch current documentation whenever the user asks about a library, framework, SDK, API, CLI tool, or cloud service -- even well-known ones like React, Next.js, Prisma, Express, Tailwind, Django, or Spring Boot. This includes API syntax, configuration, version migration, library-specific debugging, setup instructions, and CLI tool usage. Use even when you think you know the answer -- your training data may not reflect recent changes. Prefer this over web search for library docs.
2545
2602
 
2546
- When working with libraries, frameworks, or APIs \u2014 use Context7 MCP to fetch current documentation instead of relying on training data. This includes setup questions, code generation, API references, and anything involving specific packages.
2603
+ Do not use for: refactoring, writing scripts from scratch, debugging business logic, code review, or general programming concepts.
2547
2604
 
2548
2605
  ## Steps
2549
2606
 
2550
- 1. Call \`resolve-library-id\` with the library name and the user's question
2551
- 2. Pick the best match \u2014 prefer exact names and version-specific IDs when a version is mentioned
2552
- 3. Call \`query-docs\` with the selected library ID and the user's question
2553
- 4. Answer using the fetched docs \u2014 include code examples and cite the version
2607
+ 1. \`resolve-library-id\` with the library name and the user's question
2608
+ 2. Pick the best match by: exact name match, description relevance, code snippet count, source reputation (High/Medium preferred), and benchmark score (higher is better). Use version-specific IDs when the user mentions a version
2609
+ 3. \`query-docs\` with the selected library ID and the user's full question (not single words)
2610
+ 4. Answer using the fetched docs
2554
2611
  `;
2612
+ var FALLBACK_CLI = `Use the \`ctx7\` CLI to fetch current documentation whenever the user asks about a library, framework, SDK, API, CLI tool, or cloud service -- even well-known ones like React, Next.js, Prisma, Express, Tailwind, Django, or Spring Boot. This includes API syntax, configuration, version migration, library-specific debugging, setup instructions, and CLI tool usage. Use even when you think you know the answer -- your training data may not reflect recent changes. Prefer this over web search for library docs.
2555
2613
 
2556
- // src/setup/mcp-writer.ts
2557
- import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
2558
- import { dirname as dirname3 } from "path";
2559
- async function readJsonConfig(filePath) {
2560
- let raw;
2561
- try {
2562
- raw = await readFile3(filePath, "utf-8");
2563
- } catch {
2564
- return {};
2565
- }
2566
- raw = raw.trim();
2567
- if (!raw) return {};
2568
- return JSON.parse(raw);
2569
- }
2570
- function mergeServerEntry(existing, configKey, serverName, entry) {
2571
- const section = existing[configKey] ?? {};
2572
- if (serverName in section) {
2573
- return { config: existing, alreadyExists: true };
2614
+ Do not use for: refactoring, writing scripts from scratch, debugging business logic, code review, or general programming concepts.
2615
+
2616
+ ## Steps
2617
+
2618
+ 1. Resolve library: \`npx ctx7@latest library <name> "<user's question>"\`
2619
+ 2. Pick the best match by: exact name match, description relevance, code snippet count, source reputation (High/Medium preferred), and benchmark score (higher is better). If results don't look right, try the full name with punctuation (e.g., "next.js" not "nextjs")
2620
+ 3. Fetch docs: \`npx ctx7@latest docs <libraryId> "<user's question>"\`
2621
+ 4. Answer using the fetched documentation
2622
+
2623
+ You MUST call \`library\` first to get a valid ID (format: \`/org/project\`) unless the user provides one directly. Use the user's full question as the query -- specific and detailed queries return better results than vague single words. Do not run more than 3 commands per question. Do not include sensitive information (API keys, passwords, credentials) in queries.
2624
+
2625
+ For version-specific docs, use \`/org/project/version\` from the \`library\` output (e.g., \`/vercel/next.js/v14.3.0\`).
2626
+
2627
+ If a command fails with a quota error, inform the user and suggest \`npx ctx7@latest login\` or setting \`CONTEXT7_API_KEY\` env var for higher limits. Do not silently fall back to training data.
2628
+ `;
2629
+ var CURSOR_FRONTMATTER = `---
2630
+ alwaysApply: true
2631
+ ---
2632
+
2633
+ `;
2634
+ async function fetchRule(filename, fallback) {
2635
+ for (const base of GITHUB_RAW_URLS) {
2636
+ try {
2637
+ const res = await fetch(`${base}/${filename}`);
2638
+ if (res.ok) return await res.text();
2639
+ } catch {
2640
+ continue;
2641
+ }
2574
2642
  }
2575
- return {
2576
- config: {
2577
- ...existing,
2578
- [configKey]: {
2579
- ...section,
2580
- [serverName]: entry
2581
- }
2582
- },
2583
- alreadyExists: false
2584
- };
2585
- }
2586
- function mergeInstructions(config, glob) {
2587
- const instructions = config.instructions ?? [];
2588
- if (instructions.includes(glob)) return config;
2589
- return { ...config, instructions: [...instructions, glob] };
2643
+ return fallback;
2590
2644
  }
2591
- async function writeJsonConfig(filePath, config) {
2592
- await mkdir3(dirname3(filePath), { recursive: true });
2593
- await writeFile3(filePath, JSON.stringify(config, null, 2) + "\n", "utf-8");
2645
+ async function getRuleContent(mode, agent) {
2646
+ const [filename, fallback] = mode === "mcp" ? ["context7-mcp.md", FALLBACK_MCP] : ["context7-cli.md", FALLBACK_CLI];
2647
+ const body = await fetchRule(filename, fallback);
2648
+ return agent === "cursor" ? `${CURSOR_FRONTMATTER}${body}` : body;
2594
2649
  }
2595
2650
 
2596
2651
  // src/commands/setup.ts
@@ -2605,10 +2660,11 @@ function getSelectedAgents(options) {
2605
2660
  if (options.claude) agents2.push("claude");
2606
2661
  if (options.cursor) agents2.push("cursor");
2607
2662
  if (options.opencode) agents2.push("opencode");
2663
+ if (options.codex) agents2.push("codex");
2608
2664
  return agents2;
2609
2665
  }
2610
2666
  function registerSetupCommand(program2) {
2611
- program2.command("setup").description("Set up Context7 for your AI coding agent").option("--claude", "Set up for Claude Code").option("--cursor", "Set up for Cursor").option("--universal", "Set up for Universal (.agents/skills)").option("--antigravity", "Set up for Antigravity (.agent/skills)").option("--opencode", "Set up for OpenCode").option("--mcp", "Set up MCP server mode").option("--cli", "Set up CLI + Skills mode (no MCP server)").option("-p, --project", "Configure for current project instead of globally").option("-y, --yes", "Skip confirmation prompts").option("--api-key <key>", "Use API key authentication").option("--oauth", "Use OAuth endpoint (IDE handles auth flow)").action(async (options) => {
2667
+ program2.command("setup").description("Set up Context7 for your AI coding agent").option("--claude", "Set up for Claude Code").option("--cursor", "Set up for Cursor").option("--universal", "Set up for Universal (.agents/skills)").option("--antigravity", "Set up for Antigravity (.agent/skills)").option("--opencode", "Set up for OpenCode").option("--codex", "Set up for Codex").option("--mcp", "Set up MCP server mode").option("--cli", "Set up CLI + Skills mode (no MCP server)").option("-p, --project", "Configure for current project instead of globally").option("-y, --yes", "Skip confirmation prompts").option("--api-key <key>", "Use API key authentication").option("--oauth", "Use OAuth endpoint (IDE handles auth flow)").action(async (options) => {
2612
2668
  await setupCommand(options);
2613
2669
  });
2614
2670
  }
@@ -2689,8 +2745,13 @@ async function resolveCliAuth(apiKey) {
2689
2745
  }
2690
2746
  async function isAlreadyConfigured(agentName, scope) {
2691
2747
  const agent = getAgent(agentName);
2692
- const mcpPath = scope === "global" ? agent.mcp.globalPath : join9(process.cwd(), agent.mcp.projectPath);
2748
+ const mcpCandidates = scope === "global" ? agent.mcp.globalPaths : agent.mcp.projectPaths.map((p) => join9(process.cwd(), p));
2749
+ const mcpPath = await resolveMcpPath(mcpCandidates);
2693
2750
  try {
2751
+ if (mcpPath.endsWith(".toml")) {
2752
+ const { readTomlServerExists } = await import("./mcp-writer-IYBCUACD.js");
2753
+ return readTomlServerExists(mcpPath, "context7");
2754
+ }
2694
2755
  const existing = await readJsonConfig(mcpPath);
2695
2756
  const section = existing[agent.mcp.configKey] ?? {};
2696
2757
  return "context7" in section;
@@ -2713,7 +2774,7 @@ async function promptAgents(scope, mode) {
2713
2774
  log.info("Context7 is already configured for all detected agents.");
2714
2775
  return null;
2715
2776
  }
2716
- const message = mode === "cli" ? "Install find-docs skill for which agents?" : "Which agents do you want to set up?";
2777
+ const message = "Which agents do you want to set up?";
2717
2778
  try {
2718
2779
  return await checkboxWithHover(
2719
2780
  {
@@ -2741,38 +2802,79 @@ async function resolveAgents(options, scope, mode = "mcp") {
2741
2802
  }
2742
2803
  return selected;
2743
2804
  }
2805
+ async function installRule(agentName, mode, scope) {
2806
+ const agent = getAgent(agentName);
2807
+ const rule = agent.rule;
2808
+ const content = await getRuleContent(mode, agentName);
2809
+ if (rule.kind === "file") {
2810
+ const ruleDir = scope === "global" ? rule.dir("global") : join9(process.cwd(), rule.dir("project"));
2811
+ const rulePath = join9(ruleDir, rule.filename);
2812
+ await mkdir3(dirname4(rulePath), { recursive: true });
2813
+ await writeFile3(rulePath, content, "utf-8");
2814
+ return { status: "installed", path: rulePath };
2815
+ }
2816
+ const filePath = scope === "global" ? rule.file("global") : join9(process.cwd(), rule.file("project"));
2817
+ const escapedMarker = rule.sectionMarker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2818
+ const section = `${rule.sectionMarker}
2819
+ ${content}${rule.sectionMarker}`;
2820
+ let existing = "";
2821
+ try {
2822
+ existing = await readFile3(filePath, "utf-8");
2823
+ } catch {
2824
+ }
2825
+ if (existing.includes(rule.sectionMarker)) {
2826
+ const regex = new RegExp(`${escapedMarker}\\n[\\s\\S]*?${escapedMarker}`);
2827
+ const updated = existing.replace(regex, section);
2828
+ await writeFile3(filePath, updated, "utf-8");
2829
+ return { status: "updated", path: filePath };
2830
+ }
2831
+ const separator = existing.length > 0 && !existing.endsWith("\n") ? "\n\n" : existing.length > 0 ? "\n" : "";
2832
+ await mkdir3(dirname4(filePath), { recursive: true });
2833
+ await writeFile3(filePath, existing + separator + section + "\n", "utf-8");
2834
+ return { status: "installed", path: filePath };
2835
+ }
2744
2836
  async function setupAgent(agentName, auth, scope) {
2745
2837
  const agent = getAgent(agentName);
2746
- const mcpPath = scope === "global" ? agent.mcp.globalPath : join9(process.cwd(), agent.mcp.projectPath);
2838
+ const mcpCandidates = scope === "global" ? agent.mcp.globalPaths : agent.mcp.projectPaths.map((p) => join9(process.cwd(), p));
2839
+ const mcpPath = await resolveMcpPath(mcpCandidates);
2747
2840
  let mcpStatus;
2748
2841
  try {
2749
- const existing = await readJsonConfig(mcpPath);
2750
- const { config, alreadyExists } = mergeServerEntry(
2751
- existing,
2752
- agent.mcp.configKey,
2753
- "context7",
2754
- agent.mcp.buildEntry(auth)
2755
- );
2756
- if (alreadyExists) {
2757
- mcpStatus = "already configured";
2842
+ if (mcpPath.endsWith(".toml")) {
2843
+ const { alreadyExists } = await appendTomlServer(
2844
+ mcpPath,
2845
+ "context7",
2846
+ agent.mcp.buildEntry(auth)
2847
+ );
2848
+ mcpStatus = alreadyExists ? "already configured" : `configured with ${AUTH_MODE_LABELS[auth.mode]}`;
2758
2849
  } else {
2759
- mcpStatus = `configured with ${AUTH_MODE_LABELS[auth.mode]}`;
2760
- }
2761
- const finalConfig = agent.rule.instructionsGlob ? mergeInstructions(config, agent.rule.instructionsGlob(scope)) : config;
2762
- if (finalConfig !== existing) {
2763
- await writeJsonConfig(mcpPath, finalConfig);
2850
+ const existing = await readJsonConfig(mcpPath);
2851
+ const { config, alreadyExists } = mergeServerEntry(
2852
+ existing,
2853
+ agent.mcp.configKey,
2854
+ "context7",
2855
+ agent.mcp.buildEntry(auth)
2856
+ );
2857
+ if (alreadyExists) {
2858
+ mcpStatus = "already configured";
2859
+ } else {
2860
+ mcpStatus = `configured with ${AUTH_MODE_LABELS[auth.mode]}`;
2861
+ }
2862
+ if (config !== existing) {
2863
+ await writeJsonConfig(mcpPath, config);
2864
+ }
2764
2865
  }
2765
2866
  } catch (err) {
2766
2867
  mcpStatus = `failed: ${err instanceof Error ? err.message : String(err)}`;
2767
2868
  }
2768
- const rulePath = scope === "global" ? join9(agent.rule.dir("global"), agent.rule.filename) : join9(process.cwd(), agent.rule.dir("project"), agent.rule.filename);
2769
2869
  let ruleStatus;
2870
+ let rulePath;
2770
2871
  try {
2771
- await mkdir4(dirname4(rulePath), { recursive: true });
2772
- await writeFile4(rulePath, RULE_CONTENT, "utf-8");
2773
- ruleStatus = "installed";
2872
+ const result = await installRule(agentName, "mcp", scope);
2873
+ ruleStatus = result.status;
2874
+ rulePath = result.path;
2774
2875
  } catch (err) {
2775
2876
  ruleStatus = `failed: ${err instanceof Error ? err.message : String(err)}`;
2877
+ rulePath = "";
2776
2878
  }
2777
2879
  const skillDir = scope === "global" ? agent.skill.dir("global") : join9(process.cwd(), agent.skill.dir("project"));
2778
2880
  const skillPath = join9(skillDir, agent.skill.name, "SKILL.md");
@@ -2828,13 +2930,34 @@ async function setupMcp(agents2, options, scope) {
2828
2930
  trackEvent("setup", { agents: agents2, scope, authMode: auth.mode });
2829
2931
  trackEvent("install", { skills: ["/upstash/context7/context7-mcp"], ides: agents2 });
2830
2932
  }
2933
+ async function setupCliAgent(agentName, scope, downloadData) {
2934
+ const agent = getAgent(agentName);
2935
+ const skillDir = scope === "global" ? agent.skill.dir("global") : join9(process.cwd(), agent.skill.dir("project"));
2936
+ let skillStatus;
2937
+ try {
2938
+ await installSkillFiles("find-docs", downloadData.files, skillDir);
2939
+ skillStatus = "installed";
2940
+ } catch (err) {
2941
+ skillStatus = `failed: ${err instanceof Error ? err.message : String(err)}`;
2942
+ }
2943
+ const skillPath = join9(skillDir, "find-docs");
2944
+ let ruleStatus;
2945
+ let rulePath;
2946
+ try {
2947
+ const result = await installRule(agentName, "cli", scope);
2948
+ ruleStatus = result.status;
2949
+ rulePath = result.path;
2950
+ } catch (err) {
2951
+ ruleStatus = `failed: ${err instanceof Error ? err.message : String(err)}`;
2952
+ rulePath = "";
2953
+ }
2954
+ return { skillPath, skillStatus, rulePath, ruleStatus };
2955
+ }
2831
2956
  async function setupCli(options) {
2832
2957
  await resolveCliAuth(options.apiKey);
2833
- const targets = await promptForInstallTargets({ ...options, global: !options.project }, false);
2834
- if (!targets) {
2835
- log.warn("Setup cancelled");
2836
- return;
2837
- }
2958
+ const scope = options.project ? "project" : "global";
2959
+ const agents2 = await resolveAgents(options, scope, "cli");
2960
+ if (agents2.length === 0) return;
2838
2961
  log.blank();
2839
2962
  const spinner = ora4("Downloading find-docs skill...").start();
2840
2963
  const downloadData = await downloadSkill("/upstash/context7", "find-docs");
@@ -2843,28 +2966,27 @@ async function setupCli(options) {
2843
2966
  return;
2844
2967
  }
2845
2968
  spinner.succeed("Downloaded find-docs skill");
2846
- const targetDirs = getTargetDirs(targets);
2847
- const installSpinner = ora4("Installing find-docs skill...").start();
2848
- for (const dir of targetDirs) {
2849
- installSpinner.text = `Installing to ${dir}...`;
2850
- await installSkillFiles("find-docs", downloadData.files, dir);
2969
+ const installSpinner = ora4("Installing...").start();
2970
+ const results = [];
2971
+ for (const agentName of agents2) {
2972
+ installSpinner.text = `Setting up ${getAgent(agentName).displayName}...`;
2973
+ const r = await setupCliAgent(agentName, scope, downloadData);
2974
+ results.push({ agent: getAgent(agentName).displayName, ...r });
2851
2975
  }
2852
- installSpinner.stop();
2853
- log.blank();
2854
- log.plain(`${pc8.green("\u2714")} Context7 CLI setup complete`);
2976
+ installSpinner.succeed("Context7 CLI setup complete");
2855
2977
  log.blank();
2856
- for (const dir of targetDirs) {
2857
- log.itemAdd(
2858
- `find-docs ${pc8.dim("Guides your agent to fetch up-to-date library docs on demand using ctx7 CLI commands")}`
2859
- );
2860
- log.plain(` ${pc8.dim(dir)}`);
2978
+ for (const r of results) {
2979
+ log.plain(` ${pc8.bold(r.agent)}`);
2980
+ const skillIcon = r.skillStatus === "installed" ? pc8.green("+") : pc8.dim("~");
2981
+ log.plain(` ${skillIcon} Skill ${r.skillStatus}`);
2982
+ log.plain(` ${pc8.dim(r.skillPath)}`);
2983
+ const ruleIcon = r.ruleStatus === "installed" || r.ruleStatus === "updated" ? pc8.green("+") : pc8.dim("~");
2984
+ log.plain(` ${ruleIcon} Rule ${r.ruleStatus}`);
2985
+ log.plain(` ${pc8.dim(r.rulePath)}`);
2861
2986
  }
2862
2987
  log.blank();
2863
- log.plain(` ${pc8.bold("Next steps")}`);
2864
- log.plain(` Ask your agent: ${pc8.cyan(`"Use ctx7 CLI to look up React hooks"`)}`);
2865
- log.blank();
2866
2988
  trackEvent("setup", { mode: "cli" });
2867
- trackEvent("install", { skills: ["/upstash/context7/find-docs"], ides: targets.ides });
2989
+ trackEvent("install", { skills: ["/upstash/context7/find-docs"], ides: agents2 });
2868
2990
  }
2869
2991
  async function setupCommand(options) {
2870
2992
  trackEvent("command", { name: "setup" });
@@ -2953,7 +3075,7 @@ async function resolveCommand(library, query, options) {
2953
3075
  log.blank();
2954
3076
  if (data.searchFilterApplied) {
2955
3077
  log.warn(
2956
- "Your results only include libraries matching your access settings. To search across all public libraries, update your settings at https://context7.com/dashboard?tab=libraries"
3078
+ "Your results only include libraries matching your teamspace's library filters. To adjust quality thresholds or blocked libraries, update your filters at https://context7.com/dashboard?tab=policies"
2957
3079
  );
2958
3080
  log.blank();
2959
3081
  }