import ora, { type Ora } from 'ora' import { getCliClient } from '../cli-client.js' import { GenerateError, startGeneration, waitForGeneration, type PollOutcome } from '../generate.js' import type { MarketClient } from '../client.js' import { resolve } from '../resolve.js' import { install as runInstall } from '../install.js' import { generatedInstallResult, generationStartedResult } from '../output.js' import { loadReferenceImages } from './reference-images.js' import type { AssetAccess, AssetType } from '../schemas.js' export interface GenerateCommandOptions { type: AssetType /** `--reference-image` values: file paths or http(s) URLs. */ referenceImages?: string[] cwd?: string baseUrl?: string access?: AssetAccess } export async function generateCommand( description: string, opts: GenerateCommandOptions, ): Promise { const referenceImages = await loadReferenceImages(opts.referenceImages ?? []) const { client } = await getCliClient({ baseUrl: opts.baseUrl, requireAuth: true, }) const spinner = startSpinner(`Generating "${description}"`) const message = await (async () => { const started = await startGeneration(client, { description, type: opts.type, referenceImages: referenceImages.length ? referenceImages : undefined, access: opts.access, }) // Fast providers finish inline; slow, job-based ones return a job to poll. if (started.status === 'completed') { return installGenerated(client, started, { cwd: opts.cwd, spinner }) } // Block-and-poll for a bounded window (the command waits, not the agent). If the job outlasts the // window, hand back its id so the caller continues with `generate install`. const outcome = await waitForGeneration(client, started.jobId, { onProgress: (message) => { spinner.text = message }, }) return finishGeneration(client, outcome, { cwd: opts.cwd, spinner, stillRunning: generationStartedResult(started.jobId), }) })().finally(() => spinner.stop()) console.log(message) } interface InstallContext { cwd?: string spinner: Ora } // Finish a poll outcome: install on completion, fail loudly on error, or return the still-running // note (the job keeps running server-side). Shared by `generate` and `generate install`. export async function finishGeneration( client: MarketClient, outcome: PollOutcome, ctx: InstallContext & { stillRunning: string }, ): Promise { if (outcome.status === 'completed') { return installGenerated(client, outcome, ctx) } if (outcome.status === 'failed') { throw new GenerateError(outcome.error) } return ctx.stillRunning } // Install a finished generation into the project and report it. async function installGenerated( client: MarketClient, generated: { assetName: string; version: string }, ctx: InstallContext, ): Promise { ctx.spinner.text = `Resolving ${generated.assetName}@${generated.version}` const request = { name: generated.assetName, range: generated.version, saveRange: `^${generated.version}`, save: true, } const installMetadata = await client.asset.installMetadata() const resolution = await resolve(client.asset, [request]) const result = await runInstall(client, resolution, { cwd: ctx.cwd, rootRequests: [request], installMetadata, onProgress: (message) => { ctx.spinner.text = message }, }) return generatedInstallResult(result, installMetadata) } export function startSpinner(text: string): Ora { return ora({ text, isEnabled: Boolean(process.stderr.isTTY), isSilent: !process.stderr.isTTY, }).start() }