Files
stack/packages/cli/src/plugins.mjs
T
jason.woltjeandClaude Opus 5.5 fba879f6a1 mosaic plugins set: a patch changes only the kinds it names; null removes a kind (vikunja:19/309, row 145)
Jason-approved merge, rebased: git diff f4d7b91a9..0e7d1d8b8 from
work/vikunja-19-309, stacked on 305. Business reviewer verdict dfd44af2
approve on 0e7d1d8b8; Jason resolved merge decision 12a20e47 via the CLI.

No conflict. packages/plugins/README.md, which refactor also changed,
merged with the source commit's +/- lines; the other four paths are the
source blobs. Filbert reviews the rebased diff.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-15 17:50:59 -05:00

44 lines
2.0 KiB
JavaScript

// `mosaic plugins` (vikunja:19/305, REQ-PLUG-1): read and switch the organization file's plug-in
// modules through the broker's human-only plugins and plugins.set verbs. The broker refuses an
// agent, a reader and a tester, a stale revision, a bad shape and a module that doesn't exist, and
// the change is logged with its actor in the organization's changes.jsonl.
import { readFileSync } from "node:fs";
import { CliError } from "./errors.mjs";
export const PLUGINS_USAGE = "mosaic plugins [--business ID] [--json] | set --file PATCH [--business ID]";
function parse(args) {
const out = { positional: [], flags: new Set(), values: {} };
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (arg === "--json") out.flags.add(arg);
else if (["--file", "--business"].includes(arg)) {
if (!args[i + 1] || Object.hasOwn(out.values, arg)) throw new CliError(PLUGINS_USAGE, 4);
out.values[arg] = args[++i];
} else if (arg.startsWith("--")) throw new CliError(PLUGINS_USAGE, 4);
else out.positional.push(arg);
}
return out;
}
// PATCH is { "revision": <from a read>, "plugins": { "<kind>": <name or [names] or null> } }. It names
// the kinds it changes: a kind with a value is switched to it, a kind with null is removed, and a
// kind it doesn't name keeps its setting (vikunja:19/309). An empty patch is refused.
export async function pluginsCommand(args, io, callFor) {
const parsed = parse(args);
const verb = parsed.positional[0] ?? "view";
if (parsed.positional.length > 1 || !["view", "set"].includes(verb) || (verb === "set") !== Boolean(parsed.values["--file"])) {
throw new CliError(PLUGINS_USAGE, 4);
}
try {
const call = callFor(parsed);
const result = verb === "set"
? await call("plugins.set", JSON.parse(readFileSync(parsed.values["--file"], "utf8")))
: await call("plugins", {});
io.stdout.write(JSON.stringify(result, null, 2) + "\n");
} catch (error) {
if (error instanceof CliError) throw error;
throw new CliError(error.message, 2);
}
}