import path from 'node:path'; import { discoverAgentAuth, exchangeAgentToken, getAgentIdentity, registerAgent, } from './agent-auth.js'; import { loadAgentCredentials, saveAgentCredentials, type AgentCredentials, } from './agent-credentials.js'; import { getAgentWriteToken, provisionAgentSite, type AgentSite, type AgentWriteToken, } from './agent-deployment.js'; import { getAgentSiteMetadata, setAgentSiteMetadata } from './config.js'; export type DeployOptions = { apiUrl: string; directory: string; subdomain?: string; orgName?: string; newSite?: boolean; }; export type DeployResult = | { status: 'claimed' } | { status: 'ready'; site: AgentSite; write: AgentWriteToken; created: boolean; credentials: AgentCredentials; }; export async function prepareAgentDeployment( options: DeployOptions, warn: (message: string) => void, progress: (message: string) => void = () => {} ): Promise { const { apiUrl } = options; const directory = path.resolve(options.directory); const metadataKey = JSON.stringify([new URL(apiUrl).origin, directory]); const previous = getAgentSiteMetadata(metadataKey); progress('Authenticating agent...'); let saved = options.newSite ? null : await loadAgentCredentials(apiUrl, directory).catch(() => { if (previous) throw new Error( 'Could not read this site’s saved credentials. Unlock your keyring or use --new for another site.' ); warn('Could not read the keyring. Continuing with a new registration for this run.'); return null; }); const subdomain = options.subdomain ?? (options.newSite ? undefined : previous?.subdomain); if (!subdomain) throw new Error('Choose a site name with --subdomain .'); const discovery = await discoverAgentAuth(apiUrl); const save = async (credentials: AgentCredentials) => { try { await saveAgentCredentials(apiUrl, directory, credentials); } catch { warn( 'Could not save agent credentials to the keyring. Continuing for this run; later commands may not recover access.' ); } }; for (let attempt = 0; attempt < 2; attempt++) { const credentials = saved ?? (await registerAgent(discovery)); await save(credentials); const authenticated = await exchangeAgentToken(discovery, credentials); await save(authenticated); const identity = await getAgentIdentity(apiUrl, authenticated.accessToken); if (identity.registrationId !== credentials.registrationId) throw new Error('Agent identity does not match the saved registration.'); if (identity.identityType === 'user_bound') return { status: 'claimed' }; progress('Provisioning the site and repository...'); const site = await provisionAgentSite(apiUrl, authenticated.accessToken, { subdomain, orgName: options.orgName, }); if (site.status === 'expired') { warn('The previous anonymous site expired. Creating a new registration and site.'); saved = null; continue; } await setAgentSiteMetadata(metadataKey, { registrationId: credentials.registrationId, subdomain: site.subdomain, }).catch(() => warn('Could not save the site name locally. Provide --subdomain on your next invocation.') ); progress('Getting repository write access...'); const write = await getAgentWriteToken(apiUrl, authenticated.accessToken); if (write.remote !== site.remote) throw new Error('Repository credentials do not match the provisioned site.'); return { status: 'ready', site, write, created: site.created, credentials: authenticated }; } throw new Error('The new anonymous site is already expired. Please retry later.'); } export function formatDeployment(result: DeployResult, apiUrl: string): string { if (result.status === 'claimed') return 'This site has been claimed. Manage it in the Mintlify dashboard. Use mint deploy --new --subdomain to create another site.'; const { site, write } = result; const domain = new URL(apiUrl).hostname === 'leaves-dev.mintlify.com' ? 'mint-dev.mintlify.me' : 'mintlify.site'; const siteUrl = new URL(`https://${site.subdomain}.${domain}`).toString(); return [ result.created ? 'Repository provisioned; push required to build the site.' : 'Using the existing anonymous repository; push any changes to build the site.', `Site: ${siteUrl}`, `Expires: ${site.expiresAt}`, `Repository: ${site.remote}`, `Branch: ${site.branch}`, `Write token: ${write.token}`, `Write token expires: ${write.tokenExpiresAt}`, 'Commit and push your documentation to this repository to start the build.', 'The site link becomes available after the push and build complete.', ].join('\n'); }