Usage¶
ayame-diff provides comparison, web GUI, packaging, and maintenance commands.
Command overview¶
ayame-diff csv [flags] --left A --right B --out D # CSV/TSV key comparison (default)
ayame-diff text [flags] LEFT RIGHT # line-oriented text diff
ayame-diff sorted [flags] LEFT RIGHT # sort both sides, then diff
ayame-diff dir [flags] LEFT RIGHT # directory/archive comparison
ayame-diff bin [flags] LEFT RIGHT # binary/hex comparison
ayame-diff 3way [text|csv] [flags] # BASE / LEFT / RIGHT comparison
ayame-diff serve [--addr host:port] [--allow-remote] # local web GUI
ayame-diff gui [flags] [LEFT [RIGHT]] # open the GUI, optionally prefilled
ayame-diff update [--check] # check for or install the latest release
ayame-diff remove [--yes] # uninstall a standalone binary
ayame-diff shell-install # file-manager integration
ayame-diff shell-uninstall # remove integration
ayame-diff shell-select PATH # Windows Explorer integration helper
Invoking ayame-diff with --left ... --right ... and no subcommand runs as
csv for backward compatibility. The serve and gui subcommands are covered
in GUI.
Two bare paths are a quick-launch form: files use text, directories use
dir, and adding --gui opens and immediately runs the browser GUI. See
File-manager and quick launch.
csv — CSV/TSV key comparison (default)¶
Compare two CSV/TSV files (including .csv.gz and .tsv.gz) by key, even when
their row order differs, and write the differing rows to a TSV. Left and right
may use different formats; if the header names match, differing column orders
are aligned automatically.
Choosing the key¶
There are three key modes; inclusion and exclusion cannot be mixed.
- No key options — every column is part of the key (multiset row diff).
--key/--key-index— name (or 0-based index) the columns to include.--exclude-key/--exclude-key-index— keep every column as key except the ones you list.
# All columns are the key (default)
ayame-diff csv --left old.tsv --right new.csv --out diff.tsv
# Named key columns (repeatable)
ayame-diff csv --left old.tsv --right new.csv --key customer_id --key event_date --out diff.tsv
# Key by column index (0-based by default; add --index-base 1 for 1-based)
ayame-diff csv --left old.tsv --right new.tsv --key-index 0 --key-index 3 --out diff.tsv
# Keep every column as key except updated_at and checksum
ayame-diff csv --left old.tsv --right new.tsv \
--exclude-key updated_at --exclude-key checksum --out diff.tsv
Excluded columns still appear in the full row comparison and in the output: when
two rows share the remaining key but differ only in an excluded column, they are
emitted as a CHANGED pair.
Use --ignore-column / --ignore-column-index when a column should be excluded
from value comparison as well. --tolerance FLOAT compares numeric value
columns by absolute difference (with an explicit key); repeat
--column-tolerance NAME=FLOAT or --column-tolerance-index N=FLOAT for
per-column tolerances. Case, whitespace, and regex normalization are available
through --ignore-case, --ignore-whitespace, and repeatable --filter-line.
Output¶
Output is TSV by default. Two leading columns, _diff and _side, are prepended,
followed by the original columns in the left input's column order.
_diff _side id name amount
LEFT_ONLY left 10 Alice 100
RIGHT_ONLY right 20 Bob 200
CHANGED left 30 Carol 300
CHANGED right 30 Carol 350
_diff |
_side |
Meaning |
|---|---|---|
LEFT_ONLY |
left |
The key exists only on the left. |
RIGHT_ONLY |
right |
The key exists only on the right. |
CHANGED |
left |
Both sides share the key, but this left row could not be cancelled out. |
CHANGED |
right |
Both sides share the key, but this right row could not be cancelled out. |
Identical rows for the same key cancel one for one. Output order follows the hash-partition and key order — not the input order — so treat the result as a difference set.
Add --cell-diff to insert _changed_cols after _side on CHANGED pairs.
The comma-separated header names use the same ignore and numeric-tolerance
rules as row matching. The stderr summary and --summary-json rank changed
columns by pair count. The default TSV schema is unchanged when the flag is
absent.
--json (alias for --output-format jsonl --cell-diff) writes one structured
JSON object per logical difference to --out. A paired change contains the
full old / new rows and typed changed_columns entries with index, name,
old value, and new value. JSON Lines remains streamable for huge results.
ayame-diff csv --left old.csv --right new.csv --key id \
--cell-diff --out diff.tsv
ayame-diff csv --left old.csv --right new.csv --key id \
--json --out diff.jsonl
gzip output
A .gz output extension turns on gzip automatically, e.g.
--out diff.tsv.gz.
Selected csv options¶
--left PATH --right PATH --out PATH
--key NAME (repeatable)
--key-index N (repeatable)
--exclude-key NAME (repeatable)
--exclude-key-index N (repeatable)
--ignore-column NAME (repeatable)
--ignore-column-index N (repeatable)
--ignore-case
--ignore-whitespace none|change|all
--filter-line REGEX (repeatable)
--tolerance FLOAT
--column-tolerance NAME=FLOAT (repeatable)
--column-tolerance-index N=FLOAT (repeatable)
--cell-diff
--json
--output-format tsv|jsonl
--index-base 0|1
--header=true|false
--align-columns-by-name=true|false
--left-format auto|csv|tsv --right-format auto|csv|tsv
--left-parser auto|simple|rfc4180 --right-parser auto|simple|rfc4180
--partitions N --parse-workers N --workers N
--memory SIZE --merge-fan-in N --temp-dir PATH
--diff-exit-code
Run ayame-diff csv --help for the complete list, including tuning knobs for
very large inputs (--memory, --partitions, --parse-workers, --workers,
--merge-fan-in, --temp-dir).
Save or replay the full configuration with --save-project FILE and
--project FILE; see Comparison projects for the versioned JSON,
relative paths, GUI history, and cron/CI usage.
text — line-oriented text diff¶
Compare two text files (plain or .gz) in row order. A bounded resync window
keeps it linear and memory-bounded even on huge inputs. The differences are
reported as Insert, Delete and Replace hunks.
ayame-diff text old.txt new.txt # unified (default)
ayame-diff text clip: saved.txt # OS clipboard vs file
ayame-diff text --side-by-side left.txt right.txt # two-column (left | right)
ayame-diff text --json old.txt new.txt # machine-readable JSON
ayame-diff text --summary old.txt new.txt # one-line summary only
ayame-diff text --format unified -U 3 old.txt new.txt > change.patch
ayame-diff text --format context -C 3 old.txt new.txt > change.patch
ayame-diff text --format normal old.txt new.txt > change.patch
ayame-diff text --detect-moves --move-min-lines 2 old.txt new.txt
ayame-diff text --window 32 --sync 100:120 --sync 5000:5100 old.txt new.txt
Use clip: (or clipboard:) as either input to compare directly against the
OS clipboard. The CLI invokes pbpaste on macOS, PowerShell on Windows, and
wl-paste or xclip on Linux without adding a runtime library dependency.
Clipboard content can also pass through --pre like file and stdin input.
Output formats¶
| Flag | Output |
|---|---|
| (none) | Unified hunks (default). |
--side-by-side (alias --side) |
Two-column left / right layout; set the total column width with --width. |
--json |
Structured JSON with hunk kinds, line numbers and counts. |
--summary |
A single summary line on stderr. |
--format unified / -U N |
Applyable unified patch with N context lines (default 3). |
--format context / -C N |
Applyable context patch with N context lines (default 3). |
--format normal / --normal |
Classic NcN, NaN, NdN normal patch. |
text flags¶
--json emit the diff as JSON
--side-by-side, --side two-column (left | right) output
--summary print only the one-line summary
--format FORMAT patch format: normal, context, or unified
--normal alias for --format normal
-U N unified patch with N context lines
-C N context patch with N context lines
--context-lines N context for --format context/unified (default 3)
--word highlight changed words in replace hunks (unified)
--encoding VALUE auto (default), utf-8, utf-16le, utf-16be, shift_jis, euc-jp, iso-2022-jp
--ignore-case ignore case when comparing lines
--ignore-whitespace MODE none (default), change (collapse runs), all (remove)
--ignore-all-space alias for --ignore-whitespace all
--ignore-space-change alias for --ignore-whitespace change
--ignore-eol ignore CRLF/LF differences
--ignore-trailing-eol ignore only the final missing line ending
--filter-line REGEX remove regex matches for comparison (repeatable)
--detect-moves pair exact delete/insert blocks as moves (default off)
--move-min-lines N minimum moved-block length (default 2)
--move-max-candidates N per-side detection guard (default 10000)
--sync LEFT:RIGHT force corresponding 1-based lines (repeatable)
--max-hunks N maximum hunks to print; the rest are still counted (default 200)
--max-lines N maximum lines shown per hunk side (default 200)
--window N resync look-ahead window when lines differ (default 128)
--width N total width for --side-by-side (default 160)
Patch output is never truncated by --max-hunks or --max-lines. It preserves
LF/CRLF and missing-final-newline markers, rejects decoded binary/NUL input, and
uses locale-independent file-header timestamps. CI applies all three formats
with GNU patch and additionally verifies unified output with git apply.
See Encoding for --encoding and
Comparison options for ignore filters, numeric
tolerance, --word, --window and --max-hunks.
Input limits¶
A file argument is streamed through a sliding window, so its size does not drive memory. Two shapes are bounded explicitly because they cannot stream:
| Limit | Default | Applies to |
|---|---|---|
--max-line-bytes |
64MiB | one logical line; 0 disables the check |
| (built in) | 1GiB | - (stdin) and --pre output |
A file with no line breaks — minified JSON, a database dump — is a single line,
so the window cannot bound it. Such a file is refused at open time with a
pointer to --max-line-bytes or to bin mode. Peak memory while accumulating
a long line is a small multiple of the limit, not exactly it.
Standard input and a --pre command are pipes: their length is unknown in
advance and their content must be materialized. Past 1GiB they are refused with
a suggestion to write the data to a file, which streams instead.
sorted — sort, then diff¶
For files that hold the same rows in a different order, sorted sorts both
inputs line-wise and then runs the same line diff as text. It accepts every
display flag from text plus sort controls. Patch formats are rejected because
a patch of the sorted view cannot be applied safely to the original file.
ayame-diff sorted old.txt new.txt
ayame-diff sorted --numeric metrics-a.txt metrics-b.txt
ayame-diff sorted --reverse a.txt b.txt
Extra sorted flags¶
--sort-memory SIZE line data held in memory before spilling (default 256MiB)
--temp-dir DIR parent directory for spill files (default: TMPDIR)
Note
sorted is memory-bounded. Input within --sort-memory is sorted in
memory; anything larger spills sorted runs to --temp-dir and merges them,
so files larger than RAM still compare. Comparing two ~370 MB files with
--sort-memory 64MiB peaks at about 114 MiB resident instead of ~1.2 GiB.
Warning
On many Linux systems TMPDIR is /tmp, which is RAM-backed (tmpfs).
Spilling there consumes memory rather than saving it, and a large sort can
fail with "no space left on device" while the disk is empty. Point
--temp-dir (or TMPDIR) at a real filesystem when sorting very large
files.
dir — recursive folder / archive comparison¶
dir LEFT RIGHT pairs slash-normalized relative paths. Size is checked first;
equal-size candidates are streamed in parallel and compared byte-for-byte.
--quick may trust equal size and mtime. Plain .gz files compare their
decompressed content, while zip/tar/tar.gz archives compare as folder sources.
Archive expansion is bounded to 64 MiB per selected entry and 256 MiB total per
archive by default. Adjust these safeguards with --max-archive-entry-bytes and
--max-archive-bytes; oversized or decompression-bomb inputs fail explicitly
instead of exhausting process memory.
A comparison also holds one entry per file — including unchanged ones — so the
file count is bounded by --max-entries (default 2,000,000; a negative value
disables the check). Trees past the limit are refused up front, before any file
is read, with a suggestion to narrow the comparison using --include /
--exclude or to compare a subdirectory.
ayame-diff dir --include '*.csv' --exclude 'tmp/**' --workers 8 old/ new/
ayame-diff dir --tsv --all old/ new/ > folders.tsv
ayame-diff dir --json --diff-exit-code snapshot-a/ snapshot-b/
ayame-diff dir --html folder-report.html old/ new/
ayame-diff dir --csv folder-summary.csv --all old/ new/
ayame-diff dir --compare-by hash old/ new/
ayame-diff dir --filter "size > 1MiB and name =~ '\\.log$'" old/ new/
ayame-diff dir --filter-set development old/ new/
ayame-diff dir --filter-file filters.json --filter-set audit old/ new/
Dotfiles/directories are skipped unless --hidden is set. Symbolic links are
always skipped, avoiding loops and ambiguous out-of-tree reads. TSV/JSON include
status, relative path, sizes, and mtimes. In the GUI, choose folder, filter
the status tree, and click a changed regular file to drill into text diff.
--html FILE writes a self-contained light/dark tree report with status counts,
paths, sizes, and modification times. --csv FILE writes the same entry fields
as RFC 4180 CSV for downstream processing. Both write atomically; unchanged
entries are omitted unless --all is set. These file outputs are mutually
exclusive with --json and --tsv.
Comparison methods¶
--compare-by accepts five explicit methods:
| Method | Behavior |
|---|---|
contents |
Stream and compare bytes, short-circuiting on the first difference (default). |
quick |
Trust equal size + mtime; otherwise fall back to content comparison. --quick is an alias. |
hash |
Stream both files through SHA-256 and compare digests. |
date |
Compare modification times only. |
size |
Compare file sizes only. |
Plain .gz content is decompressed for contents and hash. Metadata-only
methods use the source/archive metadata as presented.
Filter expressions and reusable sets¶
--filter supports parentheses plus case-insensitive and, or, and not.
Fields are size, name, path, ext, and mtime. Size/mtime fields accept
< <= == != >= >; string fields accept == != =~ !~. Sizes accept decimal or
binary units, and mtimes accept RFC 3339 or YYYY-MM-DD.
Bundled sets are development, vcs, node, and rust; list them with
--list-filter-sets. External JSON files provide named sets:
{
"version": 1,
"default": "audit",
"filters": {
"audit": {
"includes": ["**/*.log", "**/*.json"],
"excludes": ["archive/**"],
"expression": "size >= 1KiB"
}
}
}
--filter-set is repeatable. Selected sets are combined with direct
--include, --exclude, and --filter arguments. A directory-mode
.ayamediff.json can be passed directly as --filter-file; when it contains
LEFT/RIGHT paths, those paths may be omitted on the command line.
bin — byte-level binary comparison¶
bin LEFT RIGHT streams two files and reports each differing region by byte
offset, followed by the left and right bytes in hexadecimal. It is memory-bounded
for large inputs and does not attempt to decode images or other file formats.
ayame-diff bin firmware-v1.bin firmware-v2.bin
ayame-diff bin --max-regions 20 --max-bytes 64 old.dat new.dat
--max-regions limits the number of regions printed (default 256), while
--max-bytes limits the retained bytes shown on each side of a region (default
32). The summary still reports the total differing byte count and whether the
region list was truncated.
update — update a standalone installation¶
update checks the latest GitHub release, downloads the archive for the current
OS and architecture, verifies it against the release's SHA256SUMS, and
atomically replaces the running executable. The executable's directory must be
writable.
ayame-diff update --check # report whether a newer release exists
ayame-diff update # verify and install the latest release
For Homebrew, Scoop, Nix, or another managed installation, prefer that package manager's update command so its package database remains authoritative.
remove — uninstall a standalone installation¶
remove asks for confirmation and then removes the running standalone binary.
Use --yes for a non-interactive invocation. Installs detected under Homebrew,
Scoop, or Nix are left untouched and must be removed with their package manager.
ayame-diff shell-uninstall # optional: remove file-manager entries first
ayame-diff remove
ayame-diff remove --yes
On Windows, the running executable is renamed with a .delete-me suffix; delete
that file after the process exits to finish removal.
shell-select — Windows Explorer selection helper¶
shell-select PATH is the internal bridge installed by shell-install for the
Windows Explorer Compare with Ayame Diff action. The first invocation stores
one path in the current user's configuration for up to 30 minutes. A second,
different path clears that state and opens both paths in the GUI.
Users normally invoke the Explorer action instead of running this command directly. See File-manager and quick launch for setup and the cross-platform launch forms.
Exit codes¶
Normally:
0— success2— usage error: bad flags, arguments, or incompatible options3— runtime error: I/O, comparison, server, or update failure130— interrupted or explicitly cancelled (for example, decliningremove)
With --diff-exit-code (csv and dir):
0— no differences1— differences found2/3— usage or runtime error, as above
With 3way text --merge-exit-code --output PATH:
0— output written with no unresolved conflicts1— output written with standard unresolved-conflict markers2/3— usage or runtime/write error, as above
A usage error and a runtime failure are deliberately distinct, so a script can
tell "you called it wrong" from "it could not finish". An internal crash is
reported as 3 with a stack trace on stderr; it never exits 2 and so is never
mistaken for a usage error.
Scope boundaries¶
ayame-diff is intentionally focused on large structured/text data. Image
rendering and web-page screenshot comparison are out of scope: those workflows
need image decoders or a browser engine and are better served by WinMerge or a
dedicated visual-regression tool.
Git repository inspection and management are also out of scope. Ayame-diff
accepts explicit paths when Git invokes it as a custom difftool or mergetool,
but it does not read .git, resolve revisions, or perform staging, commits,
branch, remote, or authentication operations. See
ADR 0004.
Images and other non-text files can still participate in dir comparisons as
binary content. Use ayame-diff bin LEFT RIGHT to inspect differing byte
offsets; there is no pixel-level image viewer or DOM/rendered-page comparison.