FREE & OPEN SOURCE

fedit

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.

View on GitHub Download Binary
go install github.com/amalexico/fedit@latest

The Problem

LLMs generate great code. Applying it to the right place? Not so much.

Without fedit

  • LLM says "insert at line 47" but file has shifted
  • Patch applies to wrong location, breaks code
  • Manual copy-paste loses context
  • sed/awk too fragile for multi-line edits
  • No way to verify edit landed correctly

With fedit

  • Content-based matching finds the right spot
  • insertafter/insertbefore eliminates line drift
  • map gives structural overview for context
  • Deterministic, scriptable, auditable
  • find verifies edits after applying

All Operations

Fifteen operations, streaming engine, block-aware targeting, sub-line extraction, CSV/TSV fields, and HCL/Terraform + Nix block mappers.

OperationDescription
showDisplay 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.
insertInsert content after a given line number
deleteDelete one or more lines — by line number, or by content range with -match + -endmatchv1.8
replacev1.8Replace a line range, a named block (-block/-lang), or a content-anchored range (-match + -endmatch) — no line numbers needed
writeWrite or overwrite an entire file
writerawv1.6Write or overwrite a file with no escape expansion — backslashes are literal. Pair with -texthex to eliminate all shell quoting issues.
writelinesv1.6Write lines interactively from stdin — type at the > prompt, Ctrl+Z (Windows) or Ctrl+D (Unix) to finish.
mapStructural overview — 17 languages including Go, Python, Rust, SQL, HTML, YAML, and more
findFind all lines matching a substring
insertafterRECOMMENDEDInsert content after a matching line or named block (-block/-langv1.7) — no line-number math
insertbeforeRECOMMENDEDInsert content before a matching line or named block (-block/-langv1.7) — no line-number math
replaceallGlobal find-and-replace across the entire file
moveNEWMove a line range to a new position — cut once, paste N times. Destination-overlap rejected with precise error.
copyNEWCopy a line range to a new position — snapshot semantics, all N copies are identical clones even when destination overlaps source.
fieldsNEWExtract column N from any delimited file (CSV, TSV, colon-separated). Always streaming — no memory limit. Output to stdout for piping.
-streamNEWAdd to replaceall or find to process multi-GB files line-by-line. Atomic: writes temp file then renames — original untouched on interruption.
-texthexv1.6Decode -text from a hex string (use fwencode to produce). Eliminates all shell quoting issues with special characters.
-cleanfirstv1.6Truncate the file before writing. Combine with insert to overwrite then fill with new content.
-xv1.6Machine-readable output: find returns bare line numbers only; fields suppresses the stats footer. Useful in scripts and agent loops.
-rawv1.7On 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.7Sub-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.7Pre-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.7Target 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.8Content-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.8Negative 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.8Suppress stdout on success — exit code signals result only. Ideal for CI pipelines and agent loops where verify output adds noise. Errors always print regardless.

See It in Action

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

Why fedit?

Zero Dependencies

Pure Go, stdlib only. Single binary, runs anywhere Go runs.

🎯

Content-Based Matching

insertafter and insertbefore find the right spot by content, not line numbers.

🗺

Language-Aware Map

Structural overview for 17 languages — Go, Python, Rust, Java, SQL, HTML, CSS, YAML, TOML, and more.

🤖

MCP Server Built In

Native Model Context Protocol support. AI assistants call fedit directly — no shell wrappers needed.

🛠

Scriptable

Perfect for automation, CI pipelines, or any workflow where you need deterministic file edits.

🔀

Block Move & Copy

Move or copy a block of lines anywhere in a file. Snapshot semantics, overlap-safe, repeatable with -times N.

🛠

IaC Block Mapper

Move Terraform resources, Nix attrs, and more by name. -lang hcl / -lang nix with -block — no line numbers needed.

Stream Engine

Process multi-GB logs and datasets line-by-line with atomic integrity. Add -stream to any replaceall. Original file untouched on interruption.

👍

MIT Licensed

Free forever. Use it in personal projects, commercial products, anywhere.

🔧

Block-Aware Editing

Target named functions, classes, structs, and resources by name with -block/-lang. Works on replace, insertbefore, insertafter — no line numbers ever needed.

✂️

Sub-Line Extraction

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.

🎯

Anchor-to-Anchor Ranges

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.

How LLMs Use fedit

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.

🏆

Claude

7/7 raw PASS
4/7 fedit PASS · 3 PARTIAL

⚠️

ChatGPT

1/7 raw (6 truncated)
2/7 fedit PASS · 1 PARTIAL · 4 FAIL

📊

Gemini

7/7 raw PASS*
1/7 fedit PASS · 6 FAIL

Per-Test Results

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

Eight Key Findings

1. ChatGPT cannot output large files

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.

2. LLMs hallucinate line numbers

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.

3. Content-matching ops are immune

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.

4. insertafter on a func decl matches the OPENING line

Models 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.

5. Even Claude flubs direction

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.

6. -match is single-line only — by design

ChatGPT (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.

7. Markdown rendering is a hidden adversary

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.

8. Three models converged on the same one-liner

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.

Recommendations for LLM Workflows

Prefer content-matching operations over line-number operations when generating fedit commands from an LLM:

For 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

MCP Server Mode

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

Claude Desktop

Cursor & Other MCP Clients

  • Add fedit to .cursor/mcp.json
  • All 14 operations available as MCP tools
  • Works with any MCP-compatible editor or agent

Get started in 10 seconds

One command. No configuration. No account required.

View on GitHub Download Binary

fedit is built by the makers of Amalex Handler — a self-hosted
file transfer and sync platform. Encrypted, auditable, fully under your control.

Learn about Amalex Handler →