Skip to content

rtb-cli-bin

rtb-cli-bin produces the rtb binary — the scaffolder and regenerator that creates and maintains tools built on the RTB framework. It is the Rust analogue of gtb in the Go ecosystem: it borrows gtb's command grammar (verb structure, kind names, common flag names) while the implementation underneath is idiomatic Rust.

Two things make this crate unusual among the rtb-* family:

  • It is itself an RTB tool. main.rs is rtb_cli::Application::builder() — the same entry point a scaffolded tool uses (see rtb-cli). So rtb inherits every framework default command for free (version, doctor, update, docs, mcp, config, credentials, telemetry); only the scaffolder verbs are registered on top. It eats its own dog food.
  • Its public API is the CLI grammar, not a Rust API. As a binary crate, the stable surface downstream users depend on is the command tree — rtb generate <kind>, rtb remove <kind>, rtb regenerate <kind> — plus the inherited framework commands. That grammar is what 1.0 will freeze.

The full design record is the v0.6 scope spec; this page documents the surface as implemented, which is leaner than that spec's initial sketch in places (noted inline).

Installation

The umbrella rust-tool-base crate does not depend on rtb-cli-bin, so downstream tools never pull minijinja / inquire / the embedded templates into their own binaries. The scaffolder is installed on its own:

cargo install rtb-cli-bin

Runtime features

rtb's main.rs tunes the inherited framework feature set (crates/rtb-cli-bin/src/main.rs):

.features(Features::builder().disable(Feature::Init).enable(Feature::Ai).build())
  • Init is disabled — the scaffolder runs on baked-in defaults and has no per-user config of its own to bootstrap.
  • Ai is enabled — required for the prompt/script → Rust codegen path and the autonomous repair loop. Ai is not in the framework default-on set, so it is enabled explicitly.
  • Every other default-on feature (Version, Update, Docs, Mcp, Doctor, Credentials, Config, Telemetry) stays on.

This mirrors gtb's own internal/cmd/root/root.go feature selection (v0.6 spec §9, Q-10/Q-11).

Command grammar

rtb <verb> <kind> [args] [flags]. The scaffolder adds three verbs — generate, remove, regenerate — to the inherited framework command suite documented in rtb-cli.

rtb generate <kind>

Scaffold something from the embedded templates. Five kinds.

rtb generate project [PATH]

Render a preset into a fresh directory. The most-used kind.

Arg / flag Type Default Notes
PATH (positional) path ./<name> Destination directory. Must not exist — generate refuses to overwrite, keeping the verb idempotent and safe.
-n, --name <NAME> string Tool name, lowercase ASCII kebab-case ^[a-z][a-z0-9-]{0,63}$. Required under --non-interactive; otherwise the wizard prompts.
-d, --description <TEXT> string empty One-line description embedded in the generated Cargo.toml + --about. ≤ 500 bytes, no control chars except tab.
--preset <NAME> string minimal Bundled preset to render (minimal, cli).
--non-interactive flag off Skip all prompts; every required field must be supplied by flag. Missing fields produce a single diagnostic listing all of them (v0.6 spec Q-5).
--no-git flag off Skip Repo::init + the initial commit. Useful when scaffolding inside an existing repo or on a CI image without git.

The v0.6 spec §3.1.1 sketched a much larger gtb-parity flag set (--vcs-backend, --features, --repo, --help-type, …). Those did not ship in the v0.6.0 cut — the implemented surface is the table above. They remain candidates for a later milestone.

Behaviour: validates <name> (validate.rs — NFC + character-class + reserved-name checks), walks the embedded templates/<preset>/ tree, renders each file through minijinja with the context-aware escapers, writes .rtb/manifest.yaml, and (unless --no-git) calls Repo::init with an initial commit.

rtb generate command [COMMAND-PATH]

Add a subcommand to an existing scaffolded project (refuses if .rtb/manifest.yaml is absent). Writes a new command module from the _partials/command.rs.j2 partial and registers it between the // rtb:commands-begin / -end markers in src/commands/mod.rs.

Arg / flag Type Default Notes
COMMAND-PATH (positional) string Command name (e.g. greet). Required unless a protect/unprotect sub-verb is used. Multi-segment paths are rejected with a clear diagnostic in v0.6.0.
--about <TEXT> string Scaffolded subcommand Short description for <tool> --help and the generated command's clap about.
--provider <NAME> string anthropic AI provider for --prompt/--script codegen (see AI codegen).
--model <ID> string provider default Model override.
--prompt <TEXT-OR-PATH> string Natural-language description of the command's behaviour, or a path to a file containing one. The AI drafts the run body.
--script <PATH> path Path to a bash/python/js script to port into a Rust command, preserving semantics.
--agentless flag off One-shot generation only — opt out of the verifier/repair loop.
--max-repair-iterations <N> u32 5 Cap on the verifier/repair loop.

Sub-verbs:

  • rtb generate command protect <COMMAND-PATH> — add the command to the manifest's protected: list; rtb regenerate then refuses to touch it.
  • rtb generate command unprotect <COMMAND-PATH> — reverse.

rtb generate flag <FLAG-NAME>

Add a clap flag to an existing scaffolded command — both the .arg(...) builder block (inserted between // rtb:flags-begin / -end) and the corresponding handling. A flag is a transient per-command CLI argument the user types; for a persistent config value read from file/env, use generate setting instead. See Flags vs settings.

Arg / flag Type Default Notes
FLAG-NAME (positional) string Flag long-name without the leading --. Kebab-case, ≤ 64 bytes.
-c, --command <PATH> string Required. Target command path.
--type <TYPE> enum string One of string, bool, int, float, string_slice.
-d, --description <TEXT> string empty One-line help text.
-s, --shorthand <CHAR> char Single-character shorthand (e.g. -v).
--required flag off Renders clap::Arg::required(true).
--persistent flag off Renders clap::Arg::global(true) (gtb's --persistent).
--default <VALUE> string Default value as a string literal.
--default-is-code flag off Reserved; errors if set in the current cut.
--provider <NAME> / --model <ID> string When set without --description, the AI drafts a candidate help string (≤ 80 chars) from the flag name, type, and command context.

rtb generate setting <NAME>

Add a setting — a typed field on the tool's AppConfig, inserted between the // rtb:settings-begin / -end markers. A setting is a persistent config value read from the layered config (defaults → config file → env MYTOOL_<NAME> → CLI override); contrast generate flag, which adds a transient per-command CLI argument the user types. See Flags vs settings. rtb-specific — there is no gtb counterpart, because rtb-config uses typed structs while gtb-config uses dynamic accessors (gtb fuses both into one persistent flag; rtb keeps them distinct).

Arg / flag Type Default Notes
NAME (positional) string Setting name (snake_case Rust identifier).
--type <TYPE> enum string string, bool, int, float, string_slice.
--default <VALUE> string Default value, rendered as a // default: annotation alongside the field (documentation-only for now).
-p, --path <PATH> path cwd Project root.

Renamed from config-field (cli-bin v0.6.0) — see Flags vs settings.

rtb generate docs

Write per-command Markdown into the project's docs/, matching the microsite layout rtb itself uses.

Arg / flag Type Default Notes
-p, --path <PATH> path cwd Project root.
--provider <NAME> / --model <ID> string Enable the AI prose pass over the extracted skeleton.
--page-by-page flag off Review each generated page individually rather than as a batch.

Two passes: an extraction pass (always) turns clap metadata + /// doc comments + the manifest's generated index into a structural Markdown skeleton; an AI-prose pass (only with --provider) fills in narrative sections, shown as a diff for accept/reject/regenerate.

rtb remove <kind>

Reverse a previous generate, keeping the manifest consistent. Both kinds refuse on a protected command unless --force, and both take --dry-run.

Command Args Behaviour
rtb remove command <COMMAND-PATH> --force, --dry-run Removes the command module + its pub mod line between the command markers + its manifest generated entry.
rtb remove flag <FLAG-NAME> -c/--command <PATH> (required), --force, --dry-run Splices the matching .arg(...) block out of the target command and updates the manifest.
rtb remove setting <NAME> --force, --dry-run Strips the pub <name>: field (and its preceding // default: comment) from the AppConfig settings markers in src/main.rs and re-hashes the manifest. --force overrides a protected: src/main.rs.

The remove triad mirrors the generate verbs (command / flag / setting) one-for-one.

rtb regenerate <kind>

Detect drift between a scaffolded tree and its manifest, and reconcile.

rtb regenerate project

Re-render the tool from its recorded preset + field values and reconcile against the working tree.

Arg / flag Type Default Notes
-p, --path <PATH> path cwd Project root.
--overwrite <POLICY> enum ask Conflict policy for edited generator-owned files: deny (print diff, no writes), ask (prompt per file — interactive only), allow (overwrite unconditionally, warns hard).
--force flag off Convenience alias for --overwrite=allow.
--non-interactive flag off Refuse to launch prompts; edited files fall through to deny and the run exits non-zero with the conflict list (v0.6 spec Q-16).
--dry-run flag off Show the diff without writing.
--update-docs flag off Also re-render the generate docs output for changed pages.

Files listed under protected: in the manifest are always skipped, regardless of --overwrite/--force, with an informational Skipped (protected): <path> line (v0.6 spec Q-14). unprotect first to override.

Regeneration is marker-aware: the // rtb:*-begin/-end regions that generate command / generate setting populate (command registrations, settings) are spliced from the on-disk file into the freshly rendered template, so your generated additions survive even under --overwrite=allow. Only content outside the marker regions is taken from the refreshed template.

rtb regenerate manifest

Walk the source tree, re-hash every entry in the manifest's generated map, drop entries whose files are gone, and rewrite .rtb/manifest.yaml. Useful after manual edits, hand ports, or version-skew. Flags: -p, --path <PATH>, --dry-run.

Presets

Presets are whole project templates, discovered from the subdirectories of templates/ (names starting with _ are internal, e.g. _partials/). Selected with --preset on generate project.

Preset Shape
minimal (default) The smallest viable RTB tool: a commands/ module with linkme-based BUILTIN_COMMANDS dispatch and an AppConfig stub carrying the settings markers. Doubles as the smallest-tool documentation example.
cli Fuller starting point with rtb-config integration.

minimal is a strict subset of cli; shipping both proves the preset abstraction holds under two shapes before 1.0 freezes the surface.

The manifest — .rtb/manifest.yaml

The source of truth for rtb regenerate. Written by generate project, updated by every subsequent generate/remove, and rebuilt by regenerate manifest. YAML was chosen over TOML (v0.6 spec Q-3) for gtb-consistency and a cleaner path→hash map.

rtb_version: "0.5.2"   # rtb that last wrote this; future migrations key off it
preset: minimal
preset_version: 3      # internal preset-schema version; bumped on required-output changes

fields:                # values substituted into templates (BTreeMap → stable order)
  project_name: widget
  description: "A widget tool"

generated:             # generator-owned files → blake3 content hash
  src/main.rs: "b3:abc123…"
  Cargo.toml: "b3:def456…"

protected:             # paths excluded from regeneration; omitted when empty
  - greet
  • Hashes are blake3 with a b3: algorithm prefix (v0.6 spec Q-4) — the prefix is the escape hatch for a future migrator to recognise a foreign sha256: manifest.
  • The struct deserialises with deny_unknown_fields; the schema is versioned via rtb_version. Pre-1.0 the keys may evolve — downstream tools should not parse this file outside rtb.

Marker conventions

Marker-driven edits keep generated insertions surgical — rtb finds a begin/end pair and edits only between them, never reformatting the rest of the file. Generated commands follow a one-command-per-file convention, so "skip this command" during regeneration is a clean file-level skip (v0.6 spec Q-14).

Marker pair File Governs
// rtb:commands-begin / -end src/commands/mod.rs pub mod <command>; lines (kept sorted + deduplicated)
// rtb:flags-begin / -end src/commands/<cmd>.rs clap .arg(...) blocks
// rtb:settings-begin / -end src/main.rs AppConfig settings (typed fields)

AI-assisted codegen

When rtb generate command is given --prompt or --script (and --agentless is not set), rtb runs an autonomous draft-and-repair loop rather than a single AI call.

Provider resolution (src/ai/mod.rs): anthropic (alias claude), anthropic-local, openai, openai-compat, gemini (alias google), ollama. Default provider anthropic; per-provider default models (e.g. claude-opus-4-7 for Anthropic). API keys resolve <TOOL>_<PROVIDER>_API_KEY<PROVIDER>_API_KEY → well-known fallback; ollama needs none. The endpoint is overridable via RTB_AI_BASE_URL for wiremock-backed tests.

Verifier / repair loop (src/ai/codegen.rs), mirroring gtb's internal/generator/verifier/agent.go:

  1. Render the AI draft into the target file.
  2. Run cargo check against the project.
  3. On failure, feed the diagnostics + offending code back to the AI for a corrected version; apply and return to step 2.
  4. Stop on a clean compile, or when --max-repair-iterations (default 5) is hit — in which case the last attempt is left on disk with the unresolved diagnostics printed for the user to take over.

--agentless short-circuits to a single one-shot draft. Integration tests stub the provider with wiremock — they never call a live provider.

Templates and security

Templates live under crates/rtb-cli-bin/templates/, are embedded into the binary at compile time via rust-embed (so scaffolding works airgapped), and render through minijinja (Jinja2-compatible). Template filenames themselves may carry placeholders.

Template security is defence-in-depth (CLAUDE.md "Code generation" guardrails):

  • Input validation first (src/validate.rs) — every user-influenced field is NFC-normalised and checked against a field-specific character class before it reaches a template: validate_name, validate_description, validate_env_prefix, validate_command_path, validate_flag_name. Reserved names (rtb, rust-tool-base, rtb-*, the framework command names, and Rust-reserved identifiers) are refused.
  • Context-aware escapers at render time (src/template/escapers.rs) — escape_yaml, escape_toml, escape_markdown, escape_shell_arg, registered as minijinja filters and applied at non-code render sites. Each is the identity on the safe class [a-zA-Z0-9 _.,/-], never panics, and is infallible.

When adding a new user-facing field, update validate.rs and add an entry to docs/development/template-security.md.

  • rtb-cli — the Application::builder and built-in command suite rtb is built on; explains the inherited doctor/version/… commands.
  • rtb-ai — the multi-provider chat client behind the codegen path.
  • rtb-vcsRepo::init / Repo::diff, the git contact points for generate project and regenerate project.
  • rtb-tui — the Wizard that drives interactive field collection.
  • v0.6 scope spec — the authoritative design record, including the Q-resolutions referenced above.