@mintlify/cli 4.0.1701 → 4.0.1703

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.
@@ -0,0 +1,54 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { createHash } from 'node:crypto';
11
+ import path from 'node:path';
12
+ import { z } from 'zod';
13
+ import { getKeytar } from './keyring.js';
14
+ const service = 'mintlify-agent';
15
+ export const agentCredentialsSchema = z.object({
16
+ registrationId: z.string().min(1),
17
+ issuer: z.url(),
18
+ assertion: z.string().min(1),
19
+ claimToken: z.string().min(1),
20
+ accessToken: z.string().min(1).optional(),
21
+ accessTokenExpiresAt: z.number().optional(),
22
+ });
23
+ function projectAccount(apiUrl, directory) {
24
+ return `project:${createHash('sha256')
25
+ .update(JSON.stringify([new URL(apiUrl).origin, path.resolve(directory)]))
26
+ .digest('hex')}`;
27
+ }
28
+ function registrationAccount(apiUrl, registrationId) {
29
+ return `${new URL(apiUrl).origin}:${registrationId}`;
30
+ }
31
+ export function loadAgentCredentials(apiUrl, directory) {
32
+ return __awaiter(this, void 0, void 0, function* () {
33
+ const keytar = yield getKeytar();
34
+ const registrationId = yield keytar.getPassword(service, projectAccount(apiUrl, directory));
35
+ if (!registrationId)
36
+ return null;
37
+ const value = yield keytar.getPassword(service, registrationAccount(apiUrl, registrationId));
38
+ if (!value)
39
+ throw new Error('Saved agent credentials are missing. Use --new to create another site.');
40
+ const credentials = agentCredentialsSchema.parse(JSON.parse(value));
41
+ if (credentials.registrationId !== registrationId) {
42
+ throw new Error('Saved agent credentials do not match this project.');
43
+ }
44
+ return credentials;
45
+ });
46
+ }
47
+ export function saveAgentCredentials(apiUrl, directory, credentials) {
48
+ return __awaiter(this, void 0, void 0, function* () {
49
+ const value = agentCredentialsSchema.parse(credentials);
50
+ const keytar = yield getKeytar();
51
+ yield keytar.setPassword(service, registrationAccount(apiUrl, value.registrationId), JSON.stringify(value));
52
+ yield keytar.setPassword(service, projectAccount(apiUrl, directory), value.registrationId);
53
+ });
54
+ }
@@ -0,0 +1,73 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { setTimeout } from 'node:timers/promises';
11
+ import { z } from 'zod';
12
+ import { agentRequest, agentResponse } from './agent-http.js';
13
+ const readySchema = z.object({
14
+ status: z.literal('ready'),
15
+ deploymentId: z.string().min(1),
16
+ subdomain: z.string().min(1),
17
+ remote: z.url(),
18
+ branch: z.literal('main'),
19
+ expiresAt: z.iso.datetime(),
20
+ });
21
+ const existingSchema = z.discriminatedUnion('status', [
22
+ readySchema.extend({ code: z.literal('deployment_already_exists') }),
23
+ z.object({
24
+ status: z.literal('provisioning'),
25
+ code: z.literal('deployment_already_exists'),
26
+ deploymentId: z.string().optional(),
27
+ subdomain: z.string().optional(),
28
+ }),
29
+ z.object({
30
+ status: z.literal('expired'),
31
+ code: z.literal('deployment_already_exists'),
32
+ deploymentId: z.string(),
33
+ subdomain: z.string(),
34
+ expiresAt: z.iso.datetime().optional(),
35
+ }),
36
+ ]);
37
+ const writeTokenSchema = z.object({
38
+ remote: z.url(),
39
+ branch: z.literal('main'),
40
+ token: z.string().min(1),
41
+ tokenExpiresAt: z.iso.datetime(),
42
+ });
43
+ export function provisionAgentSite(apiUrl, accessToken, settings) {
44
+ return __awaiter(this, void 0, void 0, function* () {
45
+ for (let attempt = 0; attempt < 4; attempt++) {
46
+ const response = yield agentRequest(`${apiUrl}/api/agent/deployments/provision`, {
47
+ method: 'POST',
48
+ headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json' },
49
+ body: JSON.stringify(Object.assign(Object.assign({}, settings), { docsPath: '' })),
50
+ });
51
+ if (response.status !== 409) {
52
+ return Object.assign(Object.assign({}, (yield agentResponse(response, readySchema))), { created: true });
53
+ }
54
+ const body = yield response.json().catch(() => null);
55
+ const parsed = existingSchema.safeParse(body);
56
+ if (!parsed.success)
57
+ throw new Error('The deployment service returned an unexpected response.');
58
+ if (parsed.data.status !== 'provisioning')
59
+ return Object.assign(Object.assign({}, parsed.data), { created: false });
60
+ if (attempt < 3)
61
+ yield setTimeout(1000 * (attempt + 1));
62
+ }
63
+ throw new Error('Your site is still provisioning. Run the same command again.');
64
+ });
65
+ }
66
+ export function getAgentWriteToken(apiUrl, accessToken) {
67
+ return __awaiter(this, void 0, void 0, function* () {
68
+ return agentResponse(yield agentRequest(`${apiUrl}/api/agent/deployments/token`, {
69
+ method: 'POST',
70
+ headers: { Authorization: `Bearer ${accessToken}` },
71
+ }), writeTokenSchema);
72
+ });
73
+ }
@@ -0,0 +1,45 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { z } from 'zod';
11
+ const errorSchema = z.object({ code: z.string().optional(), error: z.string().optional() });
12
+ export class AgentApiError extends Error {
13
+ constructor(status, code) {
14
+ super(`Agent request failed (HTTP ${status}).${code ? ` Code: ${code}.` : ''}`);
15
+ this.status = status;
16
+ this.code = code;
17
+ }
18
+ }
19
+ export function agentRequest(url, init) {
20
+ return __awaiter(this, void 0, void 0, function* () {
21
+ try {
22
+ const timeout = AbortSignal.timeout(30000);
23
+ const signal = (init === null || init === void 0 ? void 0 : init.signal) ? AbortSignal.any([init.signal, timeout]) : timeout;
24
+ return yield fetch(url, Object.assign(Object.assign({}, init), { redirect: 'error', signal }));
25
+ }
26
+ catch (_a) {
27
+ throw new Error('Could not reach the agent service. Retry the same command.');
28
+ }
29
+ });
30
+ }
31
+ export function agentResponse(response, schema) {
32
+ return __awaiter(this, void 0, void 0, function* () {
33
+ var _a, _b;
34
+ const body = yield response.json().catch(() => null);
35
+ if (!response.ok) {
36
+ const error = errorSchema.safeParse(body);
37
+ const code = error.success ? ((_a = error.data.code) !== null && _a !== void 0 ? _a : error.data.error) : undefined;
38
+ throw new AgentApiError(response.status, (_b = code === null || code === void 0 ? void 0 : code.match(/^[a-z_]{1,80}$/)) === null || _b === void 0 ? void 0 : _b[0]);
39
+ }
40
+ const parsed = schema.safeParse(body);
41
+ if (!parsed.success)
42
+ throw new Error('The agent service returned an unexpected response.');
43
+ return parsed.data;
44
+ });
45
+ }
package/bin/cli.js CHANGED
@@ -18,11 +18,14 @@ import yargs from 'yargs';
18
18
  import { hideBin } from 'yargs/helpers';
19
19
  import { accessibilityCheck } from './accessibilityCheck.js';
20
20
  import { addDomain } from './addDomain.js';
21
+ import { createAgentClaimLink } from './agent-claim.js';
21
22
  import { analyticsBuilder } from './analytics/index.js';
22
23
  import { comingSoon } from './comingSoon.js';
23
24
  import { setTelemetryEnabled } from './config.js';
24
25
  import { getConfigValue, setConfigValue, clearConfigValue } from './config.js';
25
26
  import { API_URL } from './constants.js';
27
+ import { DEPLOY_HELP, deploymentInstructions } from './deploy-instructions.js';
28
+ import { formatDeployment, prepareAgentDeployment } from './deploy.js';
26
29
  import { formatHandler } from './format.js';
27
30
  import { CMD_EXEC_PATH, checkPort, checkNodeVersion, autoUpgradeIfNeeded, findDocsRoot, getVersions, isAI, suppressConsoleWarnings, terminate, } from './helpers.js';
28
31
  import { init } from './init.js';
@@ -94,6 +97,59 @@ export const cli = ({ packageName = 'mint' }) => {
94
97
  .middleware(checkNodeVersion)
95
98
  .middleware(suppressConsoleWarnings)
96
99
  .middleware(telemetryMiddleware)
100
+ .command('deploy', 'Provision an anonymous docs site and get Git push instructions', (yargs) => yargs
101
+ .option('subdomain', {
102
+ type: 'string',
103
+ description: 'Requested site name; required for the first deployment or --new',
104
+ })
105
+ .option('org-name', { type: 'string', description: 'Organization display name' })
106
+ .option('directory', {
107
+ type: 'string',
108
+ default: '.',
109
+ description: 'Documentation directory associated with this site',
110
+ })
111
+ .option('new', {
112
+ type: 'boolean',
113
+ default: false,
114
+ description: 'Create a new registration and site',
115
+ })
116
+ .option('email', {
117
+ type: 'string',
118
+ description: 'User email for an optional claim link',
119
+ })
120
+ .example('$0 deploy --subdomain my-docs', 'Provision a site without signing in')
121
+ .example('$0 deploy --email you@example.com', 'Get repository access and a claim link for the current site')
122
+ .example('$0 deploy --new --subdomain another-site', 'Create another anonymous site')
123
+ .epilog(DEPLOY_HELP), (argv) => __awaiter(void 0, void 0, void 0, function* () {
124
+ try {
125
+ const result = yield prepareAgentDeployment({
126
+ apiUrl: API_URL,
127
+ directory: argv.directory,
128
+ subdomain: argv.subdomain,
129
+ orgName: argv.orgName,
130
+ newSite: argv.new,
131
+ }, (message) => {
132
+ process.stderr.write(`Warning: ${message}\n`);
133
+ }, (message) => process.stderr.write(`${message}\n`));
134
+ process.stdout.write(`${formatDeployment(result, API_URL)}\n`);
135
+ let claimUrl;
136
+ if (argv.email && result.status === 'ready') {
137
+ try {
138
+ process.stderr.write('Creating the claim link...\n');
139
+ claimUrl = yield createAgentClaimLink(API_URL, result.credentials, argv.email);
140
+ }
141
+ catch (_a) {
142
+ process.stderr.write('Warning: Could not create the claim link. Your site is provisioned; retry with --email later.\n');
143
+ }
144
+ }
145
+ process.stdout.write(`${deploymentInstructions(result, claimUrl)}\n`);
146
+ yield terminate(0);
147
+ }
148
+ catch (error) {
149
+ process.stderr.write(`${error instanceof Error ? error.message : 'Deployment failed.'}\n`);
150
+ yield terminate(1);
151
+ }
152
+ }))
97
153
  .command('dev', 'Initialize a local preview environment', (yargs) => yargs
98
154
  .option('open', {
99
155
  type: 'boolean',
package/bin/config.js CHANGED
@@ -9,7 +9,19 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  };
10
10
  import fs from 'fs';
11
11
  import { ensureDir } from 'fs-extra';
12
+ import { z } from 'zod';
12
13
  import { CLI_CONFIG_FILE, CONFIG_DIR } from './constants.js';
14
+ const agentSiteMetadataSchema = z.object({ registrationId: z.string(), subdomain: z.string() });
15
+ export function getAgentSiteMetadata(key) {
16
+ var _a;
17
+ const parsed = agentSiteMetadataSchema.safeParse((_a = readConfig().agentSites) === null || _a === void 0 ? void 0 : _a[key]);
18
+ return parsed.success ? parsed.data : undefined;
19
+ }
20
+ export function setAgentSiteMetadata(key, site) {
21
+ return __awaiter(this, void 0, void 0, function* () {
22
+ yield writeConfig({ agentSites: Object.assign(Object.assign({}, readConfig().agentSites), { [key]: site }) });
23
+ });
24
+ }
13
25
  function readConfig() {
14
26
  try {
15
27
  const raw = fs.readFileSync(CLI_CONFIG_FILE, 'utf-8');
@@ -0,0 +1,39 @@
1
+ import { API_URL } from './constants.js';
2
+ export const DEPLOY_HELP = [
3
+ 'No login is required. This command provisions a temporary site and returns repository access. Commit and push the documentation yourself; the CLI does not upload files.',
4
+ 'Run from your documentation directory, or pass --directory. Put docs.json, pages, and assets at the hosted repository root.',
5
+ 'Run again from the same documentation directory for the current anonymous site and a fresh write token. Use --new --subdomain <name> to create another site.',
6
+ 'Claimed sites are managed in the dashboard. Confirmed expired sites start a new anonymous registration.',
7
+ 'Pass --email to obtain a claim link for the user. If it is absent, ask the user for their email and rerun with --email from the same directory. Never invent an email address.',
8
+ 'The output contains a short-lived write token. Do not paste it into public logs or save it in a Git remote.',
9
+ ].join('\n\n');
10
+ export function deploymentInstructions(result, claimUrl) {
11
+ if (result.status !== 'ready')
12
+ return '';
13
+ return [
14
+ '\nNext steps:',
15
+ '1. Clone the hosted repository into a separate directory. Do not replace the user’s existing Git remote.',
16
+ '2. Copy the selected documentation into its root, including docs.json, .md/.mdx pages, images, and other required assets. Exclude credentials, .env files, node_modules, and .git.',
17
+ '3. Commit the documentation. For subsequent updates, fetch the hosted main branch and commit on top of it. Do not force-push.',
18
+ '4. Push to main. Supply the write token as an HTTP Authorization: Bearer header for this Git operation only; never embed it in the remote URL or persist it in Git config.',
19
+ '5. If the write token expires, run mint deploy again from the same documentation directory to obtain a fresh token.',
20
+ 'A successful push starts the normal build. Provisioning and Git success do not confirm build success.',
21
+ '',
22
+ 'Git examples (POSIX shell): set REPOSITORY_URL and WRITE_TOKEN from the output above. Set CHECKOUT_DIR to a new directory outside the source documentation directory. Keep the token private and do not enable shell tracing.',
23
+ 'GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0="http.${REPOSITORY_URL}.extraHeader" GIT_CONFIG_VALUE_0="Authorization: Bearer ${WRITE_TOKEN}" git clone "$REPOSITORY_URL" "$CHECKOUT_DIR"',
24
+ 'After copying and committing the documentation:',
25
+ 'GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0="http.${REPOSITORY_URL}.extraHeader" GIT_CONFIG_VALUE_0="Authorization: Bearer ${WRITE_TOKEN}" git -C "$CHECKOUT_DIR" push origin HEAD:main',
26
+ 'These settings apply only to that Git process. Use the same authentication settings for later fetches.',
27
+ '',
28
+ ...(claimUrl
29
+ ? [
30
+ `Claim link for the user: ${claimUrl}`,
31
+ 'Show this link to the user. Ask them to open it, sign in, and return the displayed code. Do not sign in on their behalf.',
32
+ `Follow the claim-completion instructions at ${API_URL}/api/agent/auth.md. This document includes the WorkOS steps and the authenticated Mintlify signup request.`,
33
+ ]
34
+ : [
35
+ 'Ask the user for the email address they want to use to claim this site. Never invent an email address.',
36
+ 'After they provide it, run mint deploy --email <their-email> from the same documentation directory (or repeat --directory). This reuses the site and returns its claim link.',
37
+ ]),
38
+ ].join('\n');
39
+ }
package/bin/deploy.js ADDED
@@ -0,0 +1,97 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import path from 'node:path';
11
+ import { discoverAgentAuth, exchangeAgentToken, getAgentIdentity, registerAgent, } from './agent-auth.js';
12
+ import { loadAgentCredentials, saveAgentCredentials, } from './agent-credentials.js';
13
+ import { getAgentWriteToken, provisionAgentSite, } from './agent-deployment.js';
14
+ import { getAgentSiteMetadata, setAgentSiteMetadata } from './config.js';
15
+ export function prepareAgentDeployment(options_1, warn_1) {
16
+ return __awaiter(this, arguments, void 0, function* (options, warn, progress = () => { }) {
17
+ var _a;
18
+ const { apiUrl } = options;
19
+ const directory = path.resolve(options.directory);
20
+ const metadataKey = JSON.stringify([new URL(apiUrl).origin, directory]);
21
+ const previous = getAgentSiteMetadata(metadataKey);
22
+ progress('Authenticating agent...');
23
+ let saved = options.newSite
24
+ ? null
25
+ : yield loadAgentCredentials(apiUrl, directory).catch(() => {
26
+ if (previous)
27
+ throw new Error('Could not read this site’s saved credentials. Unlock your keyring or use --new for another site.');
28
+ warn('Could not read the keyring. Continuing with a new registration for this run.');
29
+ return null;
30
+ });
31
+ const subdomain = (_a = options.subdomain) !== null && _a !== void 0 ? _a : (options.newSite ? undefined : previous === null || previous === void 0 ? void 0 : previous.subdomain);
32
+ if (!subdomain)
33
+ throw new Error('Choose a site name with --subdomain <name>.');
34
+ const discovery = yield discoverAgentAuth(apiUrl);
35
+ const save = (credentials) => __awaiter(this, void 0, void 0, function* () {
36
+ try {
37
+ yield saveAgentCredentials(apiUrl, directory, credentials);
38
+ }
39
+ catch (_a) {
40
+ warn('Could not save agent credentials to the keyring. Continuing for this run; later commands may not recover access.');
41
+ }
42
+ });
43
+ for (let attempt = 0; attempt < 2; attempt++) {
44
+ const credentials = saved !== null && saved !== void 0 ? saved : (yield registerAgent(discovery));
45
+ yield save(credentials);
46
+ const authenticated = yield exchangeAgentToken(discovery, credentials);
47
+ yield save(authenticated);
48
+ const identity = yield getAgentIdentity(apiUrl, authenticated.accessToken);
49
+ if (identity.registrationId !== credentials.registrationId)
50
+ throw new Error('Agent identity does not match the saved registration.');
51
+ if (identity.identityType === 'user_bound')
52
+ return { status: 'claimed' };
53
+ progress('Provisioning the site and repository...');
54
+ const site = yield provisionAgentSite(apiUrl, authenticated.accessToken, {
55
+ subdomain,
56
+ orgName: options.orgName,
57
+ });
58
+ if (site.status === 'expired') {
59
+ warn('The previous anonymous site expired. Creating a new registration and site.');
60
+ saved = null;
61
+ continue;
62
+ }
63
+ yield setAgentSiteMetadata(metadataKey, {
64
+ registrationId: credentials.registrationId,
65
+ subdomain: site.subdomain,
66
+ }).catch(() => warn('Could not save the site name locally. Provide --subdomain on your next invocation.'));
67
+ progress('Getting repository write access...');
68
+ const write = yield getAgentWriteToken(apiUrl, authenticated.accessToken);
69
+ if (write.remote !== site.remote)
70
+ throw new Error('Repository credentials do not match the provisioned site.');
71
+ return { status: 'ready', site, write, created: site.created, credentials: authenticated };
72
+ }
73
+ throw new Error('The new anonymous site is already expired. Please retry later.');
74
+ });
75
+ }
76
+ export function formatDeployment(result, apiUrl) {
77
+ if (result.status === 'claimed')
78
+ return 'This site has been claimed. Manage it in the Mintlify dashboard. Use mint deploy --new --subdomain <name> to create another site.';
79
+ const { site, write } = result;
80
+ const domain = new URL(apiUrl).hostname === 'leaves-dev.mintlify.com'
81
+ ? 'mint-dev.mintlify.me'
82
+ : 'mintlify.site';
83
+ const siteUrl = new URL(`https://${site.subdomain}.${domain}`).toString();
84
+ return [
85
+ result.created
86
+ ? 'Repository provisioned; push required to build the site.'
87
+ : 'Using the existing anonymous repository; push any changes to build the site.',
88
+ `Site: ${siteUrl}`,
89
+ `Expires: ${site.expiresAt}`,
90
+ `Repository: ${site.remote}`,
91
+ `Branch: ${site.branch}`,
92
+ `Write token: ${write.token}`,
93
+ `Write token expires: ${write.tokenExpiresAt}`,
94
+ 'Commit and push your documentation to this repository to start the build.',
95
+ 'The site link becomes available after the push and build complete.',
96
+ ].join('\n');
97
+ }
package/bin/keyring.js CHANGED
@@ -11,7 +11,7 @@ const SERVICE = 'mintlify';
11
11
  const ACCESS_TOKEN_ACCOUNT = 'access_token';
12
12
  const REFRESH_TOKEN_ACCOUNT = 'refresh_token';
13
13
  const USER_EMAIL_ACCOUNT = 'user_email';
14
- function getKeytar() {
14
+ export function getKeytar() {
15
15
  return __awaiter(this, void 0, void 0, function* () {
16
16
  var _a;
17
17
  try {