ctx7 0.5.1 → 0.5.3

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
@@ -152,15 +152,24 @@ function getGitHubHeaders() {
152
152
  ...ghToken && { Authorization: `token ${ghToken}` }
153
153
  };
154
154
  }
155
+ async function extractGitHubError(response) {
156
+ let detail = `HTTP ${response.status}`;
157
+ try {
158
+ const body = await response.json();
159
+ if (body.message) detail += `: ${body.message}`;
160
+ } catch {
161
+ }
162
+ return detail;
163
+ }
155
164
  async function fetchRepoTree(owner, repo, branch, headers) {
156
165
  const treeUrl = `${GITHUB_API}/repos/${owner}/${repo}/git/trees/${branch}?recursive=1`;
157
166
  const response = await fetch(treeUrl, { headers });
158
- if (!response.ok) return null;
167
+ if (!response.ok) return { error: await extractGitHubError(response) };
159
168
  return await response.json();
160
169
  }
161
170
  async function fetchDefaultBranch(owner, repo, headers) {
162
171
  const response = await fetch(`${GITHUB_API}/repos/${owner}/${repo}`, { headers });
163
- if (!response.ok) return { status: response.status };
172
+ if (!response.ok) return { error: await extractGitHubError(response), status: response.status };
164
173
  const data = await response.json();
165
174
  return { branch: data.default_branch };
166
175
  }
@@ -171,9 +180,12 @@ async function listSkillsFromGitHub(project) {
171
180
  const [owner, repo] = parts;
172
181
  const headers = getGitHubHeaders();
173
182
  const branchResult = await fetchDefaultBranch(owner, repo, headers);
174
- if ("status" in branchResult) return { status: "repo_not_found" };
183
+ if ("error" in branchResult) {
184
+ if (branchResult.status === 404) return { status: "repo_not_found" };
185
+ return { status: "error", error: branchResult.error };
186
+ }
175
187
  const treeData = await fetchRepoTree(owner, repo, branchResult.branch, headers);
176
- if (!treeData) return { status: "error", error: "Could not fetch repository tree" };
188
+ if ("error" in treeData) return { status: "error", error: treeData.error };
177
189
  const skillMdFiles = treeData.tree.filter(
178
190
  (item) => item.type === "blob" && item.path.toLowerCase().endsWith("skill.md")
179
191
  );
@@ -214,8 +226,9 @@ async function downloadSkillFromGitHub(skill) {
214
226
  const { owner, repo, branch, path: skillPath } = parsed;
215
227
  const ghHeaders = getGitHubHeaders();
216
228
  const treeData = await fetchRepoTree(owner, repo, branch, ghHeaders);
217
- if (!treeData) {
218
- return { files: [], error: `GitHub API error` };
229
+ if ("error" in treeData) {
230
+ const hint = !ghHeaders["Authorization"] && /403|429|rate/.test(treeData.error) ? " \u2014 run `gh auth login` or set the GITHUB_TOKEN env var to increase rate limits" : "";
231
+ return { files: [], error: `GitHub API error: ${treeData.error}${hint}` };
219
232
  }
220
233
  const skillFiles = treeData.tree.filter(
221
234
  (item) => item.type === "blob" && item.path.startsWith(skillPath + "/")
@@ -925,48 +938,153 @@ function trackEvent(event, data) {
925
938
  // src/commands/generate.ts
926
939
  import pc6 from "picocolors";
927
940
  import ora2 from "ora";
928
- import { mkdir as mkdir2, writeFile as writeFile2, readFile, unlink } from "fs/promises";
941
+ import { mkdir as mkdir3, writeFile as writeFile2, readFile, unlink } from "fs/promises";
929
942
  import { join as join4 } from "path";
930
943
  import { homedir as homedir3 } from "os";
931
944
  import { spawn } from "child_process";
932
945
  import { input, select as select2 } from "@inquirer/prompts";
933
946
 
934
947
  // src/utils/auth.ts
948
+ import * as fs2 from "fs";
949
+ import * as os2 from "os";
950
+
951
+ // src/utils/storage-paths.ts
935
952
  import * as fs from "fs";
936
- import * as path from "path";
953
+ import { access as access2, chmod, mkdir as mkdir2, rename } from "fs/promises";
937
954
  import * as os from "os";
938
- var CONFIG_DIR = path.join(os.homedir(), ".context7");
939
- var CREDENTIALS_FILE = path.join(CONFIG_DIR, "credentials.json");
955
+ import * as path from "path";
956
+ var APP_DIR = "context7";
957
+ var LEGACY_DIR = ".context7";
958
+ var CREDENTIALS_FILE_NAME = "credentials.json";
959
+ var UPDATE_STATE_FILE_NAME = "cli-state.json";
960
+ var PREVIEWS_DIR_NAME = "previews";
961
+ function xdgBase(envVar, ...defaultSegments) {
962
+ const value = process.env[envVar];
963
+ const base = value && path.isAbsolute(value) ? value : path.join(os.homedir(), ...defaultSegments);
964
+ return path.join(base, APP_DIR);
965
+ }
966
+ function getConfigDir() {
967
+ return xdgBase("XDG_CONFIG_HOME", ".config");
968
+ }
969
+ function getStateDir() {
970
+ return xdgBase("XDG_STATE_HOME", ".local", "state");
971
+ }
972
+ function getCacheDir() {
973
+ return xdgBase("XDG_CACHE_HOME", ".cache");
974
+ }
975
+ function getCredentialsFilePath() {
976
+ return path.join(getConfigDir(), CREDENTIALS_FILE_NAME);
977
+ }
978
+ function getUpdateStateFilePath() {
979
+ return path.join(getStateDir(), UPDATE_STATE_FILE_NAME);
980
+ }
981
+ function getPreviewsDir() {
982
+ return path.join(getCacheDir(), PREVIEWS_DIR_NAME);
983
+ }
984
+ function getLegacyFilePath(fileName) {
985
+ return path.join(os.homedir(), LEGACY_DIR, fileName);
986
+ }
987
+ function migrateLegacyFileSync(fileName, targetPath, mode) {
988
+ const legacyPath = getLegacyFilePath(fileName);
989
+ if (legacyPath === targetPath || fs.existsSync(targetPath) || !fs.existsSync(legacyPath)) {
990
+ return;
991
+ }
992
+ try {
993
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true, mode: 448 });
994
+ fs.renameSync(legacyPath, targetPath);
995
+ if (mode !== void 0) {
996
+ fs.chmodSync(targetPath, mode);
997
+ }
998
+ } catch {
999
+ }
1000
+ }
1001
+ async function migrateLegacyFile(fileName, targetPath, mode) {
1002
+ const legacyPath = getLegacyFilePath(fileName);
1003
+ if (legacyPath === targetPath || await exists(targetPath) || !await exists(legacyPath)) {
1004
+ return;
1005
+ }
1006
+ try {
1007
+ await mkdir2(path.dirname(targetPath), { recursive: true, mode: 448 });
1008
+ await rename(legacyPath, targetPath);
1009
+ if (mode !== void 0) {
1010
+ await chmod(targetPath, mode);
1011
+ }
1012
+ } catch {
1013
+ }
1014
+ }
1015
+ function resolveReadPathSync(fileName, targetPath, mode) {
1016
+ migrateLegacyFileSync(fileName, targetPath, mode);
1017
+ if (fs.existsSync(targetPath)) {
1018
+ return targetPath;
1019
+ }
1020
+ const legacyPath = getLegacyFilePath(fileName);
1021
+ return fs.existsSync(legacyPath) ? legacyPath : targetPath;
1022
+ }
1023
+ async function resolveReadPath(fileName, targetPath, mode) {
1024
+ await migrateLegacyFile(fileName, targetPath, mode);
1025
+ if (await exists(targetPath)) {
1026
+ return targetPath;
1027
+ }
1028
+ const legacyPath = getLegacyFilePath(fileName);
1029
+ return await exists(legacyPath) ? legacyPath : targetPath;
1030
+ }
1031
+ async function exists(filePath) {
1032
+ try {
1033
+ await access2(filePath);
1034
+ return true;
1035
+ } catch {
1036
+ return false;
1037
+ }
1038
+ }
1039
+
1040
+ // src/utils/auth.ts
940
1041
  function ensureConfigDir() {
941
- if (!fs.existsSync(CONFIG_DIR)) {
942
- fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
1042
+ const configDir = getConfigDir();
1043
+ if (!fs2.existsSync(configDir)) {
1044
+ fs2.mkdirSync(configDir, { recursive: true, mode: 448 });
943
1045
  }
944
1046
  }
1047
+ var CREDENTIALS_MODE = 384;
945
1048
  function saveTokens(tokens) {
1049
+ const credentialsFile = getCredentialsFilePath();
1050
+ migrateLegacyFileSync(CREDENTIALS_FILE_NAME, credentialsFile, CREDENTIALS_MODE);
946
1051
  ensureConfigDir();
947
1052
  const data = {
948
1053
  ...tokens,
949
1054
  expires_at: tokens.expires_at ?? (tokens.expires_in ? Date.now() + tokens.expires_in * 1e3 : void 0)
950
1055
  };
951
- fs.writeFileSync(CREDENTIALS_FILE, JSON.stringify(data, null, 2), { mode: 384 });
1056
+ fs2.writeFileSync(credentialsFile, JSON.stringify(data, null, 2), { mode: CREDENTIALS_MODE });
1057
+ fs2.chmodSync(credentialsFile, CREDENTIALS_MODE);
952
1058
  }
953
1059
  function loadTokens() {
954
- if (!fs.existsSync(CREDENTIALS_FILE)) {
1060
+ const credentialsFile = resolveReadPathSync(
1061
+ CREDENTIALS_FILE_NAME,
1062
+ getCredentialsFilePath(),
1063
+ CREDENTIALS_MODE
1064
+ );
1065
+ if (!fs2.existsSync(credentialsFile)) {
955
1066
  return null;
956
1067
  }
957
1068
  try {
958
- const data = JSON.parse(fs.readFileSync(CREDENTIALS_FILE, "utf-8"));
1069
+ const data = JSON.parse(fs2.readFileSync(credentialsFile, "utf-8"));
959
1070
  return data;
960
1071
  } catch {
961
1072
  return null;
962
1073
  }
963
1074
  }
964
1075
  function clearTokens() {
965
- if (fs.existsSync(CREDENTIALS_FILE)) {
966
- fs.unlinkSync(CREDENTIALS_FILE);
967
- return true;
1076
+ const credentialsFile = getCredentialsFilePath();
1077
+ let removed = false;
1078
+ if (fs2.existsSync(credentialsFile)) {
1079
+ fs2.unlinkSync(credentialsFile);
1080
+ removed = true;
968
1081
  }
969
- return false;
1082
+ const legacyCredentialsFile = getLegacyFilePath(CREDENTIALS_FILE_NAME);
1083
+ if (fs2.existsSync(legacyCredentialsFile)) {
1084
+ fs2.unlinkSync(legacyCredentialsFile);
1085
+ removed = true;
1086
+ }
1087
+ return removed;
970
1088
  }
971
1089
  function isTokenExpired(tokens) {
972
1090
  if (!tokens.expires_at) {
@@ -1012,7 +1130,7 @@ var DEFAULT_DEVICE_POLL_INTERVAL_SECONDS = 5;
1012
1130
  async function startDeviceAuthorization(baseUrl3, clientId) {
1013
1131
  const params = new URLSearchParams({ client_id: clientId });
1014
1132
  try {
1015
- const hostname2 = os.hostname();
1133
+ const hostname2 = os2.hostname();
1016
1134
  if (hostname2) params.set("hostname", hostname2);
1017
1135
  } catch {
1018
1136
  }
@@ -1686,8 +1804,8 @@ async function generateCommand(options) {
1686
1804
  log.blank();
1687
1805
  };
1688
1806
  const openInEditor = async () => {
1689
- const previewDir = join4(homedir3(), ".context7", "previews");
1690
- await mkdir2(previewDir, { recursive: true });
1807
+ const previewDir = getPreviewsDir();
1808
+ await mkdir3(previewDir, { recursive: true });
1691
1809
  previewFile = join4(previewDir, `${skillName}.md`);
1692
1810
  if (!previewFileWritten) {
1693
1811
  await writeFile2(previewFile, generatedContent, "utf-8");
@@ -1767,7 +1885,7 @@ async function generateCommand(options) {
1767
1885
  const skillDir = join4(finalDir, skillName);
1768
1886
  const skillPath = join4(skillDir, "SKILL.md");
1769
1887
  try {
1770
- await mkdir2(skillDir, { recursive: true });
1888
+ await mkdir3(skillDir, { recursive: true });
1771
1889
  await writeFile2(skillPath, generatedContent, "utf-8");
1772
1890
  } catch (err) {
1773
1891
  const error = err;
@@ -2619,12 +2737,12 @@ ${headerLine}`,
2619
2737
  import pc8 from "picocolors";
2620
2738
  import ora4 from "ora";
2621
2739
  import { select as select3 } from "@inquirer/prompts";
2622
- import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
2623
- import { dirname as dirname6, join as join8 } from "path";
2740
+ import { mkdir as mkdir5, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
2741
+ import { dirname as dirname7, join as join8 } from "path";
2624
2742
  import { randomBytes } from "crypto";
2625
2743
 
2626
2744
  // src/setup/agents.ts
2627
- import { access as access2 } from "fs/promises";
2745
+ import { access as access3 } from "fs/promises";
2628
2746
  import { join as join7 } from "path";
2629
2747
  import { homedir as homedir5 } from "os";
2630
2748
  var SETUP_AGENT_NAMES = {
@@ -2828,7 +2946,7 @@ function getAgent(name) {
2828
2946
  var ALL_AGENT_NAMES = Object.keys(agents);
2829
2947
  async function pathExists(p) {
2830
2948
  try {
2831
- await access2(p);
2949
+ await access3(p);
2832
2950
  return true;
2833
2951
  } catch {
2834
2952
  return false;
@@ -2937,8 +3055,8 @@ function customizeSkillFilesForAgent(agent, skillName, files) {
2937
3055
  }
2938
3056
 
2939
3057
  // src/setup/mcp-writer.ts
2940
- import { access as access3, readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
2941
- import { dirname as dirname5 } from "path";
3058
+ import { access as access4, readFile as readFile3, writeFile as writeFile3, mkdir as mkdir4 } from "fs/promises";
3059
+ import { dirname as dirname6 } from "path";
2942
3060
  function stripJsonComments(text) {
2943
3061
  let result = "";
2944
3062
  let i = 0;
@@ -3009,7 +3127,7 @@ function removeServerEntry(existing, configKey, serverName) {
3009
3127
  async function resolveMcpPath(candidates) {
3010
3128
  for (const candidate of candidates) {
3011
3129
  try {
3012
- await access3(candidate);
3130
+ await access4(candidate);
3013
3131
  return candidate;
3014
3132
  } catch {
3015
3133
  }
@@ -3017,7 +3135,7 @@ async function resolveMcpPath(candidates) {
3017
3135
  return candidates[0];
3018
3136
  }
3019
3137
  async function writeJsonConfig(filePath, config) {
3020
- await mkdir3(dirname5(filePath), { recursive: true });
3138
+ await mkdir4(dirname6(filePath), { recursive: true });
3021
3139
  await writeFile3(filePath, JSON.stringify(config, null, 2) + "\n", "utf-8");
3022
3140
  }
3023
3141
  async function readTomlServerExists(filePath, serverName) {
@@ -3137,11 +3255,11 @@ async function appendTomlServer(filePath, serverName, entry) {
3137
3255
  const before = rawBefore.length > 0 ? rawBefore + "\n\n" : "";
3138
3256
  const after = rawAfter.length > 0 ? "\n" + rawAfter : "";
3139
3257
  const content = before + block + after;
3140
- await mkdir3(dirname5(filePath), { recursive: true });
3258
+ await mkdir4(dirname6(filePath), { recursive: true });
3141
3259
  await writeFile3(filePath, content, "utf-8");
3142
3260
  } else {
3143
3261
  const separator = existing.length > 0 && !existing.endsWith("\n") ? "\n\n" : existing.length > 0 ? "\n" : "";
3144
- await mkdir3(dirname5(filePath), { recursive: true });
3262
+ await mkdir4(dirname6(filePath), { recursive: true });
3145
3263
  await writeFile3(filePath, existing + separator + block, "utf-8");
3146
3264
  }
3147
3265
  return { alreadyExists };
@@ -3174,7 +3292,7 @@ async function removeTomlServer(filePath, serverName) {
3174
3292
  const rawBefore = existing.slice(0, startIdx).replace(/\n+$/, "");
3175
3293
  const rawAfter = existing.slice(startIdx + sectionHeader.length + endOffset).replace(/^\n+/, "");
3176
3294
  const content = [rawBefore, rawAfter].filter(Boolean).join("\n\n");
3177
- await mkdir3(dirname5(filePath), { recursive: true });
3295
+ await mkdir4(dirname6(filePath), { recursive: true });
3178
3296
  await writeFile3(filePath, content.length > 0 ? `${content}
3179
3297
  ` : "", "utf-8");
3180
3298
  return { removed: true };
@@ -3320,7 +3438,7 @@ async function installRule(agentName, mode, scope) {
3320
3438
  if (rule.kind === "file") {
3321
3439
  const ruleDir = scope === "global" ? rule.dir("global") : join8(process.cwd(), rule.dir("project"));
3322
3440
  const rulePath = join8(ruleDir, rule.filename);
3323
- await mkdir4(dirname6(rulePath), { recursive: true });
3441
+ await mkdir5(dirname7(rulePath), { recursive: true });
3324
3442
  await writeFile4(rulePath, content, "utf-8");
3325
3443
  return { status: "installed", path: rulePath };
3326
3444
  }
@@ -3340,7 +3458,7 @@ ${content}${rule.sectionMarker}`;
3340
3458
  return { status: "updated", path: filePath };
3341
3459
  }
3342
3460
  const separator = existing.length > 0 && !existing.endsWith("\n") ? "\n\n" : existing.length > 0 ? "\n" : "";
3343
- await mkdir4(dirname6(filePath), { recursive: true });
3461
+ await mkdir5(dirname7(filePath), { recursive: true });
3344
3462
  await writeFile4(filePath, existing + separator + section + "\n", "utf-8");
3345
3463
  return { status: "installed", path: filePath };
3346
3464
  }
@@ -3411,6 +3529,20 @@ async function setupAgent(agentName, auth, transport, scope) {
3411
3529
  skillPath
3412
3530
  };
3413
3531
  }
3532
+ function logSkillStatus(skillStatus, skillPath) {
3533
+ const skillFailed = skillStatus.startsWith("failed:");
3534
+ const skillIcon = skillStatus === "installed" ? pc8.green("+") : skillFailed ? pc8.red("\u2716") : pc8.dim("~");
3535
+ log.plain(` ${skillIcon} Skill ${skillFailed ? "failed" : skillStatus}`);
3536
+ log.plain(` ${pc8.dim(skillPath)}`);
3537
+ if (skillFailed) {
3538
+ log.plain(` ${pc8.red(skillStatus.slice("failed: ".length))}`);
3539
+ if (skillStatus.includes("EACCES")) {
3540
+ log.plain(
3541
+ ` ${pc8.yellow("tip:")} fix permissions with: ${pc8.cyan(`sudo chown -R $(whoami) ${dirname7(dirname7(skillPath))}`)}`
3542
+ );
3543
+ }
3544
+ }
3545
+ }
3414
3546
  async function setupMcp(agents2, options, scope) {
3415
3547
  const transport = resolveTransport(options);
3416
3548
  if (transport === "stdio" && options.oauth) {
@@ -3439,14 +3571,7 @@ async function setupMcp(agents2, options, scope) {
3439
3571
  const ruleIcon = r.ruleStatus === "installed" ? pc8.green("+") : pc8.dim("~");
3440
3572
  log.plain(` ${ruleIcon} Rule ${r.ruleStatus}`);
3441
3573
  log.plain(` ${pc8.dim(r.rulePath)}`);
3442
- const skillIcon = r.skillStatus === "installed" ? pc8.green("+") : pc8.dim("~");
3443
- log.plain(` ${skillIcon} Skill ${r.skillStatus}`);
3444
- log.plain(` ${pc8.dim(r.skillPath)}`);
3445
- if (r.skillStatus.includes("EACCES")) {
3446
- log.plain(
3447
- ` ${pc8.yellow("tip:")} fix permissions with: ${pc8.cyan(`sudo chown -R $(whoami) ${dirname6(dirname6(r.skillPath))}`)}`
3448
- );
3449
- }
3574
+ logSkillStatus(r.skillStatus, r.skillPath);
3450
3575
  }
3451
3576
  log.blank();
3452
3577
  trackEvent("setup", { agents: agents2, scope, authMode: auth.mode });
@@ -3492,22 +3617,16 @@ async function setupCli(options) {
3492
3617
  const installSpinner = ora4("Installing...").start();
3493
3618
  const results = [];
3494
3619
  for (const agentName of agents2) {
3495
- installSpinner.text = `Setting up ${getAgent(agentName).displayName}...`;
3620
+ const agentDef = getAgent(agentName);
3621
+ installSpinner.text = `Setting up ${agentDef.displayName}...`;
3496
3622
  const r = await setupCliAgent(agentName, scope, downloadData);
3497
- results.push({ agent: getAgent(agentName).displayName, ...r });
3623
+ results.push({ agent: agentDef.displayName, ...r });
3498
3624
  }
3499
3625
  installSpinner.succeed("Context7 CLI setup complete");
3500
3626
  log.blank();
3501
3627
  for (const r of results) {
3502
3628
  log.plain(` ${pc8.bold(r.agent)}`);
3503
- const skillIcon = r.skillStatus === "installed" ? pc8.green("+") : pc8.dim("~");
3504
- log.plain(` ${skillIcon} Skill ${r.skillStatus}`);
3505
- log.plain(` ${pc8.dim(r.skillPath)}`);
3506
- if (r.skillStatus.includes("EACCES")) {
3507
- log.plain(
3508
- ` ${pc8.yellow("tip:")} fix permissions with: ${pc8.cyan(`sudo chown -R $(whoami) ${dirname6(dirname6(r.skillPath))}`)}`
3509
- );
3510
- }
3629
+ logSkillStatus(r.skillStatus, r.skillPath);
3511
3630
  const ruleIcon = r.ruleStatus === "installed" || r.ruleStatus === "updated" ? pc8.green("+") : pc8.dim("~");
3512
3631
  log.plain(` ${ruleIcon} Rule ${r.ruleStatus}`);
3513
3632
  log.plain(` ${pc8.dim(r.rulePath)}`);
@@ -3538,7 +3657,7 @@ async function setupCommand(options) {
3538
3657
  import pc9 from "picocolors";
3539
3658
  import ora5 from "ora";
3540
3659
  import { join as join9 } from "path";
3541
- import { access as access4, readFile as readFile5, rm as rm3, writeFile as writeFile5 } from "fs/promises";
3660
+ import { access as access5, readFile as readFile5, rm as rm3, writeFile as writeFile5 } from "fs/promises";
3542
3661
  var CHECKBOX_THEME2 = {
3543
3662
  style: {
3544
3663
  highlight: (text) => pc9.green(text),
@@ -3638,7 +3757,7 @@ function resolveFlagModes(options) {
3638
3757
  }
3639
3758
  async function pathExists2(path2) {
3640
3759
  try {
3641
- await access4(path2);
3760
+ await access5(path2);
3642
3761
  return true;
3643
3762
  } catch {
3644
3763
  return false;
@@ -3885,6 +4004,18 @@ async function removeCommand2(options) {
3885
4004
  // src/commands/docs.ts
3886
4005
  import pc10 from "picocolors";
3887
4006
  import ora6 from "ora";
4007
+
4008
+ // src/utils/library-id.ts
4009
+ function recoverLibraryId(input2) {
4010
+ if (input2.startsWith("//")) return input2.replace(/^\/+/, "/");
4011
+ if (input2.startsWith("/")) return input2;
4012
+ if (!/^[A-Za-z]:[\\/]/.test(input2)) return input2;
4013
+ const normalized = input2.replace(/\\/g, "/");
4014
+ const match = normalized.match(/^[A-Za-z]:\/.*?\/(?:Git|PortableGit|git-bash)\/(.+)$/i);
4015
+ return match ? `/${match[1]}` : input2;
4016
+ }
4017
+
4018
+ // src/commands/docs.ts
3888
4019
  var isTTY = process.stdout.isTTY;
3889
4020
  function getReputationLabel(score) {
3890
4021
  if (score === void 0 || score < 0) return "Unknown";
@@ -3970,10 +4101,16 @@ async function resolveCommand(library, query, options) {
3970
4101
  }
3971
4102
  async function queryCommand(libraryId, query, options) {
3972
4103
  trackEvent("command", { name: "docs" });
4104
+ libraryId = recoverLibraryId(libraryId);
3973
4105
  if (!libraryId.startsWith("/") || !/^\/[^/]+\/[^/]/.test(libraryId)) {
3974
4106
  log.error(`Invalid library ID: "${libraryId}"`);
3975
4107
  log.info(`Expected format: /owner/repo or /owner/repo/version (e.g., /facebook/react)`);
3976
4108
  log.info(`Run "ctx7 library <name>" to find the correct ID`);
4109
+ if (process.platform === "win32") {
4110
+ log.info(
4111
+ `On Git Bash, prefix the ID with an extra slash to avoid path conversion: ctx7 docs "//facebook/react" "<your question>"`
4112
+ );
4113
+ }
3977
4114
  process.exitCode = 1;
3978
4115
  return;
3979
4116
  }
@@ -4057,25 +4194,36 @@ import { spawn as spawn2 } from "child_process";
4057
4194
  import pc11 from "picocolors";
4058
4195
 
4059
4196
  // src/utils/update-check.ts
4060
- import { homedir as homedir6 } from "os";
4061
- import { dirname as dirname7, join as join10 } from "path";
4062
- import { mkdir as mkdir5, readFile as readFile6, writeFile as writeFile6 } from "fs/promises";
4197
+ import { dirname as dirname8 } from "path";
4198
+ import { mkdir as mkdir6, readFile as readFile6, writeFile as writeFile6 } from "fs/promises";
4063
4199
  var DEFAULT_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
4064
- var UPDATE_STATE_FILE = join10(homedir6(), ".context7", "cli-state.json");
4065
4200
  function getStateFilePath(stateFile) {
4066
- return stateFile ?? UPDATE_STATE_FILE;
4201
+ return stateFile ?? getUpdateStateFilePath();
4202
+ }
4203
+ async function readStateFilePath(stateFile) {
4204
+ if (stateFile) {
4205
+ return stateFile;
4206
+ }
4207
+ return resolveReadPath(UPDATE_STATE_FILE_NAME, getUpdateStateFilePath());
4208
+ }
4209
+ async function writeStateFilePath(stateFile) {
4210
+ const path2 = getStateFilePath(stateFile);
4211
+ if (!stateFile) {
4212
+ await migrateLegacyFile(UPDATE_STATE_FILE_NAME, path2);
4213
+ }
4214
+ return path2;
4067
4215
  }
4068
4216
  async function readUpdateState(stateFile) {
4069
4217
  try {
4070
- const raw = await readFile6(getStateFilePath(stateFile), "utf-8");
4218
+ const raw = await readFile6(await readStateFilePath(stateFile), "utf-8");
4071
4219
  return JSON.parse(raw);
4072
4220
  } catch {
4073
4221
  return {};
4074
4222
  }
4075
4223
  }
4076
4224
  async function writeUpdateState(state, stateFile) {
4077
- const path2 = getStateFilePath(stateFile);
4078
- await mkdir5(dirname7(path2), { recursive: true });
4225
+ const path2 = await writeStateFilePath(stateFile);
4226
+ await mkdir6(dirname8(path2), { recursive: true });
4079
4227
  await writeFile6(path2, JSON.stringify(state, null, 2) + "\n", "utf-8");
4080
4228
  }
4081
4229
  function compareVersions(a, b) {
@@ -4379,7 +4527,7 @@ var brand = {
4379
4527
  dim: pc12.dim
4380
4528
  };
4381
4529
  var program = new Command();
4382
- program.name("ctx7").description("Context7 CLI - Fetch documentation context and configure Context7").version(VERSION).option("--base-url <url>").hook("preAction", (thisCommand) => {
4530
+ program.name("ctx7").description("Context7 CLI - Fetch documentation context and configure Context7").version(VERSION, "-v, --version").option("--base-url <url>").hook("preAction", (thisCommand) => {
4383
4531
  const opts = thisCommand.opts();
4384
4532
  if (opts.baseUrl) {
4385
4533
  setBaseUrl(opts.baseUrl);