@agent-chat/mention-watcher 0.1.8 → 0.1.10
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/dist/watch.js +198 -28
- package/package.json +1 -1
package/dist/watch.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import * as fs from "fs";
|
|
5
5
|
import * as path from "path";
|
|
6
6
|
import * as os from "os";
|
|
7
|
+
import { spawn } from "child_process";
|
|
7
8
|
import * as pty from "node-pty";
|
|
8
9
|
import { WebSocket } from "ws";
|
|
9
10
|
function loadEnvFile(filePath) {
|
|
@@ -20,14 +21,16 @@ function loadEnvFile(filePath) {
|
|
|
20
21
|
}
|
|
21
22
|
return result;
|
|
22
23
|
}
|
|
23
|
-
function syncMcpToken(workspaceDir, mcpServerName) {
|
|
24
|
-
const
|
|
24
|
+
function syncMcpToken(workspaceDir, mcpServerName, preferredToken) {
|
|
25
|
+
const envVars = loadEnvFile(path.join(workspaceDir, ".env"));
|
|
26
|
+
const envToken = preferredToken ?? process.env.AGENT_TOKEN ?? envVars.AGENT_TOKEN ?? envVars.BOOTSTRAP_AGENT_TOKEN;
|
|
25
27
|
if (!envToken) return;
|
|
26
|
-
const
|
|
28
|
+
const mcpPaths = [
|
|
27
29
|
path.join(workspaceDir, ".mcp.json"),
|
|
28
|
-
path.join(workspaceDir, ".claude", "mcp.json")
|
|
30
|
+
path.join(workspaceDir, ".claude", "mcp.json"),
|
|
31
|
+
path.join(workspaceDir, ".cursor", "mcp.json")
|
|
29
32
|
];
|
|
30
|
-
for (const configPath of
|
|
33
|
+
for (const configPath of mcpPaths) {
|
|
31
34
|
if (!fs.existsSync(configPath)) continue;
|
|
32
35
|
let config;
|
|
33
36
|
try {
|
|
@@ -37,19 +40,53 @@ function syncMcpToken(workspaceDir, mcpServerName) {
|
|
|
37
40
|
`);
|
|
38
41
|
continue;
|
|
39
42
|
}
|
|
40
|
-
const
|
|
41
|
-
if (!
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
const servers = config?.mcpServers;
|
|
44
|
+
if (!servers || typeof servers !== "object") {
|
|
45
|
+
process.stderr.write(`[mention-watcher] No mcpServers in ${configPath}, skipping
|
|
46
|
+
`);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const explicit = servers[mcpServerName];
|
|
50
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
51
|
+
if (explicit) candidates.add(mcpServerName);
|
|
52
|
+
for (const [name, server] of Object.entries(servers)) {
|
|
53
|
+
const command = String(server?.command ?? "");
|
|
54
|
+
const args = Array.isArray(server?.args) ? server.args.map((a) => String(a)).join(" ") : "";
|
|
55
|
+
const signature = `${command} ${args}`;
|
|
56
|
+
if (signature.includes("agent-gateway") || signature.includes("@agent-chat")) {
|
|
57
|
+
candidates.add(name);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (candidates.size === 0) {
|
|
61
|
+
process.stderr.write(
|
|
62
|
+
`[mention-watcher] No matching MCP server in ${configPath} (expected "${mcpServerName}")
|
|
63
|
+
`
|
|
64
|
+
);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
let changed = false;
|
|
68
|
+
let updatedCount = 0;
|
|
69
|
+
for (const name of candidates) {
|
|
70
|
+
const server = servers[name] ?? {};
|
|
71
|
+
server.env = server.env ?? {};
|
|
72
|
+
const tokenBefore = server.env.AGENT_TOKEN;
|
|
73
|
+
const bootstrapBefore = server.env.BOOTSTRAP_AGENT_TOKEN;
|
|
74
|
+
if (tokenBefore !== envToken || bootstrapBefore !== envToken) {
|
|
75
|
+
server.env.AGENT_TOKEN = envToken;
|
|
76
|
+
server.env.BOOTSTRAP_AGENT_TOKEN = envToken;
|
|
77
|
+
servers[name] = server;
|
|
78
|
+
changed = true;
|
|
79
|
+
}
|
|
80
|
+
updatedCount += 1;
|
|
81
|
+
}
|
|
82
|
+
if (!changed) {
|
|
44
83
|
process.stderr.write(`[mention-watcher] MCP token up-to-date: ${configPath}
|
|
45
84
|
`);
|
|
46
85
|
continue;
|
|
47
86
|
}
|
|
48
|
-
server.env = server.env ?? {};
|
|
49
|
-
server.env.AGENT_TOKEN = envToken;
|
|
50
87
|
try {
|
|
51
88
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
52
|
-
process.stderr.write(`[mention-watcher] MCP token synced: ${configPath}
|
|
89
|
+
process.stderr.write(`[mention-watcher] MCP token synced (${updatedCount} server): ${configPath}
|
|
53
90
|
`);
|
|
54
91
|
} catch (err) {
|
|
55
92
|
process.stderr.write(`[mention-watcher] Failed to write ${configPath}: ${err.message}
|
|
@@ -57,6 +94,75 @@ function syncMcpToken(workspaceDir, mcpServerName) {
|
|
|
57
94
|
}
|
|
58
95
|
}
|
|
59
96
|
}
|
|
97
|
+
function buildPtyEnv(env) {
|
|
98
|
+
const out = {};
|
|
99
|
+
for (const [k, v] of Object.entries(env)) {
|
|
100
|
+
if (typeof v !== "string") continue;
|
|
101
|
+
if (k.includes("\0") || v.includes("\0")) continue;
|
|
102
|
+
if (/^(npm_|npm_config_|npm_package_|npm_lifecycle_|PNPM_|pnpm_)/.test(k)) continue;
|
|
103
|
+
out[k] = v;
|
|
104
|
+
}
|
|
105
|
+
if (!out.PATH && typeof env.PATH === "string") out.PATH = env.PATH;
|
|
106
|
+
if (!out.HOME && typeof env.HOME === "string") out.HOME = env.HOME;
|
|
107
|
+
if (!out.SHELL && typeof env.SHELL === "string") out.SHELL = env.SHELL;
|
|
108
|
+
if (!out.TERM) out.TERM = "xterm-256color";
|
|
109
|
+
return out;
|
|
110
|
+
}
|
|
111
|
+
function spawnViaScriptPty(command, args, cwd, env) {
|
|
112
|
+
const child = spawn("script", ["-q", "/dev/null", command, ...args], {
|
|
113
|
+
cwd,
|
|
114
|
+
env,
|
|
115
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
116
|
+
});
|
|
117
|
+
return {
|
|
118
|
+
mode: "script-pty",
|
|
119
|
+
write(data) {
|
|
120
|
+
if (!child.stdin.destroyed) child.stdin.write(data);
|
|
121
|
+
},
|
|
122
|
+
onData(cb) {
|
|
123
|
+
child.stdout.on("data", (buf) => cb(buf.toString()));
|
|
124
|
+
child.stderr.on("data", (buf) => cb(buf.toString()));
|
|
125
|
+
},
|
|
126
|
+
onExit(cb) {
|
|
127
|
+
child.on("exit", (code) => cb({ exitCode: code ?? 1 }));
|
|
128
|
+
child.on("error", () => cb({ exitCode: 1 }));
|
|
129
|
+
},
|
|
130
|
+
// Resize is not supported through the `script` wrapper.
|
|
131
|
+
resize() {
|
|
132
|
+
},
|
|
133
|
+
kill(signal) {
|
|
134
|
+
try {
|
|
135
|
+
child.kill(signal ?? "SIGINT");
|
|
136
|
+
} catch {
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function spawnWithoutPty(command, args, cwd, env) {
|
|
142
|
+
const child = spawn(command, args, { cwd, env, stdio: ["pipe", "pipe", "pipe"] });
|
|
143
|
+
return {
|
|
144
|
+
mode: "plain",
|
|
145
|
+
write(data) {
|
|
146
|
+
if (!child.stdin.destroyed) child.stdin.write(data);
|
|
147
|
+
},
|
|
148
|
+
onData(cb) {
|
|
149
|
+
child.stdout.on("data", (buf) => cb(buf.toString()));
|
|
150
|
+
child.stderr.on("data", (buf) => cb(buf.toString()));
|
|
151
|
+
},
|
|
152
|
+
onExit(cb) {
|
|
153
|
+
child.on("exit", (code) => cb({ exitCode: code ?? 1 }));
|
|
154
|
+
child.on("error", () => cb({ exitCode: 1 }));
|
|
155
|
+
},
|
|
156
|
+
resize() {
|
|
157
|
+
},
|
|
158
|
+
kill(signal) {
|
|
159
|
+
try {
|
|
160
|
+
child.kill(signal ?? "SIGINT");
|
|
161
|
+
} catch {
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
60
166
|
var _WORKSPACE = process.env.WORKSPACE_DIR ?? process.cwd();
|
|
61
167
|
{
|
|
62
168
|
const dirsToCheck = [.../* @__PURE__ */ new Set([process.cwd(), _WORKSPACE])];
|
|
@@ -165,6 +271,19 @@ function scheduleInject(proc) {
|
|
|
165
271
|
function drainQueue(proc) {
|
|
166
272
|
if (queue.length === 0) return;
|
|
167
273
|
const prompt = queue.shift();
|
|
274
|
+
if (proc.mode !== "pty") {
|
|
275
|
+
setTimeout(() => {
|
|
276
|
+
proc.write(prompt);
|
|
277
|
+
}, 30);
|
|
278
|
+
setTimeout(() => {
|
|
279
|
+
proc.write("\r");
|
|
280
|
+
setTimeout(() => proc.write("\n"), 20);
|
|
281
|
+
process.stderr.write(`[mention-watcher] Injected mention prompt (${proc.mode})
|
|
282
|
+
`);
|
|
283
|
+
if (queue.length > 0) scheduleInject(proc);
|
|
284
|
+
}, 120);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
168
287
|
proc.write("");
|
|
169
288
|
setTimeout(() => {
|
|
170
289
|
proc.write(prompt);
|
|
@@ -172,6 +291,7 @@ function drainQueue(proc) {
|
|
|
172
291
|
setTimeout(() => {
|
|
173
292
|
proc.write("\r");
|
|
174
293
|
setTimeout(() => proc.write("\n"), 20);
|
|
294
|
+
process.stderr.write("[mention-watcher] Injected mention prompt (pty)\n");
|
|
175
295
|
if (queue.length > 0) scheduleInject(proc);
|
|
176
296
|
}, 200);
|
|
177
297
|
}
|
|
@@ -189,8 +309,8 @@ async function main() {
|
|
|
189
309
|
process.stderr.write(" npx @agent-chat/mention-watcher setup\n");
|
|
190
310
|
process.exit(1);
|
|
191
311
|
}
|
|
192
|
-
syncMcpToken(WORKSPACE_DIR, MCP_NAME);
|
|
193
312
|
const jwt = await resolveJwt();
|
|
313
|
+
syncMcpToken(WORKSPACE_DIR, MCP_NAME, jwt);
|
|
194
314
|
const cols = process.stdout.columns || 80;
|
|
195
315
|
const rows = process.stdout.rows || 24;
|
|
196
316
|
const wdSource = process.env.WORKSPACE_DIR ? "env" : "auto-detected";
|
|
@@ -198,44 +318,94 @@ async function main() {
|
|
|
198
318
|
`);
|
|
199
319
|
process.stderr.write(`[mention-watcher] Spawning: ${COMMAND} ${CMD_ARGS.join(" ")}
|
|
200
320
|
`);
|
|
201
|
-
const
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
321
|
+
const ptyEnv = buildPtyEnv(process.env);
|
|
322
|
+
const spawnOptions = {
|
|
323
|
+
name: "xterm-256color",
|
|
324
|
+
cols,
|
|
325
|
+
rows,
|
|
326
|
+
cwd: WORKSPACE_DIR,
|
|
327
|
+
env: ptyEnv
|
|
328
|
+
};
|
|
206
329
|
let proc;
|
|
207
330
|
try {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
cols,
|
|
211
|
-
rows,
|
|
212
|
-
cwd: WORKSPACE_DIR,
|
|
213
|
-
env: process.env
|
|
214
|
-
});
|
|
331
|
+
const p = pty.spawn(COMMAND, CMD_ARGS, spawnOptions);
|
|
332
|
+
proc = { ...p, mode: "pty" };
|
|
215
333
|
} catch (err) {
|
|
216
|
-
|
|
334
|
+
const msg = String(err?.message ?? err);
|
|
335
|
+
if (!msg.includes("posix_spawnp failed")) {
|
|
336
|
+
process.stderr.write(`[mention-watcher] Failed to spawn "${COMMAND}": ${msg}
|
|
217
337
|
`);
|
|
218
|
-
|
|
338
|
+
process.stderr.write(` Run "which ${COMMAND}" in your terminal to confirm it is installed.
|
|
219
339
|
`);
|
|
220
|
-
|
|
340
|
+
process.exit(1);
|
|
341
|
+
}
|
|
342
|
+
const userShell = process.env.SHELL || "/bin/zsh";
|
|
343
|
+
const quotedArgs = [COMMAND, ...CMD_ARGS].map((a) => `'${a.replace(/'/g, "'\\''")}'`).join(" ");
|
|
344
|
+
const shellCmd = `exec ${quotedArgs}`;
|
|
345
|
+
process.stderr.write(
|
|
346
|
+
`[mention-watcher] Direct spawn failed, retry via shell: ${userShell} -lc "${shellCmd}"
|
|
347
|
+
`
|
|
348
|
+
);
|
|
349
|
+
try {
|
|
350
|
+
const p = pty.spawn(userShell, ["-lc", shellCmd], spawnOptions);
|
|
351
|
+
proc = { ...p, mode: "pty" };
|
|
352
|
+
} catch (shellErr) {
|
|
353
|
+
const shellMsg = String(shellErr?.message ?? shellErr);
|
|
354
|
+
process.stderr.write(`[mention-watcher] Shell fallback failed: ${shellMsg}
|
|
355
|
+
`);
|
|
356
|
+
process.stderr.write("[mention-watcher] Falling back to script PTY mode\n");
|
|
357
|
+
try {
|
|
358
|
+
proc = spawnViaScriptPty(COMMAND, CMD_ARGS, WORKSPACE_DIR, ptyEnv);
|
|
359
|
+
} catch (scriptErr) {
|
|
360
|
+
const scriptMsg = String(scriptErr?.message ?? scriptErr);
|
|
361
|
+
process.stderr.write(`[mention-watcher] Script PTY fallback failed: ${scriptMsg}
|
|
362
|
+
`);
|
|
363
|
+
process.stderr.write("[mention-watcher] Falling back to non-PTY mode\n");
|
|
364
|
+
proc = spawnWithoutPty(COMMAND, CMD_ARGS, WORKSPACE_DIR, ptyEnv);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
221
367
|
}
|
|
222
368
|
proc.onData((data) => {
|
|
223
369
|
process.stdout.write(data);
|
|
224
370
|
lastOutputAt = Date.now();
|
|
225
371
|
});
|
|
372
|
+
let cleanedUp = false;
|
|
373
|
+
const cleanupTerminal = () => {
|
|
374
|
+
if (cleanedUp) return;
|
|
375
|
+
cleanedUp = true;
|
|
376
|
+
if (process.stdin.isTTY) {
|
|
377
|
+
try {
|
|
378
|
+
process.stdin.setRawMode(false);
|
|
379
|
+
} catch {
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
process.stdin.pause();
|
|
383
|
+
};
|
|
226
384
|
if (process.stdin.isTTY) {
|
|
227
385
|
process.stdin.setRawMode(true);
|
|
228
386
|
}
|
|
229
387
|
process.stdin.resume();
|
|
230
388
|
process.stdin.on("data", (data) => {
|
|
389
|
+
if (data.length === 1 && data[0] === 3) {
|
|
390
|
+
proc.kill?.("SIGINT");
|
|
391
|
+
cleanupTerminal();
|
|
392
|
+
process.exit(130);
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
231
395
|
proc.write(data.toString("binary"));
|
|
232
396
|
});
|
|
233
397
|
process.stdout.on("resize", () => {
|
|
234
398
|
proc.resize(process.stdout.columns || 80, process.stdout.rows || 24);
|
|
235
399
|
});
|
|
236
400
|
proc.onExit(({ exitCode }) => {
|
|
401
|
+
cleanupTerminal();
|
|
237
402
|
process.exit(exitCode);
|
|
238
403
|
});
|
|
404
|
+
process.on("SIGINT", () => {
|
|
405
|
+
proc.kill?.("SIGINT");
|
|
406
|
+
cleanupTerminal();
|
|
407
|
+
process.exit(130);
|
|
408
|
+
});
|
|
239
409
|
await startWsListener(
|
|
240
410
|
jwt,
|
|
241
411
|
// ── mention handler ──────────────────────────────────────────────────────
|