UNPKG

@blundergoat/goat-flow

Version:

AI coding agent harness and local dashboard for Claude Code, OpenAI Codex, Google Antigravity, and GitHub Copilot - setup audits, guardrails, structured skills, deny hooks, and persistent learning loops.

90 lines 4.31 kB
/** * Implements the `hooks` command family (list / sync / enable / disable) for the CLI. * It is a thin presentation+validation layer over the server-side hook registrar: it lazy-imports * the registrar so the heavy module only loads when a hooks command actually runs, picks JSON vs * the compact text table from `--format`, and translates the registrar's typed errors into * CLIErrors with the right exit code (404 -> usage error 2, everything else -> failure 1). */ import { CLIError } from "./cli-error.js"; import { writeOutput } from "./cli-output.js"; /** Render hook state as a compact terminal table. */ function renderHooksText(hooks) { const lines = ["Hook state", ""]; for (const hook of hooks) { const agents = hook.agents && typeof hook.agents === "object" ? hook.agents : {}; const agentBits = Object.entries(agents).map(([agentId, state]) => { if (state.supported === false) return `${agentId}: not-supported`; const installed = state.installed === true ? "installed" : "missing"; const drift = typeof state.drift === "string" ? ` (${state.drift})` : ""; return `${agentId}: ${installed}${drift}`; }); lines.push(`${String(hook.id)} ${hook.enabled === true ? "enabled" : "disabled"} ${agentBits.join(", ")}`); } return lines.join("\n"); } /** * Assert a hook id is present for the enable/disable toggles, which cannot run without a target. * Throws a usage CLIError (exit 2) naming the offending subcommand when the id is missing; the * parser normally enforces this, so a throw here is a defensive guard for direct callers. */ function requireHookId(options) { if (options.hookId) return options.hookId; throw new CLIError(`hooks ${options.hookSubcommand} requires <hook-id>.`, 2); } function renderHooksResult(options, result) { writeOutput(options, options.format === "json" ? JSON.stringify(result, null, 2) : renderHooksText(result.hooks)); } /** * Render the single hook returned by an enable/disable toggle, reusing the list table for one row. * Emits JSON wrapping the hook under a `hook` key when `--format json`, otherwise the one-row text * table, so toggle output stays shape-compatible with `hooks list` for scripts that parse either. */ function renderHookToggleResult(options, hook) { writeOutput(options, options.format === "json" ? JSON.stringify({ hook }, null, 2) : renderHooksText([hook])); } /** * Handle the hooks command, dispatching list/sync/enable/disable to the lazily-imported registrar. * Reports registrar failures as CLIErrors: a HookRegistrarError 404 (unknown hook) throws exit 2, * any other registrar error throws exit 1, and non-registrar errors are rethrown unchanged. An * unrecognised subcommand that reaches the end throws a usage CLIError (exit 2) with the syntax. * * @param options - parsed CLI options; reads `hookSubcommand`, `hookId`, `projectPath`, and `format` * @returns a promise that resolves once output is written; rejects (throws) on the error paths above */ export async function handleHooksCommand(options) { const { applyHookState, HookRegistrarError, readAllHookStates, syncHookStates, } = await import("./server/hook-registrar.js"); try { switch (options.hookSubcommand) { case "list": renderHooksResult(options, { hooks: readAllHookStates(options.projectPath), }); return; case "sync": renderHooksResult(options, { hooks: syncHookStates(options.projectPath), }); return; case "enable": case "disable": renderHookToggleResult(options, applyHookState(requireHookId(options), options.hookSubcommand === "enable", options.projectPath)); return; } } catch (err) { if (err instanceof HookRegistrarError) { throw new CLIError(err.message, err.statusCode === 404 ? 2 : 1); } throw err; } throw new CLIError("Usage: goat-flow hooks <list|sync|enable <hook-id>|disable <hook-id>> [path]", 2); } //# sourceMappingURL=hooks-command.js.map