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

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

commit cbb75e30bf1ee48ee4c8c8fdb2c2dbb6cce25082
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Dec 4 16:58:17 2025 +0100

    CAMEL-22748: camel-jbang - camel debug to do remote attach to existing 
running Camel
---
 bom/camel-bom/pom.xml                              |   5 +
 .../org/apache/camel/catalog/others.properties     |   1 +
 .../org/apache/camel/catalog/others/cli-debug.json |  15 +++
 .../java/org/apache/camel/spi/CliConnector.java    |   7 ++
 .../org/apache/camel/spi/ShutdownPrepared.java     |   6 +-
 .../impl/debugger/DefaultBacklogDebugger.java      |  12 +-
 .../camel/impl/engine/AbstractCamelContext.java    |  23 ++--
 docs/components/modules/others/nav.adoc            |   1 +
 .../components/modules/others/pages/cli-debug.adoc |   1 +
 .../camel/cli/connector/LocalCliConnector.java     |  50 +++++++-
 dsl/{ => camel-cli-debug}/pom.xml                  |  61 ++++------
 .../services/org/apache/camel/debugger-factory     |   2 +
 .../services/org/apache/camel/other.properties     |   7 ++
 .../src/generated/resources/cli-debug.json         |  15 +++
 dsl/camel-cli-debug/src/main/docs/cli-debug.adoc   |  55 +++++++++
 .../cli/debug/CamelCliDebuggerFactory.java         | 127 +++++++++++++++++++++
 .../camel/dsl/jbang/core/commands/Debug.java       | 119 ++++++++++++++++++-
 dsl/pom.xml                                        |   1 +
 parent/pom.xml                                     |   5 +
 19 files changed, 451 insertions(+), 62 deletions(-)

diff --git a/bom/camel-bom/pom.xml b/bom/camel-bom/pom.xml
index 10a4c83ec8eb..710d1a661cba 100644
--- a/bom/camel-bom/pom.xml
+++ b/bom/camel-bom/pom.xml
@@ -441,6 +441,11 @@
         <artifactId>camel-cli-connector</artifactId>
         <version>4.17.0-SNAPSHOT</version>
       </dependency>
+      <dependency>
+        <groupId>org.apache.camel</groupId>
+        <artifactId>camel-cli-debug</artifactId>
+        <version>4.17.0-SNAPSHOT</version>
+      </dependency>
       <dependency>
         <groupId>org.apache.camel</groupId>
         <artifactId>camel-clickup</artifactId>
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/others.properties
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/others.properties
index ed5acbb0859e..964ecb3fc0d7 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/others.properties
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/others.properties
@@ -2,6 +2,7 @@ attachments
 aws-xray
 azure-schema-registry
 cli-connector
+cli-debug
 cloud
 cloudevents
 cluster
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/others/cli-debug.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/others/cli-debug.json
new file mode 100644
index 000000000000..4acfc083d275
--- /dev/null
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/others/cli-debug.json
@@ -0,0 +1,15 @@
+{
+  "other": {
+    "kind": "other",
+    "name": "cli-debug",
+    "title": "CLI Debug",
+    "description": "Remote CLI debugger",
+    "deprecated": false,
+    "firstVersion": "4.17.0",
+    "label": "tooling",
+    "supportLevel": "Preview",
+    "groupId": "org.apache.camel",
+    "artifactId": "camel-cli-debug",
+    "version": "4.17.0-SNAPSHOT"
+  }
+}
diff --git 
a/core/camel-api/src/main/java/org/apache/camel/spi/CliConnector.java 
b/core/camel-api/src/main/java/org/apache/camel/spi/CliConnector.java
index ded99a33e065..36997762846b 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/CliConnector.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/CliConnector.java
@@ -31,4 +31,11 @@ public interface CliConnector extends StaticService, 
NonManagedService {
      */
     void sigterm();
 
+    /**
+     * Allows to adjust the frequency delay which the CliConnect reacts. Uses 
1000 millis by default.
+     *
+     * @param delay delay in millis
+     */
+    void updateDelay(int delay);
+
 }
diff --git 
a/core/camel-api/src/main/java/org/apache/camel/spi/ShutdownPrepared.java 
b/core/camel-api/src/main/java/org/apache/camel/spi/ShutdownPrepared.java
index 60f2c8c246e0..666dc38abdcf 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/ShutdownPrepared.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/ShutdownPrepared.java
@@ -37,7 +37,7 @@ public interface ShutdownPrepared {
      * {@link ShutdownStrategy} performs a more aggressive shutdown, calling 
this method a second time with
      * <tt>true</tt> for the given forced parameter. For example by graceful 
stopping any threads or the likes.
      * <p/>
-     * In addition a service can also be suspended (not stopped), and when 
this happens the parameter
+     * In addition, a service can also be suspended (not stopped), and when 
this happens the parameter
      * <tt>suspendOnly</tt> has the value <tt>true</tt>. This can be used to 
prepare the service for suspension, such as
      * marking a worker thread to skip action.
      * <p/>
@@ -46,8 +46,8 @@ public interface ShutdownPrepared {
      *
      * @param suspendOnly <tt>true</tt> if the intention is to only suspend 
the service, and not stop/shutdown the
      *                    service.
-     * @param forced      <tt>true</tt> is forcing a more aggressive shutdown, 
<tt>false</tt> is for preparing to
-     *                    shutdown.
+     * @param forced      <tt>true</tt> is forcing a more aggressive shutdown, 
<tt>false</tt> is for preparing to shut
+     *                    down.
      */
     void prepareShutdown(boolean suspendOnly, boolean forced);
 
diff --git 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/debugger/DefaultBacklogDebugger.java
 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/debugger/DefaultBacklogDebugger.java
index 2d41f81a70a4..92daba25a46d 100644
--- 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/debugger/DefaultBacklogDebugger.java
+++ 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/debugger/DefaultBacklogDebugger.java
@@ -50,6 +50,7 @@ import org.apache.camel.spi.CamelEvent.ExchangeEvent;
 import org.apache.camel.spi.CamelLogger;
 import org.apache.camel.spi.Condition;
 import org.apache.camel.spi.Debugger;
+import org.apache.camel.spi.ShutdownPrepared;
 import org.apache.camel.support.BreakpointSupport;
 import org.apache.camel.support.CamelContextHelper;
 import org.apache.camel.support.LoggerHelper;
@@ -63,7 +64,7 @@ import org.apache.camel.util.json.JsonObject;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-public final class DefaultBacklogDebugger extends ServiceSupport implements 
BacklogDebugger {
+public final class DefaultBacklogDebugger extends ServiceSupport implements 
BacklogDebugger, ShutdownPrepared {
 
     private static final Logger LOG = 
LoggerFactory.getLogger(DefaultBacklogDebugger.class);
 
@@ -267,6 +268,15 @@ public final class DefaultBacklogDebugger extends 
ServiceSupport implements Back
         }
     }
 
+    @Override
+    public void prepareShutdown(boolean suspendOnly, boolean forced) {
+        logger.log("Preparing debugger for shutdown");
+        // camel is being shutdown so if we are suspended during debugging
+        // then turn this off so messages can finish processing
+        suspendMode = false;
+        resumeMessageProcessing();
+    }
+
     /**
      * Resolves the value of the flag indicating whether the {@code 
BacklogDebugger} should suspend processing the
      * messages and wait for a debugger to attach or not.
diff --git 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java
 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java
index a321429a24d7..73c7e1165023 100644
--- 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java
+++ 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java
@@ -2506,6 +2506,18 @@ public abstract class AbstractCamelContext extends 
BaseService
 
         forceLazyInitialization();
 
+        // setup cli-connector if not already done (before debugger)
+        if (hasService(CliConnector.class) == null) {
+            CliConnectorFactory ccf = 
getCamelContextExtension().getContextPlugin(CliConnectorFactory.class);
+            if (ccf != null && ccf.isEnabled()) {
+                CliConnector connector = ccf.createConnector();
+                addService(connector, true);
+                // force start cli connector early as otherwise it will be 
deferred until context is started
+                // but, we want status available during startup phase
+                ServiceHelper.startService(connector);
+            }
+        }
+
         // auto-detect camel-debug on classpath (if debugger has not been 
explicit added)
         boolean debuggerDetected = false;
         if (getDebugger() == null && hasService(BacklogDebugger.class) == 
null) {
@@ -2530,17 +2542,6 @@ public abstract class AbstractCamelContext extends 
BaseService
                 addService(backlog, true, true);
             }
         }
-        // setup cli-connector if not already done (after debugger)
-        if (hasService(CliConnector.class) == null) {
-            CliConnectorFactory ccf = 
getCamelContextExtension().getContextPlugin(CliConnectorFactory.class);
-            if (ccf != null && ccf.isEnabled()) {
-                CliConnector connector = ccf.createConnector();
-                addService(connector, true);
-                // force start cli connector early as otherwise it will be 
deferred until context is started
-                // but, we want status available during startup phase
-                ServiceHelper.startService(connector);
-            }
-        }
 
         addService(getManagementStrategy(), false);
 
diff --git a/docs/components/modules/others/nav.adoc 
b/docs/components/modules/others/nav.adoc
index 6ecb7d4919ce..dd4d8071bf88 100644
--- a/docs/components/modules/others/nav.adoc
+++ b/docs/components/modules/others/nav.adoc
@@ -6,6 +6,7 @@
 ** xref:aws-xray.adoc[AWS XRay]
 ** xref:azure-schema-registry.adoc[Azure Schema Registry]
 ** xref:cli-connector.adoc[CLI Connector]
+** xref:cli-debug.adoc[CLI Debug]
 ** xref:cloudevents.adoc[Cloudevents]
 ** xref:csimple-joor.adoc[CSimple jOOR]
 ** xref:cxf-transport.adoc[CXF Transport]
diff --git a/docs/components/modules/others/pages/cli-debug.adoc 
b/docs/components/modules/others/pages/cli-debug.adoc
new file mode 120000
index 000000000000..882fa9b85009
--- /dev/null
+++ b/docs/components/modules/others/pages/cli-debug.adoc
@@ -0,0 +1 @@
+../../../../../dsl/camel-cli-debug/src/main/docs/cli-debug.adoc
\ No newline at end of file
diff --git 
a/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java
 
b/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java
index 0e43279af66d..6019e7d817f0 100644
--- 
a/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java
+++ 
b/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java
@@ -40,6 +40,7 @@ import java.util.Optional;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 
@@ -63,6 +64,7 @@ import org.apache.camel.model.ProcessorDefinition;
 import org.apache.camel.model.ProcessorDefinitionHelper;
 import org.apache.camel.model.RouteDefinition;
 import org.apache.camel.model.language.ExpressionDefinition;
+import org.apache.camel.spi.BacklogDebugger;
 import org.apache.camel.spi.CliConnector;
 import org.apache.camel.spi.CliConnectorFactory;
 import org.apache.camel.spi.ContextReloadStrategy;
@@ -72,6 +74,7 @@ import org.apache.camel.spi.Resource;
 import org.apache.camel.spi.ResourceLoader;
 import org.apache.camel.spi.ResourceReloadStrategy;
 import org.apache.camel.spi.RoutesLoader;
+import org.apache.camel.spi.ShutdownPrepared;
 import org.apache.camel.support.LoadOnDemandReloadStrategy;
 import org.apache.camel.support.MessageHelper;
 import org.apache.camel.support.PatternHelper;
@@ -101,6 +104,7 @@ public class LocalCliConnector extends ServiceSupport 
implements CliConnector, C
 
     private final CliConnectorFactory cliConnectorFactory;
     private CamelContext camelContext;
+    private ScheduledFuture scheduledFuture;
     private int delay = 1000;
     private long counter;
     private String platform;
@@ -192,13 +196,32 @@ public class LocalCliConnector extends ServiceSupport 
implements CliConnector, C
             messageHistoryFile = createLockFile(lockFile.getName() + 
"-history.json");
             debugFile = createLockFile(lockFile.getName() + "-debug.json");
             receiveFile = createLockFile(lockFile.getName() + "-receive.json");
-            executor.scheduleWithFixedDelay(this::task, 0, delay, 
TimeUnit.MILLISECONDS);
+            scheduledFuture = executor.scheduleWithFixedDelay(this::task, 0, 
delay, TimeUnit.MILLISECONDS);
             LOG.info("Camel JBang CLI enabled");
         } else {
             LOG.warn("Cannot create PID file: {}. This integration cannot be 
managed by Camel JBang CLI.", getPid());
         }
     }
 
+    @Override
+    public void updateDelay(int delay) {
+        if (this.delay == delay) {
+            return;
+        }
+        if (scheduledFuture != null) {
+            try {
+                scheduledFuture.cancel(true);
+            } catch (Exception e) {
+                // ignore
+            }
+        }
+        boolean done = scheduledFuture == null || scheduledFuture.isDone();
+        if (done) {
+            this.delay = delay;
+            scheduledFuture = executor.scheduleWithFixedDelay(this::task, 0, 
delay, TimeUnit.MILLISECONDS);
+        }
+    }
+
     @Override
     public void sigterm() {
         // we are terminating
@@ -214,6 +237,12 @@ public class LocalCliConnector extends ServiceSupport 
implements CliConnector, C
             public void run() {
                 LOG.info("Camel JBang terminating JVM");
                 try {
+                    // if we are debugging then detach before stopping camel
+                    BacklogDebugger debugger = 
camelContext.hasService(BacklogDebugger.class);
+                    if (debugger instanceof ShutdownPrepared sp) {
+                        sp.prepareShutdown(false, true);
+                    }
+                    ServiceHelper.stopAndShutdownServices(debugger);
                     camelContext.stop();
                 } finally {
                     ServiceHelper.stopAndShutdownService(this);
@@ -300,6 +329,8 @@ public class LocalCliConnector extends ServiceSupport 
implements CliConnector, C
                 doActionBrowseTask(root);
             } else if ("receive".equals(action)) {
                 doActionReceiveTask(root);
+            } else if ("cli-debug".equals(action)) {
+                doActionCliDebug(root);
             }
         } catch (Exception e) {
             // ignore
@@ -312,6 +343,23 @@ public class LocalCliConnector extends ServiceSupport 
implements CliConnector, C
         }
     }
 
+    private void doActionCliDebug(JsonObject root) {
+        String command = root.getString("command");
+        String breakpoint = root.getString("breakpoint");
+        BacklogDebugger debugger = 
camelContext.hasService(BacklogDebugger.class);
+        if (debugger != null) {
+            if ("attach".equals(command)) {
+                if (breakpoint != null) {
+                    debugger.setInitialBreakpoints(breakpoint);
+                }
+                debugger.enableDebugger();
+                debugger.attach();
+            } else if ("detach".equals(command)) {
+                debugger.detach();
+            }
+        }
+    }
+
     private void doActionTransformTask(JsonObject root) throws Exception {
         StopWatch watch = new StopWatch();
         long timestamp = System.currentTimeMillis();
diff --git a/dsl/pom.xml b/dsl/camel-cli-debug/pom.xml
similarity index 64%
copy from dsl/pom.xml
copy to dsl/camel-cli-debug/pom.xml
index a3188d172db7..f4ae9f60b988 100644
--- a/dsl/pom.xml
+++ b/dsl/camel-cli-debug/pom.xml
@@ -17,51 +17,41 @@
     limitations under the License.
 
 -->
-<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/maven-v4_0_0.xsd";>
+
     <modelVersion>4.0.0</modelVersion>
 
     <parent>
         <groupId>org.apache.camel</groupId>
-        <artifactId>camel-parent</artifactId>
+        <artifactId>dsl</artifactId>
         <version>4.17.0-SNAPSHOT</version>
-        <relativePath>../parent/pom.xml</relativePath>
     </parent>
 
-    <artifactId>dsl</artifactId>
-    <packaging>pom</packaging>
-
-    <name>Camel :: DSL :: Parent</name>
-    <description>Camel DSL Parent</description>
+    <artifactId>camel-cli-debug</artifactId>
+    <packaging>jar</packaging>
+    <name>Camel :: DSL :: CLI Debug</name>
+    <description>Remote CLI debugger</description>
 
     <properties>
-        <camel.surefire.parallel>true</camel.surefire.parallel>
-        <camel-prepare-component>true</camel-prepare-component>
+        <firstVersion>4.17.0</firstVersion>
+        <title>CLI Debug</title>
+        <label>tooling</label>
     </properties>
 
-    <modules>
-        <module>camel-endpointdsl</module>
-        <module>camel-componentdsl</module>
-        <module>camel-cli-connector</module>
-        <module>camel-dsl-support</module>
-        <module>camel-dsl-modeline</module>
-        <module>camel-endpointdsl-support</module>
-        <module>camel-java-joor-dsl</module>
-        <module>camel-xml-io-dsl</module>
-        <module>camel-xml-jaxb-dsl</module>
-        <module>camel-xml-jaxb-dsl-test</module>
-        <module>camel-yaml-dsl</module>
-        <module>camel-kamelet-main</module>
-        <module>camel-jbang</module>
-    </modules>
+    <dependencies>
+
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-cli-connector</artifactId>
+        </dependency>
+
+    </dependencies>
 
     <build>
         <plugins>
             <plugin>
                 <groupId>org.apache.camel</groupId>
                 <artifactId>camel-package-maven-plugin</artifactId>
-                <configuration>
-                </configuration>
-
                 <executions>
                     <execution>
                         <id>generate</id>
@@ -79,24 +69,12 @@
                     </execution>
                 </executions>
             </plugin>
-            <plugin>
-                <artifactId>maven-compiler-plugin</artifactId>
-                <executions>
-                    <execution>
-                        <id>recompile</id>
-                        <goals>
-                            <goal>compile</goal>
-                        </goals>
-                        <phase>process-classes</phase>
-                    </execution>
-                </executions>
-            </plugin>
             <plugin>
                 <groupId>org.codehaus.mojo</groupId>
                 <artifactId>build-helper-maven-plugin</artifactId>
                 <executions>
                     <execution>
-                        <phase>generate-sources</phase>
+                        <phase>initialize</phase>
                         <goals>
                             <goal>add-source</goal>
                             <goal>add-resource</goal>
@@ -116,4 +94,5 @@
             </plugin>
         </plugins>
     </build>
+
 </project>
diff --git 
a/dsl/camel-cli-debug/src/generated/resources/META-INF/services/org/apache/camel/debugger-factory
 
b/dsl/camel-cli-debug/src/generated/resources/META-INF/services/org/apache/camel/debugger-factory
new file mode 100644
index 000000000000..1247dbb94f9a
--- /dev/null
+++ 
b/dsl/camel-cli-debug/src/generated/resources/META-INF/services/org/apache/camel/debugger-factory
@@ -0,0 +1,2 @@
+# Generated by camel build tools - do NOT edit this file!
+class=org.apache.camel.component.cli.debug.CamelCliDebuggerFactory
diff --git 
a/dsl/camel-cli-debug/src/generated/resources/META-INF/services/org/apache/camel/other.properties
 
b/dsl/camel-cli-debug/src/generated/resources/META-INF/services/org/apache/camel/other.properties
new file mode 100644
index 000000000000..d6d2632c5751
--- /dev/null
+++ 
b/dsl/camel-cli-debug/src/generated/resources/META-INF/services/org/apache/camel/other.properties
@@ -0,0 +1,7 @@
+# Generated by camel build tools - do NOT edit this file!
+name=cli-debug
+groupId=org.apache.camel
+artifactId=camel-cli-debug
+version=4.17.0-SNAPSHOT
+projectName=Camel :: DSL :: CLI Debug
+projectDescription=Remote CLI debugger
diff --git a/dsl/camel-cli-debug/src/generated/resources/cli-debug.json 
b/dsl/camel-cli-debug/src/generated/resources/cli-debug.json
new file mode 100644
index 000000000000..4acfc083d275
--- /dev/null
+++ b/dsl/camel-cli-debug/src/generated/resources/cli-debug.json
@@ -0,0 +1,15 @@
+{
+  "other": {
+    "kind": "other",
+    "name": "cli-debug",
+    "title": "CLI Debug",
+    "description": "Remote CLI debugger",
+    "deprecated": false,
+    "firstVersion": "4.17.0",
+    "label": "tooling",
+    "supportLevel": "Preview",
+    "groupId": "org.apache.camel",
+    "artifactId": "camel-cli-debug",
+    "version": "4.17.0-SNAPSHOT"
+  }
+}
diff --git a/dsl/camel-cli-debug/src/main/docs/cli-debug.adoc 
b/dsl/camel-cli-debug/src/main/docs/cli-debug.adoc
new file mode 100644
index 000000000000..265d41de125d
--- /dev/null
+++ b/dsl/camel-cli-debug/src/main/docs/cli-debug.adoc
@@ -0,0 +1,55 @@
+= CLI Debug Component
+:doctitle: CLI Debug
+:shortname: cli-debug
+:artifactid: camel-cli-debug
+:description: Remote CLI debugger
+:since: 4.17
+:supportlevel: Preview
+:tabs-sync-option:
+//Manually maintained attributes
+:camel-spring-boot-name: debug
+
+*Since Camel {since}*
+
+The camel-cli-debug enables Camel debugger for Camel CLI (camel-jbang).
+
+[IMPORTANT]
+====
+The camel-cli-debug is only for development purposes, it should **not** be 
used for production.
+
+Do not use both `camel-debug` and `camel-cli-debug` JARs in the Camel 
application classpath. Use only `camel-debug` JAR.
+====
+
+== Auto-detection from classpath
+
+To use this implementation all you need to do is to add the `camel-cli-debug` 
dependency to the classpath,
+and Camel should auto-detect this on startup and log as follows:
+
+[source,text]
+----
+Detected: camel-cli-debug JAR (Enabling Camel Debugging)
+----
+
+== Debugging
+
+Add `camel-cli-debug` JAR to the classpath of your application, and then run 
this application. For example with Spring Boot using `mvn spring-boot:run`.
+
+Then the application starts and Camel detects the CLI debugger that then logs 
a message, waiting for the CLI to remotely attach.
+
+From a CLI terminal then you can execute:
+
+[source,bash]
+----
+$ camel debug --remote-attach --name=<pid>
+----
+
+Where `<pid>` is the process id of the running Camel integration. You can see 
this from the log such as:
+
+[source,text]
+----
+2025-12-04T15:48:12.542+01:00  INFO 6667 --- [           main] 
o.a.c.impl.engine.AbstractCamelContext   : Detected: camel-cli-debug JAR 
(Enabling Camel Debugging)
+2025-12-04T15:48:12.543+01:00  INFO 6667 --- [           main] 
o.a.c.c.c.debug.CamelCliDebuggerFactory  : 
================================================================================
+2025-12-04T15:48:12.543+01:00  INFO 6667 --- [           main] 
o.a.c.c.c.debug.CamelCliDebuggerFactory  : Waiting for CLI to remote attach 
(camel debug --remote-attach --name=6667)
+----
+
+To connect the CLI to the Camel application that will then continue to start 
and perform debugging from the CLI terminal.
\ No newline at end of file
diff --git 
a/dsl/camel-cli-debug/src/main/java/org/apache/camel/component/cli/debug/CamelCliDebuggerFactory.java
 
b/dsl/camel-cli-debug/src/main/java/org/apache/camel/component/cli/debug/CamelCliDebuggerFactory.java
new file mode 100644
index 000000000000..4018beaf71cc
--- /dev/null
+++ 
b/dsl/camel-cli-debug/src/main/java/org/apache/camel/component/cli/debug/CamelCliDebuggerFactory.java
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.cli.debug;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.impl.debugger.DefaultBacklogDebugger;
+import org.apache.camel.spi.BacklogDebugger;
+import org.apache.camel.spi.CliConnector;
+import org.apache.camel.spi.Debugger;
+import org.apache.camel.spi.DebuggerFactory;
+import org.apache.camel.spi.annotations.JdkService;
+import org.apache.camel.support.LifecycleStrategySupport;
+import org.apache.camel.util.StopWatch;
+import org.apache.camel.util.concurrent.ThreadHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@JdkService(Debugger.FACTORY)
+public class CamelCliDebuggerFactory implements DebuggerFactory {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(CamelCliDebuggerFactory.class);
+
+    private final AtomicBoolean hangupIntercepted = new AtomicBoolean();
+
+    @Override
+    // Debugger is created and added as a service. This method always returns 
a null object.
+    public Debugger createDebugger(CamelContext camelContext) throws Exception 
{
+        // only create a debugger if none already exists
+        if (camelContext.hasService(BacklogDebugger.class) == null) {
+
+            // NOTE: the AutoCloseable object is added as a Service, hence it 
is closed by Camel context
+            // according to the object lifecycle.
+            BacklogDebugger backlog = 
DefaultBacklogDebugger.createDebugger(camelContext); // NOSONAR
+            backlog.setStandby(true);
+            backlog.setLoggingLevel("DEBUG");
+            backlog.setSingleStepIncludeStartEnd(true);
+            
backlog.setInitialBreakpoints(BacklogDebugger.BREAKPOINT_ALL_ROUTES);
+            backlog.setSuspendMode(true); // wait for attach via CLI
+
+            // must enable source location and history
+            // so debugger tooling knows to map breakpoints to source code
+            camelContext.setSourceLocationEnabled(true);
+            camelContext.setMessageHistory(true);
+
+            // enable debugger on camel
+            camelContext.setDebugging(true);
+
+            // we need to enable debugger after context is started
+            camelContext.addLifecycleStrategy(new LifecycleStrategySupport() {
+                @Override
+                public void onContextStarted(CamelContext context) {
+                    // noop
+                }
+
+                @Override
+                public void onContextStopping(CamelContext context) {
+                    backlog.detach();
+                    backlog.disableDebugger();
+                }
+            });
+            camelContext.addService(backlog, true, true);
+
+            // need to make debugger faster
+            CliConnector cli = camelContext.hasService(CliConnector.class);
+            if (cli != null) {
+                cli.updateDelay(100);
+            }
+
+            installHangupInterceptor();
+
+            long pid = ProcessHandle.current().pid();
+
+            LOG.info("=".repeat(80));
+            LOG.info("Waiting for CLI to remote attach (camel debug 
--remote-attach --name={})", pid);
+            StopWatch watch = new StopWatch();
+            while (!hangupIntercepted.get() && !backlog.isEnabled() && 
!camelContext.isStopping()) {
+                try {
+                    Thread.sleep(1000);
+                    if (watch.taken() > 5000) {
+                        LOG.info("Waiting for CLI to remote attach (camel 
debug --remote-attach --name={})", pid);
+                        watch.restart();
+                    }
+                } catch (InterruptedException e) {
+                    return null;
+                }
+            }
+            if (backlog.isEnabled()) {
+                LOG.info("CLI debugger attached");
+            }
+        }
+
+        // return null as we fool camel-core into using this backlog debugger 
as we added it as a service
+        return null;
+    }
+
+    @Override
+    public String toString() {
+        return "camel-cli-debug";
+    }
+
+    private void handleHangup() {
+        hangupIntercepted.set(true);
+    }
+
+    private void installHangupInterceptor() {
+        Thread task = new Thread(this::handleHangup);
+        task.setName(ThreadHelper.resolveThreadName(null, 
"CamelCliDebuggerHangupInterceptor"));
+        Runtime.getRuntime().addShutdownHook(task);
+    }
+
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Debug.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Debug.java
index c2bf8cbac795..8aac1e82db4c 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Debug.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Debug.java
@@ -42,8 +42,10 @@ import 
org.apache.camel.dsl.jbang.core.commands.action.MessageTableHelper;
 import org.apache.camel.dsl.jbang.core.common.CamelCommandHelper;
 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.ProcessHelper;
 import org.apache.camel.dsl.jbang.core.common.VersionHelper;
 import org.apache.camel.main.KameletMain;
+import org.apache.camel.support.PatternHelper;
 import org.apache.camel.util.FileUtil;
 import org.apache.camel.util.IOHelper;
 import org.apache.camel.util.StringHelper;
@@ -74,6 +76,10 @@ import static org.apache.camel.util.IOHelper.buffered;
 @Command(name = "debug", description = "Debug local Camel integration", 
sortOptions = false, showDefaultValues = true)
 public class Debug extends Run {
 
+    @CommandLine.Option(names = { "--remote-attach" },
+                        description = "Attaches debugger remotely to an 
existing running Camel integration. (Add camel-cli-debug JAR to the existing 
Camel application and run before attaching this debugger)")
+    boolean remoteAttach;
+
     @CommandLine.Option(names = { "--breakpoint" },
                         description = "To set breakpoint at the given node id 
(Multiple ids can be separated by comma). If no breakpoint is set, then the 
first route is automatic selected.")
     String breakpoint;
@@ -151,7 +157,12 @@ public class Debug extends Run {
             printConfigurationValues("Debugging integration with the following 
configuration:");
         }
 
-        Integer exit = runDebug();
+        Integer exit;
+        if (remoteAttach) {
+            exit = runRemoteAttach();
+        } else {
+            exit = runDebug();
+        }
         if (exit != null && exit != 0) {
             return exit;
         }
@@ -170,10 +181,12 @@ public class Debug extends Run {
         // read log input
         final AtomicBoolean quit = new AtomicBoolean();
         final Console c = System.console();
-        Thread t = new Thread(() -> {
-            doReadLog(quit);
-        }, "ReadLog");
-        t.start();
+        if (logLines > 0) {
+            Thread t = new Thread(() -> {
+                doReadLog(quit);
+            }, "ReadLog");
+            t.start();
+        }
 
         // read CLI input from user
         Thread t2 = new Thread(() -> doRead(c, quit), "ReadCommand");
@@ -205,6 +218,41 @@ public class Debug extends Run {
         return 0;
     }
 
+    private Integer runRemoteAttach() {
+        // find PID of running apps (if there are multiple then filter by name)
+        List<Long> pids = findPids(name);
+        if (pids.isEmpty()) {
+            return -1;
+        } else if (pids.size() > 1) {
+            printer().println("Name or pid " + name + " matches " + pids.size()
+                              + " running Camel integrations. Specify a name 
or PID that matches exactly one.");
+            return -1;
+        }
+        long pid = pids.get(0);
+
+        Path outputFile = getOutputFile(Long.toString(pid));
+        PathUtils.deleteFile(outputFile);
+
+        try {
+            JsonObject root = new JsonObject();
+            root.put("action", "cli-debug");
+            root.put("command", "attach");
+            if (breakpoint != null) {
+                root.put("breakpoint", breakpoint);
+            }
+            Path f = getActionFile(Long.toString(pid));
+            Files.writeString(f, root.toJson());
+        } catch (Exception e) {
+            return -1;
+        }
+
+        // attach to this pid
+        spawnPid = pid;
+        logLines = 0; // no logging possible
+
+        return 0;
+    }
+
     private void doReadLog(AtomicBoolean quit) {
         do {
             if (spawnOutput != null) {
@@ -1209,4 +1257,65 @@ public class Debug extends Run {
 
     }
 
+    List<Long> findPids(String name) {
+        List<Long> pids = new ArrayList<>();
+
+        // we need to know the pids of the running camel integrations
+        if (name.matches("\\d+")) {
+            return List.of(Long.parseLong(name));
+        } else {
+            if (name.endsWith("!")) {
+                // exclusive this name only
+                name = name.substring(0, name.length() - 1);
+            } else if (!name.endsWith("*")) {
+                // lets be open and match all that starts with this pattern
+                name = name + "*";
+            }
+        }
+
+        final long cur = ProcessHandle.current().pid();
+        final String pattern = name;
+        ProcessHandle.allProcesses()
+                .filter(ph -> ph.pid() != cur)
+                .forEach(ph -> {
+                    JsonObject root = loadStatus(ph.pid());
+                    // there must be a status file for the running Camel 
integration
+                    if (root != null) {
+                        String pName = ProcessHelper.extractName(root, ph);
+                        // ignore file extension, so it is easier to match by 
name
+                        pName = FileUtil.onlyName(pName);
+                        if (pName != null && !pName.isEmpty() && 
PatternHelper.matchPattern(pName, pattern)) {
+                            pids.add(ph.pid());
+                        } else {
+                            // try camel context name
+                            JsonObject context = (JsonObject) 
root.get("context");
+                            if (context != null) {
+                                pName = context.getString("name");
+                                if ("CamelJBang".equals(pName)) {
+                                    pName = null;
+                                }
+                                if (pName != null && !pName.isEmpty() && 
PatternHelper.matchPattern(pName, pattern)) {
+                                    pids.add(ph.pid());
+                                }
+                            }
+                        }
+                    }
+                });
+
+        return pids;
+    }
+
+    JsonObject loadStatus(long pid) {
+        try {
+            Path f = getStatusFile(Long.toString(pid));
+            if (f != null && Files.exists(f)) {
+                String text = Files.readString(f);
+                return (JsonObject) Jsoner.deserialize(text);
+            }
+        } catch (Exception e) {
+            // ignore
+        }
+        return null;
+    }
+
 }
diff --git a/dsl/pom.xml b/dsl/pom.xml
index a3188d172db7..2089278bba19 100644
--- a/dsl/pom.xml
+++ b/dsl/pom.xml
@@ -42,6 +42,7 @@
         <module>camel-endpointdsl</module>
         <module>camel-componentdsl</module>
         <module>camel-cli-connector</module>
+        <module>camel-cli-debug</module>
         <module>camel-dsl-support</module>
         <module>camel-dsl-modeline</module>
         <module>camel-endpointdsl-support</module>
diff --git a/parent/pom.xml b/parent/pom.xml
index e672c43b33e9..896e389ce8ba 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -2912,6 +2912,11 @@
                 <artifactId>camel-cli-connector</artifactId>
                 <version>${project.version}</version>
             </dependency>
+            <dependency>
+                <groupId>org.apache.camel</groupId>
+                <artifactId>camel-cli-debug</artifactId>
+                <version>${project.version}</version>
+            </dependency>
             <dependency>
                 <groupId>org.apache.camel</groupId>
                 <artifactId>camel-componentdsl</artifactId>


Reply via email to