This release is a pre-release and may not be stable for production use.
llm-tools-edit
LLM plugin providing tools for editing files. Gives LLMs the ability to create, view, and modify files using three interchangeable backends.
Installation
Install this plugin in the same environment as LLM.
llm install llm-tools-edit
Backends
This plugin provides three Toolbox backends. Each provides the same five tools:
| Tool | Description |
|---|---|
view |
View file contents with line numbers, or list a directory |
str_replace |
Replace an exact string in a file (must appear exactly once) |
create |
Create a new file (errors if it already exists) |
insert |
Insert text after a given line number |
list_files |
List all files |
Filesystem
Edit real files on disk, scoped to a specific directory. All paths are relative to the root directory, and path traversal outside the root is blocked.
llm -T 'Filesystem("/tmp/myproject")' "Create a Python hello world app"
The path argument is the root directory. All file operations are restricted to this directory and its subdirectories.
MemoryFS
An in-memory virtual filesystem. Files exist only in memory and are discarded when the process exits. State persists across tool calls within a single conversation, so an LLM agent can create files and iteratively edit them without touching disk.
llm -T MemoryFS "Create a Flask app with two routes"
No arguments required. Paths use absolute format (e.g. /src/main.py).
SQLiteFS
Store files in a SQLite database table. The table is created automatically if it doesn't exist. State persists across sessions — you can come back later and continue editing.
llm -T 'SQLiteFS(db_path="demo.db", table_name="files")' "Create a hello world app"
Arguments:
db_path— path to the SQLite database filetable_name— name of the table to store files in (schema:path TEXT PRIMARY KEY, content TEXT)
Output truncation
Large files and single-line blobs (e.g. minified JS) can overwhelm a model's context window. All three backends accept optional max_lines and max_chars keyword arguments that truncate view output and append a message telling the model how to see more:
llm -T 'Filesystem("/tmp/myproject", max_lines=200, max_chars=20000)' "Fix the bug in app.py"
fs = MemoryFS(max_lines=200, max_chars=20000)
sqlfs = SQLiteFS("demo.db", "files", max_lines=200, max_chars=20000)
max_lines— truncate after this many lines. A footer like[Showing lines 1-200 of 5,432 total. Use view_range to see more.]is appended.max_chars— truncate after this many characters of formatted output. Catches the degenerate case of a single 2 MB line with no newlines. A footer like[Truncated: output exceeded 20,000 character limit. ...]is appended.
Both limits are off (None) by default. When both are set, whichever triggers first takes effect — and both footers appear if both trigger. The model can always use view_range to read specific sections of a large file without hitting the limits.
Tool details
view
View a file's contents with line numbers, or list a directory's entries.
The optional view_range parameter accepts a comma-separated start and end line number (1-indexed). Use -1 for end to mean end-of-file.
Examples: view_range="1,10" shows lines 1-10, view_range="5,-1" shows line 5 to end.
str_replace
Find and replace an exact string in a file. The old_str must appear exactly once in the file — if it appears zero times or more than once, the tool raises an error. This ensures replacements are unambiguous.
create
Create a new file with the given content. Raises an error if the file already exists (to prevent accidental overwrites). For Filesystem, parent directories are created automatically if needed.
insert
Insert text after a given line number. Use insert_line=0 to insert at the beginning of the file.
Library functions
The text-editing primitives used internally by the Toolbox classes are also available as standalone functions. You can import and use these directly in your own code.
view_lines(content, view_range="", *, max_lines=None, max_chars=None)
Format a string as numbered lines. Returns each line prefixed by its 1-indexed line number and a tab.
from llm_tools_edit.lib import view_lines
view_lines("alpha\nbeta\ngamma\n")
# '1:\talpha\n2:\tbeta\n3:\tgamma'
view_lines("alpha\nbeta\ngamma\n", view_range="2,3")
# '2:\tbeta\n3:\tgamma'
view_lines("alpha\nbeta\ngamma\n", view_range="2,-1")
# '2:\tbeta\n3:\tgamma'
The view_range parameter accepts "start,end" (1-indexed). Use -1 for end to mean end-of-file.
The optional max_lines and max_chars parameters truncate output and append a descriptive footer when limits are exceeded:
content = "".join(f"line {i}\n" for i in range(10_000))
view_lines(content, max_lines=3)
# '1:\tline 0\n2:\tline 1\n3:\tline 2\n[Showing lines 1-3 of 10000 total. Use view_range to see more.]'
view_lines("x" * 1_000_000, max_chars=50)
# '1:\txxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n[Truncated: ...]'
str_replace_content(content, path, old_str, new_str)
Replace exactly one occurrence of old_str in content. Raises ValueError if the string is not found or appears more than once, ensuring every replacement is unambiguous. The path argument is used only in error messages.
from llm_tools_edit.lib import str_replace_content
str_replace_content("hello world", "f.py", "hello", "goodbye")
# 'goodbye world'
str_replace_content("aaa\naaa", "f.py", "aaa", "bbb")
# ValueError: old_str appears 2 times in f.py; must be unique
insert_content(content, insert_line, insert_text)
Insert text after a given line number. Use insert_line=0 to insert at the beginning.
from llm_tools_edit.lib import insert_content
insert_content("line1\nline2\n", 1, "new\n")
# 'line1\nnew\nline2\n'
insert_content("line1\nline2\n", 0, "header\n")
# 'header\nline1\nline2\n'
list_dir_from_paths(paths, prefix)
Given a flat list of absolute file paths and a directory prefix, return the set of immediate children — files as bare names, subdirectories with a trailing /. Returns None if nothing matches.
from llm_tools_edit.lib import list_dir_from_paths
list_dir_from_paths(["/src/main.py", "/src/utils.py", "/README.md"], "/")
# {'README.md', 'src/'}
list_dir_from_paths(["/src/main.py", "/src/lib/helper.py"], "/src")
# {'main.py', 'lib/'}
list_dir_from_paths(["/src/main.py"], "/other")
# None
Python API
import llm
from llm_tools_edit import Filesystem, MemoryFS, SQLiteFS
model = llm.get_model("gpt-4.1-mini")
# Real filesystem — scoped to a directory
fs = Filesystem("/tmp/myproject")
result = model.chain(
"Create a hello world Python app",
tools=[fs],
).text()
# In-memory filesystem
memfs = MemoryFS()
result = model.chain(
"Create a Flask app",
tools=[memfs],
).text()
# SQLite-backed filesystem
sqlfs = SQLiteFS("demo.db", "files")
result = model.chain(
"Create a hello world app",
tools=[sqlfs],
).text()
For multi-turn conversations where state persists across prompts:
memfs = MemoryFS()
conversation = model.conversation(tools=[memfs])
conversation.chain("Create /app.py with a Flask hello-world app")
conversation.chain("Add a /goodbye route to the app")
conversation.chain("View the final version of /app.py")
Development
To set up this plugin locally, first checkout the code. Then create a new virtual environment:
cd llm-tools-edit
python -m venv venv
source venv/bin/activate
Now install the dependencies and test dependencies:
llm install -e '.[test]'
To run the tests:
python -m pytest
Release files for llm-tools-edit 0.1a0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| llm_tools_edit-0.1a0.tar.gz | 19.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| llm_tools_edit-0.1a0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 41.0 kB
Release files / llm_tools_edit-0.1a0.tar.gz
| Download URL | llm_tools_edit-0.1a0.tar.gz |
|---|---|
| Size | 19.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d0420a3d6a574332f3ab542d6df0fc8fe7877760970e02e3be429e2cc71edc00
|
|
BLAKE2b-256 checksum How to use checksums |
140fa48eff0b22904b60f1fa6fbe4d896446adb3741d0a8e9c45ec85fa61ddf7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 9, 2026.
Transparency logRelease files / llm_tools_edit-0.1a0-py3-none-any.whl
| Download URL | llm_tools_edit-0.1a0-py3-none-any.whl |
|---|---|
| Size | 21.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
618bf301ed9b2f7a198b9e56f0042766ac67c34ccd3a73f8d1c92d97ed91c3f2
|
|
BLAKE2b-256 checksum How to use checksums |
c0c03cefa074b9215e83a332c4e8b41d038546cdf9883efb479335d9823beae6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 9, 2026.
Transparency log