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.
Files changed (115) hide show
  1. {sft_cli-0.1.0 → sft_cli-0.2.1}/.github/workflows/ci.yml +43 -3
  2. {sft_cli-0.1.0 → sft_cli-0.2.1}/.github/workflows/publish.yml +1 -1
  3. sft_cli-0.2.1/.gitignore +34 -0
  4. sft_cli-0.2.1/AGENTS.md +92 -0
  5. sft_cli-0.2.1/LICENSE +21 -0
  6. sft_cli-0.2.1/PKG-INFO +155 -0
  7. sft_cli-0.2.1/README.md +121 -0
  8. sft_cli-0.2.1/docs/plans/2026-03-06-implementation-plan.md +1431 -0
  9. sft_cli-0.2.1/docs/plans/2026-03-06-sft-toolkit-design.md +863 -0
  10. {sft_cli-0.1.0 → sft_cli-0.2.1}/pyproject.toml +23 -1
  11. sft_cli-0.2.1/scripts/build_skill_reference.py +113 -0
  12. sft_cli-0.2.1/scripts/hatch_build_hook.py +49 -0
  13. {sft_cli-0.1.0 → sft_cli-0.2.1}/src/sft/__init__.py +1 -1
  14. sft_cli-0.2.1/src/sft/browser.py +2529 -0
  15. sft_cli-0.2.1/src/sft/cli.py +205 -0
  16. sft_cli-0.2.1/src/sft/commands/__init__.py +0 -0
  17. sft_cli-0.2.1/src/sft/commands/cast.py +103 -0
  18. sft_cli-0.2.1/src/sft/commands/cat.py +90 -0
  19. sft_cli-0.2.1/src/sft/commands/check.py +80 -0
  20. sft_cli-0.2.1/src/sft/commands/convert.py +80 -0
  21. sft_cli-0.2.1/src/sft/commands/diff.py +208 -0
  22. sft_cli-0.2.1/src/sft/commands/info.py +73 -0
  23. sft_cli-0.2.1/src/sft/commands/lora.py +750 -0
  24. sft_cli-0.2.1/src/sft/commands/ls.py +117 -0
  25. sft_cli-0.2.1/src/sft/commands/metadata.py +96 -0
  26. sft_cli-0.2.1/src/sft/commands/rename.py +111 -0
  27. sft_cli-0.2.1/src/sft/commands/skill.py +519 -0
  28. sft_cli-0.2.1/src/sft/commands/slice.py +89 -0
  29. sft_cli-0.2.1/src/sft/commands/split.py +111 -0
  30. sft_cli-0.2.1/src/sft/commands/stat.py +134 -0
  31. sft_cli-0.2.1/src/sft/commands/strip.py +78 -0
  32. sft_cli-0.2.1/src/sft/commands/tree.py +120 -0
  33. sft_cli-0.2.1/src/sft/ops/__init__.py +0 -0
  34. sft_cli-0.2.1/src/sft/ops/cast.py +68 -0
  35. sft_cli-0.2.1/src/sft/ops/cat.py +70 -0
  36. sft_cli-0.2.1/src/sft/ops/check.py +88 -0
  37. sft_cli-0.2.1/src/sft/ops/convert.py +60 -0
  38. sft_cli-0.2.1/src/sft/ops/diff.py +167 -0
  39. sft_cli-0.2.1/src/sft/ops/info.py +73 -0
  40. sft_cli-0.2.1/src/sft/ops/lora/__init__.py +0 -0
  41. sft_cli-0.2.1/src/sft/ops/lora/add.py +131 -0
  42. sft_cli-0.2.1/src/sft/ops/lora/compat.py +79 -0
  43. sft_cli-0.2.1/src/sft/ops/lora/convert.py +286 -0
  44. sft_cli-0.2.1/src/sft/ops/lora/detect.py +189 -0
  45. sft_cli-0.2.1/src/sft/ops/lora/extract.py +115 -0
  46. sft_cli-0.2.1/src/sft/ops/lora/info.py +15 -0
  47. sft_cli-0.2.1/src/sft/ops/lora/merge.py +78 -0
  48. sft_cli-0.2.1/src/sft/ops/lora/resize.py +155 -0
  49. sft_cli-0.2.1/src/sft/ops/lora/stack.py +221 -0
  50. sft_cli-0.2.1/src/sft/ops/lora/svd.py +142 -0
  51. sft_cli-0.2.1/src/sft/ops/ls.py +48 -0
  52. sft_cli-0.2.1/src/sft/ops/metadata.py +44 -0
  53. sft_cli-0.2.1/src/sft/ops/rename.py +65 -0
  54. sft_cli-0.2.1/src/sft/ops/slice.py +86 -0
  55. sft_cli-0.2.1/src/sft/ops/split.py +128 -0
  56. sft_cli-0.2.1/src/sft/ops/stat.py +82 -0
  57. sft_cli-0.2.1/src/sft/ops/tree.py +126 -0
  58. sft_cli-0.2.1/src/sft/skill/REFERENCE.md +586 -0
  59. sft_cli-0.2.1/src/sft/skill/SKILL.md +146 -0
  60. sft_cli-0.2.1/src/sft/utils/__init__.py +0 -0
  61. sft_cli-0.2.1/src/sft/utils/dtypes.py +50 -0
  62. sft_cli-0.2.1/src/sft/utils/formatting.py +59 -0
  63. sft_cli-0.2.1/src/sft/utils/glob.py +73 -0
  64. sft_cli-0.2.1/src/sft/utils/linking.py +277 -0
  65. sft_cli-0.2.1/src/sft/utils/output.py +21 -0
  66. sft_cli-0.2.1/src/sft/utils/tensor_io.py +165 -0
  67. sft_cli-0.2.1/tests/__init__.py +0 -0
  68. sft_cli-0.2.1/tests/conftest.py +134 -0
  69. sft_cli-0.2.1/tests/test_build_hook.py +66 -0
  70. sft_cli-0.2.1/tests/test_cast.py +109 -0
  71. sft_cli-0.2.1/tests/test_cat.py +198 -0
  72. sft_cli-0.2.1/tests/test_check.py +44 -0
  73. sft_cli-0.2.1/tests/test_cli.py +102 -0
  74. sft_cli-0.2.1/tests/test_cli_contract.py +305 -0
  75. sft_cli-0.2.1/tests/test_convert.py +132 -0
  76. sft_cli-0.2.1/tests/test_diff.py +306 -0
  77. sft_cli-0.2.1/tests/test_e2e_subprocess.py +194 -0
  78. sft_cli-0.2.1/tests/test_info.py +59 -0
  79. sft_cli-0.2.1/tests/test_lora_add.py +135 -0
  80. sft_cli-0.2.1/tests/test_lora_compat.py +90 -0
  81. sft_cli-0.2.1/tests/test_lora_convert.py +250 -0
  82. sft_cli-0.2.1/tests/test_lora_detect.py +105 -0
  83. sft_cli-0.2.1/tests/test_lora_extract.py +90 -0
  84. sft_cli-0.2.1/tests/test_lora_info.py +42 -0
  85. sft_cli-0.2.1/tests/test_lora_merge.py +78 -0
  86. sft_cli-0.2.1/tests/test_lora_properties.py +353 -0
  87. sft_cli-0.2.1/tests/test_lora_resize.py +254 -0
  88. sft_cli-0.2.1/tests/test_lora_stack.py +389 -0
  89. sft_cli-0.2.1/tests/test_lora_svd.py +178 -0
  90. sft_cli-0.2.1/tests/test_ls.py +82 -0
  91. sft_cli-0.2.1/tests/test_metadata.py +126 -0
  92. sft_cli-0.2.1/tests/test_perf.py +74 -0
  93. sft_cli-0.2.1/tests/test_pipelines.py +280 -0
  94. sft_cli-0.2.1/tests/test_properties.py +100 -0
  95. sft_cli-0.2.1/tests/test_rename.py +169 -0
  96. sft_cli-0.2.1/tests/test_skill_install.py +299 -0
  97. sft_cli-0.2.1/tests/test_slice.py +105 -0
  98. sft_cli-0.2.1/tests/test_split.py +174 -0
  99. sft_cli-0.2.1/tests/test_stat.py +70 -0
  100. sft_cli-0.2.1/tests/test_strip.py +134 -0
  101. sft_cli-0.2.1/tests/test_tensor_io.py +96 -0
  102. sft_cli-0.2.1/tests/test_tree.py +44 -0
  103. sft_cli-0.2.1/tests/test_tui.py +943 -0
  104. sft_cli-0.2.1/tests/test_utils.py +160 -0
  105. sft_cli-0.2.1/uv.lock +1621 -0
  106. sft_cli-0.1.0/.gitignore +0 -23
  107. sft_cli-0.1.0/PKG-INFO +0 -115
  108. sft_cli-0.1.0/README.md +0 -89
  109. sft_cli-0.1.0/scripts/create_test_file.py +0 -86
  110. sft_cli-0.1.0/src/sft/browser.py +0 -947
  111. sft_cli-0.1.0/src/sft/cli.py +0 -63
  112. sft_cli-0.1.0/uv.lock +0 -504
  113. {sft_cli-0.1.0 → sft_cli-0.2.1}/.pre-commit-config.yaml +0 -0
  114. {sft_cli-0.1.0 → sft_cli-0.2.1}/.python-version +0 -0
  115. {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
- runs-on: ubuntu-latest
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
- python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
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:
@@ -14,7 +14,7 @@ jobs:
14
14
  uses: astral-sh/setup-uv@v4
15
15
 
16
16
  - name: Set up Python
17
- run: uv python install 3.12
17
+ run: uv python install 3.14
18
18
 
19
19
  - name: Build package
20
20
  run: uv build
@@ -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
@@ -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
+ [![PyPI](https://img.shields.io/pypi/v/sft-cli?style=flat-square)](https://pypi.org/project/sft-cli/)
40
+ [![Python](https://img.shields.io/pypi/pyversions/sft-cli?style=flat-square)](https://pypi.org/project/sft-cli/)
41
+ [![CI](https://img.shields.io/github/actions/workflow/status/matanby/sft-cli/ci.yml?branch=main&style=flat-square)](https://github.com/matanby/sft-cli/actions)
42
+ [![License](https://img.shields.io/badge/license-MIT-blue?style=flat-square)](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).
@@ -0,0 +1,121 @@
1
+ # sft
2
+
3
+ > The Swiss army knife for `.safetensors` files.
4
+
5
+ [![PyPI](https://img.shields.io/pypi/v/sft-cli?style=flat-square)](https://pypi.org/project/sft-cli/)
6
+ [![Python](https://img.shields.io/pypi/pyversions/sft-cli?style=flat-square)](https://pypi.org/project/sft-cli/)
7
+ [![CI](https://img.shields.io/github/actions/workflow/status/matanby/sft-cli/ci.yml?branch=main&style=flat-square)](https://github.com/matanby/sft-cli/actions)
8
+ [![License](https://img.shields.io/badge/license-MIT-blue?style=flat-square)](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).