@hasna/conversations 0.2.6 → 0.2.8

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) {
@@ -2646,6 +2647,14 @@ function searchMessages(opts) {
2646
2647
  extraWhere += " AND m.to_agent = ?";
2647
2648
  ftsParams.push(opts.to);
2648
2649
  }
2650
+ if (opts.since) {
2651
+ extraWhere += " AND m.created_at >= ?";
2652
+ ftsParams.push(opts.since);
2653
+ }
2654
+ if (opts.until) {
2655
+ extraWhere += " AND m.created_at <= ?";
2656
+ ftsParams.push(opts.until);
2657
+ }
2649
2658
  const orderClause = sortByRelevance ? "ORDER BY rank" : "ORDER BY m.created_at DESC, m.id DESC";
2650
2659
  const rows2 = db2.prepare(`SELECT m.*, rank,
2651
2660
  snippet(messages_fts, 0, '**', '**', '...', 20) as snippet
@@ -2678,6 +2687,14 @@ function searchMessages(opts) {
2678
2687
  conditions.push("to_agent = ?");
2679
2688
  params.push(opts.to);
2680
2689
  }
2690
+ if (opts.since) {
2691
+ conditions.push("created_at >= ?");
2692
+ params.push(opts.since);
2693
+ }
2694
+ if (opts.until) {
2695
+ conditions.push("created_at <= ?");
2696
+ params.push(opts.until);
2697
+ }
2681
2698
  const where = `WHERE ${conditions.join(" AND ")}`;
2682
2699
  const rows = db2.prepare(`SELECT * FROM messages ${where} ORDER BY created_at DESC, id DESC LIMIT ${limit}`).all(...params);
2683
2700
  return rows.map((row) => {
@@ -4424,7 +4441,7 @@ var init_poll = __esm(() => {
4424
4441
  var require_package = __commonJS((exports, module) => {
4425
4442
  module.exports = {
4426
4443
  name: "@hasna/conversations",
4427
- version: "0.2.6",
4444
+ version: "0.2.8",
4428
4445
  description: "Real-time CLI messaging for AI agents",
4429
4446
  type: "module",
4430
4447
  bin: {
@@ -33680,7 +33697,8 @@ var init_mcp2 = __esm(() => {
33680
33697
  max_content_length: exports_external.coerce.number().optional().describe("Truncate each message content to N chars (adds truncated:true flag)"),
33681
33698
  threads_only: exports_external.coerce.boolean().optional().describe("Only return root messages (reply_to IS NULL) \u2014 hides thread replies"),
33682
33699
  include_reply_counts: exports_external.coerce.boolean().optional().describe("Include reply_count on each message (adds one extra query)"),
33683
- 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.")
33684
33702
  }
33685
33703
  }, async (args) => {
33686
33704
  const agent = resolveIdentity(args.from);
@@ -33763,19 +33781,26 @@ var init_mcp2 = __esm(() => {
33763
33781
  };
33764
33782
  });
33765
33783
  server.registerTool("search_messages", {
33766
- description: "Full-text search across messages.",
33784
+ description: "Full-text search across messages. Uses FTS5 with BM25 ranking if available, falls back to LIKE. Returns messages with snippet and relevance_score.",
33767
33785
  inputSchema: {
33768
- query: exports_external.string(),
33769
- space: exports_external.string().optional(),
33770
- from: exports_external.string().optional(),
33771
- to: exports_external.string().optional(),
33772
- limit: exports_external.coerce.number().optional()
33786
+ query: exports_external.string().describe(`Search query. Wrap in quotes for exact phrase: '"BUG-005"'`),
33787
+ space: exports_external.string().optional().describe("Limit to a specific space"),
33788
+ from: exports_external.string().optional().describe("Filter by sender"),
33789
+ to: exports_external.string().optional().describe("Filter by recipient"),
33790
+ since: exports_external.string().optional().describe("ISO 8601 date \u2014 only messages after this"),
33791
+ until: exports_external.string().optional().describe("ISO 8601 date \u2014 only messages before this"),
33792
+ sort: exports_external.enum(["relevance", "recent"]).optional().describe("Sort order (default: relevance)"),
33793
+ limit: exports_external.coerce.number().optional().describe("Max results (default: 20)")
33773
33794
  }
33774
33795
  }, async (args) => {
33775
- const { query, space, from, to, limit } = args;
33776
- const messages = searchMessages({ query, space, from, to, limit });
33796
+ const { query, space, from, to, since, until, sort, limit } = args;
33797
+ const results = searchMessages({ query, space, from, to, since, until, sort, limit });
33777
33798
  return {
33778
- content: [{ type: "text", text: JSON.stringify(messages) }]
33799
+ content: [{ type: "text", text: JSON.stringify({
33800
+ results,
33801
+ count: results.length,
33802
+ query
33803
+ }) }]
33779
33804
  };
33780
33805
  });
33781
33806
  server.registerTool("export_messages", {
@@ -33930,11 +33955,12 @@ var init_mcp2 = __esm(() => {
33930
33955
  mark_read: exports_external.coerce.boolean().optional(),
33931
33956
  max_content_length: exports_external.coerce.number().optional().describe("Truncate each message content to N chars (adds truncated:true flag)"),
33932
33957
  threads_only: exports_external.coerce.boolean().optional().describe("Only return root messages (hides thread replies)"),
33933
- include_reply_counts: exports_external.coerce.boolean().optional().describe("Include reply_count on each message")
33958
+ include_reply_counts: exports_external.coerce.boolean().optional().describe("Include reply_count on each message"),
33959
+ latest: exports_external.coerce.number().optional().describe("Return the N most recent messages, newest first")
33934
33960
  }
33935
33961
  }, async (args) => {
33936
- const { space, since, limit, mark_read, max_content_length, threads_only, include_reply_counts } = args;
33937
- const messages = readMessages({ space, since, limit, max_content_length, threads_only, include_reply_counts });
33962
+ const { space, since, limit, mark_read, max_content_length, threads_only, include_reply_counts, latest } = args;
33963
+ const messages = readMessages({ space, since, limit, max_content_length, threads_only, include_reply_counts, latest });
33938
33964
  if (mark_read !== false && messages.length > 0) {
33939
33965
  markReadByIds(messages.map((m) => m.id));
33940
33966
  }
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) {
@@ -29143,6 +29144,14 @@ function searchMessages(opts) {
29143
29144
  extraWhere += " AND m.to_agent = ?";
29144
29145
  ftsParams.push(opts.to);
29145
29146
  }
29147
+ if (opts.since) {
29148
+ extraWhere += " AND m.created_at >= ?";
29149
+ ftsParams.push(opts.since);
29150
+ }
29151
+ if (opts.until) {
29152
+ extraWhere += " AND m.created_at <= ?";
29153
+ ftsParams.push(opts.until);
29154
+ }
29146
29155
  const orderClause = sortByRelevance ? "ORDER BY rank" : "ORDER BY m.created_at DESC, m.id DESC";
29147
29156
  const rows2 = db2.prepare(`SELECT m.*, rank,
29148
29157
  snippet(messages_fts, 0, '**', '**', '...', 20) as snippet
@@ -29175,6 +29184,14 @@ function searchMessages(opts) {
29175
29184
  conditions.push("to_agent = ?");
29176
29185
  params.push(opts.to);
29177
29186
  }
29187
+ if (opts.since) {
29188
+ conditions.push("created_at >= ?");
29189
+ params.push(opts.since);
29190
+ }
29191
+ if (opts.until) {
29192
+ conditions.push("created_at <= ?");
29193
+ params.push(opts.until);
29194
+ }
29178
29195
  const where = `WHERE ${conditions.join(" AND ")}`;
29179
29196
  const rows = db2.prepare(`SELECT * FROM messages ${where} ORDER BY created_at DESC, id DESC LIMIT ${limit}`).all(...params);
29180
29197
  return rows.map((row) => {
@@ -30908,7 +30925,7 @@ function getGraphStats() {
30908
30925
  // package.json
30909
30926
  var package_default = {
30910
30927
  name: "@hasna/conversations",
30911
- version: "0.2.6",
30928
+ version: "0.2.8",
30912
30929
  description: "Real-time CLI messaging for AI agents",
30913
30930
  type: "module",
30914
30931
  bin: {
@@ -31043,7 +31060,8 @@ server.registerTool("read_messages", {
31043
31060
  max_content_length: exports_external.coerce.number().optional().describe("Truncate each message content to N chars (adds truncated:true flag)"),
31044
31061
  threads_only: exports_external.coerce.boolean().optional().describe("Only return root messages (reply_to IS NULL) \u2014 hides thread replies"),
31045
31062
  include_reply_counts: exports_external.coerce.boolean().optional().describe("Include reply_count on each message (adds one extra query)"),
31046
- mentions_only: exports_external.string().optional().describe("Only return messages that @mention this agent")
31063
+ mentions_only: exports_external.string().optional().describe("Only return messages that @mention this agent"),
31064
+ latest: exports_external.coerce.number().optional().describe("Return the N most recent unread messages, newest first. Shorthand for order:desc + limit:N.")
31047
31065
  }
31048
31066
  }, async (args) => {
31049
31067
  const agent = resolveIdentity(args.from);
@@ -31126,19 +31144,26 @@ server.registerTool("mark_read", {
31126
31144
  };
31127
31145
  });
31128
31146
  server.registerTool("search_messages", {
31129
- description: "Full-text search across messages.",
31147
+ description: "Full-text search across messages. Uses FTS5 with BM25 ranking if available, falls back to LIKE. Returns messages with snippet and relevance_score.",
31130
31148
  inputSchema: {
31131
- query: exports_external.string(),
31132
- space: exports_external.string().optional(),
31133
- from: exports_external.string().optional(),
31134
- to: exports_external.string().optional(),
31135
- limit: exports_external.coerce.number().optional()
31149
+ query: exports_external.string().describe(`Search query. Wrap in quotes for exact phrase: '"BUG-005"'`),
31150
+ space: exports_external.string().optional().describe("Limit to a specific space"),
31151
+ from: exports_external.string().optional().describe("Filter by sender"),
31152
+ to: exports_external.string().optional().describe("Filter by recipient"),
31153
+ since: exports_external.string().optional().describe("ISO 8601 date \u2014 only messages after this"),
31154
+ until: exports_external.string().optional().describe("ISO 8601 date \u2014 only messages before this"),
31155
+ sort: exports_external.enum(["relevance", "recent"]).optional().describe("Sort order (default: relevance)"),
31156
+ limit: exports_external.coerce.number().optional().describe("Max results (default: 20)")
31136
31157
  }
31137
31158
  }, async (args) => {
31138
- const { query, space, from, to, limit } = args;
31139
- const messages = searchMessages({ query, space, from, to, limit });
31159
+ const { query, space, from, to, since, until, sort, limit } = args;
31160
+ const results = searchMessages({ query, space, from, to, since, until, sort, limit });
31140
31161
  return {
31141
- content: [{ type: "text", text: JSON.stringify(messages) }]
31162
+ content: [{ type: "text", text: JSON.stringify({
31163
+ results,
31164
+ count: results.length,
31165
+ query
31166
+ }) }]
31142
31167
  };
31143
31168
  });
31144
31169
  server.registerTool("export_messages", {
@@ -31293,11 +31318,12 @@ server.registerTool("read_space", {
31293
31318
  mark_read: exports_external.coerce.boolean().optional(),
31294
31319
  max_content_length: exports_external.coerce.number().optional().describe("Truncate each message content to N chars (adds truncated:true flag)"),
31295
31320
  threads_only: exports_external.coerce.boolean().optional().describe("Only return root messages (hides thread replies)"),
31296
- include_reply_counts: exports_external.coerce.boolean().optional().describe("Include reply_count on each message")
31321
+ include_reply_counts: exports_external.coerce.boolean().optional().describe("Include reply_count on each message"),
31322
+ latest: exports_external.coerce.number().optional().describe("Return the N most recent messages, newest first")
31297
31323
  }
31298
31324
  }, async (args) => {
31299
- const { space, since, limit, mark_read, max_content_length, threads_only, include_reply_counts } = args;
31300
- const messages = readMessages({ space, since, limit, max_content_length, threads_only, include_reply_counts });
31325
+ const { space, since, limit, mark_read, max_content_length, threads_only, include_reply_counts, latest } = args;
31326
+ const messages = readMessages({ space, since, limit, max_content_length, threads_only, include_reply_counts, latest });
31301
31327
  if (mark_read !== false && messages.length > 0) {
31302
31328
  markReadByIds(messages.map((m) => m.id));
31303
31329
  }
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) {
@@ -2568,6 +2569,14 @@ function searchMessages(opts) {
2568
2569
  extraWhere += " AND m.to_agent = ?";
2569
2570
  ftsParams.push(opts.to);
2570
2571
  }
2572
+ if (opts.since) {
2573
+ extraWhere += " AND m.created_at >= ?";
2574
+ ftsParams.push(opts.since);
2575
+ }
2576
+ if (opts.until) {
2577
+ extraWhere += " AND m.created_at <= ?";
2578
+ ftsParams.push(opts.until);
2579
+ }
2571
2580
  const orderClause = sortByRelevance ? "ORDER BY rank" : "ORDER BY m.created_at DESC, m.id DESC";
2572
2581
  const rows2 = db2.prepare(`SELECT m.*, rank,
2573
2582
  snippet(messages_fts, 0, '**', '**', '...', 20) as snippet
@@ -2600,6 +2609,14 @@ function searchMessages(opts) {
2600
2609
  conditions.push("to_agent = ?");
2601
2610
  params.push(opts.to);
2602
2611
  }
2612
+ if (opts.since) {
2613
+ conditions.push("created_at >= ?");
2614
+ params.push(opts.since);
2615
+ }
2616
+ if (opts.until) {
2617
+ conditions.push("created_at <= ?");
2618
+ params.push(opts.until);
2619
+ }
2603
2620
  const where = `WHERE ${conditions.join(" AND ")}`;
2604
2621
  const rows = db2.prepare(`SELECT * FROM messages ${where} ORDER BY created_at DESC, id DESC LIMIT ${limit}`).all(...params);
2605
2622
  return rows.map((row) => {
package/dist/types.d.ts CHANGED
@@ -112,14 +112,18 @@ 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;
118
119
  space?: string;
119
120
  from?: string;
120
121
  to?: string;
122
+ since?: string;
123
+ until?: string;
121
124
  limit?: number;
122
125
  sort?: "relevance" | "recent";
126
+ snippet_length?: number;
123
127
  }
124
128
  export interface SearchResult extends Message {
125
129
  snippet: string | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/conversations",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "description": "Real-time CLI messaging for AI agents",
5
5
  "type": "module",
6
6
  "bin": {