wactorz_interfaces/
cli.rs

1//! Interactive command-line interface.
2//!
3//! The CLI reads lines from stdin and dispatches them to the `MainActor`
4//! mailbox.  Special commands (prefixed with `/`) control the system:
5//!
6//! | Command | Effect |
7//! |---------|--------|
8//! | `/list` | List all registered actors and their states |
9//! | `/stop <name>` | Stop the named actor |
10//! | `/status` | Print system health summary |
11//! | `/quit` | Gracefully shut down the entire system |
12
13use anyhow::Result;
14use tokio::io::{AsyncBufReadExt, BufReader};
15
16use wactorz_core::{ActorSystem, Message};
17
18/// Start the interactive CLI loop.
19///
20/// Reads from stdin asynchronously and dispatches messages to `system`.
21/// Returns when the user types `/quit` or stdin is closed.
22pub async fn run_cli(system: ActorSystem) -> Result<()> {
23    let stdin = tokio::io::stdin();
24    let mut reader = BufReader::new(stdin).lines();
25
26    println!("AgentFlow CLI — type a message or /help for commands.");
27
28    while let Some(line) = reader.next_line().await? {
29        let trimmed = line.trim();
30        if trimmed.is_empty() {
31            continue;
32        }
33
34        if let Some(cmd) = trimmed.strip_prefix('/') {
35            handle_command(cmd, &system).await?;
36            if cmd.trim() == "quit" {
37                break;
38            }
39        } else {
40            dispatch_to_main_actor(trimmed, &system).await?;
41        }
42    }
43
44    Ok(())
45}
46
47/// Parse and execute a `/`-prefixed CLI command.
48async fn handle_command(cmd: &str, system: &ActorSystem) -> Result<()> {
49    let parts: Vec<&str> = cmd.splitn(2, ' ').collect();
50    match parts.as_slice() {
51        ["help"] => {
52            println!("Commands: /list, /stop <name>, /status, /quit");
53        }
54        ["list"] => {
55            let actors = system.registry.list().await;
56            for entry in actors {
57                println!("  {:<20} {} ({})", entry.name, entry.id, entry.state);
58            }
59        }
60        ["stop", name] => {
61            system.stop_actor(name).await?;
62            println!("Sent stop signal to '{name}'");
63        }
64        ["status"] => {
65            let actors = system.registry.list().await;
66            println!("System status: {} actor(s)", actors.len());
67            for e in &actors {
68                println!("  {} {} — {}", e.name, e.id, e.state);
69            }
70        }
71        ["quit"] => {
72            println!("Shutting down…");
73            system.shutdown().await?;
74        }
75        _ => {
76            println!("Unknown command: /{cmd}. Type /help.");
77        }
78    }
79    Ok(())
80}
81
82/// Send a plain text message to the MainActor.
83async fn dispatch_to_main_actor(text: &str, system: &ActorSystem) -> Result<()> {
84    let entry = system
85        .registry
86        .get_by_name("main-actor")
87        .await
88        .ok_or_else(|| anyhow::anyhow!("main-actor not found"))?;
89    let msg = Message::text(None, Some(entry.id.clone()), text);
90    system.registry.send(&entry.id, msg).await
91}