@hasna/conversations 0.2.30 → 0.2.32

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.30",
14931
+ version: "0.2.32",
14932
14932
  description: "Real-time CLI messaging for AI agents",
14933
14933
  type: "module",
14934
14934
  bin: {
@@ -15715,8 +15715,23 @@ import { execSync } from "child_process";
15715
15715
  function sleep(ms) {
15716
15716
  return new Promise((resolve) => setTimeout(resolve, ms));
15717
15717
  }
15718
+ function countLines(message) {
15719
+ const matches = message.match(/\r?\n/g);
15720
+ return (matches?.length ?? 0) + 1;
15721
+ }
15722
+ function getDefaultDelayMs(message) {
15723
+ const byLength = message.length * 1.5;
15724
+ const byLines = countLines(message) * 10;
15725
+ return Math.max(25, Math.min(1500, Math.round(byLength + byLines)));
15726
+ }
15727
+ function getVerifyPauseMs(message) {
15728
+ return message.length <= 120 ? 50 : 100;
15729
+ }
15730
+ function getRetryBackoffMs(attempt) {
15731
+ return Math.min(500, 100 * attempt);
15732
+ }
15718
15733
  async function tmuxSend(target, message, opts = {}) {
15719
- const delay = opts.delayMs ?? Math.max(12000, message.length * 50);
15734
+ const delay = opts.delayMs ?? getDefaultDelayMs(message);
15720
15735
  const maxRetries = opts.retries ?? 3;
15721
15736
  const verify = opts.verify !== false;
15722
15737
  for (let attempt = 1;attempt <= maxRetries; attempt++) {
@@ -15725,22 +15740,23 @@ async function tmuxSend(target, message, opts = {}) {
15725
15740
  execSync(`tmux send-keys -t ${JSON.stringify(target)} Enter`);
15726
15741
  if (!verify)
15727
15742
  return { success: true, attempts: attempt };
15728
- await sleep(500);
15743
+ await sleep(getVerifyPauseMs(message));
15729
15744
  const pane = execSync(`tmux capture-pane -t ${JSON.stringify(target)} -p`).toString();
15730
15745
  const lastLines = pane.split(`
15731
- `).slice(-3).join(`
15746
+ `).slice(-6).join(`
15732
15747
  `);
15733
- if (!lastLines.includes(message.slice(0, 20))) {
15748
+ const marker = message.slice(0, Math.min(32, message.length));
15749
+ if (!lastLines.includes(marker)) {
15734
15750
  return { success: true, attempts: attempt };
15735
15751
  }
15736
15752
  if (attempt < maxRetries)
15737
- await sleep(2000);
15753
+ await sleep(getRetryBackoffMs(attempt));
15738
15754
  }
15739
15755
  return { success: false, attempts: maxRetries };
15740
15756
  }
15741
15757
  function registerTmuxCommands(program2) {
15742
15758
  const tmux = program2.command("tmux").description("Dispatch messages to tmux windows (Claude Code sessions)");
15743
- tmux.command("send").description("Send a message to a tmux window with paste+wait+Enter+verify").requiredOption("--target <target>", "Tmux target: session:window or session:window.pane").requiredOption("--message <text>", "Message text to send").option("--delay <ms>", "Wait time (ms) after paste before hitting Enter (default: max(12000, len*50))", parseInt).option("--retries <n>", "Max retry attempts (default: 3)", parseInt).option("--no-verify", "Skip verification after sending").option("--json", "Output result as JSON").action(async (opts) => {
15759
+ tmux.command("send").description("Send a message to a tmux window with paste+wait+Enter+verify").requiredOption("--target <target>", "Tmux target: session:window or session:window.pane").requiredOption("--message <text>", "Message text to send").option("--delay <ms>", "Wait time (ms) after paste before hitting Enter (default: adaptive 25-1500ms)", parseInt).option("--retries <n>", "Max retry attempts (default: 3)", parseInt).option("--no-verify", "Skip verification after sending").option("--json", "Output result as JSON").action(async (opts) => {
15744
15760
  const target = opts.target.trim();
15745
15761
  const message = opts.message;
15746
15762
  if (!target) {
@@ -15775,10 +15791,10 @@ function registerTmuxCommands(program2) {
15775
15791
  process.exit(1);
15776
15792
  }
15777
15793
  });
15778
- tmux.command("broadcast").description("Send the same message to multiple tmux windows").requiredOption("--targets <list>", "Comma-separated list of tmux targets").requiredOption("--message <text>", "Message text to send").option("--delay <ms>", "Wait time (ms) after paste before Enter (default: max(12000, len*50))", parseInt).option("--stagger <ms>", "Delay (ms) between each target (default: 0)", parseInt).option("--retries <n>", "Max retry attempts per target (default: 3)", parseInt).option("--no-verify", "Skip verification after sending").option("--json", "Output results as JSON").action(async (opts) => {
15794
+ tmux.command("broadcast").description("Send the same message to multiple tmux windows").requiredOption("--targets <list>", "Comma-separated list of tmux targets").requiredOption("--message <text>", "Message text to send").option("--delay <ms>", "Wait time (ms) after paste before Enter (default: adaptive 25-1500ms)", parseInt).option("--stagger <ms>", "Delay (ms) between each target (default: 500)", parseInt).option("--retries <n>", "Max retry attempts per target (default: 3)", parseInt).option("--no-verify", "Skip verification after sending").option("--json", "Output results as JSON").action(async (opts) => {
15779
15795
  const targets = opts.targets.split(",").map((t) => t.trim()).filter(Boolean);
15780
15796
  const message = opts.message;
15781
- const stagger = Number.isFinite(opts.stagger) && opts.stagger > 0 ? opts.stagger : 0;
15797
+ const stagger = Number.isFinite(opts.stagger) && opts.stagger >= 0 ? opts.stagger : 500;
15782
15798
  if (targets.length === 0) {
15783
15799
  console.error(chalk9.red("--targets must be a non-empty comma-separated list."));
15784
15800
  process.exit(1);
@@ -15787,18 +15803,17 @@ function registerTmuxCommands(program2) {
15787
15803
  console.error(chalk9.red("--message cannot be empty."));
15788
15804
  process.exit(1);
15789
15805
  }
15790
- const results = [];
15791
- for (let i = 0;i < targets.length; i++) {
15792
- const target = targets[i];
15806
+ const results = new Array(targets.length);
15807
+ await Promise.all(targets.map(async (target, i) => {
15793
15808
  if (i > 0 && stagger > 0)
15794
- await sleep(stagger);
15809
+ await sleep(stagger * i);
15795
15810
  try {
15796
15811
  const result = await tmuxSend(target, message, {
15797
15812
  delayMs: Number.isFinite(opts.delay) ? opts.delay : undefined,
15798
15813
  retries: Number.isFinite(opts.retries) ? opts.retries : undefined,
15799
15814
  verify: opts.verify !== false
15800
15815
  });
15801
- results.push({ target, ...result });
15816
+ results[i] = { target, ...result };
15802
15817
  if (!opts.json) {
15803
15818
  if (result.success) {
15804
15819
  console.log(chalk9.green(` \u2713 ${target}`) + chalk9.dim(` (attempt ${result.attempts})`));
@@ -15808,12 +15823,12 @@ function registerTmuxCommands(program2) {
15808
15823
  }
15809
15824
  } catch (err) {
15810
15825
  const errMsg = err instanceof Error ? err.message : String(err);
15811
- results.push({ target, success: false, attempts: 0, error: errMsg });
15826
+ results[i] = { target, success: false, attempts: 0, error: errMsg };
15812
15827
  if (!opts.json) {
15813
15828
  console.log(chalk9.red(` \u2717 ${target}: ${errMsg}`));
15814
15829
  }
15815
15830
  }
15816
- }
15831
+ }));
15817
15832
  const succeeded = results.filter((r) => r.success).length;
15818
15833
  const failed = results.length - succeeded;
15819
15834
  if (opts.json) {
@@ -46954,13 +46969,23 @@ var init_channel = __esm(() => {
46954
46969
  function sleep2(ms) {
46955
46970
  return new Promise((resolve) => setTimeout(resolve, ms));
46956
46971
  }
46972
+ function parseOptionalDelayMs(value) {
46973
+ return typeof value === "number" && value >= 0 ? value : undefined;
46974
+ }
46975
+ function normalizeTmuxTargets(rawTargets) {
46976
+ const normalized = rawTargets.map((target) => String(target).trim());
46977
+ if (normalized.some((target) => target.length === 0)) {
46978
+ throw new Error("targets must not contain empty values");
46979
+ }
46980
+ return normalized;
46981
+ }
46957
46982
  function registerTmuxTools(server) {
46958
46983
  server.registerTool("tmux_send", {
46959
46984
  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.",
46960
46985
  inputSchema: {
46961
46986
  target: exports_external2.string().describe("Tmux target: session:window or session:window.pane (e.g. platform-alumia:1)"),
46962
46987
  message: exports_external2.string().describe("Message text to send"),
46963
- delay_ms: exports_external2.coerce.number().optional().describe("Wait time (ms) after paste before hitting Enter. Default: max(12000, message_length * 50)"),
46988
+ delay_ms: exports_external2.coerce.number().optional().describe("Wait time (ms) after paste before hitting Enter. Default: adaptive 25-1500ms"),
46964
46989
  retries: exports_external2.coerce.number().optional().describe("Max retry attempts (default: 3)"),
46965
46990
  verify: exports_external2.coerce.boolean().optional().describe("Verify message was submitted after Enter (default: true)")
46966
46991
  }
@@ -46974,7 +46999,7 @@ function registerTmuxTools(server) {
46974
46999
  }
46975
47000
  try {
46976
47001
  const result = await tmuxSend(target.trim(), message, {
46977
- delayMs: typeof delay_ms === "number" && delay_ms > 0 ? delay_ms : undefined,
47002
+ delayMs: parseOptionalDelayMs(delay_ms),
46978
47003
  retries: typeof retries === "number" && retries > 0 ? retries : undefined,
46979
47004
  verify: verify !== false
46980
47005
  });
@@ -46992,8 +47017,8 @@ function registerTmuxTools(server) {
46992
47017
  inputSchema: {
46993
47018
  targets: exports_external2.array(exports_external2.string()).describe("List of tmux targets (session:window or session:window.pane)"),
46994
47019
  message: exports_external2.string().describe("Message text to send to all targets"),
46995
- delay_ms: exports_external2.coerce.number().optional().describe("Wait time (ms) after paste before Enter. Default: max(12000, message_length * 50)"),
46996
- stagger_ms: exports_external2.coerce.number().optional().describe("Delay (ms) between sending to each target (default: 0)"),
47020
+ delay_ms: exports_external2.coerce.number().optional().describe("Wait time (ms) after paste before Enter. Default: adaptive 25-1500ms"),
47021
+ stagger_ms: exports_external2.coerce.number().optional().describe("Delay (ms) between sending to each target (default: 500)"),
46997
47022
  retries: exports_external2.coerce.number().optional().describe("Max retry attempts per target (default: 3)"),
46998
47023
  verify: exports_external2.coerce.boolean().optional().describe("Verify each message was submitted (default: true)")
46999
47024
  }
@@ -47005,24 +47030,30 @@ function registerTmuxTools(server) {
47005
47030
  if (!message || !message.trim()) {
47006
47031
  return { content: [{ type: "text", text: "message cannot be empty" }], isError: true };
47007
47032
  }
47008
- const stagger = typeof stagger_ms === "number" && stagger_ms > 0 ? stagger_ms : 0;
47009
- const results = [];
47010
- for (let i = 0;i < targets.length; i++) {
47011
- const target = targets[i].trim();
47033
+ const stagger = typeof stagger_ms === "number" && stagger_ms >= 0 ? stagger_ms : 500;
47034
+ let normalizedTargets;
47035
+ try {
47036
+ normalizedTargets = normalizeTmuxTargets(targets);
47037
+ } catch (err) {
47038
+ const msg = err instanceof Error ? err.message : String(err);
47039
+ return { content: [{ type: "text", text: msg }], isError: true };
47040
+ }
47041
+ const results = new Array(normalizedTargets.length);
47042
+ await Promise.all(normalizedTargets.map(async (target, i) => {
47012
47043
  if (i > 0 && stagger > 0)
47013
- await sleep2(stagger);
47044
+ await sleep2(stagger * i);
47014
47045
  try {
47015
47046
  const result = await tmuxSend(target, message, {
47016
- delayMs: typeof delay_ms === "number" && delay_ms > 0 ? delay_ms : undefined,
47047
+ delayMs: parseOptionalDelayMs(delay_ms),
47017
47048
  retries: typeof retries === "number" && retries > 0 ? retries : undefined,
47018
47049
  verify: verify !== false
47019
47050
  });
47020
- results.push({ target, ...result });
47051
+ results[i] = { target, ...result };
47021
47052
  } catch (err) {
47022
47053
  const errMsg = err instanceof Error ? err.message : String(err);
47023
- results.push({ target, success: false, attempts: 0, error: errMsg });
47054
+ results[i] = { target, success: false, attempts: 0, error: errMsg };
47024
47055
  }
47025
- }
47056
+ }));
47026
47057
  const succeeded = results.filter((r) => r.success).length;
47027
47058
  const failed = results.length - succeeded;
47028
47059
  return {
package/bin/mcp.js CHANGED
@@ -44113,8 +44113,23 @@ import { execSync } from "child_process";
44113
44113
  function sleep(ms) {
44114
44114
  return new Promise((resolve) => setTimeout(resolve, ms));
44115
44115
  }
44116
+ function countLines(message) {
44117
+ const matches = message.match(/\r?\n/g);
44118
+ return (matches?.length ?? 0) + 1;
44119
+ }
44120
+ function getDefaultDelayMs(message) {
44121
+ const byLength = message.length * 1.5;
44122
+ const byLines = countLines(message) * 10;
44123
+ return Math.max(25, Math.min(1500, Math.round(byLength + byLines)));
44124
+ }
44125
+ function getVerifyPauseMs(message) {
44126
+ return message.length <= 120 ? 50 : 100;
44127
+ }
44128
+ function getRetryBackoffMs(attempt) {
44129
+ return Math.min(500, 100 * attempt);
44130
+ }
44116
44131
  async function tmuxSend(target, message, opts = {}) {
44117
- const delay = opts.delayMs ?? Math.max(12000, message.length * 50);
44132
+ const delay = opts.delayMs ?? getDefaultDelayMs(message);
44118
44133
  const maxRetries = opts.retries ?? 3;
44119
44134
  const verify = opts.verify !== false;
44120
44135
  for (let attempt = 1;attempt <= maxRetries; attempt++) {
@@ -44123,16 +44138,17 @@ async function tmuxSend(target, message, opts = {}) {
44123
44138
  execSync(`tmux send-keys -t ${JSON.stringify(target)} Enter`);
44124
44139
  if (!verify)
44125
44140
  return { success: true, attempts: attempt };
44126
- await sleep(500);
44141
+ await sleep(getVerifyPauseMs(message));
44127
44142
  const pane = execSync(`tmux capture-pane -t ${JSON.stringify(target)} -p`).toString();
44128
44143
  const lastLines = pane.split(`
44129
- `).slice(-3).join(`
44144
+ `).slice(-6).join(`
44130
44145
  `);
44131
- if (!lastLines.includes(message.slice(0, 20))) {
44146
+ const marker = message.slice(0, Math.min(32, message.length));
44147
+ if (!lastLines.includes(marker)) {
44132
44148
  return { success: true, attempts: attempt };
44133
44149
  }
44134
44150
  if (attempt < maxRetries)
44135
- await sleep(2000);
44151
+ await sleep(getRetryBackoffMs(attempt));
44136
44152
  }
44137
44153
  return { success: false, attempts: maxRetries };
44138
44154
  }
@@ -44141,13 +44157,23 @@ async function tmuxSend(target, message, opts = {}) {
44141
44157
  function sleep2(ms) {
44142
44158
  return new Promise((resolve) => setTimeout(resolve, ms));
44143
44159
  }
44160
+ function parseOptionalDelayMs(value) {
44161
+ return typeof value === "number" && value >= 0 ? value : undefined;
44162
+ }
44163
+ function normalizeTmuxTargets(rawTargets) {
44164
+ const normalized = rawTargets.map((target) => String(target).trim());
44165
+ if (normalized.some((target) => target.length === 0)) {
44166
+ throw new Error("targets must not contain empty values");
44167
+ }
44168
+ return normalized;
44169
+ }
44144
44170
  function registerTmuxTools(server) {
44145
44171
  server.registerTool("tmux_send", {
44146
44172
  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.",
44147
44173
  inputSchema: {
44148
44174
  target: exports_external.string().describe("Tmux target: session:window or session:window.pane (e.g. platform-alumia:1)"),
44149
44175
  message: exports_external.string().describe("Message text to send"),
44150
- delay_ms: exports_external.coerce.number().optional().describe("Wait time (ms) after paste before hitting Enter. Default: max(12000, message_length * 50)"),
44176
+ delay_ms: exports_external.coerce.number().optional().describe("Wait time (ms) after paste before hitting Enter. Default: adaptive 25-1500ms"),
44151
44177
  retries: exports_external.coerce.number().optional().describe("Max retry attempts (default: 3)"),
44152
44178
  verify: exports_external.coerce.boolean().optional().describe("Verify message was submitted after Enter (default: true)")
44153
44179
  }
@@ -44161,7 +44187,7 @@ function registerTmuxTools(server) {
44161
44187
  }
44162
44188
  try {
44163
44189
  const result = await tmuxSend(target.trim(), message, {
44164
- delayMs: typeof delay_ms === "number" && delay_ms > 0 ? delay_ms : undefined,
44190
+ delayMs: parseOptionalDelayMs(delay_ms),
44165
44191
  retries: typeof retries === "number" && retries > 0 ? retries : undefined,
44166
44192
  verify: verify !== false
44167
44193
  });
@@ -44179,8 +44205,8 @@ function registerTmuxTools(server) {
44179
44205
  inputSchema: {
44180
44206
  targets: exports_external.array(exports_external.string()).describe("List of tmux targets (session:window or session:window.pane)"),
44181
44207
  message: exports_external.string().describe("Message text to send to all targets"),
44182
- delay_ms: exports_external.coerce.number().optional().describe("Wait time (ms) after paste before Enter. Default: max(12000, message_length * 50)"),
44183
- stagger_ms: exports_external.coerce.number().optional().describe("Delay (ms) between sending to each target (default: 0)"),
44208
+ delay_ms: exports_external.coerce.number().optional().describe("Wait time (ms) after paste before Enter. Default: adaptive 25-1500ms"),
44209
+ stagger_ms: exports_external.coerce.number().optional().describe("Delay (ms) between sending to each target (default: 500)"),
44184
44210
  retries: exports_external.coerce.number().optional().describe("Max retry attempts per target (default: 3)"),
44185
44211
  verify: exports_external.coerce.boolean().optional().describe("Verify each message was submitted (default: true)")
44186
44212
  }
@@ -44192,24 +44218,30 @@ function registerTmuxTools(server) {
44192
44218
  if (!message || !message.trim()) {
44193
44219
  return { content: [{ type: "text", text: "message cannot be empty" }], isError: true };
44194
44220
  }
44195
- const stagger = typeof stagger_ms === "number" && stagger_ms > 0 ? stagger_ms : 0;
44196
- const results = [];
44197
- for (let i = 0;i < targets.length; i++) {
44198
- const target = targets[i].trim();
44221
+ const stagger = typeof stagger_ms === "number" && stagger_ms >= 0 ? stagger_ms : 500;
44222
+ let normalizedTargets;
44223
+ try {
44224
+ normalizedTargets = normalizeTmuxTargets(targets);
44225
+ } catch (err) {
44226
+ const msg = err instanceof Error ? err.message : String(err);
44227
+ return { content: [{ type: "text", text: msg }], isError: true };
44228
+ }
44229
+ const results = new Array(normalizedTargets.length);
44230
+ await Promise.all(normalizedTargets.map(async (target, i) => {
44199
44231
  if (i > 0 && stagger > 0)
44200
- await sleep2(stagger);
44232
+ await sleep2(stagger * i);
44201
44233
  try {
44202
44234
  const result = await tmuxSend(target, message, {
44203
- delayMs: typeof delay_ms === "number" && delay_ms > 0 ? delay_ms : undefined,
44235
+ delayMs: parseOptionalDelayMs(delay_ms),
44204
44236
  retries: typeof retries === "number" && retries > 0 ? retries : undefined,
44205
44237
  verify: verify !== false
44206
44238
  });
44207
- results.push({ target, ...result });
44239
+ results[i] = { target, ...result };
44208
44240
  } catch (err) {
44209
44241
  const errMsg = err instanceof Error ? err.message : String(err);
44210
- results.push({ target, success: false, attempts: 0, error: errMsg });
44242
+ results[i] = { target, success: false, attempts: 0, error: errMsg };
44211
44243
  }
44212
- }
44244
+ }));
44213
44245
  const succeeded = results.filter((r) => r.success).length;
44214
44246
  const failed = results.length - succeeded;
44215
44247
  return {
@@ -44224,7 +44256,7 @@ function registerTmuxTools(server) {
44224
44256
  // package.json
44225
44257
  var package_default = {
44226
44258
  name: "@hasna/conversations",
44227
- version: "0.2.30",
44259
+ version: "0.2.32",
44228
44260
  description: "Real-time CLI messaging for AI agents",
44229
44261
  type: "module",
44230
44262
  bin: {
@@ -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.30",
3
+ "version": "0.2.32",
4
4
  "description": "Real-time CLI messaging for AI agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -77,4 +77,4 @@
77
77
  "url": "https://github.com/hasna/conversations/issues"
78
78
  },
79
79
  "homepage": "https://github.com/hasna/conversations#readme"
80
- }
80
+ }