Skip to content

Runtime features

Features is the set of built-in commands a particular invocation exposes. It is a runtime switch: a command that is runtime-disabled is still compiled into the binary, it is just not offered to the user.

use rtb_app::features::{Feature, Features, FeaturesBuilder};

The eleven Feature variants

Variant Gates On by default
Init the init bootstrap command yes
Version the version command yes
Update self-update: the update subcommand and the pre-run version check yes
Docs the TUI documentation browser (docs) yes
Mcp the MCP server (mcp) yes
Doctor health-check diagnostics (doctor) yes
Credentials the credentials list / add / remove / test / doctor subtree yes
Telemetry opt-in anonymous telemetry yes
Config runtime config get/set (config) yes
Ai AI-powered features — docs ask, agentic flows no
Changelog structured release-notes display (changelog) no

Nine on, two off. Feature::all() returns all eleven as a &'static [Feature], in declaration order: Init, Version, Update, Docs, Mcp, Doctor, Ai, Telemetry, Config, Changelog, Credentials.

Ai and Changelog are the deliberate exceptions: AI features imply an outbound provider call and a credential, and the changelog command is not wanted by every tool. Both are opt-in.

Turning features on and off

let features = Features::builder()          // starts from the nine defaults
    .disable(Feature::Update)
    .enable(Feature::Ai)
    .build();

assert!(features.is_enabled(Feature::Ai));
assert!(!features.is_enabled(Feature::Update));
assert!(features.is_enabled(Feature::Init));  // untouched defaults survive

Start from nothing instead with FeaturesBuilder::none():

let minimal = FeaturesBuilder::none().enable(Feature::Version).build();
Constructor Starting set
Features::builder() the nine defaults
Features::default() the nine defaults
Feature::defaults() the nine defaults
FeaturesBuilder::new() the nine defaults
FeaturesBuilder::none() empty
FeaturesBuilder::default() empty — see below

FeaturesBuilder::default() is empty, not the defaults

This is the one trap on the page. FeaturesBuilder derives Default, and a derived Default on a struct wrapping a HashSet produces an empty set. So:

FeaturesBuilder::new().build().iter().count()      // 9
FeaturesBuilder::default().build().iter().count()  // 0

FeaturesBuilder::default() behaves like FeaturesBuilder::none(), not like FeaturesBuilder::new(). Features::default() is unaffected — it delegates to builder() and gives you the nine.

Nothing warns about this, and a tool that reaches for ..Default::default() out of habit ends up shipping a CLI with every built-in command hidden. Use FeaturesBuilder::new() when you mean the defaults and FeaturesBuilder::none() when you mean empty; treat FeaturesBuilder::default() as a synonym for the latter.

Reading a Features set

pub fn is_enabled(&self, feature: Feature) -> bool;
pub fn iter(&self) -> impl Iterator<Item = Feature> + '_;

Features is backed by a HashSet, so iter() yields no defined order and the order can differ between runs of the same binary. Sort before printing anything a user or a test will compare.

Features derives Debug and Clone. It does not implement PartialEq, so two sets cannot be compared with ==; compare via is_enabled per feature, or collect and sort.

What runtime features are not

Cargo features and rtb_app::Feature are separate mechanisms that share a word.

Cargo features rtb_app::Feature
Decided at compile time, on the rtb umbrella crate runtime, per invocation
Effect when off the command's code is not compiled and never registers into BUILTIN_COMMANDS the command is compiled and registered, but filtered out before clap sees it

rtb-app itself declares no Cargo features at all, so nothing in this crate is conditionally compiled.

Feature is #[non_exhaustive], so a match over it needs a _ => arm — that is what allows a new built-in command to arrive in a minor release. For the same reason Feature::all() returns &'static [Feature] rather than [Feature; 11]: an array's length is part of its type, so returning one would make every new variant a breaking change.

  • CommandsCommandSpec::feature is what ties a command to a gate
  • Tool metadataFeature::Update gates every update field