lensmcp 1.16.23 → 1.16.25
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/lib/cli.d.ts.map +1 -1
- package/lib/cli.js +178 -2
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
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
|
@@ -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.25",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "David Antoon",
|
|
8
8
|
"email": "davidmantoon@gmail.com"
|