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

commit ce1a91857c3e0a30e96e9fce106b2057e7768c6a
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Oct 15 13:18:08 2026 +0200

    chore: camel-jbang - camel get endpoint and the Endpoint tab show the scope 
of an endpoint
    
    Besides remote, a component can now be marked internal (CAMEL-25652):
    Camel plumbing such as direct, seda, log, timer and mock that stays inside
    the integration. That makes three kinds of endpoint, which exclude each
    other: remote (an external system), internal, and local (neither, such as
    exec, bean or class, which can run code or a process on this host).
    
    camel get endpoint and the Endpoint tab show them in a SCOPE column
    instead of REMOTE; the JSON of camel get endpoint keeps remote and adds
    internal, and tui_get_table adds scope. Remote comes from the runtime,
    internal from the catalog by the scheme of the URI, through the new
    EndpointScope, which the tool confirms of camel ask and the TUI now use
    as well. A component the catalog does not know is never internal.
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    |  8 ++
 .../dsl/jbang/core/commands/ai/ToolConfirms.java   | 27 ++-----
 .../jbang/core/commands/process/ListEndpoint.java  | 11 ++-
 .../camel/dsl/jbang/core/common/EndpointScope.java | 85 ++++++++++++++++++++++
 .../core/commands/process/ListEndpointTest.java    | 66 +++++++++++++++++
 .../dsl/jbang/core/common/EndpointScopeTest.java   | 53 ++++++++++++++
 .../dsl/jbang/core/commands/tui/EndpointInfo.java  |  2 +
 .../dsl/jbang/core/commands/tui/EndpointsTab.java  | 19 ++++-
 .../dsl/jbang/core/commands/tui/StatusParser.java  |  3 +
 .../src/main/resources/tui/help/endpoints.md       | 12 +--
 .../core/commands/tui/EndpointsTabRenderTest.java  | 26 +++++++
 .../jbang/core/commands/tui/StatusParserTest.java  | 23 ++++++
 12 files changed, 302 insertions(+), 33 deletions(-)

diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index 147092569b76..5d1d302fc766 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -159,6 +159,14 @@ local model gets a core set of tools (`--tools=auto`, the 
default), a hosted mod
 of `--max-iterations` is now 25 instead of 10, and `--model llama3.2` now 
selects that model also when a
 model better at tool calling, such as `qwen3.6`, is installed.
 
+=== camel-jbang - camel get endpoint shows the scope of an endpoint
+
+`camel get endpoint` replaces the `REMOTE` column with `SCOPE`, which tells 
three kinds of endpoint apart:
+`remote` (an external system, as before), `internal` (Camel plumbing that 
stays inside the integration, such as
+`direct`, `seda` or `log`, as the catalog marks its component internal) and 
`local` (neither, such as `exec` or
+`bean`). Scripts that read the `REMOTE` column of the table must read `SCOPE` 
instead. The `--json` output keeps
+`remote` and adds `internal`.
+
 === camel-jpa, camel-hazelcast - an exchange marked rollback only is not 
consumed
 
 When a route marks the exchange rollback only (for example with 
`markRollbackOnly()` or `markRollbackOnlyLast()`)
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolConfirms.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolConfirms.java
index 2d56e29b9de6..019b506a08c2 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolConfirms.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolConfirms.java
@@ -30,9 +30,7 @@ import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 
-import org.apache.camel.catalog.CamelCatalog;
-import org.apache.camel.catalog.DefaultCamelCatalog;
-import org.apache.camel.tooling.model.ComponentModel;
+import org.apache.camel.dsl.jbang.core.common.EndpointScope;
 import org.apache.camel.util.URISupport;
 
 /**
@@ -66,27 +64,12 @@ public final class ToolConfirms {
     }
 
     /**
-     * Whether a component stays inside the integration, as the catalog marks 
it internal: its routes, in-memory state
-     * and log, and no file, network, process, or code or resource named in 
its URI, by any option. This is narrower
-     * than remote=false, which exec, class, bean, language and the components 
that load a template or a stylesheet by
-     * URI are too. The catalog is the CLI's own, so it has the flag; a 
component it does not know (a custom one, or one
-     * newer than the CLI) is not internal.
+     * Whether a component stays inside the integration, as the catalog marks 
it internal ({@link EndpointScope}): a
+     * message to it needs no confirm. This is narrower than remote=false, 
which exec, class, bean, language and the
+     * components that load a template or a stylesheet by URI are too.
      */
     public static boolean isInternal(String scheme) {
-        if (!scheme.matches("[a-z0-9-]+")) {
-            return false;
-        }
-        try {
-            ComponentModel model = Catalog.INSTANCE.componentModel(scheme);
-            return model != null && model.isInternal();
-        } catch (RuntimeException e) {
-            return false;
-        }
-    }
-
-    // loaded on the first send, not when a call only needs the SQL or 
language checks
-    private static final class Catalog {
-        static final CamelCatalog INSTANCE = new DefaultCamelCatalog(true);
+        return EndpointScope.isInternal(scheme);
     }
 
     /**
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListEndpoint.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListEndpoint.java
index b50d83401420..ebf926f93dec 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListEndpoint.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListEndpoint.java
@@ -28,6 +28,7 @@ import com.github.freva.asciitable.Column;
 import com.github.freva.asciitable.HorizontalAlign;
 import com.github.freva.asciitable.OverflowBehaviour;
 import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.common.EndpointScope;
 import org.apache.camel.dsl.jbang.core.common.ProcessHelper;
 import org.apache.camel.dsl.jbang.core.common.TerminalWidthHelper;
 import org.apache.camel.support.PatternHelper;
@@ -127,6 +128,7 @@ public class ListEndpoint extends ProcessWatchCommand {
                                 row.endpoint = o.getString("uri");
                                 row.stub = o.getBooleanOrDefault("stub", 
false);
                                 row.remote = o.getBooleanOrDefault("remote", 
true);
+                                row.scope = EndpointScope.of(row.endpoint, 
row.remote);
                                 row.direction = o.getString("direction");
                                 row.total = o.getString("hits");
                                 row.meanBodySize = 
o.getLongOrDefault("meanBodySize", -1L);
@@ -190,6 +192,7 @@ public class ListEndpoint extends ProcessWatchCommand {
                 jo.put("total", r.total);
                 jo.put("stub", r.stub);
                 jo.put("remote", r.remote);
+                jo.put("internal", EndpointScope.INTERNAL.equals(r.scope));
                 jo.put("uri", r.endpoint);
                 if (r.meanBodySize >= 0) {
                     jo.put("minBodySize", r.minBodySize);
@@ -207,8 +210,8 @@ public class ListEndpoint extends ProcessWatchCommand {
         }
         boolean hasSize = rows.stream().anyMatch(r -> r.meanBodySize >= 0 || 
r.meanHeadersSize >= 0);
         // Flexible column: URI (90/140)
-        // Fixed columns: 
PID(8)+NAME(30)+AGE(8)+DIR(3)+TOTAL(5)+STUB(4)+REMOTE(6) ~= 64
-        int fixedW = 64;
+        // Fixed columns: 
PID(8)+NAME(30)+AGE(8)+DIR(3)+TOTAL(5)+STUB(4)+SCOPE(8) ~= 66
+        int fixedW = 66;
         if (hasSize) {
             // BODY-SIZE(9)+HDR-SIZE(8) = 17
             fixedW += 17;
@@ -229,7 +232,7 @@ public class ListEndpoint extends ProcessWatchCommand {
                 new Column().header("DIR").with(r -> r.direction),
                 new Column().header("TOTAL").with(r -> r.total),
                 new 
Column().header("STUB").dataAlign(HorizontalAlign.CENTER).with(r -> r.stub ? 
"x" : ""),
-                new 
Column().header("REMOTE").dataAlign(HorizontalAlign.CENTER).with(r -> r.remote 
? "x" : ""),
+                new 
Column().header("SCOPE").dataAlign(HorizontalAlign.LEFT).with(r -> r.scope),
                 new 
Column().header("BODY-SIZE").visible(hasSize).dataAlign(HorizontalAlign.RIGHT)
                         .with(r -> sizeToString(r.meanBodySize)),
                 new 
Column().header("HDR-SIZE").visible(hasSize).dataAlign(HorizontalAlign.RIGHT)
@@ -307,6 +310,8 @@ public class ListEndpoint extends ProcessWatchCommand {
         String total;
         boolean stub;
         boolean remote;
+        /** remote, internal or local ({@link EndpointScope}) */
+        String scope;
         long meanBodySize = -1;
         long meanHeadersSize = -1;
         long minBodySize = -1;
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/EndpointScope.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/EndpointScope.java
new file mode 100644
index 000000000000..60479ced1ad8
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/EndpointScope.java
@@ -0,0 +1,85 @@
+/*
+ * 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.common;
+
+import org.apache.camel.catalog.CamelCatalog;
+import org.apache.camel.catalog.DefaultCamelCatalog;
+import org.apache.camel.tooling.model.ComponentModel;
+
+/**
+ * How far an endpoint reaches, as {@code camel get endpoint} and the Endpoint 
tab show it: {@link #REMOTE}, an external
+ * system; {@link #INTERNAL}, Camel plumbing that stays inside the 
integration; or {@link #LOCAL}, neither. The three
+ * exclude each other, as an internal component is never remote.
+ */
+public final class EndpointScope {
+
+    /** An external system, over the network or a file system: the endpoint 
says so at runtime. */
+    public static final String REMOTE = "remote";
+    /** Stays inside the integration (direct, seda, log, mock): the catalog 
marks its component internal. */
+    public static final String INTERNAL = "internal";
+    /** Neither: the component can run code or a process, or read a file or a 
resource, on this host. */
+    public static final String LOCAL = "local";
+
+    private EndpointScope() {
+    }
+
+    /**
+     * The scope of an endpoint.
+     *
+     * @param uri    the endpoint URI
+     * @param remote whether the endpoint is remote, as the runtime reports it
+     */
+    public static String of(String uri, boolean remote) {
+        if (remote) {
+            return REMOTE;
+        }
+        return isInternal(scheme(uri)) ? INTERNAL : LOCAL;
+    }
+
+    /** The component of an endpoint URI: what comes before its first colon. */
+    public static String scheme(String uri) {
+        if (uri == null) {
+            return null;
+        }
+        int colon = uri.indexOf(':');
+        return colon > 0 ? uri.substring(0, colon) : uri;
+    }
+
+    /**
+     * Whether a component stays inside the integration, as the catalog marks 
it internal: its routes, in-memory state
+     * and log, and no file, network, process, or code or resource named in 
its URI, by any option. This is narrower
+     * than remote=false, which exec, class, bean, language and the components 
that load a template or a stylesheet by
+     * URI are too. The catalog is the CLI's own, so it has the flag; a 
component it does not know (a custom one, or one
+     * newer than the CLI) is not internal.
+     */
+    public static boolean isInternal(String scheme) {
+        if (scheme == null || !scheme.matches("[a-z0-9-]+")) {
+            return false;
+        }
+        try {
+            ComponentModel model = Catalog.INSTANCE.componentModel(scheme);
+            return model != null && model.isInternal();
+        } catch (RuntimeException e) {
+            return false;
+        }
+    }
+
+    // loaded when a scope is first asked for, not when the CLI starts
+    private static final class Catalog {
+        static final CamelCatalog INSTANCE = new DefaultCamelCatalog(true);
+    }
+}
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
index e2605a1340f3..c5d57b255db7 100644
--- 
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
@@ -22,6 +22,7 @@ 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.apache.camel.util.json.Jsoner;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.MockedStatic;
@@ -132,6 +133,71 @@ class ListEndpointTest extends ProcessCommandTestSupport {
         }
     }
 
+    @Test
+    void testScopeTellsRemoteInternalAndLocalApart() throws Exception {
+        JsonObject root = buildEndpointStatus("myApp",
+                endpoint("kafka:orders", "in", "3", true),
+                endpoint("log:payment", "out", "3", false),
+                endpoint("exec:wc", "out", "1", false));
+        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));
+
+            assertEquals(0, command.doCall());
+            String output = printer.getOutput();
+            assertTrue(output.contains("SCOPE"), output);
+            assertFalse(output.contains("REMOTE"), output);
+            assertTrue(lineOf(output, "kafka:orders").contains("remote"), 
output);
+            assertTrue(lineOf(output, "log:payment").contains("internal"), 
output);
+            assertTrue(lineOf(output, "exec:wc").contains("local"), output);
+        }
+    }
+
+    @Test
+    void testJsonOutputKeepsRemoteAndAddsInternal() throws Exception {
+        JsonObject root = buildEndpointStatus("myApp",
+                endpoint("log:payment", "out", "3", false),
+                endpoint("exec:wc", "out", "1", false));
+        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));
+
+            assertEquals(0, command.doCall());
+            JsonArray rows = (JsonArray) 
Jsoner.deserialize(printer.getOutput().trim());
+            JsonObject log = (JsonObject) rows.get(0);
+            JsonObject exec = (JsonObject) rows.get(1);
+            assertEquals("log:payment", log.getString("uri"));
+            assertEquals(false, log.get("remote"));
+            assertEquals(true, log.get("internal"));
+            assertEquals(false, exec.get("internal"));
+        }
+    }
+
+    private static String lineOf(String output, String uri) {
+        return output.lines().filter(l -> 
l.contains(uri)).findFirst().orElse("");
+    }
+
+    private static JsonObject endpoint(String uri, String direction, String 
hits, boolean remote) {
+        JsonObject ep = endpoint(uri, direction, hits);
+        ep.put("remote", remote);
+        return ep;
+    }
+
     private static JsonObject buildEndpointStatus(String contextName, 
JsonObject... endpoints) {
         JsonArray epArr = new JsonArray();
         Collections.addAll(epArr, endpoints);
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/EndpointScopeTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/EndpointScopeTest.java
new file mode 100644
index 000000000000..636a132548f7
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/EndpointScopeTest.java
@@ -0,0 +1,53 @@
+/*
+ * 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.common;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class EndpointScopeTest {
+
+    @Test
+    void remoteAsTheRuntimeSaysElseInternalOrLocalByTheCatalog() {
+        assertThat(EndpointScope.of("kafka://orders", 
true)).isEqualTo(EndpointScope.REMOTE);
+        assertThat(EndpointScope.of("file://orders?noop=true", 
true)).isEqualTo(EndpointScope.REMOTE);
+        // Camel plumbing that stays inside the integration
+        assertThat(EndpointScope.of("direct://charge", 
false)).isEqualTo(EndpointScope.INTERNAL);
+        assertThat(EndpointScope.of("log:payment", 
false)).isEqualTo(EndpointScope.INTERNAL);
+        assertThat(EndpointScope.of("timer://tick?period=1000", 
false)).isEqualTo(EndpointScope.INTERNAL);
+        // not remote, yet it runs a process or code on this host
+        assertThat(EndpointScope.of("exec://wc", 
false)).isEqualTo(EndpointScope.LOCAL);
+        assertThat(EndpointScope.of("bean:greeter", 
false)).isEqualTo(EndpointScope.LOCAL);
+    }
+
+    @Test
+    void aComponentTheCatalogDoesNotKnowIsNeverInternal() {
+        assertThat(EndpointScope.of("my-custom://thing", 
false)).isEqualTo(EndpointScope.LOCAL);
+        assertThat(EndpointScope.of(null, 
false)).isEqualTo(EndpointScope.LOCAL);
+        assertThat(EndpointScope.isInternal("Direct")).isFalse();
+        assertThat(EndpointScope.isInternal("../direct")).isFalse();
+    }
+
+    @Test
+    void theSchemeIsWhatComesBeforeTheFirstColon() {
+        
assertThat(EndpointScope.scheme("seda://queue?size=10")).isEqualTo("seda");
+        assertThat(EndpointScope.scheme("log:info")).isEqualTo("log");
+        assertThat(EndpointScope.scheme("direct")).isEqualTo("direct");
+        assertThat(EndpointScope.scheme(null)).isNull();
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointInfo.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointInfo.java
index 21bef41c3e1c..214e2b6a6034 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointInfo.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointInfo.java
@@ -24,6 +24,8 @@ class EndpointInfo {
     long hits;
     boolean stub;
     boolean remote;
+    /** remote, internal or local, as EndpointScope tells them apart */
+    String scope;
     long minBodySize = -1;
     long maxBodySize = -1;
     long meanBodySize = -1;
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointsTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointsTab.java
index 4b64ba726e07..c2a5483c2a0a 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointsTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointsTab.java
@@ -44,6 +44,7 @@ import dev.tamboui.widgets.table.Cell;
 import dev.tamboui.widgets.table.Row;
 import dev.tamboui.widgets.table.Table;
 import org.apache.camel.catalog.CamelCatalog;
+import org.apache.camel.dsl.jbang.core.common.EndpointScope;
 import org.apache.camel.tooling.model.BaseOptionModel;
 import org.apache.camel.tooling.model.ComponentModel;
 import org.apache.camel.util.json.JsonArray;
@@ -255,7 +256,7 @@ class EndpointsTab extends AbstractTableTab {
                 
cells.add(rightCell(FlowHelper.sizeToString(ep.meanHeadersSize), 10));
             }
             cells.add(centerCell(ep.stub ? TuiIcons.CHECK : "", 6));
-            cells.add(centerCell(ep.remote ? TuiIcons.CHECK : "", 8));
+            cells.add(scopeCell(ep.scope));
             cells.add(Cell.from(ep.uri != null ? TuiHelper.displayUri(ep.uri) 
: ""));
             rows.add(Row.from(cells));
         }
@@ -275,7 +276,7 @@ class EndpointsTab extends AbstractTableTab {
             headerCells.add(rightCell(sortLabel("HDR", "hdr"), 10, 
sortStyle("hdr")));
         }
         headerCells.add(centerCell("STUB", 6, Style.EMPTY.bold()));
-        headerCells.add(centerCell("REMOTE", 8, Style.EMPTY.bold()));
+        headerCells.add(Cell.from(Span.styled("SCOPE", Style.EMPTY.bold())));
         headerCells.add(Cell.from(Span.styled(sortLabel("URI", "uri"), 
sortStyle("uri"))));
 
         List<Constraint> widths = new ArrayList<>();
@@ -747,6 +748,19 @@ class EndpointsTab extends AbstractTableTab {
         return DocHelper.loadHelpText("endpoints");
     }
 
+    /** The scope of an endpoint: remote in the colour the diagrams draw the 
edges of the integration in. */
+    private static Cell scopeCell(String scope) {
+        if (scope == null) {
+            return Cell.from("");
+        }
+        Style style = switch (scope) {
+            case EndpointScope.REMOTE -> 
Style.EMPTY.fg(Theme.diagramExternal());
+            case EndpointScope.INTERNAL -> Theme.muted();
+            default -> Style.EMPTY;
+        };
+        return Cell.from(Span.styled(scope, style));
+    }
+
     @Override
     public JsonObject getTableDataAsJson() {
         IntegrationInfo info = ctx.findSelectedIntegration();
@@ -764,6 +778,7 @@ class EndpointsTab extends AbstractTableTab {
             row.put("routeId", ei.routeId);
             row.put("hits", ei.hits);
             row.put("remote", ei.remote);
+            row.put("scope", ei.scope);
             row.put("stub", ei.stub);
             if (ei.meanBodySize >= 0) {
                 row.put("meanBodySize", ei.meanBodySize);
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParser.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParser.java
index 777a76008d72..bd714eeee0fd 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParser.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParser.java
@@ -28,6 +28,7 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 
+import org.apache.camel.dsl.jbang.core.common.EndpointScope;
 import org.apache.camel.dsl.jbang.core.common.ProcessHelper;
 import org.apache.camel.dsl.jbang.core.common.VersionHelper;
 import org.apache.camel.util.TimeUtils;
@@ -562,6 +563,8 @@ final class StatusParser {
                         int idx = ep.uri.indexOf(':');
                         ep.component = idx > 0 ? ep.uri.substring(0, idx) : 
ep.uri;
                     }
+                    // here on the refresh thread, as the first look at the 
catalog loads it
+                    ep.scope = EndpointScope.of(ep.uri, ep.remote);
                     info.endpoints.add(ep);
                 }
             }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/endpoints.md
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/endpoints.md
index 35ea3cffc357..68d24c3f8440 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/endpoints.md
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/endpoints.md
@@ -14,17 +14,17 @@ identifies both the component and the specific resource.
 - **BODY** — Average message body size in bytes passing through this endpoint. 
Helps identify which endpoints handle large payloads
 - **HDR** — Average message headers size in bytes. Large headers may indicate 
excessive metadata being passed
 - **STUB** — Marked `x` if the endpoint has been replaced by a stub component 
(useful during testing to avoid connecting to real external systems)
-- **REMOTE** — Marked `x` if the endpoint connects to an external system 
outside the JVM (e.g., `kafka`, `http`, `ftp`). Local endpoints like `seda`, 
`direct`, `log` are not remote
+- **SCOPE** — How far the endpoint reaches: `remote`, an external system 
outside the JVM (e.g., `kafka`, `http`, `ftp`, `file`); `internal`, Camel 
plumbing that stays inside the integration (`direct`, `seda`, `log`, `timer`, 
`mock`); or `local`, neither: it can run code or a process, or read a file or a 
resource, on this host (`exec`, `bean`, `class`, `language`, the template 
components)
 - **URI** — Full endpoint URI with parameters (e.g., 
`timer://hello?period=2000`)
 
 ## Example Screen
 
 ```
- COMPONENT  ROUTE          DIR  TOTAL  BODY  HDR  STUB  REMOTE  URI
- timer      timer-to-log   in   21     0     0                  
timer://hello?period=2000
- timer      timer-to-seda  in   14     0     0                  
timer://pump?period=3000
- seda       seda-consumer  in   14     14    0                  seda://queue
- seda       timer-to-seda  out  14     14    0                  seda://queue
+ COMPONENT  ROUTE          DIR  TOTAL  BODY  HDR  STUB  SCOPE     URI
+ timer      timer-to-log   in   21     0     0           internal  
timer://hello?period=2000
+ timer      timer-to-seda  in   14     0     0           internal  
timer://pump?period=3000
+ seda       seda-consumer  in   14     14    0           internal  seda://queue
+ seda       timer-to-seda  out  14     14    0           internal  seda://queue
 ```
 
 Notice that `seda://queue` appears twice — once as `in` (the
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointsTabRenderTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointsTabRenderTest.java
index 5c99b27db1a6..5355400b4c7c 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointsTabRenderTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointsTabRenderTest.java
@@ -27,9 +27,12 @@ import dev.tamboui.terminal.Frame;
 import dev.tamboui.text.Span;
 import dev.tamboui.tui.event.KeyEvent;
 import dev.tamboui.tui.event.KeyModifiers;
+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.assertj.core.api.Assertions.assertThat;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 /**
@@ -204,6 +207,29 @@ class EndpointsTabRenderTest {
         assertTrue(bar.views().stream().anyMatch(v -> 
v.label().equals("Detail")), "View bar should list Detail");
     }
 
+    @Test
+    void theScopeColumnTellsRemoteInternalAndLocalApart() {
+        addEndpoint("kafka", "kafka://orders", "in", "intake", 3).scope = 
"remote";
+        addEndpoint("direct", "direct://charge", "out", "intake", 3).scope = 
"internal";
+        addEndpoint("exec", "exec://wc", "out", "report", 1).scope = "local";
+
+        EndpointsTab tab = new EndpointsTab(ctx, new MetricsCollector());
+        String rendered = TuiTestHelper.renderToString(tab, 140, 30);
+
+        assertThat(rendered).contains("SCOPE").doesNotContain("REMOTE");
+        assertThat(line(rendered, "kafka://orders")).contains("remote");
+        assertThat(line(rendered, "direct://charge")).contains("internal");
+        assertThat(line(rendered, "exec://wc")).contains("local");
+        // and for the tui_get_table tool
+        JsonArray rows = (JsonArray) tab.getTableDataAsJson().get("rows");
+        assertThat(rows).extracting(r -> ((JsonObject) r).getString("scope"))
+                .containsExactlyInAnyOrder("remote", "internal", "local");
+    }
+
+    private static String line(String rendered, String uri) {
+        return rendered.lines().filter(l -> 
l.contains(uri)).findFirst().orElse("");
+    }
+
     // ---- Helper methods ----
 
     private EndpointInfo addEndpoint(String component, String uri, String 
direction, String routeId, long hits) {
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParserTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParserTest.java
index d2b290c910e5..fd1177b48885 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParserTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParserTest.java
@@ -17,6 +17,7 @@
 package org.apache.camel.dsl.jbang.core.commands.tui;
 
 import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
 
 import org.apache.camel.util.json.JsonArray;
@@ -52,6 +53,28 @@ class StatusParserTest {
         assertEquals(5, info.state);
     }
 
+    @Test
+    void anEndpointGetsItsScopeFromTheRemoteFlagAndTheCatalog() {
+        JsonArray endpoints = new JsonArray();
+        for (String[] e : new String[][] {
+                { "kafka://orders", "true" }, { "log://payment", "false" },
+                { "exec://wc", "false" } }) {
+            JsonObject ep = new JsonObject();
+            ep.put("uri", e[0]);
+            ep.put("remote", Boolean.parseBoolean(e[1]));
+            endpoints.add(ep);
+        }
+        JsonObject holder = new JsonObject();
+        holder.put("endpoints", endpoints);
+        JsonObject root = new JsonObject();
+        root.put("context", new JsonObject());
+        root.put("endpoints", holder);
+
+        IntegrationInfo info = 
StatusParser.parseIntegration(ProcessHandle.current(), root);
+
+        assertEquals(List.of("remote", "internal", "local"), 
info.endpoints.stream().map(ep -> ep.scope).toList());
+    }
+
     @Test
     void aFailedReloadIsReadAsCamelPsReadsIt() {
         JsonObject root = new JsonObject();

Reply via email to