This file is read by Claude Code, Cursor, Codex CLI, and other agents to understand the repo. It is meant for agents modifying this codebase, not end-users of the CLI.
sft-cli is a CLI ("sft") for inspecting and editing .safetensors files. It is published on PyPI as sft-cli and the user-facing command is sft.
src/sft/cli.py— Typer app entry point, the auto-browseshim, and top-level commands (browse,info,check). Every other subcommand is registered by importing its module at the bottom of this file.src/sft/commands/<name>.py— thin CLI wrappers (Typer surface, argument parsing, formatting,--json). They delegate tosrc/sft/ops/<name>.py.src/sft/ops/<name>.py— pure business logic. No Typer, no printing; returns dataclasses. Always testable in isolation.src/sft/utils/— shared helpers (formatting, dtype mapping, glob filtering, tensor IO, cross-platform linking for the skill installer).src/sft/browser.py— the interactive Textual TUI (thebrowsecommand). Self-contained, do not import from other commands.src/sft/index.py— theTensorIndex/PrefixTreedata model, parsed header-only from.safetensorsfiles.src/sft/skill/— the agent skill that ships inside the wheel and is installed bysft skill install. Two files:SKILL.md(hand-written: trigger description, command map, cookbook of cross-command patterns) andREFERENCE.md(auto-generated — never edit by hand).scripts/build_skill_reference.py— regeneratessrc/sft/skill/REFERENCE.mdfrom the live Typer CLI.scripts/hatch_build_hook.py— hatch build hook that runs the regenerator at wheel-build time (tolerates missing deps in isolated builds).tests/— pytest tests, one file per command. Shared fixtures intests/conftest.py.
-
--jsoneverywhere on commands an agent might parse. Every command supports--jsonand outputs a stable JSON contract. When you add a new command, add--jsonfrom day one and updateSKILL.md. The pattern is:if json_output: typer.echo(json.dumps(data, indent=2)) return # human-readable fallback below
Errors raised via
--jsonshould also be JSON:typer.echo(json.dumps({"error": str(e)}, indent=2))beforeraise typer.Exit(code=1). -
Separation of concerns. A new feature
foomeans:- Pure logic in
src/sft/ops/foo.py(returns a dataclass). - CLI wrapper in
src/sft/commands/foo.py(importsops/foo.py, owns flags and printing). - Register by adding
import sft.commands.footo the bottom ofsrc/sft/cli.py(the imports there look stylistically dead but they trigger@app.command(...)decorators — keep them sorted alphabetically). - Tests in
tests/test_foo.py(CLI tests usetyper.testing.CliRunner).
- Pure logic in
-
The auto-
browseshim in_entry()rewritessft x.safetensors→sft browse x.safetensors. Do not add subcommands whose names end in.safetensors. -
validate_safetensors(path)insrc/sft/cli.pyis the canonical "is this a.safetensorsfile?" check. Use it from every command that takes a.safetensorsargument. -
Output paths use the
resolve_output(output, src, suffix)helper insrc/sft/utils/output.pyto default to{stem}.{suffix}.safetensors. Write commands should never overwrite the source file. -
Dry-run. Every write-side command exposes
--dry-runand the underlying op must supportdry_run=Trueto return a result without touching disk. -
Header-only reads. Prefer
TensorIndex.from_file(path)(header-only, milliseconds) over loading tensor data unless you specifically need values. Header-only operations are whysftexists.
The shipped skill lives in src/sft/skill/:
-
SKILL.md— hand-written: trigger description (YAML frontmatter), golden--jsonrule, command map, "when to use" guidance, and an inline cookbook of cross-command patterns andjqrecipes. -
REFERENCE.md— auto-generated from Typer introspection. Regenerate with:python scripts/build_skill_reference.py
This also runs as a hatch build hook during
uv build, so the wheel always contains an up-to-date reference. The script is best-effort: if it can't importsft(e.g. inside an isolated build env), it leaves the committed file alone.
When you add a new command or flag, also:
- Update the command-map table in
SKILL.mdif the command is something agents should reach for. - If the command enables a cross-command workflow or a useful
--jsonparsing pattern that isn't obvious from the single-command help, add it to the "Cookbook" section inSKILL.md. Resist the urge to write per-command tutorials —REFERENCE.mdalready covers single-command usage.
uv sync # install deps
uv run pytest -q # full test suite
uv run pytest tests/test_<command>.py -q # one file
uv run ruff check # lint
uv run ruff format # format
python scripts/build_skill_reference.py # regenerate skill reference
uv build # produce wheel + sdist (regenerates skill ref via hook)
uv run sft skill install --dry-run # preview a skill install locally- Top-level imports in
src/sft/cli.pyuse# noqa: F401, E402because they're after function definitions and are imported for their side effect (registering commands). Keep that pattern. - We support Python ≥ 3.9. Avoid
matchstatements, walrus inside comprehensions, and other newer syntax in shared code paths. Usefrom __future__ import annotationsat the top of every module. - The pre-commit config runs
ruff(lint + format). Runuv run ruff checkanduv run ruff formatbefore committing.
- Do not add an MCP server module. The skill + reliable
--jsonis the intended agent integration story. - Do not silently rewrite
src/sft/skill/REFERENCE.mdby hand; always regenerate via the script. - Do not invent new install locations for the skill installer without updating
AGENT_DIRSinsrc/sft/commands/skill.pyand the corresponding docs inSKILL.mdandREADME.md.