Build native command-line tools in Object Pascal.
cli-fp adds command trees, typed option metadata, generated help, Bash and
PowerShell completion, progress indicators, and coloured output to an ordinary
Free Pascal program. The core framework uses standard FPC units and produces
a normal native executable—there is no separate application runtime to deploy.
The repository is also a practical Free Pascal showcase: it contains an interface-based framework, a JSON-driven source generator, platform-aware console code, shell-completion protocols, and an FPCUnit test suite. CI builds and tests the project on Windows and Linux.
| If you are... | Start with... |
|---|---|
| New to Free Pascal | Install Free Pascal, then read Free Pascal in two minutes |
New to cli-fp |
Build your first generated CLI |
| Adding CLI support to an existing Pascal program | Manual Quick Start |
| Using Lazarus | Lazarus setup |
| Looking for a specific API or design detail | Documentation map |
cli-fp targets Free Pascal 3.2.2. Install FPC from the
official download page, or use an
installation supplied with Lazarus. Confirm the compiler is available:
fpc -iV
The command should print 3.2.2. The
official Free Pascal manuals include a
language reference, compiler user guide, RTL reference, and FCL reference.
Some Linux distributions split the compiler and Free Component Library into separate packages. On Debian and Ubuntu, the same packages used by this project's CI can be installed with:
sudo apt-get install fp-compiler fp-units-fclThe FCL package supplies the JSON units used by cli-fp-gen.
This path creates, compiles, and runs a real application before you need to understand the framework internals.
git clone https://github.com/ikelaiah/cli-fp.git
cd cli-fp
fpc -Futools/cli-fp-gen/src tools/cli-fp-gen/cli_fp_gen.lpr
./tools/cli-fp-gen/cli_fp_gen init ./build-temp/myapp --name myapp
cd build-temp/myapp
fpc -Fu../../src -Fu./src -Fu./src/generated -Fu./src/commands ./src/Myapp.lpr
./src/Myapp greet --helpgit clone https://github.com/ikelaiah/cli-fp.git
Set-Location .\cli-fp
fpc "-Futools\cli-fp-gen\src" .\tools\cli-fp-gen\cli_fp_gen.lpr
.\tools\cli-fp-gen\cli_fp_gen.exe init .\build-temp\myapp --name myapp
Set-Location .\build-temp\myapp
fpc "-Fu..\..\src" "-Fu.\src" "-Fu.\src\generated" "-Fu.\src\commands" .\src\Myapp.lpr
.\src\Myapp.exe greet --helpYou now have a native executable with a registered greet command, generated
usage text, a typed --name option, and no application runtime dependency.
Open src/commands/Myapp_Command_Greet.pas. Command files are user-owned, so
the generator will not overwrite your implementation during normal
regeneration. Replace the generated Execute method with:
function TGreetCommand.Execute: Integer;
var
PersonName: string;
begin
if not GetParameterValue('--name', PersonName) then
PersonName := 'World';
WriteLn('Hello, ', PersonName, '!');
Result := 0;
end;Compile again with the same fpc command. On Linux or macOS, run:
./src/Myapp greet --name AdaOn Windows, run:
.\src\Myapp.exe greet --name AdaHello, Ada!
That small command already uses class inheritance, a virtual method, generated parameter registration, framework parsing and defaults, and native compilation.
You only need a few conventions to follow this project:
| Pascal concept | Meaning in this repository |
|---|---|
.lpr file |
The program entry point—the file passed to fpc |
.pas file |
Usually a reusable unit containing classes or other declarations |
uses |
Imports units, similar to importing modules in other languages |
-Fu<path> |
Adds a directory to FPC's unit search path |
{$mode objfpc} |
Enables the Object Pascal language mode used by cli-fp |
TBaseCommand descendant |
A command class whose Execute method performs the work |
Halt(App.Execute) |
Returns the framework's exit code to the operating system |
Free Pascal gives this library strong typing, classes, interfaces, generics,
exceptions, deterministic try..finally cleanup, conditional compilation, and
native binaries. cli-fp packages those capabilities into a focused CLI
developer experience.
Use a root command for a focused utility, named commands for a git-style
application, or combine both:
| Invocation | What runs | Option ownership |
|---|---|---|
MyApp |
Root action with its defaults | Root command |
MyApp --name Gus |
Root action | Root command |
MyApp about |
Named about command |
about command |
MyApp about --verbose |
Named about command |
about command |
MyApp --help |
General application help, including root options | Application-level |
Root options belong to the default action; they are not inherited by named commands. Built-in requests have distinct scopes:
- Used alone,
--help/-hshows general application help,--help-completeshows the complete command reference, and--version/-vprints version information. - After a named command or subcommand,
--help/-hshows help for that selected level. - Completion-script generation is handled at application level when
--completion-fileor--completion-file-pwshis the first argument.
- Commands and subcommands: Build command trees such as
app repo remote add. - Optional root commands: Build focused applications that run as
app [options], while retaining named commands when useful. - Typed parameter metadata: Define strings, numbers, paths, URLs, enums, passwords, arrays, booleans, and date/time values, with type-specific validation where implemented.
- Generated help: Show usage, defaults, required options, subcommands, and a complete command reference.
- Shell completion: Generate Bash and PowerShell scripts with contextual command, option, Boolean, and enum suggestions.
- Terminal UX: Use ANSI/Windows colour support, seven spinner styles, and progress bars with inline captions.
- Project generation: Initialize projects and add or remove nested commands
from a versioned
clifp.jsonspecification. - Testable design: Exercise argument handling without mutating the process command line; the repository includes framework and generator suites.
- Build Your First Generated CLI
- Free Pascal in Two Minutes
- Choose Your CLI Shape
- Feature Tour
- Manual Quick Start
- Root Commands
- Parameter Types and Validation
- Shell Completion
- System Requirements
- Documentation Map
- Get the source
git clone https://github.com/ikelaiah/cli-fp.gitYou can also download a release archive. Keep the repository's src/
directory available to your project.
- Add the framework to your project
Add cli-fp/src to FPC's unit search path with -Fu, or in Lazarus open
Project → Project Options → Compiler Options → Paths → Other unit files.
Then add the units you need:
uses
CLI.Interfaces, // Core interfaces
CLI.Application, // Main application framework
CLI.Command, // Base command implementation
CLI.Parameter, // Parameter handling
CLI.Progress, // Optional: Progress indicators
CLI.Console; // Optional: Colored console output- Create your first CLI app
program MyApp;
{$mode objfpc}{$H+}{$J-}
uses
SysUtils,
CLI.Interfaces,
CLI.Application,
CLI.Command;
type
// Define a new command
TGreetCommand = class(TBaseCommand)
public
function Execute: integer; override;
end;
function TGreetCommand.Execute: integer;
var
PersonName: string;
begin
// Get parameter value using helper method
if GetParameterValue('--name', PersonName) then
WriteLn('Hello, ', PersonName, '!')
else
WriteLn('Hello, World!');
Result := 0;
end;
{ Main program }
var
App: ICLIApplication;
Cmd: TGreetCommand;
begin
App := CreateCLIApplication('MyApp', '1.0.0');
// Create and configure command
Cmd := TGreetCommand.Create('greet', 'Say hello');
Cmd.AddStringParameter('-n', '--name', 'Name to greet', False, 'World');
// Register command
App.RegisterCommand(Cmd);
// Execute application
Halt(App.Execute);
end.Save the file as MyApp.lpr. If the cli-fp repository is next to your
project directory, compile and run it with:
fpc -Fu../cli-fp/src MyApp.lpr
./MyApp greet --name "John"On Windows, run the generated executable as .\MyApp.exe.
Output:
$ ./MyApp greet --name "John"
Hello, John!
$ ./MyApp greet
Hello, World!
$ ./MyApp greet --help
Usage: MyApp greet [options]
Say hello
Options:
-n, --name Name to greet
Default: World
-h, --help Show this help message
A runtime-only Lazarus package is provided in packages/lazarus/cli_fp.lpk.
To use it, open the .lpk file in Lazarus, click “Compile,” then click “Add” to add it to your project’s required packages.
A root command is the application's unnamed default action. It lets a focused utility run with defaults or accept options directly after the executable, without inventing a command name solely to hold those options:
MyApp
MyApp --name Gus
It is still an ordinary command, so its options use the same parameter, validation, help, and completion APIs as named commands. Create it with an empty name and pass it to the three-argument application factory:
var
App: ICLIApplication;
RootCommand: TGreetCommand;
begin
RootCommand := TGreetCommand.Create('', 'Greet someone');
RootCommand.AddStringParameter('-n', '--name', 'Name to greet',
False, 'World');
App := CreateCLIApplication('MyApp', '1.0.0', RootCommand);
Halt(App.Execute);
end.The root command is explicit and optional. Applications using the existing
two-argument CreateCLIApplication continue to show general help when invoked
without a named command.
With a root command configured, no arguments or a leading root option selects the root action. A registered command name still selects that named command. Root parameters are not application-global and are not automatically available to named commands. The framework does not currently model positional arguments or Cobra-style persistent flags.
Named commands can coexist with a root command:
MyApp --name Gus
MyApp about
See RootCommandDemo for a
complete example.
The framework records parameter types for validation and help generation. Command implementations retrieve the resulting values as strings and convert them as needed:
// String parameter
Cmd.AddStringParameter('-n', '--name', 'Name to greet');
// Integer parameter (required)
Cmd.AddIntegerParameter('-c', '--count', 'Number of items', True);
// Float parameter with default
Cmd.AddFloatParameter('-r', '--rate', 'Processing rate', False, '1.0');// Flag (true when present, false by default)
Cmd.AddFlag('-v', '--verbose', 'Enable verbose output'); // Standard CLI behavior
// Boolean parameter (explicit true/false)
Cmd.AddBooleanParameter('-d', '--debug', 'Enable debug mode', False, 'false');Note: By default, flags created with
AddFlagarefalseunless present on the command line. If you specify a default value of'true', the flag will betrueeven if not present, which is nonstandard for CLI flags and not recommended unless you have a specific use case.
// DateTime (YYYY-MM-DD HH:MM)
Cmd.AddDateTimeParameter('-d', '--date', 'Start date');
// Enum with allowed values
Cmd.AddEnumParameter('-l', '--level', 'Log level', 'debug|info|warn|error');
// URL with protocol validation
Cmd.AddUrlParameter('-u', '--url', 'Repository URL');
// File or directory path
Cmd.AddPathParameter('-p', '--path', 'Target path');
// Array (comma-separated)
Cmd.AddArrayParameter('-t', '--tags', 'Tag list');
// Password (stored as a string; handle it as sensitive data)
Cmd.AddPasswordParameter('-k', '--api-key', 'API Key');Each parameter type has built-in validation:
String: No validationInteger: Must be a valid integer numberFloat: Must be a valid floating-point numberFlag: Presence sets the value; absent flags use their defaultBoolean: Must be 'true' or 'false' (case-insensitive)DateTime: Parsed withTryStrToDateTime;YYYY-MM-DD HH:MMis the recommended portable inputEnum: Must match one of the pipe-separated allowed valuesURL: Must start with http://, https://, git://, or ssh://Path: No path-existence validationArray: No validation on individual itemsPassword: No validation or automatic output redaction
Generate a completion script from your application:
./yourcli --completion-file > myapp-completion.sh.\yourcli.exe --completion-file-pwsh > myapp-completion.ps1Completion is context-aware for root options, commands, subcommands, and their
parameters. Boolean values complete as true or false, and enum values use
their configured choices. At an empty root prompt, named commands are suggested
first; type - or -- to request root and global options.
See the user manual for safe installation
instructions and docs/completion-testing/ for the
detailed Bash and PowerShell verification guides.
Above: The ColorDemo example showing professional CLI styling with colors, Unicode characters, and progress indicators.
- Linux CI:
ubuntu-latestwith the distribution'sfp-compilerandfp-units-fclpackages - Windows CI:
windows-latestwith Free Pascal 3.2.2 from Lazarus 4.0
The CI workflow compiles and runs both the framework tests and generator tests. Lazarus itself is used to supply the Windows toolchain; the package is runtime-only and does not require Lazarus at application runtime.
The console implementation has Windows and Unix code paths, so macOS, FreeBSD, and other FPC-supported Unix systems are expected to be viable, but they are not currently exercised by this repository's CI. Reports and fixes from those platforms are welcome.
- No third-party libraries are required
- The framework uses units supplied with Free Pascal
- The generator additionally uses the standard FCL JSON units
- Free Pascal Compiler (the project is tested with FPC 3.2.2)
- Lazarus only when using the Lazarus IDE or supplied package
- Git only when cloning the repository rather than using a release archive
- Documentation home: Choose a guide by goal or experience
- User manual: Build applications and use framework features
- Code generator: Generate a project and evolve its command tree
- API reference: Look up public types and methods
- Technical documentation: Understand architecture and internals
- Examples: Explore focused, compilable applications
- Changelog: Review version history and updates
Perfect for building:
- Version Control Systems
- Build Tools
- Package Managers
- Development Tools
- System Utilities
- DevOps Tools
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
- Fork the Project
- Create your Feature Branch (
git checkout -b feature/AmazingFeature) - Commit your Changes (
git commit -m 'Add some AmazingFeature') - Push to the Branch (
git push origin feature/AmazingFeature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
- Inspired by modern CLI frameworks
- Built with Free Pascal and Lazarus IDE
- Thanks to the Free Pascal community for their support and contributions