@@ -12,12 +12,14 @@ import { pathIsInsideOrEqual } from "../util/path-contain.js";
1212import {
1313 parsePluginManifest ,
1414 PluginManifestSchema ,
15+ type PluginCredentialField ,
16+ type PluginKind ,
1517 type PluginManifest ,
1618} from "./manifest.js" ;
1719import { NOOP_TELEMETRY , type Telemetry } from "../telemetry/index.js" ;
1820import type { PluginLoadReporter } from "../telemetry/product-events.js" ;
1921import { runtimePluginLoadReporter } from "../telemetry/singleton.js" ;
20- import { loadDataOnlyPlugin } from "./data-only.js" ;
22+ import { isENOENT , loadDataOnlyPlugin } from "./data-only.js" ;
2123
2224import {
2325 resolvePluginWarningHandler ,
@@ -76,19 +78,46 @@ export interface PluginModule {
7678 shadowedRepoDefaultEnabled ?: boolean ;
7779}
7880
79- function isENOENT ( err : unknown ) : boolean {
80- return (
81- typeof err === "object" &&
82- err !== null &&
83- "code" in err &&
84- ( err as { code ?: unknown } ) . code === "ENOENT"
85- ) ;
86- }
87-
8881function errorText ( err : unknown ) : string {
8982 return err instanceof Error ? err . message : String ( err ) ;
9083}
9184
85+ // Flat candidate behind the tool-plugin and web-provider collectors: both
86+ // select modules by manifest kind + factory key and project the same shape,
87+ // so the per-kind entry points stay one-line typed wrappers.
88+ export interface CollectedPluginCandidate < TFactory > {
89+ id : string ;
90+ name : string ;
91+ description ?: string ;
92+ credentials : PluginCredentialField [ ] ;
93+ factory : ( options : unknown ) => TFactory | Promise < TFactory > ;
94+ }
95+
96+ export function collectPluginCandidates < TFactory > (
97+ modules : PluginModule [ ] ,
98+ opts : {
99+ kind : PluginKind ;
100+ factoryKey : "createToolPlugin" | "createWebProvider" ;
101+ } ,
102+ ) : CollectedPluginCandidate < TFactory > [ ] {
103+ const out : CollectedPluginCandidate < TFactory > [ ] = [ ] ;
104+ for ( const mod of modules ) {
105+ if ( mod . manifest ?. kind !== opts . kind ) continue ;
106+ const factory = mod [ opts . factoryKey ] ;
107+ if ( typeof factory !== "function" ) continue ;
108+ out . push ( {
109+ id : mod . manifest . id ,
110+ name : mod . manifest . name ,
111+ ...( mod . manifest . description !== undefined
112+ ? { description : mod . manifest . description }
113+ : { } ) ,
114+ credentials : mod . manifest . credentials ?? [ ] ,
115+ factory : factory as CollectedPluginCandidate < TFactory > [ "factory" ] ,
116+ } ) ;
117+ }
118+ return out ;
119+ }
120+
92121// Read and validate a manifest.json beside the module. Plugins may declare
93122// their manifest as a JS export (mod.manifest) or a sibling manifest.json file;
94123// this covers the JSON path so plugins that are pure data + commands work too.
@@ -427,23 +456,16 @@ export function expandSkipDiagnosticsHandler(
427456 return ( skip ) => diagnostics . warnings . push ( formatExpandSkip ( skip ) ) ;
428457}
429458
430- /**
431- * `onSkip` when no diagnostics collector is in play: one explicit stderr
432- * line, module-private and only reached by an internal caller's own
433- * deliberate choice (see `resolveExpandSkip` below) — never `expandPluginPath`
434- * falling back to it on its own.
435- */
436- function stderrExpandSkip ( skip : ExpandPluginPathSkip ) : void {
437- process . stderr . write ( `plugins: ${ formatExpandSkip ( skip ) } \n` ) ;
438- }
439-
440459/** Diagnostics when given, else the explicit stderr line — no silent option. */
441460function resolveExpandSkip (
442461 diagnostics ?: PluginLoadDiagnostics ,
443462) : ( skip : ExpandPluginPathSkip ) => void {
444- return diagnostics !== undefined
445- ? expandSkipDiagnosticsHandler ( diagnostics )
446- : stderrExpandSkip ;
463+ const onWarning = resolvePluginWarningHandler (
464+ diagnostics !== undefined
465+ ? { diagnostics }
466+ : { onWarning : stderrPluginWarning } ,
467+ ) ;
468+ return ( skip ) => onWarning ( formatExpandSkip ( skip ) ) ;
447469}
448470
449471/**
@@ -928,11 +950,11 @@ export async function discoverClaudeInstalledPlugins(
928950 parsed = JSON . parse ( raw ) ;
929951 } catch {
930952 // Prefer collector when present; else stderrPluginWarning.
931- resolvePluginWarningHandler (
932- opts . diagnostics !== undefined
933- ? { diagnostics : opts . diagnostics }
934- : { onWarning : stderrPluginWarning } ,
935- ) ( `failed to parse ${ registryPath } ` ) ;
953+ ( opts . diagnostics !== undefined
954+ ? resolvePluginWarningHandler ( { diagnostics : opts . diagnostics } )
955+ : resolvePluginWarningHandler ( { onWarning : stderrPluginWarning } ) ) (
956+ `failed to parse ${ registryPath } ` ,
957+ ) ;
936958 return [ ] ;
937959 }
938960 if ( typeof parsed !== "object" || parsed === null || Array . isArray ( parsed ) ) {
0 commit comments