{"version":3,"file":"listing.d.ts","sourceRoot":"","sources":["../../../../src/core/extensions/plugins/listing.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEtD,kFAAkF;AAClF,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,eAAe,CAAC,QAAQ,CAAC,GAAG,MAAM,CAO5E;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,EAAE,CAWrE;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACpC,OAAO,EAAE,SAAS,eAAe,EAAE,EACnC,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;CAAO,GAC/C,SAAS,EAAE,CAoBb;AAED,MAAM,WAAW,mBAAmB;IACnC,+DAA+D;IAC/D,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,kFAAkF;IAClF,OAAO,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,qFAAqF;AACrF,wBAAgB,mBAAmB,CAClC,OAAO,EAAE,SAAS,gBAAgB,EAAE,EACpC,OAAO,GAAE,mBAAwB,GAC/B,OAAO,EAAE,CAuBX","sourcesContent":["/**\n * Row shapes for the plugin listings, shared by every surface that prints one.\n *\n * `/plugin list`, `SearchPlugins` and `ListPlugins` describe the same objects,\n * and each used to build its own `name [a, b] — description` string, so the same\n * plugin printed three ways and the model's view never quite matched the user's.\n * They all go through here now; only the styling and the width differ.\n */\n\nimport type { ListGroup, ListRow } from \"../../format-list.js\";\nimport type { AvailablePlugin } from \"./install.js\";\nimport { pluginScopeOf } from \"./locations.js\";\nimport type { NormalizedPlugin } from \"./manifest.js\";\n\n/** Human-readable source for an available plugin (a URL, a path, an npm spec). */\nexport function formatPluginSource(source: AvailablePlugin[\"source\"]): string {\n\tif (typeof source === \"string\") return source;\n\tif (source.source === \"npm\") return source.package;\n\t// `archive` carries a url like `url` does; without this it printed\n\t// `https://…/p.zip/undefined` through the git-subdir branch below.\n\tif (source.source === \"url\" || source.source === \"archive\") return source.url;\n\treturn `${source.url}/${source.path}`;\n}\n\n/**\n * Capability classes a plugin actually ships.\n *\n * Covers every capability `NormalizedPlugin` can carry — themes and providers\n * included, which the old `ListPlugins` inline list omitted, so a plugin that\n * shipped only a theme or only a provider reported no capabilities at all.\n */\nexport function pluginCapabilities(plugin: NormalizedPlugin): string[] {\n\treturn [\n\t\tplugin.skillsDir && \"skills\",\n\t\tplugin.commandsDir && \"commands\",\n\t\tplugin.agentsDir && \"agents\",\n\t\tplugin.themesDir && \"themes\",\n\t\tplugin.hooks && \"hooks\",\n\t\tplugin.mcpServers && \"mcp\",\n\t\tplugin.providers?.length && \"providers\",\n\t\tplugin.canvasExtensions?.length && \"canvases\",\n\t].filter((value): value is string => typeof value === \"string\");\n}\n\n/**\n * Available plugins, grouped by the marketplace offering them.\n *\n * Grouping is what makes provenance legible: a flat list cannot answer \"where\n * would this come from if I installed it\", which is the one question that\n * matters at a trust boundary. Marketplace order is preserved from the caller.\n */\nexport function availablePluginGroups(\n\tplugins: readonly AvailablePlugin[],\n\toptions: { installed?: ReadonlySet<string> } = {},\n): ListGroup[] {\n\tconst groups = new Map<string, ListRow[]>();\n\tfor (const plugin of plugins) {\n\t\tconst rows = groups.get(plugin.marketplaceName) ?? [];\n\t\t// npm and archive entries are catalogued but not fetchable here, and saying\n\t\t// so in the row is cheaper than an InstallPlugin round trip that only ends\n\t\t// in the same message.\n\t\tconst installable = plugin.sourceKind !== \"npm\" && plugin.sourceKind !== \"archive\";\n\t\trows.push({\n\t\t\tname: plugin.name,\n\t\t\tmarker: options.installed?.has(plugin.name) ? \"✓\" : undefined,\n\t\t\tfacts: [\n\t\t\t\tplugin.supportPlatform.join(\"/\"),\n\t\t\t\tinstallable ? plugin.sourceKind : `${plugin.sourceKind}, not installable`,\n\t\t\t],\n\t\t\tdetail: plugin.description ?? formatPluginSource(plugin.source),\n\t\t});\n\t\tgroups.set(plugin.marketplaceName, rows);\n\t}\n\treturn Array.from(groups, ([title, rows]) => ({ title, rows }));\n}\n\nexport interface InstalledRowOptions {\n\t/** Working directory, used to place each plugin in a scope. */\n\tcwd?: string;\n\t/** Whether `cwd` is a trusted workspace; decides if executables actually load. */\n\ttrusted?: boolean;\n}\n\n/** Installed plugins as rows: id@version, platforms, format, scope, capabilities. */\nexport function installedPluginRows(\n\tplugins: readonly NormalizedPlugin[],\n\toptions: InstalledRowOptions = {},\n): ListRow[] {\n\treturn plugins.map((plugin) => {\n\t\tconst capabilities = pluginCapabilities(plugin);\n\t\t// Scope answers \"why is this loaded, and who else gets it\" — the plugin's\n\t\t// own path always knew, and nothing surfaced it.\n\t\tconst scope = options.cwd ? pluginScopeOf(plugin.root, options.cwd) : undefined;\n\t\t// A working-tree plugin's executables load only in a trusted workspace, so\n\t\t// listing \"hooks\" unqualified would overstate what is actually running.\n\t\tconst withheld = scope && scope !== \"user\" && !options.trusted && (plugin.hooks || plugin.mcpServers);\n\t\treturn {\n\t\t\tname: `${plugin.id}${plugin.version ? `@${plugin.version}` : \"\"}`,\n\t\t\t// The manifest format is parenthesised because it is usually the same\n\t\t\t// word as the platform (\"claude/github · claude\" reads as a stutter).\n\t\t\tfacts: [\n\t\t\t\tplugin.supportPlatform.join(\"/\"),\n\t\t\t\t`(${plugin.format})`,\n\t\t\t\t...(scope ? [scope] : []),\n\t\t\t\tcapabilities.join(\", \") || \"no capabilities\",\n\t\t\t\t...(withheld ? [\"hooks/mcp withheld, workspace not trusted\"] : []),\n\t\t\t],\n\t\t\tdetail: plugin.description,\n\t\t};\n\t});\n}\n"]}