digit-cli 0.3.0

A finger protocol client (RFC 1288 / RFC 742)
Documentation
# digit v0.3.0 Spec -- Hardening, Completions, and Documentation

**Date:** 2026-04-11
**Status:** Draft
**Baseline:** v0.2.0 (commit `d8ee947`)

## Overview

Four areas of improvement: response size capping, raw output mode, shell completion generation, and comprehensive documentation for docs.rs. Documentation is done incrementally with each feature, plus a final pass at the end.

## 1. Response Size Cap

Protect the client from unbounded server responses.

**New CLI flag:**
```
--max-size <BYTES>    Maximum response size in bytes [default: 1048576]
```

Default is 1 MiB (1,048,576 bytes). Overridable by the user.

**Implementation:** The `finger()` and `finger_raw()` functions accept a `max_response_size: u64` parameter. Replace `stream.read_to_end(&mut buf)` with `stream.take(max_response_size).read_to_end(&mut buf)`.

**Truncation behavior:** If the response reaches the cap, return what was read and print a warning to stderr:
```
digit: warning: response truncated at 1048576 bytes (use --max-size to increase)
```

This is a warning, not an error -- the partial response is still printed to stdout. Exit code remains 0.

**How the caller detects truncation:** `finger()` / `finger_raw()` return the data they read. The caller compares `buf.len()` to `max_response_size` -- if equal, the response was likely truncated (it read exactly the cap). Print the warning from `main.rs`.

**Testing:** Integration test with a mock server that sends more bytes than the cap, verifying the response is truncated to the cap size.

## 2. Raw Output Mode

Write raw response bytes to stdout without UTF-8 decoding.

**New CLI flag:**
```
--raw    Write raw response bytes to stdout without UTF-8 decoding
```

**Implementation:** Add `finger_raw()` to `protocol.rs` that returns `Vec<u8>` instead of `String`. Refactor `finger()` to call `finger_raw()` internally and apply `String::from_utf8_lossy`.

```rust
pub fn finger_raw(query: &Query, timeout: Duration, max_response_size: u64) -> Result<Vec<u8>, FingerError> { ... }

pub fn finger(query: &Query, timeout: Duration, max_response_size: u64) -> Result<String, FingerError> {
    let bytes = finger_raw(query, timeout, max_response_size)?;
    Ok(String::from_utf8_lossy(&bytes).into_owned())
}
```

**CLI behavior:** When `--raw` is set, call `finger_raw()` and write bytes to stdout via `std::io::stdout().write_all()`. When not set, call `finger()` as today. Truncation warning goes to stderr regardless.

**Testing:** Integration test verifying that raw mode preserves non-UTF-8 bytes (no replacement characters).

## 3. Shell Completions

Generate shell completions via a `completions` subcommand.

**New dependency:**
```toml
clap_complete = "4"
```

**CLI structure:** Add an optional subcommand to the `Cli` struct:

```rust
#[derive(Subcommand)]
enum Command {
    /// Generate shell completions
    Completions {
        /// Shell to generate completions for
        shell: clap_complete::Shell,
    },
}
```

The `Cli` struct gets `#[command(subcommand)] command: Option<Command>`. When `Some(Command::Completions { shell })`, generate completions to stdout via `clap_complete::generate()` and exit. When `None`, proceed with normal finger query.

**Supported shells:** bash, zsh, fish, powershell, elvish (all handled by `clap_complete::Shell`).

**Usage examples:**
```bash
digit completions bash > ~/.bash_completion.d/digit
digit completions zsh > ~/.zfunc/_digit
digit completions fish > ~/.config/fish/completions/digit.fish
```

## 4. Documentation

Incremental documentation with each feature, plus a final pass.

### Final pass additions

**`src/lib.rs`** -- crate-level `//!` doc comment:
- One-line description
- What the crate provides (query parsing, wire format, TCP client)
- `no_run` example showing `Query::parse` -> `finger()` -> print
- Links to RFC 1288 and RFC 742

**`src/query.rs`** -- module-level `//!` doc comment. Rustdoc example on `Query::parse` showing both success and error cases.

**`src/protocol.rs`** -- module-level `//!` doc comment. Rustdoc examples on `build_query_string` (compilable), `finger` (`no_run`), `finger_raw` (`no_run`).

**`README.md`** -- update:
- Add `--raw` and `--max-size` to options table
- Add "Shell completions" section with per-shell install instructions
- Add docs.rs badge/link for library documentation

### docs.rs build

No special `Cargo.toml` configuration needed. Publishing v0.3.0 to crates.io triggers a docs.rs build automatically. The crate-level doc comment and per-item docs will populate the docs.rs pages.

## Files Changed

| File | Change |
|---|---|
| `Cargo.toml` | Add `clap_complete` dependency, bump version to 0.3.0 |
| `src/lib.rs` | Crate-level `//!` documentation |
| `src/query.rs` | Module-level `//!` doc, rustdoc examples |
| `src/protocol.rs` | Add `finger_raw()`, `max_response_size` parameter, module docs, rustdoc examples |
| `src/main.rs` | `--max-size` flag, `--raw` flag, `completions` subcommand, truncation warning |
| `tests/integration.rs` | Tests for size cap and raw mode |
| `README.md` | New options, shell completions section, docs.rs link |

## Non-breaking API note

The `finger()` signature gains a `max_response_size` parameter. This is a breaking change for library consumers. Combined with the new `finger_raw()` function, this warrants a minor version bump to 0.3.0.