use super::*;
#[derive(Parser, Debug)]
#[command(
name = "prodex",
version,
about = "Manage multiple Codex profiles backed by isolated CODEX_HOME directories.",
after_help = CLI_TOP_LEVEL_AFTER_HELP
)]
pub(crate) struct Cli {
#[command(subcommand)]
pub(crate) command: Commands,
}
#[derive(Subcommand, Debug)]
pub(crate) enum Commands {
#[command(
subcommand,
about = "Add, inspect, remove, and activate managed profiles.",
after_help = CLI_PROFILE_AFTER_HELP
)]
Profile(ProfileCommands),
#[command(
name = "use",
about = "Set the active profile used by commands that omit --profile."
)]
UseProfile(ProfileSelector),
#[command(about = "Show the active profile and its CODEX_HOME details.")]
Current,
#[command(
name = "info",
about = "Summarize version status, running processes, quota pool, and runway."
)]
Info(InfoArgs),
#[command(
about = "Inspect local state, Codex resolution, quota readiness, and runtime logs.",
after_help = CLI_DOCTOR_AFTER_HELP
)]
Doctor(DoctorArgs),
#[command(
about = "Inspect structured enterprise audit events written to /tmp.",
after_help = CLI_AUDIT_AFTER_HELP
)]
Audit(AuditArgs),
#[command(
about = "Remove stale local runtime logs, temp homes, dead broker artifacts, and orphaned managed homes.",
after_help = CLI_CLEANUP_AFTER_HELP
)]
Cleanup,
#[command(
trailing_var_arg = true,
about = "Run codex login inside a selected or auto-created profile.",
after_help = CLI_LOGIN_AFTER_HELP
)]
Login(CodexPassthroughArgs),
#[command(about = "Run codex logout for the selected or active profile.")]
Logout(LogoutArgs),
#[command(
about = "Inspect live quota for one profile or the whole profile pool.",
after_help = CLI_QUOTA_AFTER_HELP
)]
Quota(QuotaArgs),
#[command(
trailing_var_arg = true,
about = "Run codex through prodex with quota preflight and safe auto-rotate.",
after_help = CLI_RUN_AFTER_HELP
)]
Run(RunArgs),
#[command(
trailing_var_arg = true,
about = "Run codex through prodex with the Caveman plugin active in a temporary overlay home.",
after_help = CLI_CAVEMAN_AFTER_HELP
)]
Caveman(CavemanArgs),
#[command(
trailing_var_arg = true,
about = "Alias for `prodex caveman mem --full-access`.",
after_help = CLI_SUPER_AFTER_HELP
)]
Super(SuperArgs),
#[command(
trailing_var_arg = true,
about = "Run Claude Code through prodex via an Anthropic-compatible runtime proxy.",
after_help = CLI_CLAUDE_AFTER_HELP
)]
Claude(ClaudeArgs),
#[command(name = "__runtime-broker", hide = true)]
RuntimeBroker(RuntimeBrokerArgs),
}
#[derive(Subcommand, Debug)]
pub(crate) enum ProfileCommands {
Add(AddProfileArgs),
Export(ExportProfileArgs),
Import(ImportProfileArgs),
ImportCurrent(ImportCurrentArgs),
List,
Remove(RemoveProfileArgs),
Use(ProfileSelector),
}
#[derive(Args, Debug)]
pub(crate) struct AddProfileArgs {
pub(crate) name: String,
#[arg(long, value_name = "PATH")]
pub(crate) codex_home: Option<PathBuf>,
#[arg(long, value_name = "PATH")]
pub(crate) copy_from: Option<PathBuf>,
#[arg(long)]
pub(crate) copy_current: bool,
#[arg(long)]
pub(crate) activate: bool,
}
#[derive(Args, Debug)]
pub(crate) struct ExportProfileArgs {
#[arg(short, long, value_name = "NAME")]
pub(crate) profile: Vec<String>,
#[arg(value_name = "PATH")]
pub(crate) output: Option<PathBuf>,
#[arg(long, conflicts_with = "no_password")]
pub(crate) password_protect: bool,
#[arg(long)]
pub(crate) no_password: bool,
}
#[derive(Args, Debug)]
pub(crate) struct ImportProfileArgs {
#[arg(value_name = "PATH_OR_SOURCE")]
pub(crate) path: PathBuf,
#[arg(long, value_name = "NAME")]
pub(crate) name: Option<String>,
#[arg(long)]
pub(crate) activate: bool,
}
#[derive(Args, Debug)]
pub(crate) struct ImportCurrentArgs {
#[arg(default_value = "default")]
pub(crate) name: String,
}
#[derive(Args, Debug)]
pub(crate) struct RemoveProfileArgs {
#[arg(
value_name = "NAME",
required_unless_present = "all",
conflicts_with = "all"
)]
pub(crate) name: Option<String>,
#[arg(long, conflicts_with = "name")]
pub(crate) all: bool,
#[arg(long)]
pub(crate) delete_home: bool,
}
#[derive(Args, Debug, Clone)]
pub(crate) struct ProfileSelector {
#[arg(short, long, value_name = "NAME")]
pub(crate) profile: Option<String>,
}
#[derive(Args, Debug, Clone)]
pub(crate) struct LogoutArgs {
#[arg(value_name = "NAME", conflicts_with = "profile")]
pub(crate) profile_name: Option<String>,
#[arg(short, long, value_name = "NAME")]
pub(crate) profile: Option<String>,
}
impl LogoutArgs {
pub(crate) fn selected_profile(&self) -> Option<&str> {
self.profile.as_deref().or(self.profile_name.as_deref())
}
}
#[derive(Args, Debug)]
pub(crate) struct CodexPassthroughArgs {
#[arg(short, long, value_name = "NAME")]
pub(crate) profile: Option<String>,
#[arg(value_name = "CODEX_ARG", allow_hyphen_values = true)]
pub(crate) codex_args: Vec<OsString>,
}
#[derive(Args, Debug)]
pub(crate) struct QuotaArgs {
#[arg(short, long, value_name = "NAME")]
pub(crate) profile: Option<String>,
#[arg(long)]
pub(crate) all: bool,
#[arg(long)]
pub(crate) detail: bool,
#[arg(long)]
pub(crate) raw: bool,
#[arg(long, hide = true)]
pub(crate) watch: bool,
#[arg(long, conflicts_with = "watch")]
pub(crate) once: bool,
#[arg(long, value_name = "URL")]
pub(crate) base_url: Option<String>,
}
#[derive(Args, Debug, Default)]
pub(crate) struct InfoArgs {}
#[derive(Args, Debug)]
pub(crate) struct DoctorArgs {
#[arg(long)]
pub(crate) quota: bool,
#[arg(long)]
pub(crate) runtime: bool,
#[arg(long)]
pub(crate) json: bool,
}
#[derive(Args, Debug)]
pub(crate) struct AuditArgs {
#[arg(long, default_value_t = 50, value_name = "COUNT")]
pub(crate) tail: usize,
#[arg(long)]
pub(crate) json: bool,
#[arg(long, value_name = "NAME")]
pub(crate) component: Option<String>,
#[arg(long, value_name = "NAME")]
pub(crate) action: Option<String>,
#[arg(long, value_name = "NAME")]
pub(crate) outcome: Option<String>,
}
#[derive(Args, Debug)]
pub(crate) struct RunArgs {
#[arg(short, long, value_name = "NAME")]
pub(crate) profile: Option<String>,
#[arg(long, conflicts_with = "no_auto_rotate")]
pub(crate) auto_rotate: bool,
#[arg(long)]
pub(crate) no_auto_rotate: bool,
#[arg(long)]
pub(crate) skip_quota_check: bool,
#[arg(long)]
pub(crate) full_access: bool,
#[arg(long, value_name = "URL")]
pub(crate) base_url: Option<String>,
#[arg(long)]
pub(crate) dry_run: bool,
#[arg(value_name = "CODEX_ARG", allow_hyphen_values = true)]
pub(crate) codex_args: Vec<OsString>,
}
#[derive(Args, Debug)]
pub(crate) struct ClaudeArgs {
#[arg(short, long, value_name = "NAME")]
pub(crate) profile: Option<String>,
#[arg(long, conflicts_with = "no_auto_rotate")]
pub(crate) auto_rotate: bool,
#[arg(long)]
pub(crate) no_auto_rotate: bool,
#[arg(long)]
pub(crate) skip_quota_check: bool,
#[arg(long, value_name = "URL")]
pub(crate) base_url: Option<String>,
#[arg(value_name = "CLAUDE_ARG", allow_hyphen_values = true)]
pub(crate) claude_args: Vec<OsString>,
}
#[derive(Args, Debug)]
pub(crate) struct CavemanArgs {
#[arg(short, long, value_name = "NAME")]
pub(crate) profile: Option<String>,
#[arg(long, conflicts_with = "no_auto_rotate")]
pub(crate) auto_rotate: bool,
#[arg(long)]
pub(crate) no_auto_rotate: bool,
#[arg(long)]
pub(crate) skip_quota_check: bool,
#[arg(long)]
pub(crate) full_access: bool,
#[arg(long)]
pub(crate) dry_run: bool,
#[arg(long, value_name = "URL")]
pub(crate) base_url: Option<String>,
#[arg(value_name = "CODEX_ARG", allow_hyphen_values = true)]
pub(crate) codex_args: Vec<OsString>,
}
#[derive(Args, Debug)]
pub(crate) struct SuperArgs {
#[arg(short, long, value_name = "NAME")]
pub(crate) profile: Option<String>,
#[arg(long, conflicts_with = "no_auto_rotate")]
pub(crate) auto_rotate: bool,
#[arg(long)]
pub(crate) no_auto_rotate: bool,
#[arg(long)]
pub(crate) skip_quota_check: bool,
#[arg(long)]
pub(crate) dry_run: bool,
#[arg(long, value_name = "URL", conflicts_with = "url")]
pub(crate) base_url: Option<String>,
#[arg(long, value_name = "URL")]
pub(crate) url: Option<String>,
#[arg(
long = "model",
visible_alias = "local-model",
value_name = "MODEL",
requires = "url"
)]
pub(crate) local_model: Option<String>,
#[arg(
long = "context-window",
visible_alias = "local-context-window",
value_name = "TOKENS",
requires = "url"
)]
pub(crate) local_context_window: Option<usize>,
#[arg(
long = "auto-compact-token-limit",
visible_alias = "local-auto-compact-token-limit",
value_name = "TOKENS",
requires = "url"
)]
pub(crate) local_auto_compact_token_limit: Option<usize>,
#[arg(value_name = "CODEX_ARG", allow_hyphen_values = true)]
pub(crate) codex_args: Vec<OsString>,
}
impl SuperArgs {
pub(crate) fn into_caveman_args(self) -> CavemanArgs {
let local_provider_args = self
.url
.as_deref()
.map(|url| {
super_local_provider_codex_args(
url,
self.local_model.as_deref(),
self.local_context_window,
self.local_auto_compact_token_limit,
)
})
.unwrap_or_default();
let skip_quota_check = self.skip_quota_check || self.url.is_some();
let mut codex_args =
Vec::with_capacity(self.codex_args.len() + 1 + local_provider_args.len());
codex_args.push(OsString::from("mem"));
codex_args.extend(local_provider_args);
codex_args.extend(self.codex_args);
CavemanArgs {
profile: self.profile,
auto_rotate: self.auto_rotate,
no_auto_rotate: self.no_auto_rotate,
skip_quota_check,
full_access: true,
dry_run: self.dry_run,
base_url: self.base_url,
codex_args,
}
}
}
const SUPER_LOCAL_PROVIDER_ID: &str = "prodex-local";
const SUPER_LOCAL_PROVIDER_NAME: &str = "Prodex Local";
const SUPER_DEFAULT_LOCAL_MODEL: &str = "unsloth/qwen3.5-35b-a3b";
const SUPER_DEFAULT_CONTEXT_WINDOW: usize = 16_384;
const SUPER_DEFAULT_AUTO_COMPACT_LIMIT: usize = 14_000;
fn super_local_provider_codex_args(
url: &str,
model: Option<&str>,
context_window: Option<usize>,
auto_compact_token_limit: Option<usize>,
) -> Vec<OsString> {
let base_url = super_local_provider_base_url(url);
let model = model
.filter(|model| !model.trim().is_empty())
.unwrap_or(SUPER_DEFAULT_LOCAL_MODEL);
let context_window = context_window
.filter(|value| *value > 1)
.unwrap_or(SUPER_DEFAULT_CONTEXT_WINDOW);
let auto_compact_token_limit = auto_compact_token_limit
.filter(|value| *value > 0)
.unwrap_or(SUPER_DEFAULT_AUTO_COMPACT_LIMIT)
.min(context_window.saturating_sub(1));
let overrides = [
format!(
"model_provider={}",
toml_string_literal(SUPER_LOCAL_PROVIDER_ID)
),
format!("model={}", toml_string_literal(model)),
format!(
"model_providers.{SUPER_LOCAL_PROVIDER_ID}.name={}",
toml_string_literal(SUPER_LOCAL_PROVIDER_NAME)
),
format!(
"model_providers.{SUPER_LOCAL_PROVIDER_ID}.base_url={}",
toml_string_literal(&base_url)
),
format!("model_providers.{SUPER_LOCAL_PROVIDER_ID}.wire_api=\"responses\""),
format!("model_providers.{SUPER_LOCAL_PROVIDER_ID}.supports_websockets=false"),
format!("model_context_window={context_window}"),
format!("model_auto_compact_token_limit={auto_compact_token_limit}"),
"model_reasoning_summary=\"none\"".to_string(),
"model_supports_reasoning_summaries=false".to_string(),
"web_search=\"disabled\"".to_string(),
"features.js_repl=false".to_string(),
"features.image_generation=false".to_string(),
];
let mut args = Vec::with_capacity(overrides.len() * 2);
for override_entry in overrides {
args.push(OsString::from("-c"));
args.push(OsString::from(override_entry));
}
args
}
fn super_local_provider_base_url(url: &str) -> String {
let trimmed = url.trim();
if let Ok(mut parsed) = reqwest::Url::parse(trimmed) {
let path = parsed.path().trim_end_matches('/');
if path.is_empty() || path == "/" {
parsed.set_path("/v1");
return parsed.as_str().trim_end_matches('/').to_string();
}
}
trimmed.trim_end_matches('/').to_string()
}
#[derive(Args, Debug)]
pub(crate) struct RuntimeBrokerArgs {
#[arg(long)]
pub(crate) current_profile: String,
#[arg(long)]
pub(crate) upstream_base_url: String,
#[arg(long, default_value_t = false)]
pub(crate) include_code_review: bool,
#[arg(long)]
pub(crate) broker_key: String,
#[arg(long)]
pub(crate) instance_token: String,
#[arg(long)]
pub(crate) admin_token: String,
#[arg(long)]
pub(crate) listen_addr: Option<String>,
}
#[derive(Debug, Clone, Copy, Default)]
pub(crate) struct CurrentCommand;
#[derive(Debug, Clone, Copy, Default)]
pub(crate) struct CleanupCommand;
#[derive(Debug, Clone, Copy, Default)]
pub(crate) struct ListProfilesCommand;
impl ProfileSelector {
pub(crate) fn execute(self) -> Result<()> {
handle_set_active_profile(self)
}
}
impl AddProfileArgs {
pub(crate) fn execute(self) -> Result<()> {
handle_add_profile(self)
}
}
impl ExportProfileArgs {
pub(crate) fn execute(self) -> Result<()> {
handle_export_profiles(self)
}
}
impl ImportProfileArgs {
pub(crate) fn execute(self) -> Result<()> {
handle_import_profiles(self)
}
}
impl ImportCurrentArgs {
pub(crate) fn execute(self) -> Result<()> {
handle_import_current_profile(self)
}
}
impl RemoveProfileArgs {
pub(crate) fn execute(self) -> Result<()> {
handle_remove_profile(self)
}
}
impl CodexPassthroughArgs {
pub(crate) fn execute(self) -> Result<()> {
handle_codex_login(self)
}
}
impl LogoutArgs {
pub(crate) fn execute(self) -> Result<()> {
handle_codex_logout(self)
}
}
impl CurrentCommand {
pub(crate) fn execute(self) -> Result<()> {
handle_current_profile()
}
}
impl ListProfilesCommand {
pub(crate) fn execute(self) -> Result<()> {
handle_list_profiles()
}
}
impl InfoArgs {
pub(crate) fn execute(self) -> Result<()> {
handle_info(self)
}
}
impl DoctorArgs {
pub(crate) fn execute(self) -> Result<()> {
handle_doctor(self)
}
}
impl AuditArgs {
pub(crate) fn execute(self) -> Result<()> {
handle_audit(self)
}
}
impl CleanupCommand {
pub(crate) fn execute(self) -> Result<()> {
handle_cleanup()
}
}
impl QuotaArgs {
pub(crate) fn execute(self) -> Result<()> {
handle_quota(self)
}
}
impl RunArgs {
pub(crate) fn execute(self) -> Result<()> {
handle_run(self)
}
}
impl CavemanArgs {
pub(crate) fn execute(self) -> Result<()> {
if self.dry_run || prodex_dry_run_requested(&self.codex_args) {
return handle_caveman_dry_run(self);
}
handle_caveman(self)
}
}
impl SuperArgs {
pub(crate) fn execute(self) -> Result<()> {
if self.dry_run || prodex_dry_run_requested(&self.codex_args) {
return handle_caveman_dry_run(self.into_caveman_args());
}
handle_super(self)
}
}
impl ClaudeArgs {
pub(crate) fn execute(self) -> Result<()> {
handle_claude(self)
}
}
impl RuntimeBrokerArgs {
pub(crate) fn execute(self) -> Result<()> {
handle_runtime_broker(self)
}
}