This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch fix/CAMEL-23913
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 00c8573c6f39342ff13420765ea5c27950d42d5d
Author: Claus Ibsen <[email protected]>
AuthorDate: Sun Jul 19 17:14:39 2026 +0200

    CAMEL-23913: camel-tui - Add Catalog tab and remove standalone catalog 
command
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../jbang/core/commands/tui/CamelCatalogTui.java   | 738 ---------------------
 .../dsl/jbang/core/commands/tui/CatalogTab.java    | 535 +++++++++++++++
 .../dsl/jbang/core/commands/tui/TabRegistry.java   |   3 +
 .../dsl/jbang/core/commands/tui/TuiIcons.java      |   1 +
 .../dsl/jbang/core/commands/tui/TuiPlugin.java     |   3 +-
 5 files changed, 540 insertions(+), 740 deletions(-)

diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelCatalogTui.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelCatalogTui.java
deleted file mode 100644
index 0f736f5e64ce..000000000000
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelCatalogTui.java
+++ /dev/null
@@ -1,738 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.camel.dsl.jbang.core.commands.tui;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
-import dev.tamboui.layout.Constraint;
-import dev.tamboui.layout.Layout;
-import dev.tamboui.layout.Rect;
-import dev.tamboui.style.Overflow;
-import dev.tamboui.style.Style;
-import dev.tamboui.terminal.Frame;
-import dev.tamboui.text.CharWidth;
-import dev.tamboui.text.Line;
-import dev.tamboui.text.Span;
-import dev.tamboui.text.Text;
-import dev.tamboui.tui.TuiRunner;
-import dev.tamboui.tui.event.Event;
-import dev.tamboui.tui.event.KeyCode;
-import dev.tamboui.tui.event.KeyEvent;
-import dev.tamboui.widgets.block.Block;
-import dev.tamboui.widgets.block.BorderType;
-import dev.tamboui.widgets.block.Borders;
-import dev.tamboui.widgets.paragraph.Paragraph;
-import dev.tamboui.widgets.table.Cell;
-import dev.tamboui.widgets.table.Row;
-import dev.tamboui.widgets.table.Table;
-import dev.tamboui.widgets.table.TableState;
-import org.apache.camel.catalog.CamelCatalog;
-import org.apache.camel.catalog.DefaultCamelCatalog;
-import org.apache.camel.dsl.jbang.core.commands.CamelCommand;
-import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
-import org.apache.camel.util.json.JsonArray;
-import org.apache.camel.util.json.JsonObject;
-import org.apache.camel.util.json.Jsoner;
-import picocli.CommandLine.Command;
-import sun.misc.Signal;
-
-@Command(name = "catalog",
-         description = "Interactive TUI catalog browser",
-         sortOptions = false)
-public class CamelCatalogTui extends CamelCommand {
-
-    private static final int FOCUS_LIST = 0;
-    private static final int FOCUS_OPTIONS = 1;
-
-    // Catalog data
-    private List<ComponentInfo> allComponents = Collections.emptyList();
-    private List<ComponentInfo> filteredComponents = Collections.emptyList();
-
-    // UI state
-    private final TableState listTableState = new TableState();
-    private final TableState optionsTableState = new TableState();
-    private int focus = FOCUS_LIST;
-    private final StringBuilder componentFilter = new StringBuilder();
-    private final StringBuilder optionFilter = new StringBuilder();
-    private boolean componentFullText;
-    private boolean optionFullText;
-    private int descriptionScroll;
-
-    // All options for selected component (unfiltered)
-    private List<OptionInfo> allOptionsUnfiltered = Collections.emptyList();
-    // Filtered options displayed in table
-    private List<OptionInfo> filteredOptions = Collections.emptyList();
-
-    private ClassLoader classLoader;
-
-    public CamelCatalogTui(CamelJBangMain main, ClassLoader classLoader) {
-        super(main);
-        this.classLoader = classLoader;
-    }
-
-    @Override
-    public Integer doCall() throws Exception {
-        // to make ServiceLoader work with tamboui for downloaded JARs
-        Thread.currentThread().setContextClassLoader(classLoader);
-
-        loadCatalog();
-
-        try (var tui = TuiBackendHelper.createTuiRunner()) {
-            Signal.handle(new Signal("INT"), sig -> tui.quit());
-            tui.run(this::handleEvent, this::render);
-        }
-        return 0;
-    }
-
-    // ---- Catalog Loading ----
-
-    @SuppressWarnings("unchecked")
-    private void loadCatalog() {
-        CamelCatalog catalog = new DefaultCamelCatalog();
-        List<String> names = new ArrayList<>(catalog.findComponentNames());
-        Collections.sort(names);
-
-        allComponents = new ArrayList<>();
-        for (String name : names) {
-            try {
-                String json = catalog.componentJSonSchema(name);
-                if (json == null) {
-                    continue;
-                }
-                JsonObject root = (JsonObject) Jsoner.deserialize(json);
-                JsonObject component = (JsonObject) root.get("component");
-                if (component == null) {
-                    continue;
-                }
-
-                ComponentInfo info = new ComponentInfo();
-                info.name = name;
-                info.title = component.getStringOrDefault("title", name);
-                info.description = component.getStringOrDefault("description", 
"");
-                info.label = component.getStringOrDefault("label", "");
-                info.groupId = component.getStringOrDefault("groupId", "");
-                info.artifactId = component.getStringOrDefault("artifactId", 
"");
-                info.version = component.getStringOrDefault("version", "");
-                info.scheme = component.getStringOrDefault("scheme", name);
-                info.syntax = component.getStringOrDefault("syntax", "");
-                info.deprecated = component.getBooleanOrDefault("deprecated", 
false);
-
-                // Parse properties — separate by kind
-                JsonObject properties = (JsonObject) root.get("properties");
-                if (properties != null) {
-                    for (Map.Entry<String, Object> entry : 
properties.entrySet()) {
-                        JsonObject prop = (JsonObject) entry.getValue();
-                        OptionInfo opt = parseOption(entry.getKey(), prop);
-                        String kind = prop.getStringOrDefault("kind", 
"parameter");
-                        opt.kind = kind;
-                        if ("property".equals(kind)) {
-                            info.componentOptions.add(opt);
-                        } else {
-                            info.endpointOptions.add(opt);
-                        }
-                    }
-                }
-
-                allComponents.add(info);
-            } catch (Exception e) {
-                // skip unparseable components
-            }
-        }
-
-        applyComponentFilter();
-        if (!filteredComponents.isEmpty()) {
-            listTableState.select(0);
-            updateSelectedComponent();
-        }
-    }
-
-    private OptionInfo parseOption(String name, JsonObject prop) {
-        OptionInfo opt = new OptionInfo();
-        opt.name = name;
-        opt.type = prop.getStringOrDefault("type", "");
-        opt.required = prop.getBooleanOrDefault("required", false);
-        opt.defaultValue = prop.getStringOrDefault("defaultValue", "");
-        opt.description = prop.getStringOrDefault("description", "");
-        opt.group = prop.getStringOrDefault("group", "");
-        opt.enumValues = "";
-        Object enums = prop.get("enum");
-        if (enums instanceof JsonArray arr) {
-            List<String> vals = new ArrayList<>();
-            for (Object v : arr) {
-                vals.add(String.valueOf(v));
-            }
-            opt.enumValues = String.join(", ", vals);
-        }
-        return opt;
-    }
-
-    private void applyComponentFilter() {
-        String filter = componentFilter.toString().toLowerCase();
-        if (filter.isEmpty()) {
-            filteredComponents = new ArrayList<>(allComponents);
-        } else {
-            filteredComponents = new ArrayList<>();
-            for (ComponentInfo c : allComponents) {
-                if (componentFullText) {
-                    if (c.name.toLowerCase().contains(filter)
-                            || c.title.toLowerCase().contains(filter)
-                            || c.description.toLowerCase().contains(filter)
-                            || c.label.toLowerCase().contains(filter)) {
-                        filteredComponents.add(c);
-                    }
-                } else {
-                    if (c.name.toLowerCase().contains(filter)) {
-                        filteredComponents.add(c);
-                    }
-                }
-            }
-        }
-        if (!filteredComponents.isEmpty()) {
-            listTableState.select(0);
-        } else {
-            listTableState.clearSelection();
-        }
-        updateSelectedComponent();
-    }
-
-    private void applyOptionFilter() {
-        String filter = optionFilter.toString().toLowerCase();
-        if (filter.isEmpty()) {
-            filteredOptions = new ArrayList<>(allOptionsUnfiltered);
-        } else {
-            filteredOptions = new ArrayList<>();
-            for (OptionInfo o : allOptionsUnfiltered) {
-                if (optionFullText) {
-                    if (o.name.toLowerCase().contains(filter)
-                            || o.description.toLowerCase().contains(filter)
-                            || o.group.toLowerCase().contains(filter)) {
-                        filteredOptions.add(o);
-                    }
-                } else {
-                    if (o.name.toLowerCase().contains(filter)) {
-                        filteredOptions.add(o);
-                    }
-                }
-            }
-        }
-        if (!filteredOptions.isEmpty()) {
-            optionsTableState.select(0);
-        } else {
-            optionsTableState.clearSelection();
-        }
-        descriptionScroll = 0;
-    }
-
-    private void updateSelectedComponent() {
-        Integer sel = listTableState.selected();
-        if (sel != null && sel >= 0 && sel < filteredComponents.size()) {
-            ComponentInfo info = filteredComponents.get(sel);
-            allOptionsUnfiltered = new ArrayList<>();
-            allOptionsUnfiltered.addAll(info.endpointOptions);
-            allOptionsUnfiltered.addAll(info.componentOptions);
-        } else {
-            allOptionsUnfiltered = Collections.emptyList();
-        }
-        optionFilter.setLength(0);
-        optionFullText = false;
-        applyOptionFilter();
-        descriptionScroll = 0;
-    }
-
-    // ---- Event Handling ----
-
-    private boolean handleEvent(Event event, TuiRunner runner) {
-        if (event instanceof KeyEvent ke) {
-            // Quit
-            if (ke.isQuit()) {
-                runner.quit();
-                return true;
-            }
-
-            // Escape: clear filter first, then go back, then quit
-            if (ke.isCancel()) {
-                if (focus == FOCUS_OPTIONS && (!optionFilter.isEmpty() || 
optionFullText)) {
-                    optionFilter.setLength(0);
-                    optionFullText = false;
-                    applyOptionFilter();
-                    return true;
-                }
-                if (focus == FOCUS_OPTIONS) {
-                    focus = FOCUS_LIST;
-                    descriptionScroll = 0;
-                    return true;
-                }
-                if (!componentFilter.isEmpty() || componentFullText) {
-                    componentFilter.setLength(0);
-                    componentFullText = false;
-                    applyComponentFilter();
-                    return true;
-                }
-                runner.quit();
-                return true;
-            }
-
-            // Backspace: delete from active filter
-            if (ke.isDeleteBackward()) {
-                if (focus == FOCUS_LIST && !componentFilter.isEmpty()) {
-                    componentFilter.deleteCharAt(componentFilter.length() - 1);
-                    applyComponentFilter();
-                } else if (focus == FOCUS_OPTIONS && !optionFilter.isEmpty()) {
-                    optionFilter.deleteCharAt(optionFilter.length() - 1);
-                    applyOptionFilter();
-                }
-                return true;
-            }
-
-            // Panel switching — only when no active filter on current panel
-            if (ke.isFocusNext()) {
-                if (focus == FOCUS_LIST) {
-                    focus = FOCUS_OPTIONS;
-                } else {
-                    focus = FOCUS_LIST;
-                }
-                descriptionScroll = 0;
-                return true;
-            }
-            if (ke.isRight() && focus == FOCUS_LIST) {
-                focus = FOCUS_OPTIONS;
-                descriptionScroll = 0;
-                return true;
-            }
-            if (ke.isLeft() && focus == FOCUS_OPTIONS) {
-                focus = FOCUS_LIST;
-                descriptionScroll = 0;
-                return true;
-            }
-
-            // Enter drills into options
-            if (ke.isConfirm() && focus == FOCUS_LIST) {
-                focus = FOCUS_OPTIONS;
-                descriptionScroll = 0;
-                return true;
-            }
-
-            // Navigation
-            if (ke.isUp()) {
-                if (focus == FOCUS_LIST) {
-                    listTableState.selectPrevious();
-                    updateSelectedComponent();
-                } else {
-                    optionsTableState.selectPrevious();
-                    descriptionScroll = 0;
-                }
-                return true;
-            }
-            if (ke.isDown()) {
-                if (focus == FOCUS_LIST) {
-                    listTableState.selectNext(filteredComponents.size());
-                    updateSelectedComponent();
-                } else {
-                    optionsTableState.selectNext(filteredOptions.size());
-                    descriptionScroll = 0;
-                }
-                return true;
-            }
-            if (ke.isPageUp()) {
-                descriptionScroll = Math.max(0, descriptionScroll - 5);
-                return true;
-            }
-            if (ke.isPageDown()) {
-                descriptionScroll += 5;
-                return true;
-            }
-
-            // '/' toggles full-text search mode
-            if (ke.isChar('/')) {
-                if (focus == FOCUS_LIST) {
-                    componentFullText = !componentFullText;
-                    applyComponentFilter();
-                } else {
-                    optionFullText = !optionFullText;
-                    applyOptionFilter();
-                }
-                return true;
-            }
-
-            // Typing filters the active panel
-            if (ke.code() == KeyCode.CHAR) {
-                if (focus == FOCUS_LIST) {
-                    componentFilter.append(ke.string());
-                    applyComponentFilter();
-                } else {
-                    optionFilter.append(ke.string());
-                    applyOptionFilter();
-                }
-                return true;
-            }
-        }
-        return false;
-    }
-
-    // ---- Rendering ----
-
-    private void render(Frame frame) {
-        Rect area = frame.area();
-
-        // Layout: header (3) + top panels (fill) + separator (1) + 
description (40%) + footer (1)
-        List<Rect> mainChunks = Layout.vertical()
-                .constraints(
-                        Constraint.length(3),
-                        Constraint.percentage(50),
-                        Constraint.length(1),
-                        Constraint.fill(),
-                        Constraint.length(1))
-                .split(area);
-
-        renderHeader(frame, mainChunks.get(0));
-        renderTopPanels(frame, mainChunks.get(1));
-        renderSeparator(frame, mainChunks.get(2));
-        renderDescription(frame, mainChunks.get(3));
-        renderFooter(frame, mainChunks.get(4));
-    }
-
-    private void renderHeader(Frame frame, Rect area) {
-        Line titleLine = Line.from(
-                Span.styled(" Camel Catalog", 
Style.EMPTY.fg(Theme.accent()).bold()),
-                Span.raw("  "),
-                Span.styled(filteredComponents.size() + "/" + 
allComponents.size() + " components",
-                        Style.EMPTY.fg(Theme.accent())));
-
-        Block headerBlock = Block.builder()
-                .borderType(BorderType.ROUNDED).borders(Borders.ALL)
-                .title(" Apache Camel ")
-                .build();
-
-        frame.renderWidget(
-                
Paragraph.builder().text(Text.from(titleLine)).block(headerBlock).build(),
-                area);
-    }
-
-    private void renderTopPanels(Frame frame, Rect area) {
-        // Split horizontally: component list (30%) + options table (70%)
-        List<Rect> chunks = Layout.horizontal()
-                .constraints(Constraint.percentage(30), 
Constraint.percentage(70))
-                .split(area);
-
-        renderComponentList(frame, chunks.get(0));
-        renderOptionsTable(frame, chunks.get(1));
-    }
-
-    private void renderComponentList(Frame frame, Rect area) {
-        List<Row> rows = new ArrayList<>();
-        for (ComponentInfo comp : filteredComponents) {
-            Style nameStyle = comp.deprecated
-                    ? Theme.error().dim()
-                    : Style.EMPTY.fg(Theme.accent());
-            String label = comp.name;
-            if (comp.deprecated) {
-                label = label + " (deprecated)";
-            }
-            rows.add(Row.from(Cell.from(Span.styled(label, nameStyle))));
-        }
-
-        if (rows.isEmpty()) {
-            rows.add(Row.from(Cell.from(Span.styled("No matching components", 
Style.EMPTY.dim()))));
-        }
-
-        Style borderStyle = focus == FOCUS_LIST
-                ? Style.EMPTY.fg(Theme.accent())
-                : Style.EMPTY;
-
-        String modePrefix = componentFullText ? "/" : "";
-        String listTitle = componentFilter.isEmpty() && !componentFullText
-                ? " Components "
-                : " Components [" + modePrefix + componentFilter + "] ";
-
-        Table table = Table.builder()
-                .rows(rows)
-                .widths(Constraint.fill())
-                .highlightStyle(Theme.selectionBg())
-                .highlightSpacing(Table.HighlightSpacing.ALWAYS)
-                .block(Block.builder()
-                        .borderType(BorderType.ROUNDED).borders(Borders.ALL)
-                        .borderStyle(borderStyle)
-                        .title(listTitle)
-                        .build())
-                .build();
-
-        frame.renderStatefulWidget(table, area, listTableState);
-    }
-
-    private void renderOptionsTable(Frame frame, Rect area) {
-        Style borderStyle = focus == FOCUS_OPTIONS
-                ? Style.EMPTY.fg(Theme.accent())
-                : Style.EMPTY;
-
-        String optModePrefix = optionFullText ? "/" : "";
-        String optTitle = optionFilter.isEmpty() && !optionFullText
-                ? " Options "
-                : " Options [" + optModePrefix + optionFilter + "] ";
-
-        if (filteredOptions.isEmpty()) {
-            String emptyMsg = allOptionsUnfiltered.isEmpty() ? " Select a 
component" : " No matching options";
-            frame.renderWidget(
-                    Paragraph.builder()
-                            .text(Text.from(Line.from(
-                                    Span.styled(emptyMsg, Style.EMPTY.dim()))))
-                            .block(Block.builder()
-                                    
.borderType(BorderType.ROUNDED).borders(Borders.ALL)
-                                    .borderStyle(borderStyle)
-                                    .title(optTitle)
-                                    .build())
-                            .build(),
-                    area);
-            return;
-        }
-
-        List<Row> rows = new ArrayList<>();
-        for (OptionInfo opt : filteredOptions) {
-            rows.add(optionToRow(opt));
-        }
-
-        Row header = Row.from(
-                Cell.from(Span.styled("NAME", Style.EMPTY.bold())),
-                Cell.from(Span.styled("TYPE", Style.EMPTY.bold())),
-                Cell.from(Span.styled("REQ", Style.EMPTY.bold())),
-                Cell.from(Span.styled("DEFAULT", Style.EMPTY.bold())),
-                Cell.from(Span.styled("KIND", Style.EMPTY.bold())));
-
-        Table table = Table.builder()
-                .rows(rows)
-                .header(header)
-                .widths(
-                        Constraint.length(25),
-                        Constraint.length(12),
-                        Constraint.length(4),
-                        Constraint.length(12),
-                        Constraint.fill())
-                .highlightStyle(Theme.selectionBg())
-                .highlightSpacing(Table.HighlightSpacing.ALWAYS)
-                .block(Block.builder()
-                        .borderType(BorderType.ROUNDED).borders(Borders.ALL)
-                        .borderStyle(borderStyle)
-                        .title(optTitle)
-                        .build())
-                .build();
-
-        frame.renderStatefulWidget(table, area, optionsTableState);
-    }
-
-    private void renderSeparator(Frame frame, Rect area) {
-        frame.renderWidget(
-                Block.builder()
-                        .borders(Borders.TOP_ONLY)
-                        .borderStyle(Theme.muted())
-                        .build(),
-                area);
-    }
-
-    private void renderDescription(Frame frame, Rect area) {
-        List<Line> lines = new ArrayList<>();
-        String title;
-        int wrapWidth = Math.max(20, area.width() - 4);
-
-        if (focus == FOCUS_OPTIONS) {
-            // Show selected option detail
-            Integer sel = optionsTableState.selected();
-            if (sel != null && sel >= 0 && sel < filteredOptions.size()) {
-                OptionInfo opt = filteredOptions.get(sel);
-                title = " " + opt.name + " ";
-
-                List<String[]> fields = new ArrayList<>();
-                fields.add(new String[] { "Name", opt.name });
-                if (opt.kind != null && !opt.kind.isEmpty()) {
-                    fields.add(new String[] { "Kind", opt.kind });
-                }
-                fields.add(new String[] { "Type", opt.type });
-                fields.add(new String[] { "Required", opt.required ? "Yes" : 
"No" });
-                if (!opt.defaultValue.isEmpty()) {
-                    fields.add(new String[] { "Default", opt.defaultValue });
-                }
-                if (opt.group != null && !opt.group.isEmpty()) {
-                    fields.add(new String[] { "Group", opt.group });
-                }
-                if (opt.enumValues != null && !opt.enumValues.isEmpty()) {
-                    fields.add(new String[] { "Values", opt.enumValues });
-                }
-                flowFields(lines, fields, wrapWidth, opt);
-
-                lines.add(Line.from(Span.raw("")));
-                if (opt.description != null && !opt.description.isEmpty()) {
-                    lines.add(Line.from(Span.raw(" " + opt.description)));
-                }
-            } else {
-                title = " Description ";
-            }
-        } else {
-            // Show selected component detail
-            Integer sel = listTableState.selected();
-            if (sel != null && sel >= 0 && sel < filteredComponents.size()) {
-                ComponentInfo comp = filteredComponents.get(sel);
-                title = " " + comp.title + " ";
-
-                List<String[]> fields = new ArrayList<>();
-                fields.add(new String[] { "Scheme", comp.scheme });
-                fields.add(new String[] { "Syntax", comp.syntax });
-                fields.add(new String[] { "Label", comp.label });
-                fields.add(new String[] { "Maven", comp.groupId + ":" + 
comp.artifactId + ":" + comp.version });
-                fields.add(new String[] {
-                        "Options",
-                        comp.endpointOptions.size() + " endpoint, " + 
comp.componentOptions.size() + " component" });
-                if (comp.deprecated) {
-                    fields.add(new String[] { "Status", "DEPRECATED" });
-                }
-                flowFields(lines, fields, wrapWidth, null);
-
-                lines.add(Line.from(Span.raw("")));
-                if (comp.description != null && !comp.description.isEmpty()) {
-                    lines.add(Line.from(Span.raw(" " + comp.description)));
-                }
-            } else {
-                title = " Description ";
-            }
-        }
-
-        frame.renderWidget(
-                Paragraph.builder()
-                        .text(Text.from(lines))
-                        .overflow(Overflow.WRAP_WORD)
-                        .scroll(descriptionScroll)
-                        .block(Block.builder()
-                                
.borderType(BorderType.ROUNDED).borders(Borders.ALL)
-                                .title(title)
-                                .build())
-                        .build(),
-                area);
-    }
-
-    private Row optionToRow(OptionInfo opt) {
-        Style nameStyle = opt.required
-                ? Style.EMPTY.fg(Theme.accent()).bold()
-                : Style.EMPTY.fg(Theme.accent());
-
-        return Row.from(
-                Cell.from(Span.styled(opt.name, nameStyle)),
-                Cell.from(Span.styled(opt.type, Style.EMPTY.dim())),
-                Cell.from(opt.required
-                        ? Span.styled("*", Theme.error().bold())
-                        : Span.raw("")),
-                Cell.from(Span.styled(opt.defaultValue, Style.EMPTY.dim())),
-                Cell.from(Span.styled(opt.kind != null ? opt.kind : "", 
Style.EMPTY.dim())));
-    }
-
-    private void renderFooter(Frame frame, Rect area) {
-        Line footer = Line.from(
-                Span.styled(" Type", Theme.label().bold()),
-                Span.raw(" name filter  "),
-                Span.styled("/", Theme.label().bold()),
-                Span.raw(" full-text  "),
-                Span.styled("Esc", Theme.label().bold()),
-                Span.raw(" clear/back/quit  "),
-                Span.styled("\u2191\u2193", Theme.label().bold()),
-                Span.raw(" navigate  "),
-                Span.styled("\u2190\u2192", Theme.label().bold()),
-                Span.raw("/"),
-                Span.styled("Tab", Theme.label().bold()),
-                Span.raw(" panels  "),
-                Span.styled("PgUp/Dn", Theme.label().bold()),
-                Span.raw(" scroll"));
-
-        frame.renderWidget(Paragraph.from(footer), area);
-    }
-
-    // ---- Helpers ----
-
-    private static void flowFields(List<Line> lines, List<String[]> fields, 
int maxWidth, OptionInfo opt) {
-        List<Span> currentSpans = new ArrayList<>();
-        int currentLen = 1; // leading space
-        String gap = "    ";
-
-        for (String[] field : fields) {
-            String label = field[0] + ": ";
-            String value = field[1];
-            int fieldLen = CharWidth.of(label) + CharWidth.of(value);
-
-            // If adding this field would exceed width, flush current line
-            if (!currentSpans.isEmpty() && currentLen + CharWidth.of(gap) + 
fieldLen > maxWidth) {
-                lines.add(Line.from(currentSpans));
-                currentSpans = new ArrayList<>();
-                currentLen = 1;
-            }
-
-            if (!currentSpans.isEmpty()) {
-                currentSpans.add(Span.raw(gap));
-                currentLen += CharWidth.of(gap);
-            } else {
-                currentSpans.add(Span.raw(" "));
-            }
-
-            currentSpans.add(Span.styled(label, Theme.label().bold()));
-
-            // Apply special styling for certain values
-            Style valueStyle;
-            if ("DEPRECATED".equals(value)) {
-                valueStyle = Theme.error().bold();
-            } else if (opt != null && "Required".equals(field[0]) && 
opt.required) {
-                valueStyle = Theme.error().bold();
-            } else if (opt != null && "Name".equals(field[0])) {
-                valueStyle = Style.EMPTY.fg(Theme.accent()).bold();
-            } else if ("Label".equals(field[0]) || "Values".equals(field[0])) {
-                valueStyle = Style.EMPTY.fg(Theme.accent());
-            } else {
-                valueStyle = Style.EMPTY;
-            }
-            currentSpans.add(Span.styled(value, valueStyle));
-            currentLen += fieldLen;
-        }
-
-        if (!currentSpans.isEmpty()) {
-            lines.add(Line.from(currentSpans));
-        }
-    }
-
-    // ---- Data Classes ----
-
-    static class ComponentInfo {
-        String name;
-        String title;
-        String description;
-        String label;
-        String groupId;
-        String artifactId;
-        String version;
-        String scheme;
-        String syntax;
-        boolean deprecated;
-        final List<OptionInfo> componentOptions = new ArrayList<>();
-        final List<OptionInfo> endpointOptions = new ArrayList<>();
-    }
-
-    static class OptionInfo {
-        String name;
-        String kind;
-        String type;
-        boolean required;
-        String defaultValue;
-        String description;
-        String group;
-        String enumValues;
-    }
-}
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CatalogTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CatalogTab.java
new file mode 100644
index 000000000000..ea88d61c8153
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CatalogTab.java
@@ -0,0 +1,535 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.dsl.jbang.core.commands.tui;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import dev.tamboui.layout.Constraint;
+import dev.tamboui.layout.Rect;
+import dev.tamboui.style.Style;
+import dev.tamboui.terminal.Frame;
+import dev.tamboui.text.Line;
+import dev.tamboui.text.Span;
+import dev.tamboui.text.Text;
+import dev.tamboui.tui.event.KeyCode;
+import dev.tamboui.tui.event.KeyEvent;
+import dev.tamboui.widgets.block.Block;
+import dev.tamboui.widgets.block.BorderType;
+import dev.tamboui.widgets.block.Borders;
+import dev.tamboui.widgets.input.TextInputState;
+import dev.tamboui.widgets.paragraph.Paragraph;
+import dev.tamboui.widgets.table.Cell;
+import dev.tamboui.widgets.table.Row;
+import dev.tamboui.widgets.table.Table;
+import org.apache.camel.catalog.CamelCatalog;
+import org.apache.camel.dsl.jbang.core.common.CatalogLoader;
+import org.apache.camel.tooling.model.ArtifactModel;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+
+import static org.apache.camel.dsl.jbang.core.commands.tui.TuiHelper.*;
+
+/**
+ * TUI tab showing Camel catalog artifacts (components, data formats, 
languages, others) that the running integration
+ * uses, based on its declared Maven dependencies.
+ */
+class CatalogTab extends AbstractTableTab {
+
+    private static final String[] SCOPES = { "all", "component", "dataformat", 
"language", "other" };
+
+    private final AtomicBoolean loading = new AtomicBoolean(false);
+
+    private boolean filterInputActive;
+    private TextInputState filterInputState = new TextInputState("");
+    private String filterTerm;
+    private int scopeIndex;
+    private List<CatalogEntry> allEntries = Collections.emptyList();
+    private List<CatalogEntry> filteredEntries = Collections.emptyList();
+    private String lastPid;
+    private String errorMessage;
+    private boolean dataLoaded;
+
+    CatalogTab(MonitorContext ctx) {
+        super(ctx, "name", "kind", "title");
+    }
+
+    @Override
+    public void onTabSelected() {
+        String pid = ctx.selectedPid;
+        if (pid != null && !pid.equals(lastPid)) {
+            lastPid = pid;
+            allEntries = Collections.emptyList();
+            dataLoaded = false;
+        }
+        if (!dataLoaded) {
+            loadCatalogData();
+        }
+    }
+
+    @Override
+    public void onIntegrationChanged() {
+        allEntries = Collections.emptyList();
+        filteredEntries = Collections.emptyList();
+        filterTerm = null;
+        filterInputActive = false;
+        scopeIndex = 0;
+        lastPid = null;
+        errorMessage = null;
+        dataLoaded = false;
+        loading.set(false);
+        if (ctx.selectedPid != null) {
+            lastPid = ctx.selectedPid;
+            loadCatalogData();
+        }
+    }
+
+    @Override
+    public boolean handleKeyEvent(KeyEvent ke) {
+        if (filterInputActive) {
+            return handleFilterInput(ke);
+        }
+        return super.handleKeyEvent(ke);
+    }
+
+    @Override
+    protected boolean handleTabKeyEvent(KeyEvent ke) {
+        if (ke.isChar('/')) {
+            filterInputActive = true;
+            filterInputState = new TextInputState(filterTerm != null ? 
filterTerm : "");
+            return true;
+        }
+        if (ke.isCharIgnoreCase('f')) {
+            scopeIndex = (scopeIndex + 1) % SCOPES.length;
+            refilter();
+            return true;
+        }
+        return false;
+    }
+
+    private boolean handleFilterInput(KeyEvent ke) {
+        if (ke.isKey(KeyCode.ESCAPE)) {
+            filterInputActive = false;
+            return true;
+        }
+        if (ke.isConfirm()) {
+            String text = filterInputState.text().trim();
+            filterTerm = text.isEmpty() ? null : text;
+            filterInputActive = false;
+            refilter();
+            return true;
+        }
+        FormHelper.handleTextInput(ke, filterInputState);
+        return true;
+    }
+
+    @Override
+    public boolean handleEscape() {
+        if (filterTerm != null) {
+            filterTerm = null;
+            refilter();
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    protected int getRowCount() {
+        return filteredEntries.size();
+    }
+
+    @Override
+    public void render(Frame frame, Rect area) {
+        IntegrationInfo info = ctx.findSelectedIntegration();
+        if (info == null) {
+            renderNoSelection(frame, area);
+            return;
+        }
+
+        if (loading.get() && allEntries.isEmpty()) {
+            frame.renderWidget(
+                    Paragraph.builder()
+                            .text(Text.from(Line.from(Span.styled("  Loading 
catalog...", Style.EMPTY.dim()))))
+                            
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                                    .title(" Catalog ").build())
+                            .build(),
+                    area);
+            return;
+        }
+
+        if (errorMessage != null && allEntries.isEmpty()) {
+            frame.renderWidget(
+                    Paragraph.builder()
+                            .text(Text.from(Line.from(
+                                    Span.styled("  " + errorMessage, 
Theme.error()))))
+                            
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                                    .title(" Catalog ").build())
+                            .build(),
+                    area);
+            return;
+        }
+
+        renderContent(frame, area, info);
+    }
+
+    @Override
+    protected void renderContent(Frame frame, Rect area, IntegrationInfo info) 
{
+        List<CatalogEntry> sorted = new ArrayList<>(filteredEntries);
+        sorted.sort(this::sortEntry);
+
+        List<Row> rows = new ArrayList<>();
+        for (CatalogEntry entry : sorted) {
+            Style nameStyle = entry.deprecated
+                    ? Theme.error().dim()
+                    : Style.EMPTY.fg(Theme.accent());
+            String name = entry.deprecated ? entry.name + " (deprecated)" : 
entry.name;
+            Style kindStyle = kindStyle(entry.kind);
+            rows.add(Row.from(
+                    Cell.from(Span.styled(" " + name, nameStyle)),
+                    Cell.from(Span.styled(entry.kind, kindStyle)),
+                    Cell.from(entry.title),
+                    Cell.from(Span.styled(entry.label != null ? entry.label : 
"", Style.EMPTY.dim()))));
+        }
+
+        if (rows.isEmpty() && dataLoaded) {
+            rows.add(emptyRow("No catalog entries found", 4));
+        }
+
+        String scope = SCOPES[scopeIndex];
+        boolean filtered = filterTerm != null || !"all".equals(scope);
+        StringBuilder title = new StringBuilder(" Catalog ");
+        title.append('[');
+        if (filtered) {
+            
title.append(filteredEntries.size()).append('/').append(allEntries.size());
+        } else {
+            title.append(filteredEntries.size());
+        }
+        title.append(']');
+        if (!"all".equals(scope)) {
+            title.append(" scope:").append(scope);
+        }
+        if (filterTerm != null) {
+            title.append(" filter:\"").append(filterTerm).append('"');
+        }
+        title.append(' ');
+
+        Table table = Table.builder()
+                .rows(rows)
+                .header(Row.from(
+                        Cell.from(Span.styled(" " + sortLabel("NAME", "name"), 
sortStyle("name"))),
+                        Cell.from(Span.styled(sortLabel("KIND", "kind"), 
sortStyle("kind"))),
+                        Cell.from(Span.styled(sortLabel("TITLE", "title"), 
sortStyle("title"))),
+                        Cell.from(Span.styled("LABEL", Style.EMPTY.bold()))))
+                .widths(
+                        Constraint.length(30),
+                        Constraint.length(12),
+                        Constraint.length(30),
+                        Constraint.fill())
+                .highlightStyle(Theme.selectionBg())
+                
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                        .title(title.toString()).build())
+                .build();
+
+        lastTableArea = area;
+        frame.renderStatefulWidget(table, area, tableState);
+        renderScrollbar(frame, sorted.size());
+    }
+
+    @Override
+    public void renderFooter(List<Span> spans) {
+        if (filterInputActive) {
+            spans.add(Span.styled(" /", Theme.label().bold()));
+            spans.add(Span.raw(filterInputState.text() + "█  "));
+            hint(spans, "Enter", "filter");
+            hintLast(spans, "Esc", "cancel");
+            return;
+        }
+        hint(spans, "Esc", filterTerm != null ? "clear" : "back");
+        hint(spans, "s", "sort");
+        hint(spans, "f", "scope [" + SCOPES[scopeIndex] + "]");
+        if (filterTerm != null) {
+            spans.add(Span.styled("  /", Theme.label().bold()));
+            spans.add(Span.raw("\"" + filterTerm + "\"  "));
+        } else {
+            hint(spans, "/", "filter");
+        }
+        hintLast(spans, TuiIcons.HINT_SCROLL, "navigate");
+    }
+
+    private int sortEntry(CatalogEntry a, CatalogEntry b) {
+        int result = switch (sort) {
+            case "kind" -> a.kind.compareToIgnoreCase(b.kind);
+            case "title" -> a.title.compareToIgnoreCase(b.title);
+            default -> a.name.compareToIgnoreCase(b.name); // "name"
+        };
+        return sortReversed ? -result : result;
+    }
+
+    private static Style kindStyle(String kind) {
+        return switch (kind) {
+            case "component" -> Style.EMPTY.fg(Theme.accent());
+            case "dataformat" -> Theme.success();
+            case "language" -> Theme.warning();
+            default -> Style.EMPTY.dim();
+        };
+    }
+
+    // ---- Data Loading ----
+
+    private void loadCatalogData() {
+        IntegrationInfo info = ctx.findSelectedIntegration();
+        if (info == null || ctx.runner == null) {
+            return;
+        }
+        if (!loading.compareAndSet(false, true)) {
+            return;
+        }
+
+        ctx.runner.scheduler().execute(() -> {
+            try {
+                DependencyLoader.LoadResult result = 
DependencyLoader.loadDependencies(info);
+                if (result.error() != null && result.entries().isEmpty()) {
+                    applyResult(Collections.emptyList(), result.error());
+                    return;
+                }
+
+                Set<String> appArtifacts = new HashSet<>();
+                for (DependencyLoader.DepEntry dep : result.entries()) {
+                    if (dep.isCamel()) {
+                        appArtifacts.add(dep.groupId() + ":" + 
dep.artifactId());
+                    }
+                }
+
+                CamelCatalog catalog = CatalogLoader.loadCatalog(null, 
info.camelVersion, true);
+                if (catalog == null) {
+                    applyResult(Collections.emptyList(), "Could not load 
catalog for version " + info.camelVersion);
+                    return;
+                }
+
+                List<CatalogEntry> entries = new ArrayList<>();
+                collectArtifacts(catalog, "component", 
catalog.findComponentNames(), appArtifacts, entries);
+                collectArtifacts(catalog, "dataformat", 
catalog.findDataFormatNames(), appArtifacts, entries);
+                collectArtifacts(catalog, "language", 
catalog.findLanguageNames(), appArtifacts, entries);
+                collectArtifacts(catalog, "other", catalog.findOtherNames(), 
appArtifacts, entries);
+
+                entries.sort((a, b) -> a.name.compareToIgnoreCase(b.name));
+                applyResult(entries, null);
+            } catch (Exception e) {
+                applyResult(Collections.emptyList(), "Error: " + 
e.getMessage());
+            } finally {
+                loading.set(false);
+            }
+        });
+    }
+
+    @SuppressWarnings("unchecked")
+    private static void collectArtifacts(
+            CamelCatalog catalog, String kind, List<String> names,
+            Set<String> appArtifacts, List<CatalogEntry> entries) {
+        for (String name : names) {
+            try {
+                ArtifactModel<?> model = (ArtifactModel<?>) switch (kind) {
+                    case "component" -> catalog.componentModel(name);
+                    case "dataformat" -> catalog.dataFormatModel(name);
+                    case "language" -> catalog.languageModel(name);
+                    case "other" -> catalog.otherModel(name);
+                    default -> null;
+                };
+                if (model == null) {
+                    continue;
+                }
+                String ga = model.getGroupId() + ":" + model.getArtifactId();
+                if (appArtifacts.contains(ga)) {
+                    CatalogEntry entry = new CatalogEntry();
+                    entry.name = model.getName();
+                    entry.kind = kind;
+                    entry.title = model.getTitle() != null ? model.getTitle() 
: name;
+                    entry.description = model.getDescription() != null ? 
model.getDescription() : "";
+                    entry.label = model.getLabel();
+                    entry.artifactId = model.getArtifactId();
+                    entry.deprecated = model.isDeprecated();
+                    entries.add(entry);
+                }
+            } catch (Exception e) {
+                // skip unparseable entries
+            }
+        }
+    }
+
+    private void applyResult(List<CatalogEntry> entries, String error) {
+        if (ctx.runner == null) {
+            return;
+        }
+        ctx.runner.runOnRenderThread(() -> {
+            allEntries = entries;
+            errorMessage = error;
+            dataLoaded = true;
+            refilter();
+        });
+    }
+
+    private void refilter() {
+        List<CatalogEntry> result = new ArrayList<>();
+        String ft = filterTerm != null ? filterTerm.toLowerCase() : null;
+        String scope = SCOPES[scopeIndex];
+        for (CatalogEntry entry : allEntries) {
+            if (!"all".equals(scope) && !scope.equals(entry.kind)) {
+                continue;
+            }
+            if (ft != null
+                    && !entry.name.toLowerCase().contains(ft)
+                    && !entry.title.toLowerCase().contains(ft)
+                    && !(entry.label != null && 
entry.label.toLowerCase().contains(ft))) {
+                continue;
+            }
+            result.add(entry);
+        }
+        filteredEntries = result;
+        if (!filteredEntries.isEmpty()) {
+            tableState.select(0);
+        }
+    }
+
+    @Override
+    public boolean setFilter(String filter) {
+        filterTerm = filter != null && !filter.isEmpty() ? filter : null;
+        refilter();
+        return true;
+    }
+
+    @Override
+    public boolean setInputValue(String field, String value) {
+        if ("filter".equals(field)) {
+            return setFilter(value);
+        }
+        return false;
+    }
+
+    @Override
+    public SelectionContext getSelectionContext() {
+        if (filteredEntries.isEmpty()) {
+            return null;
+        }
+        List<CatalogEntry> sorted = new ArrayList<>(filteredEntries);
+        sorted.sort(this::sortEntry);
+        List<String> items = sorted.stream().map(e -> e.name).toList();
+        Integer sel = tableState.selected();
+        return new SelectionContext("table", items, sel != null ? sel : -1, 
items.size(), "Catalog");
+    }
+
+    @Override
+    public String description() {
+        return "Camel catalog artifacts used by the integration";
+    }
+
+    @Override
+    public String getHelpText() {
+        return """
+                # Catalog
+
+                The Catalog tab shows which Camel catalog artifacts 
(components, data
+                formats, languages, and others) the running integration uses, 
based on
+                its declared Maven dependencies.
+
+                For each Camel dependency in the integration, the tab 
cross-references
+                the Camel catalog to identify all artifacts provided by that 
dependency.
+                For example, `camel-core` provides the `direct`, `seda`, 
`timer`, `bean`,
+                `log`, and `mock` components, while `camel-kafka` provides the 
`kafka`
+                component.
+
+                ## Table Columns
+
+                - **NAME** — The catalog artifact name (e.g., `kafka`, 
`timer`, `json-jackson`)
+                - **KIND** — The artifact type: `component`, `dataformat`, 
`language`, or `other`
+                - **TITLE** — Human-readable title (e.g., "Apache Kafka", 
"Timer")
+                - **LABEL** — Category labels (e.g., "messaging", 
"scheduling", "transformation")
+
+                Deprecated artifacts are shown dimmed with a "(deprecated)" 
suffix.
+
+                ## Scope
+
+                Press `f` to cycle the scope filter:
+
+                - **all** — show all catalog artifacts (default)
+                - **component** — show only components
+                - **dataformat** — show only data formats
+                - **language** — show only expression languages
+                - **other** — show only miscellaneous artifacts
+
+                ## Filter
+
+                Press `/` to open the filter input. Type a search term and 
press
+                `Enter` to filter by substring match on name, title, or label.
+
+                ## Keys
+
+                - `s` — cycle sort column (name, kind, title)
+                - `S` — reverse sort order
+                - `f` — cycle scope
+                - `/` — open filter
+                - `Esc` — clear filter or back
+                """;
+    }
+
+    @Override
+    public JsonObject getTableDataAsJson() {
+        if (filteredEntries.isEmpty()) {
+            return null;
+        }
+        List<CatalogEntry> sorted = new ArrayList<>(filteredEntries);
+        sorted.sort(this::sortEntry);
+        JsonObject result = new JsonObject();
+        result.put("tab", "Catalog");
+        JsonArray rows = new JsonArray();
+        for (CatalogEntry e : sorted) {
+            JsonObject row = new JsonObject();
+            row.put("name", e.name);
+            row.put("kind", e.kind);
+            row.put("title", e.title);
+            row.put("description", e.description);
+            if (e.label != null) {
+                row.put("label", e.label);
+            }
+            row.put("artifactId", e.artifactId);
+            if (e.deprecated) {
+                row.put("deprecated", true);
+            }
+            rows.add(row);
+        }
+        result.put("rows", rows);
+        result.put("totalRows", sorted.size());
+        Integer sel = tableState.selected();
+        result.put("selectedIndex", sel != null ? sel : -1);
+        return result;
+    }
+
+    // ---- Data Class ----
+
+    static class CatalogEntry {
+        String name;
+        String kind;
+        String title;
+        String description;
+        String label;
+        String artifactId;
+        boolean deprecated;
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TabRegistry.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TabRegistry.java
index 1a62be602b91..51a991c8404e 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TabRegistry.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TabRegistry.java
@@ -82,6 +82,7 @@ class TabRegistry {
     private ConfigurationTab configurationTab;
     private BeansTab beansTab;
     private BrowseTab browseTab;
+    private CatalogTab catalogTab;
     private ClasspathTab classpathTab;
     private MavenDependenciesTab mavenDependenciesTab;
     private CveAuditTab cveAuditTab;
@@ -132,6 +133,7 @@ class TabRegistry {
         configurationTab = new ConfigurationTab(ctx);
         beansTab = new BeansTab(ctx);
         browseTab = new BrowseTab(ctx);
+        catalogTab = new CatalogTab(ctx);
         classpathTab = new ClasspathTab(ctx);
         mavenDependenciesTab = new MavenDependenciesTab(ctx);
         cveAuditTab = new CveAuditTab(ctx);
@@ -154,6 +156,7 @@ class TabRegistry {
         moreTabs = List.of(
                 new MoreTab(TuiIcons.TAB_BEANS, "Beans", "&Beans", beansTab),
                 new MoreTab(TuiIcons.TAB_BROWSE, "Browse", "Bro&wse", 
browseTab),
+                new MoreTab(TuiIcons.TAB_CATALOG, "Catalog", "Catal&og", 
catalogTab),
                 new MoreTab(TuiIcons.TAB_CIRCUIT_BREAKER, "Circuit Breaker", 
"&Circuit Breaker", circuitBreakerTab),
                 new MoreTab(TuiIcons.TAB_CLASSPATH, "Classpath", "Cl&asspath", 
classpathTab),
                 new MoreTab(TuiIcons.TAB_CONFIGURATION, "Configuration", 
"Confi&guration", configurationTab),
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiIcons.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiIcons.java
index 95237e6e990e..395d0a118149 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiIcons.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiIcons.java
@@ -126,6 +126,7 @@ final class TuiIcons {
     // ---- More submenu tabs ----
     static final String TAB_BEANS = JAVA;
     static final String TAB_BROWSE = MESSAGE;
+    static final String TAB_CATALOG = "📖";
     static final String TAB_CIRCUIT_BREAKER = "⚡";
     static final String TAB_CLASSPATH = BUNDLED;
     static final String TAB_CONFIGURATION = DOCUMENT;
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiPlugin.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiPlugin.java
index e8eb93466796..d51a01e23aa4 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiPlugin.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiPlugin.java
@@ -34,8 +34,7 @@ public class TuiPlugin implements Plugin {
     @Override
     public void customize(CommandLine commandLine, CamelJBangMain main) {
         var cmd = new CommandLine(new TuiCommand(main, classLoader))
-                .addSubcommand("monitor", new CommandLine(new 
CamelMonitor(main, classLoader)))
-                .addSubcommand("catalog", new CommandLine(new 
CamelCatalogTui(main, classLoader)));
+                .addSubcommand("monitor", new CommandLine(new 
CamelMonitor(main, classLoader)));
 
         commandLine.addSubcommand("tui", cmd);
     }

Reply via email to