forked from kent/consciousness
bin: add ch — minimal channel CLI (send/recv)
Speaks the channel.capnp protocol over the per-daemon Unix socket at ~/.consciousness/channels/<top>.sock. Useful for ad-hoc sends from shell, tests, and out-of-process tools that don't want to embed a capnp client. ch send <channel> <message...> ch recv <channel> [--all-new] [--min-count N] Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
parent
c303653dd0
commit
713bb07729
1 changed files with 112 additions and 0 deletions
112
src/bin/ch.rs
Normal file
112
src/bin/ch.rs
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
// `ch` — minimal channel CLI.
|
||||
//
|
||||
// ch send <channel-path> <message>
|
||||
// ch recv <channel-path> [--all-new] [--min-count N]
|
||||
//
|
||||
// Connects to ~/.consciousness/channels/<top>.sock and speaks the
|
||||
// channel.capnp protocol to the appropriate daemon.
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::process::ExitCode;
|
||||
|
||||
use capnp_rpc::{rpc_twoparty_capnp, twoparty, RpcSystem};
|
||||
use futures::AsyncReadExt;
|
||||
use tokio_util::compat::TokioAsyncReadCompatExt;
|
||||
|
||||
use consciousness::channel_capnp::channel_server;
|
||||
|
||||
fn channels_dir() -> PathBuf {
|
||||
dirs::home_dir().unwrap_or_default().join(".consciousness/channels")
|
||||
}
|
||||
|
||||
fn sock_for(channel: &str) -> PathBuf {
|
||||
let top = channel.split('.').next().unwrap_or(channel);
|
||||
channels_dir().join(format!("{top}.sock"))
|
||||
}
|
||||
|
||||
async fn connect(sock: &std::path::Path) -> Result<channel_server::Client, String> {
|
||||
let stream = tokio::net::UnixStream::connect(sock).await
|
||||
.map_err(|e| format!("connect {}: {e}", sock.display()))?;
|
||||
let (reader, writer) = stream.compat().split();
|
||||
let network = Box::new(twoparty::VatNetwork::new(
|
||||
futures::io::BufReader::new(reader),
|
||||
futures::io::BufWriter::new(writer),
|
||||
rpc_twoparty_capnp::Side::Client,
|
||||
Default::default(),
|
||||
));
|
||||
let mut rpc = RpcSystem::new(network, None);
|
||||
let client: channel_server::Client = rpc.bootstrap(rpc_twoparty_capnp::Side::Server);
|
||||
tokio::task::spawn_local(rpc);
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() -> ExitCode {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
if args.len() < 2 {
|
||||
eprintln!("usage: {} <send|recv> <channel> [args...]", args[0]);
|
||||
return ExitCode::from(2);
|
||||
}
|
||||
|
||||
let cmd = args[1].clone();
|
||||
let local = tokio::task::LocalSet::new();
|
||||
let result: Result<(), String> = local.run_until(async move {
|
||||
match cmd.as_str() {
|
||||
"send" => {
|
||||
if args.len() < 4 {
|
||||
return Err("usage: ch send <channel> <message...>".into());
|
||||
}
|
||||
let channel = &args[2];
|
||||
let message = args[3..].join(" ");
|
||||
let sock = sock_for(channel);
|
||||
let client = connect(&sock).await?;
|
||||
let mut req = client.send_request();
|
||||
req.get().set_channel(channel);
|
||||
req.get().set_message(&message);
|
||||
req.send().promise.await.map_err(|e| format!("send: {e}"))?;
|
||||
println!("sent to {channel}");
|
||||
Ok(())
|
||||
}
|
||||
"recv" => {
|
||||
if args.len() < 3 {
|
||||
return Err("usage: ch recv <channel> [--all-new] [--min-count N]".into());
|
||||
}
|
||||
let channel = &args[2];
|
||||
let mut all_new = false;
|
||||
let mut min_count: u32 = 20;
|
||||
let mut i = 3;
|
||||
while i < args.len() {
|
||||
match args[i].as_str() {
|
||||
"--all-new" => { all_new = true; i += 1; }
|
||||
"--min-count" => {
|
||||
min_count = args.get(i+1)
|
||||
.ok_or("--min-count needs an argument")?
|
||||
.parse().map_err(|e| format!("--min-count: {e}"))?;
|
||||
i += 2;
|
||||
}
|
||||
other => return Err(format!("unknown arg: {other}")),
|
||||
}
|
||||
}
|
||||
let sock = sock_for(channel);
|
||||
let client = connect(&sock).await?;
|
||||
let mut req = client.recv_request();
|
||||
req.get().set_channel(channel);
|
||||
req.get().set_all_new(all_new);
|
||||
req.get().set_min_count(min_count);
|
||||
let reply = req.send().promise.await.map_err(|e| format!("recv: {e}"))?;
|
||||
let text = reply.get().map_err(|e| e.to_string())?
|
||||
.get_text().map_err(|e| e.to_string())?
|
||||
.to_str().map_err(|e| e.to_string())?;
|
||||
print!("{text}");
|
||||
if !text.ends_with('\n') { println!(); }
|
||||
Ok(())
|
||||
}
|
||||
other => Err(format!("unknown command: {other} (use send|recv)")),
|
||||
}
|
||||
}).await;
|
||||
|
||||
match result {
|
||||
Ok(()) => ExitCode::SUCCESS,
|
||||
Err(e) => { eprintln!("error: {e}"); ExitCode::from(1) }
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue