Skip to content

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

FlagDefaultDescription
--no-otsfalseSkip OpenTimestamps stamping
--config PATH.simple-ots/config.tomlConfig file path
--verbosefalsePrint each file as it is hashed
--versionPrint version and exit
-h, --helpPrint usage and exit

Exit codes

CodeMeaning
0Success
1Fatal error (no files, config error, I/O error)
2OTS step failed (root hash was written; only stamping failed)

Exit code 2 lets CI distinguish "tool broke" from "network/ots unavailable".


Examples

Standard patterns

bash
# 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-ots

Skip OTS

bash
# 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-ots

Custom config

bash
# 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.toml

Verbose output

bash
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:

json
{"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:

FieldTypeDescription
file_pathstringNormalized relative path (always present for reference)
content_sha256stringSHA-256 of the raw file content
datetimestringFile mtime, ISO 8601 UTC
didstring|nullDID bound to this leaf, or null
pathstring|nullPath value in this leaf, or null
path_variantstringWhich variant: "null", "filename", or "relative"
leaf_hashstringSHA-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.

bash
# 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.ots

Selective disclosure workflow

After stamping, to prove one file to a third party:

  1. Find the leaf(es) for that file in manifest.jsonl (filter by file_path + desired path_variant + did).
  2. Collect the sibling hashes from the manifest to form a Merkle proof.
  3. Share: the file content, the leaf JSON, the proof path, root.hash, and root.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.

Released under the Apache License 2.0.