Skip to main content

schwab_cli/commands/
agent.rs

1use 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}