use std::process;
struct CmdHelp {
name: &'static str,
group: &'static str,
summary: &'static str,
usage: &'static str,
details: &'static str,
}
const GROUPS: &[&str] = &[
"Core",
"Schema & SQL",
"Auth & policy",
"SDK & native",
"Scaffold",
"Admin",
"Diagnostics",
];
const COMMANDS: &[CmdHelp] = &[
CmdHelp {
name: "serve",
group: "Core",
summary: "Start the broker (syncs schema from protos; control plane on data-port+10).",
usage: "udb serve [<proto-root>] [<namespace>] [<data-addr>]",
details: "\
<proto-root> proto dir to load (default: ./proto; \"\" keeps the default)
<namespace> UDB_PROTO_NAMESPACE filter (\"\" = none; an over-eager filter
that loads 0 custom schemas is warned at startup)
<data-addr> data-plane bind (default 0.0.0.0:50051); auth plane = port+10
Wait for the \"UDB DataBroker is ready: data=… auth=…\" line before clients.
Run `udb requirements` and `udb doctor --enterprise` FIRST.
Example: udb serve proto \"\" 0.0.0.0:50051",
},
CmdHelp {
name: "requirements",
group: "Core",
summary: "Print the backend contract this project's manifest declares (run before first start).",
usage: "udb requirements [--json]",
details: "\
Lists each required backend (Postgres/Qdrant/object-store/Redis), its env
vars, whether it's configured, and fatal-vs-degraded. Exits non-zero if a
fatal backend is unset.",
},
CmdHelp {
name: "doctor",
group: "Core",
summary: "Env + backend readiness; --enterprise adds a manifest-aware prerequisite preflight.",
usage: "udb doctor [--enterprise] [--probe] [--human]",
details: "\
--enterprise also check encryption/password/session/auth-plane/redis/ABAC
AND report any required backend your protos declare but you
haven't configured (the same condition that stops `serve`).
--probe actively probe backend connectivity. --human text output.
Exit: 0 clean, 2 warnings, 1 fail.",
},
CmdHelp {
name: "lint",
group: "Schema & SQL",
summary: "Lint the catalog built from your annotated protos (CI-safe; nonzero on errors).",
usage: "udb lint [--human]",
details: "",
},
CmdHelp {
name: "drift",
group: "Schema & SQL",
summary: "Diff the manifest against a prior one; flags destructive/blocked changes.",
usage: "udb drift [--prior <manifest.json>]",
details: "",
},
CmdHelp {
name: "sql",
group: "Schema & SQL",
summary: "Emit the generated bootstrap SQL artifacts as JSON (no DB touched).",
usage: "udb sql",
details: "",
},
CmdHelp {
name: "plan",
group: "Schema & SQL",
summary: "Build a migration plan (optionally vs a prior manifest) as JSON.",
usage: "udb plan [--prior <manifest.json>]",
details: "",
},
CmdHelp {
name: "catalog",
group: "Schema & SQL",
summary: "Dump the parsed proto catalog as JSON.",
usage: "udb catalog",
details: "",
},
CmdHelp {
name: "dsn",
group: "Schema & SQL",
summary: "Print the unified DSN catalog derived from the protos.",
usage: "udb dsn",
details: "",
},
CmdHelp {
name: "manifest-export",
group: "Schema & SQL",
summary: "Export the current CatalogManifest to JSON (for CI plan-approval).",
usage: "udb manifest-export",
details: "",
},
CmdHelp {
name: "auth bootstrap user",
group: "Auth & policy",
summary: "Mint the FIRST admin OFFLINE (no running broker needed); prints the canonical tenant UUID.",
usage: "udb auth bootstrap user --username <u> --email <e> --password <p> --tenant <code> --project <p>",
details: "\
Needs UDB_PG_DSN + UDB_PASSWORD_HASH_SECRET. Defaults: --username admin,
--tenant acme, --project default. CAPTURE the printed tenant_id (UUID) —
it, not the human code, goes in the login JWT and tenant-scoped filters.
After this you MUST seed ABAC (default-deny) before any data CRUD works.",
},
CmdHelp {
name: "auth api-key create",
group: "Auth & policy",
summary: "Mint an API key (tenant-scoped).",
usage: "udb auth api-key create --owner <id> --name <n> --scope <s> [--scope <s>…]",
details: "",
},
CmdHelp {
name: "auth policy put",
group: "Auth & policy",
summary: "Write a control-plane Casbin governance rule (NOT the data-plane ABAC gate).",
usage: "udb auth policy put --subject <s> --action <a> --resource <r> --effect <ALLOW|DENY> --tenant <t> --project <p>",
details: "",
},
CmdHelp {
name: "policy-lint",
group: "Auth & policy",
summary: "Lint ABAC policy files from UDB_ABAC_POLICY_FILE (nonzero on broken files).",
usage: "udb policy-lint",
details: "",
},
CmdHelp {
name: "policy-seed",
group: "Auth & policy",
summary: "Generate INSERT SQL to seed ABAC policies into the UDB ABAC table.",
usage: "udb policy-seed",
details: "",
},
CmdHelp {
name: "proto export",
group: "SDK & native",
summary: "Vendor UDB's annotation protos so app protos can import udb/core/common/v1/db.proto.",
usage: "udb proto export --out <dir> [--no-buf-yaml] [--fmt]",
details: "\
Sibling verb: `udb proto fmt [<dir>] [--check]` re-wraps long UDB field
annotations onto one physical line (narrower than `buf format`).",
},
CmdHelp {
name: "sdk generate",
group: "SDK & native",
summary: "Generate/refresh a language SDK from the embedded RPC manifest + templates.",
usage: "udb sdk generate --lang <ts|python|go|java|csharp|php|all> [--out <dir>] [--surface <…>]",
details: "\
Sibling verbs: `udb sdk manifest` (dump the RPC surface as JSON),
`udb sdk list-langs` (available template dirs).",
},
CmdHelp {
name: "native list",
group: "SDK & native",
summary: "Inspect the descriptor-derived native-service contract.",
usage: "udb native <list|manifest|docs|lint|contract-diff|contract-baseline> [--json]",
details: "",
},
CmdHelp {
name: "init",
group: "Scaffold",
summary: "Project-aware scaffold planner/executor.",
usage: "udb init [--profile <p>] [--backend <b>…] [--native-service <s>…] [--yes] [--dry-run]",
details: "",
},
CmdHelp {
name: "init-project",
group: "Scaffold",
summary: "Scaffold a minimal project (sample proto, config, DDL, docker-compose).",
usage: "udb init-project",
details: "",
},
CmdHelp {
name: "app init",
group: "Scaffold",
summary: "Scaffold an app integration wiring the UdbProject facade.",
usage: "udb app init --lang <l> --services <s,…> --tenant <t> --project <p> --out <dir>",
details: "",
},
CmdHelp {
name: "dev up",
group: "Scaffold",
summary: "Start/stop/test the local multi-backend sandbox (from a repo checkout).",
usage: "udb dev <up|down|smoke> [<service>] [--yes]",
details: "",
},
CmdHelp {
name: "admin force-sync",
group: "Admin",
summary: "Force the startup lifecycle from the CLI and exit with a JSON report.",
usage: "udb admin force-sync",
details: "",
},
CmdHelp {
name: "admin release-lock",
group: "Admin",
summary: "Terminate the PG session(s) holding the startup advisory lock (clear a stale lock).",
usage: "udb admin release-lock",
details: "Run against the DIRECT DSN, not a pooler.",
},
CmdHelp {
name: "admin reset-db",
group: "Admin",
summary: "Drop all UDB-managed schemas + ledger tables (DESTRUCTIVE; needs --yes).",
usage: "udb admin reset-db --yes",
details: "",
},
CmdHelp {
name: "admin dry-run",
group: "Admin",
summary: "Generate the SQL plan and exit WITHOUT applying (safe on production).",
usage: "udb admin dry-run",
details: "",
},
CmdHelp {
name: "admin verify-audit",
group: "Admin",
summary: "Verify the tamper-evident admin audit-log hash chain.",
usage: "udb admin verify-audit [--limit <n>]",
details: "",
},
CmdHelp {
name: "sync-migrations",
group: "Admin",
summary: "Sync db_ops/migrations with the current proto AST.",
usage: "udb sync-migrations [--force-bootstrap] [--backend <b>]",
details: "",
},
CmdHelp {
name: "compat-matrix",
group: "Diagnostics",
summary: "Print the authoritative supported proto-annotation matrix as JSON.",
usage: "udb compat-matrix",
details: "",
},
CmdHelp {
name: "explain",
group: "Diagnostics",
summary: "Explain the generated DDL/DSN/policies for a message type.",
usage: "udb explain",
details: "",
},
CmdHelp {
name: "health-check",
group: "Diagnostics",
summary: "Lightweight Docker HEALTHCHECK — exit 0 if healthy.",
usage: "udb health-check",
details: "",
},
CmdHelp {
name: "tracker-ddl",
group: "Diagnostics",
summary: "Emit the migration-ledger table DDL to stdout.",
usage: "udb tracker-ddl",
details: "",
},
CmdHelp {
name: "config-skeleton",
group: "Diagnostics",
summary: "Emit a default MigrationOptions config skeleton as JSON.",
usage: "udb config-skeleton",
details: "",
},
];
pub(crate) fn handle_help_or_version(args: &[String]) {
if args.iter().any(|a| a == "--version" || a == "-V") {
println!("udb {}", env!("CARGO_PKG_VERSION"));
process::exit(0);
}
let first = args.first().map(String::as_str);
if first == Some("help") {
let topic = args[1..].join(" ");
if topic.is_empty() {
print_top_level();
} else {
print_command_help(&topic);
}
process::exit(0);
}
if args.is_empty() || matches!(first, Some("--help") | Some("-h")) {
print_top_level();
process::exit(0);
}
if args.iter().skip(1).any(|a| a == "--help" || a == "-h") {
let non_flag: Vec<&str> = args
.iter()
.map(String::as_str)
.take_while(|a| !a.starts_with('-'))
.collect();
let topic = non_flag.join(" ");
print_command_help(&topic);
process::exit(0);
}
}
fn print_top_level() {
println!(
"udb {} — proto-driven multi-database broker\n",
env!("CARGO_PKG_VERSION")
);
println!(
"USAGE:\n udb <command> [args] [flags]\n udb help <command> show a command's flags + example\n udb <command> --help same\n udb --version\n"
);
println!("COMMANDS:");
for group in GROUPS {
let mut printed_group = false;
for cmd in COMMANDS.iter().filter(|c| &c.group == group) {
if !printed_group {
println!("\n {group}:");
printed_group = true;
}
println!(" {:<22} {}", cmd.name, cmd.summary);
}
}
println!(
"\nNew project? The bootstrap runbook (proto → bootstrap admin → seed ABAC →\n\
login → CRUD) is in docs/enterprise-deployment.md and examples/ts_enterprise.\n\
Ground truth for RPCs/annotations: `udb sdk manifest`, `udb native list`, `udb compat-matrix`."
);
}
fn print_command_help(topic: &str) {
let topic = topic.trim();
let found = COMMANDS.iter().find(|c| c.name == topic).or_else(|| {
COMMANDS
.iter()
.filter(|c| topic.starts_with(c.name) || c.name.starts_with(topic))
.max_by_key(|c| c.name.len())
});
match found {
Some(cmd) => {
println!("udb {} — {}\n", cmd.name, cmd.summary);
if !cmd.usage.is_empty() {
println!("USAGE:\n {}\n", cmd.usage);
}
if !cmd.details.is_empty() {
println!("{}", cmd.details);
}
}
None => {
println!("No help for '{topic}'.\n");
print_top_level();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_command_has_a_known_group() {
for cmd in COMMANDS {
assert!(
GROUPS.contains(&cmd.group),
"command {} has unlisted group {}",
cmd.name,
cmd.group
);
}
}
#[test]
fn core_commands_are_documented() {
for want in [
"serve",
"doctor",
"requirements",
"auth bootstrap user",
"sdk generate",
] {
assert!(
COMMANDS.iter().any(|c| c.name == want),
"missing help entry for {want}"
);
}
}
}