ctx7
Version:
Context7 CLI - Fetch documentation context and configure Context7
869 lines (856 loc) • 27.9 kB
JavaScript
// src/commands/auth.ts
import pc2 from "picocolors";
import ora from "ora";
import open from "open";
import boxen from "boxen";
// src/utils/auth.ts
import * as fs3 from "fs";
import * as os2 from "os";
import pc from "picocolors";
import { confirm } from "@inquirer/prompts";
// src/constants.ts
import { readFileSync } from "fs";
import { fileURLToPath } from "url";
import { dirname, join } from "path";
var __dirname = dirname(fileURLToPath(import.meta.url));
var pkg = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf-8"));
var VERSION = pkg.version;
var NAME = pkg.name;
var CLI_CLIENT_ID = "2veBSofhicRBguUT";
// src/utils/api.ts
var DEFAULT_CONTEXT7_BASE_URL = "https://context7.com";
var baseUrl = DEFAULT_CONTEXT7_BASE_URL;
function getBaseUrl() {
return baseUrl;
}
function setBaseUrl(url) {
baseUrl = url;
}
function getAuthHeaders(accessToken) {
const headers = {
"X-Context7-Source": "cli",
"X-Context7-Client-IDE": "ctx7-cli",
"X-Context7-Client-Version": VERSION,
"X-Context7-Transport": "cli"
};
const apiKey = process.env.CONTEXT7_API_KEY;
if (apiKey) {
headers["Authorization"] = `Bearer ${apiKey}`;
} else if (accessToken) {
headers["Authorization"] = `Bearer ${accessToken}`;
}
return headers;
}
async function resolveLibrary(libraryName, query, accessToken) {
const params = new URLSearchParams({ libraryName });
if (query) {
params.set("query", query);
}
const response = await fetch(`${baseUrl}/api/v2/libs/search?${params}`, {
headers: getAuthHeaders(accessToken)
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
return {
results: [],
error: errorData.error || `HTTP error ${response.status}`,
message: errorData.message
};
}
return await response.json();
}
var CODE_OPERATIONS = [
"grep",
"definition",
"references",
"outline",
"read",
"list"
];
var CODE_LIMITS = {
context: { min: 0, max: 10 },
headLimit: { min: 1, max: 500 },
offset: { min: 0, max: 1e4 }
};
function hasSourceCode(result) {
return !/^\/(websites|llmstxt)\//.test(result.id) && !result.branch?.startsWith("_");
}
function retryAfterSeconds(bodyValue, header) {
if (typeof bodyValue === "number" && Number.isFinite(bodyValue) && bodyValue >= 0)
return Math.ceil(bodyValue);
if (header) {
const seconds = Number(header);
if (Number.isFinite(seconds) && seconds >= 0) return Math.ceil(seconds);
const date = Date.parse(header);
if (Number.isFinite(date)) return Math.max(1, Math.ceil((date - Date.now()) / 1e3));
}
return 30;
}
async function getLibraryCode(libraryId, pattern, options = {}, accessToken) {
const params = new URLSearchParams({ libraryId });
if (options.version) params.set("version", options.version);
if (pattern) params.set("pattern", pattern);
if (options.operation) params.set("operation", options.operation);
if (options.fixedStrings) params.set("fixed_strings", "true");
if (options.ignoreCase) params.set("ignore_case", "true");
for (const glob of options.glob ?? []) params.append("glob", glob);
if (options.path) params.set("path", options.path);
if (options.context !== void 0) params.set("context", String(options.context));
if (options.outputMode) params.set("output_mode", options.outputMode);
if (options.headLimit) params.set("head_limit", String(options.headLimit));
if (options.offset) params.set("offset", String(options.offset));
if (options.json) params.set("format", "json");
const response = await fetch(`${baseUrl}/api/v2/code?${params}`, {
headers: getAuthHeaders(accessToken)
});
if (response.status === 202) {
const body = await response.json().catch(() => ({}));
return {
kind: "indexing",
message: body.message ?? "The library source is being indexed.",
retryAfter: retryAfterSeconds(body.retryAfter, response.headers.get("Retry-After"))
};
}
if (!response.ok) {
const body = await response.json().catch(() => ({}));
return {
kind: "error",
error: body.error || `HTTP error ${response.status}`,
message: body.message
};
}
return { kind: "result", body: await response.text() };
}
async function getLibraryContext(libraryId, query, options, accessToken) {
const params = new URLSearchParams({ libraryId, query });
if (options?.type) {
params.set("type", options.type);
}
if (options?.language) {
params.set("language", options.language);
}
const headers = getAuthHeaders(accessToken);
const response = await fetch(`${baseUrl}/api/v2/context?${params}`, {
headers
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
if (response.status === 301 && errorData.redirectUrl) {
return {
codeSnippets: [],
infoSnippets: [],
error: errorData.error || "library_redirected",
message: errorData.message,
redirectUrl: errorData.redirectUrl
};
}
return {
codeSnippets: [],
infoSnippets: [],
error: errorData.error || `HTTP error ${response.status}`,
message: errorData.message
};
}
if (options?.type === "txt") {
return await response.text();
}
return await response.json();
}
async function searchDocumentation(query, options, accessToken) {
const params = new URLSearchParams({ query, type: options.type });
for (const library of options.libraries ?? []) {
params.append("library", library);
}
if (options.version) {
params.set("version", options.version);
}
if (options.language) {
params.set("language", options.language);
}
const response = await fetch(`${baseUrl}/api/v3/search?${params}`, {
headers: getAuthHeaders(accessToken)
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.message || errorData.error || `HTTP error ${response.status}`);
}
return options.type === "txt" ? response.text() : response.json();
}
// src/utils/file-lock.ts
import * as fs from "fs";
var DEFAULT_LOCK_STALE_MS = 1e4;
var DEFAULT_LOCK_POLL_MS = 50;
function errorCode(error) {
if (typeof error !== "object" || error === null || !("code" in error)) return void 0;
const code = error.code;
return typeof code === "string" ? code : void 0;
}
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function acquireFileLock(lockPath, options = {}) {
const staleMs = options.staleMs ?? DEFAULT_LOCK_STALE_MS;
const pollMs = options.pollMs ?? DEFAULT_LOCK_POLL_MS;
for (; ; ) {
try {
fs.closeSync(fs.openSync(lockPath, "wx", 384));
return holdLock(lockPath, staleMs);
} catch (error) {
if (errorCode(error) !== "EEXIST") return null;
}
let mtimeMs;
try {
mtimeMs = fs.statSync(lockPath).mtimeMs;
} catch (error) {
if (errorCode(error) === "ENOENT") continue;
return null;
}
if (Date.now() - mtimeMs > staleMs) {
try {
fs.unlinkSync(lockPath);
} catch (error) {
if (errorCode(error) !== "ENOENT") return null;
}
continue;
}
await sleep(pollMs);
}
}
function holdLock(lockPath, staleMs) {
const heartbeat = setInterval(
() => {
try {
const now = /* @__PURE__ */ new Date();
fs.utimesSync(lockPath, now, now);
} catch {
}
},
Math.max(1, Math.floor(staleMs / 3))
);
heartbeat.unref();
return () => {
clearInterval(heartbeat);
try {
fs.unlinkSync(lockPath);
} catch {
}
};
}
// src/utils/storage-paths.ts
import * as fs2 from "fs";
import { access, chmod, mkdir, rename } from "fs/promises";
import * as os from "os";
import * as path from "path";
var APP_DIR = "context7";
var LEGACY_DIR = ".context7";
var CREDENTIALS_FILE_NAME = "credentials.json";
var UPDATE_STATE_FILE_NAME = "cli-state.json";
function xdgBase(envVar, ...defaultSegments) {
const value = process.env[envVar];
const base = value && path.isAbsolute(value) ? value : path.join(os.homedir(), ...defaultSegments);
return path.join(base, APP_DIR);
}
function getConfigDir() {
return xdgBase("XDG_CONFIG_HOME", ".config");
}
function getStateDir() {
return xdgBase("XDG_STATE_HOME", ".local", "state");
}
function getCredentialsFilePath() {
return path.join(getConfigDir(), CREDENTIALS_FILE_NAME);
}
function getUpdateStateFilePath() {
return path.join(getStateDir(), UPDATE_STATE_FILE_NAME);
}
function getLegacyFilePath(fileName) {
return path.join(os.homedir(), LEGACY_DIR, fileName);
}
function migrateLegacyFileSync(fileName, targetPath, mode) {
const legacyPath = getLegacyFilePath(fileName);
if (legacyPath === targetPath || fs2.existsSync(targetPath) || !fs2.existsSync(legacyPath)) {
return;
}
try {
fs2.mkdirSync(path.dirname(targetPath), { recursive: true, mode: 448 });
fs2.renameSync(legacyPath, targetPath);
if (mode !== void 0) {
fs2.chmodSync(targetPath, mode);
}
} catch {
}
}
async function migrateLegacyFile(fileName, targetPath, mode) {
const legacyPath = getLegacyFilePath(fileName);
if (legacyPath === targetPath || await exists(targetPath) || !await exists(legacyPath)) {
return;
}
try {
await mkdir(path.dirname(targetPath), { recursive: true, mode: 448 });
await rename(legacyPath, targetPath);
if (mode !== void 0) {
await chmod(targetPath, mode);
}
} catch {
}
}
function resolveReadPathSync(fileName, targetPath, mode) {
migrateLegacyFileSync(fileName, targetPath, mode);
if (fs2.existsSync(targetPath)) {
return targetPath;
}
const legacyPath = getLegacyFilePath(fileName);
return fs2.existsSync(legacyPath) ? legacyPath : targetPath;
}
async function resolveReadPath(fileName, targetPath, mode) {
await migrateLegacyFile(fileName, targetPath, mode);
if (await exists(targetPath)) {
return targetPath;
}
const legacyPath = getLegacyFilePath(fileName);
return await exists(legacyPath) ? legacyPath : targetPath;
}
async function exists(filePath) {
try {
await access(filePath);
return true;
} catch {
return false;
}
}
// src/utils/auth.ts
var CONTEXT7_API_KEY_PREFIX = "ctx7sk-";
function isContext7ApiKey(accessToken) {
return accessToken.startsWith(CONTEXT7_API_KEY_PREFIX);
}
function ensureConfigDir() {
const configDir = getConfigDir();
if (!fs3.existsSync(configDir)) {
fs3.mkdirSync(configDir, { recursive: true, mode: 448 });
}
}
var CREDENTIALS_MODE = 384;
function saveTokens(tokens) {
const credentialsFile = getCredentialsFilePath();
migrateLegacyFileSync(CREDENTIALS_FILE_NAME, credentialsFile, CREDENTIALS_MODE);
ensureConfigDir();
const data = {
...tokens,
expires_at: tokens.expires_at ?? (tokens.expires_in ? Date.now() + tokens.expires_in * 1e3 : void 0)
};
fs3.writeFileSync(credentialsFile, JSON.stringify(data, null, 2), { mode: CREDENTIALS_MODE });
fs3.chmodSync(credentialsFile, CREDENTIALS_MODE);
}
function loadTokens() {
const credentialsFile = resolveReadPathSync(
CREDENTIALS_FILE_NAME,
getCredentialsFilePath(),
CREDENTIALS_MODE
);
if (!fs3.existsSync(credentialsFile)) {
return null;
}
try {
const data = JSON.parse(fs3.readFileSync(credentialsFile, "utf-8"));
return data;
} catch {
return null;
}
}
function clearTokens() {
const credentialsFile = getCredentialsFilePath();
let removed = false;
if (fs3.existsSync(credentialsFile)) {
fs3.unlinkSync(credentialsFile);
removed = true;
}
const legacyCredentialsFile = getLegacyFilePath(CREDENTIALS_FILE_NAME);
if (fs3.existsSync(legacyCredentialsFile)) {
fs3.unlinkSync(legacyCredentialsFile);
removed = true;
}
return removed;
}
function isTokenExpired(tokens) {
if (!tokens.expires_at) {
return false;
}
return Date.now() > tokens.expires_at - 6e4;
}
var RefreshTokenRejectedError = class extends Error {
};
var DEFINITIVE_REFRESH_ERRORS = /* @__PURE__ */ new Set(["invalid_grant", "invalid_client"]);
async function refreshAccessToken(refreshToken) {
const response = await postForm(
`${getBaseUrl()}/api/oauth/token`,
new URLSearchParams({
grant_type: "refresh_token",
client_id: CLI_CLIENT_ID,
refresh_token: refreshToken
})
);
if (response.ok) {
return await response.json();
}
const body = await response.text().catch(() => "");
if (response.status < 500 && response.status !== 429) {
try {
const err = JSON.parse(body);
if (err.error && DEFINITIVE_REFRESH_ERRORS.has(err.error)) {
throw new RefreshTokenRejectedError(err.error_description || err.error);
}
} catch (error) {
if (error instanceof RefreshTokenRejectedError) throw error;
}
}
throw new Error(`Failed to refresh token (HTTP ${response.status})`);
}
var SESSION_EXPIRED_NOTICE = "Your Context7 session has expired. Run ctx7 login to sign in again. Continuing without an account.";
function isInteractiveSession() {
if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
return !process.argv.includes("--json");
}
async function recoverExpiredSession() {
if (!isInteractiveSession()) {
console.error(SESSION_EXPIRED_NOTICE);
return void 0;
}
let signIn;
try {
signIn = await confirm({
message: "Your Context7 session has expired. Sign in again now?",
default: true
});
} catch (error) {
if (error instanceof Error && error.name === "ExitPromptError") {
process.stdout.write("\n");
process.exit(130);
}
signIn = false;
}
if (signIn) {
const { performLogin: performLogin2 } = await import("./auth-TGX3XKCV.js");
const token = await performLogin2();
if (token) {
console.log("");
return token;
}
}
console.log(pc.dim("Continuing without an account. Run ctx7 login to sign in later."));
console.log("");
return void 0;
}
function getRefreshLockPath() {
return `${getCredentialsFilePath()}.lock`;
}
async function acquireRefreshLock() {
try {
ensureConfigDir();
return await acquireFileLock(getRefreshLockPath());
} catch {
return null;
}
}
var REFRESH_REJECTED = Symbol("refresh-rejected");
async function refreshAndStore(refreshToken) {
try {
const newTokens = await refreshAccessToken(refreshToken);
saveTokens({ refresh_token: refreshToken, ...newTokens });
return newTokens.access_token;
} catch (error) {
if (!(error instanceof RefreshTokenRejectedError)) return void 0;
const current = loadTokens();
if (current && current.refresh_token !== refreshToken) {
return isTokenExpired(current) ? void 0 : current.access_token;
}
clearTokens();
return REFRESH_REJECTED;
}
}
async function getValidAccessToken(options = {}) {
const tokens = loadTokens();
if (!tokens) return void 0;
if (!isTokenExpired(tokens)) {
return tokens.access_token;
}
if (!tokens.refresh_token) {
return void 0;
}
let result;
const release = await acquireRefreshLock();
try {
const latest = loadTokens();
if (!latest) return void 0;
if (!isTokenExpired(latest)) return latest.access_token;
if (!latest.refresh_token) return void 0;
result = await refreshAndStore(latest.refresh_token);
} finally {
release?.();
}
if (result !== REFRESH_REJECTED) return result;
if (options.recoverExpiredSession === false || process.env.CONTEXT7_API_KEY) {
return void 0;
}
return recoverExpiredSession();
}
var DEVICE_CODE_GRANT = "urn:ietf:params:oauth:grant-type:device_code";
async function describeErrorResponse(response, fallback) {
const body = await response.text().catch(() => "");
try {
const err = JSON.parse(body);
const message = err.error_description || err.error;
if (message) return message;
} catch {
}
const excerpt = body.replace(/\s+/g, " ").trim().slice(0, 200);
const detail = `HTTP ${response.status} from ${response.url}`;
return excerpt ? `${fallback} (${detail}): ${excerpt}` : `${fallback} (${detail})`;
}
var TLS_HINT = "The TLS certificate could not be verified, which usually means a proxy is inspecting HTTPS traffic. Point NODE_EXTRA_CA_CERTS at your organization's root CA.";
var DNS_HINT = "DNS lookup failed. Check your network or VPN connection.";
var BLOCKED_HINT = "The connection was refused or reset, which usually means a firewall or proxy is blocking it.";
var TIMEOUT_HINT = "The connection timed out. A proxy or firewall may be dropping the request.";
var DEFAULT_HINT = "If you are behind a corporate proxy, note that Node does not use HTTPS_PROXY automatically.";
var CONNECTION_HINTS = {
UNABLE_TO_VERIFY_LEAF_SIGNATURE: TLS_HINT,
SELF_SIGNED_CERT_IN_CHAIN: TLS_HINT,
DEPTH_ZERO_SELF_SIGNED_CERT: TLS_HINT,
CERT_HAS_EXPIRED: TLS_HINT,
ENOTFOUND: DNS_HINT,
EAI_AGAIN: DNS_HINT,
ECONNREFUSED: BLOCKED_HINT,
ECONNRESET: BLOCKED_HINT,
EHOSTUNREACH: BLOCKED_HINT,
ENETUNREACH: BLOCKED_HINT,
UND_ERR_CONNECT_TIMEOUT: TIMEOUT_HINT,
ETIMEDOUT: TIMEOUT_HINT
};
function getErrorCause(error) {
if (typeof error !== "object" || error === null || !("cause" in error)) return {};
const cause = error.cause;
if (typeof cause !== "object" || cause === null) return {};
const { code, message } = cause;
return {
code: typeof code === "string" ? code : void 0,
message: typeof message === "string" ? message : void 0
};
}
function describeConnectionError(error, url) {
const { code, message } = getErrorCause(error);
const detail = message || (error instanceof Error ? error.message : String(error));
const hint = code && CONNECTION_HINTS[code] || DEFAULT_HINT;
return `Could not reach ${url}: ${detail}${code ? ` (${code})` : ""}
${hint}`;
}
async function postForm(url, params) {
try {
return await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: params.toString()
});
} catch (error) {
throw new Error(describeConnectionError(error, url));
}
}
async function oauthRequest(url, params, fallback) {
const response = await postForm(url, params);
if (!response.ok) {
throw new Error(await describeErrorResponse(response, fallback));
}
return await response.json();
}
var DEFAULT_DEVICE_POLL_INTERVAL_SECONDS = 5;
async function startDeviceAuthorization(baseUrl2, clientId) {
const params = new URLSearchParams({ client_id: clientId });
try {
const hostname2 = os2.hostname();
if (hostname2) params.set("hostname", hostname2);
} catch {
}
return oauthRequest(
`${baseUrl2}/api/oauth/device/code`,
params,
"Failed to start device authorization"
);
}
async function pollDeviceToken(baseUrl2, clientId, deviceCode) {
let response;
try {
response = await postForm(
`${baseUrl2}/api/oauth/device/token`,
new URLSearchParams({
grant_type: DEVICE_CODE_GRANT,
device_code: deviceCode,
client_id: clientId
})
);
} catch (error) {
return {
status: "transient",
errorMessage: error instanceof Error ? error.message : "network error"
};
}
if (response.ok) {
const tokens = await response.json();
return { status: "approved", tokens };
}
if (response.status >= 500) {
const err2 = await response.json().catch(() => ({}));
return {
status: "transient",
errorMessage: err2.error_description || err2.error || `HTTP ${response.status}`
};
}
const err = await response.json().catch(() => ({}));
switch (err.error) {
case "authorization_pending":
return { status: "pending" };
case "slow_down":
return { status: "slow_down" };
case "access_denied":
return { status: "denied" };
case "expired_token":
return { status: "expired" };
default:
throw new Error(err.error_description || err.error || "Device token poll failed");
}
}
// src/utils/tracking.ts
function trackEvent(event, data) {
if (process.env.CTX7_TELEMETRY_DISABLED || getBaseUrl() !== DEFAULT_CONTEXT7_BASE_URL) return;
fetch(`${getBaseUrl()}/api/v2/cli/events`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ event, data })
}).catch(() => {
});
}
// src/commands/auth.ts
function registerAuthCommands(program) {
program.command("login").description("Log in to Context7").option("--no-browser", "Don't open browser automatically").action(async (options) => {
await loginCommand(options);
});
program.command("logout").description("Log out of Context7").action(() => {
logoutCommand();
});
program.command("whoami").description("Show current login status").action(async () => {
await whoamiCommand();
});
}
function renderDeviceCodeBox(userCode, verificationUri, verificationUriComplete) {
const codeLine = `${pc2.dim("Your one-time code:")}
${pc2.green(pc2.bold(userCode))}`;
const linkLine = verificationUriComplete ? `${pc2.dim("Open this link to approve:")}
${pc2.cyan(verificationUriComplete)}
${pc2.dim("Or visit")} ${pc2.cyan(verificationUri)} ${pc2.dim("and enter the code above.")}` : `${pc2.dim("Visit:")} ${pc2.cyan(verificationUri)}`;
return boxen(`${codeLine}
${linkLine}`, {
title: "Sign in to Context7",
titleAlignment: "left",
padding: 1,
margin: { top: 1, bottom: 1, left: 2, right: 2 },
borderStyle: "round",
borderColor: "gray"
});
}
function waitForEnter(prompt) {
if (!process.stdin.isTTY) return Promise.resolve();
return new Promise((resolve) => {
process.stdout.write(` ${pc2.dim(prompt)} `);
const onData = (chunk) => {
if (chunk[0] === 3) {
process.stdin.removeListener("data", onData);
process.stdin.setRawMode?.(false);
process.stdin.pause();
process.stdout.write("\n");
process.exit(130);
}
process.stdin.removeListener("data", onData);
process.stdin.setRawMode?.(false);
process.stdin.pause();
process.stdout.write("\n");
resolve();
};
process.stdin.setRawMode?.(true);
process.stdin.resume();
process.stdin.on("data", onData);
});
}
async function announceIdentity(accessToken) {
if (isContext7ApiKey(accessToken)) return "Authenticated with API key";
try {
const whoami = await fetchWhoami(accessToken);
const name = whoami.email || whoami.name;
if (!name) return "Login successful!";
const team = whoami.teamspace?.name;
return team ? `Logged in as ${pc2.bold(name)} ${pc2.dim(`(${team})`)}` : `Logged in as ${pc2.bold(name)}`;
} catch {
return "Login successful!";
}
}
async function performLogin(openBrowser = true) {
const baseUrl2 = getBaseUrl();
const spinner = ora("Preparing login...").start();
let authorization;
try {
authorization = await startDeviceAuthorization(baseUrl2, CLI_CLIENT_ID);
} catch (error) {
spinner.fail(pc2.red("Login failed"));
if (error instanceof Error) console.error(pc2.red(error.message));
return null;
}
spinner.stop();
console.log(
renderDeviceCodeBox(
authorization.user_code,
authorization.verification_uri,
authorization.verification_uri_complete
)
);
const target = authorization.verification_uri_complete ?? authorization.verification_uri;
if (openBrowser) {
await waitForEnter("Press Enter to open the browser, or Ctrl-C to quit...");
try {
await open(target);
} catch {
console.log(pc2.dim(` Couldn't open a browser \u2014 visit the link above manually.`));
}
} else {
console.log(pc2.dim(" Open the link above in any browser to continue."));
console.log("");
}
const waitingSpinner = ora({ text: "Waiting for authorization...", indent: 2 }).start();
const deadline = Date.now() + authorization.expires_in * 1e3;
let intervalMs = (authorization.interval ?? DEFAULT_DEVICE_POLL_INTERVAL_SECONDS) * 1e3;
while (Date.now() < deadline) {
await new Promise((resolve) => setTimeout(resolve, intervalMs));
try {
const result = await pollDeviceToken(baseUrl2, CLI_CLIENT_ID, authorization.device_code);
if (result.status === "approved" && result.tokens) {
saveTokens(result.tokens);
const successText = await announceIdentity(result.tokens.access_token);
waitingSpinner.succeed(pc2.green(successText));
return result.tokens.access_token;
}
if (result.status === "slow_down") {
intervalMs += 5e3;
continue;
}
if (result.status === "denied") {
waitingSpinner.fail(pc2.red("Authorization denied."));
return null;
}
if (result.status === "expired") {
waitingSpinner.fail(pc2.red("Code expired. Run login again."));
return null;
}
if (result.status === "transient") {
intervalMs += 5e3;
continue;
}
} catch (error) {
waitingSpinner.fail(pc2.red("Login failed"));
if (error instanceof Error) console.error(pc2.red(error.message));
return null;
}
}
waitingSpinner.fail(pc2.red("Code expired without approval."));
return null;
}
async function loginCommand(options) {
trackEvent("command", { name: "login" });
const existingToken = await getValidAccessToken({ recoverExpiredSession: false });
if (existingToken) {
console.log(pc2.yellow("You are already logged in."));
console.log(pc2.dim("Run 'ctx7 logout' first if you want to log in with a different account."));
return;
}
clearTokens();
const token = await performLogin(options.browser);
if (!token) {
process.exit(1);
}
console.log("");
console.log(pc2.dim("You can now use authenticated Context7 features."));
}
function logoutCommand() {
trackEvent("command", { name: "logout" });
if (clearTokens()) {
console.log(pc2.green("Logged out successfully."));
} else {
console.log(pc2.yellow("You are not logged in."));
}
}
async function whoamiCommand() {
trackEvent("command", { name: "whoami" });
const accessToken = await getValidAccessToken({ recoverExpiredSession: false });
if (!accessToken) {
console.log(pc2.yellow("Not logged in."));
console.log(pc2.dim("Run 'ctx7 login' to authenticate."));
return;
}
console.log(pc2.green("Logged in"));
if (isContext7ApiKey(accessToken)) {
return;
}
try {
const whoami = await fetchWhoami(accessToken);
if (whoami.name) {
console.log(`${pc2.dim("Name:".padEnd(13))}${whoami.name}`);
}
if (whoami.email) {
console.log(`${pc2.dim("Email:".padEnd(13))}${whoami.email}`);
}
if (whoami.teamspace) {
console.log(`${pc2.dim("Teamspace:".padEnd(13))}${whoami.teamspace.name}`);
}
} catch (error) {
console.log(
pc2.dim(
error instanceof SessionRejectedError ? "Session was rejected by the server. Run 'ctx7 logout', then 'ctx7 login' to sign in again." : "Could not verify your session. Try again later."
)
);
}
}
var SessionRejectedError = class extends Error {
};
async function fetchWhoami(accessToken) {
const response = await fetch(`${getBaseUrl()}/api/dashboard/whoami`, {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
if (response.status === 401) {
throw new SessionRejectedError("Session was rejected by the server");
}
if (!response.ok) {
throw new Error("Failed to fetch user info");
}
return await response.json();
}
export {
VERSION,
NAME,
DEFAULT_CONTEXT7_BASE_URL,
getBaseUrl,
setBaseUrl,
resolveLibrary,
CODE_OPERATIONS,
CODE_LIMITS,
hasSourceCode,
getLibraryCode,
getLibraryContext,
searchDocumentation,
UPDATE_STATE_FILE_NAME,
getUpdateStateFilePath,
migrateLegacyFile,
resolveReadPath,
isContext7ApiKey,
saveTokens,
getValidAccessToken,
trackEvent,
registerAuthCommands,
performLogin
};
//# sourceMappingURL=chunk-F53NPYYA.js.map