agent-relay 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/LICENSE +22 -0
- package/PROTOCOL.md +319 -0
- package/README.md +791 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +1591 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/daemon/connection.d.ts +60 -0
- package/dist/daemon/connection.d.ts.map +1 -0
- package/dist/daemon/connection.js +245 -0
- package/dist/daemon/connection.js.map +1 -0
- package/dist/daemon/index.d.ts +4 -0
- package/dist/daemon/index.d.ts.map +1 -0
- package/dist/daemon/index.js +4 -0
- package/dist/daemon/index.js.map +1 -0
- package/dist/daemon/router.d.ts +72 -0
- package/dist/daemon/router.d.ts.map +1 -0
- package/dist/daemon/router.js +183 -0
- package/dist/daemon/router.js.map +1 -0
- package/dist/daemon/server.d.ts +52 -0
- package/dist/daemon/server.d.ts.map +1 -0
- package/dist/daemon/server.js +186 -0
- package/dist/daemon/server.js.map +1 -0
- package/dist/dashboard/public/index.html +690 -0
- package/dist/dashboard/server.d.ts +2 -0
- package/dist/dashboard/server.d.ts.map +1 -0
- package/dist/dashboard/server.js +220 -0
- package/dist/dashboard/server.js.map +1 -0
- package/dist/games/index.d.ts +2 -0
- package/dist/games/index.d.ts.map +1 -0
- package/dist/games/index.js +2 -0
- package/dist/games/index.js.map +1 -0
- package/dist/games/tictactoe.d.ts +24 -0
- package/dist/games/tictactoe.d.ts.map +1 -0
- package/dist/games/tictactoe.js +160 -0
- package/dist/games/tictactoe.js.map +1 -0
- package/dist/hooks/inbox-check/hook.d.ts +28 -0
- package/dist/hooks/inbox-check/hook.d.ts.map +1 -0
- package/dist/hooks/inbox-check/hook.js +97 -0
- package/dist/hooks/inbox-check/hook.js.map +1 -0
- package/dist/hooks/inbox-check/index.d.ts +8 -0
- package/dist/hooks/inbox-check/index.d.ts.map +1 -0
- package/dist/hooks/inbox-check/index.js +8 -0
- package/dist/hooks/inbox-check/index.js.map +1 -0
- package/dist/hooks/inbox-check/types.d.ts +31 -0
- package/dist/hooks/inbox-check/types.d.ts.map +1 -0
- package/dist/hooks/inbox-check/types.js +5 -0
- package/dist/hooks/inbox-check/types.js.map +1 -0
- package/dist/hooks/inbox-check/utils.d.ts +44 -0
- package/dist/hooks/inbox-check/utils.d.ts.map +1 -0
- package/dist/hooks/inbox-check/utils.js +107 -0
- package/dist/hooks/inbox-check/utils.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol/framing.d.ts +32 -0
- package/dist/protocol/framing.d.ts.map +1 -0
- package/dist/protocol/framing.js +71 -0
- package/dist/protocol/framing.js.map +1 -0
- package/dist/protocol/index.d.ts +3 -0
- package/dist/protocol/index.d.ts.map +1 -0
- package/dist/protocol/index.js +3 -0
- package/dist/protocol/index.js.map +1 -0
- package/dist/protocol/types.d.ts +104 -0
- package/dist/protocol/types.d.ts.map +1 -0
- package/dist/protocol/types.js +6 -0
- package/dist/protocol/types.js.map +1 -0
- package/dist/state/agent-state.d.ts +40 -0
- package/dist/state/agent-state.d.ts.map +1 -0
- package/dist/state/agent-state.js +120 -0
- package/dist/state/agent-state.js.map +1 -0
- package/dist/storage/adapter.d.ts +29 -0
- package/dist/storage/adapter.d.ts.map +1 -0
- package/dist/storage/adapter.js +2 -0
- package/dist/storage/adapter.js.map +1 -0
- package/dist/storage/sqlite-adapter.d.ts +15 -0
- package/dist/storage/sqlite-adapter.d.ts.map +1 -0
- package/dist/storage/sqlite-adapter.js +116 -0
- package/dist/storage/sqlite-adapter.js.map +1 -0
- package/dist/supervisor/inbox.d.ts +38 -0
- package/dist/supervisor/inbox.d.ts.map +1 -0
- package/dist/supervisor/inbox.js +162 -0
- package/dist/supervisor/inbox.js.map +1 -0
- package/dist/supervisor/index.d.ts +10 -0
- package/dist/supervisor/index.d.ts.map +1 -0
- package/dist/supervisor/index.js +10 -0
- package/dist/supervisor/index.js.map +1 -0
- package/dist/supervisor/spawner.d.ts +54 -0
- package/dist/supervisor/spawner.d.ts.map +1 -0
- package/dist/supervisor/spawner.js +282 -0
- package/dist/supervisor/spawner.js.map +1 -0
- package/dist/supervisor/state.d.ts +132 -0
- package/dist/supervisor/state.d.ts.map +1 -0
- package/dist/supervisor/state.js +465 -0
- package/dist/supervisor/state.js.map +1 -0
- package/dist/supervisor/supervisor.d.ts +67 -0
- package/dist/supervisor/supervisor.d.ts.map +1 -0
- package/dist/supervisor/supervisor.js +263 -0
- package/dist/supervisor/supervisor.js.map +1 -0
- package/dist/supervisor/types.d.ts +139 -0
- package/dist/supervisor/types.d.ts.map +1 -0
- package/dist/supervisor/types.js +12 -0
- package/dist/supervisor/types.js.map +1 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +2 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/name-generator.d.ts +17 -0
- package/dist/utils/name-generator.d.ts.map +1 -0
- package/dist/utils/name-generator.js +52 -0
- package/dist/utils/name-generator.js.map +1 -0
- package/dist/webhook/spawner.d.ts +79 -0
- package/dist/webhook/spawner.d.ts.map +1 -0
- package/dist/webhook/spawner.js +288 -0
- package/dist/webhook/spawner.js.map +1 -0
- package/dist/wrapper/client.d.ts +72 -0
- package/dist/wrapper/client.d.ts.map +1 -0
- package/dist/wrapper/client.js +306 -0
- package/dist/wrapper/client.js.map +1 -0
- package/dist/wrapper/inbox.d.ts +37 -0
- package/dist/wrapper/inbox.d.ts.map +1 -0
- package/dist/wrapper/inbox.js +73 -0
- package/dist/wrapper/inbox.js.map +1 -0
- package/dist/wrapper/index.d.ts +4 -0
- package/dist/wrapper/index.d.ts.map +1 -0
- package/dist/wrapper/index.js +7 -0
- package/dist/wrapper/index.js.map +1 -0
- package/dist/wrapper/parser.d.ts +94 -0
- package/dist/wrapper/parser.d.ts.map +1 -0
- package/dist/wrapper/parser.js +360 -0
- package/dist/wrapper/parser.js.map +1 -0
- package/dist/wrapper/pty-wrapper.d.ts +125 -0
- package/dist/wrapper/pty-wrapper.d.ts.map +1 -0
- package/dist/wrapper/pty-wrapper.js +494 -0
- package/dist/wrapper/pty-wrapper.js.map +1 -0
- package/dist/wrapper/tmux-wrapper.d.ts +131 -0
- package/dist/wrapper/tmux-wrapper.d.ts.map +1 -0
- package/dist/wrapper/tmux-wrapper.js +427 -0
- package/dist/wrapper/tmux-wrapper.js.map +1 -0
- package/install.sh +69 -0
- package/package.json +82 -0
package/README.md
ADDED
|
@@ -0,0 +1,791 @@
|
|
|
1
|
+
# agent-relay
|
|
2
|
+
|
|
3
|
+
[](https://github.com/khaliqgant/agent-relay/actions/workflows/test.yml)
|
|
4
|
+
[](https://codecov.io/gh/khaliqgant/agent-relay)
|
|
5
|
+
|
|
6
|
+
Real-time agent-to-agent communication system. Enables AI agents (Claude, Codex, Gemini, etc.) running in separate terminals to communicate with sub-millisecond latency.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
### One-Line Install (Recommended)
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
curl -fsSL https://raw.githubusercontent.com/khaliqgant/agent-relay/main/install.sh | bash
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
This installs to `~/.agent-relay` and adds `agent-relay` to your PATH.
|
|
17
|
+
|
|
18
|
+
### Install Options
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# Custom install directory
|
|
22
|
+
AGENT_RELAY_DIR=/opt/agent-relay curl -fsSL https://...install.sh | bash
|
|
23
|
+
|
|
24
|
+
# Install and start daemon immediately
|
|
25
|
+
AGENT_RELAY_START=true curl -fsSL https://...install.sh | bash
|
|
26
|
+
|
|
27
|
+
# Quiet mode (for agents/scripts)
|
|
28
|
+
AGENT_RELAY_QUIET=true curl -fsSL https://...install.sh | bash
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Manual Install
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
git clone https://github.com/khaliqgant/agent-relay.git
|
|
35
|
+
cd agent-relay
|
|
36
|
+
npm install
|
|
37
|
+
npm run build
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Requirements
|
|
41
|
+
|
|
42
|
+
- Node.js >= 18 (20+ recommended)
|
|
43
|
+
- macOS or Linux (Unix domain sockets)
|
|
44
|
+
|
|
45
|
+
## Troubleshooting
|
|
46
|
+
|
|
47
|
+
### `node-pty` / native module errors
|
|
48
|
+
|
|
49
|
+
If you see errors like `NODE_MODULE_VERSION ...` or `compiled against a different Node.js version`, rebuild native deps:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npm rebuild node-pty
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### `listen EPERM: operation not permitted`
|
|
56
|
+
|
|
57
|
+
If your environment restricts creating sockets under `/tmp` (some sandboxes/containers do), pick a socket path you can write to:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npx agent-relay start -f -s ./agent-relay.sock
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Why We Built This
|
|
64
|
+
|
|
65
|
+
As AI agents become more capable, there's a growing need for them to collaborate in real-time. Imagine multiple agents working together on a codebase, coordinating tasks, or even playing games against each other—all without human intervention.
|
|
66
|
+
|
|
67
|
+
**The problem:** How do you get agents running in separate terminal sessions to talk to each other seamlessly?
|
|
68
|
+
|
|
69
|
+
## For Humans: When You’d Use agent-relay
|
|
70
|
+
|
|
71
|
+
Use agent-relay when you want **fast, local, real-time coordination** between multiple CLI-based agents without adopting a larger framework.
|
|
72
|
+
|
|
73
|
+
Common scenarios:
|
|
74
|
+
- **Multi-terminal agent swarms** where each agent runs in its own terminal and needs to exchange messages quickly.
|
|
75
|
+
- **Turn-based / tight-loop coordination** (games, schedulers, orchestrators) where polling latency becomes noticeable.
|
|
76
|
+
- **“Wrap anything” workflows** where you don’t control the agent implementation but you can run it as a CLI process.
|
|
77
|
+
|
|
78
|
+
Tradeoffs to know up front:
|
|
79
|
+
- Local IPC only (Unix domain sockets); no cross-host networking.
|
|
80
|
+
- Best-effort delivery today (no persistence/guaranteed retries yet).
|
|
81
|
+
|
|
82
|
+
### Existing Solutions (and why they're great)
|
|
83
|
+
|
|
84
|
+
We built agent-relay with deep respect for existing solutions that inspired this work:
|
|
85
|
+
|
|
86
|
+
#### [mcp_agent_mail](https://github.com/Dicklesworthstone/mcp_agent_mail)
|
|
87
|
+
A thoughtful MCP-based agent communication system. Great features like auto-generated agent names (AdjectiveNoun format), file reservations, and Git-backed message persistence. If you're already in the MCP ecosystem, this is an excellent choice.
|
|
88
|
+
|
|
89
|
+
**Why choose agent-relay over mcp_agent_mail:** When you specifically want **low-latency, real-time, local IPC** and a **PTY wrapper** that can intercept output from *any* CLI agent without requiring MCP integration.
|
|
90
|
+
|
|
91
|
+
**Why choose mcp_agent_mail instead:** When you want **message persistence/auditability**, **file reservations**, and a workflow already built around MCP-style tooling.
|
|
92
|
+
|
|
93
|
+
#### [swarm-tools/swarm-mail](https://github.com/joelhooks/swarm-tools/tree/main/packages/swarm-mail)
|
|
94
|
+
Part of the swarm-tools ecosystem, providing inter-agent messaging. Well-designed for swarm coordination patterns.
|
|
95
|
+
|
|
96
|
+
**Why choose agent-relay over swarm-mail:** When you want **push-style delivery** and sub-second responsiveness; file-based polling can be great for robustness, but it’s not ideal for tight coordination loops.
|
|
97
|
+
|
|
98
|
+
**Why choose swarm-mail instead:** When you prefer **filesystem-backed messaging** (easy inspection, simple operations) and millisecond-level latency isn’t a requirement.
|
|
99
|
+
|
|
100
|
+
### Our Approach
|
|
101
|
+
|
|
102
|
+
agent-relay takes a different path:
|
|
103
|
+
- **Unix domain sockets** for sub-5ms latency
|
|
104
|
+
- **PTY wrapper** that works with any CLI (Claude, Codex, Gemini, etc.)
|
|
105
|
+
- **No protocol dependencies** - just wrap your command and go
|
|
106
|
+
- **Pattern detection** in terminal output (`@relay:` syntax)
|
|
107
|
+
- **Built-in game support** as a proof-of-concept for real-time coordination
|
|
108
|
+
|
|
109
|
+
## Features
|
|
110
|
+
|
|
111
|
+
- **Real-time messaging** via Unix domain sockets (<5ms latency)
|
|
112
|
+
- **PTY wrapper** for any CLI agent (Claude Code, Codex CLI, Gemini CLI)
|
|
113
|
+
- **Auto-generated agent names** (AdjectiveNoun format, like mcp_agent_mail)
|
|
114
|
+
- **Best-effort delivery** with per-stream ordering (ACK protocol defined, reliability optional)
|
|
115
|
+
- **Topic-based pub/sub** for game coordination and channels
|
|
116
|
+
- **Hearts game engine** as proof-of-concept for multi-agent interaction (see `src/games/hearts.ts`)
|
|
117
|
+
|
|
118
|
+
## Quick Start
|
|
119
|
+
|
|
120
|
+
### Option 1: One-Line Install (Recommended)
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
# Install agent-relay
|
|
124
|
+
curl -fsSL https://raw.githubusercontent.com/khaliqgant/agent-relay/main/install.sh | bash
|
|
125
|
+
|
|
126
|
+
# Start the daemon
|
|
127
|
+
agent-relay start -f
|
|
128
|
+
|
|
129
|
+
# In another terminal, wrap an agent (name auto-generated)
|
|
130
|
+
agent-relay wrap "claude"
|
|
131
|
+
# Output: Agent name: SilverMountain
|
|
132
|
+
|
|
133
|
+
# In another terminal, wrap another agent
|
|
134
|
+
agent-relay wrap "codex"
|
|
135
|
+
# Output: Agent name: BlueFox
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Option 2: From Source
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
git clone https://github.com/khaliqgant/agent-relay.git
|
|
142
|
+
cd agent-relay
|
|
143
|
+
npm install && npm run build
|
|
144
|
+
|
|
145
|
+
# Start the daemon
|
|
146
|
+
npx agent-relay start -f
|
|
147
|
+
|
|
148
|
+
# In another terminal, wrap an agent
|
|
149
|
+
npx agent-relay wrap "claude"
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Sending Messages Between Agents
|
|
153
|
+
|
|
154
|
+
Once agents are wrapped, they can send messages to each other:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
# Direct message (from agent terminal)
|
|
158
|
+
@relay:BlueFox Hello from SilverMountain!
|
|
159
|
+
|
|
160
|
+
# Broadcast to all agents
|
|
161
|
+
@relay:* Anyone online?
|
|
162
|
+
|
|
163
|
+
# Messages appear in recipient's terminal as:
|
|
164
|
+
# [MSG] from SilverMountain: Hello from SilverMountain!
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Enable Your Agents
|
|
168
|
+
|
|
169
|
+
Copy [`AGENTS.md`](./AGENTS.md) to your project so AI agents know how to use agent-relay:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
curl -fsSL https://raw.githubusercontent.com/khaliqgant/agent-relay/main/AGENTS.md > AGENTS.md
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
This file contains instructions that AI agents can read to learn how to send/receive messages.
|
|
176
|
+
|
|
177
|
+
## Common Use Cases
|
|
178
|
+
|
|
179
|
+
### 1. Pair Programming: Code + Review
|
|
180
|
+
|
|
181
|
+
Two agents collaborating on a codebase - one writes code, the other reviews:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
# Terminal 1: Start daemon
|
|
185
|
+
agent-relay start -f
|
|
186
|
+
|
|
187
|
+
# Terminal 2: Code writer agent
|
|
188
|
+
agent-relay wrap -n Coder "claude"
|
|
189
|
+
# Agent starts working, then sends:
|
|
190
|
+
# @relay:Reviewer I've implemented the auth module. Please review src/auth.ts
|
|
191
|
+
|
|
192
|
+
# Terminal 3: Reviewer agent
|
|
193
|
+
agent-relay wrap -n Reviewer "claude"
|
|
194
|
+
# Receives the message and reviews the code
|
|
195
|
+
# @relay:Coder Found an issue in line 45: missing input validation
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### 2. Multi-Agent Task Distribution
|
|
199
|
+
|
|
200
|
+
A coordinator distributing tasks to worker agents:
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
# Set up workers with file-based inboxes
|
|
204
|
+
mkdir -p /tmp/workers
|
|
205
|
+
agent-relay inbox-write -t Worker1 -f Coordinator -m "Process files in /data/batch1" -d /tmp/workers
|
|
206
|
+
agent-relay inbox-write -t Worker2 -f Coordinator -m "Process files in /data/batch2" -d /tmp/workers
|
|
207
|
+
|
|
208
|
+
# Each worker polls their inbox
|
|
209
|
+
agent-relay inbox-poll -n Worker1 -d /tmp/workers --clear
|
|
210
|
+
# Worker1 sees: Process files in /data/batch1
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### 3. Turn-Based Game (Tic-Tac-Toe)
|
|
214
|
+
|
|
215
|
+
Two agents playing a game with coordinated turns:
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
# Quick setup
|
|
219
|
+
agent-relay tictactoe-setup -d /tmp/ttt --player-x AgentX --player-o AgentO
|
|
220
|
+
|
|
221
|
+
# Terminal 1: Player X reads instructions
|
|
222
|
+
cat /tmp/ttt/AgentX/INSTRUCTIONS.md
|
|
223
|
+
|
|
224
|
+
# Terminal 2: Player O reads instructions
|
|
225
|
+
cat /tmp/ttt/AgentO/INSTRUCTIONS.md
|
|
226
|
+
|
|
227
|
+
# Agents communicate moves via inbox:
|
|
228
|
+
agent-relay inbox-write -t AgentO -f AgentX -m "MOVE: center" -d /tmp/ttt
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### 4. Live Collaboration Session
|
|
232
|
+
|
|
233
|
+
Multiple agents working together with real-time socket communication:
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
# Start daemon
|
|
237
|
+
agent-relay start -f
|
|
238
|
+
|
|
239
|
+
# Wrap multiple agents (3 terminals)
|
|
240
|
+
agent-relay wrap "claude" # -> GreenLake
|
|
241
|
+
agent-relay wrap "codex" # -> BlueRiver
|
|
242
|
+
agent-relay wrap "gemini-cli" # -> RedMountain
|
|
243
|
+
|
|
244
|
+
# Any agent can message others:
|
|
245
|
+
# @relay:BlueRiver Can you handle the database migration?
|
|
246
|
+
# @relay:* I'm starting on the frontend components
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
> **More examples:** See the [`examples/`](./examples) directory for complete working examples including setup scripts.
|
|
250
|
+
|
|
251
|
+
## Architecture
|
|
252
|
+
|
|
253
|
+
```
|
|
254
|
+
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
|
255
|
+
│ Claude Agent │ │ Codex Agent │ │ Gemini Agent │
|
|
256
|
+
│ (Terminal 1) │ │ (Terminal 2) │ │ (Terminal 3) │
|
|
257
|
+
└────────┬────────┘ └────────┬────────┘ └────────┬────────┘
|
|
258
|
+
│ │ │
|
|
259
|
+
┌────┴────┐ ┌────┴────┐ ┌────┴────┐
|
|
260
|
+
│ Wrapper │ │ Wrapper │ │ Wrapper │
|
|
261
|
+
└────┬────┘ └────┬────┘ └────┬────┘
|
|
262
|
+
│ │ │
|
|
263
|
+
└───────────────────────┼───────────────────────┘
|
|
264
|
+
│
|
|
265
|
+
Unix Domain Socket
|
|
266
|
+
│
|
|
267
|
+
┌────────────┴────────────┐
|
|
268
|
+
│ agent-relay daemon │
|
|
269
|
+
│ - Message Router │
|
|
270
|
+
│ - Topic Subscriptions │
|
|
271
|
+
│ - Game Coordinator │
|
|
272
|
+
└─────────────────────────┘
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## Agent Communication Syntax
|
|
276
|
+
|
|
277
|
+
Agents communicate using two formats embedded in their terminal output:
|
|
278
|
+
|
|
279
|
+
### Inline Format (single line)
|
|
280
|
+
```
|
|
281
|
+
@relay:BlueFox Your turn to play the 7 of hearts
|
|
282
|
+
@relay:* Broadcasting to all agents
|
|
283
|
+
@thinking:* I'm considering playing the Queen...
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Block Format (structured JSON)
|
|
287
|
+
```
|
|
288
|
+
[[RELAY]]
|
|
289
|
+
{
|
|
290
|
+
"to": "BlueFox",
|
|
291
|
+
"type": "action",
|
|
292
|
+
"body": "Playing my card",
|
|
293
|
+
"data": { "card": "7♥", "action": "play_card" }
|
|
294
|
+
}
|
|
295
|
+
[[/RELAY]]
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Escaping
|
|
299
|
+
To output literal `@relay:` without triggering the parser:
|
|
300
|
+
```
|
|
301
|
+
\@relay: This won't be parsed as a command
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
## CLI Commands
|
|
305
|
+
|
|
306
|
+
```bash
|
|
307
|
+
# Start the relay daemon (foreground)
|
|
308
|
+
npx agent-relay start -f
|
|
309
|
+
|
|
310
|
+
# Start daemon with custom socket path
|
|
311
|
+
npx agent-relay start -s /tmp/my-relay.sock
|
|
312
|
+
|
|
313
|
+
# Stop the daemon
|
|
314
|
+
npx agent-relay stop
|
|
315
|
+
|
|
316
|
+
# Wrap an agent (name auto-generated)
|
|
317
|
+
npx agent-relay wrap "claude"
|
|
318
|
+
|
|
319
|
+
# Wrap an agent with explicit name
|
|
320
|
+
npx agent-relay wrap -n my-agent "claude"
|
|
321
|
+
|
|
322
|
+
# Wrap with legacy PTY mode (if needed)
|
|
323
|
+
npx agent-relay wrap --pty -n PlayerX "claude"
|
|
324
|
+
|
|
325
|
+
# Check status
|
|
326
|
+
npx agent-relay status
|
|
327
|
+
|
|
328
|
+
# Send a test message
|
|
329
|
+
npx agent-relay send -t recipient -m "Hello"
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### Tmux Mode (Default)
|
|
333
|
+
|
|
334
|
+
By default, `agent-relay wrap` uses tmux mode for better stability in multi-agent coordination:
|
|
335
|
+
|
|
336
|
+
```bash
|
|
337
|
+
# Terminal 1: Start daemon
|
|
338
|
+
agent-relay start -f
|
|
339
|
+
|
|
340
|
+
# Terminal 2: Start first agent (tmux mode is default)
|
|
341
|
+
agent-relay wrap -n PlayerX "claude"
|
|
342
|
+
|
|
343
|
+
# Terminal 3: Start second agent
|
|
344
|
+
agent-relay wrap -n PlayerO "codex"
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
**How it works:**
|
|
348
|
+
- Creates a detached tmux session for each agent
|
|
349
|
+
- Attaches your terminal directly to the session
|
|
350
|
+
- Background polling captures output and parses `@relay:` commands
|
|
351
|
+
- Incoming messages are injected via `tmux send-keys`
|
|
352
|
+
|
|
353
|
+
**Tuning flags:**
|
|
354
|
+
- `-q, --quiet` to silence debug logs (stderr)
|
|
355
|
+
- `--log-interval <ms>` to throttle debug output
|
|
356
|
+
- `--inject-idle-ms <ms>` to change the idle window before injecting messages (default 1500ms)
|
|
357
|
+
- `--inject-retry-ms <ms>` to adjust how often we re-check for an idle window (default 500ms)
|
|
358
|
+
|
|
359
|
+
**Legacy PTY mode:**
|
|
360
|
+
If you need the old direct PTY mode, use the `--pty` flag:
|
|
361
|
+
```bash
|
|
362
|
+
agent-relay wrap --pty -n MyAgent "claude"
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
**Scrolling in tmux:**
|
|
366
|
+
|
|
367
|
+
By default, scroll wheel is sent to the application inside tmux. To scroll through history:
|
|
368
|
+
|
|
369
|
+
1. **Enter copy mode**: Press `Ctrl+b` then `[`
|
|
370
|
+
2. Scroll with arrow keys, Page Up/Down, or mouse wheel
|
|
371
|
+
3. Press `q` to exit copy mode
|
|
372
|
+
|
|
373
|
+
**Or enable mouse scrolling** (add to `~/.tmux.conf`):
|
|
374
|
+
```bash
|
|
375
|
+
set -g mouse on
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
Then reload: `tmux source-file ~/.tmux.conf`
|
|
379
|
+
|
|
380
|
+
**Compatibility:** Works with any CLI that accepts text input (Claude, Codex, Gemini, etc.)
|
|
381
|
+
|
|
382
|
+
### File-Based Inbox Commands
|
|
383
|
+
|
|
384
|
+
For scenarios where PTY wrapping isn't ideal (scripts, automation, or agents that read files):
|
|
385
|
+
|
|
386
|
+
```bash
|
|
387
|
+
# Write to an agent's inbox (supports broadcast with *)
|
|
388
|
+
agent-relay inbox-write -t AgentName -f SenderName -m "Your message" -d /tmp/my-dir
|
|
389
|
+
agent-relay inbox-write -t "*" -f SenderName -m "Broadcast!" -d /tmp/my-dir
|
|
390
|
+
|
|
391
|
+
# Read an agent's inbox (non-blocking)
|
|
392
|
+
agent-relay inbox-read -n AgentName -d /tmp/my-dir
|
|
393
|
+
agent-relay inbox-read -n AgentName -d /tmp/my-dir --clear # Clear after reading
|
|
394
|
+
|
|
395
|
+
# Block until inbox has messages (useful for agent loops)
|
|
396
|
+
agent-relay inbox-poll -n AgentName -d /tmp/my-dir --clear
|
|
397
|
+
agent-relay inbox-poll -n AgentName -d /tmp/my-dir -t 30 # 30s timeout
|
|
398
|
+
|
|
399
|
+
# List all agents in a data directory
|
|
400
|
+
agent-relay inbox-agents -d /tmp/my-dir
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
**Inbox message format:**
|
|
404
|
+
```markdown
|
|
405
|
+
## Message from SenderName | 2024-01-15T10:30:00Z
|
|
406
|
+
Your message content here
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### Team Commands
|
|
410
|
+
|
|
411
|
+
For coordinating multiple agents working together on a project:
|
|
412
|
+
|
|
413
|
+
```bash
|
|
414
|
+
# Initialize a team workspace
|
|
415
|
+
agent-relay team-init -d /tmp/my-team -p /path/to/project -n "my-team"
|
|
416
|
+
|
|
417
|
+
# Set up a complete team from JSON config
|
|
418
|
+
agent-relay team-setup -f team-config.json -d /tmp/my-team
|
|
419
|
+
|
|
420
|
+
# Add an agent to the team
|
|
421
|
+
agent-relay team-add -n AgentName -r "Role description" -d /tmp/my-team
|
|
422
|
+
|
|
423
|
+
# List all agents in the team
|
|
424
|
+
agent-relay team-list -d /tmp/my-team
|
|
425
|
+
|
|
426
|
+
# Show team status with message counts
|
|
427
|
+
agent-relay team-status -d /tmp/my-team
|
|
428
|
+
|
|
429
|
+
# Send a message to teammate(s)
|
|
430
|
+
agent-relay team-send -n SenderName -t RecipientName -m "Hello" -d /tmp/my-team
|
|
431
|
+
agent-relay team-send -n SenderName -t "*" -m "Broadcast" -d /tmp/my-team
|
|
432
|
+
|
|
433
|
+
# Check your inbox (blocking wait)
|
|
434
|
+
agent-relay team-check -n AgentName -d /tmp/my-team
|
|
435
|
+
agent-relay team-check -n AgentName -d /tmp/my-team --no-wait # Non-blocking
|
|
436
|
+
|
|
437
|
+
# Join an existing team (self-register)
|
|
438
|
+
agent-relay team-join -n AgentName -r "Role" -d /tmp/my-team
|
|
439
|
+
|
|
440
|
+
# Start team with auto-spawning
|
|
441
|
+
agent-relay team-start -f team-config.json -d /tmp/my-team
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
**Team config JSON format:**
|
|
445
|
+
```json
|
|
446
|
+
{
|
|
447
|
+
"name": "my-project",
|
|
448
|
+
"project": "/path/to/project",
|
|
449
|
+
"agents": [
|
|
450
|
+
{"name": "Architect", "cli": "claude", "role": "Design Lead", "tasks": ["Design system"]}
|
|
451
|
+
]
|
|
452
|
+
}
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
### Supervisor Commands
|
|
456
|
+
|
|
457
|
+
For spawn-per-message agent management:
|
|
458
|
+
|
|
459
|
+
```bash
|
|
460
|
+
# Run the supervisor (foreground)
|
|
461
|
+
agent-relay supervisor -d /tmp/relay -v
|
|
462
|
+
|
|
463
|
+
# Run supervisor in background
|
|
464
|
+
agent-relay supervisor -d /tmp/relay --detach
|
|
465
|
+
|
|
466
|
+
# Check supervisor status
|
|
467
|
+
agent-relay supervisor-status
|
|
468
|
+
|
|
469
|
+
# Stop background supervisor
|
|
470
|
+
agent-relay supervisor-stop
|
|
471
|
+
|
|
472
|
+
# Register an agent with supervisor
|
|
473
|
+
agent-relay register -n AgentName -c "claude" -d /tmp/relay
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
### Dashboard
|
|
477
|
+
|
|
478
|
+
Web-based dashboard for monitoring agent communication:
|
|
479
|
+
|
|
480
|
+
```bash
|
|
481
|
+
# Start dashboard on default port (3888)
|
|
482
|
+
agent-relay dashboard -d /tmp/my-team
|
|
483
|
+
|
|
484
|
+
# Start on custom port
|
|
485
|
+
agent-relay dashboard -p 8080 -d /tmp/my-team
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
### Games
|
|
489
|
+
|
|
490
|
+
```bash
|
|
491
|
+
# Set up tic-tac-toe for two agents
|
|
492
|
+
agent-relay tictactoe-setup -d /tmp/ttt --player-x AgentX --player-o AgentO
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
## Playing Hearts
|
|
496
|
+
|
|
497
|
+
> **Note:** The Hearts game engine (`src/games/hearts.ts`) is implemented but not yet wired to the CLI. The code below shows the intended usage pattern once CLI integration is complete.
|
|
498
|
+
|
|
499
|
+
```bash
|
|
500
|
+
# Terminal 1: Start daemon
|
|
501
|
+
npx agent-relay start -f
|
|
502
|
+
|
|
503
|
+
# Terminal 2-5: Start four agent players (names auto-generated)
|
|
504
|
+
npx agent-relay wrap "claude" # -> GoldenEagle
|
|
505
|
+
npx agent-relay wrap "codex" # -> SilverWolf
|
|
506
|
+
npx agent-relay wrap "gemini-cli" # -> CrystalLake
|
|
507
|
+
npx agent-relay wrap "claude" # -> BronzeBear
|
|
508
|
+
|
|
509
|
+
# Game coordinator integration coming soon
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
Each agent receives game state and prompts:
|
|
513
|
+
```json
|
|
514
|
+
{
|
|
515
|
+
"kind": "your_turn",
|
|
516
|
+
"hand": ["2♣", "7♥", "K♠", ...],
|
|
517
|
+
"valid_plays": ["2♣"],
|
|
518
|
+
"current_trick": [],
|
|
519
|
+
"hearts_broken": false
|
|
520
|
+
}
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
Agents respond with:
|
|
524
|
+
```
|
|
525
|
+
[[RELAY]]
|
|
526
|
+
{"to": "hearts-coordinator", "type": "action", "data": {"action": "play_card", "card": {"suit": "clubs", "rank": "2"}}}
|
|
527
|
+
[[/RELAY]]
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
---
|
|
531
|
+
|
|
532
|
+
<details>
|
|
533
|
+
<summary><h2>For Agents (Click to expand)</h2></summary>
|
|
534
|
+
|
|
535
|
+
> **This section is specifically for AI agents setting up agent-relay autonomously.**
|
|
536
|
+
|
|
537
|
+
## Autonomous Setup Instructions
|
|
538
|
+
|
|
539
|
+
If you are an AI agent and need to set up agent-relay for inter-agent communication, follow these steps:
|
|
540
|
+
|
|
541
|
+
### Quick Setup (One Command)
|
|
542
|
+
|
|
543
|
+
```bash
|
|
544
|
+
# Install and start daemon in one command
|
|
545
|
+
AGENT_RELAY_START=true AGENT_RELAY_QUIET=true \
|
|
546
|
+
curl -fsSL https://raw.githubusercontent.com/khaliqgant/agent-relay/main/install.sh | bash
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
The installer outputs machine-readable variables:
|
|
550
|
+
```
|
|
551
|
+
AGENT_RELAY_STATUS=SUCCESS
|
|
552
|
+
AGENT_RELAY_INSTALL_DIR=/home/user/.agent-relay
|
|
553
|
+
AGENT_RELAY_BIN_PATH=/home/user/.local/bin/agent-relay
|
|
554
|
+
AGENT_RELAY_DAEMON_PID=12345
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
### Manual Setup (Step by Step)
|
|
558
|
+
|
|
559
|
+
#### 1. Check Prerequisites
|
|
560
|
+
|
|
561
|
+
```bash
|
|
562
|
+
# Verify Node.js >= 18 (>= 20 recommended)
|
|
563
|
+
node --version
|
|
564
|
+
|
|
565
|
+
# Verify npm
|
|
566
|
+
npm --version
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
#### 2. Clone and Install
|
|
570
|
+
|
|
571
|
+
```bash
|
|
572
|
+
# If not already in the project directory
|
|
573
|
+
cd /path/to/agent-to-agent-communication
|
|
574
|
+
|
|
575
|
+
# Install dependencies
|
|
576
|
+
npm install
|
|
577
|
+
|
|
578
|
+
# Build the project
|
|
579
|
+
npm run build
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
#### 3. Start the Daemon
|
|
583
|
+
|
|
584
|
+
```bash
|
|
585
|
+
# Start in background (recommended for automation)
|
|
586
|
+
nohup npx agent-relay start -f > /tmp/agent-relay.log 2>&1 &
|
|
587
|
+
|
|
588
|
+
# Or start in foreground if you want to monitor
|
|
589
|
+
npx agent-relay start -f
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
#### 4. Verify Daemon is Running
|
|
593
|
+
|
|
594
|
+
```bash
|
|
595
|
+
# Check status
|
|
596
|
+
npx agent-relay status
|
|
597
|
+
|
|
598
|
+
# Should output: Status: RUNNING
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
### 5. Connect as an Agent
|
|
602
|
+
|
|
603
|
+
If you're wrapping another agent (name auto-generated):
|
|
604
|
+
```bash
|
|
605
|
+
npx agent-relay wrap "claude"
|
|
606
|
+
# Your agent name will be displayed, e.g., "Agent name: SwiftFalcon"
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
Or specify your own name:
|
|
610
|
+
```bash
|
|
611
|
+
npx agent-relay wrap -n MyAgent "claude"
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
If you're connecting programmatically:
|
|
615
|
+
```typescript
|
|
616
|
+
import { RelayClient, generateAgentName } from 'agent-relay';
|
|
617
|
+
|
|
618
|
+
// Auto-generate a name
|
|
619
|
+
const agentName = generateAgentName(); // e.g., "BraveOtter"
|
|
620
|
+
|
|
621
|
+
const client = new RelayClient({
|
|
622
|
+
agentName,
|
|
623
|
+
socketPath: '/tmp/agent-relay.sock',
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
await client.connect();
|
|
627
|
+
|
|
628
|
+
// Send messages
|
|
629
|
+
client.sendMessage('other-agent', 'Hello!', 'message');
|
|
630
|
+
|
|
631
|
+
// Listen for messages
|
|
632
|
+
client.onMessage = (from, payload) => {
|
|
633
|
+
console.log(`Message from ${from}: ${payload.body}`);
|
|
634
|
+
};
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
## Communication Protocol for Agents
|
|
638
|
+
|
|
639
|
+
### Sending Messages
|
|
640
|
+
|
|
641
|
+
Use these patterns in your terminal output:
|
|
642
|
+
|
|
643
|
+
**Direct message to another agent:**
|
|
644
|
+
```
|
|
645
|
+
@relay:SwiftFalcon Your message here
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
**Broadcast to all agents:**
|
|
649
|
+
```
|
|
650
|
+
@relay:* Message for everyone
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
**Structured action (for games/coordination):**
|
|
654
|
+
```
|
|
655
|
+
[[RELAY]]
|
|
656
|
+
{"to": "*", "type": "action", "body": "description", "data": {"key": "value"}}
|
|
657
|
+
[[/RELAY]]
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
### Message Types
|
|
661
|
+
|
|
662
|
+
| Type | Use Case |
|
|
663
|
+
|------|----------|
|
|
664
|
+
| `message` | General communication |
|
|
665
|
+
| `action` | Game moves, commands |
|
|
666
|
+
| `state` | State updates, game state |
|
|
667
|
+
| `thinking` | Share reasoning (optional) |
|
|
668
|
+
|
|
669
|
+
### Receiving Messages
|
|
670
|
+
|
|
671
|
+
Messages from other agents appear in your terminal as:
|
|
672
|
+
```
|
|
673
|
+
[MSG] from SwiftFalcon: Their message
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
Or for thinking:
|
|
677
|
+
```
|
|
678
|
+
[THINKING] from SwiftFalcon: Their reasoning
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
## Coordination Patterns
|
|
682
|
+
|
|
683
|
+
### Turn-Based Games
|
|
684
|
+
|
|
685
|
+
1. Subscribe to game topic: `client.subscribe('hearts')`
|
|
686
|
+
2. Wait for `your_turn` state message
|
|
687
|
+
3. Respond with action: `@relay:coordinator {"action": "play_card", ...}`
|
|
688
|
+
4. Wait for next state update
|
|
689
|
+
|
|
690
|
+
### Collaborative Tasks
|
|
691
|
+
|
|
692
|
+
1. Broadcast availability: `@relay:* Ready to collaborate`
|
|
693
|
+
2. Direct message coordinator: `@relay:coordinator Taking task X`
|
|
694
|
+
3. Share progress: `@relay:* Completed task X`
|
|
695
|
+
|
|
696
|
+
### Error Handling
|
|
697
|
+
|
|
698
|
+
If connection fails:
|
|
699
|
+
1. Check daemon is running: `npx agent-relay status`
|
|
700
|
+
2. Check socket exists: `ls -la /tmp/agent-relay.sock`
|
|
701
|
+
3. Restart daemon if needed: `npx agent-relay stop && npx agent-relay start -f`
|
|
702
|
+
|
|
703
|
+
## Example: Agent Self-Registration
|
|
704
|
+
|
|
705
|
+
```typescript
|
|
706
|
+
import { RelayClient, generateAgentName } from 'agent-relay';
|
|
707
|
+
|
|
708
|
+
async function setupAgent() {
|
|
709
|
+
const name = generateAgentName();
|
|
710
|
+
const client = new RelayClient({ agentName: name });
|
|
711
|
+
|
|
712
|
+
try {
|
|
713
|
+
await client.connect();
|
|
714
|
+
console.log(`Connected as ${name}`);
|
|
715
|
+
|
|
716
|
+
// Announce presence
|
|
717
|
+
client.broadcast(`${name} is online`, 'message');
|
|
718
|
+
|
|
719
|
+
// Handle incoming messages
|
|
720
|
+
client.onMessage = (from, payload) => {
|
|
721
|
+
if (payload.body.includes('ping')) {
|
|
722
|
+
client.sendMessage(from, 'pong', 'message');
|
|
723
|
+
}
|
|
724
|
+
};
|
|
725
|
+
|
|
726
|
+
return client;
|
|
727
|
+
} catch (err) {
|
|
728
|
+
console.error('Failed to connect:', err);
|
|
729
|
+
throw err;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
```
|
|
733
|
+
|
|
734
|
+
## Troubleshooting
|
|
735
|
+
|
|
736
|
+
| Issue | Solution |
|
|
737
|
+
|-------|----------|
|
|
738
|
+
| "Socket not found" | Start daemon: `npx agent-relay start -f` |
|
|
739
|
+
| "Connection refused" | Check daemon logs: `cat /tmp/agent-relay.log` |
|
|
740
|
+
| Messages not received | Verify agent name matches |
|
|
741
|
+
| High latency | Check system load, restart daemon |
|
|
742
|
+
|
|
743
|
+
## Socket Path
|
|
744
|
+
|
|
745
|
+
Default: `/tmp/agent-relay.sock`
|
|
746
|
+
|
|
747
|
+
Custom: Use `-s` flag or `socketPath` config option.
|
|
748
|
+
|
|
749
|
+
</details>
|
|
750
|
+
|
|
751
|
+
---
|
|
752
|
+
|
|
753
|
+
## Protocol Specification
|
|
754
|
+
|
|
755
|
+
See [PROTOCOL.md](./PROTOCOL.md) for the complete wire protocol specification including:
|
|
756
|
+
- Frame format (4-byte length prefix + JSON)
|
|
757
|
+
- Message types (HELLO, SEND, DELIVER, ACK, etc.)
|
|
758
|
+
- Handshake flow
|
|
759
|
+
- Reconnection and state sync (spec defined, implementation pending)
|
|
760
|
+
- Backpressure handling (spec defined, implementation pending)
|
|
761
|
+
|
|
762
|
+
**Current implementation status:** The daemon provides best-effort message delivery with per-stream ordering. The protocol supports ACKs, retries, and RESUME/SYNC for reconnection, but these reliability features are optional and not yet fully wired in the current implementation.
|
|
763
|
+
|
|
764
|
+
## Acknowledgments
|
|
765
|
+
|
|
766
|
+
This project stands on the shoulders of giants:
|
|
767
|
+
|
|
768
|
+
- **[mcp_agent_mail](https://github.com/Dicklesworthstone/mcp_agent_mail)** by Jeff Emanuel - Pioneered many patterns we adopted, including auto-generated AdjectiveNoun names, and demonstrated the power of persistent agent communication.
|
|
769
|
+
- **[swarm-tools](https://github.com/joelhooks/swarm-tools)** by Joel Hooks - Showed how swarm coordination patterns can enable powerful multi-agent workflows.
|
|
770
|
+
|
|
771
|
+
If MCP integration or file-based persistence fits your use case better, we highly recommend checking out these projects.
|
|
772
|
+
|
|
773
|
+
## Development
|
|
774
|
+
|
|
775
|
+
```bash
|
|
776
|
+
# Build
|
|
777
|
+
npm run build
|
|
778
|
+
|
|
779
|
+
# Watch mode
|
|
780
|
+
npm run dev
|
|
781
|
+
|
|
782
|
+
# Run tests
|
|
783
|
+
npm test
|
|
784
|
+
|
|
785
|
+
# Lint
|
|
786
|
+
npm run lint
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
## License
|
|
790
|
+
|
|
791
|
+
MIT
|