schwab_cli/commands/
agent.rs1use std::path::PathBuf;
2
3use anyhow::Result;
4use serde_json::json;
5
6use crate::agent::{default_state_path, load_agent_state, load_state, log_path, pid_path, run_agent_loop, spawn_background, state_summary, stop_daemon};
7use crate::cli::AgentCommands;
8use crate::config::RuntimeConfig;
9use crate::output::ResponseEnvelope;
10use crate::rules::{rules_json_schema, RulesConfig};
11
12pub async fn run(runtime: &RuntimeConfig, command: AgentCommands) -> Result<()> {
13 match command {
14 AgentCommands::Schema => {
15 runtime.emit(ResponseEnvelope::ok("agent schema", rules_json_schema()));
16 }
17 AgentCommands::Validate { file } => {
18 let rules = RulesConfig::load(&file)?;
19 runtime.emit(ResponseEnvelope::ok(
20 "agent validate",
21 json!({
22 "valid": true,
23 "agent_id": rules.agent_id,
24 "accounts": rules.accounts.len(),
25 "watchlist": rules.watchlist,
26 "llm_enabled": rules.llm.enabled,
27 "telegram_enabled": rules.notify.telegram.enabled,
28 }),
29 ));
30 }
31 AgentCommands::Status { rules_file } => {
32 let (path, state) = if let Some(p) = rules_file {
33 let path = default_state_path(&p);
34 let agent_id = RulesConfig::load(&p)
35 .map(|r| r.agent_id)
36 .unwrap_or_default();
37 let state = if path.exists() {
38 load_state(&path)?
39 } else {
40 load_agent_state(&p, &agent_id)
41 };
42 (path, state)
43 } else {
44 let path = directories::ProjectDirs::from("com", "schwabinvestbot", "schwab")
45 .map(|d| d.data_local_dir().join("agent-state.json"))
46 .unwrap_or_else(|| PathBuf::from("agent-state.json"));
47 let state = load_state(&path)?;
48 (path, state)
49 };
50 runtime.emit(ResponseEnvelope::ok(
51 "agent status",
52 json!({ "state_path": path, "state": state_summary(&state) }),
53 ));
54 }
55 AgentCommands::Run { file, once, background } => {
56 if background {
57 if once {
58 anyhow::bail!("--background cannot be combined with --once");
59 }
60 let mut extra = Vec::new();
61 if runtime.dry_run {
62 extra.push("--dry-run".into());
63 }
64 if runtime.trust {
65 extra.push("--trust".into());
66 }
67 if runtime.yes {
68 extra.push("--yes".into());
69 }
70 if runtime.output == crate::output::OutputFormat::Json {
71 extra.push("--json".into());
72 }
73 let pid = spawn_background(&file, &extra)?;
74 runtime.emit(ResponseEnvelope::ok(
75 "agent background",
76 json!({
77 "pid": pid,
78 "pid_file": pid_path(&file),
79 "log_file": log_path(&file),
80 "rules": file,
81 }),
82 ));
83 return Ok(());
84 }
85 run_agent_loop(runtime, &file, once).await?;
86 }
87 AgentCommands::Stop { file } => {
88 stop_daemon(&file)?;
89 runtime.emit(ResponseEnvelope::ok(
90 "agent stop",
91 json!({ "stopped": true, "rules": file }),
92 ));
93 }
94 }
95 Ok(())
96}