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

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 1ec4b8ba927d camel-jbang - TUI support opening project from directory 
argument
1ec4b8ba927d is described below

commit 1ec4b8ba927d8a2b6daa75dd9a2ee303b603eb0d
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Aug 4 23:12:19 2026 +0200

    camel-jbang - TUI support opening project from directory argument
    
    Allow `camel tui .` or `camel tui /path/to/project` to open the TUI
    with a project directory pre-loaded as a phantom integration, showing
    the Source tab by default. Also adds F10 process control hint to the
    footer on all tabs.
    
    camel-jbang - TUI translate Camel JBang args for Maven project launch
    
    When launching a Maven project (Spring Boot, Quarkus) from the TUI,
    translate applicable Camel JBang args (--prop, --port, --jvm-args) to
    their Maven equivalents instead of passing them through verbatim which
    caused 'Unrecognized option' errors from Maven.
    
    camel-jbang - TUI fix Source tab losing selection on phantom-to-live 
transition
    
    When a phantom project is launched via F10, the Source tab showed
    "no integration selected" until manually switching tabs. The fix
    transfers selectedPid to the live integration before removing the
    phantom, so handleAutoSelect never sees a stale PID.
    
    camel-jbang - TUI rename F10 from process to run and update docs
    
    Rename F10 popup title from "Control" to "Run" and all footer hints
    from "process"/"control" to "run". Update welcome screen text and
    add F8 AI prompt hint. Document the new camel tui <directory> feature
    in the TUI docs.
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../modules/ROOT/pages/camel-jbang-tui.adoc        | 22 +++++++-
 .../dsl/jbang/core/commands/tui/CamelMonitor.java  | 61 +++++++++++++++++++++-
 .../core/commands/tui/DataRefreshService.java      | 12 +++--
 .../dsl/jbang/core/commands/tui/LaunchManager.java | 52 +++++++++++++++++-
 .../dsl/jbang/core/commands/tui/OverviewTab.java   | 18 +++++--
 .../core/commands/tui/ProcessControlPopup.java     |  2 +-
 6 files changed, 152 insertions(+), 15 deletions(-)

diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-tui.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-jbang-tui.adoc
index 735d8be08271..f1f8ac200156 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-tui.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-tui.adoc
@@ -67,6 +67,23 @@ is auto-detected and locked to match your project.
 TIP: Press *F1* or *?* on any screen for context-sensitive help.
 Keyboard shortcuts are always shown in the footer bar.
 
+=== Option 3: Open a Project Directory
+
+You can point the TUI directly at a project directory:
+
+[source,bash]
+----
+camel tui .
+camel tui /path/to/my-project
+----
+
+The TUI opens the directory in the Source tab so you can browse the project 
files immediately.
+When a `pom.xml` is present, the runtime is auto-detected (Spring Boot, 
Quarkus, or Camel Main).
+Press *F10* to run the project -- Maven projects are launched with the 
appropriate goal
+(`spring-boot:run`, `quarkus:dev`, or `camel:run`), and plain directories are 
run with `camel run`.
+
+This is a quick way to explore and run any Camel project without starting it 
separately first.
+
 === Connecting Existing Spring Boot and Quarkus Applications
 
 The TUI auto-discovers integrations started with `camel run`. To monitor and 
control
@@ -515,6 +532,7 @@ respectively. Use *↑*/*↓* on the input line to walk 
previous entries. Limits
 | *F1* / *?* | Context-sensitive help (toggle)
 | *F2* | Actions menu
 | *F3* | Switch between integrations (when multiple running)
+| *F10* | Run menu (run, stop, restart, kill)
 | *Shift+F5* | Take screenshot
 | *Ctrl+R* | Start/stop tape recording
 | *Ctrl+C* / *Q* | Quit
@@ -816,8 +834,8 @@ vhs demo.tape               # .tape -> .gif
 |===
 | Option | Description | Default
 
-| `[name\|pid]`
-| Name or PID of the Camel integration to monitor. When omitted, the TUI 
auto-discovers running integrations.
+| `[name\|pid\|directory]`
+| Name, PID, or directory path of a Camel integration. When a directory is 
given, the TUI opens it as a project in the Source tab -- you can browse the 
source code and run it with *F10*. When omitted, the TUI auto-discovers running 
integrations.
 |
 
 | `--mcp`
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 b60077ef396d..d1083307bdb6 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
@@ -188,6 +188,16 @@ public class CamelMonitor extends CamelCommand {
     public Integer doCall() throws Exception {
         System.setProperty("java.awt.headless", "true");
 
+        // Detect if name arg is a directory path — open it as a project 
instead of using it as a name/PID filter
+        String openDir = null;
+        if (!"*".equals(name)) {
+            Path p = Path.of(name).toAbsolutePath().normalize();
+            if (Files.isDirectory(p)) {
+                openDir = p.toString();
+                name = "*";
+            }
+        }
+
         if (theme != null) {
             if (!Theme.isValidMode(theme)) {
                 String expected = String.join("' or '", ThemeMode.ids());
@@ -477,8 +487,16 @@ public class CamelMonitor extends CamelCommand {
         // Initial data load (synchronous before TUI starts)
         dataService.refreshSync(this::refreshLogData, 
this::refreshConditionalData);
 
-        // Auto-select if there's exactly one integration running
-        tabRegistry.overviewTab().selectCurrentIntegration();
+        // If the argument was a directory, open it as a project
+        if (openDir != null) {
+            openDirectoryAsPhantom(openDir);
+            // Re-scan so the phantom is merged into the data list before the 
Source tab reads it
+            dataService.refreshSync(this::refreshLogData, 
this::refreshConditionalData);
+            tabRegistry.handleTabKey(TAB_SOURCE, ctx, dataService);
+        } else {
+            // Auto-select if there's exactly one integration running
+            tabRegistry.overviewTab().selectCurrentIntegration();
+        }
 
         canvasOverlay.setOnOpen(() -> {
             popupManager.dismissAll();
@@ -2205,6 +2223,7 @@ public class CamelMonitor extends CamelCommand {
             hint(fKeySpans, "F1", "help");
         }
         hint(fKeySpans, "F2", "actions");
+        hint(fKeySpans, "F10", "run");
         spans.addAll(insertPos, fKeySpans);
         // Return total F-key span count. The footer drop loop uses this to 
remove pairs from
         // the tail, stopping before the first pair (F1 help when present).
@@ -2375,6 +2394,44 @@ public class CamelMonitor extends CamelCommand {
         }
     }
 
+    private void openDirectoryAsPhantom(String directory) {
+        Path dirPath = Path.of(directory).toAbsolutePath().normalize();
+        if (!Files.isDirectory(dirPath)) {
+            return;
+        }
+        // check if this directory already matches a running integration
+        for (IntegrationInfo info : dataService.data().get()) {
+            if (!info.vanishing && directory.equals(info.directory)) {
+                ctx.selectedPid = info.pid;
+                return;
+            }
+        }
+        // check if already registered as a phantom
+        IntegrationInfo existing = 
ctx.findPhantomByDirectory(dirPath.toString());
+        if (existing != null) {
+            ctx.selectedPid = existing.pid;
+            return;
+        }
+
+        Path pomFile = dirPath.resolve("pom.xml");
+        String runtime = Files.isRegularFile(pomFile) ? 
TuiHelper.detectPomRuntime(pomFile) : null;
+
+        IntegrationInfo phantom = new IntegrationInfo();
+        phantom.name = dirPath.getFileName().toString();
+        phantom.directory = dirPath.toString();
+        phantom.sourceDir = dirPath.toString();
+        phantom.projectType = runtime;
+        if ("spring-boot".equals(runtime)) {
+            phantom.platform = "Spring Boot";
+        } else if ("quarkus".equals(runtime)) {
+            phantom.platform = "Quarkus";
+        } else {
+            phantom.platform = "Camel";
+        }
+        ctx.addPhantom(phantom);
+        ctx.selectedPid = phantom.pid;
+    }
+
     void setRunnerForTesting(Runnable onQuit) {
         aiPanel.setExitCallbackForTestingOrRuntime(onQuit);
     }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DataRefreshService.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DataRefreshService.java
index e9f9d79d4474..3c998fea2df5 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DataRefreshService.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DataRefreshService.java
@@ -352,12 +352,16 @@ class DataRefreshService {
     }
 
     private void mergePhantoms(List<IntegrationInfo> infos) {
-        Set<String> liveDirs = infos.stream()
+        Map<String, IntegrationInfo> liveDirs = infos.stream()
                 .filter(i -> !i.vanishing && !i.phantom && i.directory != null)
-                .map(i -> i.directory)
-                .collect(Collectors.toSet());
+                .collect(Collectors.toMap(i -> i.directory, i -> i, (a, b) -> 
a));
         for (IntegrationInfo phantom : ctx.phantomIntegrations) {
-            if (phantom.sourceDir != null && 
liveDirs.contains(phantom.sourceDir)) {
+            IntegrationInfo live = phantom.sourceDir != null ? 
liveDirs.get(phantom.sourceDir) : null;
+            if (live != null) {
+                // Phantom's project is now running — switch selection to the 
live integration
+                if (phantom.pid.equals(ctx.selectedPid)) {
+                    ctx.selectedPid = live.pid;
+                }
                 ctx.removePhantom(phantom.pid);
             } else {
                 infos.add(phantom);
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LaunchManager.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LaunchManager.java
index 91a6770073e9..890e8043ad50 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LaunchManager.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LaunchManager.java
@@ -201,7 +201,8 @@ class LaunchManager {
                 case "quarkus" -> cmd.add("quarkus:dev");
                 default -> cmd.add("camel:run");
             }
-            cmd.addAll(extraArgs);
+            // Translate Camel JBang args to Maven-compatible args
+            cmd.addAll(translateArgsForMaven(extraArgs, projectType));
             Path outputFile = createSecureTempFile("camel-maven-", ".log");
             outputFile.toFile().deleteOnExit();
             ProcessBuilder pb = new ProcessBuilder(cmd);
@@ -245,6 +246,55 @@ class LaunchManager {
         return "mvn";
     }
 
+    static List<String> translateArgsForMaven(List<String> extraArgs, String 
projectType) {
+        List<String> mvnArgs = new ArrayList<>();
+        StringBuilder jvmArgs = new StringBuilder();
+        if ("spring-boot".equals(projectType)) {
+            
jvmArgs.append("-Dlogging.config=classpath:logback-camel-jbang.xml");
+        }
+        for (String arg : extraArgs) {
+            if (arg.startsWith("--prop=")) {
+                String kv = arg.substring("--prop=".length());
+                mvnArgs.add("-D" + kv);
+            } else if (arg.startsWith("--port=")) {
+                String port = arg.substring("--port=".length());
+                if ("spring-boot".equals(projectType)) {
+                    mvnArgs.add("-Dserver.port=" + port);
+                } else if ("quarkus".equals(projectType)) {
+                    mvnArgs.add("-Dquarkus.http.port=" + port);
+                }
+            } else if (arg.startsWith("--profile=")) {
+                String profile = arg.substring("--profile=".length());
+                if (!"prod".equals(profile)) {
+                    if (!jvmArgs.isEmpty()) {
+                        jvmArgs.append(" ");
+                    }
+                    jvmArgs.append("-Dcamel.main.profile=").append(profile);
+                }
+            } else if (arg.startsWith("--jvm-args=")) {
+                String extra = arg.substring("--jvm-args=".length()).trim();
+                if (!extra.isEmpty()) {
+                    if (!jvmArgs.isEmpty()) {
+                        jvmArgs.append(" ");
+                    }
+                    jvmArgs.append(extra);
+                }
+            }
+            // other Camel JBang flags (--name, --runtime, --dev, --observe, 
etc.)
+            // are not applicable to Maven and are silently dropped
+        }
+        if (!jvmArgs.isEmpty()) {
+            if ("spring-boot".equals(projectType)) {
+                mvnArgs.add("-Dspring-boot.run.jvmArguments=" + jvmArgs);
+            } else if ("quarkus".equals(projectType)) {
+                mvnArgs.add("-Djvm.args=" + jvmArgs);
+            } else {
+                mvnArgs.add("-Dcamel.jvmArgs=" + jvmArgs);
+            }
+        }
+        return mvnArgs;
+    }
+
     private void checkDeferredLaunch(long now) {
         if (deferredLaunch != null) {
             Set<String> runningAliases = infraServices.get().stream()
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 4aa3a6e975d4..c659fc645d13 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
@@ -1051,7 +1051,7 @@ class OverviewTab extends AbstractTab {
             });
         }
         if (ctx.selectedPid != null) {
-            hint(spans, "F10", "control");
+            hint(spans, "F10", "run");
         }
     }
 
@@ -1375,9 +1375,9 @@ class OverviewTab extends AbstractTab {
                 - `F2` — actions menu (includes theme toggle, go to tab, etc.)
                 - `F3` — switch integration
 
-                ## Process Control
+                ## Run
 
-                - `F10` — open process control popup (stop routes, start 
routes, restart, stop, kill)
+                - `F10` — open run popup (run, stop routes, start routes, 
restart, stop, kill)
                 - `q` — quit the TUI
 
                 By default, stop/restart actions show a confirmation dialog 
before executing.
@@ -1454,10 +1454,10 @@ class OverviewTab extends AbstractTab {
                 Span.raw("     Press "),
                 Span.styled(" F2 ", Theme.hintKey()),
                 Span.raw(" to open Actions and select "),
-                Span.styled("Open", Style.EMPTY.bold()),
+                Span.styled("Open Project", Style.EMPTY.bold()),
                 Span.raw("."))));
         lines.add(Line.from(Span.raw("")));
-        lines.add(Line.from(Span.styled(TuiIcons.indent(TuiIcons.COMPUTER) + 
"Or use the embedded JLine shell panel:",
+        lines.add(Line.from(Span.styled(TuiIcons.indent(TuiIcons.COMPUTER) + 
"Or use the embedded terminal:",
                 Style.EMPTY.bold())));
         lines.add(Line.from(List.of(
                 Span.raw("     Press "),
@@ -1465,6 +1465,14 @@ class OverviewTab extends AbstractTab {
                 Span.raw(" to open the shell and run commands directly, 
e.g.:"))));
         lines.add(Line.from(Span.styled("     camel> run examples/demo.java", 
Theme.success())));
         lines.add(Line.from(Span.raw("")));
+        lines.add(Line.from(
+                Span.styled(TuiIcons.indent(TuiIcons.MCP_BRAIN) + "Or ask AI 
to create an example for you:",
+                        Style.EMPTY.bold())));
+        lines.add(Line.from(List.of(
+                Span.raw("     Press "),
+                Span.styled(" F8 ", Theme.hintKey()),
+                Span.raw(" to open the AI prompt."))));
+        lines.add(Line.from(Span.raw("")));
         lines.add(Line.from(List.of(
                 Span.styled(TuiIcons.indent(TuiIcons.HELP) + "For shortcut 
keys and documentation, press ", Theme.muted()),
                 Span.styled(" ? ", Theme.hintKey()),
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ProcessControlPopup.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ProcessControlPopup.java
index f89172746ddf..8e8891343908 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ProcessControlPopup.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ProcessControlPopup.java
@@ -173,7 +173,7 @@ class ProcessControlPopup {
                 .block(Block.builder()
                         .borderType(BorderType.ROUNDED)
                         .borders(Borders.ALL)
-                        .title(" Control ")
+                        .title(" Run ")
                         .build())
                 .build();
         frame.renderStatefulWidget(list, popup, listState);

Reply via email to