nika_cli/machine/
status.rs1use std::path::PathBuf;
4use std::time::{SystemTime, UNIX_EPOCH};
5
6#[derive(Debug)]
8pub struct SetupResult {
9 pub name: String,
10 pub success: bool,
11 #[allow(dead_code)]
12 pub message: String,
13}
14
15#[derive(Debug, PartialEq)]
17pub enum MachineStatus {
18 NeverSetup,
20 NeedsUpdate,
22 Ready,
24}
25
26pub fn machine_setup_status() -> MachineStatus {
28 let marker = machine_toml_path();
29 let content = match std::fs::read_to_string(&marker) {
30 Ok(c) => c,
31 Err(_) => return MachineStatus::NeverSetup,
32 };
33 for line in content.lines() {
34 let trimmed = line.trim();
35 if let Some(rest) = trimmed.strip_prefix("version") {
36 let rest = rest.trim().strip_prefix('=').unwrap_or("").trim();
37 let version = rest.trim_matches('"');
38 if version == env!("CARGO_PKG_VERSION") {
39 return MachineStatus::Ready;
40 } else {
41 return MachineStatus::NeedsUpdate;
42 }
43 }
44 }
45 MachineStatus::NeedsUpdate
46}
47
48pub fn is_machine_setup() -> bool {
50 machine_setup_status() == MachineStatus::Ready
51}
52
53pub fn is_ci() -> bool {
55 use std::env;
56 if env::var("NIKA_NO_SETUP").map(|v| v == "1").unwrap_or(false) {
57 return true;
58 }
59 if env::var("CI").is_ok() {
60 return true;
61 }
62 let ci_vars = [
63 "GITHUB_ACTIONS",
64 "GITLAB_CI",
65 "CIRCLECI",
66 "JENKINS_URL",
67 "BUILDKITE",
68 "TRAVIS",
69 "CODEBUILD_BUILD_ID",
70 "TF_BUILD",
71 ];
72 if ci_vars.iter().any(|v| env::var(v).is_ok()) {
73 return true;
74 }
75 if dirs::home_dir().is_none() {
76 return true;
77 }
78 #[cfg(not(target_os = "windows"))]
79 {
80 let dumb = env::var("TERM").map(|t| t == "dumb").unwrap_or(false);
81 let no_display = env::var("DISPLAY").is_err() && env::var("WAYLAND_DISPLAY").is_err();
82 if dumb && no_display {
83 return true;
84 }
85 }
86 false
87}
88
89pub(crate) fn machine_toml_path() -> PathBuf {
91 dirs::home_dir()
92 .unwrap_or_else(|| PathBuf::from("/tmp"))
93 .join(".nika")
94 .join("machine.toml")
95}
96
97pub(super) fn write_marker(results: &[SetupResult]) {
99 let editors = super::install::detect_editors();
100 write_marker_with_editors(results, &editors);
101}
102
103fn write_marker_with_editors(results: &[SetupResult], editors: &[&str]) {
104 let marker_path = machine_toml_path();
105 let Some(dir) = marker_path.parent() else {
106 return;
107 };
108 std::fs::create_dir_all(dir).ok();
109
110 let ai_tools: Vec<&str> = results
111 .iter()
112 .filter(|r| {
113 r.success
114 && [
115 "Claude Code",
116 "Cursor",
117 "Copilot",
118 "Windsurf",
119 "Roo Code",
120 "Agent Skills",
121 ]
122 .contains(&r.name.as_str())
123 })
124 .map(|r| r.name.as_str())
125 .collect();
126
127 let secs = SystemTime::now()
128 .duration_since(UNIX_EPOCH)
129 .unwrap_or_default()
130 .as_secs();
131
132 let editors_toml: Vec<String> = editors.iter().map(|e| format!("\"{}\"", e)).collect();
134
135 let content = format!(
136 "[machine]\nsetup_at = \"{}\"\nversion = \"{}\"\neditors = [{}]\nai_tools = {:?}\n",
137 secs,
138 env!("CARGO_PKG_VERSION"),
139 editors_toml.join(", "),
140 ai_tools,
141 );
142
143 std::fs::write(&marker_path, content).ok();
145}