@mesh-tech/mesh-cli
Version:
CLI for Mesh platform development utilities
108 lines (107 loc) • 4.56 kB
JavaScript
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;
}
});
}