ctx7 0.3.1 → 0.3.2

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # ctx7
2
2
 
3
- CLI for the [Context7 Skills Registry](https://context7.com) - install and manage AI coding skills across different AI coding assistants.
3
+ CLI for [Context7](https://context7.com) - query up-to-date library documentation and manage AI coding skills.
4
4
 
5
5
  Skills are reusable prompt instructions that enhance your AI coding assistant with specialized capabilities like working with specific frameworks, libraries, or coding patterns.
6
6
 
@@ -26,6 +26,18 @@ ctx7 setup --claude
26
26
  ctx7 setup --opencode
27
27
  ```
28
28
 
29
+ ### Library Documentation
30
+
31
+ ```bash
32
+ # Find a library
33
+ ctx7 library react
34
+ ctx7 library nextjs "app router"
35
+
36
+ # Get documentation
37
+ ctx7 docs /facebook/react "useEffect cleanup"
38
+ ctx7 docs /vercel/next.js "middleware"
39
+ ```
40
+
29
41
  ### Skills
30
42
 
31
43
  ```bash
@@ -44,6 +56,32 @@ ctx7 skills list --claude
44
56
 
45
57
  ## Usage
46
58
 
59
+ ### Find a library
60
+
61
+ Resolve a library name to a Context7 library ID.
62
+
63
+ ```bash
64
+ ctx7 library react
65
+ ctx7 library nextjs "app router setup"
66
+ ctx7 library prisma "database relations"
67
+
68
+ # Output as JSON
69
+ ctx7 library react --json
70
+ ```
71
+
72
+ ### Query documentation
73
+
74
+ Fetch documentation for a specific library using its Context7 ID.
75
+
76
+ ```bash
77
+ ctx7 docs /facebook/react "useEffect cleanup"
78
+ ctx7 docs /vercel/next.js "middleware authentication"
79
+ ctx7 docs /prisma/prisma "one-to-many relations"
80
+
81
+ # Output as JSON
82
+ ctx7 docs /facebook/react "hooks" --json
83
+ ```
84
+
47
85
  ### Setup
48
86
 
49
87
  Configure Context7 MCP and a rule for your AI coding agents. Authenticates via OAuth, generates an API key, and writes the config.
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
- import pc9 from "picocolors";
5
+ import pc10 from "picocolors";
6
6
  import figlet from "figlet";
7
7
 
8
8
  // src/commands/skill.ts
@@ -289,6 +289,65 @@ async function handleGenerateResponse(response, libraryName, onEvent) {
289
289
  }
290
290
  return { content, libraryName: finalLibraryName, error };
291
291
  }
292
+ function getAuthHeaders(accessToken) {
293
+ const headers = {};
294
+ const apiKey = process.env.CONTEXT7_API_KEY;
295
+ if (apiKey) {
296
+ headers["Authorization"] = `Bearer ${apiKey}`;
297
+ } else if (accessToken) {
298
+ headers["Authorization"] = `Bearer ${accessToken}`;
299
+ }
300
+ return headers;
301
+ }
302
+ async function resolveLibrary(libraryName, query, accessToken) {
303
+ const params = new URLSearchParams({ libraryName });
304
+ if (query) {
305
+ params.set("query", query);
306
+ }
307
+ const response = await fetch(`${baseUrl}/api/v2/libs/search?${params}`, {
308
+ headers: getAuthHeaders(accessToken)
309
+ });
310
+ if (!response.ok) {
311
+ const errorData = await response.json().catch(() => ({}));
312
+ return {
313
+ results: [],
314
+ error: errorData.error || `HTTP error ${response.status}`,
315
+ message: errorData.message
316
+ };
317
+ }
318
+ return await response.json();
319
+ }
320
+ async function getLibraryContext(libraryId, query, options, accessToken) {
321
+ const params = new URLSearchParams({ libraryId, query });
322
+ if (options?.type) {
323
+ params.set("type", options.type);
324
+ }
325
+ const response = await fetch(`${baseUrl}/api/v2/context?${params}`, {
326
+ headers: getAuthHeaders(accessToken)
327
+ });
328
+ if (!response.ok) {
329
+ const errorData = await response.json().catch(() => ({}));
330
+ if (response.status === 301 && errorData.redirectUrl) {
331
+ return {
332
+ codeSnippets: [],
333
+ infoSnippets: [],
334
+ error: errorData.error || "library_redirected",
335
+ message: errorData.message,
336
+ redirectUrl: errorData.redirectUrl
337
+ };
338
+ }
339
+ return {
340
+ codeSnippets: [],
341
+ infoSnippets: [],
342
+ error: errorData.error || `HTTP error ${response.status}`,
343
+ message: errorData.message
344
+ };
345
+ }
346
+ if (options?.type === "txt") {
347
+ return await response.text();
348
+ }
349
+ return await response.json();
350
+ }
292
351
 
293
352
  // src/utils/logger.ts
294
353
  import pc from "picocolors";
@@ -2687,6 +2746,169 @@ async function setupCommand(options) {
2687
2746
  trackEvent("setup", { agents: agents2, scope, authMode: auth.mode });
2688
2747
  }
2689
2748
 
2749
+ // src/commands/docs.ts
2750
+ import pc9 from "picocolors";
2751
+ import ora5 from "ora";
2752
+ var isTTY = process.stdout.isTTY;
2753
+ function getAccessToken() {
2754
+ const tokens = loadTokens();
2755
+ if (!tokens || isTokenExpired(tokens)) return void 0;
2756
+ return tokens.access_token;
2757
+ }
2758
+ function formatLibraryResult(lib, index) {
2759
+ const lines = [];
2760
+ lines.push(`${pc9.dim(`${index + 1}.`)} ${pc9.bold(lib.title)} ${pc9.cyan(lib.id)}`);
2761
+ if (lib.description) {
2762
+ lines.push(` ${pc9.dim(lib.description)}`);
2763
+ }
2764
+ const meta = [];
2765
+ if (lib.totalSnippets) {
2766
+ meta.push(`${lib.totalSnippets} snippets`);
2767
+ }
2768
+ if (lib.stars && lib.stars > 0) {
2769
+ meta.push(`${lib.stars.toLocaleString()} stars`);
2770
+ }
2771
+ if (lib.trustScore !== void 0) {
2772
+ meta.push(`trust: ${lib.trustScore}/10`);
2773
+ }
2774
+ if (lib.benchmarkScore !== void 0 && lib.benchmarkScore > 0) {
2775
+ meta.push(`benchmark: ${lib.benchmarkScore}`);
2776
+ }
2777
+ if (meta.length > 0) {
2778
+ lines.push(` ${pc9.dim(meta.join(" \xB7 "))}`);
2779
+ }
2780
+ if (lib.versions && lib.versions.length > 0) {
2781
+ lines.push(` ${pc9.dim(`versions: ${lib.versions.join(", ")}`)}`);
2782
+ }
2783
+ return lines.join("\n");
2784
+ }
2785
+ async function resolveCommand(library, query, options) {
2786
+ trackEvent("command", { name: "library" });
2787
+ const spinner = isTTY ? ora5(`Searching for "${library}"...`).start() : null;
2788
+ const accessToken = getAccessToken();
2789
+ let data;
2790
+ try {
2791
+ data = await resolveLibrary(library, query, accessToken);
2792
+ } catch (err) {
2793
+ spinner?.fail(`Error: ${err instanceof Error ? err.message : String(err)}`);
2794
+ if (!spinner) log.error(err instanceof Error ? err.message : String(err));
2795
+ process.exitCode = 1;
2796
+ return;
2797
+ }
2798
+ if (data.error) {
2799
+ spinner?.fail(data.message || data.error);
2800
+ if (!spinner) log.error(data.message || data.error);
2801
+ process.exitCode = 1;
2802
+ return;
2803
+ }
2804
+ if (!data.results || data.results.length === 0) {
2805
+ spinner?.warn(`No libraries found matching "${library}"`);
2806
+ if (!spinner) log.warn(`No libraries found matching "${library}"`);
2807
+ return;
2808
+ }
2809
+ const results = data.results;
2810
+ spinner?.stop();
2811
+ if (options.json) {
2812
+ console.log(JSON.stringify(results, null, 2));
2813
+ return;
2814
+ }
2815
+ log.blank();
2816
+ for (let i = 0; i < results.length; i++) {
2817
+ log.plain(formatLibraryResult(results[i], i));
2818
+ log.blank();
2819
+ }
2820
+ if (isTTY && results.length > 0) {
2821
+ const best = results[0];
2822
+ log.plain(
2823
+ `${pc9.bold("Quick command:")}
2824
+ ${pc9.cyan(`ctx7 docs "${best.id}" "<your question>"`)}`
2825
+ );
2826
+ log.blank();
2827
+ }
2828
+ }
2829
+ async function queryCommand(libraryId, query, options) {
2830
+ trackEvent("command", { name: "docs" });
2831
+ if (!libraryId.startsWith("/")) {
2832
+ log.error(`Invalid library ID: ${libraryId}`);
2833
+ log.info(`Library IDs start with "/" (e.g., /facebook/react)`);
2834
+ log.info(`Run "ctx7 library <name>" to find the correct ID`);
2835
+ process.exitCode = 1;
2836
+ return;
2837
+ }
2838
+ const spinner = isTTY ? ora5(`Fetching docs for "${libraryId}"...`).start() : null;
2839
+ const accessToken = getAccessToken();
2840
+ const outputType = options.json ? "json" : "txt";
2841
+ let result;
2842
+ try {
2843
+ result = await getLibraryContext(libraryId, query, { type: outputType }, accessToken);
2844
+ } catch (err) {
2845
+ spinner?.fail(`Error: ${err instanceof Error ? err.message : String(err)}`);
2846
+ if (!spinner) log.error(err instanceof Error ? err.message : String(err));
2847
+ process.exitCode = 1;
2848
+ return;
2849
+ }
2850
+ if (typeof result === "string") {
2851
+ spinner?.stop();
2852
+ console.log(result);
2853
+ return;
2854
+ }
2855
+ const ctx = result;
2856
+ if (ctx.error) {
2857
+ if (ctx.redirectUrl) {
2858
+ spinner?.warn("Library has been redirected");
2859
+ if (!spinner) log.warn("Library has been redirected");
2860
+ log.info(`New ID: ${pc9.cyan(ctx.redirectUrl)}`);
2861
+ log.info(`Run: ${pc9.cyan(`ctx7 docs "${ctx.redirectUrl}" "${query}"`)}`);
2862
+ process.exitCode = 1;
2863
+ return;
2864
+ }
2865
+ spinner?.fail(ctx.message || ctx.error);
2866
+ if (!spinner) log.error(ctx.message || ctx.error);
2867
+ process.exitCode = 1;
2868
+ return;
2869
+ }
2870
+ const total = (ctx.codeSnippets?.length || 0) + (ctx.infoSnippets?.length || 0);
2871
+ if (total === 0) {
2872
+ spinner?.warn(`No documentation found for: "${query}"`);
2873
+ if (!spinner) log.warn(`No documentation found for: "${query}"`);
2874
+ return;
2875
+ }
2876
+ spinner?.stop();
2877
+ if (options.json) {
2878
+ console.log(JSON.stringify(ctx, null, 2));
2879
+ return;
2880
+ }
2881
+ log.blank();
2882
+ if (ctx.codeSnippets) {
2883
+ for (const snippet of ctx.codeSnippets) {
2884
+ log.plain(pc9.bold(snippet.codeTitle));
2885
+ if (snippet.codeDescription) log.dim(snippet.codeDescription);
2886
+ log.blank();
2887
+ for (const code of snippet.codeList) {
2888
+ log.plain("```" + code.language);
2889
+ log.plain(code.code);
2890
+ log.plain("```");
2891
+ log.blank();
2892
+ }
2893
+ }
2894
+ }
2895
+ if (ctx.infoSnippets) {
2896
+ for (const snippet of ctx.infoSnippets) {
2897
+ if (snippet.breadcrumb) log.plain(pc9.bold(snippet.breadcrumb));
2898
+ log.plain(snippet.content);
2899
+ log.blank();
2900
+ }
2901
+ }
2902
+ }
2903
+ function registerDocsCommands(program2) {
2904
+ program2.command("library").argument("<name>", "Library name to search for").argument("[query]", "Question or task for relevance ranking").option("--json", "Output as JSON").description("Resolve a library name to a Context7 library ID").action(async (name, query, options) => {
2905
+ await resolveCommand(name, query, options);
2906
+ });
2907
+ program2.command("docs").argument("<libraryId>", "Context7 library ID (e.g., /facebook/react)").argument("<query>", "Question or task to get docs for").option("--json", "Output as JSON").description("Query documentation for a library").action(async (libraryId, query, options) => {
2908
+ await queryCommand(libraryId, query, options);
2909
+ });
2910
+ }
2911
+
2690
2912
  // src/constants.ts
2691
2913
  import { readFileSync as readFileSync2 } from "fs";
2692
2914
  import { fileURLToPath } from "url";
@@ -2698,8 +2920,8 @@ var NAME = pkg.name;
2698
2920
 
2699
2921
  // src/index.ts
2700
2922
  var brand = {
2701
- primary: pc9.green,
2702
- dim: pc9.dim
2923
+ primary: pc10.green,
2924
+ dim: pc10.dim
2703
2925
  };
2704
2926
  var program = new Command();
2705
2927
  program.name("ctx7").description("Context7 CLI - Manage AI coding skills and documentation context").version(VERSION).option("--base-url <url>").hook("preAction", (thisCommand) => {
@@ -2728,6 +2950,10 @@ Examples:
2728
2950
  ${brand.primary("npx ctx7 skills list --claude")}
2729
2951
  ${brand.primary("npx ctx7 skills remove pdf")}
2730
2952
 
2953
+ ${brand.dim("# Query library documentation")}
2954
+ ${brand.primary('npx ctx7 library react "how to use hooks"')}
2955
+ ${brand.primary('npx ctx7 docs /facebook/react "useEffect examples"')}
2956
+
2731
2957
  Visit ${brand.primary("https://context7.com")} to browse skills
2732
2958
  `
2733
2959
  );
@@ -2735,6 +2961,7 @@ registerSkillCommands(program);
2735
2961
  registerSkillAliases(program);
2736
2962
  registerAuthCommands(program);
2737
2963
  registerSetupCommand(program);
2964
+ registerDocsCommands(program);
2738
2965
  program.action(() => {
2739
2966
  console.log("");
2740
2967
  const banner = figlet.textSync("Context7", { font: "ANSI Shadow" });