rust-tool-base 0.5.1

Rust Tool Base — the batteries-included CLI application framework. Umbrella crate re-exporting the full public API.
docs.rs failed to build rust-tool-base-0.5.1
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.

Rust Tool Base (RTB)

A batteries-included application framework for building Rust CLI tools — idiomatic, composable, and AI-ready.

Status: pre-0.1 scaffolding. Most crates are intentional stubs. The authoritative contract for each subsystem lives in docs/development/specs/rust-tool-base.md.

RTB is a Rust sibling of Go Tool Base (GTB). It is not a line-for-line port. Go Tool Base leans on Go's idioms (context.Context, functional options, package-level init(), interface{}-keyed containers); RTB leans on Rust's (? + miette, typestate builders, linkme distributed slices, strongly-typed serde config, tokio structured concurrency).

What RTB gives you

  • Opinionated application scaffoldingApplication::builder() typestate assembler that wires clap, figment, tracing, miette, tokio, and a signal- bound CancellationToken in one call.
  • Strongly-typed layered config — env > CLI flags > user file > embedded defaults, materialised into your serde::Deserialize struct. Hot-reload via notify + arc-swap with a watch::Receiver for observers.
  • Embedded-assets + overlay FSrust-embed for compile-time bundling (with dev-mode disk passthrough) + vfs::OverlayFS for user-override layering. Structured formats (YAML/JSON/TOML) are deep-merged; binaries follow last-registered-wins.
  • Diagnostic errors, not error handlers — every crate derives thiserror::Error + miette::Diagnostic; main() returns miette::Result<()> and a framework-installed hook renders them with labels, help, and severity.
  • Self-updateself_update + self-replace, signed releases (ed25519-dalek over SHA-256), atomic binary swap on every platform.
  • VCSgix-first Git ops (fallback to git2 for unsupported writes), octocrab for GitHub (Enterprise-capable, GitHub-App-capable), gitlab for self-hosted GitLab with nested groups.
  • AIgenai for multi-provider (Claude, OpenAI, Gemini, Ollama), with a direct-reqwest path for Anthropic-only features (prompt caching, managed agents, extended thinking). Structured output validated with jsonschema.
  • MCP — official rmcp SDK; commands opt in via Command::mcp_exposed = true and surface their clap::Args schema through Command::mcp_input_schema. mcp serve (stdio default) and mcp list ship as built-in subcommands.
  • TUI docs browserratatui + termimad, with streaming AI Q&A.
  • Credentialskeyring wrapped in a CredentialStore trait; secrecy::SecretString end-to-end, zeroed on drop.
  • Telemetry — opt-in, salted machine-uid, pluggable sinks (noop/file/HTTP/OTLP).

Workspace layout

rust-tool-base/
├── Cargo.toml                      # workspace root
├── crates/
│   ├── rtb/                        # umbrella crate (public API)
│   ├── rtb-app/                   # App context, ToolMetadata, Features
│   ├── rtb-error/                  # Error type + miette integration
│   ├── rtb-config/                 # figment-backed typed config
│   ├── rtb-assets/                 # rust-embed + vfs overlay
│   ├── rtb-cli/                    # Application builder + built-in commands
│   ├── rtb-update/                 # self-update subsystem
│   ├── rtb-vcs/                    # gix + octocrab + gitlab
│   ├── rtb-ai/                     # genai + structured output
│   ├── rtb-mcp/                    # rmcp server
│   ├── rtb-docs/                   # ratatui docs browser
│   ├── rtb-tui/                    # reusable widgets (Wizard, tables)
│   ├── rtb-credentials/            # keyring wrapper
│   ├── rtb-telemetry/              # opt-in telemetry
│   └── rtb-cli-bin/                # the `rtb` scaffolder binary
├── examples/
│   └── minimal/                    # smoke-test tool
├── docs/                           # user-facing documentation
│   └── development/
│       └── specs/
│           └── rust-tool-base.md   # ← authoritative spec
├── .github/workflows/              # ci.yaml, release.yaml
├── deny.toml                       # cargo-deny policy
├── rustfmt.toml
├── rust-toolchain.toml             # pinned stable
├── justfile                        # common tasks
├── LICENSE                         # MIT
└── SECURITY.md

Quick start

A working end-to-end example lives at examples/minimal. Run it with:

$ cargo run -p rtb-example-minimal -- version
$ cargo run -p rtb-example-minimal -- greet
$ cargo run -p rtb-example-minimal -- doctor
$ cargo run -p rtb-example-minimal -- --help

Anatomy

A minimal RTB-based tool has three parts: (1) a custom Command implementation, (2) a linkme registration that inserts it into the framework's command slice, and (3) an Application::builder() call in main.

use async_trait::async_trait;
use linkme::distributed_slice;
use rtb::core::app::App;
use rtb::core::command::{BUILTIN_COMMANDS, Command, CommandSpec};
use rtb::prelude::*;

// 1. Implement the Command trait.
struct Greet;

#[async_trait]
impl Command for Greet {
    fn spec(&self) -> &CommandSpec {
        static SPEC: CommandSpec = CommandSpec {
            name: "greet",
            about: "Print a friendly greeting",
            aliases: &["hi"],
            feature: None,
        };
        &SPEC
    }

    async fn run(&self, app: App) -> miette::Result<()> {
        println!("hello from {}", app.metadata.name);
        Ok(())
    }
}

// 2. Register into the framework's command slice at link time.
#[distributed_slice(BUILTIN_COMMANDS)]
fn register_greet() -> Box<dyn Command> { Box::new(Greet) }

// 3. Wire the Application.
#[tokio::main]
async fn main() -> miette::Result<()> {
    rtb::cli::Application::builder()
        .metadata(
            ToolMetadata::builder()
                .name("mytool")
                .summary("my CLI tool")
                .build(),
        )
        .version(VersionInfo::from_env())
        .build()?
        .run()
        .await
}

Downstream tools need rtb, tokio, miette, async-trait, and linkme as direct dependencies. linkme must be direct because its #[distributed_slice] attribute expands to ::linkme::… paths.

Development

just ci           # fmt + clippy + tests + cargo-deny
just check        # cargo check all targets, all features
just rtb -- --help   # run the scaffolder CLI

License

MIT — see LICENSE.