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

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

commit 4cb4ccd0c1396bf5dc22d964a5f57e3b8ea241d8
Author: Claus Ibsen <[email protected]>
AuthorDate: Sun May 24 19:40:19 2026 +0200

    CAMEL-23606: camel-tui - MCP key injection tool for AI-driven demo recording
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
---
 .../dsl/jbang/core/commands/tui/ActionsPopup.java  |  3 +-
 .../dsl/jbang/core/commands/tui/CamelMonitor.java  | 90 ++++++++++++++++++++++
 .../dsl/jbang/core/commands/tui/TuiMcpServer.java  | 33 ++++++++
 3 files changed, 125 insertions(+), 1 deletion(-)

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 bfce21ee00ef..e567ae83972c 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
@@ -700,7 +700,8 @@ class ActionsPopup {
                      + "| `tui_get_events` | Returns recent key presses and 
navigation events |\n"
                      + "| `tui_get_state` | Returns active tab, selected 
integration, etc. |\n"
                      + "| `tui_show_caption` | Shows a message on the TUI 
screen |\n"
-                     + "| `tui_navigate` | Switch tabs and select integrations 
|\n\n"
+                     + "| `tui_navigate` | Switch tabs and select integrations 
|\n"
+                     + "| `tui_send_keys` | Send key presses to control the 
TUI |\n\n"
                      + "## Setup for Claude Code\n\n"
                      + "Run this command to connect Claude Code to the 
TUI:\n\n"
                      + "    claude mcp add --transport http camel-tui " + url 
+ "\n\n"
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 ce9cdb0e5248..c0ae386a3002 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
@@ -35,8 +35,10 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Optional;
+import java.util.Queue;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.stream.Collectors;
@@ -58,6 +60,7 @@ 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.tui.event.KeyModifiers;
 import dev.tamboui.tui.event.TickEvent;
 import dev.tamboui.widgets.Clear;
 import dev.tamboui.widgets.barchart.Bar;
@@ -212,6 +215,7 @@ public class CamelMonitor extends CamelCommand {
     private boolean recording;
     private TuiEventLog eventLog;
     private TuiMcpServer mcpServer;
+    private final Queue<PendingKey> pendingKeys = new 
ConcurrentLinkedQueue<>();
     private final List<KeyRecord> recentKeys = new ArrayList<>();
     private final CaptionOverlay captionOverlay = new CaptionOverlay();
 
@@ -588,6 +592,12 @@ public class CamelMonitor extends CamelCommand {
         }
         if (event instanceof TickEvent) {
             long now = System.currentTimeMillis();
+            PendingKey pk = pendingKeys.peek();
+            if (pk != null && now >= pk.fireAt()) {
+                pendingKeys.poll();
+                handleEvent(pk.event(), runner);
+                return true;
+            }
             actionsPopup.tick(now);
             captionOverlay.tick(now);
             if (recording && !recentKeys.isEmpty()) {
@@ -3097,4 +3107,84 @@ public class CamelMonitor extends CamelCommand {
                 .toList();
     }
 
+    int injectKeys(List<String> keys, int delayMs) {
+        long fireAt = System.currentTimeMillis();
+        int count = 0;
+        for (String key : keys) {
+            KeyEvent ke = parseKey(key);
+            if (ke != null) {
+                pendingKeys.add(new PendingKey(ke, fireAt));
+                fireAt += delayMs;
+                count++;
+            }
+        }
+        return count;
+    }
+
+    static KeyEvent parseKey(String key) {
+        if (key == null || key.isEmpty()) {
+            return null;
+        }
+
+        boolean ctrl = false;
+        boolean shift = false;
+        String remainder = key;
+        while (remainder.contains("+")) {
+            int plus = remainder.indexOf('+');
+            String mod = remainder.substring(0, plus).trim();
+            remainder = remainder.substring(plus + 1).trim();
+            if (mod.equalsIgnoreCase("Ctrl")) {
+                ctrl = true;
+            } else if (mod.equalsIgnoreCase("Shift")) {
+                shift = true;
+            }
+        }
+
+        KeyModifiers mods = KeyModifiers.of(ctrl, false, shift);
+
+        KeyCode code = switch (remainder.toLowerCase(Locale.ROOT)) {
+            case "enter", "return" -> KeyCode.ENTER;
+            case "esc", "escape" -> KeyCode.ESCAPE;
+            case "tab" -> KeyCode.TAB;
+            case "backspace" -> KeyCode.BACKSPACE;
+            case "delete", "del" -> KeyCode.DELETE;
+            case "up" -> KeyCode.UP;
+            case "down" -> KeyCode.DOWN;
+            case "left" -> KeyCode.LEFT;
+            case "right" -> KeyCode.RIGHT;
+            case "home" -> KeyCode.HOME;
+            case "end" -> KeyCode.END;
+            case "pageup", "pgup" -> KeyCode.PAGE_UP;
+            case "pagedown", "pgdn" -> KeyCode.PAGE_DOWN;
+            case "f1" -> KeyCode.F1;
+            case "f2" -> KeyCode.F2;
+            case "f3" -> KeyCode.F3;
+            case "f4" -> KeyCode.F4;
+            case "f5" -> KeyCode.F5;
+            case "f6" -> KeyCode.F6;
+            case "f7" -> KeyCode.F7;
+            case "f8" -> KeyCode.F8;
+            case "f9" -> KeyCode.F9;
+            case "f10" -> KeyCode.F10;
+            case "f11" -> KeyCode.F11;
+            case "f12" -> KeyCode.F12;
+            case "space" -> null;
+            default -> null;
+        };
+
+        if (code != null) {
+            return KeyEvent.ofKey(code, mods);
+        }
+        if ("space".equalsIgnoreCase(remainder)) {
+            return KeyEvent.ofChar(' ', mods);
+        }
+        if (remainder.length() == 1) {
+            return KeyEvent.ofChar(remainder.charAt(0), mods);
+        }
+        return null;
+    }
+
+    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/TuiMcpServer.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiMcpServer.java
index 3cfd6e483b69..9c1e9378cc03 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
@@ -221,6 +221,17 @@ class TuiMcpServer {
                 Map.of("tab", propDef("string", "Tab to switch to (e.g. 
'Routes', 'Health')"),
                         "integration", propDef("string", "Integration name or 
PID to select"))));
 
+        toolList.add(toolDef(
+                "tui_send_keys",
+                "Sends key presses to the TUI. Use this to control the TUI for 
recording demos. "
+                                 + "Keys are processed one per tick with the 
specified delay between them. "
+                                 + "Key names: Enter, Esc, Tab, Backspace, 
Delete, Up, Down, Left, Right, "
+                                 + "Home, End, PgUp, PgDn, Space, F1-F12, or 
any single character. "
+                                 + "Modifiers: Ctrl+x, Shift+x, Ctrl+Shift+x.",
+                Map.of("keys", propDef("array", "Array of key name strings to 
send"),
+                        "delay", propDef("integer", "Delay in milliseconds 
between keys (default 150)")),
+                List.of("keys")));
+
         JsonObject result = new JsonObject();
         result.put("tools", toolList);
         return result;
@@ -247,6 +258,7 @@ class TuiMcpServer {
                 case "tui_get_state" -> callGetState();
                 case "tui_show_caption" -> callShowCaption(args);
                 case "tui_navigate" -> callNavigate(args);
+                case "tui_send_keys" -> callSendKeys(args);
                 default -> {
                     isError = true;
                     yield "Unknown tool: " + toolName;
@@ -376,6 +388,27 @@ class TuiMcpServer {
         return Jsoner.serialize(result);
     }
 
+    @SuppressWarnings("unchecked")
+    private String callSendKeys(Map<String, Object> args) {
+        Object keysArg = args.get("keys");
+        if (!(keysArg instanceof List)) {
+            return "Error: keys must be an array of strings";
+        }
+        List<String> keys = ((List<Object>) keysArg).stream()
+                .map(String::valueOf)
+                .toList();
+        if (keys.isEmpty()) {
+            return "Error: keys array is empty";
+        }
+        int delay = 150;
+        Object delayArg = args.get("delay");
+        if (delayArg instanceof Number n) {
+            delay = Math.max(50, n.intValue());
+        }
+        int sent = monitor.injectKeys(keys, delay);
+        return "Queued " + sent + " key(s) with " + delay + "ms delay";
+    }
+
     private static JsonArray toJsonArray(List<String> list) {
         JsonArray arr = new JsonArray();
         arr.addAll(list);

Reply via email to