32f26272ae
Five tools under one repo, all docs organized per DOCS-GUIDE.md: - aalogcli: .NET 4.8 / x86 CliFx CLI for reading System Platform binary logs (*.aaLGX) for LLM debugging, built on aaOpenSource/aaLog. Commands: last, tail, range, unread, fields. Stable JSON envelope under --llm-json. Build template under lib/build/ for rebuilding aaLogReader.dll. - aot: ArchestrA Object Toolkit 2014 v4.0 reference material. Dev guide (Markdown converted from CHM), API reference for the ArchestrA.Toolkit namespace, and the Monitor / Watchdog VS sample solutions. - graccesscli: .NET 4.8 / x86 CliFx CLI that automates Galaxy configuration via the ArchestrA GRAccess COM interop. Includes session daemon, IPC protocol, and llm-json envelope contract. - grdb: SQL/DDL exploration of the Galaxy Repository database. DDL captures, reusable queries, hierarchy / contained-name <-> tag-name translation notes. - histdb: LLM-oriented reference for AVEVA Historian retrieval. INSQL linked-server, extension tables, every wwXxx time-domain extension, every retrieval mode, alarm/event SQL recipes, REST API. Distilled from the 243-page Historian Retrieval Guide. Root contains: - CLAUDE.md: thin index pointing into each tool's README. - DOCS-GUIDE.md: doctrine for organizing docs for LLM consumption. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
246 lines
6.6 KiB
Markdown
246 lines
6.6 KiB
Markdown
# CliFx Reference (v2.3.6)
|
||
|
||
Local reference for the CliFx CLI framework. Source: https://github.com/Tyrrrz/CliFx
|
||
|
||
## Setup
|
||
|
||
```csharp
|
||
using CliFx;
|
||
|
||
public static class Program
|
||
{
|
||
public static async Task<int> Main() =>
|
||
await new CliApplicationBuilder()
|
||
.AddCommandsFromThisAssembly()
|
||
.Build()
|
||
.RunAsync();
|
||
}
|
||
```
|
||
|
||
**Important:** `Main()` must return the integer exit code from `RunAsync()`.
|
||
|
||
`RunAsync()` resolves args from `Environment.GetCommandLineArgs()` and env vars from `Environment.GetEnvironmentVariables()`. You can also provide them manually via overloads.
|
||
|
||
## Defining Commands
|
||
|
||
Commands implement `ICommand` and are decorated with `[Command]`:
|
||
|
||
```csharp
|
||
using CliFx;
|
||
using CliFx.Attributes;
|
||
|
||
[Command(Description = "Calculates the logarithm of a value.")]
|
||
public class LogCommand : ICommand
|
||
{
|
||
[CommandParameter(0, Description = "Value whose logarithm is to be found.")]
|
||
public required double Value { get; init; }
|
||
|
||
[CommandOption("base", 'b', Description = "Logarithm base.")]
|
||
public double Base { get; init; } = 10;
|
||
|
||
public ValueTask ExecuteAsync(IConsole console)
|
||
{
|
||
var result = Math.Log(Value, Base);
|
||
console.Output.WriteLine(result);
|
||
return default;
|
||
}
|
||
}
|
||
```
|
||
|
||
Use `IConsole` (not `System.Console`) for all console I/O — this enables testability.
|
||
|
||
## Parameters vs Options
|
||
|
||
| Aspect | `[CommandParameter]` | `[CommandOption]` |
|
||
|---|---|---|
|
||
| Binding | By position order | By name (`--foo`) or short name (`-f`) |
|
||
| Required by default | Yes | No |
|
||
| Make optional | Omit `required` (last param only) | Omit `required` |
|
||
| Make required | Use `required` keyword | Use `required` keyword |
|
||
| Non-scalar (collections) | Only last parameter | Any option |
|
||
| Env var fallback | No | Yes (`EnvironmentVariable = "ENV_FOO"`) |
|
||
|
||
```csharp
|
||
// Required option
|
||
[CommandOption("foo")]
|
||
public required string Foo { get; init; }
|
||
|
||
// Optional parameter (must be last)
|
||
[CommandParameter(0)]
|
||
public string? OptionalParam { get; init; }
|
||
|
||
// Option with env var fallback
|
||
[CommandOption("foo", EnvironmentVariable = "ENV_FOO")]
|
||
public required string FooWithFallback { get; init; }
|
||
|
||
// Non-scalar option
|
||
[CommandOption("items")]
|
||
public required IReadOnlyList<string> Items { get; init; }
|
||
```
|
||
|
||
## Argument Syntax (POSIX-style)
|
||
|
||
```
|
||
myapp [...directives] [command] [...parameters] [...options]
|
||
```
|
||
|
||
- `--foo bar` → option "foo" = "bar"
|
||
- `-f bar` → option 'f' = "bar"
|
||
- `--switch` → option "switch" (no value / boolean)
|
||
- `-abc` → options 'a', 'b', 'c' (no value)
|
||
- `-i file1.txt file2.txt` → option 'i' = ["file1.txt", "file2.txt"]
|
||
- `-i file1.txt -i file2.txt` → same as above
|
||
|
||
## Value Conversion
|
||
|
||
Supports out of the box:
|
||
- **Primitives**: `int`, `bool`, `double`, `ulong`, `char`, etc.
|
||
- **Date/time**: `DateTime`, `DateTimeOffset`, `TimeSpan`
|
||
- **Enums**: by name or numeric value
|
||
- **String-initializable**: types with `ctor(string)` or static `Parse(string)` — e.g. `FileInfo`, `Guid`, `BigInteger`
|
||
- **Nullable** versions of all above
|
||
- **Collections**: `T[]`, `IReadOnlyList<T>`, `List<T>`, `HashSet<T>`, etc.
|
||
|
||
### Custom Converter
|
||
|
||
```csharp
|
||
public class VectorConverter : BindingConverter<Vector2>
|
||
{
|
||
public override Vector2 Convert(string? rawValue)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(rawValue))
|
||
return default;
|
||
|
||
var components = rawValue.Split('x', 'X', ';');
|
||
var x = int.Parse(components[0], CultureInfo.InvariantCulture);
|
||
var y = int.Parse(components[1], CultureInfo.InvariantCulture);
|
||
|
||
return new Vector2(x, y);
|
||
}
|
||
}
|
||
|
||
[CommandParameter(0, Converter = typeof(VectorConverter))]
|
||
public required Vector2 Point { get; init; }
|
||
```
|
||
|
||
## Multiple / Nested Commands
|
||
|
||
Give each command a unique name. Common name segments create hierarchy:
|
||
|
||
```csharp
|
||
[Command] // Default (unnamed) command
|
||
public class DefaultCommand : ICommand { ... }
|
||
|
||
[Command("cmd1")] // Child of default
|
||
public class FirstCommand : ICommand { ... }
|
||
|
||
[Command("cmd1 sub")] // Child of cmd1
|
||
public class SubCommand : ICommand { ... }
|
||
```
|
||
|
||
Usage: `myapp cmd1 sub arg1 --opt value`
|
||
|
||
Default command is optional. If absent, running without a command shows root help text.
|
||
|
||
## Error Reporting
|
||
|
||
Use `CommandException` to report errors with specific exit codes:
|
||
|
||
```csharp
|
||
throw new CommandException("Division by zero is not supported.", 133);
|
||
```
|
||
|
||
Exit codes should be 1–255 (8-bit unsigned) to avoid overflow on Unix.
|
||
|
||
## Graceful Cancellation
|
||
|
||
```csharp
|
||
public async ValueTask ExecuteAsync(IConsole console)
|
||
{
|
||
var cancellation = console.RegisterCancellationHandler();
|
||
await DoSomethingAsync(cancellation);
|
||
}
|
||
```
|
||
|
||
First interrupt signal triggers the token. Second interrupt force-kills.
|
||
|
||
## Dependency Injection
|
||
|
||
Use `UseTypeActivator(...)` with `Microsoft.Extensions.DependencyInjection`:
|
||
|
||
```csharp
|
||
await new CliApplicationBuilder()
|
||
.AddCommandsFromThisAssembly()
|
||
.UseTypeActivator(commandTypes =>
|
||
{
|
||
var services = new ServiceCollection();
|
||
services.AddSingleton<MyService>();
|
||
foreach (var commandType in commandTypes)
|
||
services.AddTransient(commandType);
|
||
return services.BuildServiceProvider();
|
||
})
|
||
.Build()
|
||
.RunAsync();
|
||
```
|
||
|
||
## Testing
|
||
|
||
Use `FakeInMemoryConsole` to test commands in isolation:
|
||
|
||
### Command-level test
|
||
|
||
```csharp
|
||
using var console = new FakeInMemoryConsole();
|
||
|
||
var command = new ConcatCommand { Left = "foo", Right = "bar" };
|
||
await command.ExecuteAsync(console);
|
||
|
||
var stdOut = console.ReadOutputString();
|
||
Assert.Equal("foo bar", stdOut);
|
||
```
|
||
|
||
### Application-level test
|
||
|
||
```csharp
|
||
using var console = new FakeInMemoryConsole();
|
||
|
||
var app = new CliApplicationBuilder()
|
||
.AddCommand<ConcatCommand>()
|
||
.UseConsole(console)
|
||
.Build();
|
||
|
||
var args = new[] { "--left", "foo", "--right", "bar" };
|
||
var envVars = new Dictionary<string, string>();
|
||
|
||
await app.RunAsync(args, envVars);
|
||
|
||
var stdOut = console.ReadOutputString();
|
||
Assert.Equal("foo bar", stdOut);
|
||
```
|
||
|
||
## Debug & Preview Directives
|
||
|
||
```bash
|
||
# Suspend until debugger attaches
|
||
myapp [debug] cmd -o
|
||
|
||
# Print parsed args without executing
|
||
myapp [preview] cmd arg1 -o foo
|
||
```
|
||
|
||
Disable in production:
|
||
|
||
```csharp
|
||
new CliApplicationBuilder()
|
||
.AllowDebugMode(false)
|
||
.AllowPreviewMode(false)
|
||
.Build();
|
||
```
|
||
|
||
## .NET Framework 4.8 Notes
|
||
|
||
- CliFx targets .NET Standard 2.0+, fully compatible with net48
|
||
- `required` keyword requires C# 11+ — on net48 (LangVersion 9.0), use properties with default values and validate in `ExecuteAsync` instead
|
||
- `init` setters require C# 9+ (supported on net48 with LangVersion 9.0)
|
||
- `async Task<int> Main` requires `System.Threading.Tasks` using directive on net48
|