source dump of claude code
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}