import { addLog, ErrorLog, SpinnerLog, removeLastLog } from '@mintlify/previewing'; import chalk from 'chalk'; import { Text } from 'ink'; import { getConfigValue } from '../config.js'; import { terminate } from '../helpers.js'; import { getAccessToken } from '../keyring.js'; import { getCliSubdomains } from '../status.js'; import { trackEvent } from '../telemetry/track.js'; import { fetchScoreBySlug, resolveScoreForUrl } from './client.js'; import type { Check, CliCheckStatus, ScoreResponse } from './types.js'; type OutputFormat = 'table' | 'plain' | 'json'; const POLL_INTERVAL_MS = 3_000; const POLL_TIMEOUT_MS = 120_000; function resolveFormat(argv: { format?: string }): OutputFormat { if (argv.format === 'table' || argv.format === 'plain' || argv.format === 'json') return argv.format; return 'table'; } function statusIcon(status: CliCheckStatus): string { switch (status) { case 'pass': return chalk.green('✓'); case 'warn': return chalk.yellow('⚠'); case 'skip': return chalk.dim('○'); case 'fail': case 'error': return chalk.red('✗'); default: { const _exhaustive: never = status; void _exhaustive; return chalk.dim('?'); } } } function plainStatusLabel(status: CliCheckStatus): string { return status.toUpperCase(); } function formatCheckName(name: string): string { return name .replace(/[-_]+/g, ' ') .replace(/([a-z])([A-Z])/g, '$1 $2') .replace(/^./, (c) => c.toUpperCase()) .trim(); } function renderChecks(checks: Check[], indent = 0): string[] { const lines: string[] = []; const pad = ' '.repeat(indent + 1); for (const check of checks) { lines.push(`${pad}${statusIcon(check.status)} ${formatCheckName(check.name)}`); if (check.children && check.children.length > 0) { lines.push(...renderChecks(check.children, indent + 1)); } } return lines; } function renderPlain(checks: Check[], prefix = ''): string[] { const lines: string[] = []; for (const check of checks) { lines.push(`${prefix}${plainStatusLabel(check.status)}\t${check.name}`); if (check.children && check.children.length > 0) { lines.push(...renderPlain(check.children, prefix + ' ')); } } return lines; } function renderTable(score: ScoreResponse, opts?: { stale?: boolean }): string { const scoreColor = score.overallScore >= 70 ? 'green' : score.overallScore >= 40 ? 'yellow' : 'red'; const header = opts?.stale ? chalk.bold(`\nAgent Readiness Score — ${score.canonicalUrl}`) + chalk.dim(` (last run ${new Date(score.computedAt).toLocaleString()})\n`) : chalk.bold(`\nAgent Readiness Score — ${score.canonicalUrl}\n`); const lines: string[] = [header]; lines.push(` Score: ${chalk[scoreColor].bold(`${score.overallScore}%`)}\n`); lines.push(chalk.bold(' Checks')); lines.push(...renderChecks(score.checks)); return lines.join('\n'); } function output(format: OutputFormat, text: string) { if (format === 'table') { addLog({text}); } else { process.stdout.write(text + '\n'); } } async function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } async function pollForScore( slug: string, afterIso: string | null, onAttempt?: () => void ): Promise { const deadline = Date.now() + POLL_TIMEOUT_MS; while (Date.now() < deadline) { await sleep(POLL_INTERVAL_MS); onAttempt?.(); const score = await fetchScoreBySlug(slug); if (!score) continue; if (afterIso === null) return score; if (new Date(score.computedAt).getTime() > new Date(afterIso).getTime()) return score; } throw new Error(`Score did not complete within ${POLL_TIMEOUT_MS / 1000}s`); } async function resolveDefaultSubdomain(): Promise { const fromConfig = getConfigValue('subdomain'); if (fromConfig) return fromConfig; const accessToken = await getAccessToken(); if (!accessToken) return undefined; const subdomains = await getCliSubdomains(accessToken); return subdomains[0]; } export const scoreHandler = async (argv: { url?: string; format?: string }) => { const format = resolveFormat(argv); try { let url = argv.url; if (!url) { const subdomain = await resolveDefaultSubdomain(); if (!subdomain) { throw new Error( 'No URL provided and no default subdomain set. Run `mint login` to set one, or pass a URL: `mint score `.' ); } url = `${subdomain}.mintlify.site`; } if (format === 'table') addLog(); const resolved = await resolveScoreForUrl(url); let final: ScoreResponse; if (resolved.status === 'ready') { const score = await fetchScoreBySlug(resolved.slug); if (format === 'table') removeLastLog(); if (!score) throw new Error('Score was unexpectedly unavailable'); final = score; } else if (resolved.status === 'stale_refresh_queued') { const stale = await fetchScoreBySlug(resolved.slug); if (format === 'table') removeLastLog(); if (!stale) throw new Error('Stale score was unexpectedly unavailable'); if (format === 'table') { output('table', renderTable(stale, { stale: true })); addLog(); } else { process.stderr.write('Refreshing score in the background...\n'); } final = await pollForScore(resolved.slug, stale.computedAt); if (format === 'table') removeLastLog(); } else { if (format === 'table') { removeLastLog(); addLog(); } else { process.stderr.write('Queued a new scoring run. Waiting for first result...\n'); } final = await pollForScore(resolved.slug, null); if (format === 'table') removeLastLog(); } void trackEvent('cli.score.executed', { url, score: final.overallScore, format, status: resolved.status, }); if (format === 'json') { output(format, JSON.stringify(final, null, 2)); } else if (format === 'plain') { const lines = [`SCORE\t${final.overallScore}`, ...renderPlain(final.checks)]; output(format, lines.join('\n')); } else { output(format, renderTable(final)); } await terminate(0); } catch (err) { const message = err instanceof Error ? err.message : 'unknown error'; if (format === 'table') { removeLastLog(); addLog(); } else { process.stderr.write(`Error: ${message}\n`); } await terminate(1); } };