1use serde_json::{json, Value};
2
3#[derive(Debug, Clone, serde::Serialize)]
4pub struct CommandSpec {
5 pub path: &'static str,
6 pub description: &'static str,
7 pub http: Option<&'static str>,
8 pub mutation: bool,
9 pub requires_auth: bool,
10}
11
12pub fn all_commands() -> Vec<CommandSpec> {
13 vec![
14 CommandSpec {
15 path: "capabilities",
16 description: "Machine-readable command catalog",
17 http: None,
18 mutation: false,
19 requires_auth: false,
20 },
21 CommandSpec {
22 path: "env schema",
23 description: "Environment variable schema",
24 http: None,
25 mutation: false,
26 requires_auth: false,
27 },
28 CommandSpec {
29 path: "instructions",
30 description: "Agent system prompt and tool-use guidance",
31 http: None,
32 mutation: false,
33 requires_auth: false,
34 },
35 CommandSpec {
36 path: "disclaimer show",
37 description: "Print trading risk disclaimer (experimental software)",
38 http: None,
39 mutation: false,
40 requires_auth: false,
41 },
42 CommandSpec {
43 path: "disclaimer accept",
44 description: "Accept risk disclaimer (required before live trading)",
45 http: None,
46 mutation: true,
47 requires_auth: false,
48 },
49 CommandSpec {
50 path: "disclaimer status",
51 description: "Whether disclaimer was accepted on this machine",
52 http: None,
53 mutation: false,
54 requires_auth: false,
55 },
56 CommandSpec {
57 path: "auth login",
58 description: "OAuth authorization code flow",
59 http: None,
60 mutation: true,
61 requires_auth: false,
62 },
63 CommandSpec {
64 path: "auth status",
65 description: "Inspect stored OAuth tokens",
66 http: None,
67 mutation: false,
68 requires_auth: false,
69 },
70 CommandSpec {
71 path: "auth refresh",
72 description: "Refresh OAuth access token",
73 http: None,
74 mutation: true,
75 requires_auth: false,
76 },
77 CommandSpec {
78 path: "auth logout",
79 description: "Delete stored OAuth tokens",
80 http: None,
81 mutation: true,
82 requires_auth: false,
83 },
84 CommandSpec {
85 path: "accounts numbers",
86 description: "List account numbers and encrypted hash values",
87 http: Some("GET /accounts/accountNumbers"),
88 mutation: false,
89 requires_auth: true,
90 },
91 CommandSpec {
92 path: "accounts list",
93 description: "List linked accounts with balances and positions",
94 http: Some("GET /accounts"),
95 mutation: false,
96 requires_auth: true,
97 },
98 CommandSpec {
99 path: "accounts get",
100 description: "Get one account by account number hash",
101 http: Some("GET /accounts/{accountNumber}"),
102 mutation: false,
103 requires_auth: true,
104 },
105 CommandSpec {
106 path: "portfolio summary",
107 description: "Aggregate portfolio equity and holdings across accounts",
108 http: Some("GET /accounts"),
109 mutation: false,
110 requires_auth: true,
111 },
112 CommandSpec {
113 path: "portfolio buying-power",
114 description: "Cash available for trading on one account (pre-buy check)",
115 http: Some("GET /accounts/{accountNumber}"),
116 mutation: false,
117 requires_auth: true,
118 },
119 CommandSpec {
120 path: "trade buy",
121 description: "Buy equity shares with safety guardrails",
122 http: Some("POST /accounts/{accountNumber}/orders"),
123 mutation: true,
124 requires_auth: true,
125 },
126 CommandSpec {
127 path: "trade sell",
128 description: "Sell equity shares with safety guardrails",
129 http: Some("POST /accounts/{accountNumber}/orders"),
130 mutation: true,
131 requires_auth: true,
132 },
133 CommandSpec {
134 path: "safety show",
135 description: "Show active safety.json limits and path",
136 http: None,
137 mutation: false,
138 requires_auth: false,
139 },
140 CommandSpec {
141 path: "safety init",
142 description: "Create default safety.json in config directory",
143 http: None,
144 mutation: true,
145 requires_auth: false,
146 },
147 CommandSpec {
148 path: "safety path",
149 description: "Print safety.json path",
150 http: None,
151 mutation: false,
152 requires_auth: false,
153 },
154 CommandSpec {
155 path: "plan schema",
156 description: "JSON Schema for trade plan YAML/JSON files",
157 http: None,
158 mutation: false,
159 requires_auth: false,
160 },
161 CommandSpec {
162 path: "plan prompt",
163 description: "LLM workflow and template for generating trade plans",
164 http: None,
165 mutation: false,
166 requires_auth: false,
167 },
168 CommandSpec {
169 path: "plan validate",
170 description: "Validate trade plan structure and safety limits",
171 http: None,
172 mutation: false,
173 requires_auth: true,
174 },
175 CommandSpec {
176 path: "plan show",
177 description: "Display parsed trade plan",
178 http: None,
179 mutation: false,
180 requires_auth: false,
181 },
182 CommandSpec {
183 path: "plan run",
184 description: "Execute trade plan steps sequentially",
185 http: Some("POST /accounts/{accountNumber}/orders"),
186 mutation: true,
187 requires_auth: true,
188 },
189 CommandSpec {
190 path: "orders schema",
191 description: "OrderRequest JSON schema and Schwab order examples",
192 http: None,
193 mutation: false,
194 requires_auth: false,
195 },
196 CommandSpec {
197 path: "orders validate",
198 description: "Validate order JSON shape and safety.json limits",
199 http: None,
200 mutation: false,
201 requires_auth: false,
202 },
203 CommandSpec {
204 path: "orders list",
205 description: "List orders for an account",
206 http: Some("GET /accounts/{accountNumber}/orders"),
207 mutation: false,
208 requires_auth: true,
209 },
210 CommandSpec {
211 path: "orders all",
212 description: "List orders across all accounts",
213 http: Some("GET /orders"),
214 mutation: false,
215 requires_auth: true,
216 },
217 CommandSpec {
218 path: "orders get",
219 description: "Get order by ID",
220 http: Some("GET /accounts/{accountNumber}/orders/{orderId}"),
221 mutation: false,
222 requires_auth: true,
223 },
224 CommandSpec {
225 path: "orders wait",
226 description: "Poll order status until filled or timeout",
227 http: Some("GET /accounts/{accountNumber}/orders/{orderId}"),
228 mutation: false,
229 requires_auth: true,
230 },
231 CommandSpec {
232 path: "orders place",
233 description: "Place a new order",
234 http: Some("POST /accounts/{accountNumber}/orders"),
235 mutation: true,
236 requires_auth: true,
237 },
238 CommandSpec {
239 path: "orders preview",
240 description: "Preview order validation and fees",
241 http: Some("POST /accounts/{accountNumber}/previewOrder"),
242 mutation: false,
243 requires_auth: true,
244 },
245 CommandSpec {
246 path: "orders cancel",
247 description: "Cancel an open order",
248 http: Some("DELETE /accounts/{accountNumber}/orders/{orderId}"),
249 mutation: true,
250 requires_auth: true,
251 },
252 CommandSpec {
253 path: "orders replace",
254 description: "Replace an existing order",
255 http: Some("PUT /accounts/{accountNumber}/orders/{orderId}"),
256 mutation: true,
257 requires_auth: true,
258 },
259 CommandSpec {
260 path: "transactions list",
261 description: "List transactions for an account",
262 http: Some("GET /accounts/{accountNumber}/transactions"),
263 mutation: false,
264 requires_auth: true,
265 },
266 CommandSpec {
267 path: "transactions get",
268 description: "Get transaction by ID",
269 http: Some("GET /accounts/{accountNumber}/transactions/{transactionId}"),
270 mutation: false,
271 requires_auth: true,
272 },
273 CommandSpec {
274 path: "user preference",
275 description: "User preferences and streamer info",
276 http: Some("GET /userPreference"),
277 mutation: false,
278 requires_auth: true,
279 },
280 CommandSpec {
281 path: "market info",
282 description: "Agent dossier: quote + fundamentals + price context + research hints",
283 http: Some("GET /marketdata/v1/{symbol}/quotes + /instruments + /pricehistory"),
284 mutation: false,
285 requires_auth: true,
286 },
287 CommandSpec {
288 path: "market quotes",
289 description: "Get quotes for multiple symbols",
290 http: Some("GET /marketdata/v1/quotes"),
291 mutation: false,
292 requires_auth: true,
293 },
294 CommandSpec {
295 path: "market quote",
296 description: "Get quote for a single symbol",
297 http: Some("GET /marketdata/v1/{symbol}/quotes"),
298 mutation: false,
299 requires_auth: true,
300 },
301 CommandSpec {
302 path: "market history",
303 description: "Price history (OHLCV candles)",
304 http: Some("GET /marketdata/v1/pricehistory"),
305 mutation: false,
306 requires_auth: true,
307 },
308 CommandSpec {
309 path: "market instrument",
310 description: "Instrument search and fundamentals (company info)",
311 http: Some("GET /marketdata/v1/instruments"),
312 mutation: false,
313 requires_auth: true,
314 },
315 CommandSpec {
316 path: "market instrument-by-cusip",
317 description: "Instrument lookup by CUSIP",
318 http: Some("GET /marketdata/v1/instruments/{cusip}"),
319 mutation: false,
320 requires_auth: true,
321 },
322 CommandSpec {
323 path: "market hours",
324 description: "Market hours for one or more markets",
325 http: Some("GET /marketdata/v1/markets"),
326 mutation: false,
327 requires_auth: true,
328 },
329 CommandSpec {
330 path: "market hours-for",
331 description: "Market hours for a single market",
332 http: Some("GET /marketdata/v1/markets/{market}"),
333 mutation: false,
334 requires_auth: true,
335 },
336 CommandSpec {
337 path: "options chain",
338 description: "Option chain for an underlying symbol",
339 http: Some("GET /marketdata/v1/chains"),
340 mutation: false,
341 requires_auth: true,
342 },
343 CommandSpec {
344 path: "options positions",
345 description: "List option positions grouped into spreads",
346 http: Some("GET /accounts"),
347 mutation: false,
348 requires_auth: true,
349 },
350 CommandSpec {
351 path: "options schema",
352 description: "Strategy templates and symbology for options orders",
353 http: None,
354 mutation: false,
355 requires_auth: false,
356 },
357 CommandSpec {
358 path: "options validate",
359 description: "Validate strategy params against safety.json",
360 http: None,
361 mutation: false,
362 requires_auth: false,
363 },
364 CommandSpec {
365 path: "options preview",
366 description: "Preview vertical or iron condor order",
367 http: Some("POST /accounts/{accountNumber}/orders"),
368 mutation: false,
369 requires_auth: true,
370 },
371 CommandSpec {
372 path: "options open",
373 description: "Open vertical spread or iron condor",
374 http: Some("POST /accounts/{accountNumber}/orders"),
375 mutation: true,
376 requires_auth: true,
377 },
378 CommandSpec {
379 path: "options close",
380 description: "Close an option spread by position group id",
381 http: Some("POST /accounts/{accountNumber}/orders"),
382 mutation: true,
383 requires_auth: true,
384 },
385 CommandSpec {
386 path: "agent schema",
387 description: "JSON Schema for rules.yaml",
388 http: None,
389 mutation: false,
390 requires_auth: false,
391 },
392 CommandSpec {
393 path: "agent validate",
394 description: "Validate rules.yaml structure",
395 http: None,
396 mutation: false,
397 requires_auth: false,
398 },
399 CommandSpec {
400 path: "agent status",
401 description: "Show persisted agent state",
402 http: None,
403 mutation: false,
404 requires_auth: false,
405 },
406 CommandSpec {
407 path: "agent run",
408 description: "Run options agent loop from rules.yaml",
409 http: None,
410 mutation: true,
411 requires_auth: true,
412 },
413 CommandSpec {
414 path: "agent stop",
415 description: "Stop background options agent daemon",
416 http: None,
417 mutation: true,
418 requires_auth: false,
419 },
420 CommandSpec {
421 path: "agent close-all",
422 description: "Close all option spreads tracked in agent state (not whole account)",
423 http: Some("POST /accounts/{accountNumber}/orders"),
424 mutation: true,
425 requires_auth: true,
426 },
427 CommandSpec {
428 path: "dashboard",
429 description: "Rich terminal dashboard (agent, rules, positions)",
430 http: None,
431 mutation: false,
432 requires_auth: false,
433 },
434 CommandSpec {
435 path: "watch",
436 description: "Run agent in-process with live TUI (or attach to existing daemon)",
437 http: None,
438 mutation: false,
439 requires_auth: false,
440 },
441 CommandSpec {
442 path: "rules show",
443 description: "Human-readable rules configuration breakdown",
444 http: None,
445 mutation: false,
446 requires_auth: false,
447 },
448 CommandSpec {
449 path: "rules list",
450 description: "List discoverable rules/*.yaml files",
451 http: None,
452 mutation: false,
453 requires_auth: false,
454 },
455 ]
456}
457
458pub fn command_tree() -> Value {
459 json!([
460 { "group": "meta", "commands": ["capabilities", "env schema", "instructions"] },
461 { "group": "auth", "commands": ["login", "status", "refresh", "logout"] },
462 { "group": "accounts", "commands": ["numbers", "list", "get"] },
463 { "group": "portfolio", "commands": ["summary", "buying-power"] },
464 { "group": "trade", "commands": ["buy", "sell"] },
465 { "group": "safety", "commands": ["show", "init", "path"] },
466 { "group": "plan", "commands": ["schema", "prompt", "validate", "show", "run"] },
467 { "group": "market", "commands": ["info", "quotes", "quote", "history", "instrument", "instrument-by-cusip", "hours", "hours-for"] },
468 { "group": "options", "commands": ["chain", "positions", "schema", "validate", "preview", "open", "close"] },
469 { "group": "agent", "commands": ["schema", "validate", "status", "run", "stop", "close-all"] },
470 { "group": "dashboard", "commands": ["dashboard"] },
471 { "group": "watch", "commands": ["watch"] },
472 { "group": "rules", "commands": ["show", "list"] },
473 { "group": "orders", "commands": ["schema", "validate", "list", "all", "get", "wait", "place", "preview", "cancel", "replace"] },
474 { "group": "transactions", "commands": ["list", "get"] },
475 { "group": "user", "commands": ["preference"] }
476 ])
477}
478
479pub fn capabilities_json() -> Value {
480 let commands: Vec<Value> = all_commands()
481 .into_iter()
482 .map(|c| {
483 json!({
484 "path": c.path,
485 "description": c.description,
486 "http": c.http,
487 "mutation": c.mutation,
488 "requires_auth": c.requires_auth,
489 })
490 })
491 .collect();
492
493 json!({
494 "cli": "schwab",
495 "api_product": "Trader API - Individual (Accounts and Trading Production)",
496 "market_data_base_url": schwab_api::MARKET_DATA_BASE_URL,
497 "base_url": schwab_api::TRADER_BASE_URL,
498 "default_mode": "agent",
499 "output_formats": ["pretty", "json", "md"],
500 "mutation_policy": "Auth mutations require --yes in non-interactive mode",
501 "trading_policy": "Trading mutations require --trust --yes in agent mode; safety.json hard limits always enforced",
502 "global_flags": ["--yes", "--trust", "--dry-run", "--json"],
503 "commands": commands,
504 })
505}