@manuelfedele/postino 0.1.1 → 0.2.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.
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "postino",
3
+ "owner": {
4
+ "name": "Manuel Fedele",
5
+ "url": "https://github.com/manuelfedele"
6
+ },
7
+ "plugins": [
8
+ {
9
+ "name": "postino",
10
+ "description": "Inter-agent messaging, broadcasts, and real-time web GUI for Claude Code. Let your agents talk to each other across tabs and processes.",
11
+ "category": "productivity",
12
+ "source": {
13
+ "source": "url",
14
+ "url": "https://github.com/manuelfedele/postino.git"
15
+ },
16
+ "homepage": "https://github.com/manuelfedele/postino"
17
+ }
18
+ ]
19
+ }
package/README.md CHANGED
@@ -35,11 +35,22 @@
35
35
 
36
36
  ## Quick Start
37
37
 
38
+ ### As a Claude Code plugin (recommended)
39
+
40
+ ```bash
41
+ claude plugin marketplace add manuelfedele/postino
42
+ claude plugin install postino
43
+ ```
44
+
45
+ Or from within Claude Code: `/plugin marketplace add manuelfedele/postino` then `/plugin install postino`.
46
+
47
+ ### Via npx
48
+
38
49
  ```bash
39
50
  npx @manuelfedele/postino install
40
51
  ```
41
52
 
42
- That's it. Restart Claude Code. Your agent is online.
53
+ Restart Claude Code after either method. Your agent is online.
43
54
 
44
55
  > **Prerequisite:** Valkey or Redis running on `localhost:6379`
45
56
 
@@ -122,28 +133,81 @@ Updates in real-time via Server-Sent Events. When an agent sends a message from
122
133
 
123
134
  ## How It Works
124
135
 
136
+ ### 1-to-1 Messaging
137
+
138
+ Messages work like a queue: send pushes, read pops.
139
+
140
+ ```mermaid
141
+ sequenceDiagram
142
+ participant A as Tab 1 (agent-A)
143
+ participant V as Valkey
144
+ participant B as Tab 2 (agent-B)
145
+
146
+ A->>V: msg_send(to=B, "run tests")
147
+ V-->>B: pub/sub notify
148
+ Note over B: hook fires on next prompt
149
+ B->>V: msg_check()
150
+ V-->>B: "1 unread message"
151
+ B->>V: msg_read()
152
+ V-->>B: [{from: A, body: "run tests"}]
153
+ Note over V: message consumed
125
154
  ```
126
- Tab 1 (agent-A) Valkey Tab 2 (agent-B)
127
- | | |
128
- |-- msg_send(to=B, "do X") ---->| |
129
- | |-- pub/sub notify ----------->|
130
- | | |-- msg_check()
131
- | | | "1 unread message"
132
- | | |-- msg_read()
133
- | |<-- consume --------------------| [{from: A, body: "do X"}]
134
- | | |
135
- |-- msg_broadcast("deploy") --->|-- shared list -------------->|
136
- | | |-- msg_broadcasts()
137
- | | | [{from: A, body: "deploy"}]
155
+
156
+ ### Broadcasts
157
+
158
+ Broadcasts are shared. Every agent reads independently via a per-agent cursor.
159
+
160
+ ```mermaid
161
+ sequenceDiagram
162
+ participant A as Tab 1 (agent-A)
163
+ participant V as Valkey
164
+ participant B as Tab 2 (agent-B)
165
+ participant C as Tab 3 (agent-C)
166
+
167
+ A->>V: msg_broadcast("deploy freeze")
168
+ V-->>B: SSE event
169
+ V-->>C: SSE event
170
+ B->>V: msg_broadcasts()
171
+ V-->>B: [{from: A, body: "deploy freeze"}]
172
+ Note over V: cursor advanced for B
173
+ C->>V: msg_broadcasts()
174
+ V-->>C: [{from: A, body: "deploy freeze"}]
175
+ Note over V: cursor advanced for C
176
+ Note over V: message still exists (TTL expiry)
138
177
  ```
139
178
 
140
- **Messages** are Valkey lists (one per agent inbox). `msg_send` pushes, `msg_read` pops. Messages have a 24h TTL as a safety net for unread messages.
179
+ ### Architecture
180
+
181
+ ```mermaid
182
+ graph LR
183
+ subgraph Claude Code
184
+ T1[Tab 1<br/>MCP client] -->|stdio| M1[Postino<br/>MCP server]
185
+ T2[Tab 2<br/>MCP client] -->|stdio| M2[Postino<br/>MCP server]
186
+ end
187
+
188
+ M1 -->|ioredis| VK[(Valkey)]
189
+ M2 -->|ioredis| VK
190
+
191
+ M1 -->|Hono :3333| GUI[Web GUI]
192
+ VK -->|pub/sub| GUI
193
+ GUI -->|SSE| Browser
194
+
195
+ H[Hook<br/>check-messages.sh] -->|curl /api/check| M1
196
+
197
+ style VK fill:#e63030,color:#fff,stroke:none
198
+ style GUI fill:#2563eb,color:#fff,stroke:none
199
+ style H fill:#d97706,color:#fff,stroke:none
200
+ ```
201
+
202
+ ### Under the Hood
203
+
204
+ **Messages** are Valkey lists (one per inbox). `msg_send` pushes, `msg_read` pops. Unread messages expire after 24h (configurable).
141
205
 
142
- **Broadcasts** are a shared Valkey list. Each agent tracks a cursor (last-seen index). Reading broadcasts advances the cursor without deleting the data, so every agent sees every broadcast.
206
+ **Broadcasts** are a shared Valkey list. Each agent tracks a cursor (last-seen index). Reading advances the cursor without deleting, so every agent sees every broadcast.
143
207
 
144
208
  **Agent presence** uses Valkey keys with a 30-second TTL, refreshed by a heartbeat. If a process dies, it goes offline within 30 seconds.
145
209
 
146
- **The hook** (`UserPromptSubmit`) calls `GET /api/check/:agent` via curl. If there are no new messages or broadcasts, it outputs nothing (zero tokens). If there's something new, it injects a one-line hint so Claude knows to check.
210
+ **The hook** (`UserPromptSubmit`) calls `GET /api/check/:agent` via curl. Zero output when there's nothing new (zero token cost). One-line hint when messages arrive.
147
211
 
148
212
  ---
149
213
 
package/dist/index.js CHANGED
@@ -16,7 +16,21 @@ const server = new McpServer({
16
16
  });
17
17
  registerMessagingTools(server, config.agentName);
18
18
  async function main() {
19
- await connect();
19
+ try {
20
+ await connect();
21
+ }
22
+ catch (err) {
23
+ const url = config.valkeyUrl;
24
+ process.stderr.write(`\n postino: cannot connect to Valkey/Redis at ${url}\n\n`);
25
+ process.stderr.write(` Postino requires Valkey or Redis. Start one with:\n\n`);
26
+ process.stderr.write(` docker run -d --name valkey -p 6379:6379 valkey/valkey:8\n\n`);
27
+ process.stderr.write(` Or install natively:\n\n`);
28
+ process.stderr.write(` macOS: brew install valkey && brew services start valkey\n`);
29
+ process.stderr.write(` Linux: apt install valkey-server\n\n`);
30
+ process.stderr.write(` To use a different host/port:\n\n`);
31
+ process.stderr.write(` POSTINO_VALKEY_URL=redis://host:port\n\n`);
32
+ process.exit(1);
33
+ }
20
34
  await registerAgent(config.agentName);
21
35
  const transport = new StdioServerTransport();
22
36
  await server.connect(transport);
@@ -33,6 +47,6 @@ async function main() {
33
47
  process.on("SIGTERM", shutdown);
34
48
  }
35
49
  main().catch((err) => {
36
- console.error("Failed to start postino:", err);
50
+ process.stderr.write(`postino: ${err.message || err}\n`);
37
51
  process.exit(1);
38
52
  });
package/dist/valkey.js CHANGED
@@ -5,10 +5,12 @@ export const valkey = new Redis(config.valkeyUrl, {
5
5
  lazyConnect: true,
6
6
  maxRetriesPerRequest: 3,
7
7
  });
8
+ valkey.on("error", () => { }); // Handled in connect()
8
9
  export const valkeySub = new Redis(config.valkeyUrl, {
9
10
  lazyConnect: true,
10
11
  maxRetriesPerRequest: 3,
11
12
  });
13
+ valkeySub.on("error", () => { }); // Handled in connect()
12
14
  const prefix = config.keyPrefix;
13
15
  export const keys = {
14
16
  inbox: (agent) => `${prefix}inbox:${agent}`,
@@ -21,6 +23,8 @@ export const keys = {
21
23
  };
22
24
  export async function connect() {
23
25
  await valkey.connect();
26
+ // Verify the connection actually works
27
+ await valkey.ping();
24
28
  await valkeySub.connect();
25
29
  }
26
30
  export async function disconnect() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@manuelfedele/postino",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Inter-agent messaging and broadcast system for Claude Code",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",