CLI Usage & Flags
Synopsis
simple-ots [FLAGS]
<file-list-command> | simple-ots [FLAGS]When stdin is a pipe, simple-ots reads file paths one per line.
When run interactively (no pipe), it walks the current directory, skipping .simple-ots/ and .git/.
Flags
| Flag | Default | Description |
|---|---|---|
--no-ots | false | Skip OpenTimestamps stamping |
--config PATH | .simple-ots/config.toml | Config file path |
--verbose | false | Print each file as it is hashed |
--version | — | Print version and exit |
-h, --help | — | Print usage and exit |
Exit codes
| Code | Meaning |
|---|---|
0 | Success |
1 | Fatal error (no files, config error, I/O error) |
2 | OTS step failed (root hash was written; only stamping failed) |
Exit code 2 lets CI distinguish "tool broke" from "network/ots unavailable".
Examples
Standard patterns
# All Git-tracked files — the most common invocation
git ls-files | simple-ots
# Tracked files + new untracked files not in .gitignore
git ls-files --cached --others --exclude-standard | simple-ots
# Walk cwd (no pipe needed)
simple-ots
# Specific extensions
find . -name '*.go' -o -name '*.md' | simple-ots
# Everything under a subdirectory
find ./src -type f | simple-otsSkip OTS
# Compute and record hashes without network access
git ls-files | simple-ots --no-ots
# Useful in CI for hash recording; stamp separately on release
find . -type f | simple-ots --no-otsCustom config
# Use a shared team config
simple-ots --config /etc/simple-ots/team.toml
# Per-run config override (e.g. anonymous run, no DIDs)
git ls-files | simple-ots --config .simple-ots/anonymous.tomlVerbose output
git ls-files | simple-ots --verbose
# hashing: go.mod
# hashing: main.go
# hashing: ots/leaf.go
# ...
# Root hash: 51167fb0...Output structure
Every run creates a timestamped directory under .simple-ots/results/:
.simple-ots/
└── results/
└── 20260626_120000/
├── manifest.jsonl
├── root.hash
└── root.hash.ots (absent with --no-ots)manifest.jsonl
One JSON object per line, one line per leaf:
{"file_path":"src/main.go","content_sha256":"e3b0c4...","datetime":"2026-06-26T12:00:00Z","did":"did:web:example.com","path":"src/main.go","path_variant":"relative","leaf_hash":"a1b2c3..."}
{"file_path":"src/main.go","content_sha256":"e3b0c4...","datetime":"2026-06-26T12:00:00Z","did":null,"path":null,"path_variant":"null","leaf_hash":"d4e5f6..."}Fields:
| Field | Type | Description |
|---|---|---|
file_path | string | Normalized relative path (always present for reference) |
content_sha256 | string | SHA-256 of the raw file content |
datetime | string | File mtime, ISO 8601 UTC |
did | string|null | DID bound to this leaf, or null |
path | string|null | Path value in this leaf, or null |
path_variant | string | Which variant: "null", "filename", or "relative" |
leaf_hash | string | SHA-256 of the canonicalized JSON of the above fields |
root.hash
The Merkle root over all leaf hashes, as a 64-character hex string followed by a newline.
root.hash.ots
OpenTimestamps binary receipt. Initially pending; confirmed after a Bitcoin block.
# Check/upgrade after ~2 hours
ots upgrade .simple-ots/results/20260626_120000/root.hash.ots
# Verify against Bitcoin
ots verify .simple-ots/results/20260626_120000/root.hash.otsSelective disclosure workflow
After stamping, to prove one file to a third party:
- Find the leaf(es) for that file in
manifest.jsonl(filter byfile_path+ desiredpath_variant+did). - Collect the sibling hashes from the manifest to form a Merkle proof.
- Share: the file content, the leaf JSON, the proof path,
root.hash, androot.hash.ots.
The recipient verifies by recomputing the leaf hash and walking up to the root, then running ots verify.
No other files or DIDs in the tree are revealed.
gitignore recommendation
Add to .gitignore to avoid committing run artifacts:
.simple-ots/results/Or commit selectively — the root.hash and root.hash.ots files are small and useful to keep.