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.rsisrtb_cli::Application::builder()— the same entry point a scaffolded tool uses (see rtb-cli). Sortbinherits 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:
Runtime features¶
rtb's main.rs tunes the inherited framework feature set
(crates/rtb-cli-bin/src/main.rs):
Initis disabled — the scaffolder runs on baked-in defaults and has no per-user config of its own to bootstrap.Aiis enabled — required for the prompt/script → Rust codegen path and the autonomous repair loop.Aiis 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'sprotected:list;rtb regeneratethen 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 foreignsha256:manifest. - The struct deserialises with
deny_unknown_fields; the schema is versioned viartb_version. Pre-1.0 the keys may evolve — downstream tools should not parse this file outsidertb.
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:
- Render the AI draft into the target file.
- Run
cargo checkagainst the project. - On failure, feed the diagnostics + offending code back to the AI for a corrected version; apply and return to step 2.
- 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.
Related¶
- rtb-cli — the
Application::builderand built-in command suitertbis built on; explains the inheriteddoctor/version/… commands. - rtb-ai — the multi-provider chat client behind the codegen path.
- rtb-vcs —
Repo::init/Repo::diff, the git contact points forgenerate projectandregenerate project. - rtb-tui — the
Wizardthat drives interactive field collection. - v0.6 scope spec — the authoritative design record, including the Q-resolutions referenced above.