← Files CARBON LiteARCHIVED FILE

src/cli.mjs

3.8 KB · Nov 3, 2026 · 19:00 UTC

↓ Download file

import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { Store } from "./store.mjs";
import { exportWorkspace } from "./report.mjs";
import { demo } from "./demo.mjs";

const plugin = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
export const actions = [
  "start",
  "update",
  "snapshot",
  "report",
  "screenshot",
  "settings",
  "feedback",
  "demo",
];
export function execute(action, input) {
  if (!actions.includes(action))
    throw Error("Unknown action. Use: " + actions.join(", "));
  if (!input || Array.isArray(input) || typeof input !== "object")
    throw Error("Expected a JSON object.");
  if (typeof input.root !== "string" || !path.isAbsolute(input.root))
    throw Error("Use an explicit absolute project root.");
  const root = fs.realpathSync(input.root);
  if (!fs.statSync(root).isDirectory())
    throw Error("Project root must be a directory.");
  if (action === "demo") return demo(plugin, root, input);
  const store = new Store(path.join(root, ".carbon/studio-lite"));
  let result;
  const { root: unused, ...args } = input;
  if (action === "start") {
    if (typeof args.title !== "string" || !args.title.trim())
      throw Error("Run title required.");
    result = store.start({
      root,
      title: args.title,
      target: args.target || "",
    });
  }
  if (action === "update") result = store.update(args);
  if (action === "screenshot") result = store.screenshot(args);
  if (action === "settings") {
    if (args.patch && !Number.isInteger(args.revision))
      throw Error("Read the snapshot and supply its revision before changing settings.");
    if (args.patch) store.settings(args.patch, args.revision);
    result = store.snapshot().settings;
  }
  if (action === "feedback") {
    if (args.confirm !== true)
      throw Error(
        "Inspect feedback, then pass confirm:true to apply local settings and priorities. It does not authorize testing or fixes.",
      );
    result = store.feedback(args.feedback);
  }
  if (action === "snapshot") {
    const snapshot = store.snapshot();
    if (args.runId) {
      snapshot.runs = snapshot.runs.filter((r) => r.id === args.runId);
      if (!snapshot.runs.length) throw Error("Run not found.");
    }
    // Do not spend model context on base64. Full images remain in portable exports.
    snapshot.runs = snapshot.runs.map((r) => ({
      ...r,
      pages: r.pages.map(({ image, ...p }) => ({ ...p, hasImage: !!image })),
    }));
    return snapshot;
  }
  const files = exportWorkspace(store, plugin);
  if (action === "report") return files;
  // Do not repeat screenshot base64 or project root into the model response.
  return {
    result:
      action === "start" || action === "update" || action === "screenshot"
        ? { runId: result.id, revision: result.revision, status: result.status }
        : action === "settings"
          ? result
          : { saved: true },
    ...files,
  };
}
if (
  process.argv[1] &&
  fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url))
) {
  try {
    if (process.argv.length !== 4)
      throw Error(
        "Usage: node scripts/carbon.mjs ACTION input.json (or - for stdin). No server is started.",
      );
    const inputFile = process.argv[3];
    if (inputFile !== "-" && fs.statSync(inputFile).size > 12_000_000)
      throw Error("Input exceeds 12 MB.");
    let content = "";
    if (inputFile === "-")
      for await (const chunk of process.stdin) {
        content += chunk;
        if (content.length > 12_000_000) throw Error("Input exceeds 12 MB.");
      }
    else content = fs.readFileSync(inputFile, "utf8");
    console.log(
      JSON.stringify(execute(process.argv[2], JSON.parse(content)), null, 2),
    );
  } catch (e) {
    console.error(JSON.stringify({ error: e.message }));
    process.exitCode = 1;
  }
}

SHA-256: 94e5aa2925609ead0a9fd1f4f45f75d80742e4fed2f9d4b552380b95a428c466