UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

602 lines (601 loc) 25.6 kB
import { t as assertExperimentalClawsEnabled } from "./experimental-BMzbGmT5.js"; import { t as coerceErrorMessage } from "./error-coercion-D_-xJ90S.js"; import { a as writeRuntimeJson, r as defaultRuntime } from "./runtime-CF2WjnNZ.js"; import { s as readConfigFileSnapshot } from "./io.runtime-B9iJRs3w.js"; import { w as root } from "./fs-safe-B6pvPGnf.js"; import { m as resolveAgentWorkspaceDir, o as listAgentIds } from "./agent-scope-config-DcbEhP0R.js"; import { r as normalizeConfiguredMcpServers } from "./mcp-config-normalize-CWrDNtye.js"; import "./config-Cs0XXL3x.js"; import { d as isCanonicalClawHubPackageName, i as MAX_MANAGED_FILE_BYTES, m as portableClawPathKey, n as readClawManifestFile, p as isSafeClawRelativePath, v as CLAW_OUTPUT_STABILITY } from "./reader-lgRaBKW0.js"; import { a as buildClawAddPlan, n as formatClawDiagnostics, r as logClawExperimentalWarning, t as emitClawFailure } from "./claws-cli-output-BH0EUOJC.js"; import { basename, dirname, isAbsolute, join, parse, relative, resolve, sep } from "node:path"; import { chmod, link, lstat, mkdir, mkdtemp, readFile, readdir, realpath, rm, rmdir, stat, unlink, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { createHash } from "node:crypto"; import * as tar from "tar"; //#region src/claws/project.ts const CLAW_PROJECT_RESULT_SCHEMA_VERSION = "openclaw.clawProject.v1"; const MAX_PACKAGE_JSON_BYTES = 262144; const MAX_PROJECT_ENTRIES = 4096; const AGENT_ID_PATTERN = /^[a-z][a-z0-9_-]{0,63}$/; var ClawProjectError = class extends Error { constructor(code, message) { super(message); this.code = code; this.name = "ClawProjectError"; } }; function diagnostic(code, path, message) { return { level: "error", code, phase: "policy", path, message }; } function defaultSlug(value) { return value.toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^[^a-z]+/, "").replace(/-+$/g, "").slice(0, 64) || "my-claw"; } function displayName(agentId) { return agentId.split(/[-_]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" "); } async function pathState(path) { const entry = await lstat(path).catch(() => void 0); if (!entry) return "missing"; if (!entry.isDirectory()) return "occupied"; return (await readdir(path)).length === 0 ? "empty-directory" : "occupied"; } async function isFile(path) { return lstat(path).then((entry) => entry.isFile()).catch(() => false); } async function isConfinedManifestFile(root) { const manifestPath = resolve(root, "CLAW.md"); const entry = await lstat(manifestPath).catch(() => void 0); if (entry?.isFile()) return true; if (!entry?.isSymbolicLink()) return false; const [rootReal, targetReal] = await Promise.all([realpath(root).catch(() => void 0), realpath(manifestPath).catch(() => void 0)]); if (!rootReal || !targetReal) return false; const targetRelative = relative(rootReal, targetReal); if (targetRelative === "" || targetRelative === ".." || targetRelative.startsWith(`..${sep}`) || isAbsolute(targetRelative) || isExcludedProjectSource(targetRelative)) return false; return lstat(targetReal).then((target) => target.isFile()).catch(() => false); } async function discoverClawProjectRoot(projectPath) { const input = resolve(projectPath); const inputStat = await lstat(input).catch(() => void 0); if (!inputStat) throw new ClawProjectError("project_not_found", `Could not resolve Claw project path ${JSON.stringify(input)}.`); let current = inputStat.isDirectory() ? input : dirname(input); const roots = []; const filesystemRoot = parse(current).root; while (true) { if (await isFile(resolve(current, "package.json")) && await isConfinedManifestFile(current)) roots.push(await realpath(current)); if (current === filesystemRoot) break; current = dirname(current); } if (roots.length === 0) throw new ClawProjectError("project_not_found", `No Claw project containing package.json and CLAW.md was found from ${JSON.stringify(input)}.`); if (roots.length > 1) throw new ClawProjectError("ambiguous_project_root", `Multiple Claw project roots contain ${JSON.stringify(input)}: ${roots.join(", ")}.`); return roots[0]; } function projectPathKey(value, caseInsensitive) { const normalized = value.normalize("NFC"); return caseInsensitive ? normalized.toLowerCase() : normalized; } function isExcludedProjectSource(value) { return portableClawPathKey(value).split("/").some((segment) => segment === ".git" || segment === "node_modules"); } async function isCaseInsensitiveProjectRoot(root) { const [canonical, folded] = await Promise.all([lstat(resolve(root, "CLAW.md")).catch(() => void 0), lstat(resolve(root, "claw.md")).catch(() => void 0)]); return Boolean(canonical && folded && canonical.dev === folded.dev && canonical.ino === folded.ino); } async function collectExcludedPaths(root, selectedPaths) { const excluded = []; const caseInsensitive = await isCaseInsensitiveProjectRoot(root); const selectedPathKeys = new Set([...selectedPaths].map((path) => projectPathKey(path, caseInsensitive))); let entryCount = 0; const visit = async (directory) => { const entries = await readdir(resolve(root, directory), { withFileTypes: true }); for (const entry of entries) { entryCount += 1; if (entryCount > MAX_PROJECT_ENTRIES) throw new ClawProjectError("project_too_many_entries", `Claw projects may contain at most ${MAX_PROJECT_ENTRIES} entries outside excluded dependency and source-control trees.`); const path = directory ? `${directory}/${entry.name}` : entry.name; if (entry.isDirectory()) { if (entry.name === ".git" || entry.name === "node_modules") excluded.push(`${path}/`); else await visit(path); } else if (!selectedPathKeys.has(projectPathKey(path, caseInsensitive))) excluded.push(path); } }; await visit(""); return excluded.toSorted((left, right) => Buffer.compare(Buffer.from(left), Buffer.from(right))); } async function createClawProject(projectPath, options = {}) { const root = resolve(projectPath); const initialState = await pathState(root); if (initialState === "occupied") throw new ClawProjectError("project_target_not_empty", `Claw project target ${JSON.stringify(root)} must be absent or empty.`); const agentId = options.agentId ?? defaultSlug(basename(root)); if (!AGENT_ID_PATTERN.test(agentId)) throw new ClawProjectError("invalid_agent_id", `Agent id ${JSON.stringify(agentId)} must match ${AGENT_ID_PATTERN}.`); const name = options.name ?? agentId; if (!isCanonicalClawHubPackageName(name)) throw new ClawProjectError("invalid_package_name", `Package name ${JSON.stringify(name)} must be a canonical ClawHub package name.`); const packageJson = { name, version: "0.1.0", openclaw: { claw: "CLAW.md" } }; const clawMarkdown = [ "---", "schemaVersion: 1", "agent:", ` id: ${JSON.stringify(agentId)}`, ` name: ${JSON.stringify(displayName(agentId))}`, "---", `You are ${displayName(agentId)}, a purpose-built OpenClaw agent.`, "" ].join("\n"); const packageJsonPath = resolve(root, "package.json"); const clawMarkdownPath = resolve(root, "CLAW.md"); const createdPaths = []; await mkdir(root, { recursive: true }); try { await writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`, { encoding: "utf8", flag: "wx" }); createdPaths.push(packageJsonPath); await writeFile(clawMarkdownPath, clawMarkdown, { encoding: "utf8", flag: "wx" }); createdPaths.push(clawMarkdownPath); } catch (error) { await Promise.allSettled(createdPaths.map((path) => unlink(path))); if (initialState === "missing") await rmdir(root).catch(() => void 0); throw error; } return { root, packageJson, filesWritten: ["package.json", "CLAW.md"] }; } async function validateClawProject(projectPath) { let root$1; try { root$1 = await discoverClawProjectRoot(projectPath); } catch (error) { return { ok: false, root: resolve(projectPath), diagnostics: [diagnostic(error instanceof ClawProjectError ? error.code : "project_discovery_failed", "$", coerceErrorMessage(error))] }; } let packageValue; try { const read = await (await root(root$1)).read("package.json", { hardlinks: "reject", maxBytes: MAX_PACKAGE_JSON_BYTES, nonBlockingRead: true, symlinks: "reject" }); packageValue = JSON.parse(read.buffer.toString("utf8")); } catch (error) { return { ok: false, root: root$1, diagnostics: [diagnostic("invalid_project_package", "package.json", `Could not read a safe project package.json: ${error.message}`)] }; } const record = packageValue && typeof packageValue === "object" && !Array.isArray(packageValue) ? packageValue : void 0; const openclaw = record?.openclaw && typeof record.openclaw === "object" && !Array.isArray(record.openclaw) ? record.openclaw : void 0; const scripts = record?.scripts; const diagnostics = []; if (openclaw?.claw !== "CLAW.md") diagnostics.push(diagnostic("project_manifest_must_be_claw_markdown", "package.json.openclaw.claw", "A Claw project must set openclaw.claw to \"CLAW.md\".")); if (scripts !== void 0 && (typeof scripts !== "object" || scripts === null || Array.isArray(scripts) || Object.keys(scripts).length > 0)) diagnostics.push(diagnostic("project_scripts_forbidden", "package.json.scripts", "Claw projects cannot declare package scripts or lifecycle hooks.")); if (diagnostics.length > 0) return { ok: false, root: root$1, diagnostics }; const claw = await readClawManifestFile(root$1); if (!claw.ok) return { ok: false, root: root$1, diagnostics: claw.diagnostics }; const excludedSource = [...claw.snapshot.openClawProfile ? [{ path: claw.snapshot.openClawProfile.sourcePath, diagnosticPath: "$.metadata.openclaw.config" }] : [], ...claw.snapshot.workspaceSources.map((source) => ({ path: source.sourcePath, diagnosticPath: "$.workspace" }))].find((source) => isExcludedProjectSource(source.path)); if (excludedSource) return { ok: false, root: root$1, diagnostics: [diagnostic("project_excluded_source", excludedSource.diagnosticPath, `Selected project source ${JSON.stringify(excludedSource.path)} cannot come from .git or node_modules.`)] }; const reservedPackageSource = claw.snapshot.workspaceSources.find((source) => source.sourcePath.normalize("NFC").toLowerCase() === "package.json"); if (reservedPackageSource) return { ok: false, root: root$1, diagnostics: [diagnostic("project_invalid", "$.workspace.files", `Workspace source ${JSON.stringify(reservedPackageSource.sourcePath)} collides with generated package metadata.`)] }; const selectedPathList = [ "package.json", "CLAW.md", ...claw.packageBootstrap ? ["BOOTSTRAP.md"] : [], ...claw.snapshot.openClawProfile ? [claw.snapshot.openClawProfile.sourcePath] : [], ...claw.snapshot.workspaceSources.map((source) => source.sourcePath) ]; const portableSelectedPaths = /* @__PURE__ */ new Map(); for (const path of selectedPathList) { const key = portableClawPathKey(path); const existing = portableSelectedPaths.get(key); if (existing && existing !== path) return { ok: false, root: root$1, diagnostics: [diagnostic("project_path_collision", "$", `Selected project paths ${JSON.stringify(existing)} and ${JSON.stringify(path)} collide on portable filesystems.`)] }; portableSelectedPaths.set(key, path); } const selectedPaths = new Set(selectedPathList); let excludedPaths; try { excludedPaths = await collectExcludedPaths(root$1, selectedPaths); } catch (error) { return { ok: false, root: root$1, diagnostics: [diagnostic(error instanceof ClawProjectError ? error.code : "project_enumeration_failed", "$", coerceErrorMessage(error))] }; } return { ok: true, root: root$1, packageJson: { name: claw.source.name, version: claw.source.version, ...typeof record?.type === "string" ? { type: record.type } : {}, openclaw: { claw: "CLAW.md" } }, claw, excludedPaths, diagnostics: claw.diagnostics }; } //#endregion //#region src/claws/project-build.ts const CLAW_BUILD_RESULT_SCHEMA_VERSION = "openclaw.clawBuild.v1"; const ZLIB_FILTERED_STRATEGY = 1; async function writeStagedFile(stagingRoot, path, content) { const target = resolve(stagingRoot, path); const targetRelative = relative(stagingRoot, target); if (!isSafeClawRelativePath(path) || targetRelative === ".." || targetRelative.startsWith(`..${sep}`) || isAbsolute(targetRelative)) throw new ClawProjectError("unsafe_build_path", `Cannot package unsafe path ${JSON.stringify(path)}.`); await mkdir(dirname(target), { recursive: true, mode: 493 }); await writeFile(target, content, { flag: "wx", mode: 420 }); await chmod(target, 420); } async function readSelectedProjectFile(projectRoot, path) { return (await (await root(projectRoot)).read(path, { hardlinks: "reject", maxBytes: MAX_MANAGED_FILE_BYTES, nonBlockingRead: true, symlinks: path === "CLAW.md" ? "follow-within-root" : "reject" })).buffer; } function assertValidatedBytes(path, bytes, expected) { const digest = `sha256:${createHash("sha256").update(bytes).digest("hex")}`; if (bytes.byteLength !== expected.byteLength || digest !== expected.digest) throw new ClawProjectError("project_changed_during_build", `Claw project input ${JSON.stringify(path)} changed after validation; retry the build from a stable snapshot.`); } async function extractBuiltClawArtifact(artifact) { const temporaryDirectory = await mkdtemp(join(tmpdir(), "openclaw-claw-artifact-")); try { await tar.x({ cwd: temporaryDirectory, file: resolve(artifact), strict: true }); const packageRoot = join(temporaryDirectory, "package"); if (!(await lstat(packageRoot)).isDirectory()) throw new Error("artifact does not contain a package directory"); return { temporaryDirectory, packageRoot, dispose: () => rm(temporaryDirectory, { recursive: true, force: true }) }; } catch (error) { await rm(temporaryDirectory, { recursive: true, force: true }); throw new ClawProjectError("artifact_verification_failed", `Could not extract built Claw artifact: ${error.message}`); } } async function buildClawProject(projectPath, outputPath) { const project = await validateClawProject(projectPath); if (!project.ok) throw new ClawProjectError("project_invalid", project.diagnostics.map((item) => `${item.code}: ${item.message}`).join("\n")); const artifact = resolve(outputPath); if (!artifact.toLowerCase().endsWith(".tgz")) throw new ClawProjectError("invalid_artifact_path", "Claw build output must end in .tgz."); if (await lstat(artifact).catch(() => void 0)) throw new ClawProjectError("artifact_exists", `Refusing to overwrite existing artifact ${JSON.stringify(artifact)}.`); if (!(await stat(dirname(artifact)).catch(() => void 0))?.isDirectory()) throw new ClawProjectError("artifact_parent_missing", `Artifact parent directory ${JSON.stringify(dirname(artifact))} does not exist.`); const temporaryDirectory = await mkdtemp(join(dirname(artifact), ".openclaw-claw-build-")); const stagingRoot = join(temporaryDirectory, "staging"); const temporaryArtifact = join(temporaryDirectory, "claw.tgz"); try { await mkdir(stagingRoot, { mode: 493 }); const files = /* @__PURE__ */ new Map(); files.set("package.json", `${JSON.stringify(project.packageJson, null, 2)}\n`); const clawMarkdown = await readSelectedProjectFile(project.root, "CLAW.md"); assertValidatedBytes("CLAW.md", clawMarkdown, project.claw.snapshot.manifest); files.set("CLAW.md", clawMarkdown); if (project.claw.packageBootstrap) { const bootstrap = await readSelectedProjectFile(project.root, "BOOTSTRAP.md"); assertValidatedBytes("BOOTSTRAP.md", bootstrap, project.claw.packageBootstrap); files.set("BOOTSTRAP.md", bootstrap); } if (project.claw.openClawProfile) { const profileSnapshot = project.claw.snapshot.openClawProfile; if (!profileSnapshot) throw new ClawProjectError("project_invalid", "Validated OpenClaw profile is missing its source snapshot."); const profile = await readSelectedProjectFile(project.root, profileSnapshot.sourcePath); assertValidatedBytes(profileSnapshot.sourcePath, profile, profileSnapshot); files.set(profileSnapshot.sourcePath, profile); } for (const source of project.claw.snapshot.workspaceSources) { const bytes = await readSelectedProjectFile(project.root, source.sourcePath); assertValidatedBytes(source.sourcePath, bytes, source); files.set(source.sourcePath, bytes); } const fileNames = [...files.keys()].toSorted((left, right) => Buffer.compare(Buffer.from(left), Buffer.from(right))); for (const fileName of fileNames) await writeStagedFile(stagingRoot, fileName, files.get(fileName)); const tarInputNames = fileNames.map((fileName) => fileName.startsWith("@") ? `./${fileName}` : fileName); await tar.c({ cwd: stagingRoot, file: temporaryArtifact, gzip: { level: 9, portable: true, strategy: ZLIB_FILTERED_STRATEGY }, mtime: /* @__PURE__ */ new Date(0), portable: true, prefix: "package" }, tarInputNames); const archiveEntries = []; await tar.t({ file: temporaryArtifact, onentry: (entry) => archiveEntries.push({ path: entry.path, type: entry.type }) }); const expectedEntries = fileNames.map((path) => ({ path: `package/${path}`, type: "File" })); if (JSON.stringify(archiveEntries) !== JSON.stringify(expectedEntries)) throw new ClawProjectError("artifact_contents_mismatch", "Built artifact contents differ from the validated project selection."); const packed = await readFile(temporaryArtifact); const integrity = `sha256:${createHash("sha256").update(packed).digest("hex")}`; const extracted = await extractBuiltClawArtifact(temporaryArtifact); try { const reread = await readClawManifestFile(extracted.packageRoot); if (!reread.ok) throw new ClawProjectError("artifact_verification_failed", reread.diagnostics.map((item) => `${item.code}: ${item.message}`).join("\n")); if (reread.source.name !== project.packageJson.name || reread.source.version !== project.packageJson.version) throw new ClawProjectError("artifact_identity_mismatch", "Built artifact identity differs from the validated project."); } finally { await extracted.dispose(); } try { await link(temporaryArtifact, artifact); } catch (error) { if (error.code === "EEXIST") throw new ClawProjectError("artifact_exists", `Refusing to overwrite existing artifact ${JSON.stringify(artifact)}.`); throw new ClawProjectError("artifact_atomic_publish_failed", `Could not atomically publish artifact ${JSON.stringify(artifact)}: ${error.message}`); } return { schemaVersion: CLAW_BUILD_RESULT_SCHEMA_VERSION, projectSchemaVersion: CLAW_PROJECT_RESULT_SCHEMA_VERSION, artifact, integrity, byteLength: packed.byteLength, files: fileNames, excludedPaths: project.excludedPaths, claw: { name: project.packageJson.name, version: project.packageJson.version } }; } finally { await rm(temporaryDirectory, { recursive: true, force: true }); } } //#endregion //#region src/cli/claws-cli.project.ts const CLAW_DEV_RESULT_SCHEMA_VERSION = "openclaw.clawDev.v1"; function reportProjectError(error, fallbackCode, schemaVersion, json, runtime) { const code = error instanceof ClawProjectError ? error.code : fallbackCode; const message = error instanceof Error ? error.message : String(error); emitClawFailure(runtime, json, message, { schemaVersion, stability: CLAW_OUTPUT_STABILITY, ok: false, error: { code, message } }); } function logDevPlanSummary(plan, runtime) { runtime.log(`Agent: ${plan.agent.finalId}`); runtime.log(`Workspace: ${plan.agent.workspace}`); runtime.log(`Actions: ${plan.summary.totalActions}`); runtime.log(`Capability escalations: ${plan.capabilityChanges.length}`); runtime.log(`Blocked actions: ${plan.summary.blockedActions}`); } async function prepareDev(projectPath, opts) { const temporaryDirectory = await mkdtemp(join(tmpdir(), "openclaw-claw-dev-")); try { const build = await buildClawProject(projectPath, join(temporaryDirectory, "claw.tgz")); const extracted = await extractBuiltClawArtifact(build.artifact); try { const result = await readClawManifestFile(extracted.packageRoot); if (!result.ok) throw new ClawProjectError("artifact_verification_failed", formatClawDiagnostics(result.diagnostics)); const configSnapshot = await readConfigFileSnapshot({ observe: false, skipPluginValidation: true }); if (!configSnapshot.valid) throw new ClawProjectError("config_unavailable", "OpenClaw config is invalid; fix it before previewing a Claw project."); const config = configSnapshot.resolved; const existingMcpServers = normalizeConfiguredMcpServers(config.mcp?.servers); const existingAgentIds = listAgentIds(config); return { build, plan: await buildClawAddPlan({ manifest: result.manifest, clawMarkdownBody: result.clawMarkdownBody, packageBootstrap: result.packageBootstrap, openClawProfile: result.openClawProfile, source: { ...result.source, integrityKind: "artifact", integrity: build.integrity, byteLength: build.byteLength }, diagnostics: result.diagnostics, context: { ...opts.agentId ? { agentId: opts.agentId } : {}, ...opts.workspace ? { workspace: opts.workspace } : {}, existingAgentIds, existingWorkspacePaths: existingAgentIds.map((agentId) => resolveAgentWorkspaceDir(config, agentId)), existingMcpServers, sourceReferenceRoot: `claw-artifact:${build.integrity}` } }) }; } finally { await extracted.dispose(); } } finally { await rm(temporaryDirectory, { recursive: true, force: true }); } } async function runClawsCreateCommand(projectPath, opts, runtime = defaultRuntime) { assertExperimentalClawsEnabled(); try { const result = await createClawProject(projectPath, { ...opts.name ? { name: opts.name } : {}, ...opts.agentId ? { agentId: opts.agentId } : {} }); if (opts.json) { writeRuntimeJson(runtime, { schemaVersion: CLAW_PROJECT_RESULT_SCHEMA_VERSION, stability: CLAW_OUTPUT_STABILITY, ok: true, ...result }); return; } logClawExperimentalWarning(runtime); runtime.log(`Created Claw project: ${result.root}`); runtime.log(`Package: ${result.packageJson.name}@${result.packageJson.version}`); } catch (error) { reportProjectError(error, "project_create_failed", CLAW_PROJECT_RESULT_SCHEMA_VERSION, opts.json, runtime); } } async function runClawsValidateCommand(projectPath, opts, runtime = defaultRuntime) { assertExperimentalClawsEnabled(); const result = await validateClawProject(projectPath); if (!result.ok) { emitClawFailure(runtime, opts.json, formatClawDiagnostics(result.diagnostics), { schemaVersion: CLAW_PROJECT_RESULT_SCHEMA_VERSION, stability: CLAW_OUTPUT_STABILITY, ok: false, root: result.root, diagnostics: result.diagnostics }); return; } if (opts.json) { writeRuntimeJson(runtime, { schemaVersion: CLAW_PROJECT_RESULT_SCHEMA_VERSION, stability: CLAW_OUTPUT_STABILITY, ok: true, root: result.root, source: result.claw.source, manifest: result.claw.manifest, ...result.claw.openClawProfile ? { openClawProfile: result.claw.openClawProfile } : {}, excludedPaths: result.excludedPaths, diagnostics: result.diagnostics }); return; } logClawExperimentalWarning(runtime); runtime.log(`Valid Claw project: ${result.root}`); runtime.log(`Package: ${result.packageJson.name}@${result.packageJson.version}`); for (const path of result.excludedPaths) runtime.log(`Excluded: ${path}`); } async function runClawsBuildCommand(projectPath, opts, runtime = defaultRuntime) { assertExperimentalClawsEnabled(); try { const result = await buildClawProject(projectPath, opts.out); if (opts.json) { writeRuntimeJson(runtime, { ...result, stability: CLAW_OUTPUT_STABILITY, ok: true }); return; } logClawExperimentalWarning(runtime); runtime.log(`Built Claw: ${result.claw.name}@${result.claw.version}`); runtime.log(`Artifact: ${result.artifact}`); runtime.log(`Integrity: ${result.integrity}`); runtime.log(`Excluded project paths: ${result.excludedPaths.length}`); } catch (error) { reportProjectError(error, "project_build_failed", CLAW_BUILD_RESULT_SCHEMA_VERSION, opts.json, runtime); } } async function runClawsDevCommand(projectPath, opts, runtime = defaultRuntime) { assertExperimentalClawsEnabled(); let prepared; try { prepared = await prepareDev(projectPath, opts); } catch (error) { reportProjectError(error, "project_dev_failed", CLAW_DEV_RESULT_SCHEMA_VERSION, opts.json, runtime); return; } const { build, plan } = prepared; if (opts.json) writeRuntimeJson(runtime, { schemaVersion: CLAW_DEV_RESULT_SCHEMA_VERSION, stability: CLAW_OUTPUT_STABILITY, offline: true, mutationAllowed: false, build: { integrity: build.integrity, byteLength: build.byteLength, files: build.files, excludedPaths: build.excludedPaths, claw: build.claw }, plan }); else { logClawExperimentalWarning(runtime); runtime.log(`Claw dev preview: ${build.claw.name}@${build.claw.version}`); runtime.log(`Artifact integrity: ${build.integrity}`); logDevPlanSummary(plan, runtime); if (plan.blockers.length > 0) runtime.error(formatClawDiagnostics(plan.blockers)); } if (plan.blockers.length > 0) runtime.exit(1); } //#endregion export { runClawsBuildCommand, runClawsCreateCommand, runClawsDevCommand, runClawsValidateCommand };