Phase 1: project scaffold, clap CLI, self-re-exec daemon, NDJSON IPC

- Cargo.toml with clap, tokio, serde, anyhow dependencies
- Entry point with env-var routing to daemon or CLI mode
- Core protocol types (Request/Response NDJSON wire format)
- Session detection (X11 check with DISPLAY/XDG_SESSION_TYPE)
- RefMap with @wN selector resolution (direct, prefix, substring)
- Snapshot/WindowInfo shared types with Display impl
- clap derive CLI with all subcommands (snapshot, click, type, etc.)
- Client connection: socket path resolution, daemon auto-start via
  self-re-exec, NDJSON send/receive with retry backoff
- Tokio async daemon: Unix socket listener, accept loop, graceful
  shutdown via notify
- DaemonState holding session info and ref map
- Placeholder handler returning hardcoded snapshot response
This commit is contained in:
Harivansh Rathi 2026-03-24 21:19:18 -04:00
parent 17d4a1edeb
commit dfaa339594
13 changed files with 1735 additions and 0 deletions

49
src/core/protocol.rs Normal file
View file

@ -0,0 +1,49 @@
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Serialize, Deserialize)]
pub struct Request {
pub id: String,
pub action: String,
#[serde(flatten)]
pub extra: Value,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Response {
pub success: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
}
impl Request {
pub fn new(action: &str) -> Self {
Self {
id: format!("r{}", std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_micros() % 1_000_000),
action: action.to_string(),
extra: Value::Object(serde_json::Map::new()),
}
}
pub fn with_extra(mut self, key: &str, value: Value) -> Self {
if let Value::Object(ref mut map) = self.extra {
map.insert(key.to_string(), value);
}
self
}
}
impl Response {
pub fn ok(data: Value) -> Self {
Self { success: true, data: Some(data), error: None }
}
pub fn err(msg: impl Into<String>) -> Self {
Self { success: false, data: None, error: Some(msg.into()) }
}
}