All docs
Examples
Example: a CLI command
Turn a typed function into an `lsd csvkit stats` CLI command with @tool.command — required args become positionals, defaults become options.
TL;DRDecorate a (ctx, ...) function with @tool.command and its signature becomes a subcommand — zero argparse. A parameter with no default is a positional argument, one with a default is an --option, and a bool is a --flag. It runs as `lsd <tool> <name>`.
Every package can add CLI subcommands straight from a typed function — no argparse. Decorate a
(ctx, ...) function with @tool.command and it runs as
lsd <tool> <name>.
The code#
Python
# tool.py
from lsdtools import Tool, Context, Table
tool = Tool("csvkit", label="CSV Kit")
@tool.command("stats", help="print row/column counts and a null report for a CSV")
def stats(ctx: Context, src: str, sort_by: str = "") -> int:
"""`lsd csvkit stats data.csv [--sort-by col]` — quick stats without the desktop."""
t = Table.read_csv(src)
if sort_by:
t = t.sort(sort_by)
print(f"{len(t)} rows x {len(t.columns)} columns: {t.columns}")
t.null_report().print()
return 0
What each line does#
@tool.command("stats", help=...)— registers a subcommand namedstats. Thehelpstring shows inlsd csvkit --help.ctx: Context— the injectedContext; it does not become a CLI argument. A command may read host services through it (project, ui, engine).src: str— no default, so it becomes a required positional argument.sort_by: str = ""— a default, so it becomes an--sort-byoption. (Aboolparameter would become a--flag.) The type annotation drives parsing.print(...)— CLI command handlers are one of the few placesprintis the right call (not logging). Return anintexit code.
Run it#
From a shell, once the package is installed or added to your project:
bash
lsd csvkit stats people.csv
# → 3 rows x 2 columns: ['name', 'age']
# column nulls pct
# name 0 0
# age 0 0
lsd csvkit stats people.csv --sort-by age
# → same stats, rows sorted by age first
@tool.command returns the function unchanged, so you can also call it directly in a test with a
stub context:
Python
from lsdtools import Context
stats(Context.testing(), "people.csv")
# → prints the stats; returns 0
Try changing it#
- Add
limit: int = 20(an--limitoption) and printt.head(limit). - Add a
to_parquet: bool = Falseflag that writessrcout as Parquet when set. - Add a second command (
@tool.command("head", ...)) — each typed function is its own subcommand.
Related#
Learn — Commands & actions
API — Tool · Context · CLI commands
Examples — A dockable panel · A complete package
By LSD Team · Last updated Jul 17, 2026
Ask on Discord
View as Markdown