This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch feature/tui-mcp-structured-tools in repository https://gitbox.apache.org/repos/asf/camel.git
commit 58624e178b7d22e7f321f3f2373e3cbd285cb993 Author: Claus Ibsen <[email protected]> AuthorDate: Wed Jun 3 12:49:43 2026 +0200 CAMEL-23679: camel-tui - Add structured data access MCP tools Co-Authored-By: Claude <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- .../camel/dsl/jbang/core/common/RuntimeHelper.java | 19 ++++ .../dsl/jbang/core/commands/tui/ActionsPopup.java | 36 +++++++ .../dsl/jbang/core/commands/tui/CamelMonitor.java | 42 ++++++++ .../dsl/jbang/core/commands/tui/ConsumersTab.java | 34 ++++++ .../jbang/core/commands/tui/DiagramSupport.java | 4 + .../dsl/jbang/core/commands/tui/DiagramTab.java | 14 +++ .../dsl/jbang/core/commands/tui/EndpointsTab.java | 38 +++++++ .../dsl/jbang/core/commands/tui/ErrorsTab.java | 43 ++++++++ .../dsl/jbang/core/commands/tui/HealthTab.java | 30 ++++++ .../dsl/jbang/core/commands/tui/InflightTab.java | 30 ++++++ .../camel/dsl/jbang/core/commands/tui/LogTab.java | 42 ++++++++ .../dsl/jbang/core/commands/tui/MonitorTab.java | 5 + .../dsl/jbang/core/commands/tui/OverviewTab.java | 35 ++++++ .../dsl/jbang/core/commands/tui/RoutesTab.java | 35 ++++++ .../dsl/jbang/core/commands/tui/TuiMcpServer.java | 117 +++++++++++++++++++++ 15 files changed, 524 insertions(+) diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/RuntimeHelper.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/RuntimeHelper.java index 94897399442a..11bd051dc0cd 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/RuntimeHelper.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/RuntimeHelper.java @@ -233,6 +233,25 @@ public final class RuntimeHelper { } } + public static JsonObject sendMessage(long pid, String endpoint, String body, String headers) { + String result = executeAction(pid, "send", root -> { + root.put("endpoint", endpoint); + if (body != null) { + root.put("body", body); + } + if (headers != null) { + root.put("headers", headers); + } + }); + try { + return (JsonObject) Jsoner.deserialize(result); + } catch (Exception e) { + JsonObject wrapper = new JsonObject(); + wrapper.put("result", result); + return wrapper; + } + } + public static JsonObject readStatusFromFile(Path path) { try { if (Files.exists(path) && path.toFile().length() > 0) { diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java index 1fab0476680f..c37c0738799c 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java @@ -2034,6 +2034,42 @@ class ActionsPopup { record InfraServiceEntry(String alias, String description, List<String> implementations, boolean running) { } + boolean executeActionByName(String name) { + if (name == null || name.isBlank()) { + return false; + } + String normalized = name.replace("-", "_").toUpperCase(); + Action action; + try { + action = Action.valueOf(normalized); + } catch (IllegalArgumentException e) { + return false; + } + switch (action) { + case RESET_STATS -> { + if (resetStatsAction != null) { + resetStatsAction.run(); + } + } + case RESET_SCREEN -> { + if (resetScreenAction != null) { + resetScreenAction.run(); + } + } + case SCREENSHOT -> screenshotAction.run(); + case SHOW_KEYSTROKES -> toggleKeystrokes.run(); + case TAPE_RECORDING -> toggleTapeRecording.run(); + case DOCTOR -> doctorPopup.open(); + case CAPTION -> captionOverlay.openInline(); + case MCP_INFO -> openMcpInfo(); + case MCP_LOG -> openMcpLog(); + default -> { + return false; + } + } + return true; + } + private record PendingLaunch(String name, Process process, Path outputFile, long startTime) { } diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java index 737b0dc326db..f3f03a59f330 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java @@ -79,6 +79,7 @@ import org.apache.camel.dsl.jbang.core.commands.CamelCommand; import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain; import org.apache.camel.dsl.jbang.core.common.CommandLineHelper; import org.apache.camel.dsl.jbang.core.common.PathUtils; +import org.apache.camel.dsl.jbang.core.common.RuntimeHelper; import org.apache.camel.dsl.jbang.core.common.VersionHelper; import org.apache.camel.util.json.JsonArray; import org.apache.camel.util.json.JsonObject; @@ -2822,6 +2823,47 @@ public class CamelMonitor extends CamelCommand { return null; } + JsonObject getTableData(String tabName) { + if (tabName != null && !tabName.isBlank()) { + String prev = getActiveTabName(); + String switched = navigateToTab(tabName); + if (switched == null) { + return null; + } + } + MonitorTab tab = activeTab(); + return tab != null ? tab.getTableDataAsJson() : null; + } + + boolean executeAction(String actionName) { + return actionsPopup.executeActionByName(actionName); + } + + JsonObject getLogData(int limit, String filter, String level) { + return logTab.getLogDataAsJson(limit, filter, level); + } + + JsonObject getDiagramData() { + MonitorTab tab = activeTab(); + if (tab instanceof DiagramTab dt) { + return dt.getTableDataAsJson(); + } + return diagramTab.getTableDataAsJson(); + } + + JsonObject sendMessage(String endpoint, String body, String headers) { + if (ctx.selectedPid == null) { + return null; + } + long pid; + try { + pid = Long.parseLong(ctx.selectedPid); + } catch (NumberFormatException e) { + return null; + } + return RuntimeHelper.sendMessage(pid, endpoint, body, headers); + } + private record PendingKey(KeyEvent event, long fireAt) { } diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConsumersTab.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConsumersTab.java index 37ba385ea62c..b4a147a883a4 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConsumersTab.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConsumersTab.java @@ -34,6 +34,8 @@ 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.util.json.JsonArray; +import org.apache.camel.util.json.JsonObject; import static org.apache.camel.dsl.jbang.core.commands.tui.MonitorContext.*; @@ -395,4 +397,36 @@ class ConsumersTab implements MonitorTab { - `S` — reverse sort order """; } + + @Override + public JsonObject getTableDataAsJson() { + IntegrationInfo info = ctx.findSelectedIntegration(); + if (info == null) { + return null; + } + JsonObject result = new JsonObject(); + result.put("tab", "Consumers"); + JsonArray rows = new JsonArray(); + for (ConsumerInfo ci : info.consumers) { + JsonObject row = new JsonObject(); + row.put("id", ci.id); + row.put("uri", ci.uri); + row.put("state", ci.state); + row.put("className", ci.className); + row.put("scheduled", ci.scheduled); + row.put("inflight", ci.inflight); + if (ci.totalCounter != null) { + row.put("totalCounter", ci.totalCounter); + } + if (ci.polling != null) { + row.put("polling", ci.polling); + } + rows.add(row); + } + result.put("rows", rows); + result.put("totalRows", info.consumers.size()); + Integer sel = tableState.selected(); + result.put("selectedIndex", sel != null ? sel : -1); + return result; + } } diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramSupport.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramSupport.java index 43985f4db3fc..193419ff8e52 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramSupport.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramSupport.java @@ -84,6 +84,10 @@ class DiagramSupport { private int cropH = -1; private final AtomicBoolean loading = new AtomicBoolean(false); + List<String> getLines() { + return lines; + } + boolean isShowDiagram() { return showDiagram; } diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramTab.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramTab.java index 3c08ef6fbbee..034485a3729f 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramTab.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramTab.java @@ -28,6 +28,7 @@ import dev.tamboui.tui.event.KeyEvent; import dev.tamboui.widgets.block.Block; import dev.tamboui.widgets.block.BorderType; import dev.tamboui.widgets.paragraph.Paragraph; +import org.apache.camel.util.json.JsonObject; import static org.apache.camel.dsl.jbang.core.commands.tui.MonitorContext.*; @@ -276,4 +277,17 @@ class DiagramTab implements MonitorTab { - `Esc` — close diagram """; } + + @Override + public JsonObject getTableDataAsJson() { + List<String> lines = diagram.getLines(); + if (lines == null || lines.isEmpty()) { + return null; + } + JsonObject result = new JsonObject(); + result.put("tab", "Diagram"); + result.put("diagram", String.join("\n", lines)); + result.put("lines", lines.size()); + return result; + } } diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointsTab.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointsTab.java index d34f07644234..db5b29d7b7a3 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointsTab.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointsTab.java @@ -41,6 +41,8 @@ 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.util.json.JsonArray; +import org.apache.camel.util.json.JsonObject; import static org.apache.camel.dsl.jbang.core.commands.tui.MonitorContext.*; @@ -749,4 +751,40 @@ class EndpointsTab implements MonitorTab { - `f` — cycle filter mode """; } + + @Override + public JsonObject getTableDataAsJson() { + IntegrationInfo info = ctx.findSelectedIntegration(); + if (info == null) { + return null; + } + JsonObject result = new JsonObject(); + result.put("tab", "Endpoints"); + JsonArray rows = new JsonArray(); + for (EndpointInfo ei : info.endpoints) { + JsonObject row = new JsonObject(); + row.put("uri", ei.uri); + row.put("component", ei.component); + row.put("direction", ei.direction); + row.put("routeId", ei.routeId); + row.put("hits", ei.hits); + row.put("remote", ei.remote); + row.put("stub", ei.stub); + if (ei.meanBodySize >= 0) { + row.put("meanBodySize", ei.meanBodySize); + } + if (ei.maxBodySize >= 0) { + row.put("maxBodySize", ei.maxBodySize); + } + if (ei.meanHeadersSize >= 0) { + row.put("meanHeadersSize", ei.meanHeadersSize); + } + rows.add(row); + } + result.put("rows", rows); + result.put("totalRows", info.endpoints.size()); + Integer sel = tableState.selected(); + result.put("selectedIndex", sel != null ? sel : -1); + return result; + } } diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTab.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTab.java index 2b817ab500ad..b6acc9c6ff05 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTab.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTab.java @@ -36,6 +36,8 @@ import dev.tamboui.widgets.table.Row; import dev.tamboui.widgets.table.Table; import dev.tamboui.widgets.table.TableState; import org.apache.camel.diagram.RouteDiagramHelper; +import org.apache.camel.util.json.JsonArray; +import org.apache.camel.util.json.JsonObject; import org.apache.camel.util.json.Jsoner; import static org.apache.camel.dsl.jbang.core.commands.tui.MonitorContext.*; @@ -550,4 +552,45 @@ class ErrorsTab implements MonitorTab { - `Esc` — back to list """; } + + @Override + public JsonObject getTableDataAsJson() { + IntegrationInfo info = ctx.findSelectedIntegration(); + if (info == null) { + return null; + } + JsonObject result = new JsonObject(); + result.put("tab", "Errors"); + JsonArray rows = new JsonArray(); + for (ErrorInfo ei : info.errors) { + JsonObject row = new JsonObject(); + row.put("routeId", ei.routeId); + row.put("nodeId", ei.nodeId); + row.put("exchangeId", ei.exchangeId); + row.put("handled", ei.handled); + row.put("timestamp", ei.timestamp); + row.put("elapsed", ei.elapsed); + if (ei.exceptionType != null) { + row.put("exceptionType", ei.exceptionType); + } + if (ei.exceptionMessage != null) { + row.put("exceptionMessage", ei.exceptionMessage); + } + if (ei.stackTrace != null) { + row.put("stackTrace", ei.stackTrace); + } + if (ei.body != null) { + row.put("body", ei.body); + } + if (!ei.headers.isEmpty()) { + row.put("headers", new JsonObject(ei.headers)); + } + rows.add(row); + } + result.put("rows", rows); + result.put("totalRows", info.errors.size()); + Integer sel = tableState.selected(); + result.put("selectedIndex", sel != null ? sel : -1); + return result; + } } diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HealthTab.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HealthTab.java index b8645f9adacd..3be1eaa89f68 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HealthTab.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HealthTab.java @@ -32,6 +32,8 @@ 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.util.json.JsonArray; +import org.apache.camel.util.json.JsonObject; import static org.apache.camel.dsl.jbang.core.commands.tui.MonitorContext.*; @@ -282,4 +284,32 @@ class HealthTab implements MonitorTab { - `S` — reverse sort order """; } + + @Override + public JsonObject getTableDataAsJson() { + IntegrationInfo info = ctx.findSelectedIntegration(); + if (info == null) { + return null; + } + JsonObject result = new JsonObject(); + result.put("tab", "Health"); + JsonArray rows = new JsonArray(); + for (HealthCheckInfo hi : info.healthChecks) { + JsonObject row = new JsonObject(); + row.put("group", hi.group); + row.put("name", hi.name); + row.put("state", hi.state); + row.put("readiness", hi.readiness); + row.put("liveness", hi.liveness); + if (hi.message != null) { + row.put("message", hi.message); + } + rows.add(row); + } + result.put("rows", rows); + result.put("totalRows", info.healthChecks.size()); + Integer sel = tableState.selected(); + result.put("selectedIndex", sel != null ? sel : -1); + return result; + } } diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/InflightTab.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/InflightTab.java index 80704fb410da..474aede450f3 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/InflightTab.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/InflightTab.java @@ -36,6 +36,8 @@ import dev.tamboui.widgets.table.Row; import dev.tamboui.widgets.table.Table; import dev.tamboui.widgets.table.TableState; import org.apache.camel.util.TimeUtils; +import org.apache.camel.util.json.JsonArray; +import org.apache.camel.util.json.JsonObject; import static org.apache.camel.dsl.jbang.core.commands.tui.MonitorContext.*; @@ -310,4 +312,32 @@ class InflightTab implements MonitorTab { - `Esc` — back """; } + + @Override + public JsonObject getTableDataAsJson() { + IntegrationInfo info = ctx.findSelectedIntegration(); + if (info == null) { + return null; + } + JsonObject result = new JsonObject(); + result.put("tab", "Inflight"); + JsonArray rows = new JsonArray(); + for (InflightInfo ii : info.inflightExchanges) { + JsonObject row = new JsonObject(); + row.put("exchangeId", ii.exchangeId); + row.put("fromRouteId", ii.fromRouteId); + row.put("fromRemoteEndpoint", ii.fromRemoteEndpoint); + row.put("atRouteId", ii.atRouteId); + row.put("nodeId", ii.nodeId); + row.put("elapsed", ii.elapsed); + row.put("duration", ii.duration); + row.put("blocked", ii.blocked); + rows.add(row); + } + result.put("rows", rows); + result.put("totalRows", info.inflightExchanges.size()); + Integer sel = tableState.selected(); + result.put("selectedIndex", sel != null ? sel : -1); + return result; + } } diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTab.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTab.java index bb2bb659dfb9..e114eff4a211 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTab.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTab.java @@ -53,6 +53,7 @@ import dev.tamboui.widgets.paragraph.Paragraph; import dev.tamboui.widgets.scrollbar.Scrollbar; import dev.tamboui.widgets.scrollbar.ScrollbarState; import org.apache.camel.dsl.jbang.core.common.CommandLineHelper; +import org.apache.camel.util.json.JsonArray; import org.apache.camel.util.json.JsonObject; import static org.apache.camel.dsl.jbang.core.commands.tui.MonitorContext.*; @@ -847,4 +848,45 @@ class LogTab implements MonitorTab { - `Esc` — clear find / back """; } + + JsonObject getLogDataAsJson(int limit, String filter, String level) { + List<LogEntry> entries = filteredLogEntries; + if (entries == null || entries.isEmpty()) { + JsonObject result = new JsonObject(); + result.put("lines", new JsonArray()); + result.put("totalLines", 0); + result.put("returnedLines", 0); + return result; + } + + List<LogEntry> filtered = new ArrayList<>(); + for (int i = entries.size() - 1; i >= 0 && filtered.size() < limit; i--) { + LogEntry e = entries.get(i); + if (level != null && !level.isBlank() && !level.equalsIgnoreCase(e.level)) { + continue; + } + if (filter != null && !filter.isBlank() + && !e.message.toLowerCase().contains(filter.toLowerCase())) { + continue; + } + filtered.add(e); + } + + JsonObject result = new JsonObject(); + JsonArray rows = new JsonArray(); + for (LogEntry e : filtered) { + JsonObject row = new JsonObject(); + row.put("time", e.time); + row.put("level", e.level); + if (e.logger != null) { + row.put("logger", e.logger); + } + row.put("message", e.message); + rows.add(row); + } + result.put("lines", rows); + result.put("totalLines", entries.size()); + result.put("returnedLines", filtered.size()); + return result; + } } diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MonitorTab.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MonitorTab.java index 1b213347e4f8..4c80a484c3a8 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MonitorTab.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MonitorTab.java @@ -22,6 +22,7 @@ import dev.tamboui.layout.Rect; import dev.tamboui.terminal.Frame; import dev.tamboui.text.Span; import dev.tamboui.tui.event.KeyEvent; +import org.apache.camel.util.json.JsonObject; /** * Interface for TUI monitor tabs. Each tab handles its own events, rendering, and footer hints. @@ -53,4 +54,8 @@ interface MonitorTab { default String getHelpText() { return null; } + + default JsonObject getTableDataAsJson() { + return null; + } } diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OverviewTab.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OverviewTab.java index 505627de6223..a6411a3c701b 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OverviewTab.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OverviewTab.java @@ -43,6 +43,8 @@ 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.util.json.JsonArray; +import org.apache.camel.util.json.JsonObject; import static org.apache.camel.dsl.jbang.core.commands.tui.MonitorContext.*; import static org.apache.camel.dsl.jbang.core.common.CamelCommandHelper.extractState; @@ -791,4 +793,37 @@ class OverviewTab implements MonitorTab { - `F3` — switch integration """; } + + @Override + public JsonObject getTableDataAsJson() { + List<IntegrationInfo> integrations = ctx.data.get(); + if (integrations == null || integrations.isEmpty()) { + return null; + } + JsonObject result = new JsonObject(); + result.put("tab", "Overview"); + JsonArray rows = new JsonArray(); + for (IntegrationInfo info : integrations) { + JsonObject row = new JsonObject(); + row.put("pid", info.pid); + row.put("name", info.name); + row.put("camelVersion", info.camelVersion); + row.put("platform", info.platform); + row.put("state", info.state); + row.put("ready", info.ready); + row.put("uptime", info.uptime); + row.put("exchangesTotal", info.exchangesTotal); + row.put("failed", info.failed); + row.put("inflight", info.inflight); + row.put("throughput", info.throughput); + row.put("routeStarted", info.routeStarted); + row.put("routeTotal", info.routeTotal); + rows.add(row); + } + result.put("rows", rows); + result.put("totalRows", integrations.size()); + Integer sel = tableState.selected(); + result.put("selectedIndex", sel != null ? sel : -1); + return result; + } } diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java index 295dba6e591b..8fcf67c03807 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java @@ -1283,4 +1283,39 @@ class RoutesTab implements MonitorTab { - `Esc` — back to route list """; } + + @Override + public JsonObject getTableDataAsJson() { + IntegrationInfo info = ctx.findSelectedIntegration(); + if (info == null) { + return null; + } + JsonObject result = new JsonObject(); + result.put("tab", "Routes"); + JsonArray rows = new JsonArray(); + for (RouteInfo ri : info.routes) { + JsonObject row = new JsonObject(); + row.put("routeId", ri.routeId); + row.put("from", ri.from); + row.put("state", ri.state); + row.put("uptime", ri.uptime); + row.put("total", ri.total); + row.put("failed", ri.failed); + row.put("inflight", ri.inflight); + row.put("mean", ri.meanTime); + row.put("max", ri.maxTime); + row.put("min", ri.minTime); + row.put("last", ri.lastTime); + row.put("throughput", ri.throughput); + if (ri.group != null) { + row.put("group", ri.group); + } + rows.add(row); + } + result.put("rows", rows); + result.put("totalRows", info.routes.size()); + Integer sel = routeTableState.selected(); + result.put("selectedIndex", sel != null ? sel : -1); + return result; + } } diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiMcpServer.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiMcpServer.java index a1dc64ae002f..c93da5e0685b 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiMcpServer.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiMcpServer.java @@ -347,6 +347,49 @@ class TuiMcpServer { + "The underlying content is unchanged since drawing is an overlay.", Map.of())); + // --- Structured data tools --- + + toolList.add(toolDef( + "tui_get_table", + "Returns the currently visible table data as structured JSON with typed values. " + + "Much more reliable than parsing screen text. " + + "Returns tab name, rows array with all fields, totalRows, and selectedIndex.", + Map.of("tab", propDef("string", + "Tab name to get data from (e.g. 'Routes', 'Endpoints', 'Health'). " + + "If omitted, uses the active tab.")))); + toolList.add(toolDef( + "tui_action", + "Invokes a TUI action by name, bypassing fragile key sequences. " + + "Actions: reset-stats, reset-screen, screenshot, show-keystrokes, " + + "tape-recording, doctor, caption, mcp-info, mcp-log.", + Map.of("action", propDef("string", "Action name in kebab-case (e.g. 'reset-stats', 'screenshot')")), + List.of("action"))); + toolList.add(toolDef( + "tui_get_log", + "Returns recent log lines as structured data with optional filtering. " + + "Returns newest entries first.", + Map.of("limit", propDef("integer", "Maximum lines to return (default 50)"), + "filter", propDef("string", "Case-insensitive substring filter on log message"), + "level", propDef("string", "Filter by log level (INFO, WARN, ERROR, DEBUG, TRACE)")))); + toolList.add(toolDef( + "tui_get_errors", + "Returns structured error data from the Errors tab. " + + "Includes routeId, exchangeId, exception details, stack trace, body, and headers.", + Map.of())); + toolList.add(toolDef( + "tui_get_diagram", + "Returns the route topology diagram as text. " + + "Shows the ASCII/Unicode art diagram of routes and their connections.", + Map.of())); + toolList.add(toolDef( + "tui_send_message", + "Sends a message to a Camel endpoint in the selected integration. " + + "Uses the file-based IPC protocol to deliver the message directly.", + Map.of("endpoint", propDef("string", "Endpoint URI to send to (e.g. 'direct:myRoute', 'seda:queue')"), + "body", propDef("string", "Message body to send"), + "headers", propDef("string", "Message headers as key=value pairs separated by newlines")), + List.of("endpoint"))); + JsonObject result = new JsonObject(); result.put("tools", toolList); return result; @@ -380,6 +423,12 @@ class TuiMcpServer { case "tui_sleep" -> callSleep(args); case "tui_draw" -> callDraw(args); case "tui_draw_clear" -> callDrawClear(); + case "tui_get_table" -> callGetTable(args); + case "tui_action" -> callAction(args); + case "tui_get_log" -> callGetLog(args); + case "tui_get_errors" -> callGetErrors(); + case "tui_get_diagram" -> callGetDiagram(); + case "tui_send_message" -> callSendMessage(args); default -> { isError = true; yield "Unknown tool: " + toolName; @@ -844,6 +893,74 @@ class TuiMcpServer { return "Drawing cleared"; } + private String callGetTable(Map<String, Object> args) { + String tab = args.get("tab") instanceof String s ? s : null; + JsonObject data = monitor.getTableData(tab); + if (data == null) { + return "No table data available" + (tab != null ? " for tab: " + tab : ""); + } + return Jsoner.serialize(data); + } + + private String callAction(Map<String, Object> args) { + String action = (String) args.get("action"); + if (action == null || action.isBlank()) { + return "Error: action is required"; + } + boolean executed = monitor.executeAction(action); + if (executed) { + return "Action '" + action + "' executed"; + } + return "Unknown or unsupported action: " + action + + ". Available: reset-stats, reset-screen, screenshot, show-keystrokes, " + + "tape-recording, doctor, caption, mcp-info, mcp-log"; + } + + private String callGetLog(Map<String, Object> args) { + int limit = 50; + if (args.get("limit") instanceof Number n) { + limit = Math.max(1, Math.min(1000, n.intValue())); + } + String filter = args.get("filter") instanceof String s ? s : null; + String level = args.get("level") instanceof String s ? s : null; + JsonObject data = monitor.getLogData(limit, filter, level); + return Jsoner.serialize(data); + } + + private String callGetErrors() { + JsonObject data = monitor.getTableData("Errors"); + if (data == null) { + JsonObject empty = new JsonObject(); + empty.put("tab", "Errors"); + empty.put("rows", new JsonArray()); + empty.put("totalRows", 0); + return Jsoner.serialize(empty); + } + return Jsoner.serialize(data); + } + + private String callGetDiagram() { + JsonObject data = monitor.getDiagramData(); + if (data == null) { + return "No diagram available. Navigate to the Diagram tab first."; + } + return Jsoner.serialize(data); + } + + private String callSendMessage(Map<String, Object> args) { + String endpoint = (String) args.get("endpoint"); + if (endpoint == null || endpoint.isBlank()) { + return "Error: endpoint is required"; + } + String body = args.get("body") instanceof String s ? s : null; + String headers = args.get("headers") instanceof String s ? s : null; + JsonObject response = monitor.sendMessage(endpoint, body, headers); + if (response == null) { + return "Error: no integration selected or PID unavailable"; + } + return Jsoner.serialize(response); + } + private static JsonArray toJsonArray(List<String> list) { JsonArray arr = new JsonArray(); arr.addAll(list);
