LLM plugin providing tools for editing files
Project description
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
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file llm_tools_edit-0.1a0.tar.gz.
File metadata
- Download URL: llm_tools_edit-0.1a0.tar.gz
- Upload date:
- Size: 19.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d0420a3d6a574332f3ab542d6df0fc8fe7877760970e02e3be429e2cc71edc00
|
|
| MD5 |
c20a7cb8a588a4f0a93487283005598c
|
|
| BLAKE2b-256 |
140fa48eff0b22904b60f1fa6fbe4d896446adb3741d0a8e9c45ec85fa61ddf7
|
Provenance
The following attestation bundles were made for llm_tools_edit-0.1a0.tar.gz:
Publisher:
publish.yml on simonw/llm-tools-edit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
llm_tools_edit-0.1a0.tar.gz -
Subject digest:
d0420a3d6a574332f3ab542d6df0fc8fe7877760970e02e3be429e2cc71edc00 - Sigstore transparency entry: 1066923817
- Sigstore integration time:
-
Permalink:
simonw/llm-tools-edit@c3a6e167f9e6e0b090e385b9594e1fab1a6bbf85 -
Branch / Tag:
refs/tags/0.1a0 - Owner: https://github.com/simonw
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c3a6e167f9e6e0b090e385b9594e1fab1a6bbf85 -
Trigger Event:
release
-
Statement type:
File details
Details for the file llm_tools_edit-0.1a0-py3-none-any.whl.
File metadata
- Download URL: llm_tools_edit-0.1a0-py3-none-any.whl
- Upload date:
- Size: 21.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
618bf301ed9b2f7a198b9e56f0042766ac67c34ccd3a73f8d1c92d97ed91c3f2
|
|
| MD5 |
c7c7740d6c048df94f69581291a4d92c
|
|
| BLAKE2b-256 |
c0c03cefa074b9215e83a332c4e8b41d038546cdf9883efb479335d9823beae6
|
Provenance
The following attestation bundles were made for llm_tools_edit-0.1a0-py3-none-any.whl:
Publisher:
publish.yml on simonw/llm-tools-edit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
llm_tools_edit-0.1a0-py3-none-any.whl -
Subject digest:
618bf301ed9b2f7a198b9e56f0042766ac67c34ccd3a73f8d1c92d97ed91c3f2 - Sigstore transparency entry: 1066923843
- Sigstore integration time:
-
Permalink:
simonw/llm-tools-edit@c3a6e167f9e6e0b090e385b9594e1fab1a6bbf85 -
Branch / Tag:
refs/tags/0.1a0 - Owner: https://github.com/simonw
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c3a6e167f9e6e0b090e385b9594e1fab1a6bbf85 -
Trigger Event:
release
-
Statement type: