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.
@@ -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":"AAOA,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;AAqED,wBAAsB,MAAM,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAwChE"}
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 { homedir } from 'node:os';
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
- /** Narrow a flag value to string-or-undefined, treating booleans as absent. */
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.24",
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.4",
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.24",
5
+ "version": "1.16.26",
6
6
  "author": {
7
7
  "name": "David Antoon",
8
8
  "email": "davidmantoon@gmail.com"