@hasna/conversations 0.0.2 → 0.0.4
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/README.md +83 -31
- package/bin/index.js +341 -10
- package/bin/mcp.js +191 -5
- package/dist/index.d.ts +8 -6
- package/dist/index.js +124 -3
- package/dist/lib/channels.d.ts +8 -0
- package/dist/lib/messages.d.ts +1 -0
- package/dist/lib/poll.d.ts +5 -0
- package/dist/mcp/index.d.ts +3 -3
- package/dist/types.d.ts +18 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -31,10 +31,10 @@ npx @hasna/conversations
|
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
33
|
# Basic message
|
|
34
|
-
|
|
34
|
+
conversations send --to claude-code "Hello from codex"
|
|
35
35
|
|
|
36
36
|
# With context
|
|
37
|
-
|
|
37
|
+
conversations send --to claude-code "Check this branch" \
|
|
38
38
|
--from codex \
|
|
39
39
|
--priority high \
|
|
40
40
|
--working-dir /path/to/project \
|
|
@@ -42,56 +42,86 @@ convo send --to claude-code "Check this branch" \
|
|
|
42
42
|
--branch feature/auth
|
|
43
43
|
|
|
44
44
|
# With metadata
|
|
45
|
-
|
|
45
|
+
conversations send --to gemini "Deploy ready" --metadata '{"env":"staging"}'
|
|
46
46
|
```
|
|
47
47
|
|
|
48
48
|
### Read Messages
|
|
49
49
|
|
|
50
50
|
```bash
|
|
51
51
|
# Read all messages for an agent
|
|
52
|
-
|
|
52
|
+
conversations read --to codex
|
|
53
53
|
|
|
54
54
|
# Unread only, as JSON
|
|
55
|
-
|
|
55
|
+
conversations read --to codex --unread --json
|
|
56
56
|
|
|
57
57
|
# Filter by session
|
|
58
|
-
|
|
58
|
+
conversations read --session alice-bob-abc123
|
|
59
59
|
|
|
60
60
|
# Read and mark as read
|
|
61
|
-
|
|
61
|
+
conversations read --to codex --unread --mark-read
|
|
62
62
|
```
|
|
63
63
|
|
|
64
64
|
### Sessions
|
|
65
65
|
|
|
66
66
|
```bash
|
|
67
67
|
# List all sessions
|
|
68
|
-
|
|
68
|
+
conversations sessions
|
|
69
69
|
|
|
70
70
|
# Sessions for a specific agent
|
|
71
|
-
|
|
71
|
+
conversations sessions --agent claude-code --json
|
|
72
72
|
```
|
|
73
73
|
|
|
74
74
|
### Reply
|
|
75
75
|
|
|
76
76
|
```bash
|
|
77
77
|
# Reply to a message (auto-resolves session and recipient)
|
|
78
|
-
|
|
78
|
+
conversations reply --to 42 "Got it, working on it now"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Channels
|
|
82
|
+
|
|
83
|
+
Channels are broadcast spaces — any agent can post, all members can read.
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# Create a channel
|
|
87
|
+
conversations channel create deployments --description "Deployment notifications"
|
|
88
|
+
|
|
89
|
+
# List channels
|
|
90
|
+
conversations channel list
|
|
91
|
+
|
|
92
|
+
# Join a channel
|
|
93
|
+
conversations channel join deployments --from codex
|
|
94
|
+
|
|
95
|
+
# Send to a channel
|
|
96
|
+
conversations channel send deployments "v1.2 deployed to staging" --from ops
|
|
97
|
+
|
|
98
|
+
# Read channel messages
|
|
99
|
+
conversations channel read deployments
|
|
100
|
+
|
|
101
|
+
# Leave a channel
|
|
102
|
+
conversations channel leave deployments --from codex
|
|
103
|
+
|
|
104
|
+
# List members
|
|
105
|
+
conversations channel members deployments
|
|
79
106
|
```
|
|
80
107
|
|
|
81
108
|
### Mark Read
|
|
82
109
|
|
|
83
110
|
```bash
|
|
84
111
|
# Mark specific messages
|
|
85
|
-
|
|
112
|
+
conversations mark-read 1 2 3 --agent codex
|
|
86
113
|
|
|
87
114
|
# Mark entire session
|
|
88
|
-
|
|
115
|
+
conversations mark-read --session abc123 --agent codex
|
|
116
|
+
|
|
117
|
+
# Mark entire channel
|
|
118
|
+
conversations mark-read --channel deployments --agent codex
|
|
89
119
|
```
|
|
90
120
|
|
|
91
121
|
### Status
|
|
92
122
|
|
|
93
123
|
```bash
|
|
94
|
-
|
|
124
|
+
conversations status
|
|
95
125
|
# Conversations Status
|
|
96
126
|
# DB Path: ~/.conversations/messages.db
|
|
97
127
|
# Messages: 47
|
|
@@ -102,7 +132,7 @@ convo status
|
|
|
102
132
|
### Interactive TUI
|
|
103
133
|
|
|
104
134
|
```bash
|
|
105
|
-
|
|
135
|
+
conversations
|
|
106
136
|
```
|
|
107
137
|
|
|
108
138
|
Arrow keys to navigate sessions, Enter to open, `n` for new conversation, `q` to quit, Esc to go back.
|
|
@@ -112,7 +142,7 @@ Arrow keys to navigate sessions, Enter to open, `n` for new conversation, `q` to
|
|
|
112
142
|
For native AI agent integration via the Model Context Protocol:
|
|
113
143
|
|
|
114
144
|
```bash
|
|
115
|
-
|
|
145
|
+
conversations mcp
|
|
116
146
|
```
|
|
117
147
|
|
|
118
148
|
### Agent Configuration
|
|
@@ -135,11 +165,17 @@ Add to your agent's MCP config:
|
|
|
135
165
|
|
|
136
166
|
| Tool | Description |
|
|
137
167
|
|------|-------------|
|
|
138
|
-
| `send_message` | Send a message (sender auto-resolved from env) |
|
|
168
|
+
| `send_message` | Send a direct message (sender auto-resolved from env) |
|
|
139
169
|
| `read_messages` | Read messages with filters |
|
|
140
170
|
| `list_sessions` | List conversation sessions |
|
|
141
171
|
| `reply` | Reply to a message by ID |
|
|
142
172
|
| `mark_read` | Mark messages as read |
|
|
173
|
+
| `create_channel` | Create a new channel |
|
|
174
|
+
| `list_channels` | List all channels with member/message counts |
|
|
175
|
+
| `send_to_channel` | Send a message to a channel |
|
|
176
|
+
| `read_channel` | Read messages from a channel |
|
|
177
|
+
| `join_channel` | Join a channel |
|
|
178
|
+
| `leave_channel` | Leave a channel |
|
|
143
179
|
|
|
144
180
|
## Programmatic API
|
|
145
181
|
|
|
@@ -149,9 +185,12 @@ import {
|
|
|
149
185
|
readMessages,
|
|
150
186
|
listSessions,
|
|
151
187
|
startPolling,
|
|
188
|
+
createChannel,
|
|
189
|
+
listChannels,
|
|
190
|
+
joinChannel,
|
|
152
191
|
} from "@hasna/conversations";
|
|
153
192
|
|
|
154
|
-
// Send a message
|
|
193
|
+
// Send a direct message
|
|
155
194
|
const msg = sendMessage({
|
|
156
195
|
from: "my-agent",
|
|
157
196
|
to: "claude-code",
|
|
@@ -166,31 +205,44 @@ const { stop } = startPolling({
|
|
|
166
205
|
to_agent: "my-agent",
|
|
167
206
|
on_messages: (msgs) => console.log("New:", msgs),
|
|
168
207
|
});
|
|
208
|
+
|
|
209
|
+
// Channels
|
|
210
|
+
createChannel("deploys", "my-agent", "Deploy notifications");
|
|
211
|
+
joinChannel("deploys", "claude-code");
|
|
212
|
+
sendMessage({
|
|
213
|
+
from: "my-agent",
|
|
214
|
+
to: "deploys",
|
|
215
|
+
content: "v2.0 shipped",
|
|
216
|
+
channel: "deploys",
|
|
217
|
+
session_id: "channel:deploys",
|
|
218
|
+
});
|
|
219
|
+
const channelMsgs = readMessages({ channel: "deploys" });
|
|
169
220
|
```
|
|
170
221
|
|
|
171
222
|
## Architecture
|
|
172
223
|
|
|
173
224
|
```
|
|
174
|
-
|
|
175
|
-
│
|
|
176
|
-
│
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
225
|
+
┌──────────────────┐ ┌─────────────────────┐ ┌──────────────────────┐
|
|
226
|
+
│ Ink TUI │ │ Headless │ │ MCP Server │
|
|
227
|
+
│ `conversations` │ │ `conversations send`│ │ `conversations mcp` │
|
|
228
|
+
└────────┬─────────┘ └──────────┬──────────┘ └──────────┬───────────┘
|
|
229
|
+
│ │ │
|
|
230
|
+
└───────────┬───────────┴────────────────────────┘
|
|
231
|
+
│
|
|
232
|
+
┌───────▼────────┐
|
|
233
|
+
│ Core Library │
|
|
234
|
+
│ SQLite WAL │
|
|
235
|
+
│ 200ms polling │
|
|
236
|
+
└────────────────┘
|
|
237
|
+
│
|
|
238
|
+
~/.conversations/messages.db
|
|
188
239
|
```
|
|
189
240
|
|
|
190
241
|
- **SQLite WAL mode** for concurrent read/write across processes
|
|
191
242
|
- **200ms polling** for near-instant message delivery
|
|
192
243
|
- **Single shared database** at `~/.conversations/messages.db`
|
|
193
244
|
- Sessions derived from messages (no separate table)
|
|
245
|
+
- **Channels** for broadcast messaging (many-to-many)
|
|
194
246
|
|
|
195
247
|
## Development
|
|
196
248
|
|
package/bin/index.js
CHANGED
|
@@ -1890,6 +1890,7 @@ function getDb() {
|
|
|
1890
1890
|
session_id TEXT NOT NULL,
|
|
1891
1891
|
from_agent TEXT NOT NULL,
|
|
1892
1892
|
to_agent TEXT NOT NULL,
|
|
1893
|
+
channel TEXT,
|
|
1893
1894
|
content TEXT NOT NULL,
|
|
1894
1895
|
priority TEXT NOT NULL DEFAULT 'normal',
|
|
1895
1896
|
working_dir TEXT,
|
|
@@ -1903,6 +1904,27 @@ function getDb() {
|
|
|
1903
1904
|
db.exec("CREATE INDEX IF NOT EXISTS idx_messages_session ON messages(session_id)");
|
|
1904
1905
|
db.exec("CREATE INDEX IF NOT EXISTS idx_messages_to ON messages(to_agent)");
|
|
1905
1906
|
db.exec("CREATE INDEX IF NOT EXISTS idx_messages_created ON messages(created_at)");
|
|
1907
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_messages_channel ON messages(channel)");
|
|
1908
|
+
const cols = db.prepare("PRAGMA table_info(messages)").all();
|
|
1909
|
+
if (!cols.some((c) => c.name === "channel")) {
|
|
1910
|
+
db.exec("ALTER TABLE messages ADD COLUMN channel TEXT");
|
|
1911
|
+
}
|
|
1912
|
+
db.exec(`
|
|
1913
|
+
CREATE TABLE IF NOT EXISTS channels (
|
|
1914
|
+
name TEXT PRIMARY KEY,
|
|
1915
|
+
description TEXT,
|
|
1916
|
+
created_by TEXT NOT NULL,
|
|
1917
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now'))
|
|
1918
|
+
)
|
|
1919
|
+
`);
|
|
1920
|
+
db.exec(`
|
|
1921
|
+
CREATE TABLE IF NOT EXISTS channel_members (
|
|
1922
|
+
channel TEXT NOT NULL REFERENCES channels(name),
|
|
1923
|
+
agent TEXT NOT NULL,
|
|
1924
|
+
joined_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')),
|
|
1925
|
+
PRIMARY KEY (channel, agent)
|
|
1926
|
+
)
|
|
1927
|
+
`);
|
|
1906
1928
|
return db;
|
|
1907
1929
|
}
|
|
1908
1930
|
function closeDb() {
|
|
@@ -1927,11 +1949,11 @@ function sendMessage(opts) {
|
|
|
1927
1949
|
const sessionId = opts.session_id || `${[opts.from, opts.to].sort().join("-")}-${randomUUID().slice(0, 8)}`;
|
|
1928
1950
|
const metadata = opts.metadata ? JSON.stringify(opts.metadata) : null;
|
|
1929
1951
|
const stmt = db2.prepare(`
|
|
1930
|
-
INSERT INTO messages (session_id, from_agent, to_agent, content, priority, working_dir, repository, branch, metadata)
|
|
1931
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1952
|
+
INSERT INTO messages (session_id, from_agent, to_agent, channel, content, priority, working_dir, repository, branch, metadata)
|
|
1953
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1932
1954
|
RETURNING *
|
|
1933
1955
|
`);
|
|
1934
|
-
const row = stmt.get(sessionId, opts.from, opts.to, opts.content, opts.priority || "normal", opts.working_dir || null, opts.repository || null, opts.branch || null, metadata);
|
|
1956
|
+
const row = stmt.get(sessionId, opts.from, opts.to, opts.channel || null, opts.content, opts.priority || "normal", opts.working_dir || null, opts.repository || null, opts.branch || null, metadata);
|
|
1935
1957
|
return parseMessage(row);
|
|
1936
1958
|
}
|
|
1937
1959
|
function readMessages(opts = {}) {
|
|
@@ -1950,6 +1972,10 @@ function readMessages(opts = {}) {
|
|
|
1950
1972
|
conditions.push("to_agent = ?");
|
|
1951
1973
|
params.push(opts.to);
|
|
1952
1974
|
}
|
|
1975
|
+
if (opts.channel) {
|
|
1976
|
+
conditions.push("channel = ?");
|
|
1977
|
+
params.push(opts.channel);
|
|
1978
|
+
}
|
|
1953
1979
|
if (opts.since) {
|
|
1954
1980
|
conditions.push("created_at > ?");
|
|
1955
1981
|
params.push(opts.since);
|
|
@@ -1977,6 +2003,12 @@ function markSessionRead(sessionId, reader) {
|
|
|
1977
2003
|
const result = stmt.run(sessionId, reader);
|
|
1978
2004
|
return result.changes;
|
|
1979
2005
|
}
|
|
2006
|
+
function markChannelRead(channelName, reader) {
|
|
2007
|
+
const db2 = getDb();
|
|
2008
|
+
const stmt = db2.prepare(`UPDATE messages SET read_at = strftime('%Y-%m-%dT%H:%M:%f', 'now') WHERE channel = ? AND from_agent != ? AND read_at IS NULL`);
|
|
2009
|
+
const result = stmt.run(channelName, reader);
|
|
2010
|
+
return result.changes;
|
|
2011
|
+
}
|
|
1980
2012
|
function getMessageById(id) {
|
|
1981
2013
|
const db2 = getDb();
|
|
1982
2014
|
const row = db2.prepare("SELECT * FROM messages WHERE id = ?").get(id);
|
|
@@ -2019,6 +2051,64 @@ var init_sessions = __esm(() => {
|
|
|
2019
2051
|
init_db();
|
|
2020
2052
|
});
|
|
2021
2053
|
|
|
2054
|
+
// src/lib/channels.ts
|
|
2055
|
+
function createChannel(name, createdBy, description) {
|
|
2056
|
+
const db2 = getDb();
|
|
2057
|
+
const row = db2.prepare("INSERT INTO channels (name, description, created_by) VALUES (?, ?, ?) RETURNING *").get(name, description || null, createdBy);
|
|
2058
|
+
db2.prepare("INSERT OR IGNORE INTO channel_members (channel, agent) VALUES (?, ?)").run(name, createdBy);
|
|
2059
|
+
return row;
|
|
2060
|
+
}
|
|
2061
|
+
function listChannels() {
|
|
2062
|
+
const db2 = getDb();
|
|
2063
|
+
const rows = db2.prepare(`
|
|
2064
|
+
SELECT
|
|
2065
|
+
c.name,
|
|
2066
|
+
c.description,
|
|
2067
|
+
c.created_by,
|
|
2068
|
+
c.created_at,
|
|
2069
|
+
(SELECT COUNT(*) FROM channel_members WHERE channel = c.name) AS member_count,
|
|
2070
|
+
(SELECT COUNT(*) FROM messages WHERE channel = c.name) AS message_count
|
|
2071
|
+
FROM channels c
|
|
2072
|
+
ORDER BY c.name ASC
|
|
2073
|
+
`).all();
|
|
2074
|
+
return rows;
|
|
2075
|
+
}
|
|
2076
|
+
function getChannel(name) {
|
|
2077
|
+
const db2 = getDb();
|
|
2078
|
+
const row = db2.prepare(`
|
|
2079
|
+
SELECT
|
|
2080
|
+
c.name,
|
|
2081
|
+
c.description,
|
|
2082
|
+
c.created_by,
|
|
2083
|
+
c.created_at,
|
|
2084
|
+
(SELECT COUNT(*) FROM channel_members WHERE channel = c.name) AS member_count,
|
|
2085
|
+
(SELECT COUNT(*) FROM messages WHERE channel = c.name) AS message_count
|
|
2086
|
+
FROM channels c
|
|
2087
|
+
WHERE c.name = ?
|
|
2088
|
+
`).get(name);
|
|
2089
|
+
return row;
|
|
2090
|
+
}
|
|
2091
|
+
function joinChannel(channelName, agent) {
|
|
2092
|
+
const db2 = getDb();
|
|
2093
|
+
const channel = db2.prepare("SELECT name FROM channels WHERE name = ?").get(channelName);
|
|
2094
|
+
if (!channel)
|
|
2095
|
+
return false;
|
|
2096
|
+
db2.prepare("INSERT OR IGNORE INTO channel_members (channel, agent) VALUES (?, ?)").run(channelName, agent);
|
|
2097
|
+
return true;
|
|
2098
|
+
}
|
|
2099
|
+
function leaveChannel(channelName, agent) {
|
|
2100
|
+
const db2 = getDb();
|
|
2101
|
+
const result = db2.prepare("DELETE FROM channel_members WHERE channel = ? AND agent = ?").run(channelName, agent);
|
|
2102
|
+
return result.changes > 0;
|
|
2103
|
+
}
|
|
2104
|
+
function getChannelMembers(channelName) {
|
|
2105
|
+
const db2 = getDb();
|
|
2106
|
+
return db2.prepare("SELECT channel, agent, joined_at FROM channel_members WHERE channel = ? ORDER BY joined_at ASC").all(channelName);
|
|
2107
|
+
}
|
|
2108
|
+
var init_channels = __esm(() => {
|
|
2109
|
+
init_db();
|
|
2110
|
+
});
|
|
2111
|
+
|
|
2022
2112
|
// src/lib/identity.ts
|
|
2023
2113
|
function resolveIdentity(explicit) {
|
|
2024
2114
|
if (explicit)
|
|
@@ -30910,13 +31000,14 @@ var init_mcp2 = __esm(() => {
|
|
|
30910
31000
|
init_zod();
|
|
30911
31001
|
init_messages();
|
|
30912
31002
|
init_sessions();
|
|
31003
|
+
init_channels();
|
|
30913
31004
|
server = new McpServer({
|
|
30914
31005
|
name: "conversations",
|
|
30915
|
-
version: "0.0.
|
|
31006
|
+
version: "0.0.4"
|
|
30916
31007
|
});
|
|
30917
31008
|
server.registerTool("send_message", {
|
|
30918
31009
|
title: "Send Message",
|
|
30919
|
-
description: "Send a message to another agent. The sender is auto-resolved from CONVERSATIONS_AGENT_ID env var.",
|
|
31010
|
+
description: "Send a direct message to another agent. The sender is auto-resolved from CONVERSATIONS_AGENT_ID env var.",
|
|
30920
31011
|
inputSchema: {
|
|
30921
31012
|
to: exports_external.string().describe("Recipient agent ID"),
|
|
30922
31013
|
content: exports_external.string().describe("Message content"),
|
|
@@ -30952,6 +31043,7 @@ var init_mcp2 = __esm(() => {
|
|
|
30952
31043
|
session_id: exports_external.string().optional().describe("Filter by session ID"),
|
|
30953
31044
|
from: exports_external.string().optional().describe("Filter by sender agent ID"),
|
|
30954
31045
|
to: exports_external.string().optional().describe("Filter by recipient agent ID"),
|
|
31046
|
+
channel: exports_external.string().optional().describe("Filter by channel name"),
|
|
30955
31047
|
since: exports_external.string().optional().describe("Messages after this ISO timestamp"),
|
|
30956
31048
|
limit: exports_external.number().optional().describe("Max messages to return"),
|
|
30957
31049
|
unread_only: exports_external.boolean().optional().describe("Only return unread messages")
|
|
@@ -31015,6 +31107,114 @@ var init_mcp2 = __esm(() => {
|
|
|
31015
31107
|
content: [{ type: "text", text: JSON.stringify({ marked_read: count }, null, 2) }]
|
|
31016
31108
|
};
|
|
31017
31109
|
});
|
|
31110
|
+
server.registerTool("create_channel", {
|
|
31111
|
+
title: "Create Channel",
|
|
31112
|
+
description: "Create a new channel. The creator is auto-joined.",
|
|
31113
|
+
inputSchema: {
|
|
31114
|
+
name: exports_external.string().describe("Channel name (e.g. 'deployments', 'code-review')"),
|
|
31115
|
+
description: exports_external.string().optional().describe("Channel description")
|
|
31116
|
+
}
|
|
31117
|
+
}, async ({ name, description }) => {
|
|
31118
|
+
const agent = resolveIdentity();
|
|
31119
|
+
try {
|
|
31120
|
+
const ch = createChannel(name, agent, description);
|
|
31121
|
+
return {
|
|
31122
|
+
content: [{ type: "text", text: JSON.stringify(ch, null, 2) }]
|
|
31123
|
+
};
|
|
31124
|
+
} catch (e) {
|
|
31125
|
+
if (e.message?.includes("UNIQUE constraint")) {
|
|
31126
|
+
return {
|
|
31127
|
+
content: [{ type: "text", text: `Channel #${name} already exists` }],
|
|
31128
|
+
isError: true
|
|
31129
|
+
};
|
|
31130
|
+
}
|
|
31131
|
+
throw e;
|
|
31132
|
+
}
|
|
31133
|
+
});
|
|
31134
|
+
server.registerTool("list_channels", {
|
|
31135
|
+
title: "List Channels",
|
|
31136
|
+
description: "List all available channels with member and message counts."
|
|
31137
|
+
}, async () => {
|
|
31138
|
+
const channels = listChannels();
|
|
31139
|
+
return {
|
|
31140
|
+
content: [{ type: "text", text: JSON.stringify(channels, null, 2) }]
|
|
31141
|
+
};
|
|
31142
|
+
});
|
|
31143
|
+
server.registerTool("send_to_channel", {
|
|
31144
|
+
title: "Send to Channel",
|
|
31145
|
+
description: "Send a message to a channel. All members can see it.",
|
|
31146
|
+
inputSchema: {
|
|
31147
|
+
channel: exports_external.string().describe("Channel name"),
|
|
31148
|
+
content: exports_external.string().describe("Message content"),
|
|
31149
|
+
priority: exports_external.enum(["low", "normal", "high", "urgent"]).optional().describe("Message priority")
|
|
31150
|
+
}
|
|
31151
|
+
}, async ({ channel, content, priority }) => {
|
|
31152
|
+
const from = resolveIdentity();
|
|
31153
|
+
const ch = getChannel(channel);
|
|
31154
|
+
if (!ch) {
|
|
31155
|
+
return {
|
|
31156
|
+
content: [{ type: "text", text: `Channel #${channel} not found` }],
|
|
31157
|
+
isError: true
|
|
31158
|
+
};
|
|
31159
|
+
}
|
|
31160
|
+
const msg = sendMessage({
|
|
31161
|
+
from,
|
|
31162
|
+
to: channel,
|
|
31163
|
+
content,
|
|
31164
|
+
channel,
|
|
31165
|
+
session_id: `channel:${channel}`,
|
|
31166
|
+
priority
|
|
31167
|
+
});
|
|
31168
|
+
return {
|
|
31169
|
+
content: [{ type: "text", text: JSON.stringify(msg, null, 2) }]
|
|
31170
|
+
};
|
|
31171
|
+
});
|
|
31172
|
+
server.registerTool("read_channel", {
|
|
31173
|
+
title: "Read Channel",
|
|
31174
|
+
description: "Read messages from a channel.",
|
|
31175
|
+
inputSchema: {
|
|
31176
|
+
channel: exports_external.string().describe("Channel name"),
|
|
31177
|
+
since: exports_external.string().optional().describe("Messages after this ISO timestamp"),
|
|
31178
|
+
limit: exports_external.number().optional().describe("Max messages to return")
|
|
31179
|
+
}
|
|
31180
|
+
}, async ({ channel, since, limit }) => {
|
|
31181
|
+
const messages = readMessages({ channel, since, limit });
|
|
31182
|
+
return {
|
|
31183
|
+
content: [{ type: "text", text: JSON.stringify(messages, null, 2) }]
|
|
31184
|
+
};
|
|
31185
|
+
});
|
|
31186
|
+
server.registerTool("join_channel", {
|
|
31187
|
+
title: "Join Channel",
|
|
31188
|
+
description: "Join a channel to receive messages.",
|
|
31189
|
+
inputSchema: {
|
|
31190
|
+
channel: exports_external.string().describe("Channel name to join")
|
|
31191
|
+
}
|
|
31192
|
+
}, async ({ channel }) => {
|
|
31193
|
+
const agent = resolveIdentity();
|
|
31194
|
+
const ok = joinChannel(channel, agent);
|
|
31195
|
+
if (!ok) {
|
|
31196
|
+
return {
|
|
31197
|
+
content: [{ type: "text", text: `Channel #${channel} not found` }],
|
|
31198
|
+
isError: true
|
|
31199
|
+
};
|
|
31200
|
+
}
|
|
31201
|
+
return {
|
|
31202
|
+
content: [{ type: "text", text: JSON.stringify({ channel, agent, joined: true }, null, 2) }]
|
|
31203
|
+
};
|
|
31204
|
+
});
|
|
31205
|
+
server.registerTool("leave_channel", {
|
|
31206
|
+
title: "Leave Channel",
|
|
31207
|
+
description: "Leave a channel.",
|
|
31208
|
+
inputSchema: {
|
|
31209
|
+
channel: exports_external.string().describe("Channel name to leave")
|
|
31210
|
+
}
|
|
31211
|
+
}, async ({ channel }) => {
|
|
31212
|
+
const agent = resolveIdentity();
|
|
31213
|
+
const left = leaveChannel(channel, agent);
|
|
31214
|
+
return {
|
|
31215
|
+
content: [{ type: "text", text: JSON.stringify({ channel, agent, left }, null, 2) }]
|
|
31216
|
+
};
|
|
31217
|
+
});
|
|
31018
31218
|
isDirectRun = import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith("mcp.js") || process.argv[1]?.endsWith("mcp.ts");
|
|
31019
31219
|
if (isDirectRun) {
|
|
31020
31220
|
startMcpServer().catch((error48) => {
|
|
@@ -31043,6 +31243,7 @@ var {
|
|
|
31043
31243
|
// src/cli/index.tsx
|
|
31044
31244
|
init_messages();
|
|
31045
31245
|
init_sessions();
|
|
31246
|
+
init_channels();
|
|
31046
31247
|
init_db();
|
|
31047
31248
|
import chalk2 from "chalk";
|
|
31048
31249
|
import { render } from "ink";
|
|
@@ -31643,6 +31844,7 @@ function startPolling(opts) {
|
|
|
31643
31844
|
const messages = readMessages({
|
|
31644
31845
|
session_id: opts.session_id,
|
|
31645
31846
|
to: opts.to_agent,
|
|
31847
|
+
channel: opts.channel,
|
|
31646
31848
|
since: lastSeen
|
|
31647
31849
|
});
|
|
31648
31850
|
if (messages.length > 0) {
|
|
@@ -31904,7 +32106,7 @@ function App({ agent }) {
|
|
|
31904
32106
|
|
|
31905
32107
|
// src/cli/index.tsx
|
|
31906
32108
|
var program2 = new Command;
|
|
31907
|
-
program2.name("
|
|
32109
|
+
program2.name("conversations").description("Real-time CLI messaging for AI agents").version("0.0.4");
|
|
31908
32110
|
program2.command("send").description("Send a message to an agent").argument("<message>", "Message content").requiredOption("--to <agent>", "Recipient agent ID").option("--from <agent>", "Sender agent ID").option("--session <id>", "Session ID (auto-generated if omitted)").option("--priority <level>", "Priority: low, normal, high, urgent", "normal").option("--working-dir <path>", "Working directory context").option("--repository <repo>", "Repository context").option("--branch <branch>", "Branch context").option("--metadata <json>", "JSON metadata string").option("--json", "Output as JSON").action((message, opts) => {
|
|
31909
32111
|
const from = resolveIdentity(opts.from);
|
|
31910
32112
|
const metadata = opts.metadata ? JSON.parse(opts.metadata) : undefined;
|
|
@@ -31926,11 +32128,12 @@ program2.command("send").description("Send a message to an agent").argument("<me
|
|
|
31926
32128
|
}
|
|
31927
32129
|
closeDb();
|
|
31928
32130
|
});
|
|
31929
|
-
program2.command("read").description("Read messages").option("--session <id>", "Filter by session ID").option("--from <agent>", "Filter by sender").option("--to <agent>", "Filter by recipient").option("--since <timestamp>", "Messages after this ISO timestamp").option("--limit <n>", "Max messages to return", parseInt).option("--unread", "Only unread messages").option("--mark-read", "Mark returned messages as read").option("--json", "Output as JSON").action((opts) => {
|
|
32131
|
+
program2.command("read").description("Read messages").option("--session <id>", "Filter by session ID").option("--from <agent>", "Filter by sender").option("--to <agent>", "Filter by recipient").option("--channel <name>", "Filter by channel").option("--since <timestamp>", "Messages after this ISO timestamp").option("--limit <n>", "Max messages to return", parseInt).option("--unread", "Only unread messages").option("--mark-read", "Mark returned messages as read").option("--json", "Output as JSON").action((opts) => {
|
|
31930
32132
|
const messages = readMessages({
|
|
31931
32133
|
session_id: opts.session,
|
|
31932
32134
|
from: opts.from,
|
|
31933
32135
|
to: opts.to,
|
|
32136
|
+
channel: opts.channel,
|
|
31934
32137
|
since: opts.since,
|
|
31935
32138
|
limit: opts.limit,
|
|
31936
32139
|
unread_only: opts.unread
|
|
@@ -31949,7 +32152,7 @@ program2.command("read").description("Read messages").option("--session <id>", "
|
|
|
31949
32152
|
for (const msg of messages) {
|
|
31950
32153
|
const time3 = chalk2.dim(msg.created_at.slice(11, 19));
|
|
31951
32154
|
const from = chalk2.cyan(msg.from_agent);
|
|
31952
|
-
const to = chalk2.yellow(msg.to_agent);
|
|
32155
|
+
const to = msg.channel ? chalk2.magenta(`#${msg.channel}`) : chalk2.yellow(msg.to_agent);
|
|
31953
32156
|
const priority = msg.priority !== "normal" ? chalk2.red(` [${msg.priority}]`) : "";
|
|
31954
32157
|
const unread = !msg.read_at ? chalk2.green(" *") : "";
|
|
31955
32158
|
console.log(`${time3} ${from} \u2192 ${to}${priority}${unread}: ${msg.content}`);
|
|
@@ -31996,15 +32199,17 @@ program2.command("reply").description("Reply to a message (uses same session)").
|
|
|
31996
32199
|
}
|
|
31997
32200
|
closeDb();
|
|
31998
32201
|
});
|
|
31999
|
-
program2.command("mark-read").description("Mark messages as read").argument("[ids...]", "Message IDs to mark as read").option("--session <id>", "Mark all messages in session as read").option("--agent <id>", "Agent marking messages as read").option("--json", "Output as JSON").action((ids, opts) => {
|
|
32202
|
+
program2.command("mark-read").description("Mark messages as read").argument("[ids...]", "Message IDs to mark as read").option("--session <id>", "Mark all messages in session as read").option("--channel <name>", "Mark all messages in channel as read").option("--agent <id>", "Agent marking messages as read").option("--json", "Output as JSON").action((ids, opts) => {
|
|
32000
32203
|
const agent = resolveIdentity(opts.agent);
|
|
32001
32204
|
let count = 0;
|
|
32002
32205
|
if (opts.session) {
|
|
32003
32206
|
count = markSessionRead(opts.session, agent);
|
|
32207
|
+
} else if (opts.channel) {
|
|
32208
|
+
count = markChannelRead(opts.channel, agent);
|
|
32004
32209
|
} else if (ids.length > 0) {
|
|
32005
32210
|
count = markRead(ids.map(Number), agent);
|
|
32006
32211
|
} else {
|
|
32007
|
-
console.error(chalk2.red("Provide message IDs or --
|
|
32212
|
+
console.error(chalk2.red("Provide message IDs, --session, or --channel flag."));
|
|
32008
32213
|
process.exit(1);
|
|
32009
32214
|
}
|
|
32010
32215
|
if (opts.json) {
|
|
@@ -32020,10 +32225,12 @@ program2.command("status").description("Show database stats").option("--json", "
|
|
|
32020
32225
|
const totalMessages = db2.prepare("SELECT COUNT(*) as count FROM messages").get().count;
|
|
32021
32226
|
const totalSessions = db2.prepare("SELECT COUNT(DISTINCT session_id) as count FROM messages").get().count;
|
|
32022
32227
|
const totalUnread = db2.prepare("SELECT COUNT(*) as count FROM messages WHERE read_at IS NULL").get().count;
|
|
32228
|
+
const totalChannels = db2.prepare("SELECT COUNT(*) as count FROM channels").get().count;
|
|
32023
32229
|
const stats = {
|
|
32024
32230
|
db_path: dbPath,
|
|
32025
32231
|
total_messages: totalMessages,
|
|
32026
32232
|
total_sessions: totalSessions,
|
|
32233
|
+
total_channels: totalChannels,
|
|
32027
32234
|
unread_messages: totalUnread
|
|
32028
32235
|
};
|
|
32029
32236
|
if (opts.json) {
|
|
@@ -32033,10 +32240,134 @@ program2.command("status").description("Show database stats").option("--json", "
|
|
|
32033
32240
|
console.log(` DB Path: ${stats.db_path}`);
|
|
32034
32241
|
console.log(` Messages: ${stats.total_messages}`);
|
|
32035
32242
|
console.log(` Sessions: ${stats.total_sessions}`);
|
|
32243
|
+
console.log(` Channels: ${stats.total_channels}`);
|
|
32036
32244
|
console.log(` Unread: ${stats.unread_messages}`);
|
|
32037
32245
|
}
|
|
32038
32246
|
closeDb();
|
|
32039
32247
|
});
|
|
32248
|
+
var channel = program2.command("channel").description("Manage channels");
|
|
32249
|
+
channel.command("create").description("Create a new channel").argument("<name>", "Channel name").option("--description <text>", "Channel description").option("--from <agent>", "Creator agent ID").option("--json", "Output as JSON").action((name, opts) => {
|
|
32250
|
+
const agent = resolveIdentity(opts.from);
|
|
32251
|
+
try {
|
|
32252
|
+
const ch = createChannel(name, agent, opts.description);
|
|
32253
|
+
if (opts.json) {
|
|
32254
|
+
console.log(JSON.stringify(ch, null, 2));
|
|
32255
|
+
} else {
|
|
32256
|
+
console.log(chalk2.green(`Channel #${ch.name} created`) + (ch.description ? chalk2.dim(` \u2014 ${ch.description}`) : ""));
|
|
32257
|
+
}
|
|
32258
|
+
} catch (e) {
|
|
32259
|
+
if (e.message?.includes("UNIQUE constraint")) {
|
|
32260
|
+
console.error(chalk2.red(`Channel #${name} already exists.`));
|
|
32261
|
+
process.exit(1);
|
|
32262
|
+
}
|
|
32263
|
+
throw e;
|
|
32264
|
+
}
|
|
32265
|
+
closeDb();
|
|
32266
|
+
});
|
|
32267
|
+
channel.command("list").description("List all channels").option("--json", "Output as JSON").action((opts) => {
|
|
32268
|
+
const channels = listChannels();
|
|
32269
|
+
if (opts.json) {
|
|
32270
|
+
console.log(JSON.stringify(channels, null, 2));
|
|
32271
|
+
} else {
|
|
32272
|
+
if (channels.length === 0) {
|
|
32273
|
+
console.log(chalk2.dim("No channels found."));
|
|
32274
|
+
} else {
|
|
32275
|
+
for (const ch of channels) {
|
|
32276
|
+
const desc = ch.description ? chalk2.dim(` \u2014 ${ch.description}`) : "";
|
|
32277
|
+
console.log(`${chalk2.magenta(`#${ch.name}`)}${desc} ${ch.member_count} members, ${ch.message_count} messages`);
|
|
32278
|
+
}
|
|
32279
|
+
}
|
|
32280
|
+
}
|
|
32281
|
+
closeDb();
|
|
32282
|
+
});
|
|
32283
|
+
channel.command("send").description("Send a message to a channel").argument("<channel>", "Channel name").argument("<message>", "Message content").option("--from <agent>", "Sender agent ID").option("--priority <level>", "Priority: low, normal, high, urgent", "normal").option("--json", "Output as JSON").action((channelName, message, opts) => {
|
|
32284
|
+
const from = resolveIdentity(opts.from);
|
|
32285
|
+
const ch = getChannel(channelName);
|
|
32286
|
+
if (!ch) {
|
|
32287
|
+
console.error(chalk2.red(`Channel #${channelName} not found.`));
|
|
32288
|
+
process.exit(1);
|
|
32289
|
+
}
|
|
32290
|
+
const msg = sendMessage({
|
|
32291
|
+
from,
|
|
32292
|
+
to: channelName,
|
|
32293
|
+
content: message,
|
|
32294
|
+
channel: channelName,
|
|
32295
|
+
session_id: `channel:${channelName}`,
|
|
32296
|
+
priority: opts.priority
|
|
32297
|
+
});
|
|
32298
|
+
if (opts.json) {
|
|
32299
|
+
console.log(JSON.stringify(msg, null, 2));
|
|
32300
|
+
} else {
|
|
32301
|
+
console.log(chalk2.green(`Message sent to #${channelName}`) + chalk2.dim(` (id: ${msg.id})`));
|
|
32302
|
+
}
|
|
32303
|
+
closeDb();
|
|
32304
|
+
});
|
|
32305
|
+
channel.command("read").description("Read messages from a channel").argument("<channel>", "Channel name").option("--since <timestamp>", "Messages after this ISO timestamp").option("--limit <n>", "Max messages to return", parseInt).option("--json", "Output as JSON").action((channelName, opts) => {
|
|
32306
|
+
const messages = readMessages({
|
|
32307
|
+
channel: channelName,
|
|
32308
|
+
since: opts.since,
|
|
32309
|
+
limit: opts.limit
|
|
32310
|
+
});
|
|
32311
|
+
if (opts.json) {
|
|
32312
|
+
console.log(JSON.stringify(messages, null, 2));
|
|
32313
|
+
} else {
|
|
32314
|
+
if (messages.length === 0) {
|
|
32315
|
+
console.log(chalk2.dim(`No messages in #${channelName}.`));
|
|
32316
|
+
} else {
|
|
32317
|
+
for (const msg of messages) {
|
|
32318
|
+
const time3 = chalk2.dim(msg.created_at.slice(11, 19));
|
|
32319
|
+
const from = chalk2.cyan(msg.from_agent);
|
|
32320
|
+
const priority = msg.priority !== "normal" ? chalk2.red(` [${msg.priority}]`) : "";
|
|
32321
|
+
console.log(`${time3} ${from} \u2192 ${chalk2.magenta(`#${channelName}`)}${priority}: ${msg.content}`);
|
|
32322
|
+
}
|
|
32323
|
+
}
|
|
32324
|
+
}
|
|
32325
|
+
closeDb();
|
|
32326
|
+
});
|
|
32327
|
+
channel.command("join").description("Join a channel").argument("<channel>", "Channel name").option("--from <agent>", "Agent ID").option("--json", "Output as JSON").action((channelName, opts) => {
|
|
32328
|
+
const agent = resolveIdentity(opts.from);
|
|
32329
|
+
const ok = joinChannel(channelName, agent);
|
|
32330
|
+
if (!ok) {
|
|
32331
|
+
console.error(chalk2.red(`Channel #${channelName} not found.`));
|
|
32332
|
+
process.exit(1);
|
|
32333
|
+
}
|
|
32334
|
+
if (opts.json) {
|
|
32335
|
+
console.log(JSON.stringify({ channel: channelName, agent, joined: true }));
|
|
32336
|
+
} else {
|
|
32337
|
+
console.log(chalk2.green(`${agent} joined #${channelName}`));
|
|
32338
|
+
}
|
|
32339
|
+
closeDb();
|
|
32340
|
+
});
|
|
32341
|
+
channel.command("leave").description("Leave a channel").argument("<channel>", "Channel name").option("--from <agent>", "Agent ID").option("--json", "Output as JSON").action((channelName, opts) => {
|
|
32342
|
+
const agent = resolveIdentity(opts.from);
|
|
32343
|
+
const ok = leaveChannel(channelName, agent);
|
|
32344
|
+
if (opts.json) {
|
|
32345
|
+
console.log(JSON.stringify({ channel: channelName, agent, left: ok }));
|
|
32346
|
+
} else {
|
|
32347
|
+
if (ok) {
|
|
32348
|
+
console.log(chalk2.green(`${agent} left #${channelName}`));
|
|
32349
|
+
} else {
|
|
32350
|
+
console.log(chalk2.dim(`${agent} was not a member of #${channelName}`));
|
|
32351
|
+
}
|
|
32352
|
+
}
|
|
32353
|
+
closeDb();
|
|
32354
|
+
});
|
|
32355
|
+
channel.command("members").description("List channel members").argument("<channel>", "Channel name").option("--json", "Output as JSON").action((channelName, opts) => {
|
|
32356
|
+
const members = getChannelMembers(channelName);
|
|
32357
|
+
if (opts.json) {
|
|
32358
|
+
console.log(JSON.stringify(members, null, 2));
|
|
32359
|
+
} else {
|
|
32360
|
+
if (members.length === 0) {
|
|
32361
|
+
console.log(chalk2.dim(`No members in #${channelName}.`));
|
|
32362
|
+
} else {
|
|
32363
|
+
console.log(chalk2.magenta(`#${channelName}`) + chalk2.dim(` \u2014 ${members.length} member(s)`));
|
|
32364
|
+
for (const m of members) {
|
|
32365
|
+
console.log(` ${chalk2.cyan(m.agent)} ${chalk2.dim(`joined ${m.joined_at.slice(0, 10)}`)}`);
|
|
32366
|
+
}
|
|
32367
|
+
}
|
|
32368
|
+
}
|
|
32369
|
+
closeDb();
|
|
32370
|
+
});
|
|
32040
32371
|
program2.command("mcp").description("Start MCP server").action(async () => {
|
|
32041
32372
|
const { startMcpServer: startMcpServer2 } = await Promise.resolve().then(() => (init_mcp2(), exports_mcp));
|
|
32042
32373
|
await startMcpServer2();
|
package/bin/mcp.js
CHANGED
|
@@ -28339,6 +28339,7 @@ function getDb() {
|
|
|
28339
28339
|
session_id TEXT NOT NULL,
|
|
28340
28340
|
from_agent TEXT NOT NULL,
|
|
28341
28341
|
to_agent TEXT NOT NULL,
|
|
28342
|
+
channel TEXT,
|
|
28342
28343
|
content TEXT NOT NULL,
|
|
28343
28344
|
priority TEXT NOT NULL DEFAULT 'normal',
|
|
28344
28345
|
working_dir TEXT,
|
|
@@ -28352,6 +28353,27 @@ function getDb() {
|
|
|
28352
28353
|
db.exec("CREATE INDEX IF NOT EXISTS idx_messages_session ON messages(session_id)");
|
|
28353
28354
|
db.exec("CREATE INDEX IF NOT EXISTS idx_messages_to ON messages(to_agent)");
|
|
28354
28355
|
db.exec("CREATE INDEX IF NOT EXISTS idx_messages_created ON messages(created_at)");
|
|
28356
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_messages_channel ON messages(channel)");
|
|
28357
|
+
const cols = db.prepare("PRAGMA table_info(messages)").all();
|
|
28358
|
+
if (!cols.some((c) => c.name === "channel")) {
|
|
28359
|
+
db.exec("ALTER TABLE messages ADD COLUMN channel TEXT");
|
|
28360
|
+
}
|
|
28361
|
+
db.exec(`
|
|
28362
|
+
CREATE TABLE IF NOT EXISTS channels (
|
|
28363
|
+
name TEXT PRIMARY KEY,
|
|
28364
|
+
description TEXT,
|
|
28365
|
+
created_by TEXT NOT NULL,
|
|
28366
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now'))
|
|
28367
|
+
)
|
|
28368
|
+
`);
|
|
28369
|
+
db.exec(`
|
|
28370
|
+
CREATE TABLE IF NOT EXISTS channel_members (
|
|
28371
|
+
channel TEXT NOT NULL REFERENCES channels(name),
|
|
28372
|
+
agent TEXT NOT NULL,
|
|
28373
|
+
joined_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')),
|
|
28374
|
+
PRIMARY KEY (channel, agent)
|
|
28375
|
+
)
|
|
28376
|
+
`);
|
|
28355
28377
|
return db;
|
|
28356
28378
|
}
|
|
28357
28379
|
|
|
@@ -28368,11 +28390,11 @@ function sendMessage(opts) {
|
|
|
28368
28390
|
const sessionId = opts.session_id || `${[opts.from, opts.to].sort().join("-")}-${randomUUID().slice(0, 8)}`;
|
|
28369
28391
|
const metadata = opts.metadata ? JSON.stringify(opts.metadata) : null;
|
|
28370
28392
|
const stmt = db2.prepare(`
|
|
28371
|
-
INSERT INTO messages (session_id, from_agent, to_agent, content, priority, working_dir, repository, branch, metadata)
|
|
28372
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
28393
|
+
INSERT INTO messages (session_id, from_agent, to_agent, channel, content, priority, working_dir, repository, branch, metadata)
|
|
28394
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
28373
28395
|
RETURNING *
|
|
28374
28396
|
`);
|
|
28375
|
-
const row = stmt.get(sessionId, opts.from, opts.to, opts.content, opts.priority || "normal", opts.working_dir || null, opts.repository || null, opts.branch || null, metadata);
|
|
28397
|
+
const row = stmt.get(sessionId, opts.from, opts.to, opts.channel || null, opts.content, opts.priority || "normal", opts.working_dir || null, opts.repository || null, opts.branch || null, metadata);
|
|
28376
28398
|
return parseMessage(row);
|
|
28377
28399
|
}
|
|
28378
28400
|
function readMessages(opts = {}) {
|
|
@@ -28391,6 +28413,10 @@ function readMessages(opts = {}) {
|
|
|
28391
28413
|
conditions.push("to_agent = ?");
|
|
28392
28414
|
params.push(opts.to);
|
|
28393
28415
|
}
|
|
28416
|
+
if (opts.channel) {
|
|
28417
|
+
conditions.push("channel = ?");
|
|
28418
|
+
params.push(opts.channel);
|
|
28419
|
+
}
|
|
28394
28420
|
if (opts.since) {
|
|
28395
28421
|
conditions.push("created_at > ?");
|
|
28396
28422
|
params.push(opts.since);
|
|
@@ -28448,6 +28474,57 @@ function listSessions(agent) {
|
|
|
28448
28474
|
});
|
|
28449
28475
|
}
|
|
28450
28476
|
|
|
28477
|
+
// src/lib/channels.ts
|
|
28478
|
+
function createChannel(name, createdBy, description) {
|
|
28479
|
+
const db2 = getDb();
|
|
28480
|
+
const row = db2.prepare("INSERT INTO channels (name, description, created_by) VALUES (?, ?, ?) RETURNING *").get(name, description || null, createdBy);
|
|
28481
|
+
db2.prepare("INSERT OR IGNORE INTO channel_members (channel, agent) VALUES (?, ?)").run(name, createdBy);
|
|
28482
|
+
return row;
|
|
28483
|
+
}
|
|
28484
|
+
function listChannels() {
|
|
28485
|
+
const db2 = getDb();
|
|
28486
|
+
const rows = db2.prepare(`
|
|
28487
|
+
SELECT
|
|
28488
|
+
c.name,
|
|
28489
|
+
c.description,
|
|
28490
|
+
c.created_by,
|
|
28491
|
+
c.created_at,
|
|
28492
|
+
(SELECT COUNT(*) FROM channel_members WHERE channel = c.name) AS member_count,
|
|
28493
|
+
(SELECT COUNT(*) FROM messages WHERE channel = c.name) AS message_count
|
|
28494
|
+
FROM channels c
|
|
28495
|
+
ORDER BY c.name ASC
|
|
28496
|
+
`).all();
|
|
28497
|
+
return rows;
|
|
28498
|
+
}
|
|
28499
|
+
function getChannel(name) {
|
|
28500
|
+
const db2 = getDb();
|
|
28501
|
+
const row = db2.prepare(`
|
|
28502
|
+
SELECT
|
|
28503
|
+
c.name,
|
|
28504
|
+
c.description,
|
|
28505
|
+
c.created_by,
|
|
28506
|
+
c.created_at,
|
|
28507
|
+
(SELECT COUNT(*) FROM channel_members WHERE channel = c.name) AS member_count,
|
|
28508
|
+
(SELECT COUNT(*) FROM messages WHERE channel = c.name) AS message_count
|
|
28509
|
+
FROM channels c
|
|
28510
|
+
WHERE c.name = ?
|
|
28511
|
+
`).get(name);
|
|
28512
|
+
return row;
|
|
28513
|
+
}
|
|
28514
|
+
function joinChannel(channelName, agent) {
|
|
28515
|
+
const db2 = getDb();
|
|
28516
|
+
const channel = db2.prepare("SELECT name FROM channels WHERE name = ?").get(channelName);
|
|
28517
|
+
if (!channel)
|
|
28518
|
+
return false;
|
|
28519
|
+
db2.prepare("INSERT OR IGNORE INTO channel_members (channel, agent) VALUES (?, ?)").run(channelName, agent);
|
|
28520
|
+
return true;
|
|
28521
|
+
}
|
|
28522
|
+
function leaveChannel(channelName, agent) {
|
|
28523
|
+
const db2 = getDb();
|
|
28524
|
+
const result = db2.prepare("DELETE FROM channel_members WHERE channel = ? AND agent = ?").run(channelName, agent);
|
|
28525
|
+
return result.changes > 0;
|
|
28526
|
+
}
|
|
28527
|
+
|
|
28451
28528
|
// src/lib/identity.ts
|
|
28452
28529
|
function resolveIdentity(explicit) {
|
|
28453
28530
|
if (explicit)
|
|
@@ -28460,11 +28537,11 @@ function resolveIdentity(explicit) {
|
|
|
28460
28537
|
// src/mcp/index.ts
|
|
28461
28538
|
var server = new McpServer({
|
|
28462
28539
|
name: "conversations",
|
|
28463
|
-
version: "0.0.
|
|
28540
|
+
version: "0.0.4"
|
|
28464
28541
|
});
|
|
28465
28542
|
server.registerTool("send_message", {
|
|
28466
28543
|
title: "Send Message",
|
|
28467
|
-
description: "Send a message to another agent. The sender is auto-resolved from CONVERSATIONS_AGENT_ID env var.",
|
|
28544
|
+
description: "Send a direct message to another agent. The sender is auto-resolved from CONVERSATIONS_AGENT_ID env var.",
|
|
28468
28545
|
inputSchema: {
|
|
28469
28546
|
to: exports_external.string().describe("Recipient agent ID"),
|
|
28470
28547
|
content: exports_external.string().describe("Message content"),
|
|
@@ -28500,6 +28577,7 @@ server.registerTool("read_messages", {
|
|
|
28500
28577
|
session_id: exports_external.string().optional().describe("Filter by session ID"),
|
|
28501
28578
|
from: exports_external.string().optional().describe("Filter by sender agent ID"),
|
|
28502
28579
|
to: exports_external.string().optional().describe("Filter by recipient agent ID"),
|
|
28580
|
+
channel: exports_external.string().optional().describe("Filter by channel name"),
|
|
28503
28581
|
since: exports_external.string().optional().describe("Messages after this ISO timestamp"),
|
|
28504
28582
|
limit: exports_external.number().optional().describe("Max messages to return"),
|
|
28505
28583
|
unread_only: exports_external.boolean().optional().describe("Only return unread messages")
|
|
@@ -28563,6 +28641,114 @@ server.registerTool("mark_read", {
|
|
|
28563
28641
|
content: [{ type: "text", text: JSON.stringify({ marked_read: count }, null, 2) }]
|
|
28564
28642
|
};
|
|
28565
28643
|
});
|
|
28644
|
+
server.registerTool("create_channel", {
|
|
28645
|
+
title: "Create Channel",
|
|
28646
|
+
description: "Create a new channel. The creator is auto-joined.",
|
|
28647
|
+
inputSchema: {
|
|
28648
|
+
name: exports_external.string().describe("Channel name (e.g. 'deployments', 'code-review')"),
|
|
28649
|
+
description: exports_external.string().optional().describe("Channel description")
|
|
28650
|
+
}
|
|
28651
|
+
}, async ({ name, description }) => {
|
|
28652
|
+
const agent = resolveIdentity();
|
|
28653
|
+
try {
|
|
28654
|
+
const ch = createChannel(name, agent, description);
|
|
28655
|
+
return {
|
|
28656
|
+
content: [{ type: "text", text: JSON.stringify(ch, null, 2) }]
|
|
28657
|
+
};
|
|
28658
|
+
} catch (e) {
|
|
28659
|
+
if (e.message?.includes("UNIQUE constraint")) {
|
|
28660
|
+
return {
|
|
28661
|
+
content: [{ type: "text", text: `Channel #${name} already exists` }],
|
|
28662
|
+
isError: true
|
|
28663
|
+
};
|
|
28664
|
+
}
|
|
28665
|
+
throw e;
|
|
28666
|
+
}
|
|
28667
|
+
});
|
|
28668
|
+
server.registerTool("list_channels", {
|
|
28669
|
+
title: "List Channels",
|
|
28670
|
+
description: "List all available channels with member and message counts."
|
|
28671
|
+
}, async () => {
|
|
28672
|
+
const channels = listChannels();
|
|
28673
|
+
return {
|
|
28674
|
+
content: [{ type: "text", text: JSON.stringify(channels, null, 2) }]
|
|
28675
|
+
};
|
|
28676
|
+
});
|
|
28677
|
+
server.registerTool("send_to_channel", {
|
|
28678
|
+
title: "Send to Channel",
|
|
28679
|
+
description: "Send a message to a channel. All members can see it.",
|
|
28680
|
+
inputSchema: {
|
|
28681
|
+
channel: exports_external.string().describe("Channel name"),
|
|
28682
|
+
content: exports_external.string().describe("Message content"),
|
|
28683
|
+
priority: exports_external.enum(["low", "normal", "high", "urgent"]).optional().describe("Message priority")
|
|
28684
|
+
}
|
|
28685
|
+
}, async ({ channel, content, priority }) => {
|
|
28686
|
+
const from = resolveIdentity();
|
|
28687
|
+
const ch = getChannel(channel);
|
|
28688
|
+
if (!ch) {
|
|
28689
|
+
return {
|
|
28690
|
+
content: [{ type: "text", text: `Channel #${channel} not found` }],
|
|
28691
|
+
isError: true
|
|
28692
|
+
};
|
|
28693
|
+
}
|
|
28694
|
+
const msg = sendMessage({
|
|
28695
|
+
from,
|
|
28696
|
+
to: channel,
|
|
28697
|
+
content,
|
|
28698
|
+
channel,
|
|
28699
|
+
session_id: `channel:${channel}`,
|
|
28700
|
+
priority
|
|
28701
|
+
});
|
|
28702
|
+
return {
|
|
28703
|
+
content: [{ type: "text", text: JSON.stringify(msg, null, 2) }]
|
|
28704
|
+
};
|
|
28705
|
+
});
|
|
28706
|
+
server.registerTool("read_channel", {
|
|
28707
|
+
title: "Read Channel",
|
|
28708
|
+
description: "Read messages from a channel.",
|
|
28709
|
+
inputSchema: {
|
|
28710
|
+
channel: exports_external.string().describe("Channel name"),
|
|
28711
|
+
since: exports_external.string().optional().describe("Messages after this ISO timestamp"),
|
|
28712
|
+
limit: exports_external.number().optional().describe("Max messages to return")
|
|
28713
|
+
}
|
|
28714
|
+
}, async ({ channel, since, limit }) => {
|
|
28715
|
+
const messages = readMessages({ channel, since, limit });
|
|
28716
|
+
return {
|
|
28717
|
+
content: [{ type: "text", text: JSON.stringify(messages, null, 2) }]
|
|
28718
|
+
};
|
|
28719
|
+
});
|
|
28720
|
+
server.registerTool("join_channel", {
|
|
28721
|
+
title: "Join Channel",
|
|
28722
|
+
description: "Join a channel to receive messages.",
|
|
28723
|
+
inputSchema: {
|
|
28724
|
+
channel: exports_external.string().describe("Channel name to join")
|
|
28725
|
+
}
|
|
28726
|
+
}, async ({ channel }) => {
|
|
28727
|
+
const agent = resolveIdentity();
|
|
28728
|
+
const ok = joinChannel(channel, agent);
|
|
28729
|
+
if (!ok) {
|
|
28730
|
+
return {
|
|
28731
|
+
content: [{ type: "text", text: `Channel #${channel} not found` }],
|
|
28732
|
+
isError: true
|
|
28733
|
+
};
|
|
28734
|
+
}
|
|
28735
|
+
return {
|
|
28736
|
+
content: [{ type: "text", text: JSON.stringify({ channel, agent, joined: true }, null, 2) }]
|
|
28737
|
+
};
|
|
28738
|
+
});
|
|
28739
|
+
server.registerTool("leave_channel", {
|
|
28740
|
+
title: "Leave Channel",
|
|
28741
|
+
description: "Leave a channel.",
|
|
28742
|
+
inputSchema: {
|
|
28743
|
+
channel: exports_external.string().describe("Channel name to leave")
|
|
28744
|
+
}
|
|
28745
|
+
}, async ({ channel }) => {
|
|
28746
|
+
const agent = resolveIdentity();
|
|
28747
|
+
const left = leaveChannel(channel, agent);
|
|
28748
|
+
return {
|
|
28749
|
+
content: [{ type: "text", text: JSON.stringify({ channel, agent, left }, null, 2) }]
|
|
28750
|
+
};
|
|
28751
|
+
});
|
|
28566
28752
|
async function startMcpServer() {
|
|
28567
28753
|
const transport = new StdioServerTransport;
|
|
28568
28754
|
await server.connect(transport);
|
package/dist/index.d.ts
CHANGED
|
@@ -2,15 +2,17 @@
|
|
|
2
2
|
* @hasna/conversations - Real-time CLI messaging for AI agents
|
|
3
3
|
*
|
|
4
4
|
* Send and receive messages between AI agents on the same machine:
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* conversations send --to claude-code "hello from codex"
|
|
6
|
+
* conversations read --to codex --json
|
|
7
|
+
* conversations channel send deployments "v1.2 deployed"
|
|
7
8
|
*
|
|
8
9
|
* Or use the interactive TUI:
|
|
9
|
-
*
|
|
10
|
+
* conversations
|
|
10
11
|
*/
|
|
11
|
-
export { sendMessage, readMessages, markRead, markSessionRead, getMessageById, } from "./lib/messages.js";
|
|
12
|
+
export { sendMessage, readMessages, markRead, markSessionRead, markChannelRead, getMessageById, } from "./lib/messages.js";
|
|
12
13
|
export { listSessions, getSession, } from "./lib/sessions.js";
|
|
14
|
+
export { createChannel, listChannels, getChannel, joinChannel, leaveChannel, getChannelMembers, isChannelMember, } from "./lib/channels.js";
|
|
13
15
|
export { getDb, getDbPath, closeDb, } from "./lib/db.js";
|
|
14
|
-
export { startPolling, } from "./lib/poll.js";
|
|
16
|
+
export { startPolling, useChannelMessages, } from "./lib/poll.js";
|
|
15
17
|
export { resolveIdentity, requireIdentity, } from "./lib/identity.js";
|
|
16
|
-
export type { Message, Session, Priority, SendMessageOptions, ReadMessagesOptions, } from "./types.js";
|
|
18
|
+
export type { Message, Session, Channel, ChannelInfo, ChannelMember, Priority, SendMessageOptions, ReadMessagesOptions, } from "./types.js";
|
package/dist/index.js
CHANGED
|
@@ -1851,6 +1851,7 @@ function getDb() {
|
|
|
1851
1851
|
session_id TEXT NOT NULL,
|
|
1852
1852
|
from_agent TEXT NOT NULL,
|
|
1853
1853
|
to_agent TEXT NOT NULL,
|
|
1854
|
+
channel TEXT,
|
|
1854
1855
|
content TEXT NOT NULL,
|
|
1855
1856
|
priority TEXT NOT NULL DEFAULT 'normal',
|
|
1856
1857
|
working_dir TEXT,
|
|
@@ -1864,6 +1865,27 @@ function getDb() {
|
|
|
1864
1865
|
db.exec("CREATE INDEX IF NOT EXISTS idx_messages_session ON messages(session_id)");
|
|
1865
1866
|
db.exec("CREATE INDEX IF NOT EXISTS idx_messages_to ON messages(to_agent)");
|
|
1866
1867
|
db.exec("CREATE INDEX IF NOT EXISTS idx_messages_created ON messages(created_at)");
|
|
1868
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_messages_channel ON messages(channel)");
|
|
1869
|
+
const cols = db.prepare("PRAGMA table_info(messages)").all();
|
|
1870
|
+
if (!cols.some((c) => c.name === "channel")) {
|
|
1871
|
+
db.exec("ALTER TABLE messages ADD COLUMN channel TEXT");
|
|
1872
|
+
}
|
|
1873
|
+
db.exec(`
|
|
1874
|
+
CREATE TABLE IF NOT EXISTS channels (
|
|
1875
|
+
name TEXT PRIMARY KEY,
|
|
1876
|
+
description TEXT,
|
|
1877
|
+
created_by TEXT NOT NULL,
|
|
1878
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now'))
|
|
1879
|
+
)
|
|
1880
|
+
`);
|
|
1881
|
+
db.exec(`
|
|
1882
|
+
CREATE TABLE IF NOT EXISTS channel_members (
|
|
1883
|
+
channel TEXT NOT NULL REFERENCES channels(name),
|
|
1884
|
+
agent TEXT NOT NULL,
|
|
1885
|
+
joined_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')),
|
|
1886
|
+
PRIMARY KEY (channel, agent)
|
|
1887
|
+
)
|
|
1888
|
+
`);
|
|
1867
1889
|
return db;
|
|
1868
1890
|
}
|
|
1869
1891
|
function closeDb() {
|
|
@@ -1886,11 +1908,11 @@ function sendMessage(opts) {
|
|
|
1886
1908
|
const sessionId = opts.session_id || `${[opts.from, opts.to].sort().join("-")}-${randomUUID().slice(0, 8)}`;
|
|
1887
1909
|
const metadata = opts.metadata ? JSON.stringify(opts.metadata) : null;
|
|
1888
1910
|
const stmt = db2.prepare(`
|
|
1889
|
-
INSERT INTO messages (session_id, from_agent, to_agent, content, priority, working_dir, repository, branch, metadata)
|
|
1890
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1911
|
+
INSERT INTO messages (session_id, from_agent, to_agent, channel, content, priority, working_dir, repository, branch, metadata)
|
|
1912
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1891
1913
|
RETURNING *
|
|
1892
1914
|
`);
|
|
1893
|
-
const row = stmt.get(sessionId, opts.from, opts.to, opts.content, opts.priority || "normal", opts.working_dir || null, opts.repository || null, opts.branch || null, metadata);
|
|
1915
|
+
const row = stmt.get(sessionId, opts.from, opts.to, opts.channel || null, opts.content, opts.priority || "normal", opts.working_dir || null, opts.repository || null, opts.branch || null, metadata);
|
|
1894
1916
|
return parseMessage(row);
|
|
1895
1917
|
}
|
|
1896
1918
|
function readMessages(opts = {}) {
|
|
@@ -1909,6 +1931,10 @@ function readMessages(opts = {}) {
|
|
|
1909
1931
|
conditions.push("to_agent = ?");
|
|
1910
1932
|
params.push(opts.to);
|
|
1911
1933
|
}
|
|
1934
|
+
if (opts.channel) {
|
|
1935
|
+
conditions.push("channel = ?");
|
|
1936
|
+
params.push(opts.channel);
|
|
1937
|
+
}
|
|
1912
1938
|
if (opts.since) {
|
|
1913
1939
|
conditions.push("created_at > ?");
|
|
1914
1940
|
params.push(opts.since);
|
|
@@ -1936,6 +1962,12 @@ function markSessionRead(sessionId, reader) {
|
|
|
1936
1962
|
const result = stmt.run(sessionId, reader);
|
|
1937
1963
|
return result.changes;
|
|
1938
1964
|
}
|
|
1965
|
+
function markChannelRead(channelName, reader) {
|
|
1966
|
+
const db2 = getDb();
|
|
1967
|
+
const stmt = db2.prepare(`UPDATE messages SET read_at = strftime('%Y-%m-%dT%H:%M:%f', 'now') WHERE channel = ? AND from_agent != ? AND read_at IS NULL`);
|
|
1968
|
+
const result = stmt.run(channelName, reader);
|
|
1969
|
+
return result.changes;
|
|
1970
|
+
}
|
|
1939
1971
|
function getMessageById(id) {
|
|
1940
1972
|
const db2 = getDb();
|
|
1941
1973
|
const row = db2.prepare("SELECT * FROM messages WHERE id = ?").get(id);
|
|
@@ -1995,6 +2027,65 @@ function getSession(sessionId) {
|
|
|
1995
2027
|
unread_count: row.unread_count
|
|
1996
2028
|
};
|
|
1997
2029
|
}
|
|
2030
|
+
// src/lib/channels.ts
|
|
2031
|
+
function createChannel(name, createdBy, description) {
|
|
2032
|
+
const db2 = getDb();
|
|
2033
|
+
const row = db2.prepare("INSERT INTO channels (name, description, created_by) VALUES (?, ?, ?) RETURNING *").get(name, description || null, createdBy);
|
|
2034
|
+
db2.prepare("INSERT OR IGNORE INTO channel_members (channel, agent) VALUES (?, ?)").run(name, createdBy);
|
|
2035
|
+
return row;
|
|
2036
|
+
}
|
|
2037
|
+
function listChannels() {
|
|
2038
|
+
const db2 = getDb();
|
|
2039
|
+
const rows = db2.prepare(`
|
|
2040
|
+
SELECT
|
|
2041
|
+
c.name,
|
|
2042
|
+
c.description,
|
|
2043
|
+
c.created_by,
|
|
2044
|
+
c.created_at,
|
|
2045
|
+
(SELECT COUNT(*) FROM channel_members WHERE channel = c.name) AS member_count,
|
|
2046
|
+
(SELECT COUNT(*) FROM messages WHERE channel = c.name) AS message_count
|
|
2047
|
+
FROM channels c
|
|
2048
|
+
ORDER BY c.name ASC
|
|
2049
|
+
`).all();
|
|
2050
|
+
return rows;
|
|
2051
|
+
}
|
|
2052
|
+
function getChannel(name) {
|
|
2053
|
+
const db2 = getDb();
|
|
2054
|
+
const row = db2.prepare(`
|
|
2055
|
+
SELECT
|
|
2056
|
+
c.name,
|
|
2057
|
+
c.description,
|
|
2058
|
+
c.created_by,
|
|
2059
|
+
c.created_at,
|
|
2060
|
+
(SELECT COUNT(*) FROM channel_members WHERE channel = c.name) AS member_count,
|
|
2061
|
+
(SELECT COUNT(*) FROM messages WHERE channel = c.name) AS message_count
|
|
2062
|
+
FROM channels c
|
|
2063
|
+
WHERE c.name = ?
|
|
2064
|
+
`).get(name);
|
|
2065
|
+
return row;
|
|
2066
|
+
}
|
|
2067
|
+
function joinChannel(channelName, agent) {
|
|
2068
|
+
const db2 = getDb();
|
|
2069
|
+
const channel = db2.prepare("SELECT name FROM channels WHERE name = ?").get(channelName);
|
|
2070
|
+
if (!channel)
|
|
2071
|
+
return false;
|
|
2072
|
+
db2.prepare("INSERT OR IGNORE INTO channel_members (channel, agent) VALUES (?, ?)").run(channelName, agent);
|
|
2073
|
+
return true;
|
|
2074
|
+
}
|
|
2075
|
+
function leaveChannel(channelName, agent) {
|
|
2076
|
+
const db2 = getDb();
|
|
2077
|
+
const result = db2.prepare("DELETE FROM channel_members WHERE channel = ? AND agent = ?").run(channelName, agent);
|
|
2078
|
+
return result.changes > 0;
|
|
2079
|
+
}
|
|
2080
|
+
function getChannelMembers(channelName) {
|
|
2081
|
+
const db2 = getDb();
|
|
2082
|
+
return db2.prepare("SELECT channel, agent, joined_at FROM channel_members WHERE channel = ? ORDER BY joined_at ASC").all(channelName);
|
|
2083
|
+
}
|
|
2084
|
+
function isChannelMember(channelName, agent) {
|
|
2085
|
+
const db2 = getDb();
|
|
2086
|
+
const row = db2.prepare("SELECT 1 FROM channel_members WHERE channel = ? AND agent = ?").get(channelName, agent);
|
|
2087
|
+
return !!row;
|
|
2088
|
+
}
|
|
1998
2089
|
// src/lib/poll.ts
|
|
1999
2090
|
var import_react = __toESM(require_react(), 1);
|
|
2000
2091
|
function startPolling(opts) {
|
|
@@ -2007,6 +2098,7 @@ function startPolling(opts) {
|
|
|
2007
2098
|
const messages = readMessages({
|
|
2008
2099
|
session_id: opts.session_id,
|
|
2009
2100
|
to: opts.to_agent,
|
|
2101
|
+
channel: opts.channel,
|
|
2010
2102
|
since: lastSeen
|
|
2011
2103
|
});
|
|
2012
2104
|
if (messages.length > 0) {
|
|
@@ -2022,6 +2114,26 @@ function startPolling(opts) {
|
|
|
2022
2114
|
}
|
|
2023
2115
|
};
|
|
2024
2116
|
}
|
|
2117
|
+
function useChannelMessages(channelName) {
|
|
2118
|
+
const [messages, setMessages] = import_react.useState([]);
|
|
2119
|
+
const initialLoad = import_react.useRef(false);
|
|
2120
|
+
import_react.useEffect(() => {
|
|
2121
|
+
if (!initialLoad.current) {
|
|
2122
|
+
const existing = readMessages({ channel: channelName });
|
|
2123
|
+
setMessages(existing);
|
|
2124
|
+
initialLoad.current = true;
|
|
2125
|
+
}
|
|
2126
|
+
const { stop } = startPolling({
|
|
2127
|
+
channel: channelName,
|
|
2128
|
+
interval_ms: 200,
|
|
2129
|
+
on_messages: (newMessages) => {
|
|
2130
|
+
setMessages((prev) => [...prev, ...newMessages]);
|
|
2131
|
+
}
|
|
2132
|
+
});
|
|
2133
|
+
return stop;
|
|
2134
|
+
}, [channelName]);
|
|
2135
|
+
return messages;
|
|
2136
|
+
}
|
|
2025
2137
|
// src/lib/identity.ts
|
|
2026
2138
|
function resolveIdentity(explicit) {
|
|
2027
2139
|
if (explicit)
|
|
@@ -2038,6 +2150,7 @@ function requireIdentity(explicit) {
|
|
|
2038
2150
|
throw new Error("Agent identity required. Set CONVERSATIONS_AGENT_ID env var or pass --from flag.");
|
|
2039
2151
|
}
|
|
2040
2152
|
export {
|
|
2153
|
+
useChannelMessages,
|
|
2041
2154
|
startPolling,
|
|
2042
2155
|
sendMessage,
|
|
2043
2156
|
resolveIdentity,
|
|
@@ -2045,10 +2158,18 @@ export {
|
|
|
2045
2158
|
readMessages,
|
|
2046
2159
|
markSessionRead,
|
|
2047
2160
|
markRead,
|
|
2161
|
+
markChannelRead,
|
|
2048
2162
|
listSessions,
|
|
2163
|
+
listChannels,
|
|
2164
|
+
leaveChannel,
|
|
2165
|
+
joinChannel,
|
|
2166
|
+
isChannelMember,
|
|
2049
2167
|
getSession,
|
|
2050
2168
|
getMessageById,
|
|
2051
2169
|
getDbPath,
|
|
2052
2170
|
getDb,
|
|
2171
|
+
getChannelMembers,
|
|
2172
|
+
getChannel,
|
|
2173
|
+
createChannel,
|
|
2053
2174
|
closeDb
|
|
2054
2175
|
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Channel, ChannelInfo, ChannelMember } from "../types.js";
|
|
2
|
+
export declare function createChannel(name: string, createdBy: string, description?: string): Channel;
|
|
3
|
+
export declare function listChannels(): ChannelInfo[];
|
|
4
|
+
export declare function getChannel(name: string): ChannelInfo | null;
|
|
5
|
+
export declare function joinChannel(channelName: string, agent: string): boolean;
|
|
6
|
+
export declare function leaveChannel(channelName: string, agent: string): boolean;
|
|
7
|
+
export declare function getChannelMembers(channelName: string): ChannelMember[];
|
|
8
|
+
export declare function isChannelMember(channelName: string, agent: string): boolean;
|
package/dist/lib/messages.d.ts
CHANGED
|
@@ -3,4 +3,5 @@ export declare function sendMessage(opts: SendMessageOptions): Message;
|
|
|
3
3
|
export declare function readMessages(opts?: ReadMessagesOptions): Message[];
|
|
4
4
|
export declare function markRead(ids: number[], reader: string): number;
|
|
5
5
|
export declare function markSessionRead(sessionId: string, reader: string): number;
|
|
6
|
+
export declare function markChannelRead(channelName: string, reader: string): number;
|
|
6
7
|
export declare function getMessageById(id: number): Message | null;
|
package/dist/lib/poll.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { Message } from "../types.js";
|
|
|
2
2
|
export interface PollOptions {
|
|
3
3
|
session_id?: string;
|
|
4
4
|
to_agent?: string;
|
|
5
|
+
channel?: string;
|
|
5
6
|
interval_ms?: number;
|
|
6
7
|
on_messages: (messages: Message[]) => void;
|
|
7
8
|
}
|
|
@@ -15,3 +16,7 @@ export declare function startPolling(opts: PollOptions): {
|
|
|
15
16
|
* React hook for polling messages in a session.
|
|
16
17
|
*/
|
|
17
18
|
export declare function useMessages(sessionId: string, agent?: string): Message[];
|
|
19
|
+
/**
|
|
20
|
+
* React hook for polling messages in a channel.
|
|
21
|
+
*/
|
|
22
|
+
export declare function useChannelMessages(channelName: string): Message[];
|
package/dist/mcp/index.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
/**
|
|
3
3
|
* MCP server for conversations.
|
|
4
|
-
* Exposes tools for sending, reading, and managing messages between agents.
|
|
4
|
+
* Exposes tools for sending, reading, and managing messages and channels between agents.
|
|
5
5
|
*
|
|
6
6
|
* Usage:
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* conversations mcp # Start MCP server on stdio
|
|
8
|
+
* conversations-mcp # Direct binary
|
|
9
9
|
*/
|
|
10
10
|
export declare function startMcpServer(): Promise<void>;
|
package/dist/types.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export interface Message {
|
|
|
4
4
|
session_id: string;
|
|
5
5
|
from_agent: string;
|
|
6
6
|
to_agent: string;
|
|
7
|
+
channel: string | null;
|
|
7
8
|
content: string;
|
|
8
9
|
priority: Priority;
|
|
9
10
|
working_dir: string | null;
|
|
@@ -20,11 +21,27 @@ export interface Session {
|
|
|
20
21
|
message_count: number;
|
|
21
22
|
unread_count: number;
|
|
22
23
|
}
|
|
24
|
+
export interface Channel {
|
|
25
|
+
name: string;
|
|
26
|
+
description: string | null;
|
|
27
|
+
created_by: string;
|
|
28
|
+
created_at: string;
|
|
29
|
+
}
|
|
30
|
+
export interface ChannelMember {
|
|
31
|
+
channel: string;
|
|
32
|
+
agent: string;
|
|
33
|
+
joined_at: string;
|
|
34
|
+
}
|
|
35
|
+
export interface ChannelInfo extends Channel {
|
|
36
|
+
member_count: number;
|
|
37
|
+
message_count: number;
|
|
38
|
+
}
|
|
23
39
|
export interface SendMessageOptions {
|
|
24
40
|
from: string;
|
|
25
41
|
to: string;
|
|
26
42
|
content: string;
|
|
27
43
|
session_id?: string;
|
|
44
|
+
channel?: string;
|
|
28
45
|
priority?: Priority;
|
|
29
46
|
working_dir?: string;
|
|
30
47
|
repository?: string;
|
|
@@ -35,6 +52,7 @@ export interface ReadMessagesOptions {
|
|
|
35
52
|
session_id?: string;
|
|
36
53
|
from?: string;
|
|
37
54
|
to?: string;
|
|
55
|
+
channel?: string;
|
|
38
56
|
since?: string;
|
|
39
57
|
limit?: number;
|
|
40
58
|
unread_only?: boolean;
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hasna/conversations",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "Real-time CLI messaging for AI agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"
|
|
8
|
-
"
|
|
7
|
+
"conversations": "bin/index.js",
|
|
8
|
+
"conversations-mcp": "bin/mcp.js"
|
|
9
9
|
},
|
|
10
10
|
"exports": {
|
|
11
11
|
".": {
|