Every CLI step should produce a local file. After a full workflow, the user's directory contains a complete, self-contained evidence chain that anyone can verify offline.
Today the CLI mostly prints to stdout and relies on the user to redirect output:
| Command | Output | Saves File? |
|---|---|---|
pb hash dir <dir> |
JSON to stdout | No |
pb commit <dir> |
JSON to stdout | No |
pb submit <commitment.json> |
JSON to stdout | No |
pb publish github <dir> |
Console logs | Yes (publication.json) |
| Step | Command | File Created | Contents |
|---|---|---|---|
| 1 | pb hash dir <dir> |
<dir>/manifest.json |
{ spec_version, items: [{path, hash}], root_hash, created_at } |
| 2 | pb commit <dir> |
<dir>/commitment.json |
Signed commitment (same JSON as today's stdout, saved to disk) |
| 3 | pb submit <dir> |
<dir>/server-receipts.json |
{ receipts: [{ server, receipt }] } — one entry per server |
| 4 | pb reveal <dir> |
<dir>/reveal.json + <dir>/reveal-receipts.json |
Reveal payload + server responses |
| 5 | pb publish github <dir> |
<dir>/publication.json |
Mirror URLs, attestations (already exists) |
After step 5, the directory looks like:
my-benchmark/
├── result-01.json # user's data
├── result-02.json
├── result-03.json
├── manifest.json # step 1: file hashes
├── commitment.json # step 2: signed commitment
├── server-receipts.json # step 3: server timestamps
├── reveal.json # step 4: reveal payload
├── reveal-receipts.json # step 4: server reveal confirmations
└── publication.json # step 5: mirror URLs + attestations
All protocol files also get copied into the GitHub repo and summarized in a human-readable README.
walkDir() must skip protocol files at the root level so re-running pb hash dir after saving manifest.json doesn't include it:
const PROTOCOL_FILES = new Set([
'manifest.json', 'commitment.json', 'server-receipts.json',
'reveal.json', 'reveal-receipts.json', 'publication.json',
'receipt.json', 'verify.sh', 'README.md',
]);
function walkDir(dir, rootDir = dir) {
// When processing rootDir entries, skip PROTOCOL_FILES
for (const entry of entries) {
if (dir === rootDir && PROTOCOL_FILES.has(entry.name)) continue;
// ... rest unchanged
}
}
pb hash dir <dir> → manifest.jsonCurrent: prints { hash, items } to stdout.
Change:
manifest.json to the directory:{
"spec_version": "0.2.0",
"root_hash": "<hash>",
"items": [
{ "path": "result-01.json", "hash": "<sha256>" },
{ "path": "result-02.json", "hash": "<sha256>" }
],
"created_at": "<ISO timestamp>"
}
Saved manifest.json to stderrpb commit <dir> → commitment.jsonCurrent: prints full commitment JSON to stdout, user pipes to file.
Change:
<dir>/commitment.jsonSaved commitment.json to stderrpb submit <dir> → server-receipts.json (directory mode)Current: pb submit <commitment.json> takes a single file, submits to one server, prints receipt to stdout.
Change — add directory mode:
pb submit <dir> detects a directory, reads commitment.json from itserver-receipts.json:{
"spec_version": "0.2.0",
"commitment_hash": "<hash>",
"submitted_at": "<ISO timestamp>",
"receipts": [
{
"server_name": "nocherry-dev-1",
"server_url": "https://...",
"receipt": { ... full receipt ... }
}
]
}
receipt.json (the first successful receipt) for backwards compatibilitypb submit commitment.json) working as beforepb reveal <dir>This command doesn't exist yet. Currently reveal is only done in the web demo (step 6).
server-receipts.json (or receipt.json) to get beacon round + selectionreveal.json (the reveal payload) and reveal-receipts.json (server responses)pb publish github <dir> — enhancedCurrent: copies data + receipt.json + commitment.json to GitHub, writes publication.json.
Change:
README.md in the reveal folder with human-readable tables:# Commitment Reveal: <hash-prefix>
## Protocol Evidence
### Commitment
| Field | Value |
|-------|-------|
| Hash | `abc123...` |
| Items | 3 files |
| Reveal probability | 50% |
| Signing key | `did:key:z...` |
| Created | 2026-02-07T... |
### Server Receipts
| Server | Beacon Round | Received At |
|--------|-------------|-------------|
| nocherry-dev-1 | 12345678 | 2026-02-07T... |
| nocherry-dev-2 | 12345678 | 2026-02-07T... |
### Random Selection
| # | File | Hash | Selected? |
|---|------|------|-----------|
| 1 | result-01.json | `abc...` | Yes |
| 2 | result-02.json | `def...` | No |
| 3 | result-03.json | `ghi...` | Yes |
### Publication Verification
| Server | Verified Items | All Match? | Verified At |
|--------|---------------|------------|-------------|
| nocherry-dev-1 | 2/3 | Yes | 2026-02-07T... |
## Verify Yourself
\`\`\`bash
./verify.sh
\`\`\`
All "Saved X" messages go to stderr so stdout remains clean JSON for piping:
console.error('Saved manifest.json'); // stderr
console.log(JSON.stringify(manifest)); // stdout (parseable)
pb hash dir creates manifest.json in directorypb commit <dir> creates commitment.json in directorypb submit <dir> creates server-receipts.json (needs mock server or real test server)pb hash dir after saving manifest.json produces same hash (protocol files excluded)PROTOCOL_FILES exclusion to walkDir()pb hash dir → save manifest.jsonpb commit <dir> → save commitment.jsonpb submit <dir> → directory mode + save server-receipts.jsonpb reveal <dir> → new commandpb publish github to copy all protocol files + generate README tables