From f2aa8212327972d5cf98ce021b2eccaf4dc79b3b Mon Sep 17 00:00:00 2001 From: WillowDev <216433317+willowdev-code@users.noreply.github.com> Date: Tue, 27 Oct 2026 14:25:21 +0100 Subject: [PATCH 1/2] Help: per-command --help, missing options listed, test keeps help and options in step --- CHANGELOG.md | 8 +++ README.md | 4 +- README.pl.md | 4 +- src/XppLens/CommandLine.cs | 90 ++++++++++++++++++++++++- src/XppLens/Program.cs | 52 +------------- tests/XppLens.Tests/CommandLineTests.cs | 29 ++++++++ 6 files changed, 131 insertions(+), 56 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11eec22..07cedcc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to xpp-lens (called xpp-graft up to 1.2.0). Versions follow `` in `src/XppLens/XppLens.csproj`; release tags are `vX.Y.Z`. +## 1.4.1 + +### Fixed +- `xpplens --help` stopped with "unknown option '--help'"; it now shows the help of that command (also + `-h` and `xpplens help `). +- `xpplens help` did not list `config --standard-models`, `config --remove-standard-publisher` and + `register`/`unregister --exe`. A test now checks that every command and option the program accepts is in the help. + ## 1.4.0 ### Added diff --git a/README.md b/README.md index e0db65f..0133cd4 100644 --- a/README.md +++ b/README.md @@ -214,11 +214,11 @@ Switch off with `xpplens config --usage-log false`. ``` xpplens find|object|method|callers|callees|refs|ext|scaffold|build-errors|security|join|entity|changed|grep|label … xpplens build [--full-only] [--std-only] [--compiled-only] [--force] -xpplens status [--counts] [--ascii] | stats | update [--install] | detect | config | register | unregister | mcp | version +xpplens status [--counts] [--ascii] | stats | update [--install [--restart-claude]] [--pre] | detect | config | register | unregister | mcp | version xpplens skills [install [--lang en|pl] | remove | status] ``` -`xpplens help` lists every option. Environment variables: `XPPLENS_CONFIG` (another configuration), +`xpplens help` lists every option; `xpplens --help` (or `xpplens help `) only those of one command. Environment variables: `XPPLENS_CONFIG` (another configuration), `XPPLENS_VERBOSE=1` (SQL timings on stderr), `XPPLENS_TIMING=1` (total time of a CLI query). ## Development diff --git a/README.pl.md b/README.pl.md index 34b5b75..14cb797 100644 --- a/README.pl.md +++ b/README.pl.md @@ -215,11 +215,11 @@ Wyłączenie: `xpplens config --usage-log false`. ``` xpplens find|object|method|callers|callees|refs|ext|scaffold|build-errors|security|join|entity|changed|grep|label … xpplens build [--full-only] [--std-only] [--compiled-only] [--force] -xpplens status [--counts] [--ascii] | stats | update [--install] | detect | config | register | unregister | mcp | version +xpplens status [--counts] [--ascii] | stats | update [--install [--restart-claude]] [--pre] | detect | config | register | unregister | mcp | version xpplens skills [install [--lang en|pl] | remove | status] ``` -`xpplens help` wypisuje wszystkie opcje. Zmienne: `XPPLENS_CONFIG` (inna konfiguracja), `XPPLENS_VERBOSE=1` +`xpplens help` wypisuje wszystkie opcje; `xpplens --help` (albo `xpplens help `) tylko opcje jednego polecenia. Zmienne: `XPPLENS_CONFIG` (inna konfiguracja), `XPPLENS_VERBOSE=1` (czasy zapytań SQL na stderr), `XPPLENS_TIMING=1` (łączny czas zapytania w CLI). ## Rozwój narzędzia diff --git a/src/XppLens/CommandLine.cs b/src/XppLens/CommandLine.cs index 273dc36..59d8300 100644 --- a/src/XppLens/CommandLine.cs +++ b/src/XppLens/CommandLine.cs @@ -9,7 +9,7 @@ namespace XppLens; public sealed partial class CommandLine { // "name=" takes a value, "name" is a switch. - static readonly Dictionary Known = new(StringComparer.OrdinalIgnoreCase) + internal static readonly Dictionary Known = new(StringComparer.OrdinalIgnoreCase) { ["update"] = ["install", "restart-claude", "pre"], ["migrate"] = ["from=", "config="], @@ -61,6 +61,14 @@ public sealed partial class CommandLine /// An option the command does not know, or one without its value. public static CommandLine Parse(string[] args) { + // "xpplens --help" (or -h): the help of that command, as "xpplens help ". + var end = Array.IndexOf(args, "--"); + if (args.Length > 1 && args.Take(end < 0 ? args.Length : end).Skip(1).Any(a => a is "--help" or "-h" or "-?")) + { + var help = new CommandLine("help"); + help.Positional.Add(args[0].ToLowerInvariant()); + return help; + } var cl = new CommandLine(args.Length > 0 ? args[0].ToLowerInvariant() : "help"); // help, version and unknown commands: no option checking (an unknown command fails on its own). Known.TryGetValue(cl.Command, out var spec); @@ -107,4 +115,84 @@ public static CommandLine Parse(string[] args) _values.TryGetValue(name, out var q) && q.Count > 0 ? q.Dequeue() : null; public bool Flag(string name) => _switches.Contains(name); + + /// + /// The lines of about one command (its usage lines with their continuations) and the + /// options note; null for a command the help does not describe. + /// + public static string? HelpFor(string command) + { + var lines = HelpText.Replace("\r", "").Split('\n'); + var picked = new List(); + var inside = false; + foreach (var line in lines) + { + var trimmed = line.TrimStart(); + if (trimmed.StartsWith("xpplens ", StringComparison.Ordinal) && line.StartsWith(" xpplens", StringComparison.Ordinal)) + inside = trimmed == $"xpplens {command}" || trimmed.StartsWith($"xpplens {command} ", StringComparison.Ordinal); + else if (inside && !line.StartsWith(" ", StringComparison.Ordinal)) + inside = false; + if (inside) picked.Add(line); + } + if (picked.Count == 0) return null; + picked.Add(""); + picked.AddRange(lines.Where(l => l.StartsWith("options:", StringComparison.Ordinal))); + return string.Join(Environment.NewLine, picked); + } + + /// The text of "xpplens help"; CommandLineTests checks that every command and option in Known is in it. + public const string HelpText = """ + xpplens — X++ code index (D365 F&O) · WillowDev + + xpplens detect [--set [--first]] find PackagesLocalDirectory on this machine + xpplens config show settings + xpplens config --packages-dir | --index-path + --add-language pl | --remove-language pl | --languages en-US,pl + --display-language en-US + --add-full-model Contoso | --remove-full-model Contoso | --full-models A,B + --add-standard-model X | --remove-standard-model X | --standard-models A,B + --add-standard-publisher "Contoso" | --remove-standard-publisher "Contoso" + --standard-publishers Microsoft + --index-standard true|false | --standard-code true|false + --rescan-seconds 300 | --usage-log true|false + xpplens build [--full-only] [--std-only] [--compiled-only] [--force] build / update the index + (--std-only also switches indexStandard on when an installation was made without it) + xpplens status [--counts] [--ascii] --counts: also count references and labels (slow on a cold disk) + --ascii: frames from +-| (automatic when redirected) + xpplens update [--install [--restart-claude]] [--pre] check GitHub for a newer release; --install downloads, + verifies (SHA-256) and installs it, keeping settings and index + (Claude may stay open; --restart-claude restarts Claude Desktop + at the end without asking); --pre: test releases too + xpplens stats [--days 7] [--top 10] tokens, time and empty answers of MCP calls + xpplens find [--kind any|object|method|field] [--type t] [--model m] [--limit n] + [--property "Name[;Name2]" | "Name=*value*"] list root properties of the matching objects + xpplens object [--type t] [--sections list] [--parent control] [--depth n] [--filter *text*] + xpplens method [] [--type t] [--match regex] [--lines from-to] [--context n] + xpplens callers [--depth n] [--limit n] [--standard true|false] + xpplens callees [--type t] + xpplens refs [--member m] [--kind k] [--model m] [--limit n] + xpplens ext + xpplens scaffold coc|event|delegate|pre|post [--element ds|ds.field|control] [--class name] [--type t] + xpplens build-errors [--model m] [--severity error|warning|all] [--limit n] + (newest of the model build and the project build, with objects not compiled since) + xpplens security [--type t] [--limit n] + xpplens join [--hops n] [--limit n] + xpplens entity [--sections list] [--limit n] + xpplens changed [--since 24h|3d|2026-10-01|build] [--model m] [--type t] [--limit n] + (--since build: objects changed after the package's last build, i.e. not compiled yet) + xpplens grep [--model m] [--type t] [--object o] [--standard] [--limit n] + xpplens label <@id|text> [--lang l] [--limit n] + xpplens mcp MCP server over stdio + xpplens register [--desktop] [--code] [--name xpp-lens] [--exe path] add to Claude config(s) + (--exe: another xpplens.exe than this one) + xpplens unregister [--desktop] [--code] [--name xpp-lens] [--exe path] [--any] + (--any: also entries of other installations) + xpplens skills [install [--lang en|pl]|remove|status] Claude Code skills of the package in ~/.claude/skills + (a skill changed locally is kept, never overwritten; + --lang: the language of their texts, kept for updates) + xpplens version + + options: --name value, --name=value or -name value; '--' ends the options (for an argument starting with '-') + config: xpplens.json next to the executable or in a parent folder (or XPPLENS_CONFIG) + """; } diff --git a/src/XppLens/Program.cs b/src/XppLens/Program.cs index 6ba49f1..621ccc0 100644 --- a/src/XppLens/Program.cs +++ b/src/XppLens/Program.cs @@ -275,57 +275,7 @@ void SetList(List list, string addOpt, string removeOpt, string setOpt) if (cmd is "help" or "-h" or "--help") { - Console.WriteLine(""" - xpplens — X++ code index (D365 F&O) · WillowDev - - xpplens detect [--set [--first]] find PackagesLocalDirectory on this machine - xpplens config show settings - xpplens config --packages-dir | --index-path - --add-language pl | --remove-language pl | --languages en-US,pl - --display-language en-US - --add-full-model Contoso | --remove-full-model Contoso | --full-models A,B - --add-standard-model X | --remove-standard-model X - --add-standard-publisher "Contoso" | --standard-publishers Microsoft - --index-standard true|false | --standard-code true|false - --rescan-seconds 300 | --usage-log true|false - xpplens build [--full-only] [--std-only] [--compiled-only] [--force] build / update the index - (--std-only also switches indexStandard on when an installation was made without it) - xpplens status [--counts] [--ascii] --counts: also count references and labels (slow on a cold disk) - --ascii: frames from +-| (automatic when redirected) - xpplens update [--install [--restart-claude]] [--pre] check GitHub for a newer release; --install downloads, - verifies (SHA-256) and installs it, keeping settings and index - (Claude may stay open; --restart-claude restarts Claude Desktop - at the end without asking); --pre: test releases too - xpplens stats [--days 7] [--top 10] tokens, time and empty answers of MCP calls - xpplens find [--kind any|object|method|field] [--type t] [--model m] [--limit n] - [--property "Name[;Name2]" | "Name=*value*"] list root properties of the matching objects - xpplens object [--type t] [--sections list] [--parent control] [--depth n] [--filter *text*] - xpplens method [] [--type t] [--match regex] [--lines from-to] [--context n] - xpplens callers [--depth n] [--limit n] [--standard true|false] - xpplens callees [--type t] - xpplens refs [--member m] [--kind k] [--model m] [--limit n] - xpplens ext - xpplens scaffold coc|event|delegate|pre|post [--element ds|ds.field|control] [--class name] [--type t] - xpplens build-errors [--model m] [--severity error|warning|all] [--limit n] - (newest of the model build and the project build, with objects not compiled since) - xpplens security [--type t] [--limit n] - xpplens join [--hops n] [--limit n] - xpplens entity [--sections list] [--limit n] - xpplens changed [--since 24h|3d|2026-10-01|build] [--model m] [--type t] [--limit n] - (--since build: objects changed after the package's last build, i.e. not compiled yet) - xpplens grep [--model m] [--type t] [--object o] [--standard] [--limit n] - xpplens label <@id|text> [--lang l] [--limit n] - xpplens mcp MCP server over stdio - xpplens register [--desktop] [--code] [--name xpp-lens] add to Claude config(s) - xpplens unregister [--desktop] [--code] [--any] (--any: also other installations) - xpplens skills [install [--lang en|pl]|remove|status] Claude Code skills of the package in ~/.claude/skills - (a skill changed locally is kept, never overwritten; - --lang: the language of their texts, kept for updates) - xpplens version - - options: --name value, --name=value or -name value; '--' ends the options (for an argument starting with '-') - config: xpplens.json next to the executable or in a parent folder (or XPPLENS_CONFIG) - """); + Console.WriteLine(rest.Count > 0 && CommandLine.HelpFor(rest[0]) is { } one ? one : CommandLine.HelpText); return 0; } diff --git a/tests/XppLens.Tests/CommandLineTests.cs b/tests/XppLens.Tests/CommandLineTests.cs index 195453e..bdd457a 100644 --- a/tests/XppLens.Tests/CommandLineTests.cs +++ b/tests/XppLens.Tests/CommandLineTests.cs @@ -42,4 +42,33 @@ public void Repeated_options_and_double_dash() Assert.Equal(["-1"], grep.Positional); Assert.Equal("Contoso*", grep.Opt("model")); } + + [Fact] + public void Help_describes_every_command_and_option() + { + // migrate is run by install.ps1 only; detect --apply is another name of --set. + foreach (var (command, options) in CommandLine.Known.Where(k => k.Key != "migrate")) + { + Assert.NotNull(CommandLine.HelpFor(command)); + foreach (var option in options.Select(o => o.TrimEnd('=')).Where(o => o != "apply")) + Assert.True(CommandLine.HelpFor(command)!.Contains("--" + option, StringComparison.Ordinal), + $"xpplens help has no --{option} for '{command}'"); + } + } + + [Fact] + public void Help_option_after_a_command_shows_that_command() + { + var cl = CommandLine.Parse(["update", "--help"]); + Assert.Equal("help", cl.Command); + Assert.Equal(["update"], cl.Positional); + Assert.Equal("help", CommandLine.Parse(["find", "-h"]).Command); + Assert.Equal("grep", CommandLine.Parse(["grep", "--", "--help"]).Command); + + var update = CommandLine.HelpFor("update")!; + Assert.Contains("--pre", update); + Assert.DoesNotContain("xpplens find", update); + Assert.Contains("--remove-standard-publisher", CommandLine.HelpFor("config")); + Assert.Null(CommandLine.HelpFor("nonsense")); + } } From 30c2dba739d6d792f8e6ac88b7eb42a24683fd12 Mon Sep 17 00:00:00 2001 From: WillowDev <216433317+willowdev-code@users.noreply.github.com> Date: Tue, 27 Oct 2026 14:27:49 +0100 Subject: [PATCH 2/2] xpp-lens 1.4.1 ### Fixed - `xpplens --help` stopped with "unknown option '--help'"; it now shows the help of that command (also `-h` and `xpplens help `). - `xpplens help` did not list `config --standard-models`, `config --remove-standard-publisher` and `register`/`unregister --exe`. A test now checks that every command and option the program accepts is in the help. --- src/XppLens/XppLens.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XppLens/XppLens.csproj b/src/XppLens/XppLens.csproj index 57c6691..f08f366 100644 --- a/src/XppLens/XppLens.csproj +++ b/src/XppLens/XppLens.csproj @@ -22,7 +22,7 @@ xpp-lens — X++ code index for MCP X++ (Dynamics 365 F&O) code index served to AI assistants over MCP Copyright © 2026 WillowDev - 1.4.0 + 1.4.1