A C11 library for styled terminal output, interactive prompts and CLI applications:
- panels, tables, rules, columns, lists, trees, key/value blocks, alerts, badges, progress bars, spinners and live-updating dashboards;
- confirm, text, password, number, textarea, select, fuzzy, date-picker prompts and a grid-layout form – every one extensible with custom keyboard shortcuts;
- an application framework: argument parser ("clap for C"), logging, pretty errors, XDG paths, pager integration and REPL building blocks.
Ships with Rich-compatible inline markup, a header-only C++ wrapper, safe, idiomatic Rust and Python bindings, and a sparcli command-line tool that exposes everything to the shell (zsh/bash).
⚠️ Status: sparcli is under active development. The API is still settling – expect breaking changes between 0.x versions.
- sparcli – Polished CLI output & prompts
The full reference for every output component lives in docs/api-c.md; a per-widget overview is in Output widgets.
- Large set of widgets: panels, tables, rules, side-by-side columns, lists, trees, key/value blocks, alerts, badges, progress bars, spinners.
- Live display & dashboards: re-render a composed frame in place (
sc_live_*) – build dashboards from any widgets that update continuously, in-place or on the fullscreen alternate screen. A thin alt-screen session (sc_altscreen_begin/_end) hosts full-screen widgets: the fuzzy finder and the form run with a pinned header and vertical alignment, growing to fill the screen then scrolling (docs). - Multi-progress: several progress bars updated together in place for concurrent tasks (
sc_multiprogress_*), buffered to the final stack off a terminal. (C, C++, Rust, Python) - Diff rendering: colored unified diff of two texts (
sc_diff_*) –@@hunks, red/green-/+lines. (C, C++, Rust, Python) - Human-readable formatting: file sizes, durations, relative time, grouped/compact numbers and percentages (
sc_humanize_*, e.g.1536 → "1.5 KB",93 → "1m 33s"), locale-aware separators. (C, C++, Rust, Python) - Rich-compatible markup:
[bold red]error[/],[on cyan] OK [/],[rgb(120,200,255)]…[/]– same syntax as Rich/Textual. See Rich-compatible markup. - Clickable hyperlinks (OSC-8):
[link=https://…]text[/link]markup orsc_text_append_link()– Cmd/Ctrl+click opens the URL in supporting terminals, plain text everywhere else (docs). - Truecolor + 8-color ANSI, with graceful sentinels for "no color". Plus a curated named RGB palette (
SC_COLOR_ACCENT,SC_COLOR_ERROR, … /sparcli::palette::accent()/palette::ACCENT/sc.Palette.ACCENT) usable in code and in markup ([accent],[error],[orange]) and the CLI (--color accent) (docs). - Composable: capture any widget into a buffer, then pad, align, stack (
sc_vstack), or place it inside a columns layout (docs).
The full reference for every input widget lives in docs/api-c.md; a per-widget overview is in Input widgets.
- Interactive prompts: confirm, text/password, number, textarea, single & multi select, fuzzy finder, a date picker, and a grid-layout form (framed fields with widths/spans, 2D navigation, multiline fields edited in
$EDITOR) – each with a non-TTY fallback. - Custom keyboard shortcuts on every prompt (Ctrl-letter / F1–F12 / Alt) bound to return-an-action or live callbacks, plus rich prompts (mix styles, e.g. a partly-italic label) (docs).
- Input history: ↑/↓ recall of previous entries in the text input, with optional persistence in the XDG state directory (
sc_history_*).
Everything a complete command-line application needs around the widgets; overview in Application framework, full reference in docs/api-framework.md.
- Argument parser – clap for C: declarative subcommands, typed options, auto-generated
--help(rendered with sparcli panels and tables), "did you mean ...?" suggestions and zsh completion generation (sc_args_*, docs). - Logging: leveled, colored terminal logging (
sc_log_info("port %d", p)) with timestamps andfile:line, plus plain-text file sinks with their own levels – thread-safe, render-once architecture (docs). - Pretty errors: report fatal errors as red alert panels – message + cause chain + hint + exit code (
sc_die, docs). - Pager integration: pipe long output through
$PAGER/less -Rautomatically; a no-op in scripts and CI (docs). - XDG paths: resolve per-app config/data/cache/state directories (
sc_path_config("myapp")→~/.config/myapp, created on first use) (docs). - Subprocess helper: run a command without a shell and capture stdout/stderr/exit code without deadlocking (
sc_run); captured output is sanitized by default. - Layered config: merge defaults < config file (JSON/TOML/YAML by extension) < environment < flags into one tree with dotted-path typed getters (
sc_config_*); opt-in (serde-backed). - Shell completion: generate zsh, bash and fish completion scripts from an args command tree (
sc_args_print_{zsh,bash,fish}_completion). - REPL building blocks: input history with ↑/↓ recall and XDG persistence (
sc_history_*), a quote-aware line tokenizer (sc_args_split), reusable parse trees (implicitsc_args_reset), and live-dashboard + prompt composition (ScLiveOpts.prompt_rows) – see theexamples/c/apps/repl_*.cdemos.
An opt-in serde/ layer of read/write parsers over one shared ScValue tree (C and C++ only); full reference in docs/api-serde.md. Included via <serde/sparcli_serde.h> / .hpp, not by <sparcli.h>, and gated separately (make qa-serde).
- JSON, TOML, YAML (documented subset) – read and write over the shared model, so converting between them is parse-one/write-the-other (
sc_json_*,sc_toml_*,sc_yaml_*). - CSV/TSV – RFC-4180 reader+writer with quoted fields, ragged rows and header lookup (
sc_csv_*); the same parser thesparcli tableCLI uses. - Markdown – front-matter split (YAML/TOML →
ScValue) plus an editable heading/section tree (sc_markdown_*). - Render to the terminal (view layer): pretty-print any
ScValuejq-style with syntax coloring (sc_value_render), and render Markdown through the widget stack – headings, lists, code blocks, quotes, pipe tables, inline emphasis (sc_markdown_render). C and C++ only, opt-in.
- Command-line tool included: the
sparclibinary brings every output and input widget to the shell –sparcli panel,name=$(sparcli input "Name:"),sparcli confirm && …. See Command-line tool anddocs/cli.md. - C++ wrapper included: a header-only RAII C++20 layer (
<sparcli.hpp>, namespacesparcli) – no manualfree, owned strings,std::optionalinputs. Seedocs/api-cpp.md. - Rust bindings included: a safe, idiomatic crate (
bindings/rust/, builds the C viacc– no install needed) with RAII handles, builder options andResult<Option<T>>prompts. Seedocs/api-rust.md. - Python bindings included: a safe, Pythonic package (
bindings/python/, a cffi wrapper that compiles the C – no install needed) with RAII handles,@dataclassoptions andvalue/Noneprompts. Seedocs/api-python.md.
- UTF-8 & ANSI-aware width math everywhere (codepoints, not bytes).
- ANSI-injection safe by default: control bytes and escape sequences in user strings are stripped at the API boundary; opt back in globally (
sc_set_allow_ansi) or per widget (.ansi = SC_ANSI_MODE_ALLOW) with widths staying correct. - FFI-ready:
extern "C", hidden symbol visibility, opaque types, NULL-safe entry points – the C++, Rust and Python wrappers build on this. - No runtime dependencies beyond libc.
- Static + shared library,
pkg-configfile, optional ASan/UBSan build.
#include <sparcli.h>
int main(void) {
sc_markup_println("[bold green]Hello[/], [italic]sparcli[/]!");
ScPanelOpts opts = {
.border = { .type = SC_BORDER_ROUNDED },
.title = { .text = " Greeting ", .halign = SC_ALIGN_CENTER },
.padding = { 0, 2, 0, 2 },
};
sc_panel_str("Welcome aboard.", opts);
return 0;
}cc hello.c $(pkg-config --cflags --libs sparcli) -o hello && ./helloA header-only C++20 wrapper ships in include/sparcli.hpp: RAII handles (no manual free), owned strings where the C API borrows (so temporaries are safe), and std::optional returns for input prompts. Full reference: docs/api-cpp.md.
#include <sparcli.hpp>
using namespace sparcli;
int main() {
panel("Welcome aboard.", { .border = { .type = SC_BORDER_ROUNDED },
.title = { .text = " Greeting ",
.halign = SC_ALIGN_CENTER } });
Table t; // frees itself
t.add_column("Name");
t.add_row({ "Ada", std::to_string(42) }); // strings are owned → safe
t.print({ .header = { .row = true } });
if (auto name = text_input("Your name")) // std::optional<std::string>
markup::println("[green]Hi[/] " + *name);
}c++ -std=c++20 hello.cpp $(pkg-config --cflags --libs sparcli) -o helloWhy the wrapper? Two easy C-API mistakes simply can't happen with it:
// C API – two footguns:
ScTableData *t = sc_table_new(); // 1) leaks if you forget
// sc_table_free(t)
sc_table_add_row(t, (ScCell[]){ // 2) the table BORROWS the
sc_cell(std::to_string(n).c_str()) }, 1); // string; the temporary
// dies here → dangling
sc_table_print(t, (ScTableOpts){0}); // pointer read → garbage
// C++ wrapper – RAII frees, and the cell string is copied into the table:
sparcli::Table t; // frees itself
t.add_row({ std::to_string(n) }); // owned → temporary is safe
t.print();These guarantees (auto-free, owned cell strings, surviving a move) are verified by tests/cpp/test_cpp.cpp.
Safe, idiomatic bindings live in bindings/rust/ (a cargo workspace). sparcli-sys compiles the C with the cc crate, so a plain cargo build needs only a Rust toolchain – no prior make or install. RAII handles free themselves, *Opts use builder methods, callbacks are closures, and prompts return Result<Option<T>> (Ok(None) = cancelled). Full reference: docs/api-rust.md.
use sparcli::*;
fn main() -> sparcli::Result<()> {
panel("Welcome aboard.", PanelOpts::new().rounded().title("Greeting"));
let mut t = Table::new(); // frees itself
t.column("Name", ColOpts::new());
t.row(["Ada", "42"]); // strings owned → temporaries safe
t.print(TableOpts::new().header_row(true));
if let Some(name) = text_input("Your name", TextInputOpts::new())? {
markup::println(&format!("[green]Hi[/] {name}"));
}
Ok(())
}# the workspace has no bin, so run an example (from bindings/rust/):
cargo run -p sparcli --example output_table_basic # see docs/examples.mdPythonic bindings live in bindings/python/ – a cffi (API-mode) wrapper that compiles the C sources into an extension, so building needs only a C compiler. RAII handles free themselves, options are @dataclasses with keyword args, and prompts return the value or None on cancel (and raise SparcliInputUnavailable with no TTY). Full reference: docs/api-python.md.
import sparcli as sc
sc.panel("Welcome aboard.", sc.PanelOpts(title="Greeting",
border=sc.BorderStyle(sc.BorderType.ROUNDED)))
t = sc.Table() # frees itself
t.column("Name").column("Age", sc.ColOpts(halign=sc.Align.RIGHT))
t.row(["Ada", "42"])
t.print(sc.TableOpts(header_row=True))
name = sc.text_input("Your name") # str, or None if cancelled
if name:
sc.markup.println(f"[green]Hi[/] {name}")make python # build the extension in place
PYTHONPATH=bindings/python/src python examples/python/output/table_basic.py # see docs/examples.mdgit clone https://github.com/cgroening/c-sparcli.git
cd c-sparcli
make # builds libsparcli.a, the shared lib, and sparcli.pc
sudo make install # installs into /usr/local
# or, install into a user prefix:
make install PREFIX=$HOME/.localmake install lays down:
libsparcli.a(static)libsparcli.<version>.dylib/libsparcli.so.<version>plus the usual versioned symlinks (libsparcli.dylib/libsparcli.so)- All public headers under
<prefix>/include sparcli.pcunder<prefix>/lib/pkgconfig
With pkg-config (recommended):
cc app.c $(pkg-config --cflags --libs sparcli) -o appManual:
cc app.c -I<prefix>/include -L<prefix>/lib -lsparcli -o app- A POSIX platform: macOS or Linux. sparcli's tty/input layer is POSIX-only, so there is no Windows build today (a Windows port is on the roadmap).
- C11 compiler (
cc,gcc, orclang) - A UTF-8-capable terminal
- Truecolor support recommended for
[rgb(…)]markup; 8-color ANSI works everywhere
A one-line summary per widget. The full reference – every type, every option, every macro – lives in docs/api-c.md.
| Widget | Function family | What it does |
|---|---|---|
| Panel | sc_panel_* |
Bordered frame with title, padding, margin, optional background. |
| Table | sc_table_* |
Headers, footers, colspan, rowspan, striping, word-wrap, per-column widths and styles. |
| Rule | sc_rule_* |
Horizontal line with optional centered/aligned title. |
| Columns | sc_columns_* |
Side-by-side layout for any other widgets (with optional separator). |
| List | sc_list_* |
Bulleted, numbered, alpha, or roman lists with hanging indent and nesting. |
| Tree | sc_tree_* |
Hierarchical tree view with connectors. |
| Key/Value | sc_kv_* |
Aligned key: value pairs with key-column padding and optional value wrap. |
| Alert | sc_alert_* |
Preset info / warning / error / success boxes (icon + color). |
| Badge | sc_badge_* |
Inline styled token ([ DONE ]). |
| Progress bar | sc_progressbar_* |
Animated progress bar with thresholds and percent/value display. |
| Spinner | sc_spinner_* |
Animated activity indicator with success/failure finish. |
| Live display | sc_live_* |
Re-render a composed frame in place: build dashboards from any widgets that update continuously (in-place or fullscreen alternate screen). |
| Markup | sc_markup_* |
Rich-compatible [bold red]…[/] parser. |
| Capture | sc_capture_*, sc_vstack |
Render any widget into a reusable in-memory buffer; sc_vstack stacks several buffers into one column. |
| Pad | sc_pad_* |
Add top/right/bottom/left space around a rendered widget. |
| Align | sc_align_* |
Center- or right-align a rendered widget within a width. |
Interactive prompts that drive a real terminal in raw mode. Each returns an ScInputStatus – Esc and Ctrl-C cancel, and a non-TTY context (output piped, CI) returns an error so callers can fall back to a default. The full reference lives in docs/api-c.md.
| Widget | Function family | What it does |
|---|---|---|
| Confirm | sc_confirm |
Yes/No prompt; arrow / y / n selection. |
| Text input | sc_text_input |
Single-line entry with placeholder, validation, autocomplete (ghost text or navigable dropdown, prefix/fuzzy matching), char filters, optional boxed panel. |
| Password | sc_password_input |
Masked single-line entry (configurable mask glyph). |
| Number | sc_number_input |
Numeric entry with min/max/step, ↑/↓ adjustment, comma/period decimal separator, exact-text output (decimal-type safe) and an opt-in calculator mode (=1,5+2*3 evaluates inline). |
| Textarea | sc_textarea |
Multi-line entry (Ctrl-D submits) with soft-wrap. |
| Select | sc_select_* |
Single- or multi-choice list with a scrolling viewport. |
| Fuzzy finder | sc_fuzzy_* |
Incremental fuzzy search; optional table view. |
| Date picker | sc_datepicker |
Month-grid calendar; day/week/month/year navigation. |
| Form | sc_form_* |
Grid-layout form: framed fields (text/number/bool/select/multiselect/date) with per-field width and col/row spans, 2D navigation, in-place editing below the grid; multiline fields open $EDITOR. |
| Input history | sc_history_* |
↑/↓ recall of previous entries in the text input; optional persistence in the XDG state directory (REPL building block). |
| Theme | sc_input_set_theme |
Process-wide style defaults inherited by every input widget. |
Every widget shows a key-hint footer that is fully configurable: its layout (hint_layout – inline, stacked one-per-line, or hidden) and its placement (hint_pos – above, below, left, or right of the widget).
char *name = NULL;
if (sc_text_input("Your name", &name, (ScTextInputOpts){ .placeholder = "Ada" })
== SC_INPUT_OK) {
sc_markup_println("[green]Hello[/], it's nice to meet you.");
free(name);
}Custom shortcuts – bind extra keys (Ctrl-letter, F1–F12, Alt) to actions on any widget via its opts. A SC_SHORTCUT_RETURN shortcut closes the prompt and reports which key fired (the widget still returns its value); a SC_SHORTCUT_CALLBACK runs in place and keeps the prompt open – handy with sc_select_remove / sc_select_set_label for live list editing. Labeled shortcuts appear in a dim footer automatically (hide_in_footer keeps a binding active but off the footer), and each shortcut also carries a help_text + section that feed an auto-built keyboard help screen (sc_shortcut_help_show / sc_shortcut_help_show_from, C++ show_shortcuts) – a modal, filterable reference with section headers, so apps never hand-roll a "?" screen (examples/c/input/shortcuts_help.c).
Rich prompts – for partial styling (e.g. Rename Apple to) set prompt_markup = true to parse the prompt as markup, or prompt_text to pass a pre-built multi-style ScText. Works inline and in boxed mode.
External editor – sc_text_input / sc_textarea can open the value in $EDITOR (default chain ending in nvim) with external_editor = true; a key (default Ctrl-G) suspends the prompt, and save+quit brings the text back. Runs shell-free with a 0600 temp file; not available for passwords.
Runnable per-widget examples live under examples/c/input/ (and mirrored for every binding) – e.g. the confirm/select, text/password, fuzzy, datepicker and history demos, plus a custom-shortcuts + theme demo (examples/c/input/shortcuts_theme.c – F2 archives, Ctrl-X deletes):
make run-example EX=c/input/confirm_select
make run-example EX=c/input/shortcuts_themeREPLs – three full programs combine history, the line tokenizer, the argument parser and the live display into interactive shells: examples/c/apps/repl_minimal.c (prompt loop + history), examples/c/apps/repl_demo.c (a task-manager REPL on the args module), and examples/c/apps/repl_dashboard.c (a fixed header + in-place updating body above the prompt, with a shortcut action bar):
make run-example EX=c/apps/repl_demo
make run-example EX=c/apps/repl_dashboardBeyond the widgets, sparcli covers the plumbing a complete CLI application needs – the parts that clap, argparse, or Rich-style logging provide in other languages. The full reference – every type, option, and invariant – lives in docs/api-framework.md.
| Module | Function family | What it does |
|---|---|---|
| Argument parser | sc_args_* |
Declarative subcommands, typed options (string/int/double/color) with defaults, choices and required arguments, auto-generated --help rendered with sparcli widgets, "did you mean ...?" suggestions, zsh completion generation. |
| Logging | sc_log_*, sc_logger_* |
Leveled, colored terminal logging with timestamps and file:line, plus plain-text file sinks with their own levels – thread-safe. |
| Pretty errors | sc_error_*, sc_die |
Fatal errors as red alert panels: message + cause chain + hint + exit code. |
| XDG paths | sc_path_* |
Per-application config/data/cache/state directories per the XDG spec, created on first use. |
| Pager | sc_pager_* |
Pipe long output through $PAGER / less -R; a no-op when output is not a terminal (C API docs). |
| Line tokenizer | sc_args_split |
Split a REPL input line into argv (quotes + escapes) and feed it into the same parse tree, once per line. |
A taste of the argument parser – subcommands, typed options and --help in a few lines:
ScArgs *args = sc_args_new((ScArgsOpts){
.prog = "tool", .version = "1.0.0", .about = "Build things, fast",
});
ScArgsCmd *root = sc_args_root(args);
ScArgsCmd *build = sc_args_subcommand(root, "build", "Build a target");
sc_args_opt(build, "jobs", 'j', SC_ARG_INT, "N", "Parallel jobs");
sc_args_positional(build, "TARGET", SC_ARG_STR, "What to build", true, false);
/* --help, --version, did-you-mean and parse errors are all handled here */
ScArgsStatus status;
const ScArgsCmd *matched = sc_args_parse(args, argc, argv, &status);
if (status != SC_ARGS_MATCHED) {
return status == SC_ARGS_HANDLED ? 0 : 2; /* help/version printed = 0 */
}
if (matched == build) {
long jobs = sc_args_get_int(args, "jobs"); /* typed, validated access */
/* ... */
}
sc_args_free(args);The other helpers are one-liners:
sc_log_info("listening on port %d", port); /* leveled, colored, timestamped */
char *cfg = sc_path_config("myapp"); /* ~/.config/myapp (created) */
sc_die_msg(2, "No config file found", "Run 'myapp init' to create one");A tool built on the parser is examples/c/app/args.c (and the REPL-style examples/c/app/args_repl.c); the REPL demos (examples/c/apps/repl_demo.c, examples/c/apps/repl_dashboard.c) combine the parser, input history and the live display into interactive shells:
make run-example EX=c/app/argsEverything above is also available from the shell: make builds a sparcli binary (installed to $(PREFIX)/bin by make install, together with a zsh completion) that wraps every output and input widget as a subcommand.
# Output: markup, panels, rules, tables, trees, alerts, ...
sparcli print "[bold red]Error:[/] file not found"
echo "All systems operational" | sparcli panel --title "Status" --color green
df -h | tr -s ' ' '\t' | sparcli table --tsv --header-row
sparcli alert success "Deployment finished"
sparcli spin --title "Building" -- make all
# Input: prompts whose UI goes to the terminal, the value to stdout -
# perfect for command substitution and exit-code logic in scripts.
name=$(sparcli input "Your name:")
sparcli confirm "Deploy to production?" && ./deploy.sh
branch=$(git branch --format='%(refname:short)' | sparcli select)
file=$(find . -name '*.c' | sparcli fuzzy)
amount=$(sparcli number "Amount:" --decimals 2 --decimal-sep ,)
when=$(sparcli date --format %Y-%m-%d)Input commands report their outcome through the exit code (0 = confirmed, 1 = cancelled/no, 2 = error or no TTY), so && / || chains and $(...) capture work the way shell scripts expect. Markup is parsed everywhere by default (--no-markup for literal text); --no-color / NO_COLOR strip the colors.
Two runnable zsh demos ship in examples/cli/output_demo.zsh and examples/cli/input_demo.zsh. The full reference – every subcommand, flag, data format and scripting pattern – lives in docs/cli.md.
sparcli's inline markup mirrors the syntax used by Rich and Textual. Existing Rich strings drop in unchanged for the supported tag set:
sc_markup_println("[bold red]Error:[/] file not found");
sc_markup_println("[on cyan] 200 OK [/] [dim]42ms[/]");
sc_markup_println("[rgb(120,200,255)]custom[/] color");| sparcli | Rich (Python) |
|---|---|
[bold red]…[/] |
[bold red]…[/] |
[on yellow]…[/] |
[on yellow]…[/] |
[underline]…[/u] |
[underline]…[/u] |
[strike]…[/s] |
[strike]…[/s] |
[rgb(120,200,255)]…[/] |
[rgb(120,200,255)]…[/] |
[[ |
[[ |
By default, unknown tags such as [blink] are emitted verbatim. Pass ScMarkupOpts{ .strip_unknown = 1 } to silently drop them and keep only the inner content.
Backtick inline code: `code` renders the content in magenta with the backticks removed (Markdown-style); the body is literal, so tags inside are not parsed. Escape a literal backtick with \`. The style is configurable via ScMarkupOpts.code_style.
Clickable OSC-8 hyperlinks use the same syntax as Rich: [link=https://example.com]text[/link] (or sc_text_append_link() from code). Supporting terminals open the URL on Cmd/Ctrl+click; others show just the text.
Any widget that takes an ScText * accepts markup via sc_markup_parse(). For tables, use the SC_CELL_M("…") macro to embed markup directly into a cell.
make # static + shared + pkg-config
make test # run the full non-interactive test suite (all headless gates)
make test-output # visual output gallery (ARGS=--focus / --no-animated)
make test-input # interactive widget suite (needs a real terminal)
make rust # build the Rust binding (make rust-test to test it)
make python # build the Python binding (make python-test to test it)
make rebuild-all # rebuild the C lib + install + Rust + Python in one go
make cleanProject layout:
include/{core,output,input}/ public headers (sparcli.h is the umbrella)
include/sparcli.hpp header-only C++20 wrapper
src/{core,output,tty,input}/ implementation
src/output/table/ table sub-modules (see docs/api-c.md)
cli/ the sparcli command-line tool (see docs/cli.md)
completions/ zsh completion for the CLI
examples/{c,cpp,rust,python}/ per-language examples (see docs/examples.md)
bindings/rust/ safe Rust crate (sparcli-sys + sparcli)
bindings/python/ safe Python package (cffi API-mode wrapper)
tests/output/ output suite
tests/input/{logic,style,pty}/ interactive / snapshot / PTY suites
tests/cli/ CLI golden-file + PTY suites
docs/ API reference and developer guide
See docs/development.md for the full build/test/ install workflow: every make target, what each test suite covers and how to run it, the golden-file workflow, and the pre-commit checklist.
- C++ wrapper – ✅ ships as the header-only
include/sparcli.hpp(RAII overScText/ScTableData/ScColumns/…; see below). - Rust bindings – ✅ ship in
bindings/rust/(the safesparclicrate oversparcli-sys; seedocs/api-rust.md). - Python bindings – ✅ ship in
bindings/python/(the cffi API-modesparclipackage; seedocs/api-python.md). - Command-line tool – ✅ ships as the
sparclibinary (cli/; every widget as a shell subcommand with zsh completion; seedocs/cli.md). - CLI application framework – ✅ argument parser (
sc_args_*, docs), logging (sc_log_*), pretty errors (sc_die), XDG paths, pager and OSC-8 hyperlinks. - Structured data (serde) – ✅ the opt-in
serde/layer: JSON/CSV/TOML/YAML/Markdown readers and writers over a sharedScValuemodel, in C and the C++ wrapper, with its ownmake qa-serdegate; seedocs/api-serde.md. - CLI on the args module – migrate
cli/fromgetopt_long+ hand-written usage strings to the argument parser (sc_args_*) once its API has stabilized through real-world use. This would remove the duplicated option/usage/completion definitions and givesparcli --helpwidget-rendered output. Deferred on purpose: the CLI is stable and golden-tested; coupling it to a brand-new API would force follow-up changes on every API adjustment. - Output theming – a process-wide
sc_output_set_theme(...)for output components (default border style/color, title styling, …), mirroring the existingsc_input_set_themefor input widgets. - Windows support – sparcli is currently macOS/Linux only: the tty/input layer (raw mode, signals,
/dev/tty) is POSIX-only. A Windows port (VT mode + ConPTY for the interactive widgets) is planned. examples/directory – ✅ self-contained, copy-pasteable examples for every component in all four languages, grouped by language and area; seedocs/examples.md.
I picked up C to understand what higher-level languages do under the hood – and ended up falling in love with the language. But coming from Python, I missed the tooling I had taken for granted: Rich and Textual for output, Typer/argparse for command lines, prompt_toolkit and questionary for prompts. In C, the answer to most of this is still getopt and printf.
sparcli is my attempt to close that gap: a single dependency-free library that gives plain C programs styled output, interactive prompts, an argument parser, logging – the plumbing a modern CLI needs. This project is inspired by the wonderful Rich and Textual projects by Will McGugan and the Textualize team.
To move faster, sparcli is developed with the help of Claude Code (Anthropic's Opus model) under strict requirements and review: every feature ships with tests, and every change has to pass the full QA gate – sanitizers, ThreadSanitizer, fuzzing, golden-file and PTY suites (see docs/development.md).
MIT. See LICENSE for the full text.



