sft-cli 0.1.0__tar.gz → 0.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sft_cli-0.1.0 → sft_cli-0.2.1}/.github/workflows/ci.yml +43 -3
- {sft_cli-0.1.0 → sft_cli-0.2.1}/.github/workflows/publish.yml +1 -1
- sft_cli-0.2.1/.gitignore +34 -0
- sft_cli-0.2.1/AGENTS.md +92 -0
- sft_cli-0.2.1/LICENSE +21 -0
- sft_cli-0.2.1/PKG-INFO +155 -0
- sft_cli-0.2.1/README.md +121 -0
- sft_cli-0.2.1/docs/plans/2026-03-06-implementation-plan.md +1431 -0
- sft_cli-0.2.1/docs/plans/2026-03-06-sft-toolkit-design.md +863 -0
- {sft_cli-0.1.0 → sft_cli-0.2.1}/pyproject.toml +23 -1
- sft_cli-0.2.1/scripts/build_skill_reference.py +113 -0
- sft_cli-0.2.1/scripts/hatch_build_hook.py +49 -0
- {sft_cli-0.1.0 → sft_cli-0.2.1}/src/sft/__init__.py +1 -1
- sft_cli-0.2.1/src/sft/browser.py +2529 -0
- sft_cli-0.2.1/src/sft/cli.py +205 -0
- sft_cli-0.2.1/src/sft/commands/__init__.py +0 -0
- sft_cli-0.2.1/src/sft/commands/cast.py +103 -0
- sft_cli-0.2.1/src/sft/commands/cat.py +90 -0
- sft_cli-0.2.1/src/sft/commands/check.py +80 -0
- sft_cli-0.2.1/src/sft/commands/convert.py +80 -0
- sft_cli-0.2.1/src/sft/commands/diff.py +208 -0
- sft_cli-0.2.1/src/sft/commands/info.py +73 -0
- sft_cli-0.2.1/src/sft/commands/lora.py +750 -0
- sft_cli-0.2.1/src/sft/commands/ls.py +117 -0
- sft_cli-0.2.1/src/sft/commands/metadata.py +96 -0
- sft_cli-0.2.1/src/sft/commands/rename.py +111 -0
- sft_cli-0.2.1/src/sft/commands/skill.py +519 -0
- sft_cli-0.2.1/src/sft/commands/slice.py +89 -0
- sft_cli-0.2.1/src/sft/commands/split.py +111 -0
- sft_cli-0.2.1/src/sft/commands/stat.py +134 -0
- sft_cli-0.2.1/src/sft/commands/strip.py +78 -0
- sft_cli-0.2.1/src/sft/commands/tree.py +120 -0
- sft_cli-0.2.1/src/sft/ops/__init__.py +0 -0
- sft_cli-0.2.1/src/sft/ops/cast.py +68 -0
- sft_cli-0.2.1/src/sft/ops/cat.py +70 -0
- sft_cli-0.2.1/src/sft/ops/check.py +88 -0
- sft_cli-0.2.1/src/sft/ops/convert.py +60 -0
- sft_cli-0.2.1/src/sft/ops/diff.py +167 -0
- sft_cli-0.2.1/src/sft/ops/info.py +73 -0
- sft_cli-0.2.1/src/sft/ops/lora/__init__.py +0 -0
- sft_cli-0.2.1/src/sft/ops/lora/add.py +131 -0
- sft_cli-0.2.1/src/sft/ops/lora/compat.py +79 -0
- sft_cli-0.2.1/src/sft/ops/lora/convert.py +286 -0
- sft_cli-0.2.1/src/sft/ops/lora/detect.py +189 -0
- sft_cli-0.2.1/src/sft/ops/lora/extract.py +115 -0
- sft_cli-0.2.1/src/sft/ops/lora/info.py +15 -0
- sft_cli-0.2.1/src/sft/ops/lora/merge.py +78 -0
- sft_cli-0.2.1/src/sft/ops/lora/resize.py +155 -0
- sft_cli-0.2.1/src/sft/ops/lora/stack.py +221 -0
- sft_cli-0.2.1/src/sft/ops/lora/svd.py +142 -0
- sft_cli-0.2.1/src/sft/ops/ls.py +48 -0
- sft_cli-0.2.1/src/sft/ops/metadata.py +44 -0
- sft_cli-0.2.1/src/sft/ops/rename.py +65 -0
- sft_cli-0.2.1/src/sft/ops/slice.py +86 -0
- sft_cli-0.2.1/src/sft/ops/split.py +128 -0
- sft_cli-0.2.1/src/sft/ops/stat.py +82 -0
- sft_cli-0.2.1/src/sft/ops/tree.py +126 -0
- sft_cli-0.2.1/src/sft/skill/REFERENCE.md +586 -0
- sft_cli-0.2.1/src/sft/skill/SKILL.md +146 -0
- sft_cli-0.2.1/src/sft/utils/__init__.py +0 -0
- sft_cli-0.2.1/src/sft/utils/dtypes.py +50 -0
- sft_cli-0.2.1/src/sft/utils/formatting.py +59 -0
- sft_cli-0.2.1/src/sft/utils/glob.py +73 -0
- sft_cli-0.2.1/src/sft/utils/linking.py +277 -0
- sft_cli-0.2.1/src/sft/utils/output.py +21 -0
- sft_cli-0.2.1/src/sft/utils/tensor_io.py +165 -0
- sft_cli-0.2.1/tests/__init__.py +0 -0
- sft_cli-0.2.1/tests/conftest.py +134 -0
- sft_cli-0.2.1/tests/test_build_hook.py +66 -0
- sft_cli-0.2.1/tests/test_cast.py +109 -0
- sft_cli-0.2.1/tests/test_cat.py +198 -0
- sft_cli-0.2.1/tests/test_check.py +44 -0
- sft_cli-0.2.1/tests/test_cli.py +102 -0
- sft_cli-0.2.1/tests/test_cli_contract.py +305 -0
- sft_cli-0.2.1/tests/test_convert.py +132 -0
- sft_cli-0.2.1/tests/test_diff.py +306 -0
- sft_cli-0.2.1/tests/test_e2e_subprocess.py +194 -0
- sft_cli-0.2.1/tests/test_info.py +59 -0
- sft_cli-0.2.1/tests/test_lora_add.py +135 -0
- sft_cli-0.2.1/tests/test_lora_compat.py +90 -0
- sft_cli-0.2.1/tests/test_lora_convert.py +250 -0
- sft_cli-0.2.1/tests/test_lora_detect.py +105 -0
- sft_cli-0.2.1/tests/test_lora_extract.py +90 -0
- sft_cli-0.2.1/tests/test_lora_info.py +42 -0
- sft_cli-0.2.1/tests/test_lora_merge.py +78 -0
- sft_cli-0.2.1/tests/test_lora_properties.py +353 -0
- sft_cli-0.2.1/tests/test_lora_resize.py +254 -0
- sft_cli-0.2.1/tests/test_lora_stack.py +389 -0
- sft_cli-0.2.1/tests/test_lora_svd.py +178 -0
- sft_cli-0.2.1/tests/test_ls.py +82 -0
- sft_cli-0.2.1/tests/test_metadata.py +126 -0
- sft_cli-0.2.1/tests/test_perf.py +74 -0
- sft_cli-0.2.1/tests/test_pipelines.py +280 -0
- sft_cli-0.2.1/tests/test_properties.py +100 -0
- sft_cli-0.2.1/tests/test_rename.py +169 -0
- sft_cli-0.2.1/tests/test_skill_install.py +299 -0
- sft_cli-0.2.1/tests/test_slice.py +105 -0
- sft_cli-0.2.1/tests/test_split.py +174 -0
- sft_cli-0.2.1/tests/test_stat.py +70 -0
- sft_cli-0.2.1/tests/test_strip.py +134 -0
- sft_cli-0.2.1/tests/test_tensor_io.py +96 -0
- sft_cli-0.2.1/tests/test_tree.py +44 -0
- sft_cli-0.2.1/tests/test_tui.py +943 -0
- sft_cli-0.2.1/tests/test_utils.py +160 -0
- sft_cli-0.2.1/uv.lock +1621 -0
- sft_cli-0.1.0/.gitignore +0 -23
- sft_cli-0.1.0/PKG-INFO +0 -115
- sft_cli-0.1.0/README.md +0 -89
- sft_cli-0.1.0/scripts/create_test_file.py +0 -86
- sft_cli-0.1.0/src/sft/browser.py +0 -947
- sft_cli-0.1.0/src/sft/cli.py +0 -63
- sft_cli-0.1.0/uv.lock +0 -504
- {sft_cli-0.1.0 → sft_cli-0.2.1}/.pre-commit-config.yaml +0 -0
- {sft_cli-0.1.0 → sft_cli-0.2.1}/.python-version +0 -0
- {sft_cli-0.1.0 → sft_cli-0.2.1}/src/sft/index.py +0 -0
|
@@ -32,11 +32,13 @@ jobs:
|
|
|
32
32
|
run: uv run ruff format --check .
|
|
33
33
|
|
|
34
34
|
test:
|
|
35
|
-
|
|
35
|
+
name: test (py${{ matrix.python-version }} / ${{ matrix.os }})
|
|
36
|
+
runs-on: ${{ matrix.os }}
|
|
36
37
|
strategy:
|
|
37
38
|
fail-fast: false
|
|
38
39
|
matrix:
|
|
39
|
-
|
|
40
|
+
os: [ubuntu-latest, macos-latest]
|
|
41
|
+
python-version: ["3.9", "3.13", "3.14"]
|
|
40
42
|
steps:
|
|
41
43
|
- uses: actions/checkout@v4
|
|
42
44
|
|
|
@@ -47,13 +49,43 @@ jobs:
|
|
|
47
49
|
run: uv python install ${{ matrix.python-version }}
|
|
48
50
|
|
|
49
51
|
- name: Install dependencies
|
|
50
|
-
run: uv sync
|
|
52
|
+
run: uv sync --dev
|
|
51
53
|
|
|
52
54
|
- name: Verify package imports
|
|
53
55
|
run: uv run python -c "from sft.cli import app; from sft.browser import SftApp; from sft.index import TensorIndex; print('All imports successful')"
|
|
54
56
|
|
|
57
|
+
- name: Run fast test suite
|
|
58
|
+
run: uv run pytest -q
|
|
59
|
+
|
|
60
|
+
test-slow:
|
|
61
|
+
# Long-running tests (subprocess E2E, perf smoke, build-hook) only need
|
|
62
|
+
# to pass on one cell — they exercise behavior that's OS- and version-
|
|
63
|
+
# independent.
|
|
64
|
+
name: slow tests (py3.14 / ubuntu)
|
|
65
|
+
runs-on: ubuntu-latest
|
|
66
|
+
needs: test
|
|
67
|
+
steps:
|
|
68
|
+
- uses: actions/checkout@v4
|
|
69
|
+
|
|
70
|
+
- name: Install uv
|
|
71
|
+
uses: astral-sh/setup-uv@v4
|
|
72
|
+
|
|
73
|
+
- name: Set up Python
|
|
74
|
+
run: uv python install 3.14
|
|
75
|
+
|
|
76
|
+
- name: Install dependencies
|
|
77
|
+
run: uv sync --dev
|
|
78
|
+
|
|
79
|
+
- name: Install package (so the `sft` console script is on PATH)
|
|
80
|
+
run: uv pip install -e .
|
|
81
|
+
|
|
82
|
+
- name: Run slow tests
|
|
83
|
+
run: uv run pytest -q -m slow
|
|
84
|
+
|
|
55
85
|
build:
|
|
86
|
+
name: build wheel + smoke install
|
|
56
87
|
runs-on: ubuntu-latest
|
|
88
|
+
needs: test
|
|
57
89
|
steps:
|
|
58
90
|
- uses: actions/checkout@v4
|
|
59
91
|
|
|
@@ -66,6 +98,14 @@ jobs:
|
|
|
66
98
|
- name: Build package
|
|
67
99
|
run: uv build
|
|
68
100
|
|
|
101
|
+
- name: Smoke install + help from a clean venv
|
|
102
|
+
run: |
|
|
103
|
+
python3 -m venv /tmp/sft-smoke
|
|
104
|
+
/tmp/sft-smoke/bin/pip install --upgrade pip
|
|
105
|
+
/tmp/sft-smoke/bin/pip install dist/sft_cli-*.whl
|
|
106
|
+
/tmp/sft-smoke/bin/sft --help
|
|
107
|
+
/tmp/sft-smoke/bin/sft --version
|
|
108
|
+
|
|
69
109
|
- name: Upload build artifacts
|
|
70
110
|
uses: actions/upload-artifact@v4
|
|
71
111
|
with:
|
sft_cli-0.2.1/.gitignore
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# macOS
|
|
2
|
+
.DS_Store
|
|
3
|
+
|
|
4
|
+
# Generated demo artifacts
|
|
5
|
+
demo.gif
|
|
6
|
+
demo.tape
|
|
7
|
+
|
|
8
|
+
# AI agent documents
|
|
9
|
+
.ai/
|
|
10
|
+
|
|
11
|
+
# Python
|
|
12
|
+
__pycache__/
|
|
13
|
+
*.py[cod]
|
|
14
|
+
*.so
|
|
15
|
+
dist/
|
|
16
|
+
*.egg-info/
|
|
17
|
+
|
|
18
|
+
# Virtual environments
|
|
19
|
+
.venv/
|
|
20
|
+
|
|
21
|
+
# IDE
|
|
22
|
+
.vscode/
|
|
23
|
+
.idea/
|
|
24
|
+
|
|
25
|
+
# Testing
|
|
26
|
+
.pytest_cache/
|
|
27
|
+
.coverage
|
|
28
|
+
|
|
29
|
+
# Test files
|
|
30
|
+
*.safetensors
|
|
31
|
+
|
|
32
|
+
# Skill installer state (written by `sft skill install` into the source dir
|
|
33
|
+
# when developing locally — not source code).
|
|
34
|
+
.sft-skill-install.json
|
sft_cli-0.2.1/AGENTS.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# AGENTS.md — guidance for AI agents working on `sft-cli`
|
|
2
|
+
|
|
3
|
+
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.
|
|
4
|
+
|
|
5
|
+
## What this project is
|
|
6
|
+
|
|
7
|
+
`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`.
|
|
8
|
+
|
|
9
|
+
## Repo layout
|
|
10
|
+
|
|
11
|
+
- `src/sft/cli.py` — Typer app entry point, the auto-`browse` shim, and top-level commands (`browse`, `info`, `check`). Every other subcommand is registered by importing its module at the bottom of this file.
|
|
12
|
+
- `src/sft/commands/<name>.py` — thin CLI wrappers (Typer surface, argument parsing, formatting, `--json`). They delegate to `src/sft/ops/<name>.py`.
|
|
13
|
+
- `src/sft/ops/<name>.py` — pure business logic. No Typer, no printing; returns dataclasses. Always testable in isolation.
|
|
14
|
+
- `src/sft/utils/` — shared helpers (formatting, dtype mapping, glob filtering, tensor IO, cross-platform linking for the skill installer).
|
|
15
|
+
- `src/sft/browser.py` — the interactive Textual TUI (the `browse` command). Self-contained, do not import from other commands.
|
|
16
|
+
- `src/sft/index.py` — the `TensorIndex` / `PrefixTree` data model, parsed header-only from `.safetensors` files.
|
|
17
|
+
- `src/sft/skill/` — the agent skill that ships inside the wheel and is installed by `sft skill install`. Two files: `SKILL.md` (hand-written: trigger description, command map, cookbook of cross-command patterns) and `REFERENCE.md` (**auto-generated** — never edit by hand).
|
|
18
|
+
- `scripts/build_skill_reference.py` — regenerates `src/sft/skill/REFERENCE.md` from the live Typer CLI.
|
|
19
|
+
- `scripts/hatch_build_hook.py` — hatch build hook that runs the regenerator at wheel-build time (tolerates missing deps in isolated builds).
|
|
20
|
+
- `tests/` — pytest tests, one file per command. Shared fixtures in `tests/conftest.py`.
|
|
21
|
+
|
|
22
|
+
## Conventions
|
|
23
|
+
|
|
24
|
+
1. **`--json` everywhere on commands an agent might parse.** Every command supports `--json` and outputs a stable JSON contract. When you add a new command, add `--json` from day one and update `SKILL.md`. The pattern is:
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
if json_output:
|
|
28
|
+
typer.echo(json.dumps(data, indent=2))
|
|
29
|
+
return
|
|
30
|
+
# human-readable fallback below
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Errors raised via `--json` should also be JSON: `typer.echo(json.dumps({"error": str(e)}, indent=2))` before `raise typer.Exit(code=1)`.
|
|
34
|
+
|
|
35
|
+
2. **Separation of concerns.** A new feature `foo` means:
|
|
36
|
+
- Pure logic in `src/sft/ops/foo.py` (returns a dataclass).
|
|
37
|
+
- CLI wrapper in `src/sft/commands/foo.py` (imports `ops/foo.py`, owns flags and printing).
|
|
38
|
+
- Register by adding `import sft.commands.foo` to the bottom of `src/sft/cli.py` (the imports there look stylistically dead but they trigger `@app.command(...)` decorators — keep them sorted alphabetically).
|
|
39
|
+
- Tests in `tests/test_foo.py` (CLI tests use `typer.testing.CliRunner`).
|
|
40
|
+
|
|
41
|
+
3. **The auto-`browse` shim** in `_entry()` rewrites `sft x.safetensors` → `sft browse x.safetensors`. Do not add subcommands whose names end in `.safetensors`.
|
|
42
|
+
|
|
43
|
+
4. **`validate_safetensors(path)`** in `src/sft/cli.py` is the canonical "is this a `.safetensors` file?" check. Use it from every command that takes a `.safetensors` argument.
|
|
44
|
+
|
|
45
|
+
5. **Output paths** use the `resolve_output(output, src, suffix)` helper in `src/sft/utils/output.py` to default to `{stem}.{suffix}.safetensors`. Write commands should never overwrite the source file.
|
|
46
|
+
|
|
47
|
+
6. **Dry-run.** Every write-side command exposes `--dry-run` and the underlying op must support `dry_run=True` to return a result without touching disk.
|
|
48
|
+
|
|
49
|
+
7. **Header-only reads.** Prefer `TensorIndex.from_file(path)` (header-only, milliseconds) over loading tensor data unless you specifically need values. Header-only operations are why `sft` exists.
|
|
50
|
+
|
|
51
|
+
## Updating the agent skill
|
|
52
|
+
|
|
53
|
+
The shipped skill lives in `src/sft/skill/`:
|
|
54
|
+
|
|
55
|
+
- `SKILL.md` — hand-written: trigger description (YAML frontmatter), golden `--json` rule, command map, "when to use" guidance, and an inline cookbook of cross-command patterns and `jq` recipes.
|
|
56
|
+
- `REFERENCE.md` — **auto-generated** from Typer introspection. Regenerate with:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
python scripts/build_skill_reference.py
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
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 import `sft` (e.g. inside an isolated build env), it leaves the committed file alone.
|
|
63
|
+
|
|
64
|
+
When you add a new command or flag, also:
|
|
65
|
+
|
|
66
|
+
- Update the command-map table in `SKILL.md` if the command is something agents should reach for.
|
|
67
|
+
- If the command enables a cross-command workflow or a useful `--json` parsing pattern that isn't obvious from the single-command help, add it to the "Cookbook" section in `SKILL.md`. Resist the urge to write per-command tutorials — `REFERENCE.md` already covers single-command usage.
|
|
68
|
+
|
|
69
|
+
## Dev commands
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
uv sync # install deps
|
|
73
|
+
uv run pytest -q # full test suite
|
|
74
|
+
uv run pytest tests/test_<command>.py -q # one file
|
|
75
|
+
uv run ruff check # lint
|
|
76
|
+
uv run ruff format # format
|
|
77
|
+
python scripts/build_skill_reference.py # regenerate skill reference
|
|
78
|
+
uv build # produce wheel + sdist (regenerates skill ref via hook)
|
|
79
|
+
uv run sft skill install --dry-run # preview a skill install locally
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Known stylistic notes
|
|
83
|
+
|
|
84
|
+
- Top-level imports in `src/sft/cli.py` use `# noqa: F401, E402` because they're after function definitions and are imported for their side effect (registering commands). Keep that pattern.
|
|
85
|
+
- We support Python ≥ 3.9. Avoid `match` statements, walrus inside comprehensions, and other newer syntax in shared code paths. Use `from __future__ import annotations` at the top of every module.
|
|
86
|
+
- The pre-commit config runs `ruff` (lint + format). Run `uv run ruff check` and `uv run ruff format` before committing.
|
|
87
|
+
|
|
88
|
+
## Out-of-scope for changes
|
|
89
|
+
|
|
90
|
+
- Do **not** add an MCP server module. The skill + reliable `--json` is the intended agent integration story.
|
|
91
|
+
- Do **not** silently rewrite `src/sft/skill/REFERENCE.md` by hand; always regenerate via the script.
|
|
92
|
+
- Do **not** invent new install locations for the skill installer without updating `AGENT_DIRS` in `src/sft/commands/skill.py` and the corresponding docs in `SKILL.md` and `README.md`.
|
sft_cli-0.2.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Matan Ben-Yosef
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
sft_cli-0.2.1/PKG-INFO
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sft-cli
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: An interactive terminal browser for .safetensors files
|
|
5
|
+
Project-URL: Homepage, https://github.com/matanby/sft-cli
|
|
6
|
+
Project-URL: Repository, https://github.com/matanby/sft-cli
|
|
7
|
+
Author: Matan Ben-Yosef
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: browser,cli,machine-learning,safetensors,tui
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
24
|
+
Classifier: Topic :: Utilities
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Requires-Dist: ml-dtypes>=0.3
|
|
27
|
+
Requires-Dist: numpy>=1.24
|
|
28
|
+
Requires-Dist: safetensors>=0.4
|
|
29
|
+
Requires-Dist: textual>=0.40
|
|
30
|
+
Requires-Dist: typer>=0.9
|
|
31
|
+
Provides-Extra: torch
|
|
32
|
+
Requires-Dist: torch>=2.0; extra == 'torch'
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
|
|
35
|
+
# sft
|
|
36
|
+
|
|
37
|
+
> The Swiss army knife for `.safetensors` files.
|
|
38
|
+
|
|
39
|
+
[](https://pypi.org/project/sft-cli/)
|
|
40
|
+
[](https://pypi.org/project/sft-cli/)
|
|
41
|
+
[](https://github.com/matanby/sft-cli/actions)
|
|
42
|
+
[](LICENSE)
|
|
43
|
+
|
|
44
|
+
<img src="https://vhs.charm.sh/vhs-3nFXGLuvC5swABYCewgchD.gif" alt="sft demo">
|
|
45
|
+
|
|
46
|
+
`sft` is a single-binary CLI for inspecting, editing, and diffing `.safetensors` files — and an interactive terminal browser for poking around large checkpoints. Most commands read the file header only, so multi-gigabyte models open in milliseconds.
|
|
47
|
+
|
|
48
|
+
It also ships a [skill](#-ai-agents) that teaches AI coding agents (Claude Code, Cursor, Codex CLI) when to reach for it and how to parse the output.
|
|
49
|
+
|
|
50
|
+
## Install
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
uv tool install sft-cli # recommended
|
|
54
|
+
pip install sft-cli # or pip
|
|
55
|
+
uv tool install 'sft-cli[torch]' # + .pt/.pth conversion
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## 🚀 Quick start
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
sft model.safetensors # open the interactive browser
|
|
62
|
+
sft info model.safetensors # one-shot summary
|
|
63
|
+
sft info model.safetensors --json # machine-readable
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The bare `sft <file>` form is a shortcut for `sft browse <file>`. Inside the browser: `↑↓` to navigate, `/` to filter, `L` on a LoRA file for LoRA Mode, `D` to diff against another file, `q` to quit.
|
|
67
|
+
|
|
68
|
+
## What it does
|
|
69
|
+
|
|
70
|
+
**Inspect** a file without loading it:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
sft info model.safetensors # size, tensor count, dtypes, metadata
|
|
74
|
+
sft ls model.safetensors # flat list, sort/filter friendly
|
|
75
|
+
sft tree model.safetensors --depth=2 # hierarchical view
|
|
76
|
+
sft stat model.safetensors # per-tensor mean/std/min/max/sparsity
|
|
77
|
+
sft check model.safetensors # corruption + NaN/Inf scan
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**Compare** two checkpoints:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
sft diff base.safetensors finetuned.safetensors --delta \
|
|
84
|
+
--include='**.self_attn.**' # cosine, L2, max-abs per tensor
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Edit** without writing Python:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
sft slice big.safetensors --include='**.weight' -o weights-only.safetensors
|
|
91
|
+
sft strip big.safetensors --exclude='*lora_*'
|
|
92
|
+
sft cast model.safetensors --dtype fp16
|
|
93
|
+
sft cat a.safetensors b.safetensors -o merged.safetensors
|
|
94
|
+
sft rename model.safetensors --sub 'model\.' 'backbone.'
|
|
95
|
+
sft split model.safetensors --max-size 4GB
|
|
96
|
+
sft convert pytorch_model.bin # → safetensors
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Every write command supports `--dry-run` and never overwrites the input — outputs default to `{stem}.{suffix}.safetensors`.
|
|
100
|
+
|
|
101
|
+
**Adapter workflows** (PEFT and Kohya):
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
sft lora info adapter.safetensors # rank, alpha, target modules
|
|
105
|
+
sft lora svd adapter.safetensors # singular-value spectrum
|
|
106
|
+
sft lora compat base.safetensors adapter.safetensors
|
|
107
|
+
sft lora extract base.safetensors ft.safetensors --rank 16
|
|
108
|
+
sft lora resize adapter.safetensors --rank auto # per-pair adaptive rank
|
|
109
|
+
sft lora stack a.safetensors b.safetensors -a 0.7 -b 0.3
|
|
110
|
+
sft lora merge base.safetensors adapter.safetensors
|
|
111
|
+
sft lora convert adapter.safetensors --to peft # Kohya ↔ PEFT
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`--rank auto` picks each pair's output rank from its singular-value spectrum (`ceil(stable_rank) + 1`), so over-parameterized pairs compress harder than rich ones. `auto+N` adds a safety margin.
|
|
115
|
+
|
|
116
|
+
## The browser
|
|
117
|
+
|
|
118
|
+
Press a key, get a result.
|
|
119
|
+
|
|
120
|
+
| Key | Action |
|
|
121
|
+
|:---:|---|
|
|
122
|
+
| `↑` `↓` | Navigate |
|
|
123
|
+
| `←` `→` | Collapse / expand tree |
|
|
124
|
+
| `Tab` | Switch between tree and table |
|
|
125
|
+
| `/` | Search / filter |
|
|
126
|
+
| `s` | Cycle sort |
|
|
127
|
+
| `Enter` | Tensor stats popup |
|
|
128
|
+
| `m` | File metadata |
|
|
129
|
+
| `c` | Cast file dtype |
|
|
130
|
+
| `L` | LoRA Mode (per-pair stats, SVD, compress) |
|
|
131
|
+
| `D` | Diff against another file |
|
|
132
|
+
| `:` | Command palette |
|
|
133
|
+
| `q` | Quit |
|
|
134
|
+
|
|
135
|
+
## 🤖 AI agents
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
sft skill install # auto-detects Claude / Cursor / Codex
|
|
139
|
+
sft skill status
|
|
140
|
+
sft skill uninstall
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The installer symlinks `sft`'s skill into your agent's well-known skills directory (`~/.claude/skills/sft`, `~/.cursor/skills/sft`, etc.), so it stays in sync when you `uv tool upgrade sft-cli`. Pass `--mode copy` for a frozen snapshot.
|
|
144
|
+
|
|
145
|
+
Every command supports `--json` for clean parsing:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
sft info model.safetensors --json | jq '.tensors'
|
|
149
|
+
sft lora info adapter.safetensors --json | jq '.rank'
|
|
150
|
+
sft stat model.safetensors --json --include='**.q_proj.*'
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## License
|
|
154
|
+
|
|
155
|
+
MIT — see [LICENSE](LICENSE).
|
sft_cli-0.2.1/README.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# sft
|
|
2
|
+
|
|
3
|
+
> The Swiss army knife for `.safetensors` files.
|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/sft-cli/)
|
|
6
|
+
[](https://pypi.org/project/sft-cli/)
|
|
7
|
+
[](https://github.com/matanby/sft-cli/actions)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
|
|
10
|
+
<img src="https://vhs.charm.sh/vhs-3nFXGLuvC5swABYCewgchD.gif" alt="sft demo">
|
|
11
|
+
|
|
12
|
+
`sft` is a single-binary CLI for inspecting, editing, and diffing `.safetensors` files — and an interactive terminal browser for poking around large checkpoints. Most commands read the file header only, so multi-gigabyte models open in milliseconds.
|
|
13
|
+
|
|
14
|
+
It also ships a [skill](#-ai-agents) that teaches AI coding agents (Claude Code, Cursor, Codex CLI) when to reach for it and how to parse the output.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
uv tool install sft-cli # recommended
|
|
20
|
+
pip install sft-cli # or pip
|
|
21
|
+
uv tool install 'sft-cli[torch]' # + .pt/.pth conversion
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## 🚀 Quick start
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
sft model.safetensors # open the interactive browser
|
|
28
|
+
sft info model.safetensors # one-shot summary
|
|
29
|
+
sft info model.safetensors --json # machine-readable
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The bare `sft <file>` form is a shortcut for `sft browse <file>`. Inside the browser: `↑↓` to navigate, `/` to filter, `L` on a LoRA file for LoRA Mode, `D` to diff against another file, `q` to quit.
|
|
33
|
+
|
|
34
|
+
## What it does
|
|
35
|
+
|
|
36
|
+
**Inspect** a file without loading it:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
sft info model.safetensors # size, tensor count, dtypes, metadata
|
|
40
|
+
sft ls model.safetensors # flat list, sort/filter friendly
|
|
41
|
+
sft tree model.safetensors --depth=2 # hierarchical view
|
|
42
|
+
sft stat model.safetensors # per-tensor mean/std/min/max/sparsity
|
|
43
|
+
sft check model.safetensors # corruption + NaN/Inf scan
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Compare** two checkpoints:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
sft diff base.safetensors finetuned.safetensors --delta \
|
|
50
|
+
--include='**.self_attn.**' # cosine, L2, max-abs per tensor
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Edit** without writing Python:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
sft slice big.safetensors --include='**.weight' -o weights-only.safetensors
|
|
57
|
+
sft strip big.safetensors --exclude='*lora_*'
|
|
58
|
+
sft cast model.safetensors --dtype fp16
|
|
59
|
+
sft cat a.safetensors b.safetensors -o merged.safetensors
|
|
60
|
+
sft rename model.safetensors --sub 'model\.' 'backbone.'
|
|
61
|
+
sft split model.safetensors --max-size 4GB
|
|
62
|
+
sft convert pytorch_model.bin # → safetensors
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Every write command supports `--dry-run` and never overwrites the input — outputs default to `{stem}.{suffix}.safetensors`.
|
|
66
|
+
|
|
67
|
+
**Adapter workflows** (PEFT and Kohya):
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
sft lora info adapter.safetensors # rank, alpha, target modules
|
|
71
|
+
sft lora svd adapter.safetensors # singular-value spectrum
|
|
72
|
+
sft lora compat base.safetensors adapter.safetensors
|
|
73
|
+
sft lora extract base.safetensors ft.safetensors --rank 16
|
|
74
|
+
sft lora resize adapter.safetensors --rank auto # per-pair adaptive rank
|
|
75
|
+
sft lora stack a.safetensors b.safetensors -a 0.7 -b 0.3
|
|
76
|
+
sft lora merge base.safetensors adapter.safetensors
|
|
77
|
+
sft lora convert adapter.safetensors --to peft # Kohya ↔ PEFT
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`--rank auto` picks each pair's output rank from its singular-value spectrum (`ceil(stable_rank) + 1`), so over-parameterized pairs compress harder than rich ones. `auto+N` adds a safety margin.
|
|
81
|
+
|
|
82
|
+
## The browser
|
|
83
|
+
|
|
84
|
+
Press a key, get a result.
|
|
85
|
+
|
|
86
|
+
| Key | Action |
|
|
87
|
+
|:---:|---|
|
|
88
|
+
| `↑` `↓` | Navigate |
|
|
89
|
+
| `←` `→` | Collapse / expand tree |
|
|
90
|
+
| `Tab` | Switch between tree and table |
|
|
91
|
+
| `/` | Search / filter |
|
|
92
|
+
| `s` | Cycle sort |
|
|
93
|
+
| `Enter` | Tensor stats popup |
|
|
94
|
+
| `m` | File metadata |
|
|
95
|
+
| `c` | Cast file dtype |
|
|
96
|
+
| `L` | LoRA Mode (per-pair stats, SVD, compress) |
|
|
97
|
+
| `D` | Diff against another file |
|
|
98
|
+
| `:` | Command palette |
|
|
99
|
+
| `q` | Quit |
|
|
100
|
+
|
|
101
|
+
## 🤖 AI agents
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
sft skill install # auto-detects Claude / Cursor / Codex
|
|
105
|
+
sft skill status
|
|
106
|
+
sft skill uninstall
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The installer symlinks `sft`'s skill into your agent's well-known skills directory (`~/.claude/skills/sft`, `~/.cursor/skills/sft`, etc.), so it stays in sync when you `uv tool upgrade sft-cli`. Pass `--mode copy` for a frozen snapshot.
|
|
110
|
+
|
|
111
|
+
Every command supports `--json` for clean parsing:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
sft info model.safetensors --json | jq '.tensors'
|
|
115
|
+
sft lora info adapter.safetensors --json | jq '.rank'
|
|
116
|
+
sft stat model.safetensors --json --include='**.q_proj.*'
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## License
|
|
120
|
+
|
|
121
|
+
MIT — see [LICENSE](LICENSE).
|