Structural text editing for LLM agents (and humans who think like them).
sect replaces the sed/awk/cat reflexes — and the read-whole-file,
edit, re-read-whole-file loop — with verbs designed around how editing
actually fails: silent zero-match edits, whitespace-mismatched anchors,
ambiguous targets, and stale views of a changed file.
$ sect outline brief.md
brief.md @ ca59ab7d · markdown · 32 lines · 97 words
1 Q3 Strategy Brief L1 93w
1.1 Context L5 10w
1.2 Delivery Options L9 52w
1.2.1 Reallocate current resources L13 15w
...
$ sect replace brief.md "Hire externally" --old "Slower" --new "Pricier"
✓ replaced 1 occurrence in § Q3 Strategy Brief > Delivery Options > Hire externally (L20)
brief.md @ 7f4119a4 (was @ ca59ab7d)
--- a/brief.md
+++ b/brief.md
...
- Edits fail loudly and helpfully.
sedsilently succeeds on zero matches. A failedsect replacereports the closest candidate and how it differs ("L7, matches except for whitespace"). - Mutations return state. Every edit prints the diff, the new content hash, and (for structural edits) the post-edit outline — a successful edit never forces a re-read.
- Structure is addressable. Markdown sections are addressed by heading
path (
"Delivery Options > Realloc", prefix-matched) or ordinal (1.2.1). Ambiguity is an error listing candidates; sect never guesses. - Scoping restores uniqueness. Scoping a replace to a section keeps search strings short instead of padded with fragile context.
- Time travel without git. Every read and write snapshots the file
(content-addressed, local).
sect changes FILE --since HASHshows what changed since you last looked;sect restore FILE --to HASHis undo. - Text stays canonical. Mutations are byte-span splices on the original text — never a parse/re-serialize round trip. Your formatting, your bytes. The one exception: moving a section re-levels its heading markers, because not doing so corrupts the document.
Works on any text file: view, find, replace, lines, append,
changes, restore are format-blind. Markdown files additionally get
outline, read, move, rename, delete and section scoping. The
architecture (a Format provider trait: parse to a spanned node tree, plan
ops as splices, validate post-edit) is built for more providers — YAML,
JSON, tree-sitter code, XML — without changing the verbs. See
DESIGN.md.
cargo install --path crates/cli # from a checkoutAdd one line to your agent instructions (CLAUDE.md or equivalent):
For reading and editing text/markdown files, prefer
sectover sed/awk/cat and over re-reading files after edits. Runsect agent-helpfor usage.
The cheat sheet ships inside the binary (sect agent-help), so instructions
never drift from the installed version. Every command takes --json for a
stable machine-readable schema.
| Read | Edit |
|---|---|
outline FILE [--under PATH] |
replace FILE [PATH] (stdin blocks, --old/--new) |
read FILE [PATH] |
insert FILE --before|--after PATH |
view FILE 40:80 |
append FILE [PATH] |
find FILE TEXT [--in PATH] |
move FILE PATH --into|--before|--after DEST |
todos FILE |
rename FILE PATH NEW [--update-refs] |
changes FILE --since HASH |
delete FILE PATH · lines FILE … · restore FILE --to HASH |
All mutations: atomic writes, --dry-run previews, --expect HASH
optimistic locking against concurrent edits.
Replace blocks use the conflict-marker format on stdin:
sect replace brief.md "Delivery Options" <<'EOF'
<<<<<<<
text to find
=======
replacement
>>>>>>>
EOFMultiple blocks per call apply atomically (all or nothing). Content containing 7-char conflict markers? Use 8+ char markers, as in git.
Snapshots are copies of your file contents stored in
~/.local/state/sect/ (pruned after ~30 days). Set SECT_NO_SNAPSHOT=1 to
disable, or SECT_STATE_DIR to relocate.
v0.1: markdown + flat providers, full verb surface, snapshot store.
Planned: YAML provider (hardest indentation case, validates the provider
seam), tree-sitter code outlines/scoping, npm distribution via napi/WASM,
wrap verb, reference-style link rewriting.