1use chrono::DateTime;
2use serde_json::Value;
3use theater::ChainEvent;
4
5use crate::error::CliResult;
6use crate::output::{OutputFormat, OutputManager};
7use crate::utils::event_display::display_single_event;
8
9#[derive(Debug, serde::Serialize)]
11pub struct ActorList {
12 pub actors: Vec<(String, String)>,
13}
14
15impl OutputFormat for ActorList {
16 fn format_compact(&self, output: &OutputManager) -> CliResult<()> {
17 if self.actors.is_empty() {
18 output.info("No actors are currently running")?;
19 } else {
20 output.info(&format!("Running actors: {}", self.actors.len()))?;
21 for (id, name) in &self.actors {
22 println!(
23 " {} {}",
24 output.theme().accent().apply_to(id),
25 output.theme().muted().apply_to(name)
26 );
27 }
28 }
29 Ok(())
30 }
31
32 fn format_pretty(&self, output: &OutputManager) -> CliResult<()> {
33 if self.actors.is_empty() {
34 output.info("No actors are currently running")?;
35 } else {
36 println!("{}", output.theme().highlight().apply_to("Running Actors"));
37 println!("{}", "─".repeat(40));
38
39 for (i, (id, name)) in self.actors.iter().enumerate() {
40 println!(
41 "{}. {} {}",
42 i + 1,
43 output.theme().accent().apply_to(id),
44 output.theme().muted().apply_to(&format!("({})", name))
45 );
46 }
47 println!();
48 output.info(&format!("Total: {} actors", self.actors.len()))?;
49 }
50 Ok(())
51 }
52
53 fn format_table(&self, output: &OutputManager) -> CliResult<()> {
54 if self.actors.is_empty() {
55 output.info("No actors are currently running")?;
56 return Ok(());
57 }
58
59 let headers = vec!["ID", "Name"];
60 let rows: Vec<Vec<String>> = self
61 .actors
62 .iter()
63 .map(|(id, name)| vec![id.clone(), name.clone()])
64 .collect();
65
66 output.table(&headers, &rows)?;
67 Ok(())
68 }
69
70 fn format_detailed(&self, output: &OutputManager) -> CliResult<()> {
71 if self.actors.is_empty() {
72 output.info("No actors are currently running")?;
73 return Ok(());
74 }
75
76 println!(
77 "{}",
78 output.theme().highlight().apply_to("Detailed Actor List")
79 );
80 println!("{}", "─".repeat(40));
81
82 for (i, (id, name)) in self.actors.iter().enumerate() {
83 println!(
84 "{}. {} - {}",
85 i + 1,
86 output.theme().accent().apply_to(id),
87 output.theme().muted().apply_to(name)
88 );
89 }
90 Ok(())
91 }
92}
93
94#[derive(Debug, serde::Serialize)]
96pub struct ActorEvents {
97 pub actor_id: String,
98 pub events: Vec<ChainEvent>,
99}
100
101impl OutputFormat for ActorEvents {
102 fn format_compact(&self, output: &OutputManager) -> CliResult<()> {
103 if self.events.is_empty() {
104 output.info(&format!("No events found for actor {}", self.actor_id))?;
105 return Ok(());
106 }
107
108 println!(
109 "{:<12} {:<12} {:<25} {}",
110 "HASH", "PARENT", "EVENT TYPE", "DESCRIPTION"
111 );
112 println!("{}", "─".repeat(100));
113
114 for event in &self.events {
115 display_single_event(event, "compact")?;
116 }
117 Ok(())
118 }
119
120 fn format_pretty(&self, output: &OutputManager) -> CliResult<()> {
121 if self.events.is_empty() {
122 output.info(&format!("No events found for actor {}", self.actor_id))?;
123 return Ok(());
124 }
125
126 println!(
127 "{}",
128 output
129 .theme()
130 .highlight()
131 .apply_to(&format!("Events for Actor: {}", self.actor_id))
132 );
133 println!("{}", "─".repeat(80));
134
135 for event in self.events.iter() {
136 display_single_event(event, "pretty")?;
137 }
138
139 Ok(())
140 }
141
142 fn format_table(&self, output: &OutputManager) -> CliResult<()> {
143 if self.events.is_empty() {
144 output.info(&format!("No events found for actor {}", self.actor_id))?;
145 return Ok(());
146 }
147
148 let headers = vec!["Timestamp", "Type", "Description"];
149 let rows: Vec<Vec<String>> = self
150 .events
151 .iter()
152 .map(|event| {
153 vec![
154 format_timestamp(event.timestamp),
155 event.event_type.clone(),
156 truncate_string(event.description.as_deref().unwrap_or("No description"), 50),
157 ]
158 })
159 .collect();
160
161 output.table(&headers, &rows)?;
162 Ok(())
163 }
164
165 fn format_detailed(&self, output: &OutputManager) -> CliResult<()> {
166 if self.events.is_empty() {
167 output.info(&format!("No events found for actor {}", self.actor_id))?;
168 return Ok(());
169 }
170
171 println!(
172 "{}",
173 output.theme().highlight().apply_to("Detailed Actor Events")
174 );
175 println!("{}", "─".repeat(80));
176
177 for event in self.events.iter() {
178 display_single_event(event, "detailed")?;
179 }
180 Ok(())
181 }
182}
183
184#[derive(Debug, serde::Serialize)]
186pub struct ActorState {
187 pub actor_id: String,
188 pub state: Value,
189}
190
191impl OutputFormat for ActorState {
192 fn format_compact(&self, output: &OutputManager) -> CliResult<()> {
193 println!(
194 "State for actor {}: {}",
195 output.theme().accent().apply_to(&self.actor_id),
196 self.state
197 );
198 Ok(())
199 }
200
201 fn format_pretty(&self, output: &OutputManager) -> CliResult<()> {
202 println!(
203 "{}",
204 output
205 .theme()
206 .highlight()
207 .apply_to(&format!("State for Actor: {}", self.actor_id))
208 );
209 println!("{}", "─".repeat(40));
210 println!(
211 "{}",
212 serde_json::to_string_pretty(&self.state)
213 .unwrap_or_else(|_| "Invalid JSON".to_string())
214 );
215 Ok(())
216 }
217
218 fn format_table(&self, output: &OutputManager) -> CliResult<()> {
219 self.format_pretty(output)
221 }
222
223 fn format_detailed(&self, output: &OutputManager) -> CliResult<()> {
224 println!(
225 "{}",
226 output.theme().highlight().apply_to("Detailed Actor State")
227 );
228 println!("{}", "─".repeat(40));
229 println!(
230 "{}",
231 serde_json::to_string_pretty(&self.state)
232 .unwrap_or_else(|_| "Invalid JSON".to_string())
233 );
234 Ok(())
235 }
236}
237
238#[derive(Debug, serde::Serialize)]
240pub struct BuildResult {
241 pub success: bool,
242 pub project_dir: std::path::PathBuf,
243 pub wasm_path: Option<std::path::PathBuf>,
244 pub manifest_exists: bool,
245 pub manifest_path: Option<std::path::PathBuf>,
246 pub build_type: String,
247 pub package_name: String,
248 pub stdout: String,
249 pub stderr: String,
250}
251
252impl OutputFormat for BuildResult {
253 fn format_compact(&self, output: &OutputManager) -> CliResult<()> {
254 if self.success {
255 output.success("Build completed successfully")?;
256 if let Some(wasm_path) = &self.wasm_path {
257 println!(
258 " Component: {}",
259 output.theme().accent().apply_to(wasm_path.display())
260 );
261 }
262 } else {
263 output.error("Build failed")?;
264 if !self.stderr.is_empty() {
265 println!("{}", output.theme().muted().apply_to(&self.stderr));
266 }
267 }
268 Ok(())
269 }
270
271 fn format_pretty(&self, output: &OutputManager) -> CliResult<()> {
272 if self.success {
273 println!(
274 "{} {}",
275 output.theme().success_icon(),
276 output.theme().highlight().apply_to("Build Successful")
277 );
278 println!();
279 println!(
280 "Package: {}",
281 output.theme().accent().apply_to(&self.package_name)
282 );
283 println!(
284 "Build Type: {}",
285 output.theme().muted().apply_to(&self.build_type)
286 );
287
288 if let Some(wasm_path) = &self.wasm_path {
289 println!(
290 "Component: {}",
291 output.theme().accent().apply_to(wasm_path.display())
292 );
293 }
294
295 if self.manifest_exists {
296 if let Some(manifest_path) = &self.manifest_path {
297 println!("\nTo deploy your actor:");
298 println!(
299 " theater start {}",
300 output.theme().muted().apply_to(manifest_path.display())
301 );
302 }
303 } else {
304 println!(
305 "\n{} No manifest.toml found.",
306 output.theme().warning_icon()
307 );
308 if let Some(wasm_path) = &self.wasm_path {
309 println!(
310 "Create one to deploy: theater create-manifest --component-path {}",
311 output.theme().muted().apply_to(wasm_path.display())
312 );
313 }
314 }
315
316 if !self.stdout.is_empty() {
317 println!("\nBuild Output:");
318 println!("{}", output.theme().muted().apply_to(&self.stdout));
319 }
320 } else {
321 println!(
322 "{} {}",
323 output.theme().error_icon(),
324 output.theme().error().apply_to("Build Failed")
325 );
326
327 if !self.stderr.is_empty() {
328 println!("\nError Output:");
329 println!("{}", self.stderr);
330 }
331 if !self.stdout.is_empty() {
332 println!("\nBuild Output:");
333 println!("{}", self.stdout);
334 }
335 }
336 Ok(())
337 }
338
339 fn format_table(&self, output: &OutputManager) -> CliResult<()> {
340 let headers = vec!["Property", "Value"];
341 let mut rows = vec![vec![
342 "Status".to_string(),
343 if self.success {
344 "Success".to_string()
345 } else {
346 "Failed".to_string()
347 },
348 ]];
349
350 rows.push(vec!["Package".to_string(), self.package_name.clone()]);
351 rows.push(vec!["Build Type".to_string(), self.build_type.clone()]);
352 rows.push(vec![
353 "Project Dir".to_string(),
354 self.project_dir.display().to_string(),
355 ]);
356
357 if let Some(wasm_path) = &self.wasm_path {
358 rows.push(vec![
359 "Component".to_string(),
360 wasm_path.display().to_string(),
361 ]);
362 }
363
364 rows.push(vec![
365 "Manifest Exists".to_string(),
366 self.manifest_exists.to_string(),
367 ]);
368
369 if let Some(manifest_path) = &self.manifest_path {
370 rows.push(vec![
371 "Manifest Path".to_string(),
372 manifest_path.display().to_string(),
373 ]);
374 }
375
376 output.table(&headers, &rows)?;
377 Ok(())
378 }
379
380 fn format_detailed(&self, _output: &OutputManager) -> CliResult<()> {
381 todo!()
382 }
383}
384
385#[derive(Debug, serde::Serialize)]
387pub struct ActorAction {
388 pub action: String,
389 pub actor_id: String,
390 pub success: bool,
391 pub message: Option<String>,
392}
393
394impl OutputFormat for ActorAction {
395 fn format_compact(&self, output: &OutputManager) -> CliResult<()> {
396 if self.success {
397 println!(
398 "{} {} actor: {}",
399 output.theme().success_icon(),
400 self.action
401 .chars()
402 .next()
403 .unwrap()
404 .to_uppercase()
405 .collect::<String>()
406 + &self.action[1..],
407 output.theme().accent().apply_to(&self.actor_id)
408 );
409 } else {
410 println!(
411 "{} Failed to {} actor: {}",
412 output.theme().error_icon(),
413 self.action,
414 output.theme().accent().apply_to(&self.actor_id)
415 );
416 if let Some(msg) = &self.message {
417 println!("{}", output.theme().muted().apply_to(msg));
418 }
419 }
420 Ok(())
421 }
422
423 fn format_pretty(&self, output: &OutputManager) -> CliResult<()> {
424 if self.success {
425 println!(
426 "{} {}",
427 output.theme().success_icon(),
428 output.theme().highlight().apply_to(&format!(
429 "Actor {} Successfully",
430 self.action
431 .chars()
432 .next()
433 .unwrap()
434 .to_uppercase()
435 .collect::<String>()
436 + &self.action[1..]
437 ))
438 );
439 println!(
440 "Actor ID: {}",
441 output.theme().accent().apply_to(&self.actor_id)
442 );
443 } else {
444 println!(
445 "{} {}",
446 output.theme().error_icon(),
447 output
448 .theme()
449 .error()
450 .apply_to(&format!("Failed to {} Actor", self.action))
451 );
452 println!(
453 "Actor ID: {}",
454 output.theme().accent().apply_to(&self.actor_id)
455 );
456 if let Some(msg) = &self.message {
457 println!("Error: {}", msg);
458 }
459 }
460 Ok(())
461 }
462
463 fn format_table(&self, output: &OutputManager) -> CliResult<()> {
464 let headers = vec!["Property", "Value"];
465 let mut rows = vec![
466 vec!["Action".to_string(), self.action.clone()],
467 vec!["Actor ID".to_string(), self.actor_id.clone()],
468 vec![
469 "Status".to_string(),
470 if self.success {
471 "Success".to_string()
472 } else {
473 "Failed".to_string()
474 },
475 ],
476 ];
477
478 if let Some(msg) = &self.message {
479 rows.push(vec!["Message".to_string(), msg.clone()]);
480 }
481
482 output.table(&headers, &rows)?;
483 Ok(())
484 }
485
486 fn format_detailed(&self, _output: &OutputManager) -> CliResult<()> {
487 todo!()
488 }
489}
490
491#[derive(Debug, serde::Serialize)]
493pub struct ComponentUpdate {
494 pub actor_id: String,
495 pub component: String,
496 pub success: bool,
497 pub message: Option<String>,
498}
499
500impl OutputFormat for ComponentUpdate {
501 fn format_compact(&self, output: &OutputManager) -> CliResult<()> {
502 if self.success {
503 println!(
504 "{} Updated actor: {} with component: {}",
505 output.theme().success_icon(),
506 output.theme().accent().apply_to(&self.actor_id),
507 output.theme().accent().apply_to(&self.component)
508 );
509 } else {
510 println!(
511 "{} Failed to update actor: {}",
512 output.theme().error_icon(),
513 output.theme().accent().apply_to(&self.actor_id)
514 );
515 if let Some(msg) = &self.message {
516 println!("{}", output.theme().muted().apply_to(msg));
517 }
518 }
519 Ok(())
520 }
521
522 fn format_pretty(&self, output: &OutputManager) -> CliResult<()> {
523 if self.success {
524 println!(
525 "{} {}",
526 output.theme().success_icon(),
527 output
528 .theme()
529 .highlight()
530 .apply_to("Component Updated Successfully")
531 );
532 println!(
533 "Actor ID: {}",
534 output.theme().accent().apply_to(&self.actor_id)
535 );
536 println!(
537 "New Component: {}",
538 output.theme().accent().apply_to(&self.component)
539 );
540 } else {
541 println!(
542 "{} {}",
543 output.theme().error_icon(),
544 output
545 .theme()
546 .error()
547 .apply_to("Failed to Update Component")
548 );
549 println!(
550 "Actor ID: {}",
551 output.theme().accent().apply_to(&self.actor_id)
552 );
553 println!(
554 "Component: {}",
555 output.theme().accent().apply_to(&self.component)
556 );
557 if let Some(msg) = &self.message {
558 println!("Error: {}", msg);
559 }
560 }
561 Ok(())
562 }
563
564 fn format_table(&self, output: &OutputManager) -> CliResult<()> {
565 let headers = vec!["Property", "Value"];
566 let mut rows = vec![
567 vec!["Actor ID".to_string(), self.actor_id.clone()],
568 vec!["Component".to_string(), self.component.clone()],
569 vec![
570 "Status".to_string(),
571 if self.success {
572 "Success".to_string()
573 } else {
574 "Failed".to_string()
575 },
576 ],
577 ];
578
579 if let Some(msg) = &self.message {
580 rows.push(vec!["Message".to_string(), msg.clone()]);
581 }
582
583 output.table(&headers, &rows)?;
584 Ok(())
585 }
586 fn format_detailed(&self, _output: &OutputManager) -> CliResult<()> {
587 todo!()
588 }
589}
590
591#[derive(Debug, serde::Serialize)]
593pub struct MessageSent {
594 pub actor_id: String,
595 pub message: String,
596 pub success: bool,
597}
598
599impl OutputFormat for MessageSent {
600 fn format_compact(&self, output: &OutputManager) -> CliResult<()> {
601 if self.success {
602 println!(
603 "{} Message sent to actor: {}",
604 output.theme().success_icon(),
605 output.theme().accent().apply_to(&self.actor_id)
606 );
607 } else {
608 println!(
609 "{} Failed to send message to actor: {}",
610 output.theme().error_icon(),
611 output.theme().accent().apply_to(&self.actor_id)
612 );
613 }
614 Ok(())
615 }
616
617 fn format_pretty(&self, output: &OutputManager) -> CliResult<()> {
618 if self.success {
619 println!(
620 "{} {}",
621 output.theme().success_icon(),
622 output
623 .theme()
624 .highlight()
625 .apply_to("Message Sent Successfully")
626 );
627 println!(
628 "Actor ID: {}",
629 output.theme().accent().apply_to(&self.actor_id)
630 );
631 println!(
632 "Message: {}",
633 output
634 .theme()
635 .muted()
636 .apply_to(&truncate_string(&self.message, 100))
637 );
638 } else {
639 println!(
640 "{} {}",
641 output.theme().error_icon(),
642 output.theme().error().apply_to("Failed to Send Message")
643 );
644 println!(
645 "Actor ID: {}",
646 output.theme().accent().apply_to(&self.actor_id)
647 );
648 }
649 Ok(())
650 }
651
652 fn format_table(&self, output: &OutputManager) -> CliResult<()> {
653 let headers = vec!["Property", "Value"];
654 let rows = vec![
655 vec!["Actor ID".to_string(), self.actor_id.clone()],
656 vec!["Message".to_string(), truncate_string(&self.message, 100)],
657 vec![
658 "Status".to_string(),
659 if self.success {
660 "Sent".to_string()
661 } else {
662 "Failed".to_string()
663 },
664 ],
665 ];
666
667 output.table(&headers, &rows)?;
668 Ok(())
669 }
670 fn format_detailed(&self, _output: &OutputManager) -> CliResult<()> {
671 todo!()
672 }
673}
674
675#[derive(Debug, serde::Serialize)]
677pub struct MessageResponse {
678 pub actor_id: String,
679 pub request: String,
680 pub response: String,
681}
682
683impl OutputFormat for MessageResponse {
684 fn format_compact(&self, output: &OutputManager) -> CliResult<()> {
685 println!(
686 "{} Response from actor: {}",
687 output.theme().success_icon(),
688 output.theme().accent().apply_to(&self.actor_id)
689 );
690 println!("{}", self.response);
691 Ok(())
692 }
693
694 fn format_pretty(&self, output: &OutputManager) -> CliResult<()> {
695 println!(
696 "{} {}",
697 output.theme().success_icon(),
698 output.theme().highlight().apply_to("Response Received")
699 );
700 println!(
701 "Actor ID: {}",
702 output.theme().accent().apply_to(&self.actor_id)
703 );
704 println!(
705 "Request: {}",
706 output
707 .theme()
708 .muted()
709 .apply_to(&truncate_string(&self.request, 100))
710 );
711 println!("Response:");
712 println!("{}", self.response);
713 Ok(())
714 }
715
716 fn format_table(&self, output: &OutputManager) -> CliResult<()> {
717 let headers = vec!["Property", "Value"];
718 let rows = vec![
719 vec!["Actor ID".to_string(), self.actor_id.clone()],
720 vec!["Request".to_string(), truncate_string(&self.request, 100)],
721 vec!["Response".to_string(), truncate_string(&self.response, 100)],
722 ];
723
724 output.table(&headers, &rows)?;
725 Ok(())
726 }
727 fn format_detailed(&self, _output: &OutputManager) -> CliResult<()> {
728 todo!()
729 }
730}
731
732#[derive(Debug, serde::Serialize)]
734pub struct StoredActorList {
735 pub actor_ids: Vec<String>,
736 pub chains_dir: String,
737 pub directory_exists: bool,
738}
739
740impl OutputFormat for StoredActorList {
741 fn format_compact(&self, output: &OutputManager) -> CliResult<()> {
742 if !self.directory_exists {
743 output.info(&format!(
744 "No stored actors found. Chains directory does not exist: {}",
745 self.chains_dir
746 ))?;
747 } else if self.actor_ids.is_empty() {
748 output.info("No stored actors found")?;
749 } else {
750 output.info(&format!("Stored actors: {}", self.actor_ids.len()))?;
751 for actor_id in &self.actor_ids {
752 println!(" {}", output.theme().accent().apply_to(actor_id));
753 }
754 }
755 Ok(())
756 }
757
758 fn format_pretty(&self, output: &OutputManager) -> CliResult<()> {
759 if !self.directory_exists {
760 println!(
761 "{} {}",
762 output.theme().info_icon(),
763 output
764 .theme()
765 .highlight()
766 .apply_to("No Stored Actors Found")
767 );
768 println!(
769 "Chains directory does not exist: {}",
770 output.theme().muted().apply_to(&self.chains_dir)
771 );
772 } else {
773 println!(
774 "{} {}",
775 output.theme().info_icon(),
776 output
777 .theme()
778 .highlight()
779 .apply_to(&format!("Stored Actors ({})", self.actor_ids.len()))
780 );
781 println!(
782 "Directory: {}",
783 output.theme().muted().apply_to(&self.chains_dir)
784 );
785 println!("{}", "─".repeat(40));
786
787 if self.actor_ids.is_empty() {
788 println!("No stored actors found.");
789 } else {
790 for (i, actor_id) in self.actor_ids.iter().enumerate() {
791 println!("{}. {}", i + 1, output.theme().accent().apply_to(actor_id));
792 }
793 }
794 }
795 Ok(())
796 }
797
798 fn format_table(&self, output: &OutputManager) -> CliResult<()> {
799 if !self.directory_exists || self.actor_ids.is_empty() {
800 self.format_pretty(output)?;
801 return Ok(());
802 }
803
804 let headers = vec!["#", "Actor ID"];
805 let rows: Vec<Vec<String>> = self
806 .actor_ids
807 .iter()
808 .enumerate()
809 .map(|(i, id)| vec![(i + 1).to_string(), id.clone()])
810 .collect();
811
812 output.table(&headers, &rows)?;
813 Ok(())
814 }
815 fn format_detailed(&self, _output: &OutputManager) -> CliResult<()> {
816 todo!()
817 }
818}
819
820#[derive(Debug, serde::Serialize)]
822pub struct ActorLogs {
823 pub actor_id: String,
824 pub events: Vec<ChainEvent>,
825 pub follow_mode: bool,
826 pub lines_limit: usize,
827}
828
829impl OutputFormat for ActorLogs {
830 fn format_compact(&self, output: &OutputManager) -> CliResult<()> {
831 println!(
832 "{} Logs for actor: {}",
833 output.theme().info_icon(),
834 output.theme().accent().apply_to(&self.actor_id)
835 );
836
837 if self.events.is_empty() {
838 println!(" No logs found.");
839 } else {
840 for event in &self.events {
841 if let Ok(json_data) = serde_json::from_slice::<serde_json::Value>(&event.data) {
842 if let Some(message) = json_data.get("message").and_then(|m| m.as_str()) {
843 println!("[{}] {}", format_timestamp(event.timestamp), message);
844 }
845 }
846 }
847 }
848 Ok(())
849 }
850
851 fn format_pretty(&self, output: &OutputManager) -> CliResult<()> {
852 println!(
853 "{} {}",
854 output.theme().info_icon(),
855 output
856 .theme()
857 .highlight()
858 .apply_to(&format!("Logs for Actor: {}", self.actor_id))
859 );
860
861 if self.lines_limit > 0 {
862 println!("Showing last {} lines", self.lines_limit);
863 }
864
865 println!("{}", "─".repeat(80));
866
867 if self.events.is_empty() {
868 println!("No logs found.");
869 } else {
870 for event in &self.events {
871 let timestamp = format_timestamp(event.timestamp);
872 if let Ok(json_data) = serde_json::from_slice::<serde_json::Value>(&event.data) {
873 if let Some(message) = json_data.get("message").and_then(|m| m.as_str()) {
874 println!(
875 "{} {}",
876 output.theme().muted().apply_to(×tamp),
877 message
878 );
879 }
880 }
881 }
882 }
883
884 if self.follow_mode {
885 println!();
886 output.info("Following logs in real-time. Press Ctrl+C to exit.")?;
887 }
888
889 Ok(())
890 }
891
892 fn format_table(&self, output: &OutputManager) -> CliResult<()> {
893 if self.events.is_empty() {
894 output.info(&format!("No logs found for actor: {}", self.actor_id))?;
895 return Ok(());
896 }
897
898 let headers = vec!["Timestamp", "Message"];
899 let rows: Vec<Vec<String>> = self
900 .events
901 .iter()
902 .filter_map(|event| {
903 serde_json::from_slice::<serde_json::Value>(&event.data)
904 .ok()
905 .and_then(|json_data| {
906 json_data
907 .get("message")
908 .and_then(|m| m.as_str())
909 .map(|message| {
910 vec![
911 format_timestamp(event.timestamp),
912 truncate_string(message, 80),
913 ]
914 })
915 })
916 })
917 .collect();
918
919 output.table(&headers, &rows)?;
920 Ok(())
921 }
922 fn format_detailed(&self, _output: &OutputManager) -> CliResult<()> {
923 todo!()
924 }
925}
926
927#[derive(Debug, serde::Serialize)]
929pub struct ServerInfo {
930 pub info: Value,
931}
932
933impl OutputFormat for ServerInfo {
934 fn format_compact(&self, output: &OutputManager) -> CliResult<()> {
935 if let Some(version) = self.info.get("version") {
936 println!(
937 "Theater Server {}",
938 output.theme().accent().apply_to(version)
939 );
940 }
941 if let Some(uptime) = self.info.get("uptime") {
942 println!("Uptime: {}", output.theme().muted().apply_to(uptime));
943 }
944 Ok(())
945 }
946
947 fn format_pretty(&self, output: &OutputManager) -> CliResult<()> {
948 println!(
949 "{}",
950 output.theme().highlight().apply_to("Server Information")
951 );
952 println!("{}", "─".repeat(40));
953 println!(
954 "{}",
955 serde_json::to_string_pretty(&self.info).unwrap_or_else(|_| "Invalid JSON".to_string())
956 );
957 Ok(())
958 }
959
960 fn format_table(&self, output: &OutputManager) -> CliResult<()> {
961 if let Value::Object(map) = &self.info {
963 let headers = vec!["Property", "Value"];
964 let rows: Vec<Vec<String>> = map
965 .iter()
966 .map(|(key, value)| {
967 vec![
968 key.clone(),
969 match value {
970 Value::String(s) => s.clone(),
971 _ => value.to_string(),
972 },
973 ]
974 })
975 .collect();
976 output.table(&headers, &rows)?;
977 } else {
978 self.format_pretty(output)?;
979 }
980 Ok(())
981 }
982 fn format_detailed(&self, _output: &OutputManager) -> CliResult<()> {
983 todo!()
984 }
985}
986
987#[derive(Debug, serde::Serialize)]
989pub struct ActorInspection {
990 pub id: theater::id::TheaterId,
991 pub status: String,
992 pub state: Option<serde_json::Value>,
993 pub events: Vec<theater::ChainEvent>,
994 pub metrics: Option<serde_json::Value>,
995 pub detailed: bool,
996}
997
998impl OutputFormat for ActorInspection {
999 fn format_compact(&self, output: &OutputManager) -> CliResult<()> {
1000 println!(
1001 "{} - {}",
1002 output.theme().accent().apply_to(&self.id.to_string()),
1003 output.theme().muted().apply_to(&self.status)
1004 );
1005 if let Some(ref state) = self.state {
1006 let state_str =
1007 serde_json::to_string(state).unwrap_or_else(|_| "Invalid JSON".to_string());
1008 if state_str.len() > 100 {
1009 println!("State: {} bytes", state_str.len());
1010 } else {
1011 println!("State: {}", truncate_string(&state_str, 100));
1012 }
1013 } else {
1014 println!("State: null");
1015 }
1016 println!("Events: {}", self.events.len());
1017 Ok(())
1018 }
1019
1020 fn format_pretty(&self, output: &OutputManager) -> CliResult<()> {
1021 use std::time::Duration;
1022
1023 println!(
1024 "{}",
1025 output.theme().highlight().apply_to("ACTOR INFORMATION")
1026 );
1027 println!("{}", "─".repeat(50));
1028 println!(
1029 "ID: {}",
1030 output.theme().accent().apply_to(&self.id.to_string())
1031 );
1032 println!("Status: {}", output.theme().muted().apply_to(&self.status));
1033
1034 if let Some(first_event) = self.events.first() {
1036 let now = chrono::Utc::now().timestamp() as u64;
1037 let uptime = Duration::from_secs(now.saturating_sub(first_event.timestamp));
1038 println!(
1039 "Uptime: {}",
1040 crate::utils::formatting::format_duration(uptime)
1041 );
1042 }
1043
1044 println!();
1045 println!("{}", output.theme().highlight().apply_to("STATE"));
1046 println!("{}", "─".repeat(50));
1047 match &self.state {
1048 Some(state_json) => {
1049 let state_str = serde_json::to_string_pretty(state_json)
1050 .unwrap_or_else(|_| "Invalid JSON".to_string());
1051 if state_str.len() < 1000 || self.detailed {
1052 println!("{}", state_str);
1053 } else {
1054 println!("{} bytes of JSON data", state_str.len());
1055 println!("(Use --detailed to see full state)");
1056 }
1057 }
1058 None => println!("State is null"),
1059 }
1060
1061 println!();
1062 println!("{}", output.theme().highlight().apply_to("EVENTS"));
1063 println!("{}", "─".repeat(50));
1064 println!("Total events: {}", self.events.len());
1065
1066 if !self.events.is_empty() {
1067 println!();
1068 println!("Latest events:");
1069 let start_idx = if self.events.len() > 5 && !self.detailed {
1070 self.events.len() - 5
1071 } else {
1072 0
1073 };
1074
1075 for (i, event) in self.events.iter().enumerate().skip(start_idx) {
1076 println!(
1077 "{}. {}",
1078 i + 1,
1079 crate::utils::formatting::format_event_summary(event)
1080 );
1081 }
1082
1083 if self.events.len() > 5 && !self.detailed {
1084 println!();
1085 println!("(Showing only the last 5 events. Use --detailed to see all.)");
1086 }
1087 }
1088
1089 if let Some(ref metrics) = self.metrics {
1091 println!();
1092 println!("{}", output.theme().highlight().apply_to("METRICS"));
1093 println!("{}", "─".repeat(50));
1094 println!(
1095 "{}",
1096 serde_json::to_string_pretty(metrics)
1097 .unwrap_or_else(|_| "Invalid JSON".to_string())
1098 );
1099 }
1100
1101 Ok(())
1102 }
1103
1104 fn format_table(&self, output: &OutputManager) -> CliResult<()> {
1105 let headers = vec!["Property", "Value"];
1106 let mut rows = vec![
1107 vec!["ID".to_string(), self.id.to_string()],
1108 vec!["Status".to_string(), self.status.clone()],
1109 ];
1110
1111 if let Some(ref state) = self.state {
1112 let state_str =
1113 serde_json::to_string(state).unwrap_or_else(|_| "Invalid JSON".to_string());
1114 rows.push(vec!["State".to_string(), truncate_string(&state_str, 100)]);
1115 } else {
1116 rows.push(vec!["State".to_string(), "null".to_string()]);
1117 }
1118
1119 rows.push(vec!["Events".to_string(), self.events.len().to_string()]);
1120
1121 if let Some(ref metrics) = self.metrics {
1122 let metrics_str =
1123 serde_json::to_string(metrics).unwrap_or_else(|_| "Invalid JSON".to_string());
1124 rows.push(vec![
1125 "Metrics".to_string(),
1126 truncate_string(&metrics_str, 100),
1127 ]);
1128 }
1129
1130 output.table(&headers, &rows)?;
1131 Ok(())
1132 }
1133 fn format_detailed(&self, _output: &OutputManager) -> CliResult<()> {
1134 todo!()
1135 }
1136}
1137
1138#[derive(Debug, serde::Serialize)]
1140pub struct ProjectCreated {
1141 pub name: String,
1142 pub template: String,
1143 pub path: std::path::PathBuf,
1144 pub build_instructions: Vec<String>,
1145}
1146
1147impl OutputFormat for ProjectCreated {
1148 fn format_compact(&self, output: &OutputManager) -> CliResult<()> {
1149 println!(
1150 "{} Created project: {}",
1151 output.theme().success().apply_to("✓"),
1152 output.theme().accent().apply_to(&self.name)
1153 );
1154 println!(
1155 "Path: {}",
1156 output.theme().muted().apply_to(self.path.display())
1157 );
1158 Ok(())
1159 }
1160
1161 fn format_pretty(&self, output: &OutputManager) -> CliResult<()> {
1162 println!(
1163 "{} {}",
1164 output.theme().success().apply_to("✓"),
1165 output
1166 .theme()
1167 .highlight()
1168 .apply_to(&format!("Created new actor project: {}", self.name))
1169 );
1170 println!();
1171 println!(
1172 "Template: {}",
1173 output.theme().accent().apply_to(&self.template)
1174 );
1175 println!(
1176 "Location: {}",
1177 output.theme().muted().apply_to(self.path.display())
1178 );
1179 println!();
1180 println!("{}", output.theme().highlight().apply_to("Next steps:"));
1181 for (i, instruction) in self.build_instructions.iter().enumerate() {
1182 println!(
1183 " {}. {}",
1184 i + 1,
1185 output.theme().muted().apply_to(instruction)
1186 );
1187 }
1188 Ok(())
1189 }
1190
1191 fn format_table(&self, output: &OutputManager) -> CliResult<()> {
1192 let headers = vec!["Property", "Value"];
1193 let rows = vec![
1194 vec!["Name".to_string(), self.name.clone()],
1195 vec!["Template".to_string(), self.template.clone()],
1196 vec!["Path".to_string(), self.path.display().to_string()],
1197 ];
1198 output.table(&headers, &rows)?;
1199
1200 println!();
1201 println!(
1202 "{}",
1203 output.theme().highlight().apply_to("Build Instructions:")
1204 );
1205 for (i, instruction) in self.build_instructions.iter().enumerate() {
1206 println!(" {}. {}", i + 1, instruction);
1207 }
1208 Ok(())
1209 }
1210 fn format_detailed(&self, _output: &OutputManager) -> CliResult<()> {
1211 todo!()
1212 }
1213}
1214
1215#[derive(Debug, serde::Serialize)]
1217pub struct ActorStarted {
1218 pub actor_id: String,
1219 pub manifest_path: String,
1220 pub address: String,
1221 pub subscribing: bool,
1222 pub acting_as_parent: bool,
1223}
1224
1225impl OutputFormat for ActorStarted {
1226 fn format_compact(&self, output: &OutputManager) -> CliResult<()> {
1227 println!(
1228 "{} Actor started: {}",
1229 output.theme().success_icon(),
1230 output.theme().accent().apply_to(&self.actor_id)
1231 );
1232 Ok(())
1233 }
1234
1235 fn format_pretty(&self, output: &OutputManager) -> CliResult<()> {
1236 println!("{}", "─".repeat(45));
1237 println!(
1238 "{} {}",
1239 output.theme().success_icon(),
1240 output.theme().highlight().apply_to("ACTOR STARTED")
1241 );
1242 println!("{}", "─".repeat(45));
1243 println!(
1244 "Actor ID: {}",
1245 output.theme().accent().apply_to(&self.actor_id)
1246 );
1247 println!(
1248 "Manifest: {}",
1249 output.theme().muted().apply_to(&self.manifest_path)
1250 );
1251 println!("Server: {}", output.theme().muted().apply_to(&self.address));
1252
1253 if self.subscribing {
1254 println!(
1255 "Status: {}",
1256 output.theme().info().apply_to("Subscribing to events")
1257 );
1258 }
1259 if self.acting_as_parent {
1260 println!(
1261 "Role: {}",
1262 output.theme().info().apply_to("Acting as parent")
1263 );
1264 }
1265 println!("{}", "─".repeat(45));
1266 Ok(())
1267 }
1268
1269 fn format_table(&self, output: &OutputManager) -> CliResult<()> {
1270 let headers = vec!["Property", "Value"];
1271 let rows = vec![
1272 vec!["Actor ID".to_string(), self.actor_id.clone()],
1273 vec!["Manifest".to_string(), self.manifest_path.clone()],
1274 vec!["Server".to_string(), self.address.clone()],
1275 vec!["Subscribing".to_string(), self.subscribing.to_string()],
1276 vec![
1277 "Acting as Parent".to_string(),
1278 self.acting_as_parent.to_string(),
1279 ],
1280 ];
1281 output.table(&headers, &rows)?;
1282 Ok(())
1283 }
1284 fn format_detailed(&self, _output: &OutputManager) -> CliResult<()> {
1285 todo!()
1286 }
1287}
1288
1289#[derive(Debug, serde::Serialize)]
1291pub struct EventSubscription {
1292 pub actor_id: theater::id::TheaterId,
1293 pub address: String,
1294 pub event_type_filter: Option<String>,
1295 pub limit: usize,
1296 pub timeout: u64,
1297 pub format: String,
1298 pub show_history: bool,
1299 pub history_limit: usize,
1300 pub detailed: bool,
1301 pub events_received: usize,
1302 pub subscription_id: Option<String>,
1303 pub is_active: bool,
1304}
1305
1306impl OutputFormat for EventSubscription {
1307 fn format_compact(&self, output: &OutputManager) -> CliResult<()> {
1308 if self.is_active {
1309 println!(
1310 "{} Subscribed to: {}",
1311 output.theme().success_icon(),
1312 output.theme().accent().apply_to(&self.actor_id.to_string())
1313 );
1314 if let Some(filter) = &self.event_type_filter {
1315 println!(" Filter: {}", output.theme().muted().apply_to(filter));
1316 }
1317 } else {
1318 println!(
1319 "{} Subscription ended: {} events received",
1320 output.theme().info_icon(),
1321 output.theme().accent().apply_to(&self.events_received)
1322 );
1323 }
1324 Ok(())
1325 }
1326
1327 fn format_pretty(&self, output: &OutputManager) -> CliResult<()> {
1328 if self.is_active {
1329 println!(
1330 "{} {}",
1331 output.theme().info_icon(),
1332 output.theme().highlight().apply_to(&format!(
1333 "Subscribing to events for actor: {}",
1334 self.actor_id
1335 ))
1336 );
1337
1338 if let Some(filter) = &self.event_type_filter {
1339 println!(
1340 "{} {}",
1341 output.theme().info_icon(),
1342 output
1343 .theme()
1344 .highlight()
1345 .apply_to(&format!("Filtering events by type: {}", filter))
1346 );
1347 }
1348
1349 println!();
1350 println!("Server: {}", output.theme().muted().apply_to(&self.address));
1351 println!("Format: {}", output.theme().muted().apply_to(&self.format));
1352
1353 if self.limit > 0 {
1354 println!(
1355 "Limit: {} events",
1356 output.theme().muted().apply_to(&self.limit)
1357 );
1358 }
1359
1360 if self.timeout > 0 {
1361 println!(
1362 "Timeout: {} seconds",
1363 output.theme().muted().apply_to(&self.timeout)
1364 );
1365 }
1366
1367 if self.show_history {
1368 let history_desc = if self.history_limit > 0 {
1369 format!("Last {} events", self.history_limit)
1370 } else {
1371 "All historical events".to_string()
1372 };
1373 println!(
1374 "History: {}",
1375 output.theme().muted().apply_to(&history_desc)
1376 );
1377 }
1378
1379 if let Some(subscription_id) = &self.subscription_id {
1380 println!(
1381 "Subscription ID: {}",
1382 output.theme().muted().apply_to(subscription_id)
1383 );
1384 }
1385
1386 println!();
1387 } else {
1388 println!("{} Subscription ended", output.theme().success_icon());
1389 println!(
1390 "Events received: {}",
1391 output.theme().accent().apply_to(&self.events_received)
1392 );
1393 }
1394 Ok(())
1395 }
1396
1397 fn format_table(&self, output: &OutputManager) -> CliResult<()> {
1398 let headers = vec!["Property", "Value"];
1399 let mut rows = vec![
1400 vec!["Actor ID".to_string(), self.actor_id.to_string()],
1401 vec!["Server".to_string(), self.address.clone()],
1402 vec!["Format".to_string(), self.format.clone()],
1403 vec![
1404 "Status".to_string(),
1405 if self.is_active {
1406 "Active".to_string()
1407 } else {
1408 "Ended".to_string()
1409 },
1410 ],
1411 vec![
1412 "Events Received".to_string(),
1413 self.events_received.to_string(),
1414 ],
1415 ];
1416
1417 if let Some(filter) = &self.event_type_filter {
1418 rows.push(vec!["Event Filter".to_string(), filter.clone()]);
1419 }
1420
1421 if self.limit > 0 {
1422 rows.push(vec!["Limit".to_string(), self.limit.to_string()]);
1423 }
1424
1425 if self.timeout > 0 {
1426 rows.push(vec![
1427 "Timeout".to_string(),
1428 format!("{} seconds", self.timeout),
1429 ]);
1430 }
1431
1432 if let Some(subscription_id) = &self.subscription_id {
1433 rows.push(vec!["Subscription ID".to_string(), subscription_id.clone()]);
1434 }
1435
1436 output.table(&headers, &rows)?;
1437 Ok(())
1438 }
1439 fn format_detailed(&self, _output: &OutputManager) -> CliResult<()> {
1440 todo!()
1441 }
1442}
1443
1444#[derive(Debug, serde::Serialize)]
1446pub struct ServerStarted {
1447 pub address: std::net::SocketAddr,
1448 pub log_level: String,
1449 pub log_filter: Option<String>,
1450 pub log_dir: String,
1451 pub log_path: std::path::PathBuf,
1452 pub log_stdout: bool,
1453}
1454
1455impl OutputFormat for ServerStarted {
1456 fn format_compact(&self, output: &OutputManager) -> CliResult<()> {
1457 println!(
1458 "{} Theater server starting on {}",
1459 output.theme().success_icon(),
1460 output.theme().accent().apply_to(&self.address)
1461 );
1462 println!(
1463 " Logs: {}",
1464 output.theme().muted().apply_to(self.log_path.display())
1465 );
1466 Ok(())
1467 }
1468
1469 fn format_pretty(&self, output: &OutputManager) -> CliResult<()> {
1470 println!("{}", output.theme().highlight().apply_to("─".repeat(50)));
1471 println!(
1472 "{} {}",
1473 output.theme().success_icon(),
1474 output
1475 .theme()
1476 .highlight()
1477 .apply_to("THEATER SERVER STARTING")
1478 );
1479 println!("{}", output.theme().highlight().apply_to("─".repeat(50)));
1480 println!();
1481 println!(
1482 "Address: {}",
1483 output.theme().accent().apply_to(&self.address)
1484 );
1485 println!(
1486 "Log Level: {}",
1487 output.theme().muted().apply_to(&self.log_level)
1488 );
1489
1490 if let Some(filter) = &self.log_filter {
1491 println!("Log Filter: {}", output.theme().muted().apply_to(filter));
1492 }
1493
1494 println!(
1495 "Log Directory: {}",
1496 output.theme().muted().apply_to(&self.log_dir)
1497 );
1498 println!(
1499 "Log File: {}",
1500 output.theme().muted().apply_to(self.log_path.display())
1501 );
1502
1503 if self.log_stdout {
1504 println!(
1505 "Console Logging: {}",
1506 output.theme().success().apply_to("Enabled")
1507 );
1508 }
1509
1510 println!();
1511 println!("{}", output.theme().highlight().apply_to("─".repeat(50)));
1512 println!();
1513 println!(
1514 "{} Server is running. Press Ctrl+C to stop.",
1515 output.theme().info_icon()
1516 );
1517 println!();
1518 Ok(())
1519 }
1520
1521 fn format_table(&self, output: &OutputManager) -> CliResult<()> {
1522 let headers = vec!["Property", "Value"];
1523 let mut rows = vec![
1524 vec!["Address".to_string(), self.address.to_string()],
1525 vec!["Log Level".to_string(), self.log_level.clone()],
1526 vec!["Log Directory".to_string(), self.log_dir.clone()],
1527 vec!["Log File".to_string(), self.log_path.display().to_string()],
1528 vec!["Console Logging".to_string(), self.log_stdout.to_string()],
1529 ];
1530
1531 if let Some(filter) = &self.log_filter {
1532 rows.push(vec!["Custom Filter".to_string(), filter.clone()]);
1533 }
1534
1535 output.table(&headers, &rows)?;
1536 Ok(())
1537 }
1538 fn format_detailed(&self, _output: &OutputManager) -> CliResult<()> {
1539 todo!()
1540 }
1541}
1542
1543#[derive(Debug, serde::Serialize)]
1545pub struct ChannelOpened {
1546 pub actor_id: theater::id::TheaterId,
1547 pub channel_id: String,
1548 pub address: String,
1549 pub initial_message_size: usize,
1550 pub is_interactive: bool,
1551}
1552
1553impl OutputFormat for ChannelOpened {
1554 fn format_compact(&self, output: &OutputManager) -> CliResult<()> {
1555 println!(
1556 "{} Channel opened to: {}",
1557 output.theme().success_icon(),
1558 output.theme().accent().apply_to(&self.actor_id.to_string())
1559 );
1560 println!(
1561 " Channel ID: {}",
1562 output.theme().muted().apply_to(&self.channel_id)
1563 );
1564 Ok(())
1565 }
1566
1567 fn format_pretty(&self, output: &OutputManager) -> CliResult<()> {
1568 println!(
1569 "{} {}",
1570 output.theme().success_icon(),
1571 output
1572 .theme()
1573 .highlight()
1574 .apply_to(&format!("Opening channel to actor: {}", self.actor_id))
1575 );
1576 println!();
1577 println!(
1578 "Channel ID: {}",
1579 output.theme().accent().apply_to(&self.channel_id)
1580 );
1581 println!("Server: {}", output.theme().muted().apply_to(&self.address));
1582 println!(
1583 "Initial Message: {} bytes",
1584 output.theme().muted().apply_to(&self.initial_message_size)
1585 );
1586
1587 if self.is_interactive {
1588 println!();
1589 println!(
1590 "{} {}",
1591 output.theme().success_icon(),
1592 output
1593 .theme()
1594 .highlight()
1595 .apply_to("Channel opened successfully")
1596 );
1597 }
1598 Ok(())
1599 }
1600
1601 fn format_table(&self, output: &OutputManager) -> CliResult<()> {
1602 let headers = vec!["Property", "Value"];
1603 let rows = vec![
1604 vec!["Actor ID".to_string(), self.actor_id.to_string()],
1605 vec!["Channel ID".to_string(), self.channel_id.clone()],
1606 vec!["Server".to_string(), self.address.clone()],
1607 vec![
1608 "Initial Message Size".to_string(),
1609 format!("{} bytes", self.initial_message_size),
1610 ],
1611 vec![
1612 "Interactive Mode".to_string(),
1613 self.is_interactive.to_string(),
1614 ],
1615 ];
1616 output.table(&headers, &rows)?;
1617 Ok(())
1618 }
1619 fn format_detailed(&self, _output: &OutputManager) -> CliResult<()> {
1620 todo!()
1621 }
1622}
1623
1624fn format_timestamp(timestamp: u64) -> String {
1627 match DateTime::from_timestamp(timestamp as i64, 0) {
1628 Some(dt) => dt.format("%Y-%m-%d %H:%M:%S UTC").to_string(),
1629 None => timestamp.to_string(),
1630 }
1631}
1632
1633fn truncate_string(s: &str, max_len: usize) -> String {
1634 if s.len() <= max_len {
1635 s.to_string()
1636 } else {
1637 format!("{}…", &s[..max_len.saturating_sub(1)])
1638 }
1639}
1640
1641#[cfg(test)]
1642mod tests {
1643 use super::*;
1644
1645 #[test]
1646 fn test_truncate_string() {
1647 assert_eq!(truncate_string("hello", 10), "hello");
1648 assert_eq!(truncate_string("hello world", 5), "hell…");
1649 }
1650}