@hasna/conversations 0.2.31 → 0.2.33

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
@@ -14928,7 +14928,7 @@ var init_presence = __esm(() => {
14928
14928
  var require_package = __commonJS((exports, module) => {
14929
14929
  module.exports = {
14930
14930
  name: "@hasna/conversations",
14931
- version: "0.2.31",
14931
+ version: "0.2.33",
14932
14932
  description: "Real-time CLI messaging for AI agents",
14933
14933
  type: "module",
14934
14934
  bin: {
@@ -15071,6 +15071,18 @@ function listProjects(opts) {
15071
15071
  params.push(opts.status);
15072
15072
  }
15073
15073
  const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
15074
+ let pagination = "";
15075
+ if (typeof opts?.limit === "number" && Number.isFinite(opts.limit) && opts.limit > 0) {
15076
+ pagination += " LIMIT ?";
15077
+ params.push(Math.floor(opts.limit));
15078
+ }
15079
+ if (typeof opts?.offset === "number" && Number.isFinite(opts.offset) && opts.offset >= 0) {
15080
+ if (!pagination.includes("LIMIT")) {
15081
+ pagination += " LIMIT -1";
15082
+ }
15083
+ pagination += " OFFSET ?";
15084
+ params.push(Math.floor(opts.offset));
15085
+ }
15074
15086
  const rows = db2.prepare(`
15075
15087
  SELECT
15076
15088
  p.*,
@@ -15078,6 +15090,7 @@ function listProjects(opts) {
15078
15090
  FROM projects p
15079
15091
  ${where}
15080
15092
  ORDER BY p.name ASC
15093
+ ${pagination}
15081
15094
  `).all(...params);
15082
15095
  return rows.map((row) => ({
15083
15096
  ...parseProject(row),
@@ -46969,6 +46982,16 @@ var init_channel = __esm(() => {
46969
46982
  function sleep2(ms) {
46970
46983
  return new Promise((resolve) => setTimeout(resolve, ms));
46971
46984
  }
46985
+ function parseOptionalDelayMs(value) {
46986
+ return typeof value === "number" && value >= 0 ? value : undefined;
46987
+ }
46988
+ function normalizeTmuxTargets(rawTargets) {
46989
+ const normalized = rawTargets.map((target) => String(target).trim());
46990
+ if (normalized.some((target) => target.length === 0)) {
46991
+ throw new Error("targets must not contain empty values");
46992
+ }
46993
+ return normalized;
46994
+ }
46972
46995
  function registerTmuxTools(server) {
46973
46996
  server.registerTool("tmux_send", {
46974
46997
  description: "Send a message to a tmux window (e.g. another agent's Claude Code session). " + "Pastes the text literally, waits for the pane to be idle, hits Enter, then verifies the message was submitted. " + "Retries up to N times on failure.",
@@ -46989,7 +47012,7 @@ function registerTmuxTools(server) {
46989
47012
  }
46990
47013
  try {
46991
47014
  const result = await tmuxSend(target.trim(), message, {
46992
- delayMs: typeof delay_ms === "number" && delay_ms > 0 ? delay_ms : undefined,
47015
+ delayMs: parseOptionalDelayMs(delay_ms),
46993
47016
  retries: typeof retries === "number" && retries > 0 ? retries : undefined,
46994
47017
  verify: verify !== false
46995
47018
  });
@@ -47021,14 +47044,20 @@ function registerTmuxTools(server) {
47021
47044
  return { content: [{ type: "text", text: "message cannot be empty" }], isError: true };
47022
47045
  }
47023
47046
  const stagger = typeof stagger_ms === "number" && stagger_ms >= 0 ? stagger_ms : 500;
47024
- const results = new Array(targets.length);
47025
- await Promise.all(targets.map(async (rawTarget, i) => {
47026
- const target = String(rawTarget).trim();
47047
+ let normalizedTargets;
47048
+ try {
47049
+ normalizedTargets = normalizeTmuxTargets(targets);
47050
+ } catch (err) {
47051
+ const msg = err instanceof Error ? err.message : String(err);
47052
+ return { content: [{ type: "text", text: msg }], isError: true };
47053
+ }
47054
+ const results = new Array(normalizedTargets.length);
47055
+ await Promise.all(normalizedTargets.map(async (target, i) => {
47027
47056
  if (i > 0 && stagger > 0)
47028
47057
  await sleep2(stagger * i);
47029
47058
  try {
47030
47059
  const result = await tmuxSend(target, message, {
47031
- delayMs: typeof delay_ms === "number" && delay_ms > 0 ? delay_ms : undefined,
47060
+ delayMs: parseOptionalDelayMs(delay_ms),
47032
47061
  retries: typeof retries === "number" && retries > 0 ? retries : undefined,
47033
47062
  verify: verify !== false
47034
47063
  });
@@ -49614,6 +49643,11 @@ init_projects();
49614
49643
  init_db();
49615
49644
  init_identity();
49616
49645
  import chalk6 from "chalk";
49646
+ function requireDeleteConfirmation(confirmed) {
49647
+ if (!confirmed) {
49648
+ throw new Error("Project deletion requires --yes confirmation");
49649
+ }
49650
+ }
49617
49651
  function registerProjectCommands(program2) {
49618
49652
  const project = program2.command("project").description("Manage projects");
49619
49653
  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) => {
@@ -49660,9 +49694,15 @@ function registerProjectCommands(program2) {
49660
49694
  }
49661
49695
  closeDb();
49662
49696
  });
49663
- project.command("list").description("List all projects").option("--status <status>", "Filter by status (active/archived)").option("--json", "Output as JSON").action((opts) => {
49697
+ project.command("list").description("List all projects").option("--status <status>", "Filter by status (active/archived)").option("--limit <n>", "Limit results", parseInt).option("--offset <n>", "Skip first N results", parseInt).option("--json", "Output as JSON").action((opts) => {
49664
49698
  const status = opts.status === "active" || opts.status === "archived" ? opts.status : undefined;
49665
- const projects = listProjects(status ? { status } : undefined);
49699
+ const limit = Number.isFinite(opts.limit) && opts.limit > 0 ? opts.limit : undefined;
49700
+ const offset = Number.isFinite(opts.offset) && opts.offset >= 0 ? opts.offset : undefined;
49701
+ const projects = listProjects({
49702
+ ...status ? { status } : {},
49703
+ ...limit !== undefined ? { limit } : {},
49704
+ ...offset !== undefined ? { offset } : {}
49705
+ });
49666
49706
  if (opts.json) {
49667
49707
  console.log(JSON.stringify(projects, null, 2));
49668
49708
  } else {
@@ -49739,8 +49779,9 @@ function registerProjectCommands(program2) {
49739
49779
  }
49740
49780
  closeDb();
49741
49781
  });
49742
- project.command("delete").description("Delete a project").argument("<id-or-name>", "Project ID or name").option("--json", "Output as JSON").action((id, opts) => {
49782
+ project.command("delete").description("Delete a project").argument("<id-or-name>", "Project ID or name").option("--yes", "Confirm project deletion").option("--json", "Output as JSON").action((id, opts) => {
49743
49783
  try {
49784
+ requireDeleteConfirmation(opts.yes);
49744
49785
  const isUuid = /^[0-9a-f-]{36}$/i.test(id);
49745
49786
  const resolvedId = isUuid ? id : getProjectByName(id)?.id ?? id;
49746
49787
  const deleted = deleteProject(resolvedId);
@@ -49754,7 +49795,11 @@ function registerProjectCommands(program2) {
49754
49795
  console.log(chalk6.green(`Project deleted.`));
49755
49796
  }
49756
49797
  } catch (e) {
49757
- console.error(chalk6.red(e.message));
49798
+ if (opts.json) {
49799
+ console.log(JSON.stringify({ id, deleted: false, error: e.message }));
49800
+ } else {
49801
+ console.error(chalk6.red(e.message));
49802
+ }
49758
49803
  process.exit(1);
49759
49804
  }
49760
49805
  closeDb();
@@ -49767,6 +49812,26 @@ init_identity();
49767
49812
  init_presence();
49768
49813
  init_projects();
49769
49814
  import chalk7 from "chalk";
49815
+ function buildWhoamiPayload(agent, source, presence, nowMs = Date.now()) {
49816
+ if (!presence) {
49817
+ return {
49818
+ agent,
49819
+ source,
49820
+ online: false,
49821
+ last_seen_at: null,
49822
+ last_seen_ago_seconds: null
49823
+ };
49824
+ }
49825
+ const lastSeenMs = new Date(`${presence.last_seen_at}Z`).getTime();
49826
+ const deltaSeconds = Number.isFinite(lastSeenMs) ? Math.max(0, Math.floor((nowMs - lastSeenMs) / 1000)) : null;
49827
+ return {
49828
+ agent,
49829
+ source,
49830
+ online: presence.online,
49831
+ last_seen_at: presence.last_seen_at,
49832
+ last_seen_ago_seconds: deltaSeconds
49833
+ };
49834
+ }
49770
49835
  function registerAgentCommands(program2) {
49771
49836
  const agents = program2.command("agents").description("Manage agents");
49772
49837
  agents.command("list").description("List all agents with their presence status").option("--online", "Only show online agents").option("--json", "Output as JSON").action((opts) => {
@@ -49911,7 +49976,7 @@ function registerAgentCommands(program2) {
49911
49976
  }
49912
49977
  closeDb();
49913
49978
  });
49914
- program2.command("whoami").description("Show current agent identity and online status").option("--from <agent>", "Explicit agent identity").action((opts) => {
49979
+ program2.command("whoami").description("Show current agent identity and online status").option("--from <agent>", "Explicit agent identity").option("--json", "Output as JSON").action((opts) => {
49915
49980
  const envValue = process.env.CONVERSATIONS_AGENT_ID?.trim();
49916
49981
  const agent = resolveIdentity(opts.from);
49917
49982
  let source;
@@ -49927,11 +49992,15 @@ function registerAgentCommands(program2) {
49927
49992
  source = `auto-generated (${agentIdFile})`;
49928
49993
  }
49929
49994
  const presence = getPresence(agent);
49995
+ const payload = buildWhoamiPayload(agent, source, presence);
49996
+ if (opts.json) {
49997
+ console.log(JSON.stringify(payload, null, 2));
49998
+ closeDb();
49999
+ return;
50000
+ }
49930
50001
  let onlineStatus;
49931
50002
  if (presence && presence.online) {
49932
- const lastSeenMs = new Date(presence.last_seen_at + "Z").getTime();
49933
- const agoMs = Date.now() - lastSeenMs;
49934
- const agoSec = Math.floor(agoMs / 1000);
50003
+ const agoSec = payload.last_seen_ago_seconds ?? 0;
49935
50004
  const agoStr = agoSec < 60 ? `${agoSec}s ago` : `${Math.floor(agoSec / 60)}m ago`;
49936
50005
  onlineStatus = chalk7.green(`yes`) + chalk7.dim(` (last seen ${agoStr})`);
49937
50006
  } else if (presence) {
package/bin/mcp.js CHANGED
@@ -42094,6 +42094,18 @@ function listProjects(opts) {
42094
42094
  params.push(opts.status);
42095
42095
  }
42096
42096
  const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
42097
+ let pagination = "";
42098
+ if (typeof opts?.limit === "number" && Number.isFinite(opts.limit) && opts.limit > 0) {
42099
+ pagination += " LIMIT ?";
42100
+ params.push(Math.floor(opts.limit));
42101
+ }
42102
+ if (typeof opts?.offset === "number" && Number.isFinite(opts.offset) && opts.offset >= 0) {
42103
+ if (!pagination.includes("LIMIT")) {
42104
+ pagination += " LIMIT -1";
42105
+ }
42106
+ pagination += " OFFSET ?";
42107
+ params.push(Math.floor(opts.offset));
42108
+ }
42097
42109
  const rows = db2.prepare(`
42098
42110
  SELECT
42099
42111
  p.*,
@@ -42101,6 +42113,7 @@ function listProjects(opts) {
42101
42113
  FROM projects p
42102
42114
  ${where}
42103
42115
  ORDER BY p.name ASC
42116
+ ${pagination}
42104
42117
  `).all(...params);
42105
42118
  return rows.map((row) => ({
42106
42119
  ...parseProject(row),
@@ -44157,6 +44170,16 @@ async function tmuxSend(target, message, opts = {}) {
44157
44170
  function sleep2(ms) {
44158
44171
  return new Promise((resolve) => setTimeout(resolve, ms));
44159
44172
  }
44173
+ function parseOptionalDelayMs(value) {
44174
+ return typeof value === "number" && value >= 0 ? value : undefined;
44175
+ }
44176
+ function normalizeTmuxTargets(rawTargets) {
44177
+ const normalized = rawTargets.map((target) => String(target).trim());
44178
+ if (normalized.some((target) => target.length === 0)) {
44179
+ throw new Error("targets must not contain empty values");
44180
+ }
44181
+ return normalized;
44182
+ }
44160
44183
  function registerTmuxTools(server) {
44161
44184
  server.registerTool("tmux_send", {
44162
44185
  description: "Send a message to a tmux window (e.g. another agent's Claude Code session). " + "Pastes the text literally, waits for the pane to be idle, hits Enter, then verifies the message was submitted. " + "Retries up to N times on failure.",
@@ -44177,7 +44200,7 @@ function registerTmuxTools(server) {
44177
44200
  }
44178
44201
  try {
44179
44202
  const result = await tmuxSend(target.trim(), message, {
44180
- delayMs: typeof delay_ms === "number" && delay_ms > 0 ? delay_ms : undefined,
44203
+ delayMs: parseOptionalDelayMs(delay_ms),
44181
44204
  retries: typeof retries === "number" && retries > 0 ? retries : undefined,
44182
44205
  verify: verify !== false
44183
44206
  });
@@ -44209,14 +44232,20 @@ function registerTmuxTools(server) {
44209
44232
  return { content: [{ type: "text", text: "message cannot be empty" }], isError: true };
44210
44233
  }
44211
44234
  const stagger = typeof stagger_ms === "number" && stagger_ms >= 0 ? stagger_ms : 500;
44212
- const results = new Array(targets.length);
44213
- await Promise.all(targets.map(async (rawTarget, i) => {
44214
- const target = String(rawTarget).trim();
44235
+ let normalizedTargets;
44236
+ try {
44237
+ normalizedTargets = normalizeTmuxTargets(targets);
44238
+ } catch (err) {
44239
+ const msg = err instanceof Error ? err.message : String(err);
44240
+ return { content: [{ type: "text", text: msg }], isError: true };
44241
+ }
44242
+ const results = new Array(normalizedTargets.length);
44243
+ await Promise.all(normalizedTargets.map(async (target, i) => {
44215
44244
  if (i > 0 && stagger > 0)
44216
44245
  await sleep2(stagger * i);
44217
44246
  try {
44218
44247
  const result = await tmuxSend(target, message, {
44219
- delayMs: typeof delay_ms === "number" && delay_ms > 0 ? delay_ms : undefined,
44248
+ delayMs: parseOptionalDelayMs(delay_ms),
44220
44249
  retries: typeof retries === "number" && retries > 0 ? retries : undefined,
44221
44250
  verify: verify !== false
44222
44251
  });
@@ -44240,7 +44269,7 @@ function registerTmuxTools(server) {
44240
44269
  // package.json
44241
44270
  var package_default = {
44242
44271
  name: "@hasna/conversations",
44243
- version: "0.2.31",
44272
+ version: "0.2.33",
44244
44273
  description: "Real-time CLI messaging for AI agents",
44245
44274
  type: "module",
44246
44275
  bin: {
@@ -1,2 +1,15 @@
1
1
  import type { Command } from "commander";
2
+ type PresenceView = {
3
+ online: boolean;
4
+ last_seen_at: string;
5
+ } | null;
6
+ export type WhoamiPayload = {
7
+ agent: string;
8
+ source: string;
9
+ online: boolean;
10
+ last_seen_at: string | null;
11
+ last_seen_ago_seconds: number | null;
12
+ };
13
+ export declare function buildWhoamiPayload(agent: string, source: string, presence: PresenceView, nowMs?: number): WhoamiPayload;
2
14
  export declare function registerAgentCommands(program: Command): void;
15
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -1,2 +1,3 @@
1
1
  import type { Command } from "commander";
2
+ export declare function requireDeleteConfirmation(confirmed?: boolean): void;
2
3
  export declare function registerProjectCommands(program: Command): void;
@@ -0,0 +1 @@
1
+ export {};
package/dist/index.js CHANGED
@@ -12428,6 +12428,18 @@ function listProjects(opts) {
12428
12428
  params.push(opts.status);
12429
12429
  }
12430
12430
  const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
12431
+ let pagination = "";
12432
+ if (typeof opts?.limit === "number" && Number.isFinite(opts.limit) && opts.limit > 0) {
12433
+ pagination += " LIMIT ?";
12434
+ params.push(Math.floor(opts.limit));
12435
+ }
12436
+ if (typeof opts?.offset === "number" && Number.isFinite(opts.offset) && opts.offset >= 0) {
12437
+ if (!pagination.includes("LIMIT")) {
12438
+ pagination += " LIMIT -1";
12439
+ }
12440
+ pagination += " OFFSET ?";
12441
+ params.push(Math.floor(opts.offset));
12442
+ }
12431
12443
  const rows = db2.prepare(`
12432
12444
  SELECT
12433
12445
  p.*,
@@ -12435,6 +12447,7 @@ function listProjects(opts) {
12435
12447
  FROM projects p
12436
12448
  ${where}
12437
12449
  ORDER BY p.name ASC
12450
+ ${pagination}
12438
12451
  `).all(...params);
12439
12452
  return rows.map((row) => ({
12440
12453
  ...parseProject(row),
@@ -11,6 +11,8 @@ export declare function createProject(opts: {
11
11
  }): Project;
12
12
  export declare function listProjects(opts?: {
13
13
  status?: "active" | "archived";
14
+ limit?: number;
15
+ offset?: number;
14
16
  }): ProjectInfo[];
15
17
  export declare function getProject(id: string): ProjectInfo | null;
16
18
  export declare function getProjectByName(name: string): ProjectInfo | null;
@@ -5,4 +5,6 @@
5
5
  * smart paste → wait → Enter → verify behavior.
6
6
  */
7
7
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8
+ export declare function parseOptionalDelayMs(value: unknown): number | undefined;
9
+ export declare function normalizeTmuxTargets(rawTargets: unknown[]): string[];
8
10
  export declare function registerTmuxTools(server: McpServer): void;
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/conversations",
3
- "version": "0.2.31",
3
+ "version": "0.2.33",
4
4
  "description": "Real-time CLI messaging for AI agents",
5
5
  "type": "module",
6
6
  "bin": {