@hasna/conversations 0.1.4 → 0.1.6

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
@@ -4,6 +4,7 @@ var __create = Object.create;
4
4
  var __getProtoOf = Object.getPrototypeOf;
5
5
  var __defProp = Object.defineProperty;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
9
  var __toESM = (mod, isNodeMode, target) => {
9
10
  target = mod != null ? __create(__getProtoOf(mod)) : {};
@@ -16,6 +17,20 @@ var __toESM = (mod, isNodeMode, target) => {
16
17
  });
17
18
  return to;
18
19
  };
20
+ var __moduleCache = /* @__PURE__ */ new WeakMap;
21
+ var __toCommonJS = (from) => {
22
+ var entry = __moduleCache.get(from), desc;
23
+ if (entry)
24
+ return entry;
25
+ entry = __defProp({}, "__esModule", { value: true });
26
+ if (from && typeof from === "object" || typeof from === "function")
27
+ __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
28
+ get: () => from[key],
29
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
30
+ }));
31
+ __moduleCache.set(from, entry);
32
+ return entry;
33
+ };
19
34
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
20
35
  var __export = (target, all) => {
21
36
  for (var name in all)
@@ -1869,6 +1884,12 @@ var require_commander = __commonJS((exports) => {
1869
1884
  });
1870
1885
 
1871
1886
  // src/lib/db.ts
1887
+ var exports_db = {};
1888
+ __export(exports_db, {
1889
+ getDbPath: () => getDbPath,
1890
+ getDb: () => getDb,
1891
+ closeDb: () => closeDb
1892
+ });
1872
1893
  import { Database } from "bun:sqlite";
1873
1894
  import { mkdirSync } from "fs";
1874
1895
  import { join, dirname } from "path";
@@ -2006,6 +2027,10 @@ function getDb() {
2006
2027
  db.exec("ALTER TABLE messages ADD COLUMN pinned_at TEXT");
2007
2028
  db.exec("CREATE INDEX IF NOT EXISTS idx_messages_pinned ON messages(pinned_at)");
2008
2029
  }
2030
+ if (!colNames2.includes("blocking")) {
2031
+ db.exec("ALTER TABLE messages ADD COLUMN blocking INTEGER NOT NULL DEFAULT 0");
2032
+ db.exec("CREATE INDEX IF NOT EXISTS idx_messages_blocking ON messages(blocking)");
2033
+ }
2009
2034
  return db;
2010
2035
  }
2011
2036
  function closeDb() {
@@ -2030,7 +2055,8 @@ function parseMessage(row) {
2030
2055
  }
2031
2056
  return {
2032
2057
  ...row,
2033
- metadata
2058
+ metadata,
2059
+ blocking: !!row.blocking
2034
2060
  };
2035
2061
  }
2036
2062
  function sendMessage(opts) {
@@ -2039,12 +2065,13 @@ function sendMessage(opts) {
2039
2065
  const sessionId = explicitSession ?? (opts.space ? `space:${opts.space}` : `${[opts.from, opts.to].sort().join("-")}-${randomUUID().slice(0, 8)}`);
2040
2066
  const metadata = opts.metadata ? JSON.stringify(opts.metadata) : null;
2041
2067
  const normalizedPriority = opts.priority === "low" || opts.priority === "normal" || opts.priority === "high" || opts.priority === "urgent" ? opts.priority : "normal";
2068
+ const blocking = opts.blocking ? 1 : 0;
2042
2069
  const stmt = db2.prepare(`
2043
- INSERT INTO messages (session_id, from_agent, to_agent, space, content, priority, working_dir, repository, branch, metadata)
2044
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2070
+ INSERT INTO messages (session_id, from_agent, to_agent, space, content, priority, working_dir, repository, branch, metadata, blocking)
2071
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2045
2072
  RETURNING *
2046
2073
  `);
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);
2074
+ 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, blocking);
2048
2075
  return parseMessage(row);
2049
2076
  }
2050
2077
  function readMessages(opts = {}) {
@@ -2213,6 +2240,19 @@ function getPinnedMessages(opts) {
2213
2240
  const rows = db2.prepare(`SELECT * FROM messages ${where} ORDER BY pinned_at DESC, id DESC ${limit}`).all(...params);
2214
2241
  return rows.map(parseMessage);
2215
2242
  }
2243
+ function getUnreadBlockers(agent) {
2244
+ const db2 = getDb();
2245
+ const rows = db2.prepare(`
2246
+ SELECT * FROM messages
2247
+ WHERE blocking = 1 AND read_at IS NULL
2248
+ AND (
2249
+ to_agent = ?
2250
+ OR space IN (SELECT space FROM space_members WHERE agent = ?)
2251
+ )
2252
+ ORDER BY created_at ASC, id ASC
2253
+ `).all(agent, agent);
2254
+ return rows.map(parseMessage);
2255
+ }
2216
2256
  function searchMessages(opts) {
2217
2257
  const db2 = getDb();
2218
2258
  const conditions = ["content LIKE ?"];
@@ -2975,6 +3015,16 @@ var init_names = __esm(() => {
2975
3015
  import { readFileSync, writeFileSync, mkdirSync as mkdirSync2 } from "fs";
2976
3016
  import { join as join2, dirname as dirname2 } from "path";
2977
3017
  import { homedir as homedir2 } from "os";
3018
+ function isNameTaken(name) {
3019
+ try {
3020
+ const { getDb: getDb2 } = (init_db(), __toCommonJS(exports_db));
3021
+ const db2 = getDb2();
3022
+ const row = db2.prepare("SELECT agent FROM agent_presence WHERE agent = ?").get(name);
3023
+ return !!row;
3024
+ } catch {
3025
+ return false;
3026
+ }
3027
+ }
2978
3028
  function getAutoName() {
2979
3029
  if (cachedAutoName)
2980
3030
  return cachedAutoName;
@@ -2985,7 +3035,14 @@ function getAutoName() {
2985
3035
  return name2;
2986
3036
  }
2987
3037
  } catch {}
2988
- const name = AGENT_NAMES[Math.floor(Math.random() * AGENT_NAMES.length)];
3038
+ const shuffled = [...AGENT_NAMES].sort(() => Math.random() - 0.5);
3039
+ let name = shuffled[0];
3040
+ for (const candidate of shuffled) {
3041
+ if (!isNameTaken(candidate)) {
3042
+ name = candidate;
3043
+ break;
3044
+ }
3045
+ }
2989
3046
  cachedAutoName = name;
2990
3047
  try {
2991
3048
  mkdirSync2(dirname2(AGENT_ID_FILE), { recursive: true });
@@ -3055,6 +3112,22 @@ function listAgents(opts) {
3055
3112
  const rows = db2.prepare(query).all(...params);
3056
3113
  return rows.map(parsePresence);
3057
3114
  }
3115
+ function removePresence(agent) {
3116
+ const db2 = getDb();
3117
+ const result = db2.prepare("DELETE FROM agent_presence WHERE agent = ?").run(agent);
3118
+ return result.changes > 0;
3119
+ }
3120
+ function renameAgent(oldName, newName) {
3121
+ const db2 = getDb();
3122
+ const existing = db2.prepare("SELECT agent FROM agent_presence WHERE agent = ?").get(oldName);
3123
+ if (!existing)
3124
+ return false;
3125
+ const conflict = db2.prepare("SELECT agent FROM agent_presence WHERE agent = ?").get(newName);
3126
+ if (conflict)
3127
+ throw new Error(`Agent "${newName}" already exists`);
3128
+ db2.prepare("UPDATE agent_presence SET agent = ? WHERE agent = ?").run(newName, oldName);
3129
+ return true;
3130
+ }
3058
3131
  var ONLINE_THRESHOLD_SECONDS = 60;
3059
3132
  var init_presence = __esm(() => {
3060
3133
  init_db();
@@ -3064,12 +3137,13 @@ var init_presence = __esm(() => {
3064
3137
  var require_package = __commonJS((exports, module) => {
3065
3138
  module.exports = {
3066
3139
  name: "@hasna/conversations",
3067
- version: "0.1.4",
3140
+ version: "0.1.6",
3068
3141
  description: "Real-time CLI messaging for AI agents",
3069
3142
  type: "module",
3070
3143
  bin: {
3071
3144
  conversations: "bin/index.js",
3072
- "conversations-mcp": "bin/mcp.js"
3145
+ "conversations-mcp": "bin/mcp.js",
3146
+ "conversations-hook": "bin/hook.js"
3073
3147
  },
3074
3148
  exports: {
3075
3149
  ".": {
@@ -3087,7 +3161,7 @@ var require_package = __commonJS((exports, module) => {
3087
3161
  main: "./dist/index.js",
3088
3162
  types: "./dist/index.d.ts",
3089
3163
  scripts: {
3090
- 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",
3164
+ 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/hooks/blocker-hook.ts --outfile ./bin/hook.js --target bun && bun build ./src/index.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist",
3091
3165
  "build:dashboard": "cd dashboard && bun install && bun run build",
3092
3166
  test: "bun test",
3093
3167
  dev: "bun run ./src/cli/index.tsx",
@@ -32041,9 +32115,10 @@ var init_mcp2 = __esm(() => {
32041
32115
  working_dir: exports_external.string().optional().describe("Working directory context"),
32042
32116
  repository: exports_external.string().optional().describe("Repository context"),
32043
32117
  branch: exports_external.string().optional().describe("Branch context"),
32044
- metadata: exports_external.string().optional().describe("JSON metadata string")
32118
+ metadata: exports_external.string().optional().describe("JSON metadata string"),
32119
+ blocking: exports_external.boolean().optional().describe("Send as a blocking message. Recipients must acknowledge before continuing.")
32045
32120
  }
32046
- }, async ({ from: fromParam, to, content, session_id, priority, working_dir, repository, branch, metadata }) => {
32121
+ }, async ({ from: fromParam, to, content, session_id, priority, working_dir, repository, branch, metadata, blocking }) => {
32047
32122
  const from = resolveIdentity(fromParam);
32048
32123
  let parsedMetadata;
32049
32124
  if (metadata) {
@@ -32065,7 +32140,8 @@ var init_mcp2 = __esm(() => {
32065
32140
  working_dir,
32066
32141
  repository,
32067
32142
  branch,
32068
- metadata: parsedMetadata
32143
+ metadata: parsedMetadata,
32144
+ blocking
32069
32145
  });
32070
32146
  return {
32071
32147
  content: [{ type: "text", text: JSON.stringify(msg, null, 2) }]
@@ -32252,9 +32328,10 @@ var init_mcp2 = __esm(() => {
32252
32328
  from: exports_external.string().optional().describe("Your agent ID. Falls back to CONVERSATIONS_AGENT_ID env var."),
32253
32329
  space: exports_external.string().describe("Space name"),
32254
32330
  content: exports_external.string().describe("Message content"),
32255
- priority: exports_external.enum(["low", "normal", "high", "urgent"]).optional().describe("Message priority")
32331
+ priority: exports_external.enum(["low", "normal", "high", "urgent"]).optional().describe("Message priority"),
32332
+ blocking: exports_external.boolean().optional().describe("Send as a blocking message. All space members must acknowledge.")
32256
32333
  }
32257
- }, async ({ from: fromParam, space, content, priority }) => {
32334
+ }, async ({ from: fromParam, space, content, priority, blocking }) => {
32258
32335
  const from = resolveIdentity(fromParam);
32259
32336
  const sp = getSpace(space);
32260
32337
  if (!sp) {
@@ -32269,7 +32346,8 @@ var init_mcp2 = __esm(() => {
32269
32346
  content,
32270
32347
  space,
32271
32348
  session_id: `space:${space}`,
32272
- priority
32349
+ priority,
32350
+ blocking
32273
32351
  });
32274
32352
  return {
32275
32353
  content: [{ type: "text", text: JSON.stringify(msg, null, 2) }]
@@ -32708,6 +32786,74 @@ var init_mcp2 = __esm(() => {
32708
32786
  content: [{ type: "text", text: JSON.stringify(agents, null, 2) }]
32709
32787
  };
32710
32788
  });
32789
+ server.registerTool("get_blockers", {
32790
+ title: "Get Blockers",
32791
+ description: "Check for unread blocking messages targeting you. Returns messages that must be acknowledged before continuing.",
32792
+ inputSchema: {
32793
+ from: exports_external.string().optional().describe("Your agent ID. Falls back to CONVERSATIONS_AGENT_ID env var.")
32794
+ }
32795
+ }, async ({ from: fromParam }) => {
32796
+ const agent = resolveIdentity(fromParam);
32797
+ const blockers = getUnreadBlockers(agent);
32798
+ return {
32799
+ content: [{ type: "text", text: JSON.stringify(blockers, null, 2) }]
32800
+ };
32801
+ });
32802
+ server.registerTool("remove_agent", {
32803
+ title: "Remove Agent",
32804
+ description: "Remove an agent from the presence list. Only the agent itself should remove its own presence.",
32805
+ inputSchema: {
32806
+ from: exports_external.string().optional().describe("Your agent ID. Falls back to CONVERSATIONS_AGENT_ID env var."),
32807
+ agent: exports_external.string().optional().describe("Agent to remove (defaults to yourself)")
32808
+ }
32809
+ }, async ({ from: fromParam, agent: targetAgent }) => {
32810
+ const self = resolveIdentity(fromParam);
32811
+ const agent = targetAgent?.trim() || self;
32812
+ const removed = removePresence(agent);
32813
+ if (!removed) {
32814
+ return {
32815
+ content: [{ type: "text", text: `Agent "${agent}" not found` }],
32816
+ isError: true
32817
+ };
32818
+ }
32819
+ return {
32820
+ content: [{ type: "text", text: JSON.stringify({ agent, removed: true }, null, 2) }]
32821
+ };
32822
+ });
32823
+ server.registerTool("rename_agent", {
32824
+ title: "Rename Agent",
32825
+ description: "Rename an agent in the presence list. By default renames yourself.",
32826
+ inputSchema: {
32827
+ from: exports_external.string().optional().describe("Your current agent ID. Falls back to CONVERSATIONS_AGENT_ID env var."),
32828
+ new_name: exports_external.string().describe("The new name for the agent")
32829
+ }
32830
+ }, async ({ from: fromParam, new_name }) => {
32831
+ const oldName = resolveIdentity(fromParam);
32832
+ const newName = new_name.trim();
32833
+ if (!newName) {
32834
+ return {
32835
+ content: [{ type: "text", text: "New name cannot be empty" }],
32836
+ isError: true
32837
+ };
32838
+ }
32839
+ try {
32840
+ const renamed = renameAgent(oldName, newName);
32841
+ if (!renamed) {
32842
+ return {
32843
+ content: [{ type: "text", text: `Agent "${oldName}" not found in presence list` }],
32844
+ isError: true
32845
+ };
32846
+ }
32847
+ return {
32848
+ content: [{ type: "text", text: JSON.stringify({ old_name: oldName, new_name: newName, renamed: true }, null, 2) }]
32849
+ };
32850
+ } catch (e) {
32851
+ return {
32852
+ content: [{ type: "text", text: e.message }],
32853
+ isError: true
32854
+ };
32855
+ }
32856
+ });
32711
32857
  isDirectRun = import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith("mcp.js") || process.argv[1]?.endsWith("mcp.ts");
32712
32858
  if (isDirectRun) {
32713
32859
  startMcpServer().catch((error48) => {
@@ -34227,7 +34373,7 @@ function App({ agent }) {
34227
34373
  var import__package2 = __toESM(require_package(), 1);
34228
34374
  var program2 = new Command;
34229
34375
  program2.name("conversations").description("Real-time CLI messaging for AI agents").version(import__package2.default.version);
34230
- 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) => {
34376
+ 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("--blocking", "Send as a blocking message (recipient must acknowledge)").option("--json", "Output as JSON").action((message, opts) => {
34231
34377
  const from = resolveIdentity(opts.from).trim();
34232
34378
  const to = typeof opts.to === "string" ? opts.to.trim() : "";
34233
34379
  const content = typeof message === "string" ? message : "";
@@ -34262,7 +34408,8 @@ program2.command("send").description("Send a message to an agent").argument("<me
34262
34408
  working_dir: opts.workingDir,
34263
34409
  repository: opts.repository,
34264
34410
  branch: opts.branch,
34265
- metadata
34411
+ metadata,
34412
+ blocking: opts.blocking
34266
34413
  });
34267
34414
  if (opts.json) {
34268
34415
  console.log(JSON.stringify(msg, null, 2));
@@ -34979,17 +35126,18 @@ program2.command("unpin").description("Unpin a message").argument("<id>", "Messa
34979
35126
  }
34980
35127
  closeDb();
34981
35128
  });
34982
- program2.command("agents").description("List all agents with their presence status").option("--online", "Only show online agents").option("--json", "Output as JSON").action((opts) => {
35129
+ var agents = program2.command("agents").description("Manage agents");
35130
+ agents.command("list").description("List all agents with their presence status").option("--online", "Only show online agents").option("--json", "Output as JSON").action((opts) => {
34983
35131
  const agent = resolveIdentity();
34984
35132
  heartbeat(agent);
34985
- const agents = listAgents({ online_only: opts.online });
35133
+ const agentsList = listAgents({ online_only: opts.online });
34986
35134
  if (opts.json) {
34987
- console.log(JSON.stringify(agents, null, 2));
35135
+ console.log(JSON.stringify(agentsList, null, 2));
34988
35136
  } else {
34989
- if (agents.length === 0) {
35137
+ if (agentsList.length === 0) {
34990
35138
  console.log(chalk2.dim("No agents found."));
34991
35139
  } else {
34992
- for (const a of agents) {
35140
+ for (const a of agentsList) {
34993
35141
  const status = a.online ? chalk2.green("online") : chalk2.dim("offline");
34994
35142
  const lastSeen = chalk2.dim(a.last_seen_at.slice(0, 19));
34995
35143
  const agentName = a.agent === agent ? chalk2.cyan(`${a.agent} (you)`) : chalk2.cyan(a.agent);
@@ -34999,6 +35147,71 @@ program2.command("agents").description("List all agents with their presence stat
34999
35147
  }
35000
35148
  closeDb();
35001
35149
  });
35150
+ agents.command("remove").description("Remove an agent from the presence list").argument("<name>", "Agent name to remove").option("--json", "Output as JSON").action((name, opts) => {
35151
+ const agentName = typeof name === "string" ? name.trim() : "";
35152
+ if (!agentName) {
35153
+ console.error(chalk2.red("Agent name cannot be empty."));
35154
+ process.exit(1);
35155
+ }
35156
+ const removed = removePresence(agentName);
35157
+ if (opts.json) {
35158
+ console.log(JSON.stringify({ agent: agentName, removed }));
35159
+ } else {
35160
+ if (removed) {
35161
+ console.log(chalk2.green(`Agent "${agentName}" removed.`));
35162
+ } else {
35163
+ console.error(chalk2.red(`Agent "${agentName}" not found.`));
35164
+ process.exit(1);
35165
+ }
35166
+ }
35167
+ closeDb();
35168
+ });
35169
+ agents.command("rename").description("Rename an agent in the presence list").argument("<old-name>", "Current agent name").argument("<new-name>", "New agent name").option("--json", "Output as JSON").action((oldName, newName, opts) => {
35170
+ const old = typeof oldName === "string" ? oldName.trim() : "";
35171
+ const renamed = typeof newName === "string" ? newName.trim() : "";
35172
+ if (!old || !renamed) {
35173
+ console.error(chalk2.red("Both old and new names are required."));
35174
+ process.exit(1);
35175
+ }
35176
+ try {
35177
+ const ok = renameAgent(old, renamed);
35178
+ if (!ok) {
35179
+ console.error(chalk2.red(`Agent "${old}" not found.`));
35180
+ process.exit(1);
35181
+ }
35182
+ if (opts.json) {
35183
+ console.log(JSON.stringify({ old_name: old, new_name: renamed, renamed: true }));
35184
+ } else {
35185
+ console.log(chalk2.green(`Agent "${old}" renamed to "${renamed}".`));
35186
+ }
35187
+ } catch (e) {
35188
+ console.error(chalk2.red(e.message));
35189
+ process.exit(1);
35190
+ }
35191
+ closeDb();
35192
+ });
35193
+ program2.command("blockers").description("Check for unread blocking messages").option("--from <agent>", "Agent to check blockers for").option("--json", "Output as JSON").action((opts) => {
35194
+ const agent = resolveIdentity(opts.from);
35195
+ const blockers = getUnreadBlockers(agent);
35196
+ if (opts.json) {
35197
+ console.log(JSON.stringify(blockers, null, 2));
35198
+ } else {
35199
+ if (blockers.length === 0) {
35200
+ console.log(chalk2.dim("No blocking messages."));
35201
+ } else {
35202
+ console.log(chalk2.red.bold(`${blockers.length} blocking message(s):
35203
+ `));
35204
+ for (const b of blockers) {
35205
+ const where = b.space ? chalk2.magenta(`#${b.space}`) : chalk2.yellow("DM");
35206
+ const time3 = chalk2.dim(b.created_at.slice(11, 19));
35207
+ console.log(` ${chalk2.red(`[#${b.id}]`)} ${time3} ${chalk2.cyan(b.from_agent)} ${where}: ${b.content}`);
35208
+ }
35209
+ console.log(chalk2.dim(`
35210
+ Acknowledge with: conversations mark-read ${blockers.map((b) => b.id).join(" ")}`));
35211
+ }
35212
+ }
35213
+ closeDb();
35214
+ });
35002
35215
  program2.command("mcp").description("Start MCP server").action(async () => {
35003
35216
  const { startMcpServer: startMcpServer2 } = await Promise.resolve().then(() => (init_mcp2(), exports_mcp));
35004
35217
  await startMcpServer2();