Coverage Report

Created: 2026-04-05 07:19

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
crates/flotilla-commands/src/commands/agent.rs
Line
Count
Source
1
use std::path::PathBuf;
2
3
use clap::{Parser, Subcommand};
4
use flotilla_protocol::{Command, CommandAction};
5
6
use crate::{
7
    resolved::{HostResolution, RepoContext},
8
    Resolved,
9
};
10
11
#[derive(Debug, Clone, PartialEq, Eq, Parser)]
12
#[command(about = "Cloud agents")]
13
pub struct AgentNoun {
14
    /// Agent/session ID
15
    pub subject: String,
16
17
    #[command(subcommand)]
18
    pub verb: AgentVerb,
19
}
20
21
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
22
pub enum AgentVerb {
23
    /// Connect to a remote agent session
24
    Teleport {
25
        #[arg(long)]
26
        branch: Option<String>,
27
        #[arg(long)]
28
        checkout: Option<PathBuf>,
29
    },
30
    /// Archive an agent session
31
    Archive,
32
}
33
34
impl AgentNoun {
35
4
    pub fn resolve(self) -> Result<Resolved, String> {
36
4
        match self.verb {
37
3
            AgentVerb::Teleport { branch, checkout } => Ok(Resolved::NeedsContext {
38
3
                command: Command {
39
3
                    host: None,
40
3
                    provisioning_target: None,
41
3
                    context_repo: None,
42
3
                    action: CommandAction::TeleportSession { session_id: self.subject, branch, checkout_key: checkout },
43
3
                },
44
3
                repo: RepoContext::Inferred,
45
3
                host: HostResolution::Local,
46
3
            }),
47
1
            AgentVerb::Archive => Ok(Resolved::NeedsContext {
48
1
                command: Command {
49
1
                    host: None,
50
1
                    provisioning_target: None,
51
1
                    context_repo: None,
52
1
                    action: CommandAction::ArchiveSession { session_id: self.subject },
53
1
                },
54
1
                repo: RepoContext::Inferred,
55
1
                host: HostResolution::ProviderHost,
56
1
            }),
57
        }
58
4
    }
59
}
60
61
impl std::fmt::Display for AgentNoun {
62
6
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63
6
        write!(f, "agent {}", self.subject)
?0
;
64
6
        match &self.verb {
65
4
            AgentVerb::Teleport { branch, checkout } => {
66
4
                write!(f, " teleport")
?0
;
67
4
                if let Some(
b3
) = branch {
68
3
                    write!(f, " --branch {b}")
?0
;
69
1
                }
70
4
                if let Some(
c1
) = checkout {
71
1
                    write!(f, " --checkout {}", c.display())
?0
;
72
3
                }
73
            }
74
2
            AgentVerb::Archive => write!(f, " archive")
?0
,
75
        }
76
6
        Ok(())
77
6
    }
78
}
79
80
#[cfg(test)]
81
mod tests {
82
    use std::path::PathBuf;
83
84
    use clap::Parser;
85
    use flotilla_protocol::{Command, CommandAction};
86
87
    use super::AgentNoun;
88
    use crate::{
89
        resolved::{HostResolution, RepoContext},
90
        test_utils::assert_round_trip,
91
        Resolved,
92
    };
93
94
4
    fn parse(args: &[&str]) -> AgentNoun {
95
4
        AgentNoun::try_parse_from(args).expect("should parse")
96
4
    }
97
98
    #[test]
99
1
    fn agent_teleport_no_flags() {
100
1
        let resolved = parse(&["agent", "claude-1", "teleport"]).resolve().unwrap();
101
1
        assert_eq!(resolved, Resolved::NeedsContext {
102
1
            command: Command {
103
1
                host: None,
104
1
                provisioning_target: None,
105
1
                context_repo: None,
106
1
                action: CommandAction::TeleportSession { session_id: "claude-1".into(), branch: None, checkout_key: None },
107
1
            },
108
1
            repo: RepoContext::Inferred,
109
1
            host: HostResolution::Local,
110
1
        });
111
1
    }
112
113
    #[test]
114
1
    fn agent_teleport_with_branch() {
115
1
        let resolved = parse(&["agent", "claude-1", "teleport", "--branch", "feat"]).resolve().unwrap();
116
1
        assert_eq!(resolved, Resolved::NeedsContext {
117
1
            command: Command {
118
1
                host: None,
119
1
                provisioning_target: None,
120
1
                context_repo: None,
121
1
                action: CommandAction::TeleportSession { session_id: "claude-1".into(), branch: Some("feat".into()), checkout_key: None },
122
1
            },
123
1
            repo: RepoContext::Inferred,
124
1
            host: HostResolution::Local,
125
1
        });
126
1
    }
127
128
    #[test]
129
1
    fn agent_teleport_with_branch_and_checkout() {
130
1
        let resolved = parse(&["agent", "claude-1", "teleport", "--branch", "feat", "--checkout", "/tmp/wt"]).resolve().unwrap();
131
1
        assert_eq!(resolved, Resolved::NeedsContext {
132
1
            command: Command {
133
1
                host: None,
134
1
                provisioning_target: None,
135
1
                context_repo: None,
136
1
                action: CommandAction::TeleportSession {
137
1
                    session_id: "claude-1".into(),
138
1
                    branch: Some("feat".into()),
139
1
                    checkout_key: Some(PathBuf::from("/tmp/wt")),
140
1
                },
141
1
            },
142
1
            repo: RepoContext::Inferred,
143
1
            host: HostResolution::Local,
144
1
        });
145
1
    }
146
147
    #[test]
148
1
    fn agent_archive() {
149
1
        let resolved = parse(&["agent", "claude-1", "archive"]).resolve().unwrap();
150
1
        assert_eq!(resolved, Resolved::NeedsContext {
151
1
            command: Command {
152
1
                host: None,
153
1
                provisioning_target: None,
154
1
                context_repo: None,
155
1
                action: CommandAction::ArchiveSession { session_id: "claude-1".into() }
156
1
            },
157
1
            repo: RepoContext::Inferred,
158
1
            host: HostResolution::ProviderHost,
159
1
        });
160
1
    }
161
162
    #[test]
163
1
    fn round_trip_teleport() {
164
1
        assert_round_trip::<AgentNoun>(&["agent", "claude-1", "teleport"]);
165
1
    }
166
167
    #[test]
168
1
    fn round_trip_teleport_with_branch() {
169
1
        assert_round_trip::<AgentNoun>(&["agent", "claude-1", "teleport", "--branch", "feat"]);
170
1
    }
171
172
    #[test]
173
1
    fn round_trip_archive() {
174
1
        assert_round_trip::<AgentNoun>(&["agent", "claude-1", "archive"]);
175
1
    }
176
}