All docs
Commands & actions
Add a CLI subcommand from a typed function with @tool.command, and route menu or context actions with @tool.action — then invoke them via tool.dispatch.
Steps make data; commands and actions make a tool interactive. A command is a CLI
subcommand; an action is a routed handler wired into menus and context menus. Both receive a
Context — not a StepContext — so they can touch the live project.
Usage#
from lsdtools import Tool, Context, Table
tool = Tool("io")
@tool.command("csv2parquet", help="Convert a CSV to Parquet")
def csv2parquet(ctx: Context, src: str, dst: str = "out.parquet") -> int:
Table.read_csv(src).write_parquet(dst)
return 0
# → run it: lsd io csv2parquet data.csv --dst results.parquet
Commands: a CLI from the signature#
@tool.command("name") generates the subcommand lsd <tool> <name> straight from the function
signature — zero argparse. The mapping:
- the first
ctx: Contextparameter is injected (never on the command line); - a parameter with no default → a required positional argument;
- a parameter with a default → an optional
--flag; - the returned
intis the process exit code (0= success).
So csv2parquet above exposes src as positional and dst as --dst. See
CLI commands for how lsd discovers your tool.
Actions: routes into the UI#
@tool.action("route") registers a (ctx: Context, **values) handler under a route, which
decides where it shows up:
app/<verb>→ an application-level command (a Run-menu item);entity/<type>/<verb>→ a context-menu action on that entity type.
@tool.action("app/refresh", label="Refresh cache", icon="lsd-refresh-symbolic")
def refresh(ctx: Context, **values) -> None:
ctx.engine.run_all()
ctx.ui.notify("cache refreshed")
@tool.action("entity/drillhole/desurvey", label="Desurvey")
def desurvey(ctx: Context, dip: float = 60.0, **values) -> None:
ctx.entity.update(dip=dip)
ctx.entity.run()
The label and icon decorate the generated menu item. At tool.mount(), app/* actions become
menu items; entity/*/* actions attach to the matching entity type's context menu.
Dispatching an action#
Actions are also callable directly by route with tool.dispatch(...) — handy from a view button,
a test, or another handler. If you don't pass a ctx, one is built from the mounted host:
tool.dispatch("app/refresh") # ctx built from the host
tool.dispatch("entity/drillhole/desurvey", ctx, dip=75)
The **values you pass arrive as the handler's keyword arguments — the same values a form or view
would supply. Views wire their buttons to actions this way; see
Contributing UI.
Related#
Learn — Contributing UI · The Tool object · Entity types
API — Tool · Context · CLI commands
Examples — A CLI command · A dockable panel
Frequently asked questions
Do commands and actions receive a StepContext like steps do?
No — they receive the richer Context, with live access to the host, engine, project, selection and ui. StepContext is only for steps, which must stay pure for the cache. Commands and actions are user-triggered, so they get the full Context.
How does @tool.command decide positional args versus flags?
From the signature. A parameter with no default becomes a required positional argument; a parameter with a default becomes an optional --flag. The first parameter, ctx, is injected and never appears on the command line.