zyris 0.2.0

The Zyris protocol stack behind one name: a library you call to connect a node, announce typed capabilities, and use the ones the peer announces back
Documentation
# zyris

The [Zyris](https://github.com/attacca-cc/zyris-protocol) protocol stack behind one name.

A **Zyris node** is any machine that dials a server over one websocket, announces typed
**capabilities** — named, versioned sets of tools with JSON-Schema'd arguments — and, on the same
connection, consumes the capabilities the server announces back. The protocol is
direction-symmetric: once the handshake is done there is no client role and no server role, and
either peer may call the other, open streams, or change what it offers mid-session.

```toml
[dependencies]
zyris = { version = "0.2", features = ["full", "tls-ring"] }
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
```

```rust
use zyris::{Node, NodeKind};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let link = Node::builder()
        .kind(NodeKind::Service)
        .capability(MyServer(my_impl))
        .build()?
        .connect(zyris::DEFAULT_SERVER_URL, std::env::var("ZYRIS_NODE_TOKEN")?)
        .await?;

    link.wait_closed().await?;
    Ok(())
}
```

There is no exit code, no signal handler and no config directory in that snippet, and none in
this crate either. Enrollment hands the code back as a value, `Account` hands rotations back
through a callback, and when the process stops is the program's decision.

## What this crate is

One dependency for the whole stack. The runtime is re-exported at the root, and each layer above it
is a module behind a feature:

| Path | Feature | Crate | What it is |
|---|---|---|---|
| `zyris::*` | — | [`zyris-core`]https://crates.io/crates/zyris-core | The runtime: connection state machine, transports, announce/accept, the dial-and-reconnect loop, device-grant enrollment. |
| `zyris::proto` | — | [`zyris-proto`]https://crates.io/crates/zyris-proto | Wire types only. No I/O, no async. |
| `zyris::caps` | `caps` | [`zyris-caps`]https://crates.io/crates/zyris-caps | The standard capability catalogue. Declarations only. |
| `zyris::p2p` | `p2p` | [`zyris-p2p`]https://crates.io/crates/zyris-p2p | Transport that carries Zyris directly between two nodes. |

Each is also a crate of its own. Depending on one directly *and* on this one is not a mistake and
links nothing twice — the module here **is** that crate.

## Features

`default = ["client", "hostname"]` — the same default as `zyris-core`, so `cargo add zyris` costs
what depending on the runtime alone does.

- **Runtime:** `client`, `enroll`, `hostname`, `axum`, `testing`
- **TLS:** `tls-ring` or `tls-aws-lc` — exactly one, and **without one this crate refuses to dial
  `wss://`**. `enroll` names `tls-ring` for you; `p2p` reaches one through iroh.
- **Stack:** `caps`, `p2p`, and `full` for both plus `enroll`

Neither TLS feature is on by default because both compile C: `ring` wants a working `cc`, and
`aws-lc-rs` wants `cmake` as well off its nine named targets. That keeps `cargo add zyris`
buildable on a machine with no toolchain — everything short of dialling works there. Rustls picks
its provider from crate features and *panics* when they name none, deferred to the first
connection with nothing at compile time to warn you; a build that skipped this gets
`ConnectError::NoTlsProvider` before a socket is opened instead. An application that calls
`rustls::crypto::CryptoProvider::install_default` itself is believed and needs neither feature.

**Implementations are not here.** The repository's `zyris-capkit` has reference ones and is not
published: a node decides what it offers, and a crate that pins a git fork of `enigo` could not go
to crates.io in any case. Depend on it by git when you want them.

## A capability

One trait. The macro turns it into the descriptor, a server wrapper, and a client:

```rust
#[zyris::capability(name = "hello", version = 1)]
pub trait Hello {
    /// Return a random friendly greeting, optionally addressed to `name`.
    async fn greet(&self, name: Option<String>) -> zyris::Result<Greeting>;
}
```

Doc comments become the tool and field descriptions a model reads, so write them for the model.

## Where to start

[`examples/hello.rs`](examples/hello.rs) is the whole library in one runnable file: declare a
capability, take a node, serve it, answer "Hello World" when a model calls it.

```bash
cargo run -p zyris --example hello --features enroll
```

For a node with more in it — a credential stored on disk, file transfer, screen capture and input
— read [`ridanit-ruma/zyris-hello`](https://github.com/ridanit-ruma/zyris-hello).

[`docs/zyris-protocol.md`](https://github.com/attacca-cc/zyris-protocol/blob/main/docs/zyris-protocol.md)
is the normative wire reference.

## License

MIT or Apache-2.0, at your option.