source dump of claude code
at main 363 lines 11 kB view raw
1import type { LspServerConfig } from '../services/lsp/types.js' 2import type { McpServerConfig } from '../services/mcp/types.js' 3import type { BundledSkillDefinition } from '../skills/bundledSkills.js' 4import type { 5 CommandMetadata, 6 PluginAuthor, 7 PluginManifest, 8} from '../utils/plugins/schemas.js' 9import type { HooksSettings } from '../utils/settings/types.js' 10 11export type { PluginAuthor, PluginManifest, CommandMetadata } 12 13/** 14 * Definition for a built-in plugin that ships with the CLI. 15 * Built-in plugins appear in the /plugin UI and can be enabled/disabled by 16 * users (persisted to user settings). 17 */ 18export type BuiltinPluginDefinition = { 19 /** Plugin name (used in `{name}@builtin` identifier) */ 20 name: string 21 /** Description shown in the /plugin UI */ 22 description: string 23 /** Optional version string */ 24 version?: string 25 /** Skills provided by this plugin */ 26 skills?: BundledSkillDefinition[] 27 /** Hooks provided by this plugin */ 28 hooks?: HooksSettings 29 /** MCP servers provided by this plugin */ 30 mcpServers?: Record<string, McpServerConfig> 31 /** Whether this plugin is available (e.g. based on system capabilities). Unavailable plugins are hidden entirely. */ 32 isAvailable?: () => boolean 33 /** Default enabled state before the user sets a preference (defaults to true) */ 34 defaultEnabled?: boolean 35} 36 37export type PluginRepository = { 38 url: string 39 branch: string 40 lastUpdated?: string 41 commitSha?: string 42} 43 44export type PluginConfig = { 45 repositories: Record<string, PluginRepository> 46} 47 48export type LoadedPlugin = { 49 name: string 50 manifest: PluginManifest 51 path: string 52 source: string 53 repository: string // Repository identifier, usually same as source 54 enabled?: boolean 55 isBuiltin?: boolean // true for built-in plugins that ship with the CLI 56 sha?: string // Git commit SHA for version pinning (from marketplace entry source) 57 commandsPath?: string 58 commandsPaths?: string[] // Additional command paths from manifest 59 commandsMetadata?: Record<string, CommandMetadata> // Metadata for named commands from object-mapping format 60 agentsPath?: string 61 agentsPaths?: string[] // Additional agent paths from manifest 62 skillsPath?: string 63 skillsPaths?: string[] // Additional skill paths from manifest 64 outputStylesPath?: string 65 outputStylesPaths?: string[] // Additional output style paths from manifest 66 hooksConfig?: HooksSettings 67 mcpServers?: Record<string, McpServerConfig> 68 lspServers?: Record<string, LspServerConfig> 69 settings?: Record<string, unknown> 70} 71 72export type PluginComponent = 73 | 'commands' 74 | 'agents' 75 | 'skills' 76 | 'hooks' 77 | 'output-styles' 78 79/** 80 * Discriminated union of plugin error types. 81 * Each error type has specific contextual data for better debugging and user guidance. 82 * 83 * This replaces the previous string-based error matching approach with type-safe 84 * error handling that can't break when error messages change. 85 * 86 * IMPLEMENTATION STATUS: 87 * Currently used in production (2 types): 88 * - generic-error: Used for various plugin loading failures 89 * - plugin-not-found: Used when plugin not found in marketplace 90 * 91 * Planned for future use (10 types - see TODOs in pluginLoader.ts): 92 * - path-not-found, git-auth-failed, git-timeout, network-error 93 * - manifest-parse-error, manifest-validation-error 94 * - marketplace-not-found, marketplace-load-failed 95 * - mcp-config-invalid, hook-load-failed, component-load-failed 96 * 97 * These unused types support UI formatting and provide a clear roadmap for 98 * improving error specificity. They can be incrementally implemented as 99 * error creation sites are refactored. 100 */ 101export type PluginError = 102 | { 103 type: 'path-not-found' 104 source: string 105 plugin?: string 106 path: string 107 component: PluginComponent 108 } 109 | { 110 type: 'git-auth-failed' 111 source: string 112 plugin?: string 113 gitUrl: string 114 authType: 'ssh' | 'https' 115 } 116 | { 117 type: 'git-timeout' 118 source: string 119 plugin?: string 120 gitUrl: string 121 operation: 'clone' | 'pull' 122 } 123 | { 124 type: 'network-error' 125 source: string 126 plugin?: string 127 url: string 128 details?: string 129 } 130 | { 131 type: 'manifest-parse-error' 132 source: string 133 plugin?: string 134 manifestPath: string 135 parseError: string 136 } 137 | { 138 type: 'manifest-validation-error' 139 source: string 140 plugin?: string 141 manifestPath: string 142 validationErrors: string[] 143 } 144 | { 145 type: 'plugin-not-found' 146 source: string 147 pluginId: string 148 marketplace: string 149 } 150 | { 151 type: 'marketplace-not-found' 152 source: string 153 marketplace: string 154 availableMarketplaces: string[] 155 } 156 | { 157 type: 'marketplace-load-failed' 158 source: string 159 marketplace: string 160 reason: string 161 } 162 | { 163 type: 'mcp-config-invalid' 164 source: string 165 plugin: string 166 serverName: string 167 validationError: string 168 } 169 | { 170 type: 'mcp-server-suppressed-duplicate' 171 source: string 172 plugin: string 173 serverName: string 174 duplicateOf: string 175 } 176 | { 177 type: 'lsp-config-invalid' 178 source: string 179 plugin: string 180 serverName: string 181 validationError: string 182 } 183 | { 184 type: 'hook-load-failed' 185 source: string 186 plugin: string 187 hookPath: string 188 reason: string 189 } 190 | { 191 type: 'component-load-failed' 192 source: string 193 plugin: string 194 component: PluginComponent 195 path: string 196 reason: string 197 } 198 | { 199 type: 'mcpb-download-failed' 200 source: string 201 plugin: string 202 url: string 203 reason: string 204 } 205 | { 206 type: 'mcpb-extract-failed' 207 source: string 208 plugin: string 209 mcpbPath: string 210 reason: string 211 } 212 | { 213 type: 'mcpb-invalid-manifest' 214 source: string 215 plugin: string 216 mcpbPath: string 217 validationError: string 218 } 219 | { 220 type: 'lsp-config-invalid' 221 source: string 222 plugin: string 223 serverName: string 224 validationError: string 225 } 226 | { 227 type: 'lsp-server-start-failed' 228 source: string 229 plugin: string 230 serverName: string 231 reason: string 232 } 233 | { 234 type: 'lsp-server-crashed' 235 source: string 236 plugin: string 237 serverName: string 238 exitCode: number | null 239 signal?: string 240 } 241 | { 242 type: 'lsp-request-timeout' 243 source: string 244 plugin: string 245 serverName: string 246 method: string 247 timeoutMs: number 248 } 249 | { 250 type: 'lsp-request-failed' 251 source: string 252 plugin: string 253 serverName: string 254 method: string 255 error: string 256 } 257 | { 258 type: 'marketplace-blocked-by-policy' 259 source: string 260 plugin?: string 261 marketplace: string 262 blockedByBlocklist?: boolean // true if blocked by blockedMarketplaces, false if not in strictKnownMarketplaces 263 allowedSources: string[] // Formatted source strings (e.g., "github:owner/repo") 264 } 265 | { 266 type: 'dependency-unsatisfied' 267 source: string 268 plugin: string 269 dependency: string 270 reason: 'not-enabled' | 'not-found' 271 } 272 | { 273 type: 'plugin-cache-miss' 274 source: string 275 plugin: string 276 installPath: string 277 } 278 | { 279 type: 'generic-error' 280 source: string 281 plugin?: string 282 error: string 283 } 284 285export type PluginLoadResult = { 286 enabled: LoadedPlugin[] 287 disabled: LoadedPlugin[] 288 errors: PluginError[] 289} 290 291/** 292 * Helper function to get a display message from any PluginError 293 * Useful for logging and simple error displays 294 */ 295export function getPluginErrorMessage(error: PluginError): string { 296 switch (error.type) { 297 case 'generic-error': 298 return error.error 299 case 'path-not-found': 300 return `Path not found: ${error.path} (${error.component})` 301 case 'git-auth-failed': 302 return `Git authentication failed (${error.authType}): ${error.gitUrl}` 303 case 'git-timeout': 304 return `Git ${error.operation} timeout: ${error.gitUrl}` 305 case 'network-error': 306 return `Network error: ${error.url}${error.details ? ` - ${error.details}` : ''}` 307 case 'manifest-parse-error': 308 return `Manifest parse error: ${error.parseError}` 309 case 'manifest-validation-error': 310 return `Manifest validation failed: ${error.validationErrors.join(', ')}` 311 case 'plugin-not-found': 312 return `Plugin ${error.pluginId} not found in marketplace ${error.marketplace}` 313 case 'marketplace-not-found': 314 return `Marketplace ${error.marketplace} not found` 315 case 'marketplace-load-failed': 316 return `Marketplace ${error.marketplace} failed to load: ${error.reason}` 317 case 'mcp-config-invalid': 318 return `MCP server ${error.serverName} invalid: ${error.validationError}` 319 case 'mcp-server-suppressed-duplicate': { 320 const dup = error.duplicateOf.startsWith('plugin:') 321 ? `server provided by plugin "${error.duplicateOf.split(':')[1] ?? '?'}"` 322 : `already-configured "${error.duplicateOf}"` 323 return `MCP server "${error.serverName}" skipped — same command/URL as ${dup}` 324 } 325 case 'hook-load-failed': 326 return `Hook load failed: ${error.reason}` 327 case 'component-load-failed': 328 return `${error.component} load failed from ${error.path}: ${error.reason}` 329 case 'mcpb-download-failed': 330 return `Failed to download MCPB from ${error.url}: ${error.reason}` 331 case 'mcpb-extract-failed': 332 return `Failed to extract MCPB ${error.mcpbPath}: ${error.reason}` 333 case 'mcpb-invalid-manifest': 334 return `MCPB manifest invalid at ${error.mcpbPath}: ${error.validationError}` 335 case 'lsp-config-invalid': 336 return `Plugin "${error.plugin}" has invalid LSP server config for "${error.serverName}": ${error.validationError}` 337 case 'lsp-server-start-failed': 338 return `Plugin "${error.plugin}" failed to start LSP server "${error.serverName}": ${error.reason}` 339 case 'lsp-server-crashed': 340 if (error.signal) { 341 return `Plugin "${error.plugin}" LSP server "${error.serverName}" crashed with signal ${error.signal}` 342 } 343 return `Plugin "${error.plugin}" LSP server "${error.serverName}" crashed with exit code ${error.exitCode ?? 'unknown'}` 344 case 'lsp-request-timeout': 345 return `Plugin "${error.plugin}" LSP server "${error.serverName}" timed out on ${error.method} request after ${error.timeoutMs}ms` 346 case 'lsp-request-failed': 347 return `Plugin "${error.plugin}" LSP server "${error.serverName}" ${error.method} request failed: ${error.error}` 348 case 'marketplace-blocked-by-policy': 349 if (error.blockedByBlocklist) { 350 return `Marketplace '${error.marketplace}' is blocked by enterprise policy` 351 } 352 return `Marketplace '${error.marketplace}' is not in the allowed marketplace list` 353 case 'dependency-unsatisfied': { 354 const hint = 355 error.reason === 'not-enabled' 356 ? 'disabled — enable it or remove the dependency' 357 : 'not found in any configured marketplace' 358 return `Dependency "${error.dependency}" is ${hint}` 359 } 360 case 'plugin-cache-miss': 361 return `Plugin "${error.plugin}" not cached at ${error.installPath} — run /plugins to refresh` 362 } 363}