# Feno

**Mesh daemon and cluster runtime** for the Feno stack: encrypted peer-to-peer links between nodes, HTTP API, model proxy with failover, task handoff, remote tool execution, and ACP-backed agent sessions. The web dashboard (`apps/web`) consumes the same wire protocol.

## Requirements

- **[Bun](https://bun.sh)** — runtime for the published CLI (`feno` entry is TypeScript with a Bun shebang).
- **TypeScript** `^5` — peer dependency for type-aware tooling in host projects.

Native addons (for example `better-sqlite3`, `node-pty`) are built for your platform on `bun install` / `npm install`.

## Install

### From npm (after publish)

```bash
npm install feno
# or
bun add feno
```

Global CLI (optional):

```bash
npm install -g feno
# ensure `bun` is on PATH — the `feno` bin executes Bun
feno --help
```

### From source (monorepo)

```bash
git clone https://github.com/ruiming/mono.git
cd mono
bun install
bun --filter feno test
```

## Usage

### Standalone daemon (CLI)

First-time setup writes `~/.feno/config.json`:

```bash
bun run src/cli/main.ts setup
# or, if `feno` is on PATH:
feno setup
```

Start the daemon (foreground):

```bash
feno start
# short form: no subcommand → setup if missing config, else start
feno
```

Background:

```bash
feno start --detach
```

Other useful commands:

| Command | Purpose |
|--------|---------|
| `feno status [-w] [--json]` | Running state (watch mode with `-w`, NDJSON with `--json`) |
| `feno stop` | Stop daemon |
| `feno pair [--install\|--auto-approve]` | Device pairing (QR code / install script) |
| `feno join <url>` | Join a cluster from a pairing URL |
| `feno relay start` | Self-hosted relay |
| `feno claude` | Claude Code integration |
| `feno agents` | List registered agents |
| `feno setup --yes` | Non-interactive setup (auto-detect and write defaults) |
| `feno setup-status` | Agent installation status |
| `feno check --node <id\|name\|prefix\|tags:X>` | Reachability check (exit 8 if unreachable) |
| `feno peer list` | Peer connections |
| `feno sessions prompt --node ... --agent ... --task ...` | Send a prompt (`--json` for NDJSON) |
| `feno tools call --tool X --params-file payload.json` | Invoke a remote tool (accepts `-` for stdin) |
| `feno models list` | Cluster models |
| `feno config reload` | Hot-reload config |
| `feno availability` | Node uptime stats |
| `feno help-json [--flat]` | Full command tree (auto-derived JSON schema) |
| `feno meta` | Runtime metadata + exit-code table |
| `feno completion bash\|zsh\|fish` | Shell completion script |

Run `feno --help` for a full list or `feno <command> --help` for details.

### AI / scripted consumer notes

All commands support two output modes:

| Mode | Trigger | Shape |
|------|---------|-------|
| Human | default | plain text / pretty-JSON to stdout, progress to stderr |
| JSON | `--json` flag **or** `FENO_JSON=1` env var | `{ok:true,data}` / `{ok:false,error:{code,message}}` envelope; NDJSON for streams |

Exit codes are categorized so callers can branch without parsing text:

| Code | Meaning |
|------|---------|
| `0` | OK |
| `1` | Generic failure |
| `2` | Validation (bad flag / malformed JSON / missing required arg) |
| `3` | Daemon not running |
| `4` | Remote / agent reported an error |
| `5` | Daemon responded with HTTP 4xx |
| `6` | Daemon responded with HTTP 5xx |
| `7` | Request timed out |
| `8` | Target unreachable (`feno check`) |

Run `feno meta` at any time to pull the full table plus default ports and config paths.

Long payloads can be capped by setting `FENO_MAX_OUTPUT=<bytes>` — excess content is truncated with a tail marker noting how many bytes were dropped.

Shell-escape-free JSON inputs: commands that take inline JSON (`tools call --params`, `tools batch --items`, `automation save --json`) also accept a companion `--*-file <path>` flag (pass `-` to read stdin).

## How it works (architecture)

### Layered runtime

The codebase is split so dependencies flow inward:

1. **`core/`** — Config (Zod), protocol types (discriminated unions), errors, events, logging. No imports from other layers.
2. **`transport/`** — WebSocket transport, handshake, heartbeat, **E2EE** (X25519 ECDH, encrypt/decrypt), HMAC auth, node identity (TOFU-style keys).
3. **`mesh/`** — Peer manager, routing table, relay and deduplication, **mDNS + gossip discovery**, peer approval, rate limits.
4. **`persistence/`** — SQLite (versioned schema), replication and audit trails.
5. **`services/`** — **ClusterRuntime** (composition / DI): handoff, model proxy (circuit breaker / failover), tool proxy (local + remote), **ACP** session pool and adapters for multiple agent backends, optional native **channels** (Codex, Claude) on top of ACP.
6. **`api/`** — HTTP server and route modules (agents, ACP, config, approvals, etc.).

Design highlights: grouped dispatch instead of a giant handler registry; each service owns its pending state; unified error frames and API `{ ok, data?, error? }` shape; local ACP fast path when the target is the same node.

### Mesh and security

- Peers connect over WebSocket after an authenticated handshake; payloads for session traffic use end-to-end encryption derived from long-lived node keys.
- New peers can be gated via **approval** flows; discovery can use LAN mDNS and cluster gossip, plus optional **relay** for NAT traversal scenarios.

### ACP and agents

Session lifecycle follows **[ACP](https://agentclientprotocol.com/)**: prompt turns, tool calls, and transcripts. The daemon runs and coordinates ACP agent processes; the proxy can send work to a **remote** node’s agent when routing says so. Optional channel implementations reduce latency for specific CLIs while ACP remains the contract for history and tooling.

### Related (within the mono workspace)

- **`apps/web/`** — desktop SPA (RsBuild + React 19) consuming the same daemon API.
- **`apps/relay/`** — Cloudflare Worker / standalone Bun relay forwarding E2EE ciphertext between nodes on different networks.
- **`packages/sandbox/`** — virtual Ubuntu + XFCE desktop plugin that joins the mesh and exposes `computer_*` tools.

## Development

Run from the mono root:

```bash
bun install
bun --filter feno test
bun --filter feno coverage
bun --filter feno watch    # daemon hot-reload
```

## Publishing (maintainers)

- **Version bump**: `bun --filter feno release` (uses [bumpp](https://github.com/antfu-collective/bumpp)).
- **Checks before registry publish**: `bun --filter feno prepublishOnly` runs core tests + [publint](https://publint.dev/) on `feno`.
- **Inspect tarball contents**: `bun --filter feno pack:check` (dry-run `npm pack`).
- **CI**: pushing a GitHub **Release** triggers `.github/workflows/publish-npm.yml` if `NPM_TOKEN` is configured in repository secrets.

## License

MIT
