From 27fa7c729e07c2611eda93944b5e91994b2c5f53 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 22 May 2026 17:29:49 +0000 Subject: [PATCH 1/3] Initial plan From a810119a17adb2479f330a3727b8fe3399815c68 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 22 May 2026 17:34:49 +0000 Subject: [PATCH 2/3] Make --prompt a global alias for --title across all clets - Add --prompt / -p as a built-in global alias for --title / -t in CommandLineRoot.DispatchAlias and IsKnownOptionToken - Remove the per-clet --prompt option from ConfirmClet (it now uses the global --title/--prompt flag like every other clet) - Update help docs, spec, decisions, and README - Update tests to reflect the change --- README.md | 2 +- specs/clet-spec.md | 4 ++-- specs/decisions.md | 4 ++-- src/Clet/Clets/Input/ConfirmClet.cs | 12 +++------- src/Clet/Help/confirm.md | 4 ++-- src/Clet/Help/overview.md | 2 +- src/Clet/Hosting/CommandLineRoot.cs | 6 ++--- tests/Clet.UnitTests/CommandLineRootTests.cs | 24 +++++++++++++++++++- tests/Clet.UnitTests/ConfirmCletTests.cs | 6 ++--- 9 files changed, 39 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index ce8bae8..bcb59f9 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Works for humans and AI agents alike. | `text`, `multiline-text`, `mt` | Prompts for multi-line text input using an editor and returns the entered string. | | | `int` | Prompts for an integer value using a numeric spinner. | `--step` | | `decimal` | Prompts for a decimal value using a numeric spinner. | `--step` | -| `confirm` | Prompts for a yes/no confirmation and returns a boolean. | `--prompt` | +| `confirm` | Prompts for a yes/no confirmation and returns a boolean. | | | `date` | Prompts for a date and returns an ISO-8601 date string (YYYY-MM-DD). | | | `time` | Prompts for a time and returns an ISO-8601 time string (HH:MM:SS). | | | `duration` | Prompts for a duration and returns an ISO-8601 duration string (e.g. PT1H30M). | | diff --git a/specs/clet-spec.md b/specs/clet-spec.md index adc7428..802bbb5 100644 --- a/specs/clet-spec.md +++ b/specs/clet-spec.md @@ -256,7 +256,7 @@ See `src/Clet/Hosting/Program.cs`. The host creates a `CancellationTokenSource`, ### 4.7 CLI surface ``` -clet [positional...] [--initial ] [--title ] [--json] [--timeout 30s] [--fullscreen] [--cat] [--no-browse] [--rows ] [--output ] [-- ]... +clet [positional...] [--initial ] [--title|--prompt ] [--json] [--timeout 30s] [--fullscreen] [--cat] [--no-browse] [--rows ] [--output ] [-- ]... clet list [--json] clet help clet --help @@ -273,7 +273,7 @@ clet --version ╚═╝╩═╝╚═╝ ╩ ``` -**Built-in flags.** `--initial`, `--title`, `--json`, `--timeout`, `--fullscreen`, `--cat`, `--no-browse`, `--rows`, and `--output` are parsed at the host level and apply to every clet. Anything else of the form `-- ` is validated against the dispatched clet's option descriptors and then forwarded as a clet-specific option (see each clet's `clet help `). Unknown options are rejected with a usage error (exit 2) instead of consuming the next token as a value. Bare positional tokens are forwarded as `CletRunOptions.Arguments` for clets that consume them (e.g. `select`, `multi-select`, `md`); clets that do not consume positional args reject them with a usage error (exit 2) before the clet runs. See [D-025](decisions.md) for the `AcceptsPositionalArgs` design and [D-014](decisions.md) for why `--title` is a host flag. +**Built-in flags.** `--initial`, `--title` (alias `--prompt`), `--json`, `--timeout`, `--fullscreen`, `--cat`, `--no-browse`, `--rows`, and `--output` are parsed at the host level and apply to every clet. Anything else of the form `-- ` is validated against the dispatched clet's option descriptors and then forwarded as a clet-specific option (see each clet's `clet help `). Unknown options are rejected with a usage error (exit 2) instead of consuming the next token as a value. Bare positional tokens are forwarded as `CletRunOptions.Arguments` for clets that consume them (e.g. `select`, `multi-select`, `md`); clets that do not consume positional args reject them with a usage error (exit 2) before the clet runs. See [D-025](decisions.md) for the `AcceptsPositionalArgs` design and [D-014](decisions.md) for why `--title` is a host flag. **`--cat` (non-interactive rendering).** When `--cat` is passed to a viewer clet (currently `md`), content is rendered as ANSI-formatted text directly to stdout — no alt-screen, no interactive session. Useful for piping (`clet md --cat README.md | less -R`), CI logs, and AI agents. Content is resolved from file arguments, `--initial`, or stdin, same as the normal viewer path. If no content is available, exits with usage error (exit 2). See [D-027](decisions.md). diff --git a/specs/decisions.md b/specs/decisions.md index d8ec524..c66e794 100644 --- a/specs/decisions.md +++ b/specs/decisions.md @@ -172,9 +172,9 @@ Revisit when download numbers show users hitting Gatekeeper/SmartScreen friction ## D-014: `--title` is a built-in CLI flag, not a per-clet option (Active) -**Context.** Every input clet renders its `RunnableWrapper`/`OpenDialog` with a `Title` and falls back to a per-clet default ("Select an option…", "Enter a number…", etc.). All 14 clets honor `CletRunOptions.Title` if set. The CLI parser, however, had no way to populate it — `--title` was being routed into the per-clet `--` bucket where most clets ignored it. +**Context.** Every input clet renders its `RunnableWrapper`/`OpenDialog` with a `Title` and falls back to a per-clet default ("Select an option…", "Enter a number…", etc.). All 14 clets honor `CletRunOptions.Title` if set. The CLI parser, however, had no way to populate it — `--title` was being routed into the per-clet `--` bucket where most clets ignored it. Additionally, `ConfirmClet` had a per-clet `--prompt` option that overrode `--title`, making the two flags behave inconsistently across clets. -**Decision.** `--title ` is parsed at the host level (`CommandLineRoot.DispatchAlias`) alongside `--initial`, `--json`, `--timeout`, `--fullscreen`, and stored as `CletRunOptions.Title`. Individual clets do **not** declare `title` in their `Options` list — adding it 14 times would be churn and the per-clet help would falsely imply each clet handles it differently. +**Decision.** `--title ` (and its alias `--prompt `) is parsed at the host level (`CommandLineRoot.DispatchAlias`) alongside `--initial`, `--json`, `--timeout`, `--fullscreen`, and stored as `CletRunOptions.Title`. `--prompt` / `-p` is a synonym for `--title` / `-t` — both set the same value. Individual clets do **not** declare `title` or `prompt` in their `Options` list — adding it 14 times would be churn and the per-clet help would falsely imply each clet handles it differently. **Status.** Active. Listed in root help (§4.7). diff --git a/src/Clet/Clets/Input/ConfirmClet.cs b/src/Clet/Clets/Input/ConfirmClet.cs index 458cbc6..6e97b92 100644 --- a/src/Clet/Clets/Input/ConfirmClet.cs +++ b/src/Clet/Clets/Input/ConfirmClet.cs @@ -11,10 +11,7 @@ internal sealed class ConfirmClet : IClet public CletKind Kind => CletKind.Input; public Type ResultType => typeof (bool); - public IReadOnlyList Options => - [ - new ("prompt", "p", typeof (string), "Custom prompt text displayed as the title.", false, null), - ]; + public IReadOnlyList Options => []; public bool TryValidateInitial (string initial, CletRunOptions options) => string.Equals (initial, "true", StringComparison.OrdinalIgnoreCase) @@ -48,16 +45,13 @@ public bool TryValidateInitial (string initial, CletRunOptions options) } } - // --prompt option overrides --title for the window title - string effectiveTitle = options.CletOptions?.TryGetValue ("prompt", out string? promptValue) == true - ? promptValue - : "Confirm (Enter to accept, Esc to cancel)"; + string defaultTitle = "Confirm (Enter to accept, Esc to cancel)"; RunnableWrapper wrapper = new (selector); return await InputCletRunner.RunAsync ( app, wrapper, options, - effectiveTitle, + defaultTitle, cancellationToken, result => { diff --git a/src/Clet/Help/confirm.md b/src/Clet/Help/confirm.md index 967e192..e2378b0 100644 --- a/src/Clet/Help/confirm.md +++ b/src/Clet/Help/confirm.md @@ -4,8 +4,8 @@ # Yes/No confirmation: clet confirm --prompt "Deploy to production?" -# With a title: -clet confirm --title "Confirm" --prompt "Delete 40k rows?" +# Equivalent using --title: +clet confirm --title "Delete 40k rows?" # Default to yes: clet confirm --initial "true" --prompt "Continue?" diff --git a/src/Clet/Help/overview.md b/src/Clet/Help/overview.md index a6e37e0..48a5ba0 100644 --- a/src/Clet/Help/overview.md +++ b/src/Clet/Help/overview.md @@ -13,7 +13,7 @@ | Option | Description | |--------|-------------| | `--initial`, `-i` `` | Pre-populate the input with a value | -| `--title`, `-t` `` | Custom title for the prompt window | +| `--title`, `--prompt`, `-t`, `-p` `` | Custom title for the prompt window | | `--json`, `-j` | Emit result as a JSON envelope | | `--timeout` `` | Auto-cancel after duration (e.g. `30s`, `1m`, `500ms`) | | `--fullscreen`, `-f` | Force fullscreen rendering (default for viewers) | diff --git a/src/Clet/Hosting/CommandLineRoot.cs b/src/Clet/Hosting/CommandLineRoot.cs index b52d6c0..9b5f7cb 100644 --- a/src/Clet/Hosting/CommandLineRoot.cs +++ b/src/Clet/Hosting/CommandLineRoot.cs @@ -178,11 +178,11 @@ private async Task DispatchAlias ( continue; } - if (arg is "--title" or "-t") + if (arg is "--title" or "-t" or "--prompt" or "-p") { if (!TryReadOptionValue (args, i, clet, out string value)) { - await stderr.WriteLineAsync ("error: --title requires a value."); + await stderr.WriteLineAsync ($"error: {arg} requires a value."); return ExitCodes.UsageError; } @@ -332,7 +332,7 @@ private static bool IsKnownOptionToken (string token, IClet clet) or "--allow-file" or "--timeout" or "--initial" or "-i" - or "--title" or "-t" + or "--title" or "-t" or "--prompt" or "-p" or "--output" or "-o" or "--rows" or "-r") { diff --git a/tests/Clet.UnitTests/CommandLineRootTests.cs b/tests/Clet.UnitTests/CommandLineRootTests.cs index 37d9fb7..21de3e8 100644 --- a/tests/Clet.UnitTests/CommandLineRootTests.cs +++ b/tests/Clet.UnitTests/CommandLineRootTests.cs @@ -182,7 +182,7 @@ public async Task Alias_ShortTitleFlag_MissingValue_ExitsWithUsageError () int exit = await root.InvokeAsync (["select", "-t"], CancellationToken.None, stdout, stderr); Assert.Equal (ExitCodes.UsageError, exit); - Assert.Contains ("--title", stderr.ToString ()); + Assert.Contains ("-t", stderr.ToString ()); } [Fact] @@ -196,6 +196,28 @@ public async Task Alias_ShortInitialFlag_MissingValue_ExitsWithUsageError () Assert.Contains ("--initial", stderr.ToString ()); } + [Fact] + public async Task Alias_PromptMissingValue_ExitsWithUsageError () + { + (CommandLineRoot root, StringWriter stdout, StringWriter stderr) = Build (); + + int exit = await root.InvokeAsync (["select", "--prompt"], CancellationToken.None, stdout, stderr); + + Assert.Equal (ExitCodes.UsageError, exit); + Assert.Contains ("--prompt", stderr.ToString ()); + } + + [Fact] + public async Task Alias_ShortPromptFlag_MissingValue_ExitsWithUsageError () + { + (CommandLineRoot root, StringWriter stdout, StringWriter stderr) = Build (); + + int exit = await root.InvokeAsync (["select", "-p"], CancellationToken.None, stdout, stderr); + + Assert.Equal (ExitCodes.UsageError, exit); + Assert.Contains ("-p", stderr.ToString ()); + } + [Fact] public async Task Alias_PositionalArgs_NonPositionalClet_ExitsWithUsageError () { diff --git a/tests/Clet.UnitTests/ConfirmCletTests.cs b/tests/Clet.UnitTests/ConfirmCletTests.cs index bf2421d..ebf1db1 100644 --- a/tests/Clet.UnitTests/ConfirmCletTests.cs +++ b/tests/Clet.UnitTests/ConfirmCletTests.cs @@ -45,13 +45,11 @@ public void Aliases_ContainsConfirm () } [Fact] - public void Options_ContainsPrompt () + public void Options_IsEmpty () { ConfirmClet clet = new (); - Assert.Single (clet.Options); - Assert.Equal ("prompt", clet.Options[0].Name); - Assert.False (clet.Options[0].Required); + Assert.Empty (clet.Options); } [Fact] From 811567698a9367af744566c3effc9df50a591118 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 22 May 2026 17:35:32 +0000 Subject: [PATCH 3/3] Improve confirm.md examples to show --title and --prompt interchangeably --- src/Clet/Help/confirm.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Clet/Help/confirm.md b/src/Clet/Help/confirm.md index e2378b0..51e1ede 100644 --- a/src/Clet/Help/confirm.md +++ b/src/Clet/Help/confirm.md @@ -4,11 +4,11 @@ # Yes/No confirmation: clet confirm --prompt "Deploy to production?" -# Equivalent using --title: +# --title and --prompt are interchangeable: clet confirm --title "Delete 40k rows?" # Default to yes: -clet confirm --initial "true" --prompt "Continue?" +clet confirm --initial "true" --title "Continue?" # Use in a script: if clet confirm --prompt "Apply patch?"; then