UNPKG

@mesh-tech/mesh-cli

Version:

CLI for Mesh platform development utilities

108 lines (107 loc) 4.56 kB
import * as fs from "fs"; import * as path from "path"; import { logError, logInfo, logSuccess, logWarn } from "../utils/log.js"; import { agentApiFetch, describeHttpError, describeNetworkError, resolveTarget } from "./agent-api-client.js"; const DEFAULT_ARTIFACT_ID = "workflow-artifact"; function parseRef(ref) { const colonIdx = ref.indexOf(":"); if (colonIdx === -1) { return { conversationId: ref, artifactId: DEFAULT_ARTIFACT_ID }; } return { conversationId: ref.slice(0, colonIdx), artifactId: ref.slice(colonIdx + 1), }; } const DEFAULT_AUTH_CONTEXT = "mesh.dev"; async function fetchExportBundle(target, conversationId, artifactId) { const apiPath = `/v1/conversations/${encodeURIComponent(conversationId)}/artifacts/${encodeURIComponent(artifactId)}/export`; const response = await agentApiFetch(target, apiPath); if (!response.ok) { throw new Error(await describeHttpError(response, target, { id: conversationId })); } return (await response.json()); } async function getArtifact(ref, options, target) { const { conversationId, artifactId } = parseRef(ref); logInfo(`Fetching ${conversationId}:${artifactId} from ${target.apiBaseUrl}…`); const bundle = await fetchExportBundle(target, conversationId, artifactId); const outputDir = options.output ?? path.join("workflows", bundle.primaryWorkflow); fs.mkdirSync(outputDir, { recursive: true }); let filesWritten = 0; for (const file of bundle.files) { const filePath = path.join(outputDir, file.filename); fs.writeFileSync(filePath, file.code, "utf-8"); logSuccess(` ${file.filename}`); filesWritten++; } if (bundle.overview) { const readmePath = path.join(outputDir, "README.md"); fs.writeFileSync(readmePath, bundle.overview, "utf-8"); logSuccess(` README.md`); filesWritten++; } if (bundle.ir) { const irPath = path.join(outputDir, "workflow-ir.json"); fs.writeFileSync(irPath, JSON.stringify(bundle.ir, null, 2), "utf-8"); logSuccess(` workflow-ir.json`); filesWritten++; } const metaPath = path.join(outputDir, ".workflow-meta.json"); const meta = { conversationId: bundle.conversationId, artifactId, primaryWorkflow: bundle.primaryWorkflow, exportedAt: bundle.exportedAt, files: bundle.files.map((f) => f.filename), }; fs.writeFileSync(metaPath, JSON.stringify(meta, null, 2), "utf-8"); console.log(); logSuccess(`Exported ${filesWritten} files to ${outputDir}/`); if (bundle.files.length > 1) { const primary = bundle.files.find((f) => f.name === bundle.primaryWorkflow); if (primary) { logInfo(`Entry point: ${primary.filename}`); } } logInfo(`Ref: ${conversationId}:${artifactId}`); } export function registerArtifactsCommands(program) { const artifacts = program .command("artifacts") .description("Manage workflow artifacts from AI agent conversations"); artifacts .command("get <ref>") .description("Download artifact files from an agent conversation\n\n" + "Ref format: <conversationId>:<artifactId>\n" + ` Short form uses default artifactId "${DEFAULT_ARTIFACT_ID}"`) .option("-o, --output <dir>", "Output directory (default: workflows/<workflowName>)") .option("--target <name>", "Named agent target from the agent-targets registry") .option("--api-url <url>", "Agent API URL (overrides --target; ad-hoc, no registry lookup)") .option("--context <ctx>", `Zitadel auth context, used with --api-url (default: ${DEFAULT_AUTH_CONTEXT})`) .action(async (ref, opts) => { let target; try { target = resolveTarget(opts); } catch (error) { logError(error instanceof Error ? error.message : String(error)); process.exitCode = 1; return; } try { await getArtifact(ref, opts, target); } catch (error) { const message = error instanceof Error ? error.message : String(error); logError(message); const hint = describeNetworkError(error, target); if (hint !== message) { logWarn(hint); logWarn("Or specify the URL: mesh artifacts get <ref> --api-url https://your-agent-api.example.com"); } process.exitCode = 1; } }); }