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    pub safety: SafetyContext,
26    pub sink: OutputSink,
27}
28
29impl RuntimeConfig {
30    pub fn from_cli(cli: &Cli) -> Result<Self> {
31        let safety_cfg = SafetyConfig::load().context("Failed to load safety.json")?;
32        anyhow::ensure!(
33            !(cli.dry_run && cli.simulate),
34            "--dry-run and --simulate are mutually exclusive"
35        );
36        Ok(Self {
37            mode: cli.mode,
38            output: cli.effective_output(),
39            yes: cli.yes,
40            dry_run: cli.dry_run,
41            simulate: cli.simulate,
42            trust: cli.trust,
43            suppress_tick_output: false,
44            safety: SafetyContext::new(safety_cfg),
45            sink: OutputSink::stdout(),
46        })
47    }
48
49    pub fn emit(&self, envelope: crate::output::ResponseEnvelope) {
50        self.sink.write(&envelope, self.output);
51    }
52
53    pub fn is_tty(&self) -> bool {
54        use std::io::{stdin, stdout, IsTerminal};
55        stdin().is_terminal() && stdout().is_terminal()
56    }
57
58    /// Human-mode guided prompts (account pickers, etc.).
59    pub fn is_interactive(&self) -> bool {
60        use std::io::stdout;
61        use std::io::IsTerminal;
62        self.mode.is_human() && stdout().is_terminal()
63    }
64
65    pub fn build_api(&self) -> Result<Arc<TraderApi>> {
66        let config = ClientConfig::from_env().context("Failed to load Schwab client config")?;
67        let client = SchwabClient::new(config);
68        Ok(Arc::new(TraderApi::new(client)))
69    }
70
71    pub fn build_market_api(&self) -> Result<Arc<MarketDataApi>> {
72        let config = ClientConfig::from_env().context("Failed to load Schwab client config")?;
73        let client = SchwabClient::new(config);
74        Ok(Arc::new(MarketDataApi::new(client)))
75    }
76
77    /// Agent-mode runtime for order execution (shared with `schwab-trader`).
78    pub fn for_agent_trading(
79        output: OutputFormat,
80        yes: bool,
81        dry_run: bool,
82        simulate: bool,
83        trust: bool,
84        suppress_tick_output: bool,
85        safety: SafetyContext,
86        sink: OutputSink,
87    ) -> Self {
88        Self {
89            mode: CliMode::Agent,
90            output,
91            yes,
92            dry_run,
93            simulate,
94            trust,
95            suppress_tick_output,
96            safety,
97            sink,
98        }
99    }
100}