/** * Built-in Plugin Registry * * Manages built-in plugins that ship with the CLI and can be enabled/disabled * by users via the /plugin UI. * * Built-in plugins differ from bundled skills (src/skills/bundled/) in that: * - They appear in the /plugin UI under a "Built-in" section * - Users can enable/disable them (persisted to user settings) * - They can provide multiple components (skills, hooks, MCP servers) * * Plugin IDs use the format `{name}@builtin` to distinguish them from * marketplace plugins (`{name}@{marketplace}`). */ import type { Command } from '../commands.js' import type { BundledSkillDefinition } from '../skills/bundledSkills.js' import type { BuiltinPluginDefinition, LoadedPlugin } from '../types/plugin.js' import { getSettings_DEPRECATED } from '../utils/settings/settings.js' const BUILTIN_PLUGINS: Map = new Map() export const BUILTIN_MARKETPLACE_NAME = 'builtin' /** * Register a built-in plugin. Call this from initBuiltinPlugins() at startup. */ export function registerBuiltinPlugin( definition: BuiltinPluginDefinition, ): void { BUILTIN_PLUGINS.set(definition.name, definition) } /** * Check if a plugin ID represents a built-in plugin (ends with @builtin). */ export function isBuiltinPluginId(pluginId: string): boolean { return pluginId.endsWith(`@${BUILTIN_MARKETPLACE_NAME}`) } /** * Get a specific built-in plugin definition by name. * Useful for the /plugin UI to show the skills/hooks/MCP list without * a marketplace lookup. */ export function getBuiltinPluginDefinition( name: string, ): BuiltinPluginDefinition | undefined { return BUILTIN_PLUGINS.get(name) } /** * Get all registered built-in plugins as LoadedPlugin objects, split into * enabled/disabled based on user settings (with defaultEnabled as fallback). * Plugins whose isAvailable() returns false are omitted entirely. */ export function getBuiltinPlugins(): { enabled: LoadedPlugin[] disabled: LoadedPlugin[] } { const settings = getSettings_DEPRECATED() const enabled: LoadedPlugin[] = [] const disabled: LoadedPlugin[] = [] for (const [name, definition] of BUILTIN_PLUGINS) { if (definition.isAvailable && !definition.isAvailable()) { continue } const pluginId = `${name}@${BUILTIN_MARKETPLACE_NAME}` const userSetting = settings?.enabledPlugins?.[pluginId] // Enabled state: user preference > plugin default > true const isEnabled = userSetting !== undefined ? userSetting === true : (definition.defaultEnabled ?? true) const plugin: LoadedPlugin = { name, manifest: { name, description: definition.description, version: definition.version, }, path: BUILTIN_MARKETPLACE_NAME, // sentinel — no filesystem path source: pluginId, repository: pluginId, enabled: isEnabled, isBuiltin: true, hooksConfig: definition.hooks, mcpServers: definition.mcpServers, } if (isEnabled) { enabled.push(plugin) } else { disabled.push(plugin) } } return { enabled, disabled } } /** * Get skills from enabled built-in plugins as Command objects. * Skills from disabled plugins are not returned. */ export function getBuiltinPluginSkillCommands(): Command[] { const { enabled } = getBuiltinPlugins() const commands: Command[] = [] for (const plugin of enabled) { const definition = BUILTIN_PLUGINS.get(plugin.name) if (!definition?.skills) continue for (const skill of definition.skills) { commands.push(skillDefinitionToCommand(skill)) } } return commands } /** * Clear built-in plugins registry (for testing). */ export function clearBuiltinPlugins(): void { BUILTIN_PLUGINS.clear() } // -- function skillDefinitionToCommand(definition: BundledSkillDefinition): Command { return { type: 'prompt', name: definition.name, description: definition.description, hasUserSpecifiedDescription: true, allowedTools: definition.allowedTools ?? [], argumentHint: definition.argumentHint, whenToUse: definition.whenToUse, model: definition.model, disableModelInvocation: definition.disableModelInvocation ?? false, userInvocable: definition.userInvocable ?? true, contentLength: 0, // 'bundled' not 'builtin' — 'builtin' in Command.source means hardcoded // slash commands (/help, /clear). Using 'bundled' keeps these skills in // the Skill tool's listing, analytics name logging, and prompt-truncation // exemption. The user-toggleable aspect is tracked on LoadedPlugin.isBuiltin. source: 'bundled', loadedFrom: 'bundled', hooks: definition.hooks, context: definition.context, agent: definition.agent, isEnabled: definition.isEnabled ?? (() => true), isHidden: !(definition.userInvocable ?? true), progressMessage: 'running', getPromptForCommand: definition.getPromptForCommand, } }