Skip to main content

tftio_cli_common/
lib.rs

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