1pub mod attention;
9pub mod classify;
10pub mod collector;
11pub mod panes;
12pub mod proc;
13pub mod session_meta;
14pub mod state;
15pub mod ui;
16
17use std::path::PathBuf;
18use std::time::SystemTime;
19
20use serde::Serialize;
21
22#[derive(Debug, Clone, Serialize)]
23pub struct Agent {
24 pub id: String,
27 pub pane: Option<PaneLocator>,
28 pub kind: AgentKind,
29 pub status: Status,
30 pub cwd: PathBuf,
31 pub repo_name: String,
32 pub flags: Flags,
33 pub extra: String,
34}
35
36#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
37pub struct PaneLocator {
38 pub session: String,
39 pub window: u32,
40 pub pane: u32,
41 pub pane_pid: u32,
42}
43
44impl PaneLocator {
45 pub fn target(&self) -> String {
46 format!("{}:{}.{}", self.session, self.window, self.pane)
47 }
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
51#[serde(rename_all = "kebab-case")]
52pub enum AgentKind {
53 Claude,
54 Codex,
55 Shell,
56 Other,
57}
58
59impl AgentKind {
60 pub fn from_command(cmd: &str) -> Self {
61 match cmd {
62 "claude" => Self::Claude,
63 "codex" => Self::Codex,
64 "zsh" | "bash" | "fish" | "sh" => Self::Shell,
65 _ => Self::Other,
66 }
67 }
68
69 pub fn label(self) -> &'static str {
70 match self {
71 Self::Claude => "claude",
72 Self::Codex => "codex",
73 Self::Shell => "shell",
74 Self::Other => "other",
75 }
76 }
77}
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
80#[serde(rename_all = "kebab-case")]
81pub enum Status {
82 Working,
85 Ready,
88 AwaitingDecision,
91 Idle,
93 Background,
95}
96
97impl Status {
98 pub fn glyph(self) -> &'static str {
99 match self {
100 Self::Working => "●",
101 Self::Ready => "◐",
102 Self::AwaitingDecision => "⚠",
103 Self::Idle => "○",
104 Self::Background => "▷",
105 }
106 }
107
108 pub fn label(self) -> &'static str {
109 match self {
110 Self::Working => "working",
111 Self::Ready => "ready",
112 Self::AwaitingDecision => "decision",
113 Self::Idle => "idle",
114 Self::Background => "bg",
115 }
116 }
117}
118
119#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
120pub struct Flags {
121 pub has_intent: bool,
122 pub blocked: bool,
123 pub reviewed_fresh: bool,
124}
125
126#[derive(Debug, Clone, Serialize)]
127pub struct Snapshot {
128 pub agents: Vec<Agent>,
129 #[serde(rename = "captured_at_ms", serialize_with = "serialize_epoch_ms")]
132 pub captured_at: SystemTime,
133 pub global_blocked: usize,
135 pub panes_error: Option<String>,
139 pub attention: Vec<attention::AttentionEntry>,
143}
144
145fn serialize_epoch_ms<S: serde::Serializer>(t: &SystemTime, s: S) -> Result<S::Ok, S::Error> {
146 let ms = t
147 .duration_since(std::time::UNIX_EPOCH)
148 .map(|d| d.as_millis() as i64)
149 .unwrap_or(0);
150 s.serialize_i64(ms)
151}
152
153impl Snapshot {
154 pub fn empty() -> Self {
155 Self {
156 agents: Vec::new(),
157 captured_at: SystemTime::now(),
158 global_blocked: 0,
159 panes_error: None,
160 attention: Vec::new(),
161 }
162 }
163}
164
165pub fn run() -> anyhow::Result<()> {
166 ui::run()
167}