Skip to main content

cc_switch/daemon/
mod.rs

1//! Daemon subsystem: in-process supervision of one ccs-proxy per upstream URL.
2//!
3//! See docs/superpowers/specs/2026-05-28-cc-switch-daemon-design.md for design.
4
5pub mod aggregate;
6pub mod commands;
7pub mod pidfile;
8pub mod state;
9pub mod status;
10
11#[cfg(unix)]
12pub mod fork;
13#[cfg(unix)]
14pub mod lifecycle;
15#[cfg(unix)]
16pub mod logging;
17
18pub use commands::{DaemonAction, handle_daemon_command};
19
20use crate::daemon::pidfile::{Pidfile, process_alive};
21use crate::daemon::state::DaemonState;
22
23/// Result of attempting to resolve a proxy URL for a given upstream.
24pub enum ProxyResolution {
25    /// Daemon is running and has a matching proxy.
26    Proxied { proxy_url: String },
27    /// Daemon is not running or has no match; use direct URL.
28    Direct,
29}
30
31/// Check whether the daemon is alive and has a proxy for the given upstream URL.
32/// Returns the proxy URL (http://127.0.0.1:<port>) if available, otherwise Direct.
33pub fn try_resolve_proxy(upstream: &str) -> ProxyResolution {
34    let home = match dirs::home_dir() {
35        Some(h) => h,
36        None => return ProxyResolution::Direct,
37    };
38    let cc_switch_dir = home.join(".cc-switch");
39    let state_path = cc_switch_dir.join("daemon-state.json");
40    let pidfile_path = cc_switch_dir.join("daemon.pid");
41
42    try_resolve_proxy_from_paths(upstream, &state_path, &pidfile_path)
43}
44
45fn try_resolve_proxy_from_paths(
46    upstream: &str,
47    state_path: &std::path::Path,
48    pidfile_path: &std::path::Path,
49) -> ProxyResolution {
50    let state = match DaemonState::load(state_path) {
51        Ok(Some(s)) => s,
52        _ => return ProxyResolution::Direct,
53    };
54
55    let pidfile = Pidfile::new(pidfile_path.to_path_buf());
56    let pid = match pidfile.read() {
57        Ok(Some(pid)) => pid,
58        _ => return ProxyResolution::Direct,
59    };
60
61    match process_alive(pid) {
62        Ok(true) => {}
63        _ => return ProxyResolution::Direct,
64    }
65
66    match state.find_proxy("claude", upstream) {
67        Some(entry) => ProxyResolution::Proxied {
68            proxy_url: format!("http://127.0.0.1:{}", entry.proxy_port),
69        },
70        None => ProxyResolution::Direct,
71    }
72}