theater_cli/
lib.rs

1pub mod client;
2pub mod commands;
3pub mod config;
4pub mod error;
5pub mod output;
6pub mod templates;
7pub mod tui;
8pub mod utils;
9
10use clap::{Parser, Subcommand};
11
12/// Theater CLI - A WebAssembly actor system that enables state management,
13/// verification, and flexible interaction patterns.
14#[derive(Debug, Parser)]
15#[command(name = "theater")]
16#[command(author, version, about, long_about = None)]
17pub struct Cli {
18    /// Turn on verbose output
19    #[arg(short, long, global = true)]
20    pub verbose: bool,
21
22    /// Display output in JSON format
23    #[arg(long, global = true)]
24    pub json: bool,
25
26    #[command(subcommand)]
27    pub command: Commands,
28}
29
30#[derive(Debug, Subcommand)]
31pub enum Commands {
32    /// Create a new Theater actor project
33    #[command(name = "create")]
34    Create(commands::create::CreateArgs),
35
36    /// Build a Theater actor to WebAssembly
37    #[command(name = "build")]
38    Build(commands::build::BuildArgs),
39
40    /// Start or deploy an actor from a manifest
41    #[command(name = "start")]
42    Start(commands::start::StartArgs),
43
44    /// Subscribe to real-time events from an actor
45    #[command(name = "subscribe")]
46    Subscribe(commands::subscribe::SubscribeArgs),
47
48    /// List all running actors
49    #[command(name = "list")]
50    List(commands::list::ListArgs),
51
52    /// Get actor state
53    #[command(name = "state")]
54    State(commands::state::StateArgs),
55
56    /// Get actor events (from running actor or filesystem)
57    #[command(name = "events")]
58    Events(commands::events::EventsArgs),
59
60    /// Interactively explore actor events with TUI
61    #[command(name = "events-explore")]
62    EventsExplore(commands::events_explore::ExploreArgs),
63
64    /// Inspect a running actor (detailed view)
65    #[command(name = "inspect")]
66    Inspect(commands::inspect::InspectArgs),
67
68    /// Stop a running actor
69    #[command(name = "stop")]
70    Stop(commands::stop::StopArgs),
71
72    /// Send a message to an actor
73    #[command(name = "message")]
74    Message(commands::message::MessageArgs),
75
76    /// List stored actor IDs
77    #[command(name = "list-stored")]
78    ListStored(commands::list_stored::ListStoredArgs),
79
80    /// Channel operations
81    #[command(name = "channel")]
82    Channel(commands::channel::ChannelArgs),
83
84    /// Generate shell completion scripts
85    #[command(name = "completion")]
86    Completion(commands::completion::CompletionArgs),
87
88    /// Generate dynamic completions (internal use)
89    #[command(name = "dynamic-completion", hide = true)]
90    DynamicCompletion(commands::dynamic_completion::DynamicCompletionArgs),
91}
92
93/// Run the Theater CLI asynchronously
94pub async fn run(
95    cli: Cli,
96    config: config::Config,
97    _shutdown_rx: tokio::sync::oneshot::Receiver<()>,
98) -> anyhow::Result<()> {
99    // Create output manager
100    let output = output::OutputManager::new(config.output.clone());
101
102    // Create a context that contains shared resources
103    let ctx = CommandContext {
104        config,
105        output,
106        verbose: cli.verbose,
107        json: cli.json,
108    };
109
110    // Execute the command - using legacy functions for now, can be modernized incrementally
111    let result = match &cli.command {
112        Commands::Subscribe(args) => commands::subscribe::execute_async(args, &ctx)
113            .await
114            .map_err(|e| anyhow::Error::from(e)),
115        Commands::Create(args) => commands::create::execute_async(args, &ctx)
116            .await
117            .map_err(|e| anyhow::Error::from(e)),
118        Commands::Build(args) => commands::build::execute_async(args, &ctx)
119            .await
120            .map_err(|e| anyhow::Error::from(e)),
121        Commands::List(args) => commands::list::execute_async(args, &ctx)
122            .await
123            .map_err(|e| anyhow::Error::from(e)),
124        Commands::State(args) => commands::state::execute_async(args, &ctx)
125            .await
126            .map_err(|e| anyhow::Error::from(e)),
127        Commands::Events(args) => commands::events::execute_async(args, &ctx)
128            .await
129            .map_err(|e| anyhow::Error::from(e)),
130        Commands::EventsExplore(args) => commands::events_explore::execute_async(args, &ctx)
131            .await
132            .map_err(|e| anyhow::Error::from(e)),
133        Commands::Inspect(args) => commands::inspect::execute_async(args, &ctx)
134            .await
135            .map_err(|e| anyhow::Error::from(e)),
136        Commands::Start(args) => commands::start::execute_async(args, &ctx)
137            .await
138            .map_err(|e| anyhow::Error::from(e)),
139        Commands::Stop(args) => commands::stop::execute_async(args, &ctx)
140            .await
141            .map_err(|e| anyhow::Error::from(e)),
142        Commands::Message(args) => commands::message::execute_async(args, &ctx)
143            .await
144            .map_err(|e| anyhow::Error::from(e)),
145        Commands::Channel(args) => match &args.command {
146            commands::channel::ChannelCommands::Open(open_args) => {
147                commands::channel::open::execute_async(open_args, &ctx)
148                    .await
149                    .map_err(|e| anyhow::Error::from(e))
150            }
151        },
152        Commands::ListStored(args) => commands::list_stored::execute_async(args, &ctx)
153            .await
154            .map_err(|e| anyhow::Error::from(e)),
155        Commands::Completion(args) => commands::completion::execute_async(args, &ctx)
156            .await
157            .map_err(|e| anyhow::Error::from(e)),
158        Commands::DynamicCompletion(args) => {
159            commands::dynamic_completion::execute_async(args, &ctx)
160                .await
161                .map_err(|e| anyhow::Error::from(e))
162        }
163    };
164
165    // Handle the result
166    match result {
167        Ok(()) => Ok(()),
168        Err(e) => {
169            // Use our enhanced error handling
170            if let Some(cli_error) = e.downcast_ref::<error::CliError>() {
171                ctx.output.error(&cli_error.user_message())?;
172                if ctx.verbose {
173                    eprintln!("\nDebug info: {:?}", cli_error);
174                }
175            } else {
176                ctx.output.error(&format!("Error: {}", e))?;
177                if ctx.verbose {
178                    eprintln!("\nDebug info: {:?}", e);
179                }
180            }
181            std::process::exit(1);
182        }
183    }
184}
185
186/// Shared context for command execution
187pub struct CommandContext {
188    pub config: config::Config,
189    pub output: output::OutputManager,
190    pub verbose: bool,
191    pub json: bool,
192}
193
194impl CommandContext {
195    /// Create a theater client using the configured server address
196    pub fn create_client(&self) -> client::TheaterClient {
197        client::TheaterClient::new(self.config.server.default_address)
198    }
199
200    /// Get the server address from config or override
201    pub fn server_address(
202        &self,
203        override_addr: Option<std::net::SocketAddr>,
204    ) -> std::net::SocketAddr {
205        override_addr.unwrap_or(self.config.server.default_address)
206    }
207}