use super::*;
pub(crate) enum Command {
InvalidUsage {
message: String,
},
Catalog,
Dsn,
Sql,
Plan,
Lint,
Drift,
Doctor {
output_mode: DoctorOutputMode,
with_probes: bool,
enterprise: bool,
fix: bool,
},
Requirements {
json: bool,
},
HealthCheck,
Serve,
AdminForceSync,
AdminDryRun,
TrackerDdl,
SystemDdl,
StatusSchema,
FsmStates,
ConfigSkeleton,
InitProject,
Init(InitArgs),
Dev {
action: DevAction,
service: Option<String>,
confirmed: bool,
},
Explain,
ManifestExport,
PolicyLint,
PolicySeed,
FieldMaskPreview,
CompatMatrix,
AdminReleaseLock,
AdminVerifyAudit {
limit: i64,
},
AdminResetDb {
confirmed: bool,
},
SyncMigrations {
force_bootstrap: bool,
backend: Option<String>,
},
Auth(AuthCommand),
Authz(AuthzCommand),
Compliance(ComplianceCommand),
ProtoExport {
out_dir: String,
manage_buf_yaml: bool,
format_proto: bool,
},
ProtoFmt {
root: String,
check: bool,
},
Sdk {
action: SdkAction,
lang: String,
templates_dir: String,
out_dir: String,
selector: SdkSelector,
},
Orm {
action: OrmAction,
lang: String,
entity: Option<String>,
templates_dir: String,
out_dir: String,
},
Native {
action: NativeAction,
services: Vec<String>,
out_dir: String,
lang: Option<String>,
framework: String,
json: bool,
confirmed: bool,
baseline: String,
},
AppInit {
lang: String,
framework: String,
services: Vec<String>,
tenant: String,
project: String,
auth: String,
out_dir: String,
package_manager: String,
confirmed: bool,
},
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub(crate) struct InitArgs {
pub tui: bool,
pub no_tui: bool,
pub confirmed: bool,
pub force: bool,
pub revert: bool,
pub config: Option<String>,
pub json_plan: bool,
pub dry_run: bool,
pub profile: Option<String>,
pub framework: Option<String>,
pub backends: Vec<String>,
pub native_services: Vec<String>,
pub features: Vec<String>,
pub proto_strategy: Option<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub(crate) struct SdkSelector {
pub surface: Option<String>,
pub services: Vec<String>,
pub native_only: bool,
pub include_deps: bool,
pub strict_server_capabilities: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum SdkAction {
Generate,
Manifest,
ListLangs,
Init,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum OrmAction {
Scaffold,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum NativeAction {
Manifest,
List,
Lint,
Add,
Remove,
Generate,
Doctor,
Smoke,
ContractBaseline,
ContractDiff,
Docs,
}
pub(crate) enum AuthCommand {
PrincipalList {
tenant_id: String,
},
IdentityLink {
user_id: String,
provider_id: String,
subject: String,
},
SessionRevoke {
session_id: String,
principal_id: String,
all_for_principal: bool,
},
ApiKeyCreate {
owner_id: String,
name: String,
scopes: Vec<String>,
},
ApiKeyRevoke {
key_prefix: String,
},
ApiKeyList {
owner_id: String,
},
Bootstrap {
username: String,
email: String,
password: String,
tenant: String,
project: String,
},
MigrateGrants {
dry_run: bool,
},
GrantCreate {
tenant_id: String,
user_id: String,
service_identity: String,
project_id: String,
scopes: Vec<String>,
reason: String,
},
GrantGet {
tenant_id: String,
user_id: String,
},
GrantList {
tenant_id: String,
},
GrantReplace {
tenant_id: String,
user_id: String,
scopes: Vec<String>,
project_id: String,
reason: String,
expected_revision: i64,
},
GrantRotateIdentity {
tenant_id: String,
user_id: String,
new_service_identity: String,
reason: String,
expected_revision: i64,
},
GrantRevoke {
tenant_id: String,
user_id: String,
reason: String,
},
CertBindingCreate {
tenant_id: String,
user_id: String,
selector_kind: String,
selector_value: String,
scope_subset: Vec<String>,
not_before_unix: u64,
not_after_unix: u64,
reason: String,
},
CertBindingList {
tenant_id: String,
},
CertBindingRevoke {
tenant_id: String,
binding_id: String,
reason: String,
},
RoleBind {
user_id: String,
role_id: String,
domain: String,
assigned_by: String,
},
RelationPut {
subject: String,
relation: String,
object: String,
tenant: String,
project: String,
},
PolicyPut {
subject: String,
role: String,
action: String,
resource: String,
effect: String,
tenant: String,
project: String,
},
PolicyLint,
}
pub(crate) enum AuthzCommand {
Simulate {
bundle: String,
draft_id: String,
tenant: String,
project: String,
persist: bool,
policy_version_id: String,
},
}
pub(crate) enum ComplianceCommand {
Evidence {
since: String,
until: String,
tenant: String,
backend: String,
bucket: String,
prefix: String,
project: String,
limit: i64,
out: String,
dry_run: bool,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum DevAction {
Up,
Down,
Logs,
Status,
Reset,
Smoke,
}
impl DevAction {
pub(crate) fn parse(value: Option<&str>) -> Result<Self, String> {
match value.unwrap_or("up") {
"up" | "start" => Ok(Self::Up),
"down" | "stop" => Ok(Self::Down),
"logs" => Ok(Self::Logs),
"status" | "ps" => Ok(Self::Status),
"reset" => Ok(Self::Reset),
"smoke" | "test" => Ok(Self::Smoke),
other => Err(format!(
"unknown dev action '{other}'{}; known: {}",
did_you_mean(other, DEV_ACTIONS),
DEV_ACTIONS.join(", ")
)),
}
}
}
const KNOWN_COMMANDS: &[&str] = &[
"catalog",
"dsn",
"sql",
"plan",
"lint",
"drift",
"doctor",
"health-check",
"init",
"init-project",
"scaffold",
"dev",
"explain",
"manifest-export",
"policy-lint",
"policy-seed",
"field-mask-preview",
"compat-matrix",
"sync-migrations",
"dbops sync",
"auth",
"authz simulate",
"compliance evidence",
"serve",
"proto export",
"proto fmt",
"sdk",
"native",
"app init",
"admin force-sync",
"admin release-lock",
"admin verify-audit",
"admin dry-run",
"admin reset-db",
"tracker-ddl",
"system-ddl",
"status-schema",
"fsm-states",
"config-skeleton",
];
const DEV_ACTIONS: &[&str] = &[
"up/start",
"down/stop",
"logs",
"status/ps",
"reset",
"smoke/test",
];
const SDK_ACTIONS: &[&str] = &[
"generate",
"init/doctor/preflight",
"manifest",
"list-langs/languages",
];
const ORM_ACTIONS: &[&str] = &["scaffold"];
const NATIVE_ACTIONS: &[&str] = &[
"manifest",
"list",
"lint",
"add",
"remove/rm",
"generate/gen",
"doctor",
"smoke",
"contract-baseline",
"contract-diff",
"docs",
];
fn invalid_usage(message: impl Into<String>) -> Command {
Command::InvalidUsage {
message: message.into(),
}
}
fn edit_distance(a: &str, b: &str) -> usize {
let a: Vec<char> = a.chars().collect();
let b: Vec<char> = b.chars().collect();
let mut prev: Vec<usize> = (0..=b.len()).collect();
let mut curr = vec![0usize; b.len() + 1];
for (i, ca) in a.iter().enumerate() {
curr[0] = i + 1;
for (j, cb) in b.iter().enumerate() {
let cost = usize::from(ca != cb);
curr[j + 1] = (prev[j + 1] + 1).min(curr[j] + 1).min(prev[j] + cost);
}
std::mem::swap(&mut prev, &mut curr);
}
prev[b.len()]
}
fn did_you_mean(input: &str, candidates: &[&str]) -> String {
let best = candidates
.iter()
.flat_map(|c| c.split(['/', ' ']))
.filter(|atom| !atom.is_empty())
.map(|atom| (atom, edit_distance(input, atom)))
.filter(|(_, d)| (1..=2).contains(d))
.min_by_key(|(_, d)| *d)
.map(|(atom, _)| atom);
match best {
Some(atom) => format!("; did you mean '{atom}'?"),
None => String::new(),
}
}
fn parse_auth_subcommand(args: &[String]) -> Option<(AuthCommand, usize)> {
let has_flag = |flag: &str| -> bool { args.iter().any(|a| a == flag) };
let flag_value = |flag: &str| -> Option<String> {
args.windows(2).find(|w| w[0] == flag).map(|w| w[1].clone())
};
let flag_values = |flag: &str| -> Vec<String> {
args.windows(2)
.filter(|w| w[0] == flag)
.map(|w| w[1].clone())
.collect()
};
let noun = args.get(1).map(|value| value.as_str());
let verb = args.get(2).map(|value| value.as_str());
let command = match (noun, verb) {
(Some("principal"), Some("list")) => AuthCommand::PrincipalList {
tenant_id: flag_value("--tenant").unwrap_or_default(),
},
(Some("identity"), Some("link")) => AuthCommand::IdentityLink {
user_id: flag_value("--user").unwrap_or_default(),
provider_id: flag_value("--provider").unwrap_or_default(),
subject: flag_value("--subject").unwrap_or_default(),
},
(Some("session"), Some("revoke")) => AuthCommand::SessionRevoke {
session_id: flag_value("--session").unwrap_or_default(),
principal_id: flag_value("--principal").unwrap_or_default(),
all_for_principal: has_flag("--all-for-principal"),
},
(Some("api-key"), Some("create")) => AuthCommand::ApiKeyCreate {
owner_id: flag_value("--owner").unwrap_or_default(),
name: flag_value("--name").unwrap_or_default(),
scopes: flag_values("--scope"),
},
(Some("api-key"), Some("revoke")) => AuthCommand::ApiKeyRevoke {
key_prefix: flag_value("--key").unwrap_or_default(),
},
(Some("api-key"), Some("list")) => AuthCommand::ApiKeyList {
owner_id: flag_value("--owner").unwrap_or_default(),
},
(Some("migrate-grants"), _) => AuthCommand::MigrateGrants {
dry_run: has_flag("--dry-run"),
},
(Some("grant"), Some("create")) => AuthCommand::GrantCreate {
tenant_id: flag_value("--tenant").unwrap_or_default(),
user_id: flag_value("--user").unwrap_or_default(),
service_identity: flag_value("--identity").unwrap_or_default(),
project_id: flag_value("--project").unwrap_or_default(),
scopes: flag_values("--scope"),
reason: flag_value("--reason").unwrap_or_default(),
},
(Some("grant"), Some("get")) => AuthCommand::GrantGet {
tenant_id: flag_value("--tenant").unwrap_or_default(),
user_id: flag_value("--user").unwrap_or_default(),
},
(Some("grant"), Some("list")) => AuthCommand::GrantList {
tenant_id: flag_value("--tenant").unwrap_or_default(),
},
(Some("grant"), Some("replace")) => AuthCommand::GrantReplace {
tenant_id: flag_value("--tenant").unwrap_or_default(),
user_id: flag_value("--user").unwrap_or_default(),
scopes: flag_values("--scope"),
project_id: flag_value("--project").unwrap_or_default(),
reason: flag_value("--reason").unwrap_or_default(),
expected_revision: flag_value("--expected-revision")
.and_then(|value| value.trim().parse::<i64>().ok())
.unwrap_or(0),
},
(Some("grant"), Some("revoke")) => AuthCommand::GrantRevoke {
tenant_id: flag_value("--tenant").unwrap_or_default(),
user_id: flag_value("--user").unwrap_or_default(),
reason: flag_value("--reason").unwrap_or_default(),
},
(Some("grant"), Some("rotate-identity")) => AuthCommand::GrantRotateIdentity {
tenant_id: flag_value("--tenant").unwrap_or_default(),
user_id: flag_value("--user").unwrap_or_default(),
new_service_identity: flag_value("--identity").unwrap_or_default(),
reason: flag_value("--reason").unwrap_or_default(),
expected_revision: flag_value("--expected-revision")
.and_then(|value| value.trim().parse::<i64>().ok())
.unwrap_or(0),
},
(Some("cert-binding"), Some("create")) => AuthCommand::CertBindingCreate {
tenant_id: flag_value("--tenant").unwrap_or_default(),
user_id: flag_value("--user").unwrap_or_default(),
selector_kind: flag_value("--selector-kind").unwrap_or_default(),
selector_value: flag_value("--selector-value").unwrap_or_default(),
scope_subset: flag_values("--scope"),
not_before_unix: flag_value("--not-before-unix")
.and_then(|value| value.trim().parse::<u64>().ok())
.unwrap_or(0),
not_after_unix: flag_value("--not-after-unix")
.and_then(|value| value.trim().parse::<u64>().ok())
.unwrap_or(0),
reason: flag_value("--reason").unwrap_or_default(),
},
(Some("cert-binding"), Some("list")) => AuthCommand::CertBindingList {
tenant_id: flag_value("--tenant").unwrap_or_default(),
},
(Some("cert-binding"), Some("revoke")) => AuthCommand::CertBindingRevoke {
tenant_id: flag_value("--tenant").unwrap_or_default(),
binding_id: flag_value("--binding").unwrap_or_default(),
reason: flag_value("--reason").unwrap_or_default(),
},
(Some("bootstrap"), Some("user")) => AuthCommand::Bootstrap {
username: flag_value("--username").unwrap_or_else(|| "admin".to_string()),
email: flag_value("--email").unwrap_or_else(|| "admin@example.com".to_string()),
password: flag_value("--password").unwrap_or_default(),
tenant: flag_value("--tenant").unwrap_or_else(|| "acme".to_string()),
project: flag_value("--project").unwrap_or_else(|| "default".to_string()),
},
(Some("role"), Some("bind")) => AuthCommand::RoleBind {
user_id: flag_value("--user").unwrap_or_default(),
role_id: flag_value("--role").unwrap_or_default(),
domain: flag_value("--domain").unwrap_or_default(),
assigned_by: flag_value("--by").unwrap_or_default(),
},
(Some("relation"), Some("put")) => AuthCommand::RelationPut {
subject: flag_value("--subject").unwrap_or_default(),
relation: flag_value("--relation").unwrap_or_default(),
object: flag_value("--object").unwrap_or_default(),
tenant: flag_value("--tenant").unwrap_or_default(),
project: flag_value("--project").unwrap_or_default(),
},
(Some("policy"), Some("put")) => AuthCommand::PolicyPut {
subject: flag_value("--subject").unwrap_or_default(),
role: flag_value("--role").unwrap_or_default(),
action: flag_value("--action").unwrap_or_default(),
resource: flag_value("--resource").unwrap_or_default(),
effect: flag_value("--effect").unwrap_or_else(|| "ALLOW".to_string()),
tenant: flag_value("--tenant").unwrap_or_default(),
project: flag_value("--project").unwrap_or_default(),
},
(Some("policy"), Some("lint")) => AuthCommand::PolicyLint,
_ => return None,
};
Some((command, 3))
}
fn parse_authz_subcommand(args: &[String]) -> Option<(AuthzCommand, usize)> {
let has_flag = |flag: &str| -> bool { args.iter().any(|a| a == flag) };
let flag_value = |flag: &str| -> Option<String> {
args.windows(2).find(|w| w[0] == flag).map(|w| w[1].clone())
};
let command = match args.get(1).map(|value| value.as_str()) {
Some("simulate") | Some("sim") => AuthzCommand::Simulate {
bundle: flag_value("--bundle").unwrap_or_default(),
draft_id: flag_value("--draft").unwrap_or_default(),
tenant: flag_value("--tenant").unwrap_or_default(),
project: flag_value("--project").unwrap_or_default(),
persist: has_flag("--persist"),
policy_version_id: flag_value("--policy-version").unwrap_or_default(),
},
_ => return None,
};
Some((command, 2))
}
fn parse_compliance_subcommand(args: &[String]) -> Option<(ComplianceCommand, usize)> {
let has_flag = |flag: &str| -> bool { args.iter().any(|a| a == flag) };
let flag_value = |flag: &str| -> Option<String> {
args.windows(2).find(|w| w[0] == flag).map(|w| w[1].clone())
};
let command = match args.get(1).map(|value| value.as_str()) {
Some("evidence") | Some("export") => ComplianceCommand::Evidence {
since: flag_value("--since").unwrap_or_default(),
until: flag_value("--until").unwrap_or_default(),
tenant: flag_value("--tenant").unwrap_or_default(),
backend: flag_value("--backend").unwrap_or_default(),
bucket: flag_value("--bucket").unwrap_or_default(),
prefix: flag_value("--prefix").unwrap_or_default(),
project: flag_value("--project").unwrap_or_default(),
limit: flag_value("--limit")
.and_then(|value| value.parse::<i64>().ok())
.unwrap_or(0),
out: flag_value("--out").unwrap_or_default(),
dry_run: has_flag("--dry-run"),
},
_ => return None,
};
Some((command, 2))
}
pub(crate) fn split_csv(value: &str) -> Vec<String> {
value
.split(',')
.map(|token| token.trim().to_string())
.filter(|token| !token.is_empty())
.collect()
}
const VALUE_FLAGS: &[&str] = &[
"--prior",
"--backend",
"--out",
"--lang",
"--entity",
"--templates",
"--config",
"--tenant",
"--project",
"--user",
"--provider",
"--subject",
"--session",
"--principal",
"--owner",
"--name",
"--scope",
"--role",
"--domain",
"--by",
"--relation",
"--object",
"--action",
"--resource",
"--effect",
"--root",
"--limit",
"--surface",
"--service",
"--services",
"--framework",
"--auth",
"--package-manager",
"--baseline",
"--profile",
"--native-service",
"--feature",
"--proto-strategy",
"--bundle",
"--draft",
"--policy-version",
"--since",
"--until",
"--bucket",
"--prefix",
];
fn collect_positional_services(args: &[String], start: usize) -> Vec<String> {
let mut out = Vec::new();
let mut i = start;
while i < args.len() {
let arg = args[i].as_str();
if VALUE_FLAGS.contains(&arg) {
i += 2; continue;
}
if arg.starts_with("--") {
i += 1;
continue;
}
out.push(args[i].clone());
i += 1;
}
out
}
pub(crate) fn parse_args(args: &[String]) -> (Command, String, String, String) {
let mut offset = 0usize;
let has_flag = |flag: &str| -> bool { args.iter().any(|a| a == flag) };
let flag_value = |flag: &str| -> Option<String> {
args.windows(2).find(|w| w[0] == flag).map(|w| w[1].clone())
};
let flag_values = |flag: &str| -> Vec<String> {
args.windows(2)
.filter(|w| w[0] == flag)
.map(|w| w[1].clone())
.collect()
};
let doctor_output_mode = if has_flag("--human") {
DoctorOutputMode::Human
} else {
DoctorOutputMode::Json
};
let with_probes = has_flag("--probe");
let doctor_enterprise = has_flag("--enterprise");
let doctor_fix = has_flag("--fix");
let prior_manifest_path: Option<String> = flag_value("--prior");
let _ = prior_manifest_path;
let command = match args.first().map(|value| value.as_str()) {
Some("catalog") => {
offset = 1;
Command::Catalog
}
Some("dsn") => {
offset = 1;
Command::Dsn
}
Some("sql") => {
offset = 1;
Command::Sql
}
Some("plan") => {
offset = 1;
Command::Plan
}
Some("lint") => {
offset = 1;
Command::Lint
}
Some("drift") => {
offset = 1;
Command::Drift
}
Some("doctor") => {
offset = 1;
Command::Doctor {
output_mode: doctor_output_mode,
with_probes,
enterprise: doctor_enterprise,
fix: doctor_fix,
}
}
Some("requirements") | Some("requires") => {
offset = 1;
Command::Requirements {
json: has_flag("--json"),
}
}
Some("health-check") | Some("healthcheck") => {
offset = 1;
Command::HealthCheck
}
Some("init") => {
offset = 1;
Command::Init(InitArgs {
tui: has_flag("--tui"),
no_tui: has_flag("--no-tui"),
confirmed: has_flag("--yes"),
force: has_flag("--force"),
revert: has_flag("--revert"),
config: flag_value("--config"),
json_plan: has_flag("--json-plan"),
dry_run: has_flag("--dry-run"),
profile: flag_value("--profile"),
framework: flag_value("--framework"),
backends: flag_values("--backend")
.into_iter()
.flat_map(|value| split_csv(&value))
.collect(),
native_services: flag_values("--native-service")
.into_iter()
.chain(flag_values("--services"))
.flat_map(|value| split_csv(&value))
.collect(),
features: flag_values("--feature")
.into_iter()
.flat_map(|value| split_csv(&value))
.collect(),
proto_strategy: flag_value("--proto-strategy"),
})
}
Some("init-project") | Some("scaffold") => {
offset = 1;
Command::InitProject
}
Some("dev") => {
offset = 1;
match DevAction::parse(
args.get(1)
.map(String::as_str)
.filter(|value| !value.starts_with("--")),
) {
Ok(action) => Command::Dev {
action,
service: args
.get(2)
.filter(|value| !value.starts_with("--"))
.cloned(),
confirmed: has_flag("--yes"),
},
Err(message) => invalid_usage(message),
}
}
Some("explain") => {
offset = 1;
Command::Explain
}
Some("manifest-export") => {
offset = 1;
Command::ManifestExport
}
Some("policy-lint") => {
offset = 1;
Command::PolicyLint
}
Some("policy-seed") => {
offset = 1;
Command::PolicySeed
}
Some("field-mask-preview") => {
offset = 1;
Command::FieldMaskPreview
}
Some("compat-matrix") => {
offset = 1;
Command::CompatMatrix
}
Some("sync-migrations") => {
offset = 1;
Command::SyncMigrations {
force_bootstrap: has_flag("--force-bootstrap"),
backend: flag_value("--backend"),
}
}
Some("dbops") if args.get(1).map(|value| value.as_str()) == Some("sync") => {
offset = 2;
Command::SyncMigrations {
force_bootstrap: has_flag("--force-bootstrap"),
backend: flag_value("--backend"),
}
}
Some("auth") if parse_auth_subcommand(args).is_some() => {
let (auth_command, consumed) =
parse_auth_subcommand(args).expect("guard guarantees Some");
offset = consumed;
Command::Auth(auth_command)
}
Some("authz") if parse_authz_subcommand(args).is_some() => {
let (authz_command, consumed) =
parse_authz_subcommand(args).expect("guard guarantees Some");
offset = consumed;
Command::Authz(authz_command)
}
Some("compliance") if parse_compliance_subcommand(args).is_some() => {
let (compliance_command, consumed) =
parse_compliance_subcommand(args).expect("guard guarantees Some");
offset = consumed;
Command::Compliance(compliance_command)
}
Some("serve") => {
offset = 1;
Command::Serve
}
Some("proto") if args.get(1).map(|value| value.as_str()) == Some("export") => {
offset = 2;
Command::ProtoExport {
out_dir: flag_value("--out").unwrap_or_default(),
manage_buf_yaml: !has_flag("--no-buf-yaml"),
format_proto: has_flag("--fmt") || has_flag("--format"),
}
}
Some("proto") if args.get(1).map(|value| value.as_str()) == Some("fmt") => {
offset = 2;
Command::ProtoFmt {
root: flag_value("--out")
.or_else(|| flag_value("--root"))
.or_else(|| {
args.get(2)
.filter(|value| !value.starts_with("--"))
.cloned()
})
.unwrap_or_default(),
check: has_flag("--check"),
}
}
Some("sdk") => {
offset = 2;
let action = match args.get(1).map(|value| value.as_str()) {
None => SdkAction::Generate,
Some(value) if value.starts_with("--") => SdkAction::Generate,
Some("generate") | Some("gen") => SdkAction::Generate,
Some("init") | Some("doctor") | Some("preflight") => SdkAction::Init,
Some("manifest") => SdkAction::Manifest,
Some("list-langs") | Some("languages") => SdkAction::ListLangs,
Some(other) => {
return (
invalid_usage(format!(
"unknown sdk action '{other}'{}; known: {}",
did_you_mean(other, SDK_ACTIONS),
SDK_ACTIONS.join(", ")
)),
"proto".to_string(),
String::new(),
DEFAULT_GRPC_BIND_ADDR.to_string(),
);
}
};
let services: Vec<String> = flag_value("--service")
.map(|value| split_csv(&value))
.unwrap_or_default();
Command::Sdk {
action,
lang: flag_value("--lang").unwrap_or_else(|| "all".to_string()),
templates_dir: flag_value("--templates")
.unwrap_or_else(|| "sdk-templates".to_string()),
out_dir: flag_value("--out").unwrap_or_else(|| "sdk".to_string()),
selector: SdkSelector {
surface: flag_value("--surface"),
services,
native_only: has_flag("--native-services"),
include_deps: has_flag("--include-deps"),
strict_server_capabilities: has_flag("--strict-server-capabilities"),
},
}
}
Some("orm") => {
offset = 2;
let action = match args.get(1).map(|value| value.as_str()) {
None => OrmAction::Scaffold,
Some(value) if value.starts_with("--") => OrmAction::Scaffold,
Some("scaffold") | Some("models") | Some("generate") => OrmAction::Scaffold,
Some(other) => {
return (
invalid_usage(format!(
"unknown orm action '{other}'{}; known: {}",
did_you_mean(other, ORM_ACTIONS),
ORM_ACTIONS.join(", ")
)),
"proto".to_string(),
String::new(),
DEFAULT_GRPC_BIND_ADDR.to_string(),
);
}
};
Command::Orm {
action,
lang: flag_value("--lang").unwrap_or_else(|| "all".to_string()),
entity: flag_value("--entity"),
templates_dir: flag_value("--templates")
.unwrap_or_else(|| "sdk-templates".to_string()),
out_dir: flag_value("--out").unwrap_or_else(|| "sdk".to_string()),
}
}
Some("native") => {
offset = 2;
let action = match args.get(1).map(|value| value.as_str()) {
None => NativeAction::Manifest,
Some(value) if value.starts_with("--") => NativeAction::Manifest,
Some("manifest") => NativeAction::Manifest,
Some("list") => NativeAction::List,
Some("lint") => NativeAction::Lint,
Some("add") => NativeAction::Add,
Some("remove") | Some("rm") => NativeAction::Remove,
Some("generate") | Some("gen") => NativeAction::Generate,
Some("doctor") => NativeAction::Doctor,
Some("smoke") => NativeAction::Smoke,
Some("contract-baseline") => NativeAction::ContractBaseline,
Some("contract-diff") => NativeAction::ContractDiff,
Some("docs") => NativeAction::Docs,
Some(other) => {
return (
invalid_usage(format!(
"unknown native action '{other}'{}; known: {}",
did_you_mean(other, NATIVE_ACTIONS),
NATIVE_ACTIONS.join(", ")
)),
"proto".to_string(),
String::new(),
DEFAULT_GRPC_BIND_ADDR.to_string(),
);
}
};
let services = collect_positional_services(args, 2);
Command::Native {
action,
services,
out_dir: flag_value("--out").unwrap_or_else(|| ".".to_string()),
lang: flag_value("--lang"),
framework: flag_value("--framework").unwrap_or_default(),
json: has_flag("--json"),
confirmed: has_flag("--yes"),
baseline: flag_value("--baseline").unwrap_or_default(),
}
}
Some("app") if args.get(1).map(|value| value.as_str()) == Some("init") => {
offset = 2;
let services: Vec<String> = flag_value("--services")
.map(|value| split_csv(&value))
.unwrap_or_default();
Command::AppInit {
lang: flag_value("--lang").unwrap_or_else(|| "typescript".to_string()),
framework: flag_value("--framework").unwrap_or_default(),
services,
tenant: flag_value("--tenant").unwrap_or_default(),
project: flag_value("--project").unwrap_or_default(),
auth: flag_value("--auth").unwrap_or_default(),
out_dir: flag_value("--out").unwrap_or_else(|| ".".to_string()),
package_manager: flag_value("--package-manager").unwrap_or_default(),
confirmed: has_flag("--yes"),
}
}
Some("admin") if args.get(1).map(|value| value.as_str()) == Some("force-sync") => {
offset = 2;
Command::AdminForceSync
}
Some("admin") if args.get(1).map(|value| value.as_str()) == Some("release-lock") => {
offset = 2;
Command::AdminReleaseLock
}
Some("admin")
if matches!(
args.get(1).map(|value| value.as_str()),
Some("verify-audit" | "verify-audit-log")
) =>
{
offset = 2;
Command::AdminVerifyAudit {
limit: flag_value("--limit")
.and_then(|value| value.parse::<i64>().ok())
.unwrap_or(0),
}
}
Some("admin") if args.get(1).map(|value| value.as_str()) == Some("dry-run") => {
offset = 2;
Command::AdminDryRun
}
Some("admin") if args.get(1).map(|value| value.as_str()) == Some("reset-db") => {
offset = 2;
Command::AdminResetDb {
confirmed: has_flag("--yes"),
}
}
Some("tracker-ddl") => {
offset = 1;
Command::TrackerDdl
}
Some("system-ddl") => {
offset = 1;
Command::SystemDdl
}
Some("status-schema") => {
offset = 1;
Command::StatusSchema
}
Some("fsm-states") => {
offset = 1;
Command::FsmStates
}
Some("config-skeleton") => {
offset = 1;
Command::ConfigSkeleton
}
Some(other) => invalid_usage(format!(
"unknown command '{other}'{}; known: {}",
did_you_mean(other, KNOWN_COMMANDS),
KNOWN_COMMANDS.join(", ")
)),
None => Command::Catalog,
};
let positional_args: Vec<String> = args
.iter()
.enumerate()
.skip(offset)
.filter_map(|(index, arg)| {
if index > offset && VALUE_FLAGS.contains(&args[index - 1].as_str()) {
return None;
}
if VALUE_FLAGS.contains(&arg.as_str())
|| matches!(
arg.as_str(),
"--human"
| "--probe"
| "--fix"
| "--enterprise"
| "--force-bootstrap"
| "--yes"
| "--force"
| "--revert"
| "--all-for-principal"
| "--buf-yaml"
| "--no-buf-yaml"
| "--fmt"
| "--format"
| "--check"
| "--json"
| "--native-services"
| "--include-deps"
| "--strict-server-capabilities"
| "--tui"
| "--no-tui"
| "--json-plan"
| "--dry-run"
| "--persist"
)
{
None
} else {
Some(arg.clone())
}
})
.collect();
let proto_root = positional_args
.first()
.cloned()
.or_else(|| env::var("UDB_PROTO_ROOT").ok())
.or_else(|| env::var("UDB_PROTO_DIR").ok())
.unwrap_or_else(|| "proto".to_string());
let namespace = positional_args
.get(1)
.cloned()
.or_else(|| env::var("UDB_PROTO_NAMESPACE").ok())
.unwrap_or_default();
let serve_addr = positional_args
.get(2)
.cloned()
.or_else(|| env::var("UDB_GRPC_BIND_ADDR").ok())
.or_else(|| env::var("UDB_GRPC_ADDR").ok())
.unwrap_or_else(|| DEFAULT_GRPC_BIND_ADDR.to_string());
(command, proto_root, namespace, serve_addr)
}