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 trust: bool,
53
54 #[arg(long, global = true)]
56 pub help_json: bool,
57
58 #[command(subcommand)]
59 pub command: Option<Commands>,
60}
61
62#[derive(Debug, Subcommand)]
63pub enum Commands {
64 Capabilities,
66
67 Env {
69 #[command(subcommand)]
70 command: EnvCommands,
71 },
72
73 Instructions,
75
76 Disclaimer {
78 #[command(subcommand)]
79 command: DisclaimerCommands,
80 },
81
82 Auth {
84 #[command(subcommand)]
85 command: AuthCommands,
86 },
87
88 Accounts {
90 #[command(subcommand)]
91 command: AccountsCommands,
92 },
93
94 Orders {
96 #[command(subcommand)]
97 command: OrdersCommands,
98 },
99
100 Transactions {
102 #[command(subcommand)]
103 command: TransactionsCommands,
104 },
105
106 User {
108 #[command(subcommand)]
109 command: UserCommands,
110 },
111
112 Portfolio {
114 #[command(subcommand)]
115 command: PortfolioCommands,
116 },
117
118 Trade {
120 #[command(subcommand)]
121 command: TradeCommands,
122 },
123
124 Safety {
126 #[command(subcommand)]
127 command: SafetyCommands,
128 },
129
130 Plan {
132 #[command(subcommand)]
133 command: PlanCommands,
134 },
135
136 Market {
138 #[command(subcommand)]
139 command: MarketCommands,
140 },
141
142 Options {
144 #[command(subcommand)]
145 command: OptionsCommands,
146 },
147
148 Agent {
150 #[command(subcommand)]
151 command: AgentCommands,
152 },
153}
154
155#[derive(Debug, Subcommand)]
156pub enum EnvCommands {
157 Schema,
159}
160
161#[derive(Debug, Subcommand)]
162pub enum AuthCommands {
163 Login {
165 #[arg(long)]
167 code: Option<String>,
168 },
169 Status,
171 Refresh,
173 Logout,
175}
176
177#[derive(Debug, Subcommand)]
178pub enum AccountsCommands {
179 Numbers,
181 List {
183 #[arg(long)]
184 fields: Option<String>,
185 },
186 Get {
188 account_number: String,
189 #[arg(long)]
190 fields: Option<String>,
191 },
192}
193
194#[derive(Debug, Subcommand)]
195pub enum OrdersCommands {
196 Schema,
198 Validate {
200 #[arg(long)]
202 order: String,
203 #[arg(long)]
205 account_number: Option<String>,
206 },
207 List {
209 account_number: String,
210 #[arg(long)]
211 from_entered_time: Option<String>,
212 #[arg(long)]
213 to_entered_time: Option<String>,
214 #[arg(long)]
215 status: Option<String>,
216 #[arg(long)]
217 max_results: Option<String>,
218 },
219 All {
221 #[arg(long)]
222 from_entered_time: Option<String>,
223 #[arg(long)]
224 to_entered_time: Option<String>,
225 #[arg(long)]
226 status: Option<String>,
227 #[arg(long)]
228 max_results: Option<String>,
229 },
230 Get {
232 account_number: String,
233 order_id: String,
234 },
235 Wait {
237 account_number: String,
238 order_id: String,
239 #[arg(long, default_value = "filled")]
241 until: String,
242 #[arg(long, default_value = "3600")]
244 timeout_seconds: u64,
245 #[arg(long, default_value = "5")]
247 interval_seconds: u64,
248 #[arg(long, default_value = "false")]
250 proceed_on_partial_fill: bool,
251 },
252 Place {
254 account_number: String,
255 #[arg(long)]
257 order: String,
258 },
259 Preview {
261 account_number: String,
262 #[arg(long)]
263 order: String,
264 },
265 Cancel {
267 account_number: String,
268 order_id: String,
269 },
270 Replace {
272 account_number: String,
273 order_id: String,
274 #[arg(long)]
275 order: String,
276 },
277}
278
279#[derive(Debug, Subcommand)]
280pub enum TransactionsCommands {
281 List {
283 account_number: String,
284 #[arg(long)]
285 start_date: Option<String>,
286 #[arg(long)]
287 end_date: Option<String>,
288 #[arg(long)]
289 types: Option<String>,
290 #[arg(long)]
291 symbol: Option<String>,
292 },
293 Get {
295 account_number: String,
296 transaction_id: String,
297 },
298}
299
300#[derive(Debug, Subcommand)]
301pub enum UserCommands {
302 Preference,
304}
305
306#[derive(Debug, Subcommand)]
307pub enum PortfolioCommands {
308 Summary,
310 BuyingPower {
312 #[arg(long)]
314 account_number: String,
315 },
316}
317
318#[derive(Debug, Subcommand)]
319pub enum TradeCommands {
320 Buy {
322 #[arg(long)]
324 account_number: String,
325 #[arg(long)]
327 symbol: String,
328 #[arg(long)]
330 quantity: f64,
331 #[arg(long, default_value = "market")]
333 order_type: String,
334 #[arg(long)]
336 price: Option<f64>,
337 #[arg(long)]
339 duration: Option<String>,
340 #[arg(long)]
342 session: Option<String>,
343 },
344 Sell {
346 #[arg(long)]
347 account_number: String,
348 #[arg(long)]
349 symbol: String,
350 #[arg(long)]
351 quantity: f64,
352 #[arg(long, default_value = "market")]
353 order_type: String,
354 #[arg(long)]
355 price: Option<f64>,
356 #[arg(long)]
357 duration: Option<String>,
358 #[arg(long)]
359 session: Option<String>,
360 },
361}
362
363#[derive(Debug, Subcommand)]
364pub enum SafetyCommands {
365 Show,
367 Init,
369 Path,
371}
372
373#[derive(Debug, Subcommand)]
374pub enum DisclaimerCommands {
375 Show,
377 Accept,
379 Status,
381}
382
383#[derive(Debug, Subcommand)]
384pub enum PlanCommands {
385 Schema,
387 Prompt,
389 Validate {
391 file: PathBuf,
393 },
394 Show {
396 file: PathBuf,
397 },
398 Run {
400 file: PathBuf,
401 #[arg(long)]
403 step: Option<String>,
404 #[arg(long)]
406 from_step: Option<String>,
407 },
408}
409
410#[derive(Debug, Subcommand)]
411pub enum MarketCommands {
412 Info {
414 symbol: String,
416 #[arg(long)]
418 no_history: bool,
419 #[arg(long, default_value = "month")]
420 history_period_type: String,
421 #[arg(long, default_value_t = 1)]
422 history_period: u32,
423 #[arg(long, default_value = "daily")]
424 history_frequency_type: String,
425 },
426 Quotes {
428 #[arg(long)]
430 symbols: String,
431 #[arg(long)]
433 fields: Option<String>,
434 #[arg(long)]
435 indicative: Option<bool>,
436 },
437 Quote {
439 symbol: String,
440 #[arg(long)]
441 fields: Option<String>,
442 #[arg(long)]
443 indicative: Option<bool>,
444 },
445 History {
447 symbol: String,
448 #[arg(long)]
449 period_type: Option<String>,
450 #[arg(long)]
451 period: Option<u32>,
452 #[arg(long)]
453 frequency_type: Option<String>,
454 #[arg(long)]
455 frequency: Option<u32>,
456 #[arg(long)]
458 start_date: Option<i64>,
459 #[arg(long)]
460 end_date: Option<i64>,
461 #[arg(long)]
462 need_extended_hours_data: Option<bool>,
463 #[arg(long)]
464 need_previous_close: Option<bool>,
465 },
466 Instrument {
468 #[arg(long)]
470 symbol: String,
471 #[arg(long, default_value = "fundamental")]
473 projection: String,
474 },
475 InstrumentByCusip {
477 cusip: String,
478 },
479 Hours {
481 #[arg(long, default_value = "equity")]
483 markets: String,
484 #[arg(long)]
486 date: Option<String>,
487 },
488 HoursFor {
490 market: String,
492 #[arg(long)]
493 date: Option<String>,
494 },
495}
496
497#[derive(Debug, Subcommand)]
498pub enum OptionsCommands {
499 Chain {
501 #[arg(long)]
502 symbol: String,
503 #[arg(long, name = "type")]
505 contract_type: Option<String>,
506 #[arg(long)]
507 strike_count: Option<u32>,
508 #[arg(long)]
509 from_date: Option<String>,
510 #[arg(long)]
511 to_date: Option<String>,
512 },
513 Positions {
515 #[arg(long)]
516 account_number: Option<String>,
517 },
518 Schema,
520 Validate {
522 #[arg(long)]
523 strategy: String,
524 #[arg(long)]
526 params: String,
527 #[arg(long)]
528 account_number: Option<String>,
529 #[arg(long, default_value = "margin")]
530 account_type: Option<String>,
531 },
532 Preview {
534 #[arg(long)]
535 account_number: String,
536 #[arg(long)]
537 strategy: String,
538 #[arg(long)]
539 params: String,
540 },
541 Open {
543 #[arg(long)]
544 account_number: String,
545 #[arg(long)]
546 strategy: String,
547 #[arg(long)]
548 params: String,
549 },
550 Close {
552 #[arg(long)]
553 account_number: String,
554 #[arg(long)]
555 position_id: String,
556 },
557}
558
559#[derive(Debug, Subcommand)]
560pub enum AgentCommands {
561 Schema,
563 Validate {
565 file: PathBuf,
566 },
567 Status {
569 #[arg(long)]
570 rules_file: Option<PathBuf>,
571 },
572 Run {
574 file: PathBuf,
575 #[arg(long)]
577 once: bool,
578 #[arg(long)]
580 background: bool,
581 },
582 Stop {
584 file: PathBuf,
585 },
586}
587
588impl Cli {
590 pub fn effective_output(&self) -> OutputFormat {
591 if self.json {
592 OutputFormat::Json
593 } else if self.md {
594 OutputFormat::Md
595 } else {
596 self.output
597 }
598 }
599}