use crate::desk::{self, Claim};
use crate::store::{self, Session};
use crate::{adb, install, proc};
use serde_json::{json, Value};
use std::io::{Read, Write};
pub fn run(event: Option<&str>) -> i32 {
let mut input = String::new();
let _ = std::io::stdin().read_to_string(&mut input);
let v: Value = serde_json::from_str(&input).unwrap_or(Value::Null);
let Some(id) = v["session_id"].as_str().filter(|s| !s.is_empty()) else {
return 0;
};
match event {
Some("session-start") => session_start(id, &v),
Some("pre-tool-use") => pre_tool_use(id, &v),
Some("session-end") => session_end(id, &v),
_ => {}
}
0
}
fn session_start(id: &str, v: &Value) {
let agent = std::env::var("CLAUDE_PID")
.ok()
.and_then(|pid| proc::info(pid.parse().ok()?))
.map(|p| proc::Proc {
name: "claude".into(),
..p
})
.unwrap_or_else(store::anchor);
let session = Session {
pid: agent.pid,
start: agent.start,
name: agent.name,
cwd: v["cwd"].as_str().unwrap_or_default().to_string(),
};
store::sweep_sessions();
let _ = store::write_session(id, &session);
if store::debug() {
store::log_event(
"session started",
&store::me_for_session(id),
"",
v["source"].as_str().unwrap_or_default(),
);
}
let _ = store::adopt(&store::me_for_session(id));
let _ = install::ensure_shim();
if let Some(path) = std::env::var_os("CLAUDE_ENV_FILE").filter(|p| !p.is_empty()) {
let lines = install::env_lines(Some(id));
let current = std::fs::read_to_string(&path).unwrap_or_default();
let named = current
.lines()
.rfind(|l| l.starts_with("export ADBRENT_SESSION="));
if named != lines.lines().next() {
if let Ok(mut f) = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(path)
{
let _ = writeln!(f, "{lines}");
}
}
}
}
fn device_args(v: &Value, key: &str, out: &mut Vec<String>) {
match v {
Value::String(s) => {
let k = key.to_ascii_lowercase();
if ["udid", "serial", "device"].iter().any(|n| k.contains(n)) {
out.push(s.clone());
}
}
Value::Array(items) => items.iter().for_each(|i| device_args(i, key, out)),
Value::Object(map) => map.iter().for_each(|(k, val)| device_args(val, k, out)),
_ => {}
}
}
fn pre_tool_use(id: &str, v: &Value) {
if !v["tool_name"]
.as_str()
.is_some_and(|n| n.starts_with("mcp__"))
{
return;
}
let mut named = Vec::new();
device_args(&v["tool_input"], "", &mut named);
if named.is_empty() {
return;
}
let Some(real) = adb::real_adb() else { return };
let devs = adb::devices(&real, &[]);
let me = store::me_for_session(id);
for d in devs.iter().filter(|d| named.contains(&d.serial)) {
if store::debug() {
store::log_event(
"tool call",
&me,
&d.serial,
v["tool_name"].as_str().unwrap_or_default(),
);
}
if let Claim::Refused(reason) = desk::claim(&real, &d.serial, &me, &devs, false) {
println!(
"{}",
json!({"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": reason,
}})
);
return;
}
}
}
fn session_end(id: &str, v: &Value) {
if v["reason"].as_str() != Some("clear") {
let owner = format!("s:{id}");
let _ = store::release(|l| l.owner == owner, Some("its session ended"));
let _ = store::leave(&owner);
}
if store::debug() {
store::log_event(
"session ended",
&store::me_for_session(id),
"",
v["reason"].as_str().unwrap_or_default(),
);
}
store::remove_session(id);
}