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

davsclaus pushed a commit to branch feature/CAMEL-24003-cli-activity-connector
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to 
refs/heads/feature/CAMEL-24003-cli-activity-connector by this push:
     new 95e4bd3863f5 CAMEL-24003: Add camel get activity CLI command
95e4bd3863f5 is described below

commit 95e4bd3863f5e1b6a57cb616bf34dace77148e27
Author: Claus Ibsen <[email protected]>
AuthorDate: Sat Jul 11 10:50:53 2026 +0200

    CAMEL-24003: Add camel get activity CLI command
    
    New command that reads {pid}-activity.json and displays a table of
    recent completed exchanges with PID, name, exchange ID, route,
    status (OK/FAILED), elapsed time, since, and endpoint URI.
    
    Supports --watch, --filter (by route ID), --sort, --limit,
    --short-uri, --wide-uri, and --json output.
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../jbang-commands/camel-jbang-get-activity.adoc   |  32 +++
 .../ROOT/pages/jbang-commands/camel-jbang-get.adoc |   1 +
 .../META-INF/camel-jbang-commands-metadata.json    |   2 +-
 .../dsl/jbang/core/commands/CamelCommand.java      |   4 +
 .../dsl/jbang/core/commands/CamelJBangMain.java    |   1 +
 .../jbang/core/commands/process/ListActivity.java  | 293 +++++++++++++++++++++
 6 files changed, 332 insertions(+), 1 deletion(-)

diff --git 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-get-activity.adoc
 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-get-activity.adoc
new file mode 100644
index 000000000000..2d3bd89014b2
--- /dev/null
+++ 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-get-activity.adoc
@@ -0,0 +1,32 @@
+
+// AUTO-GENERATED by camel-package-maven-plugin - DO NOT EDIT THIS FILE
+= camel get activity
+
+Get recent completed exchange activity
+
+
+== Usage
+
+[source,bash]
+----
+camel get activity [options]
+----
+
+
+
+== Options
+
+[cols="2,5,1,2",options="header"]
+|===
+| Option | Description | Default | Type
+| `--filter` | Filter activity by route ID |  | String
+| `--json` | Output in JSON Format |  | boolean
+| `--limit` | Filter activity by limiting to the given number of rows |  | int
+| `--short-uri` | List endpoint URI without query parameters (short) |  | 
boolean
+| `--sort` | Sort by pid, name, age, elapsed, or since | since | String
+| `--watch` | Execute periodically and showing output fullscreen |  | boolean
+| `--wide-uri` | List endpoint URI in full details |  | boolean
+| `-h,--help` | Display the help and sub-commands |  | boolean
+|===
+
+
diff --git 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-get.adoc 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-get.adoc
index 5365803b1101..1e4f3601dfc9 100644
--- a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-get.adoc
+++ b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-get.adoc
@@ -18,6 +18,7 @@ camel get [options]
 [cols="2,5",options="header"]
 |===
 | Subcommand | Description
+| xref:jbang-commands/camel-jbang-get-activity.adoc[activity] | Get recent 
completed exchange activity
 | xref:jbang-commands/camel-jbang-get-bean.adoc[bean] | List beans in a 
running Camel integration
 | xref:jbang-commands/camel-jbang-get-blocked.adoc[blocked] | Get blocked 
messages of Camel integrations
 | xref:jbang-commands/camel-jbang-get-circuit-breaker.adoc[circuit-breaker] | 
Get status of Circuit Breaker EIPs
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
 
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
index 92d22a2b1b27..092515439667 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
@@ -15,7 +15,7 @@
     { "name": "eval", "fullName": "eval", "description": "Evaluate Camel 
expressions and scripts", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.EvalCommand", "options": [ { "names": 
"-h,--help", "description": "Display the help and sub-commands", "javaType": 
"boolean", "type": "boolean" } ], "subcommands": [ { "name": "expression", 
"fullName": "eval expression", "description": "Evaluates Camel expression", 
"sourceClass": "org.apache.camel.dsl.jbang.core.commands.action.EvalEx [...]
     { "name": "explain", "fullName": "explain", "description": "Explain what a 
Camel route does using AI\/LLM", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.Explain", "options": [ { "names": 
"--api-key", "description": "API key for authentication. Also reads 
ANTHROPIC_API_KEY, OPENAI_API_KEY, or LLM_API_KEY env vars", "javaType": 
"java.lang.String", "type": "string" }, { "names": "--api-type", "description": 
"API type: 'ollama', 'openai' (OpenAI-compatible), or 'anthropic' (A [...]
     { "name": "export", "fullName": "export", "description": "Export to other 
runtimes (Camel Main, Spring Boot, or Quarkus)", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.Export", "options": [ { "names": 
"--build-property", "description": "Maven build properties, ex. 
--build-property=prop1=foo", "javaType": "java.util.List", "type": "array" }, { 
"names": "--camel-spring-boot-version", "description": "Camel version to use 
with Spring Boot", "javaType": "java.lang.String", "ty [...]
-    { "name": "get", "fullName": "get", "description": "Get status of Camel 
integrations", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.process.CamelStatus", "options": [ { 
"names": "--watch", "description": "Execute periodically and showing output 
fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": 
"-h,--help", "description": "Display the help and sub-commands", "javaType": 
"boolean", "type": "boolean" } ], "subcommands": [ { "name": "bean", 
"fullName": "get  [...]
+    { "name": "get", "fullName": "get", "description": "Get status of Camel 
integrations", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.process.CamelStatus", "options": [ { 
"names": "--watch", "description": "Execute periodically and showing output 
fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": 
"-h,--help", "description": "Display the help and sub-commands", "javaType": 
"boolean", "type": "boolean" } ], "subcommands": [ { "name": "activity", 
"fullName": " [...]
     { "name": "harden", "fullName": "harden", "description": "Suggest security 
hardening for Camel routes using AI\/LLM", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.Harden", "options": [ { "names": 
"--api-key", "description": "API key for authentication. Also reads 
OPENAI_API_KEY or LLM_API_KEY env vars", "javaType": "java.lang.String", 
"type": "string" }, { "names": "--api-type", "description": "API type: 'ollama' 
or 'openai' (OpenAI-compatible)", "defaultValue": "ollama", [...]
     { "name": "hawtio", "fullName": "hawtio", "description": "Launch Hawtio 
web console", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.process.Hawtio", "options": [ { 
"names": "--host", "description": "Hostname to bind the Hawtio web console to", 
"defaultValue": "127.0.0.1", "javaType": "java.lang.String", "type": "string" 
}, { "names": "--openUrl", "description": "To automatic open Hawtio web console 
in the web browser", "defaultValue": "true", "javaType": "boolean", "type": 
[...]
     { "name": "infra", "fullName": "infra", "description": "List and Run 
external services for testing and prototyping", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.infra.InfraCommand", "options": [ { 
"names": "--json", "description": "Output in JSON Format", "javaType": 
"boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display 
the help and sub-commands", "javaType": "boolean", "type": "boolean" } ], 
"subcommands": [ { "name": "get", "fullName": "infra  [...]
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelCommand.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelCommand.java
index 4d675773de1d..d8766317ac37 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelCommand.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelCommand.java
@@ -135,6 +135,10 @@ public abstract class CamelCommand implements 
Callable<Integer> {
         return CommandLineHelper.getCamelDir().resolve(pid + "-history.json");
     }
 
+    public Path getActivityFile(String pid) {
+        return CommandLineHelper.getCamelDir().resolve(pid + "-activity.json");
+    }
+
     public Path getRunBackgroundLogFile(String uuid) {
         return CommandLineHelper.getCamelDir().resolve(uuid + "-run.log");
     }
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java
index 93625b9906df..87014e76d6f5 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java
@@ -155,6 +155,7 @@ public class CamelJBangMain implements Callable<Integer> {
                 .addSubcommand("ask", new CommandLine(new Ask(this)))
                 .addSubcommand("harden", new CommandLine(new Harden(this)))
                 .addSubcommand("get", new CommandLine(new CamelStatus(this))
+                        .addSubcommand("activity", new CommandLine(new 
ListActivity(this)))
                         .addSubcommand("bean", new CommandLine(new 
CamelBeanDump(this)))
                         .addSubcommand("blocked", new CommandLine(new 
ListBlocked(this)))
                         .addSubcommand("circuit-breaker", new CommandLine(new 
ListCircuitBreaker(this)))
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListActivity.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListActivity.java
new file mode 100644
index 000000000000..8568d32eb1d0
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListActivity.java
@@ -0,0 +1,293 @@
+/*
+ * 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.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import com.github.freva.asciitable.AsciiTable;
+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.ProcessHelper;
+import org.apache.camel.dsl.jbang.core.common.TerminalWidthHelper;
+import org.apache.camel.support.PatternHelper;
+import org.apache.camel.util.TimeUtils;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.apache.camel.util.json.Jsoner;
+import picocli.CommandLine;
+import picocli.CommandLine.Command;
+
+@Command(name = "activity", description = "Get recent completed exchange 
activity",
+         sortOptions = false, showDefaultValues = true,
+         footer = {
+                 "%nExamples:",
+                 "  camel get activity",
+                 "  camel get activity --filter=route1",
+                 "  camel get activity --watch" })
+public class ListActivity extends ProcessWatchCommand {
+
+    public static class PidNameAgeElapsedSinceCandidates implements 
Iterable<String> {
+
+        public PidNameAgeElapsedSinceCandidates() {
+        }
+
+        @Override
+        public Iterator<String> iterator() {
+            return List.of("pid", "name", "age", "elapsed", 
"since").iterator();
+        }
+    }
+
+    @CommandLine.Parameters(description = "Name or pid of running Camel 
integration", arity = "0..1")
+    String name = "*";
+
+    @CommandLine.Option(names = { "--sort" }, completionCandidates = 
PidNameAgeElapsedSinceCandidates.class,
+                        description = "Sort by pid, name, age, elapsed, or 
since", defaultValue = "since")
+    String sort;
+
+    @CommandLine.Option(names = { "--limit" },
+                        description = "Filter activity by limiting to the 
given number of rows")
+    int limit;
+
+    @CommandLine.Option(names = { "--filter" },
+                        description = "Filter activity by route ID")
+    String filter;
+
+    @CommandLine.Option(names = { "--short-uri" },
+                        description = "List endpoint URI without query 
parameters (short)")
+    boolean shortUri;
+
+    @CommandLine.Option(names = { "--wide-uri" },
+                        description = "List endpoint URI in full details")
+    boolean wideUri;
+
+    public ListActivity(CamelJBangMain main) {
+        super(main);
+    }
+
+    @Override
+    public Integer doProcessWatchCall() throws Exception {
+        List<Row> rows = new ArrayList<>();
+
+        if (filter != null && !filter.endsWith("*")) {
+            filter += "*";
+        }
+
+        List<Long> pids = findPids(name);
+        ProcessHandle.allProcesses()
+                .filter(ph -> pids.contains(ph.pid()))
+                .forEach(ph -> {
+                    JsonObject root = loadStatus(ph.pid());
+                    if (root != null) {
+                        JsonObject context = (JsonObject) root.get("context");
+                        if (context != null) {
+                            String integrationName = context.getString("name");
+                            if ("CamelJBang".equals(integrationName)) {
+                                integrationName = 
ProcessHelper.extractName(root, ph);
+                            }
+                            long uptime = extractSince(ph);
+                            String age = TimeUtils.printSince(uptime);
+
+                            JsonObject activity = loadActivityFile(ph.pid());
+                            if (activity != null) {
+                                JsonArray array = (JsonArray) 
activity.get("activity");
+                                if (array != null) {
+                                    for (int i = 0; i < array.size(); i++) {
+                                        JsonObject o = (JsonObject) 
array.get(i);
+                                        Row row = new Row();
+                                        row.pid = Long.toString(ph.pid());
+                                        row.name = integrationName;
+                                        row.uptime = uptime;
+                                        row.age = age;
+                                        row.exchangeId = 
o.getString("exchangeId");
+                                        row.routeId = o.getString("routeId");
+                                        row.failed = 
o.getBooleanOrDefault("failed", false);
+                                        row.status = row.failed ? "FAILED" : 
"OK";
+                                        row.elapsed = 
o.getLongOrDefault("elapsed", 0);
+                                        row.timestamp = 
o.getLongOrDefault("timestamp", 0);
+                                        row.endpointUri = 
o.getString("endpointUri");
+                                        if (row.timestamp > 0) {
+                                            long ago = 
System.currentTimeMillis() - row.timestamp;
+                                            row.since = 
TimeUtils.printSince(ago);
+                                        } else {
+                                            row.since = "";
+                                        }
+
+                                        boolean add = true;
+                                        if (filter != null) {
+                                            String f = filter;
+                                            boolean negate = 
filter.startsWith("-");
+                                            if (negate) {
+                                                f = f.substring(1);
+                                            }
+                                            boolean match = 
PatternHelper.matchPattern(row.routeId, f);
+                                            if (negate) {
+                                                match = !match;
+                                            }
+                                            if (!match) {
+                                                add = false;
+                                            }
+                                        }
+                                        if (add) {
+                                            rows.add(row);
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                });
+
+        // sort rows
+        rows.sort(this::sortRow);
+
+        if (limit > 0 && rows.size() > limit) {
+            rows.subList(limit, rows.size()).clear();
+        }
+
+        if (!rows.isEmpty()) {
+            printTable(rows);
+        }
+
+        return 0;
+    }
+
+    private JsonObject loadActivityFile(long pid) {
+        try {
+            Path f = getActivityFile(Long.toString(pid));
+            if (f != null && Files.exists(f)) {
+                String text = Files.readString(f);
+                if (text != null && !text.isBlank()) {
+                    return (JsonObject) Jsoner.deserialize(text);
+                }
+            }
+        } catch (Exception e) {
+            // ignore
+        }
+        return null;
+    }
+
+    protected void printTable(List<Row> rows) {
+        if (jsonOutput) {
+            printer().println(Jsoner.serialize(rows.stream().map(r -> {
+                JsonObject jo = new JsonObject();
+                jo.put("pid", r.pid);
+                jo.put("name", r.name);
+                jo.put("age", r.age);
+                jo.put("exchangeId", r.exchangeId);
+                jo.put("routeId", r.routeId);
+                jo.put("status", r.status);
+                jo.put("elapsed", r.elapsed);
+                jo.put("since", r.since);
+                if (r.endpointUri != null) {
+                    jo.put("endpointUri", r.endpointUri);
+                }
+                return jo;
+            }).collect(Collectors.toList())));
+            return;
+        }
+
+        // Flexible column: ENDPOINT (40/140)
+        // Fixed columns: 
PID(8)+NAME(30)+AGE(8)+EXCHANGE(40)+ROUTE(25)+STATUS(6)+ELAPSED(8)+SINCE(8) ~= 
133
+        int fixedW = 133;
+        int numCols = 9;
+        int tw = terminalWidth();
+        int uriW = TerminalWidthHelper.flexWidth(tw, fixedW, 
TerminalWidthHelper.noBorderOverhead(numCols), 20, 40);
+        int uriWideW = TerminalWidthHelper.flexWidth(tw, fixedW, 
TerminalWidthHelper.noBorderOverhead(numCols), 20, 140);
+        printer().println(AsciiTable.getTable(AsciiTable.NO_BORDERS, rows, 
Arrays.asList(
+                new 
Column().header("PID").headerAlign(HorizontalAlign.CENTER).with(r -> r.pid),
+                new 
Column().header("NAME").dataAlign(HorizontalAlign.LEFT).maxWidth(30, 
OverflowBehaviour.ELLIPSIS_RIGHT)
+                        .with(r -> r.name),
+                new 
Column().header("AGE").headerAlign(HorizontalAlign.CENTER).with(r -> r.age),
+                new 
Column().header("EXCHANGE").dataAlign(HorizontalAlign.LEFT).maxWidth(40, 
OverflowBehaviour.ELLIPSIS_RIGHT)
+                        .with(r -> r.exchangeId),
+                new 
Column().header("ROUTE").dataAlign(HorizontalAlign.LEFT).maxWidth(25, 
OverflowBehaviour.ELLIPSIS_RIGHT)
+                        .with(r -> r.routeId),
+                new Column().header("STATUS").dataAlign(HorizontalAlign.CENTER)
+                        .with(r -> r.status),
+                new 
Column().header("ELAPSED").headerAlign(HorizontalAlign.RIGHT).dataAlign(HorizontalAlign.RIGHT)
+                        .with(r -> r.elapsed + "ms"),
+                new 
Column().header("SINCE").headerAlign(HorizontalAlign.CENTER)
+                        .with(r -> r.since),
+                new 
Column().header("ENDPOINT").visible(!wideUri).dataAlign(HorizontalAlign.LEFT)
+                        .maxWidth(uriW, OverflowBehaviour.ELLIPSIS_RIGHT)
+                        .with(this::getUri),
+                new 
Column().header("ENDPOINT").visible(wideUri).dataAlign(HorizontalAlign.LEFT)
+                        .maxWidth(uriWideW, OverflowBehaviour.NEWLINE)
+                        .with(this::getUri))));
+    }
+
+    private String getUri(Row r) {
+        String u = r.endpointUri;
+        if (u == null) {
+            return "";
+        }
+        if (shortUri) {
+            int pos = u.indexOf('?');
+            if (pos > 0) {
+                u = u.substring(0, pos);
+            }
+        }
+        return u;
+    }
+
+    protected int sortRow(Row o1, Row o2) {
+        String s = sort;
+        int negate = 1;
+        if (s.startsWith("-")) {
+            s = s.substring(1);
+            negate = -1;
+        }
+        switch (s) {
+            case "pid":
+                return Long.compare(Long.parseLong(o1.pid), 
Long.parseLong(o2.pid)) * negate;
+            case "name":
+                return o1.name.compareToIgnoreCase(o2.name) * negate;
+            case "age":
+                return Long.compare(o1.uptime, o2.uptime) * negate;
+            case "elapsed":
+                return Long.compare(o1.elapsed, o2.elapsed) * negate;
+            case "since":
+                return Long.compare(o2.timestamp, o1.timestamp) * negate;
+            default:
+                return 0;
+        }
+    }
+
+    static class Row {
+        String pid;
+        String name;
+        long uptime;
+        String age;
+        String exchangeId;
+        String routeId;
+        boolean failed;
+        String status;
+        long elapsed;
+        long timestamp;
+        String since;
+        String endpointUri;
+    }
+
+}

Reply via email to