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 | <out-dir>/config.toml | Config file path, must exist if given |
--out DIR | ./.simple-ots | Directory to write results under |
--verbose | false | Print each file as it is hashed |
--version | n/a | Print version and exit |
-h, --help | n/a | Print usage and exit |
--output-dir is accepted as an alias for --out.
Environment variables
| Variable | Description |
|---|---|
SIMPLE_OTS_CONFIG_PATH | Config file path. --config takes precedence. |
SIMPLE_OTS_OUT_DIR | Output directory. --out takes precedence. |
OTS_CMD | Full ots invocation to use instead of the one on PATH. |
In PowerShell:
$env:SIMPLE_OTS_CONFIG_PATH = "./config.toml"
git ls-files | simple-otsThe config file that was actually loaded is printed as the Config: line of the run summary. A path given through --config or SIMPLE_OTS_CONFIG_PATH must exist; if it does not, the run fails with config file not found rather than quietly using the defaults.
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, since the root.hash and root.hash.ots files are small and useful to keep.