From 6e3222922eae9c3cf5ab0c604842c7a5e7309c0d Mon Sep 17 00:00:00 2001 From: mudler-agent Date: Sun, 25 Oct 2026 19:13:30 +0100 Subject: [PATCH] feat(addons): show the tool count of each add-on before install (#12898) The add-on cards, the Find add-ons tab and the install dialog said "Adds a page" for a library entry but nothing about chat tools, so the tools only showed once the add-on was installed. The library index now carries toolCount and tools on each entry, with the same names as the installed view. They come from capabilities.tools of the manifest; admin tools are not chat tools and are not counted. Tools is capped at 32 names and toolCount stays the full number. The catalog reader validates the hint and refuses it on a backend entry. addonpkg catalog writes the fields. The new catalog-tools subcommand adds them to an existing index from addons//manifest.json, and leaves every digest and artifact as it was. The UI reads the fields through the same helper as the installed view and shows "Adds N tools" when N is above zero. Assisted-by: Claude:claude-sonnet-5-5 [Claude Code] Signed-off-by: Ettore Di Giacinto Co-authored-by: Ettore Di Giacinto --- .../react-ui/public/locales/de/addons.json | 2 + .../react-ui/public/locales/en/addons.json | 2 + .../react-ui/public/locales/es/addons.json | 2 + .../react-ui/public/locales/fr/addons.json | 2 + .../react-ui/public/locales/id/addons.json | 2 + .../react-ui/public/locales/it/addons.json | 2 + .../react-ui/public/locales/ko/addons.json | 2 + .../react-ui/public/locales/pt-BR/addons.json | 2 + .../react-ui/public/locales/zh-CN/addons.json | 2 + .../src/components/addons/AddonBits.jsx | 17 +++- .../src/components/addons/BrowseView.jsx | 6 +- .../src/components/addons/FirstRun.jsx | 4 +- .../src/components/addons/InstallDialog.jsx | 3 +- .../src/components/addons/addonState.js | 8 ++ core/http/react-ui/src/utils/addonsApi.js | 3 + core/http/react-ui/tests/unit/addons.test.mjs | 32 ++++++- core/services/addons/catalog/backend_entry.go | 2 +- core/services/addons/catalog/catalog.go | 28 ++++++ .../addons/catalog/index_yaml_test.go | 49 ++++++++++ core/services/addons/catalog/sources.go | 6 +- docs/content/features/addons.md | 8 ++ gallery/addons/README.md | 11 +++ gallery/addons/index.yaml | 36 +++++++ pkg/addons/packaging/catalog.go | 96 ++++++++++++++++++- pkg/addons/packaging/packaging_test.go | 62 ++++++++++++ pkg/addons/packaging/run.go | 25 ++--- 26 files changed, 390 insertions(+), 24 deletions(-) diff --git a/core/http/react-ui/public/locales/de/addons.json b/core/http/react-ui/public/locales/de/addons.json index c840f1096..fb7b613cc 100644 --- a/core/http/react-ui/public/locales/de/addons.json +++ b/core/http/react-ui/public/locales/de/addons.json @@ -625,6 +625,8 @@ "addsPage": "Fügt eine Seite hinzu", "chatTools_one": "{{count}} chat tool", "chatTools_other": "{{count}} chat tools", + "addsTools_one": "Adds {{count}} tool", + "addsTools_other": "Adds {{count}} tools", "openNamed": "Open {{name}}", "removeTitle": "Remove {{name}}?", "removeBody": "{{name}} turns off and is removed from this server. Its data is kept, so installing it again picks it up. Nothing is deleted." diff --git a/core/http/react-ui/public/locales/en/addons.json b/core/http/react-ui/public/locales/en/addons.json index 27e17d0c2..453a02575 100644 --- a/core/http/react-ui/public/locales/en/addons.json +++ b/core/http/react-ui/public/locales/en/addons.json @@ -625,6 +625,8 @@ "addsPage": "Adds a page", "chatTools_one": "{{count}} chat tool", "chatTools_other": "{{count}} chat tools", + "addsTools_one": "Adds {{count}} tool", + "addsTools_other": "Adds {{count}} tools", "openNamed": "Open {{name}}", "removeTitle": "Remove {{name}}?", "removeBody": "{{name}} turns off and is removed from this server. Its data is kept, so installing it again picks it up. Nothing is deleted." diff --git a/core/http/react-ui/public/locales/es/addons.json b/core/http/react-ui/public/locales/es/addons.json index a88124c7e..f418a8bfa 100644 --- a/core/http/react-ui/public/locales/es/addons.json +++ b/core/http/react-ui/public/locales/es/addons.json @@ -625,6 +625,8 @@ "addsPage": "Añade una página", "chatTools_one": "{{count}} chat tool", "chatTools_other": "{{count}} chat tools", + "addsTools_one": "Adds {{count}} tool", + "addsTools_other": "Adds {{count}} tools", "openNamed": "Open {{name}}", "removeTitle": "Remove {{name}}?", "removeBody": "{{name}} turns off and is removed from this server. Its data is kept, so installing it again picks it up. Nothing is deleted." diff --git a/core/http/react-ui/public/locales/fr/addons.json b/core/http/react-ui/public/locales/fr/addons.json index 33f4d828d..5b67cd50f 100644 --- a/core/http/react-ui/public/locales/fr/addons.json +++ b/core/http/react-ui/public/locales/fr/addons.json @@ -629,6 +629,8 @@ "addsPage": "Ajoute une page", "chatTools_one": "{{count}} chat tool", "chatTools_other": "{{count}} chat tools", + "addsTools_one": "Adds {{count}} tool", + "addsTools_other": "Adds {{count}} tools", "openNamed": "Open {{name}}", "removeTitle": "Remove {{name}}?", "removeBody": "{{name}} turns off and is removed from this server. Its data is kept, so installing it again picks it up. Nothing is deleted." diff --git a/core/http/react-ui/public/locales/id/addons.json b/core/http/react-ui/public/locales/id/addons.json index e42492047..2be63fa16 100644 --- a/core/http/react-ui/public/locales/id/addons.json +++ b/core/http/react-ui/public/locales/id/addons.json @@ -625,6 +625,8 @@ "addsPage": "Menambah satu halaman", "chatTools_one": "{{count}} chat tool", "chatTools_other": "{{count}} chat tools", + "addsTools_one": "Adds {{count}} tool", + "addsTools_other": "Adds {{count}} tools", "openNamed": "Open {{name}}", "removeTitle": "Remove {{name}}?", "removeBody": "{{name}} turns off and is removed from this server. Its data is kept, so installing it again picks it up. Nothing is deleted." diff --git a/core/http/react-ui/public/locales/it/addons.json b/core/http/react-ui/public/locales/it/addons.json index f31a146ea..5b2236dd8 100644 --- a/core/http/react-ui/public/locales/it/addons.json +++ b/core/http/react-ui/public/locales/it/addons.json @@ -625,6 +625,8 @@ "addsPage": "Aggiunge una pagina", "chatTools_one": "{{count}} chat tool", "chatTools_other": "{{count}} chat tools", + "addsTools_one": "Adds {{count}} tool", + "addsTools_other": "Adds {{count}} tools", "openNamed": "Open {{name}}", "removeTitle": "Remove {{name}}?", "removeBody": "{{name}} turns off and is removed from this server. Its data is kept, so installing it again picks it up. Nothing is deleted." diff --git a/core/http/react-ui/public/locales/ko/addons.json b/core/http/react-ui/public/locales/ko/addons.json index 61965e907..45930827b 100644 --- a/core/http/react-ui/public/locales/ko/addons.json +++ b/core/http/react-ui/public/locales/ko/addons.json @@ -625,6 +625,8 @@ "addsPage": "페이지를 추가합니다", "chatTools_one": "{{count}} chat tool", "chatTools_other": "{{count}} chat tools", + "addsTools_one": "Adds {{count}} tool", + "addsTools_other": "Adds {{count}} tools", "openNamed": "Open {{name}}", "removeTitle": "Remove {{name}}?", "removeBody": "{{name}} turns off and is removed from this server. Its data is kept, so installing it again picks it up. Nothing is deleted." diff --git a/core/http/react-ui/public/locales/pt-BR/addons.json b/core/http/react-ui/public/locales/pt-BR/addons.json index 2f187eb8d..af59df8c5 100644 --- a/core/http/react-ui/public/locales/pt-BR/addons.json +++ b/core/http/react-ui/public/locales/pt-BR/addons.json @@ -625,6 +625,8 @@ "addsPage": "Adiciona uma página", "chatTools_one": "{{count}} chat tool", "chatTools_other": "{{count}} chat tools", + "addsTools_one": "Adds {{count}} tool", + "addsTools_other": "Adds {{count}} tools", "openNamed": "Open {{name}}", "removeTitle": "Remove {{name}}?", "removeBody": "{{name}} turns off and is removed from this server. Its data is kept, so installing it again picks it up. Nothing is deleted." diff --git a/core/http/react-ui/public/locales/zh-CN/addons.json b/core/http/react-ui/public/locales/zh-CN/addons.json index 8b21f8da8..0fe146c1f 100644 --- a/core/http/react-ui/public/locales/zh-CN/addons.json +++ b/core/http/react-ui/public/locales/zh-CN/addons.json @@ -625,6 +625,8 @@ "addsPage": "添加一个页面", "chatTools_one": "{{count}} chat tool", "chatTools_other": "{{count}} chat tools", + "addsTools_one": "Adds {{count}} tool", + "addsTools_other": "Adds {{count}} tools", "openNamed": "Open {{name}}", "removeTitle": "Remove {{name}}?", "removeBody": "{{name}} turns off and is removed from this server. Its data is kept, so installing it again picks it up. Nothing is deleted." diff --git a/core/http/react-ui/src/components/addons/AddonBits.jsx b/core/http/react-ui/src/components/addons/AddonBits.jsx index 08b2added..570861095 100644 --- a/core/http/react-ui/src/components/addons/AddonBits.jsx +++ b/core/http/react-ui/src/components/addons/AddonBits.jsx @@ -1,6 +1,6 @@ import { useTranslation } from 'react-i18next' import Icon from '../Icon' -import { stateChip } from './addonState' +import { addedByEntry, stateChip } from './addonState' import { installSteps } from './installSteps' import './addons.css' @@ -44,6 +44,21 @@ export function AppIcon({ src, size = 40 }) { ) } +// What a library entry adds, before it is installed: "Adds a page" when it has +// a main page, "Adds N tools" when it declares chat tools. It shows nothing +// for an entry that says neither. +export function EntryAdds({ entry }) { + const { t } = useTranslation('addons') + const adds = addedByEntry(entry) + if (!adds.page && adds.tools === 0) return null + return ( +

+ {adds.page && } + {adds.tools > 0 && } +

+ ) +} + // A small fact chip: platform, signature policy and so on. export function Fact({ icon, children, tone }) { return ( diff --git a/core/http/react-ui/src/components/addons/BrowseView.jsx b/core/http/react-ui/src/components/addons/BrowseView.jsx index 5ae8ad25e..341708139 100644 --- a/core/http/react-ui/src/components/addons/BrowseView.jsx +++ b/core/http/react-ui/src/components/addons/BrowseView.jsx @@ -8,6 +8,7 @@ import { paramsToQuery, queryToParams } from '../catalog/catalogState' import { AppIcon, Fact, UnknownNotice } from './AddonBits' import FlowStatus from './InstallFlow' import InstallDialog from './InstallDialog' +import { addedByEntry } from './addonState' import { updateView } from './updateState' import { describeError, describeOutcome } from './errors' import { useAddonActions } from './useAddonActions' @@ -51,7 +52,7 @@ export default function BrowseView({ api, adapter }) { const flow = actions.flows[`flow:${entry.key}`] const pending = Boolean(actions.unknown[entry.id]) const working = ['install', 'turn-on'].includes(flow?.step) - const addsPage = Boolean(entry.ui && entry.ui.mainPage) + const adds = addedByEntry(entry) const off = have && have.enabled === false return (
@@ -67,7 +68,8 @@ export default function BrowseView({ api, adapter }) {

{entry.description &&

{entry.description}

}
- {addsPage && {t('card.addsPage')}} + {adds.page && {t('card.addsPage')}} + {adds.tools > 0 && {t('card.addsTools', { count: adds.tools })}} {have && {have.version === entry.version ? t('browse.installedSame', { version: have.version }) : t('browse.installedOther', { version: have.version })}} {have && updateView(have.update).state === 'update-available' && {t('updates.badge.available', { version: updateView(have.update).version })}}
diff --git a/core/http/react-ui/src/components/addons/FirstRun.jsx b/core/http/react-ui/src/components/addons/FirstRun.jsx index 843e71ec7..52b4d064e 100644 --- a/core/http/react-ui/src/components/addons/FirstRun.jsx +++ b/core/http/react-ui/src/components/addons/FirstRun.jsx @@ -4,7 +4,7 @@ import { Link } from 'react-router-dom' import { useTranslation } from 'react-i18next' import Icon from '../Icon' import LoadingSpinner from '../LoadingSpinner' -import { AppIcon } from './AddonBits' +import { AppIcon, EntryAdds } from './AddonBits' import FlowStatus from './InstallFlow' import InstallDialog from './InstallDialog' import { featuredEntries } from './addonState' @@ -48,7 +48,7 @@ export default function FirstRun({ adapter, actions }) { {entry.description &&

{entry.description}

} - {entry.ui && entry.ui.mainPage &&

} + actions.installAndTurnOn(entry, { skipInstall: flowOf(entry).failedStep === 'turn-on' }) : undefined} />