agent-coord-mcp 0.3.7 → 0.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-coord-mcp",
3
- "version": "0.3.7",
3
+ "version": "0.3.8",
4
4
  "description": "File-backed MCP server for coordinating multiple AI coding agents (Claude Code, Cursor, Cline, etc.) on the same machine.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -80,16 +80,65 @@ function agentColor(id) {
80
80
 
81
81
  await register();
82
82
 
83
+ // Visual separator above the prompt — delimits "typing area starts here."
84
+ // Embedded in the prompt string itself so readline owns the redraw; no
85
+ // scroll-region gymnastics needed (those don't survive tmux cleanly).
86
+ const TTY = !!process.stdout.isTTY;
87
+ let COLS = process.stdout.columns || 80;
88
+ const sepLine = () => A.dim("─".repeat(Math.max(10, COLS)));
89
+
90
+ // inputAreaReady flips to true after the banner + initial drain finish and
91
+ // we lay down the first separator. From that point say() treats the line
92
+ // above the prompt as a separator slot it owns.
93
+ let inputAreaReady = false;
94
+
95
+ if (TTY) {
96
+ process.stdout.on("resize", () => {
97
+ COLS = process.stdout.columns || 80;
98
+ if (typeof rl !== "undefined") {
99
+ rl.setPrompt(makePrompt());
100
+ rl.prompt(true);
101
+ }
102
+ });
103
+ }
104
+
105
+ const SLASH_COMMANDS = [
106
+ "/dm", "/list", "/who", "/whoami", "/last", "/clear", "/cls",
107
+ "/me", "/help", "/?", "/quit", "/exit",
108
+ ];
109
+
110
+ function completer(line) {
111
+ // Tab-complete slash commands and DM targets.
112
+ if (line.startsWith("/dm ")) {
113
+ const partial = line.slice(4);
114
+ const reg = readJsonSafe(AGENTS_FILE, {});
115
+ const ids = Object.keys(reg).filter((id) => id !== ID && id.startsWith(partial));
116
+ const hits = ids.map((id) => `/dm ${id} `);
117
+ return [hits, line];
118
+ }
119
+ if (line.startsWith("/")) {
120
+ const hits = SLASH_COMMANDS.filter((c) => c.startsWith(line));
121
+ return [hits, line];
122
+ }
123
+ return [[], line];
124
+ }
125
+
83
126
  const rl = readline.createInterface({
84
127
  input: process.stdin,
85
128
  output: process.stdout,
86
129
  prompt: makePrompt(),
130
+ completer,
87
131
  });
88
132
 
89
133
  // Banner — printed once on launch. Keep it tight; this is a CLI, not a poster.
90
134
  printBanner();
91
135
  await drainAndPrint();
92
136
 
137
+ // Lay down the first separator, then activate input-area mode so subsequent
138
+ // say() calls maintain a sep line directly above the prompt.
139
+ process.stdout.write(sepLine() + "\n");
140
+ inputAreaReady = true;
141
+
93
142
  try { watch(INBOX_FILE, () => void drainAndPrint()); } catch {}
94
143
  try { watch(ROOM_FILE, () => void drainAndPrint()); } catch {}
95
144
  try { watch(AGENTS_FILE, () => refreshPrompt()); } catch {}
@@ -104,7 +153,8 @@ rl.on("line", async (line) => {
104
153
  try {
105
154
  if (text === "/quit" || text === "/exit") {
106
155
  await unregister();
107
- say(A.dim("bye."));
156
+ teardownFooter();
157
+ process.stdout.write(A.dim("bye.\n"));
108
158
  process.exit(0);
109
159
  } else if (text === "/help" || text === "/?") {
110
160
  printHelp();
@@ -135,16 +185,24 @@ rl.on("line", async (line) => {
135
185
  } catch (e) {
136
186
  say(A.red(`error: ${e?.message ?? e}`));
137
187
  }
188
+ // After Enter, terminal advanced to a new line. Print a fresh separator
189
+ // there so the next prompt sits below a sep line, maintaining the layout.
190
+ process.stdout.write(sepLine() + "\n");
138
191
  rl.prompt();
139
192
  });
140
193
 
141
194
  process.on("SIGINT", async () => {
142
195
  try { await unregister(); } catch {}
143
- process.stdout.write("\n");
144
- say("bye.");
196
+ teardownFooter();
197
+ process.stdout.write("\n" + A.dim("bye.\n"));
145
198
  process.exit(0);
146
199
  });
147
200
 
201
+ process.on("exit", () => {
202
+ // Final safety net — restore terminal state if we exit via any path.
203
+ teardownFooter();
204
+ });
205
+
148
206
  // ---------- helpers ----------
149
207
 
150
208
  function parseArgs(argv) {
@@ -166,11 +224,17 @@ function sanitize(s) {
166
224
  return s.replace(/[^a-zA-Z0-9._-]/g, "_");
167
225
  }
168
226
 
169
- // Write output without clobbering whatever the user is typing.
170
227
  function say(line) {
171
- readline.clearLine(process.stdout, 0);
172
- readline.cursorTo(process.stdout, 0);
228
+ if (!inputAreaReady) {
229
+ process.stdout.write(line + "\n");
230
+ return;
231
+ }
232
+ // We're on the prompt line. The line above is the current separator.
233
+ // Move up to it, clear it, drop our message there, then a fresh separator,
234
+ // then re-render the prompt on the next line.
235
+ process.stdout.write("\x1b[1A\r\x1b[2K");
173
236
  process.stdout.write(line + "\n");
237
+ process.stdout.write(sepLine() + "\n");
174
238
  if (typeof rl !== "undefined") rl.prompt(true);
175
239
  }
176
240
 
@@ -190,6 +254,9 @@ function makePrompt() {
190
254
  return `${agentColor(ID)(ID)} ${A.dim(`(${peers})`)}${A.dim(">")} `;
191
255
  }
192
256
 
257
+ // No-op stubs kept so the exit paths don't reference deleted functions.
258
+ function teardownFooter() {}
259
+
193
260
  function refreshPrompt() {
194
261
  if (typeof rl === "undefined") return;
195
262
  rl.setPrompt(makePrompt());