1use anyhow::Result;
2use clap::{Parser, ValueEnum};
3use std::io::Write;
4use std::path::PathBuf;
5use tokio::sync::mpsc;
6use tracing::{debug, error};
7
8use crate::{error::CliError, CommandContext};
9use theater::chain::ChainEvent;
10use theater::config::actor_manifest::{
11 RuntimeHostConfig, StoreHandlerConfig, SupervisorHostConfig, TcpHandlerConfig,
12 TerminalHandlerConfig, TimerHandlerConfig,
13};
14use theater::handler::HandlerRegistry;
15use theater::messages::TheaterCommand;
16use theater::pack_bridge::Value;
17use theater::theater_runtime::TheaterRuntime;
18use theater::utils::resolve_reference;
19use theater::ManifestConfig;
20use theater::TheaterId;
21use theater_handler_loop::LoopHandler;
22use theater_handler_message_server::{MessageRouter, MessageServerHandler};
23use theater_handler_rpc::RpcHandler;
24use theater_handler_runtime::RuntimeHandler;
25use theater_handler_store::StoreHandler;
26use theater_handler_supervisor::SupervisorHandler;
27use theater_handler_tcp::TcpHandler;
28use theater_handler_terminal::TerminalHandler;
29use theater_handler_timer::TimerHandler;
30
31#[derive(Debug, Clone, Copy, ValueEnum, Default)]
33pub enum EventFormat {
34 Json,
36 #[default]
38 Short,
39 Full,
41}
42
43#[derive(Debug, Parser)]
44pub struct StartArgs {
45 #[arg(default_value = "manifest.toml")]
47 pub manifest: String,
48
49 #[arg(long)]
51 pub events: bool,
52
53 #[arg(long, value_enum, default_value = "short")]
55 pub events_format: EventFormat,
56
57 #[arg(long, default_missing_value = ".chains", num_args = 0..=1)]
60 pub save: Option<PathBuf>,
61
62 #[arg(long)]
64 pub no_init: bool,
65
66 #[arg(long)]
68 pub no_actor_logs: bool,
69}
70
71fn format_event_short(event: &ChainEvent, actor_id: &TheaterId) -> String {
73 let id_str = actor_id.to_string();
74 let short_id = &id_str[..8.min(id_str.len())];
75 format!("[{}] {}\n", short_id, event)
76}
77
78fn format_event_full(event: &ChainEvent, actor_id: &TheaterId) -> String {
80 let id_str = actor_id.to_string();
81 let short_id = &id_str[..8.min(id_str.len())];
82 let hash_hex = hex::encode(&event.hash);
83 let parent_hex = event
84 .parent_hash
85 .as_ref()
86 .map(hex::encode)
87 .unwrap_or_else(|| "none".to_string());
88 let data_str = String::from_utf8_lossy(&event.data);
89
90 format!(
91 "EVENT [{}] {}\nparent: {}\ntype: {}\nsize: {}\n{}\n\n",
92 short_id,
93 hash_hex,
94 parent_hex,
95 event.event_type,
96 event.data.len(),
97 data_str
98 )
99}
100
101fn format_event_json(event: &ChainEvent, actor_id: &TheaterId) -> String {
103 let json = serde_json::json!({
104 "actor_id": actor_id.to_string(),
105 "hash": hex::encode(&event.hash),
106 "parent_hash": event.parent_hash.as_ref().map(hex::encode),
107 "event_type": event.event_type,
108 "data": format!("{} bytes (pack-encoded)", event.data.len())
109 });
110 serde_json::to_string(&json).unwrap_or_else(|_| "{}".to_string())
111}
112
113fn create_handler_registry(
115 theater_tx: mpsc::Sender<TheaterCommand>,
116 show_actor_logs: bool,
117) -> Result<HandlerRegistry, CliError> {
118 let mut registry = HandlerRegistry::new();
119
120 let runtime_config = RuntimeHostConfig {};
122 registry.register(
123 RuntimeHandler::new(runtime_config, theater_tx.clone(), None)
124 .with_show_logs(show_actor_logs),
125 );
126
127 let store_config = StoreHandlerConfig::default();
129 registry.register(StoreHandler::new(store_config, None));
130
131 let supervisor_config = SupervisorHostConfig {};
133 registry.register(SupervisorHandler::new(supervisor_config, None));
134
135 let message_router = MessageRouter::new();
137 registry.register(MessageServerHandler::new(None, message_router.clone()));
138
139 registry.register(RpcHandler::new(theater_tx.clone()));
141
142 let tcp_config = TcpHandlerConfig {
144 listen: None,
145 max_connections: None,
146 ..Default::default()
147 };
148 registry.register(TcpHandler::new(tcp_config));
149
150 let terminal_config = TerminalHandlerConfig::default();
152 registry.register(TerminalHandler::new(terminal_config));
153
154 let timer_config = TimerHandlerConfig::default();
156 registry.register(TimerHandler::new(timer_config));
157
158 registry.register(LoopHandler::new());
160
161 Ok(registry)
162}
163
164pub async fn execute_async(args: &StartArgs, ctx: &CommandContext) -> Result<(), CliError> {
166 debug!("Starting actor from manifest: {}", args.manifest);
167
168 let manifest_bytes = resolve_reference(&args.manifest).await.map_err(|e| {
170 CliError::invalid_manifest(format!(
171 "Failed to resolve manifest reference '{}': {}",
172 args.manifest, e
173 ))
174 })?;
175
176 let manifest_content = String::from_utf8(manifest_bytes).map_err(|e| {
177 CliError::invalid_manifest(format!("Manifest content is not valid UTF-8: {}", e))
178 })?;
179
180 let manifest = ManifestConfig::from_toml_str(&manifest_content)
184 .map_err(|e| CliError::invalid_manifest(format!("Failed to parse manifest: {}", e)))?;
185
186 let (theater_tx, theater_rx) = mpsc::channel::<TheaterCommand>(32);
188 let handler_registry = create_handler_registry(theater_tx.clone(), !args.no_actor_logs)?;
189
190 let mut runtime = TheaterRuntime::new(
191 theater_tx.clone(),
192 theater_rx,
193 None, handler_registry,
195 )
196 .await
197 .map_err(|e| CliError::server_error(format!("Failed to create runtime: {}", e)))?;
198
199 if let Some(ref dir) = args.save {
201 runtime.chain_dir = Some(dir.clone());
202 }
203
204 let (global_events_tx, mut global_events_rx) = mpsc::channel(256);
206 runtime.add_global_subscription(global_events_tx);
207
208 let runtime_handle = tokio::spawn(async move {
210 if let Err(e) = runtime.run().await {
211 error!("Theater runtime error: {}", e);
212 }
213 });
214
215 let wasm_path = if manifest.package.starts_with('/') || manifest.package.contains("://") {
217 manifest.package.clone()
219 } else {
220 let manifest_path = std::path::Path::new(&args.manifest);
222 if let Some(manifest_dir) = manifest_path.parent() {
223 manifest_dir
224 .join(&manifest.package)
225 .to_string_lossy()
226 .to_string()
227 } else {
228 manifest.package.clone()
229 }
230 };
231
232 let wasm_bytes = resolve_reference(&wasm_path).await.map_err(|e| {
234 CliError::server_error(format!("Failed to load WASM from '{}': {}", wasm_path, e))
235 })?;
236
237 let (response_tx, response_rx) = tokio::sync::oneshot::channel();
239
240 let (supervisor_tx, mut supervisor_rx) = mpsc::channel(32);
242
243 theater_tx
244 .send(TheaterCommand::SpawnActor {
245 wasm_bytes,
246 name: Some(manifest.name.clone()),
247 manifest: Some(manifest),
248 init_bytes: None,
249 response_tx,
250 supervisor_tx: Some(supervisor_tx),
251 subscription_tx: None, })
253 .await
254 .map_err(|e| CliError::server_error(format!("Failed to send spawn command: {}", e)))?;
255
256 let actor_id = match response_rx.await {
258 Ok(Ok(id)) => {
259 debug!("Actor started: {}", id);
260 id
261 }
262 Ok(Err(e)) => {
263 return Err(CliError::server_error(format!(
264 "Failed to start actor: {}",
265 e
266 )));
267 }
268 Err(e) => {
269 return Err(CliError::server_error(format!(
270 "Failed to receive spawn response: {}",
271 e
272 )));
273 }
274 };
275
276 if !args.no_init {
278 let (handle_tx, handle_rx) = tokio::sync::oneshot::channel();
280 theater_tx
281 .send(TheaterCommand::GetActorHandle {
282 actor_id,
283 response_tx: handle_tx,
284 })
285 .await
286 .map_err(|e| CliError::server_error(format!("Failed to get actor handle: {}", e)))?;
287
288 let actor_handle = match handle_rx.await {
289 Ok(Some(handle)) => handle,
290 Ok(None) => {
291 return Err(CliError::server_error("Actor handle not found".to_string()));
292 }
293 Err(e) => {
294 return Err(CliError::server_error(format!(
295 "Failed to receive actor handle: {}",
296 e
297 )));
298 }
299 };
300
301 let init_params = Value::Tuple(vec![]);
303 debug!("Calling init on actor {}", actor_id);
304 let _init_result = actor_handle
305 .call_function("theater:simple/actor.init".to_string(), init_params)
306 .await
307 .map_err(|e| CliError::server_error(format!("Failed to call init: {}", e)))?;
308 debug!("Init completed");
309 }
310
311 loop {
321 tokio::select! {
322 result = supervisor_rx.recv() => {
324 match result {
325 Some(actor_result) => {
326 debug!("Actor exited: {:?}", actor_result);
327 match actor_result {
328 theater::messages::ActorResult::Success(success) => {
329 if let Some(output) = success.result {
330 let _ = std::io::stdout().write_all(&output);
332 let _ = std::io::stdout().flush();
333 }
334 }
335 theater::messages::ActorResult::Error(err) => {
336 eprintln!("Actor error: {}", err.error);
337 std::process::exit(1);
338 }
339 theater::messages::ActorResult::ExternalStop(_) => {
340 debug!("Actor stopped externally");
341 }
342 }
343 break;
344 }
345 None => {
346 debug!("Supervisor channel closed");
348 break;
349 }
350 }
351 }
352
353 event = global_events_rx.recv() => {
355 if let Some((event_actor_id, event_result)) = event {
356 match event_result {
357 Ok(chain_event) => {
358 if args.events {
361 match args.events_format {
362 EventFormat::Json => {
363 println!("{}", format_event_json(&chain_event, &event_actor_id));
364 }
365 EventFormat::Short => {
366 print!("{}", format_event_short(&chain_event, &event_actor_id));
367 }
368 EventFormat::Full => {
369 print!("{}", format_event_full(&chain_event, &event_actor_id));
370 }
371 }
372 }
373
374 if event_actor_id == actor_id && chain_event.event_type == "shutdown" {
376 break;
377 }
378 }
379 Err(e) => {
380 debug!("Actor error event: {:?}", e);
381 }
382 }
383 }
384 }
385
386 _ = tokio::signal::ctrl_c() => {
388 debug!("Received Ctrl+C, stopping actor {}", actor_id);
389 eprintln!("\nStopping actor...");
390
391 let (stop_tx, stop_rx) = tokio::sync::oneshot::channel();
392 let _ = theater_tx.send(TheaterCommand::StopActor {
393 actor_id,
394 response_tx: stop_tx,
395 }).await;
396
397 match tokio::time::timeout(
399 tokio::time::Duration::from_secs(5),
400 stop_rx,
401 ).await {
402 Ok(Ok(Ok(()))) => debug!("Actor stopped gracefully"),
403 _ => debug!("Actor stop timed out or failed"),
404 }
405 break;
406 }
407
408 _ = ctx.shutdown_token.cancelled() => {
410 debug!("Shutdown token cancelled");
411 break;
412 }
413 }
414 }
415
416 drop(theater_tx);
418
419 let _ = tokio::time::timeout(tokio::time::Duration::from_secs(5), runtime_handle).await;
421
422 Ok(())
423}