@hasna/conversations 0.2.30 → 0.2.31

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.
Files changed (3) hide show
  1. package/bin/index.js +42 -27
  2. package/bin/mcp.js +33 -17
  3. package/package.json +2 -2
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.31",
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) {
@@ -46960,7 +46975,7 @@ function registerTmuxTools(server) {
46960
46975
  inputSchema: {
46961
46976
  target: exports_external2.string().describe("Tmux target: session:window or session:window.pane (e.g. platform-alumia:1)"),
46962
46977
  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)"),
46978
+ delay_ms: exports_external2.coerce.number().optional().describe("Wait time (ms) after paste before hitting Enter. Default: adaptive 25-1500ms"),
46964
46979
  retries: exports_external2.coerce.number().optional().describe("Max retry attempts (default: 3)"),
46965
46980
  verify: exports_external2.coerce.boolean().optional().describe("Verify message was submitted after Enter (default: true)")
46966
46981
  }
@@ -46992,8 +47007,8 @@ function registerTmuxTools(server) {
46992
47007
  inputSchema: {
46993
47008
  targets: exports_external2.array(exports_external2.string()).describe("List of tmux targets (session:window or session:window.pane)"),
46994
47009
  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)"),
47010
+ delay_ms: exports_external2.coerce.number().optional().describe("Wait time (ms) after paste before Enter. Default: adaptive 25-1500ms"),
47011
+ stagger_ms: exports_external2.coerce.number().optional().describe("Delay (ms) between sending to each target (default: 500)"),
46997
47012
  retries: exports_external2.coerce.number().optional().describe("Max retry attempts per target (default: 3)"),
46998
47013
  verify: exports_external2.coerce.boolean().optional().describe("Verify each message was submitted (default: true)")
46999
47014
  }
@@ -47005,24 +47020,24 @@ function registerTmuxTools(server) {
47005
47020
  if (!message || !message.trim()) {
47006
47021
  return { content: [{ type: "text", text: "message cannot be empty" }], isError: true };
47007
47022
  }
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();
47023
+ 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();
47012
47027
  if (i > 0 && stagger > 0)
47013
- await sleep2(stagger);
47028
+ await sleep2(stagger * i);
47014
47029
  try {
47015
47030
  const result = await tmuxSend(target, message, {
47016
47031
  delayMs: typeof delay_ms === "number" && delay_ms > 0 ? delay_ms : undefined,
47017
47032
  retries: typeof retries === "number" && retries > 0 ? retries : undefined,
47018
47033
  verify: verify !== false
47019
47034
  });
47020
- results.push({ target, ...result });
47035
+ results[i] = { target, ...result };
47021
47036
  } catch (err) {
47022
47037
  const errMsg = err instanceof Error ? err.message : String(err);
47023
- results.push({ target, success: false, attempts: 0, error: errMsg });
47038
+ results[i] = { target, success: false, attempts: 0, error: errMsg };
47024
47039
  }
47025
- }
47040
+ }));
47026
47041
  const succeeded = results.filter((r) => r.success).length;
47027
47042
  const failed = results.length - succeeded;
47028
47043
  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
  }
@@ -44147,7 +44163,7 @@ function registerTmuxTools(server) {
44147
44163
  inputSchema: {
44148
44164
  target: exports_external.string().describe("Tmux target: session:window or session:window.pane (e.g. platform-alumia:1)"),
44149
44165
  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)"),
44166
+ delay_ms: exports_external.coerce.number().optional().describe("Wait time (ms) after paste before hitting Enter. Default: adaptive 25-1500ms"),
44151
44167
  retries: exports_external.coerce.number().optional().describe("Max retry attempts (default: 3)"),
44152
44168
  verify: exports_external.coerce.boolean().optional().describe("Verify message was submitted after Enter (default: true)")
44153
44169
  }
@@ -44179,8 +44195,8 @@ function registerTmuxTools(server) {
44179
44195
  inputSchema: {
44180
44196
  targets: exports_external.array(exports_external.string()).describe("List of tmux targets (session:window or session:window.pane)"),
44181
44197
  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)"),
44198
+ delay_ms: exports_external.coerce.number().optional().describe("Wait time (ms) after paste before Enter. Default: adaptive 25-1500ms"),
44199
+ stagger_ms: exports_external.coerce.number().optional().describe("Delay (ms) between sending to each target (default: 500)"),
44184
44200
  retries: exports_external.coerce.number().optional().describe("Max retry attempts per target (default: 3)"),
44185
44201
  verify: exports_external.coerce.boolean().optional().describe("Verify each message was submitted (default: true)")
44186
44202
  }
@@ -44192,24 +44208,24 @@ function registerTmuxTools(server) {
44192
44208
  if (!message || !message.trim()) {
44193
44209
  return { content: [{ type: "text", text: "message cannot be empty" }], isError: true };
44194
44210
  }
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();
44211
+ 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();
44199
44215
  if (i > 0 && stagger > 0)
44200
- await sleep2(stagger);
44216
+ await sleep2(stagger * i);
44201
44217
  try {
44202
44218
  const result = await tmuxSend(target, message, {
44203
44219
  delayMs: typeof delay_ms === "number" && delay_ms > 0 ? delay_ms : undefined,
44204
44220
  retries: typeof retries === "number" && retries > 0 ? retries : undefined,
44205
44221
  verify: verify !== false
44206
44222
  });
44207
- results.push({ target, ...result });
44223
+ results[i] = { target, ...result };
44208
44224
  } catch (err) {
44209
44225
  const errMsg = err instanceof Error ? err.message : String(err);
44210
- results.push({ target, success: false, attempts: 0, error: errMsg });
44226
+ results[i] = { target, success: false, attempts: 0, error: errMsg };
44211
44227
  }
44212
- }
44228
+ }));
44213
44229
  const succeeded = results.filter((r) => r.success).length;
44214
44230
  const failed = results.length - succeeded;
44215
44231
  return {
@@ -44224,7 +44240,7 @@ function registerTmuxTools(server) {
44224
44240
  // package.json
44225
44241
  var package_default = {
44226
44242
  name: "@hasna/conversations",
44227
- version: "0.2.30",
44243
+ version: "0.2.31",
44228
44244
  description: "Real-time CLI messaging for AI agents",
44229
44245
  type: "module",
44230
44246
  bin: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/conversations",
3
- "version": "0.2.30",
3
+ "version": "0.2.31",
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
+ }