#![doc = include_str!("../../examples/together_companion.rs")]
mod executor;
pub(crate) mod runtime;
#[cfg(any(feature = "wasi", not(target_arch = "wasm32")))]
pub mod http;
#[cfg(all(feature = "wasi", target_arch = "wasm32"))]
mod http_wasi;
#[cfg(all(feature = "wasi", target_arch = "wasm32"))]
pub mod io;
#[cfg(all(feature = "wasi", target_arch = "wasm32"))]
pub use wasip2 as wasi;
use alloc::string::String;
use alloc::vec::Vec;
use core::future::Future;
use serde::de::DeserializeOwned;
use crate::api::{self, Json};
use crate::error::{Error, Result};
use crate::host;
pub use executor::{EVENT_QUEUE, NextEvent, NextEventUntil, Sleep};
pub const INTEREST_EVENT: &str = "system.plugin.interest";
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Interest {
surfaces: Option<alloc::collections::BTreeSet<String>>,
}
impl Interest {
pub fn of<I, S>(surfaces: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
Self {
surfaces: Some(surfaces.into_iter().map(Into::into).collect()),
}
}
pub fn is_known(&self) -> bool {
self.surfaces.is_some()
}
pub fn any(&self) -> bool {
self.surfaces.as_ref().is_none_or(|shown| !shown.is_empty())
}
pub fn shows(&self, surface: &str) -> bool {
self.surfaces
.as_ref()
.is_none_or(|shown| shown.contains(surface))
}
pub fn surfaces(&self) -> impl Iterator<Item = &str> {
self.surfaces.iter().flatten().map(String::as_str)
}
pub(crate) fn from_payload(payload: &str) -> Option<Self> {
#[derive(serde::Deserialize)]
struct Wire {
surfaces: Vec<String>,
}
let wire: Wire = serde_json::from_str(payload).ok()?;
Some(Self::of(wire.surfaces))
}
}
pub trait DaemonPlugin: Sized + 'static {
fn activate(cx: &Context) -> Self;
fn run(&self, cx: Context) -> impl Future<Output = ()> {
let _ = cx;
core::future::ready(())
}
fn call(&self, method: &str, request: Json, cx: &Context) -> Result<Json> {
let _ = (request, cx);
Err(Error::Invalid(alloc::format!("unknown method {method:?}")))
}
fn deactivate(&self) {}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum PaneChangeKind {
Created,
Changed,
Exited,
Closed,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Caller {
pub machine_id: String,
pub plugin_id: String,
pub kind: String,
pub epoch: Option<u64>,
pub controller_machine_id: Option<String>,
pub controller_fencing_token: Option<String>,
}
impl Caller {
pub fn from_controller(&self) -> bool {
self.controller_machine_id.as_deref() == Some(self.machine_id.as_str())
}
}
#[derive(Clone, Debug, Default)]
pub struct Context {
config: String,
}
impl Context {
pub(crate) fn new(config: String) -> Self {
Self { config }
}
pub fn config<T: DeserializeOwned>(&self) -> Result<T> {
if self.config.trim().is_empty() {
return Json(String::from("null")).parse();
}
Json(self.config.clone()).parse()
}
pub fn config_json(&self) -> Json {
Json(self.config.clone())
}
pub fn values(&self) -> api::values::Values {
api::values()
}
pub fn live(&self) -> api::live::Live {
api::live()
}
pub fn events(&self) -> api::events::Events {
api::events()
}
pub fn claims(&self) -> api::claims::Claims {
api::claims::Claims
}
pub fn calls(&self) -> api::calls::Calls {
api::calls()
}
pub fn now_ms(&self) -> u64 {
executor::now_ms()
}
pub fn next_event(&self) -> NextEvent {
NextEvent::new()
}
pub fn interest(&self) -> Interest {
runtime::current_interest()
}
pub fn next_event_until(&self, at_ms: Option<u64>) -> NextEventUntil {
NextEventUntil::new(at_ms)
}
pub fn sleep(&self, ms: u64) -> Sleep {
Sleep::until(executor::now_ms().saturating_add(ms))
}
pub fn sleep_until(&self, at_ms: u64) -> Sleep {
Sleep::until(at_ms)
}
pub fn spawn(&self, task: impl Future<Output = ()> + 'static) {
executor::spawn(task);
}
pub fn caller(&self) -> Option<Caller> {
runtime::current_caller()
}
pub fn machine_id(&self) -> String {
host::daemon_machine_id()
}
pub fn account_id(&self) -> Option<String> {
host::daemon_account_id()
}
pub fn lease_epoch(&self) -> Option<u64> {
host::daemon_lease_epoch()
}
pub fn release(&self) -> Option<Release> {
host::daemon_release()
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Release {
pub channel: String,
pub version: String,
pub git_sha: String,
}
impl Release {
pub fn git_ref(&self) -> String {
match self.channel.strip_prefix("branch:") {
Some(branch) if !branch.is_empty() => alloc::format!("refs/heads/{branch}"),
_ => "refs/heads/main".into(),
}
}
}
pub mod env {
use super::*;
pub fn vars() -> Vec<(String, String)> {
host::daemon_environment()
}
pub fn var(name: &str) -> Option<String> {
vars()
.into_iter()
.find(|(key, _)| key == name)
.map(|(_, value)| value)
}
pub fn home_dir() -> Option<String> {
var("HOME").filter(|home| home.starts_with('/'))
}
}
pub mod process {
use super::*;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Child {
pub pid: u32,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Stdio {
Null,
Piped,
#[default]
Log,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Command {
pub program: String,
pub args: Vec<String>,
pub env: Vec<(String, String)>,
pub env_remove: Vec<String>,
pub clear_env: bool,
pub cwd: Option<String>,
pub stdin: Stdio,
pub stdout: Stdio,
pub stderr: Stdio,
}
impl Command {
pub fn new(program: &str) -> Self {
Self {
program: String::from(program),
args: Vec::new(),
env: Vec::new(),
env_remove: Vec::new(),
clear_env: false,
cwd: None,
stdin: Stdio::Null,
stdout: Stdio::Log,
stderr: Stdio::Log,
}
}
pub fn arg(mut self, arg: &str) -> Self {
self.args.push(String::from(arg));
self
}
pub fn args(mut self, args: &[&str]) -> Self {
self.args.extend(args.iter().map(|arg| String::from(*arg)));
self
}
pub fn env(mut self, key: &str, value: &str) -> Self {
self.env.push((String::from(key), String::from(value)));
self
}
pub fn env_remove(mut self, key: &str) -> Self {
self.env_remove.push(String::from(key));
self
}
pub fn clear_env(mut self) -> Self {
self.clear_env = true;
self
}
pub fn cwd(mut self, cwd: &str) -> Self {
self.cwd = Some(String::from(cwd));
self
}
pub fn stdin(mut self, stdin: Stdio) -> Self {
self.stdin = stdin;
self
}
pub fn stdout(mut self, stdout: Stdio) -> Self {
self.stdout = stdout;
self
}
pub fn stderr(mut self, stderr: Stdio) -> Self {
self.stderr = stderr;
self
}
pub fn spawn(&self) -> Result<Child> {
host::process_run(self).map(|pid| Child { pid })
}
}
pub fn spawn(program: &str, args: &[&str], cwd: Option<&str>) -> Result<Child> {
let args: Vec<String> = args.iter().map(|arg| String::from(*arg)).collect();
host::process_spawn(program, &args, cwd).map(|pid| Child { pid })
}
impl Child {
pub fn wait(self) -> Result<i32> {
host::process_wait(self.pid)
}
pub fn try_wait(self) -> Result<Option<i32>> {
host::process_try_wait(self.pid)
}
pub fn write(self, bytes: &[u8]) -> Result<()> {
host::process_write(self.pid, bytes)
}
pub fn close_stdin(self) -> Result<()> {
host::process_close_stdin(self.pid)
}
pub fn kill(self) -> Result<()> {
host::process_kill(self.pid)
}
}
}
pub mod watch {
use super::*;
#[derive(Debug, PartialEq, Eq)]
pub struct Watch {
pub handle: u32,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Options {
pub recursive: bool,
pub exclude: Vec<String>,
}
impl Default for Options {
fn default() -> Self {
Self {
recursive: true,
exclude: Vec::new(),
}
}
}
impl Options {
pub fn shallow() -> Self {
Self {
recursive: false,
exclude: Vec::new(),
}
}
pub fn exclude(mut self, glob: &str) -> Self {
self.exclude.push(String::from(glob));
self
}
}
pub fn watch(path: &str) -> Result<Watch> {
watch_with(path, &Options::default())
}
pub fn watch_with(path: &str, options: &Options) -> Result<Watch> {
host::watch(path, options).map(|handle| Watch { handle })
}
impl Watch {
pub fn unwatch(self) -> Result<()> {
host::unwatch(self.handle)
}
}
}
pub mod net {
use super::*;
pub const WEBSOCKET_EVENT: &str = "system.net.websocket";
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct WebSocket {
pub socket: u32,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum SocketEvent {
Open { socket: u32 },
Message { socket: u32, text: String },
Closed { socket: u32, reason: String },
}
impl SocketEvent {
pub fn from_event(event: &crate::Event) -> Option<Self> {
let crate::Event::Plugin { name, payload } = event else {
return None;
};
if name != WEBSOCKET_EVENT {
return None;
}
let value: serde_json::Value = serde_json::from_str(&payload.0).ok()?;
let socket = u32::try_from(value.get("socket")?.as_u64()?).ok()?;
let text = |field: &str| -> String {
value
.get(field)
.and_then(serde_json::Value::as_str)
.unwrap_or_default()
.into()
};
match value.get("kind")?.as_str()? {
"open" => Some(Self::Open { socket }),
"message" => Some(Self::Message {
socket,
text: text("text"),
}),
"closed" => Some(Self::Closed {
socket,
reason: text("reason"),
}),
_ => None,
}
}
}
impl WebSocket {
pub fn open(url: &str, headers: &[(String, String)]) -> Result<Self> {
host::websocket_open(url, headers).map(|socket| Self { socket })
}
pub fn send(&self, text: &str) -> Result<()> {
host::websocket_send(self.socket, text)
}
pub fn close(self) {
host::websocket_close(self.socket);
}
}
}
pub mod panes {
use super::*;
pub use crate::api::account::Pane;
pub fn list() -> Result<Vec<Pane>> {
host::panes()
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Created {
pub id: String,
pub generation: u64,
}
impl Created {
pub fn key(&self) -> String {
alloc::format!("{}@{}", self.id, self.generation)
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct NewPane {
pub cwd: String,
pub command: Option<String>,
pub env: Vec<(String, String)>,
pub title: Option<String>,
}
impl NewPane {
pub fn new(cwd: &str) -> Self {
Self {
cwd: cwd.into(),
..Self::default()
}
}
pub fn command(mut self, command: &str) -> Self {
self.command = Some(command.into());
self
}
pub fn env(mut self, name: &str, value: &str) -> Self {
self.env.retain(|(key, _)| key != name);
self.env.push((name.into(), value.into()));
self
}
pub fn title(mut self, title: &str) -> Self {
self.title = Some(title.into());
self
}
pub fn create(&self) -> Result<Created> {
match &self.title {
None => host::pane_create(&self.cwd, self.command.as_deref(), &self.env),
Some(title) => host::pane_create_with(
&self.cwd,
self.command.as_deref(),
&self.env,
Some(title),
),
}
.map(|(id, generation)| Created { id, generation })
}
}
pub fn create(cwd: &str, command: Option<&str>) -> Result<Created> {
NewPane {
cwd: cwd.into(),
command: command.map(Into::into),
env: Vec::new(),
title: None,
}
.create()
}
pub fn input(pane: &str, bytes: &[u8]) -> Result<()> {
host::pane_input(pane, bytes)
}
pub fn close(pane: &str) -> Result<()> {
host::pane_close(pane)
}
pub fn subscribe() -> Result<()> {
host::panes_subscribe(true)
}
pub fn unsubscribe() -> Result<()> {
host::panes_subscribe(false)
}
pub fn wait(pane: &str, timeout_ms: u32) -> Result<bool> {
host::pane_wait(pane, timeout_ms)
}
}