@lensmcp/nx-plugin 1.18.4 → 1.18.7

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.
@@ -1,86 +1,2 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.default = agentBuildExecutor;
4
- const node_fs_1 = require("node:fs");
5
- const node_path_1 = require("node:path");
6
- const node_child_process_1 = require("node:child_process");
7
- const node_fs_2 = require("node:fs");
8
- /**
9
- * `agent-build` — one-shot production build of the host's Vite project,
10
- * with `@lensmcp/vite-plugin` already wired (the `setup-vite` generator
11
- * patches the host's `vite.config.ts`).
12
- *
13
- * Phase 2 keeps this minimal: spawn Vite's build CLI inheriting stdio,
14
- * then (if the build succeeds and `updateBaseline` is true) write a
15
- * stub baseline file under `.lensmcp/baseline/bundle.json` recording the
16
- * timestamp + buildOutDir. The reducer-side baseline (per-chunk JSON)
17
- * is written by the running `lensmcp-mcp` session — agent-build only
18
- * spawns Vite; the report flows into MCP via the plugin's WS bridge
19
- * once the supervisor wiring lands in 1.5.
20
- */
21
- async function agentBuildExecutor(options, context) {
22
- const opts = {
23
- kind: options.kind ?? 'vite-react',
24
- updateBaseline: options.updateBaseline ?? true,
25
- projectRoot: options.projectRoot,
26
- };
27
- if (opts.kind !== 'vite-react') {
28
- console.error(`[agent-build] Phase 2 only supports kind="vite-react" (got "${opts.kind}").`);
29
- return { success: false };
30
- }
31
- const projectName = context.projectName;
32
- const projectRoot = opts.projectRoot
33
- ? (0, node_path_1.resolve)(context.root, opts.projectRoot)
34
- : projectName && context.projectsConfigurations
35
- ? (0, node_path_1.resolve)(context.root, context.projectsConfigurations.projects[projectName]?.root ?? '.')
36
- : context.root;
37
- const viteBin = locateBin('vite', [projectRoot, context.root]);
38
- if (!viteBin) {
39
- console.error('[agent-build] Could not find vite binary in node_modules/.bin.');
40
- return { success: false };
41
- }
42
- const viteConfig = resolveFirstExisting(['vite.config.ts', 'vite.config.js', 'vite.config.mjs'], projectRoot) ??
43
- (0, node_path_1.join)(projectRoot, 'vite.config.ts');
44
- console.log(`[agent-build] vite build (project=${projectName ?? projectRoot})`);
45
- const result = (0, node_child_process_1.spawnSync)(viteBin, ['build', '--config', viteConfig, projectRoot], {
46
- cwd: projectRoot,
47
- stdio: 'inherit',
48
- env: { ...process.env },
49
- });
50
- const success = (result.status ?? 1) === 0;
51
- if (success && opts.updateBaseline) {
52
- const baselinePath = (0, node_path_1.join)(context.root, '.lensmcp', 'baseline', `${projectName ?? 'app'}.bundle.json`);
53
- try {
54
- (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(baselinePath), { recursive: true });
55
- (0, node_fs_1.writeFileSync)(baselinePath, JSON.stringify({
56
- schemaVersion: 1,
57
- project: projectName ?? null,
58
- buildOutDir: (0, node_path_1.join)(projectRoot, 'dist'),
59
- timestamp: new Date().toISOString(),
60
- note: 'Phase 2 baseline marker. Per-chunk JSON is materialised by the running ' +
61
- 'lensmcp-mcp session (the self-contained bundle) once the supervisor wires the plugin bus.',
62
- }, null, 2) + '\n');
63
- console.log(`[agent-build] baseline written: ${baselinePath}`);
64
- }
65
- catch (e) {
66
- console.warn(`[agent-build] could not write baseline: ${e.message}`);
67
- }
68
- }
69
- return { success };
70
- }
71
- function locateBin(name, roots) {
72
- for (const root of roots) {
73
- const candidate = (0, node_path_1.join)(root, 'node_modules', '.bin', name);
74
- if ((0, node_fs_2.existsSync)(candidate))
75
- return candidate;
76
- }
77
- return undefined;
78
- }
79
- function resolveFirstExisting(names, root) {
80
- for (const n of names) {
81
- const p = (0, node_path_1.join)(root, n);
82
- if ((0, node_fs_2.existsSync)(p))
83
- return p;
84
- }
85
- return undefined;
86
- }
1
+ "use strict";var f=Object.defineProperty;var r=(n,e)=>f(n,"name",{value:e,configurable:!0});var d=Object.defineProperty,i=r((n,e)=>d(n,"name",{value:e,configurable:!0}),"i");Object.defineProperty(exports,"__esModule",{value:!0}),exports.default=agentBuildExecutor;const node_fs_1=require("node:fs"),node_path_1=require("node:path"),node_child_process_1=require("node:child_process"),node_fs_2=require("node:fs");async function agentBuildExecutor(n,e){const t={kind:n.kind??"vite-react",updateBaseline:n.updateBaseline??!0,projectRoot:n.projectRoot};if(t.kind!=="vite-react")return console.error(`[agent-build] Phase 2 only supports kind="vite-react" (got "${t.kind}").`),{success:!1};const o=e.projectName,s=t.projectRoot?(0,node_path_1.resolve)(e.root,t.projectRoot):o&&e.projectsConfigurations?(0,node_path_1.resolve)(e.root,e.projectsConfigurations.projects[o]?.root??"."):e.root,u=locateBin("vite",[s,e.root]);if(!u)return console.error("[agent-build] Could not find vite binary in node_modules/.bin."),{success:!1};const a=resolveFirstExisting(["vite.config.ts","vite.config.js","vite.config.mjs"],s)??(0,node_path_1.join)(s,"vite.config.ts");console.log(`[agent-build] vite build (project=${o??s})`);const l=((0,node_child_process_1.spawnSync)(u,["build","--config",a,s],{cwd:s,stdio:"inherit",env:{...process.env}}).status??1)===0;if(l&&t.updateBaseline){const c=(0,node_path_1.join)(e.root,".lensmcp","baseline",`${o??"app"}.bundle.json`);try{(0,node_fs_1.mkdirSync)((0,node_path_1.dirname)(c),{recursive:!0}),(0,node_fs_1.writeFileSync)(c,JSON.stringify({schemaVersion:1,project:o??null,buildOutDir:(0,node_path_1.join)(s,"dist"),timestamp:new Date().toISOString(),note:"Phase 2 baseline marker. Per-chunk JSON is materialised by the running lensmcp-mcp session (the self-contained bundle) once the supervisor wires the plugin bus."},null,2)+`
2
+ `),console.log(`[agent-build] baseline written: ${c}`)}catch(p){console.warn(`[agent-build] could not write baseline: ${p.message}`)}}return{success:l}}r(agentBuildExecutor,"agentBuildExecutor"),i(agentBuildExecutor,"agentBuildExecutor");function locateBin(n,e){for(const t of e){const o=(0,node_path_1.join)(t,"node_modules",".bin",n);if((0,node_fs_2.existsSync)(o))return o}}r(locateBin,"locateBin"),i(locateBin,"locateBin");function resolveFirstExisting(n,e){for(const t of n){const o=(0,node_path_1.join)(e,t);if((0,node_fs_2.existsSync)(o))return o}}r(resolveFirstExisting,"resolveFirstExisting"),i(resolveFirstExisting,"resolveFirstExisting");
@@ -1,425 +1,4 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.default = agentDevExecutor;
4
- const node_child_process_1 = require("node:child_process");
5
- const node_fs_1 = require("node:fs");
6
- const node_path_1 = require("node:path");
7
- const lens_frontend_1 = require("../../lens-frontend");
8
- /**
9
- * `agent-dev` — long-running executor that spawns:
10
- *
11
- * 1. The host project's Vite dev server (with `@lensmcp/vite-plugin`
12
- * already wired by `setup-vite`).
13
- * 2. (Optional) a Chrome sidecar pointed at the dev URL so CDP
14
- * collectors see what the user sees.
15
- * 3. The LensMCP MCP server, so an agent can connect.
16
- *
17
- * The MCP server and the browser-capture runner are both spawned from the
18
- * self-contained `lensmcp` CLI bundle (`bundled/main.js` +
19
- * `bundled/capture-runner.js`), which inlines every internal `@lensmcp/*`
20
- * lib — so the executor itself imports NONE of them and a host needs only
21
- * `npm i @lensmcp/nx-plugin lensmcp` (no unpublished workspace libs, no
22
- * symlink bridging).
23
- *
24
- * Streams everyone's stdio to this terminal so the developer can see
25
- * what's happening. Ctrl-C triggers a clean teardown.
26
- */
27
- async function agentDevExecutor(options, context) {
28
- const opts = {
29
- kind: options.kind ?? 'vite-react',
30
- chrome: options.chrome ?? true,
31
- headless: options.headless ?? true,
32
- node: options.node ?? true,
33
- devTarget: options.devTarget,
34
- projectRoot: options.projectRoot,
35
- port: options.port,
36
- openUrl: options.openUrl,
37
- devCommand: options.devCommand,
38
- wsPort: options.wsPort,
39
- };
40
- if (opts.kind !== 'vite-react' && opts.kind !== 'nestjs' && opts.kind !== 'imported') {
41
- console.error(`[agent-dev] supported kinds are "vite-react", "nestjs", "imported" (got "${opts.kind}").`);
42
- return { success: false };
43
- }
44
- const project = context.projectName;
45
- const projectRoot = opts.projectRoot
46
- ? (0, node_path_1.resolve)(context.root, opts.projectRoot)
47
- : project && context.projectsConfigurations
48
- ? (0, node_path_1.resolve)(context.root, context.projectsConfigurations.projects[project]?.root ?? '.')
49
- : context.root;
50
- // ---- NestJS branch ----
51
- if (opts.kind === 'nestjs') {
52
- return runNestjs(projectRoot, context);
53
- }
54
- // ---- Imported / bring-your-own-build branch ----
55
- // (webpack, Next.js, custom builder, or no build at all)
56
- if (opts.kind === 'imported') {
57
- return runImported(projectRoot, context, {
58
- devCommand: opts.devCommand,
59
- chrome: opts.chrome,
60
- headless: opts.headless,
61
- openUrl: opts.openUrl,
62
- wsPort: opts.wsPort ?? 5747,
63
- node: opts.node,
64
- });
65
- }
66
- // ---- Vite/React branch (Phase 1) ----
67
- // The per-app half (Vite + browser capture) is the SHARED helper
68
- // `spawnLensFrontend`, which the cluster gateway also calls for `lens:true`
69
- // apps. agent-dev additionally owns the cluster-wide SINGLETONS (the MCP
70
- // server below) since, standalone, nothing else does.
71
- // 2. LensMCP MCP — prebuilt bundle from `apps/lensmcp-mcp/dist/main.js`.
72
- // In a host workspace post-`lensmcp install`, this is the bundled
73
- // CLI binary that ships with `lensmcp`. For dev in-repo we
74
- // locate the workspace's own build artefact.
75
- const mcpBundle = (0, lens_frontend_1.findMcpBundle)(context.root);
76
- if (!mcpBundle) {
77
- console.warn("[agent-dev] No lensmcp-mcp bundle found — skipping MCP server. Install the self-contained CLI: `npm i -D lensmcp` (it ships `bundled/main.js`).");
78
- }
79
- const children = [];
80
- const respawnCounts = new Map();
81
- let exitRequested = false;
82
- function spawnChild(label, bin, args, env, optional = false, respawn) {
83
- const child = (0, node_child_process_1.spawn)(bin, args, {
84
- cwd: projectRoot,
85
- env: { ...process.env, ...(env ?? {}) },
86
- stdio: 'inherit',
87
- });
88
- children.push(child);
89
- child.on('exit', (code, sig) => {
90
- if (exitRequested)
91
- return;
92
- if (optional) {
93
- // Best-effort children (e.g. browser capture) may exit on their
94
- // own (no Chrome, page closed) without tearing down the session.
95
- // With a respawn policy they come back — a killed/crashed Chrome
96
- // otherwise silently ends visual capture for the whole session.
97
- const used = respawnCounts.get(label) ?? 0;
98
- if (respawn && used < respawn.max) {
99
- respawnCounts.set(label, used + 1);
100
- console.warn(`[agent-dev] ${label} exited (code=${code} sig=${sig}); respawning in ${Math.round(respawn.delayMs / 1000)}s (${used + 1}/${respawn.max}).`);
101
- const t = setTimeout(() => {
102
- if (!exitRequested)
103
- spawnChild(label, bin, args, env, optional, respawn);
104
- }, respawn.delayMs);
105
- t.unref?.();
106
- return;
107
- }
108
- console.warn(`[agent-dev] ${label} exited (code=${code} sig=${sig}); continuing.`);
109
- return;
110
- }
111
- console.warn(`[agent-dev] ${label} exited unexpectedly (code=${code} sig=${sig}); shutting down others.`);
112
- shutdown();
113
- });
114
- return child;
115
- }
116
- function shutdown() {
117
- if (exitRequested)
118
- return;
119
- exitRequested = true;
120
- for (const c of children) {
121
- try {
122
- c.kill('SIGTERM');
123
- }
124
- catch { /* swallow */ }
125
- }
126
- }
127
- process.on('SIGINT', shutdown);
128
- process.on('SIGTERM', shutdown);
129
- // Cross-process event bridge: the Vite plugin (in the dev-server child)
130
- // and the MCP server (in its own child) rendezvous on a shared JSONL
131
- // event file. The plugin appends browser events; the server tails it
132
- // via `startEventIngest`. Truncate it on start so a fresh run doesn't
133
- // replay a previous session's events.
134
- const eventFile = process.env['LENSMCP_EVENT_FILE'] ?? (0, node_path_1.join)(context.root, '.lensmcp', 'events.jsonl');
135
- try {
136
- (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(eventFile), { recursive: true });
137
- // Truncate ONLY a stale file: another producer (e.g. the instrumented API in
138
- // its own agent-dev) may be mid-session on the same bridge — wiping its
139
- // events would blind every reader. "Active" = modified in the last 60s.
140
- const st = (0, node_fs_1.statSync)(eventFile, { throwIfNoEntry: false });
141
- if (!st || Date.now() - st.mtimeMs > 60_000) {
142
- (0, node_fs_1.writeFileSync)(eventFile, '');
143
- }
144
- else {
145
- console.log('[agent-dev] event bridge is active (written <60s ago) — appending, not truncating.');
146
- }
147
- }
148
- catch {
149
- /* non-fatal — ingest tolerates a missing file */
150
- }
151
- const bridgeEnv = { LENSMCP_EVENT_FILE: eventFile };
152
- console.log(`[agent-dev] event bridge → ${eventFile}`);
153
- // The per-app half — Vite + browser capture — via the SHARED helper (the
154
- // cluster gateway calls the identical function for its `lens:true` apps).
155
- try {
156
- (0, lens_frontend_1.spawnLensFrontend)({
157
- projectRoot,
158
- workspaceRoot: context.root,
159
- ...(project ? { project } : {}),
160
- eventFile,
161
- ...(opts.port !== undefined ? { port: opts.port } : {}),
162
- chrome: opts.chrome,
163
- headless: opts.headless,
164
- ...(opts.openUrl ? { openUrl: opts.openUrl } : {}),
165
- log: (l) => console.log(l),
166
- }, spawnChild);
167
- }
168
- catch (e) {
169
- console.error(e.message);
170
- shutdown();
171
- return { success: false };
172
- }
173
- // The SINGLETON half — standalone, agent-dev owns the MCP server (in cluster
174
- // mode the gateway owns it instead; a `lens:true` app must NOT spawn its own).
175
- if (mcpBundle) {
176
- console.log(`[agent-dev] starting LensMCP MCP server (${mcpBundle})`);
177
- spawnChild('lensmcp-mcp', process.execPath, [mcpBundle], {
178
- ...bridgeEnv,
179
- LENSMCP_TRANSPORT: process.env['LENSMCP_TRANSPORT'] ?? 'http',
180
- LENSMCP_PORT: process.env['LENSMCP_PORT'] ?? '3000',
181
- });
182
- }
183
- // Wait until any child exits.
184
- return new Promise((res) => {
185
- const tick = () => {
186
- if (exitRequested) {
187
- Promise.allSettled(children.map((c) => new Promise((r) => {
188
- if (c.exitCode !== null)
189
- r();
190
- else
191
- c.on('exit', () => r());
192
- }))).then(() => res({ success: true }));
193
- }
194
- else {
195
- setTimeout(tick, 500);
196
- }
197
- };
198
- tick();
199
- });
200
- }
201
- // ---------- nestjs branch ----------
202
- async function runNestjs(projectRoot, context) {
203
- // The built entry lives in different places per setup:
204
- // 1. the project build target's declared outputPath (most precise),
205
- // 2. <projectRoot>/dist/main.js (standalone tsc layout),
206
- // 3. <workspaceRoot>/dist/<projectRoot>/main.js (standard Nx layout).
207
- const projectName = context.projectName;
208
- const buildTarget = projectName
209
- ? context.projectsConfigurations?.projects[projectName]?.targets?.['build']
210
- : undefined;
211
- const outputPath = buildTarget?.options?.['outputPath'];
212
- const candidates = [
213
- ...(typeof outputPath === 'string' ? [(0, node_path_1.join)(context.root, outputPath, 'main.js')] : []),
214
- (0, node_path_1.join)(projectRoot, 'dist', 'main.js'),
215
- (0, node_path_1.join)(context.root, 'dist', (0, node_path_1.relative)(context.root, projectRoot), 'main.js'),
216
- ];
217
- const main = candidates.find((c) => (0, node_fs_1.existsSync)(c));
218
- if (!main) {
219
- console.error(`[agent-dev] No built NestJS entry found. Looked in:\n` +
220
- candidates.map((c) => ` • ${c}`).join('\n') +
221
- `\nBuild first: \`nx build ${projectName ?? '<project>'}\`.`);
222
- return { success: false };
223
- }
224
- console.log(`[agent-dev] node ${main}`);
225
- const child = (0, node_child_process_1.spawn)(process.execPath, [main], {
226
- cwd: projectRoot,
227
- stdio: 'inherit',
228
- env: {
229
- ...process.env,
230
- LENSMCP_TRANSPORT: process.env['LENSMCP_TRANSPORT'] ?? 'http',
231
- LENSMCP_EVENT_FILE: process.env['LENSMCP_EVENT_FILE'] ??
232
- (0, node_path_1.join)(context.root, '.lensmcp', 'events.jsonl'),
233
- },
234
- });
235
- return new Promise((res) => {
236
- const onSignal = () => {
237
- try {
238
- child.kill('SIGTERM');
239
- }
240
- catch { /* swallow */ }
241
- };
242
- process.on('SIGINT', onSignal);
243
- process.on('SIGTERM', onSignal);
244
- child.on('exit', (code) => {
245
- process.off('SIGINT', onSignal);
246
- process.off('SIGTERM', onSignal);
247
- res({ success: code === 0 });
248
- });
249
- });
250
- }
251
- // ---------- imported / bring-your-own-build branch ----------
252
- /**
253
- * `kind: "imported"` — for hosts LensMCP does NOT build: webpack, Next.js, a
254
- * custom builder, or no build at all. Instead of owning the dev server, we run
255
- * the host's OWN dev command and wrap it with the build-agnostic seams:
256
- *
257
- * 1. the standalone bridge sidecar (`@lensmcp/bridge`) on a FIXED ws port,
258
- * so the injected `@lensmcp/client-runtime` finds it by a known URL;
259
- * 2. the LensMCP MCP server (tails the shared event file);
260
- * 3. the host's `devCommand`, with `LENSMCP_EVENT_FILE` + `LENSMCP_WS_PORT`
261
- * exported and (when installed) `NODE_OPTIONS=--import
262
- * @lensmcp/node-instrumentation/register` for zero-touch backend taps;
263
- * 4. an optional Chrome capture sidecar against `openUrl`.
264
- *
265
- * The frontend seam is the user's responsibility (one line —
266
- * `import '@lensmcp/client-runtime/auto'`), since we don't control their build.
267
- */
268
- async function runImported(projectRoot, context, opts) {
269
- if (!opts.devCommand) {
270
- console.error('[agent-dev] kind="imported" needs a `devCommand` (your dev-server command, e.g. "npm run dev").\n' +
271
- ' Add it to the agent-dev target options, or pass `--devCommand "npm run dev"`.');
272
- return { success: false };
273
- }
274
- const bridgeBundle = (0, lens_frontend_1.findBridgeBundle)(context.root);
275
- if (!bridgeBundle) {
276
- console.error("[agent-dev] No lensmcp bridge bundle found. Install the self-contained CLI: `npm i -D lensmcp` (it ships `bundled/bridge.js`).");
277
- return { success: false };
278
- }
279
- const mcpBundle = (0, lens_frontend_1.findMcpBundle)(context.root);
280
- if (!mcpBundle) {
281
- console.warn("[agent-dev] No lensmcp-mcp bundle found — skipping MCP server. Install the self-contained CLI: `npm i -D lensmcp`.");
282
- }
283
- const children = [];
284
- const respawnCounts = new Map();
285
- let exitRequested = false;
286
- function spawnChild(label, bin, args, env, optional = false, respawn, useShell = false) {
287
- const child = (0, node_child_process_1.spawn)(bin, args, {
288
- cwd: projectRoot,
289
- env: { ...process.env, ...(env ?? {}) },
290
- stdio: 'inherit',
291
- shell: useShell,
292
- });
293
- children.push(child);
294
- child.on('exit', (code, sig) => {
295
- if (exitRequested)
296
- return;
297
- if (optional) {
298
- const used = respawnCounts.get(label) ?? 0;
299
- if (respawn && used < respawn.max) {
300
- respawnCounts.set(label, used + 1);
301
- console.warn(`[agent-dev] ${label} exited (code=${code} sig=${sig}); respawning in ${Math.round(respawn.delayMs / 1000)}s (${used + 1}/${respawn.max}).`);
302
- const t = setTimeout(() => {
303
- if (!exitRequested)
304
- spawnChild(label, bin, args, env, optional, respawn, useShell);
305
- }, respawn.delayMs);
306
- t.unref?.();
307
- return;
308
- }
309
- console.warn(`[agent-dev] ${label} exited (code=${code} sig=${sig}); continuing.`);
310
- return;
311
- }
312
- console.warn(`[agent-dev] ${label} exited unexpectedly (code=${code} sig=${sig}); shutting down others.`);
313
- shutdown();
314
- });
315
- return child;
316
- }
317
- function shutdown() {
318
- if (exitRequested)
319
- return;
320
- exitRequested = true;
321
- for (const c of children) {
322
- try {
323
- c.kill('SIGTERM');
324
- }
325
- catch { /* swallow */ }
326
- }
327
- }
328
- process.on('SIGINT', shutdown);
329
- process.on('SIGTERM', shutdown);
330
- // Shared event bridge file (same contract as the Vite branch).
331
- const eventFile = process.env['LENSMCP_EVENT_FILE'] ?? (0, node_path_1.join)(context.root, '.lensmcp', 'events.jsonl');
332
- try {
333
- (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(eventFile), { recursive: true });
334
- const st = (0, node_fs_1.statSync)(eventFile, { throwIfNoEntry: false });
335
- if (!st || Date.now() - st.mtimeMs > 60_000) {
336
- (0, node_fs_1.writeFileSync)(eventFile, '');
337
- }
338
- else {
339
- console.log('[agent-dev] event bridge is active (written <60s ago) — appending, not truncating.');
340
- }
341
- }
342
- catch {
343
- /* non-fatal — ingest tolerates a missing file */
344
- }
345
- const bridgeEnv = { LENSMCP_EVENT_FILE: eventFile };
346
- console.log(`[agent-dev] event bridge → ${eventFile}`);
347
- // 1. Standalone browser-event bridge on a fixed port.
348
- console.log(`[agent-dev] starting bridge → ws://127.0.0.1:${opts.wsPort}`);
349
- spawnChild('bridge', process.execPath, [bridgeBundle], {
350
- ...bridgeEnv,
351
- LENSMCP_WS_HOST: process.env['LENSMCP_WS_HOST'] ?? '127.0.0.1',
352
- LENSMCP_WS_PORT: String(opts.wsPort),
353
- });
354
- // 2. MCP server.
355
- if (mcpBundle) {
356
- console.log(`[agent-dev] starting LensMCP MCP server (${mcpBundle})`);
357
- spawnChild('lensmcp-mcp', process.execPath, [mcpBundle], {
358
- ...bridgeEnv,
359
- LENSMCP_TRANSPORT: process.env['LENSMCP_TRANSPORT'] ?? 'http',
360
- LENSMCP_PORT: process.env['LENSMCP_PORT'] ?? '3000',
361
- });
362
- }
363
- // 3. The host's own dev command, wrapped with the event bridge + ws port,
364
- // plus zero-touch backend taps when @lensmcp/node-instrumentation is
365
- // installed (it's a no-op import otherwise, so we gate on presence).
366
- const devEnv = { ...bridgeEnv, LENSMCP_WS_PORT: String(opts.wsPort) };
367
- if (opts.node) {
368
- const hasNodeInstr = [projectRoot, context.root].some((r) => (0, node_fs_1.existsSync)((0, node_path_1.join)(r, 'node_modules', '@lensmcp', 'node-instrumentation')));
369
- if (hasNodeInstr) {
370
- const register = '--import @lensmcp/node-instrumentation/register';
371
- devEnv.NODE_OPTIONS = process.env['NODE_OPTIONS']
372
- ? `${process.env['NODE_OPTIONS']} ${register}`
373
- : register;
374
- console.log('[agent-dev] backend taps: NODE_OPTIONS += --import @lensmcp/node-instrumentation/register');
375
- }
376
- else {
377
- console.warn('[agent-dev] @lensmcp/node-instrumentation not installed — skipping backend taps. Add it: `npm i -D @lensmcp/node-instrumentation`.');
378
- }
379
- }
380
- console.log(`[agent-dev] front-end seam: add \`import '@lensmcp/client-runtime/auto'\` to your app entry (connects to ws://127.0.0.1:${opts.wsPort}).`);
381
- console.log(`[agent-dev] starting dev command: ${opts.devCommand}`);
382
- spawnChild('dev', opts.devCommand, [], devEnv, false, undefined, true /* shell */);
383
- // 4. Optional Chrome capture (needs a known URL — we don't infer the port).
384
- if (opts.chrome) {
385
- const captureRunner = (0, lens_frontend_1.findCaptureRunner)(context.root);
386
- const devUrl = opts.openUrl;
387
- if (!captureRunner) {
388
- console.warn("[agent-dev] browser-capture runner not found — skipping live capture. Install the self-contained CLI: `npm i -D lensmcp`.");
389
- }
390
- else if (!devUrl) {
391
- console.warn('[agent-dev] no `openUrl` provided — skipping Chrome capture (set openUrl to your dev URL).');
392
- }
393
- else {
394
- console.log(`[agent-dev] starting browser capture → ${devUrl}`);
395
- spawnChild('browser-capture', process.execPath, [captureRunner], {
396
- ...bridgeEnv,
397
- LENSMCP_DEV_URL: devUrl,
398
- LENSMCP_TOKENS_FILE: (0, node_path_1.join)(context.root, 'lensmcp.tokens.json'),
399
- LENSMCP_RULES_FILE: (0, node_path_1.join)(context.root, 'lensmcp.rules.json'),
400
- LENSMCP_HEADLESS: opts.headless === false ? 'false' : 'true',
401
- }, true, { delayMs: 10_000, max: 5 });
402
- }
403
- }
404
- // Wait until any non-optional child exits.
405
- return new Promise((res) => {
406
- const tick = () => {
407
- if (exitRequested) {
408
- Promise.allSettled(children.map((c) => new Promise((r) => {
409
- if (c.exitCode !== null)
410
- r();
411
- else
412
- c.on('exit', () => r());
413
- }))).then(() => res({ success: true }));
414
- }
415
- else {
416
- setTimeout(tick, 500);
417
- }
418
- };
419
- tick();
420
- });
421
- }
422
- // ---------- helpers ----------
423
- // The bundle/bin resolvers (locateBin, findMcpBundle, findCaptureRunner,
424
- // findBridgeBundle, …) now live in the shared `../../lens-frontend` module so
425
- // the cluster gateway reuses the EXACT same resolution. Imported at the top.
1
+ "use strict";var L=Object.defineProperty;var E=(t,o)=>L(t,"name",{value:o,configurable:!0});var T=Object.defineProperty,l=E((t,o)=>T(t,"name",{value:o,configurable:!0}),"l");Object.defineProperty(exports,"__esModule",{value:!0}),exports.default=agentDevExecutor;const node_child_process_1=require("node:child_process"),node_fs_1=require("node:fs"),node_path_1=require("node:path"),lens_frontend_1=require("../../lens-frontend");async function agentDevExecutor(t,o){const n={kind:t.kind??"vite-react",chrome:t.chrome??!0,headless:t.headless??!0,node:t.node??!0,devTarget:t.devTarget,projectRoot:t.projectRoot,port:t.port,openUrl:t.openUrl,devCommand:t.devCommand,wsPort:t.wsPort};if(n.kind!=="vite-react"&&n.kind!=="nestjs"&&n.kind!=="imported")return console.error(`[agent-dev] supported kinds are "vite-react", "nestjs", "imported" (got "${n.kind}").`),{success:!1};const P=o.projectName,d=n.projectRoot?(0,node_path_1.resolve)(o.root,n.projectRoot):P&&o.projectsConfigurations?(0,node_path_1.resolve)(o.root,o.projectsConfigurations.projects[P]?.root??"."):o.root;if(n.kind==="nestjs")return runNestjs(d,o);if(n.kind==="imported")return runImported(d,o,{devCommand:n.devCommand,chrome:n.chrome,headless:n.headless,openUrl:n.openUrl,wsPort:n.wsPort??5747,node:n.node});const a=(0,lens_frontend_1.findMcpBundle)(o.root);a||console.warn("[agent-dev] No lensmcp-mcp bundle found \u2014 skipping MCP server. Install the self-contained CLI: `npm i -D lensmcp` (it ships `bundled/main.js`).");const S=[],c=new Map;let s=!1;function p(e,r,v,g,C=!1,i){const w=(0,node_child_process_1.spawn)(r,v,{cwd:d,env:{...process.env,...g??{}},stdio:"inherit"});return S.push(w),w.on("exit",(f,h)=>{if(!s){if(C){const N=c.get(e)??0;if(i&&N<i.max){c.set(e,N+1),console.warn(`[agent-dev] ${e} exited (code=${f} sig=${h}); respawning in ${Math.round(i.delayMs/1e3)}s (${N+1}/${i.max}).`),setTimeout(()=>{s||p(e,r,v,g,C,i)},i.delayMs).unref?.();return}console.warn(`[agent-dev] ${e} exited (code=${f} sig=${h}); continuing.`);return}console.warn(`[agent-dev] ${e} exited unexpectedly (code=${f} sig=${h}); shutting down others.`),u()}}),w}E(p,"d"),l(p,"spawnChild");function u(){if(!s){s=!0;for(const e of S)try{e.kill("SIGTERM")}catch{}}}E(u,"a"),l(u,"shutdown"),process.on("SIGINT",u),process.on("SIGTERM",u);const m=process.env.LENSMCP_EVENT_FILE??(0,node_path_1.join)(o.root,".lensmcp","events.jsonl");try{(0,node_fs_1.mkdirSync)((0,node_path_1.dirname)(m),{recursive:!0});const e=(0,node_fs_1.statSync)(m,{throwIfNoEntry:!1});!e||Date.now()-e.mtimeMs>6e4?(0,node_fs_1.writeFileSync)(m,""):console.log("[agent-dev] event bridge is active (written <60s ago) \u2014 appending, not truncating.")}catch{}const M={LENSMCP_EVENT_FILE:m};console.log(`[agent-dev] event bridge \u2192 ${m}`);try{(0,lens_frontend_1.spawnLensFrontend)({projectRoot:d,workspaceRoot:o.root,...P?{project:P}:{},eventFile:m,...n.port!==void 0?{port:n.port}:{},chrome:n.chrome,headless:n.headless,...n.openUrl?{openUrl:n.openUrl}:{},log:l(e=>console.log(e),"log")},p)}catch(e){return console.error(e.message),u(),{success:!1}}return a&&(console.log(`[agent-dev] starting LensMCP MCP server (${a})`),p("lensmcp-mcp",process.execPath,[a],{...M,LENSMCP_TRANSPORT:process.env.LENSMCP_TRANSPORT??"http",LENSMCP_PORT:process.env.LENSMCP_PORT??"3000"})),new Promise(e=>{const r=l(()=>{s?Promise.allSettled(S.map(v=>new Promise(g=>{v.exitCode!==null?g():v.on("exit",()=>g())}))).then(()=>e({success:!0})):setTimeout(r,500)},"tick");r()})}E(agentDevExecutor,"agentDevExecutor"),l(agentDevExecutor,"agentDevExecutor");async function runNestjs(t,o){const n=o.projectName,P=(n?o.projectsConfigurations?.projects[n]?.targets?.build:void 0)?.options?.outputPath,d=[...typeof P=="string"?[(0,node_path_1.join)(o.root,P,"main.js")]:[],(0,node_path_1.join)(t,"dist","main.js"),(0,node_path_1.join)(o.root,"dist",(0,node_path_1.relative)(o.root,t),"main.js")],a=d.find(c=>(0,node_fs_1.existsSync)(c));if(!a)return console.error(`[agent-dev] No built NestJS entry found. Looked in:
2
+ `+d.map(c=>` \u2022 ${c}`).join(`
3
+ `)+`
4
+ Build first: \`nx build ${n??"<project>"}\`.`),{success:!1};console.log(`[agent-dev] node ${a}`);const S=(0,node_child_process_1.spawn)(process.execPath,[a],{cwd:t,stdio:"inherit",env:{...process.env,LENSMCP_TRANSPORT:process.env.LENSMCP_TRANSPORT??"http",LENSMCP_EVENT_FILE:process.env.LENSMCP_EVENT_FILE??(0,node_path_1.join)(o.root,".lensmcp","events.jsonl")}});return new Promise(c=>{const s=l(()=>{try{S.kill("SIGTERM")}catch{}},"onSignal");process.on("SIGINT",s),process.on("SIGTERM",s),S.on("exit",p=>{process.off("SIGINT",s),process.off("SIGTERM",s),c({success:p===0})})})}E(runNestjs,"runNestjs"),l(runNestjs,"runNestjs");async function runImported(t,o,n){if(!n.devCommand)return console.error('[agent-dev] kind="imported" needs a `devCommand` (your dev-server command, e.g. "npm run dev").\n Add it to the agent-dev target options, or pass `--devCommand "npm run dev"`.'),{success:!1};const P=(0,lens_frontend_1.findBridgeBundle)(o.root);if(!P)return console.error("[agent-dev] No lensmcp bridge bundle found. Install the self-contained CLI: `npm i -D lensmcp` (it ships `bundled/bridge.js`)."),{success:!1};const d=(0,lens_frontend_1.findMcpBundle)(o.root);d||console.warn("[agent-dev] No lensmcp-mcp bundle found \u2014 skipping MCP server. Install the self-contained CLI: `npm i -D lensmcp`.");const a=[],S=new Map;let c=!1;function s(e,r,v,g,C=!1,i,w=!1){const f=(0,node_child_process_1.spawn)(r,v,{cwd:t,env:{...process.env,...g??{}},stdio:"inherit",shell:w});return a.push(f),f.on("exit",(h,N)=>{if(!c){if(C){const _=S.get(e)??0;if(i&&_<i.max){S.set(e,_+1),console.warn(`[agent-dev] ${e} exited (code=${h} sig=${N}); respawning in ${Math.round(i.delayMs/1e3)}s (${_+1}/${i.max}).`),setTimeout(()=>{c||s(e,r,v,g,C,i,w)},i.delayMs).unref?.();return}console.warn(`[agent-dev] ${e} exited (code=${h} sig=${N}); continuing.`);return}console.warn(`[agent-dev] ${e} exited unexpectedly (code=${h} sig=${N}); shutting down others.`),p()}}),f}E(s,"r"),l(s,"spawnChild");function p(){if(!c){c=!0;for(const e of a)try{e.kill("SIGTERM")}catch{}}}E(p,"d"),l(p,"shutdown"),process.on("SIGINT",p),process.on("SIGTERM",p);const u=process.env.LENSMCP_EVENT_FILE??(0,node_path_1.join)(o.root,".lensmcp","events.jsonl");try{(0,node_fs_1.mkdirSync)((0,node_path_1.dirname)(u),{recursive:!0});const e=(0,node_fs_1.statSync)(u,{throwIfNoEntry:!1});!e||Date.now()-e.mtimeMs>6e4?(0,node_fs_1.writeFileSync)(u,""):console.log("[agent-dev] event bridge is active (written <60s ago) \u2014 appending, not truncating.")}catch{}const m={LENSMCP_EVENT_FILE:u};console.log(`[agent-dev] event bridge \u2192 ${u}`),console.log(`[agent-dev] starting bridge \u2192 ws://127.0.0.1:${n.wsPort}`),s("bridge",process.execPath,[P],{...m,LENSMCP_WS_HOST:process.env.LENSMCP_WS_HOST??"127.0.0.1",LENSMCP_WS_PORT:String(n.wsPort)}),d&&(console.log(`[agent-dev] starting LensMCP MCP server (${d})`),s("lensmcp-mcp",process.execPath,[d],{...m,LENSMCP_TRANSPORT:process.env.LENSMCP_TRANSPORT??"http",LENSMCP_PORT:process.env.LENSMCP_PORT??"3000"}));const M={...m,LENSMCP_WS_PORT:String(n.wsPort)};if(n.node)if([t,o.root].some(e=>(0,node_fs_1.existsSync)((0,node_path_1.join)(e,"node_modules","@lensmcp","node-instrumentation")))){const e="--import @lensmcp/node-instrumentation/register";M.NODE_OPTIONS=process.env.NODE_OPTIONS?`${process.env.NODE_OPTIONS} ${e}`:e,console.log("[agent-dev] backend taps: NODE_OPTIONS += --import @lensmcp/node-instrumentation/register")}else console.warn("[agent-dev] @lensmcp/node-instrumentation not installed \u2014 skipping backend taps. Add it: `npm i -D @lensmcp/node-instrumentation`.");if(console.log(`[agent-dev] front-end seam: add \`import '@lensmcp/client-runtime/auto'\` to your app entry (connects to ws://127.0.0.1:${n.wsPort}).`),console.log(`[agent-dev] starting dev command: ${n.devCommand}`),s("dev",n.devCommand,[],M,!1,void 0,!0),n.chrome){const e=(0,lens_frontend_1.findCaptureRunner)(o.root),r=n.openUrl;e?r?(console.log(`[agent-dev] starting browser capture \u2192 ${r}`),s("browser-capture",process.execPath,[e],{...m,LENSMCP_DEV_URL:r,LENSMCP_TOKENS_FILE:(0,node_path_1.join)(o.root,"lensmcp.tokens.json"),LENSMCP_RULES_FILE:(0,node_path_1.join)(o.root,"lensmcp.rules.json"),LENSMCP_HEADLESS:n.headless===!1?"false":"true"},!0,{delayMs:1e4,max:5})):console.warn("[agent-dev] no `openUrl` provided \u2014 skipping Chrome capture (set openUrl to your dev URL)."):console.warn("[agent-dev] browser-capture runner not found \u2014 skipping live capture. Install the self-contained CLI: `npm i -D lensmcp`.")}return new Promise(e=>{const r=l(()=>{c?Promise.allSettled(a.map(v=>new Promise(g=>{v.exitCode!==null?g():v.on("exit",()=>g())}))).then(()=>e({success:!0})):setTimeout(r,500)},"tick");r()})}E(runImported,"runImported"),l(runImported,"runImported");