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 42e0cc14a806 CAMEL-23857: TUI allow to choose runtime when running 
from folder or examples
42e0cc14a806 is described below

commit 42e0cc14a80637c8d34c61f266014a6f24c2e039
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Jun 30 09:37:01 2026 +0200

    CAMEL-23857: TUI allow to choose runtime when running from folder or 
examples
    
    TUI run options form now lets users choose runtime (Camel Main, Spring Boot,
    Quarkus) when running from folder or examples. Export passes exportBaseDir 
so
    SB/Quarkus exports work from any directory. Added dev/prod profile toggle to
    run options form with profile passed to exported application.properties.
    
    Moved profile from MainConfigurationProperties to 
DefaultConfigurationProperties
    so all runtimes can use it. ProfileConfigurer.configureCommon now sets
    tracingStandby for SB/Quarkus dev-mode tracing support.
    
    Browse Files supports navigating subdirectories. Restart disabled for 
SB/Quarkus
    until reliable.
    
    Closes #24329
    
    Co-Authored-By: Claude <[email protected]>
---
 .../main/camel-main-configuration-metadata.json    |  2 +-
 .../camel-main-configuration-metadata.json         |  2 +-
 .../camel/main/DefaultConfigurationProperties.java | 33 ++++++++
 .../camel/main/MainConfigurationProperties.java    | 34 --------
 .../org/apache/camel/main/ProfileConfigurer.java   | 17 ++--
 .../dsl/jbang/core/commands/ExportBaseCommand.java |  5 ++
 .../apache/camel/dsl/jbang/core/commands/Run.java  |  7 ++
 .../dsl/jbang/core/commands/tui/CamelMonitor.java  | 16 +++-
 .../dsl/jbang/core/commands/tui/FilesBrowser.java  | 92 ++++++++++++++++------
 .../jbang/core/commands/tui/RunOptionsForm.java    | 85 +++++++++++++++++---
 10 files changed, 210 insertions(+), 83 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
index 6f476fc39be7..ebf86b1e192c 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
@@ -105,7 +105,7 @@
     { "name": "camel.main.modeline", "required": false, "description": 
"Whether to support JBang style \/\/DEPS to specify additional dependencies 
when running Camel CLI", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", 
"javaType": "boolean", "defaultValue": false, "secret": false },
     { "name": "camel.main.name", "required": false, "description": "Sets the 
name of the CamelContext.", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "type": "string", 
"javaType": "java.lang.String", "secret": false },
     { "name": "camel.main.producerTemplateCacheSize", "required": false, 
"description": "Producer template endpoints cache size.", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "type": "integer", 
"javaType": "int", "defaultValue": 1000, "secret": false },
-    { "name": "camel.main.profile", "required": false, "description": "Camel 
profile to use when running. The dev profile is for development, which enables 
a set of additional developer focus functionality, tracing, debugging, and 
gathering additional runtime statistics that are useful during development. 
However, those additional features has a slight overhead cost, and are not 
enabled for production profile. The default profile is prod.", "sourceType": 
"org.apache.camel.main.MainConfig [...]
+    { "name": "camel.main.profile", "required": false, "description": "Camel 
profile to use when running. The dev profile is for development, which enables 
a set of additional developer focus functionality, tracing, debugging, and 
gathering additional runtime statistics that are useful during development. 
However, those additional features has a slight overhead cost, and are not 
enabled for production profile. The default profile is prod.", "sourceType": 
"org.apache.camel.main.DefaultCon [...]
     { "name": "camel.main.routeFilterExcludePattern", "required": false, 
"description": "Used for filtering routes routes matching the given pattern, 
which follows the following rules: - Match by route id - Match by route input 
endpoint uri The matching is using exact match, by wildcard and regular 
expression as documented by PatternHelper#matchPattern(String,String) . For 
example to only include routes which starts with foo in their route id's, use: 
include=foo&#42; And to exclude route [...]
     { "name": "camel.main.routeFilterIncludePattern", "required": false, 
"description": "Used for filtering routes matching the given pattern, which 
follows the following rules: - Match by route id - Match by route input 
endpoint uri The matching is using exact match, by wildcard and regular 
expression as documented by PatternHelper#matchPattern(String,String) . For 
example to only include routes which starts with foo in their route id's, use: 
include=foo&#42; And to exclude routes which [...]
     { "name": "camel.main.routesBuilderClasses", "required": false, 
"description": "Sets classes names that implement RoutesBuilder .", 
"sourceType": "org.apache.camel.main.MainConfigurationProperties", "type": 
"string", "javaType": "java.lang.String", "secret": false },
diff --git 
a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
 
b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
index 6f476fc39be7..ebf86b1e192c 100644
--- 
a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
+++ 
b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
@@ -105,7 +105,7 @@
     { "name": "camel.main.modeline", "required": false, "description": 
"Whether to support JBang style \/\/DEPS to specify additional dependencies 
when running Camel CLI", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", 
"javaType": "boolean", "defaultValue": false, "secret": false },
     { "name": "camel.main.name", "required": false, "description": "Sets the 
name of the CamelContext.", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "type": "string", 
"javaType": "java.lang.String", "secret": false },
     { "name": "camel.main.producerTemplateCacheSize", "required": false, 
"description": "Producer template endpoints cache size.", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "type": "integer", 
"javaType": "int", "defaultValue": 1000, "secret": false },
-    { "name": "camel.main.profile", "required": false, "description": "Camel 
profile to use when running. The dev profile is for development, which enables 
a set of additional developer focus functionality, tracing, debugging, and 
gathering additional runtime statistics that are useful during development. 
However, those additional features has a slight overhead cost, and are not 
enabled for production profile. The default profile is prod.", "sourceType": 
"org.apache.camel.main.MainConfig [...]
+    { "name": "camel.main.profile", "required": false, "description": "Camel 
profile to use when running. The dev profile is for development, which enables 
a set of additional developer focus functionality, tracing, debugging, and 
gathering additional runtime statistics that are useful during development. 
However, those additional features has a slight overhead cost, and are not 
enabled for production profile. The default profile is prod.", "sourceType": 
"org.apache.camel.main.DefaultCon [...]
     { "name": "camel.main.routeFilterExcludePattern", "required": false, 
"description": "Used for filtering routes routes matching the given pattern, 
which follows the following rules: - Match by route id - Match by route input 
endpoint uri The matching is using exact match, by wildcard and regular 
expression as documented by PatternHelper#matchPattern(String,String) . For 
example to only include routes which starts with foo in their route id's, use: 
include=foo&#42; And to exclude route [...]
     { "name": "camel.main.routeFilterIncludePattern", "required": false, 
"description": "Used for filtering routes matching the given pattern, which 
follows the following rules: - Match by route id - Match by route input 
endpoint uri The matching is using exact match, by wildcard and regular 
expression as documented by PatternHelper#matchPattern(String,String) . For 
example to only include routes which starts with foo in their route id's, use: 
include=foo&#42; And to exclude routes which [...]
     { "name": "camel.main.routesBuilderClasses", "required": false, 
"description": "Sets classes names that implement RoutesBuilder .", 
"sourceType": "org.apache.camel.main.MainConfigurationProperties", "type": 
"string", "javaType": "java.lang.String", "secret": false },
diff --git 
a/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java
 
b/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java
index 01e3034a48db..c2bba2d5a1c2 100644
--- 
a/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java
+++ 
b/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java
@@ -34,6 +34,8 @@ public abstract class DefaultConfigurationProperties<T> {
 
     private String name;
     private String description;
+    @Metadata(enums = "dev,test,prod")
+    private String profile;
     @Metadata(defaultValue = "Default", enums = 
"Verbose,Default,Brief,Oneline,Off")
     private StartupSummaryLevel startupSummaryLevel;
     private int durationMaxSeconds;
@@ -190,6 +192,23 @@ public abstract class DefaultConfigurationProperties<T> {
         this.description = description;
     }
 
+    public String getProfile() {
+        return profile;
+    }
+
+    /**
+     * Camel profile to use when running.
+     *
+     * The dev profile is for development, which enables a set of additional 
developer focus functionality, tracing,
+     * debugging, and gathering additional runtime statistics that are useful 
during development. However, those
+     * additional features has a slight overhead cost, and are not enabled for 
production profile.
+     *
+     * The default profile is prod.
+     */
+    public void setProfile(String profile) {
+        this.profile = profile;
+    }
+
     public StartupSummaryLevel getStartupSummaryLevel() {
         return startupSummaryLevel;
     }
@@ -1776,6 +1795,20 @@ public abstract class DefaultConfigurationProperties<T> {
         return (T) this;
     }
 
+    /**
+     * Camel profile to use when running.
+     *
+     * The dev profile is for development, which enables a set of additional 
developer focus functionality, tracing,
+     * debugging, and gathering additional runtime statistics that are useful 
during development. However, those
+     * additional features has a slight overhead cost, and are not enabled for 
production profile.
+     *
+     * The default profile is prod.
+     */
+    public T withProfile(String profile) {
+        this.profile = profile;
+        return (T) this;
+    }
+
     /**
      * To specify for how long time in seconds to keep running the JVM before 
automatic terminating the JVM. You can use
      * this to run Camel for a short while.
diff --git 
a/core/camel-main/src/main/java/org/apache/camel/main/MainConfigurationProperties.java
 
b/core/camel-main/src/main/java/org/apache/camel/main/MainConfigurationProperties.java
index 5a6da245a292..7502b3702ed4 100644
--- 
a/core/camel-main/src/main/java/org/apache/camel/main/MainConfigurationProperties.java
+++ 
b/core/camel-main/src/main/java/org/apache/camel/main/MainConfigurationProperties.java
@@ -26,7 +26,6 @@ import org.apache.camel.builder.LambdaRouteBuilder;
 import org.apache.camel.builder.RouteBuilder;
 import org.apache.camel.spi.BootstrapCloseable;
 import org.apache.camel.spi.Configurer;
-import org.apache.camel.spi.Metadata;
 
 /**
  * Global configuration for Camel Main to configure context name, stream 
caching and other global configurations.
@@ -35,8 +34,6 @@ import org.apache.camel.spi.Metadata;
 public class MainConfigurationProperties extends 
DefaultConfigurationProperties<MainConfigurationProperties>
         implements BootstrapCloseable {
 
-    @Metadata(enums = "dev,test,prod")
-    private String profile;
     private boolean autoConfigurationEnabled = true;
     private boolean autoConfigurationEnvironmentVariablesEnabled = true;
     private boolean autoConfigurationSystemPropertiesEnabled = true;
@@ -506,23 +503,6 @@ public class MainConfigurationProperties extends 
DefaultConfigurationProperties<
     // getter and setters
     // --------------------------------------------------------------
 
-    public String getProfile() {
-        return profile;
-    }
-
-    /**
-     * Camel profile to use when running.
-     *
-     * The dev profile is for development, which enables a set of additional 
developer focus functionality, tracing,
-     * debugging, and gathering additional runtime statistics that are useful 
during development. However, those
-     * additional features has a slight overhead cost, and are not enabled for 
production profile.
-     *
-     * The default profile is prod.
-     */
-    public void setProfile(String profile) {
-        this.profile = profile;
-    }
-
     public boolean isAutoConfigurationEnabled() {
         return autoConfigurationEnabled;
     }
@@ -852,20 +832,6 @@ public class MainConfigurationProperties extends 
DefaultConfigurationProperties<
     // fluent builders
     // --------------------------------------------------------------
 
-    /**
-     * Camel profile to use when running.
-     *
-     * The dev profile is for development, which enables a set of additional 
developer focus functionality, tracing,
-     * debugging, and gathering additional runtime statistics that are useful 
during development. However, those
-     * additional features has a slight overhead cost, and are not enabled for 
production profile.
-     *
-     * The default profile is prod.
-     */
-    public MainConfigurationProperties withProfile(String profile) {
-        this.profile = profile;
-        return this;
-    }
-
     /**
      * Whether auto configuration of components/dataformats/languages is 
enabled or not. When enabled the configuration
      * parameters are loaded from the properties component and configured as 
defaults (similar to spring-boot
diff --git 
a/core/camel-main/src/main/java/org/apache/camel/main/ProfileConfigurer.java 
b/core/camel-main/src/main/java/org/apache/camel/main/ProfileConfigurer.java
index 89c361ca29d5..6abe909806b5 100644
--- a/core/camel-main/src/main/java/org/apache/camel/main/ProfileConfigurer.java
+++ b/core/camel-main/src/main/java/org/apache/camel/main/ProfileConfigurer.java
@@ -24,9 +24,10 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * Configure Camel Main with the chosen profile.
+ * Configure Camel with the chosen profile.
  *
- * This is for Camel CLI and Standalone Camel, not Spring Boot or Quarkus; as 
they have their own profile concept.
+ * The {@link #configureMain} method is for Camel Main (CLI and standalone). 
The {@link #configureCommon} method is for
+ * all runtimes (standalone, Spring Boot, Quarkus).
  */
 public class ProfileConfigurer {
 
@@ -48,19 +49,13 @@ public class ProfileConfigurer {
         }
 
         if ("dev".equals(profile)) {
-            // make tracing at least standby so we can use it in dev-mode
+            // make tracer config at least standby so we can use it in dev-mode
             boolean enabled = config.tracerConfig().isEnabled();
             if (!enabled) {
                 config.tracerConfig().withStandby(true);
             }
-            if (!config.isTracing()) {
-                config.setTracingStandby(true);
-            }
             // enable error registry to capture routing errors
             config.errorRegistryConfig().withEnabled(true);
-        }
-
-        if ("dev".equals(profile)) {
             // dev profile allows insecure:dev options by default since those 
features
             // (devConsole, upload, etc.) are expected in development.
             // Users can still override this explicitly via 
camel.security.insecureDevPolicy=warn/fail.
@@ -97,6 +92,10 @@ public class ProfileConfigurer {
         }
 
         if ("dev".equals(profile)) {
+            // make tracing at least standby so we can use it in dev-mode
+            if (!config.isTracing()) {
+                config.setTracingStandby(true);
+            }
             // enable developer features as defaults — user properties can 
override any of these
             setIfNotConfigured(autoConfigured, "camel.main.devConsoleEnabled", 
() -> config.setDevConsoleEnabled(true));
             setIfNotConfigured(autoConfigured, 
"camel.main.camelEventsTimestampEnabled",
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportBaseCommand.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportBaseCommand.java
index d6bf27e829e1..4465ccc09146 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportBaseCommand.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportBaseCommand.java
@@ -1011,6 +1011,11 @@ public abstract class ExportBaseCommand extends 
CamelCommand {
             customize.apply(profileProps);
         }
 
+        // include camel profile if set (so exported runtimes like Spring Boot 
and Quarkus know the profile)
+        if (this.profile != null && 
!profileProps.containsKey("camel.main.profile")) {
+            profileProps.put("camel.main.profile", this.profile);
+        }
+
         StringBuilder content = new StringBuilder();
         for (Map.Entry<Object, Object> entry : profileProps.entrySet()) {
             String k = entry.getKey().toString();
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
index efc31fca6eab..f6c852b81348 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
@@ -1326,6 +1326,7 @@ public class Run extends CamelCommand {
 
         // export to hidden folder
         ExportQuarkus eq = new ExportQuarkus(getMain());
+        eq.exportBaseDir = this.exportBaseDir;
         eq.javaLiveReload = this.dev;
         eq.symbolicLink = this.dev;
         eq.mavenWrapper = true;
@@ -1361,6 +1362,7 @@ public class Run extends CamelCommand {
         eq.loggingLevel = "off";
         eq.ignoreLoadingError = this.ignoreLoadingError;
         eq.lazyBean = this.lazyBean;
+        eq.profile = this.profile;
         eq.applicationProperties = this.property;
 
         printer().println("Running using Quarkus (preparing and downloading 
files)");
@@ -1431,6 +1433,7 @@ public class Run extends CamelCommand {
 
         // export to hidden folder
         ExportSpringBoot eq = new ExportSpringBoot(getMain());
+        eq.exportBaseDir = this.exportBaseDir;
         // the code reload is not supported, since we use symlink, spring-boot 
devtools doesn't support symlink
         if (this.dev) {
             printer().println("WARN: Code reload is not supported with Spring 
Boot.");
@@ -1476,6 +1479,7 @@ public class Run extends CamelCommand {
         eq.loggingLevel = "off";
         eq.ignoreLoadingError = this.ignoreLoadingError;
         eq.lazyBean = this.lazyBean;
+        eq.profile = this.profile;
         eq.applicationProperties = this.property;
 
         printer().println("Running using Spring Boot (preparing and 
downloading files)");
@@ -1623,6 +1627,9 @@ public class Run extends CamelCommand {
             kameletsVersion = answer.getProperty(KAMELETS_VERSION, 
kameletsVersion);
             springBootVersion = answer.getProperty(SPRING_BOOT_VERSION, 
springBootVersion);
             javaVersion = answer.getProperty(JAVA_VERSION, javaVersion);
+            if (quarkusPlatform == null) {
+                quarkusPlatform = new QuarkusPlatformMixin();
+            }
             quarkusPlatform = QuarkusPlatformMixin.of(answer, quarkusPlatform);
             gav = answer.getProperty(GAV, gav);
             stub = answer.getProperty(STUB, stub);
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 1d38c39ac4c1..6550316f5b4d 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
@@ -1195,6 +1195,20 @@ public class CamelMonitor extends CamelCommand {
             return;
         }
 
+        String platform = info.platform;
+        boolean isSpringBoot = "Spring Boot".equals(platform);
+        boolean isQuarkus = "Quarkus".equals(platform);
+
+        // TODO: restart for Spring Boot and Quarkus is not yet reliable
+        if (isSpringBoot || isQuarkus) {
+            setNotification("Restart not supported for " + platform, true);
+            return;
+        }
+
+        restartCamelMainProcess(ph, info);
+    }
+
+    private void restartCamelMainProcess(ProcessHandle ph, IntegrationInfo 
info) {
         // capture command line before stopping
         Optional<String> cmdOpt = ph.info().command();
         Optional<String[]> argsOpt = ph.info().arguments();
@@ -1260,7 +1274,7 @@ public class CamelMonitor extends CamelCommand {
     private void setNotification(String message, boolean error) {
         monitorNotification = message;
         monitorNotificationError = error;
-        monitorNotificationExpiry = System.currentTimeMillis() + 5000;
+        monitorNotificationExpiry = System.currentTimeMillis() + (error ? 
15000 : 5000);
     }
 
     static List<String> parseCommandLine(String commandLine) {
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FilesBrowser.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FilesBrowser.java
index b6089cf01d64..e6cc0837027f 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FilesBrowser.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FilesBrowser.java
@@ -45,7 +45,7 @@ import dev.tamboui.widgets.list.ScrollMode;
 
 class FilesBrowser {
 
-    record FileEntry(String emoji, String name, long size, String path) {
+    record FileEntry(String emoji, String name, long size, String path, 
boolean directory) {
     }
 
     private static final String[] CAMEL_YAML_MARKERS = {
@@ -65,6 +65,8 @@ class FilesBrowser {
 
     private boolean visible;
     private String title;
+    private Path rootDir;
+    private Path currentDir;
     private final ListState listState = new ListState();
     private List<FileEntry> entries = Collections.emptyList();
     private final SourceViewer sourceViewer = new SourceViewer();
@@ -83,6 +85,8 @@ class FilesBrowser {
 
     void reset() {
         visible = false;
+        rootDir = null;
+        currentDir = null;
         entries = Collections.emptyList();
         sourceViewer.reset();
     }
@@ -92,33 +96,56 @@ class FilesBrowser {
         if (dir == null || !Files.isDirectory(dir)) {
             return;
         }
-        List<FileEntry> found = new ArrayList<>();
+        rootDir = dir;
+        currentDir = dir;
+        title = info.name != null ? info.name : "?";
+        sourceViewer.reset();
+        if (!loadDirectory(dir)) {
+            return;
+        }
+        visible = true;
+    }
+
+    private boolean loadDirectory(Path dir) {
+        List<FileEntry> dirs = new ArrayList<>();
+        List<FileEntry> files = new ArrayList<>();
         try (var stream = Files.list(dir)) {
-            stream.filter(Files::isRegularFile)
-                    .limit(99)
+            stream.limit(200)
                     .forEach(p -> {
                         String name = p.getFileName().toString();
-                        String emoji = fileEmoji(p);
-                        long size = 0;
-                        try {
-                            size = Files.size(p);
-                        } catch (IOException e) {
-                            // ignore
+                        if (Files.isDirectory(p)) {
+                            dirs.add(new FileEntry("📁", name, -1, 
p.toString(), true));
+                        } else if (Files.isRegularFile(p)) {
+                            String emoji = fileEmoji(p);
+                            long size = 0;
+                            try {
+                                size = Files.size(p);
+                            } catch (IOException e) {
+                                // ignore
+                            }
+                            files.add(new FileEntry(emoji, name, size, 
p.toString(), false));
                         }
-                        found.add(new FileEntry(emoji, name, size, 
p.toString()));
                     });
         } catch (IOException e) {
-            return;
+            return false;
+        }
+        dirs.sort(Comparator.comparing(FileEntry::name, 
String.CASE_INSENSITIVE_ORDER));
+        files.sort(Comparator.comparing(FileEntry::name, 
String.CASE_INSENSITIVE_ORDER));
+
+        List<FileEntry> found = new ArrayList<>();
+        if (!dir.equals(rootDir)) {
+            found.add(new FileEntry("📁", "..", -1, dir.getParent().toString(), 
true));
         }
+        found.addAll(dirs);
+        found.addAll(files);
+
         if (found.isEmpty()) {
-            return;
+            return false;
         }
-        found.sort(Comparator.comparing(FileEntry::name, 
String.CASE_INSENSITIVE_ORDER));
         entries = found;
-        title = info.name != null ? info.name : "?";
         listState.select(0);
-        visible = true;
-        sourceViewer.reset();
+        currentDir = dir;
+        return true;
     }
 
     boolean handleKeyEvent(KeyEvent ke) {
@@ -148,7 +175,11 @@ class FilesBrowser {
                 Integer sel = listState.selected();
                 if (sel != null && sel < entries.size()) {
                     FileEntry entry = entries.get(sel);
-                    sourceViewer.loadFile(Path.of(entry.path()));
+                    if (entry.directory()) {
+                        loadDirectory(Path.of(entry.path()));
+                    } else {
+                        sourceViewer.loadFile(Path.of(entry.path()));
+                    }
                 }
                 return true;
             }
@@ -168,7 +199,9 @@ class FilesBrowser {
         }
 
         int nameWidth = entries.stream().mapToInt(e -> 
e.name().length()).max().orElse(10);
-        int sizeWidth = entries.stream().mapToInt(e -> 
formatFileSize(e.size()).length()).max().orElse(4);
+        int sizeWidth = entries.stream()
+                .filter(e -> !e.directory())
+                .mapToInt(e -> 
formatFileSize(e.size()).length()).max().orElse(4);
         int itemWidth = 4 + nameWidth + 2 + sizeWidth + 2;
         int popupW = Math.min(area.width() - 4, Math.max(30, itemWidth + 4));
         int popupH = Math.min(area.height() - 4, entries.size() + 2);
@@ -182,10 +215,21 @@ class FilesBrowser {
         ListItem[] items = new ListItem[entries.size()];
         for (int i = 0; i < entries.size(); i++) {
             FileEntry entry = entries.get(i);
-            String sizeStr = formatFileSize(entry.size());
-            String label = String.format("  %s %-" + nameWidth + "s  %s", 
entry.emoji(), entry.name(), sizeStr);
-            items[i] = ListItem.from(label);
+            if (entry.directory()) {
+                String label = String.format("  %s %-" + nameWidth + "s", 
entry.emoji(), entry.name());
+                items[i] = ListItem.from(label);
+            } else {
+                String sizeStr = formatFileSize(entry.size());
+                String label = String.format("  %s %-" + nameWidth + "s  %s", 
entry.emoji(), entry.name(), sizeStr);
+                items[i] = ListItem.from(label);
+            }
+        }
+
+        String popupTitle = " Files: " + title;
+        if (rootDir != null && currentDir != null && 
!currentDir.equals(rootDir)) {
+            popupTitle += "/" + rootDir.relativize(currentDir);
         }
+        popupTitle += " ";
 
         ListWidget list = ListWidget.builder()
                 .items(items)
@@ -195,7 +239,7 @@ class FilesBrowser {
                 .block(Block.builder()
                         .borderType(BorderType.ROUNDED).borders(Borders.ALL)
                         .title(Title.from(Line
-                                .from(Span.styled(" Files: " + title + " ", 
Style.EMPTY.fg(Color.YELLOW).bold()))))
+                                .from(Span.styled(popupTitle, 
Style.EMPTY.fg(Color.YELLOW).bold()))))
                         .build())
                 .build();
         frame.renderStatefulWidget(list, popup, listState);
@@ -205,7 +249,7 @@ class FilesBrowser {
         if (sourceViewer.isVisible()) {
             sourceViewer.renderFooter(spans);
         } else {
-            MonitorContext.hint(spans, "Up/Down", "navigate");
+            MonitorContext.hint(spans, "↑↓", "navigate");
             MonitorContext.hint(spans, "Enter", "open");
             MonitorContext.hint(spans, "Esc", "close");
         }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RunOptionsForm.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RunOptionsForm.java
index 64c776b53636..b9cfdbb1aac2 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RunOptionsForm.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RunOptionsForm.java
@@ -44,15 +44,17 @@ class RunOptionsForm {
 
     // Row indices for page 0
     private static final int ROW_NAME = 0;
-    private static final int ROW_PORT = 1;
-    private static final int ROW_MAX = 2;
-    private static final int ROW_CONSOLE = 3;
-    private static final int ROW_DEV = 4;
-    private static final int ROW_OBSERVE = 5;
-    private static final int ROW_TRACE = 6;
-    private static final int ROW_STUB = 7;
-    private static final int ROW_OTEL_AGENT = 8;
-    private static final int ROW_COUNT = 9;
+    private static final int ROW_RUNTIME = 1;
+    private static final int ROW_PROFILE = 2;
+    private static final int ROW_PORT = 3;
+    private static final int ROW_MAX = 4;
+    private static final int ROW_CONSOLE = 5;
+    private static final int ROW_DEV = 6;
+    private static final int ROW_OBSERVE = 7;
+    private static final int ROW_TRACE = 8;
+    private static final int ROW_STUB = 9;
+    private static final int ROW_OTEL_AGENT = 10;
+    private static final int ROW_COUNT = 11;
 
     private boolean visible;
     private int page;
@@ -60,12 +62,17 @@ class RunOptionsForm {
 
     private static final String[] MAX_MODES = { "Max seconds:", "Max 
messages:", "Max idle secs:" };
     private static final String[] MAX_FLAGS = { "--max-seconds=", 
"--max-messages=", "--max-idle-seconds=" };
+    private static final String[] RUNTIME_LABELS = { "Camel Main", "Spring 
Boot", "Quarkus" };
+    private static final String[] RUNTIME_VALUES = { "camel-main", 
"spring-boot", "quarkus" };
+    private static final String[] PROFILE_LABELS = { "dev", "prod" };
 
     // Text fields
     private TextInputState nameInput;
     private TextInputState portInput;
     private TextInputState maxInput;
     private int maxMode;
+    private int runtimeMode;
+    private int profileMode;
 
     // Checkboxes
     private boolean devMode;
@@ -96,6 +103,8 @@ class RunOptionsForm {
         portInput = new TextInputState("");
         maxInput = new TextInputState("");
         maxMode = 0;
+        runtimeMode = 0;
+        profileMode = 0;
         devMode = dev;
         observe = false;
         backlogTrace = false;
@@ -161,7 +170,9 @@ class RunOptionsForm {
             } else {
                 hint(spans, "Tab", "next");
             }
-            if (selectedRow >= ROW_CONSOLE) {
+            if (selectedRow == ROW_RUNTIME || selectedRow == ROW_PROFILE || 
selectedRow == ROW_MAX) {
+                hint(spans, "Space", "cycle");
+            } else if (selectedRow >= ROW_CONSOLE) {
                 hint(spans, "Space", "toggle");
             }
             if (hasProperties()) {
@@ -184,6 +195,10 @@ class RunOptionsForm {
         if (!name.isEmpty()) {
             args.add("--name=" + name);
         }
+        if (runtimeMode > 0) {
+            args.add("--runtime=" + RUNTIME_VALUES[runtimeMode]);
+        }
+        args.add("--profile=" + PROFILE_LABELS[profileMode]);
         String port = portInput.text().trim();
         if (!port.isEmpty()) {
             args.add("--port=" + port);
@@ -277,6 +292,26 @@ class RunOptionsForm {
             return true;
         }
 
+        // Runtime row: Space or Left/Right cycles
+        if (selectedRow == ROW_RUNTIME) {
+            if (ke.isChar(' ') || ke.isRight()) {
+                runtimeMode = (runtimeMode + 1) % RUNTIME_LABELS.length;
+                return true;
+            }
+            if (ke.isLeft()) {
+                runtimeMode = (runtimeMode - 1 + RUNTIME_LABELS.length) % 
RUNTIME_LABELS.length;
+                return true;
+            }
+        }
+
+        // Profile row: Space or Left/Right cycles
+        if (selectedRow == ROW_PROFILE) {
+            if (ke.isChar(' ') || ke.isRight() || ke.isLeft()) {
+                profileMode = (profileMode + 1) % PROFILE_LABELS.length;
+                return true;
+            }
+        }
+
         // Max row: Space cycles mode
         if (ke.isChar(' ') && selectedRow == ROW_MAX) {
             maxMode = (maxMode + 1) % MAX_MODES.length;
@@ -296,8 +331,8 @@ class RunOptionsForm {
             return true;
         }
 
-        // Text field rows: delegate to active input
-        if (selectedRow <= ROW_MAX) {
+        // Text field rows: delegate to active input (skip runtime row — it 
uses cycling)
+        if (selectedRow <= ROW_MAX && selectedRow != ROW_RUNTIME && 
selectedRow != ROW_PROFILE) {
             TextInputState active = activeInput();
             if (active != null) {
                 handleTextInput(ke, active, selectedRow == ROW_PORT || 
selectedRow == ROW_MAX);
@@ -408,7 +443,7 @@ class RunOptionsForm {
 
     private void renderOptionsPage(Frame frame, Rect area) {
         int popupW = Math.min(56, area.width() - 4);
-        int popupH = 13;
+        int popupH = 15;
         int x = area.left() + Math.max(0, (area.width() - popupW) / 2);
         int y = area.top() + Math.max(0, (area.height() - popupH) / 4);
         Rect popup = new Rect(x, y, Math.min(popupW, area.width()), 
Math.min(popupH, area.height()));
@@ -438,6 +473,14 @@ class RunOptionsForm {
         renderTextInput(frame, innerX + labelW, rowY, fieldW, nameInput, 
selectedRow == ROW_NAME);
         rowY++;
 
+        renderLabel(frame, innerX, rowY, labelW, "Runtime:", selectedRow == 
ROW_RUNTIME);
+        renderCycler(frame, innerX + labelW, rowY, fieldW, RUNTIME_LABELS, 
runtimeMode, selectedRow == ROW_RUNTIME);
+        rowY++;
+
+        renderLabel(frame, innerX, rowY, labelW, "Profile:", selectedRow == 
ROW_PROFILE);
+        renderCycler(frame, innerX + labelW, rowY, fieldW, PROFILE_LABELS, 
profileMode, selectedRow == ROW_PROFILE);
+        rowY++;
+
         renderLabel(frame, innerX, rowY, labelW, "Port:", selectedRow == 
ROW_PORT);
         renderTextInput(frame, innerX + labelW, rowY, fieldW, portInput, 
selectedRow == ROW_PORT);
         rowY++;
@@ -644,6 +687,22 @@ class RunOptionsForm {
         }
     }
 
+    private void renderCycler(Frame frame, int x, int y, int w, String[] 
labels, int active, boolean selected) {
+        List<Span> spans = new ArrayList<>();
+        for (int i = 0; i < labels.length; i++) {
+            if (i > 0) {
+                spans.add(Span.styled(" ", Style.EMPTY));
+            }
+            if (i == active) {
+                spans.add(Span.styled("[" + labels[i] + "]", selected ? 
Style.EMPTY.bold() : Style.EMPTY));
+            } else {
+                spans.add(Span.styled(" " + labels[i] + " ", 
Style.EMPTY.dim()));
+            }
+        }
+        Rect area = new Rect(x, y, w, 1);
+        frame.renderWidget(Paragraph.from(Line.from(spans)), area);
+    }
+
     private void renderCheckbox(Frame frame, int x, int y, int w, String 
label, boolean checked, boolean selected) {
         String box = checked ? "[x]" : "[ ]";
         Style style = selected ? Style.EMPTY.bold().reversed() : Style.EMPTY;


Reply via email to