lensmcp 1.16.24 → 1.16.26
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/bundled/dashboard.js +23 -0
- package/bundled/main.js +15 -0
- package/lib/cli.d.ts.map +1 -1
- package/lib/cli.js +178 -2
- package/package.json +2 -2
- package/plugin/.claude-plugin/plugin.json +1 -1
package/bundled/dashboard.js
CHANGED
|
@@ -20162,7 +20162,22 @@ function buildFlowDetail(rawEvents) {
|
|
|
20162
20162
|
return { events: rich };
|
|
20163
20163
|
}
|
|
20164
20164
|
|
|
20165
|
+
// servers/lensmcp-mcp/dist/parent-watchdog.js
|
|
20166
|
+
function watchParentGateway(label) {
|
|
20167
|
+
const parentPid = Number(process.env["LENSMCP_PARENT_PID"] ?? 0);
|
|
20168
|
+
if (!Number.isInteger(parentPid) || parentPid <= 0)
|
|
20169
|
+
return;
|
|
20170
|
+
const timer = setInterval(() => {
|
|
20171
|
+
if (process.ppid !== parentPid) {
|
|
20172
|
+
console.error(`[lensmcp] ${label}: parent gateway (pid ${parentPid}) is gone \u2014 exiting to free the port.`);
|
|
20173
|
+
process.exit(0);
|
|
20174
|
+
}
|
|
20175
|
+
}, 5e3);
|
|
20176
|
+
timer.unref();
|
|
20177
|
+
}
|
|
20178
|
+
|
|
20165
20179
|
// servers/lensmcp-mcp/dist/dashboard.js
|
|
20180
|
+
watchParentGateway("dashboard");
|
|
20166
20181
|
var { session } = createLensSession();
|
|
20167
20182
|
var PORT = Number(process.env["LENSMCP_DASHBOARD_PORT"] ?? 4321);
|
|
20168
20183
|
var DASH_BASE = (process.env["LENSMCP_DASHBOARD_BASE"] ?? "").replace(/\/+$/, "");
|
|
@@ -20544,6 +20559,14 @@ var server = createServer2((req, res) => {
|
|
|
20544
20559
|
res.writeHead(200, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" });
|
|
20545
20560
|
res.end(PAGE);
|
|
20546
20561
|
});
|
|
20562
|
+
server.on("error", (err) => {
|
|
20563
|
+
const code = err.code;
|
|
20564
|
+
if (code === "EADDRINUSE") {
|
|
20565
|
+
console.error(`[lensmcp] dashboard: port ${PORT} is already in use (an orphaned dashboard from a crashed gateway?) \u2014 exiting.`);
|
|
20566
|
+
process.exit(1);
|
|
20567
|
+
}
|
|
20568
|
+
throw err;
|
|
20569
|
+
});
|
|
20547
20570
|
server.listen(PORT, () => {
|
|
20548
20571
|
const n = session.resources.list().length;
|
|
20549
20572
|
console.error(`[lensmcp] dashboard \u2192 http://localhost:${PORT} (${n} resources registered)`);
|
package/bundled/main.js
CHANGED
|
@@ -20372,7 +20372,22 @@ SetupApp = __decorate([
|
|
|
20372
20372
|
})
|
|
20373
20373
|
], SetupApp);
|
|
20374
20374
|
|
|
20375
|
+
// servers/lensmcp-mcp/dist/parent-watchdog.js
|
|
20376
|
+
function watchParentGateway(label) {
|
|
20377
|
+
const parentPid = Number(process.env["LENSMCP_PARENT_PID"] ?? 0);
|
|
20378
|
+
if (!Number.isInteger(parentPid) || parentPid <= 0)
|
|
20379
|
+
return;
|
|
20380
|
+
const timer = setInterval(() => {
|
|
20381
|
+
if (process.ppid !== parentPid) {
|
|
20382
|
+
console.error(`[lensmcp] ${label}: parent gateway (pid ${parentPid}) is gone \u2014 exiting to free the port.`);
|
|
20383
|
+
process.exit(0);
|
|
20384
|
+
}
|
|
20385
|
+
}, 5e3);
|
|
20386
|
+
timer.unref();
|
|
20387
|
+
}
|
|
20388
|
+
|
|
20375
20389
|
// servers/lensmcp-mcp/dist/main.js
|
|
20390
|
+
watchParentGateway("mcp");
|
|
20376
20391
|
var { session, providers } = createLensSession();
|
|
20377
20392
|
var transport = process.env["LENSMCP_TRANSPORT"] ?? "stdio";
|
|
20378
20393
|
var apps = [
|
package/lib/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/lib/cli.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/lib/cli.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,UAAU;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,4EAA4E;IAC5E,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,qDAAqD;IACrD,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,gEAAgE;IAChE,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9B;AAqFD,wBAAsB,MAAM,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CA4ChE"}
|
package/lib/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { spawn, spawnSync } from 'node:child_process';
|
|
2
2
|
import { existsSync, mkdirSync, openSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
|
3
|
-
import {
|
|
3
|
+
import { request as httpRequest } from 'node:http';
|
|
4
|
+
import { homedir, tmpdir } from 'node:os';
|
|
4
5
|
import { basename, dirname, join, relative, resolve } from 'node:path';
|
|
5
6
|
import { fileURLToPath } from 'node:url';
|
|
6
7
|
import { ensureLensConfig } from './workspace-scope.js';
|
|
@@ -67,6 +68,22 @@ Commands:
|
|
|
67
68
|
promote, abort, add-rule, remove-rule, status. Validated +
|
|
68
69
|
atomic write; the prod gateway picks it up live. Run
|
|
69
70
|
\`lensmcp rollout\` (no args) for the op list.
|
|
71
|
+
logs [service] [--tail <n>] [--no-follow] [--plain] [--cwd <dir>]
|
|
72
|
+
Attach this terminal to a running service's live log
|
|
73
|
+
stream (docker-logs style — for humans and agents alike).
|
|
74
|
+
No <service> lists every attachable service across
|
|
75
|
+
workspaces. --tail <n> replays that many history lines
|
|
76
|
+
first (default 100); --no-follow prints them and exits
|
|
77
|
+
(the agent-friendly snapshot); --plain strips ANSI color
|
|
78
|
+
(automatic when output is piped). Needs no ports — it
|
|
79
|
+
finds the devserver's control socket under $TMPDIR.
|
|
80
|
+
debug <service> [--port <base>] [--cwd <dir>]
|
|
81
|
+
Open every pod's Node inspector of a RUNNING service —
|
|
82
|
+
in place, no restart, app state intact — and print the
|
|
83
|
+
ws:// attach URLs (pods on base+slot, workers get
|
|
84
|
+
SIGUSR1). Then attach WebStorm (Attach to Node.js/
|
|
85
|
+
Chrome) to the printed port, or click the "Debugger
|
|
86
|
+
listening" link in \`lensmcp logs <service>\`.
|
|
70
87
|
--version Print the lensmcp version.
|
|
71
88
|
--help Show this help.
|
|
72
89
|
`;
|
|
@@ -101,6 +118,10 @@ export async function runCli(ctx) {
|
|
|
101
118
|
return runDoctor(ctx, rest, out);
|
|
102
119
|
case 'rollout':
|
|
103
120
|
return runRollout(ctx, rest, err);
|
|
121
|
+
case 'logs':
|
|
122
|
+
return runLogs(ctx, rest, out, err);
|
|
123
|
+
case 'debug':
|
|
124
|
+
return runDebug(ctx, rest, out, err);
|
|
104
125
|
default:
|
|
105
126
|
err(`Unknown command: ${command}`);
|
|
106
127
|
err(HELP);
|
|
@@ -1164,7 +1185,162 @@ function ensureLensmcpDir(cwd) {
|
|
|
1164
1185
|
function isInteractive() {
|
|
1165
1186
|
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
1166
1187
|
}
|
|
1167
|
-
/**
|
|
1188
|
+
/** Find every running devserver's control socket: `$TMPDIR[/<wsKey>]/<service>-devserver/parent.sock`
|
|
1189
|
+
* — the same path derivation the devserver (`main.devserver.ts` SOCK_DIR) and the
|
|
1190
|
+
* gateway's pod discovery use. A socket file may be stale (crashed parent); the
|
|
1191
|
+
* attach attempt reports that rather than this scan. */
|
|
1192
|
+
function discoverDevserverSocks() {
|
|
1193
|
+
const base = tmpdir();
|
|
1194
|
+
const found = [];
|
|
1195
|
+
const scan = (dir, wsKey) => {
|
|
1196
|
+
try {
|
|
1197
|
+
for (const e of readdirSync(dir, { withFileTypes: true })) {
|
|
1198
|
+
if (!e.isDirectory() || !e.name.endsWith('-devserver'))
|
|
1199
|
+
continue;
|
|
1200
|
+
const sock = join(dir, e.name, 'parent.sock');
|
|
1201
|
+
if (existsSync(sock)) {
|
|
1202
|
+
found.push({ service: e.name.slice(0, -'-devserver'.length), wsKey, sock });
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
catch {
|
|
1207
|
+
/* unreadable dir — not ours, skip */
|
|
1208
|
+
}
|
|
1209
|
+
};
|
|
1210
|
+
scan(base, '');
|
|
1211
|
+
try {
|
|
1212
|
+
for (const e of readdirSync(base, { withFileTypes: true })) {
|
|
1213
|
+
// workspace keys are slugs — skip the rest of the tmp noise
|
|
1214
|
+
if (e.isDirectory() && /^[a-z0-9][a-z0-9-]*$/.test(e.name) && !e.name.endsWith('-devserver')) {
|
|
1215
|
+
scan(join(base, e.name), e.name);
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
catch {
|
|
1220
|
+
/* no tmpdir — nothing to list */
|
|
1221
|
+
}
|
|
1222
|
+
return found.sort((a, b) => `${a.wsKey}/${a.service}`.localeCompare(`${b.wsKey}/${b.service}`));
|
|
1223
|
+
}
|
|
1224
|
+
const fullServiceName = (d) => (d.wsKey ? `${d.wsKey}/${d.service}` : d.service);
|
|
1225
|
+
/** Resolve a service argument (`name` or `wsKey/name`) to ONE control socket. A bare
|
|
1226
|
+
* name running in several workspaces is disambiguated by the current workspace's key.
|
|
1227
|
+
* Prints the helpful error itself and returns null when nothing (or too much) matches. */
|
|
1228
|
+
function resolveServiceSock(service, all, cwd, err) {
|
|
1229
|
+
let matches = all.filter((d) => d.service === service || fullServiceName(d) === service);
|
|
1230
|
+
if (matches.length > 1) {
|
|
1231
|
+
try {
|
|
1232
|
+
const key = ensureLensConfig(cwd).key;
|
|
1233
|
+
const scoped = matches.filter((d) => d.wsKey === key);
|
|
1234
|
+
if (scoped.length === 1)
|
|
1235
|
+
matches = scoped;
|
|
1236
|
+
}
|
|
1237
|
+
catch {
|
|
1238
|
+
/* not inside a workspace — keep it ambiguous and say so below */
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
if (matches.length === 0) {
|
|
1242
|
+
err(`No running service named "${service}".`);
|
|
1243
|
+
if (all.length > 0)
|
|
1244
|
+
err(`Running: ${all.map(fullServiceName).join(', ')}`);
|
|
1245
|
+
return null;
|
|
1246
|
+
}
|
|
1247
|
+
if (matches.length > 1) {
|
|
1248
|
+
err(`"${service}" is ambiguous — use one of: ${matches.map(fullServiceName).join(', ')}`);
|
|
1249
|
+
return null;
|
|
1250
|
+
}
|
|
1251
|
+
return matches[0];
|
|
1252
|
+
}
|
|
1253
|
+
async function runLogs(ctx, args, out, err) {
|
|
1254
|
+
const opts = parseFlags(args, {
|
|
1255
|
+
string: ['--tail', '--cwd'],
|
|
1256
|
+
boolean: ['--no-follow', '--plain'],
|
|
1257
|
+
});
|
|
1258
|
+
const service = opts.positional[0];
|
|
1259
|
+
const all = discoverDevserverSocks();
|
|
1260
|
+
if (!service) {
|
|
1261
|
+
if (all.length === 0) {
|
|
1262
|
+
out('No running services found (no devserver control sockets under $TMPDIR).');
|
|
1263
|
+
out('Start one with `yarn nx serve <project>` or `lensmcp gateway start`.');
|
|
1264
|
+
}
|
|
1265
|
+
else {
|
|
1266
|
+
out('Running services — attach with `lensmcp logs <service>`:');
|
|
1267
|
+
for (const d of all)
|
|
1268
|
+
out(` ${fullServiceName(d)}`);
|
|
1269
|
+
}
|
|
1270
|
+
return { exitCode: 0 };
|
|
1271
|
+
}
|
|
1272
|
+
const target = resolveServiceSock(service, all, resolve(stringFlag(opts.flags['--cwd']) ?? ctx.cwd), err);
|
|
1273
|
+
if (!target)
|
|
1274
|
+
return { exitCode: 1 };
|
|
1275
|
+
const follow = opts.flags['--no-follow'] !== true;
|
|
1276
|
+
const tailRaw = Number(stringFlag(opts.flags['--tail']) ?? NaN);
|
|
1277
|
+
const tail = Number.isInteger(tailRaw) && tailRaw >= 0 ? tailRaw : 100; // 0 = live-only, no history
|
|
1278
|
+
const plain = opts.flags['--plain'] === true || !process.stdout.isTTY;
|
|
1279
|
+
const reqPath = `/webpack/logs?tail=${tail}&follow=${follow ? 1 : 0}${plain ? '&plain=1' : ''}`;
|
|
1280
|
+
return new Promise((done) => {
|
|
1281
|
+
const req = httpRequest({ socketPath: target.sock, path: reqPath, method: 'GET' }, (res) => {
|
|
1282
|
+
// Raw stream passthrough — chunks go straight to the terminal, bypassing the
|
|
1283
|
+
// line-based out() (which would re-buffer and add newlines).
|
|
1284
|
+
res.on('data', (chunk) => process.stdout.write(chunk));
|
|
1285
|
+
res.on('end', () => done({ exitCode: 0 }));
|
|
1286
|
+
res.on('error', () => done({ exitCode: 1 }));
|
|
1287
|
+
});
|
|
1288
|
+
req.on('error', (e) => {
|
|
1289
|
+
err(`Cannot attach to ${fullServiceName(target)}: ${e.message}`);
|
|
1290
|
+
err('The devserver may have crashed leaving a stale socket — restart the service and retry.');
|
|
1291
|
+
done({ exitCode: 1 });
|
|
1292
|
+
});
|
|
1293
|
+
req.end();
|
|
1294
|
+
});
|
|
1295
|
+
}
|
|
1296
|
+
async function runDebug(ctx, args, out, err) {
|
|
1297
|
+
const opts = parseFlags(args, { string: ['--port', '--cwd'] });
|
|
1298
|
+
const service = opts.positional[0];
|
|
1299
|
+
const all = discoverDevserverSocks();
|
|
1300
|
+
if (!service) {
|
|
1301
|
+
err('Usage: lensmcp debug <service> [--port <base>]');
|
|
1302
|
+
if (all.length > 0)
|
|
1303
|
+
err(`Running: ${all.map(fullServiceName).join(', ')}`);
|
|
1304
|
+
return { exitCode: 2 };
|
|
1305
|
+
}
|
|
1306
|
+
const target = resolveServiceSock(service, all, resolve(stringFlag(opts.flags['--cwd']) ?? ctx.cwd), err);
|
|
1307
|
+
if (!target)
|
|
1308
|
+
return { exitCode: 1 };
|
|
1309
|
+
const port = Number(stringFlag(opts.flags['--port']) ?? '') || undefined;
|
|
1310
|
+
const reqPath = `/webpack/debug${port ? `?port=${port}` : ''}`;
|
|
1311
|
+
const body = await new Promise((done) => {
|
|
1312
|
+
const req = httpRequest({ socketPath: target.sock, path: reqPath, method: 'POST' }, (res) => {
|
|
1313
|
+
let b = '';
|
|
1314
|
+
res.on('data', (c) => (b += c));
|
|
1315
|
+
res.on('end', () => done(b));
|
|
1316
|
+
res.on('error', () => done(null));
|
|
1317
|
+
});
|
|
1318
|
+
req.on('error', () => done(null));
|
|
1319
|
+
req.end();
|
|
1320
|
+
});
|
|
1321
|
+
if (body === null) {
|
|
1322
|
+
err(`Cannot reach ${fullServiceName(target)} — the devserver may have crashed leaving a stale socket.`);
|
|
1323
|
+
return { exitCode: 1 };
|
|
1324
|
+
}
|
|
1325
|
+
let parsed;
|
|
1326
|
+
try {
|
|
1327
|
+
parsed = JSON.parse(body);
|
|
1328
|
+
}
|
|
1329
|
+
catch {
|
|
1330
|
+
err(`Unexpected response from the devserver: ${body.slice(0, 200)}`);
|
|
1331
|
+
return { exitCode: 1 };
|
|
1332
|
+
}
|
|
1333
|
+
out(`Inspectors open on ${fullServiceName(target)} — in place, no restart:`);
|
|
1334
|
+
for (const p of parsed.pods ?? []) {
|
|
1335
|
+
out(` pod #${p.id} ${p.inspectorUrl ?? '(no url reported — check lensmcp logs)'}`);
|
|
1336
|
+
}
|
|
1337
|
+
for (const w of parsed.workers ?? []) {
|
|
1338
|
+
out(` worker ${w.name} 127.0.0.1:${w.port}`);
|
|
1339
|
+
}
|
|
1340
|
+
out(`Attach WebStorm (Run → Attach to Node.js/Chrome) to 127.0.0.1:${parsed.basePort ?? '?'} — ` +
|
|
1341
|
+
`or click the "Debugger listening" link in \`lensmcp logs ${service}\`.`);
|
|
1342
|
+
return { exitCode: 0 };
|
|
1343
|
+
}
|
|
1168
1344
|
function stringFlag(v) {
|
|
1169
1345
|
if (v === undefined)
|
|
1170
1346
|
return undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lensmcp",
|
|
3
|
-
"version": "1.16.
|
|
3
|
+
"version": "1.16.26",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"module": "./index.js",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
}
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@frontmcp/sdk": "^1.5.
|
|
20
|
+
"@frontmcp/sdk": "^1.5.6",
|
|
21
21
|
"reflect-metadata": "^0.2.2",
|
|
22
22
|
"tslib": "^2.3.0",
|
|
23
23
|
"vectoriadb": "^2.2.0"
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "lensmcp",
|
|
3
3
|
"displayName": "LensMCP",
|
|
4
4
|
"description": "The observability lens for coding agents. One command brings up the dev cluster gateway (every project.json `cluster` decl → its host on :443), the per-project lens dashboard at https://lensmcp.local/<project>/, and the MCP server your agent connects to — scoped automatically to whatever project you opened Claude Code in.",
|
|
5
|
-
"version": "1.16.
|
|
5
|
+
"version": "1.16.26",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "David Antoon",
|
|
8
8
|
"email": "davidmantoon@gmail.com"
|