ctx7 0.3.8 → 0.3.9

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,11 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- appendTomlServer,
4
- mergeServerEntry,
5
- readJsonConfig,
6
- resolveMcpPath,
7
- writeJsonConfig
8
- } from "./chunk-WKOIWR6Y.js";
9
2
 
10
3
  // src/index.ts
11
4
  import { Command } from "commander";
@@ -2433,8 +2426,8 @@ ${headerLine}`,
2433
2426
  import pc8 from "picocolors";
2434
2427
  import ora4 from "ora";
2435
2428
  import { select as select3 } from "@inquirer/prompts";
2436
- import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
2437
- import { dirname as dirname4, join as join9 } from "path";
2429
+ import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
2430
+ import { dirname as dirname5, join as join9 } from "path";
2438
2431
  import { randomBytes as randomBytes2 } from "crypto";
2439
2432
 
2440
2433
  // src/setup/agents.ts
@@ -2648,6 +2641,127 @@ async function getRuleContent(mode, agent) {
2648
2641
  return agent === "cursor" ? `${CURSOR_FRONTMATTER}${body}` : body;
2649
2642
  }
2650
2643
 
2644
+ // src/setup/mcp-writer.ts
2645
+ import { access as access3, readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
2646
+ import { dirname as dirname4 } from "path";
2647
+ function stripJsonComments(text) {
2648
+ let result = "";
2649
+ let i = 0;
2650
+ while (i < text.length) {
2651
+ if (text[i] === '"') {
2652
+ const start = i++;
2653
+ while (i < text.length && text[i] !== '"') {
2654
+ if (text[i] === "\\") i++;
2655
+ i++;
2656
+ }
2657
+ result += text.slice(start, ++i);
2658
+ } else if (text[i] === "/" && text[i + 1] === "/") {
2659
+ i += 2;
2660
+ while (i < text.length && text[i] !== "\n") i++;
2661
+ } else if (text[i] === "/" && text[i + 1] === "*") {
2662
+ i += 2;
2663
+ while (i < text.length && !(text[i] === "*" && text[i + 1] === "/")) i++;
2664
+ i += 2;
2665
+ } else {
2666
+ result += text[i++];
2667
+ }
2668
+ }
2669
+ return result;
2670
+ }
2671
+ async function readJsonConfig(filePath) {
2672
+ let raw;
2673
+ try {
2674
+ raw = await readFile3(filePath, "utf-8");
2675
+ } catch {
2676
+ return {};
2677
+ }
2678
+ raw = raw.trim();
2679
+ if (!raw) return {};
2680
+ return JSON.parse(stripJsonComments(raw));
2681
+ }
2682
+ function mergeServerEntry(existing, configKey, serverName, entry) {
2683
+ const section = existing[configKey] ?? {};
2684
+ const alreadyExists = serverName in section;
2685
+ return {
2686
+ config: {
2687
+ ...existing,
2688
+ [configKey]: {
2689
+ ...section,
2690
+ [serverName]: entry
2691
+ }
2692
+ },
2693
+ alreadyExists
2694
+ };
2695
+ }
2696
+ async function resolveMcpPath(candidates) {
2697
+ for (const candidate of candidates) {
2698
+ try {
2699
+ await access3(candidate);
2700
+ return candidate;
2701
+ } catch {
2702
+ }
2703
+ }
2704
+ return candidates[0];
2705
+ }
2706
+ async function writeJsonConfig(filePath, config) {
2707
+ await mkdir3(dirname4(filePath), { recursive: true });
2708
+ await writeFile3(filePath, JSON.stringify(config, null, 2) + "\n", "utf-8");
2709
+ }
2710
+ function buildTomlServerBlock(serverName, entry) {
2711
+ const lines = [`[mcp_servers.${serverName}]`];
2712
+ const headers = entry.headers;
2713
+ for (const [key, value] of Object.entries(entry)) {
2714
+ if (key === "headers") continue;
2715
+ lines.push(`${key} = ${JSON.stringify(value)}`);
2716
+ }
2717
+ if (headers && Object.keys(headers).length > 0) {
2718
+ lines.push("");
2719
+ lines.push(`[mcp_servers.${serverName}.http_headers]`);
2720
+ for (const [key, value] of Object.entries(headers)) {
2721
+ lines.push(`${key} = ${JSON.stringify(value)}`);
2722
+ }
2723
+ }
2724
+ return lines.join("\n") + "\n";
2725
+ }
2726
+ async function appendTomlServer(filePath, serverName, entry) {
2727
+ const block = buildTomlServerBlock(serverName, entry);
2728
+ let existing = "";
2729
+ try {
2730
+ existing = await readFile3(filePath, "utf-8");
2731
+ } catch {
2732
+ }
2733
+ const sectionHeader = `[mcp_servers.${serverName}]`;
2734
+ const alreadyExists = existing.includes(sectionHeader);
2735
+ if (alreadyExists) {
2736
+ const subPrefix = `[mcp_servers.${serverName}.`;
2737
+ const startIdx = existing.indexOf(sectionHeader);
2738
+ const rest = existing.slice(startIdx + sectionHeader.length);
2739
+ let endOffset = rest.length;
2740
+ const re = /^\[/gm;
2741
+ let m;
2742
+ while ((m = re.exec(rest)) !== null) {
2743
+ const lineEnd = rest.indexOf("\n", m.index);
2744
+ const line = rest.slice(m.index, lineEnd === -1 ? void 0 : lineEnd);
2745
+ if (!line.startsWith(subPrefix)) {
2746
+ endOffset = m.index;
2747
+ break;
2748
+ }
2749
+ }
2750
+ const rawBefore = existing.slice(0, startIdx).replace(/\n+$/, "");
2751
+ const rawAfter = existing.slice(startIdx + sectionHeader.length + endOffset).replace(/^\n+/, "");
2752
+ const before = rawBefore.length > 0 ? rawBefore + "\n\n" : "";
2753
+ const after = rawAfter.length > 0 ? "\n" + rawAfter : "";
2754
+ const content = before + block + after;
2755
+ await mkdir3(dirname4(filePath), { recursive: true });
2756
+ await writeFile3(filePath, content, "utf-8");
2757
+ } else {
2758
+ const separator = existing.length > 0 && !existing.endsWith("\n") ? "\n\n" : existing.length > 0 ? "\n" : "";
2759
+ await mkdir3(dirname4(filePath), { recursive: true });
2760
+ await writeFile3(filePath, existing + separator + block, "utf-8");
2761
+ }
2762
+ return { alreadyExists };
2763
+ }
2764
+
2651
2765
  // src/commands/setup.ts
2652
2766
  var CHECKBOX_THEME = {
2653
2767
  style: {
@@ -2743,37 +2857,11 @@ async function resolveCliAuth(apiKey) {
2743
2857
  }
2744
2858
  await performLogin();
2745
2859
  }
2746
- async function isAlreadyConfigured(agentName, scope) {
2747
- const agent = getAgent(agentName);
2748
- const mcpCandidates = scope === "global" ? agent.mcp.globalPaths : agent.mcp.projectPaths.map((p) => join9(process.cwd(), p));
2749
- const mcpPath = await resolveMcpPath(mcpCandidates);
2750
- try {
2751
- if (mcpPath.endsWith(".toml")) {
2752
- const { readTomlServerExists } = await import("./mcp-writer-IYBCUACD.js");
2753
- return readTomlServerExists(mcpPath, "context7");
2754
- }
2755
- const existing = await readJsonConfig(mcpPath);
2756
- const section = existing[agent.mcp.configKey] ?? {};
2757
- return "context7" in section;
2758
- } catch {
2759
- return false;
2760
- }
2761
- }
2762
- async function promptAgents(scope, mode) {
2763
- const choices = await Promise.all(
2764
- ALL_AGENT_NAMES.map(async (name) => {
2765
- const configured = mode === "mcp" ? await isAlreadyConfigured(name, scope) : false;
2766
- return {
2767
- name: SETUP_AGENT_NAMES[name],
2768
- value: name,
2769
- disabled: configured ? "(already configured)" : false
2770
- };
2771
- })
2772
- );
2773
- if (choices.every((c) => c.disabled)) {
2774
- log.info("Context7 is already configured for all detected agents.");
2775
- return null;
2776
- }
2860
+ async function promptAgents() {
2861
+ const choices = ALL_AGENT_NAMES.map((name) => ({
2862
+ name: SETUP_AGENT_NAMES[name],
2863
+ value: name
2864
+ }));
2777
2865
  const message = "Which agents do you want to set up?";
2778
2866
  try {
2779
2867
  return await checkboxWithHover(
@@ -2789,13 +2877,13 @@ async function promptAgents(scope, mode) {
2789
2877
  return null;
2790
2878
  }
2791
2879
  }
2792
- async function resolveAgents(options, scope, mode = "mcp") {
2880
+ async function resolveAgents(options, scope) {
2793
2881
  const explicit = getSelectedAgents(options);
2794
2882
  if (explicit.length > 0) return explicit;
2795
2883
  const detected = await detectAgents(scope);
2796
2884
  if (detected.length > 0 && options.yes) return detected;
2797
2885
  log.blank();
2798
- const selected = await promptAgents(scope, mode);
2886
+ const selected = await promptAgents();
2799
2887
  if (!selected) {
2800
2888
  log.warn("Setup cancelled");
2801
2889
  return [];
@@ -2809,8 +2897,8 @@ async function installRule(agentName, mode, scope) {
2809
2897
  if (rule.kind === "file") {
2810
2898
  const ruleDir = scope === "global" ? rule.dir("global") : join9(process.cwd(), rule.dir("project"));
2811
2899
  const rulePath = join9(ruleDir, rule.filename);
2812
- await mkdir3(dirname4(rulePath), { recursive: true });
2813
- await writeFile3(rulePath, content, "utf-8");
2900
+ await mkdir4(dirname5(rulePath), { recursive: true });
2901
+ await writeFile4(rulePath, content, "utf-8");
2814
2902
  return { status: "installed", path: rulePath };
2815
2903
  }
2816
2904
  const filePath = scope === "global" ? rule.file("global") : join9(process.cwd(), rule.file("project"));
@@ -2819,18 +2907,18 @@ async function installRule(agentName, mode, scope) {
2819
2907
  ${content}${rule.sectionMarker}`;
2820
2908
  let existing = "";
2821
2909
  try {
2822
- existing = await readFile3(filePath, "utf-8");
2910
+ existing = await readFile4(filePath, "utf-8");
2823
2911
  } catch {
2824
2912
  }
2825
2913
  if (existing.includes(rule.sectionMarker)) {
2826
2914
  const regex = new RegExp(`${escapedMarker}\\n[\\s\\S]*?${escapedMarker}`);
2827
2915
  const updated = existing.replace(regex, section);
2828
- await writeFile3(filePath, updated, "utf-8");
2916
+ await writeFile4(filePath, updated, "utf-8");
2829
2917
  return { status: "updated", path: filePath };
2830
2918
  }
2831
2919
  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");
2920
+ await mkdir4(dirname5(filePath), { recursive: true });
2921
+ await writeFile4(filePath, existing + separator + section + "\n", "utf-8");
2834
2922
  return { status: "installed", path: filePath };
2835
2923
  }
2836
2924
  async function setupAgent(agentName, auth, scope) {
@@ -2845,7 +2933,7 @@ async function setupAgent(agentName, auth, scope) {
2845
2933
  "context7",
2846
2934
  agent.mcp.buildEntry(auth)
2847
2935
  );
2848
- mcpStatus = alreadyExists ? "already configured" : `configured with ${AUTH_MODE_LABELS[auth.mode]}`;
2936
+ mcpStatus = alreadyExists ? `reconfigured with ${AUTH_MODE_LABELS[auth.mode]}` : `configured with ${AUTH_MODE_LABELS[auth.mode]}`;
2849
2937
  } else {
2850
2938
  const existing = await readJsonConfig(mcpPath);
2851
2939
  const { config, alreadyExists } = mergeServerEntry(
@@ -2854,14 +2942,8 @@ async function setupAgent(agentName, auth, scope) {
2854
2942
  "context7",
2855
2943
  agent.mcp.buildEntry(auth)
2856
2944
  );
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
- }
2945
+ mcpStatus = alreadyExists ? `reconfigured with ${AUTH_MODE_LABELS[auth.mode]}` : `configured with ${AUTH_MODE_LABELS[auth.mode]}`;
2946
+ await writeJsonConfig(mcpPath, config);
2865
2947
  }
2866
2948
  } catch (err) {
2867
2949
  mcpStatus = `failed: ${err instanceof Error ? err.message : String(err)}`;
@@ -2916,7 +2998,7 @@ async function setupMcp(agents2, options, scope) {
2916
2998
  log.blank();
2917
2999
  for (const r of results) {
2918
3000
  log.plain(` ${pc8.bold(r.agent)}`);
2919
- const mcpIcon = r.mcpStatus.startsWith("configured") ? pc8.green("+") : pc8.dim("~");
3001
+ const mcpIcon = r.mcpStatus.startsWith("configured") || r.mcpStatus.startsWith("reconfigured") ? pc8.green("+") : pc8.dim("~");
2920
3002
  log.plain(` ${mcpIcon} MCP server ${r.mcpStatus}`);
2921
3003
  log.plain(` ${pc8.dim(r.mcpPath)}`);
2922
3004
  const ruleIcon = r.ruleStatus === "installed" ? pc8.green("+") : pc8.dim("~");
@@ -2956,7 +3038,7 @@ async function setupCliAgent(agentName, scope, downloadData) {
2956
3038
  async function setupCli(options) {
2957
3039
  await resolveCliAuth(options.apiKey);
2958
3040
  const scope = options.project ? "project" : "global";
2959
- const agents2 = await resolveAgents(options, scope, "cli");
3041
+ const agents2 = await resolveAgents(options, scope);
2960
3042
  if (agents2.length === 0) return;
2961
3043
  log.blank();
2962
3044
  const spinner = ora4("Downloading find-docs skill...").start();
@@ -2994,7 +3076,7 @@ async function setupCommand(options) {
2994
3076
  const mode = await resolveMode(options);
2995
3077
  if (mode === "mcp") {
2996
3078
  const scope = options.project ? "project" : "global";
2997
- const agents2 = await resolveAgents(options, scope, mode);
3079
+ const agents2 = await resolveAgents(options, scope);
2998
3080
  if (agents2.length === 0) return;
2999
3081
  await setupMcp(agents2, options, scope);
3000
3082
  } else {