feat(daemon,tui): visible secured state — status response + TUI notice
Some checks are pending
CI / rust (pull_request) Waiting to run
CI / markdown (pull_request) Waiting to run
CI / port (pull_request) Waiting to run
CI / agent-jail-pkgs (pull_request) Waiting to run

Issue #183 Part B: show node secured/unsecured state so operators can
tell the difference between a broken node and one waiting for first-boot
password setup.

  daemon:  add "secured": bool to status response
           (true iff ${data_dir}/.secured exists)
  TUI:     fetch secured from daemon status each refresh cycle
           render [UNSECURED — set root password to activate agent]
           in red bold when node is unsecured

Part A (rc.d gate gating autospawn on .secured) was already complete.
This commit is contained in:
Sam & Claude 2026-06-26 15:33:18 +02:00
parent 32f47a75f5
commit e29b9c10e1
2 changed files with 18 additions and 0 deletions

View file

@ -346,10 +346,14 @@ async fn cmd_status(state: &SharedState) -> ColibriResponse {
let last_warm_hit = *state.last_warm_cache_hit.read().await;
let last_warm_tokens = *state.last_warm_hit_tokens.read().await;
let secured_path = state.config.data_dir.join(".secured");
let secured = secured_path.exists();
ColibriResponse::ok(serde_json::json!({
"daemon": "colibri-daemon",
"version": env!("CARGO_PKG_VERSION"),
"host": state.config.host,
"secured": secured,
"paths": {
"data_dir": state.config.data_dir,
"db_path": state.config.db_path,

View file

@ -82,6 +82,7 @@ struct App {
sessions: Vec<String>, // all known session IDs
session_idx: usize, // which session is selected
attention_only: bool, // 'a' filter: show only attention panes
secured: Option<bool>, // node secured state (None = unknown yet)
}
impl App {
@ -97,6 +98,7 @@ impl App {
sessions: Vec::new(),
session_idx: 0,
attention_only: false,
secured: None,
}
}
@ -234,6 +236,10 @@ impl App {
self.error = Some(format!("daemon error: {e}"));
}
}
// check secured state on each refresh (lightweight status call)
if let Ok(st) = self.client.status().await {
self.secured = st.get("secured").and_then(|v| v.as_bool());
}
// decay status message
if let Some((_, ref mut ttl)) = &mut self.status_msg {
*ttl = ttl.saturating_sub(1);
@ -392,6 +398,14 @@ impl App {
)));
}
// Unsecured node notice
if self.secured == Some(false) {
lines.push(Line::from(Span::styled(
"[UNSECURED — set root password to activate agent]",
Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),
)));
}
f.render_widget(
Paragraph::new(lines).block(Block::default().borders(Borders::NONE)),
area,