@hasna/conversations 0.0.8 → 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/README.md +222 -100
- package/bin/index.js +2349 -283
- package/bin/mcp.js +1124 -111
- package/dashboard/dist/assets/index-B6bl8Jzt.css +1 -0
- package/dashboard/dist/assets/index-Dbrg7by9.js +183 -0
- package/dashboard/dist/index.html +2 -2
- package/dist/cli/components/ChatView.d.ts +2 -2
- package/dist/cli/components/SessionList.d.ts +2 -2
- package/dist/index.d.ts +7 -5
- package/dist/index.js +700 -93
- package/dist/lib/messages.d.ts +22 -2
- package/dist/lib/poll.d.ts +3 -3
- package/dist/lib/presence.d.ts +7 -0
- package/dist/lib/projects.d.ts +27 -0
- package/dist/lib/projects.test.d.ts +1 -0
- package/dist/lib/spaces.d.ts +29 -0
- package/dist/lib/spaces.test.d.ts +1 -0
- package/dist/mcp/index.d.ts +1 -1
- package/dist/server/serve.d.ts +1 -1
- package/dist/types.d.ts +45 -7
- package/package.json +1 -1
- package/dashboard/dist/assets/index-C5hQqoWV.js +0 -163
- package/dashboard/dist/assets/index-Dr54QXlJ.css +0 -1
- package/dist/lib/channels.d.ts +0 -8
- /package/dist/lib/{channels.test.d.ts → presence.test.d.ts} +0 -0
package/bin/index.js
CHANGED
|
@@ -1892,7 +1892,7 @@ function getDb() {
|
|
|
1892
1892
|
session_id TEXT NOT NULL,
|
|
1893
1893
|
from_agent TEXT NOT NULL,
|
|
1894
1894
|
to_agent TEXT NOT NULL,
|
|
1895
|
-
|
|
1895
|
+
space TEXT,
|
|
1896
1896
|
content TEXT NOT NULL,
|
|
1897
1897
|
priority TEXT NOT NULL DEFAULT 'normal',
|
|
1898
1898
|
working_dir TEXT,
|
|
@@ -1906,27 +1906,106 @@ function getDb() {
|
|
|
1906
1906
|
db.exec("CREATE INDEX IF NOT EXISTS idx_messages_session ON messages(session_id)");
|
|
1907
1907
|
db.exec("CREATE INDEX IF NOT EXISTS idx_messages_to ON messages(to_agent)");
|
|
1908
1908
|
db.exec("CREATE INDEX IF NOT EXISTS idx_messages_created ON messages(created_at)");
|
|
1909
|
-
db.exec("CREATE INDEX IF NOT EXISTS
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1909
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_messages_space ON messages(space)");
|
|
1910
|
+
db.exec(`
|
|
1911
|
+
CREATE TABLE IF NOT EXISTS projects (
|
|
1912
|
+
id TEXT PRIMARY KEY,
|
|
1913
|
+
name TEXT NOT NULL UNIQUE,
|
|
1914
|
+
description TEXT,
|
|
1915
|
+
path TEXT,
|
|
1916
|
+
created_by TEXT NOT NULL,
|
|
1917
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')),
|
|
1918
|
+
metadata TEXT,
|
|
1919
|
+
tags TEXT,
|
|
1920
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
1921
|
+
repository TEXT,
|
|
1922
|
+
settings TEXT
|
|
1923
|
+
)
|
|
1924
|
+
`);
|
|
1925
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_projects_name ON projects(name)");
|
|
1926
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_projects_status ON projects(status)");
|
|
1914
1927
|
db.exec(`
|
|
1915
|
-
CREATE TABLE IF NOT EXISTS
|
|
1928
|
+
CREATE TABLE IF NOT EXISTS spaces (
|
|
1916
1929
|
name TEXT PRIMARY KEY,
|
|
1917
1930
|
description TEXT,
|
|
1931
|
+
parent_id TEXT REFERENCES spaces(name),
|
|
1932
|
+
project_id TEXT REFERENCES projects(id),
|
|
1918
1933
|
created_by TEXT NOT NULL,
|
|
1919
|
-
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now'))
|
|
1934
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')),
|
|
1935
|
+
archived_at TEXT
|
|
1920
1936
|
)
|
|
1921
1937
|
`);
|
|
1938
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_spaces_parent ON spaces(parent_id)");
|
|
1939
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_spaces_project ON spaces(project_id)");
|
|
1922
1940
|
db.exec(`
|
|
1923
|
-
CREATE TABLE IF NOT EXISTS
|
|
1924
|
-
|
|
1941
|
+
CREATE TABLE IF NOT EXISTS space_members (
|
|
1942
|
+
space TEXT NOT NULL REFERENCES spaces(name),
|
|
1925
1943
|
agent TEXT NOT NULL,
|
|
1926
1944
|
joined_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')),
|
|
1927
|
-
PRIMARY KEY (
|
|
1945
|
+
PRIMARY KEY (space, agent)
|
|
1946
|
+
)
|
|
1947
|
+
`);
|
|
1948
|
+
db.exec(`
|
|
1949
|
+
CREATE TABLE IF NOT EXISTS agent_presence (
|
|
1950
|
+
agent TEXT PRIMARY KEY,
|
|
1951
|
+
status TEXT NOT NULL DEFAULT 'online',
|
|
1952
|
+
last_seen_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')),
|
|
1953
|
+
metadata TEXT
|
|
1928
1954
|
)
|
|
1929
1955
|
`);
|
|
1956
|
+
const existingTables = db.prepare("SELECT name FROM sqlite_master WHERE type='table'").all();
|
|
1957
|
+
const tableNames = existingTables.map((t) => t.name);
|
|
1958
|
+
if (tableNames.includes("channels") && tableNames.includes("spaces")) {
|
|
1959
|
+
const spaceCount = db.prepare("SELECT COUNT(*) as c FROM spaces").get().c;
|
|
1960
|
+
const channelCount = db.prepare("SELECT COUNT(*) as c FROM channels").get().c;
|
|
1961
|
+
if (channelCount > 0 && spaceCount === 0) {
|
|
1962
|
+
db.exec("BEGIN");
|
|
1963
|
+
try {
|
|
1964
|
+
db.exec(`
|
|
1965
|
+
INSERT OR IGNORE INTO spaces (name, description, created_by, created_at)
|
|
1966
|
+
SELECT name, description, created_by, created_at FROM channels
|
|
1967
|
+
`);
|
|
1968
|
+
if (tableNames.includes("channel_members")) {
|
|
1969
|
+
db.exec(`
|
|
1970
|
+
INSERT OR IGNORE INTO space_members (space, agent, joined_at)
|
|
1971
|
+
SELECT channel, agent, joined_at FROM channel_members
|
|
1972
|
+
`);
|
|
1973
|
+
}
|
|
1974
|
+
db.exec("COMMIT");
|
|
1975
|
+
} catch (e) {
|
|
1976
|
+
db.exec("ROLLBACK");
|
|
1977
|
+
throw e;
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
db.exec("DROP TABLE IF EXISTS channel_members");
|
|
1981
|
+
db.exec("DROP TABLE IF EXISTS channels");
|
|
1982
|
+
}
|
|
1983
|
+
const msgCols = db.prepare("PRAGMA table_info(messages)").all();
|
|
1984
|
+
const colNames = msgCols.map((c) => c.name);
|
|
1985
|
+
if (colNames.includes("channel") && !colNames.includes("space")) {
|
|
1986
|
+
db.exec("ALTER TABLE messages ADD COLUMN space TEXT");
|
|
1987
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_messages_space ON messages(space)");
|
|
1988
|
+
db.exec("UPDATE messages SET space = channel WHERE channel IS NOT NULL");
|
|
1989
|
+
db.exec(`
|
|
1990
|
+
UPDATE messages
|
|
1991
|
+
SET session_id = 'space:' || substr(session_id, 9)
|
|
1992
|
+
WHERE session_id LIKE 'channel:%'
|
|
1993
|
+
`);
|
|
1994
|
+
}
|
|
1995
|
+
const spaceCols = db.prepare("PRAGMA table_info(spaces)").all();
|
|
1996
|
+
const spaceColNames = spaceCols.map((c) => c.name);
|
|
1997
|
+
if (!spaceColNames.includes("archived_at")) {
|
|
1998
|
+
db.exec("ALTER TABLE spaces ADD COLUMN archived_at TEXT");
|
|
1999
|
+
}
|
|
2000
|
+
const msgCols2 = db.prepare("PRAGMA table_info(messages)").all();
|
|
2001
|
+
const colNames2 = msgCols2.map((c) => c.name);
|
|
2002
|
+
if (!colNames2.includes("edited_at")) {
|
|
2003
|
+
db.exec("ALTER TABLE messages ADD COLUMN edited_at TEXT");
|
|
2004
|
+
}
|
|
2005
|
+
if (!colNames2.includes("pinned_at")) {
|
|
2006
|
+
db.exec("ALTER TABLE messages ADD COLUMN pinned_at TEXT");
|
|
2007
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_messages_pinned ON messages(pinned_at)");
|
|
2008
|
+
}
|
|
1930
2009
|
return db;
|
|
1931
2010
|
}
|
|
1932
2011
|
function closeDb() {
|
|
@@ -1941,21 +2020,31 @@ var init_db = () => {};
|
|
|
1941
2020
|
// src/lib/messages.ts
|
|
1942
2021
|
import { randomUUID } from "crypto";
|
|
1943
2022
|
function parseMessage(row) {
|
|
2023
|
+
let metadata = null;
|
|
2024
|
+
if (row.metadata) {
|
|
2025
|
+
try {
|
|
2026
|
+
metadata = JSON.parse(row.metadata);
|
|
2027
|
+
} catch {
|
|
2028
|
+
metadata = null;
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
1944
2031
|
return {
|
|
1945
2032
|
...row,
|
|
1946
|
-
metadata
|
|
2033
|
+
metadata
|
|
1947
2034
|
};
|
|
1948
2035
|
}
|
|
1949
2036
|
function sendMessage(opts) {
|
|
1950
2037
|
const db2 = getDb();
|
|
1951
|
-
const
|
|
2038
|
+
const explicitSession = opts.session_id && opts.session_id.trim().length > 0 ? opts.session_id : undefined;
|
|
2039
|
+
const sessionId = explicitSession ?? (opts.space ? `space:${opts.space}` : `${[opts.from, opts.to].sort().join("-")}-${randomUUID().slice(0, 8)}`);
|
|
1952
2040
|
const metadata = opts.metadata ? JSON.stringify(opts.metadata) : null;
|
|
2041
|
+
const normalizedPriority = opts.priority === "low" || opts.priority === "normal" || opts.priority === "high" || opts.priority === "urgent" ? opts.priority : "normal";
|
|
1953
2042
|
const stmt = db2.prepare(`
|
|
1954
|
-
INSERT INTO messages (session_id, from_agent, to_agent,
|
|
2043
|
+
INSERT INTO messages (session_id, from_agent, to_agent, space, content, priority, working_dir, repository, branch, metadata)
|
|
1955
2044
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1956
2045
|
RETURNING *
|
|
1957
2046
|
`);
|
|
1958
|
-
const row = stmt.get(sessionId, opts.from, opts.to, opts.
|
|
2047
|
+
const row = stmt.get(sessionId, opts.from, opts.to, opts.space || null, opts.content, normalizedPriority, opts.working_dir || null, opts.repository || null, opts.branch || null, metadata);
|
|
1959
2048
|
return parseMessage(row);
|
|
1960
2049
|
}
|
|
1961
2050
|
function readMessages(opts = {}) {
|
|
@@ -1974,20 +2063,25 @@ function readMessages(opts = {}) {
|
|
|
1974
2063
|
conditions.push("to_agent = ?");
|
|
1975
2064
|
params.push(opts.to);
|
|
1976
2065
|
}
|
|
1977
|
-
if (opts.
|
|
1978
|
-
conditions.push("
|
|
1979
|
-
params.push(opts.
|
|
2066
|
+
if (opts.space) {
|
|
2067
|
+
conditions.push("space = ?");
|
|
2068
|
+
params.push(opts.space);
|
|
1980
2069
|
}
|
|
1981
2070
|
if (opts.since) {
|
|
1982
2071
|
conditions.push("created_at > ?");
|
|
1983
2072
|
params.push(opts.since);
|
|
1984
2073
|
}
|
|
2074
|
+
if (opts.since_id !== undefined) {
|
|
2075
|
+
conditions.push("id > ?");
|
|
2076
|
+
params.push(opts.since_id);
|
|
2077
|
+
}
|
|
1985
2078
|
if (opts.unread_only) {
|
|
1986
2079
|
conditions.push("read_at IS NULL");
|
|
1987
2080
|
}
|
|
1988
2081
|
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
1989
|
-
const limit = opts.limit ? `LIMIT ${opts.limit}` : "";
|
|
1990
|
-
const
|
|
2082
|
+
const limit = Number.isFinite(opts.limit) && opts.limit > 0 ? `LIMIT ${Math.floor(opts.limit)}` : "";
|
|
2083
|
+
const order = opts.order?.toLowerCase() === "desc" ? "DESC" : "ASC";
|
|
2084
|
+
const rows = db2.prepare(`SELECT * FROM messages ${where} ORDER BY created_at ${order}, id ${order} ${limit}`).all(...params);
|
|
1991
2085
|
return rows.map(parseMessage);
|
|
1992
2086
|
}
|
|
1993
2087
|
function markRead(ids, reader) {
|
|
@@ -2005,10 +2099,10 @@ function markSessionRead(sessionId, reader) {
|
|
|
2005
2099
|
const result = stmt.run(sessionId, reader);
|
|
2006
2100
|
return result.changes;
|
|
2007
2101
|
}
|
|
2008
|
-
function
|
|
2102
|
+
function markSpaceRead(spaceName, reader) {
|
|
2009
2103
|
const db2 = getDb();
|
|
2010
|
-
const stmt = db2.prepare(`UPDATE messages SET read_at = strftime('%Y-%m-%dT%H:%M:%f', 'now') WHERE
|
|
2011
|
-
const result = stmt.run(
|
|
2104
|
+
const stmt = db2.prepare(`UPDATE messages SET read_at = strftime('%Y-%m-%dT%H:%M:%f', 'now') WHERE space = ? AND from_agent != ? AND read_at IS NULL`);
|
|
2105
|
+
const result = stmt.run(spaceName, reader);
|
|
2012
2106
|
return result.changes;
|
|
2013
2107
|
}
|
|
2014
2108
|
function getMessageById(id) {
|
|
@@ -2016,6 +2110,130 @@ function getMessageById(id) {
|
|
|
2016
2110
|
const row = db2.prepare("SELECT * FROM messages WHERE id = ?").get(id);
|
|
2017
2111
|
return row ? parseMessage(row) : null;
|
|
2018
2112
|
}
|
|
2113
|
+
function markAllRead(agent) {
|
|
2114
|
+
const db2 = getDb();
|
|
2115
|
+
const stmt = db2.prepare(`UPDATE messages SET read_at = strftime('%Y-%m-%dT%H:%M:%f', 'now') WHERE to_agent = ? AND read_at IS NULL`);
|
|
2116
|
+
const result = stmt.run(agent);
|
|
2117
|
+
return result.changes;
|
|
2118
|
+
}
|
|
2119
|
+
function escapeCsvField(value) {
|
|
2120
|
+
if (value === null || value === undefined)
|
|
2121
|
+
return "";
|
|
2122
|
+
const str = String(value);
|
|
2123
|
+
if (str.includes(",") || str.includes('"') || str.includes(`
|
|
2124
|
+
`) || str.includes("\r")) {
|
|
2125
|
+
return `"${str.replace(/"/g, '""')}"`;
|
|
2126
|
+
}
|
|
2127
|
+
return str;
|
|
2128
|
+
}
|
|
2129
|
+
function exportMessages(opts) {
|
|
2130
|
+
const db2 = getDb();
|
|
2131
|
+
const conditions = [];
|
|
2132
|
+
const params = [];
|
|
2133
|
+
if (opts?.space) {
|
|
2134
|
+
conditions.push("space = ?");
|
|
2135
|
+
params.push(opts.space);
|
|
2136
|
+
}
|
|
2137
|
+
if (opts?.session_id) {
|
|
2138
|
+
conditions.push("session_id = ?");
|
|
2139
|
+
params.push(opts.session_id);
|
|
2140
|
+
}
|
|
2141
|
+
if (opts?.from) {
|
|
2142
|
+
conditions.push("from_agent = ?");
|
|
2143
|
+
params.push(opts.from);
|
|
2144
|
+
}
|
|
2145
|
+
if (opts?.since) {
|
|
2146
|
+
conditions.push("created_at >= ?");
|
|
2147
|
+
params.push(opts.since);
|
|
2148
|
+
}
|
|
2149
|
+
if (opts?.until) {
|
|
2150
|
+
conditions.push("created_at <= ?");
|
|
2151
|
+
params.push(opts.until);
|
|
2152
|
+
}
|
|
2153
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
2154
|
+
const rows = db2.prepare(`SELECT * FROM messages ${where} ORDER BY created_at ASC, id ASC`).all(...params);
|
|
2155
|
+
const messages = rows.map(parseMessage);
|
|
2156
|
+
const format = opts?.format ?? "json";
|
|
2157
|
+
if (format === "csv") {
|
|
2158
|
+
const headers = "id,session_id,from_agent,to_agent,space,content,priority,created_at,read_at";
|
|
2159
|
+
const lines = messages.map((m) => [
|
|
2160
|
+
String(m.id),
|
|
2161
|
+
escapeCsvField(m.session_id),
|
|
2162
|
+
escapeCsvField(m.from_agent),
|
|
2163
|
+
escapeCsvField(m.to_agent),
|
|
2164
|
+
escapeCsvField(m.space),
|
|
2165
|
+
escapeCsvField(m.content),
|
|
2166
|
+
escapeCsvField(m.priority),
|
|
2167
|
+
escapeCsvField(m.created_at),
|
|
2168
|
+
escapeCsvField(m.read_at)
|
|
2169
|
+
].join(","));
|
|
2170
|
+
return [headers, ...lines].join(`
|
|
2171
|
+
`);
|
|
2172
|
+
}
|
|
2173
|
+
return JSON.stringify(messages, null, 2);
|
|
2174
|
+
}
|
|
2175
|
+
function deleteMessage(id, agent) {
|
|
2176
|
+
const db2 = getDb();
|
|
2177
|
+
const stmt = db2.prepare("DELETE FROM messages WHERE id = ? AND from_agent = ?");
|
|
2178
|
+
const result = stmt.run(id, agent);
|
|
2179
|
+
return result.changes > 0;
|
|
2180
|
+
}
|
|
2181
|
+
function editMessage(id, agent, newContent) {
|
|
2182
|
+
const db2 = getDb();
|
|
2183
|
+
const stmt = db2.prepare(`UPDATE messages SET content = ?, edited_at = strftime('%Y-%m-%dT%H:%M:%f', 'now') WHERE id = ? AND from_agent = ? RETURNING *`);
|
|
2184
|
+
const row = stmt.get(newContent, id, agent);
|
|
2185
|
+
return row ? parseMessage(row) : null;
|
|
2186
|
+
}
|
|
2187
|
+
function pinMessage(id) {
|
|
2188
|
+
const db2 = getDb();
|
|
2189
|
+
const stmt = db2.prepare(`UPDATE messages SET pinned_at = strftime('%Y-%m-%dT%H:%M:%f', 'now') WHERE id = ? RETURNING *`);
|
|
2190
|
+
const row = stmt.get(id);
|
|
2191
|
+
return row ? parseMessage(row) : null;
|
|
2192
|
+
}
|
|
2193
|
+
function unpinMessage(id) {
|
|
2194
|
+
const db2 = getDb();
|
|
2195
|
+
const stmt = db2.prepare(`UPDATE messages SET pinned_at = NULL WHERE id = ? RETURNING *`);
|
|
2196
|
+
const row = stmt.get(id);
|
|
2197
|
+
return row ? parseMessage(row) : null;
|
|
2198
|
+
}
|
|
2199
|
+
function getPinnedMessages(opts) {
|
|
2200
|
+
const db2 = getDb();
|
|
2201
|
+
const conditions = ["pinned_at IS NOT NULL"];
|
|
2202
|
+
const params = [];
|
|
2203
|
+
if (opts?.space) {
|
|
2204
|
+
conditions.push("space = ?");
|
|
2205
|
+
params.push(opts.space);
|
|
2206
|
+
}
|
|
2207
|
+
if (opts?.session_id) {
|
|
2208
|
+
conditions.push("session_id = ?");
|
|
2209
|
+
params.push(opts.session_id);
|
|
2210
|
+
}
|
|
2211
|
+
const where = `WHERE ${conditions.join(" AND ")}`;
|
|
2212
|
+
const limit = Number.isFinite(opts?.limit) && opts.limit > 0 ? `LIMIT ${Math.floor(opts.limit)}` : "";
|
|
2213
|
+
const rows = db2.prepare(`SELECT * FROM messages ${where} ORDER BY pinned_at DESC, id DESC ${limit}`).all(...params);
|
|
2214
|
+
return rows.map(parseMessage);
|
|
2215
|
+
}
|
|
2216
|
+
function searchMessages(opts) {
|
|
2217
|
+
const db2 = getDb();
|
|
2218
|
+
const conditions = ["content LIKE ?"];
|
|
2219
|
+
const params = [`%${opts.query}%`];
|
|
2220
|
+
if (opts.space) {
|
|
2221
|
+
conditions.push("space = ?");
|
|
2222
|
+
params.push(opts.space);
|
|
2223
|
+
}
|
|
2224
|
+
if (opts.from) {
|
|
2225
|
+
conditions.push("from_agent = ?");
|
|
2226
|
+
params.push(opts.from);
|
|
2227
|
+
}
|
|
2228
|
+
if (opts.to) {
|
|
2229
|
+
conditions.push("to_agent = ?");
|
|
2230
|
+
params.push(opts.to);
|
|
2231
|
+
}
|
|
2232
|
+
const limit = Number.isFinite(opts.limit) && opts.limit > 0 ? Math.floor(opts.limit) : 50;
|
|
2233
|
+
const where = `WHERE ${conditions.join(" AND ")}`;
|
|
2234
|
+
const rows = db2.prepare(`SELECT * FROM messages ${where} ORDER BY created_at DESC, id DESC LIMIT ${limit}`).all(...params);
|
|
2235
|
+
return rows.map(parseMessage);
|
|
2236
|
+
}
|
|
2019
2237
|
var init_messages = __esm(() => {
|
|
2020
2238
|
init_db();
|
|
2021
2239
|
});
|
|
@@ -2053,73 +2271,493 @@ var init_sessions = __esm(() => {
|
|
|
2053
2271
|
init_db();
|
|
2054
2272
|
});
|
|
2055
2273
|
|
|
2056
|
-
// src/lib/
|
|
2057
|
-
function
|
|
2274
|
+
// src/lib/spaces.ts
|
|
2275
|
+
function getSpaceDepth(spaceName) {
|
|
2276
|
+
const db2 = getDb();
|
|
2277
|
+
let depth = 0;
|
|
2278
|
+
let current = spaceName;
|
|
2279
|
+
for (let i = 0;i < 10; i++) {
|
|
2280
|
+
const row = db2.prepare("SELECT parent_id FROM spaces WHERE name = ?").get(current);
|
|
2281
|
+
if (!row || !row.parent_id)
|
|
2282
|
+
break;
|
|
2283
|
+
depth++;
|
|
2284
|
+
current = row.parent_id;
|
|
2285
|
+
}
|
|
2286
|
+
return depth;
|
|
2287
|
+
}
|
|
2288
|
+
function createSpace(name, createdBy, options) {
|
|
2058
2289
|
const db2 = getDb();
|
|
2059
|
-
|
|
2060
|
-
|
|
2290
|
+
if (options?.parent_id) {
|
|
2291
|
+
const parentExists = db2.prepare("SELECT name FROM spaces WHERE name = ?").get(options.parent_id);
|
|
2292
|
+
if (!parentExists) {
|
|
2293
|
+
throw new Error(`Parent space not found: ${options.parent_id}`);
|
|
2294
|
+
}
|
|
2295
|
+
const parentDepth = getSpaceDepth(options.parent_id);
|
|
2296
|
+
if (parentDepth >= 2) {
|
|
2297
|
+
throw new Error("Maximum space nesting depth is 3 levels");
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
if (options?.project_id) {
|
|
2301
|
+
const projectExists = db2.prepare("SELECT id FROM projects WHERE id = ?").get(options.project_id);
|
|
2302
|
+
if (!projectExists) {
|
|
2303
|
+
throw new Error(`Project not found: ${options.project_id}`);
|
|
2304
|
+
}
|
|
2305
|
+
}
|
|
2306
|
+
const row = db2.prepare("INSERT INTO spaces (name, description, parent_id, project_id, created_by) VALUES (?, ?, ?, ?, ?) RETURNING *").get(name, options?.description || null, options?.parent_id || null, options?.project_id || null, createdBy);
|
|
2307
|
+
db2.prepare("INSERT OR IGNORE INTO space_members (space, agent) VALUES (?, ?)").run(name, createdBy);
|
|
2061
2308
|
return row;
|
|
2062
2309
|
}
|
|
2063
|
-
function
|
|
2310
|
+
function listSpaces(options) {
|
|
2064
2311
|
const db2 = getDb();
|
|
2312
|
+
const conditions = [];
|
|
2313
|
+
const params = [];
|
|
2314
|
+
if (options?.project_id) {
|
|
2315
|
+
conditions.push("s.project_id = ?");
|
|
2316
|
+
params.push(options.project_id);
|
|
2317
|
+
}
|
|
2318
|
+
if (options?.parent_id !== undefined) {
|
|
2319
|
+
if (options.parent_id === null) {
|
|
2320
|
+
conditions.push("s.parent_id IS NULL");
|
|
2321
|
+
} else {
|
|
2322
|
+
conditions.push("s.parent_id = ?");
|
|
2323
|
+
params.push(options.parent_id);
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
if (!options?.include_archived) {
|
|
2327
|
+
conditions.push("s.archived_at IS NULL");
|
|
2328
|
+
}
|
|
2329
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
2065
2330
|
const rows = db2.prepare(`
|
|
2066
2331
|
SELECT
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2332
|
+
s.name,
|
|
2333
|
+
s.description,
|
|
2334
|
+
s.parent_id,
|
|
2335
|
+
s.project_id,
|
|
2336
|
+
s.created_by,
|
|
2337
|
+
s.created_at,
|
|
2338
|
+
s.archived_at,
|
|
2339
|
+
(SELECT COUNT(*) FROM space_members WHERE space = s.name) AS member_count,
|
|
2340
|
+
(SELECT COUNT(*) FROM messages WHERE space = s.name) AS message_count
|
|
2341
|
+
FROM spaces s
|
|
2342
|
+
${where}
|
|
2343
|
+
ORDER BY s.name ASC
|
|
2344
|
+
`).all(...params);
|
|
2076
2345
|
return rows;
|
|
2077
2346
|
}
|
|
2078
|
-
function
|
|
2347
|
+
function getSpace(name) {
|
|
2079
2348
|
const db2 = getDb();
|
|
2080
2349
|
const row = db2.prepare(`
|
|
2081
2350
|
SELECT
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2351
|
+
s.name,
|
|
2352
|
+
s.description,
|
|
2353
|
+
s.parent_id,
|
|
2354
|
+
s.project_id,
|
|
2355
|
+
s.created_by,
|
|
2356
|
+
s.created_at,
|
|
2357
|
+
s.archived_at,
|
|
2358
|
+
(SELECT COUNT(*) FROM space_members WHERE space = s.name) AS member_count,
|
|
2359
|
+
(SELECT COUNT(*) FROM messages WHERE space = s.name) AS message_count
|
|
2360
|
+
FROM spaces s
|
|
2361
|
+
WHERE s.name = ?
|
|
2090
2362
|
`).get(name);
|
|
2091
2363
|
return row;
|
|
2092
2364
|
}
|
|
2093
|
-
function
|
|
2365
|
+
function joinSpace(spaceName, agent) {
|
|
2094
2366
|
const db2 = getDb();
|
|
2095
|
-
const
|
|
2096
|
-
if (!
|
|
2367
|
+
const space = db2.prepare("SELECT name FROM spaces WHERE name = ?").get(spaceName);
|
|
2368
|
+
if (!space)
|
|
2097
2369
|
return false;
|
|
2098
|
-
db2.prepare("INSERT OR IGNORE INTO
|
|
2370
|
+
db2.prepare("INSERT OR IGNORE INTO space_members (space, agent) VALUES (?, ?)").run(spaceName, agent);
|
|
2099
2371
|
return true;
|
|
2100
2372
|
}
|
|
2101
|
-
function
|
|
2373
|
+
function leaveSpace(spaceName, agent) {
|
|
2102
2374
|
const db2 = getDb();
|
|
2103
|
-
const result = db2.prepare("DELETE FROM
|
|
2375
|
+
const result = db2.prepare("DELETE FROM space_members WHERE space = ? AND agent = ?").run(spaceName, agent);
|
|
2104
2376
|
return result.changes > 0;
|
|
2105
2377
|
}
|
|
2106
|
-
function
|
|
2378
|
+
function getSpaceMembers(spaceName) {
|
|
2379
|
+
const db2 = getDb();
|
|
2380
|
+
return db2.prepare("SELECT space, agent, joined_at FROM space_members WHERE space = ? ORDER BY joined_at ASC").all(spaceName);
|
|
2381
|
+
}
|
|
2382
|
+
function updateSpace(name, updates) {
|
|
2383
|
+
const db2 = getDb();
|
|
2384
|
+
const existing = db2.prepare("SELECT * FROM spaces WHERE name = ?").get(name);
|
|
2385
|
+
if (!existing) {
|
|
2386
|
+
throw new Error(`Space not found: ${name}`);
|
|
2387
|
+
}
|
|
2388
|
+
if (updates.parent_id !== undefined && updates.parent_id !== existing.parent_id) {
|
|
2389
|
+
if (updates.parent_id !== null) {
|
|
2390
|
+
const parentExists = db2.prepare("SELECT name FROM spaces WHERE name = ?").get(updates.parent_id);
|
|
2391
|
+
if (!parentExists) {
|
|
2392
|
+
throw new Error(`Parent space not found: ${updates.parent_id}`);
|
|
2393
|
+
}
|
|
2394
|
+
const parentDepth = getSpaceDepth(updates.parent_id);
|
|
2395
|
+
if (parentDepth >= 2) {
|
|
2396
|
+
throw new Error("Maximum space nesting depth is 3 levels");
|
|
2397
|
+
}
|
|
2398
|
+
if (updates.parent_id === name) {
|
|
2399
|
+
throw new Error("A space cannot be its own parent");
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
}
|
|
2403
|
+
if (updates.project_id !== undefined && updates.project_id !== existing.project_id) {
|
|
2404
|
+
if (updates.project_id !== null) {
|
|
2405
|
+
const projectExists = db2.prepare("SELECT id FROM projects WHERE id = ?").get(updates.project_id);
|
|
2406
|
+
if (!projectExists) {
|
|
2407
|
+
throw new Error(`Project not found: ${updates.project_id}`);
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
const sets = [];
|
|
2412
|
+
const params = [];
|
|
2413
|
+
if (updates.description !== undefined) {
|
|
2414
|
+
sets.push("description = ?");
|
|
2415
|
+
params.push(updates.description);
|
|
2416
|
+
}
|
|
2417
|
+
if (updates.parent_id !== undefined) {
|
|
2418
|
+
sets.push("parent_id = ?");
|
|
2419
|
+
params.push(updates.parent_id);
|
|
2420
|
+
}
|
|
2421
|
+
if (updates.project_id !== undefined) {
|
|
2422
|
+
sets.push("project_id = ?");
|
|
2423
|
+
params.push(updates.project_id);
|
|
2424
|
+
}
|
|
2425
|
+
if (sets.length === 0) {
|
|
2426
|
+
return existing;
|
|
2427
|
+
}
|
|
2428
|
+
params.push(name);
|
|
2429
|
+
const row = db2.prepare(`UPDATE spaces SET ${sets.join(", ")} WHERE name = ? RETURNING *`).get(...params);
|
|
2430
|
+
return row;
|
|
2431
|
+
}
|
|
2432
|
+
function archiveSpace(name) {
|
|
2433
|
+
const db2 = getDb();
|
|
2434
|
+
const existing = db2.prepare("SELECT * FROM spaces WHERE name = ?").get(name);
|
|
2435
|
+
if (!existing) {
|
|
2436
|
+
throw new Error(`Space not found: ${name}`);
|
|
2437
|
+
}
|
|
2438
|
+
const row = db2.prepare("UPDATE spaces SET archived_at = strftime('%Y-%m-%dT%H:%M:%f', 'now') WHERE name = ? RETURNING *").get(name);
|
|
2439
|
+
return row;
|
|
2440
|
+
}
|
|
2441
|
+
function unarchiveSpace(name) {
|
|
2442
|
+
const db2 = getDb();
|
|
2443
|
+
const existing = db2.prepare("SELECT * FROM spaces WHERE name = ?").get(name);
|
|
2444
|
+
if (!existing) {
|
|
2445
|
+
throw new Error(`Space not found: ${name}`);
|
|
2446
|
+
}
|
|
2447
|
+
const row = db2.prepare("UPDATE spaces SET archived_at = NULL WHERE name = ? RETURNING *").get(name);
|
|
2448
|
+
return row;
|
|
2449
|
+
}
|
|
2450
|
+
var init_spaces = __esm(() => {
|
|
2451
|
+
init_db();
|
|
2452
|
+
});
|
|
2453
|
+
|
|
2454
|
+
// src/lib/projects.ts
|
|
2455
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
2456
|
+
function parseProject(row) {
|
|
2457
|
+
let metadata = null;
|
|
2458
|
+
if (row.metadata) {
|
|
2459
|
+
try {
|
|
2460
|
+
metadata = JSON.parse(row.metadata);
|
|
2461
|
+
} catch {
|
|
2462
|
+
metadata = null;
|
|
2463
|
+
}
|
|
2464
|
+
}
|
|
2465
|
+
let tags = [];
|
|
2466
|
+
if (row.tags) {
|
|
2467
|
+
try {
|
|
2468
|
+
tags = JSON.parse(row.tags);
|
|
2469
|
+
} catch {
|
|
2470
|
+
tags = [];
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
let settings = null;
|
|
2474
|
+
if (row.settings) {
|
|
2475
|
+
try {
|
|
2476
|
+
settings = JSON.parse(row.settings);
|
|
2477
|
+
} catch {
|
|
2478
|
+
settings = null;
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
return {
|
|
2482
|
+
id: row.id,
|
|
2483
|
+
name: row.name,
|
|
2484
|
+
description: row.description || null,
|
|
2485
|
+
path: row.path || null,
|
|
2486
|
+
created_by: row.created_by,
|
|
2487
|
+
created_at: row.created_at,
|
|
2488
|
+
metadata,
|
|
2489
|
+
tags,
|
|
2490
|
+
status: row.status || "active",
|
|
2491
|
+
repository: row.repository || null,
|
|
2492
|
+
settings
|
|
2493
|
+
};
|
|
2494
|
+
}
|
|
2495
|
+
function createProject(opts) {
|
|
2496
|
+
const db2 = getDb();
|
|
2497
|
+
const id = randomUUID2();
|
|
2498
|
+
const metadata = opts.metadata ? JSON.stringify(opts.metadata) : null;
|
|
2499
|
+
const tags = opts.tags ? JSON.stringify(opts.tags) : null;
|
|
2500
|
+
const settings = opts.settings ? JSON.stringify(opts.settings) : null;
|
|
2501
|
+
const row = db2.prepare(`
|
|
2502
|
+
INSERT INTO projects (id, name, description, path, created_by, metadata, tags, repository, settings)
|
|
2503
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2504
|
+
RETURNING *
|
|
2505
|
+
`).get(id, opts.name, opts.description || null, opts.path || null, opts.created_by, metadata, tags, opts.repository || null, settings);
|
|
2506
|
+
return parseProject(row);
|
|
2507
|
+
}
|
|
2508
|
+
function listProjects(opts) {
|
|
2509
|
+
const db2 = getDb();
|
|
2510
|
+
const conditions = [];
|
|
2511
|
+
const params = [];
|
|
2512
|
+
if (opts?.status) {
|
|
2513
|
+
conditions.push("p.status = ?");
|
|
2514
|
+
params.push(opts.status);
|
|
2515
|
+
}
|
|
2516
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
2517
|
+
const rows = db2.prepare(`
|
|
2518
|
+
SELECT
|
|
2519
|
+
p.*,
|
|
2520
|
+
(SELECT COUNT(*) FROM spaces WHERE project_id = p.id) AS space_count
|
|
2521
|
+
FROM projects p
|
|
2522
|
+
${where}
|
|
2523
|
+
ORDER BY p.name ASC
|
|
2524
|
+
`).all(...params);
|
|
2525
|
+
return rows.map((row) => ({
|
|
2526
|
+
...parseProject(row),
|
|
2527
|
+
space_count: row.space_count
|
|
2528
|
+
}));
|
|
2529
|
+
}
|
|
2530
|
+
function getProject(id) {
|
|
2531
|
+
const db2 = getDb();
|
|
2532
|
+
const row = db2.prepare(`
|
|
2533
|
+
SELECT
|
|
2534
|
+
p.*,
|
|
2535
|
+
(SELECT COUNT(*) FROM spaces WHERE project_id = p.id) AS space_count
|
|
2536
|
+
FROM projects p
|
|
2537
|
+
WHERE p.id = ?
|
|
2538
|
+
`).get(id);
|
|
2539
|
+
if (!row)
|
|
2540
|
+
return null;
|
|
2541
|
+
return {
|
|
2542
|
+
...parseProject(row),
|
|
2543
|
+
space_count: row.space_count
|
|
2544
|
+
};
|
|
2545
|
+
}
|
|
2546
|
+
function getProjectByName(name) {
|
|
2547
|
+
const db2 = getDb();
|
|
2548
|
+
const row = db2.prepare(`
|
|
2549
|
+
SELECT
|
|
2550
|
+
p.*,
|
|
2551
|
+
(SELECT COUNT(*) FROM spaces WHERE project_id = p.id) AS space_count
|
|
2552
|
+
FROM projects p
|
|
2553
|
+
WHERE p.name = ?
|
|
2554
|
+
`).get(name);
|
|
2555
|
+
if (!row)
|
|
2556
|
+
return null;
|
|
2557
|
+
return {
|
|
2558
|
+
...parseProject(row),
|
|
2559
|
+
space_count: row.space_count
|
|
2560
|
+
};
|
|
2561
|
+
}
|
|
2562
|
+
function updateProject(id, updates) {
|
|
2563
|
+
const db2 = getDb();
|
|
2564
|
+
const existing = db2.prepare("SELECT * FROM projects WHERE id = ?").get(id);
|
|
2565
|
+
if (!existing) {
|
|
2566
|
+
throw new Error(`Project not found: ${id}`);
|
|
2567
|
+
}
|
|
2568
|
+
const sets = [];
|
|
2569
|
+
const params = [];
|
|
2570
|
+
if (updates.name !== undefined) {
|
|
2571
|
+
sets.push("name = ?");
|
|
2572
|
+
params.push(updates.name);
|
|
2573
|
+
}
|
|
2574
|
+
if (updates.description !== undefined) {
|
|
2575
|
+
sets.push("description = ?");
|
|
2576
|
+
params.push(updates.description);
|
|
2577
|
+
}
|
|
2578
|
+
if (updates.path !== undefined) {
|
|
2579
|
+
sets.push("path = ?");
|
|
2580
|
+
params.push(updates.path);
|
|
2581
|
+
}
|
|
2582
|
+
if (updates.metadata !== undefined) {
|
|
2583
|
+
sets.push("metadata = ?");
|
|
2584
|
+
params.push(JSON.stringify(updates.metadata));
|
|
2585
|
+
}
|
|
2586
|
+
if (updates.tags !== undefined) {
|
|
2587
|
+
sets.push("tags = ?");
|
|
2588
|
+
params.push(JSON.stringify(updates.tags));
|
|
2589
|
+
}
|
|
2590
|
+
if (updates.status !== undefined) {
|
|
2591
|
+
sets.push("status = ?");
|
|
2592
|
+
params.push(updates.status);
|
|
2593
|
+
}
|
|
2594
|
+
if (updates.repository !== undefined) {
|
|
2595
|
+
sets.push("repository = ?");
|
|
2596
|
+
params.push(updates.repository);
|
|
2597
|
+
}
|
|
2598
|
+
if (updates.settings !== undefined) {
|
|
2599
|
+
sets.push("settings = ?");
|
|
2600
|
+
params.push(JSON.stringify(updates.settings));
|
|
2601
|
+
}
|
|
2602
|
+
if (sets.length === 0) {
|
|
2603
|
+
return parseProject(existing);
|
|
2604
|
+
}
|
|
2605
|
+
params.push(id);
|
|
2606
|
+
const row = db2.prepare(`UPDATE projects SET ${sets.join(", ")} WHERE id = ? RETURNING *`).get(...params);
|
|
2607
|
+
return parseProject(row);
|
|
2608
|
+
}
|
|
2609
|
+
function deleteProject(id) {
|
|
2107
2610
|
const db2 = getDb();
|
|
2108
|
-
|
|
2611
|
+
const spaceCount = db2.prepare("SELECT COUNT(*) as c FROM spaces WHERE project_id = ?").get(id).c;
|
|
2612
|
+
if (spaceCount > 0) {
|
|
2613
|
+
throw new Error(`Cannot delete project: ${spaceCount} space(s) still reference it`);
|
|
2614
|
+
}
|
|
2615
|
+
const result = db2.prepare("DELETE FROM projects WHERE id = ?").run(id);
|
|
2616
|
+
return result.changes > 0;
|
|
2109
2617
|
}
|
|
2110
|
-
var
|
|
2618
|
+
var init_projects = __esm(() => {
|
|
2111
2619
|
init_db();
|
|
2112
2620
|
});
|
|
2113
2621
|
|
|
2114
2622
|
// src/lib/identity.ts
|
|
2115
2623
|
function resolveIdentity(explicit) {
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2624
|
+
const explicitValue = explicit?.trim();
|
|
2625
|
+
if (explicitValue)
|
|
2626
|
+
return explicitValue;
|
|
2627
|
+
const envValue = process.env.CONVERSATIONS_AGENT_ID?.trim();
|
|
2628
|
+
if (envValue)
|
|
2629
|
+
return envValue;
|
|
2120
2630
|
return "user";
|
|
2121
2631
|
}
|
|
2122
2632
|
|
|
2633
|
+
// src/lib/presence.ts
|
|
2634
|
+
function parsePresence(row) {
|
|
2635
|
+
let metadata = null;
|
|
2636
|
+
if (row.metadata) {
|
|
2637
|
+
try {
|
|
2638
|
+
metadata = JSON.parse(row.metadata);
|
|
2639
|
+
} catch {
|
|
2640
|
+
metadata = null;
|
|
2641
|
+
}
|
|
2642
|
+
}
|
|
2643
|
+
const lastSeenAt = row.last_seen_at;
|
|
2644
|
+
const lastSeenMs = new Date(lastSeenAt + "Z").getTime();
|
|
2645
|
+
const nowMs = Date.now();
|
|
2646
|
+
const online = nowMs - lastSeenMs < ONLINE_THRESHOLD_SECONDS * 1000;
|
|
2647
|
+
return {
|
|
2648
|
+
agent: row.agent,
|
|
2649
|
+
status: row.status,
|
|
2650
|
+
last_seen_at: lastSeenAt,
|
|
2651
|
+
online,
|
|
2652
|
+
metadata
|
|
2653
|
+
};
|
|
2654
|
+
}
|
|
2655
|
+
function heartbeat(agent, status, metadata) {
|
|
2656
|
+
const db2 = getDb();
|
|
2657
|
+
const metadataJson = metadata ? JSON.stringify(metadata) : null;
|
|
2658
|
+
const resolvedStatus = status || "online";
|
|
2659
|
+
db2.prepare(`
|
|
2660
|
+
INSERT INTO agent_presence (agent, status, last_seen_at, metadata)
|
|
2661
|
+
VALUES (?, ?, strftime('%Y-%m-%dT%H:%M:%f', 'now'), ?)
|
|
2662
|
+
ON CONFLICT(agent) DO UPDATE SET
|
|
2663
|
+
status = excluded.status,
|
|
2664
|
+
last_seen_at = excluded.last_seen_at,
|
|
2665
|
+
metadata = excluded.metadata
|
|
2666
|
+
`).run(agent, resolvedStatus, metadataJson);
|
|
2667
|
+
}
|
|
2668
|
+
function listAgents(opts) {
|
|
2669
|
+
const db2 = getDb();
|
|
2670
|
+
let query = "SELECT * FROM agent_presence";
|
|
2671
|
+
const params = [];
|
|
2672
|
+
if (opts?.online_only) {
|
|
2673
|
+
query += " WHERE last_seen_at > strftime('%Y-%m-%dT%H:%M:%f', 'now', '-60 seconds')";
|
|
2674
|
+
}
|
|
2675
|
+
query += " ORDER BY last_seen_at DESC";
|
|
2676
|
+
const rows = db2.prepare(query).all(...params);
|
|
2677
|
+
return rows.map(parsePresence);
|
|
2678
|
+
}
|
|
2679
|
+
var ONLINE_THRESHOLD_SECONDS = 60;
|
|
2680
|
+
var init_presence = __esm(() => {
|
|
2681
|
+
init_db();
|
|
2682
|
+
});
|
|
2683
|
+
|
|
2684
|
+
// package.json
|
|
2685
|
+
var require_package = __commonJS((exports, module) => {
|
|
2686
|
+
module.exports = {
|
|
2687
|
+
name: "@hasna/conversations",
|
|
2688
|
+
version: "0.1.0",
|
|
2689
|
+
description: "Real-time CLI messaging for AI agents",
|
|
2690
|
+
type: "module",
|
|
2691
|
+
bin: {
|
|
2692
|
+
conversations: "bin/index.js",
|
|
2693
|
+
"conversations-mcp": "bin/mcp.js"
|
|
2694
|
+
},
|
|
2695
|
+
exports: {
|
|
2696
|
+
".": {
|
|
2697
|
+
import: "./dist/index.js",
|
|
2698
|
+
types: "./dist/index.d.ts"
|
|
2699
|
+
}
|
|
2700
|
+
},
|
|
2701
|
+
files: [
|
|
2702
|
+
"dist/",
|
|
2703
|
+
"bin/",
|
|
2704
|
+
"dashboard/dist/",
|
|
2705
|
+
"LICENSE",
|
|
2706
|
+
"README.md"
|
|
2707
|
+
],
|
|
2708
|
+
main: "./dist/index.js",
|
|
2709
|
+
types: "./dist/index.d.ts",
|
|
2710
|
+
scripts: {
|
|
2711
|
+
build: "bun build ./src/cli/index.tsx --outdir ./bin --target bun --external ink --external react --external chalk && bun build ./src/mcp/index.ts --outfile ./bin/mcp.js --target bun && bun build ./src/index.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist",
|
|
2712
|
+
"build:dashboard": "cd dashboard && bun install && bun run build",
|
|
2713
|
+
test: "bun test",
|
|
2714
|
+
dev: "bun run ./src/cli/index.tsx",
|
|
2715
|
+
typecheck: "tsc --noEmit",
|
|
2716
|
+
prepublishOnly: "bun run build"
|
|
2717
|
+
},
|
|
2718
|
+
keywords: [
|
|
2719
|
+
"conversations",
|
|
2720
|
+
"messaging",
|
|
2721
|
+
"ai",
|
|
2722
|
+
"agent",
|
|
2723
|
+
"cli",
|
|
2724
|
+
"typescript",
|
|
2725
|
+
"bun",
|
|
2726
|
+
"claude",
|
|
2727
|
+
"mcp"
|
|
2728
|
+
],
|
|
2729
|
+
author: "Hasna",
|
|
2730
|
+
license: "Apache-2.0",
|
|
2731
|
+
devDependencies: {
|
|
2732
|
+
"@types/bun": "latest",
|
|
2733
|
+
"@types/react": "^18.2.0",
|
|
2734
|
+
typescript: "^5"
|
|
2735
|
+
},
|
|
2736
|
+
dependencies: {
|
|
2737
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
2738
|
+
chalk: "^5.3.0",
|
|
2739
|
+
commander: "^12.1.0",
|
|
2740
|
+
ink: "^5.0.1",
|
|
2741
|
+
"ink-select-input": "^6.0.0",
|
|
2742
|
+
"ink-spinner": "^5.0.0",
|
|
2743
|
+
"ink-text-input": "^6.0.0",
|
|
2744
|
+
react: "^18.2.0",
|
|
2745
|
+
zod: "^4.3.6"
|
|
2746
|
+
},
|
|
2747
|
+
engines: {
|
|
2748
|
+
bun: ">=1.0.0"
|
|
2749
|
+
},
|
|
2750
|
+
publishConfig: {
|
|
2751
|
+
registry: "https://registry.npmjs.org",
|
|
2752
|
+
access: "public"
|
|
2753
|
+
},
|
|
2754
|
+
repository: {
|
|
2755
|
+
type: "git",
|
|
2756
|
+
url: "git+https://github.com/hasna/conversations.git"
|
|
2757
|
+
}
|
|
2758
|
+
};
|
|
2759
|
+
});
|
|
2760
|
+
|
|
2123
2761
|
// node_modules/zod/v3/helpers/util.js
|
|
2124
2762
|
var util, objectUtil, ZodParsedType, getParsedType = (data) => {
|
|
2125
2763
|
const t = typeof data;
|
|
@@ -29410,7 +30048,7 @@ var require_formats = __commonJS((exports) => {
|
|
|
29410
30048
|
}
|
|
29411
30049
|
var TIME = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)(z|([+-])(\d\d)(?::?(\d\d))?)?$/i;
|
|
29412
30050
|
function getTime(strictTimeZone) {
|
|
29413
|
-
return function
|
|
30051
|
+
return function time(str) {
|
|
29414
30052
|
const matches = TIME.exec(str);
|
|
29415
30053
|
if (!matches)
|
|
29416
30054
|
return false;
|
|
@@ -31002,10 +31640,12 @@ var init_mcp2 = __esm(() => {
|
|
|
31002
31640
|
init_zod();
|
|
31003
31641
|
init_messages();
|
|
31004
31642
|
init_sessions();
|
|
31005
|
-
|
|
31643
|
+
init_spaces();
|
|
31644
|
+
init_projects();
|
|
31645
|
+
init_presence();
|
|
31006
31646
|
server = new McpServer({
|
|
31007
31647
|
name: "conversations",
|
|
31008
|
-
version: "0.0
|
|
31648
|
+
version: "0.1.0"
|
|
31009
31649
|
});
|
|
31010
31650
|
server.registerTool("send_message", {
|
|
31011
31651
|
title: "Send Message",
|
|
@@ -31022,7 +31662,17 @@ var init_mcp2 = __esm(() => {
|
|
|
31022
31662
|
}
|
|
31023
31663
|
}, async ({ to, content, session_id, priority, working_dir, repository, branch, metadata }) => {
|
|
31024
31664
|
const from = resolveIdentity();
|
|
31025
|
-
|
|
31665
|
+
let parsedMetadata;
|
|
31666
|
+
if (metadata) {
|
|
31667
|
+
try {
|
|
31668
|
+
parsedMetadata = JSON.parse(metadata);
|
|
31669
|
+
} catch {
|
|
31670
|
+
return {
|
|
31671
|
+
content: [{ type: "text", text: "Invalid metadata JSON." }],
|
|
31672
|
+
isError: true
|
|
31673
|
+
};
|
|
31674
|
+
}
|
|
31675
|
+
}
|
|
31026
31676
|
const msg = sendMessage({
|
|
31027
31677
|
from,
|
|
31028
31678
|
to,
|
|
@@ -31045,7 +31695,7 @@ var init_mcp2 = __esm(() => {
|
|
|
31045
31695
|
session_id: exports_external.string().optional().describe("Filter by session ID"),
|
|
31046
31696
|
from: exports_external.string().optional().describe("Filter by sender agent ID"),
|
|
31047
31697
|
to: exports_external.string().optional().describe("Filter by recipient agent ID"),
|
|
31048
|
-
|
|
31698
|
+
space: exports_external.string().optional().describe("Filter by space name"),
|
|
31049
31699
|
since: exports_external.string().optional().describe("Messages after this ISO timestamp"),
|
|
31050
31700
|
limit: exports_external.number().optional().describe("Max messages to return"),
|
|
31051
31701
|
unread_only: exports_external.boolean().optional().describe("Only return unread messages")
|
|
@@ -31085,12 +31735,15 @@ var init_mcp2 = __esm(() => {
|
|
|
31085
31735
|
};
|
|
31086
31736
|
}
|
|
31087
31737
|
const from = resolveIdentity();
|
|
31738
|
+
const space = original.space || (original.session_id?.startsWith("space:") ? original.session_id.slice(6) : undefined);
|
|
31739
|
+
const to = space ? space : original.from_agent === from ? original.to_agent : original.from_agent;
|
|
31088
31740
|
const msg = sendMessage({
|
|
31089
31741
|
from,
|
|
31090
|
-
to
|
|
31742
|
+
to,
|
|
31091
31743
|
content,
|
|
31092
31744
|
session_id: original.session_id,
|
|
31093
|
-
priority
|
|
31745
|
+
priority,
|
|
31746
|
+
space
|
|
31094
31747
|
});
|
|
31095
31748
|
return {
|
|
31096
31749
|
content: [{ type: "text", text: JSON.stringify(msg, null, 2) }]
|
|
@@ -31098,145 +31751,612 @@ var init_mcp2 = __esm(() => {
|
|
|
31098
31751
|
});
|
|
31099
31752
|
server.registerTool("mark_read", {
|
|
31100
31753
|
title: "Mark Read",
|
|
31101
|
-
description: "Mark message IDs as read for the current agent.",
|
|
31754
|
+
description: "Mark message IDs as read for the current agent. Set 'all' to true to mark all unread messages as read.",
|
|
31102
31755
|
inputSchema: {
|
|
31103
|
-
ids: exports_external.array(exports_external.number()).describe("Message IDs to mark as read")
|
|
31756
|
+
ids: exports_external.array(exports_external.number()).optional().describe("Message IDs to mark as read"),
|
|
31757
|
+
all: exports_external.boolean().optional().describe("Mark all unread messages as read")
|
|
31104
31758
|
}
|
|
31105
|
-
}, async ({ ids }) => {
|
|
31759
|
+
}, async ({ ids, all }) => {
|
|
31106
31760
|
const agent = resolveIdentity();
|
|
31107
|
-
|
|
31761
|
+
let count;
|
|
31762
|
+
if (all) {
|
|
31763
|
+
count = markAllRead(agent);
|
|
31764
|
+
} else if (ids && ids.length > 0) {
|
|
31765
|
+
count = markRead(ids, agent);
|
|
31766
|
+
} else {
|
|
31767
|
+
return {
|
|
31768
|
+
content: [{ type: "text", text: "Provide message IDs or set 'all' to true." }],
|
|
31769
|
+
isError: true
|
|
31770
|
+
};
|
|
31771
|
+
}
|
|
31108
31772
|
return {
|
|
31109
31773
|
content: [{ type: "text", text: JSON.stringify({ marked_read: count }, null, 2) }]
|
|
31110
31774
|
};
|
|
31111
31775
|
});
|
|
31112
|
-
server.registerTool("
|
|
31113
|
-
title: "
|
|
31114
|
-
description: "
|
|
31776
|
+
server.registerTool("search_messages", {
|
|
31777
|
+
title: "Search Messages",
|
|
31778
|
+
description: "Full-text search across message content. Returns matching messages ordered by newest first.",
|
|
31779
|
+
inputSchema: {
|
|
31780
|
+
query: exports_external.string().describe("Search query string"),
|
|
31781
|
+
space: exports_external.string().optional().describe("Filter by space name"),
|
|
31782
|
+
from: exports_external.string().optional().describe("Filter by sender agent ID"),
|
|
31783
|
+
to: exports_external.string().optional().describe("Filter by recipient agent ID"),
|
|
31784
|
+
limit: exports_external.number().optional().describe("Max results to return (default 50)")
|
|
31785
|
+
}
|
|
31786
|
+
}, async ({ query, space, from, to, limit }) => {
|
|
31787
|
+
const messages = searchMessages({ query, space, from, to, limit });
|
|
31788
|
+
return {
|
|
31789
|
+
content: [{ type: "text", text: JSON.stringify(messages, null, 2) }]
|
|
31790
|
+
};
|
|
31791
|
+
});
|
|
31792
|
+
server.registerTool("export_messages", {
|
|
31793
|
+
title: "Export Messages",
|
|
31794
|
+
description: "Export messages as JSON or CSV with optional filters.",
|
|
31795
|
+
inputSchema: {
|
|
31796
|
+
space: exports_external.string().optional().describe("Filter by space name"),
|
|
31797
|
+
session_id: exports_external.string().optional().describe("Filter by session ID"),
|
|
31798
|
+
from: exports_external.string().optional().describe("Filter by sender agent ID"),
|
|
31799
|
+
since: exports_external.string().optional().describe("Messages after this ISO date"),
|
|
31800
|
+
until: exports_external.string().optional().describe("Messages before this ISO date"),
|
|
31801
|
+
format: exports_external.enum(["json", "csv"]).optional().describe("Output format (default: json)")
|
|
31802
|
+
}
|
|
31803
|
+
}, async ({ space, session_id, from, since, until, format }) => {
|
|
31804
|
+
const result = exportMessages({ space, session_id, from, since, until, format });
|
|
31805
|
+
return {
|
|
31806
|
+
content: [{ type: "text", text: result }]
|
|
31807
|
+
};
|
|
31808
|
+
});
|
|
31809
|
+
server.registerTool("create_space", {
|
|
31810
|
+
title: "Create Space",
|
|
31811
|
+
description: "Create a new space. The creator is auto-joined. Spaces can be nested (max 3 levels) and associated with a project.",
|
|
31115
31812
|
inputSchema: {
|
|
31116
|
-
name: exports_external.string().describe("
|
|
31117
|
-
description: exports_external.string().optional().describe("
|
|
31813
|
+
name: exports_external.string().describe("Space name (e.g. 'deployments', 'code-review')"),
|
|
31814
|
+
description: exports_external.string().optional().describe("Space description"),
|
|
31815
|
+
parent_id: exports_external.string().optional().describe("Parent space name for nesting (max 3 levels deep)"),
|
|
31816
|
+
project_id: exports_external.string().optional().describe("Project ID to associate this space with")
|
|
31118
31817
|
}
|
|
31119
|
-
}, async ({ name, description }) => {
|
|
31818
|
+
}, async ({ name, description, parent_id, project_id }) => {
|
|
31120
31819
|
const agent = resolveIdentity();
|
|
31121
31820
|
try {
|
|
31122
|
-
const
|
|
31821
|
+
const sp = createSpace(name, agent, { description, parent_id, project_id });
|
|
31123
31822
|
return {
|
|
31124
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
31823
|
+
content: [{ type: "text", text: JSON.stringify(sp, null, 2) }]
|
|
31125
31824
|
};
|
|
31126
31825
|
} catch (e) {
|
|
31127
31826
|
if (e.message?.includes("UNIQUE constraint")) {
|
|
31128
31827
|
return {
|
|
31129
|
-
content: [{ type: "text", text: `
|
|
31828
|
+
content: [{ type: "text", text: `Space #${name} already exists` }],
|
|
31130
31829
|
isError: true
|
|
31131
31830
|
};
|
|
31132
31831
|
}
|
|
31133
|
-
|
|
31832
|
+
return {
|
|
31833
|
+
content: [{ type: "text", text: e.message }],
|
|
31834
|
+
isError: true
|
|
31835
|
+
};
|
|
31134
31836
|
}
|
|
31135
31837
|
});
|
|
31136
|
-
server.registerTool("
|
|
31137
|
-
title: "List
|
|
31138
|
-
description: "List all available
|
|
31139
|
-
|
|
31140
|
-
|
|
31838
|
+
server.registerTool("list_spaces", {
|
|
31839
|
+
title: "List Spaces",
|
|
31840
|
+
description: "List all available spaces with member and message counts. Can filter by project or parent. Archived spaces are excluded by default.",
|
|
31841
|
+
inputSchema: {
|
|
31842
|
+
project_id: exports_external.string().optional().describe("Filter by project ID"),
|
|
31843
|
+
parent_id: exports_external.string().optional().describe("Filter by parent space name. Use 'null' for top-level only."),
|
|
31844
|
+
include_archived: exports_external.boolean().optional().describe("Include archived spaces (default: false)")
|
|
31845
|
+
}
|
|
31846
|
+
}, async ({ project_id, parent_id, include_archived }) => {
|
|
31847
|
+
const opts = {};
|
|
31848
|
+
if (project_id)
|
|
31849
|
+
opts.project_id = project_id;
|
|
31850
|
+
if (parent_id === "null") {
|
|
31851
|
+
opts.parent_id = null;
|
|
31852
|
+
} else if (parent_id) {
|
|
31853
|
+
opts.parent_id = parent_id;
|
|
31854
|
+
}
|
|
31855
|
+
if (include_archived)
|
|
31856
|
+
opts.include_archived = true;
|
|
31857
|
+
const spaces = listSpaces(opts);
|
|
31141
31858
|
return {
|
|
31142
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
31859
|
+
content: [{ type: "text", text: JSON.stringify(spaces, null, 2) }]
|
|
31143
31860
|
};
|
|
31144
31861
|
});
|
|
31145
|
-
server.registerTool("
|
|
31146
|
-
title: "Send to
|
|
31147
|
-
description: "Send a message to a
|
|
31862
|
+
server.registerTool("send_to_space", {
|
|
31863
|
+
title: "Send to Space",
|
|
31864
|
+
description: "Send a message to a space. All members can see it.",
|
|
31148
31865
|
inputSchema: {
|
|
31149
|
-
|
|
31866
|
+
space: exports_external.string().describe("Space name"),
|
|
31150
31867
|
content: exports_external.string().describe("Message content"),
|
|
31151
31868
|
priority: exports_external.enum(["low", "normal", "high", "urgent"]).optional().describe("Message priority")
|
|
31152
31869
|
}
|
|
31153
|
-
}, async ({
|
|
31870
|
+
}, async ({ space, content, priority }) => {
|
|
31154
31871
|
const from = resolveIdentity();
|
|
31155
|
-
const
|
|
31156
|
-
if (!
|
|
31872
|
+
const sp = getSpace(space);
|
|
31873
|
+
if (!sp) {
|
|
31157
31874
|
return {
|
|
31158
|
-
content: [{ type: "text", text: `
|
|
31875
|
+
content: [{ type: "text", text: `Space #${space} not found` }],
|
|
31159
31876
|
isError: true
|
|
31160
31877
|
};
|
|
31161
31878
|
}
|
|
31162
31879
|
const msg = sendMessage({
|
|
31163
31880
|
from,
|
|
31164
|
-
to:
|
|
31881
|
+
to: space,
|
|
31165
31882
|
content,
|
|
31166
|
-
|
|
31167
|
-
session_id: `
|
|
31883
|
+
space,
|
|
31884
|
+
session_id: `space:${space}`,
|
|
31168
31885
|
priority
|
|
31169
31886
|
});
|
|
31170
31887
|
return {
|
|
31171
31888
|
content: [{ type: "text", text: JSON.stringify(msg, null, 2) }]
|
|
31172
31889
|
};
|
|
31173
31890
|
});
|
|
31174
|
-
server.registerTool("
|
|
31175
|
-
title: "Read
|
|
31176
|
-
description: "Read messages from a
|
|
31891
|
+
server.registerTool("read_space", {
|
|
31892
|
+
title: "Read Space",
|
|
31893
|
+
description: "Read messages from a space.",
|
|
31177
31894
|
inputSchema: {
|
|
31178
|
-
|
|
31895
|
+
space: exports_external.string().describe("Space name"),
|
|
31179
31896
|
since: exports_external.string().optional().describe("Messages after this ISO timestamp"),
|
|
31180
31897
|
limit: exports_external.number().optional().describe("Max messages to return")
|
|
31181
31898
|
}
|
|
31182
|
-
}, async ({
|
|
31183
|
-
const messages = readMessages({
|
|
31899
|
+
}, async ({ space, since, limit }) => {
|
|
31900
|
+
const messages = readMessages({ space, since, limit });
|
|
31184
31901
|
return {
|
|
31185
31902
|
content: [{ type: "text", text: JSON.stringify(messages, null, 2) }]
|
|
31186
31903
|
};
|
|
31187
31904
|
});
|
|
31188
|
-
server.registerTool("
|
|
31189
|
-
title: "Join
|
|
31190
|
-
description: "Join a
|
|
31905
|
+
server.registerTool("join_space", {
|
|
31906
|
+
title: "Join Space",
|
|
31907
|
+
description: "Join a space to receive messages.",
|
|
31191
31908
|
inputSchema: {
|
|
31192
|
-
|
|
31909
|
+
space: exports_external.string().describe("Space name to join")
|
|
31193
31910
|
}
|
|
31194
|
-
}, async ({
|
|
31911
|
+
}, async ({ space }) => {
|
|
31195
31912
|
const agent = resolveIdentity();
|
|
31196
|
-
const ok =
|
|
31913
|
+
const ok = joinSpace(space, agent);
|
|
31197
31914
|
if (!ok) {
|
|
31198
31915
|
return {
|
|
31199
|
-
content: [{ type: "text", text: `
|
|
31916
|
+
content: [{ type: "text", text: `Space #${space} not found` }],
|
|
31200
31917
|
isError: true
|
|
31201
31918
|
};
|
|
31202
31919
|
}
|
|
31203
31920
|
return {
|
|
31204
|
-
content: [{ type: "text", text: JSON.stringify({
|
|
31921
|
+
content: [{ type: "text", text: JSON.stringify({ space, agent, joined: true }, null, 2) }]
|
|
31205
31922
|
};
|
|
31206
31923
|
});
|
|
31207
|
-
server.registerTool("
|
|
31208
|
-
title: "Leave
|
|
31209
|
-
description: "Leave a
|
|
31924
|
+
server.registerTool("leave_space", {
|
|
31925
|
+
title: "Leave Space",
|
|
31926
|
+
description: "Leave a space.",
|
|
31210
31927
|
inputSchema: {
|
|
31211
|
-
|
|
31928
|
+
space: exports_external.string().describe("Space name to leave")
|
|
31212
31929
|
}
|
|
31213
|
-
}, async ({
|
|
31930
|
+
}, async ({ space }) => {
|
|
31214
31931
|
const agent = resolveIdentity();
|
|
31215
|
-
const left =
|
|
31932
|
+
const left = leaveSpace(space, agent);
|
|
31216
31933
|
return {
|
|
31217
|
-
content: [{ type: "text", text: JSON.stringify({
|
|
31934
|
+
content: [{ type: "text", text: JSON.stringify({ space, agent, left }, null, 2) }]
|
|
31218
31935
|
};
|
|
31219
31936
|
});
|
|
31220
|
-
|
|
31221
|
-
|
|
31222
|
-
|
|
31223
|
-
|
|
31224
|
-
|
|
31225
|
-
|
|
31226
|
-
|
|
31227
|
-
|
|
31228
|
-
|
|
31229
|
-
|
|
31230
|
-
|
|
31231
|
-
|
|
31232
|
-
|
|
31937
|
+
server.registerTool("update_space", {
|
|
31938
|
+
title: "Update Space",
|
|
31939
|
+
description: "Update a space's description, parent, or project association.",
|
|
31940
|
+
inputSchema: {
|
|
31941
|
+
name: exports_external.string().describe("Space name to update"),
|
|
31942
|
+
description: exports_external.string().optional().describe("New description"),
|
|
31943
|
+
parent_id: exports_external.string().optional().describe("New parent space name (use 'null' to remove parent)"),
|
|
31944
|
+
project_id: exports_external.string().optional().describe("New project ID (use 'null' to remove project)")
|
|
31945
|
+
}
|
|
31946
|
+
}, async ({ name, description, parent_id, project_id }) => {
|
|
31947
|
+
const updates = {};
|
|
31948
|
+
if (description !== undefined)
|
|
31949
|
+
updates.description = description;
|
|
31950
|
+
if (parent_id !== undefined)
|
|
31951
|
+
updates.parent_id = parent_id === "null" ? null : parent_id;
|
|
31952
|
+
if (project_id !== undefined)
|
|
31953
|
+
updates.project_id = project_id === "null" ? null : project_id;
|
|
31954
|
+
try {
|
|
31955
|
+
const sp = updateSpace(name, updates);
|
|
31956
|
+
return {
|
|
31957
|
+
content: [{ type: "text", text: JSON.stringify(sp, null, 2) }]
|
|
31958
|
+
};
|
|
31959
|
+
} catch (e) {
|
|
31960
|
+
return {
|
|
31961
|
+
content: [{ type: "text", text: e.message }],
|
|
31962
|
+
isError: true
|
|
31963
|
+
};
|
|
31964
|
+
}
|
|
31965
|
+
});
|
|
31966
|
+
server.registerTool("archive_space", {
|
|
31967
|
+
title: "Archive Space",
|
|
31968
|
+
description: "Archive a space. Archived spaces are hidden from list by default.",
|
|
31969
|
+
inputSchema: {
|
|
31970
|
+
name: exports_external.string().describe("Space name to archive")
|
|
31971
|
+
}
|
|
31972
|
+
}, async ({ name }) => {
|
|
31973
|
+
try {
|
|
31974
|
+
const sp = archiveSpace(name);
|
|
31975
|
+
return {
|
|
31976
|
+
content: [{ type: "text", text: JSON.stringify(sp, null, 2) }]
|
|
31977
|
+
};
|
|
31978
|
+
} catch (e) {
|
|
31979
|
+
return {
|
|
31980
|
+
content: [{ type: "text", text: e.message }],
|
|
31981
|
+
isError: true
|
|
31982
|
+
};
|
|
31983
|
+
}
|
|
31984
|
+
});
|
|
31985
|
+
server.registerTool("unarchive_space", {
|
|
31986
|
+
title: "Unarchive Space",
|
|
31987
|
+
description: "Unarchive a previously archived space.",
|
|
31988
|
+
inputSchema: {
|
|
31989
|
+
name: exports_external.string().describe("Space name to unarchive")
|
|
31990
|
+
}
|
|
31991
|
+
}, async ({ name }) => {
|
|
31992
|
+
try {
|
|
31993
|
+
const sp = unarchiveSpace(name);
|
|
31994
|
+
return {
|
|
31995
|
+
content: [{ type: "text", text: JSON.stringify(sp, null, 2) }]
|
|
31996
|
+
};
|
|
31997
|
+
} catch (e) {
|
|
31998
|
+
return {
|
|
31999
|
+
content: [{ type: "text", text: e.message }],
|
|
32000
|
+
isError: true
|
|
32001
|
+
};
|
|
32002
|
+
}
|
|
32003
|
+
});
|
|
32004
|
+
server.registerTool("create_project", {
|
|
32005
|
+
title: "Create Project",
|
|
32006
|
+
description: "Create a new project. Projects organize spaces and provide context for agent collaboration.",
|
|
32007
|
+
inputSchema: {
|
|
32008
|
+
name: exports_external.string().describe("Project name (unique)"),
|
|
32009
|
+
description: exports_external.string().optional().describe("Project description"),
|
|
32010
|
+
path: exports_external.string().optional().describe("Absolute path to project on disk"),
|
|
32011
|
+
repository: exports_external.string().optional().describe("Repository URL"),
|
|
32012
|
+
tags: exports_external.string().optional().describe(`JSON array of tags (e.g. '["backend", "api"]')`),
|
|
32013
|
+
metadata: exports_external.string().optional().describe("JSON metadata string"),
|
|
32014
|
+
settings: exports_external.string().optional().describe("JSON settings string")
|
|
32015
|
+
}
|
|
32016
|
+
}, async ({ name, description, path, repository, tags, metadata, settings }) => {
|
|
32017
|
+
const agent = resolveIdentity();
|
|
32018
|
+
let parsedTags;
|
|
32019
|
+
if (tags) {
|
|
32020
|
+
try {
|
|
32021
|
+
parsedTags = JSON.parse(tags);
|
|
32022
|
+
} catch {
|
|
32023
|
+
return {
|
|
32024
|
+
content: [{ type: "text", text: "Invalid tags JSON. Expected array of strings." }],
|
|
32025
|
+
isError: true
|
|
32026
|
+
};
|
|
32027
|
+
}
|
|
32028
|
+
}
|
|
32029
|
+
let parsedMetadata;
|
|
32030
|
+
if (metadata) {
|
|
32031
|
+
try {
|
|
32032
|
+
parsedMetadata = JSON.parse(metadata);
|
|
32033
|
+
} catch {
|
|
32034
|
+
return {
|
|
32035
|
+
content: [{ type: "text", text: "Invalid metadata JSON." }],
|
|
32036
|
+
isError: true
|
|
32037
|
+
};
|
|
32038
|
+
}
|
|
32039
|
+
}
|
|
32040
|
+
let parsedSettings;
|
|
32041
|
+
if (settings) {
|
|
32042
|
+
try {
|
|
32043
|
+
parsedSettings = JSON.parse(settings);
|
|
32044
|
+
} catch {
|
|
32045
|
+
return {
|
|
32046
|
+
content: [{ type: "text", text: "Invalid settings JSON." }],
|
|
32047
|
+
isError: true
|
|
32048
|
+
};
|
|
32049
|
+
}
|
|
32050
|
+
}
|
|
32051
|
+
try {
|
|
32052
|
+
const project = createProject({
|
|
32053
|
+
name,
|
|
32054
|
+
created_by: agent,
|
|
32055
|
+
description,
|
|
32056
|
+
path,
|
|
32057
|
+
repository,
|
|
32058
|
+
tags: parsedTags,
|
|
32059
|
+
metadata: parsedMetadata,
|
|
32060
|
+
settings: parsedSettings
|
|
32061
|
+
});
|
|
32062
|
+
return {
|
|
32063
|
+
content: [{ type: "text", text: JSON.stringify(project, null, 2) }]
|
|
32064
|
+
};
|
|
32065
|
+
} catch (e) {
|
|
32066
|
+
if (e.message?.includes("UNIQUE constraint")) {
|
|
32067
|
+
return {
|
|
32068
|
+
content: [{ type: "text", text: `Project "${name}" already exists` }],
|
|
32069
|
+
isError: true
|
|
32070
|
+
};
|
|
32071
|
+
}
|
|
32072
|
+
return {
|
|
32073
|
+
content: [{ type: "text", text: e.message }],
|
|
32074
|
+
isError: true
|
|
32075
|
+
};
|
|
32076
|
+
}
|
|
32077
|
+
});
|
|
32078
|
+
server.registerTool("list_projects", {
|
|
32079
|
+
title: "List Projects",
|
|
32080
|
+
description: "List all registered projects.",
|
|
32081
|
+
inputSchema: {
|
|
32082
|
+
status: exports_external.enum(["active", "archived"]).optional().describe("Filter by project status")
|
|
32083
|
+
}
|
|
32084
|
+
}, async ({ status }) => {
|
|
32085
|
+
const projects = listProjects(status ? { status } : undefined);
|
|
32086
|
+
return {
|
|
32087
|
+
content: [{ type: "text", text: JSON.stringify(projects, null, 2) }]
|
|
32088
|
+
};
|
|
32089
|
+
});
|
|
32090
|
+
server.registerTool("get_project", {
|
|
32091
|
+
title: "Get Project",
|
|
32092
|
+
description: "Get full details of a project by ID or name.",
|
|
32093
|
+
inputSchema: {
|
|
32094
|
+
id: exports_external.string().describe("Project ID (UUID) or name")
|
|
32095
|
+
}
|
|
32096
|
+
}, async ({ id }) => {
|
|
32097
|
+
let project = getProject(id);
|
|
32098
|
+
if (!project) {
|
|
32099
|
+
project = getProjectByName(id);
|
|
32100
|
+
}
|
|
32101
|
+
if (!project) {
|
|
32102
|
+
return {
|
|
32103
|
+
content: [{ type: "text", text: `Project "${id}" not found` }],
|
|
32104
|
+
isError: true
|
|
32105
|
+
};
|
|
32106
|
+
}
|
|
32107
|
+
return {
|
|
32108
|
+
content: [{ type: "text", text: JSON.stringify(project, null, 2) }]
|
|
32109
|
+
};
|
|
32110
|
+
});
|
|
32111
|
+
server.registerTool("update_project", {
|
|
32112
|
+
title: "Update Project",
|
|
32113
|
+
description: "Update a project's fields.",
|
|
32114
|
+
inputSchema: {
|
|
32115
|
+
id: exports_external.string().describe("Project ID (UUID)"),
|
|
32116
|
+
name: exports_external.string().optional().describe("New project name"),
|
|
32117
|
+
description: exports_external.string().optional().describe("New description"),
|
|
32118
|
+
path: exports_external.string().optional().describe("New path"),
|
|
32119
|
+
status: exports_external.enum(["active", "archived"]).optional().describe("New status"),
|
|
32120
|
+
repository: exports_external.string().optional().describe("New repository URL"),
|
|
32121
|
+
tags: exports_external.string().optional().describe("JSON array of tags"),
|
|
32122
|
+
metadata: exports_external.string().optional().describe("JSON metadata string"),
|
|
32123
|
+
settings: exports_external.string().optional().describe("JSON settings string")
|
|
32124
|
+
}
|
|
32125
|
+
}, async ({ id, name, description, path, status, repository, tags, metadata, settings }) => {
|
|
32126
|
+
const updates = {};
|
|
32127
|
+
if (name !== undefined)
|
|
32128
|
+
updates.name = name;
|
|
32129
|
+
if (description !== undefined)
|
|
32130
|
+
updates.description = description;
|
|
32131
|
+
if (path !== undefined)
|
|
32132
|
+
updates.path = path;
|
|
32133
|
+
if (status !== undefined)
|
|
32134
|
+
updates.status = status;
|
|
32135
|
+
if (repository !== undefined)
|
|
32136
|
+
updates.repository = repository;
|
|
32137
|
+
if (tags) {
|
|
32138
|
+
try {
|
|
32139
|
+
updates.tags = JSON.parse(tags);
|
|
32140
|
+
} catch {
|
|
32141
|
+
return {
|
|
32142
|
+
content: [{ type: "text", text: "Invalid tags JSON." }],
|
|
32143
|
+
isError: true
|
|
32144
|
+
};
|
|
32145
|
+
}
|
|
32146
|
+
}
|
|
32147
|
+
if (metadata) {
|
|
32148
|
+
try {
|
|
32149
|
+
updates.metadata = JSON.parse(metadata);
|
|
32150
|
+
} catch {
|
|
32151
|
+
return {
|
|
32152
|
+
content: [{ type: "text", text: "Invalid metadata JSON." }],
|
|
32153
|
+
isError: true
|
|
32154
|
+
};
|
|
32155
|
+
}
|
|
32156
|
+
}
|
|
32157
|
+
if (settings) {
|
|
32158
|
+
try {
|
|
32159
|
+
updates.settings = JSON.parse(settings);
|
|
32160
|
+
} catch {
|
|
32161
|
+
return {
|
|
32162
|
+
content: [{ type: "text", text: "Invalid settings JSON." }],
|
|
32163
|
+
isError: true
|
|
32164
|
+
};
|
|
32165
|
+
}
|
|
32166
|
+
}
|
|
32167
|
+
try {
|
|
32168
|
+
const project = updateProject(id, updates);
|
|
32169
|
+
return {
|
|
32170
|
+
content: [{ type: "text", text: JSON.stringify(project, null, 2) }]
|
|
32171
|
+
};
|
|
32172
|
+
} catch (e) {
|
|
32173
|
+
return {
|
|
32174
|
+
content: [{ type: "text", text: e.message }],
|
|
32175
|
+
isError: true
|
|
32176
|
+
};
|
|
32177
|
+
}
|
|
32178
|
+
});
|
|
32179
|
+
server.registerTool("delete_project", {
|
|
32180
|
+
title: "Delete Project",
|
|
32181
|
+
description: "Delete a project permanently. Fails if spaces still reference it.",
|
|
32182
|
+
inputSchema: {
|
|
32183
|
+
id: exports_external.string().describe("Project ID (UUID)")
|
|
32184
|
+
}
|
|
32185
|
+
}, async ({ id }) => {
|
|
32186
|
+
try {
|
|
32187
|
+
const deleted = deleteProject(id);
|
|
32188
|
+
if (!deleted) {
|
|
32189
|
+
return {
|
|
32190
|
+
content: [{ type: "text", text: `Project "${id}" not found` }],
|
|
32191
|
+
isError: true
|
|
32192
|
+
};
|
|
32193
|
+
}
|
|
32194
|
+
return {
|
|
32195
|
+
content: [{ type: "text", text: JSON.stringify({ id, deleted: true }, null, 2) }]
|
|
32196
|
+
};
|
|
32197
|
+
} catch (e) {
|
|
32198
|
+
return {
|
|
32199
|
+
content: [{ type: "text", text: e.message }],
|
|
32200
|
+
isError: true
|
|
32201
|
+
};
|
|
32202
|
+
}
|
|
32203
|
+
});
|
|
32204
|
+
server.registerTool("delete_message", {
|
|
32205
|
+
title: "Delete Message",
|
|
32206
|
+
description: "Delete a message. Only the sender can delete their own messages. The agent is auto-resolved.",
|
|
32207
|
+
inputSchema: {
|
|
32208
|
+
id: exports_external.number().describe("Message ID to delete")
|
|
32209
|
+
}
|
|
32210
|
+
}, async ({ id }) => {
|
|
32211
|
+
const agent = resolveIdentity();
|
|
32212
|
+
const deleted = deleteMessage(id, agent);
|
|
32213
|
+
if (!deleted) {
|
|
32214
|
+
return {
|
|
32215
|
+
content: [{ type: "text", text: `Message #${id} not found or not your message` }],
|
|
32216
|
+
isError: true
|
|
32217
|
+
};
|
|
32218
|
+
}
|
|
32219
|
+
return {
|
|
32220
|
+
content: [{ type: "text", text: JSON.stringify({ deleted: true }, null, 2) }]
|
|
32221
|
+
};
|
|
32222
|
+
});
|
|
32223
|
+
server.registerTool("edit_message", {
|
|
32224
|
+
title: "Edit Message",
|
|
32225
|
+
description: "Edit a message's content. Only the sender can edit their own messages. The agent is auto-resolved.",
|
|
32226
|
+
inputSchema: {
|
|
32227
|
+
id: exports_external.number().describe("Message ID to edit"),
|
|
32228
|
+
content: exports_external.string().describe("New message content")
|
|
32229
|
+
}
|
|
32230
|
+
}, async ({ id, content }) => {
|
|
32231
|
+
const agent = resolveIdentity();
|
|
32232
|
+
const msg = editMessage(id, agent, content);
|
|
32233
|
+
if (!msg) {
|
|
32234
|
+
return {
|
|
32235
|
+
content: [{ type: "text", text: `Message #${id} not found or not your message` }],
|
|
32236
|
+
isError: true
|
|
32237
|
+
};
|
|
32238
|
+
}
|
|
32239
|
+
return {
|
|
32240
|
+
content: [{ type: "text", text: JSON.stringify(msg, null, 2) }]
|
|
32241
|
+
};
|
|
32242
|
+
});
|
|
32243
|
+
server.registerTool("pin_message", {
|
|
32244
|
+
title: "Pin Message",
|
|
32245
|
+
description: "Pin a message. Pinned messages can be retrieved with get_pinned_messages.",
|
|
32246
|
+
inputSchema: {
|
|
32247
|
+
id: exports_external.number().describe("Message ID to pin")
|
|
32248
|
+
}
|
|
32249
|
+
}, async ({ id }) => {
|
|
32250
|
+
const msg = pinMessage(id);
|
|
32251
|
+
if (!msg) {
|
|
32252
|
+
return {
|
|
32253
|
+
content: [{ type: "text", text: `Message #${id} not found` }],
|
|
32254
|
+
isError: true
|
|
32255
|
+
};
|
|
32256
|
+
}
|
|
32257
|
+
return {
|
|
32258
|
+
content: [{ type: "text", text: JSON.stringify(msg, null, 2) }]
|
|
32259
|
+
};
|
|
32260
|
+
});
|
|
32261
|
+
server.registerTool("unpin_message", {
|
|
32262
|
+
title: "Unpin Message",
|
|
32263
|
+
description: "Unpin a previously pinned message.",
|
|
32264
|
+
inputSchema: {
|
|
32265
|
+
id: exports_external.number().describe("Message ID to unpin")
|
|
32266
|
+
}
|
|
32267
|
+
}, async ({ id }) => {
|
|
32268
|
+
const msg = unpinMessage(id);
|
|
32269
|
+
if (!msg) {
|
|
32270
|
+
return {
|
|
32271
|
+
content: [{ type: "text", text: `Message #${id} not found` }],
|
|
32272
|
+
isError: true
|
|
32273
|
+
};
|
|
32274
|
+
}
|
|
32275
|
+
return {
|
|
32276
|
+
content: [{ type: "text", text: JSON.stringify(msg, null, 2) }]
|
|
32277
|
+
};
|
|
32278
|
+
});
|
|
32279
|
+
server.registerTool("get_pinned_messages", {
|
|
32280
|
+
title: "Get Pinned Messages",
|
|
32281
|
+
description: "Retrieve pinned messages, optionally filtered by space or session.",
|
|
32282
|
+
inputSchema: {
|
|
32283
|
+
space: exports_external.string().optional().describe("Filter by space name"),
|
|
32284
|
+
session_id: exports_external.string().optional().describe("Filter by session ID"),
|
|
32285
|
+
limit: exports_external.number().optional().describe("Max messages to return")
|
|
32286
|
+
}
|
|
32287
|
+
}, async ({ space, session_id, limit }) => {
|
|
32288
|
+
const messages = getPinnedMessages({ space, session_id, limit });
|
|
32289
|
+
return {
|
|
32290
|
+
content: [{ type: "text", text: JSON.stringify(messages, null, 2) }]
|
|
32291
|
+
};
|
|
32292
|
+
});
|
|
32293
|
+
server.registerTool("heartbeat", {
|
|
32294
|
+
title: "Heartbeat",
|
|
32295
|
+
description: "Send a heartbeat to indicate agent is alive. Auto-resolves agent from CONVERSATIONS_AGENT_ID env var. Optionally set a status.",
|
|
32296
|
+
inputSchema: {
|
|
32297
|
+
status: exports_external.string().optional().describe("Agent status (e.g. 'online', 'busy', 'idle'). Defaults to 'online'.")
|
|
32298
|
+
}
|
|
32299
|
+
}, async ({ status }) => {
|
|
32300
|
+
const agent = resolveIdentity();
|
|
32301
|
+
heartbeat(agent, status);
|
|
32302
|
+
return {
|
|
32303
|
+
content: [{ type: "text", text: JSON.stringify({ agent, status: status || "online", heartbeat: true }, null, 2) }]
|
|
32304
|
+
};
|
|
32305
|
+
});
|
|
32306
|
+
server.registerTool("list_agents", {
|
|
32307
|
+
title: "List Agents",
|
|
32308
|
+
description: "List all agents with their presence status. Returns agent name, status, last seen time, and whether they are online.",
|
|
32309
|
+
inputSchema: {
|
|
32310
|
+
online_only: exports_external.boolean().optional().describe("Only return agents that are currently online (seen within last 60 seconds)")
|
|
32311
|
+
}
|
|
32312
|
+
}, async ({ online_only }) => {
|
|
32313
|
+
const agents = listAgents({ online_only });
|
|
32314
|
+
return {
|
|
32315
|
+
content: [{ type: "text", text: JSON.stringify(agents, null, 2) }]
|
|
32316
|
+
};
|
|
32317
|
+
});
|
|
32318
|
+
isDirectRun = import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith("mcp.js") || process.argv[1]?.endsWith("mcp.ts");
|
|
32319
|
+
if (isDirectRun) {
|
|
32320
|
+
startMcpServer().catch((error48) => {
|
|
32321
|
+
console.error("MCP server error:", error48);
|
|
32322
|
+
process.exit(1);
|
|
32323
|
+
});
|
|
32324
|
+
}
|
|
32325
|
+
});
|
|
32326
|
+
|
|
32327
|
+
// src/server/serve.ts
|
|
32328
|
+
var exports_serve = {};
|
|
32329
|
+
__export(exports_serve, {
|
|
32330
|
+
startDashboardServer: () => startDashboardServer
|
|
31233
32331
|
});
|
|
31234
|
-
import { join as join2 } from "path";
|
|
32332
|
+
import { join as join2, resolve, sep } from "path";
|
|
31235
32333
|
import { existsSync } from "fs";
|
|
32334
|
+
function securityHeaders(base) {
|
|
32335
|
+
const headers = new Headers(base);
|
|
32336
|
+
if (!headers.has("X-Content-Type-Options"))
|
|
32337
|
+
headers.set("X-Content-Type-Options", "nosniff");
|
|
32338
|
+
if (!headers.has("X-Frame-Options"))
|
|
32339
|
+
headers.set("X-Frame-Options", "DENY");
|
|
32340
|
+
if (!headers.has("Referrer-Policy"))
|
|
32341
|
+
headers.set("Referrer-Policy", "no-referrer");
|
|
32342
|
+
if (!headers.has("Permissions-Policy")) {
|
|
32343
|
+
headers.set("Permissions-Policy", "camera=(), microphone=(), geolocation=()");
|
|
32344
|
+
}
|
|
32345
|
+
if (!headers.has("Cross-Origin-Resource-Policy")) {
|
|
32346
|
+
headers.set("Cross-Origin-Resource-Policy", "same-origin");
|
|
32347
|
+
}
|
|
32348
|
+
if (!headers.has("Content-Security-Policy")) {
|
|
32349
|
+
headers.set("Content-Security-Policy", "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; base-uri 'self'; frame-ancestors 'none'; object-src 'none'");
|
|
32350
|
+
}
|
|
32351
|
+
return headers;
|
|
32352
|
+
}
|
|
31236
32353
|
function jsonResponse(data, status = 200) {
|
|
31237
32354
|
return new Response(JSON.stringify(data), {
|
|
31238
32355
|
status,
|
|
31239
|
-
headers: {
|
|
32356
|
+
headers: securityHeaders({
|
|
32357
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
32358
|
+
"Cache-Control": "no-store"
|
|
32359
|
+
})
|
|
31240
32360
|
});
|
|
31241
32361
|
}
|
|
31242
32362
|
function getStatus() {
|
|
@@ -31245,20 +32365,46 @@ function getStatus() {
|
|
|
31245
32365
|
const totalMessages = db2.prepare("SELECT COUNT(*) as count FROM messages").get().count;
|
|
31246
32366
|
const totalSessions = db2.prepare("SELECT COUNT(DISTINCT session_id) as count FROM messages").get().count;
|
|
31247
32367
|
const totalUnread = db2.prepare("SELECT COUNT(*) as count FROM messages WHERE read_at IS NULL").get().count;
|
|
31248
|
-
const
|
|
32368
|
+
const totalSpaces = db2.prepare("SELECT COUNT(*) as count FROM spaces").get().count;
|
|
32369
|
+
const totalProjects = db2.prepare("SELECT COUNT(*) as count FROM projects").get().count;
|
|
31249
32370
|
return {
|
|
31250
32371
|
db_path: dbPath,
|
|
31251
32372
|
total_messages: totalMessages,
|
|
31252
32373
|
total_sessions: totalSessions,
|
|
31253
|
-
|
|
32374
|
+
total_spaces: totalSpaces,
|
|
32375
|
+
total_projects: totalProjects,
|
|
31254
32376
|
unread_messages: totalUnread
|
|
31255
32377
|
};
|
|
31256
32378
|
}
|
|
31257
|
-
function
|
|
32379
|
+
function normalizeHost(value) {
|
|
32380
|
+
const host = typeof value === "string" ? value.trim() : "";
|
|
32381
|
+
return host.length > 0 ? host : "127.0.0.1";
|
|
32382
|
+
}
|
|
32383
|
+
function normalizePort(value, fallback) {
|
|
32384
|
+
const parsed = typeof value === "string" ? parseInt(value, 10) : value;
|
|
32385
|
+
if (!Number.isFinite(parsed))
|
|
32386
|
+
return fallback;
|
|
32387
|
+
const port = parsed;
|
|
32388
|
+
if (port < 0 || port > 65535)
|
|
32389
|
+
return fallback;
|
|
32390
|
+
return port;
|
|
32391
|
+
}
|
|
32392
|
+
function isSameOrigin(req) {
|
|
32393
|
+
const origin = req.headers.get("origin");
|
|
32394
|
+
if (!origin)
|
|
32395
|
+
return true;
|
|
32396
|
+
if (origin === "null")
|
|
32397
|
+
return false;
|
|
32398
|
+
return origin === new URL(req.url).origin;
|
|
32399
|
+
}
|
|
32400
|
+
function startDashboardServer(port = 3456, host) {
|
|
32401
|
+
const resolvedPort = normalizePort(port, 3456);
|
|
32402
|
+
const resolvedHost = normalizeHost(host ?? process.env.CONVERSATIONS_DASHBOARD_HOST);
|
|
31258
32403
|
const dashboardDist = join2(import.meta.dir, "../../dashboard/dist");
|
|
31259
32404
|
const hasDist = existsSync(dashboardDist);
|
|
31260
32405
|
const server2 = Bun.serve({
|
|
31261
|
-
port,
|
|
32406
|
+
port: resolvedPort,
|
|
32407
|
+
hostname: resolvedHost,
|
|
31262
32408
|
async fetch(req) {
|
|
31263
32409
|
const url2 = new URL(req.url);
|
|
31264
32410
|
const path = url2.pathname;
|
|
@@ -31266,57 +32412,390 @@ function startDashboardServer(port = 3456) {
|
|
|
31266
32412
|
return jsonResponse(getStatus());
|
|
31267
32413
|
}
|
|
31268
32414
|
if (path === "/api/messages" && req.method === "GET") {
|
|
31269
|
-
const
|
|
32415
|
+
const rawLimit = url2.searchParams.get("limit");
|
|
32416
|
+
let limit = parseInt(rawLimit || "50", 10);
|
|
32417
|
+
if (!Number.isFinite(limit) || limit <= 0)
|
|
32418
|
+
limit = 50;
|
|
32419
|
+
if (limit > 500)
|
|
32420
|
+
limit = 500;
|
|
31270
32421
|
const session = url2.searchParams.get("session") || undefined;
|
|
31271
|
-
const
|
|
32422
|
+
const space = url2.searchParams.get("space") || undefined;
|
|
31272
32423
|
const from = url2.searchParams.get("from") || undefined;
|
|
31273
32424
|
const to = url2.searchParams.get("to") || undefined;
|
|
31274
|
-
const messages = readMessages({ session_id: session,
|
|
31275
|
-
return jsonResponse(messages
|
|
32425
|
+
const messages = readMessages({ session_id: session, space, from, to, limit, order: "desc" });
|
|
32426
|
+
return jsonResponse(messages);
|
|
31276
32427
|
}
|
|
31277
32428
|
if (path === "/api/messages" && req.method === "POST") {
|
|
32429
|
+
if (!isSameOrigin(req)) {
|
|
32430
|
+
return jsonResponse({ error: "Invalid origin" }, 403);
|
|
32431
|
+
}
|
|
31278
32432
|
try {
|
|
31279
32433
|
const text = await req.text();
|
|
31280
32434
|
const body = JSON.parse(text);
|
|
32435
|
+
const from = typeof body.from === "string" ? body.from.trim() : "";
|
|
32436
|
+
const to = typeof body.to === "string" ? body.to.trim() : "";
|
|
32437
|
+
const content = typeof body.content === "string" ? body.content.trim() : "";
|
|
32438
|
+
const space = typeof body.space === "string" ? body.space.trim() : undefined;
|
|
32439
|
+
const priority = typeof body.priority === "string" ? body.priority.trim().toLowerCase() : undefined;
|
|
32440
|
+
if (!from || !to || !content) {
|
|
32441
|
+
return jsonResponse({ error: "from, to, and content are required" }, 400);
|
|
32442
|
+
}
|
|
32443
|
+
if (priority && !["low", "normal", "high", "urgent"].includes(priority)) {
|
|
32444
|
+
return jsonResponse({ error: "Invalid priority" }, 400);
|
|
32445
|
+
}
|
|
31281
32446
|
const msg = sendMessage({
|
|
31282
|
-
from
|
|
31283
|
-
to
|
|
31284
|
-
content
|
|
31285
|
-
|
|
31286
|
-
priority
|
|
32447
|
+
from,
|
|
32448
|
+
to,
|
|
32449
|
+
content,
|
|
32450
|
+
space,
|
|
32451
|
+
priority
|
|
31287
32452
|
});
|
|
31288
32453
|
return jsonResponse(msg);
|
|
31289
32454
|
} catch (e) {
|
|
31290
32455
|
return jsonResponse({ error: e.message }, 400);
|
|
31291
32456
|
}
|
|
31292
32457
|
}
|
|
32458
|
+
if (path === "/api/messages/search" && req.method === "GET") {
|
|
32459
|
+
const q = url2.searchParams.get("q") || "";
|
|
32460
|
+
if (!q.trim()) {
|
|
32461
|
+
return jsonResponse({ error: "Query parameter 'q' is required" }, 400);
|
|
32462
|
+
}
|
|
32463
|
+
const rawLimit = url2.searchParams.get("limit");
|
|
32464
|
+
let limit = parseInt(rawLimit || "50", 10);
|
|
32465
|
+
if (!Number.isFinite(limit) || limit <= 0)
|
|
32466
|
+
limit = 50;
|
|
32467
|
+
if (limit > 500)
|
|
32468
|
+
limit = 500;
|
|
32469
|
+
const space = url2.searchParams.get("space") || undefined;
|
|
32470
|
+
const from = url2.searchParams.get("from") || undefined;
|
|
32471
|
+
const to = url2.searchParams.get("to") || undefined;
|
|
32472
|
+
const messages = searchMessages({ query: q.trim(), space, from, to, limit });
|
|
32473
|
+
return jsonResponse(messages);
|
|
32474
|
+
}
|
|
32475
|
+
if (path === "/api/export" && req.method === "GET") {
|
|
32476
|
+
const space = url2.searchParams.get("space") || undefined;
|
|
32477
|
+
const session = url2.searchParams.get("session") || undefined;
|
|
32478
|
+
const from = url2.searchParams.get("from") || undefined;
|
|
32479
|
+
const since = url2.searchParams.get("since") || undefined;
|
|
32480
|
+
const until = url2.searchParams.get("until") || undefined;
|
|
32481
|
+
const format = url2.searchParams.get("format") === "csv" ? "csv" : "json";
|
|
32482
|
+
const result = exportMessages({ space, session_id: session, from, since, until, format });
|
|
32483
|
+
if (format === "csv") {
|
|
32484
|
+
return new Response(result, {
|
|
32485
|
+
status: 200,
|
|
32486
|
+
headers: securityHeaders({
|
|
32487
|
+
"Content-Type": "text/csv; charset=utf-8",
|
|
32488
|
+
"Content-Disposition": 'attachment; filename="messages.csv"',
|
|
32489
|
+
"Cache-Control": "no-store"
|
|
32490
|
+
})
|
|
32491
|
+
});
|
|
32492
|
+
}
|
|
32493
|
+
return jsonResponse(JSON.parse(result));
|
|
32494
|
+
}
|
|
32495
|
+
if (path === "/api/messages/pinned" && req.method === "GET") {
|
|
32496
|
+
const space = url2.searchParams.get("space") || undefined;
|
|
32497
|
+
const session_id = url2.searchParams.get("session_id") || undefined;
|
|
32498
|
+
const rawLimit = url2.searchParams.get("limit");
|
|
32499
|
+
let limit;
|
|
32500
|
+
if (rawLimit) {
|
|
32501
|
+
limit = parseInt(rawLimit, 10);
|
|
32502
|
+
if (!Number.isFinite(limit) || limit <= 0)
|
|
32503
|
+
limit = 50;
|
|
32504
|
+
if (limit > 500)
|
|
32505
|
+
limit = 500;
|
|
32506
|
+
}
|
|
32507
|
+
const messages = getPinnedMessages({ space, session_id, limit });
|
|
32508
|
+
return jsonResponse(messages);
|
|
32509
|
+
}
|
|
32510
|
+
const pinMatch = path.match(/^\/api\/messages\/(\d+)\/pin$/);
|
|
32511
|
+
if (pinMatch) {
|
|
32512
|
+
const messageId = parseInt(pinMatch[1], 10);
|
|
32513
|
+
if (req.method === "POST") {
|
|
32514
|
+
if (!isSameOrigin(req)) {
|
|
32515
|
+
return jsonResponse({ error: "Invalid origin" }, 403);
|
|
32516
|
+
}
|
|
32517
|
+
const msg = pinMessage(messageId);
|
|
32518
|
+
if (!msg)
|
|
32519
|
+
return jsonResponse({ error: "Message not found" }, 404);
|
|
32520
|
+
return jsonResponse(msg);
|
|
32521
|
+
}
|
|
32522
|
+
if (req.method === "DELETE") {
|
|
32523
|
+
if (!isSameOrigin(req)) {
|
|
32524
|
+
return jsonResponse({ error: "Invalid origin" }, 403);
|
|
32525
|
+
}
|
|
32526
|
+
const msg = unpinMessage(messageId);
|
|
32527
|
+
if (!msg)
|
|
32528
|
+
return jsonResponse({ error: "Message not found" }, 404);
|
|
32529
|
+
return jsonResponse(msg);
|
|
32530
|
+
}
|
|
32531
|
+
}
|
|
32532
|
+
const messageMatch = path.match(/^\/api\/messages\/(\d+)$/);
|
|
32533
|
+
if (messageMatch) {
|
|
32534
|
+
const messageId = parseInt(messageMatch[1], 10);
|
|
32535
|
+
if (req.method === "DELETE") {
|
|
32536
|
+
if (!isSameOrigin(req)) {
|
|
32537
|
+
return jsonResponse({ error: "Invalid origin" }, 403);
|
|
32538
|
+
}
|
|
32539
|
+
const from = url2.searchParams.get("from") || "";
|
|
32540
|
+
if (!from) {
|
|
32541
|
+
return jsonResponse({ error: "'from' query parameter is required" }, 400);
|
|
32542
|
+
}
|
|
32543
|
+
const deleted = deleteMessage(messageId, from);
|
|
32544
|
+
if (!deleted)
|
|
32545
|
+
return jsonResponse({ error: "Message not found or not your message" }, 404);
|
|
32546
|
+
return jsonResponse({ id: messageId, deleted: true });
|
|
32547
|
+
}
|
|
32548
|
+
if (req.method === "PUT") {
|
|
32549
|
+
if (!isSameOrigin(req)) {
|
|
32550
|
+
return jsonResponse({ error: "Invalid origin" }, 403);
|
|
32551
|
+
}
|
|
32552
|
+
try {
|
|
32553
|
+
const text = await req.text();
|
|
32554
|
+
const body = JSON.parse(text);
|
|
32555
|
+
const content = typeof body.content === "string" ? body.content.trim() : "";
|
|
32556
|
+
const from = typeof body.from === "string" ? body.from.trim() : "";
|
|
32557
|
+
if (!content || !from) {
|
|
32558
|
+
return jsonResponse({ error: "content and from are required" }, 400);
|
|
32559
|
+
}
|
|
32560
|
+
const msg = editMessage(messageId, from, content);
|
|
32561
|
+
if (!msg)
|
|
32562
|
+
return jsonResponse({ error: "Message not found or not your message" }, 404);
|
|
32563
|
+
return jsonResponse(msg);
|
|
32564
|
+
} catch (e) {
|
|
32565
|
+
return jsonResponse({ error: e.message }, 400);
|
|
32566
|
+
}
|
|
32567
|
+
}
|
|
32568
|
+
}
|
|
31293
32569
|
if (path === "/api/sessions") {
|
|
31294
32570
|
const agent = url2.searchParams.get("agent") || undefined;
|
|
31295
32571
|
return jsonResponse(listSessions(agent));
|
|
31296
32572
|
}
|
|
31297
|
-
if (path === "/api/
|
|
31298
|
-
|
|
32573
|
+
if (path === "/api/spaces" && req.method === "GET") {
|
|
32574
|
+
const projectId = url2.searchParams.get("project_id") || undefined;
|
|
32575
|
+
const includeArchived = url2.searchParams.get("include_archived") === "true";
|
|
32576
|
+
const listOpts = {};
|
|
32577
|
+
if (projectId)
|
|
32578
|
+
listOpts.project_id = projectId;
|
|
32579
|
+
if (includeArchived)
|
|
32580
|
+
listOpts.include_archived = true;
|
|
32581
|
+
return jsonResponse(listSpaces(Object.keys(listOpts).length > 0 ? listOpts : undefined));
|
|
32582
|
+
}
|
|
32583
|
+
if (path === "/api/spaces" && req.method === "POST") {
|
|
32584
|
+
if (!isSameOrigin(req)) {
|
|
32585
|
+
return jsonResponse({ error: "Invalid origin" }, 403);
|
|
32586
|
+
}
|
|
32587
|
+
try {
|
|
32588
|
+
const text = await req.text();
|
|
32589
|
+
const body = JSON.parse(text);
|
|
32590
|
+
const name = typeof body.name === "string" ? body.name.trim() : "";
|
|
32591
|
+
const createdBy = typeof body.created_by === "string" ? body.created_by.trim() : "";
|
|
32592
|
+
const description = typeof body.description === "string" ? body.description.trim() : undefined;
|
|
32593
|
+
const parent_id = typeof body.parent_id === "string" ? body.parent_id.trim() : undefined;
|
|
32594
|
+
const project_id = typeof body.project_id === "string" ? body.project_id.trim() : undefined;
|
|
32595
|
+
if (!name || !createdBy) {
|
|
32596
|
+
return jsonResponse({ error: "name and created_by are required" }, 400);
|
|
32597
|
+
}
|
|
32598
|
+
const sp = createSpace(name, createdBy, { description, parent_id, project_id });
|
|
32599
|
+
return jsonResponse(sp);
|
|
32600
|
+
} catch (e) {
|
|
32601
|
+
return jsonResponse({ error: e.message }, 400);
|
|
32602
|
+
}
|
|
32603
|
+
}
|
|
32604
|
+
const spaceArchiveMatch = path.match(/^\/api\/spaces\/([^/]+)\/archive$/);
|
|
32605
|
+
if (spaceArchiveMatch && req.method === "POST") {
|
|
32606
|
+
if (!isSameOrigin(req)) {
|
|
32607
|
+
return jsonResponse({ error: "Invalid origin" }, 403);
|
|
32608
|
+
}
|
|
32609
|
+
try {
|
|
32610
|
+
const sp = archiveSpace(decodeURIComponent(spaceArchiveMatch[1]));
|
|
32611
|
+
return jsonResponse(sp);
|
|
32612
|
+
} catch (e) {
|
|
32613
|
+
return jsonResponse({ error: e.message }, 400);
|
|
32614
|
+
}
|
|
32615
|
+
}
|
|
32616
|
+
const spaceUnarchiveMatch = path.match(/^\/api\/spaces\/([^/]+)\/unarchive$/);
|
|
32617
|
+
if (spaceUnarchiveMatch && req.method === "POST") {
|
|
32618
|
+
if (!isSameOrigin(req)) {
|
|
32619
|
+
return jsonResponse({ error: "Invalid origin" }, 403);
|
|
32620
|
+
}
|
|
32621
|
+
try {
|
|
32622
|
+
const sp = unarchiveSpace(decodeURIComponent(spaceUnarchiveMatch[1]));
|
|
32623
|
+
return jsonResponse(sp);
|
|
32624
|
+
} catch (e) {
|
|
32625
|
+
return jsonResponse({ error: e.message }, 400);
|
|
32626
|
+
}
|
|
32627
|
+
}
|
|
32628
|
+
const spaceMatch = path.match(/^\/api\/spaces\/([^/]+)$/);
|
|
32629
|
+
if (spaceMatch) {
|
|
32630
|
+
const spaceName = decodeURIComponent(spaceMatch[1]);
|
|
32631
|
+
if (req.method === "GET") {
|
|
32632
|
+
const sp = getSpace(spaceName);
|
|
32633
|
+
if (!sp)
|
|
32634
|
+
return jsonResponse({ error: "Space not found" }, 404);
|
|
32635
|
+
return jsonResponse(sp);
|
|
32636
|
+
}
|
|
32637
|
+
if (req.method === "PUT") {
|
|
32638
|
+
if (!isSameOrigin(req)) {
|
|
32639
|
+
return jsonResponse({ error: "Invalid origin" }, 403);
|
|
32640
|
+
}
|
|
32641
|
+
try {
|
|
32642
|
+
const text = await req.text();
|
|
32643
|
+
const body = JSON.parse(text);
|
|
32644
|
+
const updates = {};
|
|
32645
|
+
if (body.description !== undefined)
|
|
32646
|
+
updates.description = body.description;
|
|
32647
|
+
if (body.parent_id !== undefined)
|
|
32648
|
+
updates.parent_id = body.parent_id;
|
|
32649
|
+
if (body.project_id !== undefined)
|
|
32650
|
+
updates.project_id = body.project_id;
|
|
32651
|
+
const sp = updateSpace(spaceName, updates);
|
|
32652
|
+
return jsonResponse(sp);
|
|
32653
|
+
} catch (e) {
|
|
32654
|
+
return jsonResponse({ error: e.message }, 400);
|
|
32655
|
+
}
|
|
32656
|
+
}
|
|
32657
|
+
}
|
|
32658
|
+
if (path === "/api/projects" && req.method === "GET") {
|
|
32659
|
+
const status = url2.searchParams.get("status");
|
|
32660
|
+
return jsonResponse(listProjects(status ? { status } : undefined));
|
|
31299
32661
|
}
|
|
31300
|
-
if (path === "/api/
|
|
32662
|
+
if (path === "/api/projects" && req.method === "POST") {
|
|
32663
|
+
if (!isSameOrigin(req)) {
|
|
32664
|
+
return jsonResponse({ error: "Invalid origin" }, 403);
|
|
32665
|
+
}
|
|
31301
32666
|
try {
|
|
31302
32667
|
const text = await req.text();
|
|
31303
32668
|
const body = JSON.parse(text);
|
|
31304
|
-
const
|
|
31305
|
-
|
|
32669
|
+
const name = typeof body.name === "string" ? body.name.trim() : "";
|
|
32670
|
+
const createdBy = typeof body.created_by === "string" ? body.created_by.trim() : "";
|
|
32671
|
+
if (!name || !createdBy) {
|
|
32672
|
+
return jsonResponse({ error: "name and created_by are required" }, 400);
|
|
32673
|
+
}
|
|
32674
|
+
const project = createProject({
|
|
32675
|
+
name,
|
|
32676
|
+
created_by: createdBy,
|
|
32677
|
+
description: body.description,
|
|
32678
|
+
path: body.path,
|
|
32679
|
+
repository: body.repository,
|
|
32680
|
+
tags: body.tags,
|
|
32681
|
+
metadata: body.metadata,
|
|
32682
|
+
settings: body.settings
|
|
32683
|
+
});
|
|
32684
|
+
return jsonResponse(project);
|
|
31306
32685
|
} catch (e) {
|
|
31307
32686
|
return jsonResponse({ error: e.message }, 400);
|
|
31308
32687
|
}
|
|
31309
32688
|
}
|
|
32689
|
+
const projectMatch = path.match(/^\/api\/projects\/([^/]+)$/);
|
|
32690
|
+
if (projectMatch) {
|
|
32691
|
+
const projectId = projectMatch[1];
|
|
32692
|
+
if (req.method === "GET") {
|
|
32693
|
+
let project = getProject(projectId);
|
|
32694
|
+
if (!project)
|
|
32695
|
+
project = getProjectByName(projectId);
|
|
32696
|
+
if (!project)
|
|
32697
|
+
return jsonResponse({ error: "Project not found" }, 404);
|
|
32698
|
+
return jsonResponse(project);
|
|
32699
|
+
}
|
|
32700
|
+
if (req.method === "PUT") {
|
|
32701
|
+
if (!isSameOrigin(req)) {
|
|
32702
|
+
return jsonResponse({ error: "Invalid origin" }, 403);
|
|
32703
|
+
}
|
|
32704
|
+
try {
|
|
32705
|
+
const text = await req.text();
|
|
32706
|
+
const body = JSON.parse(text);
|
|
32707
|
+
const project = updateProject(projectId, body);
|
|
32708
|
+
return jsonResponse(project);
|
|
32709
|
+
} catch (e) {
|
|
32710
|
+
return jsonResponse({ error: e.message }, 400);
|
|
32711
|
+
}
|
|
32712
|
+
}
|
|
32713
|
+
if (req.method === "DELETE") {
|
|
32714
|
+
if (!isSameOrigin(req)) {
|
|
32715
|
+
return jsonResponse({ error: "Invalid origin" }, 403);
|
|
32716
|
+
}
|
|
32717
|
+
try {
|
|
32718
|
+
const deleted = deleteProject(projectId);
|
|
32719
|
+
if (!deleted)
|
|
32720
|
+
return jsonResponse({ error: "Project not found" }, 404);
|
|
32721
|
+
return jsonResponse({ id: projectId, deleted: true });
|
|
32722
|
+
} catch (e) {
|
|
32723
|
+
return jsonResponse({ error: e.message }, 400);
|
|
32724
|
+
}
|
|
32725
|
+
}
|
|
32726
|
+
}
|
|
32727
|
+
if (path === "/api/agents" && req.method === "GET") {
|
|
32728
|
+
const onlineOnly = url2.searchParams.get("online_only") === "true";
|
|
32729
|
+
const agents = listAgents({ online_only: onlineOnly });
|
|
32730
|
+
return jsonResponse(agents);
|
|
32731
|
+
}
|
|
32732
|
+
if (path === "/api/version" && req.method === "GET") {
|
|
32733
|
+
try {
|
|
32734
|
+
const pkg = await Promise.resolve().then(() => __toESM(require_package(), 1));
|
|
32735
|
+
const current = pkg.version;
|
|
32736
|
+
const res = await fetch("https://registry.npmjs.org/@hasna/conversations/latest");
|
|
32737
|
+
const data = await res.json();
|
|
32738
|
+
const latest = data.version;
|
|
32739
|
+
return jsonResponse({ current, latest, updateAvailable: current !== latest });
|
|
32740
|
+
} catch (e) {
|
|
32741
|
+
return jsonResponse({ error: e.message }, 500);
|
|
32742
|
+
}
|
|
32743
|
+
}
|
|
32744
|
+
if (path === "/api/update" && req.method === "POST") {
|
|
32745
|
+
if (!isSameOrigin(req)) {
|
|
32746
|
+
return jsonResponse({ error: "Invalid origin" }, 403);
|
|
32747
|
+
}
|
|
32748
|
+
try {
|
|
32749
|
+
const pkg = await Promise.resolve().then(() => __toESM(require_package(), 1));
|
|
32750
|
+
const current = pkg.version;
|
|
32751
|
+
const res = await fetch("https://registry.npmjs.org/@hasna/conversations/latest");
|
|
32752
|
+
const data = await res.json();
|
|
32753
|
+
const latest = data.version;
|
|
32754
|
+
if (current === latest) {
|
|
32755
|
+
return jsonResponse({ current, latest, status: "up-to-date" });
|
|
32756
|
+
}
|
|
32757
|
+
const proc = Bun.spawn(["bun", "install", "-g", `@hasna/conversations@${latest}`], {
|
|
32758
|
+
stdout: "pipe",
|
|
32759
|
+
stderr: "pipe"
|
|
32760
|
+
});
|
|
32761
|
+
const exitCode = await proc.exited;
|
|
32762
|
+
const stdout = await new Response(proc.stdout).text();
|
|
32763
|
+
const stderr = await new Response(proc.stderr).text();
|
|
32764
|
+
if (exitCode === 0) {
|
|
32765
|
+
return jsonResponse({ current, latest, status: "updated", stdout });
|
|
32766
|
+
} else {
|
|
32767
|
+
return jsonResponse({ current, latest, status: "failed", exitCode, stderr }, 500);
|
|
32768
|
+
}
|
|
32769
|
+
} catch (e) {
|
|
32770
|
+
return jsonResponse({ error: e.message }, 500);
|
|
32771
|
+
}
|
|
32772
|
+
}
|
|
31310
32773
|
if (hasDist) {
|
|
31311
|
-
|
|
32774
|
+
const baseDir = resolve(dashboardDist);
|
|
32775
|
+
const safePath = path === "/" ? "index.html" : path.replace(/^\/+/, "");
|
|
32776
|
+
const filePath = resolve(baseDir, safePath);
|
|
32777
|
+
if (!filePath.startsWith(baseDir + sep)) {
|
|
32778
|
+
return new Response("Not Found", { status: 404 });
|
|
32779
|
+
}
|
|
31312
32780
|
let file2 = Bun.file(filePath);
|
|
31313
|
-
if (await file2.exists())
|
|
31314
|
-
|
|
32781
|
+
if (await file2.exists()) {
|
|
32782
|
+
const headers = securityHeaders();
|
|
32783
|
+
if (file2.type)
|
|
32784
|
+
headers.set("Content-Type", file2.type);
|
|
32785
|
+
return new Response(file2, { headers });
|
|
32786
|
+
}
|
|
31315
32787
|
file2 = Bun.file(join2(dashboardDist, "index.html"));
|
|
31316
|
-
if (await file2.exists())
|
|
31317
|
-
|
|
32788
|
+
if (await file2.exists()) {
|
|
32789
|
+
const headers = securityHeaders();
|
|
32790
|
+
if (file2.type)
|
|
32791
|
+
headers.set("Content-Type", file2.type);
|
|
32792
|
+
return new Response(file2, { headers });
|
|
32793
|
+
}
|
|
31318
32794
|
}
|
|
31319
|
-
return new Response("Not Found", {
|
|
32795
|
+
return new Response("Not Found", {
|
|
32796
|
+
status: 404,
|
|
32797
|
+
headers: securityHeaders({ "Content-Type": "text/plain; charset=utf-8" })
|
|
32798
|
+
});
|
|
31320
32799
|
}
|
|
31321
32800
|
});
|
|
31322
32801
|
console.log(`Dashboard running at http://localhost:${server2.port}`);
|
|
@@ -31326,11 +32805,13 @@ var isDirectRun2;
|
|
|
31326
32805
|
var init_serve = __esm(() => {
|
|
31327
32806
|
init_messages();
|
|
31328
32807
|
init_sessions();
|
|
31329
|
-
|
|
32808
|
+
init_spaces();
|
|
32809
|
+
init_projects();
|
|
31330
32810
|
init_db();
|
|
32811
|
+
init_presence();
|
|
31331
32812
|
isDirectRun2 = import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith("serve.ts") || process.argv[1]?.endsWith("serve.js");
|
|
31332
32813
|
if (isDirectRun2) {
|
|
31333
|
-
const port =
|
|
32814
|
+
const port = normalizePort(process.env.PORT, 3456);
|
|
31334
32815
|
startDashboardServer(port);
|
|
31335
32816
|
}
|
|
31336
32817
|
});
|
|
@@ -31354,8 +32835,10 @@ var {
|
|
|
31354
32835
|
// src/cli/index.tsx
|
|
31355
32836
|
init_messages();
|
|
31356
32837
|
init_sessions();
|
|
31357
|
-
|
|
32838
|
+
init_spaces();
|
|
32839
|
+
init_projects();
|
|
31358
32840
|
init_db();
|
|
32841
|
+
init_presence();
|
|
31359
32842
|
import chalk2 from "chalk";
|
|
31360
32843
|
import { render } from "ink";
|
|
31361
32844
|
import React8 from "react";
|
|
@@ -31865,15 +33348,21 @@ function SelectInput({ items = [], isFocused = true, initialIndex = 0, indicator
|
|
|
31865
33348
|
var SelectInput_default = SelectInput;
|
|
31866
33349
|
// src/cli/components/SessionList.tsx
|
|
31867
33350
|
init_sessions();
|
|
31868
|
-
|
|
33351
|
+
init_spaces();
|
|
33352
|
+
init_db();
|
|
31869
33353
|
import { jsxDEV } from "react/jsx-dev-runtime";
|
|
31870
|
-
function
|
|
33354
|
+
function getSpaceUnreadCount(spaceName, agent) {
|
|
33355
|
+
const db2 = getDb();
|
|
33356
|
+
const row = db2.prepare("SELECT COUNT(*) as count FROM messages WHERE space = ? AND from_agent != ? AND read_at IS NULL").get(spaceName, agent);
|
|
33357
|
+
return row.count;
|
|
33358
|
+
}
|
|
33359
|
+
function SessionList({ agent, onSelect, onSelectSpace, onNew }) {
|
|
31871
33360
|
const [sessions, setSessions] = useState3(() => listSessions(agent));
|
|
31872
|
-
const [
|
|
33361
|
+
const [spaces, setSpaces] = useState3(() => listSpaces());
|
|
31873
33362
|
useEffect3(() => {
|
|
31874
33363
|
const timer = setInterval(() => {
|
|
31875
33364
|
setSessions(listSessions(agent));
|
|
31876
|
-
|
|
33365
|
+
setSpaces(listSpaces());
|
|
31877
33366
|
}, 1000);
|
|
31878
33367
|
return () => clearInterval(timer);
|
|
31879
33368
|
}, [agent]);
|
|
@@ -31881,11 +33370,36 @@ function SessionList({ agent, onSelect, onSelectChannel, onNew }) {
|
|
|
31881
33370
|
if (input === "n")
|
|
31882
33371
|
onNew();
|
|
31883
33372
|
});
|
|
31884
|
-
const
|
|
31885
|
-
|
|
31886
|
-
|
|
31887
|
-
|
|
31888
|
-
|
|
33373
|
+
const topLevel = spaces.filter((sp) => !sp.parent_id);
|
|
33374
|
+
const children = spaces.filter((sp) => sp.parent_id);
|
|
33375
|
+
const spaceItems = [];
|
|
33376
|
+
for (const sp of topLevel) {
|
|
33377
|
+
const unread = getSpaceUnreadCount(sp.name, agent);
|
|
33378
|
+
const unreadBadge = unread > 0 ? ` (${unread} unread)` : "";
|
|
33379
|
+
spaceItems.push({
|
|
33380
|
+
label: `#${sp.name}${sp.description ? ` \u2014 ${sp.description}` : ""} ${sp.message_count} msgs${unreadBadge}`,
|
|
33381
|
+
value: `space:${sp.name}`
|
|
33382
|
+
});
|
|
33383
|
+
const directChildren = children.filter((c) => c.parent_id === sp.name);
|
|
33384
|
+
for (const child of directChildren) {
|
|
33385
|
+
const childUnread = getSpaceUnreadCount(child.name, agent);
|
|
33386
|
+
const childBadge = childUnread > 0 ? ` (${childUnread} unread)` : "";
|
|
33387
|
+
spaceItems.push({
|
|
33388
|
+
label: ` \u2514 #${child.name}${child.description ? ` \u2014 ${child.description}` : ""} ${child.message_count} msgs${childBadge}`,
|
|
33389
|
+
value: `space:${child.name}`
|
|
33390
|
+
});
|
|
33391
|
+
const grandChildren = children.filter((gc) => gc.parent_id === child.name);
|
|
33392
|
+
for (const gc of grandChildren) {
|
|
33393
|
+
const gcUnread = getSpaceUnreadCount(gc.name, agent);
|
|
33394
|
+
const gcBadge = gcUnread > 0 ? ` (${gcUnread} unread)` : "";
|
|
33395
|
+
spaceItems.push({
|
|
33396
|
+
label: ` \u2514 #${gc.name}${gc.description ? ` \u2014 ${gc.description}` : ""} ${gc.message_count} msgs${gcBadge}`,
|
|
33397
|
+
value: `space:${gc.name}`
|
|
33398
|
+
});
|
|
33399
|
+
}
|
|
33400
|
+
}
|
|
33401
|
+
}
|
|
33402
|
+
const dmSessions = sessions.filter((s) => !s.session_id.startsWith("space:"));
|
|
31889
33403
|
const sessionItems = dmSessions.map((s) => {
|
|
31890
33404
|
const others = s.participants.filter((p) => p !== agent).join(", ") || agent;
|
|
31891
33405
|
const unread = s.unread_count > 0 ? ` (${s.unread_count} unread)` : "";
|
|
@@ -31894,7 +33408,7 @@ function SessionList({ agent, onSelect, onSelectChannel, onNew }) {
|
|
|
31894
33408
|
value: s.session_id
|
|
31895
33409
|
};
|
|
31896
33410
|
});
|
|
31897
|
-
const allItems = [...
|
|
33411
|
+
const allItems = [...spaceItems, ...sessionItems];
|
|
31898
33412
|
if (allItems.length === 0) {
|
|
31899
33413
|
return /* @__PURE__ */ jsxDEV(Box3, {
|
|
31900
33414
|
flexDirection: "column",
|
|
@@ -31972,8 +33486,8 @@ function SessionList({ agent, onSelect, onSelectChannel, onNew }) {
|
|
|
31972
33486
|
/* @__PURE__ */ jsxDEV(SelectInput_default, {
|
|
31973
33487
|
items: allItems,
|
|
31974
33488
|
onSelect: (item) => {
|
|
31975
|
-
if (item.value.startsWith("
|
|
31976
|
-
|
|
33489
|
+
if (item.value.startsWith("space:")) {
|
|
33490
|
+
onSelectSpace(item.value.slice(6));
|
|
31977
33491
|
} else {
|
|
31978
33492
|
const session = dmSessions.find((s) => s.session_id === item.value);
|
|
31979
33493
|
if (session)
|
|
@@ -31986,31 +33500,55 @@ function SessionList({ agent, onSelect, onSelectChannel, onNew }) {
|
|
|
31986
33500
|
}
|
|
31987
33501
|
|
|
31988
33502
|
// src/cli/components/ChatView.tsx
|
|
31989
|
-
import { useState as useState5, useEffect as useEffect5 } from "react";
|
|
33503
|
+
import { useState as useState5, useEffect as useEffect5, useRef as useRef2 } from "react";
|
|
31990
33504
|
import { Box as Box5, Text as Text6, useInput as useInput4 } from "ink";
|
|
31991
33505
|
init_messages();
|
|
31992
33506
|
|
|
31993
33507
|
// src/lib/poll.ts
|
|
31994
33508
|
init_messages();
|
|
31995
|
-
import { useState as useState4, useEffect as useEffect4
|
|
33509
|
+
import { useState as useState4, useEffect as useEffect4 } from "react";
|
|
31996
33510
|
function startPolling(opts) {
|
|
31997
33511
|
const interval = opts.interval_ms ?? 200;
|
|
31998
|
-
let lastSeen = new Date().toISOString();
|
|
31999
33512
|
let stopped = false;
|
|
32000
|
-
|
|
32001
|
-
|
|
32002
|
-
|
|
32003
|
-
const
|
|
33513
|
+
let inFlight = false;
|
|
33514
|
+
let lastSeenId = 0;
|
|
33515
|
+
const seedLastSeen = () => {
|
|
33516
|
+
const latest = readMessages({
|
|
32004
33517
|
session_id: opts.session_id,
|
|
32005
33518
|
to: opts.to_agent,
|
|
32006
|
-
|
|
32007
|
-
|
|
33519
|
+
space: opts.space,
|
|
33520
|
+
order: "desc",
|
|
33521
|
+
limit: 1
|
|
32008
33522
|
});
|
|
32009
|
-
if (
|
|
32010
|
-
|
|
32011
|
-
|
|
33523
|
+
if (latest.length > 0) {
|
|
33524
|
+
lastSeenId = latest[0].id;
|
|
33525
|
+
}
|
|
33526
|
+
};
|
|
33527
|
+
const poll = () => {
|
|
33528
|
+
if (stopped || inFlight)
|
|
33529
|
+
return;
|
|
33530
|
+
inFlight = true;
|
|
33531
|
+
try {
|
|
33532
|
+
const messages = readMessages({
|
|
33533
|
+
session_id: opts.session_id,
|
|
33534
|
+
to: opts.to_agent,
|
|
33535
|
+
space: opts.space,
|
|
33536
|
+
since_id: lastSeenId,
|
|
33537
|
+
order: "asc"
|
|
33538
|
+
});
|
|
33539
|
+
if (messages.length > 0) {
|
|
33540
|
+
lastSeenId = messages[messages.length - 1].id;
|
|
33541
|
+
try {
|
|
33542
|
+
opts.on_messages(messages);
|
|
33543
|
+
} catch (error) {
|
|
33544
|
+
console.error("Polling callback error:", error);
|
|
33545
|
+
}
|
|
33546
|
+
}
|
|
33547
|
+
} finally {
|
|
33548
|
+
inFlight = false;
|
|
32012
33549
|
}
|
|
32013
33550
|
};
|
|
33551
|
+
seedLastSeen();
|
|
32014
33552
|
const timer = setInterval(poll, interval);
|
|
32015
33553
|
return {
|
|
32016
33554
|
stop: () => {
|
|
@@ -32059,38 +33597,51 @@ function MessageBubble({ message, isOwn }) {
|
|
|
32059
33597
|
|
|
32060
33598
|
// src/cli/components/ChatView.tsx
|
|
32061
33599
|
import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
|
|
32062
|
-
function ChatView({ agent, onBack, sessionId: initialSessionId, recipient,
|
|
33600
|
+
function ChatView({ agent, onBack, sessionId: initialSessionId, recipient, spaceName }) {
|
|
32063
33601
|
const [messages, setMessages] = useState5([]);
|
|
32064
33602
|
const [input, setInput] = useState5("");
|
|
32065
33603
|
const [sessionId, setSessionId] = useState5(initialSessionId);
|
|
32066
|
-
const
|
|
33604
|
+
const isSpace = !!spaceName;
|
|
33605
|
+
const seenIds = useRef2(new Set);
|
|
32067
33606
|
useEffect5(() => {
|
|
32068
|
-
|
|
32069
|
-
|
|
33607
|
+
seenIds.current = new Set;
|
|
33608
|
+
const opts = isSpace ? { space: spaceName } : sessionId ? { session_id: sessionId } : {};
|
|
33609
|
+
if (isSpace || sessionId) {
|
|
32070
33610
|
const existing = readMessages(opts);
|
|
33611
|
+
for (const msg of existing) {
|
|
33612
|
+
seenIds.current.add(msg.id);
|
|
33613
|
+
}
|
|
32071
33614
|
setMessages(existing);
|
|
33615
|
+
} else {
|
|
33616
|
+
setMessages([]);
|
|
32072
33617
|
}
|
|
32073
|
-
const pollOpts =
|
|
33618
|
+
const pollOpts = isSpace ? { space: spaceName } : sessionId ? { session_id: sessionId } : null;
|
|
32074
33619
|
if (!pollOpts)
|
|
32075
33620
|
return;
|
|
32076
33621
|
const { stop } = startPolling({
|
|
32077
33622
|
...pollOpts,
|
|
32078
33623
|
interval_ms: 200,
|
|
32079
33624
|
on_messages: (newMsgs) => {
|
|
32080
|
-
|
|
33625
|
+
const unseen = newMsgs.filter((msg) => !seenIds.current.has(msg.id));
|
|
33626
|
+
if (unseen.length === 0)
|
|
33627
|
+
return;
|
|
33628
|
+
for (const msg of unseen) {
|
|
33629
|
+
seenIds.current.add(msg.id);
|
|
33630
|
+
}
|
|
33631
|
+
setMessages((prev) => [...prev, ...unseen]);
|
|
32081
33632
|
}
|
|
32082
33633
|
});
|
|
32083
33634
|
return stop;
|
|
32084
|
-
}, [sessionId,
|
|
33635
|
+
}, [sessionId, spaceName]);
|
|
32085
33636
|
useEffect5(() => {
|
|
32086
33637
|
if (messages.length === 0)
|
|
32087
33638
|
return;
|
|
32088
|
-
if (
|
|
32089
|
-
|
|
33639
|
+
if (isSpace && spaceName) {
|
|
33640
|
+
markSpaceRead(spaceName, agent);
|
|
32090
33641
|
} else if (sessionId) {
|
|
32091
33642
|
markSessionRead(sessionId, agent);
|
|
32092
33643
|
}
|
|
32093
|
-
}, [messages.length]);
|
|
33644
|
+
}, [messages.length, isSpace, spaceName, sessionId, agent]);
|
|
32094
33645
|
useInput4((_, key) => {
|
|
32095
33646
|
if (key.escape)
|
|
32096
33647
|
onBack();
|
|
@@ -32098,14 +33649,15 @@ function ChatView({ agent, onBack, sessionId: initialSessionId, recipient, chann
|
|
|
32098
33649
|
const handleSubmit = (value) => {
|
|
32099
33650
|
if (!value.trim())
|
|
32100
33651
|
return;
|
|
32101
|
-
if (
|
|
33652
|
+
if (isSpace && spaceName) {
|
|
32102
33653
|
const msg = sendMessage({
|
|
32103
33654
|
from: agent,
|
|
32104
|
-
to:
|
|
33655
|
+
to: spaceName,
|
|
32105
33656
|
content: value.trim(),
|
|
32106
|
-
|
|
32107
|
-
session_id: `
|
|
33657
|
+
space: spaceName,
|
|
33658
|
+
session_id: `space:${spaceName}`
|
|
32108
33659
|
});
|
|
33660
|
+
seenIds.current.add(msg.id);
|
|
32109
33661
|
setMessages((prev) => [...prev, msg]);
|
|
32110
33662
|
} else {
|
|
32111
33663
|
const to = recipient || agent;
|
|
@@ -32115,14 +33667,16 @@ function ChatView({ agent, onBack, sessionId: initialSessionId, recipient, chann
|
|
|
32115
33667
|
content: value.trim(),
|
|
32116
33668
|
session_id: sessionId
|
|
32117
33669
|
});
|
|
33670
|
+
seenIds.current.add(msg.id);
|
|
33671
|
+
setMessages((prev) => [...prev, msg]);
|
|
32118
33672
|
if (!sessionId) {
|
|
32119
33673
|
setSessionId(msg.session_id);
|
|
32120
33674
|
}
|
|
32121
33675
|
}
|
|
32122
33676
|
setInput("");
|
|
32123
33677
|
};
|
|
32124
|
-
const title =
|
|
32125
|
-
const prompt =
|
|
33678
|
+
const title = isSpace ? `#${spaceName}` : recipient || "self";
|
|
33679
|
+
const prompt = isSpace ? `${agent} \u2192 #${spaceName}` : `${agent} \u2192 ${recipient || "self"}`;
|
|
32126
33680
|
return /* @__PURE__ */ jsxDEV3(Box5, {
|
|
32127
33681
|
flexDirection: "column",
|
|
32128
33682
|
padding: 1,
|
|
@@ -32132,7 +33686,7 @@ function ChatView({ agent, onBack, sessionId: initialSessionId, recipient, chann
|
|
|
32132
33686
|
children: [
|
|
32133
33687
|
/* @__PURE__ */ jsxDEV3(Text6, {
|
|
32134
33688
|
bold: true,
|
|
32135
|
-
color:
|
|
33689
|
+
color: isSpace ? "magenta" : "cyan",
|
|
32136
33690
|
children: title
|
|
32137
33691
|
}, undefined, false, undefined, this),
|
|
32138
33692
|
/* @__PURE__ */ jsxDEV3(Text6, {
|
|
@@ -32156,7 +33710,7 @@ function ChatView({ agent, onBack, sessionId: initialSessionId, recipient, chann
|
|
|
32156
33710
|
marginTop: 1,
|
|
32157
33711
|
children: [
|
|
32158
33712
|
/* @__PURE__ */ jsxDEV3(Text6, {
|
|
32159
|
-
color:
|
|
33713
|
+
color: isSpace ? "magenta" : "cyan",
|
|
32160
33714
|
children: [
|
|
32161
33715
|
prompt,
|
|
32162
33716
|
": "
|
|
@@ -32180,7 +33734,7 @@ function App({ agent }) {
|
|
|
32180
33734
|
const { exit } = useApp();
|
|
32181
33735
|
const [view, setView] = useState6("sessions");
|
|
32182
33736
|
const [currentSession, setCurrentSession] = useState6(null);
|
|
32183
|
-
const [
|
|
33737
|
+
const [currentSpace, setCurrentSpace] = useState6(null);
|
|
32184
33738
|
const [newTo, setNewTo] = useState6("");
|
|
32185
33739
|
useInput5((input, key) => {
|
|
32186
33740
|
if (input === "q" && view === "sessions") {
|
|
@@ -32195,9 +33749,9 @@ function App({ agent }) {
|
|
|
32195
33749
|
setCurrentSession(session);
|
|
32196
33750
|
setView("chat");
|
|
32197
33751
|
};
|
|
32198
|
-
const
|
|
32199
|
-
|
|
32200
|
-
setView("
|
|
33752
|
+
const handleSelectSpace = (spaceName) => {
|
|
33753
|
+
setCurrentSpace(spaceName);
|
|
33754
|
+
setView("space");
|
|
32201
33755
|
};
|
|
32202
33756
|
const handleNewConversation = () => {
|
|
32203
33757
|
setView("new");
|
|
@@ -32217,7 +33771,7 @@ function App({ agent }) {
|
|
|
32217
33771
|
};
|
|
32218
33772
|
const handleBack = () => {
|
|
32219
33773
|
setCurrentSession(null);
|
|
32220
|
-
|
|
33774
|
+
setCurrentSpace(null);
|
|
32221
33775
|
setView("sessions");
|
|
32222
33776
|
};
|
|
32223
33777
|
if (view === "new") {
|
|
@@ -32251,10 +33805,10 @@ function App({ agent }) {
|
|
|
32251
33805
|
]
|
|
32252
33806
|
}, undefined, true, undefined, this);
|
|
32253
33807
|
}
|
|
32254
|
-
if (view === "
|
|
33808
|
+
if (view === "space" && currentSpace) {
|
|
32255
33809
|
return /* @__PURE__ */ jsxDEV4(ChatView, {
|
|
32256
33810
|
agent,
|
|
32257
|
-
|
|
33811
|
+
spaceName: currentSpace,
|
|
32258
33812
|
onBack: handleBack
|
|
32259
33813
|
}, undefined, false, undefined, this);
|
|
32260
33814
|
}
|
|
@@ -32270,22 +33824,45 @@ function App({ agent }) {
|
|
|
32270
33824
|
return /* @__PURE__ */ jsxDEV4(SessionList, {
|
|
32271
33825
|
agent,
|
|
32272
33826
|
onSelect: handleSelectSession,
|
|
32273
|
-
|
|
33827
|
+
onSelectSpace: handleSelectSpace,
|
|
32274
33828
|
onNew: handleNewConversation
|
|
32275
33829
|
}, undefined, false, undefined, this);
|
|
32276
33830
|
}
|
|
32277
33831
|
|
|
32278
33832
|
// src/cli/index.tsx
|
|
32279
33833
|
var program2 = new Command;
|
|
32280
|
-
program2.name("conversations").description("Real-time CLI messaging for AI agents").version("0.0
|
|
33834
|
+
program2.name("conversations").description("Real-time CLI messaging for AI agents").version("0.1.0");
|
|
32281
33835
|
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) => {
|
|
32282
|
-
const from = resolveIdentity(opts.from);
|
|
32283
|
-
const
|
|
33836
|
+
const from = resolveIdentity(opts.from).trim();
|
|
33837
|
+
const to = typeof opts.to === "string" ? opts.to.trim() : "";
|
|
33838
|
+
const content = typeof message === "string" ? message : "";
|
|
33839
|
+
const session = typeof opts.session === "string" && opts.session.trim() ? opts.session.trim() : undefined;
|
|
33840
|
+
if (!from) {
|
|
33841
|
+
console.error(chalk2.red("Sender identity is required."));
|
|
33842
|
+
process.exit(1);
|
|
33843
|
+
}
|
|
33844
|
+
if (!to) {
|
|
33845
|
+
console.error(chalk2.red("Recipient is required."));
|
|
33846
|
+
process.exit(1);
|
|
33847
|
+
}
|
|
33848
|
+
if (!content.trim()) {
|
|
33849
|
+
console.error(chalk2.red("Message content cannot be empty."));
|
|
33850
|
+
process.exit(1);
|
|
33851
|
+
}
|
|
33852
|
+
let metadata;
|
|
33853
|
+
if (opts.metadata) {
|
|
33854
|
+
try {
|
|
33855
|
+
metadata = JSON.parse(opts.metadata);
|
|
33856
|
+
} catch {
|
|
33857
|
+
console.error(chalk2.red("Invalid --metadata JSON."));
|
|
33858
|
+
process.exit(1);
|
|
33859
|
+
}
|
|
33860
|
+
}
|
|
32284
33861
|
const msg = sendMessage({
|
|
32285
33862
|
from,
|
|
32286
|
-
to
|
|
32287
|
-
content
|
|
32288
|
-
session_id:
|
|
33863
|
+
to,
|
|
33864
|
+
content,
|
|
33865
|
+
session_id: session,
|
|
32289
33866
|
priority: opts.priority,
|
|
32290
33867
|
working_dir: opts.workingDir,
|
|
32291
33868
|
repository: opts.repository,
|
|
@@ -32299,31 +33876,71 @@ program2.command("send").description("Send a message to an agent").argument("<me
|
|
|
32299
33876
|
}
|
|
32300
33877
|
closeDb();
|
|
32301
33878
|
});
|
|
32302
|
-
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("--
|
|
33879
|
+
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("--space <name>", "Filter by space").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) => {
|
|
32303
33880
|
const messages = readMessages({
|
|
32304
33881
|
session_id: opts.session,
|
|
32305
33882
|
from: opts.from,
|
|
32306
33883
|
to: opts.to,
|
|
32307
|
-
|
|
33884
|
+
space: opts.space,
|
|
32308
33885
|
since: opts.since,
|
|
32309
33886
|
limit: opts.limit,
|
|
32310
33887
|
unread_only: opts.unread
|
|
32311
33888
|
});
|
|
32312
|
-
if (opts.markRead
|
|
32313
|
-
const
|
|
32314
|
-
if (
|
|
32315
|
-
|
|
33889
|
+
if (opts.markRead) {
|
|
33890
|
+
const reader = resolveIdentity(opts.to);
|
|
33891
|
+
if (opts.space) {
|
|
33892
|
+
markSpaceRead(opts.space, reader);
|
|
33893
|
+
} else if (opts.session) {
|
|
33894
|
+
markSessionRead(opts.session, reader);
|
|
33895
|
+
} else {
|
|
33896
|
+
const ids = messages.filter((m) => m.to_agent === reader && !m.read_at).map((m) => m.id);
|
|
33897
|
+
if (ids.length > 0)
|
|
33898
|
+
markRead(ids, reader);
|
|
33899
|
+
}
|
|
33900
|
+
}
|
|
33901
|
+
if (opts.json) {
|
|
33902
|
+
console.log(JSON.stringify(messages, null, 2));
|
|
33903
|
+
} else {
|
|
33904
|
+
if (messages.length === 0) {
|
|
33905
|
+
console.log(chalk2.dim("No messages found."));
|
|
33906
|
+
} else {
|
|
33907
|
+
for (const msg of messages) {
|
|
33908
|
+
const time3 = chalk2.dim(msg.created_at.slice(11, 19));
|
|
33909
|
+
const from = chalk2.cyan(msg.from_agent);
|
|
33910
|
+
const to = msg.space ? chalk2.magenta(`#${msg.space}`) : chalk2.yellow(msg.to_agent);
|
|
33911
|
+
const priority = msg.priority !== "normal" ? chalk2.red(` [${msg.priority}]`) : "";
|
|
33912
|
+
const unread = !msg.read_at ? chalk2.green(" *") : "";
|
|
33913
|
+
console.log(`${time3} ${from} \u2192 ${to}${priority}${unread}: ${msg.content}`);
|
|
33914
|
+
}
|
|
33915
|
+
}
|
|
32316
33916
|
}
|
|
33917
|
+
closeDb();
|
|
33918
|
+
});
|
|
33919
|
+
program2.command("search").description("Search messages by content").argument("<query>", "Search query string").option("--space <name>", "Filter by space").option("--from <agent>", "Filter by sender").option("--to <agent>", "Filter by recipient").option("--limit <n>", "Max results to return", parseInt).option("--json", "Output as JSON").action((query, opts) => {
|
|
33920
|
+
const q = typeof query === "string" ? query.trim() : "";
|
|
33921
|
+
if (!q) {
|
|
33922
|
+
console.error(chalk2.red("Search query cannot be empty."));
|
|
33923
|
+
process.exit(1);
|
|
33924
|
+
}
|
|
33925
|
+
const messages = searchMessages({
|
|
33926
|
+
query: q,
|
|
33927
|
+
space: opts.space,
|
|
33928
|
+
from: opts.from,
|
|
33929
|
+
to: opts.to,
|
|
33930
|
+
limit: opts.limit
|
|
33931
|
+
});
|
|
32317
33932
|
if (opts.json) {
|
|
32318
33933
|
console.log(JSON.stringify(messages, null, 2));
|
|
32319
33934
|
} else {
|
|
32320
33935
|
if (messages.length === 0) {
|
|
32321
33936
|
console.log(chalk2.dim("No messages found."));
|
|
32322
33937
|
} else {
|
|
33938
|
+
console.log(chalk2.dim(`Found ${messages.length} result(s) for "${q}":
|
|
33939
|
+
`));
|
|
32323
33940
|
for (const msg of messages) {
|
|
32324
33941
|
const time3 = chalk2.dim(msg.created_at.slice(11, 19));
|
|
32325
33942
|
const from = chalk2.cyan(msg.from_agent);
|
|
32326
|
-
const to = msg.
|
|
33943
|
+
const to = msg.space ? chalk2.magenta(`#${msg.space}`) : chalk2.yellow(msg.to_agent);
|
|
32327
33944
|
const priority = msg.priority !== "normal" ? chalk2.red(` [${msg.priority}]`) : "";
|
|
32328
33945
|
const unread = !msg.read_at ? chalk2.green(" *") : "";
|
|
32329
33946
|
console.log(`${time3} ${from} \u2192 ${to}${priority}${unread}: ${msg.content}`);
|
|
@@ -32355,13 +33972,25 @@ program2.command("reply").description("Reply to a message (uses same session)").
|
|
|
32355
33972
|
console.error(chalk2.red(`Message #${opts.to} not found.`));
|
|
32356
33973
|
process.exit(1);
|
|
32357
33974
|
}
|
|
32358
|
-
const from = resolveIdentity(opts.from);
|
|
33975
|
+
const from = resolveIdentity(opts.from).trim();
|
|
33976
|
+
const content = typeof message === "string" ? message : "";
|
|
33977
|
+
if (!from) {
|
|
33978
|
+
console.error(chalk2.red("Sender identity is required."));
|
|
33979
|
+
process.exit(1);
|
|
33980
|
+
}
|
|
33981
|
+
if (!content.trim()) {
|
|
33982
|
+
console.error(chalk2.red("Reply content cannot be empty."));
|
|
33983
|
+
process.exit(1);
|
|
33984
|
+
}
|
|
33985
|
+
const space = original.space || (original.session_id?.startsWith("space:") ? original.session_id.slice(6) : undefined);
|
|
33986
|
+
const to = space ? space : original.from_agent === from ? original.to_agent : original.from_agent;
|
|
32359
33987
|
const msg = sendMessage({
|
|
32360
33988
|
from,
|
|
32361
|
-
to
|
|
32362
|
-
content
|
|
33989
|
+
to,
|
|
33990
|
+
content,
|
|
32363
33991
|
session_id: original.session_id,
|
|
32364
|
-
priority: opts.priority
|
|
33992
|
+
priority: opts.priority,
|
|
33993
|
+
space
|
|
32365
33994
|
});
|
|
32366
33995
|
if (opts.json) {
|
|
32367
33996
|
console.log(JSON.stringify(msg, null, 2));
|
|
@@ -32370,17 +33999,19 @@ program2.command("reply").description("Reply to a message (uses same session)").
|
|
|
32370
33999
|
}
|
|
32371
34000
|
closeDb();
|
|
32372
34001
|
});
|
|
32373
|
-
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("--
|
|
34002
|
+
program2.command("mark-read").description("Mark messages as read").argument("[ids...]", "Message IDs to mark as read").option("--all", "Mark all messages as read").option("--session <id>", "Mark all messages in session as read").option("--space <name>", "Mark all messages in space as read").option("--agent <id>", "Agent marking messages as read").option("--json", "Output as JSON").action((ids, opts) => {
|
|
32374
34003
|
const agent = resolveIdentity(opts.agent);
|
|
32375
34004
|
let count = 0;
|
|
32376
|
-
if (opts.
|
|
34005
|
+
if (opts.all) {
|
|
34006
|
+
count = markAllRead(agent);
|
|
34007
|
+
} else if (opts.session) {
|
|
32377
34008
|
count = markSessionRead(opts.session, agent);
|
|
32378
|
-
} else if (opts.
|
|
32379
|
-
count =
|
|
34009
|
+
} else if (opts.space) {
|
|
34010
|
+
count = markSpaceRead(opts.space, agent);
|
|
32380
34011
|
} else if (ids.length > 0) {
|
|
32381
34012
|
count = markRead(ids.map(Number), agent);
|
|
32382
34013
|
} else {
|
|
32383
|
-
console.error(chalk2.red("Provide message IDs, --session, or --
|
|
34014
|
+
console.error(chalk2.red("Provide message IDs, --all, --session, or --space flag."));
|
|
32384
34015
|
process.exit(1);
|
|
32385
34016
|
}
|
|
32386
34017
|
if (opts.json) {
|
|
@@ -32390,18 +34021,33 @@ program2.command("mark-read").description("Mark messages as read").argument("[id
|
|
|
32390
34021
|
}
|
|
32391
34022
|
closeDb();
|
|
32392
34023
|
});
|
|
34024
|
+
program2.command("export").description("Export messages as JSON or CSV").option("--space <name>", "Filter by space").option("--session <id>", "Filter by session ID").option("--from <agent>", "Filter by sender").option("--since <date>", "Messages after this ISO date").option("--until <date>", "Messages before this ISO date").option("--format <format>", "Output format: json or csv", "json").action((opts) => {
|
|
34025
|
+
const format = opts.format === "csv" ? "csv" : "json";
|
|
34026
|
+
const result = exportMessages({
|
|
34027
|
+
space: opts.space,
|
|
34028
|
+
session_id: opts.session,
|
|
34029
|
+
from: opts.from,
|
|
34030
|
+
since: opts.since,
|
|
34031
|
+
until: opts.until,
|
|
34032
|
+
format
|
|
34033
|
+
});
|
|
34034
|
+
console.log(result);
|
|
34035
|
+
closeDb();
|
|
34036
|
+
});
|
|
32393
34037
|
program2.command("status").description("Show database stats").option("--json", "Output as JSON").action((opts) => {
|
|
32394
34038
|
const db2 = getDb();
|
|
32395
34039
|
const dbPath = getDbPath();
|
|
32396
34040
|
const totalMessages = db2.prepare("SELECT COUNT(*) as count FROM messages").get().count;
|
|
32397
34041
|
const totalSessions = db2.prepare("SELECT COUNT(DISTINCT session_id) as count FROM messages").get().count;
|
|
32398
34042
|
const totalUnread = db2.prepare("SELECT COUNT(*) as count FROM messages WHERE read_at IS NULL").get().count;
|
|
32399
|
-
const
|
|
34043
|
+
const totalSpaces = db2.prepare("SELECT COUNT(*) as count FROM spaces").get().count;
|
|
34044
|
+
const totalProjects = db2.prepare("SELECT COUNT(*) as count FROM projects").get().count;
|
|
32400
34045
|
const stats = {
|
|
32401
34046
|
db_path: dbPath,
|
|
32402
34047
|
total_messages: totalMessages,
|
|
32403
34048
|
total_sessions: totalSessions,
|
|
32404
|
-
|
|
34049
|
+
total_spaces: totalSpaces,
|
|
34050
|
+
total_projects: totalProjects,
|
|
32405
34051
|
unread_messages: totalUnread
|
|
32406
34052
|
};
|
|
32407
34053
|
if (opts.json) {
|
|
@@ -32411,71 +34057,236 @@ program2.command("status").description("Show database stats").option("--json", "
|
|
|
32411
34057
|
console.log(` DB Path: ${stats.db_path}`);
|
|
32412
34058
|
console.log(` Messages: ${stats.total_messages}`);
|
|
32413
34059
|
console.log(` Sessions: ${stats.total_sessions}`);
|
|
32414
|
-
console.log(`
|
|
34060
|
+
console.log(` Spaces: ${stats.total_spaces}`);
|
|
34061
|
+
console.log(` Projects: ${stats.total_projects}`);
|
|
32415
34062
|
console.log(` Unread: ${stats.unread_messages}`);
|
|
32416
34063
|
}
|
|
32417
34064
|
closeDb();
|
|
32418
34065
|
});
|
|
32419
|
-
|
|
32420
|
-
|
|
32421
|
-
const
|
|
34066
|
+
program2.command("update").description("Check for and install updates").option("--check", "Only check for updates, don't install").option("--json", "Output as JSON").action(async (opts) => {
|
|
34067
|
+
const pkg = await Promise.resolve().then(() => __toESM(require_package(), 1));
|
|
34068
|
+
const current = pkg.version;
|
|
34069
|
+
let latest;
|
|
34070
|
+
try {
|
|
34071
|
+
const res = await fetch("https://registry.npmjs.org/@hasna/conversations/latest");
|
|
34072
|
+
const data = await res.json();
|
|
34073
|
+
latest = data.version;
|
|
34074
|
+
} catch {
|
|
34075
|
+
if (opts.json) {
|
|
34076
|
+
console.log(JSON.stringify({ error: "Failed to check npm registry" }));
|
|
34077
|
+
} else {
|
|
34078
|
+
console.error(chalk2.red("Failed to check npm registry for updates."));
|
|
34079
|
+
}
|
|
34080
|
+
process.exit(1);
|
|
34081
|
+
}
|
|
34082
|
+
const updateAvailable = current !== latest;
|
|
34083
|
+
if (opts.check || !updateAvailable) {
|
|
34084
|
+
if (opts.json) {
|
|
34085
|
+
console.log(JSON.stringify({ current, latest, updateAvailable }));
|
|
34086
|
+
} else if (updateAvailable) {
|
|
34087
|
+
console.log(`Current version: ${chalk2.yellow(current)}`);
|
|
34088
|
+
console.log(`Latest version: ${chalk2.green(latest)}`);
|
|
34089
|
+
console.log(chalk2.cyan(`Run ${chalk2.bold("conversations update")} to install.`));
|
|
34090
|
+
} else {
|
|
34091
|
+
console.log(chalk2.green(`Already on latest version (${current})`));
|
|
34092
|
+
}
|
|
34093
|
+
return;
|
|
34094
|
+
}
|
|
34095
|
+
if (opts.json) {
|
|
34096
|
+
console.log(JSON.stringify({ current, latest, updateAvailable, status: "updating" }));
|
|
34097
|
+
} else {
|
|
34098
|
+
console.log(`Updating from ${chalk2.yellow(current)} to ${chalk2.green(latest)}...`);
|
|
34099
|
+
}
|
|
34100
|
+
const proc = Bun.spawn(["bun", "install", "-g", `@hasna/conversations@${latest}`], {
|
|
34101
|
+
stdout: "inherit",
|
|
34102
|
+
stderr: "inherit"
|
|
34103
|
+
});
|
|
34104
|
+
const exitCode = await proc.exited;
|
|
34105
|
+
if (exitCode === 0) {
|
|
34106
|
+
if (!opts.json) {
|
|
34107
|
+
console.log(chalk2.green(`
|
|
34108
|
+
Successfully updated to v${latest}`));
|
|
34109
|
+
}
|
|
34110
|
+
} else {
|
|
34111
|
+
if (opts.json) {
|
|
34112
|
+
console.log(JSON.stringify({ error: "Update failed", exitCode }));
|
|
34113
|
+
} else {
|
|
34114
|
+
console.error(chalk2.red(`
|
|
34115
|
+
Update failed (exit code ${exitCode})`));
|
|
34116
|
+
}
|
|
34117
|
+
process.exit(1);
|
|
34118
|
+
}
|
|
34119
|
+
});
|
|
34120
|
+
var space = program2.command("space").description("Manage spaces");
|
|
34121
|
+
space.command("create").description("Create a new space").argument("<name>", "Space name").option("--description <text>", "Space description").option("--parent <name>", "Parent space name (for nesting)").option("--project <id>", "Project ID to associate with").option("--from <agent>", "Creator agent ID").option("--json", "Output as JSON").action((name, opts) => {
|
|
34122
|
+
const agent = resolveIdentity(opts.from).trim();
|
|
34123
|
+
const spaceName = typeof name === "string" ? name.trim() : "";
|
|
34124
|
+
if (!agent) {
|
|
34125
|
+
console.error(chalk2.red("Creator identity is required."));
|
|
34126
|
+
process.exit(1);
|
|
34127
|
+
}
|
|
34128
|
+
if (!spaceName) {
|
|
34129
|
+
console.error(chalk2.red("Space name cannot be empty."));
|
|
34130
|
+
process.exit(1);
|
|
34131
|
+
}
|
|
32422
34132
|
try {
|
|
32423
|
-
const
|
|
34133
|
+
const description = typeof opts.description === "string" && opts.description.trim() ? opts.description.trim() : undefined;
|
|
34134
|
+
const sp = createSpace(spaceName, agent, {
|
|
34135
|
+
description,
|
|
34136
|
+
parent_id: opts.parent,
|
|
34137
|
+
project_id: opts.project
|
|
34138
|
+
});
|
|
32424
34139
|
if (opts.json) {
|
|
32425
|
-
console.log(JSON.stringify(
|
|
34140
|
+
console.log(JSON.stringify(sp, null, 2));
|
|
32426
34141
|
} else {
|
|
32427
|
-
console.log(chalk2.green(`
|
|
34142
|
+
console.log(chalk2.green(`Space #${sp.name} created`) + (sp.description ? chalk2.dim(` \u2014 ${sp.description}`) : ""));
|
|
32428
34143
|
}
|
|
32429
34144
|
} catch (e) {
|
|
32430
34145
|
if (e.message?.includes("UNIQUE constraint")) {
|
|
32431
|
-
console.error(chalk2.red(`
|
|
34146
|
+
console.error(chalk2.red(`Space #${spaceName} already exists.`));
|
|
32432
34147
|
process.exit(1);
|
|
32433
34148
|
}
|
|
32434
|
-
|
|
34149
|
+
console.error(chalk2.red(e.message));
|
|
34150
|
+
process.exit(1);
|
|
32435
34151
|
}
|
|
32436
34152
|
closeDb();
|
|
32437
34153
|
});
|
|
32438
|
-
|
|
32439
|
-
const
|
|
34154
|
+
space.command("list").description("List all spaces").option("--project <id>", "Filter by project ID").option("--parent <name>", "Filter by parent space name").option("--top-level", "Show only top-level spaces").option("--archived", "Include archived spaces").option("--json", "Output as JSON").action((opts) => {
|
|
34155
|
+
const listOpts = {};
|
|
34156
|
+
if (opts.project)
|
|
34157
|
+
listOpts.project_id = opts.project;
|
|
34158
|
+
if (opts.topLevel) {
|
|
34159
|
+
listOpts.parent_id = null;
|
|
34160
|
+
} else if (opts.parent) {
|
|
34161
|
+
listOpts.parent_id = opts.parent;
|
|
34162
|
+
}
|
|
34163
|
+
if (opts.archived)
|
|
34164
|
+
listOpts.include_archived = true;
|
|
34165
|
+
const spaces = listSpaces(listOpts);
|
|
32440
34166
|
if (opts.json) {
|
|
32441
|
-
console.log(JSON.stringify(
|
|
34167
|
+
console.log(JSON.stringify(spaces, null, 2));
|
|
32442
34168
|
} else {
|
|
32443
|
-
if (
|
|
32444
|
-
console.log(chalk2.dim("No
|
|
34169
|
+
if (spaces.length === 0) {
|
|
34170
|
+
console.log(chalk2.dim("No spaces found."));
|
|
32445
34171
|
} else {
|
|
32446
|
-
for (const
|
|
32447
|
-
const desc =
|
|
32448
|
-
|
|
34172
|
+
for (const sp of spaces) {
|
|
34173
|
+
const desc = sp.description ? chalk2.dim(` \u2014 ${sp.description}`) : "";
|
|
34174
|
+
const parent = sp.parent_id ? chalk2.dim(` (child of ${sp.parent_id})`) : "";
|
|
34175
|
+
const archived = sp.archived_at ? chalk2.yellow(" [archived]") : "";
|
|
34176
|
+
console.log(`${chalk2.magenta(`#${sp.name}`)}${desc}${parent}${archived} ${sp.member_count} members, ${sp.message_count} messages`);
|
|
32449
34177
|
}
|
|
32450
34178
|
}
|
|
32451
34179
|
}
|
|
32452
34180
|
closeDb();
|
|
32453
34181
|
});
|
|
32454
|
-
|
|
32455
|
-
const
|
|
32456
|
-
|
|
32457
|
-
|
|
32458
|
-
|
|
34182
|
+
space.command("update").description("Update a space").argument("<name>", "Space name").option("--description <text>", "New description").option("--parent <name>", "New parent space name").option("--project <id>", "New project ID").option("--json", "Output as JSON").action((name, opts) => {
|
|
34183
|
+
const spaceName = typeof name === "string" ? name.trim() : "";
|
|
34184
|
+
if (!spaceName) {
|
|
34185
|
+
console.error(chalk2.red("Space name cannot be empty."));
|
|
34186
|
+
process.exit(1);
|
|
34187
|
+
}
|
|
34188
|
+
const updates = {};
|
|
34189
|
+
if (opts.description !== undefined)
|
|
34190
|
+
updates.description = opts.description;
|
|
34191
|
+
if (opts.parent !== undefined)
|
|
34192
|
+
updates.parent_id = opts.parent || null;
|
|
34193
|
+
if (opts.project !== undefined)
|
|
34194
|
+
updates.project_id = opts.project || null;
|
|
34195
|
+
try {
|
|
34196
|
+
const sp = updateSpace(spaceName, updates);
|
|
34197
|
+
if (opts.json) {
|
|
34198
|
+
console.log(JSON.stringify(sp, null, 2));
|
|
34199
|
+
} else {
|
|
34200
|
+
console.log(chalk2.green(`Space #${sp.name} updated.`));
|
|
34201
|
+
}
|
|
34202
|
+
} catch (e) {
|
|
34203
|
+
console.error(chalk2.red(e.message));
|
|
34204
|
+
process.exit(1);
|
|
34205
|
+
}
|
|
34206
|
+
closeDb();
|
|
34207
|
+
});
|
|
34208
|
+
space.command("archive").description("Archive a space").argument("<name>", "Space name").option("--json", "Output as JSON").action((name, opts) => {
|
|
34209
|
+
const spaceName = typeof name === "string" ? name.trim() : "";
|
|
34210
|
+
if (!spaceName) {
|
|
34211
|
+
console.error(chalk2.red("Space name cannot be empty."));
|
|
34212
|
+
process.exit(1);
|
|
34213
|
+
}
|
|
34214
|
+
try {
|
|
34215
|
+
const sp = archiveSpace(spaceName);
|
|
34216
|
+
if (opts.json) {
|
|
34217
|
+
console.log(JSON.stringify(sp, null, 2));
|
|
34218
|
+
} else {
|
|
34219
|
+
console.log(chalk2.green(`Space #${sp.name} archived.`));
|
|
34220
|
+
}
|
|
34221
|
+
} catch (e) {
|
|
34222
|
+
console.error(chalk2.red(e.message));
|
|
34223
|
+
process.exit(1);
|
|
34224
|
+
}
|
|
34225
|
+
closeDb();
|
|
34226
|
+
});
|
|
34227
|
+
space.command("unarchive").description("Unarchive a space").argument("<name>", "Space name").option("--json", "Output as JSON").action((name, opts) => {
|
|
34228
|
+
const spaceName = typeof name === "string" ? name.trim() : "";
|
|
34229
|
+
if (!spaceName) {
|
|
34230
|
+
console.error(chalk2.red("Space name cannot be empty."));
|
|
34231
|
+
process.exit(1);
|
|
34232
|
+
}
|
|
34233
|
+
try {
|
|
34234
|
+
const sp = unarchiveSpace(spaceName);
|
|
34235
|
+
if (opts.json) {
|
|
34236
|
+
console.log(JSON.stringify(sp, null, 2));
|
|
34237
|
+
} else {
|
|
34238
|
+
console.log(chalk2.green(`Space #${sp.name} unarchived.`));
|
|
34239
|
+
}
|
|
34240
|
+
} catch (e) {
|
|
34241
|
+
console.error(chalk2.red(e.message));
|
|
34242
|
+
process.exit(1);
|
|
34243
|
+
}
|
|
34244
|
+
closeDb();
|
|
34245
|
+
});
|
|
34246
|
+
space.command("send").description("Send a message to a space").argument("<space>", "Space 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((spaceName, message, opts) => {
|
|
34247
|
+
const from = resolveIdentity(opts.from).trim();
|
|
34248
|
+
const spaceArg = typeof spaceName === "string" ? spaceName.trim() : "";
|
|
34249
|
+
const content = typeof message === "string" ? message : "";
|
|
34250
|
+
if (!from) {
|
|
34251
|
+
console.error(chalk2.red("Sender identity is required."));
|
|
34252
|
+
process.exit(1);
|
|
34253
|
+
}
|
|
34254
|
+
if (!spaceArg) {
|
|
34255
|
+
console.error(chalk2.red("Space name cannot be empty."));
|
|
34256
|
+
process.exit(1);
|
|
34257
|
+
}
|
|
34258
|
+
if (!content.trim()) {
|
|
34259
|
+
console.error(chalk2.red("Message content cannot be empty."));
|
|
34260
|
+
process.exit(1);
|
|
34261
|
+
}
|
|
34262
|
+
const sp = getSpace(spaceArg);
|
|
34263
|
+
if (!sp) {
|
|
34264
|
+
console.error(chalk2.red(`Space #${spaceArg} not found.`));
|
|
32459
34265
|
process.exit(1);
|
|
32460
34266
|
}
|
|
32461
34267
|
const msg = sendMessage({
|
|
32462
34268
|
from,
|
|
32463
|
-
to:
|
|
32464
|
-
content
|
|
32465
|
-
|
|
32466
|
-
session_id: `
|
|
34269
|
+
to: spaceArg,
|
|
34270
|
+
content,
|
|
34271
|
+
space: spaceArg,
|
|
34272
|
+
session_id: `space:${spaceArg}`,
|
|
32467
34273
|
priority: opts.priority
|
|
32468
34274
|
});
|
|
32469
34275
|
if (opts.json) {
|
|
32470
34276
|
console.log(JSON.stringify(msg, null, 2));
|
|
32471
34277
|
} else {
|
|
32472
|
-
console.log(chalk2.green(`Message sent to #${
|
|
34278
|
+
console.log(chalk2.green(`Message sent to #${spaceArg}`) + chalk2.dim(` (id: ${msg.id})`));
|
|
32473
34279
|
}
|
|
32474
34280
|
closeDb();
|
|
32475
34281
|
});
|
|
32476
|
-
|
|
34282
|
+
space.command("read").description("Read messages from a space").argument("<space>", "Space name").option("--since <timestamp>", "Messages after this ISO timestamp").option("--limit <n>", "Max messages to return", parseInt).option("--json", "Output as JSON").action((spaceName, opts) => {
|
|
34283
|
+
const spaceArg = typeof spaceName === "string" ? spaceName.trim() : "";
|
|
34284
|
+
if (!spaceArg) {
|
|
34285
|
+
console.error(chalk2.red("Space name cannot be empty."));
|
|
34286
|
+
process.exit(1);
|
|
34287
|
+
}
|
|
32477
34288
|
const messages = readMessages({
|
|
32478
|
-
|
|
34289
|
+
space: spaceArg,
|
|
32479
34290
|
since: opts.since,
|
|
32480
34291
|
limit: opts.limit
|
|
32481
34292
|
});
|
|
@@ -32483,55 +34294,78 @@ channel.command("read").description("Read messages from a channel").argument("<c
|
|
|
32483
34294
|
console.log(JSON.stringify(messages, null, 2));
|
|
32484
34295
|
} else {
|
|
32485
34296
|
if (messages.length === 0) {
|
|
32486
|
-
console.log(chalk2.dim(`No messages in #${
|
|
34297
|
+
console.log(chalk2.dim(`No messages in #${spaceArg}.`));
|
|
32487
34298
|
} else {
|
|
32488
34299
|
for (const msg of messages) {
|
|
32489
34300
|
const time3 = chalk2.dim(msg.created_at.slice(11, 19));
|
|
32490
34301
|
const from = chalk2.cyan(msg.from_agent);
|
|
32491
34302
|
const priority = msg.priority !== "normal" ? chalk2.red(` [${msg.priority}]`) : "";
|
|
32492
|
-
console.log(`${time3} ${from} \u2192 ${chalk2.magenta(`#${
|
|
34303
|
+
console.log(`${time3} ${from} \u2192 ${chalk2.magenta(`#${spaceArg}`)}${priority}: ${msg.content}`);
|
|
32493
34304
|
}
|
|
32494
34305
|
}
|
|
32495
34306
|
}
|
|
32496
34307
|
closeDb();
|
|
32497
34308
|
});
|
|
32498
|
-
|
|
32499
|
-
const agent = resolveIdentity(opts.from);
|
|
32500
|
-
const
|
|
34309
|
+
space.command("join").description("Join a space").argument("<space>", "Space name").option("--from <agent>", "Agent ID").option("--json", "Output as JSON").action((spaceName, opts) => {
|
|
34310
|
+
const agent = resolveIdentity(opts.from).trim();
|
|
34311
|
+
const spaceArg = typeof spaceName === "string" ? spaceName.trim() : "";
|
|
34312
|
+
if (!agent) {
|
|
34313
|
+
console.error(chalk2.red("Agent identity is required."));
|
|
34314
|
+
process.exit(1);
|
|
34315
|
+
}
|
|
34316
|
+
if (!spaceArg) {
|
|
34317
|
+
console.error(chalk2.red("Space name cannot be empty."));
|
|
34318
|
+
process.exit(1);
|
|
34319
|
+
}
|
|
34320
|
+
const ok = joinSpace(spaceArg, agent);
|
|
32501
34321
|
if (!ok) {
|
|
32502
|
-
console.error(chalk2.red(`
|
|
34322
|
+
console.error(chalk2.red(`Space #${spaceArg} not found.`));
|
|
32503
34323
|
process.exit(1);
|
|
32504
34324
|
}
|
|
32505
34325
|
if (opts.json) {
|
|
32506
|
-
console.log(JSON.stringify({
|
|
34326
|
+
console.log(JSON.stringify({ space: spaceArg, agent, joined: true }));
|
|
32507
34327
|
} else {
|
|
32508
|
-
console.log(chalk2.green(`${agent} joined #${
|
|
34328
|
+
console.log(chalk2.green(`${agent} joined #${spaceArg}`));
|
|
32509
34329
|
}
|
|
32510
34330
|
closeDb();
|
|
32511
34331
|
});
|
|
32512
|
-
|
|
32513
|
-
const agent = resolveIdentity(opts.from);
|
|
32514
|
-
const
|
|
34332
|
+
space.command("leave").description("Leave a space").argument("<space>", "Space name").option("--from <agent>", "Agent ID").option("--json", "Output as JSON").action((spaceName, opts) => {
|
|
34333
|
+
const agent = resolveIdentity(opts.from).trim();
|
|
34334
|
+
const spaceArg = typeof spaceName === "string" ? spaceName.trim() : "";
|
|
34335
|
+
if (!agent) {
|
|
34336
|
+
console.error(chalk2.red("Agent identity is required."));
|
|
34337
|
+
process.exit(1);
|
|
34338
|
+
}
|
|
34339
|
+
if (!spaceArg) {
|
|
34340
|
+
console.error(chalk2.red("Space name cannot be empty."));
|
|
34341
|
+
process.exit(1);
|
|
34342
|
+
}
|
|
34343
|
+
const ok = leaveSpace(spaceArg, agent);
|
|
32515
34344
|
if (opts.json) {
|
|
32516
|
-
console.log(JSON.stringify({
|
|
34345
|
+
console.log(JSON.stringify({ space: spaceArg, agent, left: ok }));
|
|
32517
34346
|
} else {
|
|
32518
34347
|
if (ok) {
|
|
32519
|
-
console.log(chalk2.green(`${agent} left #${
|
|
34348
|
+
console.log(chalk2.green(`${agent} left #${spaceArg}`));
|
|
32520
34349
|
} else {
|
|
32521
|
-
console.log(chalk2.dim(`${agent} was not a member of #${
|
|
34350
|
+
console.log(chalk2.dim(`${agent} was not a member of #${spaceArg}`));
|
|
32522
34351
|
}
|
|
32523
34352
|
}
|
|
32524
34353
|
closeDb();
|
|
32525
34354
|
});
|
|
32526
|
-
|
|
32527
|
-
const
|
|
34355
|
+
space.command("members").description("List space members").argument("<space>", "Space name").option("--json", "Output as JSON").action((spaceName, opts) => {
|
|
34356
|
+
const spaceArg = typeof spaceName === "string" ? spaceName.trim() : "";
|
|
34357
|
+
if (!spaceArg) {
|
|
34358
|
+
console.error(chalk2.red("Space name cannot be empty."));
|
|
34359
|
+
process.exit(1);
|
|
34360
|
+
}
|
|
34361
|
+
const members = getSpaceMembers(spaceArg);
|
|
32528
34362
|
if (opts.json) {
|
|
32529
34363
|
console.log(JSON.stringify(members, null, 2));
|
|
32530
34364
|
} else {
|
|
32531
34365
|
if (members.length === 0) {
|
|
32532
|
-
console.log(chalk2.dim(`No members in #${
|
|
34366
|
+
console.log(chalk2.dim(`No members in #${spaceArg}.`));
|
|
32533
34367
|
} else {
|
|
32534
|
-
console.log(chalk2.magenta(`#${
|
|
34368
|
+
console.log(chalk2.magenta(`#${spaceArg}`) + chalk2.dim(` \u2014 ${members.length} member(s)`));
|
|
32535
34369
|
for (const m of members) {
|
|
32536
34370
|
console.log(` ${chalk2.cyan(m.agent)} ${chalk2.dim(`joined ${m.joined_at.slice(0, 10)}`)}`);
|
|
32537
34371
|
}
|
|
@@ -32539,13 +34373,245 @@ channel.command("members").description("List channel members").argument("<channe
|
|
|
32539
34373
|
}
|
|
32540
34374
|
closeDb();
|
|
32541
34375
|
});
|
|
34376
|
+
var project = program2.command("project").description("Manage projects");
|
|
34377
|
+
project.command("create").description("Create a new project").argument("<name>", "Project name").option("--description <text>", "Project description").option("--path <path>", "Project path on disk").option("--repository <url>", "Repository URL").option("--tags <json>", "JSON array of tags").option("--from <agent>", "Creator agent ID").option("--json", "Output as JSON").action((name, opts) => {
|
|
34378
|
+
const agent = resolveIdentity(opts.from).trim();
|
|
34379
|
+
const projectName = typeof name === "string" ? name.trim() : "";
|
|
34380
|
+
if (!agent) {
|
|
34381
|
+
console.error(chalk2.red("Creator identity is required."));
|
|
34382
|
+
process.exit(1);
|
|
34383
|
+
}
|
|
34384
|
+
if (!projectName) {
|
|
34385
|
+
console.error(chalk2.red("Project name cannot be empty."));
|
|
34386
|
+
process.exit(1);
|
|
34387
|
+
}
|
|
34388
|
+
let tags;
|
|
34389
|
+
if (opts.tags) {
|
|
34390
|
+
try {
|
|
34391
|
+
tags = JSON.parse(opts.tags);
|
|
34392
|
+
} catch {
|
|
34393
|
+
console.error(chalk2.red("Invalid --tags JSON. Expected array of strings."));
|
|
34394
|
+
process.exit(1);
|
|
34395
|
+
}
|
|
34396
|
+
}
|
|
34397
|
+
try {
|
|
34398
|
+
const p = createProject({
|
|
34399
|
+
name: projectName,
|
|
34400
|
+
created_by: agent,
|
|
34401
|
+
description: opts.description,
|
|
34402
|
+
path: opts.path,
|
|
34403
|
+
repository: opts.repository,
|
|
34404
|
+
tags
|
|
34405
|
+
});
|
|
34406
|
+
if (opts.json) {
|
|
34407
|
+
console.log(JSON.stringify(p, null, 2));
|
|
34408
|
+
} else {
|
|
34409
|
+
console.log(chalk2.green(`Project "${p.name}" created`) + chalk2.dim(` (id: ${p.id})`));
|
|
34410
|
+
}
|
|
34411
|
+
} catch (e) {
|
|
34412
|
+
if (e.message?.includes("UNIQUE constraint")) {
|
|
34413
|
+
console.error(chalk2.red(`Project "${projectName}" already exists.`));
|
|
34414
|
+
process.exit(1);
|
|
34415
|
+
}
|
|
34416
|
+
console.error(chalk2.red(e.message));
|
|
34417
|
+
process.exit(1);
|
|
34418
|
+
}
|
|
34419
|
+
closeDb();
|
|
34420
|
+
});
|
|
34421
|
+
project.command("list").description("List all projects").option("--status <status>", "Filter by status (active/archived)").option("--json", "Output as JSON").action((opts) => {
|
|
34422
|
+
const status = opts.status === "active" || opts.status === "archived" ? opts.status : undefined;
|
|
34423
|
+
const projects = listProjects(status ? { status } : undefined);
|
|
34424
|
+
if (opts.json) {
|
|
34425
|
+
console.log(JSON.stringify(projects, null, 2));
|
|
34426
|
+
} else {
|
|
34427
|
+
if (projects.length === 0) {
|
|
34428
|
+
console.log(chalk2.dim("No projects found."));
|
|
34429
|
+
} else {
|
|
34430
|
+
for (const p of projects) {
|
|
34431
|
+
const desc = p.description ? chalk2.dim(` \u2014 ${p.description}`) : "";
|
|
34432
|
+
const statusBadge = p.status === "archived" ? chalk2.yellow(" [archived]") : "";
|
|
34433
|
+
console.log(`${chalk2.bold(p.name)}${desc}${statusBadge} ${p.space_count} spaces`);
|
|
34434
|
+
}
|
|
34435
|
+
}
|
|
34436
|
+
}
|
|
34437
|
+
closeDb();
|
|
34438
|
+
});
|
|
34439
|
+
project.command("get").description("Get project details").argument("<id-or-name>", "Project ID or name").option("--json", "Output as JSON").action((idOrName, opts) => {
|
|
34440
|
+
let p = getProject(idOrName);
|
|
34441
|
+
if (!p)
|
|
34442
|
+
p = getProjectByName(idOrName);
|
|
34443
|
+
if (!p) {
|
|
34444
|
+
console.error(chalk2.red(`Project "${idOrName}" not found.`));
|
|
34445
|
+
process.exit(1);
|
|
34446
|
+
}
|
|
34447
|
+
if (opts.json) {
|
|
34448
|
+
console.log(JSON.stringify(p, null, 2));
|
|
34449
|
+
} else {
|
|
34450
|
+
console.log(chalk2.bold(p.name));
|
|
34451
|
+
if (p.description)
|
|
34452
|
+
console.log(` Description: ${p.description}`);
|
|
34453
|
+
if (p.path)
|
|
34454
|
+
console.log(` Path: ${p.path}`);
|
|
34455
|
+
if (p.repository)
|
|
34456
|
+
console.log(` Repository: ${p.repository}`);
|
|
34457
|
+
console.log(` Status: ${p.status}`);
|
|
34458
|
+
console.log(` Spaces: ${p.space_count}`);
|
|
34459
|
+
if (p.tags.length > 0)
|
|
34460
|
+
console.log(` Tags: ${p.tags.join(", ")}`);
|
|
34461
|
+
console.log(` Created by: ${p.created_by} on ${p.created_at.slice(0, 10)}`);
|
|
34462
|
+
}
|
|
34463
|
+
closeDb();
|
|
34464
|
+
});
|
|
34465
|
+
project.command("update").description("Update a project").argument("<id>", "Project ID").option("--name <name>", "New name").option("--description <text>", "New description").option("--path <path>", "New path").option("--status <status>", "New status (active/archived)").option("--repository <url>", "New repository URL").option("--tags <json>", "New tags (JSON array)").option("--json", "Output as JSON").action((id, opts) => {
|
|
34466
|
+
const updates = {};
|
|
34467
|
+
if (opts.name)
|
|
34468
|
+
updates.name = opts.name;
|
|
34469
|
+
if (opts.description)
|
|
34470
|
+
updates.description = opts.description;
|
|
34471
|
+
if (opts.path)
|
|
34472
|
+
updates.path = opts.path;
|
|
34473
|
+
if (opts.status)
|
|
34474
|
+
updates.status = opts.status;
|
|
34475
|
+
if (opts.repository)
|
|
34476
|
+
updates.repository = opts.repository;
|
|
34477
|
+
if (opts.tags) {
|
|
34478
|
+
try {
|
|
34479
|
+
updates.tags = JSON.parse(opts.tags);
|
|
34480
|
+
} catch {
|
|
34481
|
+
console.error(chalk2.red("Invalid --tags JSON."));
|
|
34482
|
+
process.exit(1);
|
|
34483
|
+
}
|
|
34484
|
+
}
|
|
34485
|
+
try {
|
|
34486
|
+
const p = updateProject(id, updates);
|
|
34487
|
+
if (opts.json) {
|
|
34488
|
+
console.log(JSON.stringify(p, null, 2));
|
|
34489
|
+
} else {
|
|
34490
|
+
console.log(chalk2.green(`Project "${p.name}" updated.`));
|
|
34491
|
+
}
|
|
34492
|
+
} catch (e) {
|
|
34493
|
+
console.error(chalk2.red(e.message));
|
|
34494
|
+
process.exit(1);
|
|
34495
|
+
}
|
|
34496
|
+
closeDb();
|
|
34497
|
+
});
|
|
34498
|
+
project.command("delete").description("Delete a project").argument("<id>", "Project ID").option("--json", "Output as JSON").action((id, opts) => {
|
|
34499
|
+
try {
|
|
34500
|
+
const deleted = deleteProject(id);
|
|
34501
|
+
if (!deleted) {
|
|
34502
|
+
console.error(chalk2.red(`Project "${id}" not found.`));
|
|
34503
|
+
process.exit(1);
|
|
34504
|
+
}
|
|
34505
|
+
if (opts.json) {
|
|
34506
|
+
console.log(JSON.stringify({ id, deleted: true }));
|
|
34507
|
+
} else {
|
|
34508
|
+
console.log(chalk2.green(`Project deleted.`));
|
|
34509
|
+
}
|
|
34510
|
+
} catch (e) {
|
|
34511
|
+
console.error(chalk2.red(e.message));
|
|
34512
|
+
process.exit(1);
|
|
34513
|
+
}
|
|
34514
|
+
closeDb();
|
|
34515
|
+
});
|
|
34516
|
+
program2.command("delete").description("Delete a message (only sender can delete)").argument("<id>", "Message ID", parseInt).option("--from <agent>", "Sender agent ID").option("--json", "Output as JSON").action((id, opts) => {
|
|
34517
|
+
const agent = resolveIdentity(opts.from).trim();
|
|
34518
|
+
if (!agent) {
|
|
34519
|
+
console.error(chalk2.red("Agent identity is required."));
|
|
34520
|
+
process.exit(1);
|
|
34521
|
+
}
|
|
34522
|
+
const result = deleteMessage(id, agent);
|
|
34523
|
+
if (opts.json) {
|
|
34524
|
+
console.log(JSON.stringify({ id, deleted: result }));
|
|
34525
|
+
} else {
|
|
34526
|
+
if (result) {
|
|
34527
|
+
console.log(chalk2.green(`Message #${id} deleted.`));
|
|
34528
|
+
} else {
|
|
34529
|
+
console.error(chalk2.red(`Message #${id} not found or not your message.`));
|
|
34530
|
+
process.exit(1);
|
|
34531
|
+
}
|
|
34532
|
+
}
|
|
34533
|
+
closeDb();
|
|
34534
|
+
});
|
|
34535
|
+
program2.command("edit").description("Edit a message (only sender can edit)").argument("<id>", "Message ID", parseInt).argument("<new-content>", "New message content").option("--from <agent>", "Sender agent ID").option("--json", "Output as JSON").action((id, newContent, opts) => {
|
|
34536
|
+
const agent = resolveIdentity(opts.from).trim();
|
|
34537
|
+
const content = typeof newContent === "string" ? newContent : "";
|
|
34538
|
+
if (!agent) {
|
|
34539
|
+
console.error(chalk2.red("Agent identity is required."));
|
|
34540
|
+
process.exit(1);
|
|
34541
|
+
}
|
|
34542
|
+
if (!content.trim()) {
|
|
34543
|
+
console.error(chalk2.red("New content cannot be empty."));
|
|
34544
|
+
process.exit(1);
|
|
34545
|
+
}
|
|
34546
|
+
const msg = editMessage(id, agent, content);
|
|
34547
|
+
if (opts.json) {
|
|
34548
|
+
console.log(JSON.stringify(msg, null, 2));
|
|
34549
|
+
} else {
|
|
34550
|
+
if (msg) {
|
|
34551
|
+
console.log(chalk2.green(`Message #${id} edited.`));
|
|
34552
|
+
} else {
|
|
34553
|
+
console.error(chalk2.red(`Message #${id} not found or not your message.`));
|
|
34554
|
+
process.exit(1);
|
|
34555
|
+
}
|
|
34556
|
+
}
|
|
34557
|
+
closeDb();
|
|
34558
|
+
});
|
|
34559
|
+
program2.command("pin").description("Pin a message").argument("<id>", "Message ID", parseInt).option("--json", "Output as JSON").action((id, opts) => {
|
|
34560
|
+
const msg = pinMessage(id);
|
|
34561
|
+
if (opts.json) {
|
|
34562
|
+
console.log(JSON.stringify(msg, null, 2));
|
|
34563
|
+
} else {
|
|
34564
|
+
if (msg) {
|
|
34565
|
+
console.log(chalk2.green(`Message #${id} pinned.`));
|
|
34566
|
+
} else {
|
|
34567
|
+
console.error(chalk2.red(`Message #${id} not found.`));
|
|
34568
|
+
process.exit(1);
|
|
34569
|
+
}
|
|
34570
|
+
}
|
|
34571
|
+
closeDb();
|
|
34572
|
+
});
|
|
34573
|
+
program2.command("unpin").description("Unpin a message").argument("<id>", "Message ID", parseInt).option("--json", "Output as JSON").action((id, opts) => {
|
|
34574
|
+
const msg = unpinMessage(id);
|
|
34575
|
+
if (opts.json) {
|
|
34576
|
+
console.log(JSON.stringify(msg, null, 2));
|
|
34577
|
+
} else {
|
|
34578
|
+
if (msg) {
|
|
34579
|
+
console.log(chalk2.green(`Message #${id} unpinned.`));
|
|
34580
|
+
} else {
|
|
34581
|
+
console.error(chalk2.red(`Message #${id} not found.`));
|
|
34582
|
+
process.exit(1);
|
|
34583
|
+
}
|
|
34584
|
+
}
|
|
34585
|
+
closeDb();
|
|
34586
|
+
});
|
|
34587
|
+
program2.command("agents").description("List all agents with their presence status").option("--online", "Only show online agents").option("--json", "Output as JSON").action((opts) => {
|
|
34588
|
+
const agent = resolveIdentity();
|
|
34589
|
+
heartbeat(agent);
|
|
34590
|
+
const agents = listAgents({ online_only: opts.online });
|
|
34591
|
+
if (opts.json) {
|
|
34592
|
+
console.log(JSON.stringify(agents, null, 2));
|
|
34593
|
+
} else {
|
|
34594
|
+
if (agents.length === 0) {
|
|
34595
|
+
console.log(chalk2.dim("No agents found."));
|
|
34596
|
+
} else {
|
|
34597
|
+
for (const a of agents) {
|
|
34598
|
+
const status = a.online ? chalk2.green("online") : chalk2.dim("offline");
|
|
34599
|
+
const lastSeen = chalk2.dim(a.last_seen_at.slice(0, 19));
|
|
34600
|
+
const agentName = a.agent === agent ? chalk2.cyan(`${a.agent} (you)`) : chalk2.cyan(a.agent);
|
|
34601
|
+
console.log(` ${agentName} ${status} ${chalk2.dim(a.status)} ${lastSeen}`);
|
|
34602
|
+
}
|
|
34603
|
+
}
|
|
34604
|
+
}
|
|
34605
|
+
closeDb();
|
|
34606
|
+
});
|
|
32542
34607
|
program2.command("mcp").description("Start MCP server").action(async () => {
|
|
32543
34608
|
const { startMcpServer: startMcpServer2 } = await Promise.resolve().then(() => (init_mcp2(), exports_mcp));
|
|
32544
34609
|
await startMcpServer2();
|
|
32545
34610
|
});
|
|
32546
|
-
program2.command("dashboard").description("Start web dashboard").option("--port <port>", "Port to listen on", parseInt).action(async (opts) => {
|
|
34611
|
+
program2.command("dashboard").description("Start web dashboard").option("--port <port>", "Port to listen on", parseInt).option("--host <host>", "Host to bind (default: 127.0.0.1)").action(async (opts) => {
|
|
32547
34612
|
const { startDashboardServer: startDashboardServer2 } = await Promise.resolve().then(() => (init_serve(), exports_serve));
|
|
32548
|
-
|
|
34613
|
+
const port = Number.isFinite(opts.port) && opts.port >= 0 && opts.port <= 65535 ? opts.port : 3456;
|
|
34614
|
+
startDashboardServer2(port, opts.host);
|
|
32549
34615
|
});
|
|
32550
34616
|
program2.action(() => {
|
|
32551
34617
|
const agent = resolveIdentity();
|