Twig ๐ฟ
Inspect. Navigate. Understand. A local terminal explorer for JSON, YAML, and JSON-based HAR files, written in Rust.
Explore nested data with Miller columns, search keys and values, jump to paths, and inspect values without a browser. The TUI never edits your input. Separate CLI modes format data and repair JSON.
Website ยท Guide ยท Releases ยท Contributing
Installation
Linux and macOS
curl -fsSL https://twig.wtf/install.sh | bash
The Bash installer downloads the latest published release, requires a matching SHA-256 checksum, and installs to ~/.local/bin. It supports sha256sum or macOS's shasum -a 256. Add the destination to your PATH if needed.
To inspect the script first or choose a version/directory:
curl -fsSL https://twig.wtf/install.sh -o install.sh
bash install.sh --help
bash install.sh --version v3.1.0 --to "$HOME/.local/bin" --yes
Use --method build to compile the selected release with Cargo; it honors the same destination. Interactive invocations ask before installing unless --yes is supplied. Piped invocations run noninteractively. Re-running upgrades an existing installation through an atomic executable replacement.
Manual download and Windows
Download twig-<target>.tar.gz and its separate .tar.gz.sha256 file from Releases.
| OS | Targets |
|---|---|
| Linux (GNU libc) | x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu |
| macOS | x86_64-apple-darwin, aarch64-apple-darwin |
| Windows | x86_64-pc-windows-msvc |
Verify the archive with sha256sum -c <checksum-file> (Linux), shasum -a 256 -c <checksum-file> (macOS), or compare PowerShell's Get-FileHash <archive> -Algorithm SHA256 against the checksum file (Windows). Extract it with tar -xzf <archive>. Archives contain twig/twig.exe and LICENSE. Put the executable on PATH.
These are native executables, not universal static binaries. Linux builds use GNU libc; macOS targets a deployment minimum of 11.0. Release .build.txt assets record the source commit, compiler, size, and available linkage diagnostics. Clipboard support depends on the host desktop; SSH sessions may not provide it. Binaries are not OS code-signed/notarized. Release archives have checksums and GitHub build provenance attestations.
Build from source
Requires Rust 1.88 or newer, Cargo, and a C compiler/linker for bundled SQLite.
git clone https://github.com/workdone0/twig.git
cd twig
cargo build --release --locked
./target/release/twig --help
Or install directly from the Git release:
cargo install --locked --git https://github.com/workdone0/twig --tag v3.1.0 twig
Use the Git source explicitly: the twig package on crates.io is unrelated. Python is not needed to run the application.
Usage
twig samples/cloud_infrastructure.json
twig samples/k8s_manifest.yaml
twig samples/browser_navigation.har
# Format JSON to stdout or an output file
twig --print data.json --indent 4
twig --print data.json --indent 4 -o formatted.json
# Repair JSON, then format the repaired value
twig --fix --print broken.json -o repaired.json
# Validate/load without a terminal; force parsing for a cold benchmark
twig --check --rebuild-db data.json
# Keep no persistent cache
twig --no-cache secrets.json
# JSON stdin is available for non-interactive modes
cat response.json | twig --print -
Output is plain JSON/YAML, suitable for piping. -o writes atomically after successful parsing and can replace an existing file, including the input. Prefer a separate output when reviewing repairs. Never redirect stdout to the input path: the shell truncates that file before Twig reads it.
CLI reference
| Option | Behavior |
|---|---|
<FILE> | Positional JSON, YAML, or HAR path; - means JSON stdin in CLI modes |
-p, --print | Format JSON or a YAML document stream |
--fix | Repair JSON and format it; may combine with --print |
-o, --output <PATH> | Write formatted output atomically; requires print/fix |
-i, --indent <N> | JSON indentation, 0โ16 spaces, default 2; YAML uses its formatter |
--check | Report validation/load time, size, nodes, throughput; conflicts with print/fix |
--rebuild-db | Parse again even if a matching completed cache exists |
--no-cache | Use a private temporary SQLite database |
--clear-cache | Remove stored cache files and exit; takes no input file |
-h, --help | Show help |
-V, -v, --version | Show version; -v preserves the Python alias |
Format detection is case-insensitive: .yaml/.yml use YAML, other extensions use JSON. HAR is ordinary JSON. There is no --file flag or live watch mode. The TUI requires stdin and stdout attached to a terminal.
Keyboard controls
| Action | Controls |
|---|---|
| Move up / down | โ / โ, k / j |
| Open selected container | โ, l, Enter |
| Return to parent | โ, h, Esc |
| First / last sibling | g / G, Home / End |
| Search keys and values | /, query, Enter |
| Next / previous match | n / N |
| Jump to a path | :, path, Enter |
| Copy path / entire selected value | c / y |
| Cycle theme | t |
| Open help | ? |
| Dismiss search/jump | Esc |
| Dismiss help | Esc, ?, h, or Enter |
| Quit | q in normal/loading mode; Ctrl+C in any mode |
Mouse wheel moves selection. Click a row to select it; click the selected row to open its container. Right-click returns to the parent. Clipboard ownership is kept while Twig runs; persistence after exit depends on the desktop clipboard manager. Failures are shown in the status message.
Paths look like .users[0].name; root arrays use .[0]. Keys containing punctuation, spaces, or empty strings use JSON-quoted brackets, such as .regions["us-east-1"] or .["a.b"]. This is a path lookup syntax, not a full jq or JSONPath query language. YAML documents are wrapped in an array: .[0].kind addresses the first document, and .kind is a shorthand fallback.
Search is a literal substring match, with ASCII case-insensitivity; % and _ are ordinary characters. Matches cycle in source traversal order, including numeric array order. Non-ASCII characters match exactly.
The inspector shows a limited preview. Clipboard y exports the complete selected value, preserving scalar types and order, up to 10,000 nodes; larger selections give an explicit error directing you to --print. Serialization does not preserve original whitespace, comments, anchors, or quoting.
Configuration and local data
Themes: catppuccin-mocha (default) and solarized-dark. Press t to save. Unknown config keys are preserved. Unknown theme names fall back to the default; custom theme definitions are not supported.
{"theme": "catppuccin-mocha"}
| Platform | Config directory (config.json) | Cache directory |
|---|---|---|
| Linux | $XDG_CONFIG_HOME/twig or ~/.config/twig | $XDG_CACHE_HOME/twig or ~/.cache/twig |
| macOS | ~/Library/Application Support/twig | ~/Library/Caches/twig |
| Windows | %APPDATA%\twig | %LOCALAPPDATA%\twig |
On macOS, when the new config is missing, Twig imports the legacy Python config from $XDG_CONFIG_HOME/twig/config.json or ~/.config/twig/config.json. An existing new config is never overwritten by migration.
The explorer makes no network requests or telemetry calls. Parsed values are stored unencrypted in private SQLite cache files. Unix cache directories are restricted to mode 0700 and published files to 0600; Windows uses profile ACLs.
Every load snapshots and hashes the input. Only completed caches matching the source contents and schema version can be reused. Failed/cancelled builds are never published. Concurrent loads use independent staging databases and publish immutable generations. A source change automatically selects a new generation.
New-format caches are pruned on load after seven days, or oldest-first when the existing cache exceeds 512 MiB. A newly built cache may exceed that budget until a subsequent load. Close other Twig processes and run twig --clear-cache to remove caches, including older versions. Configuration is preserved.
--no-cache uses temporary disk storage, not an in-memory database. Normal exit cleans up temporary data; abrupt OS/process termination may leave temporary files. Sensitive data is not encrypted in either mode.
Performance and supported data
JSON is parsed into node events and inserted in batches of 1,024. It does not materialize the whole document tree. Memory still depends on the largest scalar, nesting/path lengths, SQLite working space, and requested exports. YAML parsing can buffer document state; bounded parser memory is not guaranteed.
Navigation fetches pages of at most 256 siblings, with a horizontal viewport for deep trees. Inspector previews have depth/node limits. Search is a SQLite substring scan; broad searches on very large files can take time.
JSON input must contain a single document. Duplicate object keys and ambiguous stored paths are rejected. Maximum supported nesting is 128 levels (the JSON parser may reject at its own recursion boundary). YAML's TUI model supports JSON-compatible scalar types, sequences, and string-keyed mappings; non-finite numbers, tagged values, and complex mapping keys are not supported. --print uses the YAML value serializer and supports a wider YAML value model.
Measure cold ingestion explicitly:
cargo build --release --locked
python3 scripts/benchmark.py --binary target/release/twig
The benchmark generates a deterministic file and reports wall time and peak RSS. --check uses MiB units. Cached loads still read/hash the source to verify it; they are not evidence of parsing throughput. No universal latency or fixed binary-size claim is made.
Development
- Contributing: setup, tests, review, release workflow.
- Architecture: data flow and implementation boundaries.
- Migration: Python 2.x and Rust 3.0 upgrade differences.
- Release evaluation: audit findings and their resolution.
- Changelog and release notes.
Website source lives in website/. Its guides are generated from these Markdown files, and the deployed installer is copied from this repository's install.sh. The Python source remains on legacy-python.
License
MIT โ see LICENSE.