@agent-chat/mention-watcher 0.1.9 → 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 -23
- 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,39 +318,94 @@ async function main() {
|
|
|
198
318
|
`);
|
|
199
319
|
process.stderr.write(`[mention-watcher] Spawning: ${COMMAND} ${CMD_ARGS.join(" ")}
|
|
200
320
|
`);
|
|
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
|
+
};
|
|
201
329
|
let proc;
|
|
202
330
|
try {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
cols,
|
|
206
|
-
rows,
|
|
207
|
-
cwd: WORKSPACE_DIR,
|
|
208
|
-
env: process.env
|
|
209
|
-
});
|
|
331
|
+
const p = pty.spawn(COMMAND, CMD_ARGS, spawnOptions);
|
|
332
|
+
proc = { ...p, mode: "pty" };
|
|
210
333
|
} catch (err) {
|
|
211
|
-
|
|
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}
|
|
212
337
|
`);
|
|
213
|
-
|
|
338
|
+
process.stderr.write(` Run "which ${COMMAND}" in your terminal to confirm it is installed.
|
|
214
339
|
`);
|
|
215
|
-
|
|
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
|
+
}
|
|
216
367
|
}
|
|
217
368
|
proc.onData((data) => {
|
|
218
369
|
process.stdout.write(data);
|
|
219
370
|
lastOutputAt = Date.now();
|
|
220
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
|
+
};
|
|
221
384
|
if (process.stdin.isTTY) {
|
|
222
385
|
process.stdin.setRawMode(true);
|
|
223
386
|
}
|
|
224
387
|
process.stdin.resume();
|
|
225
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
|
+
}
|
|
226
395
|
proc.write(data.toString("binary"));
|
|
227
396
|
});
|
|
228
397
|
process.stdout.on("resize", () => {
|
|
229
398
|
proc.resize(process.stdout.columns || 80, process.stdout.rows || 24);
|
|
230
399
|
});
|
|
231
400
|
proc.onExit(({ exitCode }) => {
|
|
401
|
+
cleanupTerminal();
|
|
232
402
|
process.exit(exitCode);
|
|
233
403
|
});
|
|
404
|
+
process.on("SIGINT", () => {
|
|
405
|
+
proc.kill?.("SIGINT");
|
|
406
|
+
cleanupTerminal();
|
|
407
|
+
process.exit(130);
|
|
408
|
+
});
|
|
234
409
|
await startWsListener(
|
|
235
410
|
jwt,
|
|
236
411
|
// ── mention handler ──────────────────────────────────────────────────────
|