Conversation
Rename the CLI command for brevity and clarity: - `deaddrop` → `dd` (short for "Dead Drop") - `deaddrop leave` → `dd put` - `deaddrop pickup` → `dd get` Bump peeroxide-cli to 0.2.0 for the breaking CLI change. Add stale man page cleanup to `init --man-pages` so renamed pages (e.g. peeroxide-deaddrop.1) are automatically removed. Update all documentation, tests, and man page content to reflect the new naming.
…trap resolution Remove the --firewalled flag and simplify --public/--no-public semantics. The public flag now controls inclusion of default HyperDHT bootstrap nodes rather than firewall advertisement: - public=Some(true): add DEFAULT_BOOTSTRAP to bootstrap list - public=None + empty bootstrap: auto-add DEFAULT_BOOTSTRAP - public=Some(false): remove DEFAULT_BOOTSTRAP entries Extract resolve_bootstrap() with additive merge logic, remove FIREWALL_OPEN/FIREWALL_CONSISTENT propagation from CLI to SwarmConfig, and update tests (3×6 matrix, integration) to match new semantics.
Introduces a verbose flag (count-based: -v for info, -vv for debug) that controls tracing output without requiring RUST_LOG. At -v, shows config source and resolved bootstrap nodes; at -vv, adds decision logic and DHT internals. RUST_LOG still takes precedence when set.
Co-authored-by: Copilot <copilot@github.com>
Every profile now always has a screen name. When no name is user-supplied, one is derived deterministically from the profile's 32-byte Ed25519 seed using Docker-style word lists. - Add names.rs: vendored ADJECTIVES (108) + SURNAMES (236) with generate_name_from_seed(&[u8; 32]) -> String - Wire into create_profile: always writes name file; derives from seed when screen_name is None - Wire into load_profile: derives name on the fly when name file is absent (existing profiles get a name without disk writes) - Add unit tests: determinism, format, underscore count, seed independence; profile tests for user-name preservation and seed-derived name format No new crate dependencies. Uses rand 0.9 StdRng already in tree.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
- chat/overview.md: known_users moved out of the 'Each profile includes' bullet list and into its own paragraph that explicitly calls it process-wide and not per-profile. Each profile now lists only its actual local contents (seed, optional name, optional bio, friends list). - peeroxide-cli/CHAT.md, CHAT_IMPL_PROMPT.md, DEBUG_FLAG.md, DHT_REF.md: each working/historical design document now carries the same 'proposed for removal / see docs/src/' banner already on CHAT_CLI.md and DEADDROP_V2.md, so any reader stumbling onto one of them is pointed at the canonical shipped documentation in docs/src/.
- chat/protocol.md: DM nudges are NOT empty announces. They are
encrypted InviteRecord mutable_puts (lure truncated to 800 bytes)
followed by an announce on the recipient's inbox topic, matching
the regular inbox-invite write path. (inbox.rs)
- dd/format.md: the v2 receiver-done sentinel is a raw empty byte
string at the need topic, NOT the encoded need-list with count=0.
The decoder treats zero-byte values specially. (fetch.rs / need.rs)
- dd/format.md: define what need-list {start, end} entries actually
mean (half-open [start, end) ranges of data-chunk indices in the
canonical DFS file order; sender republishes those chunks plus
the full index-tree path needed to reach them).
- chat/tui/interactive.rs: the stale comment on the history VecDeque
said the ring is bounded to 10_000 lines, but HISTORY_CAP is 500.
Comment now states the actual constant.
- chat/overview.md: sender does not post an Invite record TO the inbox
topic. They generate a one-shot invite-feed keypair, mutable_put the
encrypted InviteRecord at that feed, and then announce that feed on
the recipient's current inbox topic. Matches inbox.rs::send_dm_invite
and send_private_invite (and send_dm_nudge for nudges).
- dd/architecture.md: the v2 need-list lifecycle has two distinct
cadences:
- mutable_put of the encoded missing-range need-list every 20s
(need_publish_interval)
- announce keepalive on the need topic every 60s
(NEED_REANNOUNCE_INTERVAL via run_need_announcer)
Documenting both instead of conflating them into 'announce ranges
every 20s'.
…v2/mod.rs
- docs/src/dd/{overview,architecture}.md: 'Merkel tree' -> 'Merkle tree'.
- docs/src/dd/operations.md: add a paragraph noting the inherited
top-level globals (--config, --no-default-config, --public, --no-public,
--bootstrap, -v) that dd put / dd get also accept, with a cross-link
to init/overview.md's bootstrap-resolution algorithm.
- peeroxide-cli/src/cmd/deaddrop/v2/mod.rs: remove the leftover 'working
name v3' historical breadcrumb from the module docstring.
dd put / dd get also accept --config, --no-default-config, --public, --no-public, --bootstrap (repeatable), and -v/--verbose. These are inherited from the top-level Cli struct (see init/overview.md for the bootstrap-resolution algorithm). The previous edit attempt missed this section because operations.md was rewritten with a different section structure than I'd assumed.
…semantics The earlier wording said 'start with the combined list from the config file and --bootstrap CLI flags', but config.rs uses 'flags.bootstrap.clone().or(file_config.network.bootstrap)' — i.e. CLI --bootstrap OVERRIDES the file's network.bootstrap, it does not concatenate. Split the resolution into two explicit stages: (1) base-list selection via the override rule in config.rs, (2) the public-default adjustment in resolve_bootstrap. Also note that init itself uses its own local --public/--bootstrap to populate the generated config and does not run this resolution.
…, PR body CLI --bootstrap overrides the config file's network.bootstrap; it does not combine with it. Earlier copy in three places still said 'config + CLI merge' / 'additive (config + CLI)', which doesn't match peeroxide-cli/src/config.rs's flags.bootstrap.or(file_config...) call. - docs/src/init/overview.md: drop 'config/CLI merge' from the section intro and from the trailing init-vs-runtime note. - peeroxide-cli/AGENTS.md: rewrite resolve_bootstrap docstring to state CLI overrides config, then describe the public-default adjustment. - PR body (gh pr edit): replace the 'additive (config + CLI → ...)' sentence with the correct two-stage description.
These three docs predate the new bootstrap-resolution rules and the auto-public default. They claimed (incorrectly) that the public bootstrap set requires --public, that the node is isolated without --public, and that cp's --public both selects public bootstraps AND flips the firewall open. Reality (per peeroxide-cli/src/config.rs and cmd/mod.rs::resolve_bootstrap): - CLI --bootstrap OVERRIDES config bootstraps (not combine). - --public adds default public bootstrap nodes; an empty list auto-fills with the defaults; --no-public removes them. - cp uses build_dht_config(cfg) like every other runtime command; --public does not flip the firewall state. Rewrite all three sections to match.
…ment cold-start scan - peeroxide-cli/src/cmd/init.rs: --public --help no longer claims the node is 'publicly reachable (not behind NAT/firewall)'; it now describes what the flag actually does: marks network.public = true in the generated config so runtime subcommands add default public HyperDHT bootstrap nodes. Generated config comments correspondingly updated to describe the real bootstrap-resolution rule (CLI override, auto-public defaults, --no-public removes). - docs/src/chat/protocol.md: Reader Discovery Loop section now also documents the cold-start historical scan (20 epochs × 4 buckets = 80 concurrent lookups on session start, ~20-minute backward window), matching peeroxide-cli/src/cmd/chat/reader.rs:240-246. Previously only the steady-state 8-lookup loop was documented.
--public / network.public only drives bootstrap node selection at runtime; it does not flip firewall semantics. The 'Open if public=true, Consistent otherwise' wording in the Swarm Setup bullet predates the new bootstrap-resolution rules and conflicted with the corrected init/overview.md, cp/architecture.md, and cp/protocol.md. Replace with an honest description and a cross-link to the canonical flag-resolution section in init/overview.md.
The peeroxide-cli/DHT_REF.md working file is one of the candidate-removal
docs in the PR, but sections A.1-A.4 (plus the generic part of A.5) are
broadly useful as a 'what operations does peeroxide-dht expose' reference
for anyone implementing on top of the DHT layer. Rather than deleting
that content, move it into the docs/ mdBook:
- docs/src/concepts/dht-primitives.md: replace the 'Content coming in
Phase 3a' stub with the four-primitive reference. immutable_put/get,
mutable_put/get, announce/lookup, and TTL each get an explanatory
paragraph plus a property table. The mutable_put 1002-byte size-budget
derivation is kept because it is broadly useful for protocol authors;
the chat-specific size math from DHT_REF.md A.5 is dropped because the
chat::wire constants are already documented in docs/src/chat/reference.md.
- docs/src/SUMMARY.md: list the new chapter under # Concepts.
- docs/src/dd/{architecture,format}.md and docs/src/chat/wire-format.md:
add a one-sentence cross-link to the new primitives reference.
- peeroxide-cli/DHT_REF.md: delete from the working tree (content
preserved in the mdBook chapter).
Net working-files removal candidates count goes down by one.
…ttern Earlier wording dismissed announce/lookup as 'not general-purpose value storage', but chat and dd v2 both deliberately use it as a general rendezvous mechanism: the announcer's 32-byte pubkey acts as a pointer to a further mutable_put slot owned by the same keypair. That sidesteps mutable_put's single-writer-per-pubkey limitation and lets readers parallelize one lookup followed by N parallel mutable_gets. Add a 'The Rendezvous Pattern' subsection that: - Spells out the 3-step pattern (topic derivation -> ephemeral keypair + mutable_put + announce -> reader lookup + parallel mutable_gets). - Tabulates the four concrete uses in this workspace: * chat::crypto::announce_topic -> FeedRecord * chat::crypto::inbox_topic -> InviteRecord * dd::v2::keys::need_topic -> encoded need-list * dd::v2::keys::ack_topic -> (announcer count only) - Notes that relay_addresses is left empty in all of those uses and that epoch+bucket rotation bounds traffic-analysis exposure. Soften the section intro accordingly.
…vous cleanup Working files folded into docs/src/ (see prior commit 0ba7795 for the DHT_REF.md fold-in) or otherwise superseded by the mdBook chapters are now deleted from the working tree: - peeroxide-cli/CHAT.md (chat protocol design notes) - peeroxide-cli/CHAT_CLI.md (chat CLI design notes) - peeroxide-cli/CHAT_IMPL_PROMPT.md (chat implementation prompt) - peeroxide-cli/DEADDROP_V2.md (dd v2 design / spec; superseded by docs/src/dd/{architecture,format,operations}.md) - peeroxide-cli/DEBUG_FLAG.md (working note for the chat --debug flag) Also: - .gitignore: drop redundant literal task-artifact filenames now matched by the *_PLAN.md / *_PROMPT.md / *_TODOS.md globs; add *_TODO.md to the same family; add .claude/ and .mcp.json (local agent / MCP state that should never land in git). - SECURITY.md: drop the '48 hours' SLA from the vulnerability-report acknowledgement line; keep the 90-day resolution target. - docs/src/concepts/dht-primitives.md: rewrite the rendezvous-pattern section as a generic concept. An announce 'topic' is just a 32-byte address; the announcer's pubkey acts as a pointer to a further mutable_put slot. Add a section on extending the 20-announcers-per- topic-per-node cap with epoch/bucket salt in the topic-derivation hash. Push concrete chat/dd uses out to those subsystems' own protocol/wire-format chapters instead of duplicating them here.
peeroxide chat had 14 generated man pages (one per leaf subcommand, including nested profiles/friends groups). Following the cp / dd pattern, consolidate them into a single peeroxide-chat(1). manpage.rs: - Add 'peeroxide-chat' to the CONSOLIDATED list. - Extend the consolidated renderer to recurse into nested subcommand groups. Leaves now render as .SS join, .SS dm, ... and nested leaves as .SS profiles list, .SS friends add, etc. - Surface the parent command's own non-global args in an OPTIONS section before COMMANDS, guarded so cp/dd (which have no non-global parent args) still produce no spurious OPTIONS section. - Add a peeroxide-chat long_about covering identity model, channels vs DMs, DHT rendezvous discovery, the encryption/signing model, and TUI-vs-line-mode auto-selection. - Add per-leaf long_about for the 14 chat leaves and groups: join, dm, inbox, whoami, profiles + list/create/delete, friends + list/ add/remove/refresh, nexus. - Add 9 worked examples for peeroxide-chat. - Add peeroxide-chat to the standard exit-status block and see-also entries; cross-link from the top-level peeroxide(1) SEE ALSO list. - Refresh peeroxide-dd long_about to document both v1 (0x01) and v2 (0x02), the soft depth cap of 4 (~27 GB at default chunk size), and the auto-dispatch on the get side. - Add 3 new peeroxide-dd-put examples covering --v1, --no-progress, --json; one new --json example on the get side. - Strip roff escape codes (\fB...\fR) from the top-level long_about entries, since clap_mangen's render_description_section escapes them and they were showing up as literal text in DESCRIPTION. peeroxide-cli/README.md: drop the prior 23-page disclosure; man pages are back to 9 (one per top-level command). Verified: 'peeroxide init --man-pages /tmp/peeroxide-man-check' produces exactly 9 pages, and 'mandoc /tmp/.../peeroxide-chat.1' renders cleanly with proper SYNOPSIS / DESCRIPTION / OPTIONS / COMMANDS (including nested profiles/friends leaves) / EXAMPLES / EXIT STATUS / SEE ALSO.
The nexus section described --set-name / --set-bio but did not tell
the reader where the underlying name and bio files actually live, that
they can be edited directly, or what the practical size budget is.
Add a 'Screen Name and Bio Files' subsection covering:
- File locations under ~/.config/peeroxide/chat/profiles/<profile>/
- Both files are optional (vendor-name fallback when name is absent)
- Two ways to populate: --set-name/--set-bio (with trim semantics) or
edit directly. Multi-line bios are supported; the friends list shows
the cached first line of each friend's bio (per reference.md), but
chat nexus --lookup shows the full record.
Add a 'Size Limit' subsection covering:
- 1000-byte cap on the NexusRecord (3 framing bytes + name + bio bytes)
- Practical bio budget of ~950-990 UTF-8 bytes with a typical screen name
- Exact error message on overflow ('record too large: N bytes exceeds
1000 byte limit') and the recovery path (file is still saved on disk;
only the publish is skipped).
The chat docs previously mentioned --stealth only as a one-liner
('Shorthand for --no-nexus --read-only --no-friends'), with no
guidance on when stealth is sufficient, what it does not protect
against, or how it interacts with --no-inbox.
Add a new top-level 'Stealth Mode' section in docs/src/chat/user-guide.md
between 'Personal Page: nexus' and 'Interactive Usage':
- What --stealth suppresses: the three publishing-side operations,
with the exact DHT primitive each suppresses (announce on rendezvous
topics, mutable_put of FeedRecord/NexusRecord, periodic mutable_get
on friends' identity pubkeys).
- What --stealth does NOT suppress: channel discovery still issues
lookups + per-announcer mutable_gets; inbox monitoring is independent
and keeps polling the profile's inbox topics; DM under stealth is
receive-only; network-level metadata (IP visible to DHT peers) is
unchanged.
- Inbox-vs-pubkey note: the wire-level inbox lookup does NOT carry the
pubkey (the topic is keyed_blake2b(hash(pubkey), ...)), but an
observer who already knows the pubkey can derive the same topic and
correlate the polling source IP. Pair with --no-inbox if that matters.
- When --stealth is enough: fresh profile, lurking before posting.
- When --stealth is not enough: pubkey already known + IP correlation
matters. Calls out the exploitable chain (pubkey -> derived inbox /
Nexus / announce topics -> DHT lookups from your IP) and points at
a trustworthy VPN (different egress, traffic mixing, no per-flow
logging) as the appropriate transport-layer mitigation.
- Three recipes: --stealth, --stealth --no-inbox, --stealth --profile burner.
Also update docs/src/chat/reference.md: the terse --stealth row now
notes that the flag does NOT suppress inbox polling, with a cross-link
to the new user-guide section.
docs/ascii_art.txt grew from a stray decorative file into the project's
canonical banner. The original taglines were claim-checked against the
shipped code and two of them needed rewording:
ENCRYPTED BY DEFAULT. (kept: true for chat / cp; nuance documented
in the protocol docs)
ANONYMOUS BY DESIGN. -> PSEUDONYMOUS BY DESIGN.
(peeroxide does not provide transport-layer
anonymity; see chat user-guide Stealth Mode
for the full breakdown)
SPEAK FREELY. -> NO SERVERS. NO ACCOUNTS. NO GATEKEEPERS.
LEAVE NO TRACE. (replaced; local files persist and DHT-side
queries are visible to peers serving them)
TRUST NO ONE. (kept: aspirational, refers to the no-central-
TALK TO ANYONE. authority DHT model)
Embed the banner in three places:
- peeroxide-cli/src/main.rs: a new LONG_VERSION const composes
env!('CARGO_PKG_VERSION') + the ascii_art.txt include_str! contents
and is wired through clap's #[command(long_version = ...)]. Result:
'peeroxide -V' stays terse ('peeroxide 0.2.0', script-friendly) and
'peeroxide --version' shows the banner with the version header on top.
- peeroxide-cli/README.md: code-fenced banner block at the top of the
crate README so crates.io / GitHub readers see it first.
- docs/src/introduction.md: same banner block at the top of the mdBook
introduction.
peeroxide-cli 0.2.0: new chat and init commands, dd v2 protocol, progress UX, global --no-public/-v flags, consolidated chat manpage, new mdBook chapters, embedded ASCII banner. Removes legacy 'config init', '--generate-man', and '--firewalled'. peeroxide-dht 1.3.0: additive WireCounters API and wire_stats / wire_counters accessors on Io, DhtHandle, and HyperDhtHandle. No breaking changes. peeroxide (unreleased): notes the peeroxide-dht 1.2 to 1.3 dep bump.
…stall guide
Now that peeroxide-cli ships to crates.io and via the
rightbracket/peeroxide homebrew tap, the top-level docs need to reflect
that:
* README:
- Architecture diagram gains peeroxide-cli as the top layer.
- New 'Install the CLI' section between Architecture and the
library-focused Quick Start: brew (auto-tap three-segment form),
cargo, and brew --HEAD for build-from-source. Includes the
prebuilt platform matrix and links to the tap.
- Existing 'Quick Start' renamed 'Quick Start (library)' for
clarity since casual users will land on Install first.
- Crates list gains a peeroxide-cli row with the crates.io badge.
* AGENTS.md:
- Workspace crates table flips peeroxide-cli's 'Published' column
from 'binary only' to 'crates.io + homebrew tap'.
- Prose follow-up rewritten to match.
…, drop chat-internal noise
Trim the [0.2.0] section to focus on what's new and notable for the
upgrading user rather than exhaustively enumerating chat's flag surface
(every chat flag is by definition new because the whole subcommand is
new). Net: 56 -> 41 bullets, 91 -> 76 lines.
Specifically:
* Collapsed the seven chat-specific Added bullets (chat join, chat dm,
interactive TUI, Ctrl-C semantics, inbox monitor, parallel scan,
burst-ordered publishing, scrollback preserve, history replay, probe
flag) into one solid 'peeroxide chat' bullet that describes what it
IS and refers readers to docs/src/chat/ for the full reference.
* Dropped chat-internal Fixed bullets (Ctrl-C backpressure
responsiveness, per-feed chain anchoring) — those bugs only existed
during this development cycle and never shipped publicly because
'chat' itself is new in 0.2.0.
* Dropped chat-internal Changed bullets (EOF graceful drain, auto
line-mode, chat dm full TUI consumer) for the same reason.
* Surfaced the library API consumption explicitly: the dd progress
display bullet now names the new peeroxide-dht 1.3.0 wire-counter
API (HyperDhtHandle::wire_stats / wire_counters) and points readers
at peeroxide-dht/CHANGELOG.md for the full additive symbol set.
dd renames, bootstrap resolution, --json behavior, man-page
consolidation, dd v2 + progress UI, and the brew tap distribution all
stay — those affect anyone upgrading from 0.1.0.
This was referenced May 14, 2026
Merged
Merged
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
peeroxide-cli: apeeroxide initbootstrap command, theddv2 tree-indexed protocol rewrite (with progress UX), and the fullchatsubsystem (channels, DMs, inbox monitor, interactive TUI).peeroxide-dht1.2.0 → 1.3.0 (additive only — no breaking changes).docs/mdBook with newinit/andchat/chapters and rewrites thedd/chapter to cover both v1 and v2 protocols.Motivation
The branch grew from three independent threads that share a common substrate (the existing peeroxide DHT primitives):
peeroxide initremoves the "edit a TOML file by hand before anything works" papercut for new users and gives us a single entry point for both config bootstrap and man-page installation.ddv2 addresses a structural ceiling in v1: the v1 chain was a sequential singly linked list, so a 1 GB payload required ~35,800 sequentialmutable_getround trips on the critical path even though every chunk could otherwise be fetched in parallel. v2 turns the index layer into a canonical tree (O(log₃₁ N)round trips) and splits index (mutable) from data (immutable, content-addressed). On the publisher side, the rewrite also lands AIMD concurrency control, a stall watchdog, per-put timeouts, a sticky double-ctrl-c shutdown primitive, and a need-list mechanism that only advertises chunks the receiver actually tried and failed to fetch.chatis a brand-new feature: anonymous, end-to-end-encrypted, verifiable group + DM chat built on the existing DHT primitives, with no protocol changes to peeroxide, no relay code, and no peer cooperation required. It ships with feed rotation, per-feed chain anchoring, ordering + dedup invariants, an inbox-monitor framework, a unifiedchat::sessionconsumed by bothchat joinandchat dm, and a full interactive TUI with status bar, slash commands, history replay, and bracketed paste.The
ddprogress UX work (bar / log / JSON / off, TTY-aware auto-selection) was added alongside the v2 rewrite so users finally have actionable feedback on large transfers and tools can integrate via JSON-Lines instead of parsing stderr.Changes
peeroxide-clipeeroxide initsrc/cmd/init.rs, ~489 LOC). Replaces and removes the oldpeeroxide config initsubcommand (src/cmd/config.rsdeleted;Commands::Configremoved frommain.rs). Two mutually-exclusive modes: config bootstrap (default) and man-page installation (--man-pages [PATH]). Config mode supports--force(overwrite),--update(patchnetwork.public/network.bootstrapwhile preserving comments), and writes a fresh commented TOML. Man-page mode writes generated roff toPATH/man1/and cleans stalepeeroxide*.1files.peeroxide chatcmd/chat/module tree (~35 source files). Subcommands:join,dm,inbox,whoami,profiles {list,create,delete},friends {list,add,remove,refresh},nexus. Full identity / profile / friend / known-users / nexus system. XSalsa20-Poly1305 + Ed25519 + BLAKE2b crypto. Per-session feed rotation with ±50% wobble, per-feed(id_pubkey, feed_pubkey)chain anchoring, ChainGate strict ordering with 5 s force-release, 1000-entry DedupRing, summary eviction on feed overflow (20→15 + walkback). Inbox monitor with parallel DHT scan (8 lookups + parallelmutable_gets per cycle, ~2-4 s wall-clock vs ~10-20 s in earlier nested-serial design). Sharedchat::sessionconsumed byjoinanddm; DM derivesdm_channel_keyand per-pairdm_msg_keyvia Ed25519→X25519 ECDH, sends per-epoch nudges, and best-effort retracts the invite on shutdown.ddCLIdeaddrop→dd;leave/pickup→put/get.dd putdispatches on--v1(default v2);dd getauto-dispatches on the root record's first byte (0x01→v1,0x02→v2).ddv2 protocol0x02. New module tree atcmd/deaddrop/v2/(build,tree,wire,keys,queue,publish,fetch,need,stream). Canonical-shape index tree with 31-slot non-root nodes and a 30-slot root that carriesfile_size: u64+crc32c: u32. Data chunks useimmutable_put+ a reserved per-deaddrop salt byte (currently fixed at0x00); index nodes usemutable_putwith deterministic per-index keypairs. Receiver does BFS fetch withPARALLEL_FETCH_CAP = 64, sliding-window--timeout, and an ephemeral need-feed announcing only attempted-and-failed positions on a 20 s cadence. Soft depth cap of 4 → 27.7M data chunks (~27 GB).ddprogress UXcmd/deaddrop/progress/subsystem (state,format,rate,events,json,bar,log,mode,reporter). TTY-aware mode selection:--json>--no-progress> stderr-TTY → indicatif bar > periodic 2 s stderr log. Bar layouts: 2-bar (v1 put, v2 put) or 4-bar (v2 get) usingindicatif::MultiProgresswith a wire-rate / amplification line. JSON-Lines schema documented indocs/src/dd/operations.md(events:start,progress,result,ack,done).dd putrobustnessShutdownprimitive (first SIGINT/SIGTERM cancels gracefully; second exits 130). Per-putPUT_TIMEOUT = 30 senforced in dispatcher. EWMA AIMD controller (α 0.1, decision interval 20, fast-trip threshold 10, shrink 0.75×, grow +2). Stall watchdog kicks concurrency off the floor every 5 s if 30 s pass with no successful put (rate-limited to once / 120 s).dd putperfChunkIdwork queue withLane::Highpriority for need-driven republishes. Interleaved index/data dispatch. Initial sender concurrency 128. Sliding-window get timeout.mpsc(64), batches ≤--batch-size(default 16) over a serial pipeline (join_all(immutable_put) → mutable_putwith 200/500/1000 ms retries →announce). Replaces the per-messagetokio::spawnrace that previously advertised FeedRecords whose referenced immutables had not yet propagated. Stdin EOF triggers a banner-prefixed graceful drain by default;--stay-after-eofpreserves the script-then-watch flow./inboxslash command (shared betweenchat joinand thechat inboxCLI; brief-lock concurrency, lock never held across.await).chat dmbecomes a thin consumer ofchat::sessionand gets the TUI / status bar / scroll history / inbox monitor / slash commands / all the same flags for free (join.rsgoes from ~666 → ~141 LOC; the bulk ofdm's command path now lives indm_cmd.rsat ~145 LOC, withdm.rsreduced to a small helper shim).NameResolverextracted as canonical pubkey → display-name resolution (friend alias > known screen name > vendor fallback).cmd/chat/tui/module (mod,status,commands,input,interactive,line,terminal). Auto-selection between interactive TUI (TTY stdin + stdout) and line mode (everything else). Pinned status bar with activity dot, sticky-width left segments, centered INBOX segment (yellow-on-black when unread > 0), and right-sideFeeds/DHT/ channel segments. Bounded 500-line in-memory scrollback ring replays through the scroll region on resize /Ctrl-L. Ctrl-C semantics: nonempty buffer clears, empty buffer arms a 2 s force-quit window with a transient overlay. Backpressured publisher sends are wrapped in aselect!withctrl_cso SIGINT stays responsive even mid-batch. RAIITerminalGuardrestores raw mode / bracketed paste / cursor / colors / scroll region on every exit path (clean shutdown, Ctrl-C, panic).ddprogress reads live wire-byte counters fromHyperDhtHandle::wire_counters()and displays aW ↑ rate ↓ rate (×amp)line.-v / --verbosecount flag (warn/info/debug;RUST_LOGoverrides). New global--no-publicflag. Runtime bootstrap resolution (incmd/mod.rs::resolve_bootstrap) is two-stage: CLI--bootstrapoverrides the config file'snetwork.bootstrapfor the base list (not additive), then--publicadds default public HyperDHT bootstrap nodes, an empty list auto-fills with the defaults, and--no-publicremoves the defaults. The legacy--firewalledflag is replaced by--no-public+ the auto-public default. Chat-scoped globals:--debug,--probe,--line-mode(env:PEEROXIDE_LINE_MODE=1).peeroxide-init(1)andpeeroxide-chat(1)join the existing pages.peeroxide init --man-pages [PATH]replaces the old--generate-man <DIR>flag and cleans stalepeeroxide*.1before writing.tests/chat_integration.rs(~889 LOC).tests/local_commands.rsexpanded from ~225 to ~1063 LOC.tests/live_commands.rsupdated for the renameddd put/dd getsurface. Unit-test coverage added acrosschat::ordering,chat::name_resolver,chat::inbox_monitor,chat::tui::status,chat::tui::interactive,chat::tui::commands,chat::tui::input, anddd v2 v2::need::compute_need_entries.peeroxide-dhtWireCounterstype inio.rs(atomicbytes_sent/bytes_receivedhandles +new+snapshot).Iogains apub wire: WireCountersfield and awire_counters()accessor.DhtHandleandHyperDhtHandlegainwire_stats() -> (u64, u64)andwire_counters() -> WireCounters. Drives theddprogress amplification line.rpc.rsWireCountersthroughDhtHandleand the spawn path. No existing signature changes.hyperdht.rspeeroxideCargo.tomlpeeroxide-dhtdependency from1.2.0to1.3.0. No source changes.Documentation (
docs/)docs/src/init/overview.md;docs/src/chat/{overview,user-guide,interactive-tui,wire-format,protocol,reference}.md.docs/src/dd/{overview,architecture,format,operations,future-direction}.md(now cover v1 + v2 side-by-side;future-direction.mdcollapsed to a one-liner stating v2 is shipped).docs/src/SUMMARY.md(addsSetup → initandCommands → chatsections);docs/src/introduction.md(mentionsinit,chat, dd v1/v2; lists eight primary commands);docs/src/appendices/limits-and-performance.md(newChat+Dead Drop (v2)constants tables);docs/src/appendices/security-model.md(minor edits);docs/AGENTS.md(small note updates).docs/ascii_art.txtadded (decorative ASCII art asset).mdbook build docs/— clean build, no broken links.Repository documentation
.github/PULL_REQUEST_TEMPLATE.mdpeeroxide-cli/README.mdinit,chat,ddv2, and progress flag surface; man-pages section now describes the recursive 23-page output (including nested chat subcommands likepeeroxide-chat-profiles-create(1)andpeeroxide-chat-friends-add(1)); config-path precedence includes$XDG_CONFIG_HOMEand platformdirs::config_dir()lookups.peeroxide-cli/CHANGELOG.md[Unreleased]entries documenting theddrename + new progress UX flags + JSON event schema (the chat/init work is implicitly part of the0.2.0bump documented in Cargo.toml).CONTRIBUTING.mdAGENTS.md,peeroxide-cli/AGENTS.md,docs/AGENTS.mdcmd/chat/tree, the deaddrop split intov1.rs+v2/, current v2 constants (DATA_PAYLOAD_MAX=998,NON_ROOT_INDEX_SLOT_CAP=31,ROOT_INDEX_SLOT_CAP=30,SOFT_DEPTH_CAP=4, etc.), the new chat constants (MAX_RECORD_SIZE=1000,HISTORY_CAP=500,GAP_TIMEOUT=5s, …), and the now-shipped state ofddv2 (the previous "v2 not yet implemented" note is gone).Version bumps
peeroxide-clipeeroxide-dhtpeeroxidepeeroxide-dhtbumped.libudxNew
peeroxide-clidependencies (binary only)blake2,ed25519-dalek,curve25519-dalek,sha2,xsalsa20poly1305,chrono,memmap2,crossterm(withevent-stream+bracketed-paste),arc-swap,toml_edit,fs2. All required by the chat or dd-v2 work. No new library-crate dependencies.Public API Changes
All changes below are: [x] Additive only (semver-compatible) / [ ] Breaking (requires maintainer approval)
peeroxide-dhtio::WireCountersbytes_sent: Arc<AtomicU64>,bytes_received: Arc<AtomicU64>).peeroxide-dhtio::WireCounters::newpeeroxide-dhtio::WireCounters::snapshot(sent, received).peeroxide-dhtio::Io::wireIoalready had private fields, so adding a new field is non-breaking per Cargo SemVer rules).peeroxide-dhtio::Io::wire_counterspeeroxide-dhtrpc::DhtHandle::wire_stats(u64, u64).peeroxide-dhtrpc::DhtHandle::wire_countersWireCounters.peeroxide-dhthyperdht::HyperDhtHandle::wire_stats(u64, u64).peeroxide-dhthyperdht::HyperDhtHandle::wire_countersWireCounters.No existing public symbols were removed, renamed, or had their signatures changed. No breaking changes in
peeroxide,peeroxide-dht, orlibudx.peeroxide-cli's CLI/flag surface changed substantially, but it is a binary and not subject to library SemVer rules.MSRV: unchanged (workspace
rust-version = 1.85).Testing
cargo test --workspace— all suites green (unit + integration + Node.js cross-language interop testhyperswarm_cross_language_connect).cargo test -p peeroxide-cli --test live_commands -- --ignored— public-network live tests green:test_live_lookup,test_live_announce_then_lookup,test_live_cp_send_recv,test_live_dd_roundtrip.cargo clippy --workspace --all-targets -- -D warnings— clean.cargo +1.85.0 check --workspace --all-targets— MSRV gate green (let-chain syntax that was failing on the remoteCheck MSRV workspacejob has been rewritten into stable Rust 1.85 control flow inpeeroxide-cli/src/cmd/chat/{name_resolver,session,tui/interactive}.rsandpeeroxide-cli/tests/chat_integration.rs).mdbook build docs/— clean build, no broken links.Notes
chat::sessionrefactor, status-bar inbox segment, resize replay). The description above reflects the end state, not the commit history.ddv2 wire byte is0x02. The design was iterated under the internal working name "v3"; the canonical spec atpeeroxide-cli/DEADDROP_V2.mdand all user-facing surfaces use v2, and in-tree source comments now point atDEADDROP_V2.mdand the newdocs/src/dd/chapters (no remainingDEADDROP_V3.mdreferences).0x00(the slot is reserved for future per-deaddrop randomization). Documented honestly indocs/src/dd/format.md.chat dmandchat joininteractive sessions share the samechat::sessionorchestration so any future session-level feature (status segment, slash command, shutdown step) lands in one place for both.screen_name+content. Over-length content surfacesWireError::RecordTooLargeat point-of-attempt with a clear error; multi-frame / continuation messages are out of scope by design.Working files cleanup
Design notes, implementation prompts, and working spec drafts that lived next to the code during development have been folded into the
docs/mdBook or deleted from the working tree. None of them ship in the publishedpeeroxide-clicrate. Summary of what happened to each:INIT_COMMAND_PLAN.md(root)*_PLAN.md; never tracked. Not in this PR. Implementation now documented indocs/src/init/overview.md.peeroxide-cli/CHAT.mddocs/src/chat/{wire-format,protocol}.md.peeroxide-cli/CHAT_CLI.mddocs/src/chat/{user-guide,interactive-tui,reference}.md.peeroxide-cli/CHAT_IMPL_PROMPT.mdpeeroxide-cli/DEBUG_FLAG.md--debugflag is implemented and documented indocs/src/chat/user-guide.mdanddocs/src/chat/reference.md.peeroxide-cli/DHT_REF.mddocs/src/concepts/dht-primitives.md; chat-specific size math dropped (already covered bydocs/src/chat/reference.md).peeroxide-cli/DEADDROP_V2.mddocs/src/dd/{architecture,format,operations}.md.docs/ascii_art.txt(The
.claude/,.mcp.json, andtestfilepaths in the workspace root are untracked / gitignored and not in the PR.)