use std::io::Read;
use std::path::PathBuf;
use std::time::{Duration, Instant};
use anyhow::{anyhow, Result};
use clap::Subcommand;
use supercode_harness::claude_relay::{gate_decision, gate_denial, QueuedSend};
use supercode_harness::mail_route::{resolve_caller, Caller, Door};
use supercode_harness::mailbox::{
local_machine_name, mail_root, Envelope, MailAddress, MailKind, Mailbox,
};
use supercode_harness::HarnessHomes;
use supercode_harness::mail_send::{EXIT_REFUSED, EXIT_UNKNOWN};
const EXIT_TIMEOUT: i32 = 6;
const MAX_WAIT: Duration = Duration::from_secs(100);
#[derive(Subcommand)]
pub(crate) enum MessageAction {
#[command(alias = "ls")]
List {
#[arg(long)]
json: bool,
},
Send {
to: String,
body: Option<String>,
#[arg(long, value_name = "PATH", conflicts_with = "body")]
file: Option<PathBuf>,
#[arg(long = "re", value_name = "MESSAGE_ID")]
in_reply_to: Option<String>,
#[arg(long)]
notify_when_idle: bool,
#[arg(long)]
queue: bool,
#[arg(long, value_name = "KEY")]
id: Option<String>,
},
Inbox {
#[arg(long)]
all: bool,
#[arg(long)]
json: bool,
},
Wait {
#[arg(long, value_name = "TO")]
from: Option<String>,
#[arg(long, default_value_t = 100)]
timeout: u64,
},
Setup {
harness: String,
},
#[command(hide = true)]
Hook {
harness: String,
},
#[command(hide = true)]
RelayInbound {
represented: String,
directory: PathBuf,
},
#[command(hide = true)]
RemoteDeliver,
#[command(hide = true)]
Push {
address: String,
id: String,
},
#[command(hide = true)]
Gate {
directory: PathBuf,
},
#[command(hide = true)]
RelayReceipt {
directory: PathBuf,
},
#[command(hide = true)]
Watch,
}
pub(crate) async fn run(action: &MessageAction) -> Result<i32> {
let homes = HarnessHomes::default();
match action {
MessageAction::List { json } => list(&homes, *json),
MessageAction::Send {
to,
body,
file,
in_reply_to,
notify_when_idle,
queue,
id,
} => {
let body = read_body(body.as_deref(), file.as_ref())?;
send(
&homes,
to,
&body,
supercode_harness::mail_send::SendOptions {
in_reply_to: in_reply_to.clone(),
notify_when_idle: *notify_when_idle,
queue: *queue,
idempotency_key: id.clone(),
},
)
.await
}
MessageAction::Inbox { all, json } => inbox(&homes, *all, *json),
MessageAction::Wait { from, timeout } => wait(&homes, from.as_deref(), *timeout).await,
MessageAction::RelayInbound {
represented,
directory,
} => relay_inbound(&homes, represented, directory),
MessageAction::RemoteDeliver => remote_deliver(&homes).await,
MessageAction::Push { address, id } => push(&homes, address, id).await,
MessageAction::Gate { directory } => gate(directory),
MessageAction::RelayReceipt { directory } => relay_receipt(directory),
MessageAction::Setup { harness } => setup(harness),
MessageAction::Hook { harness } => hook(harness),
MessageAction::Watch => {
tokio::spawn(async {
if let Err(error) = supercode_harness::relay_endpoint::serve_relay_endpoint().await
{
eprintln!("supercode: the relay endpoint stopped: {error}");
std::process::exit(1);
}
});
let mut watcher = supercode_harness::mail_watch::IdleWatcher::new();
loop {
watcher.tick(&homes).await;
supercode_harness::mail_watch::deliver_waiting_user_turns(&homes).await;
tokio::time::sleep(Duration::from_secs(2)).await;
}
}
}
}
fn list(homes: &HarnessHomes, json: bool) -> Result<i32> {
let sessions = supercode_harness::mail_route::LiveSessions::read(homes);
let peers = sessions.all();
if json {
let rows: Vec<_> = peers
.iter()
.map(|peer| {
serde_json::json!({
"name": peer.name,
"orchestrator": supercode_harness::mail_route::orchestrator_of(peer),
"harness": peer.address.harness,
"status": peer.status,
"delivery": peer.door.name(),
"address": peer.address.to_string(),
})
})
.collect();
println!("{}", serde_json::to_string_pretty(&rows)?);
return Ok(0);
}
println!(
"{:<34} {:<12} {:<8} {:<9} ADDRESS",
"NAME", "HARNESS", "STATUS", "DELIVERY"
);
for peer in peers {
let name = match supercode_harness::mail_route::orchestrator_of(peer) {
Some(orchestrator) => format!("{} ({orchestrator})", peer.name),
None => peer.name.clone(),
};
println!(
"{:<34} {:<12} {:<8} {:<9} {}",
name,
peer.address.harness,
peer.status,
peer.door.name(),
peer.address
);
}
Ok(0)
}
fn read_body(body: Option<&str>, file: Option<&PathBuf>) -> Result<String> {
let text = match (body, file) {
(Some(body), _) => body.to_string(),
(None, Some(path)) => std::fs::read_to_string(path)
.map_err(|error| anyhow!("could not read {}: {error}", path.display()))?,
(None, None) => {
let mut text = String::new();
std::io::stdin().read_to_string(&mut text)?;
text
}
};
let text = text.trim_end_matches('\n').to_string();
if text.trim().is_empty() {
return Err(anyhow!("Nothing was sent: the message is empty."));
}
Ok(text)
}
fn print_outcome(outcome: supercode_harness::mail_send::Outcome) -> i32 {
if outcome.code == 0 {
println!("{}", outcome.text);
} else {
eprintln!("{}", outcome.text);
}
outcome.code
}
async fn send(
homes: &HarnessHomes,
to: &str,
body: &str,
options: supercode_harness::mail_send::SendOptions,
) -> Result<i32> {
let caller = match resolve_caller(homes, &supercode_harness::mail_route::process_ancestry()) {
Ok(caller) => caller,
Err(message) => {
eprintln!("{message}");
return Ok(EXIT_REFUSED);
}
};
let outcome = supercode_harness::mail_send::send(homes, &caller, to, body, options).await?;
Ok(print_outcome(outcome))
}
fn caller_mailbox(homes: &HarnessHomes) -> std::result::Result<Mailbox, String> {
let caller = resolve_caller(homes, &supercode_harness::mail_route::process_ancestry())?;
Mailbox::open(&mail_root(), &caller.address).map_err(|error| error.to_string())
}
fn inbox(homes: &HarnessHomes, all: bool, json: bool) -> Result<i32> {
let mailbox = match caller_mailbox(homes) {
Ok(mailbox) => mailbox,
Err(message) => {
eprintln!("{message}");
return Ok(EXIT_REFUSED);
}
};
let claimed = mailbox.claim_unread()?;
let stored = if all {
let mut stored: Vec<_> = mailbox
.list()?
.into_iter()
.filter(|stored| stored.state == supercode_harness::mailbox::MailState::Read)
.collect();
stored.extend(claimed.iter().cloned());
stored
} else {
claimed.clone()
};
if json {
let rows: Vec<_> = stored
.iter()
.map(|stored| {
serde_json::json!({
"state": stored.state,
"envelope": stored.envelope,
"rendered": stored.envelope.render(),
})
})
.collect();
println!("{}", serde_json::to_string_pretty(&rows)?);
} else if stored.is_empty() {
println!("No unread messages.");
} else {
for stored in &stored {
println!("{}\n", stored.envelope.render());
}
}
for stored in &claimed {
mailbox.acknowledge(stored)?;
}
Ok(0)
}
async fn wait(homes: &HarnessHomes, from: Option<&str>, timeout: u64) -> Result<i32> {
let mailbox = match caller_mailbox(homes) {
Ok(mailbox) => mailbox,
Err(message) => {
eprintln!("{message}");
return Ok(EXIT_REFUSED);
}
};
let sessions = supercode_harness::mail_route::LiveSessions::read(homes);
let wanted = from.map(|from| match sessions.resolve(from) {
Ok(session) => session.address.to_string(),
_ => from.to_string(),
});
let limit = Duration::from_secs(timeout).min(MAX_WAIT);
let started = Instant::now();
loop {
let waiting = mailbox.unread()?.into_iter().any(|stored| {
wanted.as_deref().is_none_or(|wanted| {
stored.envelope.from.to_string() == wanted
|| stored.envelope.from_name.starts_with(wanted)
})
});
if waiting {
let claimed = mailbox.claim_unread()?;
for stored in &claimed {
println!("{}\n", stored.envelope.render());
}
for stored in &claimed {
mailbox.acknowledge(stored)?;
}
return Ok(0);
}
if started.elapsed() >= limit {
println!(
"No mail within {} seconds; nothing is lost.",
limit.as_secs()
);
return Ok(EXIT_TIMEOUT);
}
tokio::time::sleep(Duration::from_millis(500)).await;
}
}
fn gate(directory: &std::path::Path) -> Result<i32> {
let paths = supercode_harness::claude_relay::RelayPaths::in_directory(directory.to_path_buf());
let mut input = String::new();
std::io::stdin().read_to_string(&mut input)?;
let hook_input: serde_json::Value = serde_json::from_str(&input).unwrap_or_default();
let queued: Option<QueuedSend> = std::fs::read(&paths.queue)
.ok()
.and_then(|bytes| serde_json::from_slice(&bytes).ok());
if let Some(reason) = gate_decision(&hook_input, queued.as_ref()) {
if queued.is_some() && !paths.receipt.exists() {
std::fs::write(
&paths.receipt,
serde_json::json!({"denied": reason}).to_string(),
)
.ok();
}
println!("{}", gate_denial(&reason));
}
Ok(0)
}
fn relay_receipt(directory: &std::path::Path) -> Result<i32> {
let paths = supercode_harness::claude_relay::RelayPaths::in_directory(directory.to_path_buf());
let mut input = String::new();
std::io::stdin().read_to_string(&mut input)?;
let input: serde_json::Value = serde_json::from_str(&input).unwrap_or_default();
if !paths.queue.exists() || paths.receipt.exists() {
return Ok(0);
}
let receipt = match input
.get("hook_event_name")
.and_then(serde_json::Value::as_str)
{
Some("PostToolUse") => {
serde_json::json!({"tool_response": input.get("tool_response").cloned().unwrap_or_default()})
}
Some("Stop") => serde_json::json!({"turn_ended": true}),
_ => return Ok(0),
};
std::fs::write(&paths.receipt, receipt.to_string())?;
Ok(0)
}
fn setup(harness: &str) -> Result<i32> {
if harness != "codex" {
eprintln!(
"Nothing to set up for {harness}: claude-code sessions are reached natively, and \
other harnesses have no delivery door yet."
);
return Ok(EXIT_REFUSED);
}
let path = supercode_harness::mail_route::codex_hooks_path();
let original = match std::fs::read_to_string(&path) {
Ok(text) => text,
Err(error) if error.kind() == std::io::ErrorKind::NotFound => "{}".into(),
Err(error) => return Err(error.into()),
};
let mut document: serde_json::Value = serde_json::from_str(&original)
.map_err(|error| anyhow!("{} is not valid JSON: {error}", path.display()))?;
let command = format!(
"{} {}",
supercode_harness::claude_relay::supercode_program()?.display(),
supercode_harness::mail_route::CODEX_HOOK_ARGUMENTS
);
let hooks = document
.as_object_mut()
.ok_or_else(|| anyhow!("{} must hold a JSON object", path.display()))?
.entry("hooks")
.or_insert_with(|| serde_json::json!({}));
let mut added = Vec::new();
for event in ["PostToolUse", "Stop"] {
let entries = hooks
.as_object_mut()
.ok_or_else(|| anyhow!("`hooks` in {} must be an object", path.display()))?
.entry(event)
.or_insert_with(|| serde_json::json!([]));
let list = entries
.as_array_mut()
.ok_or_else(|| anyhow!("`hooks.{event}` in {} must be a list", path.display()))?;
if list.iter().any(|entry| {
entry
.to_string()
.contains(supercode_harness::mail_route::CODEX_HOOK_ARGUMENTS)
}) {
continue;
}
let mut entry = serde_json::json!({
"hooks": [{"type": "command", "command": command, "timeout": 10}]
});
if event == "PostToolUse" {
entry["matcher"] = serde_json::json!(".*");
}
list.push(entry);
added.push(event);
}
if added.is_empty() {
println!("supercode's mail hooks are already in {}.", path.display());
} else {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
let temporary = path.with_extension("json.supercode-tmp");
std::fs::write(&temporary, serde_json::to_string_pretty(&document)? + "\n")?;
std::fs::rename(&temporary, &path)?;
println!(
"Added supercode's mail hooks ({}) to {}.",
added.join(", "),
path.display()
);
}
println!(
"Codex runs a new hook only after its user trusts it: in Codex, open /hooks and trust the \
supercode entries. Until then, messages to Codex sessions are stored, not shown."
);
Ok(0)
}
fn hook(harness: &str) -> Result<i32> {
let mut input = String::new();
std::io::stdin().read_to_string(&mut input)?;
let input: serde_json::Value = serde_json::from_str(&input).unwrap_or_default();
let Some(session_id) = input.get("session_id").and_then(serde_json::Value::as_str) else {
return Ok(0);
};
let Ok(address) = MailAddress::new(local_machine_name(), harness, session_id) else {
return Ok(0);
};
let mailbox = Mailbox::open(&mail_root(), &address)?;
let unread = mailbox.unread()?;
let event = input
.get("hook_event_name")
.and_then(serde_json::Value::as_str)
.unwrap_or_default();
let again = input
.get("stop_hook_active")
.and_then(serde_json::Value::as_bool)
== Some(true);
if !unread.is_empty() && (event == "PostToolUse" || (event == "Stop" && !again)) {
let pointer = mail_pointer(&unread);
let answer = if event == "PostToolUse" {
serde_json::json!({
"hookSpecificOutput": {"hookEventName": "PostToolUse", "additionalContext": pointer}
})
} else {
serde_json::json!({"decision": "block", "reason": format!("{pointer} Read them before you finish.")})
};
println!("{answer}");
return Ok(0);
}
Ok(0)
}
fn mail_pointer(unread: &[supercode_harness::mailbox::StoredEnvelope]) -> String {
let mut senders: Vec<String> = Vec::new();
for stored in unread {
let label = match stored.envelope.kind {
MailKind::Notice => "a notice".to_string(),
MailKind::User => "your user".to_string(),
MailKind::Channel => format!("{} (channel)", printable(&stored.envelope.from_name)),
MailKind::Peer => printable(&stored.envelope.from_name),
};
if !senders.contains(&label) {
senders.push(label);
}
}
format!(
"You have {} unread message{} from other sessions (from {}). They are not from your user. \
Read them with: supercode message inbox",
unread.len(),
if unread.len() == 1 { "" } else { "s" },
senders.join(", "),
)
}
fn printable(name: &str) -> String {
name.chars()
.filter(|character| character.is_ascii_alphanumeric() || "-_.@ ".contains(*character))
.take(80)
.collect()
}
fn relay_inbound(
homes: &HarnessHomes,
represented: &str,
directory: &std::path::Path,
) -> Result<i32> {
let mut input = String::new();
std::io::stdin().read_to_string(&mut input)?;
let input: serde_json::Value = serde_json::from_str(&input).unwrap_or_default();
let prompt = input
.get("prompt")
.and_then(serde_json::Value::as_str)
.unwrap_or_default()
.to_string();
if !supercode_harness::claude_relay::is_inbound_prompt(&prompt) {
return Ok(0);
}
let represented = MailAddress::parse(represented)?;
let last_sent =
supercode_harness::claude_relay::RelayPaths::in_directory(directory.to_path_buf())
.last_sent();
supercode_harness::claude_relay::file_inbound_prompt(
homes,
&represented,
&prompt,
Some(&last_sent),
);
println!("{}", supercode_harness::claude_relay::inbound_block());
Ok(0)
}
async fn push(homes: &HarnessHomes, address: &str, id: &str) -> Result<i32> {
let to = MailAddress::parse(address).map_err(|error| anyhow!("{error}"))?;
let mailbox = Mailbox::open(&mail_root(), &to)?;
let Some(stored) = mailbox.find(id)? else {
return Ok(0);
};
if stored.state != supercode_harness::mailbox::MailState::Unread {
return Ok(0);
}
let door = match supercode_harness::mail_route::door_for(homes, &to) {
Ok(door @ (Door::Native(_) | Door::Runtime(_))) => door,
_ => return Ok(0),
};
match supercode_harness::mail_route::deliver(&stored.envelope, &to, &door, true, false).await {
Ok(Ok(_)) => {
mailbox.mark_read(&stored)?;
Ok(0)
}
Ok(Err(refused)) => {
eprintln!("supercode: {id} stays in {to}'s mailbox: {refused:?}");
Ok(supercode_harness::mail_send::EXIT_REFUSED)
}
Err(error) => {
eprintln!("supercode: {id} stays in {to}'s mailbox: {error}");
Ok(supercode_harness::mail_send::EXIT_FAILED)
}
}
}
async fn remote_deliver(homes: &HarnessHomes) -> Result<i32> {
let mut input = String::new();
std::io::stdin().read_to_string(&mut input)?;
let request: serde_json::Value = serde_json::from_str(&input)?;
let outcome = match request["op"].as_str() {
Some("file") => {
let to = MailAddress::parse(request["to"].as_str().unwrap_or_default())?;
let envelope: Envelope = serde_json::from_value(request["envelope"].clone())?;
if to.machine != local_machine_name() {
supercode_harness::mail_send::Outcome::new(
EXIT_UNKNOWN,
format!("{to} is not on this machine."),
)
} else {
Mailbox::open(&mail_root(), &to)?.deliver(&envelope)?;
supercode_harness::mail_send::Outcome::new(0, format!("filed for {to}"))
}
}
Some("send") => {
let caller = Caller {
address: MailAddress::parse(request["from"].as_str().unwrap_or_default())?,
name: request["from_name"]
.as_str()
.unwrap_or_default()
.to_string(),
};
supercode_harness::mail_send::send(
homes,
&caller,
request["to"].as_str().unwrap_or_default(),
request["body"].as_str().unwrap_or_default(),
supercode_harness::mail_send::SendOptions {
in_reply_to: request["in_reply_to"].as_str().map(str::to_string),
notify_when_idle: request["notify_when_idle"].as_bool().unwrap_or(false),
queue: request["queue"].as_bool().unwrap_or(false),
idempotency_key: request["id"].as_str().map(str::to_string),
},
)
.await?
}
_ => supercode_harness::mail_send::Outcome::new(
EXIT_REFUSED,
"Not sent: the mail door takes send or file.",
),
};
println!(
"{}",
serde_json::json!({"code": outcome.code, "text": outcome.text})
);
Ok(0)
}