You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2026/06/08 07:40:35 UTC

(camel) branch main updated: CAMEL-23688: Add extensive test coverage for camel-jbang CLI commands

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 15fd9819fdfa CAMEL-23688: Add extensive test coverage for camel-jbang CLI commands
15fd9819fdfa is described below

commit 15fd9819fdfaba380a53837167e5abb7fcead560
Author: Adriano Machado <60...@users.noreply.github.com>
AuthorDate: Mon Jun 8 03:40:28 2026 -0400

    CAMEL-23688: Add extensive test coverage for camel-jbang CLI commands
    
    Add 24 new test classes covering camel-jbang-core CLI commands that
    previously had zero or minimal test coverage. Tests cover the
    commands/process/ package (ListProcess, CamelContextStatus,
    CamelRouteStatus, ListEndpoint, ListHealth, ListRest, ListConsumer,
    ListError, ListVariable, ListMetric, ListCircuitBreaker, ListBlocked,
    ListEvent, ListInflight, ListInternalTask, ListProducer, ListService,
    ListTransformer, ListProperties), commands/action/ package
    (CamelStartupRecorderAction, LoggerAction, MessageTableHelper,
    CamelSourceAction), and CamelCommandHelper. Uses Mockito mockStatic
    for ProcessHandle mocking with hand-crafted JSON status file fixtures.
    
    Closes #23812
---
 .../core/commands/CamelCommandHelperTest.java      |  65 ++++++
 .../commands/action/CamelSourceActionTest.java     |  60 +++++
 .../action/CamelStartupRecorderActionTest.java     | 195 ++++++++++++++++
 .../core/commands/action/LoggerActionTest.java     | 197 ++++++++++++++++
 .../commands/action/MessageTableHelperTest.java    | 116 ++++++++++
 .../commands/process/CamelContextStatusTest.java   | 147 ++++++++++++
 .../commands/process/CamelRouteStatusTest.java     | 255 +++++++++++++++++++++
 .../core/commands/process/ListBlockedTest.java     | 133 +++++++++++
 .../commands/process/ListCircuitBreakerTest.java   | 159 +++++++++++++
 .../core/commands/process/ListConsumerTest.java    | 139 +++++++++++
 .../core/commands/process/ListEndpointTest.java    | 160 +++++++++++++
 .../jbang/core/commands/process/ListErrorTest.java | 133 +++++++++++
 .../jbang/core/commands/process/ListEventTest.java | 165 +++++++++++++
 .../core/commands/process/ListHealthTest.java      | 174 ++++++++++++++
 .../core/commands/process/ListInflightTest.java    | 137 +++++++++++
 .../commands/process/ListInternalTaskTest.java     | 139 +++++++++++
 .../core/commands/process/ListMetricTest.java      | 132 +++++++++++
 .../core/commands/process/ListProcessTest.java     | 238 +++++++++++++++++++
 .../core/commands/process/ListProducerTest.java    | 194 ++++++++++++++++
 .../core/commands/process/ListPropertiesTest.java  | 173 ++++++++++++++
 .../jbang/core/commands/process/ListRestTest.java  | 135 +++++++++++
 .../core/commands/process/ListServiceTest.java     | 174 ++++++++++++++
 .../core/commands/process/ListTransformerTest.java | 132 +++++++++++
 .../core/commands/process/ListVariableTest.java    | 131 +++++++++++
 24 files changed, 3683 insertions(+)

diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/CamelCommandHelperTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/CamelCommandHelperTest.java
new file mode 100644
index 000000000000..0ce42940e136
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/CamelCommandHelperTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.dsl.jbang.core.commands;
+
+import org.apache.camel.dsl.jbang.core.common.CamelCommandHelper;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class CamelCommandHelperTest {
+
+    @Test
+    void testStartingState() {
+        // Phase <= 4 maps to Starting
+        assertEquals("Starting", CamelCommandHelper.extractState(0));
+        assertEquals("Starting", CamelCommandHelper.extractState(1));
+        assertEquals("Starting", CamelCommandHelper.extractState(4));
+    }
+
+    @Test
+    void testRunningState() {
+        assertEquals("Running", CamelCommandHelper.extractState(5));
+    }
+
+    @Test
+    void testSuspendingState() {
+        assertEquals("Suspending", CamelCommandHelper.extractState(6));
+    }
+
+    @Test
+    void testSuspendedState() {
+        assertEquals("Suspended", CamelCommandHelper.extractState(7));
+    }
+
+    @Test
+    void testTerminatingState() {
+        assertEquals("Terminating", CamelCommandHelper.extractState(8));
+    }
+
+    @Test
+    void testTerminatedState() {
+        assertEquals("Terminated", CamelCommandHelper.extractState(9));
+    }
+
+    @Test
+    void testUnknownPhaseFallsToTerminated() {
+        // Any phase beyond 9 defaults to Terminated — prevents NPE during display
+        assertEquals("Terminated", CamelCommandHelper.extractState(99));
+        assertEquals("Terminated", CamelCommandHelper.extractState(Integer.MAX_VALUE));
+    }
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelSourceActionTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelSourceActionTest.java
new file mode 100644
index 000000000000..cdfdbf13c262
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelSourceActionTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.dsl.jbang.core.commands.action;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+class CamelSourceActionTest {
+
+    @Test
+    void testNullReturnsNull() {
+        assertNull(CamelSourceAction.extractSourceName(null));
+    }
+
+    @Test
+    void testPlainFilenameUnchanged() {
+        assertEquals("MyRoute.yaml", CamelSourceAction.extractSourceName("MyRoute.yaml"));
+    }
+
+    @Test
+    void testSchemeAndPathStripsSchemeAndDirectory() {
+        // "file:/some/path/MyRoute.yaml" -> strip scheme -> "/some/path/MyRoute.yaml" -> strip path -> "MyRoute.yaml"
+        assertEquals("MyRoute.yaml", CamelSourceAction.extractSourceName("file:/some/path/MyRoute.yaml"));
+    }
+
+    @Test
+    void testClasspathSchemeStripsScheme() {
+        assertEquals("routes.xml", CamelSourceAction.extractSourceName("classpath:routes.xml"));
+    }
+
+    @Test
+    void testSingleColonTreatedAsScheme() {
+        // Only one colon: treated as scheme separator, not line number.
+        // "MyRoute.java:42" -> substring after ':' = "42" -> stripPath("42") = "42"
+        assertEquals("42", CamelSourceAction.extractSourceName("MyRoute.java:42"));
+    }
+
+    @Test
+    void testSchemeWithLineNumberStripsSchemeAndLineNumber() {
+        // Two colons: stripSourceLocationLineNumber removes ":10" -> "file:/path/MyRoute.yaml"
+        // then strip scheme and path -> "MyRoute.yaml"
+        assertEquals("MyRoute.yaml", CamelSourceAction.extractSourceName("file:/path/MyRoute.yaml:10"));
+    }
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelStartupRecorderActionTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelStartupRecorderActionTest.java
new file mode 100644
index 000000000000..3cbdb9fc3a05
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelStartupRecorderActionTest.java
@@ -0,0 +1,195 @@
+/*
+ * 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.dsl.jbang.core.commands.action;
+
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelCommandBaseTestSupport;
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.common.CommandLineHelper;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class CamelStartupRecorderActionTest extends CamelCommandBaseTestSupport {
+
+    @BeforeEach
+    @Override
+    public void setup() throws Exception {
+        super.setup();
+        CommandLineHelper.useHomeDir("target");
+    }
+
+    @Test
+    void testDisplaysStepsFromOutput() throws Exception {
+        JsonObject mockOutput = buildStartupOutput(
+                step(1, 0, 0, "MyRoute", "Route", "Build route", 150),
+                step(2, 1, 1, null, "From", "timer:tick", 50),
+                step(3, 1, 1, null, "To", "log:info", 30));
+
+        TestableStartupRecorder command = new TestableStartupRecorder(
+                new CamelJBangMain().withPrinter(printer), mockOutput);
+
+        int exit = command.doWatchCall();
+
+        assertEquals(0, exit);
+        String output = printer.getOutput();
+        assertTrue(output.contains("Route"), "Should show step type Route");
+        assertTrue(output.contains("From"), "Should show step type From");
+        assertTrue(output.contains("Build route"), "Should show step description");
+    }
+
+    @Test
+    void testStepWithNameShowsParentheses() throws Exception {
+        JsonObject mockOutput = buildStartupOutput(
+                step(1, 0, 0, "myRoute", "Route", "Configure route", 100));
+
+        TestableStartupRecorder command = new TestableStartupRecorder(
+                new CamelJBangMain().withPrinter(printer), mockOutput);
+
+        int exit = command.doWatchCall();
+
+        assertEquals(0, exit);
+        // When a step has a name, it appears as "description(name)"
+        assertTrue(printer.getOutput().contains("Configure route(myRoute)"),
+                "Name should be appended in parentheses");
+    }
+
+    @Test
+    void testSortByDuration() throws Exception {
+        JsonObject mockOutput = buildStartupOutput(
+                step(1, 0, 0, null, "Route", "FastStep", 10),
+                step(2, 0, 0, null, "Route", "SlowStep", 500),
+                step(3, 0, 0, null, "Route", "MidStep", 200));
+
+        TestableStartupRecorder command = new TestableStartupRecorder(
+                new CamelJBangMain().withPrinter(printer), mockOutput);
+        command.sort = "duration";
+
+        int exit = command.doWatchCall();
+
+        assertEquals(0, exit);
+        String output = printer.getOutput();
+        int fastPos = output.indexOf("FastStep");
+        int midPos = output.indexOf("MidStep");
+        int slowPos = output.indexOf("SlowStep");
+        // Ascending duration: FastStep (10ms) before MidStep (200ms) before SlowStep (500ms)
+        assertTrue(fastPos < midPos && midPos < slowPos,
+                "Sort by duration ascending: fastest step must appear first");
+    }
+
+    @Test
+    void testSortByDurationDescending() throws Exception {
+        JsonObject mockOutput = buildStartupOutput(
+                step(1, 0, 0, null, "Route", "FastStep", 10),
+                step(2, 0, 0, null, "Route", "SlowStep", 500));
+
+        TestableStartupRecorder command = new TestableStartupRecorder(
+                new CamelJBangMain().withPrinter(printer), mockOutput);
+        command.sort = "-duration";
+
+        int exit = command.doWatchCall();
+
+        assertEquals(0, exit);
+        String output = printer.getOutput();
+        int slowPos = output.indexOf("SlowStep");
+        int fastPos = output.indexOf("FastStep");
+        assertTrue(slowPos < fastPos, "Descending duration: slowest step must appear first");
+    }
+
+    @Test
+    void testZeroDurationDisplayedAsEmpty() throws Exception {
+        JsonObject mockOutput = buildStartupOutput(
+                step(1, 0, 0, null, "Route", "ZeroDurationStep", 0));
+
+        TestableStartupRecorder command = new TestableStartupRecorder(
+                new CamelJBangMain().withPrinter(printer), mockOutput);
+
+        int exit = command.doWatchCall();
+
+        assertEquals(0, exit);
+        // Zero duration is rendered as empty string, not "0" — verify step name still appears
+        assertTrue(printer.getOutput().contains("ZeroDurationStep"));
+    }
+
+    @Test
+    void testEmptyOutputWhenNoPidFound() throws Exception {
+        // No pids returned — command should return error code 1
+        TestableStartupRecorder command = new TestableStartupRecorder(
+                new CamelJBangMain().withPrinter(printer), null) {
+            @Override
+            List<Long> findPids(String name) {
+                return List.of();
+            }
+        };
+
+        int exit = command.doWatchCall();
+
+        assertEquals(1, exit, "Should return error when no pids found");
+    }
+
+    private static JsonObject buildStartupOutput(JsonObject... steps) {
+        JsonArray arr = new JsonArray();
+        Collections.addAll(arr, steps);
+        JsonObject jo = new JsonObject();
+        jo.put("steps", arr);
+        return jo;
+    }
+
+    private static JsonObject step(
+            int id, int parentId, int level, String name, String type, String description,
+            long duration) {
+        JsonObject step = new JsonObject();
+        step.put("id", id);
+        step.put("parentId", parentId);
+        step.put("level", level);
+        step.put("name", name);
+        step.put("type", type);
+        step.put("description", description);
+        step.put("duration", duration);
+        return step;
+    }
+
+    /**
+     * Test subclass that bypasses process discovery and output file waiting so the rendering logic can be tested
+     * against a pre-built JSON response.
+     */
+    private static class TestableStartupRecorder extends CamelStartupRecorderAction {
+        private final JsonObject mockOutput;
+
+        TestableStartupRecorder(CamelJBangMain main, JsonObject mockOutput) {
+            super(main);
+            this.mockOutput = mockOutput;
+        }
+
+        @Override
+        List<Long> findPids(String name) {
+            return List.of(12345L);
+        }
+
+        @Override
+        protected JsonObject waitForOutputFile(Path outputFile) {
+            return mockOutput;
+        }
+    }
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/LoggerActionTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/LoggerActionTest.java
new file mode 100644
index 000000000000..8fedd383260c
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/LoggerActionTest.java
@@ -0,0 +1,197 @@
+/*
+ * 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.dsl.jbang.core.commands.action;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.time.Instant;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelCommandBaseTestSupport;
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.common.CommandLineHelper;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.MockedStatic;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class LoggerActionTest extends CamelCommandBaseTestSupport {
+
+    private static final long TEST_PID = 12345L;
+    private static final long CURRENT_PID = 99999L;
+
+    @BeforeEach
+    @Override
+    public void setup() throws Exception {
+        super.setup();
+        CommandLineHelper.useHomeDir("target/test-logger");
+        Files.createDirectories(CommandLineHelper.getCamelDir());
+    }
+
+    @AfterEach
+    void cleanup() throws IOException {
+        Path camelDir = CommandLineHelper.getCamelDir();
+        if (Files.exists(camelDir)) {
+            try (var entries = Files.list(camelDir)) {
+                for (Path p : entries.toList()) {
+                    Files.deleteIfExists(p);
+                }
+            }
+            Files.deleteIfExists(camelDir);
+        }
+    }
+
+    @Test
+    void testListShowsLoggers() throws Exception {
+        JsonObject levels = new JsonObject();
+        levels.put("org.apache.camel", "INFO");
+        levels.put("com.example", "DEBUG");
+
+        JsonObject logger = new JsonObject();
+        logger.put("levels", levels);
+
+        JsonObject context = new JsonObject();
+        context.put("name", "myApp");
+
+        JsonObject root = new JsonObject();
+        root.put("context", context);
+        root.put("logger", logger);
+        writeStatusFile(TEST_PID, root);
+
+        LoggerAction command = new LoggerAction(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockHandle(TEST_PID);
+            ProcessHandle current = mockHandle(CURRENT_PID);
+            mocked.when(ProcessHandle::current).thenReturn(current);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.contains("org.apache.camel"), "Should show logger name");
+            assertTrue(output.contains("INFO"), "Should show logger level");
+            assertTrue(output.contains("com.example"), "Should list all loggers");
+            assertTrue(output.contains("DEBUG"), "Should show DEBUG level");
+        }
+    }
+
+    @Test
+    void testEmptyOutputWhenNoLoggers() throws Exception {
+        JsonObject context = new JsonObject();
+        context.put("name", "myApp");
+
+        JsonObject root = new JsonObject();
+        root.put("context", context);
+        // No logger section
+        writeStatusFile(TEST_PID, root);
+
+        LoggerAction command = new LoggerAction(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockHandle(TEST_PID);
+            ProcessHandle current = mockHandle(CURRENT_PID);
+            mocked.when(ProcessHandle::current).thenReturn(current);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            assertEquals("", printer.getOutput().trim());
+        }
+    }
+
+    @Test
+    void testSortByName() throws Exception {
+        // "sort=name" sorts rows by context/process name; use two processes with different names
+        long pidZzz = TEST_PID;
+        long pidAaa = TEST_PID + 1;
+
+        JsonObject levels = new JsonObject();
+        levels.put("root", "INFO");
+
+        JsonObject logger = new JsonObject();
+        logger.put("levels", levels);
+
+        JsonObject rootZzz = new JsonObject();
+        rootZzz.put("context", contextJson("zzzApp"));
+        rootZzz.put("logger", logger);
+        writeStatusFile(pidZzz, rootZzz);
+
+        JsonObject rootAaa = new JsonObject();
+        rootAaa.put("context", contextJson("aaaApp"));
+        rootAaa.put("logger", logger);
+        writeStatusFile(pidAaa, rootAaa);
+
+        LoggerAction command = new LoggerAction(new CamelJBangMain().withPrinter(printer));
+        command.sort = "name";
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle phZzz = mockHandle(pidZzz);
+            ProcessHandle phAaa = mockHandle(pidAaa);
+            ProcessHandle current = mockHandle(CURRENT_PID);
+            mocked.when(ProcessHandle::current).thenReturn(current);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(phZzz, phAaa));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            int aaaPos = output.indexOf("aaaApp");
+            int zzzPos = output.indexOf("zzzApp");
+            assertTrue(aaaPos < zzzPos, "Sort by name ascending: aaaApp must appear before zzzApp");
+        }
+    }
+
+    private static JsonObject contextJson(String name) {
+        JsonObject ctx = new JsonObject();
+        ctx.put("name", name);
+        return ctx;
+    }
+
+    private static ProcessHandle mockHandle(long pid) {
+        ProcessHandle ph = mock(ProcessHandle.class);
+        ProcessHandle.Info info = mock(ProcessHandle.Info.class);
+        when(ph.pid()).thenReturn(pid);
+        // info/commandLine/startInstant are only used for process handles, not for the current handle
+        lenient().when(ph.info()).thenReturn(info);
+        lenient().when(info.commandLine()).thenReturn(Optional.empty());
+        lenient().when(info.startInstant()).thenReturn(Optional.of(Instant.now().minusSeconds(60)));
+        return ph;
+    }
+
+    private static void writeStatusFile(long pid, JsonObject root) throws Exception {
+        Path f = CommandLineHelper.getCamelDir().resolve(pid + "-status.json");
+        Files.writeString(f, root.toJson());
+    }
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/MessageTableHelperTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/MessageTableHelperTest.java
new file mode 100644
index 000000000000..9e41b1ae9f94
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/MessageTableHelperTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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.dsl.jbang.core.commands.action;
+
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class MessageTableHelperTest {
+
+    @Test
+    void testBodyAppearsInOutput() {
+        MessageTableHelper helper = new MessageTableHelper();
+
+        JsonObject root = new JsonObject();
+        root.put("exchangeType", "DefaultExchange");
+        root.put("messageType", "DefaultMessage");
+
+        JsonObject body = new JsonObject();
+        body.put("type", "String");
+        body.put("value", "Hello World");
+        root.put("body", body);
+
+        String result = helper.getDataAsTable("exch-1", "InOnly", null, null, null, root, null);
+
+        assertNotNull(result, "Result should not be null");
+        assertTrue(result.contains("Hello World"), "Output should contain the body value");
+    }
+
+    @Test
+    void testHeadersAppearsWhenEnabled() {
+        MessageTableHelper helper = new MessageTableHelper();
+        helper.setShowHeaders(true);
+
+        JsonObject root = new JsonObject();
+        root.put("exchangeType", "DefaultExchange");
+
+        JsonArray headers = new JsonArray();
+        JsonObject header = new JsonObject();
+        header.put("key", "Content-Type");
+        header.put("type", "String");
+        header.put("value", "application/json");
+        headers.add(header);
+        root.put("headers", headers);
+
+        String result = helper.getDataAsTable("exch-2", "InOnly", null, null, null, root, null);
+
+        assertNotNull(result);
+        assertTrue(result.contains("Content-Type"), "Output should contain header name when showHeaders=true");
+        assertTrue(result.contains("application/json"), "Output should contain header value");
+    }
+
+    @Test
+    void testHeadersHiddenWhenDisabled() {
+        MessageTableHelper helper = new MessageTableHelper();
+        helper.setShowHeaders(false);
+
+        JsonObject root = new JsonObject();
+        root.put("exchangeType", "DefaultExchange");
+
+        JsonArray headers = new JsonArray();
+        JsonObject header = new JsonObject();
+        header.put("key", "X-Custom-Header");
+        header.put("type", "String");
+        header.put("value", "hidden-value");
+        headers.add(header);
+        root.put("headers", headers);
+
+        String result = helper.getDataAsTable("exch-3", "InOnly", null, null, null, root, null);
+
+        assertNotNull(result);
+        assertFalse(result.contains("X-Custom-Header"), "Headers should not appear when showHeaders=false");
+
+    }
+
+    @Test
+    void testNullRootReturnsEmptyTable() {
+        MessageTableHelper helper = new MessageTableHelper();
+
+        String result = helper.getDataAsTable("exch-4", "InOnly", null, null, null, null, null);
+
+        assertNotNull(result, "Result should not be null even for null root");
+        assertTrue(result.isBlank(), "Should return empty output when root is null");
+    }
+
+    @Test
+    void testExchangeIdAppearsInOutput() {
+        MessageTableHelper helper = new MessageTableHelper();
+
+        JsonObject root = new JsonObject();
+        root.put("exchangeType", "DefaultExchange");
+
+        String result = helper.getDataAsTable("my-exchange-id-12345", "InOut", null, null, null, root, null);
+
+        assertNotNull(result);
+        assertTrue(result.contains("my-exchange-id-12345"), "Exchange ID should appear in output");
+    }
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelContextStatusTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelContextStatusTest.java
new file mode 100644
index 000000000000..801cb0077482
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelContextStatusTest.java
@@ -0,0 +1,147 @@
+/*
+ * 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.dsl.jbang.core.commands.process;
+
+import java.util.stream.Stream;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.MockedStatic;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mockStatic;
+
+@ExtendWith(MockitoExtension.class)
+class CamelContextStatusTest extends ProcessCommandTestSupport {
+
+    @Test
+    void testDisplaysRunningContext() throws Exception {
+        JsonObject status = buildContextStatus("myApp", 5);
+        writeStatusFile(TEST_PID, status);
+
+        CamelContextStatus command = new CamelContextStatus(new CamelJBangMain().withPrinter(printer));
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.contains("myApp"), "Should display context name");
+            assertTrue(output.contains("Running"), "Phase 5 should show Running status");
+        }
+    }
+
+    @Test
+    void testDisplaysSuspendedContext() throws Exception {
+        JsonObject status = buildContextStatus("suspendedApp", 7);
+        writeStatusFile(TEST_PID, status);
+
+        CamelContextStatus command = new CamelContextStatus(new CamelJBangMain().withPrinter(printer));
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            assertTrue(printer.getOutput().contains("Suspended"), "Phase 7 should show Suspended");
+        }
+    }
+
+    @Test
+    void testRouteCountsInOutput() throws Exception {
+        JsonObject status = buildContextStatus("routeCountApp", 5);
+        // Add two routes: one started, one suspended
+        JsonArray routes = new JsonArray();
+        JsonObject r1 = new JsonObject();
+        r1.put("state", "Started");
+        JsonObject r2 = new JsonObject();
+        r2.put("state", "Suspended");
+        routes.add(r1);
+        routes.add(r2);
+        status.put("routes", routes);
+        writeStatusFile(TEST_PID, status);
+
+        CamelContextStatus command = new CamelContextStatus(new CamelJBangMain().withPrinter(printer));
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            // The context status shows started/total routes — 1 started out of 2 total
+            String output = printer.getOutput();
+            assertTrue(output.contains("1/2"), "Should show 1 started out of 2 total routes");
+        }
+    }
+
+    @Test
+    void testEmptyOutputWhenNoProcesses() throws Exception {
+        CamelContextStatus command = new CamelContextStatus(new CamelJBangMain().withPrinter(printer));
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.empty());
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            assertEquals("", printer.getOutput().trim());
+        }
+    }
+
+    @Test
+    void testJsonOutput() throws Exception {
+        JsonObject status = buildContextStatus("jsonApp", 5);
+        writeStatusFile(TEST_PID, status);
+
+        CamelContextStatus command = new CamelContextStatus(new CamelJBangMain().withPrinter(printer));
+        command.jsonOutput = true;
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.startsWith("["), "JSON output should be a JSON array");
+            assertTrue(output.contains("jsonApp"), "JSON should contain the context name");
+        }
+    }
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteStatusTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteStatusTest.java
new file mode 100644
index 000000000000..68261a1f36ce
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteStatusTest.java
@@ -0,0 +1,255 @@
+/*
+ * 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.dsl.jbang.core.commands.process;
+
+import java.util.stream.Stream;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.MockedStatic;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mockStatic;
+
+@ExtendWith(MockitoExtension.class)
+class CamelRouteStatusTest extends ProcessCommandTestSupport {
+
+    @Test
+    void testEmptyOutputWhenNoProcesses() throws Exception {
+        CamelRouteStatus command = new CamelRouteStatus(new CamelJBangMain().withPrinter(printer));
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.empty());
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            assertEquals("", printer.getOutput().trim());
+        }
+    }
+
+    @Test
+    void testDisplaysSingleRoute() throws Exception {
+        JsonObject status = buildRouteStatus("route1", "Started");
+        writeStatusFile(TEST_PID, status);
+
+        CamelRouteStatus command = new CamelRouteStatus(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.contains("route1"), "Should show route ID");
+            assertTrue(output.contains("timer:tick"), "Should show from URI");
+            assertTrue(output.contains("Started"), "Should show route state");
+        }
+    }
+
+    @Test
+    void testDisplaysMultipleRoutes() throws Exception {
+        JsonObject routeStats = buildRouteStats();
+        JsonObject route1 = buildRoute("route1", "timer:tick1", routeStats);
+        JsonObject route2 = buildRoute("route2", "timer:tick2", routeStats);
+
+        JsonArray routes = new JsonArray();
+        routes.add(route1);
+        routes.add(route2);
+
+        JsonObject context = new JsonObject();
+        context.put("name", "multiRouteApp");
+        context.put("phase", 5);
+
+        JsonObject root = new JsonObject();
+        root.put("context", context);
+        root.put("routes", routes);
+        writeStatusFile(TEST_PID, root);
+
+        CamelRouteStatus command = new CamelRouteStatus(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.contains("route1"));
+            assertTrue(output.contains("route2"));
+        }
+    }
+
+    @Test
+    void testSuspendedRouteShownCorrectly() throws Exception {
+        JsonObject status = buildRouteStatus("suspendedRoute", "Suspended");
+        writeStatusFile(TEST_PID, status);
+
+        CamelRouteStatus command = new CamelRouteStatus(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            assertTrue(printer.getOutput().contains("Suspended"));
+        }
+    }
+
+    @Test
+    void testJsonOutput() throws Exception {
+        JsonObject status = buildRouteStatus("route1", "Started");
+        writeStatusFile(TEST_PID, status);
+
+        CamelRouteStatus command = new CamelRouteStatus(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+        command.jsonOutput = true;
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.startsWith("["), "JSON output should be array");
+            assertTrue(output.contains("route1"));
+        }
+    }
+
+    @Test
+    void testFilterByRouteId() throws Exception {
+        JsonObject routeStats = buildRouteStats();
+        JsonObject route1 = buildRoute("matchingRoute", "timer:tick1", routeStats);
+        JsonObject route2 = buildRoute("otherRoute", "timer:tick2", routeStats);
+
+        JsonArray routes = new JsonArray();
+        routes.add(route1);
+        routes.add(route2);
+
+        JsonObject context = new JsonObject();
+        context.put("name", "myApp");
+        context.put("phase", 5);
+
+        JsonObject root = new JsonObject();
+        root.put("context", context);
+        root.put("routes", routes);
+        writeStatusFile(TEST_PID, root);
+
+        CamelRouteStatus command = new CamelRouteStatus(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+        command.filter = new String[] { "matchingRoute" };
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.contains("matchingRoute"), "Filtered route should appear");
+            assertFalse(output.contains("otherRoute"), "Non-matching route should be hidden by filter");
+        }
+    }
+
+    @Test
+    void testLimitReducesRows() throws Exception {
+        JsonObject routeStats = buildRouteStats();
+        JsonArray routes = new JsonArray();
+        for (int i = 1; i <= 5; i++) {
+            routes.add(buildRoute("route" + i, "timer:tick" + i, routeStats));
+        }
+
+        JsonObject context = new JsonObject();
+        context.put("name", "bigApp");
+        context.put("phase", 5);
+
+        JsonObject root = new JsonObject();
+        root.put("context", context);
+        root.put("routes", routes);
+        writeStatusFile(TEST_PID, root);
+
+        CamelRouteStatus command = new CamelRouteStatus(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+        command.limit = 2;
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            // Only 2 routes should appear — limit is enforced during row collection
+            assertTrue(output.contains("route1"));
+            assertTrue(output.contains("route2"));
+            assertFalse(output.contains("route5"), "route5 should be excluded by limit=2");
+        }
+    }
+
+    private static JsonObject buildRouteStats() {
+        JsonObject stats = new JsonObject();
+        stats.put("exchangesTotal", "0");
+        stats.put("exchangesFailed", "0");
+        stats.put("exchangesInflight", "0");
+        stats.put("meanProcessingTime", "-1");
+        stats.put("maxProcessingTime", "0");
+        stats.put("minProcessingTime", "0");
+        return stats;
+    }
+
+    private static JsonObject buildRoute(String routeId, String fromUri, JsonObject stats) {
+        JsonObject route = new JsonObject();
+        route.put("routeId", routeId);
+        route.put("from", fromUri);
+        route.put("state", "Started");
+        route.put("uptime", "1m");
+        route.put("statistics", stats);
+        return route;
+    }
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListBlockedTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListBlockedTest.java
new file mode 100644
index 000000000000..25f247ca37e7
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListBlockedTest.java
@@ -0,0 +1,133 @@
+/*
+ * 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.dsl.jbang.core.commands.process;
+
+import java.util.Collections;
+import java.util.stream.Stream;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.MockedStatic;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mockStatic;
+
+@ExtendWith(MockitoExtension.class)
+class ListBlockedTest extends ProcessCommandTestSupport {
+
+    @Test
+    void testEmptyOutputWhenNoBlockedExchanges() throws Exception {
+        JsonObject root = new JsonObject();
+        root.put("context", contextObj());
+        writeStatusFile(TEST_PID, root);
+
+        ListBlocked command = new ListBlocked(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            assertEquals("", printer.getOutput().trim());
+        }
+    }
+
+    @Test
+    void testShowsBlockedExchange() throws Exception {
+        JsonObject root = new JsonObject();
+        root.put("context", contextObj());
+        root.put("blocked", exchangeContainer(blockedExchange("exchange-1", "route1", "to1")));
+        writeStatusFile(TEST_PID, root);
+
+        ListBlocked command = new ListBlocked(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.contains("exchange-1"), "Should show blocked exchange id");
+            assertTrue(output.contains("route1"), "Should show route id");
+            assertTrue(output.contains("to1"), "Should show node id");
+        }
+    }
+
+    @Test
+    void testJsonOutput() throws Exception {
+        JsonObject root = new JsonObject();
+        root.put("context", contextObj());
+        root.put("blocked", exchangeContainer(blockedExchange("exchange-2", "route2", "bean1")));
+        writeStatusFile(TEST_PID, root);
+
+        ListBlocked command = new ListBlocked(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+        command.jsonOutput = true;
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.startsWith("["), "JSON output should be array");
+            assertTrue(output.contains("exchange-2"));
+        }
+    }
+
+    private static JsonObject contextObj() {
+        JsonObject ctx = new JsonObject();
+        ctx.put("name", "myApp");
+        return ctx;
+    }
+
+    private static JsonObject exchangeContainer(JsonObject... exchanges) {
+        JsonArray arr = new JsonArray();
+        Collections.addAll(arr, exchanges);
+        JsonObject container = new JsonObject();
+        container.put("exchanges", arr);
+        return container;
+    }
+
+    private static JsonObject blockedExchange(String exchangeId, String routeId, String nodeId) {
+        JsonObject e = new JsonObject();
+        e.put("exchangeId", exchangeId);
+        e.put("routeId", routeId);
+        e.put("nodeId", nodeId);
+        e.put("duration", 1500L);
+        return e;
+    }
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListCircuitBreakerTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListCircuitBreakerTest.java
new file mode 100644
index 000000000000..32c73e1dc511
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListCircuitBreakerTest.java
@@ -0,0 +1,159 @@
+/*
+ * 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.dsl.jbang.core.commands.process;
+
+import java.util.Collections;
+import java.util.stream.Stream;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.MockedStatic;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mockStatic;
+
+@ExtendWith(MockitoExtension.class)
+class ListCircuitBreakerTest extends ProcessCommandTestSupport {
+
+    @Test
+    void testEmptyOutputWhenNoCircuitBreakers() throws Exception {
+        JsonObject root = new JsonObject();
+        root.put("context", contextObj());
+        writeStatusFile(TEST_PID, root);
+
+        ListCircuitBreaker command = new ListCircuitBreaker(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            assertEquals("", printer.getOutput().trim());
+        }
+    }
+
+    @Test
+    void testShowsClosedCircuitBreaker() throws Exception {
+        JsonObject root = new JsonObject();
+        root.put("context", contextObj());
+        root.put("resilience4j", cbContainer(cbEntry("CLOSED")));
+        writeStatusFile(TEST_PID, root);
+
+        ListCircuitBreaker command = new ListCircuitBreaker(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.contains("myCB"), "Should show circuit breaker ID");
+            assertTrue(output.contains("CLOSED"), "Should show CLOSED state");
+        }
+    }
+
+    @Test
+    void testShowsOpenCircuitBreaker() throws Exception {
+        JsonObject root = new JsonObject();
+        root.put("context", contextObj());
+        root.put("resilience4j", cbContainer(cbEntry("OPEN")));
+        writeStatusFile(TEST_PID, root);
+
+        ListCircuitBreaker command = new ListCircuitBreaker(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            assertTrue(printer.getOutput().contains("OPEN"), "Should show OPEN state");
+        }
+    }
+
+    @Test
+    void testJsonOutput() throws Exception {
+        JsonObject root = new JsonObject();
+        root.put("context", contextObj());
+        root.put("resilience4j", cbContainer(cbEntry("CLOSED")));
+        writeStatusFile(TEST_PID, root);
+
+        ListCircuitBreaker command = new ListCircuitBreaker(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+        command.jsonOutput = true;
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.startsWith("["), "JSON output should be array");
+            assertTrue(output.contains("myCB"));
+        }
+    }
+
+    private static JsonObject contextObj() {
+        JsonObject ctx = new JsonObject();
+        ctx.put("name", "myApp");
+        return ctx;
+    }
+
+    private static JsonObject cbContainer(JsonObject... cbs) {
+        JsonArray arr = new JsonArray();
+        Collections.addAll(arr, cbs);
+        JsonObject container = new JsonObject();
+        container.put("circuitBreakers", arr);
+        return container;
+    }
+
+    private static JsonObject cbEntry(String state) {
+        JsonObject cb = new JsonObject();
+        cb.put("id", "myCB");
+        cb.put("routeId", "myRoute");
+        cb.put("state", state);
+        cb.put("bufferedCalls", 0);
+        cb.put("successfulCalls", 0);
+        cb.put("failedCalls", 0);
+        cb.put("notPermittedCalls", 0L);
+        cb.put("failureRate", 0.0);
+        return cb;
+    }
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListConsumerTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListConsumerTest.java
new file mode 100644
index 000000000000..d31e51f557bd
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListConsumerTest.java
@@ -0,0 +1,139 @@
+/*
+ * 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.dsl.jbang.core.commands.process;
+
+import java.util.Collections;
+import java.util.stream.Stream;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.MockedStatic;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mockStatic;
+
+@ExtendWith(MockitoExtension.class)
+class ListConsumerTest extends ProcessCommandTestSupport {
+
+    @Test
+    void testEmptyOutputWhenNoConsumers() throws Exception {
+        JsonObject root = new JsonObject();
+        root.put("context", contextObj());
+        writeStatusFile(TEST_PID, root);
+
+        ListConsumer command = new ListConsumer(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            assertEquals("", printer.getOutput().trim());
+        }
+    }
+
+    @Test
+    void testShowsConsumerUriAndState() throws Exception {
+        JsonObject root = new JsonObject();
+        root.put("context", contextObj());
+        root.put("consumers", consumerContainer(consumer()));
+        writeStatusFile(TEST_PID, root);
+
+        ListConsumer command = new ListConsumer(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.contains("timer:tick"), "Should show consumer URI");
+            assertTrue(output.contains("Started"), "Should show consumer state");
+        }
+    }
+
+    @Test
+    void testJsonOutput() throws Exception {
+        JsonObject root = new JsonObject();
+        root.put("context", contextObj());
+        root.put("consumers", consumerContainer(consumer()));
+        writeStatusFile(TEST_PID, root);
+
+        ListConsumer command = new ListConsumer(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+        command.jsonOutput = true;
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.startsWith("["), "JSON output should be array");
+            assertTrue(output.contains("timer:tick"));
+        }
+    }
+
+    private static JsonObject contextObj() {
+        JsonObject ctx = new JsonObject();
+        ctx.put("name", "myApp");
+        return ctx;
+    }
+
+    private static JsonObject consumerContainer(JsonObject... consumers) {
+        JsonArray arr = new JsonArray();
+        Collections.addAll(arr, consumers);
+        JsonObject container = new JsonObject();
+        container.put("consumers", arr);
+        return container;
+    }
+
+    private static JsonObject consumer() {
+        JsonObject c = new JsonObject();
+        c.put("id", "route1");
+        c.put("uri", "timer:tick");
+        c.put("state", "Started");
+        c.put("class", "org.apache.camel.component.timer.TimerConsumer");
+        c.put("inflight", 0);
+        c.put("polling", false);
+        c.put("scheduled", false);
+        c.put("hostedService", false);
+        c.put("totalCounter", 0L);
+        c.put("delay", 0L);
+        c.put("period", 1000L);
+        return c;
+    }
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListEndpointTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListEndpointTest.java
new file mode 100644
index 000000000000..e2605a1340f3
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListEndpointTest.java
@@ -0,0 +1,160 @@
+/*
+ * 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.dsl.jbang.core.commands.process;
+
+import java.util.Collections;
+import java.util.stream.Stream;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.MockedStatic;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mockStatic;
+
+@ExtendWith(MockitoExtension.class)
+class ListEndpointTest extends ProcessCommandTestSupport {
+
+    @Test
+    void testDisplaysEndpoints() throws Exception {
+        JsonObject root = buildEndpointStatus("myApp",
+                endpoint("timer:tick", "in", "10"),
+                endpoint("log:info", "out", "10"));
+        writeStatusFile(TEST_PID, root);
+
+        ListEndpoint command = new ListEndpoint(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.contains("timer:tick"), "Should show consumer endpoint");
+            assertTrue(output.contains("log:info"), "Should show producer endpoint");
+        }
+    }
+
+    @Test
+    void testEmptyOutputWhenNoEndpoints() throws Exception {
+        JsonObject root = buildEndpointStatus("myApp");
+        writeStatusFile(TEST_PID, root);
+
+        ListEndpoint command = new ListEndpoint(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            assertEquals("", printer.getOutput().trim());
+        }
+    }
+
+    @Test
+    void testFilterByDirection() throws Exception {
+        JsonObject root = buildEndpointStatus("myApp",
+                endpoint("timer:tick", "in", "5"),
+                endpoint("kafka:topic", "out", "5"));
+        writeStatusFile(TEST_PID, root);
+
+        ListEndpoint command = new ListEndpoint(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+        command.filterDirection = "in";
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.contains("timer:tick"), "Consumer endpoint should be shown for direction=in");
+            assertFalse(output.contains("kafka:topic"), "Producer endpoint should be hidden for direction=in");
+        }
+    }
+
+    @Test
+    void testJsonOutput() throws Exception {
+        JsonObject root = buildEndpointStatus("myApp",
+                endpoint("timer:tick", "in", "1"));
+        writeStatusFile(TEST_PID, root);
+
+        ListEndpoint command = new ListEndpoint(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+        command.jsonOutput = true;
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.startsWith("["), "JSON output should be an array");
+            assertTrue(output.contains("timer:tick"));
+        }
+    }
+
+    private static JsonObject buildEndpointStatus(String contextName, JsonObject... endpoints) {
+        JsonArray epArr = new JsonArray();
+        Collections.addAll(epArr, endpoints);
+
+        JsonObject epContainer = new JsonObject();
+        epContainer.put("endpoints", epArr);
+
+        JsonObject context = new JsonObject();
+        context.put("name", contextName);
+
+        JsonObject root = new JsonObject();
+        root.put("context", context);
+        root.put("endpoints", epContainer);
+        return root;
+    }
+
+    private static JsonObject endpoint(String uri, String direction, String hits) {
+        JsonObject ep = new JsonObject();
+        ep.put("uri", uri);
+        ep.put("direction", direction);
+        ep.put("hits", hits);
+        ep.put("stub", false);
+        ep.put("remote", true);
+        return ep;
+    }
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListErrorTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListErrorTest.java
new file mode 100644
index 000000000000..c17661f43cb5
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListErrorTest.java
@@ -0,0 +1,133 @@
+/*
+ * 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.dsl.jbang.core.commands.process;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.stream.Stream;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.common.CommandLineHelper;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.MockedStatic;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mockStatic;
+
+@ExtendWith(MockitoExtension.class)
+class ListErrorTest extends ProcessCommandTestSupport {
+
+    @Test
+    void testEmptyOutputWhenNoErrors() throws Exception {
+        JsonObject status = buildContextStatus("myApp", 5);
+        writeStatusFile(TEST_PID, status);
+        // no error file written
+
+        ListError command = new ListError(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            assertEquals("", printer.getOutput().trim());
+        }
+    }
+
+    @Test
+    void testShowsErrorRouteId() throws Exception {
+        JsonObject status = buildContextStatus("myApp", 5);
+        writeStatusFile(TEST_PID, status);
+        writeErrorFile(errorEntry());
+
+        ListError command = new ListError(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.contains("myRoute"), "Should show routeId");
+        }
+    }
+
+    @Test
+    void testJsonOutput() throws Exception {
+        JsonObject status = buildContextStatus("myApp", 5);
+        writeStatusFile(TEST_PID, status);
+        writeErrorFile(errorEntry());
+
+        ListError command = new ListError(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+        command.jsonOutput = true;
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.startsWith("["), "JSON output should be array");
+            assertTrue(output.contains("myRoute"));
+        }
+    }
+
+    private static void writeErrorFile(JsonObject... errors) throws Exception {
+        JsonArray arr = new JsonArray();
+        Collections.addAll(arr, errors);
+        JsonObject root = new JsonObject();
+        root.put("errors", arr);
+        Path f = CommandLineHelper.getCamelDir().resolve(ProcessCommandTestSupport.TEST_PID + "-error.json");
+        Files.writeString(f, root.toJson());
+    }
+
+    private static JsonObject errorEntry() {
+        JsonObject ex = new JsonObject();
+        ex.put("type", "java.lang.RuntimeException");
+        ex.put("message", "Test error");
+
+        JsonObject e = new JsonObject();
+        e.put("routeId", "myRoute");
+        e.put("nodeId", "myNode");
+        e.put("exchangeId", "abc-123");
+        e.put("handled", false);
+        e.put("timestamp", System.currentTimeMillis());
+        e.put("exception", ex);
+        return e;
+    }
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListEventTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListEventTest.java
new file mode 100644
index 000000000000..16ceaaac64e7
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListEventTest.java
@@ -0,0 +1,165 @@
+/*
+ * 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.dsl.jbang.core.commands.process;
+
+import java.util.Collections;
+import java.util.stream.Stream;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.MockedStatic;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mockStatic;
+
+@ExtendWith(MockitoExtension.class)
+class ListEventTest extends ProcessCommandTestSupport {
+
+    @Test
+    void testEmptyOutputWhenNoEvents() throws Exception {
+        JsonObject root = new JsonObject();
+        root.put("context", contextObj());
+        writeStatusFile(TEST_PID, root);
+
+        ListEvent command = new ListEvent(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            assertEquals("", printer.getOutput().trim());
+        }
+    }
+
+    @Test
+    void testShowsAllEventGroups() throws Exception {
+        JsonObject root = buildEventStatus();
+        writeStatusFile(TEST_PID, root);
+
+        ListEvent command = new ListEvent(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.contains("context started"), "Should show context events");
+            assertTrue(output.contains("route started"), "Should show route events");
+            assertTrue(output.contains("exchange done"), "Should show exchange events");
+        }
+    }
+
+    @Test
+    void testFilterRouteEvents() throws Exception {
+        JsonObject root = buildEventStatus();
+        writeStatusFile(TEST_PID, root);
+
+        ListEvent command = new ListEvent(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+        command.filter = "route";
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.contains("route started"), "Route events should be shown");
+            assertFalse(output.contains("context started"), "Context events should be filtered out");
+            assertFalse(output.contains("exchange done"), "Exchange events should be filtered out");
+        }
+    }
+
+    @Test
+    void testJsonOutput() throws Exception {
+        JsonObject root = buildEventStatus();
+        writeStatusFile(TEST_PID, root);
+
+        ListEvent command = new ListEvent(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+        command.jsonOutput = true;
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.startsWith("["), "JSON output should be array");
+            assertTrue(output.contains("ContextStarted"));
+        }
+    }
+
+    private static JsonObject contextObj() {
+        JsonObject ctx = new JsonObject();
+        ctx.put("name", "myApp");
+        return ctx;
+    }
+
+    private static JsonObject buildEventStatus() {
+        JsonObject events = new JsonObject();
+        events.put("events", eventArray(event("ContextStarted", "context started")));
+        events.put("routeEvents", eventArray(event("RouteStarted", "route started")));
+        events.put("exchangeEvents", eventArray(event("ExchangeCompleted", "exchange done")));
+
+        JsonObject root = new JsonObject();
+        root.put("context", contextObj());
+        root.put("events", events);
+        return root;
+    }
+
+    private static JsonArray eventArray(JsonObject... events) {
+        JsonArray arr = new JsonArray();
+        Collections.addAll(arr, events);
+        return arr;
+    }
+
+    private static JsonObject event(String type, String message) {
+        JsonObject e = new JsonObject();
+        e.put("type", type);
+        e.put("timestamp", System.currentTimeMillis() - 1000);
+        e.put("exchangeId", "exchange-1");
+        e.put("message", message);
+        return e;
+    }
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListHealthTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListHealthTest.java
new file mode 100644
index 000000000000..a386b9b6cce3
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListHealthTest.java
@@ -0,0 +1,174 @@
+/*
+ * 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.dsl.jbang.core.commands.process;
+
+import java.util.Collections;
+import java.util.stream.Stream;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.MockedStatic;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mockStatic;
+
+@ExtendWith(MockitoExtension.class)
+class ListHealthTest extends ProcessCommandTestSupport {
+
+    @Test
+    void testEmptyOutputWhenNoProcesses() throws Exception {
+        ListHealth command = new ListHealth(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.empty());
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            assertEquals("", printer.getOutput().trim());
+        }
+    }
+
+    @Test
+    void testShowsUpHealthCheck() throws Exception {
+        JsonObject root = buildHealthStatus(check("context", "UP", true));
+        writeStatusFile(TEST_PID, root);
+
+        ListHealth command = new ListHealth(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.contains("UP"), "Should show UP state");
+            assertTrue(output.contains("context"), "Should show check ID");
+        }
+    }
+
+    @Test
+    void testShowsDownHealthCheck() throws Exception {
+        JsonObject root = buildHealthStatus(check("context", "DOWN", true));
+        writeStatusFile(TEST_PID, root);
+
+        ListHealth command = new ListHealth(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            assertTrue(printer.getOutput().contains("DOWN"), "Should show DOWN state");
+        }
+    }
+
+    @Test
+    void testJsonOutput() throws Exception {
+        JsonObject root = buildHealthStatus(check("context", "UP", true));
+        writeStatusFile(TEST_PID, root);
+
+        ListHealth command = new ListHealth(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+        command.jsonOutput = true;
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.startsWith("["), "JSON output should be an array");
+            assertTrue(output.contains("context"));
+        }
+    }
+
+    @Test
+    void testFilterReadyOnly() throws Exception {
+        JsonObject root = buildHealthStatus(
+                check("context", "UP", true),
+                check("consumer", "DOWN", false));
+        writeStatusFile(TEST_PID, root);
+
+        ListHealth command = new ListHealth(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+        command.ready = true;
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.contains("context"), "Readiness check should be shown");
+            assertFalse(output.contains("consumer"), "Non-readiness check should be hidden with --ready");
+        }
+    }
+
+    private static JsonObject buildHealthStatus(JsonObject... checks) {
+        JsonArray checksArr = new JsonArray();
+        Collections.addAll(checksArr, checks);
+
+        JsonObject hc = new JsonObject();
+        hc.put("checks", checksArr);
+
+        JsonObject context = new JsonObject();
+        context.put("name", "myApp");
+
+        JsonObject root = new JsonObject();
+        root.put("context", context);
+        root.put("healthChecks", hc);
+        return root;
+    }
+
+    private static JsonObject check(String id, String state, boolean readiness) {
+        JsonObject c = new JsonObject();
+        c.put("id", id);
+        c.put("group", "camel");
+        c.put("state", state);
+        c.put("readiness", readiness);
+        c.put("liveness", true);
+        return c;
+    }
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListInflightTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListInflightTest.java
new file mode 100644
index 000000000000..6caec235e053
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListInflightTest.java
@@ -0,0 +1,137 @@
+/*
+ * 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.dsl.jbang.core.commands.process;
+
+import java.util.Collections;
+import java.util.stream.Stream;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.MockedStatic;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mockStatic;
+
+@ExtendWith(MockitoExtension.class)
+class ListInflightTest extends ProcessCommandTestSupport {
+
+    @Test
+    void testEmptyOutputWhenNoInflightExchanges() throws Exception {
+        JsonObject root = new JsonObject();
+        root.put("context", contextObj());
+        writeStatusFile(TEST_PID, root);
+
+        ListInflight command = new ListInflight(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            assertEquals("", printer.getOutput().trim());
+        }
+    }
+
+    @Test
+    void testShowsInflightExchangeWithRemoteColumn() throws Exception {
+        JsonObject root = new JsonObject();
+        root.put("context", contextObj());
+        root.put("inflight", exchangeContainer(inflightExchange("exchange-1", true)));
+        writeStatusFile(TEST_PID, root);
+
+        ListInflight command = new ListInflight(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.contains("exchange-1"), "Should show inflight exchange id");
+            assertTrue(output.contains("REMOTE"), "Remote column should be visible when status has remote data");
+            assertTrue(output.contains("to1"), "Should show current node id");
+        }
+    }
+
+    @Test
+    void testJsonOutput() throws Exception {
+        JsonObject root = new JsonObject();
+        root.put("context", contextObj());
+        root.put("inflight", exchangeContainer(inflightExchange("exchange-2", false)));
+        writeStatusFile(TEST_PID, root);
+
+        ListInflight command = new ListInflight(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+        command.jsonOutput = true;
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.startsWith("["), "JSON output should be array");
+            assertTrue(output.contains("\"remote\":false"));
+            assertTrue(output.contains("exchange-2"));
+        }
+    }
+
+    private static JsonObject contextObj() {
+        JsonObject ctx = new JsonObject();
+        ctx.put("name", "myApp");
+        return ctx;
+    }
+
+    private static JsonObject exchangeContainer(JsonObject... exchanges) {
+        JsonArray arr = new JsonArray();
+        Collections.addAll(arr, exchanges);
+        JsonObject container = new JsonObject();
+        container.put("exchanges", arr);
+        return container;
+    }
+
+    private static JsonObject inflightExchange(String exchangeId, boolean remote) {
+        JsonObject e = new JsonObject();
+        e.put("exchangeId", exchangeId);
+        e.put("fromRouteId", "route1");
+        e.put("fromRemoteEndpoint", remote);
+        e.put("atRouteId", "route1");
+        e.put("nodeId", "to1");
+        e.put("elapsed", 100L);
+        e.put("duration", 250L);
+        return e;
+    }
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListInternalTaskTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListInternalTaskTest.java
new file mode 100644
index 000000000000..7b700e6ffc7d
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListInternalTaskTest.java
@@ -0,0 +1,139 @@
+/*
+ * 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.dsl.jbang.core.commands.process;
+
+import java.util.Collections;
+import java.util.stream.Stream;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.MockedStatic;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mockStatic;
+
+@ExtendWith(MockitoExtension.class)
+class ListInternalTaskTest extends ProcessCommandTestSupport {
+
+    @Test
+    void testEmptyOutputWhenNoInternalTasks() throws Exception {
+        JsonObject root = new JsonObject();
+        root.put("context", contextObj());
+        writeStatusFile(TEST_PID, root);
+
+        ListInternalTask command = new ListInternalTask(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            assertEquals("", printer.getOutput().trim());
+        }
+    }
+
+    @Test
+    void testShowsInternalTask() throws Exception {
+        JsonObject root = new JsonObject();
+        root.put("context", contextObj());
+        root.put("internal-tasks", taskContainer(task("reload", "Blocked")));
+        writeStatusFile(TEST_PID, root);
+
+        ListInternalTask command = new ListInternalTask(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.contains("reload"), "Should show task name");
+            assertTrue(output.contains("Blocked"), "Should show task status");
+            assertTrue(output.contains("boom"), "Should show task failure");
+        }
+    }
+
+    @Test
+    void testJsonOutput() throws Exception {
+        JsonObject root = new JsonObject();
+        root.put("context", contextObj());
+        root.put("internal-tasks", taskContainer(task("sync", "Done")));
+        writeStatusFile(TEST_PID, root);
+
+        ListInternalTask command = new ListInternalTask(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+        command.jsonOutput = true;
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.startsWith("["), "JSON output should be array");
+            assertTrue(output.contains("sync"));
+        }
+    }
+
+    private static JsonObject contextObj() {
+        JsonObject ctx = new JsonObject();
+        ctx.put("name", "myApp");
+        return ctx;
+    }
+
+    private static JsonObject taskContainer(JsonObject... tasks) {
+        JsonArray arr = new JsonArray();
+        Collections.addAll(arr, tasks);
+        JsonObject container = new JsonObject();
+        container.put("tasks", arr);
+        return container;
+    }
+
+    private static JsonObject task(String name, String status) {
+        long now = System.currentTimeMillis();
+        JsonObject t = new JsonObject();
+        t.put("name", name);
+        t.put("status", status);
+        t.put("attempts", 2L);
+        t.put("delay", 1000L);
+        t.put("elapsed", 50L);
+        t.put("firstTime", now - 5000);
+        t.put("lastTime", now - 1000);
+        t.put("nextTime", now + 1000);
+        t.put("error", "boom");
+        return t;
+    }
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListMetricTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListMetricTest.java
new file mode 100644
index 000000000000..40f6909a0a8a
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListMetricTest.java
@@ -0,0 +1,132 @@
+/*
+ * 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.dsl.jbang.core.commands.process;
+
+import java.util.Collections;
+import java.util.stream.Stream;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.MockedStatic;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mockStatic;
+
+@ExtendWith(MockitoExtension.class)
+class ListMetricTest extends ProcessCommandTestSupport {
+
+    @Test
+    void testEmptyOutputWhenNoMetrics() throws Exception {
+        JsonObject root = new JsonObject();
+        root.put("context", contextObj());
+        writeStatusFile(TEST_PID, root);
+
+        ListMetric command = new ListMetric(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            assertEquals("", printer.getOutput().trim());
+        }
+    }
+
+    @Test
+    void testShowsCounterMetric() throws Exception {
+        JsonObject root = new JsonObject();
+        root.put("context", contextObj());
+        root.put("micrometer", micrometerObj(counterEntry(42.0)));
+        writeStatusFile(TEST_PID, root);
+
+        ListMetric command = new ListMetric(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+        command.all = true;
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.contains("myCounter"), "Should show metric name");
+        }
+    }
+
+    @Test
+    void testJsonOutput() throws Exception {
+        JsonObject root = new JsonObject();
+        root.put("context", contextObj());
+        root.put("micrometer", micrometerObj(counterEntry(10.0)));
+        writeStatusFile(TEST_PID, root);
+
+        ListMetric command = new ListMetric(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+        command.all = true;
+        command.jsonOutput = true;
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.startsWith("["), "JSON output should be array");
+            assertTrue(output.contains("myCounter"));
+        }
+    }
+
+    private static JsonObject contextObj() {
+        JsonObject ctx = new JsonObject();
+        ctx.put("name", "myApp");
+        return ctx;
+    }
+
+    private static JsonObject micrometerObj(JsonObject... counters) {
+        JsonArray arr = new JsonArray();
+        Collections.addAll(arr, counters);
+        JsonObject mo = new JsonObject();
+        mo.put("counters", arr);
+        return mo;
+    }
+
+    private static JsonObject counterEntry(double count) {
+        JsonObject c = new JsonObject();
+        c.put("name", "myCounter");
+        c.put("description", "");
+        c.put("count", count);
+        return c;
+    }
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListProcessTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListProcessTest.java
new file mode 100644
index 000000000000..4c277d82bee5
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListProcessTest.java
@@ -0,0 +1,238 @@
+/*
+ * 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.dsl.jbang.core.commands.process;
+
+import java.util.List;
+import java.util.stream.Stream;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.MockedStatic;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mockStatic;
+
+@ExtendWith(MockitoExtension.class)
+class ListProcessTest extends ProcessCommandTestSupport {
+
+    @Test
+    void testEmptyOutputWhenNoProcesses() throws Exception {
+        ListProcess command = new ListProcess(new CamelJBangMain().withPrinter(printer));
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.empty());
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            assertEquals("", printer.getOutput().trim());
+        }
+    }
+
+    @Test
+    void testListsRunningProcess() throws Exception {
+        JsonObject stats = buildStats();
+        JsonObject status = buildContextStatus("myApp", 5);
+        ((JsonObject) status.get("context")).put("statistics", stats);
+        writeStatusFile(TEST_PID, status);
+
+        ListProcess command = new ListProcess(new CamelJBangMain().withPrinter(printer));
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.contains("myApp"), "Should contain context name");
+            assertTrue(output.contains("Running"), "Should show Running status for phase 5");
+            assertTrue(output.contains("1/1"), "Should show ready 1/1 when phase is 5");
+        }
+    }
+
+    @Test
+    void testListsStartingProcess() throws Exception {
+        // Phase 3 = Starting (anything <= 4 is Starting)
+        JsonObject status = buildContextStatus("startingApp", 3);
+        writeStatusFile(TEST_PID, status);
+
+        ListProcess command = new ListProcess(new CamelJBangMain().withPrinter(printer));
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.contains("startingApp"));
+            assertTrue(output.contains("Starting"), "Phase <= 4 should map to Starting");
+            assertTrue(output.contains("0/1"), "Should show 0/1 ready when not phase 5");
+        }
+    }
+
+    @Test
+    void testPidOnlyFlag() throws Exception {
+        JsonObject status = buildContextStatus("myApp", 5);
+        writeStatusFile(TEST_PID, status);
+
+        ListProcess command = new ListProcess(new CamelJBangMain().withPrinter(printer));
+        command.pid = true;
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            List<String> lines = printer.getLines();
+            assertEquals(1, lines.size(), "Should output exactly one PID line");
+            assertEquals(String.valueOf(TEST_PID), lines.get(0).trim());
+        }
+    }
+
+    @Test
+    void testJsonOutput() throws Exception {
+        JsonObject status = buildContextStatus("myApp", 5);
+        writeStatusFile(TEST_PID, status);
+
+        ListProcess command = new ListProcess(new CamelJBangMain().withPrinter(printer));
+        command.jsonOutput = true;
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.startsWith("["), "JSON output should be a JSON array");
+            assertTrue(output.contains("myApp"), "JSON should contain the context name");
+        }
+    }
+
+    @Test
+    void testJsonPidOnlyFlag() throws Exception {
+        JsonObject status = buildContextStatus("myApp", 5);
+        writeStatusFile(TEST_PID, status);
+
+        ListProcess command = new ListProcess(new CamelJBangMain().withPrinter(printer));
+        command.pid = true;
+        command.jsonOutput = true;
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.startsWith("["), "JSON pid output should be a JSON array");
+            assertTrue(output.contains(String.valueOf(TEST_PID)), "Should contain the PID");
+        }
+    }
+
+    @Test
+    void testSortByName() throws Exception {
+        long pid1 = 11111L;
+        long pid2 = 22222L;
+
+        JsonObject status1 = buildContextStatus("zebra", 5);
+        JsonObject status2 = buildContextStatus("apple", 5);
+        writeStatusFile(pid1, status1);
+        writeStatusFile(pid2, status2);
+
+        ListProcess command = new ListProcess(new CamelJBangMain().withPrinter(printer));
+        command.sort = "name";
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph1 = mockProcessHandle(pid1);
+            ProcessHandle ph2 = mockProcessHandle(pid2);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph1, ph2));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            // apple should appear before zebra when sorted ascending by name
+            int applePos = output.indexOf("apple");
+            int zebraPos = output.indexOf("zebra");
+            assertTrue(applePos < zebraPos, "Sort by name ascending: apple must precede zebra");
+        }
+    }
+
+    @Test
+    void testReloadErrorShownInOutput() throws Exception {
+        JsonObject status = buildContextStatus("faultyApp", 5);
+        JsonObject stats = (JsonObject) ((JsonObject) status.get("context")).get("statistics");
+        JsonObject reload = new JsonObject();
+        JsonObject lastError = new JsonObject();
+        lastError.put("message", "Route file parse error");
+        reload.put("lastError", lastError);
+        stats.put("reload", reload);
+        writeStatusFile(TEST_PID, status);
+
+        ListProcess command = new ListProcess(new CamelJBangMain().withPrinter(printer));
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.contains("Error"), "Should show Error status when reload failed");
+            // Description column is width-limited; "Reload" is the start of the description
+            assertTrue(output.contains("Reload"), "Should display the reload description");
+        }
+    }
+
+    private static JsonObject buildStats() {
+        JsonObject stats = new JsonObject();
+        stats.put("exchangesTotal", "100");
+        stats.put("exchangesFailed", "5");
+        stats.put("exchangesInflight", "3");
+        return stats;
+    }
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListProducerTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListProducerTest.java
new file mode 100644
index 000000000000..90d58230e34d
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListProducerTest.java
@@ -0,0 +1,194 @@
+/*
+ * 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.dsl.jbang.core.commands.process;
+
+import java.util.Collections;
+import java.util.stream.Stream;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.MockedStatic;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mockStatic;
+
+@ExtendWith(MockitoExtension.class)
+class ListProducerTest extends ProcessCommandTestSupport {
+
+    @Test
+    void testEmptyOutputWhenNoProducers() throws Exception {
+        JsonObject root = new JsonObject();
+        root.put("context", contextObj());
+        writeStatusFile(TEST_PID, root);
+
+        ListProducer command = new ListProducer(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            assertEquals("", printer.getOutput().trim());
+        }
+    }
+
+    @Test
+    void testShowsProducerUriAndState() throws Exception {
+        JsonObject root = new JsonObject();
+        root.put("context", contextObj());
+        root.put("producers", producerContainer(producer("route1", "log:info?showAll=true")));
+        writeStatusFile(TEST_PID, root);
+
+        ListProducer command = new ListProducer(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.contains("log:info"), "Should show producer URI");
+            assertTrue(output.contains("Started"), "Should show producer state");
+        }
+    }
+
+    @Test
+    void testFilterAndShortUri() throws Exception {
+        JsonObject root = new JsonObject();
+        root.put("context", contextObj());
+        root.put("producers", producerContainer(
+                producer("route1", "log:info?showAll=true"),
+                producer("route2", "mock:result?retainFirst=1")));
+        writeStatusFile(TEST_PID, root);
+
+        ListProducer command = new ListProducer(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+        command.filter = "log:";
+        command.shortUri = true;
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.contains("log:info"), "Matching producer should be shown");
+            assertFalse(output.contains("showAll"), "Short URI should remove query parameters");
+            assertFalse(output.contains("mock:result"), "Non-matching producer should be hidden");
+        }
+    }
+
+    @Test
+    void testLimit() throws Exception {
+        JsonObject root = new JsonObject();
+        root.put("context", contextObj());
+        root.put("producers", producerContainer(
+                producer("route1", "log:one"),
+                producer("route2", "log:two")));
+        writeStatusFile(TEST_PID, root);
+
+        ListProducer command = new ListProducer(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+        command.limit = 1;
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.contains("log:one"), "First producer should be shown");
+            assertFalse(output.contains("log:two"), "Second producer should be hidden by limit");
+        }
+    }
+
+    @Test
+    void testJsonOutput() throws Exception {
+        JsonObject root = new JsonObject();
+        root.put("context", contextObj());
+        root.put("producers", producerContainer(producer("route1", "direct:out")));
+        writeStatusFile(TEST_PID, root);
+
+        ListProducer command = new ListProducer(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+        command.jsonOutput = true;
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.startsWith("["), "JSON output should be array");
+            assertTrue(output.contains("direct:out"));
+            assertTrue(output.contains("Log"), "Producer suffix should be removed from type");
+        }
+    }
+
+    private static JsonObject contextObj() {
+        JsonObject ctx = new JsonObject();
+        ctx.put("name", "myApp");
+        return ctx;
+    }
+
+    private static JsonObject producerContainer(JsonObject... producers) {
+        JsonArray arr = new JsonArray();
+        Collections.addAll(arr, producers);
+        JsonObject container = new JsonObject();
+        container.put("producers", arr);
+        return container;
+    }
+
+    private static JsonObject producer(String routeId, String uri) {
+        JsonObject p = new JsonObject();
+        p.put("routeId", routeId);
+        p.put("uri", uri);
+        p.put("state", "Started");
+        p.put("class", "org.apache.camel.component.log.LogProducer");
+        p.put("singleton", true);
+        p.put("remote", false);
+        return p;
+    }
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListPropertiesTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListPropertiesTest.java
new file mode 100644
index 000000000000..8ab0232b492f
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListPropertiesTest.java
@@ -0,0 +1,173 @@
+/*
+ * 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.dsl.jbang.core.commands.process;
+
+import java.util.Collections;
+import java.util.stream.Stream;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.MockedStatic;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mockStatic;
+
+@ExtendWith(MockitoExtension.class)
+class ListPropertiesTest extends ProcessCommandTestSupport {
+
+    @Test
+    void testEmptyOutputWhenNoProperties() throws Exception {
+        JsonObject root = new JsonObject();
+        root.put("context", contextObj());
+        root.put("properties", propertyContainer("properties"));
+        writeStatusFile(TEST_PID, root);
+
+        ListProperties command = new ListProperties(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            assertEquals("", printer.getOutput().trim());
+        }
+    }
+
+    @Test
+    void testMasksSensitiveValuesByDefault() throws Exception {
+        JsonObject root = new JsonObject();
+        root.put("context", contextObj());
+        root.put("properties", propertyContainer("properties",
+                property("camel.password", "secret", "secret", false, "properties:app", "initial")));
+        writeStatusFile(TEST_PID, root);
+
+        ListProperties command = new ListProperties(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            // "camel.password" is 14 chars; the KEY column minimum is 15 but may truncate with ellipsis
+            // in narrow terminal environments — check the prefix that always survives truncation
+            assertTrue(output.contains("camel.pass"), "Should show sensitive property key (possibly truncated)");
+            assertTrue(output.contains("xxxxxx"), "Sensitive value should be masked");
+            assertFalse(output.contains("secret"), "Raw sensitive value should not be printed");
+        }
+    }
+
+    @Test
+    void testInternalPropertiesHiddenUnlessRequested() throws Exception {
+        JsonObject root = new JsonObject();
+        root.put("context", contextObj());
+        root.put("properties", propertyContainer("properties",
+                property("public.key", "visible", "visible", false, "properties:app", "ENV"),
+                property("internal.key", "hidden", "hidden", true, "properties:app", "SYS")));
+        writeStatusFile(TEST_PID, root);
+
+        ListProperties command = new ListProperties(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.contains("public.key"), "Public property should be shown");
+            assertFalse(output.contains("internal.key"), "Internal property should be hidden by default");
+        }
+    }
+
+    @Test
+    void testStartupVerboseJsonOutput() throws Exception {
+        JsonObject root = new JsonObject();
+        root.put("context", contextObj());
+        root.put("main-configuration", propertyContainer("configurations",
+                property("camel.main.name", "demo", "demo", false, "sys:camel.main.name", "CLI")));
+        writeStatusFile(TEST_PID, root);
+
+        ListProperties command = new ListProperties(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+        command.startup = true;
+        command.verbose = true;
+        command.jsonOutput = true;
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.startsWith("["), "JSON output should be array");
+            assertTrue(output.contains("camel.main.name"));
+            assertTrue(output.contains("Command Line"), "CLI location should be sanitized");
+            assertTrue(output.contains("\"function\":\"sys\""), "Function should be derived from source prefix");
+        }
+    }
+
+    private static JsonObject contextObj() {
+        JsonObject ctx = new JsonObject();
+        ctx.put("name", "myApp");
+        return ctx;
+    }
+
+    private static JsonObject propertyContainer(String key, JsonObject... properties) {
+        JsonArray arr = new JsonArray();
+        Collections.addAll(arr, properties);
+        JsonObject container = new JsonObject();
+        container.put(key, arr);
+        return container;
+    }
+
+    private static JsonObject property(
+            String key, String value, String originalValue, boolean internal, String source, String location) {
+        JsonObject p = new JsonObject();
+        p.put("key", key);
+        p.put("value", value);
+        p.put("originalValue", originalValue);
+        p.put("internal", internal);
+        p.put("source", source);
+        p.put("location", location);
+        return p;
+    }
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListRestTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListRestTest.java
new file mode 100644
index 000000000000..2672932e43a6
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListRestTest.java
@@ -0,0 +1,135 @@
+/*
+ * 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.dsl.jbang.core.commands.process;
+
+import java.util.Collections;
+import java.util.stream.Stream;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.MockedStatic;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mockStatic;
+
+@ExtendWith(MockitoExtension.class)
+class ListRestTest extends ProcessCommandTestSupport {
+
+    @Test
+    void testEmptyOutputWhenNoRests() throws Exception {
+        JsonObject root = new JsonObject();
+        root.put("context", contextObj());
+        writeStatusFile(TEST_PID, root);
+
+        ListRest command = new ListRest(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            assertEquals("", printer.getOutput().trim());
+        }
+    }
+
+    @Test
+    void testShowsRestEndpoints() throws Exception {
+        JsonObject root = new JsonObject();
+        root.put("context", contextObj());
+        root.put("rests", restContainer(restEntry("/api/users", "GET")));
+        writeStatusFile(TEST_PID, root);
+
+        ListRest command = new ListRest(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.contains("api/users"), "Should show URL");
+            assertTrue(output.contains("GET"), "Should show HTTP method");
+        }
+    }
+
+    @Test
+    void testJsonOutput() throws Exception {
+        JsonObject root = new JsonObject();
+        root.put("context", contextObj());
+        root.put("rests", restContainer(restEntry("/api/items", "POST")));
+        writeStatusFile(TEST_PID, root);
+
+        ListRest command = new ListRest(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+        command.jsonOutput = true;
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.startsWith("["), "JSON output should be an array");
+            assertTrue(output.contains("items"), "Should contain the REST URL path");
+            assertTrue(output.contains("POST"), "Should contain the HTTP method");
+        }
+    }
+
+    private static JsonObject contextObj() {
+        JsonObject ctx = new JsonObject();
+        ctx.put("name", "myApp");
+        return ctx;
+    }
+
+    private static JsonObject restContainer(JsonObject... entries) {
+        JsonArray arr = new JsonArray();
+        Collections.addAll(arr, entries);
+        JsonObject container = new JsonObject();
+        container.put("rests", arr);
+        return container;
+    }
+
+    private static JsonObject restEntry(String url, String method) {
+        JsonObject e = new JsonObject();
+        e.put("url", url);
+        e.put("method", method);
+        e.put("consumes", "application/json");
+        e.put("produces", "application/json");
+        e.put("description", "");
+        e.put("contractFirst", false);
+        return e;
+    }
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListServiceTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListServiceTest.java
new file mode 100644
index 000000000000..315902e39040
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListServiceTest.java
@@ -0,0 +1,174 @@
+/*
+ * 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.dsl.jbang.core.commands.process;
+
+import java.util.Collections;
+import java.util.stream.Stream;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.MockedStatic;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mockStatic;
+
+@ExtendWith(MockitoExtension.class)
+class ListServiceTest extends ProcessCommandTestSupport {
+
+    @Test
+    void testEmptyOutputWhenNoServices() throws Exception {
+        JsonObject root = new JsonObject();
+        root.put("context", contextObj());
+        writeStatusFile(TEST_PID, root);
+
+        ListService command = new ListService(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            assertEquals("", printer.getOutput().trim());
+        }
+    }
+
+    @Test
+    void testShowsServiceDetails() throws Exception {
+        JsonObject root = new JsonObject();
+        root.put("context", contextObj());
+        root.put("services", serviceContainer(service("platform-http", "in", "http", "http://0.0.0.0:8080/hello")));
+        writeStatusFile(TEST_PID, root);
+
+        ListService command = new ListService(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.contains("platform-http"), "Should show service component");
+            assertTrue(output.contains("http://0.0.0.0:8080/hello"), "Should show service URL");
+        }
+    }
+
+    @Test
+    void testMetadataAndShortUri() throws Exception {
+        JsonObject root = new JsonObject();
+        root.put("context", contextObj());
+        root.put("services", serviceContainer(service("rest", "in", "http", "http://localhost:8080/orders")));
+        writeStatusFile(TEST_PID, root);
+
+        ListService command = new ListService(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+        command.metadata = true;
+        command.shortUri = true;
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.contains("method=GET"), "Metadata should be shown when requested");
+            assertTrue(output.contains("direct:orders"), "Endpoint URI should be shown");
+            assertFalse(output.contains("bridgeEndpoint"), "Short URI should remove endpoint query parameters");
+        }
+    }
+
+    @Test
+    void testJsonOutputUsesEmptyRouteWhenMissing() throws Exception {
+        JsonObject entry = service("servlet", "in", "http", "http://localhost:8080/ping");
+        entry.remove("routeId");
+
+        JsonObject root = new JsonObject();
+        root.put("context", contextObj());
+        root.put("services", serviceContainer(entry));
+        writeStatusFile(TEST_PID, root);
+
+        ListService command = new ListService(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+        command.jsonOutput = true;
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.startsWith("["), "JSON output should be array");
+            assertTrue(output.contains("\"routeId\":\"\""), "Missing route id should be serialized as an empty string");
+            assertTrue(output.contains("servlet"));
+        }
+    }
+
+    private static JsonObject contextObj() {
+        JsonObject ctx = new JsonObject();
+        ctx.put("name", "myApp");
+        return ctx;
+    }
+
+    private static JsonObject serviceContainer(JsonObject... services) {
+        JsonArray arr = new JsonArray();
+        Collections.addAll(arr, services);
+        JsonObject container = new JsonObject();
+        container.put("services", arr);
+        return container;
+    }
+
+    private static JsonObject service(String component, String direction, String protocol, String serviceUrl) {
+        JsonObject metadata = new JsonObject();
+        metadata.put("method", "GET");
+        metadata.put("path", "/orders");
+
+        JsonObject s = new JsonObject();
+        s.put("component", component);
+        s.put("direction", direction);
+        s.put("hosted", true);
+        s.put("protocol", protocol);
+        s.put("serviceUrl", serviceUrl);
+        s.put("endpointUri", "direct:orders?bridgeEndpoint=true");
+        s.put("hits", 3L);
+        s.put("routeId", "route1");
+        s.put("metadata", metadata);
+        return s;
+    }
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListTransformerTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListTransformerTest.java
new file mode 100644
index 000000000000..4deeff87521e
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListTransformerTest.java
@@ -0,0 +1,132 @@
+/*
+ * 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.dsl.jbang.core.commands.process;
+
+import java.util.Collections;
+import java.util.stream.Stream;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.MockedStatic;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mockStatic;
+
+@ExtendWith(MockitoExtension.class)
+class ListTransformerTest extends ProcessCommandTestSupport {
+
+    @Test
+    void testEmptyOutputWhenNoTransformers() throws Exception {
+        JsonObject root = new JsonObject();
+        root.put("context", contextObj());
+        writeStatusFile(TEST_PID, root);
+
+        ListTransformer command = new ListTransformer(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            assertEquals("", printer.getOutput().trim());
+        }
+    }
+
+    @Test
+    void testShowsTransformerDataTypes() throws Exception {
+        JsonObject root = new JsonObject();
+        root.put("context", contextObj());
+        root.put("transformers", transformerContainer(transformer("order-transformer", "xml:Order", "json:Order")));
+        writeStatusFile(TEST_PID, root);
+
+        ListTransformer command = new ListTransformer(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.contains("order-transformer"), "Should show transformer name");
+            assertTrue(output.contains("xml:Order"), "Should show source data type");
+            assertTrue(output.contains("json:Order"), "Should show target data type");
+        }
+    }
+
+    @Test
+    void testJsonOutput() throws Exception {
+        JsonObject root = new JsonObject();
+        root.put("context", contextObj());
+        root.put("transformers", transformerContainer(transformer("order-transformer", "xml:Order", "json:Order")));
+        writeStatusFile(TEST_PID, root);
+
+        ListTransformer command = new ListTransformer(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+        command.jsonOutput = true;
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.startsWith("["), "JSON output should be array");
+            assertTrue(output.contains("order-transformer"));
+        }
+    }
+
+    private static JsonObject contextObj() {
+        JsonObject ctx = new JsonObject();
+        ctx.put("name", "myApp");
+        return ctx;
+    }
+
+    private static JsonObject transformerContainer(JsonObject... transformers) {
+        JsonArray arr = new JsonArray();
+        Collections.addAll(arr, transformers);
+        JsonObject container = new JsonObject();
+        container.put("transformers", arr);
+        return container;
+    }
+
+    private static JsonObject transformer(String name, String from, String to) {
+        JsonObject t = new JsonObject();
+        t.put("name", name);
+        t.put("from", from);
+        t.put("to", to);
+        return t;
+    }
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListVariableTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListVariableTest.java
new file mode 100644
index 000000000000..cb8bd15fff39
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/ListVariableTest.java
@@ -0,0 +1,131 @@
+/*
+ * 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.dsl.jbang.core.commands.process;
+
+import java.util.Collections;
+import java.util.stream.Stream;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.MockedStatic;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mockStatic;
+
+@ExtendWith(MockitoExtension.class)
+class ListVariableTest extends ProcessCommandTestSupport {
+
+    @Test
+    void testEmptyOutputWhenNoVariables() throws Exception {
+        JsonObject root = new JsonObject();
+        root.put("context", contextObj());
+        writeStatusFile(TEST_PID, root);
+
+        ListVariable command = new ListVariable(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            assertEquals("", printer.getOutput().trim());
+        }
+    }
+
+    @Test
+    void testShowsVariableKeyAndValue() throws Exception {
+        JsonObject root = new JsonObject();
+        root.put("context", contextObj());
+        root.put("variables", variablesObj(variableEntry("myKey", "String", "hello")));
+        writeStatusFile(TEST_PID, root);
+
+        ListVariable command = new ListVariable(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.contains("myKey"), "Should show variable key");
+            assertTrue(output.contains("hello"), "Should show variable value");
+        }
+    }
+
+    @Test
+    void testJsonOutput() throws Exception {
+        JsonObject root = new JsonObject();
+        root.put("context", contextObj());
+        root.put("variables", variablesObj(variableEntry("counter", "Integer", "42")));
+        writeStatusFile(TEST_PID, root);
+
+        ListVariable command = new ListVariable(new CamelJBangMain().withPrinter(printer));
+        command.sort = "pid";
+        command.jsonOutput = true;
+
+        try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) {
+            ProcessHandle ph = mockProcessHandle(TEST_PID);
+            ProcessHandle currentHandle = mockCurrentHandle();
+            mocked.when(ProcessHandle::current).thenReturn(currentHandle);
+            mocked.when(ProcessHandle::allProcesses).thenAnswer(inv -> Stream.of(ph));
+
+            int exit = command.doCall();
+
+            assertEquals(0, exit);
+            String output = printer.getOutput();
+            assertTrue(output.startsWith("["), "JSON output should be array");
+            assertTrue(output.contains("counter"));
+        }
+    }
+
+    private static JsonObject contextObj() {
+        JsonObject ctx = new JsonObject();
+        ctx.put("name", "myApp");
+        return ctx;
+    }
+
+    private static JsonObject variablesObj(JsonObject... entries) {
+        JsonArray arr = new JsonArray();
+        Collections.addAll(arr, entries);
+        JsonObject vars = new JsonObject();
+        vars.put("global", arr);
+        return vars;
+    }
+
+    private static JsonObject variableEntry(String key, String type, String value) {
+        JsonObject e = new JsonObject();
+        e.put("key", key);
+        e.put("type", type);
+        e.put("value", value);
+        return e;
+    }
+}