A line-oriented file editor for LLM-assisted coding
Surgical file edits for LLM-assisted coding — insert, replace, delete, move, copy, stream, fields, block-aware targeting, sub-line extraction. No SDK, no dependencies, single binary. Works with any LLM.
go install github.com/amalexico/fedit@latest
LLMs generate great code. Applying it to the right place? Not so much.
Fifteen operations, streaming engine, block-aware targeting, sub-line extraction, CSV/TSV fields, and HCL/Terraform + Nix block mappers.
| Operation | Description |
|---|---|
show | Display lines — entire file, a specific range, a named block (-block/-lang), or a content-anchored range (-match + -endmatchv1.8). Add -raw to strip line numbers for piping. |
insert | Insert content after a given line number |
delete | Delete one or more lines — by line number, or by content range with -match + -endmatchv1.8 |
replacev1.8 | Replace a line range, a named block (-block/-lang), or a content-anchored range (-match + -endmatch) — no line numbers needed |
write | Write or overwrite an entire file |
writerawv1.6 | Write or overwrite a file with no escape expansion — backslashes are literal. Pair with -texthex to eliminate all shell quoting issues. |
writelinesv1.6 | Write lines interactively from stdin — type at the > prompt, Ctrl+Z (Windows) or Ctrl+D (Unix) to finish. |
map | Structural overview — 17 languages including Go, Python, Rust, SQL, HTML, YAML, and more |
find | Find all lines matching a substring |
insertafterRECOMMENDED | Insert content after a matching line or named block (-block/-langv1.7) — no line-number math |
insertbeforeRECOMMENDED | Insert content before a matching line or named block (-block/-langv1.7) — no line-number math |
replaceall | Global find-and-replace across the entire file |
moveNEW | Move a line range to a new position — cut once, paste N times. Destination-overlap rejected with precise error. |
copyNEW | Copy a line range to a new position — snapshot semantics, all N copies are identical clones even when destination overlaps source. |
fieldsNEW | Extract column N from any delimited file (CSV, TSV, colon-separated). Always streaming — no memory limit. Output to stdout for piping. |
-streamNEW | Add to replaceall or find to process multi-GB files line-by-line. Atomic: writes temp file then renames — original untouched on interruption. |
-texthexv1.6 | Decode -text from a hex string (use fwencode to produce). Eliminates all shell quoting issues with special characters. |
-cleanfirstv1.6 | Truncate the file before writing. Combine with insert to overwrite then fill with new content. |
-xv1.6 | Machine-readable output: find returns bare line numbers only; fields suppresses the stats footer. Useful in scripts and agent loops. |
-rawv1.7 | On show: output bare content with no line-number prefix — pipe directly to fwencode or other tools. Essential for block-to-block transfer workflows. |
-extract SPECv1.7 | Sub-line extraction from a matched line. WN = word N · WN[s:c] = chars s–s+c−1 of word N · WN/DELIM/F = field F after splitting word N by DELIM. Eliminates a separate awk call. |
-get REGEXv1.7 | Pre-filter: extract only the portion of the matched line captured by the regex before applying -extract. Pairs with -wdelim CHAR to set a custom word delimiter (default: whitespace). |
-block NAME + -lang LANGv1.7 | Target a named function, class, struct, or resource by name in show, replace, insertbefore, insertafter, move, copy. 10 languages: Go, Python, JS/TS, Rust, Java, C#, Ruby, PHP, HCL/Terraform, Nix. |
-match TEXT + -endmatch TEXTv1.8 | Content-anchored range for show, replace, delete, move, copy. Both anchor lines inclusive. Combine with -nth N to target the Nth occurrence. Eliminates line numbers entirely for block operations. |
-line -N · -line -N: · -line N:+Mv1.8 | Negative and colon line syntax. -line -1 = last line · -line -3: = last 3 lines · -line 5:+2 = lines 5–7 · -line : = EOF (append for insert, last line for show/delete/replace). -end -N also supported. |
-quietv1.8 | Suppress stdout on success — exit code signals result only. Ideal for CI pipelines and agent loops where verify output adds noise. Errors always print regardless. |
Real commands you can copy and run.
# Get a structural overview of any Go file fedit -file server.go -op map -lang go # Find where a function is defined fedit -file server.go -op find -match "handleCreate" # Insert code after a specific match (no line numbers needed!) fedit -file server.go -op insertafter -match 'log.Info().Msg("created")' -textfile patch.txt # Show lines 100-150 fedit -file server.go -op show -line 100 -end 150 # Replace lines 50-52 with new content fedit -file server.go -op replace -line 50 -end 52 -textfile patch.txt # Move a function block before another (reorder without copy-paste) fedit -file routes.go -op move -match "func OldHelper(" -end 45 -beforematch "func NewHelper(" -v # Copy a config block and duplicate it 3 times at a new location fedit -file values.yaml -op copy -line 50 -end 65 -aftermatch "# staging" -times 3 -v # Extract column 2 from a TSV file (v1.4+) fedit -file data.tsv -op fields -col 2 # Streaming replace on a multi-GB log without loading into memory (v1.4+) # Write a Windows path literally -- no backslash escaping needed (v1.6+) fedit -file config.ini -op writeraw -text "basedir=C:\Program Files\App" # Get bare line numbers for scripting -- no context noise (v1.6+) fedit -file main.go -op find -match "TODO" -x 2>/dev/null # Truncate then insert fresh content (v1.6+) fedit -file output.txt -op insert -line 0 -cleanfirst -text "# regenerated" fedit -file server.log -op replaceall -match "10.0.0.1" -text "10.0.0.2" -stream # Move a Terraform resource before another -- no line numbers (v1.5+) fedit -file main.tf -op move -block 'resource "aws_instance" "web"' -beforeblock 'resource "aws_s3_bucket" "data"' -lang hcl -v # Block-aware replace -- swap a whole function body, no line numbers needed (v1.7+) fedit -file server.go -op replace -block "handleLogin" -lang go -texthex $hex -v # Block-aware insertafter -- add a method right after a named function (v1.7+) fedit -file server.go -op insertafter -block "setupRoutes" -lang go -texthex $hex -v # Pipe a function block to fwencode for transfer to another file (v1.7+) $hex = fedit -file src.go -op show -block "OldFunc" -lang go -raw 2>$null | fwencode fedit -file dst.go -op replace -block "NewFunc" -lang go -texthex $hex -v # Extract word 3 from each matching line -- no awk needed (v1.7+) fedit -file access.log -op find -match "ERROR" -extract W3 # Extract field 2 split by colon from a matched line (v1.7+) fedit -file config.txt -op find -match "server_addr" -extract W1/:/2 # Extract chars 1-4 from word 2 of a specific line (v1.7+) fedit -file version.go -op find -match "Version =" -extract W3[1:4] # Show a content-anchored range by match + endmatch (v1.8+) fedit -file server.go -op show -match "func handleLogin(" -endmatch "^}" # Replace a content-anchored block -- no line numbers needed (v1.8+) fedit -file deploy.yaml -op replace -match "# staging config" -endmatch "# end staging" -textfile new_staging.yaml -v # Delete a content-anchored block by match + endmatch (v1.8+) fedit -file main.go -op delete -match "// BEGIN deprecated" -endmatch "// END deprecated" -v # Show last 5 lines of a file (v1.8+) fedit -file app.log -op show -line -5: # Delete from line 8 through 2 more (lines 8-10) -- colon range (v1.8+) fedit -file config.yaml -op delete -line 8:+2 -v # Silent mutation for CI -- exit code only, no output on success (v1.8+) fedit -file version.go -op replaceall -match "v1.7.0" -text "v1.8.0" -quiet
Pure Go, stdlib only. Single binary, runs anywhere Go runs.
insertafter and insertbefore find the right spot by content, not line numbers.
Structural overview for 17 languages — Go, Python, Rust, Java, SQL, HTML, CSS, YAML, TOML, and more.
Native Model Context Protocol support. AI assistants call fedit directly — no shell wrappers needed.
Perfect for automation, CI pipelines, or any workflow where you need deterministic file edits.
Move or copy a block of lines anywhere in a file. Snapshot semantics, overlap-safe, repeatable with -times N.
Move Terraform resources, Nix attrs, and more by name. -lang hcl / -lang nix with -block — no line numbers needed.
Process multi-GB logs and datasets line-by-line with atomic integrity. Add -stream to any replaceall. Original file untouched on interruption.
Free forever. Use it in personal projects, commercial products, anywhere.
Target named functions, classes, structs, and resources by name with -block/-lang. Works on replace, insertbefore, insertafter — no line numbers ever needed.
Pull word N, a char slice, or a delimited field from any matching line with -extract. Replaces a separate awk call and composes naturally with -get and -wdelim.
Use -match + -endmatch to target a range by content on show, replace, delete, move, and copy. No line numbers, no drift. Combine with -nth for the Nth occurrence.
We tested 3 frontier models on 7 realistic editing tasks. Here is what we found.
Each model — Claude Sonnet 4.6, ChatGPT GPT-4o, and Gemini 2.5 Pro — ran the same 7 editing tasks against files of 565–1206 lines. Each task was run twice: once asking the model to output the whole rewritten file ("raw"), once asking it to output fedit commands only ("fedit"). All outputs were diffed byte-for-byte against ground truth.
7/7 raw PASS
4/7 fedit PASS · 3 PARTIAL
1/7 raw (6 truncated)
2/7 fedit PASS · 1 PARTIAL · 4 FAIL
7/7 raw PASS*
1/7 fedit PASS · 6 FAIL
PASS+ optimal one-liner · PASS correct · PASS* correct content, formatting artifact · PARTIAL correct intent, fragile · FAIL wrong output
| Test | File | Task | CL raw | CL fedit | GPT raw | GPT fedit | GM raw | GM fedit |
|---|---|---|---|---|---|---|---|---|
| T1 | processor.go (575 L) | Insert method after struct method | PASS | PARTIAL | FAIL | PARTIAL | PASS | FAIL |
| T2 | config.yaml (1059 L) | Replace 24-line deployment block | PASS | PASS | FAIL | FAIL | PASS | FAIL |
| T3 | styles.css (565 L) | Find and delete CSS rule block | PASS | PASS | PASS* | FAIL | PASS* | FAIL |
| T4 | system.go (980 L) | Global rename (36 occurrences) | PASS | PASS+ | FAIL | PASS+ | PASS* | PASS+ |
| T5 | analytics.py (682 L) | 3-step chain (insert + delete + replace) | PASS | PARTIAL | FAIL | FAIL | PASS | FAIL |
| T6 | dashboard.html (891 L) | Insert before 3rd match (-nth) |
PASS | PARTIAL | FAIL | PASS+ | PASS | FAIL |
| T7 | engine.go (1196 L) | Map + targeted insert after method | PASS | PASS | FAIL | FAIL | PASS | FAIL |
6 of 7 raw tests truncated. T6 inserted the literal placeholder [... TRUNCATED FOR BREVITY ...] into otherwise valid HTML — output looked structurally complete but contained placeholder strings. T7 truncated 1206 lines down to 166.
And it gets worse with chain length. Gemini drifted by 36–56 lines on T2, 45 on T3, then 73 lines on T5 (3-step chain). Only Claude produced runnable line-numbered commands consistently.
Gemini failed every fedit test that required line numbers but PASSED T4 with replaceall -match. Same model, same task complexity, dramatically different reliability. The bottleneck is counting, not understanding.
insertafter on a func decl matches the OPENING lineModels repeatedly tried insertafter -match "func MyFunc()" to add code AFTER the function ended — instead it inserted INSIDE the body. Workaround: insertbefore the next structural element. Confirmed in T1 and T7.
T6: Claude used insert -line 30 intending to add a banner BEFORE line 30. But insert -line N adds AFTER line N. Even the strongest model gets direction wrong about 1 in 6 single-step ops when reaching for line numbers.
-match is single-line only — by designChatGPT (T3) tried passing a multi-line CSS block as -match with literal \n escapes. fedit refuses to interpret escapes — zero matches, no edit. Safe failure beats a fragile multi-line pattern that could mismatch.
Chat UIs ate __name__, __init__, and stripped Python/CSS indentation. The underlying files (downloaded directly) were correct. Use the model's "copy code" button or download links — never select-and-copy from rendered output.
For T4, Claude, ChatGPT, and Gemini independently produced fedit -op replaceall -match "FetchUser" -text "GetAccount". When the right tool is obvious, models reach for it. fedit's design surface makes the right tool obvious for content-driven edits.
Prefer content-matching operations over line-number operations when generating fedit commands from an LLM:
insertbefore -match "next anchor" instead of insert -line Nreplaceall -match "old" -text "new" instead of replace -line N -end Mfind -match and show to confirm line numbers before any line-numbered opFor best results, give the LLM an MCP connection to fedit (see MCP Server Mode below). This eliminates the line-number hallucination class entirely — the path Claude consistently took when recon was available.
Methodology: each test in a fresh chat with the original file uploaded; identical prompts across models (only "raw" vs "fedit" framing differed); outputs diffed byte-for-byte against ground truth via Compare-Object. PASS* indicates byte-difference from indentation-stripping in chat UI render, with content structurally correct.
Full results, ground truth files, prompts, and the 7 test corpus files: github.com/amalexico/fedit#llm-benchmark
fedit speaks the Model Context Protocol natively. AI assistants use it as a tool — no shell, no wrappers.
# Start the MCP server (stdin/stdout JSON-RPC 2.0) fedit mcp
One command. No configuration. No account required.
fedit is built by the makers of Amalex Handler — a self-hosted
file transfer and sync platform. Encrypted, auditable, fully under your control.