Skip to main content

schwab_cli/
config.rs

1use std::sync::Arc;
2
3use anyhow::{Context, Result};
4use schwab_api::{ClientConfig, SchwabClient, TraderApi};
5use schwab_market_data::MarketDataApi;
6
7use crate::cli::Cli;
8use crate::mode::CliMode;
9use crate::output::{OutputFormat, OutputSink};
10use crate::safety::SafetyContext;
11use crate::safety_config::SafetyConfig;
12
13#[derive(Debug, Clone)]
14pub struct RuntimeConfig {
15    pub mode: CliMode,
16    pub output: OutputFormat,
17    pub yes: bool,
18    pub dry_run: bool,
19    /// Paper options trading — separate state file, no broker orders.
20    pub simulate: bool,
21    /// Explicit trusted agent mode — required with --yes for autonomous trading.
22    pub trust: bool,
23    /// When true, agent ticks do not print to stdout (watch TUI mode).
24    pub suppress_tick_output: bool,
25    /// When true, skip spoken trade-event audio cues.
26    pub no_audio: bool,
27    pub safety: SafetyContext,
28    pub sink: OutputSink,
29}
30
31impl RuntimeConfig {
32    pub fn from_cli(cli: &Cli) -> Result<Self> {
33        let safety_cfg = SafetyConfig::load().context("Failed to load safety.json")?;
34        anyhow::ensure!(
35            !(cli.dry_run && cli.simulate),
36            "--dry-run and --simulate are mutually exclusive"
37        );
38        Ok(Self {
39            mode: cli.mode,
40            output: cli.effective_output(),
41            yes: cli.yes,
42            dry_run: cli.dry_run,
43            simulate: cli.simulate,
44            trust: cli.trust,
45            suppress_tick_output: false,
46            no_audio: cli.no_audio,
47            safety: SafetyContext::new(safety_cfg),
48            sink: OutputSink::stdout(),
49        })
50    }
51
52    pub fn emit(&self, envelope: crate::output::ResponseEnvelope) {
53        self.sink.write(&envelope, self.output);
54    }
55
56    pub fn is_tty(&self) -> bool {
57        use std::io::{stdin, stdout, IsTerminal};
58        stdin().is_terminal() && stdout().is_terminal()
59    }
60
61    /// Human-mode guided prompts (account pickers, etc.).
62    pub fn is_interactive(&self) -> bool {
63        use std::io::stdout;
64        use std::io::IsTerminal;
65        self.mode.is_human() && stdout().is_terminal()
66    }
67
68    pub fn build_api(&self) -> Result<Arc<TraderApi>> {
69        let config = ClientConfig::from_env().context("Failed to load Schwab client config")?;
70        let client = SchwabClient::new(config);
71        Ok(Arc::new(TraderApi::new(client)))
72    }
73
74    pub fn build_market_api(&self) -> Result<Arc<MarketDataApi>> {
75        let config = ClientConfig::from_env().context("Failed to load Schwab client config")?;
76        let client = SchwabClient::new(config);
77        Ok(Arc::new(MarketDataApi::new(client)))
78    }
79
80    /// Agent-mode runtime for order execution (shared with `schwab-trader`).
81    pub fn for_agent_trading(
82        output: OutputFormat,
83        yes: bool,
84        dry_run: bool,
85        simulate: bool,
86        trust: bool,
87        suppress_tick_output: bool,
88        no_audio: bool,
89        safety: SafetyContext,
90        sink: OutputSink,
91    ) -> Self {
92        Self {
93            mode: CliMode::Agent,
94            output,
95            yes,
96            dry_run,
97            simulate,
98            trust,
99            suppress_tick_output,
100            no_audio,
101            safety,
102            sink,
103        }
104    }
105}