pub mod action;
pub mod audit;
pub mod audit_replay;
pub mod completions;
pub mod ctl;
pub mod drift;
pub mod envs;
pub mod explain;
pub mod lint;
pub mod mcp;
pub mod versions;
pub(crate) use crate::deploy_poll::{decide_poll, PollDecision};
pub(crate) use crate::util::{json_escape as cli_esc, json_string};
pub(crate) fn refuse_if_frozen(prog: &str) {
if let Some(m) = crate::freeze::read_active() {
let reason = if m.reason.is_empty() {
"no reason given"
} else {
m.reason.as_str()
};
eprintln!(
"{prog}: refusing — fleet freeze active ({reason}) — lift with `{}` in the owning TUI (pid {})",
m.remedy(),
m.pid
);
std::process::exit(3);
}
}
pub(crate) fn take_value<'a, I: Iterator<Item = &'a String>>(
iter: &mut I,
prog: &str,
flag: &str,
what: &str,
) -> Result<String, String> {
let Some(v) = iter.next() else {
return Err(format!("{prog}: {flag} expects {what}"));
};
if v.starts_with("--") {
return Err(format!("{prog}: {flag} expects {what}, got flag '{v}'"));
}
Ok(v.clone())
}
pub(crate) async fn exit_after_drain(code: i32) -> ! {
crate::audit::drain_webhooks(std::time::Duration::from_secs(12)).await;
std::process::exit(code);
}
pub(crate) async fn drain_before_return() {
crate::audit::drain_webhooks(std::time::Duration::from_secs(12)).await;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cli_esc_escapes_quotes_and_backslashes() {
assert_eq!(cli_esc("hello"), "hello");
assert_eq!(cli_esc("a\"b"), "a\\\"b");
assert_eq!(cli_esc("a\\b"), "a\\\\b");
assert_eq!(cli_esc("a\nb"), "a\\nb");
assert_eq!(cli_esc("a\tb"), "a\\tb");
}
#[test]
fn json_string_wraps_in_quotes_and_escapes() {
assert_eq!(json_string(""), "\"\"");
assert_eq!(json_string("hello"), "\"hello\"");
assert_eq!(json_string("a\"b"), "\"a\\\"b\"");
let s = "line1\nline2 \"with quotes\"";
let escaped = json_string(s);
let parsed: String =
serde_yml::from_str(&escaped).expect("hand-rolled JSON should parse as YAML");
assert_eq!(parsed, s);
}
}