From 91b333fded9e31b6e9c286f9bd317cf7a91c6c0a Mon Sep 17 00:00:00 2001 From: Peter Amiri Date: Tue, 6 Oct 2026 04:52:18 -0700 Subject: [PATCH 1/5] fix(cli): hints name the binary the user ran Hints such as "Use 'lucli server new' to create a new lucee.json" were hard-coded to 'lucli', also under an aliased binary like 'wheels'. LuCLI.cliName() returns the active profile's name (default 'lucli'), and the 27 hard-coded hint strings in 14 files use it. picocli @Command help text is an annotation constant and is left as is. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Peter Amiri --- CHANGELOG.md | 2 ++ FORK.md | 1 + src/main/java/org/lucee/lucli/LuCLI.java | 18 +++++++--- .../org/lucee/lucli/cflint/CFLintCommand.java | 2 +- .../lucee/lucli/cli/commands/AiCommand.java | 2 +- .../lucli/cli/commands/DaemonCommand.java | 2 +- .../lucee/lucli/cli/commands/RunCommand.java | 4 +-- .../lucli/cli/commands/SecretsCommand.java | 2 +- .../lucli/cli/commands/ServerCommand.java | 6 ++-- .../modules/ModulesListCommandImpl.java | 4 +-- .../modules/ModuleRuntimeConfigResolver.java | 2 +- .../lucli/monitoring/MonitorCommand.java | 2 +- .../secrets/LucliSecretProviderSupport.java | 2 +- .../org/lucee/lucli/server/LogCommand.java | 2 +- .../lucee/lucli/server/LuceeServerConfig.java | 2 +- .../lucli/server/ServerCommandHandler.java | 14 ++++---- .../java/org/lucee/lucli/CliNameTest.java | 36 +++++++++++++++++++ 17 files changed, 76 insertions(+), 27 deletions(-) create mode 100644 src/test/java/org/lucee/lucli/CliNameTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b0e8e33..e90c1dbf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ All notable changes to this project will be documented in this file. ## Unreleased +- **Fix: Hints Name the Binary You Ran:** User-facing hints such as "Use 'lucli server new' …", "Run 'lucli secrets init' …" or "run 'lucli modules list' …" always said `lucli`, also under an aliased binary (e.g. `wheels`). They now use `LuCLI.cliName()`, the active profile's name. picocli `@Command` help text (annotation constants) is unchanged. Adds `CliNameTest`. + - **Fix: `--env` / `--envfile` Reach Modules Run Through the Module Shortcut:** `lucli … --env=x`, `lucli --env=x …` and an aliased binary (`-Dlucli.binary.name=`, e.g. `wheels start --env=prod`) re-execute `modules run …` on the root command, which reset the root's options, so the module saw `LuCLI.getCurrentEnvironment() == null` (only `LUCLI_ENV` worked). The shortcut now re-injects `--env` / `--envfile` at the root position, as it already did for `--verbose` / `--debug` (after the module args). Upstream: cybersonic/LuCLI#136. Adds `ModuleShortcutArgsTest`. - **Feature: Modules Can Tell an MCP `tools/call` From a Terminal Call:** During `tools/call`, the module function now receives a reserved `__lucliMcpCall=true` argument, so a module can apply a different policy to MCP clients (for example refusing a command meant for a person at the terminal). The argument is runtime-owned: any argument of that name (any case, with or without `--`) supplied by an MCP client or on the command line is removed before the module runs, so it can't be forged or suppressed, and a terminal invocation never carries it. The positional binder never fills a declared parameter of that name from a terminal `argN`. Documented in the MCP module servers guide. Adds coverage in `McpCommandTest` (`toolsCallCarriesARuntimeOwnedMcpMarker`, `terminalInvocationNeverCarriesTheMcpMarker`, `declaredMarkerParameterIsNeverBoundFromATerminalPositional`). diff --git a/FORK.md b/FORK.md index bcb1f7f8..9fc47ac6 100644 --- a/FORK.md +++ b/FORK.md @@ -84,6 +84,7 @@ No release is tagged for this queue yet; tagging needs the Wheels release owner' |---|---|---| | 8 | wheels-dev/LuCLI [#19](https://github.com/wheels-dev/LuCLI/pull/19) (issue [#17](https://github.com/wheels-dev/LuCLI/issues/17)) | A module function run by an MCP `tools/call` receives the runtime-owned `__lucliMcpCall=true` argument; any caller-supplied argument of that name is dropped, and a terminal call never carries it | | 9 | wheels-dev/LuCLI [#21](https://github.com/wheels-dev/LuCLI/pull/21) (issue: upstream [cybersonic/LuCLI#136](https://github.com/cybersonic/LuCLI/issues/136)) | `--env` / `--envfile` reach a module run through the module shortcut or an aliased binary (re-injected at the root position) | +| 14 | wheels-dev/LuCLI PR (this change; fork-only polish, no upstream issue) | User-facing hints name the active binary (`wheels server new`, not `lucli server new`) via `LuCLI.cliName()` | Upstream (row 8): issue [cybersonic/LuCLI#133](https://github.com/cybersonic/LuCLI/issues/133), PR [cybersonic/LuCLI#134](https://github.com/cybersonic/LuCLI/pull/134) (a `cherry-pick -x` of diff --git a/src/main/java/org/lucee/lucli/LuCLI.java b/src/main/java/org/lucee/lucli/LuCLI.java index 60fe4b4b..25af985c 100644 --- a/src/main/java/org/lucee/lucli/LuCLI.java +++ b/src/main/java/org/lucee/lucli/LuCLI.java @@ -208,6 +208,16 @@ public class LuCLI implements Callable { public static Map scriptEnvironment = new HashMap<>(System.getenv()); + /** + * The command name to show in user-facing hints ("Use 'wheels server new' ..."): + * the active profile's name, so an aliased binary doesn't tell users to run + * {@code lucli}. Falls back to {@code lucli}. + */ + public static String cliName() { + String name = activeProfile != null ? activeProfile.name() : null; + return (name == null || name.isBlank()) ? "lucli" : name; + } + /** * The active CLI profile, determined by the binary name at startup. * Controls branding, home directory name, and prompt prefix. @@ -354,7 +364,7 @@ private Integer routeCommand(String arg, String[] args) throws Exception { // Check if it's a CFML file (.cfm, .cfc, .cfs) if (file.exists() && arg.endsWith(".cfc")) { StringOutput.Quick.error("Executing .cfc files directly is not supported."); - StringOutput.getInstance().println("Use a module entry point instead (e.g. 'lucli modules run ')."); + StringOutput.getInstance().println("Use a module entry point instead (e.g. '" + cliName() + " modules run ')."); return 1; } if (file.exists() && (arg.endsWith(".cfm") || @@ -380,7 +390,7 @@ private Integer routeCommand(String arg, String[] args) throws Exception { spec.commandLine(), "Unknown command, file, or module: '" + arg + "'\n" + " - If it's a file, check the path and extension (.cfm, .cfs, .lucli)\n" + - " - If it's a module, run 'lucli modules list' to see available modules\n" + + " - If it's a module, run '" + cliName() + " modules list' to see available modules\n" + " - Run 'lucli --help' to see available commands" ); } @@ -415,7 +425,7 @@ private Integer executeViaModulesCommand(String moduleName, String[] args) throw } verbose("Executing module shortcut: " + moduleName + - " (equivalent to 'lucli modules run " + moduleName + " " + String.join(" ", args) + "')"); + " (equivalent to '" + cliName() + " modules run " + moduleName + " " + String.join(" ", args) + "')"); List cmdArgs = moduleShortcutArgs(moduleName, args, isVerbose(), isDebug(), envOption, envFileOption); return spec.commandLine().execute(cmdArgs.toArray(new String[0])); @@ -1093,7 +1103,7 @@ private static void initializeLucliScriptSecretStore() throws Exception { Path storePath = Paths.get(home).resolve("secrets").resolve("local.json"); if (!Files.exists(storePath)) { - throw new IllegalStateException("Script references ${secret:...} but local secret store does not exist. Run 'lucli secrets init' and define the required secrets."); + throw new IllegalStateException("Script references ${secret:...} but local secret store does not exist. Run '" + cliName() + " secrets init' and define the required secrets."); } // Prefer non-interactive passphrase from environment when available diff --git a/src/main/java/org/lucee/lucli/cflint/CFLintCommand.java b/src/main/java/org/lucee/lucli/cflint/CFLintCommand.java index a4f0ca5f..a03cd478 100644 --- a/src/main/java/org/lucee/lucli/cflint/CFLintCommand.java +++ b/src/main/java/org/lucee/lucli/cflint/CFLintCommand.java @@ -188,7 +188,7 @@ private boolean ensureCFLintAvailable() { return true; } catch (Exception e) { System.err.println("❌ Error downloading or loading CFLint: " + e.getMessage()); - System.err.println("💡 Try running 'lucli lint status' to check CFLint availability"); + System.err.println("💡 Try running '" + org.lucee.lucli.LuCLI.cliName() + " lint status' to check CFLint availability"); return false; } } diff --git a/src/main/java/org/lucee/lucli/cli/commands/AiCommand.java b/src/main/java/org/lucee/lucli/cli/commands/AiCommand.java index 63e0e0dc..edd70ef6 100644 --- a/src/main/java/org/lucee/lucli/cli/commands/AiCommand.java +++ b/src/main/java/org/lucee/lucli/cli/commands/AiCommand.java @@ -628,7 +628,7 @@ public Integer call() throws Exception { String endpoint = firstNonBlank(endpointName, config.defaultEndpoint); if (isBlank(endpoint)) { - StringOutput.Quick.error("No endpoint specified. Use --endpoint or set --default-endpoint via 'lucli ai config'."); + StringOutput.Quick.error("No endpoint specified. Use --endpoint or set --default-endpoint via '" + org.lucee.lucli.LuCLI.cliName() + " ai config'."); return 1; } diff --git a/src/main/java/org/lucee/lucli/cli/commands/DaemonCommand.java b/src/main/java/org/lucee/lucli/cli/commands/DaemonCommand.java index 4c02a125..405ed7f6 100644 --- a/src/main/java/org/lucee/lucli/cli/commands/DaemonCommand.java +++ b/src/main/java/org/lucee/lucli/cli/commands/DaemonCommand.java @@ -211,7 +211,7 @@ private Integer runLspDaemon() throws Exception { if (!ModuleCommand.moduleExists(lspModuleName)) { LuCLI.error("LSP module '" + lspModuleName + "' not found under ~/.lucli/modules."); - LuCLI.error("Install or create it with 'lucli modules init " + lspModuleName + "'."); + LuCLI.error("Install or create it with '" + org.lucee.lucli.LuCLI.cliName() + " modules init " + lspModuleName + "'."); return 1; } diff --git a/src/main/java/org/lucee/lucli/cli/commands/RunCommand.java b/src/main/java/org/lucee/lucli/cli/commands/RunCommand.java index 9cf697be..1a6d0084 100644 --- a/src/main/java/org/lucee/lucli/cli/commands/RunCommand.java +++ b/src/main/java/org/lucee/lucli/cli/commands/RunCommand.java @@ -109,8 +109,8 @@ else if (fileName.endsWith(".cfm") ) { } } else if (fileName.endsWith(".cfc") ) { - StringOutput.Quick.error("Executing .cfc files via 'lucli run' is not supported."); - StringOutput.getInstance().println("Use a module entry point instead (e.g. 'lucli modules run ')."); + StringOutput.Quick.error("Executing .cfc files via '" + org.lucee.lucli.LuCLI.cliName() + " run' is not supported."); + StringOutput.getInstance().println("Use a module entry point instead (e.g. '" + org.lucee.lucli.LuCLI.cliName() + " modules run ')."); return 1; } else if (fileName.endsWith(".cfs") ) { diff --git a/src/main/java/org/lucee/lucli/cli/commands/SecretsCommand.java b/src/main/java/org/lucee/lucli/cli/commands/SecretsCommand.java index a4c0c7ac..b0ff61ee 100644 --- a/src/main/java/org/lucee/lucli/cli/commands/SecretsCommand.java +++ b/src/main/java/org/lucee/lucli/cli/commands/SecretsCommand.java @@ -254,7 +254,7 @@ static class GetCommand implements Callable { @Override public Integer call() throws Exception { if (!show) { - System.err.println("By default, 'lucli secrets get' does not print raw values. Use --show if you really need to see it."); + System.err.println("By default, '" + org.lucee.lucli.LuCLI.cliName() + " secrets get' does not print raw values. Use --show if you really need to see it."); return 1; } String provider = selectedProvider(); diff --git a/src/main/java/org/lucee/lucli/cli/commands/ServerCommand.java b/src/main/java/org/lucee/lucli/cli/commands/ServerCommand.java index 392264dc..16f1dbfe 100644 --- a/src/main/java/org/lucee/lucli/cli/commands/ServerCommand.java +++ b/src/main/java/org/lucee/lucli/cli/commands/ServerCommand.java @@ -233,7 +233,7 @@ public Integer call() throws Exception { } if (invokedAsTart) { - System.out.println("Tip: 'lucli server tart' also starts a server. Sweet."); + System.out.println("Tip: '" + org.lucee.lucli.LuCLI.cliName() + " server tart' also starts a server. Sweet."); System.out.println(" .-\"\"-."); System.out.println(" .' .-. '."); System.out.println(" / ( ) \\"); @@ -1164,7 +1164,7 @@ public Integer call() throws Exception { if (name != null && !name.trim().isEmpty()) { System.err.println("No running Lucee server found with name '" + name + "'."); } else { - System.err.println("No running Lucee server found for this directory. Start one with 'lucli server start'."); + System.err.println("No running Lucee server found for this directory. Start one with '" + org.lucee.lucli.LuCLI.cliName() + " server start'."); } return 1; } @@ -1257,7 +1257,7 @@ public Integer call() throws Exception { if (!Files.exists(cfg)) { System.err.println("❌ Config file not found: " + cfg.toAbsolutePath()); - System.err.println(" Use 'lucli server new' to create a new lucee.json."); + System.err.println(" Use '" + org.lucee.lucli.LuCLI.cliName() + " server new' to create a new lucee.json."); return 1; } diff --git a/src/main/java/org/lucee/lucli/cli/commands/modules/ModulesListCommandImpl.java b/src/main/java/org/lucee/lucli/cli/commands/modules/ModulesListCommandImpl.java index d25a47af..4af28fa2 100644 --- a/src/main/java/org/lucee/lucli/cli/commands/modules/ModulesListCommandImpl.java +++ b/src/main/java/org/lucee/lucli/cli/commands/modules/ModulesListCommandImpl.java @@ -51,7 +51,7 @@ private void listModules() throws IOException { if (!Files.exists(modulesDir)) { StringOutput.getInstance().println("${EMOJI_INFO} No modules directory found at: " + modulesDir); - StringOutput.getInstance().println("${EMOJI_BULB} Use 'lucli modules init ' to create your first module."); + StringOutput.getInstance().println("${EMOJI_BULB} Use '" + org.lucee.lucli.LuCLI.cliName() + " modules init ' to create your first module."); return; } @@ -70,7 +70,7 @@ private void listModules() throws IOException { if (installed.isEmpty() && repoModules.isEmpty()) { StringOutput.getInstance().println("${EMOJI_INFO} No modules found."); - StringOutput.getInstance().println("${EMOJI_BULB} Use 'lucli modules init ' to create a new module."); + StringOutput.getInstance().println("${EMOJI_BULB} Use '" + org.lucee.lucli.LuCLI.cliName() + " modules init ' to create a new module."); return; } diff --git a/src/main/java/org/lucee/lucli/modules/ModuleRuntimeConfigResolver.java b/src/main/java/org/lucee/lucli/modules/ModuleRuntimeConfigResolver.java index 4585cb5e..85f83d5c 100644 --- a/src/main/java/org/lucee/lucli/modules/ModuleRuntimeConfigResolver.java +++ b/src/main/java/org/lucee/lucli/modules/ModuleRuntimeConfigResolver.java @@ -231,7 +231,7 @@ private LocalSecretStore getLocalSecretStore(boolean required) throws Exception if (required) { throw new IllegalStateException( "Local secret store not found at " + storePath + - ". Run 'lucli secrets init' to create it." + ". Run '" + org.lucee.lucli.LuCLI.cliName() + " secrets init' to create it." ); } return null; diff --git a/src/main/java/org/lucee/lucli/monitoring/MonitorCommand.java b/src/main/java/org/lucee/lucli/monitoring/MonitorCommand.java index a1ab1312..bad18368 100644 --- a/src/main/java/org/lucee/lucli/monitoring/MonitorCommand.java +++ b/src/main/java/org/lucee/lucli/monitoring/MonitorCommand.java @@ -340,7 +340,7 @@ private static ConnectionDetails resolveConnectionDetails(String serverName, Str if (serverName != null) { LuceeServerManager.ServerInfo serverInfo = serverManager.getServerInfoByName(serverName); if (serverInfo == null) { - lastErrorMessage = "❌ Server '" + serverName + "' not found.\n💡 Use 'lucli server list' to see available servers."; + lastErrorMessage = "❌ Server '" + serverName + "' not found.\n💡 Use '" + org.lucee.lucli.LuCLI.cliName() + " server list' to see available servers."; return null; } diff --git a/src/main/java/org/lucee/lucli/secrets/LucliSecretProviderSupport.java b/src/main/java/org/lucee/lucli/secrets/LucliSecretProviderSupport.java index bd3378f2..70e29046 100644 --- a/src/main/java/org/lucee/lucli/secrets/LucliSecretProviderSupport.java +++ b/src/main/java/org/lucee/lucli/secrets/LucliSecretProviderSupport.java @@ -193,7 +193,7 @@ public static String readSecretFromLocalStore( if (required) { throw new IllegalStateException( "Local secret store not found at " + getLocalStorePath() + - ". Run 'lucli secrets init' first." + ". Run '" + org.lucee.lucli.LuCLI.cliName() + " secrets init' first." ); } return null; diff --git a/src/main/java/org/lucee/lucli/server/LogCommand.java b/src/main/java/org/lucee/lucli/server/LogCommand.java index eb430387..c5900958 100644 --- a/src/main/java/org/lucee/lucli/server/LogCommand.java +++ b/src/main/java/org/lucee/lucli/server/LogCommand.java @@ -62,7 +62,7 @@ public static void executeLog(String[] args) { LuceeServerManager.ServerInstance serverInstance = serverManager.getRunningServer(currentDir); if (serverInstance == null) { System.err.println("No running server found for the current directory."); - System.err.println("Use 'lucli server start' to start a server first."); + System.err.println("Use '" + org.lucee.lucli.LuCLI.cliName() + " server start' to start a server first."); System.exit(1); } diff --git a/src/main/java/org/lucee/lucli/server/LuceeServerConfig.java b/src/main/java/org/lucee/lucli/server/LuceeServerConfig.java index e1f5ab15..415c0b4e 100644 --- a/src/main/java/org/lucee/lucli/server/LuceeServerConfig.java +++ b/src/main/java/org/lucee/lucli/server/LuceeServerConfig.java @@ -1707,7 +1707,7 @@ public static void resolveSecretPlaceholders(ServerConfig config, Path projectDi if (!LucliSecretProviderSupport.hasLocalStoreFile()) { throw new IOException( "Configuration references secret placeholders but local secret store does not exist. " + - "Run 'lucli secrets init' and define the required secrets." + "Run '" + org.lucee.lucli.LuCLI.cliName() + " secrets init' and define the required secrets." ); } localPassphrase = LucliSecretProviderSupport.resolvePassphrase( diff --git a/src/main/java/org/lucee/lucli/server/ServerCommandHandler.java b/src/main/java/org/lucee/lucli/server/ServerCommandHandler.java index a29f764d..e164b00f 100644 --- a/src/main/java/org/lucee/lucli/server/ServerCommandHandler.java +++ b/src/main/java/org/lucee/lucli/server/ServerCommandHandler.java @@ -407,7 +407,7 @@ private String handleServerStart(LuceeServerManager serverManager, String[] args result.append(" - ").append(certsDir.resolve("keystore.pass")).append("\n"); } - result.append("\nYou can now inspect this instance or start it later with 'lucli server start' (without --create-config).\n"); + result.append("\nYou can now inspect this instance or start it later with '" + org.lucee.lucli.LuCLI.cliName() + " server start' (without --create-config).\n"); return formatOutput(result.toString(), false); } @@ -1621,7 +1621,7 @@ private String handleServerInfo(LuceeServerManager serverManager, String[] args) missing.append("❌ Config file not found: ") .append(cfgPath.toAbsolutePath()) .append("\n"); - missing.append(" Use 'lucli server new' to create a new lucee.json for this project.\n"); + missing.append(" Use '" + org.lucee.lucli.LuCLI.cliName() + " server new' to create a new lucee.json for this project.\n"); return formatOutput(missing.toString(), true); } @@ -1770,7 +1770,7 @@ private String handleServerEnv(String[] args) throws Exception { missing.append("❌ Config file not found: ") .append(cfgPath.toAbsolutePath()) .append("\n"); - missing.append(" Use 'lucli server new' to create a new lucee.json for this project.\n"); + missing.append(" Use '" + org.lucee.lucli.LuCLI.cliName() + " server new' to create a new lucee.json for this project.\n"); return formatOutput(missing.toString(), true); } @@ -2129,9 +2129,9 @@ private String handleServerLock(String[] args) throws Exception { if (existing != null && existing.locked && !update) { StringBuilder msg = new StringBuilder(); msg.append("❌ Server configuration is already locked for env '").append(envKey).append("'.\n"); - msg.append(" Use 'lucli server lock --env=").append("_default".equals(envKey) ? "" : envKey) + msg.append(" Use '" + org.lucee.lucli.LuCLI.cliName() + " server lock --env=").append("_default".equals(envKey) ? "" : envKey) .append(" --update' to refresh from the current ").append(cfgFile).append(".\n"); - msg.append(" Or 'lucli server unlock"); + msg.append(" Or '" + org.lucee.lucli.LuCLI.cliName() + " server unlock"); if (!"_default".equals(envKey)) { msg.append(" --env=").append(envKey); } @@ -2153,7 +2153,7 @@ private String handleServerLock(String[] args) throws Exception { StringBuilder result = new StringBuilder(); result.append("🔒 Server configuration locked for env '").append(envKey).append("'.\n"); result.append(" Source file: ").append(cfgFile).append("\n"); - result.append(" Future 'lucli server start"); + result.append(" Future '" + org.lucee.lucli.LuCLI.cliName() + " server start"); if (!"_default".equals(envKey)) { result.append(" --env=").append(envKey); } @@ -2196,7 +2196,7 @@ private String handleServerUnlock(String[] args) throws Exception { StringBuilder result = new StringBuilder(); result.append("🔓 Server configuration unlocked for env '").append(envKey).append("'.\n"); - result.append(" Future 'lucli server start"); + result.append(" Future '" + org.lucee.lucli.LuCLI.cliName() + " server start"); if (!"_default".equals(envKey)) { result.append(" --env=").append(envKey); } diff --git a/src/test/java/org/lucee/lucli/CliNameTest.java b/src/test/java/org/lucee/lucli/CliNameTest.java new file mode 100644 index 00000000..8323ac25 --- /dev/null +++ b/src/test/java/org/lucee/lucli/CliNameTest.java @@ -0,0 +1,36 @@ +package org.lucee.lucli; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.lucee.lucli.profile.CliProfile; + +/** User-facing hints name the binary the user actually ran. */ +class CliNameTest { + + private CliProfile original; + + @BeforeEach + void save() { + original = LuCLI.getActiveProfile(); + } + + @AfterEach + void restore() { + LuCLI.setActiveProfile(original); + } + + @Test + void defaultProfileIsLucli() { + LuCLI.setActiveProfile(CliProfile.forBinaryName("lucli")); + assertEquals("lucli", LuCLI.cliName()); + } + + @Test + void aliasedBinaryNameIsUsed() { + LuCLI.setActiveProfile(CliProfile.forBinaryName("wheels")); + assertEquals("wheels", LuCLI.cliName()); + } +} From 3c46be22fc04ed50c42cc9d26abcfb2357c2fba9 Mon Sep 17 00:00:00 2001 From: Peter Amiri Date: Tue, 6 Oct 2026 04:55:59 -0700 Subject: [PATCH 2/5] docs(fork): link row 14 to #27 Co-Authored-By: Claude Opus 5.5 Signed-off-by: Peter Amiri --- FORK.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FORK.md b/FORK.md index 9fc47ac6..211b1050 100644 --- a/FORK.md +++ b/FORK.md @@ -84,7 +84,7 @@ No release is tagged for this queue yet; tagging needs the Wheels release owner' |---|---|---| | 8 | wheels-dev/LuCLI [#19](https://github.com/wheels-dev/LuCLI/pull/19) (issue [#17](https://github.com/wheels-dev/LuCLI/issues/17)) | A module function run by an MCP `tools/call` receives the runtime-owned `__lucliMcpCall=true` argument; any caller-supplied argument of that name is dropped, and a terminal call never carries it | | 9 | wheels-dev/LuCLI [#21](https://github.com/wheels-dev/LuCLI/pull/21) (issue: upstream [cybersonic/LuCLI#136](https://github.com/cybersonic/LuCLI/issues/136)) | `--env` / `--envfile` reach a module run through the module shortcut or an aliased binary (re-injected at the root position) | -| 14 | wheels-dev/LuCLI PR (this change; fork-only polish, no upstream issue) | User-facing hints name the active binary (`wheels server new`, not `lucli server new`) via `LuCLI.cliName()` | +| 14 | wheels-dev/LuCLI [#27](https://github.com/wheels-dev/LuCLI/pull/27) (fork-only polish, no upstream issue) | User-facing hints name the active binary (`wheels server new`, not `lucli server new`) via `LuCLI.cliName()` | Upstream (row 8): issue [cybersonic/LuCLI#133](https://github.com/cybersonic/LuCLI/issues/133), PR [cybersonic/LuCLI#134](https://github.com/cybersonic/LuCLI/pull/134) (a `cherry-pick -x` of From dff75a0407f222ed98f41369728e4a4b853fb8be Mon Sep 17 00:00:00 2001 From: Peter Amiri Date: Tue, 6 Oct 2026 05:00:49 -0700 Subject: [PATCH 3/5] fix(cli): name the active binary in the '--help' hint too The unknown-command message said "run 'wheels modules list'" and then "Run 'lucli --help'": the hint with a flag after 'lucli' was missed. Review follow-up on #27. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Peter Amiri --- src/main/java/org/lucee/lucli/LuCLI.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/lucee/lucli/LuCLI.java b/src/main/java/org/lucee/lucli/LuCLI.java index 25af985c..b22362aa 100644 --- a/src/main/java/org/lucee/lucli/LuCLI.java +++ b/src/main/java/org/lucee/lucli/LuCLI.java @@ -391,7 +391,7 @@ private Integer routeCommand(String arg, String[] args) throws Exception { "Unknown command, file, or module: '" + arg + "'\n" + " - If it's a file, check the path and extension (.cfm, .cfs, .lucli)\n" + " - If it's a module, run '" + cliName() + " modules list' to see available modules\n" + - " - Run 'lucli --help' to see available commands" + " - Run '" + cliName() + " --help' to see available commands" ); } From b4230cc653ef6527386cc4818b1992e8680357a7 Mon Sep 17 00:00:00 2001 From: Peter Amiri Date: Tue, 6 Oct 2026 05:00:49 -0700 Subject: [PATCH 4/5] fix(lang): escape # in help strings so the lang module compiles Eight writeOutput() help strings in modules/lang/Module.cfc contained a lone '#' ('# e.g., ...'), which CFML parses as an expression delimiter: the module failed to compile ('Invalid identifier' at line 121) and was unusable. Escaped as '##'. Also: the module's unknown-command hint now names the active binary (LuCLI.cliName()), as requested on #27. The same unescaped '#' is on upstream main. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Peter Amiri --- CHANGELOG.md | 4 +++- src/main/resources/modules/lang/Module.cfc | 18 +++++++++--------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e90c1dbf..78d93452 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,9 @@ All notable changes to this project will be documented in this file. ## Unreleased -- **Fix: Hints Name the Binary You Ran:** User-facing hints such as "Use 'lucli server new' …", "Run 'lucli secrets init' …" or "run 'lucli modules list' …" always said `lucli`, also under an aliased binary (e.g. `wheels`). They now use `LuCLI.cliName()`, the active profile's name. picocli `@Command` help text (annotation constants) is unchanged. Adds `CliNameTest`. +- **Fix: Hints Name the Binary You Ran:** User-facing hints such as "Use 'lucli server new' …", "Run 'lucli secrets init' …" or "run 'lucli modules list' …" always said `lucli`, also under an aliased binary (e.g. `wheels`). They now use `LuCLI.cliName()`, the active profile's name. The bundled `lang` module's "Use 'lucli lang help'" hint does the same. picocli `@Command` help text (annotation constants) is unchanged. Adds `CliNameTest`. + +- **Fix: The Bundled `lang` Module Compiles:** eight help strings in `modules/lang/Module.cfc` contained an unescaped `#` (`# e.g., …`), which CFML reads as an expression delimiter, so the module failed to compile ("Invalid identifier") and `lang` was unusable. They are escaped as `##`. - **Fix: `--env` / `--envfile` Reach Modules Run Through the Module Shortcut:** `lucli … --env=x`, `lucli --env=x …` and an aliased binary (`-Dlucli.binary.name=`, e.g. `wheels start --env=prod`) re-execute `modules run …` on the root command, which reset the root's options, so the module saw `LuCLI.getCurrentEnvironment() == null` (only `LUCLI_ENV` worked). The shortcut now re-injects `--env` / `--envfile` at the root position, as it already did for `--verbose` / `--debug` (after the module args). Upstream: cybersonic/LuCLI#136. Adds `ModuleShortcutArgsTest`. diff --git a/src/main/resources/modules/lang/Module.cfc b/src/main/resources/modules/lang/Module.cfc index 2c861362..34e5dc83 100644 --- a/src/main/resources/modules/lang/Module.cfc +++ b/src/main/resources/modules/lang/Module.cfc @@ -54,7 +54,7 @@ component { default: writeOutput("❌ Unknown command: " & command & chr(10)); - writeOutput("Use 'lucli lang help' for available commands." & chr(10)); + writeOutput("Use '" & createObject("java", "org.lucee.lucli.LuCLI").cliName() & " lang help' for available commands." & chr(10)); } return true; } @@ -118,8 +118,8 @@ component { writeOutput(chr(10)); writeOutput("💡 To switch language:" & chr(10)); - writeOutput(" lucli lang set # e.g., lucli lang set es" & chr(10)); - writeOutput(" LUCLI_LOCALE= # e.g., LUCLI_LOCALE=fr lucli ..." & chr(10)); + writeOutput(" lucli lang set ## e.g., lucli lang set es" & chr(10)); + writeOutput(" LUCLI_LOCALE= ## e.g., LUCLI_LOCALE=fr lucli ..." & chr(10)); } /** @@ -152,7 +152,7 @@ component { writeOutput(chr(10)); writeOutput("Option 1 - Environment Variable (recommended):" & chr(10)); writeOutput(" export LUCLI_LOCALE=" & langCode & chr(10)); - writeOutput(" lucli server start # Will use " & langNames[langCode] & chr(10)); + writeOutput(" lucli server start ## Will use " & langNames[langCode] & chr(10)); writeOutput(chr(10)); writeOutput("Option 2 - Per-command:" & chr(10)); @@ -191,11 +191,11 @@ component { writeOutput(" help, --help, -h Show this help message" & chr(10)); writeOutput(chr(10)); writeOutput("EXAMPLES:" & chr(10)); - writeOutput(" lucli lang # Show current and available languages" & chr(10)); - writeOutput(" lucli lang list # List available languages" & chr(10)); - writeOutput(" lucli lang current # Show current language" & chr(10)); - writeOutput(" lucli lang set es # Get instructions to set Spanish" & chr(10)); - writeOutput(" lucli lang set fr # Get instructions to set French" & chr(10)); + writeOutput(" lucli lang ## Show current and available languages" & chr(10)); + writeOutput(" lucli lang list ## List available languages" & chr(10)); + writeOutput(" lucli lang current ## Show current language" & chr(10)); + writeOutput(" lucli lang set es ## Get instructions to set Spanish" & chr(10)); + writeOutput(" lucli lang set fr ## Get instructions to set French" & chr(10)); writeOutput(chr(10)); writeOutput("SUPPORTED LANGUAGES:" & chr(10)); writeOutput(" 🇺🇸 en - English" & chr(10)); From ced9d1cc36e39a06df708b61d53cbdb3aace13eb Mon Sep 17 00:00:00 2001 From: Peter Amiri Date: Tue, 6 Oct 2026 05:09:22 -0700 Subject: [PATCH 5/5] docs(changelog): link the lang fix to upstream #146 / PR #147 Co-Authored-By: Claude Opus 5.5 Signed-off-by: Peter Amiri --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26e55c83..82784111 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. ## Unreleased - **Fix: Hints Name the Binary You Ran:** User-facing hints such as "Use 'lucli server new' …", "Run 'lucli secrets init' …" or "run 'lucli modules list' …" always said `lucli`, also under an aliased binary (e.g. `wheels`). They now use `LuCLI.cliName()`, the active profile's name. The bundled `lang` module's "Use 'lucli lang help'" hint does the same. picocli `@Command` help text (annotation constants) is unchanged. Adds `CliNameTest`. -- **Fix: The Bundled `lang` Module Compiles:** eight help strings in `modules/lang/Module.cfc` contained an unescaped `#` (`# e.g., …`), which CFML reads as an expression delimiter, so the module failed to compile ("Invalid identifier") and `lang` was unusable. They are escaped as `##`. +- **Fix: The Bundled `lang` Module Compiles:** eight help strings in `modules/lang/Module.cfc` contained an unescaped `#` (`# e.g., …`), which CFML reads as an expression delimiter, so the module failed to compile ("Invalid identifier") and `lang` was unusable. They are escaped as `##`. Upstream: cybersonic/LuCLI#146 (PR cybersonic/LuCLI#147). - **Fix: `lucli run .cfm` No Longer Prints the Built-in Variables Setup:** For a `.cfm` (a template, where text outside `` is output), the built-in variables setup was prepended as plain text, so about 20 lines of setup code and an `// === Original Script Content ===` marker were printed before the template's output. The setup now runs inside `` ahead of the unchanged template, so the output is exactly what the template renders, and the built-in variables (`__scriptPath`, `ARGS`, …) are still available. `.cfs` scripts were not affected. Upstream: cybersonic/LuCLI#137. Adds `TemplatePreludeTest`.