fallow_types/cloud.rs
1//! Cross-surface strings for the cloud runtime-coverage path.
2//!
3//! The cloud commands of `fallow coverage` and the matching MCP cloud tools
4//! refuse the same call for the same reason, and an agent reads both.
5//! Keeping the refusal text here makes the two surfaces say one sentence
6//! instead of holding two copies that drift apart.
7
8/// A cloud command that needs a Fallow Cloud API key. The refusal names the
9/// command that ran, so the example line can be copied as it is.
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
11pub enum CloudCommand {
12 /// `fallow coverage analyze --cloud`, and the MCP `get_cloud_runtime_context` tool.
13 Analyze,
14 /// `fallow coverage review-packet`, and the MCP `get_cloud_review_packet` tool.
15 ReviewPacket,
16 /// `fallow coverage deployment-changes`, and the MCP `get_cloud_deployment_changes` tool.
17 DeploymentChanges,
18}
19
20impl CloudCommand {
21 /// A minimal command line for this command, without the API key.
22 #[must_use]
23 pub const fn example(self) -> &'static str {
24 match self {
25 Self::Analyze => "fallow coverage analyze --cloud --repo owner/repo",
26 Self::ReviewPacket => "fallow coverage review-packet --repo owner/repo",
27 Self::DeploymentChanges => "fallow coverage deployment-changes --repo owner/repo",
28 }
29 }
30}
31
32/// Refusal emitted when no Fallow Cloud API key is available. Names the
33/// environment variable first because that is the only route the MCP tools
34/// have; the flag and the command line of `command` follow for the CLI surface.
35#[must_use]
36pub fn cloud_api_key_missing_message(command: CloudCommand) -> String {
37 format!(
38 "Cloud runtime coverage requires an API key.\n\nSet FALLOW_API_KEY or pass --api-key:\n\n FALLOW_API_KEY=fallow_live_... {}",
39 command.example()
40 )
41}
42
43#[cfg(test)]
44mod tests {
45 use super::*;
46
47 #[test]
48 fn each_command_names_itself_in_the_example() {
49 for (command, subcommand) in [
50 (CloudCommand::Analyze, "coverage analyze --cloud"),
51 (CloudCommand::ReviewPacket, "coverage review-packet"),
52 (
53 CloudCommand::DeploymentChanges,
54 "coverage deployment-changes",
55 ),
56 ] {
57 let message = cloud_api_key_missing_message(command);
58 assert!(message.starts_with("Cloud runtime coverage requires an API key."));
59 assert!(
60 message.ends_with(&format!(
61 "fallow_live_... fallow {subcommand} --repo owner/repo"
62 )),
63 "{command:?}: {message}"
64 );
65 }
66 }
67}