Edit config files without reflowing them.
edit, meets edict. A lossless, format-preserving config editor for
JSONC/JSON5, INI, TOML, YAML, KDL, and sectionless
key-value files (.env, .properties). It edits with a jq-flavored expression language
and a sed-flavored execution model, changing only the bytes you target -
comments, indentation, quoting, and trailing commas in every untouched region
survive byte-for-byte.
# query - reads like jq
edikt '.compilerOptions.strict' tsconfig.json
# edit in place - comments, indent, comma style all preserved
edikt -i '.compilerOptions.target = "ES2022"' tsconfig.json
# edit YAML in place - anchors, flow style, and comments survive
edikt -i '.services.web.replicas = 3' compose.yaml
# compute, not just place
edikt -i '.version |= . + "-dev"' package.jsonc
# stream-first, like sed
cat settings.jsonc | edikt 'del(.telemetry) | .theme = "dark"'
# script from a file
edikt -f release.edk -i config.jsonc
# convert, where feasible - comments carried across, in the target's syntax
edikt -T yaml tsconfig.jsoncWhat it does: edits commented, hand-formatted config - settings.json,
tsconfig.json, devcontainer.json, compose.yaml, Cargo.toml - and writes
back a file that is byte-identical except for the one value you changed.
Comments, trailing commas, indentation, and quoting all survive. Query it like
jq; convert between formats with -T.
brew install jhheider/tap/edikt # Homebrew
cargo install edikt # Cargo (crates.io)
pkgx install edikt # pkgx (builds from source)Or grab a prebuilt binary (Linux, macOS, and Windows; x86_64 and arm64) from the releases page.
edikt is happy editing the config it ships in. Here is the whole version bump
for a release - a Cargo.toml per crate plus the internal dependency pins in
the workspace root - done losslessly:
# each crate's own version
for c in crates/*/Cargo.toml; do
edikt -i '.package.version = "0.2.0"' "$c"
done
# the [workspace.dependencies] pins (inline tables), chained with -e
edikt -i \
-e '.workspace.dependencies."edikt-core".version = "0.2.0"' \
-e '.workspace.dependencies."edikt-syntax".version = "0.2.0"' \
Cargo.tomlEach edit moves exactly one version = "...". The # Internal crates comment
above the dependency table, the path = "crates/edikt-core" sitting next to
each version, the brace-and-space style of every inline table, and every other
byte come back unchanged - the kind of diff a reviewer reads at a glance.
One quoting note: a key containing - is written ."edikt-core" (or
.["edikt-core"]), because a bare .edikt-core would read the - as
subtraction. edikt says so with a clear error rather than guessing.
Honestly, a weekend project - one that started with a real itch. The problem crystallized while reading "Respectful" YAML Patching in Rust, which surveys the Rust libraries for patching YAML and lands on the gap in one line: none of them preserve both the formatting and the comments. That's the exact thing I kept wanting - surgically change one value (or one comment) and leave every other byte, comment, and blank line alone - and not just for YAML but for the whole pile of config formats a project accumulates.
The good tools each own their corner: jq and yq for querying, taplo and
prettier for formatting, and the excellent toml_edit and kdl-rs crates for
lossless edits (edikt is built on those last two). edikt isn't trying to
replace them - it's the piece I couldn't find off the shelf: one jq-flavored
tool that edits and queries and converts across JSONC, INI, TOML, YAML,
KDL, and .env, touching only the bytes you point at. If your need is
single-format, reach for the specialist; if it's "the same surgical edit, across
all of these," that's the gap this fills.
Status: v0.2.0 - seven formats with lossless in-place edit, query, and
conversion (comments carried across), on crates.io, Homebrew, and pkgx. See
CLAUDE.md for the build contract.
- Exit codes are sed-shaped:
0= success - a query that matches nothing is a silent no-op (safe underset -e);2= parse or evaluation error. For presence tests,--exit-statusopts into jq's1on zero matches; for defaults, use//:edikt '.maybe.key // "fallback"' f.yaml. - The expression language is deliberately capped in v1. jq's navigation,
mutation, arithmetic,
//defaults, and a curated builtin registry (including regextest/match/capture/sub/gsub,split/join) are in; variables (as $x),if/then,reduce, and user-defined functions are not (yet). - Many files, sed-style:
edikt -i '.v = 9' a.json b.json(let the shell glob:edikt -i 'del(.telemetry)' config/*.jsonc). Queries over several files concatenate results in order. .envis flat and string-valued - no arrays or nesting, ever - but string computation on values works fine:edikt -i '.VERSION |= sub("^v"; "")' .env.
Licensed under either of Apache License, Version 2.0 or MIT license, at your option.