Every binary in the workspace currently sources configuration from environment variables (via envconfig::Envconfig). Each binary should expose those same knobs as native clap CLI arguments so users can set them on the command line without env wrapping. Env-var fallback stays as the secondary source; the CLI arg takes precedence when both are set.
Scope
Four binaries, each ships envconfig-derived Configs today:
- objectiveai-cli —
objectiveai_cli::load_config() (see objectiveai-cli/src/config.rs and friends).
- objectiveai-api —
objectiveai_api::ConfigBuilder::init_from_env() (driven by the env-config map in objectiveai-api/src/run.rs:140+, ~25+ fields like MOCK_DELAY_MS, MCP_CONNECT_TIMEOUT, FUNCTIONS_INVENTIONS_SUBSCRIBE_TOOLS_TIMEOUT, ADDRESS, PORT, MCP_BACKOFF_*, etc.).
- objectiveai-mcp (objectiveai-mcp-cli) —
objectiveai_mcp_cli::ConfigBuilder::init_from_env().
- objectiveai-viewer —
objectiveai_viewer::ConfigBuilder::init_from_env().
Each ConfigBuilder enumerates its env vars in one place (clap derive macros on a struct field-by-field, or an explicit map). The clap migration is a per-binary, per-field tweak: add #[arg(long = "...", env = "...")] so clap natively handles both sources with documented precedence.
Suggested approach
- Per binary: replace the existing
#[derive(Envconfig)] struct with a #[derive(clap::Parser)] struct. Each field gets #[arg(long, env = "ENV_NAME")] so:
--port 8080 → wins
PORT=8080 objectiveai-api → still works
- Default value: same as today.
- Drop
envconfig + dotenv (or keep dotenv() call at startup so a .env file still hydrates the env, then clap's env attribute picks it up — net behavior unchanged for .env-using developers).
- Update each binary's
main.rs to parse via Parser::parse() instead of init_from_env().
- Keep all existing env var NAMES so existing deployments don't break.
Why
- Discoverability:
objectiveai-api --help should list every knob, not require reading the source.
- Composability: scripting one-off runs is easier without env-var wrapping.
- Test ergonomics: integration tests can pass flags instead of building
Command::env(...) chains.
Out of scope
- Renaming env vars.
- Changing the underlying Config struct shape.
- Removing env-var support (it stays as the secondary source).
Every binary in the workspace currently sources configuration from environment variables (via
envconfig::Envconfig). Each binary should expose those same knobs as nativeclapCLI arguments so users can set them on the command line without env wrapping. Env-var fallback stays as the secondary source; the CLI arg takes precedence when both are set.Scope
Four binaries, each ships
envconfig-derivedConfigs today:objectiveai_cli::load_config()(seeobjectiveai-cli/src/config.rsand friends).objectiveai_api::ConfigBuilder::init_from_env()(driven by the env-config map inobjectiveai-api/src/run.rs:140+, ~25+ fields likeMOCK_DELAY_MS,MCP_CONNECT_TIMEOUT,FUNCTIONS_INVENTIONS_SUBSCRIBE_TOOLS_TIMEOUT,ADDRESS,PORT,MCP_BACKOFF_*, etc.).objectiveai_mcp_cli::ConfigBuilder::init_from_env().objectiveai_viewer::ConfigBuilder::init_from_env().Each ConfigBuilder enumerates its env vars in one place (clap derive macros on a struct field-by-field, or an explicit map). The clap migration is a per-binary, per-field tweak: add
#[arg(long = "...", env = "...")]so clap natively handles both sources with documented precedence.Suggested approach
#[derive(Envconfig)]struct with a#[derive(clap::Parser)]struct. Each field gets#[arg(long, env = "ENV_NAME")]so:--port 8080→ winsPORT=8080 objectiveai-api→ still worksenvconfig+dotenv(or keepdotenv()call at startup so a.envfile still hydrates the env, then clap'senvattribute picks it up — net behavior unchanged for.env-using developers).main.rsto parse viaParser::parse()instead ofinit_from_env().Why
objectiveai-api --helpshould list every knob, not require reading the source.Command::env(...)chains.Out of scope