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 +142 -60
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-WKOIWR6Y.js +0 -120
- package/dist/chunk-WKOIWR6Y.js.map +0 -1
- package/dist/mcp-writer-IYBCUACD.js +0 -20
- package/dist/mcp-writer-IYBCUACD.js.map +0 -1
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
|
|
2437
|
-
import { dirname as
|
|
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
|
|
2747
|
-
const
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
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
|
|
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(
|
|
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
|
|
2813
|
-
await
|
|
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
|
|
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
|
|
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
|
|
2833
|
-
await
|
|
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 ?
|
|
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
|
-
|
|
2858
|
-
|
|
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
|
|
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
|
|
3079
|
+
const agents2 = await resolveAgents(options, scope);
|
|
2998
3080
|
if (agents2.length === 0) return;
|
|
2999
3081
|
await setupMcp(agents2, options, scope);
|
|
3000
3082
|
} else {
|