import { type ExtensionAPI, type ExtensionCommandContext, getSettingsListTheme, } from "@earendil-works/pi-coding-agent"; import { type SettingItem, SettingsList } from "@earendil-works/pi-tui"; import type { CommandConfigStore } from "./config-store"; import { DEFAULT_EXTENSION_CONFIG, type PermissionSystemExtensionConfig, } from "./extension-config"; import type { Ruleset } from "./rule"; interface PermissionSystemConfigController { config: CommandConfigStore; /** Precomputed global config file path. */ configPath: string; /** Returns the composed config-layer ruleset for the active agent scope. */ getActiveAgentConfigRules(): Ruleset; } const ON_OFF = ["on", "off"]; const COMMAND_ARGUMENTS = [ { value: "show", label: "Show active settings", description: "Display the current permission-system config summary", }, { value: "path", label: "Show config path", description: "Display the config.json path used by pi-permission-system", }, { value: "reset", label: "Reset defaults", description: "Restore default yolo/logging settings and persist them", }, { value: "help", label: "Show help", description: "Display command usage", }, ] as const; const USAGE_TEXT = "Usage: /permission-system [show|path|reset|help] (or run /permission-system with no args to open settings modal)"; function cloneDefaultConfig(): PermissionSystemExtensionConfig { return { debugLog: DEFAULT_EXTENSION_CONFIG.debugLog, permissionReviewLog: DEFAULT_EXTENSION_CONFIG.permissionReviewLog, yoloMode: DEFAULT_EXTENSION_CONFIG.yoloMode, doublePressToConfirm: DEFAULT_EXTENSION_CONFIG.doublePressToConfirm, }; } function toOnOff(value: boolean): string { return value ? "on" : "off"; } function formatRulesSummary(rules: Ruleset): string { // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- origin may be absent despite its type const configRules = rules.filter((r) => r.layer === "config" && r.origin); if (configRules.length === 0) return ""; const formatted = configRules .map((r) => { const key = r.pattern === "*" ? r.surface : `${r.surface}["${r.pattern}"]`; return `${key}=${r.action} (${r.origin})`; }) .join(", "); return `\n rules: ${formatted}`; } function summarizeConfig( config: PermissionSystemExtensionConfig, rules?: Ruleset, ): string { const knobs = [ `yoloMode=${toOnOff(config.yoloMode)}`, `permissionReviewLog=${toOnOff(config.permissionReviewLog)}`, `debugLog=${toOnOff(config.debugLog)}`, ].join(", "); const rulesSuffix = rules ? formatRulesSummary(rules) : ""; return `${knobs}${rulesSuffix}`; } function buildSettingItems( config: PermissionSystemExtensionConfig, ): SettingItem[] { return [ { id: "yoloMode", label: "YOLO mode", description: "Auto-approve ask-state permission checks, including subagent approval forwarding", currentValue: toOnOff(config.yoloMode), values: ON_OFF, }, { id: "permissionReviewLog", label: "Permission review log", description: "Write permission request and decision audit events to the extension logs directory", currentValue: toOnOff(config.permissionReviewLog), values: ON_OFF, }, { id: "debugLog", label: "Debug logging", description: "Write verbose permission-system diagnostics to the extension logs directory", currentValue: toOnOff(config.debugLog), values: ON_OFF, }, { id: "doublePressToConfirm", label: "Double-press to confirm", description: "Require a confirming second press of a decision hotkey in the inline TUI permission dialog", currentValue: toOnOff(config.doublePressToConfirm), values: ON_OFF, }, ]; } function applySetting( config: PermissionSystemExtensionConfig, id: string, value: string, ): PermissionSystemExtensionConfig { switch (id) { case "yoloMode": return { ...config, yoloMode: value === "on" }; case "permissionReviewLog": return { ...config, permissionReviewLog: value === "on" }; case "debugLog": return { ...config, debugLog: value === "on" }; case "doublePressToConfirm": return { ...config, doublePressToConfirm: value === "on" }; default: return config; } } function syncSettingValues( settingsList: SettingsList, config: PermissionSystemExtensionConfig, ): void { settingsList.updateValue("yoloMode", toOnOff(config.yoloMode)); settingsList.updateValue( "permissionReviewLog", toOnOff(config.permissionReviewLog), ); settingsList.updateValue("debugLog", toOnOff(config.debugLog)); settingsList.updateValue( "doublePressToConfirm", toOnOff(config.doublePressToConfirm), ); } function getArgumentCompletions( argumentPrefix: string, ): Array<{ value: string; label: string; description: string }> | null { const normalized = argumentPrefix.trim().toLowerCase(); if (normalized.includes(" ")) { return null; } const filtered = COMMAND_ARGUMENTS.filter((item) => item.value.startsWith(normalized), ); return filtered.length > 0 ? [...filtered] : null; } async function openSettingsModal( ctx: ExtensionCommandContext, controller: PermissionSystemConfigController, ): Promise { const overlayOptions = { anchor: "center" as const, width: 82, maxHeight: "85%" as const, margin: 1, }; // eslint-disable-next-line @typescript-eslint/no-invalid-void-type -- ctx.ui.custom is valid; rule does not allow void in generic fn call type args await ctx.ui.custom( (_tui, _theme, _keybindings, done) => { let current = controller.config.current(); const settingsList = new SettingsList( buildSettingItems(current), 10, getSettingsListTheme(), (id, newValue) => { current = applySetting(current, id, newValue); controller.config.save(current, ctx); current = controller.config.current(); syncSettingValues(settingsList, current); }, () => done(), ); return settingsList; }, { overlay: true, overlayOptions }, ); } function handleArgs( args: string, ctx: ExtensionCommandContext, controller: PermissionSystemConfigController, ): boolean { const normalized = args.trim().toLowerCase(); if (!normalized) { return false; } if (normalized === "show") { const rules = controller.getActiveAgentConfigRules(); ctx.ui.notify( `permission-system: ${summarizeConfig(controller.config.current(), rules)}`, "info", ); return true; } if (normalized === "path") { ctx.ui.notify(`permission-system config: ${controller.configPath}`, "info"); return true; } if (normalized === "reset") { controller.config.save(cloneDefaultConfig(), ctx); ctx.ui.notify("Permission system settings reset to defaults.", "info"); return true; } if (normalized === "help") { ctx.ui.notify(USAGE_TEXT, "info"); return true; } ctx.ui.notify(USAGE_TEXT, "warning"); return true; } export function registerPermissionSystemCommand( pi: ExtensionAPI, controller: PermissionSystemConfigController, ): void { pi.registerCommand("permission-system", { description: "Configure pi-permission-system logging and yolo-mode behavior", getArgumentCompletions, handler: async (args, ctx) => { if (handleArgs(args, ctx, controller)) { return; } if (!ctx.hasUI) { ctx.ui.notify( "/permission-system requires interactive TUI mode.", "warning", ); return; } await openSettingsModal(ctx, controller); }, }); }