{"id":32398991,"url":"https://github.com/zig-utils/zig-cli","last_synced_at":"2026-04-01T17:46:57.461Z","repository":{"id":320624032,"uuid":"1082736557","full_name":"zig-utils/zig-cli","owner":"zig-utils","description":"A modern, feature-rich CLI library for Zig.","archived":false,"fork":false,"pushed_at":"2026-03-26T16:33:09.000Z","size":126,"stargazers_count":1,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-27T06:49:36.672Z","etag":null,"topics":["binaries","cac","clapp","cli-framework","performance","zig"],"latest_commit_sha":null,"homepage":"","language":"Zig","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zig-utils.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-10-24T17:30:59.000Z","updated_at":"2026-03-26T16:33:17.000Z","dependencies_parsed_at":"2025-10-24T22:06:28.929Z","dependency_job_id":"436ffdae-29da-4c9d-a0c5-8f9d17ce5d6b","html_url":"https://github.com/zig-utils/zig-cli","commit_stats":null,"previous_names":["zig-utils/zig-cli"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/zig-utils/zig-cli","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zig-utils%2Fzig-cli","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zig-utils%2Fzig-cli/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zig-utils%2Fzig-cli/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zig-utils%2Fzig-cli/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zig-utils","download_url":"https://codeload.github.com/zig-utils/zig-cli/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zig-utils%2Fzig-cli/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31290585,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-01T13:12:26.723Z","status":"ssl_error","status_checked_at":"2026-04-01T13:12:25.102Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["binaries","cac","clapp","cli-framework","performance","zig"],"created_at":"2025-10-25T08:15:51.220Z","updated_at":"2026-04-01T17:46:57.455Z","avatar_url":"https://github.com/zig-utils.png","language":"Zig","readme":"# zig-cli\n\nA **type-safe, compile-time validated** CLI library for Zig 0.16+. Define your CLI with structs, get full type safety and zero runtime overhead.\n\n**No string-based lookups. No runtime parsing. Just pure, type-safe Zig.**\n\n```zig\n// Define options as a struct\nconst MyOptions = struct {\n    name: []const u8,\n    port: u16 = 8080,\n};\n\n// Type-safe action\nfn run(ctx: *cli.Context(MyOptions)) !void {\n    const name = ctx.get(.name);  // Compile-time validated!\n    const port = ctx.get(.port);\n}\n```\n\nInspired by modern CLI frameworks, built for Zig's strengths.\n\n## Features\n\n### CLI Framework (Type-Safe)\n- **Compile-Time Validation**: All field access validated at compile time\n- **Struct-Based Options**: Define CLI options as structs - auto-generate everything\n- **Zero Runtime Overhead**: All type checking happens at compile time\n- **IDE Autocomplete**: Full IntelliSense/LSP support for field names\n- **Command Routing**: Support for nested subcommands with aliases\n- **Auto-Generated Help**: Beautiful help text from struct definitions\n- **Type Safety**: Enums, optionals, nested structs all supported\n- **Middleware System**: Type-safe pre/post command hooks\n\n### Interactive Prompts\n- **State Machine**: Clean 5-state state machine (initial -\u003e active \u003c-\u003e error -\u003e submit/cancel)\n- **Event-driven**: Fine-grained event system for prompt interactions\n- **Terminal Detection**: Automatic Unicode/ASCII and color support detection\n- **Multiple Prompt Types**:\n  - Text input with validation and placeholders\n  - Confirmation prompts\n  - Select (single choice)\n  - MultiSelect (multiple choices)\n  - Password input with masking\n  - Number input with range validation (integer/float)\n  - Path selection with Tab autocomplete\n  - Group prompts for multi-step workflows\n  - Spinner for loading/activity indicators\n  - Progress bars with multiple styles\n  - Messages (intro, outro, note, log, cancel)\n  - Box/panel rendering for organized output\n\n### Terminal Features\n- **ANSI Colors**: Full color support with automatic detection\n- **Style Chaining**: Composable styling API (`.red().bold().underline()`)\n- **Raw Mode**: Cross-platform terminal raw mode handling\n- **Cursor Control**: Hide/show, save/restore cursor position\n- **Unicode Support**: Graceful fallback to ASCII when needed\n- **Keyboard Input**: Full keyboard event handling (arrows, enter, backspace, etc.)\n- **Dimension Detection**: Automatic terminal width/height detection\n- **Box Rendering**: Multiple box styles (single, double, rounded, ASCII)\n- **Table Rendering**: Column alignment, auto-width, multiple border styles\n\n### Configuration\n- **Multiple Formats**: TOML, JSONC (JSON with Comments), JSON5\n- **Auto-discovery**: Automatically find config files in standard locations\n- **Type-safe Access**: Typed getters for strings, integers, floats, booleans\n- **Nested Values**: Support for tables/objects and arrays\n- **Flexible Syntax**: Comments, trailing commas, unquoted keys (format-dependent)\n\n## Installation\n\nAdd zig-cli to your `build.zig`:\n\n```zig\nconst zig_cli = b.dependency(\"zig-cli\", .{\n    .target = target,\n    .optimize = optimize,\n});\n\nexe.root_module.addImport(\"zig-cli\", zig_cli.module(\"zig-cli\"));\n```\n\n## Quick Start\n\n### Basic CLI Application\n\n```zig\nconst std = @import(\"std\");\nconst cli = @import(\"zig-cli\");\n\n// 1. Define options as a struct - that's it!\nconst GreetOptions = struct {\n    name: []const u8 = \"World\",  // With default value\n    enthusiastic: bool = false,   // Boolean flag\n};\n\n// 2. Type-safe action function\nfn greet(ctx: *cli.Context(GreetOptions)) !void {\n    const io = std.Options.debug_io;\n    var buf: [4096]u8 = undefined;\n    var file_writer = std.Io.File.stdout().writerStreaming(io, \u0026buf);\n    const stdout = \u0026file_writer.interface;\n\n    // Compile-time validated field access - no strings!\n    const name = ctx.get(.name);\n    const punct: []const u8 = if (ctx.get(.enthusiastic)) \"!\" else \".\";\n\n    try stdout.print(\"Hello, {s}{s}\\n\", .{ name, punct });\n    try stdout.flush();\n}\n\npub fn main(init: std.process.Init) !void {\n    const allocator = init.gpa;\n\n    // 3. Create command - options auto-generated!\n    var cmd = try cli.Command(GreetOptions).init(allocator, \"greet\", \"Greet someone\");\n    defer cmd.deinit();\n\n    _ = cmd.setAction(greet);\n\n    // 4. Parse args and execute\n    var args_list = std.ArrayList([]const u8){};\n    defer args_list.deinit(allocator);\n\n    var args_iter = std.process.Args.Iterator.init(init.minimal.args);\n    _ = args_iter.skip(); // skip program name\n    while (args_iter.next()) |arg| {\n        try args_list.append(allocator, arg);\n    }\n\n    var parser = cli.Parser.init(allocator);\n    try parser.parse(cmd.getCommand(), args_list.items);\n}\n```\n\nThat's it! Run with: `myapp greet --name Alice --enthusiastic`\n\n**Benefits:**\n- Options auto-generated from struct fields\n- Compile-time validation - typos caught by compiler\n- Full IDE autocomplete support\n- No string-based lookups\n- Zero runtime overhead\n\n### Interactive Prompts\n\n```zig\nconst std = @import(\"std\");\nconst prompt = @import(\"zig-cli\").prompt;\n\npub fn main() !void {\n    var gpa = std.heap.GeneralPurposeAllocator(.{}){};\n    defer _ = gpa.deinit();\n    const allocator = gpa.allocator();\n\n    // Text prompt\n    var text_prompt = prompt.TextPrompt.init(allocator, \"What is your name?\");\n    defer text_prompt.deinit();\n    const name = try text_prompt.prompt();\n    defer allocator.free(name);\n\n    // Confirm prompt\n    var confirm_prompt = prompt.ConfirmPrompt.init(allocator, \"Continue?\");\n    defer confirm_prompt.deinit();\n    const confirmed = try confirm_prompt.prompt();\n\n    _ = confirmed;\n\n    // Select prompt\n    const choices = [_]prompt.SelectPrompt.Choice{\n        .{ .label = \"Option 1\", .value = \"opt1\" },\n        .{ .label = \"Option 2\", .value = \"opt2\" },\n    };\n    var select_prompt = prompt.SelectPrompt.init(allocator, \"Choose:\", \u0026choices);\n    defer select_prompt.deinit();\n    const selected = try select_prompt.prompt();\n    defer allocator.free(selected);\n}\n```\n\n## API Reference\n\n### CLI Framework\n\n#### Type-Safe Commands (Recommended)\n\nDefine your command options as a struct and get compile-time validation:\n\n```zig\nconst GreetOptions = struct {\n    name: []const u8,              // Required string\n    age: ?u16 = null,              // Optional integer\n    times: u8 = 1,                 // With default value\n    verbose: bool = false,         // Boolean flag\n    format: enum { text, json } = .text, // Enum support\n};\n\nfn greetAction(ctx: *cli.Context(GreetOptions)) !void {\n    // Compile-time validated field access - no string lookups!\n    const name = ctx.get(.name);      // Returns []const u8\n    const age = ctx.get(.age);        // Returns ?u16\n    const times = ctx.get(.times);    // Returns u8\n\n    // Or parse entire struct at once\n    const opts = try ctx.parse();\n\n    _ = age;\n    _ = times;\n    std.debug.print(\"Hello, {s}!\\n\", .{opts.name});\n}\n\npub fn main(init: std.process.Init) !void {\n    const allocator = init.gpa;\n\n    // Auto-generates CLI options from struct fields!\n    var cmd = try cli.Command(GreetOptions).init(allocator, \"greet\", \"Greet a user\");\n    defer cmd.deinit();\n\n    _ = cmd.setAction(greetAction);\n\n    var args_list = std.ArrayList([]const u8){};\n    defer args_list.deinit(allocator);\n\n    var args_iter = std.process.Args.Iterator.init(init.minimal.args);\n    _ = args_iter.skip();\n    while (args_iter.next()) |arg| {\n        try args_list.append(allocator, arg);\n    }\n\n    var parser = cli.Parser.init(allocator);\n    try parser.parse(cmd.getCommand(), args_list.items);\n}\n```\n\nBenefits:\n- **Compile-time validation** - field names validated at compile time\n- **IDE autocomplete** - full IntelliSense support\n- **Type safety** - no runtime string parsing or optionals\n- **Auto-generation** - options automatically created from struct\n- **Zero overhead** - comptime code generates efficient runtime\n\n#### Low-Level Command API\n\nFor more control, use `BaseCommand` directly:\n\n```zig\n// Create a base command\nconst cmd = try cli.BaseCommand.init(allocator, \"myapp\", \"Description\");\n\n// Add options manually\nconst option = cli.Option.init(\"name\", \"long-name\", \"Description\", .string)\n    .withShort('n')              // Short flag (-n)\n    .withRequired(true)          // Make it required\n    .withDefault(\"value\");       // Set default value\n\n_ = try cmd.addOption(option);\n```\n\nOption types:\n- `.string` - String value\n- `.int` - Integer value\n- `.float` - Float value\n- `.bool` - Boolean flag\n\n#### Adding Arguments\n\n```zig\nconst arg = cli.Argument.init(\"name\", \"Description\", .string)\n    .withRequired(true)          // Required argument\n    .withVariadic(false);        // Accept multiple values\n\n_ = try cmd.addArgument(arg);\n```\n\n#### Creating Subcommands\n\n```zig\nconst subcmd = try cli.BaseCommand.init(allocator, \"subcmd\", \"Subcommand description\");\n\n// Add aliases for the command\n_ = try subcmd.addAlias(\"sub\");\n_ = try subcmd.addAlias(\"s\");\n\nconst opt = cli.Option.init(\"opt\", \"option\", \"Option description\", .string);\n_ = try subcmd.addOption(opt);\n\n_ = subcmd.setAction(myAction);\n_ = try app.addCommand(subcmd);\n```\n\nNow you can call the subcommand with: `myapp subcmd`, `myapp sub`, or `myapp s`\n\n#### Middleware\n\nAdd pre/post command hooks to your CLI:\n\n```zig\nvar chain = cli.Middleware.MiddlewareChain.init(allocator);\ndefer chain.deinit();\n\n// Add built-in middleware\ntry chain.use(cli.Middleware.Middleware.init(\"logging\", cli.Middleware.loggingMiddleware));\ntry chain.use(cli.Middleware.Middleware.init(\"timing\", cli.Middleware.timingMiddleware));\ntry chain.use(cli.Middleware.Middleware.init(\"validation\", cli.Middleware.validationMiddleware));\n\n// Custom middleware\nfn authMiddleware(ctx: *cli.Middleware.MiddlewareContext) !bool {\n    const is_authenticated = checkAuth();\n    if (!is_authenticated) {\n        try ctx.set(\"error\", \"Unauthorized\");\n        return false; // Stop chain\n    }\n    try ctx.set(\"user\", \"john@example.com\");\n    return true; // Continue\n}\n\n// Add with priority (lower runs first)\ntry chain.use(cli.Middleware.Middleware.init(\"auth\", authMiddleware).withOrder(-10));\n\n// Execute middleware chain before command\nvar middleware_ctx = cli.Middleware.MiddlewareContext.init(allocator, parse_context, command);\ndefer middleware_ctx.deinit();\n\nif (try chain.execute(\u0026middleware_ctx)) {\n    // All middleware passed, execute command\n    try command.executeAction(parse_context);\n}\n```\n\nBuilt-in middleware:\n- `loggingMiddleware` - Logs command execution\n- `timingMiddleware` - Records start time\n- `validationMiddleware` - Validates required options\n- `environmentCheckMiddleware` - Checks environment variables\n\n#### Command Actions (Low-Level)\n\n```zig\nfn myAction(ctx: *cli.BaseCommand.ParseContext) !void {\n    // Get option value\n    const value = ctx.getOption(\"name\") orelse \"default\";\n\n    // Check if option was provided\n    if (ctx.hasOption(\"verbose\")) {\n        // Do something\n    }\n\n    // Get positional argument\n    const arg = ctx.getArgument(0) orelse return error.MissingArgument;\n\n    // Get argument count\n    const count = ctx.getArgumentCount();\n\n    _ = value;\n    _ = arg;\n    _ = count;\n}\n```\n\n#### Runtime vs Typed API Comparison\n\n```zig\n// Runtime API (string-based, via BaseCommand)\nconst value = ctx.getOption(\"name\");  // Returns ?[]const u8\nif (value) |v| {\n    const age_str = ctx.getOption(\"age\") orelse \"0\";\n    const age = try std.fmt.parseInt(u16, age_str, 10);\n    _ = v;\n    _ = age;\n}\n\n// Typed API (compile-time validated, via cli.Command(T))\nconst name = ctx.get(.name);  // Returns []const u8 directly\nconst age = ctx.get(.age);    // Returns u16, already parsed\n//              ^^^^^ Compile-time validated enum field!\n```\n\nSee `examples/typed.zig` for a complete working example.\n\n#### Type-Safe Config\n\nLoad config files with compile-time schema validation:\n\n```zig\nconst AppConfig = struct {\n    database: struct {\n        host: []const u8,\n        port: u16,\n        max_connections: u32 = 100,\n    },\n    log_level: enum { debug, info, warn, @\"error\" } = .info,\n    debug: bool = false,\n};\n\n// Load with full type checking\nvar config = try cli.config.load(AppConfig, allocator, \"config.toml\");\ndefer config.deinit();\n\n// Direct field access - no optionals, no string parsing!\nstd.debug.print(\"DB: {s}:{d}\\n\", .{\n    config.value.database.host,\n    config.value.database.port,\n});\nstd.debug.print(\"Log Level: {s}\\n\", .{@tagName(config.value.log_level)});\n\n// Auto-discovery also works\nvar discovered = try cli.config.discover(AppConfig, allocator, \"myapp\");\ndefer discovered.deinit();\n```\n\nSupported types:\n- Primitives: `bool`, `i8`-`i64`, `u8`-`u64`, `f32`, `f64`\n- Strings: `[]const u8`\n- Enums: Any Zig enum\n- Optionals: `?T` for optional fields\n- Nested structs: Arbitrary depth\n- Arrays: Fixed-size arrays\n\n### Prompts\n\n#### Text Prompt\n\n```zig\nvar text = prompt.TextPrompt.init(allocator, \"Enter value:\");\ndefer text.deinit();\n\n_ = text.withPlaceholder(\"placeholder text\");\n_ = text.withDefault(\"default value\");\n_ = text.withValidation(myValidator);\n\nconst value = try text.prompt();\ndefer allocator.free(value);\n```\n\nCustom validator:\n```zig\nfn myValidator(value: []const u8) ?[]const u8 {\n    if (value.len \u003c 3) {\n        return \"Value must be at least 3 characters\";\n    }\n    return null;  // Valid\n}\n```\n\n#### Confirm Prompt\n\n```zig\nvar confirm = prompt.ConfirmPrompt.init(allocator, \"Continue?\");\ndefer confirm.deinit();\n\n_ = confirm.withDefault(true);\n\nconst result = try confirm.prompt();  // Returns bool\n```\n\n#### Select Prompt\n\n```zig\nconst choices = [_]prompt.SelectPrompt.Choice{\n    .{ .label = \"TypeScript\", .value = \"ts\", .description = \"JavaScript with types\" },\n    .{ .label = \"Zig\", .value = \"zig\", .description = \"Systems programming\" },\n};\n\nvar select = prompt.SelectPrompt.init(allocator, \"Choose a language:\", \u0026choices);\ndefer select.deinit();\n\nconst selected = try select.prompt();\ndefer allocator.free(selected);\n```\n\n#### MultiSelect Prompt\n\n```zig\nconst choices = [_]prompt.MultiSelectPrompt.Choice{\n    .{ .label = \"Option 1\", .value = \"opt1\" },\n    .{ .label = \"Option 2\", .value = \"opt2\" },\n};\n\nvar multi = try prompt.MultiSelectPrompt.init(allocator, \"Select options:\", \u0026choices);\ndefer multi.deinit();\n\nconst selected = try multi.prompt();  // Returns [][]const u8\ndefer {\n    for (selected) |item| allocator.free(item);\n    allocator.free(selected);\n}\n```\n\n#### Password Prompt\n\n```zig\nvar password = prompt.PasswordPrompt.init(allocator, \"Enter password:\");\ndefer password.deinit();\n\n_ = password.withMaskChar('*');\n_ = password.withValidation(validatePassword);\n\nconst pwd = try password.prompt();\ndefer allocator.free(pwd);\n```\n\n#### Spinner Prompt\n\n```zig\nvar spinner = prompt.SpinnerPrompt.init(allocator, \"Loading data...\");\ntry spinner.start();\n\n// Do some work\n_ = std.c.nanosleep(\u0026.{ .sec = 2, .nsec = 0 }, null);\n\ntry spinner.stop(\"Data loaded successfully!\");\n```\n\n#### Message Prompts\n\n```zig\n// Intro/Outro for CLI flows\ntry prompt.intro(allocator, \"My CLI Application\");\n// ... your application logic ...\ntry prompt.outro(allocator, \"All done! Thanks for using our CLI.\");\n\n// Notes and logs\ntry prompt.note(allocator, \"Important\", \"This is additional information\");\ntry prompt.log(allocator, .info, \"Starting process...\");\ntry prompt.log(allocator, .success, \"Process completed!\");\ntry prompt.log(allocator, .warning, \"This is a warning\");\ntry prompt.log(allocator, .error_level, \"An error occurred\");\n\n// Cancel message\ntry prompt.cancel(allocator, \"Operation was canceled\");\n```\n\n#### Box Rendering\n\n```zig\n// Simple box\ntry prompt.box(allocator, \"Title\", \"This is the content\");\n\n// Custom box with styling\nvar box = prompt.Box.init(allocator);\nbox = box.withStyle(.rounded);  // .single, .double, .rounded, .ascii\nbox = box.withPadding(2);\ntry box.render(\"My Box\",\n    \\\\Line 1 of content\n    \\\\Line 2 of content\n    \\\\Line 3 of content\n);\n```\n\n#### Number Prompt\n\n```zig\nvar num_prompt = prompt.NumberPrompt.init(allocator, \"Enter port:\", .integer);\ndefer num_prompt.deinit();\n\n_ = num_prompt.withRange(1, 65535);  // Set min/max\n_ = num_prompt.withDefault(8080);\n\nconst port = try num_prompt.prompt();  // Returns f64\nconst port_int = @as(u16, @intFromFloat(port));\n```\n\nNumber types:\n- `.integer` - Integer values\n- `.float` - Floating-point values\n\n#### Path Prompt\n\n```zig\nvar path_prompt = prompt.PathPrompt.init(allocator, \"Select file:\", .file);\ndefer path_prompt.deinit();\n\n_ = path_prompt.withMustExist(true);  // Must exist\n_ = path_prompt.withDefault(\"./config.toml\");\n\nconst path = try path_prompt.prompt();\ndefer allocator.free(path);\n\n// Press Tab to autocomplete based on filesystem\n```\n\nPath types:\n- `.file` - File selection\n- `.directory` - Directory selection\n- `.any` - File or directory\n\n#### Group Prompts\n\n```zig\nconst prompts = [_]prompt.GroupPrompt.PromptDef{\n    .{ .text = .{ .key = \"name\", .message = \"Your name?\" } },\n    .{ .number = .{ .key = \"age\", .message = \"Your age?\", .number_type = .integer } },\n    .{ .confirm = .{ .key = \"agree\", .message = \"Do you agree?\" } },\n    .{ .select = .{\n        .key = \"lang\",\n        .message = \"Choose language:\",\n        .choices = \u0026[_]prompt.SelectPrompt.Choice{\n            .{ .label = \"Zig\", .value = \"zig\" },\n            .{ .label = \"TypeScript\", .value = \"ts\" },\n        },\n    }},\n};\n\nvar group = prompt.GroupPrompt.init(allocator, \u0026prompts);\ndefer group.deinit();\n\ntry group.run();\n\n// Access results by key\nconst name = group.getText(\"name\");\nconst age = group.getNumber(\"age\");\nconst agreed = group.getBool(\"agree\");\nconst lang = group.getText(\"lang\");\n```\n\n#### Progress Bar\n\n```zig\nvar progress = prompt.ProgressBar.init(allocator, 100, \"Processing files\");\ndefer progress.deinit();\n\ntry progress.start();\n\nfor (0..100) |i| {\n    // Do some work\n    _ = std.c.nanosleep(\u0026.{ .sec = 0, .nsec = 50 * std.time.ns_per_ms }, null);\n    try progress.update(i + 1);\n}\n\ntry progress.finish();\n```\n\nProgress bar styles:\n- `.bar` - Classic progress bar\n- `.blocks` - Block characters\n- `.dots` - Dots\n- `.ascii` - ASCII fallback\n\n#### Table Rendering\n\n```zig\nconst columns = [_]prompt.Table.Column{\n    .{ .header = \"Name\", .alignment = .left },\n    .{ .header = \"Age\", .alignment = .right },\n    .{ .header = \"Status\", .alignment = .center },\n};\n\nvar table = prompt.Table.init(allocator, \u0026columns);\ndefer table.deinit();\n\ntable = table.withStyle(.rounded);  // .simple, .rounded, .double, .minimal\n\ntry table.addRow(\u0026[_][]const u8{ \"Alice\", \"30\", \"Active\" });\ntry table.addRow(\u0026[_][]const u8{ \"Bob\", \"25\", \"Inactive\" });\ntry table.addRow(\u0026[_][]const u8{ \"Charlie\", \"35\", \"Active\" });\n\ntry table.render();\n```\n\n#### Style Chaining\n\n```zig\n// Create styled text with chainable API\nconst styled = try prompt.style(allocator, \"Error occurred\")\n    .red()\n    .bold()\n    .underline()\n    .render();\ndefer allocator.free(styled);\n\ntry prompt.Terminal.init().write(styled);\n\n// Available colors: black, red, green, yellow, blue, magenta, cyan, white\n// Available styles: bold(), dim(), italic(), underline()\n// Available backgrounds: bgRed(), bgGreen(), bgBlue(), etc.\n```\n\n### Configuration Files\n\nzig-cli supports type-safe configuration loading from TOML, JSONC (JSON with Comments), and JSON5 files.\n\n#### Loading Config\n\n```zig\n// 1. Define your config schema as a struct\nconst AppConfig = struct {\n    database: struct {\n        host: []const u8,\n        port: u16,\n    },\n    log_level: enum { debug, info, warn, @\"error\" } = .info,\n    debug: bool = false,\n};\n\n// 2. Load with full type checking\nvar config = try cli.config.load(AppConfig, allocator, \"config.toml\");\ndefer config.deinit();\n\n// 3. Direct field access - type-safe!\nstd.debug.print(\"DB: {s}:{d}\\n\", .{\n    config.value.database.host,\n    config.value.database.port,\n});\n\n// Load from string\nvar config2 = try cli.config.loadFromString(AppConfig, allocator, toml_content, .toml);\ndefer config2.deinit();\n\n// Auto-discover config file\nvar config3 = try cli.config.discover(AppConfig, allocator, \"myapp\");\ndefer config3.deinit();\n// Searches for: myapp.toml, myapp.json5, myapp.jsonc\n// In: ., ./.config, ~/.config/myapp\n```\n\n#### Untyped Config Access\n\nFor simple cases, use the raw `Config` type:\n\n```zig\nvar raw_config = cli.config.Config.init(allocator);\ndefer raw_config.deinit();\n\ntry raw_config.loadFromFile(\"config.toml\", .auto);\n\n// Get typed values\nif (raw_config.getString(\"name\")) |name| {\n    std.debug.print(\"Name: {s}\\n\", .{name});\n}\n\nif (raw_config.getInt(\"port\")) |port| {\n    std.debug.print(\"Port: {d}\\n\", .{port});\n}\n\nif (raw_config.getBool(\"debug\")) |debug| {\n    std.debug.print(\"Debug: {}\\n\", .{debug});\n}\n```\n\n#### Supported Formats\n\n**TOML:**\n```toml\n# config.toml\nname = \"myapp\"\nport = 8080\n\n[database]\nhost = \"localhost\"\n```\n\n**JSONC (JSON with Comments):**\n```jsonc\n{\n  // Comments are allowed\n  \"name\": \"myapp\",\n  \"port\": 8080,\n  \"database\": {\n    \"host\": \"localhost\"\n  },  // trailing commas allowed\n}\n```\n\n**JSON5:**\n```json5\n{\n  // Unquoted keys\n  name: 'myapp',  // single quotes\n  port: 8080,\n  permissions: 0x755,  // hex numbers\n  ratio: .5,  // leading decimal\n  maxValue: Infinity,  // special values\n}\n```\n\n### Terminal \u0026 ANSI\n\n#### Colors\n\n```zig\nconst ansi = @import(\"zig-cli\").prompt.Ansi;\n\nconst colored = try ansi.colorize(allocator, \"text\", .green);\ndefer allocator.free(colored);\n\n// Convenience functions\nconst bold = try ansi.bold(allocator, \"text\");\nconst red = try ansi.red(allocator, \"error\");\nconst green = try ansi.green(allocator, \"success\");\n```\n\n#### Symbols\n\n```zig\nconst symbols = ansi.Symbols.forTerminal(supports_unicode);\n\nstd.debug.print(\"{s} Success!\\n\", .{symbols.checkmark});\nstd.debug.print(\"{s} Error!\\n\", .{symbols.cross});\nstd.debug.print(\"{s} Loading...\\n\", .{symbols.spinner[0]});\n```\n\n## Examples\n\nCheck out the `examples/` directory for complete examples:\n\n- `simple.zig` - Minimal typed CLI example\n- `basic.zig` - Basic CLI with options and subcommands\n- `typed.zig` - Type-safe API examples (compile-time validated)\n- `advanced.zig` - Complex CLI with multiple commands and arguments\n- `prompts.zig` - All prompt types with validation\n- `showcase.zig` - Comprehensive feature demonstration\n- `config.zig` - Configuration file examples (TOML, JSONC, JSON5)\n\nExample config files are in `examples/configs/`:\n- `example.toml` - TOML format example\n- `example.jsonc` - JSONC format example\n- `example.json5` - JSON5 format example\n\nBuild and run examples:\n\n```bash\nzig build examples          # Build all examples\nzig build run-simple        # Run a specific example\nzig build run-showcase      # Run the showcase\n```\n\n## Architecture\n\n### CLI Framework\n```\nCommand(T) (typed, compile-time validated)\n├── BaseCommand (underlying command)\n│   ├── Options (parsed from --flags)\n│   ├── Arguments (positional)\n│   └── Subcommands (nested)\n├── Context(T) (typed parse context)\n└── Parser (validation pipeline)\n```\n\n### Prompt System\n```\nPromptCore (state machine)\n├── Terminal I/O\n│   ├── Raw mode handling\n│   ├── Keyboard input\n│   └── ANSI output\n├── State: initial -\u003e active \u003c-\u003e error -\u003e submit/cancel\n└── Events: value, cursor, key, submit, cancel\n```\n\n## Design Principles\n\n1. **Type Safety**: Leverage Zig's type system for compile-time safety\n2. **Memory Ownership**: Clear allocation/deallocation patterns\n3. **Error Handling**: Explicit error handling with Zig's error unions\n4. **Cross-platform**: Works on macOS, Linux, and Windows\n5. **Zero Dependencies**: Only uses Zig standard library\n6. **Composable**: Mix and match CLI and prompt features\n\n## Comparison with clapp\n\nzig-cli is inspired by the TypeScript library clapp, bringing similar developer experience to Zig:\n\n| Feature | clapp | zig-cli |\n|---------|-------|---------|\n| Builder Pattern | yes | yes |\n| Subcommands | yes | yes |\n| Command Aliases | yes | yes |\n| Interactive Prompts | yes | yes |\n| State Machine | yes | yes |\n| Type Validation | yes | yes |\n| ANSI Colors | yes | yes |\n| Style Chaining | yes | yes |\n| Spinner/Loading | yes | yes |\n| Progress Bars | yes | yes |\n| Box Rendering | yes | yes |\n| Table Rendering | yes | yes |\n| Message Prompts | yes | yes |\n| Number Prompts | yes | yes |\n| Path Prompts | yes | yes |\n| Group Prompts | yes | yes |\n| Terminal Detection | yes | yes |\n| Dimension Detection | yes | yes |\n| Config Files (TOML/JSONC/JSON5) | yes | yes |\n| Middleware System | yes | yes |\n| Language | TypeScript | Zig |\n| Binary Size | ~50MB (with Node.js) | ~500KB |\n| Startup Time | ~50-100ms | \u003c1ms |\n\n## Testing\n\n```bash\nzig build test\n```\n\n## Building\n\n```bash\nzig build              # Build the library\nzig build test         # Run all tests\nzig build examples     # Build all examples\n```\n\n## License\n\nMIT\n\n## Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request.\n\n## Roadmap\n\n### Completed Features\n- [x] Spinner/loading indicators\n- [x] Box/panel rendering\n- [x] Message prompts (intro, outro, note, log, cancel)\n- [x] Terminal dimension detection\n- [x] Command aliases\n- [x] Config file support (TOML, JSONC, JSON5)\n- [x] Auto-discovery of config files\n- [x] Progress bars with multiple styles\n- [x] Table rendering with column alignment\n- [x] Style chaining (`.red().bold().underline()`)\n- [x] Group prompts with result access\n- [x] Number prompt with range validation\n- [x] Path prompt with autocomplete\n- [x] Middleware system for commands\n- [x] Type-safe API with compile-time validation\n  - [x] TypedCommand with auto-generated options from structs\n  - [x] TypedConfig with schema validation\n  - [x] TypedMiddleware with compile-time field checking\n\n### Future Enhancements\n- [ ] Tree rendering for hierarchical data\n- [ ] Date/time prompts\n- [ ] Shell completion generation (bash, zsh, fish)\n- [ ] Better Windows terminal support\n- [ ] Task prompts with status indicators\n- [ ] Streaming output prompts\n- [ ] Vim keybindings for prompts\n- [ ] Multi-column layout support\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzig-utils%2Fzig-cli","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzig-utils%2Fzig-cli","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzig-utils%2Fzig-cli/lists"}