Skip to main content

tftio_cli_common/
lib.rs

1//! Common functionality for Workhelix Rust CLI tools.
2//!
3//! This library provides shared functionality for CLI tools including:
4//! - Shell completion generation
5//! - Health check framework
6//! - License display
7//! - Terminal output utilities
8//!
9//! # Example Usage
10//!
11//! ```no_run
12//! use tftio_cli_common::{
13//!     RepoInfo, DoctorChecks, DoctorCheck,
14//!     completions, doctor, license,
15//! };
16//! use clap::Parser;
17//!
18//! #[derive(Parser)]
19//! struct Cli {
20//!     // your CLI definition
21//! }
22//!
23//! struct MyTool;
24//!
25//! impl DoctorChecks for MyTool {
26//!     fn repo_info() -> RepoInfo {
27//!         RepoInfo::new("myorg", "mytool")
28//!     }
29//!
30//!     fn current_version() -> &'static str {
31//!         env!("CARGO_PKG_VERSION")
32//!     }
33//!
34//!     fn tool_checks(&self) -> Vec<DoctorCheck> {
35//!         vec![
36//!             DoctorCheck::file_exists("~/.config/mytool/config.toml"),
37//!         ]
38//!     }
39//! }
40//!
41//! // Generate completions
42//! completions::generate_completions::<Cli>(clap_complete::Shell::Bash);
43//!
44//! // Run health check
45//! let tool = MyTool;
46//! let exit_code = doctor::run_doctor(&tool);
47//! ```
48
49// Re-export main types and traits
50pub use doctor::DoctorChecks;
51pub use license::LicenseType;
52pub use types::{DoctorCheck, RepoInfo};
53
54// Public modules
55pub mod agent;
56pub mod app;
57pub mod command;
58pub mod completions;
59pub mod doctor;
60pub mod error;
61pub mod json;
62pub mod license;
63pub mod output;
64pub mod progress;
65pub mod runner;
66pub mod types;
67pub mod update;
68
69// Re-export commonly used items
70pub use agent::{
71    AGENT_TOKEN_ENV, AGENT_TOKEN_EXPECTED_ENV, AgentCapability, AgentDispatch, AgentModeContext,
72    AgentSkillError, AgentSurfaceSpec, CommandSelector, FlagSelector, apply_agent_surface,
73    parse_with_agent_surface, parse_with_agent_surface_from, render_agent_help, render_agent_skill,
74};
75pub use app::{ToolSpec, workspace_tool};
76pub use command::{
77    NoDoctor, StandardCommand, StandardCommandMap, map_standard_command,
78    maybe_run_standard_command, maybe_run_standard_command_no_doctor,
79    parse_command_ref_with_agent_surface_from, parse_command_with_agent_surface,
80    parse_command_with_agent_surface_from, run_standard_command_no_doctor,
81};
82pub use completions::{
83    CompletionOutput, generate_completions, generate_completions_from_command, render_completion,
84    render_completion_from_command, render_completion_instructions, write_completion,
85};
86pub use doctor::{DoctorReport, print_doctor_report_json, print_doctor_report_text, run_doctor};
87pub use error::{fatal_error, print_error};
88pub use json::{
89    err_response, ok_response, render_response, render_response_parts, render_response_with,
90};
91pub use license::display_license;
92pub use progress::make_spinner;
93pub use runner::{
94    FatalCliError, parse_and_exit, parse_and_run, run_with_display_error_handler,
95    run_with_fatal_handler,
96};
97
98#[cfg(test)]
99/// Shared test utilities for workspace integration tests.
100pub mod test_support {
101    use std::sync::{Mutex, MutexGuard, OnceLock};
102
103    static ENV_LOCK: OnceLock<Mutex<()>> = OnceLock::new();
104
105    /// Acquire a process-wide lock for tests that mutate environment variables.
106    pub fn env_lock() -> MutexGuard<'static, ()> {
107        ENV_LOCK
108            .get_or_init(|| Mutex::new(()))
109            .lock()
110            .unwrap_or_else(std::sync::PoisonError::into_inner)
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117
118    #[test]
119    fn test_repo_info_creation() {
120        let repo = RepoInfo::new("workhelix", "test");
121        assert_eq!(repo.owner, "workhelix");
122        assert_eq!(repo.name, "test");
123    }
124
125    #[test]
126    fn test_doctor_check_creation() {
127        let check = DoctorCheck::pass("test");
128        assert!(check.passed);
129
130        let check = DoctorCheck::fail("test", "failed");
131        assert!(!check.passed);
132    }
133
134    #[test]
135    fn test_license_type() {
136        assert_eq!(LicenseType::MIT.name(), "MIT");
137        assert_eq!(LicenseType::Apache2.name(), "Apache-2.0");
138        assert_eq!(LicenseType::CC0.name(), "CC0-1.0");
139    }
140}