@kolisachint/hoocode-agent 0.5.7 → 0.5.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/dist/{modes/interactive → core}/brand.d.ts +6 -0
  3. package/dist/core/brand.d.ts.map +1 -0
  4. package/dist/{modes/interactive → core}/brand.js +6 -0
  5. package/dist/core/brand.js.map +1 -0
  6. package/dist/core/extensions/plugins/listing.d.ts +34 -0
  7. package/dist/core/extensions/plugins/listing.d.ts.map +1 -0
  8. package/dist/core/extensions/plugins/listing.js +69 -0
  9. package/dist/core/extensions/plugins/listing.js.map +1 -0
  10. package/dist/core/extensions/types.d.ts +9 -0
  11. package/dist/core/extensions/types.d.ts.map +1 -1
  12. package/dist/core/extensions/types.js.map +1 -1
  13. package/dist/core/format-list.d.ts +101 -0
  14. package/dist/core/format-list.d.ts.map +1 -0
  15. package/dist/core/format-list.js +171 -0
  16. package/dist/core/format-list.js.map +1 -0
  17. package/dist/core/skills.d.ts +9 -0
  18. package/dist/core/skills.d.ts.map +1 -1
  19. package/dist/core/skills.js +9 -0
  20. package/dist/core/skills.js.map +1 -1
  21. package/dist/core/tools/plugins.d.ts.map +1 -1
  22. package/dist/core/tools/plugins.js +93 -28
  23. package/dist/core/tools/plugins.js.map +1 -1
  24. package/dist/extensions/core/marketplace.d.ts.map +1 -1
  25. package/dist/extensions/core/marketplace.js +61 -13
  26. package/dist/extensions/core/marketplace.js.map +1 -1
  27. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  28. package/dist/modes/interactive/components/footer.js +1 -1
  29. package/dist/modes/interactive/components/footer.js.map +1 -1
  30. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  31. package/dist/modes/interactive/interactive-mode.js +24 -3
  32. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  33. package/dist/modes/interactive/resource-display.d.ts +2 -0
  34. package/dist/modes/interactive/resource-display.d.ts.map +1 -1
  35. package/dist/modes/interactive/resource-display.js +10 -7
  36. package/dist/modes/interactive/resource-display.js.map +1 -1
  37. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  38. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  39. package/examples/extensions/sandbox/package.json +1 -1
  40. package/examples/extensions/with-deps/package.json +1 -1
  41. package/package.json +4 -4
  42. package/dist/modes/interactive/brand.d.ts.map +0 -1
  43. package/dist/modes/interactive/brand.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,52 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.5.8] - 2026-08-12
4
+
5
+ ### Changed
6
+
7
+ - **Capability listings share one layout.** `/plugin list`,
8
+ `/plugin marketplace list`, `SearchPlugins` and `ListPlugins` each built their
9
+ own `name [a, b] — description` line, so the same plugin printed three ways
10
+ and the model's view never matched the user's. They now go through a single
11
+ row formatter (`core/format-list.ts`, `core/extensions/plugins/listing.ts`):
12
+ aligned name column, facts beside it, description wrapped with a hanging
13
+ indent instead of restarting at column 0, and plugins grouped by the
14
+ marketplace that offers them. `/plugin list` marks what is already installed.
15
+ - `SearchPlugins` takes a `limit` (default 10). With no query it previously
16
+ returned every plugin in every registered marketplace — dozens of entries,
17
+ several lines each — into the chat and the context.
18
+ - The five plugin tools define `renderResult`, as every other built-in tool
19
+ already did. Results are bounded to 20 lines with an expand hint rather than
20
+ dumped whole.
21
+ - `ListPlugins` reports the manifest format its description already promised,
22
+ counts `themes` and `providers` as capabilities (a plugin shipping only a
23
+ theme reported none), and carries the plugin description through.
24
+ - `[Agents]` in the startup summary is one line per agent, truncated to the
25
+ terminal, instead of a 200-character summary wrapping into a paragraph.
26
+ - `brand.ts` moved to `core/` so non-interactive surfaces can label capability
27
+ classes; added a `marketplaces` glyph.
28
+
29
+ ### Fixed
30
+
31
+ - Status messages carrying their own colors are no longer wrapped in a blanket
32
+ dim. `theme.fg` closes with `\x1b[39m` (reset, not restore), so the dim died
33
+ at the first inner span and — because the wrapper carries that state across
34
+ newlines — every line after it lost the dim too.
35
+ - `InstallPlugin` no longer erases its own announcement. It notified "installing
36
+ X because Y", then notified the outcome; consecutive info notifications
37
+ coalesce into one chat line, so the second replaced the first and the
38
+ transparency the tool guidelines require never reached the screen.
39
+ - Truncation in the new listings does not emit escape codes. The TUI's
40
+ `truncateToWidth` appends `\x1b[0m` even for plain input — a full reset that
41
+ would both leak into model-facing text and clear any enclosing style mid-line.
42
+
43
+ ### Added
44
+
45
+ - `ExtensionUIContext.columns` exposes the terminal width to extensions, so a
46
+ listing can wrap to the actual terminal. Optional, and undefined outside a
47
+ terminal (RPC, print), which is the signal to emit unwrapped text rather than
48
+ wrap to someone else's width.
49
+
3
50
  ## [0.5.7] - 2026-08-12
4
51
 
5
52
  ### Added
@@ -11,6 +11,11 @@
11
11
  * Glyphs are chosen to be single terminal cell, widely supported, and distinct
12
12
  * from the task-panel's own status/owner glyphs (◐ ◆ ◇ ▸ ⧉ ✓ ✗ ○) so the two
13
13
  * vocabularies never read as the same signal.
14
+ *
15
+ * This lives in `core/` rather than `modes/interactive/` because the `/plugin`
16
+ * command extension labels the same capability classes, and extensions must not
17
+ * reach into a mode's internals. The module is pure data — no theme, no
18
+ * rendering — so it is safe for every surface.
14
19
  */
15
20
  /** The HooCode mark — a filled hexagon, rendered in the accent colour. */
16
21
  export declare const BRAND_MARK = "\u2B22";
@@ -25,6 +30,7 @@ export declare const CATEGORY_GLYPH: {
25
30
  readonly agents: "◈";
26
31
  readonly mcp: "⧉";
27
32
  readonly plugins: "⬡";
33
+ readonly marketplaces: "⊞";
28
34
  readonly themes: "◒";
29
35
  readonly context: "❯";
30
36
  readonly extensions: "⊹";
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brand.d.ts","sourceRoot":"","sources":["../../src/core/brand.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,4EAA0E;AAC1E,eAAO,MAAM,UAAU,WAAM,CAAC;AAE9B;;;;GAIG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;CAUjB,CAAC;AAEX,MAAM,MAAM,WAAW,GAAG,MAAM,OAAO,cAAc,CAAC;AAEtD,iEAAiE;AACjE,eAAO,MAAM,WAAW,WAAM,CAAC;AAE/B,yCAAyC;AACzC,eAAO,MAAM,gBAAgB,WAAM,CAAC","sourcesContent":["/**\n * HooCode brand identity — the single source of truth for the product mark and\n * the glyph vocabulary the interactive surfaces share.\n *\n * The brand *colour* is the theme's `accent` token (see theme/dark.json,\n * theme/light.json) so it stays theme-aware and swappable; this module owns the\n * non-colour identity: the mark and the category glyphs used by the footer,\n * the startup resource summary, and anywhere a capability class is labelled.\n * Keeping them here means a future rebrand touches one file, not twenty.\n *\n * Glyphs are chosen to be single terminal cell, widely supported, and distinct\n * from the task-panel's own status/owner glyphs (◐ ◆ ◇ ▸ ⧉ ✓ ✗ ○) so the two\n * vocabularies never read as the same signal.\n *\n * This lives in `core/` rather than `modes/interactive/` because the `/plugin`\n * command extension labels the same capability classes, and extensions must not\n * reach into a mode's internals. The module is pure data — no theme, no\n * rendering — so it is safe for every surface.\n */\n\n/** The HooCode mark — a filled hexagon, rendered in the accent colour. */\nexport const BRAND_MARK = \"⬢\";\n\n/**\n * Glyphs for the capability classes a session can load. Used by the startup\n * \"resources ready\" summary and reusable anywhere a class needs a label. Each is\n * a single cell so counts and columns stay aligned.\n */\nexport const CATEGORY_GLYPH = {\n\tskills: \"✦\",\n\tcommands: \"⌘\",\n\tagents: \"◈\",\n\tmcp: \"⧉\",\n\tplugins: \"⬡\",\n\tmarketplaces: \"⊞\",\n\tthemes: \"◒\",\n\tcontext: \"❯\",\n\textensions: \"⊹\",\n} as const;\n\nexport type CategoryKey = keyof typeof CATEGORY_GLYPH;\n\n/** A soft dot separator used between footer/summary segments. */\nexport const SEGMENT_SEP = \"·\";\n\n/** Fork glyph preceding a git branch. */\nexport const GIT_BRANCH_GLYPH = \"⑂\";\n"]}
@@ -11,6 +11,11 @@
11
11
  * Glyphs are chosen to be single terminal cell, widely supported, and distinct
12
12
  * from the task-panel's own status/owner glyphs (◐ ◆ ◇ ▸ ⧉ ✓ ✗ ○) so the two
13
13
  * vocabularies never read as the same signal.
14
+ *
15
+ * This lives in `core/` rather than `modes/interactive/` because the `/plugin`
16
+ * command extension labels the same capability classes, and extensions must not
17
+ * reach into a mode's internals. The module is pure data — no theme, no
18
+ * rendering — so it is safe for every surface.
14
19
  */
15
20
  /** The HooCode mark — a filled hexagon, rendered in the accent colour. */
16
21
  export const BRAND_MARK = "⬢";
@@ -25,6 +30,7 @@ export const CATEGORY_GLYPH = {
25
30
  agents: "◈",
26
31
  mcp: "⧉",
27
32
  plugins: "⬡",
33
+ marketplaces: "⊞",
28
34
  themes: "◒",
29
35
  context: "❯",
30
36
  extensions: "⊹",
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brand.js","sourceRoot":"","sources":["../../src/core/brand.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,4EAA0E;AAC1E,MAAM,CAAC,MAAM,UAAU,GAAG,KAAG,CAAC;AAE9B;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;IAC7B,MAAM,EAAE,KAAG;IACX,QAAQ,EAAE,KAAG;IACb,MAAM,EAAE,KAAG;IACX,GAAG,EAAE,KAAG;IACR,OAAO,EAAE,KAAG;IACZ,YAAY,EAAE,KAAG;IACjB,MAAM,EAAE,KAAG;IACX,OAAO,EAAE,KAAG;IACZ,UAAU,EAAE,KAAG;CACN,CAAC;AAIX,iEAAiE;AACjE,MAAM,CAAC,MAAM,WAAW,GAAG,IAAG,CAAC;AAE/B,yCAAyC;AACzC,MAAM,CAAC,MAAM,gBAAgB,GAAG,KAAG,CAAC","sourcesContent":["/**\n * HooCode brand identity — the single source of truth for the product mark and\n * the glyph vocabulary the interactive surfaces share.\n *\n * The brand *colour* is the theme's `accent` token (see theme/dark.json,\n * theme/light.json) so it stays theme-aware and swappable; this module owns the\n * non-colour identity: the mark and the category glyphs used by the footer,\n * the startup resource summary, and anywhere a capability class is labelled.\n * Keeping them here means a future rebrand touches one file, not twenty.\n *\n * Glyphs are chosen to be single terminal cell, widely supported, and distinct\n * from the task-panel's own status/owner glyphs (◐ ◆ ◇ ▸ ⧉ ✓ ✗ ○) so the two\n * vocabularies never read as the same signal.\n *\n * This lives in `core/` rather than `modes/interactive/` because the `/plugin`\n * command extension labels the same capability classes, and extensions must not\n * reach into a mode's internals. The module is pure data — no theme, no\n * rendering — so it is safe for every surface.\n */\n\n/** The HooCode mark — a filled hexagon, rendered in the accent colour. */\nexport const BRAND_MARK = \"⬢\";\n\n/**\n * Glyphs for the capability classes a session can load. Used by the startup\n * \"resources ready\" summary and reusable anywhere a class needs a label. Each is\n * a single cell so counts and columns stay aligned.\n */\nexport const CATEGORY_GLYPH = {\n\tskills: \"✦\",\n\tcommands: \"⌘\",\n\tagents: \"◈\",\n\tmcp: \"⧉\",\n\tplugins: \"⬡\",\n\tmarketplaces: \"⊞\",\n\tthemes: \"◒\",\n\tcontext: \"❯\",\n\textensions: \"⊹\",\n} as const;\n\nexport type CategoryKey = keyof typeof CATEGORY_GLYPH;\n\n/** A soft dot separator used between footer/summary segments. */\nexport const SEGMENT_SEP = \"·\";\n\n/** Fork glyph preceding a git branch. */\nexport const GIT_BRANCH_GLYPH = \"⑂\";\n"]}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Row shapes for the plugin listings, shared by every surface that prints one.
3
+ *
4
+ * `/plugin list`, `SearchPlugins` and `ListPlugins` describe the same objects,
5
+ * and each used to build its own `name [a, b] — description` string, so the same
6
+ * plugin printed three ways and the model's view never quite matched the user's.
7
+ * They all go through here now; only the styling and the width differ.
8
+ */
9
+ import type { ListGroup, ListRow } from "../../format-list.js";
10
+ import type { AvailablePlugin } from "./install.js";
11
+ import type { NormalizedPlugin } from "./manifest.js";
12
+ /** Human-readable source for an available plugin (a URL, a path, an npm spec). */
13
+ export declare function formatPluginSource(source: AvailablePlugin["source"]): string;
14
+ /**
15
+ * Capability classes a plugin actually ships.
16
+ *
17
+ * Covers every capability `NormalizedPlugin` can carry — themes and providers
18
+ * included, which the old `ListPlugins` inline list omitted, so a plugin that
19
+ * shipped only a theme or only a provider reported no capabilities at all.
20
+ */
21
+ export declare function pluginCapabilities(plugin: NormalizedPlugin): string[];
22
+ /**
23
+ * Available plugins, grouped by the marketplace offering them.
24
+ *
25
+ * Grouping is what makes provenance legible: a flat list cannot answer "where
26
+ * would this come from if I installed it", which is the one question that
27
+ * matters at a trust boundary. Marketplace order is preserved from the caller.
28
+ */
29
+ export declare function availablePluginGroups(plugins: readonly AvailablePlugin[], options?: {
30
+ installed?: ReadonlySet<string>;
31
+ }): ListGroup[];
32
+ /** Installed plugins as rows: id@version, platforms, format, capabilities. */
33
+ export declare function installedPluginRows(plugins: readonly NormalizedPlugin[]): ListRow[];
34
+ //# sourceMappingURL=listing.d.ts.map
@@ -0,0 +1 @@
1
+ {"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;AACpD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEtD,kFAAkF;AAClF,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,eAAe,CAAC,QAAQ,CAAC,GAAG,MAAM,CAI5E;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,EAAE,CAUrE;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,CAab;AAED,8EAA8E;AAC9E,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,SAAS,gBAAgB,EAAE,GAAG,OAAO,EAAE,CAWnF","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 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 === \"url\") 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].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\trows.push({\n\t\t\tname: plugin.name,\n\t\t\tmarker: options.installed?.has(plugin.name) ? \"✓\" : undefined,\n\t\t\tfacts: [plugin.supportPlatform.join(\"/\"), plugin.sourceKind],\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\n/** Installed plugins as rows: id@version, platforms, format, capabilities. */\nexport function installedPluginRows(plugins: readonly NormalizedPlugin[]): ListRow[] {\n\treturn plugins.map((plugin) => {\n\t\tconst capabilities = pluginCapabilities(plugin);\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: [plugin.supportPlatform.join(\"/\"), `(${plugin.format})`, capabilities.join(\", \") || \"no capabilities\"],\n\t\t\tdetail: plugin.description,\n\t\t};\n\t});\n}\n"]}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Row shapes for the plugin listings, shared by every surface that prints one.
3
+ *
4
+ * `/plugin list`, `SearchPlugins` and `ListPlugins` describe the same objects,
5
+ * and each used to build its own `name [a, b] — description` string, so the same
6
+ * plugin printed three ways and the model's view never quite matched the user's.
7
+ * They all go through here now; only the styling and the width differ.
8
+ */
9
+ /** Human-readable source for an available plugin (a URL, a path, an npm spec). */
10
+ export function formatPluginSource(source) {
11
+ if (typeof source === "string")
12
+ return source;
13
+ if (source.source === "url")
14
+ return source.url;
15
+ return `${source.url}/${source.path}`;
16
+ }
17
+ /**
18
+ * Capability classes a plugin actually ships.
19
+ *
20
+ * Covers every capability `NormalizedPlugin` can carry — themes and providers
21
+ * included, which the old `ListPlugins` inline list omitted, so a plugin that
22
+ * shipped only a theme or only a provider reported no capabilities at all.
23
+ */
24
+ export function pluginCapabilities(plugin) {
25
+ return [
26
+ plugin.skillsDir && "skills",
27
+ plugin.commandsDir && "commands",
28
+ plugin.agentsDir && "agents",
29
+ plugin.themesDir && "themes",
30
+ plugin.hooks && "hooks",
31
+ plugin.mcpServers && "mcp",
32
+ plugin.providers?.length && "providers",
33
+ ].filter((value) => typeof value === "string");
34
+ }
35
+ /**
36
+ * Available plugins, grouped by the marketplace offering them.
37
+ *
38
+ * Grouping is what makes provenance legible: a flat list cannot answer "where
39
+ * would this come from if I installed it", which is the one question that
40
+ * matters at a trust boundary. Marketplace order is preserved from the caller.
41
+ */
42
+ export function availablePluginGroups(plugins, options = {}) {
43
+ const groups = new Map();
44
+ for (const plugin of plugins) {
45
+ const rows = groups.get(plugin.marketplaceName) ?? [];
46
+ rows.push({
47
+ name: plugin.name,
48
+ marker: options.installed?.has(plugin.name) ? "✓" : undefined,
49
+ facts: [plugin.supportPlatform.join("/"), plugin.sourceKind],
50
+ detail: plugin.description ?? formatPluginSource(plugin.source),
51
+ });
52
+ groups.set(plugin.marketplaceName, rows);
53
+ }
54
+ return Array.from(groups, ([title, rows]) => ({ title, rows }));
55
+ }
56
+ /** Installed plugins as rows: id@version, platforms, format, capabilities. */
57
+ export function installedPluginRows(plugins) {
58
+ return plugins.map((plugin) => {
59
+ const capabilities = pluginCapabilities(plugin);
60
+ return {
61
+ name: `${plugin.id}${plugin.version ? `@${plugin.version}` : ""}`,
62
+ // The manifest format is parenthesised because it is usually the same
63
+ // word as the platform ("claude/github · claude" reads as a stutter).
64
+ facts: [plugin.supportPlatform.join("/"), `(${plugin.format})`, capabilities.join(", ") || "no capabilities"],
65
+ detail: plugin.description,
66
+ };
67
+ });
68
+ }
69
+ //# sourceMappingURL=listing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"listing.js","sourceRoot":"","sources":["../../../../src/core/extensions/plugins/listing.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,kFAAkF;AAClF,MAAM,UAAU,kBAAkB,CAAC,MAAiC,EAAU;IAC7E,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC;IAC9C,IAAI,MAAM,CAAC,MAAM,KAAK,KAAK;QAAE,OAAO,MAAM,CAAC,GAAG,CAAC;IAC/C,OAAO,GAAG,MAAM,CAAC,GAAG,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;AAAA,CACtC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAwB,EAAY;IACtE,OAAO;QACN,MAAM,CAAC,SAAS,IAAI,QAAQ;QAC5B,MAAM,CAAC,WAAW,IAAI,UAAU;QAChC,MAAM,CAAC,SAAS,IAAI,QAAQ;QAC5B,MAAM,CAAC,SAAS,IAAI,QAAQ;QAC5B,MAAM,CAAC,KAAK,IAAI,OAAO;QACvB,MAAM,CAAC,UAAU,IAAI,KAAK;QAC1B,MAAM,CAAC,SAAS,EAAE,MAAM,IAAI,WAAW;KACvC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAmB,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC;AAAA,CAChE;AAED;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CACpC,OAAmC,EACnC,OAAO,GAAwC,EAAE,EACnC;IACd,MAAM,MAAM,GAAG,IAAI,GAAG,EAAqB,CAAC;IAC5C,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC9B,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,eAAe,CAAC,IAAI,EAAE,CAAC;QACtD,IAAI,CAAC,IAAI,CAAC;YACT,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,MAAM,EAAE,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAG,CAAC,CAAC,CAAC,SAAS;YAC7D,KAAK,EAAE,CAAC,MAAM,CAAC,eAAe,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;YAC5D,MAAM,EAAE,MAAM,CAAC,WAAW,IAAI,kBAAkB,CAAC,MAAM,CAAC,MAAM,CAAC;SAC/D,CAAC,CAAC;QACH,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,eAAe,EAAE,IAAI,CAAC,CAAC;IAC1C,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AAAA,CAChE;AAED,8EAA8E;AAC9E,MAAM,UAAU,mBAAmB,CAAC,OAAoC,EAAa;IACpF,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC;QAC9B,MAAM,YAAY,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC;QAChD,OAAO;YACN,IAAI,EAAE,GAAG,MAAM,CAAC,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE;YACjE,sEAAsE;YACtE,uEAAsE;YACtE,KAAK,EAAE,CAAC,MAAM,CAAC,eAAe,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,MAAM,CAAC,MAAM,GAAG,EAAE,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,iBAAiB,CAAC;YAC7G,MAAM,EAAE,MAAM,CAAC,WAAW;SAC1B,CAAC;IAAA,CACF,CAAC,CAAC;AAAA,CACH","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 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 === \"url\") 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].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\trows.push({\n\t\t\tname: plugin.name,\n\t\t\tmarker: options.installed?.has(plugin.name) ? \"✓\" : undefined,\n\t\t\tfacts: [plugin.supportPlatform.join(\"/\"), plugin.sourceKind],\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\n/** Installed plugins as rows: id@version, platforms, format, capabilities. */\nexport function installedPluginRows(plugins: readonly NormalizedPlugin[]): ListRow[] {\n\treturn plugins.map((plugin) => {\n\t\tconst capabilities = pluginCapabilities(plugin);\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: [plugin.supportPlatform.join(\"/\"), `(${plugin.format})`, capabilities.join(\", \") || \"no capabilities\"],\n\t\t\tdetail: plugin.description,\n\t\t};\n\t});\n}\n"]}
@@ -99,6 +99,15 @@ export interface ExtensionUIContext {
99
99
  askOptions(questions: AskQuestion[], opts?: ExtensionUIDialogOptions): Promise<string[] | undefined>;
100
100
  /** Show a notification to the user. */
101
101
  notify(message: string, type?: "info" | "warning" | "error"): void;
102
+ /**
103
+ * Terminal width in columns, when the surface has one.
104
+ *
105
+ * Undefined outside a terminal (RPC, print, headless), which is the signal to
106
+ * emit unwrapped text and let the consumer lay it out — a listing pre-wrapped
107
+ * to someone else's terminal is worse than no wrapping at all. Optional so
108
+ * third-party implementations of this interface keep compiling.
109
+ */
110
+ readonly columns?: number;
102
111
  /** Listen to raw terminal input (interactive mode only). Returns an unsubscribe function. */
103
112
  onTerminalInput(handler: TerminalInputHandler): () => void;
104
113
  /** Set status text in the footer/status bar. Pass undefined to clear. */