@hasna/conversations 0.2.7 → 0.2.9

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/bin/index.js CHANGED
@@ -2395,9 +2395,10 @@ function readMessages(opts = {}) {
2395
2395
  conditions.push(`id IN (SELECT message_id FROM message_mentions WHERE mentioned_agent = ?)`);
2396
2396
  params.push(opts.mentions_only.toLowerCase());
2397
2397
  }
2398
+ const isLatest = opts.latest && opts.latest > 0;
2398
2399
  const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
2399
- const resolvedLimit = Number.isFinite(opts.limit) && opts.limit > 0 ? Math.floor(opts.limit) : 20;
2400
- const order = opts.order?.toLowerCase() === "desc" ? "DESC" : "ASC";
2400
+ const resolvedLimit = isLatest ? Math.floor(opts.latest) : Number.isFinite(opts.limit) && opts.limit > 0 ? Math.floor(opts.limit) : 20;
2401
+ const order = isLatest ? "DESC" : opts.order?.toLowerCase() === "desc" ? "DESC" : "ASC";
2401
2402
  const rows = db2.prepare(`SELECT * FROM messages ${where} ORDER BY created_at ${order}, id ${order} LIMIT ${resolvedLimit}`).all(...params);
2402
2403
  let messages = rows.map(parseMessage);
2403
2404
  if (opts.include_reply_counts && messages.length > 0) {
@@ -4440,7 +4441,7 @@ var init_poll = __esm(() => {
4440
4441
  var require_package = __commonJS((exports, module) => {
4441
4442
  module.exports = {
4442
4443
  name: "@hasna/conversations",
4443
- version: "0.2.7",
4444
+ version: "0.2.9",
4444
4445
  description: "Real-time CLI messaging for AI agents",
4445
4446
  type: "module",
4446
4447
  bin: {
@@ -33696,7 +33697,8 @@ var init_mcp2 = __esm(() => {
33696
33697
  max_content_length: exports_external.coerce.number().optional().describe("Truncate each message content to N chars (adds truncated:true flag)"),
33697
33698
  threads_only: exports_external.coerce.boolean().optional().describe("Only return root messages (reply_to IS NULL) \u2014 hides thread replies"),
33698
33699
  include_reply_counts: exports_external.coerce.boolean().optional().describe("Include reply_count on each message (adds one extra query)"),
33699
- mentions_only: exports_external.string().optional().describe("Only return messages that @mention this agent")
33700
+ mentions_only: exports_external.string().optional().describe("Only return messages that @mention this agent"),
33701
+ latest: exports_external.coerce.number().optional().describe("Return the N most recent unread messages, newest first. Shorthand for order:desc + limit:N.")
33700
33702
  }
33701
33703
  }, async (args) => {
33702
33704
  const agent = resolveIdentity(args.from);
@@ -33778,6 +33780,19 @@ var init_mcp2 = __esm(() => {
33778
33780
  content: [{ type: "text", text: JSON.stringify({ marked_read: count }) }]
33779
33781
  };
33780
33782
  });
33783
+ server.registerTool("mark_space_read", {
33784
+ description: "Mark ALL messages in a space as read without fetching them. Use this on busy spaces (200+ messages) where read_messages would overflow tokens.",
33785
+ inputSchema: {
33786
+ space: exports_external.string().describe("Space name"),
33787
+ from: exports_external.string().optional().describe("Mark read on behalf of this agent (default: current agent)")
33788
+ }
33789
+ }, async (args) => {
33790
+ const { space, from: fromParam } = args;
33791
+ const count = markSpaceRead(space, fromParam);
33792
+ return {
33793
+ content: [{ type: "text", text: JSON.stringify({ space, marked_read: count }) }]
33794
+ };
33795
+ });
33781
33796
  server.registerTool("search_messages", {
33782
33797
  description: "Full-text search across messages. Uses FTS5 with BM25 ranking if available, falls back to LIKE. Returns messages with snippet and relevance_score.",
33783
33798
  inputSchema: {
@@ -33953,11 +33968,12 @@ var init_mcp2 = __esm(() => {
33953
33968
  mark_read: exports_external.coerce.boolean().optional(),
33954
33969
  max_content_length: exports_external.coerce.number().optional().describe("Truncate each message content to N chars (adds truncated:true flag)"),
33955
33970
  threads_only: exports_external.coerce.boolean().optional().describe("Only return root messages (hides thread replies)"),
33956
- include_reply_counts: exports_external.coerce.boolean().optional().describe("Include reply_count on each message")
33971
+ include_reply_counts: exports_external.coerce.boolean().optional().describe("Include reply_count on each message"),
33972
+ latest: exports_external.coerce.number().optional().describe("Return the N most recent messages, newest first")
33957
33973
  }
33958
33974
  }, async (args) => {
33959
- const { space, since, limit, mark_read, max_content_length, threads_only, include_reply_counts } = args;
33960
- const messages = readMessages({ space, since, limit, max_content_length, threads_only, include_reply_counts });
33975
+ const { space, since, limit, mark_read, max_content_length, threads_only, include_reply_counts, latest } = args;
33976
+ const messages = readMessages({ space, since, limit, max_content_length, threads_only, include_reply_counts, latest });
33961
33977
  if (mark_read !== false && messages.length > 0) {
33962
33978
  markReadByIds(messages.map((m) => m.id));
33963
33979
  }
@@ -34881,6 +34897,7 @@ var init_mcp2 = __esm(() => {
34881
34897
  list_sessions: "List all DM sessions. Optional: agent?(filter by participant)",
34882
34898
  reply: "Reply to a specific message, creating a thread (sets reply_to). Use read_thread to retrieve. Required: message_id, content. Optional: from?",
34883
34899
  mark_read: "Mark messages as read. Optional: from?, ids?(array), all?(bool \u2014 mark all unread)",
34900
+ mark_space_read: "Mark ALL messages in a space as read without fetching. Required: space. Optional: from?",
34884
34901
  search_messages: "Full-text search messages. Required: query. Optional: space?, from?, to?, limit?",
34885
34902
  export_messages: "Export messages as JSON or CSV. Optional: space?, session_id?, from?, since?, until?, format?(json|csv)",
34886
34903
  create_space: "Create space and auto-join. Required: name. Optional: from?, description?, parent_id?(max 3 levels), project_id?",
package/bin/mcp.js CHANGED
@@ -28904,9 +28904,10 @@ function readMessages(opts = {}) {
28904
28904
  conditions.push(`id IN (SELECT message_id FROM message_mentions WHERE mentioned_agent = ?)`);
28905
28905
  params.push(opts.mentions_only.toLowerCase());
28906
28906
  }
28907
+ const isLatest = opts.latest && opts.latest > 0;
28907
28908
  const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
28908
- const resolvedLimit = Number.isFinite(opts.limit) && opts.limit > 0 ? Math.floor(opts.limit) : 20;
28909
- const order = opts.order?.toLowerCase() === "desc" ? "DESC" : "ASC";
28909
+ const resolvedLimit = isLatest ? Math.floor(opts.latest) : Number.isFinite(opts.limit) && opts.limit > 0 ? Math.floor(opts.limit) : 20;
28910
+ const order = isLatest ? "DESC" : opts.order?.toLowerCase() === "desc" ? "DESC" : "ASC";
28910
28911
  const rows = db2.prepare(`SELECT * FROM messages ${where} ORDER BY created_at ${order}, id ${order} LIMIT ${resolvedLimit}`).all(...params);
28911
28912
  let messages = rows.map(parseMessage);
28912
28913
  if (opts.include_reply_counts && messages.length > 0) {
@@ -28936,6 +28937,12 @@ function markRead(ids, reader) {
28936
28937
  const result = stmt.run(...ids, reader);
28937
28938
  return result.changes;
28938
28939
  }
28940
+ function markSpaceRead(spaceName, reader) {
28941
+ const db2 = getDb();
28942
+ 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`);
28943
+ const result = stmt.run(spaceName, reader);
28944
+ return result.changes;
28945
+ }
28939
28946
  function getMessageById(id) {
28940
28947
  const db2 = getDb();
28941
28948
  const row = db2.prepare("SELECT * FROM messages WHERE id = ?").get(id);
@@ -30924,7 +30931,7 @@ function getGraphStats() {
30924
30931
  // package.json
30925
30932
  var package_default = {
30926
30933
  name: "@hasna/conversations",
30927
- version: "0.2.7",
30934
+ version: "0.2.9",
30928
30935
  description: "Real-time CLI messaging for AI agents",
30929
30936
  type: "module",
30930
30937
  bin: {
@@ -31059,7 +31066,8 @@ server.registerTool("read_messages", {
31059
31066
  max_content_length: exports_external.coerce.number().optional().describe("Truncate each message content to N chars (adds truncated:true flag)"),
31060
31067
  threads_only: exports_external.coerce.boolean().optional().describe("Only return root messages (reply_to IS NULL) \u2014 hides thread replies"),
31061
31068
  include_reply_counts: exports_external.coerce.boolean().optional().describe("Include reply_count on each message (adds one extra query)"),
31062
- mentions_only: exports_external.string().optional().describe("Only return messages that @mention this agent")
31069
+ mentions_only: exports_external.string().optional().describe("Only return messages that @mention this agent"),
31070
+ latest: exports_external.coerce.number().optional().describe("Return the N most recent unread messages, newest first. Shorthand for order:desc + limit:N.")
31063
31071
  }
31064
31072
  }, async (args) => {
31065
31073
  const agent = resolveIdentity(args.from);
@@ -31141,6 +31149,19 @@ server.registerTool("mark_read", {
31141
31149
  content: [{ type: "text", text: JSON.stringify({ marked_read: count }) }]
31142
31150
  };
31143
31151
  });
31152
+ server.registerTool("mark_space_read", {
31153
+ description: "Mark ALL messages in a space as read without fetching them. Use this on busy spaces (200+ messages) where read_messages would overflow tokens.",
31154
+ inputSchema: {
31155
+ space: exports_external.string().describe("Space name"),
31156
+ from: exports_external.string().optional().describe("Mark read on behalf of this agent (default: current agent)")
31157
+ }
31158
+ }, async (args) => {
31159
+ const { space, from: fromParam } = args;
31160
+ const count = markSpaceRead(space, fromParam);
31161
+ return {
31162
+ content: [{ type: "text", text: JSON.stringify({ space, marked_read: count }) }]
31163
+ };
31164
+ });
31144
31165
  server.registerTool("search_messages", {
31145
31166
  description: "Full-text search across messages. Uses FTS5 with BM25 ranking if available, falls back to LIKE. Returns messages with snippet and relevance_score.",
31146
31167
  inputSchema: {
@@ -31316,11 +31337,12 @@ server.registerTool("read_space", {
31316
31337
  mark_read: exports_external.coerce.boolean().optional(),
31317
31338
  max_content_length: exports_external.coerce.number().optional().describe("Truncate each message content to N chars (adds truncated:true flag)"),
31318
31339
  threads_only: exports_external.coerce.boolean().optional().describe("Only return root messages (hides thread replies)"),
31319
- include_reply_counts: exports_external.coerce.boolean().optional().describe("Include reply_count on each message")
31340
+ include_reply_counts: exports_external.coerce.boolean().optional().describe("Include reply_count on each message"),
31341
+ latest: exports_external.coerce.number().optional().describe("Return the N most recent messages, newest first")
31320
31342
  }
31321
31343
  }, async (args) => {
31322
- const { space, since, limit, mark_read, max_content_length, threads_only, include_reply_counts } = args;
31323
- const messages = readMessages({ space, since, limit, max_content_length, threads_only, include_reply_counts });
31344
+ const { space, since, limit, mark_read, max_content_length, threads_only, include_reply_counts, latest } = args;
31345
+ const messages = readMessages({ space, since, limit, max_content_length, threads_only, include_reply_counts, latest });
31324
31346
  if (mark_read !== false && messages.length > 0) {
31325
31347
  markReadByIds(messages.map((m) => m.id));
31326
31348
  }
@@ -32244,6 +32266,7 @@ server.registerTool("describe_tools", {
32244
32266
  list_sessions: "List all DM sessions. Optional: agent?(filter by participant)",
32245
32267
  reply: "Reply to a specific message, creating a thread (sets reply_to). Use read_thread to retrieve. Required: message_id, content. Optional: from?",
32246
32268
  mark_read: "Mark messages as read. Optional: from?, ids?(array), all?(bool \u2014 mark all unread)",
32269
+ mark_space_read: "Mark ALL messages in a space as read without fetching. Required: space. Optional: from?",
32247
32270
  search_messages: "Full-text search messages. Required: query. Optional: space?, from?, to?, limit?",
32248
32271
  export_messages: "Export messages as JSON or CSV. Optional: space?, session_id?, from?, since?, until?, format?(json|csv)",
32249
32272
  create_space: "Create space and auto-join. Required: name. Optional: from?, description?, parent_id?(max 3 levels), project_id?",
package/dist/index.js CHANGED
@@ -2369,9 +2369,10 @@ function readMessages(opts = {}) {
2369
2369
  conditions.push(`id IN (SELECT message_id FROM message_mentions WHERE mentioned_agent = ?)`);
2370
2370
  params.push(opts.mentions_only.toLowerCase());
2371
2371
  }
2372
+ const isLatest = opts.latest && opts.latest > 0;
2372
2373
  const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
2373
- const resolvedLimit = Number.isFinite(opts.limit) && opts.limit > 0 ? Math.floor(opts.limit) : 20;
2374
- const order = opts.order?.toLowerCase() === "desc" ? "DESC" : "ASC";
2374
+ const resolvedLimit = isLatest ? Math.floor(opts.latest) : Number.isFinite(opts.limit) && opts.limit > 0 ? Math.floor(opts.limit) : 20;
2375
+ const order = isLatest ? "DESC" : opts.order?.toLowerCase() === "desc" ? "DESC" : "ASC";
2375
2376
  const rows = db2.prepare(`SELECT * FROM messages ${where} ORDER BY created_at ${order}, id ${order} LIMIT ${resolvedLimit}`).all(...params);
2376
2377
  let messages = rows.map(parseMessage);
2377
2378
  if (opts.include_reply_counts && messages.length > 0) {
package/dist/types.d.ts CHANGED
@@ -112,6 +112,7 @@ export interface ReadMessagesOptions {
112
112
  threads_only?: boolean;
113
113
  include_reply_counts?: boolean;
114
114
  mentions_only?: string;
115
+ latest?: number;
115
116
  }
116
117
  export interface SearchMessagesOptions {
117
118
  query: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/conversations",
3
- "version": "0.2.7",
3
+ "version": "0.2.9",
4
4
  "description": "Real-time CLI messaging for AI agents",
5
5
  "type": "module",
6
6
  "bin": {