1use std::path::PathBuf;
2
3use clap::{Parser, Subcommand};
4
5use crate::disclaimer::HELP_DISCLAIMER;
6use crate::mode::CliMode;
7use crate::output::OutputFormat;
8
9#[derive(Debug, Parser)]
10#[command(
11 name = "schwab",
12 version,
13 about = "Agent-first CLI for Charles Schwab Trader API (experimental — use at your own risk)",
14 long_about = "Schwab Trader API CLI (Accounts and Trading Production).\n\n\
15 ⚠️ EXPERIMENTAL — USE AT YOUR OWN RISK. Can submit real trades.\n\
16 Run `schwab disclaimer show` and `schwab disclaimer accept --yes` before live trading.\n\n\
17 AGENTS: Prefer --mode agent (default). Discover commands with:\n\
18 schwab --help --json\n\
19 schwab capabilities --json\n\
20 schwab env schema --json\n\
21 schwab instructions --json\n\n\
22 HUMANS: Use --mode human for guided prompts when arguments are omitted.",
23 after_help = HELP_DISCLAIMER
24)]
25pub struct Cli {
26 #[arg(long, env = "SCHWAB_MODE", default_value = "agent")]
28 pub mode: CliMode,
29
30 #[arg(long, env = "SCHWAB_OUTPUT", default_value = "pretty")]
32 pub output: OutputFormat,
33
34 #[arg(long, short = 'j', global = true)]
36 pub json: bool,
37
38 #[arg(long, global = true)]
40 pub md: bool,
41
42 #[arg(long, global = true)]
44 pub yes: bool,
45
46 #[arg(long, global = true)]
48 pub dry_run: bool,
49
50 #[arg(long, global = true)]
52 pub simulate: bool,
53
54 #[arg(long, global = true)]
56 pub trust: bool,
57
58 #[arg(long, global = true, env = "SCHWAB_NO_AUDIO")]
60 pub no_audio: bool,
61
62 #[arg(long, global = true)]
64 pub help_json: bool,
65
66 #[command(subcommand)]
67 pub command: Option<Commands>,
68}
69
70#[derive(Debug, Subcommand)]
71pub enum Commands {
72 Capabilities,
74
75 Env {
77 #[command(subcommand)]
78 command: EnvCommands,
79 },
80
81 Instructions,
83
84 Disclaimer {
86 #[command(subcommand)]
87 command: DisclaimerCommands,
88 },
89
90 Auth {
92 #[command(subcommand)]
93 command: AuthCommands,
94 },
95
96 Accounts {
98 #[command(subcommand)]
99 command: AccountsCommands,
100 },
101
102 Orders {
104 #[command(subcommand)]
105 command: OrdersCommands,
106 },
107
108 Transactions {
110 #[command(subcommand)]
111 command: TransactionsCommands,
112 },
113
114 User {
116 #[command(subcommand)]
117 command: UserCommands,
118 },
119
120 Portfolio {
122 #[command(subcommand)]
123 command: PortfolioCommands,
124 },
125
126 Trade {
128 #[command(subcommand)]
129 command: TradeCommands,
130 },
131
132 Safety {
134 #[command(subcommand)]
135 command: SafetyCommands,
136 },
137
138 Plan {
140 #[command(subcommand)]
141 command: PlanCommands,
142 },
143
144 Market {
146 #[command(subcommand)]
147 command: MarketCommands,
148 },
149
150 Options {
152 #[command(subcommand)]
153 command: OptionsCommands,
154 },
155
156 Agent {
158 #[command(subcommand)]
159 command: AgentCommands,
160 },
161
162 Dashboard {
164 file: Option<PathBuf>,
166 },
167
168 Watch {
170 file: Option<PathBuf>,
172 #[arg(long)]
174 monitor_only: bool,
175 },
176
177 Rules {
179 #[command(subcommand)]
180 command: RulesCommands,
181 },
182}
183
184#[derive(Debug, Subcommand)]
185pub enum EnvCommands {
186 Schema,
188}
189
190#[derive(Debug, Subcommand)]
191pub enum AuthCommands {
192 Login {
194 #[arg(long)]
196 code: Option<String>,
197 },
198 Status,
200 Refresh,
202 Logout,
204}
205
206#[derive(Debug, Subcommand)]
207pub enum AccountsCommands {
208 Numbers,
210 List {
212 #[arg(long)]
213 fields: Option<String>,
214 },
215 Get {
217 account_number: String,
218 #[arg(long)]
219 fields: Option<String>,
220 },
221}
222
223#[derive(Debug, Subcommand)]
224pub enum OrdersCommands {
225 Schema,
227 Validate {
229 #[arg(long)]
231 order: String,
232 #[arg(long)]
234 account_number: Option<String>,
235 },
236 List {
238 account_number: String,
239 #[arg(long)]
240 from_entered_time: Option<String>,
241 #[arg(long)]
242 to_entered_time: Option<String>,
243 #[arg(long)]
244 status: Option<String>,
245 #[arg(long)]
246 max_results: Option<String>,
247 },
248 All {
250 #[arg(long)]
251 from_entered_time: Option<String>,
252 #[arg(long)]
253 to_entered_time: Option<String>,
254 #[arg(long)]
255 status: Option<String>,
256 #[arg(long)]
257 max_results: Option<String>,
258 },
259 Get {
261 account_number: String,
262 order_id: String,
263 },
264 Wait {
266 account_number: String,
267 order_id: String,
268 #[arg(long, default_value = "filled")]
270 until: String,
271 #[arg(long, default_value = "3600")]
273 timeout_seconds: u64,
274 #[arg(long, default_value = "5")]
276 interval_seconds: u64,
277 #[arg(long, default_value = "false")]
279 proceed_on_partial_fill: bool,
280 },
281 Place {
283 account_number: String,
284 #[arg(long)]
286 order: String,
287 },
288 Preview {
290 account_number: String,
291 #[arg(long)]
292 order: String,
293 },
294 Cancel {
296 account_number: String,
297 order_id: String,
298 },
299 Replace {
301 account_number: String,
302 order_id: String,
303 #[arg(long)]
304 order: String,
305 },
306}
307
308#[derive(Debug, Subcommand)]
309pub enum TransactionsCommands {
310 List {
312 account_number: String,
313 #[arg(long)]
314 start_date: Option<String>,
315 #[arg(long)]
316 end_date: Option<String>,
317 #[arg(long)]
318 types: Option<String>,
319 #[arg(long)]
320 symbol: Option<String>,
321 },
322 Get {
324 account_number: String,
325 transaction_id: String,
326 },
327}
328
329#[derive(Debug, Subcommand)]
330pub enum UserCommands {
331 Preference,
333}
334
335#[derive(Debug, Subcommand)]
336pub enum PortfolioCommands {
337 Summary,
339 BuyingPower {
341 #[arg(long)]
343 account_number: String,
344 },
345}
346
347#[derive(Debug, Subcommand)]
348pub enum TradeCommands {
349 Buy {
351 #[arg(long)]
353 account_number: String,
354 #[arg(long)]
356 symbol: String,
357 #[arg(long)]
359 quantity: f64,
360 #[arg(long, default_value = "market")]
362 order_type: String,
363 #[arg(long)]
365 price: Option<f64>,
366 #[arg(long)]
368 duration: Option<String>,
369 #[arg(long)]
371 session: Option<String>,
372 },
373 Sell {
375 #[arg(long)]
376 account_number: String,
377 #[arg(long)]
378 symbol: String,
379 #[arg(long)]
380 quantity: f64,
381 #[arg(long, default_value = "market")]
382 order_type: String,
383 #[arg(long)]
384 price: Option<f64>,
385 #[arg(long)]
386 duration: Option<String>,
387 #[arg(long)]
388 session: Option<String>,
389 },
390}
391
392#[derive(Debug, Subcommand)]
393pub enum SafetyCommands {
394 Show,
396 Init,
398 Path,
400}
401
402#[derive(Debug, Subcommand)]
403pub enum DisclaimerCommands {
404 Show,
406 Accept,
408 Status,
410}
411
412#[derive(Debug, Subcommand)]
413pub enum PlanCommands {
414 Schema,
416 Prompt,
418 Validate {
420 file: PathBuf,
422 },
423 Show { file: PathBuf },
425 Run {
427 file: PathBuf,
428 #[arg(long)]
430 step: Option<String>,
431 #[arg(long)]
433 from_step: Option<String>,
434 },
435}
436
437#[derive(Debug, Subcommand)]
438pub enum MarketCommands {
439 Info {
441 symbol: String,
443 #[arg(long)]
445 no_history: bool,
446 #[arg(long, default_value = "month")]
447 history_period_type: String,
448 #[arg(long, default_value_t = 1)]
449 history_period: u32,
450 #[arg(long, default_value = "daily")]
451 history_frequency_type: String,
452 },
453 Quotes {
455 #[arg(long)]
457 symbols: String,
458 #[arg(long)]
460 fields: Option<String>,
461 #[arg(long)]
462 indicative: Option<bool>,
463 },
464 Quote {
466 symbol: String,
467 #[arg(long)]
468 fields: Option<String>,
469 #[arg(long)]
470 indicative: Option<bool>,
471 },
472 History {
474 symbol: String,
475 #[arg(long)]
476 period_type: Option<String>,
477 #[arg(long)]
478 period: Option<u32>,
479 #[arg(long)]
480 frequency_type: Option<String>,
481 #[arg(long)]
482 frequency: Option<u32>,
483 #[arg(long)]
485 start_date: Option<i64>,
486 #[arg(long)]
487 end_date: Option<i64>,
488 #[arg(long)]
489 need_extended_hours_data: Option<bool>,
490 #[arg(long)]
491 need_previous_close: Option<bool>,
492 },
493 Instrument {
495 #[arg(long)]
497 symbol: String,
498 #[arg(long, default_value = "fundamental")]
500 projection: String,
501 },
502 InstrumentByCusip { cusip: String },
504 Hours {
506 #[arg(long, default_value = "equity")]
508 markets: String,
509 #[arg(long)]
511 date: Option<String>,
512 },
513 HoursFor {
515 market: String,
517 #[arg(long)]
518 date: Option<String>,
519 },
520}
521
522#[derive(Debug, Subcommand)]
523pub enum OptionsCommands {
524 Chain {
526 #[arg(long)]
527 symbol: String,
528 #[arg(long, name = "type")]
530 contract_type: Option<String>,
531 #[arg(long)]
532 strike_count: Option<u32>,
533 #[arg(long)]
534 from_date: Option<String>,
535 #[arg(long)]
536 to_date: Option<String>,
537 },
538 Positions {
540 #[arg(long)]
541 account_number: Option<String>,
542 },
543 Schema,
545 Validate {
547 #[arg(long)]
548 strategy: String,
549 #[arg(long)]
551 params: String,
552 #[arg(long)]
553 account_number: Option<String>,
554 #[arg(long, default_value = "margin")]
555 account_type: Option<String>,
556 },
557 Preview {
559 #[arg(long)]
560 account_number: String,
561 #[arg(long)]
562 strategy: String,
563 #[arg(long)]
564 params: String,
565 },
566 Open {
568 #[arg(long)]
569 account_number: String,
570 #[arg(long)]
571 strategy: String,
572 #[arg(long)]
573 params: String,
574 },
575 Close {
577 #[arg(long)]
578 account_number: String,
579 #[arg(long)]
580 position_id: String,
581 },
582}
583
584#[derive(Debug, Subcommand)]
585pub enum AgentCommands {
586 Schema,
588 Validate { file: PathBuf },
590 Status {
592 #[arg(long)]
593 rules_file: Option<PathBuf>,
594 },
595 Run {
597 file: PathBuf,
598 #[arg(long)]
600 once: bool,
601 #[arg(long)]
603 background: bool,
604 },
605 Stop { file: PathBuf },
607 CloseAll { file: PathBuf },
609 Sim {
611 #[command(subcommand)]
612 command: AgentSimCommands,
613 },
614 Backtest {
616 #[command(subcommand)]
617 command: AgentBacktestCommands,
618 },
619}
620
621#[derive(Debug, Subcommand)]
622pub enum AgentBacktestCommands {
623 Prefetch {
625 #[arg(long)]
626 rules_file: PathBuf,
627 #[arg(long)]
628 from: Option<String>,
629 #[arg(long)]
630 to: Option<String>,
631 #[arg(long)]
632 force: bool,
633 },
634 Run {
636 #[arg(long)]
637 rules_file: PathBuf,
638 #[arg(long)]
639 from: Option<String>,
640 #[arg(long)]
641 to: Option<String>,
642 #[arg(long)]
643 fresh: bool,
644 },
645 Report {
647 #[arg(long)]
648 rules_file: PathBuf,
649 #[arg(long)]
650 output: Option<PathBuf>,
651 },
652}
653
654#[derive(Debug, Subcommand)]
655pub enum AgentSimCommands {
656 Stats { file: PathBuf },
658 Report {
660 file: PathBuf,
661 #[arg(long)]
662 output: Option<PathBuf>,
663 },
664 Reset { file: PathBuf },
666}
667
668#[derive(Debug, Subcommand)]
669pub enum RulesCommands {
670 Show { file: Option<PathBuf> },
672 List,
674}
675
676impl Cli {
678 pub fn effective_output(&self) -> OutputFormat {
679 if self.json {
680 OutputFormat::Json
681 } else if self.md {
682 OutputFormat::Md
683 } else {
684 self.output
685 }
686 }
687}