# Example: your first tool
URL: https://lsd.tools/docs/ex-hello-tool
Updated: 2026-07-17

> A full tool.py with a load step (emit rows) and a shape step (keep the top N). Drop it in a project folder next to your .lsd file and LSD hot-loads it — both nodes appear in the palette with generated forms.

The smallest real tool: one node that makes data, one that transforms it. Save this as `tool.py`
in a folder, point LSD at it (["Add local package…"](/docs/pkg-develop)), and both nodes appear in
the add-node palette with generated forms.

## The code

```python
# tool.py
from lsdtools import Tool, Table

tool = Tool("hello", label="Hello")


@tool.load
def numbers(count: int = 5) -> Table:
    """Emit a table of the first `count` integers and their squares."""
    xs = list(range(count))
    return Table({"n": xs, "square": [x * x for x in xs]})


@tool.shape
def top(t: Table, limit: int = 3) -> Table:
    """Keep the `limit` rows with the largest square."""
    return t.sort("square", descending=True).head(limit)
```

## What each line does

- **`tool = Tool("hello")`** — one `Tool` per package, created at module scope. Its name
 namespaces the nodes (`hello.numbers`, `hello.top`).
- **`@tool.load`** — makes a node that *produces* data. `count: int = 5` has a default, so it is an
 optional **parameter** and renders as a number field.
- **`Table({...})`** — builds a [`Table`](/docs/api-table) from a dict of columns.
- **`@tool.shape`** — makes a *transform* node. `t: Table` is an **input port** (wire it from the
 load node); `limit: int` is another parameter.
- Every function **returns a step, never data at import time** — LSD runs it when the node runs.

## Run it in Python

You don't need the desktop to try it — wire the steps and run with an [`Engine`](/docs/api-engine):

```python
from lsdtools import Engine

n = numbers(count=6)
result = Engine().run(top(n, limit=3))
result.output.print()
# → n:[5,4,3] square:[25,16,9]
```

`top(n, ...)` wires the shape node's input port to the load node `n`. `Engine().run(...)` executes
the chain and caches each step; `result.output` is the final `Table`.

## Try changing it

- Add a `@tool.deliver` node that calls `t.write_csv("out.csv")`.
- Give `numbers` a `Source` parameter to load a real file instead — see
 [File loader](/docs/ex-file-loader).
- Add a second output with `outputs=["evens", "odds"]` — see [Multi-output](/docs/ex-multi-output).

## Related

**Learn** — [Load steps](/docs/tools-load) · [Shape steps](/docs/tools-shape) · [Develop a package](/docs/pkg-develop)

**API** — [Tool](/docs/api-tool) · [Table](/docs/api-table) · [Engine](/docs/api-engine)
