import { readFile } from "node:fs/promises"; import { prisma } from "../db.js"; import { OrganizationAgentConfiguration } from "../agent/configuration.js"; import { readSkillStoreRoot, verifyStoredSkill } from "../agent/skillStore.js"; import { readSiloOrganizationId } from "./silo.js"; async function main(argv: readonly string[]): Promise { const [command, ...args] = argv; if (command === undefined || command === "help" || command === "--help") { printHelp(); return; } const options = parseOptions(args); const organizationId = required(options, "organization"); const siloOrganizationId = readSiloOrganizationId(); if (organizationId !== siloOrganizationId) { throw new Error(`Silo Agent configuration is restricted to ${siloOrganizationId}`); } const configuration = new OrganizationAgentConfiguration(prisma, readSkillStoreRoot()); switch (command) { case "install-skill": { const installed = await configuration.installSkill({ organizationId, sourceDir: required(options, "source"), version: required(options, "version"), }); console.log(JSON.stringify(installed)); return; } case "upsert-role": { const systemPromptFile = options.get("system-prompt-file"); const toolsJson = options.get("tools-json"); const tools = toolsJson === undefined ? undefined : toolsJson === "null" ? null : parseTools(toolsJson); const sortOrderRaw = options.get("sort-order"); const role = await configuration.upsertRole({ organizationId, roleId: required(options, "role"), label: required(options, "label"), ...(options.has("model") ? { defaultModel: options.get("model") ?? null } : {}), ...(systemPromptFile !== undefined ? { systemPrompt: await readFile(systemPromptFile, "utf8") } : {}), ...(tools !== undefined ? { tools } : {}), ...(sortOrderRaw !== undefined ? { sortOrder: integer(sortOrderRaw, "sort-order") } : {}), }); console.log(JSON.stringify(role)); return; } case "set-role-skills": { const skills = required(options, "skills").split(",").map((name) => name.trim()).filter(Boolean); await configuration.setRoleSkills({ organizationId, roleId: required(options, "role"), skillNames: skills, }); console.log(JSON.stringify({ roleId: required(options, "role"), skills })); return; } case "list": { const roles = await prisma.organizationAgentRole.findMany({ where: { organizationId }, orderBy: [{ sortOrder: "asc" }, { roleId: "asc" }], include: { skillBindings: { orderBy: [{ sortOrder: "asc" }, { agentSkillId: "asc" }], include: { skill: { select: { name: true, version: true, disabledAt: true } } }, }, }, }); console.log(JSON.stringify(roles.map((role) => ({ roleId: role.roleId, label: role.label, defaultModel: role.defaultModel, systemPromptConfigured: role.systemPrompt !== null, tools: role.tools, disabled: role.disabledAt !== null, skills: role.skillBindings.map((binding) => ({ name: binding.skill.name, version: binding.skill.version, disabled: binding.skill.disabledAt !== null, })), })), null, 2)); return; } case "verify-store": { const skills = await prisma.organizationAgentSkill.findMany({ where: { organizationId, disabledAt: null }, select: { name: true, contentDigest: true }, }); for (const skill of skills) { await verifyStoredSkill({ storeRoot: readSkillStoreRoot(), ...skill }); } console.log(JSON.stringify({ verifiedSkills: skills.length })); return; } default: throw new Error(`unknown Agent configuration command: ${command}`); } } function parseOptions(args: readonly string[]): Map { const options = new Map(); for (let index = 0; index < args.length; index += 2) { const flag = args[index]; const value = args[index + 1]; if (flag === undefined || !flag.startsWith("--") || value === undefined) { throw new Error(`expected --name value, got: ${args.slice(index).join(" ")}`); } const name = flag.slice(2); if (options.has(name)) throw new Error(`duplicate option: --${name}`); options.set(name, value); } return options; } function required(options: ReadonlyMap, name: string): string { const value = options.get(name)?.trim(); if (value === undefined || value === "") throw new Error(`--${name} is required`); return value; } function parseTools(raw: string): readonly string[] { const parsed = JSON.parse(raw) as unknown; if (!Array.isArray(parsed) || parsed.some((value) => typeof value !== "string")) { throw new Error("--tools-json must be null or a JSON string array"); } return parsed; } function integer(raw: string, name: string): number { const value = Number(raw); if (!Number.isSafeInteger(value)) throw new Error(`--${name} must be an integer`); return value; } function printHelp(): void { console.log(`Usage: agent-config install-skill --organization ORG --source DIR --version VERSION agent-config upsert-role --organization ORG --role ID --label LABEL [--model MODEL] [--system-prompt-file FILE] [--tools-json JSON] [--sort-order N] agent-config set-role-skills --organization ORG --role ID --skills name,name agent-config list --organization ORG agent-config verify-store --organization ORG`); } main(process.argv.slice(2)) .catch((error) => { console.error(error instanceof Error ? error.message : String(error)); process.exitCode = 1; }) .finally(async () => { await prisma.$disconnect(); });