@_deep4wee/agent-lens 1.0.1 → 1.1.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/dist/cli.mjs CHANGED
@@ -1,15 +1,8 @@
1
- #!/usr/bin/env node
2
1
  var __getOwnPropNames = Object.getOwnPropertyNames;
3
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
4
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
5
- }) : x)(function(x) {
6
- if (typeof require !== "undefined") return require.apply(this, arguments);
7
- throw Error('Dynamic require of "' + x + '" is not supported');
8
- });
9
2
  var __esm = (fn, res) => function __init() {
10
3
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
4
  };
12
- var __commonJS = (cb, mod) => function __require2() {
5
+ var __commonJS = (cb, mod) => function __require() {
13
6
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
14
7
  };
15
8
 
@@ -180,9 +173,9 @@ var init_reporter = __esm({
180
173
  burstGroups.set(s.burstGroup, list);
181
174
  });
182
175
  for (const snap of regularSnapshots) {
183
- const fileUri = `file:///${snap.filePath.replace(/\\/g, "/")}`;
176
+ const relativeLink = snap.relativeUri || `./${snap.fileName}`;
184
177
  rows.push(
185
- `| **${String(snap.index).padStart(2, "0")}** | \`${snap.viewport.width}x${snap.viewport.height}\` | ${snap.name} | [${snap.fileName}](${fileUri}) |`
178
+ `| **${String(snap.index).padStart(2, "0")}** | \`${snap.viewport.width}x${snap.viewport.height}\` | ${snap.name} | [${snap.fileName}](${relativeLink}) |`
186
179
  );
187
180
  }
188
181
  let burstSections = "";
@@ -199,8 +192,8 @@ var init_reporter = __esm({
199
192
  | :--- | :--- | :--- | :--- |
200
193
  `;
201
194
  for (const frame of frames) {
202
- const fileUri = `file:///${frame.filePath.replace(/\\/g, "/")}`;
203
- burstSections += `| Frame ${frame.frameIndex} | \`${frame.viewport.width}x${frame.viewport.height}\` | [${frame.fileName}](${fileUri}) | ![](${fileUri}) |
195
+ const relativeLink = frame.relativeUri || `./${frame.fileName}`;
196
+ burstSections += `| Frame ${frame.frameIndex} | \`${frame.viewport.width}x${frame.viewport.height}\` | [${frame.fileName}](${relativeLink}) | ![](${relativeLink}) |
204
197
  `;
205
198
  }
206
199
  burstSections += `
@@ -289,7 +282,9 @@ ${consoleSections}
289
282
 
290
283
  ## \u{1F4CB} AI Agent Verification Checklist:
291
284
  - [ ] **Console Errors**: ${consoleErrors.length === 0 ? "\u2705 No errors found" : `\u274C ${consoleErrors.length} errors \u2014 MUST REVIEW`}
285
+ - [ ] **Visual Layout Check**: Inspect snapshots (e.g. view_image on \`./01_${regularSnapshots[0]?.fileName || "quick_snap"}\`) for layout shifts or clipped elements.
292
286
  - [ ] **Responsiveness at \`1024x768\`**: Elements do not overflow the screen, no unwanted horizontal scroll.
287
+
293
288
  - [ ] **Typography & Spacing**: Spacing matches the design system and layout grids.
294
289
  - [ ] **Color Palette & Theme**: Background tints and button accent colors match the concept.
295
290
  - [ ] **Component States**: Modals open centered, dropdowns do not overlap with other layers (z-index).
@@ -320,8 +315,6 @@ var init_consoleTracker = __esm({
320
315
  ConsoleTracker = class {
321
316
  entries = [];
322
317
  attached = false;
323
- /**
324
- */
325
318
  attach(page) {
326
319
  if (this.attached) return;
327
320
  this.attached = true;
@@ -390,8 +383,6 @@ var init_consoleTracker = __esm({
390
383
  return "log";
391
384
  }
392
385
  }
393
- /**
394
- */
395
386
  isIgnoredWarning(text) {
396
387
  const ignoredPatterns = [
397
388
  "findDOMNode is deprecated",
@@ -411,10 +402,30 @@ var init_consoleTracker = __esm({
411
402
  });
412
403
 
413
404
  // src/shared/lib/playwrightLoader.ts
414
- import { chromium } from "playwright";
405
+ import { chromium as baseChromium } from "playwright";
406
+ var chromium;
415
407
  var init_playwrightLoader = __esm({
416
408
  "src/shared/lib/playwrightLoader.ts"() {
417
409
  "use strict";
410
+ chromium = new Proxy(baseChromium, {
411
+ get(target, prop, receiver) {
412
+ if (prop === "launch") {
413
+ return async (...args) => {
414
+ try {
415
+ return await target.launch(...args);
416
+ } catch (err) {
417
+ if (err.message?.includes("Executable doesn't exist") || err.message?.includes("playwright install") || err.message?.includes("browser has not been downloaded")) {
418
+ console.error("\n\u274C [AgentLens] Playwright Chromium browser binary is missing!");
419
+ console.error("\u{1F449} Please install it by running: npx playwright install chromium\n");
420
+ }
421
+ throw err;
422
+ }
423
+ };
424
+ }
425
+ const val = Reflect.get(target, prop, receiver);
426
+ return typeof val === "function" ? val.bind(target) : val;
427
+ }
428
+ });
418
429
  }
419
430
  });
420
431
 
@@ -423,6 +434,7 @@ import { spawn } from "child_process";
423
434
  import http from "http";
424
435
  import path3 from "path";
425
436
  import fs3 from "fs";
437
+ import treeKill from "tree-kill";
426
438
  var DesktopDriver;
427
439
  var init_desktopDriver = __esm({
428
440
  "src/shared/drivers/desktopDriver.ts"() {
@@ -501,6 +513,11 @@ ${this.processStderr.trim() || "(no stderr output)"}`
501
513
  );
502
514
  }
503
515
  console.log(`[DesktopDriver] Launching: ${resolvedExe}`);
516
+ const finalArgs = [...this.args];
517
+ const hasDebugPort = finalArgs.some((a) => a.startsWith("--remote-debugging-port="));
518
+ if (!hasDebugPort) {
519
+ finalArgs.push(`--remote-debugging-port=${this.port}`);
520
+ }
504
521
  const mergedEnv = {
505
522
  ...process.env,
506
523
  ...this.env,
@@ -509,6 +526,16 @@ ${this.processStderr.trim() || "(no stderr output)"}`
509
526
  this.processStderr = "";
510
527
  this.processExited = false;
511
528
  this.exitCode = null;
529
+ this.childProcess = spawn(resolvedExe, finalArgs, {
530
+ env: mergedEnv,
531
+ cwd: this.cwd || path3.dirname(resolvedExe),
532
+ stdio: ["ignore", "ignore", "pipe"],
533
+ detached: false
534
+ });
535
+ ;
536
+ this.processStderr = "";
537
+ this.processExited = false;
538
+ this.exitCode = null;
512
539
  this.childProcess = spawn(resolvedExe, this.args, {
513
540
  env: mergedEnv,
514
541
  cwd: this.cwd || path3.dirname(resolvedExe),
@@ -552,16 +579,12 @@ ${this.processStderr.trim() || "(no stderr output)"}`
552
579
  });
553
580
  this.browser = null;
554
581
  }
555
- if (this.childProcess && !this.childProcess.killed) {
556
- console.log("[DesktopDriver] Terminating spawned desktop process...");
557
- try {
558
- if (process.platform === "win32" && this.childProcess.pid) {
559
- spawn("taskkill", ["/pid", String(this.childProcess.pid), "/T", "/F"], { stdio: "ignore" });
560
- } else {
561
- this.childProcess.kill("SIGTERM");
562
- }
563
- } catch {
564
- }
582
+ if (this.childProcess && !this.childProcess.killed && this.childProcess.pid) {
583
+ console.log("[DesktopDriver] Terminating spawned desktop process tree...");
584
+ const pid = this.childProcess.pid;
585
+ await new Promise((resolve) => {
586
+ treeKill(pid, "SIGTERM", () => resolve());
587
+ });
565
588
  this.childProcess = null;
566
589
  }
567
590
  }
@@ -600,42 +623,45 @@ function generateMockIpcScript(registry) {
600
623
  }
601
624
  };
602
625
 
603
- // Legacy fallback for generic window.external
604
- if (!window.external) {
605
- window.external = {};
606
- }
607
-
608
- window.external.sendMessage = (msg) => {
609
- try {
610
- const parsed = JSON.parse(msg);
611
- const action = parsed.Action || parsed.action;
612
- const id = parsed.Id || parsed.id;
613
-
614
- const mock = window.__visualRunnerMocks[action];
626
+ // Safe fallback bridge for hybrid webviews (Photino / CEF / WebView2)
627
+ try {
628
+ if (!window.external) {
629
+ (window as any).external = {};
630
+ }
631
+ (window.external as any).sendMessage = (msg) => {
632
+ try {
633
+ const parsed = typeof msg === 'string' ? JSON.parse(msg) : msg;
634
+ const action = parsed.Action || parsed.action;
635
+ const id = parsed.Id || parsed.id;
636
+ const mock = (window as any).__visualRunnerMocks[action];
615
637
 
616
- if (mock) {
617
- const response = { Id: id, Type: mock.type || 'SUCCESS', Data: mock.data };
618
- setTimeout(() => {
619
- const cb = window.__mockCallback;
620
- if (cb) cb(JSON.stringify(response));
621
- }, mock.delayMs || 20);
622
- } else {
623
- setTimeout(() => {
624
- const cb = window.__mockCallback;
625
- if (cb) cb(JSON.stringify({ Id: id, Type: 'SUCCESS', Data: null }));
626
- }, 20);
638
+ if (mock) {
639
+ const response = { Id: id, Type: mock.type || 'SUCCESS', Data: mock.data };
640
+ setTimeout(() => {
641
+ const cb = (window as any).__mockCallback;
642
+ if (typeof cb === 'function') cb(JSON.stringify(response));
643
+ }, mock.delayMs || 20);
644
+ } else {
645
+ setTimeout(() => {
646
+ const cb = (window as any).__mockCallback;
647
+ if (typeof cb === 'function') cb(JSON.stringify({ Id: id, Type: 'SUCCESS', Data: null }));
648
+ }, 20);
649
+ }
650
+ } catch (e) {
651
+ console.error('[Mock IPC] Failed to process message:', e);
627
652
  }
628
- } catch (e) {
629
- console.error('[Mock IPC] Failed to process legacy message:', e);
630
- }
631
- };
653
+ };
632
654
 
633
- window.external.receiveMessage = (callback) => {
634
- window.__mockCallback = callback;
635
- };
655
+ (window.external as any).receiveMessage = (callback) => {
656
+ (window as any).__mockCallback = callback;
657
+ };
658
+ } catch {
659
+ // Ignored if window.external is read-only in strict Chromium sandboxes
660
+ }
636
661
 
637
- console.log('[Visual Runner] Mock IPC bridge initialized with', Object.keys(window.__visualRunnerMocks).length, 'mocked actions');
662
+ console.log('[Visual Runner] Mock IPC bridge initialized with', Object.keys((window as any).__visualRunnerMocks).length, 'mocked actions');
638
663
  })();
664
+
639
665
  `;
640
666
  }
641
667
  var MockIpcRegistry;
@@ -685,22 +711,22 @@ var init_mockIpc = __esm({
685
711
  });
686
712
 
687
713
  // src/shared/lib/config.ts
688
- import fs4 from "fs";
689
714
  import path4 from "path";
715
+ import fs4 from "fs";
690
716
  function loadConfig(cwd = process.cwd()) {
691
- const configPath = path4.join(cwd, "agent-lens.json");
692
- if (fs4.existsSync(configPath)) {
717
+ const jsonConfigPath = path4.join(cwd, "agent-lens.json");
718
+ if (fs4.existsSync(jsonConfigPath)) {
693
719
  try {
694
- const raw = fs4.readFileSync(configPath, "utf8");
720
+ const raw = fs4.readFileSync(jsonConfigPath, "utf-8");
695
721
  return JSON.parse(raw);
696
722
  } catch (e) {
697
- console.warn(`\u26A0\uFE0F Warning: Failed to parse agent-lens.json:`, e);
723
+ console.warn(`\u26A0\uFE0F [Config] Failed to parse agent-lens.json: ${e.message}`);
698
724
  }
699
725
  }
700
- const pkgPath = path4.join(cwd, "package.json");
701
- if (fs4.existsSync(pkgPath)) {
726
+ const packageJsonPath = path4.join(cwd, "package.json");
727
+ if (fs4.existsSync(packageJsonPath)) {
702
728
  try {
703
- const raw = fs4.readFileSync(pkgPath, "utf8");
729
+ const raw = fs4.readFileSync(packageJsonPath, "utf-8");
704
730
  const pkg = JSON.parse(raw);
705
731
  if (pkg.agentLens && typeof pkg.agentLens === "object") {
706
732
  return pkg.agentLens;
@@ -710,78 +736,60 @@ function loadConfig(cwd = process.cwd()) {
710
736
  }
711
737
  return {};
712
738
  }
713
- function resolveWwwrootDir(customPath, cwd = process.cwd()) {
714
- if (customPath) {
715
- return path4.resolve(cwd, customPath);
739
+ function detectStartCwd(providedCwd) {
740
+ if (providedCwd) {
741
+ const resolved = path4.resolve(process.cwd(), providedCwd);
742
+ if (fs4.existsSync(resolved)) {
743
+ return providedCwd;
744
+ }
716
745
  }
717
- const standardDirs = [
718
- "dist",
719
- "build",
720
- "wwwroot",
721
- "Frontend/dist",
722
- "frontend/dist",
723
- "client/dist",
724
- "web/dist",
725
- "ui/dist"
726
- ];
727
- for (const rel of standardDirs) {
728
- const candidate = path4.resolve(cwd, rel);
729
- if (fs4.existsSync(candidate) && fs4.existsSync(path4.join(candidate, "index.html"))) {
730
- return candidate;
746
+ const rootPkgPath = path4.join(process.cwd(), "package.json");
747
+ let rootHasDevScript = false;
748
+ if (fs4.existsSync(rootPkgPath)) {
749
+ try {
750
+ const rootPkg = JSON.parse(fs4.readFileSync(rootPkgPath, "utf-8"));
751
+ rootHasDevScript = Boolean(rootPkg.scripts?.dev || rootPkg.scripts?.start);
752
+ } catch {
731
753
  }
732
754
  }
733
- const foundDeepWwwroot = findDeepIndexHtmlDir(cwd, 4);
734
- if (foundDeepWwwroot) {
735
- return foundDeepWwwroot;
755
+ if (rootHasDevScript) {
756
+ return void 0;
736
757
  }
737
- for (const rel of standardDirs) {
738
- const candidate = path4.resolve(cwd, rel);
739
- if (fs4.existsSync(candidate)) {
740
- return candidate;
758
+ const candidates = ["Frontend", "frontend", "client", "web", "ui", "apps/web", "src/frontend"];
759
+ for (const candidate of candidates) {
760
+ const candidatePkg = path4.join(process.cwd(), candidate, "package.json");
761
+ if (fs4.existsSync(candidatePkg)) {
762
+ return `./${candidate}`;
741
763
  }
742
764
  }
743
- return path4.resolve(cwd, "dist");
765
+ return void 0;
744
766
  }
745
- function detectStartCwd(customCwd, rootCwd = process.cwd()) {
746
- if (customCwd) {
747
- return path4.resolve(rootCwd, customCwd);
767
+ function resolveWwwrootDir(customDir) {
768
+ if (customDir) {
769
+ return path4.resolve(process.cwd(), customDir);
748
770
  }
749
- const subdirectories = ["Frontend", "frontend", "client", "web", "ui", "app"];
750
- for (const sub of subdirectories) {
751
- const subPkg = path4.join(rootCwd, sub, "package.json");
752
- if (fs4.existsSync(subPkg)) {
753
- try {
754
- const json = JSON.parse(fs4.readFileSync(subPkg, "utf8"));
755
- if (json.scripts && (json.scripts.dev || json.scripts.start || json.scripts.build)) {
756
- return path4.join(rootCwd, sub);
757
- }
758
- } catch {
759
- }
771
+ const candidates = [
772
+ "dist",
773
+ "build",
774
+ "out",
775
+ "wwwroot",
776
+ "Frontend/dist",
777
+ "frontend/dist",
778
+ "client/dist"
779
+ ];
780
+ for (const c of candidates) {
781
+ const candidatePath = path4.resolve(process.cwd(), c);
782
+ if (fs4.existsSync(candidatePath) && fs4.existsSync(path4.join(candidatePath, "index.html"))) {
783
+ return candidatePath;
760
784
  }
761
785
  }
762
- return rootCwd;
763
- }
764
- function findDeepIndexHtmlDir(dir, maxDepth, currentDepth = 0) {
765
- if (currentDepth > maxDepth || !fs4.existsSync(dir)) return null;
766
- try {
767
- const entries = fs4.readdirSync(dir, { withFileTypes: true });
768
- for (const entry of entries) {
769
- if (entry.isDirectory()) {
770
- if (entry.name === "node_modules" || entry.name === ".git" || entry.name === "artifacts") {
771
- continue;
772
- }
773
- const subDir = path4.join(dir, entry.name);
774
- if (entry.name === "wwwroot" && fs4.existsSync(path4.join(subDir, "index.html"))) {
775
- return subDir;
776
- }
777
- const found = findDeepIndexHtmlDir(subDir, maxDepth, currentDepth + 1);
778
- if (found) return found;
779
- }
786
+ for (const c of candidates) {
787
+ const candidatePath = path4.resolve(process.cwd(), c);
788
+ if (fs4.existsSync(candidatePath)) {
789
+ return candidatePath;
780
790
  }
781
- } catch {
782
- return null;
783
791
  }
784
- return null;
792
+ return path4.resolve(process.cwd(), "dist");
785
793
  }
786
794
  var init_config = __esm({
787
795
  "src/shared/lib/config.ts"() {
@@ -823,6 +831,7 @@ var init_previewDriver = __esm({
823
831
  serverPort = 0;
824
832
  options;
825
833
  _baseUrl = "";
834
+ initialRouteMocks = [];
826
835
  mockRegistry;
827
836
  constructor(options) {
828
837
  this.options = options || {};
@@ -892,9 +901,41 @@ var init_previewDriver = __esm({
892
901
  await this.context.addInitScript(mockScript);
893
902
  }
894
903
  this.page = await this.context.newPage();
904
+ if (this.initialRouteMocks.length > 0) {
905
+ for (const entry of this.initialRouteMocks) {
906
+ await this.addRouteMock(entry);
907
+ }
908
+ }
895
909
  await this.page.goto(targetUrl, { waitUntil: "domcontentloaded" });
896
910
  return { page: this.page, context: this.context, browser: this.browser };
897
911
  }
912
+ async addRouteMock(entry) {
913
+ if (!this.page) {
914
+ this.initialRouteMocks.push(entry);
915
+ return;
916
+ }
917
+ await this.page.route(entry.url, async (route) => {
918
+ const req = route.request();
919
+ if (entry.method && req.method().toUpperCase() !== entry.method.toUpperCase()) {
920
+ return route.continue();
921
+ }
922
+ if (entry.delayMs) {
923
+ await new Promise((r) => setTimeout(r, entry.delayMs));
924
+ }
925
+ const isJson = typeof entry.body === "object" && entry.body !== null;
926
+ await route.fulfill({
927
+ status: entry.status ?? 200,
928
+ contentType: isJson ? "application/json" : "text/plain; charset=utf-8",
929
+ body: isJson ? JSON.stringify(entry.body) : String(entry.body ?? ""),
930
+ headers: entry.headers
931
+ });
932
+ });
933
+ }
934
+ async setupRouteMocks(routes) {
935
+ for (const r of routes) {
936
+ await this.addRouteMock(r);
937
+ }
938
+ }
898
939
  async updateMockIpc(action, data, options) {
899
940
  if (!this.page) {
900
941
  throw new Error("PreviewDriver not started. Call start() first.");
@@ -941,108 +982,79 @@ var init_previewDriver = __esm({
941
982
 
942
983
  // src/shared/lib/processManager.ts
943
984
  import { spawn as spawn2 } from "child_process";
944
- import treeKill from "tree-kill";
985
+ import treeKill2 from "tree-kill";
945
986
  var ProcessManager;
946
987
  var init_processManager = __esm({
947
988
  "src/shared/lib/processManager.ts"() {
948
989
  "use strict";
949
990
  ProcessManager = class {
950
- child = null;
951
- stderrOutput = "";
952
- stdoutOutput = "";
953
- hasExited = false;
954
- exitCode = null;
955
- /**
956
- * Spawns a background process (e.g. dev server, backend, or app)
957
- */
991
+ childProcess = null;
992
+ isStopped = false;
958
993
  async start(command, options) {
959
- this.stderrOutput = "";
960
- this.stdoutOutput = "";
961
- this.hasExited = false;
962
- this.exitCode = null;
963
994
  const cwd = options?.cwd || process.cwd();
964
- const env = { ...process.env, ...options?.env };
965
995
  console.log(`\u{1F680} [ProcessManager] Starting command: "${command}" in ${cwd}`);
966
- this.child = spawn2(command, {
996
+ this.childProcess = spawn2(command, {
967
997
  cwd,
968
- env,
969
- shell: options?.shell ?? true,
998
+ env: { ...process.env, ...options?.env },
999
+ shell: true,
970
1000
  stdio: ["ignore", "pipe", "pipe"]
971
1001
  });
972
- this.child.stdout?.on("data", (chunk) => {
973
- const str = chunk.toString();
974
- this.stdoutOutput += str;
975
- });
976
- this.child.stderr?.on("data", (chunk) => {
977
- const str = chunk.toString();
978
- this.stderrOutput += str;
1002
+ this.childProcess.stdout?.on("data", (chunk) => {
1003
+ const line = chunk.toString().trim();
1004
+ if (line) {
1005
+ }
979
1006
  });
980
- this.child.on("exit", (code) => {
981
- this.hasExited = true;
982
- this.exitCode = code;
1007
+ this.childProcess.stderr?.on("data", (chunk) => {
1008
+ const line = chunk.toString().trim();
1009
+ if (line && !line.includes("ExperimentalWarning")) {
1010
+ }
983
1011
  });
984
- this.child.on("error", (err) => {
985
- console.error(`\u274C [ProcessManager] Failed to start command: "${command}":`, err.message);
1012
+ this.childProcess.on("exit", (code, signal) => {
1013
+ if (!this.isStopped && code !== 0 && code !== null) {
1014
+ console.warn(`\u26A0\uFE0F [ProcessManager] Subprocess exited prematurely with code ${code}, signal ${signal}`);
1015
+ }
986
1016
  });
987
1017
  }
988
- /**
989
- * Polls a URL until it starts responding or until timeout is reached.
990
- */
991
1018
  async waitForUrl(url, timeoutMs = 3e4) {
1019
+ console.log(`\u23F3 [ProcessManager] Waiting for ${url} to respond...`);
992
1020
  const startTime = Date.now();
993
- console.log(`\u23F3 [ProcessManager] Waiting for URL to become available: ${url} (timeout: ${timeoutMs / 1e3}s)...`);
994
1021
  while (Date.now() - startTime < timeoutMs) {
995
- if (this.hasExited && this.exitCode !== 0) {
1022
+ if (this.childProcess && this.childProcess.exitCode !== null) {
996
1023
  throw new Error(
997
- `[ProcessManager] Process exited prematurely with code ${this.exitCode}.
998
- Stderr:
999
- ${this.stderrOutput.trim() || "(no stderr)"}
1000
- Stdout:
1001
- ${this.stdoutOutput.slice(-500).trim()}`
1024
+ `[ProcessManager] Server process exited with code ${this.childProcess.exitCode} while waiting for ${url}`
1002
1025
  );
1003
1026
  }
1004
1027
  try {
1005
- const response = await fetch(url, { method: "GET", signal: AbortSignal.timeout(2e3) });
1028
+ const response = await fetch(url, { signal: AbortSignal.timeout(1e3) });
1006
1029
  if (response.status) {
1007
- console.log(`\u2705 [ProcessManager] Server responded with status ${response.status} at ${url}`);
1008
- return;
1030
+ console.log(`\u2705 [ProcessManager] Target ${url} is ready (status: ${response.status})!`);
1031
+ return true;
1009
1032
  }
1010
1033
  } catch {
1011
1034
  }
1012
- await new Promise((resolve) => setTimeout(resolve, 350));
1035
+ await new Promise((r) => setTimeout(r, 500));
1013
1036
  }
1014
- throw new Error(
1015
- `[ProcessManager] Timeout (${timeoutMs / 1e3}s) waiting for server at ${url}.
1016
- Last stdout:
1017
- ${this.stdoutOutput.slice(-500).trim()}
1018
- Last stderr:
1019
- ${this.stderrOutput.trim()}`
1020
- );
1037
+ throw new Error(`[ProcessManager] Timeout after ${timeoutMs}ms waiting for ${url} to respond.`);
1021
1038
  }
1022
- /**
1023
- * Gracefully and forcefully kills the process and all its children.
1024
- */
1025
1039
  async stop() {
1026
- if (!this.child || !this.child.pid || this.hasExited) {
1027
- this.child = null;
1040
+ if (this.isStopped || !this.childProcess || !this.childProcess.pid) {
1028
1041
  return;
1029
1042
  }
1030
- const pid = this.child.pid;
1031
- console.log(`\u{1F6D1} [ProcessManager] Terminating process tree (PID: ${pid})...`);
1043
+ this.isStopped = true;
1044
+ const pid = this.childProcess.pid;
1045
+ console.log(`\u{1F6D1} [ProcessManager] Terminating process tree for PID ${pid}...`);
1032
1046
  await new Promise((resolve) => {
1033
- treeKill(pid, "SIGKILL", (err) => {
1047
+ treeKill2(pid, "SIGTERM", (err) => {
1034
1048
  if (err) {
1035
- if (process.platform === "win32") {
1036
- try {
1037
- spawn2("taskkill", ["/pid", String(pid), "/T", "/F"], { stdio: "ignore" });
1038
- } catch {
1039
- }
1049
+ try {
1050
+ treeKill2(pid, "SIGKILL");
1051
+ } catch {
1040
1052
  }
1041
1053
  }
1042
1054
  resolve();
1043
1055
  });
1044
1056
  });
1045
- this.child = null;
1057
+ this.childProcess = null;
1046
1058
  }
1047
1059
  };
1048
1060
  }
@@ -1117,9 +1129,16 @@ async function runVisualScenario(options) {
1117
1129
  console.log(`\u{1F4E6} [Mock IPC] Applying ${mergedMocks.length} mocks`);
1118
1130
  previewDriver.mockRegistry.setBatch(mergedMocks);
1119
1131
  }
1132
+ if (scenario.mockRoutes && scenario.mockRoutes.length > 0) {
1133
+ console.log(`\u{1F310} [Mock Network] Queuing ${scenario.mockRoutes.length} route mock(s)`);
1134
+ await previewDriver.setupRouteMocks(scenario.mockRoutes);
1135
+ }
1120
1136
  const res = await previewDriver.start(currentViewport);
1121
1137
  page = res.page;
1122
1138
  context = res.context;
1139
+ if (scenario.mockRoutes && scenario.mockRoutes.length > 0) {
1140
+ await previewDriver.setupRouteMocks(scenario.mockRoutes);
1141
+ }
1123
1142
  }
1124
1143
  consoleTracker.attach(page);
1125
1144
  console.log(`\u{1F50D} [Console Tracker] Attached \u2014 errors and warnings will be captured
@@ -1265,6 +1284,25 @@ async function runVisualScenario(options) {
1265
1284
  console.log(`\u{1F4E6} [Mock IPC] Set ${action} -> ${typeof data === "string" ? data : JSON.stringify(data).slice(0, 80)}...`);
1266
1285
  await previewDriver.updateMockIpc(action, data, mockOptions);
1267
1286
  },
1287
+ setMockRoute: async (url, body, routeOptions) => {
1288
+ if (targetMode === "desktop") {
1289
+ console.log(`\u26A0\uFE0F [Mock Route] setMockRoute ignored in desktop mode`);
1290
+ return;
1291
+ }
1292
+ if (!previewDriver) {
1293
+ console.log(`\u26A0\uFE0F [Mock Route] PreviewDriver not available`);
1294
+ return;
1295
+ }
1296
+ console.log(`\u{1F310} [Mock Route] Intercepting ${routeOptions?.method || "ALL"} ${url} -> ${routeOptions?.status ?? 200}`);
1297
+ await previewDriver.addRouteMock({
1298
+ url,
1299
+ body,
1300
+ method: routeOptions?.method,
1301
+ status: routeOptions?.status,
1302
+ delayMs: routeOptions?.delayMs,
1303
+ headers: routeOptions?.headers
1304
+ });
1305
+ },
1268
1306
  getConsoleErrors: () => consoleTracker.getErrors(),
1269
1307
  getConsoleWarnings: () => consoleTracker.getWarnings(),
1270
1308
  hasConsoleErrors: () => consoleTracker.hasErrors,
@@ -1464,17 +1502,24 @@ async function isPortResponding(url) {
1464
1502
  return false;
1465
1503
  }
1466
1504
  }
1505
+ async function detectActiveDevServer() {
1506
+ for (const port of CANDIDATE_PORTS) {
1507
+ const url = `http://localhost:${port}`;
1508
+ if (await isPortResponding(url)) {
1509
+ return url;
1510
+ }
1511
+ }
1512
+ return null;
1513
+ }
1467
1514
  async function runQuickSnap(options) {
1468
1515
  let targetUrl = options.url;
1469
1516
  let useStaticPreview = false;
1470
1517
  let wwwrootDir;
1471
1518
  if (!targetUrl && !options.start) {
1472
- if (await isPortResponding("http://localhost:5173")) {
1473
- targetUrl = "http://localhost:5173";
1474
- console.log(`\u{1F310} [Quick Snap] Detected active dev server on http://localhost:5173`);
1475
- } else if (await isPortResponding("http://localhost:3000")) {
1476
- targetUrl = "http://localhost:3000";
1477
- console.log(`\u{1F310} [Quick Snap] Detected active dev server on http://localhost:3000`);
1519
+ const activeUrl = await detectActiveDevServer();
1520
+ if (activeUrl) {
1521
+ targetUrl = activeUrl;
1522
+ console.log(`\u{1F310} [Quick Snap] Detected active dev server on ${targetUrl}`);
1478
1523
  } else {
1479
1524
  const candidateDir = resolveWwwrootDir();
1480
1525
  if (fs7.existsSync(candidateDir) && fs7.existsSync(path7.join(candidateDir, "index.html"))) {
@@ -1483,7 +1528,7 @@ async function runQuickSnap(options) {
1483
1528
  console.log(`\u{1F4E6} [Quick Snap] No active server found. Detected built static directory at "${candidateDir}". Launching static preview...`);
1484
1529
  } else {
1485
1530
  console.error(`
1486
- \u274C [Quick Snap] No active server found on http://localhost:5173 or :3000, and no static build found.`);
1531
+ \u274C [Quick Snap] No active server found on common ports (${CANDIDATE_PORTS.join(", ")}), and no static build found.`);
1487
1532
  console.log(`\u{1F4A1} Suggested actions:`);
1488
1533
  console.log(` 1. Pass a start command: npx agent-lens snap --start="npm run dev"`);
1489
1534
  console.log(` 2. Specify your URL: npx agent-lens snap --url=http://localhost:8080`);
@@ -1565,7 +1610,7 @@ async function runQuickSnap(options) {
1565
1610
  `);
1566
1611
  return result.success && result.consoleErrors === 0;
1567
1612
  }
1568
- var PRESET_MAP;
1613
+ var PRESET_MAP, CANDIDATE_PORTS;
1569
1614
  var init_snap = __esm({
1570
1615
  "src/features/snap/snap.ts"() {
1571
1616
  "use strict";
@@ -1582,12 +1627,14 @@ var init_snap = __esm({
1582
1627
  tablet: { name: "tablet", width: 768, height: 1024 },
1583
1628
  "full-hd": VIEWPORT_PRESETS.FULL_HD
1584
1629
  };
1630
+ CANDIDATE_PORTS = [5173, 3e3, 4321, 4200, 8080, 8e3, 3001];
1585
1631
  }
1586
1632
  });
1587
1633
 
1588
1634
  // src/app/cli.ts
1589
1635
  import path8 from "path";
1590
1636
  import fs8 from "fs";
1637
+ import { execSync } from "child_process";
1591
1638
  import { createJiti } from "jiti";
1592
1639
  var require_cli = __commonJS({
1593
1640
  "src/app/cli.ts"() {
@@ -1651,15 +1698,19 @@ var require_cli = __commonJS({
1651
1698
  } else if (arg.startsWith("--selector=")) {
1652
1699
  options.selector = arg.split("=")[1];
1653
1700
  } else if (arg.startsWith("--viewports=")) {
1654
- options.viewports = arg.split("=")[1].split(",").map((v) => v.trim()).filter(Boolean);
1701
+ const raw = arg.slice("--viewports=".length);
1702
+ options.viewports = raw.split(",").map((v) => v.trim()).filter(Boolean);
1655
1703
  } else if (arg.startsWith("--wait=")) {
1656
- options.waitMs = parseInt(arg.split("=")[1], 10);
1704
+ options.waitMs = parseInt(arg.slice("--wait=".length), 10);
1657
1705
  } else if (arg.startsWith("--name=")) {
1658
- options.name = arg.split("=")[1];
1659
- } else if (arg.startsWith("--exe=") || arg.startsWith("--executable=")) {
1660
- options.exe = arg.split("=")[1];
1706
+ options.name = arg.slice("--name=".length);
1707
+ } else if (arg.startsWith("--exe=")) {
1708
+ options.exe = arg.slice("--exe=".length);
1709
+ } else if (arg.startsWith("--executable=")) {
1710
+ options.exe = arg.slice("--executable=".length);
1661
1711
  } else if (arg.startsWith("--clean=") || arg.startsWith("--cleanup=")) {
1662
- const rawPaths = arg.split("=")[1];
1712
+ const prefix = arg.startsWith("--clean=") ? "--clean=" : "--cleanup=";
1713
+ const rawPaths = arg.slice(prefix.length);
1663
1714
  options.clean = rawPaths.split(",").map((p) => p.trim()).filter(Boolean);
1664
1715
  } else if (arg.startsWith("--dir=")) {
1665
1716
  options.dir = arg.split("=")[1];
@@ -1845,7 +1896,6 @@ export default [
1845
1896
  const buildCmd = typeof options.build === "string" ? options.build : fileConfig.buildCommand || "npm run build";
1846
1897
  console.log(`
1847
1898
  \u{1F528} [Build] Running build process: "${buildCmd}"...`);
1848
- const { execSync } = __require("child_process");
1849
1899
  try {
1850
1900
  execSync(buildCmd, { stdio: "inherit", cwd: options.startCwd || process.cwd() });
1851
1901
  } catch (e) {