@kolisachint/hoocode-agent 0.5.7 → 0.5.9

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 (78) hide show
  1. package/CHANGELOG.md +100 -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/ask-options.d.ts.map +1 -1
  28. package/dist/modes/interactive/components/ask-options.js +6 -6
  29. package/dist/modes/interactive/components/ask-options.js.map +1 -1
  30. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
  31. package/dist/modes/interactive/components/config-selector.js +5 -4
  32. package/dist/modes/interactive/components/config-selector.js.map +1 -1
  33. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  34. package/dist/modes/interactive/components/extension-selector.js +11 -7
  35. package/dist/modes/interactive/components/extension-selector.js.map +1 -1
  36. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  37. package/dist/modes/interactive/components/footer.js +1 -1
  38. package/dist/modes/interactive/components/footer.js.map +1 -1
  39. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  40. package/dist/modes/interactive/components/model-selector.js +9 -16
  41. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  42. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
  43. package/dist/modes/interactive/components/oauth-selector.js +7 -12
  44. package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  45. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  46. package/dist/modes/interactive/components/scoped-models-selector.js +6 -3
  47. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  48. package/dist/modes/interactive/components/selected-row-list.d.ts +28 -0
  49. package/dist/modes/interactive/components/selected-row-list.d.ts.map +1 -0
  50. package/dist/modes/interactive/components/selected-row-list.js +37 -0
  51. package/dist/modes/interactive/components/selected-row-list.js.map +1 -0
  52. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  53. package/dist/modes/interactive/components/session-selector.js +4 -7
  54. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  55. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  56. package/dist/modes/interactive/components/tree-selector.js +4 -7
  57. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  58. package/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -1
  59. package/dist/modes/interactive/components/user-message-selector.js +6 -4
  60. package/dist/modes/interactive/components/user-message-selector.js.map +1 -1
  61. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  62. package/dist/modes/interactive/interactive-mode.js +24 -3
  63. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  64. package/dist/modes/interactive/resource-display.d.ts +2 -0
  65. package/dist/modes/interactive/resource-display.d.ts.map +1 -1
  66. package/dist/modes/interactive/resource-display.js +10 -7
  67. package/dist/modes/interactive/resource-display.js.map +1 -1
  68. package/dist/modes/interactive/theme/theme.d.ts +24 -1
  69. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  70. package/dist/modes/interactive/theme/theme.js +32 -1
  71. package/dist/modes/interactive/theme/theme.js.map +1 -1
  72. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  73. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  74. package/examples/extensions/sandbox/package.json +1 -1
  75. package/examples/extensions/with-deps/package.json +1 -1
  76. package/package.json +4 -4
  77. package/dist/modes/interactive/brand.d.ts.map +0 -1
  78. package/dist/modes/interactive/brand.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,105 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.5.9] - 2026-08-12
4
+
5
+ ### Fixed
6
+
7
+ - The session tree selector's highlight stopped wherever the entry text
8
+ stopped, because the row was wrapped in `selectedBg` without being padded
9
+ first — a 17-cell band on a 120-column terminal. It now fills the row, like
10
+ the `/resume` picker beside it.
11
+ - The resource picker (`/config`) marked its selected row with bold alone — no
12
+ accent, no highlight — which on a light theme was close to invisible. It now
13
+ carries the same accent and band as every other picker.
14
+
15
+ ### Changed
16
+
17
+ - **The selected row is highlighted the same way in more of the app.** Until now
18
+ only the `/resume` session picker filled its selected row with `selectedBg`;
19
+ every other picker marked selection with an arrow and accent text alone, and
20
+ the band in `/resume` reached the right edge only because that component
21
+ right-aligns a timestamp column. `getSelectListTheme()` and
22
+ `getSettingsListTheme()` now supply a `selectedRow` background, so the theme
23
+ picker, thinking picker, show-images picker, the `/settings` screens and the
24
+ editor's autocomplete all draw the same full-width highlight. The accent
25
+ styling stays: `accent` clears 4.6:1 against `selectedBg` in every shipped
26
+ theme, while `muted` and `dim` fall to 2.8:1 and 1.9:1 on `dark`, so the band
27
+ is added to the existing marker rather than replacing it. The hand-rolled
28
+ pickers now do the same, via `SelectedRowList` (below).
29
+ - **Every picker marks its selected row with the same cursor.** `→`, `›` and
30
+ `>` were all in use — four glyphs counting the one `SelectList` hardcoded —
31
+ so which one you saw depended on which list you had opened. They all use `›`
32
+ now, from a single `SELECT_CURSOR`. In the ask-for-input pane this also stops
33
+ the row cursor colliding with the `>` that prompts its custom-answer field.
34
+ - Unselected rows are indented by `SELECT_GUTTER`, derived from the cursor's
35
+ visible width, rather than by a hardcoded two columns. The two agreed before
36
+ only because every cursor in use happened to be two columns wide.
37
+ - The model, scoped models, oauth and extension pickers build their rows
38
+ through the new `SelectedRowList` component instead of adding a `Text` child
39
+ per row. Those rows were assembled in an update method that never sees the
40
+ terminal width — width only arrives later, when the container renders each
41
+ child — so there was no point at which a row could be padded before being
42
+ painted. `SelectedRowList` holds the rows and renders them lazily, which puts
43
+ the width back in reach.
44
+ - The fork-from-message picker highlights the message line the cursor is on
45
+ (its metadata line stays unpainted) and colors it with `accent`, which it
46
+ previously left to bold alone.
47
+ - The ask-for-input pane keeps its plain rows: it is a question form with a
48
+ live text field, not a scrolling list, and a band under an input caret reads
49
+ as a rendering artifact rather than a selection.
50
+ - The `/resume` session picker and the session tree selector paint their
51
+ selected row through a shared `paintSelectedRow` helper rather than each
52
+ calling `theme.bg` on a row they had padded (or not padded) themselves.
53
+ `/resume` no longer depends on its right-aligned timestamp column to reach
54
+ the right edge.
55
+
56
+ ## [0.5.8] - 2026-08-12
57
+
58
+ ### Changed
59
+
60
+ - **Capability listings share one layout.** `/plugin list`,
61
+ `/plugin marketplace list`, `SearchPlugins` and `ListPlugins` each built their
62
+ own `name [a, b] — description` line, so the same plugin printed three ways
63
+ and the model's view never matched the user's. They now go through a single
64
+ row formatter (`core/format-list.ts`, `core/extensions/plugins/listing.ts`):
65
+ aligned name column, facts beside it, description wrapped with a hanging
66
+ indent instead of restarting at column 0, and plugins grouped by the
67
+ marketplace that offers them. `/plugin list` marks what is already installed.
68
+ - `SearchPlugins` takes a `limit` (default 10). With no query it previously
69
+ returned every plugin in every registered marketplace — dozens of entries,
70
+ several lines each — into the chat and the context.
71
+ - The five plugin tools define `renderResult`, as every other built-in tool
72
+ already did. Results are bounded to 20 lines with an expand hint rather than
73
+ dumped whole.
74
+ - `ListPlugins` reports the manifest format its description already promised,
75
+ counts `themes` and `providers` as capabilities (a plugin shipping only a
76
+ theme reported none), and carries the plugin description through.
77
+ - `[Agents]` in the startup summary is one line per agent, truncated to the
78
+ terminal, instead of a 200-character summary wrapping into a paragraph.
79
+ - `brand.ts` moved to `core/` so non-interactive surfaces can label capability
80
+ classes; added a `marketplaces` glyph.
81
+
82
+ ### Fixed
83
+
84
+ - Status messages carrying their own colors are no longer wrapped in a blanket
85
+ dim. `theme.fg` closes with `\x1b[39m` (reset, not restore), so the dim died
86
+ at the first inner span and — because the wrapper carries that state across
87
+ newlines — every line after it lost the dim too.
88
+ - `InstallPlugin` no longer erases its own announcement. It notified "installing
89
+ X because Y", then notified the outcome; consecutive info notifications
90
+ coalesce into one chat line, so the second replaced the first and the
91
+ transparency the tool guidelines require never reached the screen.
92
+ - Truncation in the new listings does not emit escape codes. The TUI's
93
+ `truncateToWidth` appends `\x1b[0m` even for plain input — a full reset that
94
+ would both leak into model-facing text and clear any enclosing style mid-line.
95
+
96
+ ### Added
97
+
98
+ - `ExtensionUIContext.columns` exposes the terminal width to extensions, so a
99
+ listing can wrap to the actual terminal. Optional, and undefined outside a
100
+ terminal (RPC, print), which is the signal to emit unwrapped text rather than
101
+ wrap to someone else's width.
102
+
3
103
  ## [0.5.7] - 2026-08-12
4
104
 
5
105
  ### 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. */