diff --git a/automation/source-repo-templates/api-docs.rust.yml b/automation/source-repo-templates/api-docs.rust.yml index 4d9127aa..74778d4a 100644 --- a/automation/source-repo-templates/api-docs.rust.yml +++ b/automation/source-repo-templates/api-docs.rust.yml @@ -252,22 +252,62 @@ jobs: rustdoc_src = rustdoc_md_dir / meta["lib_name"] if rustdoc_src.is_dir(): # Rich rustdoc output exists. Copy the whole tree - # into OUTPUT_DIR// and inject the version - # banner after the H1 of the landing index.md. + # into OUTPUT_DIR//, then REPLACE the + # auto-generated index.md with a hybrid landing: + # + # 1. version banner + # 2. cleaned README.md from the crate source + # (mermaid diagrams, badges, install + # instructions, etc. — the human-readable + # intro that cargo-doc-md doesn't produce) + # 3. ## Modules section linking to each + # cargo-doc-md per-module page + # + # The original cargo-doc-md index.md content (just + # the lib.rs //! doc + a flat module list) is + # dropped because the README provides the same + # information more richly, and the per-module pages + # keep the full API surface intact. dest = out_root / meta["name"] if dest.exists(): shutil.rmtree(dest) shutil.copytree(rustdoc_src, dest) - landing = dest / "index.md" - if landing.is_file(): - text = landing.read_text(encoding="utf-8") - h1 = re.match(r"(# [^\n]+\n+)", text) - banner = banner_for(meta) - if h1: - text = text[: h1.end()] + banner + text[h1.end():] - else: - text = banner + text - landing.write_text(text, encoding="utf-8") + + module_files = sorted( + p for p in dest.iterdir() + if p.is_file() + and p.suffix == ".md" + and p.stem not in ("index", meta["lib_name"]) + ) + module_lines = [ + f"- [`{p.stem}`](./{p.stem})" + for p in module_files + ] + + readme = crate_dir / "README.md" + body_parts = [f"# {meta['name']}", "", banner_for(meta).rstrip(), ""] + if readme.is_file(): + body = readme.read_text(encoding="utf-8") + body = re.sub(r"^\s*\s*", "", body, count=1, flags=re.S) + body = re.sub(r"^#\s+[^\n]+\n+", "", body, count=1) + body = rewrite_relative_links(body, crate_dir) + body_parts.extend([body.rstrip(), ""]) + + if module_lines: + body_parts.extend([ + "## Modules", + "", + "Click through to each module for the full " + "rustdoc-rendered API surface (types, traits, " + "functions, methods).", + "", + *module_lines, + "", + ]) + + (dest / "index.md").write_text( + "\n".join(body_parts) + "\n", encoding="utf-8" + ) rendered += 1 print(f" rustdoc: {meta['name']}") continue diff --git a/sdks/rust/api/resq-ai/index.md b/sdks/rust/api/resq-ai/index.md index af4da8f2..cee91c02 100644 --- a/sdks/rust/api/resq-ai/index.md +++ b/sdks/rust/api/resq-ai/index.md @@ -1,23 +1,12 @@ -# resq_ai +# resq-ai > **Version:** `v0.1.0` · **License:** `Apache-2.0` · **Crate:** [crates.io](https://crates.io/crates/resq-ai) · **API docs:** [docs.rs](https://docs.rs/resq-ai/0.1.0) -Multi-provider LLM abstraction for `ResQ` developer tools. - -Supports Anthropic, `OpenAI`, and Google Gemini APIs with a unified -`complete()` interface and cascading config resolution. - ## Modules -### [`config`](./config.md) - -*1 function, 1 struct* - -### [`provider`](./provider.md) - -*1 enum, 1 function* - -### [`token`](./token.md) +Click through to each module for the full rustdoc-rendered API surface (types, traits, functions, methods). -*2 functions* +- [`config`](./config) +- [`provider`](./provider) +- [`token`](./token) diff --git a/sdks/rust/api/resq-bin/index.md b/sdks/rust/api/resq-bin/index.md index 3e8638cd..bfcfc486 100644 --- a/sdks/rust/api/resq-bin/index.md +++ b/sdks/rust/api/resq-bin/index.md @@ -1,16 +1,380 @@ -# bin_explorer +# resq-bin > **Version:** `v0.1.16` · **License:** `Apache-2.0` · **Crate:** [crates.io](https://crates.io/crates/resq-bin) · **API docs:** [docs.rs](https://docs.rs/resq-bin/0.1.16) -Core analysis APIs for Binary Explorer. +[![Crates.io](https://img.shields.io/crates/v/resq-bin.svg)](https://crates.io/crates/resq-bin) +[![License](https://img.shields.io/crates/l/resq-bin.svg)](LICENSE) -## Modules +Binary and machine-code analyzer for the ResQ platform. Provides deep inspection of ELF, Mach-O, and PE object files with interactive TUI exploration, Capstone-powered disassembly with objdump fallback, symbol and section analysis, and a persistent CRC32-based cache for fast repeated analysis of large binaries. + +## Capabilities + +- **Multi-format binary parsing** -- ELF, Mach-O, PE, and WASM via the `object` crate. +- **Dual disassembly backends** -- Capstone for high-quality instruction decoding (x86_64, AArch64), with automatic fallback to `llvm-objdump`/`objdump` for missing functions. +- **Interactive TUI** -- Three-pane terminal interface (Targets, Functions, Disassembly) with regex search, function filtering, and keyboard navigation. +- **CLI output modes** -- Human-readable plain text and structured JSON for CI pipelines and tooling integration. +- **Smart caching** -- Persistent analysis cache keyed on file path, size, modification time, and analysis options. Falls back to CRC32 content hashing when mtime is unavailable. +- **Batch analysis** -- Recursive directory scanning with extension filtering for analyzing entire build output trees. +- **Read-only operation** -- Never mutates inspected binaries. + +## Architecture + +```mermaid +flowchart TB + subgraph Input + A[Single File
--file] --> C[BinaryAnalyzer] + B[Directory
--dir --recursive] --> D[Directory Walker] + D --> C + end + + subgraph Parsing ["Parsing (object crate)"] + C --> E[Format Detection
ELF / Mach-O / PE / WASM] + E --> F[Section Extraction] + E --> G[Symbol Collection] + E --> H[Function Discovery
text section symbols] + end + + subgraph Disassembly + H --> I{Capstone
x86_64 / AArch64} + I -->|Success| J[Instruction Decode] + I -->|Failure or gaps| K{objdump Fallback
llvm-objdump / objdump} + K -->|Success| L[Parse objdump Output] + K -->|Failure| M[Warnings] + J --> N[Merge Results] + L --> N + end + + subgraph Cache ["Cache System (.cache/resq/bin-explorer)"] + C <-->|Check / Store| O[CRC32 Cache Key
path + size + mtime + options] + O <--> P[(JSON Cache Files)] + end + + subgraph Output + N --> Q[BinaryReport] + F --> Q + G --> Q + Q --> R{Output Mode} + R -->|--tui / default TTY| S[Interactive TUI
Ratatui + Crossterm] + R -->|--plain| T[Human-readable Text] + R -->|--json| U[Structured JSON] + end +``` + +## Installation + +```bash +# Build from the workspace root +cargo build --release -p resq-bin + +# Binary is placed at: +# target/release/resq-bin + +# Or install directly +cargo install --path resq-bin +``` + +### System Requirements + +- **Rust**: Latest stable toolchain. +- **Capstone**: The `capstone` crate bundles its own copy; no system library required. +- **objdump** (optional): `llvm-objdump` or `objdump` on `PATH` enables the fallback disassembly backend. Analysis works without it, but some functions may lack disassembly when Capstone cannot decode them. + +## CLI Reference + +### Arguments + +| Flag | Default | Description | +|------|---------|-------------| +| `--file ` | -- | Analyze a single binary file. Mutually exclusive with `--dir`. | +| `--dir ` | -- | Analyze all object-like files under a directory. Mutually exclusive with `--file`. | +| `--recursive` | `false` | Recurse into subdirectories when using `--dir`. | +| `--ext ` | -- | Filter files by suffix in `--dir` mode (e.g., `.so`, `.o`). | +| `--no-disasm` | `false` | Skip disassembly; collect only metadata (sections, symbols, entry point). | +| `--max-functions ` | `40` | Maximum number of functions to disassemble per binary. | +| `--config ` | `.resq-bin-explorer.toml` | Path to a TOML configuration file. | +| `--no-cache` | `false` | Disable the result cache entirely (no reads or writes). | +| `--rebuild-cache` | `false` | Ignore existing cache entries and re-analyze all targets. New results are still cached. | +| `--tui` | -- | Force interactive TUI mode. Mutually exclusive with `--json` and `--plain`. | +| `--plain` | `false` | Emit a human-readable non-interactive report. Mutually exclusive with `--json` and `--tui`. | +| `--json` | `false` | Emit a structured JSON report. Mutually exclusive with `--plain` and `--tui`. | + +When none of `--tui`, `--plain`, or `--json` is specified, the output mode is determined automatically: TUI if stdout is a terminal, plain text otherwise. + +## Usage Examples + +### Single file -- interactive TUI (default on a terminal) + +```bash +resq-bin --file target/release/resq +``` + +### Single file -- plain text report + +```bash +resq-bin --file target/release/resq --plain +``` + +Output: + +``` +scan: total=1 processed=1 failed=0 cache_hits=0 +== target/release/resq == +format=Elf arch=X86_64 endian=Little size=4521984B entry=0x5040 +sections=30 symbols=1842 functions=40 +disasm_backend=capstone attempts=capstone: ok +coverage: total=40 with_insn=40 capstone=40 objdump=0 missing=0 + + fn main @ 0x5040 size=128 insn=24 + fn _start @ 0x5000 size=47 insn=11 + ... +``` + +### Single file -- JSON output (for CI) + +```bash +resq-bin --file target/release/resq --json | jq '.stats' +``` + +```json +{ + "total": 1, + "processed": 1, + "failed": 0, + "cache_hits": 0 +} +``` + +### Directory scan -- recursive with extension filter + +```bash +resq-bin --dir target/release --recursive --ext .so --plain +``` + +### Batch analysis -- metadata only (fast) + +```bash +resq-bin --dir target/release --recursive --no-disasm --json > report.json +``` + +### Force TUI with a non-default config + +```bash +resq-bin --file my-service --tui --config my-config.toml +``` + +### Rebuild cache after recompilation + +```bash +resq-bin --dir target/release --recursive --rebuild-cache --plain +``` + +## TUI Mode -### [`bin_explorer`](./bin_explorer.md) +The interactive TUI provides a three-pane layout for exploring binary analysis results. -*1 module* +### Layout + +``` ++-- resq-bin -----------------------------------------------------------+ +| file: target/release/resq [ELF64 x86_64] | ++-----------------------------------------------------------------------+ +| TARGETS | FUNCTIONS | Summary | +| resq [Elf X86_64] | main [0x5040] insn=24 | file: target/release/.. | +| resq-bin [Elf ..] | _start [0x5000] insn=11| format=Elf arch=X86_64 | +| | +-------------------------+ +| | | Disassembly | +| | | 0x5040 push rbp | +| | | 0x5041 mov rbp, rsp | ++-----------------------------------------------------------------------+ +| Q Quit | Tab Focus | / Fn Filter | ? Regex | N Jump | H Help | Normal| ++-----------------------------------------------------------------------+ +``` + +### Keyboard Shortcuts + +| Key | Action | +|-----|--------| +| `q` / `Esc` | Quit the TUI | +| `Tab` | Cycle focus forward: Targets -> Functions -> Disassembly | +| `Shift+Tab` | Cycle focus backward | +| `Left` / `Right` | Switch focus between panes | +| `Up` / `k` | Navigate up in the focused pane | +| `Down` / `j` | Navigate down in the focused pane | +| `Page Up` | Scroll disassembly up by 12 lines | +| `Page Down` | Scroll disassembly down by 12 lines | +| `Home` | Scroll disassembly to top | +| `/` | Open function substring filter dialog | +| `?` | Open disassembly regex search dialog (case-insensitive) | +| `c` | Clear the active function filter | +| `n` | Jump to next match (function filter or disassembly regex) | +| `N` | Jump to previous match | +| `h` | Toggle help overlay | + +## Cache System + +resq-bin maintains a persistent cache of analysis results to avoid redundant heavy disassembly work on unchanged binaries. + +### Cache Location + +The default cache directory is `.cache/resq/bin-explorer` relative to the working directory. This can be overridden with the `cache_dir` key in the configuration file. + +### Cache Key Computation + +Each cache entry is keyed by a CRC32 hash of a fingerprint string that includes: + +1. **Protocol version** (`v5`) -- cache is automatically invalidated on schema changes. +2. **Canonical file path** -- absolute path to the binary. +3. **File size** -- byte length from filesystem metadata. +4. **Analysis options** -- disassembly enabled/disabled, max functions, max symbols, max instructions per function. +5. **Modification time** -- nanosecond-precision mtime when available. +6. **Content CRC32** -- full file content hash as a fallback when mtime is unavailable (e.g., some network filesystems). + +### Cache Behavior + +| Scenario | Behavior | +|----------|----------| +| Default | Read from cache on hit; write new entries after analysis. | +| `--no-cache` | Bypass cache entirely. No reads, no writes. | +| `--rebuild-cache` | Ignore existing entries but write fresh results. | +| Binary recompiled | Cache miss (mtime or size changed); automatic re-analysis. | +| Options changed | Cache miss (different max_functions, disasm toggle, etc.). | + +### Cache Storage Format + +Each entry is stored as a JSON file named `{crc32_hex}.json` containing the full `BinaryReport` structure. + +## Configuration File + +resq-bin reads an optional TOML configuration file. By default it looks for `.resq-bin-explorer.toml` in the current directory, or you can specify a path with `--config`. + +### Format + +```toml +# All fields are optional. CLI flags override config values. + +# Recurse into subdirectories in --dir mode +recursive = true + +# Filter files by extension +ext = ".so" + +# Disable disassembly (metadata only) +no_disasm = false + +# Maximum functions to disassemble per binary +max_functions = 60 + +# Output mode: "tui", "plain", or "json" +output = "tui" + +# Disable cache +no_cache = false + +# Force cache rebuild +rebuild_cache = false + +# Custom cache directory +cache_dir = "/tmp/resq-cache" +``` + +CLI flags always take precedence over configuration file values. + +## JSON Output Format + +The `--json` flag emits a single JSON object with three top-level fields: + +```json +{ + "stats": { + "total": 5, + "processed": 4, + "failed": 1, + "cache_hits": 2 + }, + "reports": [ + { + "path": "target/release/resq", + "format": "Elf", + "architecture": "X86_64", + "endianness": "Little", + "entry": 20544, + "size_bytes": 4521984, + "sections": [ + { "name": ".text", "address": 4096, "size": 1048576, "kind": "Text" } + ], + "symbols": [ + { "name": "main", "address": 20544, "size": 128, "kind": "Text", "is_global": true } + ], + "functions": [ + { + "name": "main", + "address": 20544, + "size": 128, + "instructions": [ + { "address": 20544, "text": "push rbp" }, + { "address": 20545, "text": "mov rbp, rsp" } + ] + } + ], + "disassembly_backend": "capstone", + "disassembly_attempts": ["capstone: ok"], + "disassembly_coverage": { + "total_functions": 40, + "functions_with_instructions": 40, + "capstone_functions": 40, + "objdump_functions": 0, + "missing_functions": 0 + }, + "function_backend_coverage": [ + { "name": "main", "backend": "capstone", "instruction_count": 24 } + ], + "warnings": [] + } + ], + "issues": [ + { "path": "target/release/build-script", "error": "failed to parse object file" } + ] +} +``` + +### Field Reference + +| Field | Type | Description | +|-------|------|-------------| +| `stats.total` | integer | Number of files considered for analysis. | +| `stats.processed` | integer | Number of files successfully analyzed. | +| `stats.failed` | integer | Number of files that produced errors. | +| `stats.cache_hits` | integer | Number of results served from cache. | +| `reports[].format` | string | Object file format (`Elf`, `Pe`, `MachO`, `Wasm`). | +| `reports[].architecture` | string | CPU architecture (`X86_64`, `Aarch64`, etc.). | +| `reports[].entry` | integer | Entrypoint virtual address. | +| `reports[].disassembly_backend` | string or null | Backend used: `capstone`, `objdump`, `capstone+objdump`, or null. | +| `reports[].disassembly_coverage` | object or null | Counts of functions decoded by each backend. | +| `issues[].path` | string | Path to the file that failed analysis. | +| `issues[].error` | string | Error message describing the failure. | + +## Source Layout + +``` +resq-bin/ + src/ + main.rs CLI entry point, argument parsing, mode selection + lib.rs Library root (re-exports analysis module) + tui.rs Interactive TUI (Ratatui + Crossterm) + cache.rs CRC32-based persistent cache system + analysis/ + mod.rs BinaryAnalyzer, Capstone/objdump backends, report types + Cargo.toml Crate manifest +``` + +## License + +Licensed under the Apache License, Version 2.0. See [LICENSE](http://www.apache.org/licenses/LICENSE-2.0) for details. + +## Modules -### [`analysis`](./analysis.md) +Click through to each module for the full rustdoc-rendered API surface (types, traits, functions, methods). -*9 structs* +- [`analysis`](./analysis) +- [`bin_explorer`](./bin_explorer) diff --git a/sdks/rust/api/resq-cli/index.md b/sdks/rust/api/resq-cli/index.md index 3369fdb5..3efb4024 100644 --- a/sdks/rust/api/resq-cli/index.md +++ b/sdks/rust/api/resq-cli/index.md @@ -1,108 +1,590 @@ -# resq_cli +# resq-cli > **Version:** `v0.3.0` · **License:** `Apache-2.0` · **Crate:** [crates.io](https://crates.io/crates/resq-cli) · **API docs:** [docs.rs](https://docs.rs/resq-cli/0.3.0) -`ResQ` CLI - Command-line interface for managing `ResQ` services. +[![Crates.io](https://img.shields.io/crates/v/resq-cli.svg)](https://crates.io/crates/resq-cli) +[![License](https://img.shields.io/crates/l/resq-cli.svg)](LICENSE) + +Unified developer CLI for the ResQ platform. Provides commands for license header management, security auditing, secret scanning, dependency cost analysis, image placeholder generation, documentation export, monorepo versioning, and launching a suite of interactive TUI tools. + +## Overview + +`resq` is the single entry point for all ResQ developer tooling. It wraps standalone TUI applications (logs, health, deploy, perf, clean, asm) and provides built-in commands for CI workflows, pre-commit hooks, and repository maintenance. + +```mermaid +graph TD + resq["resq (CLI)"] + + resq --> copyright["copyright"] + resq --> lqip["lqip"] + resq --> audit["audit"] + resq --> cost["cost"] + resq --> secrets["secrets"] + resq --> tree_shake["tree-shake"] + resq --> pre_commit["pre-commit"] + + resq --> dev["dev"] + dev --> kill_ports["dev kill-ports"] + dev --> sync_env["dev sync-env"] + dev --> upgrade["dev upgrade"] + dev --> install_hooks["dev install-hooks"] + + resq --> version["version"] + version --> version_add["version add"] + version --> version_apply["version apply"] + version --> version_check["version check"] + + resq --> docs["docs"] + + resq --> explore["explore (resq-perf)"] + resq --> logs["logs (resq-logs)"] + resq --> health["health (resq-health)"] + resq --> deploy["deploy (resq-deploy)"] + resq --> clean["clean (resq-clean)"] + resq --> asm["asm (resq-bin)"] + + style resq fill:#2563eb,color:#fff + style dev fill:#7c3aed,color:#fff + style version fill:#7c3aed,color:#fff + style explore fill:#059669,color:#fff + style logs fill:#059669,color:#fff + style health fill:#059669,color:#fff + style deploy fill:#059669,color:#fff + style clean fill:#059669,color:#fff + style asm fill:#059669,color:#fff +``` + +**Legend:** Blue = entry point, Purple = commands with subcommands, Green = TUI launchers. + +## Installation + +```bash +# Build from the workspace root +cargo build --release -p resq-cli + +# Install globally +cargo install --path cli + +# Or use the cargo alias (defined in .cargo/config.toml) +cargo resq help +``` + +The binary is installed as `resq` at `target/release/resq`. + +### Cargo Aliases + +The following workspace aliases are defined in `.cargo/config.toml`: + +| Alias | Maps to | +| ----------------- | -------------------- | +| `cargo resq` | `resq` | +| `cargo health` | `resq health` | +| `cargo logs` | `resq logs` | +| `cargo perf` | `resq explore` | +| `cargo deploy` | `resq deploy` | +| `cargo cleanup` | `resq clean` | +| `cargo bin` | `resq asm` | +| `cargo check-all` | workspace check | +| `cargo t` | workspace test | +| `cargo c` | workspace clippy | +| `cargo flame` | `resq-flame` binary | + +--- + +## Commands + +### `copyright` -- License Header Management + +Adds or checks copyright/license headers across all source files in the repository. Supports multiple comment styles (C-style block, XML/HTML, hash-line, double-dash, Elisp, AsciiDoc). Shebangs (`#!/...`) are always preserved at line 0. + +#### Arguments + +| Flag / Option | Type | Default | Description | +| --- | --- | --- | --- | +| `-l, --license ` | `String` | `apache-2.0` | License type: `apache-2.0`, `mit`, `gpl-3.0`, `bsd-3-clause` | +| `-a, --author ` | `String` | `ResQ` | Copyright holder name | +| `-y, --year ` | `String` | current year | Copyright year | +| `--force` | `bool` | `false` | Overwrite existing headers | +| `--dry-run` | `bool` | `false` | Preview changes without writing files | +| `--check` | `bool` | `false` | CI mode -- exits non-zero if any headers are missing | +| `-v, --verbose` | `bool` | `false` | Print detailed processing info | +| `--glob ...` | `Vec` | none | Glob patterns to match files (e.g. `src/**/*.rs`) | +| `--ext ` | `Vec` | none | Comma-separated file extensions to include (e.g. `rs,js,py`) | +| `-e, --exclude ...` | `Vec` | none | Patterns to exclude from processing | + +#### Examples + +```bash +# Check all tracked files (CI -- exits 1 if any missing) +resq copyright --check + +# Preview what would be added without writing +resq copyright --dry-run + +# Add headers to all files missing them +resq copyright + +# Overwrite existing headers with a different license and author +resq copyright --force --license apache-2.0 --author "Acme Corp" --year 2026 + +# Process only Rust and TypeScript files +resq copyright --ext rs,ts + +# Process files matching a glob pattern +resq copyright --glob "crates/**/*.rs" +``` + +--- + +### `lqip` -- Low-Quality Image Placeholders + +Generates tiny base64-encoded data URIs from images for use as blur-up placeholders in web applications. Supports JPEG, PNG, and WebP formats. + +#### Arguments + +| Flag / Option | Type | Default | Description | +| --- | --- | --- | --- | +| `-t, --target ` | `String` | *required* | Directory or file to process | +| `--width ` | `u32` | `20` | Width of the generated LQIP in pixels | +| `--height ` | `u32` | `15` | Height of the generated LQIP in pixels | +| `-r, --recursive` | `bool` | `false` | Recursively search directories for images | +| `--format ` | `String` | `text` | Output format: `text` or `json` | + +#### Examples + +```bash +# Single image -- prints data URI +resq lqip --target hero.jpg + +# Directory of images -- text list +resq lqip --target public/images/ + +# Recursive with JSON output +resq lqip --target public/ --recursive --format json + +# Custom dimensions +resq lqip --target photo.png --width 32 --height 24 +``` + +--- + +### `audit` -- Security & Quality Audit -This crate provides a unified CLI for interacting with the `ResQ` platform, -including service management, blockchain queries, and deployment operations. +Three-pass security and quality sweep covering all language ecosystems. Runs Google OSV Scanner (cross-ecosystem), npm audit-ci, and React Doctor. -# Commands +#### Arguments -Grouped: -- `scan audit` — run cargo/bun/uv audit across the workspace -- `scan secrets` — scan for leaked credentials -- `scan copyright` — check or apply license headers -- `tui explore` / `logs` / `health` / `deploy` / `clean` / `asm` — TUI explorers +| Flag / Option | Type | Default | Description | +| --- | --- | --- | --- | +| `--root ` | `PathBuf` | `.` | Root directory to start search from | +| `--level ` | `String` | `critical` | Minimum npm vulnerability severity to fail on (`critical`, `high`, `moderate`, `low`) | +| `--report-type ` | `String` | `important` | audit-ci report verbosity (`important`, `full`, `summary`) | +| `--skip-prepare` | `bool` | `false` | Skip the yarn.lock generation step required by audit-ci | +| `--skip-npm` | `bool` | `false` | Skip the npm audit-ci pass | +| `--skip-osv` | `bool` | `false` | Skip the Google OSV Scanner pass | +| `--osv-format ` | `String` | `table` | OSV Scanner output format (`table`, `json`, `sarif`, `gh-annotations`) | +| `--skip-react` | `bool` | `false` | Skip the react-doctor pass | +| `--react-target ` | `PathBuf` | `/services/web-dashboard` | Path to the React/Next.js project for react-doctor | +| `--react-diff ` | `String` | none | Only scan React files changed vs this base branch | +| `--react-min-score ` | `u8` | `75` | Minimum react-doctor health score to pass (0-100) | -Top-level: -- `format` — format Rust / TS / Python / C++ / C# in one pass -- `pre-commit` — full pre-commit gate (copyright, secrets, audit, format) -- `hooks` — inspect / update installed git hooks -- `dev` — repository utilities (workspace ops) -- `version` / `docs` / `commit` — release + docs + AI commit messages -- `completions` — emit shell completions for bash/zsh/fish/elvish/powershell +#### Examples -Legacy flat forms (`resq audit`, `resq explore`, etc.) remain as hidden -aliases for one release cycle. +```bash +# Full audit (all three passes) +resq audit + +# Run only the OSV Scanner pass +resq audit --skip-npm --skip-react + +# Strict npm audit (fail on moderate) +resq audit --skip-osv --skip-react --level moderate + +# OSV Scanner with JSON output +resq audit --skip-npm --skip-react --osv-format json + +# React Doctor with custom threshold and diff mode +resq audit --skip-osv --skip-npm --react-min-score 90 --react-diff main +``` + +--- + +### `cost` -- Dependency Size Analysis + +Fetches package sizes from registries (npm, crates.io, PyPI) and categorizes dependencies by download footprint into high (>10 MB), medium (1-10 MB), and low (<1 MB) buckets. Results are saved as JSON files. + +#### Arguments + +| Flag / Option | Type | Default | Description | +| --- | --- | --- | --- | +| `--root ` | `PathBuf` | `.` | Root directory containing project manifest | +| `--output ` | `PathBuf` | `scripts/out` | Output directory for result JSON files | +| `--project-type ` | `String` | auto-detected | Force a specific project type: `node`, `rust`, `python` | -# Usage +#### Examples ```bash -resq scan audit -resq format --check +# Auto-detect project type and analyze +resq cost + +# Analyze a specific project directory +resq cost --root services/coordination-hce + +# Force Rust analysis and custom output +resq cost --project-type rust --output reports/deps +``` + +--- + +### `secrets` -- Secret Scanner + +Scans source files for hardcoded credentials, API keys, private keys, tokens, and high-entropy strings. Uses pattern matching with entropy analysis (Shannon entropy with charset-specific thresholds for hex, base64, and alphanumeric strings) and Aho-Corasick multi-pattern matching for performance. + +#### Arguments + +| Flag / Option | Type | Default | Description | +| --- | --- | --- | --- | +| `--root ` | `PathBuf` | `.` | Root directory to scan | +| `--git-only` | `bool` | `true` | Only scan git-tracked files | +| `-v, --verbose` | `bool` | `false` | Show verbose output (print matched content) | +| `--allowlist ` | `PathBuf` | none | Path to allowlist file (one pattern per line) | +| `--staged` | `bool` | `false` | Scan only staged git changes (for pre-commit hook) | +| `--history` | `bool` | `false` | Also scan git history (all commits reachable from HEAD) | +| `--since ` | `String` | none | Limit history scan to commits after this rev/date (e.g. `"30 days ago"`, `"v1.0.0"`) | + +#### Examples + +```bash +# Scan all git-tracked files (default) +resq secrets + +# Only scan staged changes (pre-commit hook) +resq secrets --staged + +# Scan with history and allowlist +resq secrets --history --since "v1.0.0" --allowlist .secrets-allowlist + +# Verbose output showing matched content +resq secrets --verbose +``` + +--- + +### `tree-shake` -- TypeScript Dead Code Removal + +Runs [`tsr`](https://github.com/line/ts-remove-unused) to remove unused TypeScript exports from project entry points. Requires `bun` to be installed. + +#### Arguments + +This command takes no arguments. + +#### Examples + +```bash +resq tree-shake +``` + +--- + +### `dev` -- Development Utilities + +Unified entry point for repository-level development tasks. + +#### Subcommands + +##### `dev kill-ports` -- Kill Processes on Ports + +Finds and terminates processes listening on specified TCP ports. + +| Flag / Option | Type | Default | Description | +| --- | --- | --- | --- | +| `...` | `Vec` | *required* | Ports or ranges (e.g. `8000` or `8000..8010`) | +| `-f, --force` | `bool` | `false` | Use SIGKILL instead of SIGTERM | +| `-y, --yes` | `bool` | `false` | Skip confirmation prompt | + +```bash +# Kill process on port 3000 +resq dev kill-ports 3000 + +# Kill range of ports without confirmation +resq dev kill-ports 8000..8010 --yes + +# Force kill +resq dev kill-ports 3000 --force +``` + +##### `dev sync-env` -- Sync Environment Variables to turbo.json + +Scans `.env.example` files across the monorepo and synchronizes discovered environment variable names into `turbo.json` task configurations. + +| Flag / Option | Type | Default | Description | +| --- | --- | --- | --- | +| `-t, --tasks ` | `String` | `build,dev,start,test` | Comma-separated tasks to update in turbo.json | +| `-d, --dry-run` | `bool` | `false` | Preview changes without writing | +| `--max-depth ` | `usize` | `10` | Maximum directory depth to search | + +```bash +# Sync all environment variables +resq dev sync-env + +# Preview changes +resq dev sync-env --dry-run + +# Sync only for build and dev tasks +resq dev sync-env --tasks build,dev +``` + +##### `dev upgrade` -- Upgrade Dependencies + +Upgrades dependencies across all language silos in the monorepo (Python/uv, Rust/cargo, JS/bun, C++/Conan, C#/dotnet, Nix). + +| Flag / Option | Type | Default | Description | +| --- | --- | --- | --- | +| `` | `String` | `all` | Silo to upgrade: `python`, `rust`, `js`, `cpp`, `csharp`, `nix`, or `all` | + +```bash +# Upgrade all ecosystems +resq dev upgrade + +# Upgrade only Rust dependencies +resq dev upgrade rust + +# Upgrade only JavaScript dependencies +resq dev upgrade js +``` + +##### `dev install-hooks` -- Install Git Hooks + +Configures `git core.hooksPath` to point at the `.git-hooks` directory and makes all hook scripts executable. + +```bash +resq dev install-hooks +``` + +--- + +### `pre-commit` -- Unified Pre-Commit Hook + +Runs a suite of checks suitable for a git pre-commit hook: copyright headers, secret scanning, formatting, audits, and versioning prompts. Includes an interactive TUI progress display. + +#### Arguments + +| Flag / Option | Type | Default | Description | +| --- | --- | --- | --- | +| `--root ` | `PathBuf` | `.` | Project root (defaults to auto-detected) | +| `--skip-audit` | `bool` | `false` | Skip security audit (osv-scanner + npm audit-ci) | +| `--skip-format` | `bool` | `false` | Skip formatting step | +| `--skip-versioning` | `bool` | `false` | Skip changeset/versioning prompt | +| `--max-file-size ` | `u64` | `10485760` (10 MiB) | Maximum file size in bytes | +| `--no-tui` | `bool` | `false` | Disable TUI (plain output for CI or piped stderr) | + +#### Examples + +```bash +# Run all pre-commit checks resq pre-commit -resq tui health -resq completions bash > /usr/local/share/bash-completion/completions/resq + +# Skip audit and formatting (fast mode) +resq pre-commit --skip-audit --skip-format + +# CI-friendly plain output +resq pre-commit --no-tui ``` -## Modules +--- -### [`resq_cli`](./resq_cli.md) +### `version` -- Monorepo Versioning -*3 modules* +Manages package versions and changesets across the monorepo using a changeset-based workflow. Supports Cargo.toml, package.json, pyproject.toml, and Directory.Build.props manifests. -### [`commands`](./commands.md) +#### Subcommands -*13 modules* +##### `version add` -- Create a Changeset -### [`commands::audit`](./commands/audit.md) +| Flag / Option | Type | Default | Description | +| --- | --- | --- | --- | +| `-b, --bump ` | `String` | `patch` | Type of change: `patch`, `minor`, `major` | +| `-m, --message ` | `String` | *required* | Summary of what changed | -*1 function, 1 struct* +```bash +resq version add --bump minor --message "Add new health check endpoint" +``` -### [`commands::commit`](./commands/commit.md) +##### `version apply` -- Apply Version Bumps -*1 function, 1 struct* +Consumes all pending changesets, determines the highest bump level, updates all manifests, and appends to CHANGELOG.md. -### [`commands::completions`](./commands/completions.md) +| Flag / Option | Type | Default | Description | +| --- | --- | --- | --- | +| `--dry-run` | `bool` | `false` | Preview what would change without modifying files | -*1 function, 1 struct* +```bash +# Apply version bumps +resq version apply -### [`commands::copyright`](./commands/copyright.md) +# Preview only +resq version apply --dry-run +``` + +##### `version check` -- Verify Version Sync + +Checks that all manifest files contain the same version string. + +```bash +resq version check +``` + +--- + +### `docs` -- Documentation Export + +Exports and publishes OpenAPI specifications from the Infrastructure API and Coordination HCE services. + +#### Arguments + +| Flag / Option | Type | Default | Description | +| --- | --- | --- | --- | +| `-e, --export-only` | `bool` | `false` | Only export specs locally without publishing | +| `-p, --publish` | `bool` | `false` | Publish specifications to the documentation repository via GitHub API | +| `--dry-run` | `bool` | `false` | Show what would be done without executing | + +#### Examples + +```bash +# Export specs locally +resq docs --export-only + +# Export and publish to GitHub +resq docs --publish + +# Preview what would happen +resq docs --dry-run +``` + +--- + +### TUI Launchers + +These commands launch standalone TUI applications from the ResQ workspace. Each delegates to a separate binary via `cargo run -p `. -*1 function, 1 struct* +#### `explore` -- Performance Monitor (resq-perf) -### [`commands::dev`](./commands/dev.md) +| Flag / Option | Type | Default | Description | +| --- | --- | --- | --- | +| `` | `String` | `http://localhost:3000/admin/status` | Service URL to monitor | +| `--refresh-ms ` | `u64` | `500` | Refresh rate in milliseconds | -*1 enum, 3 functions, 5 structs* +```bash +resq explore +resq explore http://localhost:8080/status --refresh-ms 1000 +``` + +#### `logs` -- Log Aggregator (resq-logs) + +| Flag / Option | Type | Default | Description | +| --- | --- | --- | --- | +| `--source ` | `String` | `docker` | Log source: `docker` or `file` | +| `--service ` | `String` | none | Filter to a specific service name | + +```bash +resq logs +resq logs --source file --service edge-aeai +``` + +#### `health` -- Health Dashboard (resq-health) + +| Flag / Option | Type | Default | Description | +| --- | --- | --- | --- | +| `-i, --interval ` | `u64` | `5` | Poll interval in seconds | + +```bash +resq health +resq health --interval 10 +``` + +#### `deploy` -- Deployment Manager (resq-deploy) -### [`commands::docs`](./commands/docs.md) +| Flag / Option | Type | Default | Description | +| --- | --- | --- | --- | +| `--env ` | `String` | `dev` | Target environment: `dev`, `staging`, `prod` | +| `--k8s` | `bool` | `false` | Use Kubernetes instead of Docker Compose | -*1 function, 1 struct* +```bash +resq deploy +resq deploy --env staging --k8s +``` -### [`commands::explore`](./commands/explore.md) +#### `clean` -- Workspace Cleaner (resq-clean) -*6 functions, 6 structs* +| Flag / Option | Type | Default | Description | +| --- | --- | --- | --- | +| `--dry-run` | `bool` | `false` | Preview what would be deleted without removing anything | -### [`commands::format`](./commands/format.md) +```bash +resq clean +resq clean --dry-run +``` -*1 enum, 1 struct, 6 functions* +#### `asm` -- Binary Analyzer (resq-bin) + +| Flag / Option | Type | Default | Description | +| --- | --- | --- | --- | +| `--file ` | `String` | none | Analyze a single binary (conflicts with `--dir`) | +| `--dir ` | `String` | none | Analyze binaries under a directory (conflicts with `--file`) | +| `--recursive` | `bool` | `false` | Recursively traverse directory in batch mode | +| `--ext ` | `String` | none | Suffix filter in batch mode (e.g. `.so`, `.o`) | +| `--config ` | `String` | none | Path to resq-bin config TOML | +| `--no-cache` | `bool` | `false` | Disable cache reads/writes | +| `--rebuild-cache` | `bool` | `false` | Force cache rebuild | +| `--no-disasm` | `bool` | `false` | Only collect metadata, skip disassembly | +| `--max-functions ` | `usize` | none | Maximum functions to disassemble per binary | +| `--tui` | `bool` | `false` | Force interactive TUI mode | +| `--plain` | `bool` | `false` | Use non-interactive plain output | +| `--json` | `bool` | `false` | Emit JSON report output | -### [`commands::hook_templates`](./commands/hook_templates.md) +```bash +resq asm --file target/release/resq +resq asm --dir target/release/ --recursive --ext .so +resq asm --file mylib.o --plain --no-cache +resq asm --dir . --recursive --json --max-functions 100 +``` -*1 constant, 1 function* +--- -### [`commands::hooks`](./commands/hooks.md) +## Environment Variables -*1 enum, 1 struct, 2 functions* +| Variable | Used By | Description | +| --- | --- | --- | +| `RUST_LOG` | all commands | Controls `tracing-subscriber` log level (e.g. `debug`, `info`, `warn`) | +| `GH_TOKEN` / `GITHUB_TOKEN` | `docs --publish` | GitHub API authentication for publishing specs | -### [`commands::pre_commit`](./commands/pre_commit.md) +The `pre-commit` and `audit` commands shell out to external tools (`osv-scanner`, `bun`, `npx`, `audit-ci`, `react-doctor`) which may read their own environment variables. -*1 function, 1 struct* +## Configuration -### [`commands::secrets`](./commands/secrets.md) +- **Project root detection**: The CLI walks up the directory tree looking for `resQ.sln`, `package.json`, `Cargo.toml`, `pyproject.toml`, or `.git` to locate the project root. +- **Gitignore integration**: The `secrets` and `copyright` commands parse `.gitignore` for directory exclusion. When `.gitignore` is missing, a built-in fallback list is used (`node_modules`, `.git`, `dist`, `build`, `.next`, `target`, `__pycache__`, `.venv`, `venv`, `vendor`, `.turbo`, `coverage`). +- **OSV Scanner config**: If `osv-scanner.toml` exists at the project root, it is passed automatically to the `audit` command. +- **Secrets allowlist**: Create a text file with one pattern per line and pass it via `--allowlist`. +- **Changesets**: Version changesets are stored as markdown files in `.changesets/` at the repository root. -*1 function, 1 struct* +## Exit Codes -### [`commands::version`](./commands/version.md) +| Code | Meaning | +| --- | --- | +| `0` | Success | +| `1` | Command failed (e.g. audit found vulnerabilities, copyright headers missing in `--check` mode, secrets detected, versions out of sync) | +| `2` | CLI argument parsing error | -*1 enum, 1 function, 3 structs* +## License -### [`gitignore`](./gitignore.md) +Licensed under the Apache License, Version 2.0. See [LICENSE](https://github.com/resq-software/crates/blob/master/LICENSE) for the full text. -*2 functions* +## Modules -### [`utils`](./utils.md) +Click through to each module for the full rustdoc-rendered API surface (types, traits, functions, methods). -*1 function* +- [`commands`](./commands) +- [`gitignore`](./gitignore) +- [`utils`](./utils) diff --git a/sdks/rust/api/resq-dsa/index.md b/sdks/rust/api/resq-dsa/index.md index 9616dab7..7dc61f20 100644 --- a/sdks/rust/api/resq-dsa/index.md +++ b/sdks/rust/api/resq-dsa/index.md @@ -1,67 +1,455 @@ -# resq_dsa +# resq-dsa > **Version:** `v0.1.3` · **License:** `Apache-2.0` · **Crate:** [crates.io](https://crates.io/crates/resq-dsa) · **API docs:** [docs.rs](https://docs.rs/resq-dsa/0.1.3) -Production-grade data structures and algorithms — zero external dependencies. +[![Crates.io](https://img.shields.io/crates/v/resq-dsa.svg)](https://crates.io/crates/resq-dsa) +[![docs.rs](https://img.shields.io/docsrs/resq-dsa)](https://docs.rs/resq-dsa) +[![License](https://img.shields.io/crates/l/resq-dsa.svg)](https://github.com/wombocombo/wrk/blob/master/LICENSE) -A collection of space-efficient probabilistic data structures and -graph algorithms for general-purpose use. +Production-grade data structures and algorithms with **zero external dependencies**. Designed for `no_std` environments and embedded systems while remaining ergonomic in standard Rust applications. The crate provides space-efficient probabilistic data structures (Bloom filter, Count-Min sketch), graph algorithms (BFS, Dijkstra, A\*), a bounded heap for K-nearest-neighbor tracking, a trie for prefix-based search, and Rabin-Karp rolling-hash string matching. -# Modules +## Module Structure -- [`bloom`] - Bloom filter for approximate set membership -- [`count_min`] - Count-Min sketch for frequency estimation -- [`graph`] - Graph algorithms (BFS, Dijkstra, A*) -- [`heap`] - Bounded heap for K-nearest neighbor tracking -- [`trie`] - Trie prefix tree and Rabin-Karp string matching +```mermaid +graph TD + A[resq-dsa] --> B[bloom] + A --> C[count_min] + A --> D[graph] + A --> E[heap] + A --> F[trie] -# Usage + B --> B1[BloomFilter] + C --> C1[CountMinSketch] + D --> D1["Graph<Id>"] + D1 --> D2[bfs] + D1 --> D3[dijkstra] + D1 --> D4[astar] + E --> E1["BoundedHeap<T, D>"] + F --> F1[Trie] + F --> F2[rabin_karp] + style A fill:#2d333b,stroke:#539bf5,color:#adbac7 + style B fill:#2d333b,stroke:#57ab5a,color:#adbac7 + style C fill:#2d333b,stroke:#57ab5a,color:#adbac7 + style D fill:#2d333b,stroke:#57ab5a,color:#adbac7 + style E fill:#2d333b,stroke:#57ab5a,color:#adbac7 + style F fill:#2d333b,stroke:#57ab5a,color:#adbac7 ``` + +## Feature Flags + +| Feature | Default | Description | +|---------|---------|-------------| +| `std` | Yes | Enables standard library support. Disable for `no_std` environments (the crate uses `alloc` internally). | + +## Installation + +### With `std` (default) + +```toml +[dependencies] +resq-dsa = "0.1" +``` + +### `no_std` environments + +```toml +[dependencies] +resq-dsa = { version = "0.1", default-features = false } +``` + +When `std` is disabled the crate compiles with `#![no_std]` and relies only on `alloc`. You must provide a global allocator in your binary. + +--- + +## Data Structures + +### Bloom Filter + +A space-efficient probabilistic set-membership data structure. It can tell you if an element is **possibly** in the set or **definitely not** in the set. False positives are possible; false negatives are not. + +The filter uses `k` independent FNV-1a hash functions (seeded variants) to set bits in an `m`-bit array. Optimal values for `k` and `m` are computed automatically from the desired capacity and false-positive rate. + +#### Complexity + +| Operation | Time | Space | +|-----------|--------|-------| +| `new` | O(m) | O(m) | +| `add` | O(k) | -- | +| `has` | O(k) | -- | +| `len` | O(1) | -- | +| `is_empty`| O(1) | -- | +| `clear` | O(m) | -- | + +Where **m** is the bit-array size and **k** is the number of hash functions (both derived from `capacity` and `error_rate`). + +#### API Reference + +| Method | Signature | Description | +|--------|-----------|-------------| +| `new` | `fn new(capacity: usize, error_rate: f64) -> Self` | Creates a new Bloom filter sized for `capacity` elements at the given false-positive `error_rate` (must be in `(0, 1)`). Panics if `error_rate` is out of range or `capacity` is zero. | +| `add` | `fn add(&mut self, item: impl AsRef<[u8]>)` | Adds an element to the filter. Accepts `&str`, `String`, `&[u8]`, `Vec`, etc. | +| `has` | `fn has(&self, item: impl AsRef<[u8]>) -> bool` | Returns `true` if the element is possibly in the set, `false` if it is definitely absent. | +| `len` | `const fn len(&self) -> usize` | Returns the number of elements that have been added. | +| `is_empty` | `const fn is_empty(&self) -> bool` | Returns `true` if no elements have been added. | +| `clear` | `fn clear(&mut self)` | Resets the filter, removing all elements. | + +#### Example + +```rust use resq_dsa::bloom::BloomFilter; -use resq_dsa::count_min::CountMinSketch; -use resq_dsa::graph::Graph; -// Bloom filter for deduplication -let mut bf = BloomFilter::new(1000, 0.01); +// Create a filter for 10,000 items with a 1% false-positive rate +let mut bf = BloomFilter::new(10_000, 0.01); + bf.add("drone-001"); -assert!(bf.has("drone-001")); +bf.add("drone-002"); +bf.add(b"raw-bytes" as &[u8]); + +assert!(bf.has("drone-001")); // definitely present +assert!(!bf.has("drone-999")); // definitely absent +assert_eq!(bf.len(), 3); + +bf.clear(); +assert!(bf.is_empty()); +assert!(!bf.has("drone-001")); // cleared +``` + +--- + +### Count-Min Sketch + +A space-efficient probabilistic data structure for **frequency estimation**. It may overcount but never undercounts. Estimates are within `epsilon * N` of the true count with probability `1 - delta`, where `N` is the total count of all increments. -// Count-Min for frequency tracking +Uses `depth` independent FNV-1a hash functions mapping elements to `width` columns. The estimated count for a key is the **minimum** across all rows. + +#### Complexity + +| Operation | Time | Space | +|-------------|----------|----------------| +| `new` | O(w * d) | O(w * d) | +| `increment` | O(d) | -- | +| `estimate` | O(d) | -- | + +Where **w** = `ceil(e / epsilon)` (width) and **d** = `ceil(ln(1 / delta))` (depth). + +#### API Reference + +| Method | Signature | Description | +|--------|-----------|-------------| +| `new` | `fn new(epsilon: f64, delta: f64) -> Self` | Creates a new sketch. `epsilon` controls error magnitude, `delta` controls failure probability. Both must be in `(0, 1)`. | +| `increment` | `fn increment(&mut self, key: impl AsRef<[u8]>, count: u64)` | Increments the count for `key` by `count`. Counts are stored as `u64` and saturate on overflow. | +| `estimate` | `fn estimate(&self, key: impl AsRef<[u8]>) -> u64` | Returns the estimated count for `key`. Guaranteed to be >= the true count. | + +#### Example + +```rust +use resq_dsa::count_min::CountMinSketch; + +// Estimates within 1% of true count, 99% of the time let mut cms = CountMinSketch::new(0.01, 0.01); -cms.increment("sensor-reading", 5); -// Graph for pathfinding +cms.increment("sensor-temp-high", 5); +cms.increment("sensor-temp-high", 3); +cms.increment("sensor-humidity", 1); + +let temp_count = cms.estimate("sensor-temp-high"); +assert!(temp_count >= 8); // never undercounts + +// Works with byte slices +cms.increment(b"raw-key" as &[u8], 10); +assert!(cms.estimate(b"raw-key" as &[u8]) >= 10); + +// Unknown keys return 0 +assert_eq!(cms.estimate("never-seen"), 0); +``` + +--- + +### Graph (Weighted Directed) + +A weighted directed graph with three pathfinding algorithms: breadth-first search (BFS), Dijkstra's shortest path, and A\* with a user-provided heuristic. + +Node identifiers can be any type that implements `Eq + Hash + Clone` (and additionally `Ord` for Dijkstra and A\*). Edge weights are `u64`. + +```mermaid +graph LR + A["base"] -- "100" --> B["waypoint-1"] + B -- "50" --> C["waypoint-2"] + A -- "200" --> C + C -- "25" --> D["target"] + + style A fill:#2d333b,stroke:#539bf5,color:#adbac7 + style D fill:#2d333b,stroke:#e5534b,color:#adbac7 +``` + +#### Complexity + +| Operation | Time | Space | +|------------|-----------------------|--------------| +| `new` | O(1) | O(1) | +| `add_edge` | O(1) amortized | O(1) | +| `bfs` | O(V + E) | O(V) | +| `dijkstra` | O((V + E) log V) | O(V) | +| `astar` | O((V + E) log V) * | O(V) | + +\* A\* worst case matches Dijkstra; with a good heuristic it explores fewer nodes. + +#### API Reference + +| Method | Signature | Description | +|--------|-----------|-------------| +| `new` | `fn new() -> Self` | Creates a new empty directed graph. Also implements `Default`. | +| `add_edge` | `fn add_edge(&mut self, from: Id, to: Id, weight: u64)` | Adds a directed edge. For undirected graphs, call twice with reversed nodes. | +| `bfs` | `fn bfs(&self, start: &Id) -> Vec` | Returns all reachable nodes from `start` in breadth-first order. Requires `Id: Eq + Hash + Clone`. | +| `dijkstra` | `fn dijkstra(&self, start: &Id, end: &Id) -> Option<(Vec, u64)>` | Finds the shortest path and its cost. Returns `None` if `end` is unreachable. Requires `Id: Eq + Hash + Clone + Ord`. | +| `astar` | `fn astar u64>(&self, start: &Id, end: &Id, h: H) -> Option<(Vec, u64)>` | A\* shortest path with heuristic `h(node, goal)`. The heuristic must be admissible and consistent for correct results. Requires `Id: Eq + Hash + Clone + Ord`. | + +#### Examples + +```rust +use resq_dsa::graph::Graph; + let mut g = Graph::<&str>::new(); + +// Build the graph g.add_edge("base", "waypoint-1", 100); -g.add_edge("waypoint-1", "target", 50); +g.add_edge("waypoint-1", "waypoint-2", 50); +g.add_edge("base", "waypoint-2", 200); +g.add_edge("waypoint-2", "target", 25); + +// BFS -- visit all reachable nodes +let visited = g.bfs(&"base"); +assert!(visited.contains(&"target")); + +// Dijkstra -- find shortest path let (path, cost) = g.dijkstra(&"base", &"target").unwrap(); -assert_eq!(path, vec!["base", "waypoint-1", "target"]); +assert_eq!(path, vec!["base", "waypoint-1", "waypoint-2", "target"]); +assert_eq!(cost, 175); + +// Unreachable nodes return None +assert!(g.dijkstra(&"target", &"base").is_none()); ``` -## Modules +**A\* with a heuristic:** + +```rust +use resq_dsa::graph::Graph; + +let mut g = Graph::::new(); +g.add_edge(0, 1, 1); +g.add_edge(1, 2, 1); +g.add_edge(0, 3, 10); +g.add_edge(3, 2, 1); + +// Use absolute difference as heuristic +let (path, cost) = g.astar(&0, &2, |a, b| a.abs_diff(*b)).unwrap(); +assert_eq!(path, vec![0, 1, 2]); +assert_eq!(cost, 2); +``` + +--- + +### Bounded Heap + +A bounded max-heap that retains only the **K entries with the smallest distance values**. Useful for K-nearest-neighbor search, top-K tracking, and streaming scenarios where you want to keep only the closest results. + +The root always holds the entry with the **largest** distance among the retained items. When the heap is full and a new entry has a smaller distance than the root, the root is evicted. + +#### Complexity + +| Operation | Time | Space | +|-------------|-----------|-------| +| `new` | O(1) | O(k) | +| `insert` | O(log k) | -- | +| `peek` | O(1) | -- | +| `to_sorted` | O(k log k)| O(k) | +| `len` | O(1) | -- | +| `is_empty` | O(1) | -- | + +Where **k** is the heap limit. + +#### API Reference + +| Method | Signature | Description | +|--------|-----------|-------------| +| `new` | `fn new(limit: usize, dist: D) -> Self` | Creates a heap that keeps at most `limit` items, ordered by the distance function `dist: Fn(&T) -> f64`. | +| `insert` | `fn insert(&mut self, entry: T)` | Inserts an entry. If full and the new entry's distance is less than the current max, the max is evicted. Otherwise the entry is rejected. | +| `peek` | `fn peek(&self) -> Option<&T>` | Returns a reference to the entry with the maximum distance (the root), or `None` if empty. | +| `to_sorted` | `fn to_sorted(&self) -> Vec<&T>` | Returns all entries sorted by distance ascending (nearest first). Allocates a new `Vec` on each call. | +| `len` | `const fn len(&self) -> usize` | Returns the current number of entries. | +| `is_empty` | `const fn is_empty(&self) -> bool` | Returns `true` if the heap is empty. | + +#### Example + +```rust +use resq_dsa::heap::BoundedHeap; + +#[derive(Debug)] +struct Waypoint { id: u32, distance: f64 } + +// Keep the 3 nearest waypoints +let mut h = BoundedHeap::new(3, |w: &Waypoint| w.distance); + +h.insert(Waypoint { id: 1, distance: 10.0 }); +h.insert(Waypoint { id: 2, distance: 2.0 }); +h.insert(Waypoint { id: 3, distance: 7.0 }); +h.insert(Waypoint { id: 4, distance: 1.0 }); // evicts id=1 (distance 10.0) +h.insert(Waypoint { id: 5, distance: 50.0 }); // rejected (too far) + +assert_eq!(h.len(), 3); + +// peek returns the current max (the eviction threshold) +assert_eq!(h.peek().unwrap().id, 3); // id=3 has distance 7.0 + +// Sorted nearest-first +let sorted: Vec = h.to_sorted().iter().map(|w| w.id).collect(); +assert_eq!(sorted, vec![4, 2, 3]); +``` + +**Works with closures:** + +```rust +use resq_dsa::heap::BoundedHeap; + +let offset = 1.0; +let mut h = BoundedHeap::new(5, move |x: &f64| *x + offset); +h.insert(3.0); +h.insert(1.0); +assert_eq!(h.len(), 2); +``` + +--- -### [`resq_dsa`](./resq_dsa.md) +### Trie (Prefix Tree) -*5 modules* +A prefix tree for efficient string storage, exact lookup, and prefix-based autocomplete. All operations run in O(m) time where m is the length of the input string. -### [`bloom`](./bloom.md) +Internally, each node stores a `HashMap`, making the trie Unicode-aware. -*1 struct* +#### Complexity -### [`count_min`](./count_min.md) +| Operation | Time | Space | +|---------------|------|-----------------| +| `new` | O(1) | O(1) | +| `insert` | O(m) | O(m) worst case | +| `search` | O(m) | -- | +| `starts_with` | O(m + r) | O(r) | -*1 struct* +Where **m** is the string length and **r** is the total length of all matching results. -### [`graph`](./graph.md) +#### API Reference -*1 struct* +| Method | Signature | Description | +|--------|-----------|-------------| +| `new` | `fn new() -> Self` | Creates a new empty trie. Also implements `Default`. | +| `insert` | `fn insert(&mut self, word: &str)` | Inserts a word into the trie. | +| `search` | `fn search(&self, word: &str) -> bool` | Returns `true` if the exact word exists in the trie. | +| `starts_with` | `fn starts_with(&self, prefix: &str) -> Vec` | Returns all words that start with the given prefix. Uses DFS with a shared buffer to minimize allocations. | -### [`heap`](./heap.md) +#### Example -*1 struct* +```rust +use resq_dsa::trie::Trie; + +let mut t = Trie::new(); +t.insert("drone"); +t.insert("drone-001"); +t.insert("drone-002"); +t.insert("deploy"); + +// Exact search +assert!(t.search("drone")); +assert!(!t.search("dro")); // prefix alone is not a word + +// Autocomplete +let mut suggestions = t.starts_with("drone-"); +suggestions.sort(); +assert_eq!(suggestions, vec!["drone-001", "drone-002"]); + +// All words starting with "d" +let mut all_d = t.starts_with("d"); +all_d.sort(); +assert_eq!(all_d, vec!["deploy", "drone", "drone-001", "drone-002"]); +``` + +--- + +### Rabin-Karp String Search + +A rolling-hash string matching algorithm that finds all occurrences of a pattern in a text. Uses a polynomial rolling hash with modular arithmetic (base 31, mod 10^9 + 7). + +The algorithm is **Unicode-aware** -- it operates on `char` boundaries, so multi-byte characters are handled correctly. Indices in the result are character positions, not byte offsets. + +#### Complexity + +| Case | Time | Space | +|---------|------------|-------| +| Average | O(n + m) | O(m) | +| Worst | O(n * m) | O(m) | + +Where **n** is the text length and **m** is the pattern length (both in chars). + +#### API Reference + +| Function | Signature | Description | +|----------|-----------|-------------| +| `rabin_karp` | `fn rabin_karp(text: &str, pattern: &str) -> Vec` | Returns a vector of starting character indices where `pattern` occurs in `text`. Returns an empty vector if there are no matches or the pattern is empty. | + +#### Example + +```rust +use resq_dsa::trie::rabin_karp; + +// Multiple overlapping matches +let matches = rabin_karp("ababab", "ab"); +assert_eq!(matches, vec![0, 2, 4]); + +// Single match +let single = rabin_karp("hello world", "world"); +assert_eq!(single, vec![6]); + +// No match +let none = rabin_karp("hello", "xyz"); +assert!(none.is_empty()); + +// Unicode support +let unicode = rabin_karp("cafe\u{0301} cafe\u{0301}", "cafe\u{0301}"); +assert_eq!(unicode, vec![0, 6]); +``` + +--- + +## Quick Reference + +| Module | Primary Type | Key Methods | +|--------|-------------|-------------| +| `bloom` | `BloomFilter` | `new`, `add`, `has`, `len`, `is_empty`, `clear` | +| `count_min` | `CountMinSketch` | `new`, `increment`, `estimate` | +| `graph` | `Graph` | `new`, `add_edge`, `bfs`, `dijkstra`, `astar` | +| `heap` | `BoundedHeap` | `new`, `insert`, `peek`, `to_sorted`, `len`, `is_empty` | +| `trie` | `Trie` | `new`, `insert`, `search`, `starts_with` | +| `trie` | (free fn) `rabin_karp` | `rabin_karp(text, pattern)` | + +## Contributing + +1. Fork the repository and create a feature branch. +2. Run `cargo test` to ensure all tests pass. +3. All new source files must include the Apache-2.0 license header. +4. Keep binary names consistent with the `resq-` convention. +5. Open a pull request against `master`. + +## License + +Licensed under the Apache License, Version 2.0. See [LICENSE](https://github.com/resq-software/crates/blob/master/crates/LICENSE) for details. + +## Modules -### [`trie`](./trie.md) +Click through to each module for the full rustdoc-rendered API surface (types, traits, functions, methods). -*1 function, 1 struct* +- [`bloom`](./bloom) +- [`count_min`](./count_min) +- [`graph`](./graph) +- [`heap`](./heap) +- [`trie`](./trie) diff --git a/sdks/rust/api/resq-tui/index.md b/sdks/rust/api/resq-tui/index.md index 2ce6335c..1f15c020 100644 --- a/sdks/rust/api/resq-tui/index.md +++ b/sdks/rust/api/resq-tui/index.md @@ -1,17 +1,689 @@ -# resq_tui +# resq-tui > **Version:** `v0.1.8` · **License:** `Apache-2.0` · **Crate:** [crates.io](https://crates.io/crates/resq-tui) · **API docs:** [docs.rs](https://docs.rs/resq-tui/0.1.8) -Shared TUI components and themes for `ResQ` developer tools. -Inspired by binsider architecture. +[![Crates.io](https://img.shields.io/crates/v/resq-tui.svg)](https://crates.io/crates/resq-tui) +[![License](https://img.shields.io/crates/l/resq-tui.svg)](LICENSE) -## Modules +Shared TUI component library for all **ResQ** developer tools. Provides a unified theme system, console formatters, table rendering, progress bars, spinners, and terminal lifecycle management built on [Ratatui](https://ratatui.rs) and [Crossterm](https://docs.rs/crossterm). + +## Overview + +`resq-tui` ensures every ResQ tool (`resq-logs`, `resq-perf`, `resq-flame`, `resq-health`, etc.) shares a consistent visual identity and interaction model. It provides two tiers of output: + +- **Full-screen TUI** -- Ratatui-based widgets (header, footer, tabs, popups) with a standardized theme for interactive terminal applications. +- **Non-TUI CLI** -- Styled console formatters, tables, progress bars, and spinners for traditional command-line output that gracefully degrade when piped or redirected. + +All styling is gated through environment detection so ANSI codes never bleed into pipes, redirects, or screen-reader environments. + +## Architecture + +```mermaid +graph TD + subgraph resq-tui + LIB["lib.rs
Theme, Widgets, Utilities"] + THEME["theme.rs
AdaptiveColor, Palette"] + DETECT["detect.rs
TTY, Color Mode, Accessibility"] + CONSOLE["console.rs
Styled Message Formatters"] + TABLE["table.rs
CLI Table Renderer"] + PROGRESS["progress.rs
CLI Progress Bar"] + SPINNER["spinner.rs
Threaded CLI Spinner"] + TERMINAL["terminal.rs
Init, Restore, Event Loop"] + end + + THEME --> DETECT + CONSOLE --> DETECT + CONSOLE --> THEME + TABLE --> DETECT + TABLE --> THEME + PROGRESS --> DETECT + PROGRESS --> THEME + SPINNER --> DETECT + LIB --> THEME + + subgraph Consumers + LOGS["resq-logs"] + PERF["resq-perf"] + FLAME["resq-flame"] + HEALTH["resq-health"] + BIN["resq-bin"] + CLEAN["resq-clean"] + end + + LOGS --> LIB + PERF --> LIB + FLAME --> LIB + HEALTH --> LIB + BIN --> LIB + CLEAN --> LIB +``` + +### Module dependency flow + +```mermaid +flowchart LR + detect.rs -->|ColorMode| theme.rs + theme.rs -->|AdaptiveColor| console.rs + theme.rs -->|AdaptiveColor| table.rs + theme.rs -->|AdaptiveColor| progress.rs + detect.rs -->|should_style| console.rs + detect.rs -->|should_style| table.rs + detect.rs -->|should_style| progress.rs + detect.rs -->|should_style / is_tty| spinner.rs + lib.rs -->|re-exports| theme.rs + terminal.rs -->|crossterm + ratatui| lib.rs +``` + +## Installation + +Add to your `Cargo.toml`: + +```toml +[dependencies] +resq-tui = { workspace = true } +``` + +Or from crates.io: + +```toml +[dependencies] +resq-tui = "0.1.4" +``` + +## Module Reference + +### `lib.rs` -- Core Widgets and Utilities + +The root module re-exports `crossterm` and `ratatui` for convenience and provides the original `Theme` struct alongside shared TUI drawing functions. + +#### `Theme` (root) + +The original hardcoded dark-palette theme struct, retained for backward compatibility. For new code, prefer `theme::Theme::adaptive()` (see below). + +| Field | Type | Default | Description | +|-------------|---------|----------------|-------------------------------| +| `primary` | `Color` | `Cyan` | Primary brand color | +| `secondary` | `Color` | `Blue` | Secondary supporting color | +| `accent` | `Color` | `Magenta` | Metadata accent | +| `success` | `Color` | `Green` | Success state | +| `warning` | `Color` | `Yellow` | Warning / pending state | +| `error` | `Color` | `Red` | Error / critical state | +| `bg` | `Color` | `Black` | Background | +| `fg` | `Color` | `White` | Foreground text | +| `highlight` | `Color` | `Rgb(50,50,50)`| Selection highlight | +| `inactive` | `Color` | `DarkGray` | Muted / inactive elements | + +#### Widget Functions + +##### `draw_header` + +Renders a standardized header bar with service name, status badge, PID, and URL. + +```rust +use resq_tui::{self as tui, Theme}; + +fn draw(f: &mut ratatui::Frame, area: ratatui::layout::Rect) { + let theme = Theme::default(); + tui::draw_header( + f, + area, + "My-Explorer", + "READY", + theme.success, + Some(1234), // PID (or None) + "http://localhost:3000", + &theme, + ); +} +``` + +**Signature:** +```rust +pub fn draw_header( + frame: &mut Frame, + area: Rect, + title: &str, + status: &str, + status_color: Color, + pid: Option, + url: &str, + theme: &Theme, +) +``` + +##### `draw_footer` + +Renders a keyboard-shortcut footer bar. + +```rust +tui::draw_footer( + f, + area, + &[("Q", "Quit"), ("Tab", "Focus"), ("Up/Down", "Navigate")], + &theme, +); +``` + +**Signature:** +```rust +pub fn draw_footer(frame: &mut Frame, area: Rect, keys: &[(&str, &str)], theme: &Theme) +``` + +##### `draw_tabs` + +Renders a tab bar with selection highlight. Uses the default theme internally. + +```rust +tui::draw_tabs(f, area, vec!["Overview", "Details", "Logs"], 0); +``` + +**Signature:** +```rust +pub fn draw_tabs(frame: &mut Frame, area: Rect, titles: Vec<&str>, selected: usize) +``` + +##### `draw_popup` + +Renders a centered modal overlay for help dialogs or error messages. + +```rust +use ratatui::text::Line; + +tui::draw_popup( + f, + area, + "Help", + &[Line::raw("Press Q to quit"), Line::raw("Press ? for help")], + 60, // percent_x + 40, // percent_y + &theme, +); +``` + +**Signature:** +```rust +pub fn draw_popup( + frame: &mut Frame, + area: Rect, + title: &str, + lines: &[Line], + percent_x: u16, + percent_y: u16, + theme: &Theme, +) +``` + +##### `centered_rect` + +Helper that computes a centered `Rect` given percentage dimensions. Used internally by `draw_popup`. + +```rust +let popup_area = tui::centered_rect(60, 40, area); +``` + +#### Utility Functions + +##### `format_bytes` + +Converts a byte count to a human-readable string using binary units (KiB, MiB, GiB). + +```rust +use resq_tui::format_bytes; + +assert_eq!(format_bytes(0), "0 B"); +assert_eq!(format_bytes(1024), "1.0 KiB"); +assert_eq!(format_bytes(5242880), "5.0 MiB"); +assert_eq!(format_bytes(1073741824), "1.00 GiB"); +``` + +##### `format_duration` + +Converts seconds to a human-readable duration string. + +```rust +use resq_tui::format_duration; + +assert_eq!(format_duration(45), "45s"); +assert_eq!(format_duration(125), "2m 5s"); +assert_eq!(format_duration(3661), "1h 1m 1s"); +assert_eq!(format_duration(90061), "1d 1h 1m"); +``` + +##### `SPINNER_FRAMES` + +Braille animation frames for TUI spinner widgets: + +```rust +pub const SPINNER_FRAMES: &[&str] = &["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]; +``` + +--- + +### `theme` -- Adaptive Color System + +The theme module provides a Dracula-inspired adaptive color palette that switches between light and dark variants based on terminal environment detection. + +#### `AdaptiveColor` + +A color pair with `light` and `dark` variants that resolves at runtime via `detect_color_mode()`. + +```rust +use resq_tui::theme::{AdaptiveColor, COLOR_PRIMARY}; +use ratatui::style::Color; + +let resolved: Color = COLOR_PRIMARY.resolve(); +``` + +| Method | Returns | Description | +|-------------|---------|--------------------------------------------------| +| `resolve()` | `Color` | Returns the appropriate variant for the terminal | + +#### Palette Constants + +| Constant | Dark (Dracula) | Light | Usage | +|------------------------|-------------------------|-------------------------|-------------------------| +| `COLOR_PRIMARY` | `Rgb(139, 233, 253)` | `Rgb(0, 139, 139)` | Brand / primary accent | +| `COLOR_SECONDARY` | `Rgb(189, 147, 249)` | `Rgb(68, 71, 144)` | Supporting elements | +| `COLOR_ACCENT` | `Rgb(255, 121, 198)` | `Rgb(163, 55, 136)` | Metadata / PID | +| `COLOR_SUCCESS` | `Rgb(80, 250, 123)` | `Rgb(40, 130, 40)` | Success states | +| `COLOR_WARNING` | `Rgb(241, 250, 140)` | `Rgb(180, 120, 0)` | Warning states | +| `COLOR_ERROR` | `Rgb(255, 85, 85)` | `Rgb(215, 55, 55)` | Error states | +| `COLOR_FG` | `Rgb(248, 248, 242)` | `Rgb(40, 42, 54)` | Foreground text | +| `COLOR_BG` | `Rgb(40, 42, 54)` | `Rgb(248, 248, 242)` | Background | +| `COLOR_INACTIVE` | `Rgb(98, 114, 164)` | `Rgb(140, 140, 140)` | Muted / comments | +| `COLOR_HIGHLIGHT` | `Rgb(68, 71, 90)` | `Rgb(230, 230, 230)` | Selection background | +| `COLOR_PROGRESS_START` | `Rgb(189, 147, 249)` | `Rgb(100, 60, 180)` | Progress bar fill start | +| `COLOR_PROGRESS_END` | `Rgb(139, 233, 253)` | `Rgb(0, 139, 139)` | Progress bar fill end | +| `COLOR_PROGRESS_EMPTY` | `Rgb(98, 114, 164)` | `Rgb(200, 200, 200)` | Progress bar empty | + +#### `Theme` (theme module) + +Extended theme struct with adaptive color support. + +| Constructor | Description | +|-----------------|-------------------------------------------------------------------| +| `Theme::adaptive()` | Resolves all colors via `AdaptiveColor::resolve()` (recommended) | +| `Theme::default()` | Hardcoded dark palette for backward compatibility | + +```rust +use resq_tui::theme::Theme; + +// Recommended: adapts to terminal background +let theme = Theme::adaptive(); + +// Legacy: always dark +let theme = Theme::default(); +``` + +--- + +### `detect` -- Terminal Environment Detection + +Detects TTY status, color support, and accessibility mode. All detection is cached per-process via `OnceLock`. + +#### Environment Variables + +| Variable | Effect when set | +|--------------|--------------------------------------------------| +| `NO_COLOR` | Disables all ANSI styling ([no-color.org](https://no-color.org)) | +| `TERM=dumb` | Disables all ANSI styling | +| `ACCESSIBLE` | Enables screen-reader / accessible mode | +| `COLORFGBG` | Used to detect light vs dark terminal background | + +#### `ColorMode` + +```rust +pub enum ColorMode { + Dark, // Dark terminal background (default assumption) + Light, // Light terminal background + None, // No color support +} +``` + +#### Public Functions + +| Function | Returns | Description | +|-----------------------|-------------|--------------------------------------------------------| +| `is_tty_stdout()` | `bool` | Whether stdout is a TTY | +| `is_tty_stderr()` | `bool` | Whether stderr is a TTY | +| `is_accessible_mode()`| `bool` | Whether accessible / plain output is requested | +| `should_style()` | `bool` | Master gate -- all console formatters check this | +| `detect_color_mode()`| `ColorMode` | Resolved color mode for adaptive color selection | + +--- + +### `console` -- Styled Message Formatters + +TTY-gated console formatters for non-TUI CLI output. Diagnostics go to stderr, structured data to stdout. All styling respects `detect::should_style()`. + +#### Format Functions (return `String`) -### [`resq_tui`](./resq_tui.md) +| Function | Prefix | Color | Usage | +|---------------------------|--------|-----------|------------------------------| +| `format_success(msg)` | `✅` | Success | Completion messages | +| `format_error(msg)` | `❌` | Error | Error messages (bold) | +| `format_warning(msg)` | `⚠️` | Warning | Warning messages | +| `format_info(msg)` | `ℹ️` | Primary | Informational messages | +| `format_command(cmd)` | `▶` | Secondary | Command references (bold) | +| `format_progress(msg)` | `⏳` | Warning | In-flight operations | +| `format_prompt(msg)` | `?` | Primary | Interactive prompts (bold) | +| `format_verbose(msg)` | -- | Dim | Debug / verbose output | +| `format_list_item(msg)` | ` •` | -- | Indented list items | +| `format_section_header(h)`| `━━━` | Primary | Section dividers with rule | +| `format_count(msg)` | `📊` | Accent | Metrics / counts | +| `format_location(msg)` | `📁` | Secondary | File paths / locations | +| `format_list_header(h)` | -- | FG (bold) | List / section headers | +| `format_search(msg)` | `🔍` | Primary | Search / scan operations | -*1 constant, 1 module, 1 struct, 7 functions* +#### Print Functions (write to stderr) + +Convenience wrappers that call the corresponding `format_*` function and print to stderr: + +```rust +use resq_tui::console; + +console::success("Deployment complete"); +console::error("Connection refused"); +console::warning("Certificate expires soon"); +console::info("Scanning 42 services"); +console::progress("Uploading artifacts..."); +console::verbose("Retry attempt 3/5"); +console::section("Results"); +``` + +--- + +### `table` -- CLI Table Renderer + +Renders styled tables to stderr with zebra-striped rows, auto-computed column widths, and adaptive colors. Falls back to plain aligned text when styling is disabled. + +#### `Align` + +```rust +pub enum Align { + Left, // Default alignment + Right, // Right-aligned (for numeric columns) +} +``` + +#### `Column` + +Builder for table column definitions. + +| Method | Description | +|-----------------------|---------------------------------------| +| `Column::new(header)` | Left-aligned column | +| `Column::right(header)`| Right-aligned column | +| `.width(w)` | Sets minimum column width | + +#### `render_table` + +Renders a complete table to stderr. + +```rust +use resq_tui::table::{Column, render_table}; + +let columns = vec![ + Column::new("Service"), + Column::right("Latency"), + Column::new("Status").width(10), +]; + +let rows = vec![ + vec!["api".into(), "12ms".into(), "healthy".into()], + vec!["worker".into(), "340ms".into(), "degraded".into()], + vec!["cache".into(), "2ms".into(), "healthy".into()], +]; + +render_table(&columns, &rows); +``` + +Output (styled): +``` + Service Latency Status + ─────── ─────── ────────── + api 12ms healthy + worker 340ms degraded ← dimmed (zebra stripe) + cache 2ms healthy +``` + +--- + +### `progress` -- CLI Progress Bar + +Non-TUI progress bar rendered to stderr with adaptive gradient colors. Falls back to plain ASCII in non-TTY mode. + +#### `ProgressBar` + +| Method | Description | +|--------------------------------|---------------------------------------------------| +| `ProgressBar::new(msg, width)` | Creates a progress bar with message and width | +| `.render(fraction)` | Renders at the given fraction (0.0 to 1.0) | +| `.finish()` | Ends the bar with a newline | +| `.finish_with_message(msg)` | Clears the bar and prints a final message | + +```rust +use resq_tui::progress::ProgressBar; + +let pb = ProgressBar::new("Downloading", 40); +for i in 0..=100 { + pb.render(i as f64 / 100.0); +} +pb.finish_with_message("✅ Download complete"); +``` + +TTY output: `Downloading ████████████████░░░░░░░░░░░░░░░░░░░░░░░░ 40%` + +Non-TTY output: `Downloading [################------------------------] 40%` + +--- + +### `spinner` -- Threaded CLI Spinner + +Thread-safe stderr spinner that respects TTY and accessibility settings. Uses braille animation by default with a plain-dots fallback. + +#### `SPINNER_FRAMES` + +Braille frames used by both the TUI spinner constant and the non-TUI `Spinner`: + +```rust +pub const SPINNER_FRAMES: &[&str] = &["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]; +``` + +#### `Spinner` + +| Method | Description | +|-------------------------------|-------------------------------------------------| +| `Spinner::start(msg)` | Starts the spinner in a background thread | +| `.stop_with_message(msg)` | Stops and prints a final message | +| `.stop()` | Stops without a final message | + +The spinner is also stopped automatically on `Drop`. + +```rust +use resq_tui::spinner::Spinner; + +let spinner = Spinner::start("Fetching service health"); +// ... long-running operation ... +spinner.stop_with_message("✅ Health check complete"); +``` + +In non-TTY mode, `start()` prints `"Fetching service health..."` once and returns immediately. + +--- + +### `terminal` -- Terminal Lifecycle Management + +Manages raw mode, alternate screen, and provides a standard event loop for Ratatui applications. + +#### Type Alias + +```rust +pub type Term = Terminal>; +``` + +#### `init() -> anyhow::Result` + +Enables raw mode, enters the alternate screen, and returns an initialized `Term`. + +#### `restore()` + +Leaves the alternate screen and disables raw mode. Safe to call even in a partially-initialized state. + +#### `TuiApp` Trait + +Implement this trait on your application state to use `run_loop`. + +```rust +pub trait TuiApp { + fn draw(&mut self, frame: &mut ratatui::Frame); + fn handle_key(&mut self, key: crossterm::event::KeyEvent) -> anyhow::Result; +} +``` + +Return `false` from `handle_key` to exit the event loop. `Ctrl+C` always exits. + +#### `run_loop` + +Runs a standard TUI event loop. `poll_ms` controls input polling frequency. + +```rust +pub fn run_loop( + terminal: &mut Term, + poll_ms: u64, + app: &mut dyn TuiApp, +) -> anyhow::Result<()> +``` + +--- + +## Integration Guide + +### Building a new ResQ TUI tool + +1. **Add the dependency** to your crate's `Cargo.toml`: + +```toml +[dependencies] +resq-tui = { workspace = true } +``` + +2. **Implement `TuiApp`** on your application state: + +```rust +use resq_tui::terminal::TuiApp; +use resq_tui::theme::Theme; +use resq_tui::{draw_header, draw_footer}; +use ratatui::layout::{Constraint, Layout}; + +struct MyApp { + theme: Theme, +} + +impl TuiApp for MyApp { + fn draw(&mut self, frame: &mut ratatui::Frame) { + let area = frame.area(); + let chunks = Layout::vertical([ + Constraint::Length(3), // header + Constraint::Min(1), // body + Constraint::Length(3), // footer + ]) + .split(area); + + draw_header( + frame, chunks[0], + "My-Tool", "RUNNING", self.theme.success, + None, "localhost:8080", &self.theme, + ); + + // ... render your body content in chunks[1] ... + + draw_footer( + frame, chunks[2], + &[("Q", "Quit"), ("Tab", "Switch"), ("?", "Help")], + &self.theme, + ); + } + + fn handle_key( + &mut self, + key: crossterm::event::KeyEvent, + ) -> anyhow::Result { + use crossterm::event::KeyCode; + match key.code { + KeyCode::Char('q') => Ok(false), + _ => Ok(true), + } + } +} +``` + +3. **Run the event loop** in `main`: + +```rust +fn main() -> anyhow::Result<()> { + let mut terminal = resq_tui::terminal::init()?; + let mut app = MyApp { + theme: Theme::adaptive(), + }; + + let result = resq_tui::terminal::run_loop(&mut terminal, 100, &mut app); + resq_tui::terminal::restore(); + result +} +``` + +### Using non-TUI console output + +For CLI tools that do not need a full-screen TUI: + +```rust +use resq_tui::console; +use resq_tui::table::{Column, render_table}; +use resq_tui::progress::ProgressBar; +use resq_tui::spinner::Spinner; + +fn main() { + console::section("Service Health"); + + let spinner = Spinner::start("Checking services"); + // ... check services ... + spinner.stop_with_message("✅ All services checked"); + + let columns = vec![ + Column::new("Service"), + Column::right("Latency"), + Column::new("Status"), + ]; + let rows = vec![ + vec!["api".into(), "12ms".into(), "healthy".into()], + ]; + render_table(&columns, &rows); + + let pb = ProgressBar::new("Deploying", 30); + for i in 0..=100 { + pb.render(i as f64 / 100.0); + } + pb.finish_with_message(&console::format_success("Deployed")); +} +``` + +## Accessibility + +`resq-tui` respects the following standards: + +- **`NO_COLOR`** ([no-color.org](https://no-color.org)) -- disables all ANSI color codes +- **`TERM=dumb`** -- plain text output only +- **`ACCESSIBLE`** -- activates screen-reader-friendly output (plain dot spinners, no animation) +- Non-TTY pipes and redirects receive unstyled output automatically + +## License + +Licensed under the Apache License, Version 2.0. See [LICENSE](https://github.com/resq-software/crates/blob/master/crates/LICENSE) for details. + +## Modules -### [`terminal`](./terminal.md) +Click through to each module for the full rustdoc-rendered API surface (types, traits, functions, methods). -*1 struct, 1 trait, 1 type alias, 3 functions* +- [`terminal`](./terminal)