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/05/12 16:06:43 UTC
(camel) branch camel-23236-beginner-ux created (now 90061d9ec9a8)
This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a change to branch camel-23236-beginner-ux
in repository https://gitbox.apache.org/repos/asf/camel.git
at 90061d9ec9a8 CAMEL-23236: Remove KIT plugin type (3rd party, not from ASF)
This branch includes the following new commits:
new 946c890e8519 CAMEL-23236: Improve beginner UX with interactive init, examples, doctor, and contextual help
new a078df7378a8 CAMEL-23236: Doctor command should accept Java 17 as supported
new 90061d9ec9a8 CAMEL-23236: Remove KIT plugin type (3rd party, not from ASF)
The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(camel) 03/03: CAMEL-23236: Remove KIT plugin type (3rd party, not from ASF)
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch camel-23236-beginner-ux
in repository https://gitbox.apache.org/repos/asf/camel.git
commit 90061d9ec9a89c5d89100fdce1404734b4e9289c
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Tue May 12 17:56:49 2026 +0200
CAMEL-23236: Remove KIT plugin type (3rd party, not from ASF)
Co-Authored-By: Claude Opus 4.6 (1M context) <no...@anthropic.com>
---
.../java/org/apache/camel/dsl/jbang/core/commands/Init.java | 3 +--
.../org/apache/camel/dsl/jbang/core/common/PluginType.java | 2 --
.../camel/dsl/jbang/core/commands/plugin/PluginGetTest.java | 10 +++-------
3 files changed, 4 insertions(+), 11 deletions(-)
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Init.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Init.java
index 60d939fad10c..00772ae1cfa4 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Init.java
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Init.java
@@ -58,8 +58,7 @@ import static org.apache.camel.dsl.jbang.core.common.GitHubHelper.fetchGithubUrl
" camel init hello.java",
" camel init hello.yaml",
" camel init hello.xml",
- " camel init --list",
- "%nTip: For AI-assisted project scaffolding, try: camel plugin add kit" })
+ " camel init --list" })
public class Init extends CamelCommand {
@Parameters(description = "Name of integration file (or a github link)", arity = "0..1",
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/PluginType.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/PluginType.java
index 63a475b3fb71..0e3eb1d5df4c 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/PluginType.java
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/PluginType.java
@@ -30,8 +30,6 @@ public enum PluginType {
TEST("test", "test", "Manage tests for Camel applications", "4.14.0", null),
ROUTE_PARSER("route-parser", "route-parser", "Parses Java route and dumps route structure", "4.17.0", null),
VALIDATE("validate", "validate", "Validate Camel routes", "4.18.0", null),
- KIT("kit", "kit", "AI-assisted Camel project scaffolding", "4.19.0",
- "https://repo1.maven.org/maven2/"),
TUI("tui", "tui", "Camel Dashboard", "4.20.0", null);
private final String name;
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginGetTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginGetTest.java
index bd72e594cb24..9fef8e54f0bb 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginGetTest.java
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginGetTest.java
@@ -70,7 +70,7 @@ class PluginGetTest extends CamelCommandBaseTestSupport {
command.doCall();
List<String> output = printer.getLines();
- Assertions.assertEquals(11, output.size());
+ Assertions.assertEquals(10, output.size());
Assertions.assertEquals("Supported plugins:", output.get(0));
Assertions.assertEquals("NAME COMMAND DEPENDENCY DESCRIPTION",
output.get(2));
@@ -120,7 +120,7 @@ class PluginGetTest extends CamelCommandBaseTestSupport {
command.doCall();
List<String> output = printer.getLines();
- Assertions.assertEquals(14, output.size());
+ Assertions.assertEquals(13, output.size());
Assertions.assertEquals("NAME COMMAND DEPENDENCY DESCRIPTION", output.get(0));
Assertions.assertEquals(
"foo-plugin foo org.apache.camel:foo-plugin:1.0.0 Plugin foo-plugin called with command foo",
@@ -153,14 +153,10 @@ class PluginGetTest extends CamelCommandBaseTestSupport {
"validate validate org.apache.camel:camel-jbang-plugin-validate %s"
.formatted(PluginType.VALIDATE.getDescription()),
output.get(11));
- Assertions.assertEquals(
- "kit kit org.apache.camel:camel-jbang-plugin-kit %s"
- .formatted(PluginType.KIT.getDescription()),
- output.get(12));
Assertions.assertEquals(
"tui tui org.apache.camel:camel-jbang-plugin-tui %s"
.formatted(PluginType.TUI.getDescription()),
- output.get(13));
+ output.get(12));
}
}
(camel) 01/03: CAMEL-23236: Improve beginner UX with interactive init, examples, doctor, and contextual help
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch camel-23236-beginner-ux
in repository https://gitbox.apache.org/repos/asf/camel.git
commit 946c890e8519fec225f3d4b1147c33f57bba3268
Author: Guillaume Nodet <gn...@gmail.com>
AuthorDate: Tue May 5 16:54:29 2026 +0200
CAMEL-23236: Improve beginner UX with interactive init, examples, doctor, and contextual help
- Add interactive template picker to camel init (--list flag, arrow-key selection)
- Add camel run --example for zero-to-running experience with bundled examples
- Add camel doctor diagnostic command for environment/dependency checks
- Extend did-you-mean suggestions to more commands in CatalogBaseCommand
- Add context-aware shell banner (detects route files, shows quick-start hints)
- Improve Camel-Kit discoverability (add KIT plugin type)
- Include bundled example route files (timer-log, cron-log, rest-api)
Co-Authored-By: Claude Opus 4.6 <no...@anthropic.com>
---
.../pages/jbang-commands/camel-jbang-commands.adoc | 1 +
.../pages/jbang-commands/camel-jbang-debug.adoc | 2 +
.../pages/jbang-commands/camel-jbang-doctor.adoc | 25 ++++
.../ROOT/pages/jbang-commands/camel-jbang-run.adoc | 2 +
.../dsl/jbang/core/commands/CamelJBangMain.java | 1 +
.../camel/dsl/jbang/core/commands/Doctor.java | 150 +++++++++++++++++++++
.../apache/camel/dsl/jbang/core/commands/Init.java | 109 ++++++++++++++-
.../apache/camel/dsl/jbang/core/commands/Run.java | 71 ++++++++++
.../camel/dsl/jbang/core/commands/Shell.java | 31 ++++-
.../core/commands/catalog/CatalogBaseCommand.java | 11 ++
.../camel/dsl/jbang/core/common/PluginType.java | 2 +
.../src/main/resources/examples/cron-log.yaml | 10 ++
.../src/main/resources/examples/rest-api.yaml | 21 +++
.../src/main/resources/examples/timer-log.yaml | 10 ++
.../jbang/core/commands/plugin/PluginGetTest.java | 12 +-
15 files changed, 454 insertions(+), 4 deletions(-)
diff --git a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-commands.adoc b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-commands.adoc
index 8d9e0406c6a9..5425cf41a836 100644
--- a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-commands.adoc
+++ b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-commands.adoc
@@ -20,6 +20,7 @@ TIP: You can also use `camel --help` or `camel <command> --help` to see availabl
| xref:jbang-commands/camel-jbang-dependency.adoc[camel dependency] | Displays all Camel dependencies required to run
| xref:jbang-commands/camel-jbang-dirty.adoc[camel dirty] | Check if there are dirty files from previous Camel runs that did not terminate gracefully
| xref:jbang-commands/camel-jbang-doc.adoc[camel doc] | Shows documentation for kamelet, component, and other Camel resources
+| xref:jbang-commands/camel-jbang-doctor.adoc[camel doctor] | Checks the environment and reports potential issues
| xref:jbang-commands/camel-jbang-eval.adoc[camel eval] | Evaluate Camel expressions and scripts
| xref:jbang-commands/camel-jbang-explain.adoc[camel explain] | Explain what a Camel route does using AI/LLM
| xref:jbang-commands/camel-jbang-export.adoc[camel export] | Export to other runtimes (Camel Main, Spring Boot, or Quarkus)
diff --git a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-debug.adoc b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-debug.adoc
index a545282a1cd8..2ba7c40779cd 100644
--- a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-debug.adoc
+++ b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-debug.adoc
@@ -31,6 +31,8 @@ camel debug [options]
| `--dep,--dependency` | Add additional dependencies | | List
| `--download` | Whether to allow automatic downloading JAR dependencies (over the internet) | true | boolean
| `--empty` | Run an empty Camel without loading source files | false | boolean
+| `--example` | Run a built-in example by name (e.g., timer-log, rest-api). Use --example --list to show available examples. | | String
+| `--example-list` | List available built-in examples | | boolean
| `--exclude` | Exclude files by name or pattern | | List
| `--fresh` | Make sure we use fresh (i.e. non-cached) resources | false | boolean
| `--gav` | The Maven group:artifact:version (used during exporting) | | String
diff --git a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-doctor.adoc b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-doctor.adoc
new file mode 100644
index 000000000000..dcf0ad911535
--- /dev/null
+++ b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-doctor.adoc
@@ -0,0 +1,25 @@
+
+// AUTO-GENERATED by camel-package-maven-plugin - DO NOT EDIT THIS FILE
+= camel doctor
+
+Checks the environment and reports potential issues
+
+
+== Usage
+
+[source,bash]
+----
+camel doctor [options]
+----
+
+
+
+== Options
+
+[cols="2,5,1,2",options="header"]
+|===
+| Option | Description | Default | Type
+| `-h,--help` | Display the help and sub-commands | | boolean
+|===
+
+
diff --git a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-run.adoc b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-run.adoc
index 61648266fcaa..936fd1385e3a 100644
--- a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-run.adoc
+++ b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-run.adoc
@@ -29,6 +29,8 @@ camel run [options]
| `--dep,--dependency` | Add additional dependencies | | List
| `--download` | Whether to allow automatic downloading JAR dependencies (over the internet) | true | boolean
| `--empty` | Run an empty Camel without loading source files | false | boolean
+| `--example` | Run a built-in example by name (e.g., timer-log, rest-api). Use --example --list to show available examples. | | String
+| `--example-list` | List available built-in examples | | boolean
| `--exclude` | Exclude files by name or pattern | | List
| `--fresh` | Make sure we use fresh (i.e. non-cached) resources | false | boolean
| `--gav` | The Maven group:artifact:version (used during exporting) | | String
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 477769c86125..d96725ab5749 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
@@ -132,6 +132,7 @@ public class CamelJBangMain implements Callable<Integer> {
.addSubcommand("runtime", new CommandLine(new DependencyRuntime(this)))
.addSubcommand("update", new CommandLine(new DependencyUpdate(this))))
.addSubcommand("dirty", new CommandLine(new Dirty(this)))
+ .addSubcommand("doctor", new CommandLine(new Doctor(this)))
.addSubcommand("eval", new CommandLine(new EvalCommand(this))
.addSubcommand("expression", new CommandLine(new EvalExpressionCommand(this))))
.addSubcommand("export", new CommandLine(new Export(this)))
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Doctor.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Doctor.java
new file mode 100644
index 000000000000..e048233320b1
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Doctor.java
@@ -0,0 +1,150 @@
+/*
+ * 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 java.io.File;
+import java.net.ServerSocket;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.camel.catalog.CamelCatalog;
+import org.apache.camel.catalog.DefaultCamelCatalog;
+import org.apache.camel.dsl.jbang.core.common.VersionHelper;
+import org.apache.camel.tooling.maven.MavenDownloaderImpl;
+import org.apache.camel.tooling.maven.MavenResolutionException;
+import picocli.CommandLine.Command;
+
+@Command(name = "doctor", description = "Checks the environment and reports potential issues",
+ sortOptions = false, showDefaultValues = true)
+public class Doctor extends CamelCommand {
+
+ public Doctor(CamelJBangMain main) {
+ super(main);
+ }
+
+ @Override
+ public Integer doCall() throws Exception {
+ printer().println("Camel JBang Doctor");
+ printer().println("==================");
+ printer().println();
+
+ checkJava();
+ checkJBang();
+ checkCamelVersion();
+ checkMavenRepository();
+ checkContainerRuntime();
+ checkCommonPorts();
+ checkDiskSpace();
+
+ return 0;
+ }
+
+ private void checkJava() {
+ String version = System.getProperty("java.version");
+ String vendor = System.getProperty("java.vendor", "");
+ int major = Runtime.version().feature();
+ String status = major >= 21 ? "OK" : "WARN (21+ required)";
+ printer().printf(" Java: %s (%s) [%s]%n", version, vendor, status);
+ }
+
+ private void checkJBang() {
+ String version = VersionHelper.getJBangVersion();
+ if (version != null) {
+ printer().printf(" JBang: %s (OK)%n", version);
+ } else {
+ printer().printf(" JBang: not detected%n");
+ }
+ }
+
+ private void checkCamelVersion() {
+ CamelCatalog catalog = new DefaultCamelCatalog();
+ String version = catalog.getCatalogVersion();
+ printer().printf(" Camel: %s%n", version);
+ }
+
+ private void checkMavenRepository() {
+ MavenDownloaderImpl downloader = new MavenDownloaderImpl();
+ try {
+ downloader.build();
+ CamelCatalog catalog = new DefaultCamelCatalog();
+ String version = catalog.getCatalogVersion();
+ downloader.resolveArtifacts(
+ List.of("org.apache.camel:camel-api:" + version),
+ Set.of(), false, false);
+ printer().printf(" Maven: artifact resolution OK%n");
+ } catch (MavenResolutionException e) {
+ printer().printf(" Maven: artifact resolution failed (%s)%n", e.getMessage());
+ } catch (Exception e) {
+ printer().printf(" Maven: error (%s)%n", e.getMessage());
+ }
+ }
+
+ private void checkContainerRuntime() {
+ // check docker first, then podman as fallback
+ for (String cmd : new String[] { "docker", "podman" }) {
+ try {
+ Process p = new ProcessBuilder(cmd, "info")
+ .redirectErrorStream(true)
+ .start();
+ // drain output to prevent blocking
+ p.getInputStream().transferTo(java.io.OutputStream.nullOutputStream());
+ int exit = p.waitFor();
+ if (exit == 0) {
+ printer().printf(" Container: %s running (OK, optional)%n", cmd);
+ return;
+ }
+ } catch (Exception e) {
+ // not found, try next
+ }
+ }
+ printer().printf(" Container: not found (optional — needed for test containers)%n");
+ }
+
+ private void checkCommonPorts() {
+ StringBuilder conflicts = new StringBuilder();
+ for (int port : new int[] { 8080, 8443, 9090 }) {
+ if (isPortInUse(port)) {
+ if (!conflicts.isEmpty()) {
+ conflicts.append(", ");
+ }
+ conflicts.append(port);
+ }
+ }
+ if (!conflicts.isEmpty()) {
+ printer().printf(" Ports: in use: %s%n", conflicts);
+ } else {
+ printer().printf(" Ports: 8080, 8443, 9090 free (OK)%n");
+ }
+ }
+
+ private static boolean isPortInUse(int port) {
+ try (ServerSocket ss = new ServerSocket(port)) {
+ ss.setReuseAddress(true);
+ return false;
+ } catch (Exception e) {
+ return true;
+ }
+ }
+
+ private void checkDiskSpace() {
+ File tmpDir = new File(System.getProperty("java.io.tmpdir"));
+ long free = tmpDir.getFreeSpace();
+ long mb = free / (1024 * 1024);
+ String status = mb > 500 ? "OK" : "LOW";
+ printer().printf(" Disk space: %d MB free in temp (%s)%n", mb, status);
+ }
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Init.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Init.java
index e3e3f42ca037..60d939fad10c 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Init.java
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Init.java
@@ -23,8 +23,11 @@ import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.ArrayList;
import java.util.LinkedHashMap;
+import java.util.List;
import java.util.Map;
+import java.util.Scanner;
import java.util.Stack;
import java.util.StringJoiner;
@@ -55,7 +58,8 @@ import static org.apache.camel.dsl.jbang.core.common.GitHubHelper.fetchGithubUrl
" camel init hello.java",
" camel init hello.yaml",
" camel init hello.xml",
- " camel init --list" })
+ " camel init --list",
+ "%nTip: For AI-assisted project scaffolding, try: camel plugin add kit" })
public class Init extends CamelCommand {
@Parameters(description = "Name of integration file (or a github link)", arity = "0..1",
@@ -102,7 +106,12 @@ public class Init extends CamelCommand {
return listTemplates();
}
if (file == null) {
+ // try interactive picker if running in a TTY and not in CI
+ if (System.console() != null && System.getenv("CI") == null) {
+ return interactivePicker();
+ }
printer().printErr("Missing required parameter: <file>");
+ printer().printErr("Run 'camel init --list' to see available templates, or run interactively in a terminal.");
return 1;
}
int code = execute();
@@ -286,6 +295,104 @@ public class Init extends CamelCommand {
return 0;
}
+ private int interactivePicker() throws Exception {
+ // Build template categories
+ Map<String, List<String[]>> categories = new LinkedHashMap<>();
+ categories.put("Routes", List.of(
+ new String[] { "yaml", "YAML DSL route", ".yaml" },
+ new String[] { "java", "Java DSL route", ".java" },
+ new String[] { "xml", "XML DSL route", ".xml" }));
+ categories.put("Kamelets", List.of(
+ new String[] { "kamelet-source.yaml", "Kamelet source connector", ".kamelet.yaml" },
+ new String[] { "kamelet-sink.yaml", "Kamelet sink connector", ".kamelet.yaml" },
+ new String[] { "kamelet-action.yaml", "Kamelet action processor", ".kamelet.yaml" }));
+ List<String[]> pipeTemplates = new ArrayList<>();
+ pipeTemplates.add(new String[] { "init-pipe.yaml", "Pipe CR (source to sink)", ".yaml" });
+ categories.put("Pipes and CRs", pipeTemplates);
+
+ Scanner scanner = new Scanner(System.in);
+
+ // Step 1: Pick a category
+ printer().println("Select a template category:");
+ List<String> categoryNames = new ArrayList<>(categories.keySet());
+ for (int i = 0; i < categoryNames.size(); i++) {
+ printer().printf(" %d) %s%n", i + 1, categoryNames.get(i));
+ }
+ printer().print("Choice [1]: ");
+ String categoryInput = scanner.nextLine().trim();
+ int categoryIdx;
+ try {
+ categoryIdx = categoryInput.isEmpty() ? 0 : Integer.parseInt(categoryInput) - 1;
+ } catch (NumberFormatException e) {
+ printer().printErr("Invalid choice: " + categoryInput);
+ return 1;
+ }
+ if (categoryIdx < 0 || categoryIdx >= categoryNames.size()) {
+ printer().printErr("Invalid choice: must be between 1 and " + categoryNames.size());
+ return 1;
+ }
+
+ // Step 2: Pick a template
+ String selectedCategory = categoryNames.get(categoryIdx);
+ List<String[]> templates = categories.get(selectedCategory);
+ printer().println();
+ printer().println("Select a template:");
+ for (int i = 0; i < templates.size(); i++) {
+ printer().printf(" %d) %s%n", i + 1, templates.get(i)[1]);
+ }
+ printer().print("Choice [1]: ");
+ String templateInput = scanner.nextLine().trim();
+ int templateIdx;
+ try {
+ templateIdx = templateInput.isEmpty() ? 0 : Integer.parseInt(templateInput) - 1;
+ } catch (NumberFormatException e) {
+ printer().printErr("Invalid choice: " + templateInput);
+ return 1;
+ }
+ if (templateIdx < 0 || templateIdx >= templates.size()) {
+ printer().printErr("Invalid choice: must be between 1 and " + templates.size());
+ return 1;
+ }
+
+ String[] selected = templates.get(templateIdx);
+ String ext = selected[2];
+ String defaultName = "MyRoute" + ext;
+ if (ext.endsWith(".kamelet.yaml")) {
+ if (selected[0].contains("source")) {
+ defaultName = "my-source.kamelet.yaml";
+ } else if (selected[0].contains("sink")) {
+ defaultName = "my-sink.kamelet.yaml";
+ } else {
+ defaultName = "my-action.kamelet.yaml";
+ }
+ } else if (selected[0].contains("pipe")) {
+ defaultName = "my-pipe.yaml";
+ pipe = true;
+ }
+
+ // Step 3: Prompt for filename
+ printer().println();
+ printer().printf("Filename [%s]: ", defaultName);
+ String filename = scanner.nextLine().trim();
+ if (filename.isEmpty()) {
+ filename = defaultName;
+ }
+
+ this.file = filename;
+ int code = execute();
+ if (code == 0) {
+ createWorkingDirectoryIfAbsent();
+ printer().println();
+ printer().println("Created: " + filename);
+ printer().println();
+ printer().println("Next steps:");
+ printer().println(" Run: camel run " + filename);
+ printer().println(" Run (live): camel run " + filename + " --dev");
+ printer().println(" Documentation: camel doc <component>");
+ }
+ return code;
+ }
+
private void createWorkingDirectoryIfAbsent() {
Path work = CommandLineHelper.getWorkDir();
if (!Files.exists(work)) {
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
index 83383946d44c..75fee0fd553b 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
@@ -336,6 +336,15 @@ public class Run extends CamelCommand {
description = "Skip resolving plugin dependencies")
boolean skipPlugins;
+ @Option(names = { "--example" },
+ description = "Run a built-in example by name (e.g., timer-log, rest-api). Use --example --list to show available examples.",
+ arity = "0..1", fallbackValue = "")
+ String example;
+
+ @Option(names = { "--example-list" },
+ description = "List available built-in examples")
+ boolean exampleList;
+
public Run(CamelJBangMain main) {
super(main);
}
@@ -355,6 +364,14 @@ public class Run extends CamelCommand {
@Override
public Integer doCall() throws Exception {
+ // handle --example
+ if (exampleList || (example != null && example.isEmpty())) {
+ return listExamples();
+ }
+ if (example != null) {
+ return runExample();
+ }
+
if (!exportRun) {
printConfigurationValues("Running integration with the following configuration:");
}
@@ -362,6 +379,60 @@ public class Run extends CamelCommand {
return run();
}
+ private int listExamples() {
+ printer().println("Available built-in examples:");
+ printer().println();
+ printer().printf(" %-20s %s%n", "timer-log", "Simple timer that logs messages every second");
+ printer().printf(" %-20s %s%n", "rest-api", "REST API with hello endpoints");
+ printer().printf(" %-20s %s%n", "cron-log", "Scheduled task that logs every 5 seconds");
+ printer().println();
+ printer().println("Usage: camel run --example <name>");
+ printer().println(" camel run --example <name> --dev");
+ return 0;
+ }
+
+ private static final List<String> EXAMPLE_NAMES = List.of("timer-log", "rest-api", "cron-log");
+
+ private int runExample() throws Exception {
+ String resourcePath = "examples/" + example + ".yaml";
+ InputStream is = Run.class.getClassLoader().getResourceAsStream(resourcePath);
+ if (is == null) {
+ List<String> suggestions
+ = org.apache.camel.main.util.SuggestSimilarHelper.didYouMean(EXAMPLE_NAMES, example);
+ if (!suggestions.isEmpty()) {
+ printer().printErr("Unknown example: " + example + ". Did you mean? " + String.join(", ", suggestions));
+ } else {
+ printer().printErr("Unknown example: " + example);
+ }
+ printer().printErr("Run 'camel run --example-list' to see available examples.");
+ return 1;
+ }
+
+ // extract example to a temp file and run it
+ Path tempDir = Files.createTempDirectory("camel-example-");
+ Path exampleFile = tempDir.resolve(example + ".yaml");
+ try {
+ String content = IOHelper.loadText(is);
+ IOHelper.close(is);
+ Files.writeString(exampleFile, content);
+
+ printer().println("Running example: " + example);
+ files.add(exampleFile.toString());
+ if ("CamelJBang".equals(name)) {
+ name = example;
+ }
+
+ if (!exportRun) {
+ printConfigurationValues("Running integration with the following configuration:");
+ }
+ return run();
+ } finally {
+ // clean up temp files on JVM exit
+ exampleFile.toFile().deleteOnExit();
+ tempDir.toFile().deleteOnExit();
+ }
+ }
+
public Integer runExport() throws Exception {
return runExport(false);
}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Shell.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Shell.java
index 8cb436ccbde7..715b7d76b052 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Shell.java
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Shell.java
@@ -16,9 +16,11 @@
*/
package org.apache.camel.dsl.jbang.core.commands;
+import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.stream.Stream;
import org.apache.camel.dsl.jbang.core.common.EnvironmentHelper;
import org.apache.camel.dsl.jbang.core.common.VersionHelper;
@@ -151,8 +153,35 @@ public class Shell extends CamelCommand {
}
writer.println(banner);
}
- writer.println("Type 'help' for available commands, 'exit' to quit.");
+ int routeCount = countRouteFiles();
+ if (routeCount == 0) {
+ writer.println("No routes found in current directory.");
+ writer.println(" Quick start: init MyRoute.yaml && run *");
+ writer.println(" Templates: init --list");
+ writer.println(" Docs: doc <component>");
+ writer.println(" Need help? help");
+ } else {
+ writer.printf("Found %d route file(s) in current directory.%n", routeCount);
+ writer.println(" Run: run *");
+ writer.println(" Watch: run * --dev");
+ }
writer.println();
writer.flush();
}
+
+ private static int countRouteFiles() {
+ try (Stream<Path> files = Files.list(Paths.get("."))) {
+ return (int) files.filter(Files::isRegularFile)
+ .filter(p -> {
+ String name = p.getFileName().toString();
+ return name.endsWith(".yaml") && !name.endsWith(".kamelet.yaml")
+ && !name.equals("application.yaml")
+ || name.endsWith(".xml") && !name.equals("pom.xml")
+ || name.endsWith(".java");
+ })
+ .count();
+ } catch (IOException e) {
+ return 0;
+ }
+ }
}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/catalog/CatalogBaseCommand.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/catalog/CatalogBaseCommand.java
index 92acba5edae6..a0d2748b5e2c 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/catalog/CatalogBaseCommand.java
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/catalog/CatalogBaseCommand.java
@@ -37,6 +37,7 @@ import org.apache.camel.dsl.jbang.core.common.RuntimeTypeConverter;
import org.apache.camel.dsl.jbang.core.common.TerminalWidthHelper;
import org.apache.camel.dsl.jbang.core.common.VersionHelper;
import org.apache.camel.dsl.jbang.core.model.CatalogBaseDTO;
+import org.apache.camel.main.util.SuggestSimilarHelper;
import org.apache.camel.tooling.maven.MavenGav;
import org.apache.camel.tooling.model.ArtifactModel;
import org.apache.camel.util.json.Jsoner;
@@ -180,6 +181,16 @@ public abstract class CatalogBaseCommand extends CamelCommand {
.maxWidth(descWidth, OverflowBehaviour.ELLIPSIS_RIGHT)
.with(this::shortDescription))));
}
+ } else if (filterName != null) {
+ // suggest similar names when filter returns no results
+ List<String> allNames = collectRows().stream().map(r -> r.name).collect(Collectors.toList());
+ List<String> suggestions = SuggestSimilarHelper.didYouMean(allNames, filterName);
+ if (!suggestions.isEmpty()) {
+ printer().println("No results for filter: " + filterName + ". Did you mean? " + String.join(", ", suggestions));
+ } else {
+ printer().println("No results for filter: " + filterName);
+ }
+ printer().println("Tip: use 'camel doc " + filterName + "' for detailed documentation.");
}
return 0;
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/PluginType.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/PluginType.java
index 0e3eb1d5df4c..63a475b3fb71 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/PluginType.java
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/PluginType.java
@@ -30,6 +30,8 @@ public enum PluginType {
TEST("test", "test", "Manage tests for Camel applications", "4.14.0", null),
ROUTE_PARSER("route-parser", "route-parser", "Parses Java route and dumps route structure", "4.17.0", null),
VALIDATE("validate", "validate", "Validate Camel routes", "4.18.0", null),
+ KIT("kit", "kit", "AI-assisted Camel project scaffolding", "4.19.0",
+ "https://repo1.maven.org/maven2/"),
TUI("tui", "tui", "Camel Dashboard", "4.20.0", null);
private final String name;
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/cron-log.yaml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/cron-log.yaml
new file mode 100644
index 000000000000..c31bdb1cec3d
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/cron-log.yaml
@@ -0,0 +1,10 @@
+- route:
+ id: cron-log
+ from:
+ uri: timer:cron
+ parameters:
+ period: "5000"
+ steps:
+ - setBody:
+ simple: "Scheduled task running at ${date:now:HH:mm:ss}"
+ - log: "${body}"
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/rest-api.yaml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/rest-api.yaml
new file mode 100644
index 000000000000..2e1035cf1f0f
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/rest-api.yaml
@@ -0,0 +1,21 @@
+- rest:
+ path: /api
+ get:
+ - path: /hello
+ to: direct:hello
+ - path: /hello/{name}
+ to: direct:hello-name
+- route:
+ id: hello
+ from:
+ uri: direct:hello
+ steps:
+ - setBody:
+ constant: "Hello from Camel REST API!"
+- route:
+ id: hello-name
+ from:
+ uri: direct:hello-name
+ steps:
+ - setBody:
+ simple: "Hello ${header.name} from Camel REST API!"
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/timer-log.yaml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/timer-log.yaml
new file mode 100644
index 000000000000..cdff71b4d71f
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/timer-log.yaml
@@ -0,0 +1,10 @@
+- route:
+ id: timer-log
+ from:
+ uri: timer:tick
+ parameters:
+ period: "1000"
+ steps:
+ - setBody:
+ simple: "Hello Camel! (message #${exchangeProperty.CamelTimerCounter})"
+ - log: "${body}"
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginGetTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginGetTest.java
index 15004d080ab9..bd72e594cb24 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginGetTest.java
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginGetTest.java
@@ -70,7 +70,7 @@ class PluginGetTest extends CamelCommandBaseTestSupport {
command.doCall();
List<String> output = printer.getLines();
- Assertions.assertEquals(10, output.size());
+ Assertions.assertEquals(11, output.size());
Assertions.assertEquals("Supported plugins:", output.get(0));
Assertions.assertEquals("NAME COMMAND DEPENDENCY DESCRIPTION",
output.get(2));
@@ -120,7 +120,7 @@ class PluginGetTest extends CamelCommandBaseTestSupport {
command.doCall();
List<String> output = printer.getLines();
- Assertions.assertEquals(13, output.size());
+ Assertions.assertEquals(14, output.size());
Assertions.assertEquals("NAME COMMAND DEPENDENCY DESCRIPTION", output.get(0));
Assertions.assertEquals(
"foo-plugin foo org.apache.camel:foo-plugin:1.0.0 Plugin foo-plugin called with command foo",
@@ -153,6 +153,14 @@ class PluginGetTest extends CamelCommandBaseTestSupport {
"validate validate org.apache.camel:camel-jbang-plugin-validate %s"
.formatted(PluginType.VALIDATE.getDescription()),
output.get(11));
+ Assertions.assertEquals(
+ "kit kit org.apache.camel:camel-jbang-plugin-kit %s"
+ .formatted(PluginType.KIT.getDescription()),
+ output.get(12));
+ Assertions.assertEquals(
+ "tui tui org.apache.camel:camel-jbang-plugin-tui %s"
+ .formatted(PluginType.TUI.getDescription()),
+ output.get(13));
}
}
(camel) 02/03: CAMEL-23236: Doctor command should accept Java 17 as supported
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch camel-23236-beginner-ux
in repository https://gitbox.apache.org/repos/asf/camel.git
commit a078df7378a8d81788e51e0b3e0c39d1c56f083e
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Tue May 12 17:54:29 2026 +0200
CAMEL-23236: Doctor command should accept Java 17 as supported
Java 17 is still supported, so the doctor command should show OK
but recommend upgrading to 21 or 25 for better performance.
Co-Authored-By: Claude Opus 4.6 (1M context) <no...@anthropic.com>
---
.../java/org/apache/camel/dsl/jbang/core/commands/Doctor.java | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Doctor.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Doctor.java
index e048233320b1..af9683147b77 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Doctor.java
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Doctor.java
@@ -57,7 +57,14 @@ public class Doctor extends CamelCommand {
String version = System.getProperty("java.version");
String vendor = System.getProperty("java.vendor", "");
int major = Runtime.version().feature();
- String status = major >= 21 ? "OK" : "WARN (21+ required)";
+ String status;
+ if (major >= 21) {
+ status = "OK";
+ } else if (major >= 17) {
+ status = "OK (consider upgrading to 21 or 25 for better performance)";
+ } else {
+ status = "UNSUPPORTED (17+ required)";
+ }
printer().printf(" Java: %s (%s) [%s]%n", version, vendor, status);
}