@mintlify/cli 4.0.1109 → 4.0.1111

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mintlify/cli",
3
- "version": "4.0.1109",
3
+ "version": "4.0.1111",
4
4
  "description": "The Mintlify CLI",
5
5
  "engines": {
6
6
  "node": ">=18.0.0"
@@ -93,5 +93,5 @@
93
93
  "vitest": "2.1.9",
94
94
  "vitest-mock-process": "1.0.4"
95
95
  },
96
- "gitHead": "b1b4d6293cb62b84161652d8a26e509941f562b5"
96
+ "gitHead": "38703fc288945eaeaea97ea561f5ff35b15fd6e4"
97
97
  }
package/src/cli.tsx CHANGED
@@ -15,7 +15,7 @@ import {
15
15
  import { render, Text } from 'ink';
16
16
  import fs from 'node:fs/promises';
17
17
  import path from 'path';
18
- import yargs from 'yargs';
18
+ import yargs, { type Argv } from 'yargs';
19
19
  import { hideBin } from 'yargs/helpers';
20
20
 
21
21
  import { accessibilityCheck } from './accessibilityCheck.js';
@@ -40,6 +40,7 @@ import { logout } from './logout.js';
40
40
  import { mdxLinter } from './mdxLinter.js';
41
41
  import { createTelemetryMiddleware } from './middlewares/telemetryMiddleware.js';
42
42
  import { checkOpenApiFile, getOpenApiFilenamesFromDocsConfig } from './openApiCheck.js';
43
+ import { scoreHandler } from './score/index.js';
43
44
  import { status, getCliSubdomains } from './status.js';
44
45
  import { trackTelemetryPreferenceChange } from './telemetry/track.js';
45
46
  import { update } from './update.js';
@@ -570,6 +571,24 @@ export const cli = ({ packageName = 'mint' }: { packageName?: string }) => {
570
571
  }
571
572
  )
572
573
  .command('analytics', 'View analytics for your documentation', analyticsBuilder)
574
+ .command(
575
+ 'score <url>',
576
+ 'Run agent readiness checks on a docs site',
577
+ (yargs: Argv) =>
578
+ yargs
579
+ .positional('url', {
580
+ type: 'string',
581
+ demandOption: true,
582
+ description: 'URL of the docs site to check',
583
+ })
584
+ .option('format', {
585
+ type: 'string',
586
+ choices: ['table', 'plain', 'json'] as const,
587
+ description: 'Output format',
588
+ })
589
+ .example('mint score docs.example.com', 'Run agent readiness checks'),
590
+ scoreHandler
591
+ )
573
592
  // Coming soon commands — visible in help, tracked via telemetry to gauge interest.
574
593
  .command(
575
594
  'ai',
@@ -3,15 +3,39 @@ import { getVersions } from '../helpers.js';
3
3
  import { trackCommand } from '../telemetry/track.js';
4
4
 
5
5
  const SCRAPE_SUBCOMMANDS = new Set(['page', 'site', 'openapi']);
6
+ const ANALYTICS_SUBCOMMANDS = new Set(['stats', 'search', 'feedback', 'conversation']);
7
+ const CONFIG_SUBCOMMANDS = new Set(['set', 'get', 'clear']);
8
+ const CONVERSATION_SUBCOMMANDS = new Set(['list', 'view', 'buckets']);
9
+ const BUCKETS_SUBCOMMANDS = new Set(['list', 'view']);
6
10
 
7
11
  export function getSanitizedCommandForTelemetry(_: (string | number)[]): string {
8
12
  const parts = _.filter((p): p is string => typeof p === 'string');
9
13
  if (parts.length === 0) return '';
10
14
  const first = parts[0]!;
11
15
  const second = parts[1];
16
+ const third = parts[2];
17
+
12
18
  if (first === 'scrape' && second !== undefined && SCRAPE_SUBCOMMANDS.has(second)) {
13
19
  return `scrape ${second}`;
14
20
  }
21
+
22
+ if (first === 'config' && second !== undefined && CONFIG_SUBCOMMANDS.has(second)) {
23
+ return `config ${second}`;
24
+ }
25
+
26
+ if (first === 'analytics' && second !== undefined && ANALYTICS_SUBCOMMANDS.has(second)) {
27
+ if (second === 'conversation' && third !== undefined) {
28
+ if (CONVERSATION_SUBCOMMANDS.has(third)) {
29
+ const fourth = parts[3];
30
+ if (third === 'buckets' && fourth !== undefined && BUCKETS_SUBCOMMANDS.has(fourth)) {
31
+ return `analytics conversation buckets ${fourth}`;
32
+ }
33
+ return `analytics conversation ${third}`;
34
+ }
35
+ }
36
+ return `analytics ${second}`;
37
+ }
38
+
15
39
  return first;
16
40
  }
17
41
 
@@ -0,0 +1,19 @@
1
+ import { authenticatedFetch } from '../authenticatedFetch.js';
2
+ import { API_URL } from '../constants.js';
3
+ import type { ScoreResponse } from './types.js';
4
+
5
+ export async function getScore(url: string): Promise<ScoreResponse> {
6
+ const endpoint = new URL(`${API_URL}/api/cli/score`);
7
+ endpoint.searchParams.set('url', url);
8
+
9
+ const res = await authenticatedFetch(endpoint.toString(), {
10
+ headers: { Accept: 'application/json' },
11
+ });
12
+
13
+ if (!res.ok) {
14
+ const body = await res.text().catch(() => '');
15
+ throw new Error(`API error (${res.status}): ${body || res.statusText}`);
16
+ }
17
+
18
+ return res.json() as Promise<ScoreResponse>;
19
+ }
@@ -0,0 +1,104 @@
1
+ import { addLog, ErrorLog, SpinnerLog, removeLastLog } from '@mintlify/previewing';
2
+ import chalk from 'chalk';
3
+ import { Text } from 'ink';
4
+
5
+ import { terminate } from '../helpers.js';
6
+ import { trackEvent } from '../telemetry/track.js';
7
+ import { getScore } from './client.js';
8
+ import type { Check } from './types.js';
9
+
10
+ type OutputFormat = 'table' | 'plain' | 'json';
11
+
12
+ function resolveFormat(argv: { format?: string }): OutputFormat {
13
+ if (argv.format === 'table' || argv.format === 'plain' || argv.format === 'json')
14
+ return argv.format;
15
+ return 'table';
16
+ }
17
+
18
+ function statusIcon(pass: boolean): string {
19
+ return pass ? chalk.green('✓') : chalk.red('✗');
20
+ }
21
+
22
+ function formatCheckName(name: string): string {
23
+ return name
24
+ .replace(/([A-Z])/g, ' $1')
25
+ .replace(/^./, (c) => c.toUpperCase())
26
+ .trim();
27
+ }
28
+
29
+ function renderChecks(checks: Check[], indent = 0): string[] {
30
+ const lines: string[] = [];
31
+ const pad = ' '.repeat(indent + 1);
32
+ for (const check of checks) {
33
+ lines.push(`${pad}${statusIcon(check.pass)} ${formatCheckName(check.name)}`);
34
+ if (check.children && check.children.length > 0) {
35
+ lines.push(...renderChecks(check.children, indent + 1));
36
+ }
37
+ }
38
+ return lines;
39
+ }
40
+
41
+ function renderPlain(checks: Check[], prefix = ''): string[] {
42
+ const lines: string[] = [];
43
+ for (const check of checks) {
44
+ lines.push(`${prefix}${check.pass ? 'PASS' : 'FAIL'}\t${check.name}`);
45
+ if (check.children && check.children.length > 0) {
46
+ lines.push(...renderPlain(check.children, prefix + ' '));
47
+ }
48
+ }
49
+ return lines;
50
+ }
51
+
52
+ function output(format: OutputFormat, text: string) {
53
+ if (format === 'table') {
54
+ addLog(<Text>{text}</Text>);
55
+ } else {
56
+ process.stdout.write(text + '\n');
57
+ }
58
+ }
59
+
60
+ export const scoreHandler = async (argv: { url: string; format?: string }) => {
61
+ const format = resolveFormat(argv);
62
+ try {
63
+ if (format === 'table') addLog(<SpinnerLog message="Running agent readiness checks..." />);
64
+ const data = await getScore(argv.url);
65
+ void trackEvent('cli.score.executed', {
66
+ url: argv.url,
67
+ score: data.overallScore,
68
+ format,
69
+ });
70
+ if (format === 'table') removeLastLog();
71
+
72
+ if (format === 'json') {
73
+ output(format, JSON.stringify(data, null, 2));
74
+ await terminate(0);
75
+ return;
76
+ }
77
+
78
+ if (format === 'plain') {
79
+ const lines = [`SCORE\t${data.overallScore}`, ...renderPlain(data.checks)];
80
+ output(format, lines.join('\n'));
81
+ await terminate(0);
82
+ return;
83
+ }
84
+
85
+ const scoreColor =
86
+ data.overallScore >= 70 ? 'green' : data.overallScore >= 40 ? 'yellow' : 'red';
87
+ const lines: string[] = [];
88
+ lines.push(chalk.bold(`\nAgent Readiness Score — ${data.url}\n`));
89
+ lines.push(` Score: ${chalk[scoreColor].bold(`${data.overallScore}%`)}\n`);
90
+ lines.push(chalk.bold(' Checks'));
91
+ lines.push(...renderChecks(data.checks));
92
+ output(format, lines.join('\n'));
93
+ await terminate(0);
94
+ } catch (err) {
95
+ const message = err instanceof Error ? err.message : 'unknown error';
96
+ if (format === 'table') {
97
+ removeLastLog();
98
+ addLog(<ErrorLog message={message} />);
99
+ } else {
100
+ process.stderr.write(`Error: ${message}\n`);
101
+ }
102
+ await terminate(1);
103
+ }
104
+ };
@@ -0,0 +1,12 @@
1
+ export type Check = {
2
+ name: string;
3
+ pass: boolean;
4
+ metadata?: Record<string, unknown>;
5
+ children?: Check[];
6
+ };
7
+
8
+ export type ScoreResponse = {
9
+ url: string;
10
+ overallScore: number;
11
+ checks: Check[];
12
+ };
@@ -77,6 +77,25 @@ export async function trackLoginFailed(reason: string): Promise<void> {
77
77
  return trackLoginEvent('cli.login.failed', { reason });
78
78
  }
79
79
 
80
+ export async function trackEvent(
81
+ event: string,
82
+ properties?: Record<string, unknown>
83
+ ): Promise<void> {
84
+ if (!isTelemetryEnabled()) return;
85
+
86
+ try {
87
+ const { cli: cliVersion } = getVersions();
88
+ await captureWithTimeout(event, {
89
+ ...properties,
90
+ cli_version: cliVersion,
91
+ os: os.platform(),
92
+ arch: os.arch(),
93
+ node_version: process.version,
94
+ is_ai_agent: isAI(),
95
+ });
96
+ } catch {}
97
+ }
98
+
80
99
  export async function trackTelemetryPreferenceChange(options: { enabled: boolean }): Promise<void> {
81
100
  if (process.env.CLI_TEST_MODE === 'true') return;
82
101