Documentation home · Project README · Code generator · API reference
cli-fp is an Object Pascal library for native command-line applications. It
provides root commands, named commands and subcommands, typed parameter
metadata, generated help, shell completion, coloured output, and progress
indicators.
This guide starts with complete programs and then explains individual features. For the shortest first success, generate and compile the README tutorial project before returning here.
The project is tested with Free Pascal 3.2.2. Confirm that the compiler is on your command path:
fpc -iV
When a command in this guide contains -Fu../cli-fp/src, adjust the path so it
points to this repository's src directory. -Fu adds a unit-search path;
units named in a Pascal uses clause are found there.
If .lpr, .pas, uses, or {$mode objfpc} are new to you, read
Free Pascal in two minutes.
| Goal | Start here |
|---|---|
| Build a conventional CLI with named commands | Simple application |
Build a focused tool invoked as app [options] |
Root-command application |
Build git-style nested commands |
Git-like CLI |
| Add spinners or progress bars | Progress indicators |
| Look up registration calls quickly | API cheat sheet |
| Generate the project structure | Code-generator guide |
- Features
- How an application executes
- Installation
- Quick Start
- Parameter types and validation
- Command-line usage
- Troubleshooting
- Best practices
- API cheat sheet
- Bash completion
- PowerShell tab completion
- Where to go next
- Command and subcommand support
- Optional root commands for applications with a default action
- Parameter handling with validation
- Progress indicators (spinner and progress bar)
- Colored console output
- Built-in help system
- Automatic usage examples generation
flowchart TD
A[Start Application] --> B[Parse Command Line]
B --> C{Empty Command Line?}
C -->|Yes| D{Root Command?}
D -->|No| R[Show General Help]
D -->|Yes| M
C -->|No| E{Application-level request?}
E -->|Yes| F[Show Help, Version, or Completion Script]
E -->|No| G{Leading option?}
G -->|Yes| S{Root Command?}
S -->|No| H[Show Error & Brief Help]
S -->|Yes| M
G -->|No| T{Valid Command?}
T -->|No| H
T -->|Yes| I{Has Subcommands?}
I -->|Yes| J[Process Subcommand]
J --> K{Valid Subcommand?}
K -->|No| L[Show Subcommand Help]
K -->|Yes| M[Parse Parameters]
I -->|No| M
M --> N{Valid Parameters?}
N -->|No| O[Show Parameter Help]
N -->|Yes| P[Execute Command]
P --> Q[Return Exit Code]
R --> Q
F --> Q
H --> Q
L --> Q
O --> Q
flowchart TD
A[Start Command Creation] --> B[Define Command Class]
B --> C[Implement Execute Method]
C --> D[Create Command Instance]
D --> E[Add Parameters]
E --> F{Parameter Type?}
F -->|String| G[Add String Parameter]
F -->|Integer| H[Add Integer Parameter]
F -->|Boolean| I[Add Boolean Flag]
F -->|Float| J[Add Float Parameter]
G --> K[Configure Parameter]
H --> K
I --> K
J --> K
K --> L[Set Short Flag]
L --> M[Set Long Flag]
M --> N[Set Description]
N --> O[Set Required/Optional]
O --> P[Set Default Value]
P --> Q{More Parameters?}
Q -->|Yes| E
Q -->|No| R[Register Command]
-
Clone the repository:
git clone https://github.com/ikelaiah/cli-fp.git
-
Add to your project:
uses CLI.Interfaces, CLI.Application, CLI.Command, CLI.Parameter, CLI.Progress, // For progress indicators CLI.Console; // For colored output
program MyApp;
{$mode objfpc}{$H+}{$J-}
uses
SysUtils,
CLI.Interfaces,
CLI.Application,
CLI.Command,
CLI.Parameter,
CLI.Console;
type
TGreetCommand = class(TBaseCommand)
public
function Execute: Integer; override;
end;
function TGreetCommand.Execute: Integer;
var
PersonName: string;
begin
if GetParameterValue('--name', PersonName) then
TConsole.WriteLn('Hello, ' + PersonName + '!')
else
TConsole.WriteLn('Hello, World!');
Result := 0;
end;
var
App: ICLIApplication;
Cmd: TGreetCommand;
ExitCode: Integer;
begin
try
App := CreateCLIApplication('MyApp', '1.0.0');
Cmd := TGreetCommand.Create('greet', 'Greet a person');
// Add a string parameter for the name. It's optional.
// If not provided and no default is set here, 'Hello, World!' will be printed by the Execute logic.
Cmd.AddStringParameter('-n', '--name', 'Name to greet', False);
// The above is a shorthand for:
// Cmd.AddParameter('-n', '--name', 'Name to greet', False, ptString, '');
App.RegisterCommand(Cmd);
ExitCode := App.Execute;
except
on E: Exception do
begin
TConsole.WriteLn('Error: ' + E.Message, ccRed);
ExitCode := 1;
end;
end;
Halt(ExitCode);
end.An optional root command runs as the executable's default action. It uses the
same TBaseCommand parameter and validation APIs as named commands, but its
name is not typed on the command line:
type
TGreetRootCommand = class(TBaseCommand)
public
function Execute: Integer; override;
end;
function TGreetRootCommand.Execute: Integer;
var
PersonName: string;
begin
if not GetParameterValue('--name', PersonName) then
PersonName := 'World';
WriteLn('Hello, ', PersonName, '!');
Result := 0;
end;
var
App: ICLIApplication;
RootCommand: TGreetRootCommand;
begin
RootCommand := TGreetRootCommand.Create('', 'Greet someone');
RootCommand.AddStringParameter('-n', '--name', 'Name to greet',
False, 'World');
App := CreateCLIApplication('MyApp', '1.0.0', RootCommand);
Halt(App.Execute);
end.Usage:
MyApp
MyApp --name Gus
Root commands are opt-in. The existing two-argument factory continues to show
general help for an empty command line. A root command may coexist with
commands registered through RegisterCommand; a leading command name selects
the named command, while a leading option selects the root command.
Root parameters are not inherited by named commands, and positional arguments are not currently supported. Sole help/version requests and first-argument completion-script requests are dispatched at application level before root execution.
type
TRepoCommand = class(TBaseCommand)
public
function Execute: Integer; override;
end;
TCloneCommand = class(TBaseCommand)
public
function Execute: Integer; override;
end;
TInitCommand = class(TBaseCommand)
public
function Execute: Integer; override;
end;
function TRepoCommand.Execute: Integer;
begin
// The application displays command help when no subcommand is selected.
Result := 0;
end;
function TCloneCommand.Execute: Integer;
var
Url: string;
Progress: IProgressIndicator;
begin
if not GetParameterValue('--url', Url) then
begin
TConsole.WriteLn('Error: URL is required', ccRed);
Exit(1);
end;
Progress := CreateSpinner(ssLine);
Progress.Start;
try
TConsole.WriteLn('Cloning from ' + Url + '...', ccCyan);
Sleep(2000); // Simulate work
TConsole.WriteLn('Clone complete!', ccGreen);
Result := 0;
finally
Progress.Stop;
end;
end;
function TInitCommand.Execute: Integer;
var
Path: string;
Progress: IProgressIndicator;
begin
if not GetParameterValue('--path', Path) then
Path := GetCurrentDir;
Progress := CreateSpinner(ssLine);
Progress.Start;
try
TConsole.WriteLn('Initializing repository at ' + Path + '...', ccCyan);
Sleep(1000); // Simulate work
TConsole.WriteLn('Repository initialized!', ccGreen);
Result := 0;
finally
Progress.Stop;
end;
end;
var
App: ICLIApplication;
RepoCmd: TRepoCommand;
CloneCmd: TCloneCommand;
InitCmd: TInitCommand;
ExitCode: Integer;
begin
try
App := CreateCLIApplication('MyGit', '1.0.0');
RepoCmd := TRepoCommand.Create('repo', 'Repository management');
CloneCmd := TCloneCommand.Create('clone', 'Clone a repository');
// Add a required string parameter for the URL.
CloneCmd.AddStringParameter('-u', '--url', 'Repository URL to clone', True);
// The above is a shorthand for:
// CloneCmd.AddParameter('-u', '--url', 'Repository URL to clone', True, ptString, '');
RepoCmd.AddSubCommand(CloneCmd);
InitCmd := TInitCommand.Create('init', 'Initialize a repository');
// Add an optional path parameter, defaulting to the current directory.
InitCmd.AddPathParameter('-p', '--path', 'Path to initialize repository', False, GetCurrentDir);
// The above is a shorthand for:
// InitCmd.AddParameter('-p', '--path', 'Path to initialize repository', False, ptPath, GetCurrentDir);
RepoCmd.AddSubCommand(InitCmd);
App.RegisterCommand(RepoCmd);
ExitCode := App.Execute;
except
on E: Exception do
begin
TConsole.WriteLn('Error: ' + E.Message, ccRed);
ExitCode := 1;
end;
end;
Halt(ExitCode);
end;The framework provides two types of progress indicators: spinners for indeterminate progress (when you don't know the total steps) and progress bars for determinate progress (when you know the total steps). Both support optional inline status text via Update(Progress, Caption).
The framework supports various spinner styles to match your application's needs:
-
Dots (ssDots) - Braille dots animation
⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏Best for: Modern terminals with Unicode support
Spinner := CreateSpinner(ssDots);
-
Line (ssLine) - Simple ASCII line animation
-\|/Best for: Legacy terminals or when Unicode isn't supported
Spinner := CreateSpinner(ssLine); // Default style -
Circle (ssCircle) - Unicode circle animation
◐◓◑◒Best for: Clean, minimalist look
Spinner := CreateSpinner(ssCircle);
-
Square (ssSquare) - Square rotation animation
◰◳◲◱Best for: Alternative to circle style
Spinner := CreateSpinner(ssSquare);
-
Arrow (ssArrow) - Arrow rotation animation
←↖↑↗→↘↓↙Best for: Directional indication
Spinner := CreateSpinner(ssArrow);
-
Bounce (ssBounce) - Bouncing dot animation
⠁⠂⠄⠂Best for: Subtle indication
Spinner := CreateSpinner(ssBounce);
-
Bar (ssBar) - Wave block animation
▏▎▍▌▋▊▉█▊▋▌▍▎▏Best for: Smooth, wave-like animation that flows left to right
Spinner := CreateSpinner(ssBar);
The animation creates a fluid motion by:
- Starting thin on the left (▏)
- Growing progressively thicker (▎▍▌▋▊▉)
- Reaching full block (█)
- Smoothly reducing thickness (▊▋▌▍▎▏) This creates a natural wave-like effect that's easy on the eyes.
Here's a complete example of using a spinner:
procedure ProcessFiles(const Files: TStringList);
var
Spinner: IProgressIndicator;
i: Integer;
begin
// Create a spinner with dots style
Spinner := CreateSpinner(ssDots);
// Start the spinner
Spinner.Start;
try
TConsole.WriteLn('Processing files...', ccCyan);
// Your processing loop
for i := 0 to Files.Count - 1 do
begin
// Update spinner (will animate)
Spinner.Update(0, 'Loading...'); // Progress value is ignored for spinners
// Do your work here
ProcessFile(Files[i]);
Sleep(100); // Simulate work
end;
TConsole.WriteLn('Processing complete!', ccGreen);
finally
// Always stop the spinner in a finally block
Spinner.Stop;
end;
end;Important notes for using spinners:
- Always use a try-finally block to ensure the spinner is stopped
- Call Update regularly to maintain animation
- Choose a style appropriate for your terminal's capabilities
- The Update parameter is ignored for spinners (used for interface compatibility)
For operations where you know the total steps, use a progress bar:
procedure CopyFiles(const Files: TStringList);
var
Progress: IProgressIndicator;
i: Integer;
begin
// Create a progress bar (total steps, width in characters)
Progress := CreateProgressBar(Files.Count, 20);
// Start the progress bar
Progress.Start;
try
TConsole.WriteLn('Copying files...', ccCyan);
// Your processing loop
for i := 0 to Files.Count - 1 do
begin
// Update progress (current step)
Progress.Update(i + 1, Format('File %d/%d', [i + 1, Files.Count]));
// Do your work here
CopyFile(Files[i], DestPath + ExtractFileName(Files[i]));
Sleep(50); // Simulate work
end;
TConsole.WriteLn('Copy complete!', ccGreen);
finally
// Always stop the progress bar in a finally block
Progress.Stop;
end;
end;Progress bar features:
- Shows percentage complete
- Visual bar indicates progress
- Automatically updates only when percentage changes
- Width is customizable
Use a Spinner when:
- The operation has no measurable progress
- You can't determine the total steps
- The operation is relatively quick
- You want to show activity without specifics
Use a Progress Bar when:
- You know the total number of steps
- The operation has measurable progress
- You want to show specific completion percentage
- The user needs to know how much longer to wait
The framework provides a rich set of parameter types with built-in validation:
// Any text value
AddStringParameter('-n', '--name', 'Name parameter');// Must be a valid integer
AddIntegerParameter('-c', '--count', 'Count parameter', True); // Required// Must be a valid floating-point number
AddFloatParameter('-r', '--rate', 'Rate parameter');// Flag: Presence indicates true, false by default
AddFlag('-v', '--verbose', 'Enable verbose mode');
// Boolean: Must be 'true' or 'false'
AddBooleanParameter('-d', '--debug', 'Debug mode', False, 'false');Note:
AddFlaguses the default string'false'unless you override it. Presence without an explicit value produces'true'. A custom default of'true'therefore makes an absent flag true, which is usually surprising.
// Recommended portable format: "YYYY-MM-DD HH:MM"
AddDateTimeParameter('-d', '--date', 'Date parameter');// Must match one of the pipe-separated values
AddEnumParameter('-l', '--level', 'Log level', 'debug|info|warn|error');// Must start with http://, https://, git://, or ssh://
AddUrlParameter('-u', '--url', 'Repository URL');// Comma-separated values
AddArrayParameter('-t', '--tags', 'Tag list');// Treat the retrieved value as sensitive
AddPasswordParameter('-k', '--api-key', 'API Key');Password parameters are returned to command code as ordinary strings. The framework does not automatically redact values written by your application or external logging.
The framework validates all parameters before executing a command. Each parameter type has specific validation rules:
- String: No validation
- Integer: Must be a valid integer number
- Float: Must be a valid floating-point number
- Boolean: Must be 'true' or 'false' (case-insensitive)
- DateTime: Uses
TryStrToDateTimewithyyyy-mm-dddate settings.YYYY-MM-DD HH:MMis the recommended portable form, but the current parser may also accept date-only values or seconds. - Enum: Must match one of the pipe-separated allowed values
- URL: Must start with http://, https://, git://, or ssh://
- Array: No validation on individual items
- Password: No validation or automatic output redaction
The framework provides clear error messages when validation fails:
Error: Parameter "--count" must be an integer
Error: Parameter "--rate" must be a float
Error: Parameter "--debug" must be "true" or "false"
Error: Parameter "--date" must be in format YYYY-MM-DD HH:MM
Error: Parameter "--url" must be a valid URL starting with http://, https://, git://, or ssh://
Error: Parameter "--level" must be one of: debug|info|warn|error
Parameters can be marked as required:
// Required parameter
AddIntegerParameter('-c', '--count', 'Count parameter', True);
// Optional parameter with default
AddStringParameter('-n', '--name', 'Name parameter', False, 'default');If a required parameter is missing, the command will not execute and an error message will be displayed:
Error: Required parameter "--count" not provided
Optional parameters can have default values:
// String with default
AddStringParameter('-n', '--name', 'Name parameter', False, 'World');
// Float with default
AddFloatParameter('-r', '--rate', 'Rate parameter', False, '1.0');
// Enum with default
AddEnumParameter('-l', '--level', 'Log level', 'debug|info|warn|error', False, 'info');The default value will be used when:
- The parameter is not provided on the command line
- A default value is specified
A non-empty default also satisfies the current required-parameter check.
Avoid combining Required = True with a default when you need to distinguish
user input from fallback configuration.
To retrieve parameter values in your command's Execute method:
function TMyCommand.Execute: Integer;
var
PersonName, CountStr, RateStr, Level: string;
Count: Integer;
Rate: Double;
begin
// Get parameter values with error checking
if GetParameterValue('--name', PersonName) then
WriteLn('Name: ', PersonName);
if GetParameterValue('--count', CountStr) and TryStrToInt(CountStr, Count) then
WriteLn('Count: ', Count);
if GetParameterValue('--rate', RateStr) and TryStrToFloat(RateStr, Rate) then
WriteLn('Rate: ', Rate:0:2);
if GetParameterValue('--level', Level) then
WriteLn('Level: ', Level);
Result := 0;
end;-
Check the Retrieved Boolean Text:
GetParameterValuereturnsTruewhen a value or non-empty default exists. Standard flags have the default string'false', so an absent flag normally still returnsTrue. UseSameText(Value, 'true')to determine its value; the return value does not tell you whether the user explicitly typed the flag. -
Convert Retrieved Strings Explicitly:
GetParameterValuereturns strings, so useSysUtilshelpers after reading the value:var CountValue, RateValue, EnabledValue: string; Count: Integer; Rate: Double; IsEnabled: Boolean; GetParameterValue('--count', CountValue); TryStrToInt(CountValue, Count); GetParameterValue('--rate', RateValue); TryStrToFloat(RateValue, Rate); GetParameterValue('--enabled', EnabledValue); IsEnabled := SameText(EnabledValue, 'true');
-
Provide Clear Descriptions: Parameter descriptions appear in help text:
AddStringParameter('-n', '--name', 'Your name (required for personalized greeting)');
-
Use Appropriate Types: Choose the most appropriate parameter type:
- Use
AddFlagfor simple on/off features - Use
AddBooleanParameterfor explicit true/false values - Use
AddEnumParameterfor fixed sets of values - Use
AddPasswordParameterfor sensitive data
- Use
myapp <command> [options]
myapp [root-options] # when a root command is configured- Used alone,
myapp -hormyapp --helpshows general application help. myapp --help-completeshows the complete command reference.myapp <command> --helpshows help for the selected command level.- Used alone,
myapp -vormyapp --versionprints application version information.
The framework supports various parameter formats:
- Long format:
--param=valueor--param value - Short format:
-p value - Boolean flags:
--flagor-f(false by default, true when present)
Example:
myapp test --flag # --flag is true
myapp test # --flag is false-
Command Not Found
- Verify command name spelling
- Check if command is properly registered with
App.RegisterCommand - When diagnosing dispatch, construct the concrete application class and
enable its debug output before execution:
Application := TCLIApplication.Create('MyApp', '1.0.0'); Application.DebugMode := True;
DebugModeis aTCLIApplicationproperty and is not exposed by theICLIApplicationreturned from the factory. Manage the concrete instance's lifetime normally.
-
Parameter Errors
- Check parameter format:
--param=value # Equals syntax --param value # Space syntax -p value # Short format
- Verify required parameters are provided
- Check parameter type matches expected value
- Use
GetParameterValuecorrectly:var Value: string; begin if GetParameterValue('--param', Value) then // A provided value or non-empty default is available else // No value or default is available end;
- Check parameter format:
-
Console Colors Not Working
- Windows foreground and background colours use the console API
- Unix-like systems require a terminal that interprets ANSI colour codes
- Cursor movement and position helpers require ANSI-compatible terminal behaviour on every platform
- Always reset colors:
TConsole.SetForegroundColor(ccRed); try // Your colored output finally TConsole.ResetColors; end;
-
Command Organization
- Group related functionality into commands
- Use subcommands for complex features
- Keep command names clear and consistent
- Follow naming conventions
-
User Experience
- Provide helpful descriptions
- Include examples in help text
- Use progress indicators for long operations
- Provide feedback for all operations
-
Error Handling
- Display clear error messages using appropriate colors
- Use appropriate exit codes
- Validate user input
- Always handle exceptions
-
Color Usage
- Use red for errors
- Use yellow for warnings
- Use green for success messages
- Use cyan for information
- Use white for normal output
-
Progress Indication
- Use spinners for indeterminate progress
- Use progress bars for determinate progress
- Always stop indicators in a finally block
- Provide status messages with progress
// Status indicators
'✓' // Success/Done
'✘' // Error/Failed
'⚠' // Warning
'ℹ' // Info
'❯' // Current item/Selection
'►' // Action/Process
'•' // Bullet point
'○' // Empty bullet
'●' // Filled bullet
// Progress/Loading
'⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏' // Braille dots animation
'◐◓◑◒' // Circle animation
'▏▎▍▌▋▊▉█' // Progress bar blocks
// Borders/Boxes
'╔═╗' // Top border
'║ ║' // Side borders
'╚═╝' // Bottom border
Essential commands for building CLI applications:
// Create new CLI application
App := CreateCLIApplication('AppName', '1.0.0');// TMyCommand must descend from TBaseCommand and override Execute
Cmd := TMyCommand.Create('command-name', 'Command description');// String parameter
Cmd.AddStringParameter('-n', '--name', 'Description', False, 'default');
// Integer parameter
Cmd.AddIntegerParameter('-c', '--count', 'Description', True); // Required
// Float parameter
Cmd.AddFloatParameter('-r', '--rate', 'Description', False, '1.0');
// Boolean flag (presence means true)
Cmd.AddFlag('-v', '--verbose', 'Description');
// Boolean parameter (explicit true/false)
Cmd.AddBooleanParameter('-d', '--debug', 'Description', False, 'false');
// Path parameter
Cmd.AddPathParameter('-p', '--path', 'Description', False, GetCurrentDir);
// Enum parameter
Cmd.AddEnumParameter('-l', '--level', 'Description', 'debug|info|warn|error');
// DateTime parameter (recommended format: YYYY-MM-DD HH:MM)
Cmd.AddDateTimeParameter('-d', '--date', 'Description');
// Array parameter (comma-separated values)
Cmd.AddArrayParameter('-t', '--tags', 'Description', False, 'tag1,tag2');
// Password parameter (treat the retrieved string as sensitive)
Cmd.AddPasswordParameter('-k', '--key', 'Description', True);
// URL parameter (validates URL format)
Cmd.AddUrlParameter('-u', '--url', 'Description', True);var
StrValue, IntValueStr, FloatValueStr, BoolValueStr: string;
IntValue: Integer;
FloatValue: Double;
BoolValue: Boolean;
begin
// For non-boolean parameters, returns True when a value or default exists
if GetParameterValue('--param-name', StrValue) then
// Use StrValue...
// Retrieve text, then convert explicitly
if GetParameterValue('--count', IntValueStr) then
TryStrToInt(IntValueStr, IntValue);
if GetParameterValue('--rate', FloatValueStr) then
TryStrToFloat(FloatValueStr, FloatValue);
GetParameterValue('--verbose', BoolValueStr);
BoolValue := SameText(BoolValueStr, 'true');
end;// Both classes descend from TBaseCommand and override Execute
MainCmd := TGitCommand.Create('git', 'Git operations');
// Create and add subcommand
SubCmd := TCloneCommand.Create('clone', 'Clone repository');
MainCmd.AddSubCommand(SubCmd);// Register command
App.RegisterCommand(Cmd);
// Run application
Halt(App.Execute);// Spinner (for unknown duration)
Spinner := CreateSpinner(ssDots);
Spinner.Start;
try
Spinner.Update(0, 'Working...');
// Work here, calling Update regularly...
finally
Spinner.Stop;
end;
// Progress bar (for known steps)
Progress := CreateProgressBar(TotalSteps);
Progress.Start;
try
for i := 1 to TotalSteps do
begin
// Work here...
Progress.Update(i);
end;
finally
Progress.Stop;
end;The CLI framework provides an advanced Bash completion system. You can generate a completion script using the --completion-file global flag:
./yourcli --completion-file > myapp-completion.shSafe Usage:
- Do NOT write the completion script directly to your
.bashrcor.bash_profile. - Instead, source the generated script from your shell config:
echo "source \"$PWD/myapp-completion.sh\"" >> ~/.bashrcThe CLI can only warn you if you pass
.bashrc(or similar) as a direct argument. If you use shell redirection (> ~/.bashrc), the CLI cannot detect this, so please follow the safe usage instructions above.
- Tab-completion for all commands, subcommands, and parameters
- Context-aware command metadata: Subcommand and parameter candidates are scoped to the current command path; built-in application flags follow the behavior described below
- Root options: Starting
-at the root offers root-command parameters plus--help,-h,--help-complete,--version,-v,--completion-file, and--completion-file-pwsh. - Named command options: At an empty new token, completion includes
subcommands, command parameters, and help. With an option prefix, the current
engine also exposes
--versionand-v. - Automatic value completion: Boolean parameters automatically complete with
true/false, and enum parameters complete with their allowed values. - Stays up-to-date with your CLI's structure
- Requires Bash 4+ and the common
sedandtailutilities
- The generated shell function forwards the current tokens to the executable's
hidden
__completeentrypoint. DoCompleteresolves the command path and returns one candidate per line, followed by a completion-directive line.- The generated script currently also emits a command-tree associative array
for compatibility, but dynamic completion is driven by
__complete.
Example: If your CLI has a structure like:
mycli repo clone --url ...
mycli repo init --path ...
mycli repo remote add ...
Then, after typing mycli repo and pressing Tab, you will see its valid
subcommands plus help. After mycli repo clone -, you will see valid
parameters for clone, help, and the version options currently recognized by
the completion engine.
- The completion script asks the running application for candidates, so command metadata and root parameters stay synchronized with the executable.
- Empty root completion is commands-first. Type
-or--to request root and application options. - Boolean and enum value candidates come from parameter metadata.
The CLI now provides robust, context-aware PowerShell tab completion for all commands, subcommands, and flags. This matches the experience of modern CLI tools (e.g., Go's Cobra, git, etc.).
- Generate the completion script:
./YourApp.exe --completion-file-pwsh > myapp-completion.ps1
- Load it in your PowerShell session:
. ./myapp-completion.ps1
- (Optional) Add the above line to your
$PROFILEfor automatic loading.
- After typing the executable and a space, press Tab to see named commands.
Type
-or--to see root and application options. - After typing a subcommand and a space, press Tab to see sub-subcommands and flags for that subcommand.
- Command, subcommand, and parameter candidates are scoped to the resolved command path.
- Automatic value completion: Boolean parameters automatically complete with
true/false, and enum parameters complete with their allowed values. - If there are no further subcommands, only flags are shown.
- If nothing matches, file completion is suppressed.
PS> ./SubCommandDemo.exe <Tab>
repo
PS> ./SubCommandDemo.exe repo <Tab>
init clone remote --help -h
- Uses
Register-ArgumentCompleter; PowerShell 7+ additionally receives native executable registration - The completion script is dynamically generated from the CLI command tree.
- Bash completion is also supported (see Bash Completion).
- Generate a maintainable project layout with the code-generator guide.
- Look up exact signatures in the API reference.
- Read the technical documentation when changing parser, ownership, help, or completion internals.
- Explore the focused example programs.