lensmcp 1.0.0

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/index.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { runCli, type CliResult } from './lib/cli.js';
2
+ //# sourceMappingURL=index.d.ts.map
package/index.d.ts.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC"}
package/index.js ADDED
@@ -0,0 +1 @@
1
+ export { runCli } from './lib/cli.js';
package/lib/cli.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ export interface CliResult {
2
+ exitCode: number;
3
+ message?: string;
4
+ }
5
+ interface CliContext {
6
+ cwd: string;
7
+ /** Process argv after the binary name, e.g. `["mcp"]` for `lensmcp mcp`. */
8
+ argv: string[];
9
+ /** Override env for child processes (test hooks). */
10
+ env?: NodeJS.ProcessEnv;
11
+ /** Output channel — defaults to console.log / console.error. */
12
+ out?: (line: string) => void;
13
+ err?: (line: string) => void;
14
+ }
15
+ export declare function runCli(ctx: CliContext): Promise<CliResult>;
16
+ export {};
17
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/lib/cli.ts"],"names":[],"mappings":"AAKA,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;AAmCD,wBAAsB,MAAM,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAgChE"}
package/lib/cli.js ADDED
@@ -0,0 +1,488 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { existsSync, mkdirSync, readFileSync, readdirSync } from 'node:fs';
3
+ import { basename, dirname, join, resolve } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ const HELP = `lensmcp — CLI for LensMCP (FrontMCP-based observability for coding agents)
6
+
7
+ Usage:
8
+ lensmcp <command> [options]
9
+
10
+ Commands:
11
+ mcp [--cwd <dir>] [--transport stdio|http] [--port <n>]
12
+ Launch the LensMCP MCP server. Default transport is
13
+ stdio (set LENSMCP_TRANSPORT=http or pass --transport
14
+ for HTTP on --port, default 3000).
15
+ dashboard [--cwd <dir>] [--port <n>]
16
+ Serve a live web view of the lens (flows, signals,
17
+ stories, status) on --port (default 4321). Reads the
18
+ same .lensmcp/events.jsonl your agent-dev producers write.
19
+ install [--cwd <dir>] [--skip-format] [--register-host-config <mode>]
20
+ Install LensMCP into the host Nx workspace at --cwd
21
+ (current directory by default). Delegates to
22
+ \`nx g @lensmcp/nx-plugin:init\`. Idempotent.
23
+ doctor [--cwd <dir>] [--json]
24
+ Diagnose the install: Node, workspace + plugin,
25
+ .mcp.json server entry, MCP bundle, per-project
26
+ vite/nest wiring, agent-dev targets, and Chrome for
27
+ browser capture. \`--json\` emits a machine-readable
28
+ report (for the agent). Exits non-zero on errors.
29
+ rollout <config.json> <op> [args]
30
+ Edit the gateway rollout config — add-cohort, set-weight,
31
+ promote, abort, add-rule, remove-rule, status. Validated +
32
+ atomic write; the prod gateway picks it up live. Run
33
+ \`lensmcp rollout\` (no args) for the op list.
34
+ --version Print the lensmcp version.
35
+ --help Show this help.
36
+ `;
37
+ export async function runCli(ctx) {
38
+ const out = ctx.out ?? ((l) => console.log(l));
39
+ const err = ctx.err ?? ((l) => console.error(l));
40
+ const [command, ...rest] = ctx.argv;
41
+ if (!command || command === '--help' || command === '-h') {
42
+ out(HELP);
43
+ return { exitCode: 0 };
44
+ }
45
+ if (command === '--version' || command === '-V') {
46
+ out(readCliVersion());
47
+ return { exitCode: 0 };
48
+ }
49
+ switch (command) {
50
+ case 'mcp':
51
+ return runMcp(ctx, rest, out, err);
52
+ case 'dashboard':
53
+ return runDashboard(ctx, rest, out, err);
54
+ case 'install':
55
+ return runInstall(ctx, rest, out, err);
56
+ case 'doctor':
57
+ return runDoctor(ctx, rest, out);
58
+ case 'rollout':
59
+ return runRollout(ctx, rest, err);
60
+ default:
61
+ err(`Unknown command: ${command}`);
62
+ err(HELP);
63
+ return { exitCode: 2, message: `Unknown command: ${command}` };
64
+ }
65
+ }
66
+ // ---------- commands ----------
67
+ function runMcp(ctx, args, _out, err) {
68
+ const opts = parseFlags(args, {
69
+ string: ['--cwd', '--transport', '--port'],
70
+ });
71
+ const cwd = resolve(stringFlag(opts.flags['--cwd']) ?? ctx.cwd);
72
+ const transport = stringFlag(opts.flags['--transport']) ?? ctx.env?.['LENSMCP_TRANSPORT'] ?? 'stdio';
73
+ const port = stringFlag(opts.flags['--port']) ?? ctx.env?.['LENSMCP_PORT'] ?? '3000';
74
+ const binPath = findMcpBinary(cwd);
75
+ if (!binPath) {
76
+ err('Could not locate the lensmcp-mcp server bundle.\n' +
77
+ 'Looked in (in order):\n' +
78
+ ' • <cli-package>/bundled/main.js (published bin)\n' +
79
+ ' • <workspace>/apps/lensmcp-mcp/dist/main.js (dev build)\n' +
80
+ 'For dev: `yarn nx build @lensmcp/lensmcp-mcp`.');
81
+ return { exitCode: 1 };
82
+ }
83
+ ensureLensmcpDir(cwd);
84
+ // Default the cross-process event file to `<cwd>/.lensmcp/events.jsonl`
85
+ // so the server's ingest and the host's `agent-dev` producers (which
86
+ // write to the same default) rendezvous with zero extra config.
87
+ const eventFile = ctx.env?.['LENSMCP_EVENT_FILE'] ??
88
+ process.env['LENSMCP_EVENT_FILE'] ??
89
+ join(cwd, '.lensmcp', 'events.jsonl');
90
+ const childEnv = {
91
+ ...process.env,
92
+ ...(ctx.env ?? {}),
93
+ LENSMCP_TRANSPORT: transport,
94
+ LENSMCP_PORT: port,
95
+ LENSMCP_EVENT_FILE: eventFile,
96
+ };
97
+ const result = spawnSync(process.execPath, [binPath], {
98
+ cwd,
99
+ stdio: 'inherit',
100
+ env: childEnv,
101
+ });
102
+ return { exitCode: result.status ?? 1 };
103
+ }
104
+ function runDashboard(ctx, args, out, err) {
105
+ const opts = parseFlags(args, { string: ['--cwd', '--port'] });
106
+ const cwd = resolve(stringFlag(opts.flags['--cwd']) ?? ctx.cwd);
107
+ const port = stringFlag(opts.flags['--port']) ?? ctx.env?.['LENSMCP_DASHBOARD_PORT'] ?? '4321';
108
+ const binPath = findServerBundle(cwd, 'dashboard.js');
109
+ if (!binPath) {
110
+ err('Could not locate the lensmcp dashboard bundle.\n' +
111
+ 'Looked in (in order):\n' +
112
+ ' • <cli-package>/bundled/dashboard.js (published bin)\n' +
113
+ ' • <workspace>/servers/lensmcp-mcp/dist/dashboard.js (dev build)\n' +
114
+ 'For dev: `yarn nx build @lensmcp/lensmcp-mcp`.');
115
+ return { exitCode: 1 };
116
+ }
117
+ ensureLensmcpDir(cwd);
118
+ // Tail the same event file the host's agent-dev producers write to, so the
119
+ // dashboard materialises the same live resources the MCP server does.
120
+ const eventFile = ctx.env?.['LENSMCP_EVENT_FILE'] ??
121
+ process.env['LENSMCP_EVENT_FILE'] ??
122
+ join(cwd, '.lensmcp', 'events.jsonl');
123
+ out(`lensmcp dashboard → http://localhost:${port} (reading ${eventFile})`);
124
+ const result = spawnSync(process.execPath, [binPath], {
125
+ cwd,
126
+ stdio: 'inherit',
127
+ env: {
128
+ ...process.env,
129
+ ...(ctx.env ?? {}),
130
+ LENSMCP_DASHBOARD_PORT: port,
131
+ LENSMCP_EVENT_FILE: eventFile,
132
+ },
133
+ });
134
+ return { exitCode: result.status ?? 1 };
135
+ }
136
+ function runInstall(ctx, args, out, err) {
137
+ const opts = parseFlags(args, {
138
+ string: ['--cwd', '--register-host-config'],
139
+ boolean: ['--skip-format', '--yes'],
140
+ });
141
+ const cwd = resolve(stringFlag(opts.flags['--cwd']) ?? ctx.cwd);
142
+ if (!existsSync(join(cwd, 'nx.json'))) {
143
+ err(`No nx.json found at ${cwd}.`);
144
+ err('Run `lensmcp install` from inside a Nx workspace.');
145
+ return { exitCode: 1 };
146
+ }
147
+ const nxBin = findNxBinary(cwd);
148
+ if (!nxBin) {
149
+ err('Could not find the `nx` binary in the host workspace. Install Nx first: `yarn add -D nx`.');
150
+ return { exitCode: 1 };
151
+ }
152
+ const nxArgs = ['g', '@lensmcp/nx-plugin:init', '--no-interactive'];
153
+ if (opts.flags['--skip-format'] === true)
154
+ nxArgs.push('--skipFormat');
155
+ if (opts.flags['--register-host-config']) {
156
+ nxArgs.push(`--registerHostConfig=${String(opts.flags['--register-host-config'])}`);
157
+ }
158
+ out(`> ${nxBin} ${nxArgs.join(' ')}`);
159
+ const result = spawnSync(nxBin, nxArgs, {
160
+ cwd,
161
+ stdio: 'inherit',
162
+ env: { ...process.env, ...(ctx.env ?? {}) },
163
+ });
164
+ return { exitCode: result.status ?? 1 };
165
+ }
166
+ function runDoctor(ctx, args, out) {
167
+ const opts = parseFlags(args, { string: ['--cwd'], boolean: ['--json'] });
168
+ const cwd = resolve(stringFlag(opts.flags['--cwd']) ?? ctx.cwd);
169
+ const asJson = opts.flags['--json'] === true;
170
+ const checks = [];
171
+ checkNode(checks);
172
+ checkWorkspace(cwd, checks);
173
+ checkMcpConfig(cwd, checks);
174
+ checkServerBundle(cwd, checks);
175
+ checkRuntimeDir(cwd, checks);
176
+ checkProjectWiring(cwd, checks);
177
+ checkChrome(checks);
178
+ const failed = checks.some((c) => c.status === 'fail');
179
+ const warned = checks.some((c) => c.status === 'warn');
180
+ if (asJson) {
181
+ out(JSON.stringify({
182
+ ok: !failed,
183
+ summary: { total: checks.length, failed: checks.filter((c) => c.status === 'fail').length, warnings: checks.filter((c) => c.status === 'warn').length },
184
+ checks,
185
+ }, null, 2));
186
+ return { exitCode: failed ? 1 : 0 };
187
+ }
188
+ for (const c of checks) {
189
+ const icon = c.status === 'pass' ? '✓' : c.status === 'warn' ? '!' : '✗';
190
+ out(`${icon} ${c.label}${c.detail ? ` — ${c.detail}` : ''}`);
191
+ if (c.status !== 'pass' && c.hint)
192
+ out(` ↳ ${c.hint}`);
193
+ }
194
+ out('');
195
+ out(failed
196
+ ? 'doctor: issues found (see ✗ above).'
197
+ : warned
198
+ ? 'doctor: ok, with warnings (!).'
199
+ : 'doctor: all checks passed.');
200
+ return { exitCode: failed ? 1 : 0 };
201
+ }
202
+ // ---------- doctor checks ----------
203
+ function add(checks, status, label, detail, hint) {
204
+ checks.push({ status, label, detail, hint });
205
+ }
206
+ function checkNode(checks) {
207
+ const major = Number(process.versions.node.split('.')[0]);
208
+ if (major >= 20)
209
+ add(checks, 'pass', `Node ${process.versions.node}`);
210
+ else if (major >= 18)
211
+ add(checks, 'warn', `Node ${process.versions.node}`, 'older than recommended', 'upgrade to Node 20+ for best results');
212
+ else
213
+ add(checks, 'fail', `Node ${process.versions.node}`, 'too old', 'LensMCP needs Node 18+ (20+ recommended)');
214
+ }
215
+ function checkWorkspace(cwd, checks) {
216
+ const nxJsonPath = join(cwd, 'nx.json');
217
+ if (!existsSync(nxJsonPath)) {
218
+ add(checks, 'fail', 'nx.json at workspace root', 'missing', 'run `lensmcp` from inside a Nx workspace');
219
+ return;
220
+ }
221
+ let nx;
222
+ try {
223
+ nx = JSON.parse(readFileSync(nxJsonPath, 'utf8'));
224
+ }
225
+ catch (e) {
226
+ add(checks, 'fail', 'nx.json parses', e.message, 'fix the JSON syntax in nx.json');
227
+ return;
228
+ }
229
+ const registered = (nx.plugins ?? []).some((p) => typeof p === 'string'
230
+ ? p === '@lensmcp/nx-plugin'
231
+ : !!p && typeof p === 'object' && p.plugin === '@lensmcp/nx-plugin');
232
+ add(checks, registered ? 'pass' : 'fail', '@lensmcp/nx-plugin registered in nx.json#plugins', registered ? undefined : 'not registered', registered ? undefined : 'run `lensmcp install`');
233
+ add(checks, nx.lensmcp?.schemaVersion === 1 ? 'pass' : 'warn', 'lensmcp config block in nx.json', nx.lensmcp?.schemaVersion === 1 ? undefined : 'missing', nx.lensmcp?.schemaVersion === 1 ? undefined : 'run `lensmcp install` to write the config defaults');
234
+ }
235
+ function checkMcpConfig(cwd, checks) {
236
+ const mcpPath = join(cwd, '.mcp.json');
237
+ if (!existsSync(mcpPath)) {
238
+ add(checks, 'warn', '.mcp.json with a lensmcp server', 'missing', 'run `lensmcp install` so agents auto-discover the MCP server');
239
+ return;
240
+ }
241
+ try {
242
+ const cfg = JSON.parse(readFileSync(mcpPath, 'utf8'));
243
+ const has = !!cfg.mcpServers && !!cfg.mcpServers['lensmcp'];
244
+ add(checks, has ? 'pass' : 'warn', '.mcp.json registers the lensmcp MCP server', has ? undefined : 'no lensmcp entry', has ? undefined : 'run `lensmcp install`');
245
+ }
246
+ catch (e) {
247
+ add(checks, 'warn', '.mcp.json parses', e.message, 'fix the JSON syntax in .mcp.json');
248
+ }
249
+ }
250
+ function checkServerBundle(cwd, checks) {
251
+ const bin = findMcpBinary(cwd);
252
+ add(checks, bin ? 'pass' : 'fail', 'lensmcp-mcp server bundle reachable', bin ?? 'missing', bin ? undefined : 'dev: `yarn nx build @lensmcp/lensmcp-mcp`');
253
+ }
254
+ function checkRuntimeDir(cwd, checks) {
255
+ const lensmcpDir = join(cwd, '.lensmcp');
256
+ add(checks, existsSync(lensmcpDir) ? 'pass' : 'warn', '.lensmcp/ runtime directory', existsSync(lensmcpDir) ? undefined : 'not created yet', existsSync(lensmcpDir) ? undefined : 'created automatically on first `lensmcp mcp` run');
257
+ // .gitignore should exclude the per-session runtime artifacts.
258
+ const giPath = join(cwd, '.gitignore');
259
+ const gi = existsSync(giPath) ? safeRead(giPath) : '';
260
+ const ignored = gi.split('\n').some((l) => l.trim() === '.lensmcp/' || l.trim() === '.lensmcp');
261
+ add(checks, ignored ? 'pass' : 'warn', '.gitignore excludes .lensmcp/', ignored ? undefined : 'not ignored', ignored ? undefined : 'add `.lensmcp/` to .gitignore (lensmcp install does this)');
262
+ }
263
+ function checkProjectWiring(cwd, checks) {
264
+ // Frontend: vite.config.* referencing the LensMCP plugin.
265
+ const viteConfigs = walkProjectFiles(cwd, (n) => /^vite\.config\.[cm]?[jt]s$/.test(n));
266
+ const viteWired = viteConfigs.filter((f) => /lensmcpVitePlugin|@lensmcp\/vite-plugin/.test(safeRead(f)));
267
+ if (viteConfigs.length > 0) {
268
+ const all = viteWired.length === viteConfigs.length;
269
+ add(checks, all ? 'pass' : 'warn', `Vite wiring: ${viteWired.length}/${viteConfigs.length} config(s) use the LensMCP plugin`, undefined, all ? undefined : 'wire each: `nx g @lensmcp/nx-plugin:setup-vite <project>`');
270
+ }
271
+ // Backend: main.ts bootstraps using createLensmcpNestApp vs raw NestFactory.
272
+ const mains = walkProjectFiles(cwd, (n) => n === 'main.ts');
273
+ const nestMains = mains.filter((f) => /NestFactory\.create|createLensmcpNestApp/.test(safeRead(f)));
274
+ const nestWired = nestMains.filter((f) => /createLensmcpNestApp/.test(safeRead(f)));
275
+ if (nestMains.length > 0) {
276
+ const all = nestWired.length === nestMains.length;
277
+ add(checks, all ? 'pass' : 'warn', `Nest wiring: ${nestWired.length}/${nestMains.length} bootstrap(s) use createLensmcpNestApp`, undefined, all ? undefined : 'wire each: `nx g @lensmcp/nx-plugin:setup-nest <project>`');
278
+ }
279
+ // agent-dev targets — the marker that setup-vite/setup-nest ran.
280
+ const configFiles = walkProjectFiles(cwd, (n) => n === 'project.json' || n === 'package.json');
281
+ const agentDev = configFiles.filter((f) => /"agent-dev"/.test(safeRead(f)));
282
+ add(checks, agentDev.length > 0 ? 'pass' : 'warn', `agent-dev targets: ${agentDev.length} project(s)`, undefined, agentDev.length > 0 ? undefined : 'no project wired — run setup-vite / setup-nest');
283
+ }
284
+ function checkChrome(checks) {
285
+ const envPath = process.env['CHROME_PATH'];
286
+ const candidates = process.platform === 'darwin'
287
+ ? [
288
+ '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
289
+ '/Applications/Chromium.app/Contents/MacOS/Chromium',
290
+ ]
291
+ : process.platform === 'win32'
292
+ ? [
293
+ 'C:/Program Files/Google/Chrome/Application/chrome.exe',
294
+ 'C:/Program Files (x86)/Google/Chrome/Application/chrome.exe',
295
+ ]
296
+ : ['/usr/bin/google-chrome', '/usr/bin/chromium-browser', '/usr/bin/chromium', '/usr/bin/google-chrome-stable'];
297
+ const found = (envPath && existsSync(envPath) ? envPath : undefined) ?? candidates.find((p) => existsSync(p));
298
+ add(checks, found ? 'pass' : 'warn', 'Chrome for browser:// capture', found ? basename(found) : 'not found', found ? undefined : 'install Chrome or set CHROME_PATH (only needed for browser capture)');
299
+ }
300
+ /** Read a file, returning '' on any error. */
301
+ function safeRead(path) {
302
+ try {
303
+ return readFileSync(path, 'utf8');
304
+ }
305
+ catch {
306
+ return '';
307
+ }
308
+ }
309
+ /**
310
+ * Bounded recursive walk collecting files whose basename matches `match`.
311
+ * Skips node_modules / build / VCS dirs and caps scanned entries so doctor
312
+ * stays fast on large workspaces.
313
+ */
314
+ function walkProjectFiles(root, match, maxDepth = 6, cap = 6000) {
315
+ const SKIP = new Set([
316
+ 'node_modules',
317
+ 'dist',
318
+ 'build',
319
+ '.git',
320
+ '.nx',
321
+ '.lensmcp',
322
+ 'coverage',
323
+ 'tmp',
324
+ '.cache',
325
+ '.yarn',
326
+ ]);
327
+ const found = [];
328
+ const stack = [{ dir: root, depth: 0 }];
329
+ let scanned = 0;
330
+ while (stack.length > 0) {
331
+ const { dir, depth } = stack.pop();
332
+ let entries;
333
+ try {
334
+ entries = readdirSync(dir, { withFileTypes: true });
335
+ }
336
+ catch {
337
+ continue;
338
+ }
339
+ for (const e of entries) {
340
+ if (scanned++ > cap)
341
+ return found;
342
+ if (e.isDirectory()) {
343
+ if (SKIP.has(e.name) || e.name.startsWith('.'))
344
+ continue;
345
+ if (depth < maxDepth)
346
+ stack.push({ dir: join(dir, e.name), depth: depth + 1 });
347
+ }
348
+ else if (e.isFile() && match(e.name)) {
349
+ found.push(join(dir, e.name));
350
+ }
351
+ }
352
+ }
353
+ return found;
354
+ }
355
+ // ---------- helpers ----------
356
+ function readCliVersion() {
357
+ try {
358
+ const pkgPath = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'package.json');
359
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
360
+ return pkg.version;
361
+ }
362
+ catch {
363
+ return '0.0.0';
364
+ }
365
+ }
366
+ /** `lensmcp rollout` — delegate to the @lensmcp/cluster rollout helper (file IO +
367
+ * atomic write), resolved at runtime from node_modules / workspace dist. */
368
+ function runRollout(ctx, args, err) {
369
+ const cwd = resolve(ctx.cwd);
370
+ const bundle = findRolloutBundle(cwd);
371
+ if (!bundle) {
372
+ err('Could not locate the lensmcp rollout helper (@lensmcp/cluster).\n' +
373
+ 'Looked for main.rollout.js (walking up from cwd):\n' +
374
+ ' • <dir>/node_modules/@lensmcp/cluster/executors/gateway/main.rollout.js\n' +
375
+ ' • <dir>/dist/libs/cluster/executors/gateway/main.rollout.js\n' +
376
+ 'Install @lensmcp/cluster, or `yarn nx build @lensmcp/cluster` in the workspace.');
377
+ return { exitCode: 1 };
378
+ }
379
+ const res = spawnSync(process.execPath, [bundle, ...args], { stdio: 'inherit', cwd, env: { ...process.env, ...ctx.env } });
380
+ return { exitCode: res.status ?? 1 };
381
+ }
382
+ function findRolloutBundle(cwd) {
383
+ let dir = cwd;
384
+ for (let i = 0; i < 6; i++) {
385
+ for (const c of [
386
+ join(dir, 'node_modules', '@lensmcp', 'cluster', 'executors', 'gateway', 'main.rollout.js'),
387
+ join(dir, 'dist', 'libs', 'cluster', 'executors', 'gateway', 'main.rollout.js'),
388
+ ])
389
+ if (existsSync(c))
390
+ return c;
391
+ const parent = dirname(dir);
392
+ if (parent === dir)
393
+ break;
394
+ dir = parent;
395
+ }
396
+ return undefined;
397
+ }
398
+ function findMcpBinary(cwd) {
399
+ return findServerBundle(cwd, 'main.js');
400
+ }
401
+ /** Resolve a built server bundle (`main.js` | `dashboard.js`): the published
402
+ * `bundled/<file>` first, then the workspace dev build (walking up from cwd). */
403
+ function findServerBundle(cwd, file) {
404
+ // 1. Bundled inside the published lensmcp (ship step).
405
+ const here = dirname(fileURLToPath(import.meta.url));
406
+ const bundled = resolve(here, '..', '..', 'bundled', file);
407
+ if (existsSync(bundled))
408
+ return bundled;
409
+ // 2. Dev build in the workspace (four-bucket layout: servers/).
410
+ const devBuild = join(cwd, 'servers', 'lensmcp-mcp', 'dist', file);
411
+ if (existsSync(devBuild))
412
+ return devBuild;
413
+ // 3. Common monorepo locations relative to cwd (walk up).
414
+ let dir = cwd;
415
+ for (let i = 0; i < 5; i++) {
416
+ const candidate = join(dir, 'servers', 'lensmcp-mcp', 'dist', file);
417
+ if (existsSync(candidate))
418
+ return candidate;
419
+ const parent = dirname(dir);
420
+ if (parent === dir)
421
+ break;
422
+ dir = parent;
423
+ }
424
+ return undefined;
425
+ }
426
+ function findNxBinary(cwd) {
427
+ let dir = cwd;
428
+ for (let i = 0; i < 5; i++) {
429
+ const candidate = join(dir, 'node_modules', '.bin', 'nx');
430
+ if (existsSync(candidate))
431
+ return candidate;
432
+ const parent = dirname(dir);
433
+ if (parent === dir)
434
+ break;
435
+ dir = parent;
436
+ }
437
+ return undefined;
438
+ }
439
+ function ensureLensmcpDir(cwd) {
440
+ const dir = join(cwd, '.lensmcp');
441
+ if (!existsSync(dir))
442
+ mkdirSync(dir, { recursive: true });
443
+ }
444
+ /** Narrow a flag value to string-or-undefined, treating booleans as absent. */
445
+ function stringFlag(v) {
446
+ if (v === undefined)
447
+ return undefined;
448
+ return typeof v === 'string' ? v : undefined;
449
+ }
450
+ function parseFlags(argv, spec = {}) {
451
+ const strings = new Set(spec.string ?? []);
452
+ const booleans = new Set(spec.boolean ?? []);
453
+ const flags = {};
454
+ const positional = [];
455
+ for (let i = 0; i < argv.length; i++) {
456
+ const tok = argv[i];
457
+ if (tok.startsWith('--')) {
458
+ const eq = tok.indexOf('=');
459
+ const key = eq === -1 ? tok : tok.slice(0, eq);
460
+ if (booleans.has(key)) {
461
+ flags[key] = true;
462
+ }
463
+ else if (strings.has(key)) {
464
+ if (eq !== -1) {
465
+ flags[key] = tok.slice(eq + 1);
466
+ }
467
+ else {
468
+ const next = argv[i + 1];
469
+ if (next === undefined || next.startsWith('--')) {
470
+ flags[key] = '';
471
+ }
472
+ else {
473
+ flags[key] = next;
474
+ i++;
475
+ }
476
+ }
477
+ }
478
+ else {
479
+ // unknown flag — record raw
480
+ flags[key] = eq === -1 ? true : tok.slice(eq + 1);
481
+ }
482
+ }
483
+ else {
484
+ positional.push(tok);
485
+ }
486
+ }
487
+ return { flags, positional };
488
+ }
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "lensmcp",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "main": "./index.js",
6
+ "module": "./index.js",
7
+ "types": "./index.d.ts",
8
+ "bin": {
9
+ "lensmcp": "bin.js"
10
+ },
11
+ "exports": {
12
+ "./package.json": "./package.json",
13
+ ".": {
14
+ "types": "./index.d.ts",
15
+ "import": "./index.js",
16
+ "default": "./index.js"
17
+ }
18
+ },
19
+ "dependencies": {
20
+ "@frontmcp/sdk": "^1.4.1",
21
+ "reflect-metadata": "^0.2.2",
22
+ "tslib": "^2.3.0",
23
+ "vectoriadb": "^2.2.0"
24
+ },
25
+ "devDependencies": {
26
+ "esbuild": "^0.27.0"
27
+ },
28
+ "license": "Apache-2.0",
29
+ "homepage": "https://github.com/kiwiapps-ltd/lensmcp#readme",
30
+ "bugs": {
31
+ "url": "https://github.com/kiwiapps-ltd/lensmcp/issues"
32
+ },
33
+ "engines": {
34
+ "node": ">=18"
35
+ },
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "description": "LensMCP CLI — install and run the MCP observability server coding agents use to see running apps.",
40
+ "keywords": [
41
+ "lensmcp",
42
+ "mcp",
43
+ "observability",
44
+ "ai-agents",
45
+ "claude",
46
+ "cli"
47
+ ],
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "git+https://github.com/kiwiapps-ltd/lensmcp.git",
51
+ "directory": "libs/cli"
52
+ }
53
+ }