Skip to main content

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