@_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.js CHANGED
@@ -1,4 +1,3 @@
1
- #!/usr/bin/env node
2
1
  "use strict";
3
2
  var __create = Object.create;
4
3
  var __defProp = Object.defineProperty;
@@ -26,6 +25,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
25
  // src/app/cli.ts
27
26
  var import_path8 = __toESM(require("path"));
28
27
  var import_fs8 = __toESM(require("fs"));
28
+ var import_child_process3 = require("child_process");
29
29
 
30
30
  // src/features/runner/runner.ts
31
31
  var import_path6 = __toESM(require("path"));
@@ -182,9 +182,9 @@ var VisualReporter = class {
182
182
  burstGroups.set(s.burstGroup, list);
183
183
  });
184
184
  for (const snap of regularSnapshots) {
185
- const fileUri = `file:///${snap.filePath.replace(/\\/g, "/")}`;
185
+ const relativeLink = snap.relativeUri || `./${snap.fileName}`;
186
186
  rows.push(
187
- `| **${String(snap.index).padStart(2, "0")}** | \`${snap.viewport.width}x${snap.viewport.height}\` | ${snap.name} | [${snap.fileName}](${fileUri}) |`
187
+ `| **${String(snap.index).padStart(2, "0")}** | \`${snap.viewport.width}x${snap.viewport.height}\` | ${snap.name} | [${snap.fileName}](${relativeLink}) |`
188
188
  );
189
189
  }
190
190
  let burstSections = "";
@@ -201,8 +201,8 @@ var VisualReporter = class {
201
201
  | :--- | :--- | :--- | :--- |
202
202
  `;
203
203
  for (const frame of frames) {
204
- const fileUri = `file:///${frame.filePath.replace(/\\/g, "/")}`;
205
- burstSections += `| Frame ${frame.frameIndex} | \`${frame.viewport.width}x${frame.viewport.height}\` | [${frame.fileName}](${fileUri}) | ![](${fileUri}) |
204
+ const relativeLink = frame.relativeUri || `./${frame.fileName}`;
205
+ burstSections += `| Frame ${frame.frameIndex} | \`${frame.viewport.width}x${frame.viewport.height}\` | [${frame.fileName}](${relativeLink}) | ![](${relativeLink}) |
206
206
  `;
207
207
  }
208
208
  burstSections += `
@@ -291,7 +291,9 @@ ${consoleSections}
291
291
 
292
292
  ## \u{1F4CB} AI Agent Verification Checklist:
293
293
  - [ ] **Console Errors**: ${consoleErrors.length === 0 ? "\u2705 No errors found" : `\u274C ${consoleErrors.length} errors \u2014 MUST REVIEW`}
294
+ - [ ] **Visual Layout Check**: Inspect snapshots (e.g. view_image on \`./01_${regularSnapshots[0]?.fileName || "quick_snap"}\`) for layout shifts or clipped elements.
294
295
  - [ ] **Responsiveness at \`1024x768\`**: Elements do not overflow the screen, no unwanted horizontal scroll.
296
+
295
297
  - [ ] **Typography & Spacing**: Spacing matches the design system and layout grids.
296
298
  - [ ] **Color Palette & Theme**: Background tints and button accent colors match the concept.
297
299
  - [ ] **Component States**: Modals open centered, dropdowns do not overlap with other layers (z-index).
@@ -316,8 +318,6 @@ ${consoleSections}
316
318
  var ConsoleTracker = class {
317
319
  entries = [];
318
320
  attached = false;
319
- /**
320
- */
321
321
  attach(page) {
322
322
  if (this.attached) return;
323
323
  this.attached = true;
@@ -386,8 +386,6 @@ var ConsoleTracker = class {
386
386
  return "log";
387
387
  }
388
388
  }
389
- /**
390
- */
391
389
  isIgnoredWarning(text) {
392
390
  const ignoredPatterns = [
393
391
  "findDOMNode is deprecated",
@@ -412,8 +410,28 @@ var import_fs3 = __toESM(require("fs"));
412
410
 
413
411
  // src/shared/lib/playwrightLoader.ts
414
412
  var import_playwright = require("playwright");
413
+ var chromium = new Proxy(import_playwright.chromium, {
414
+ get(target, prop, receiver) {
415
+ if (prop === "launch") {
416
+ return async (...args2) => {
417
+ try {
418
+ return await target.launch(...args2);
419
+ } catch (err) {
420
+ if (err.message?.includes("Executable doesn't exist") || err.message?.includes("playwright install") || err.message?.includes("browser has not been downloaded")) {
421
+ console.error("\n\u274C [AgentLens] Playwright Chromium browser binary is missing!");
422
+ console.error("\u{1F449} Please install it by running: npx playwright install chromium\n");
423
+ }
424
+ throw err;
425
+ }
426
+ };
427
+ }
428
+ const val = Reflect.get(target, prop, receiver);
429
+ return typeof val === "function" ? val.bind(target) : val;
430
+ }
431
+ });
415
432
 
416
433
  // src/shared/drivers/desktopDriver.ts
434
+ var import_tree_kill = __toESM(require("tree-kill"));
417
435
  var DesktopDriver = class {
418
436
  port;
419
437
  executablePath;
@@ -487,6 +505,11 @@ ${this.processStderr.trim() || "(no stderr output)"}`
487
505
  );
488
506
  }
489
507
  console.log(`[DesktopDriver] Launching: ${resolvedExe}`);
508
+ const finalArgs = [...this.args];
509
+ const hasDebugPort = finalArgs.some((a) => a.startsWith("--remote-debugging-port="));
510
+ if (!hasDebugPort) {
511
+ finalArgs.push(`--remote-debugging-port=${this.port}`);
512
+ }
490
513
  const mergedEnv = {
491
514
  ...process.env,
492
515
  ...this.env,
@@ -495,6 +518,16 @@ ${this.processStderr.trim() || "(no stderr output)"}`
495
518
  this.processStderr = "";
496
519
  this.processExited = false;
497
520
  this.exitCode = null;
521
+ this.childProcess = (0, import_child_process.spawn)(resolvedExe, finalArgs, {
522
+ env: mergedEnv,
523
+ cwd: this.cwd || import_path3.default.dirname(resolvedExe),
524
+ stdio: ["ignore", "ignore", "pipe"],
525
+ detached: false
526
+ });
527
+ ;
528
+ this.processStderr = "";
529
+ this.processExited = false;
530
+ this.exitCode = null;
498
531
  this.childProcess = (0, import_child_process.spawn)(resolvedExe, this.args, {
499
532
  env: mergedEnv,
500
533
  cwd: this.cwd || import_path3.default.dirname(resolvedExe),
@@ -517,7 +550,7 @@ ${this.processStderr.trim() || "(no stderr output)"}`
517
550
  console.log(`[DesktopDriver] Attached to already running process on port ${this.port}`);
518
551
  }
519
552
  console.log(`[DesktopDriver] Connecting Playwright CDP to http://127.0.0.1:${this.port}...`);
520
- this.browser = await import_playwright.chromium.connectOverCDP(`http://127.0.0.1:${this.port}`);
553
+ this.browser = await chromium.connectOverCDP(`http://127.0.0.1:${this.port}`);
521
554
  const contexts = this.browser.contexts();
522
555
  this.context = contexts[0] || await this.browser.newContext();
523
556
  const pages = this.context.pages();
@@ -538,16 +571,12 @@ ${this.processStderr.trim() || "(no stderr output)"}`
538
571
  });
539
572
  this.browser = null;
540
573
  }
541
- if (this.childProcess && !this.childProcess.killed) {
542
- console.log("[DesktopDriver] Terminating spawned desktop process...");
543
- try {
544
- if (process.platform === "win32" && this.childProcess.pid) {
545
- (0, import_child_process.spawn)("taskkill", ["/pid", String(this.childProcess.pid), "/T", "/F"], { stdio: "ignore" });
546
- } else {
547
- this.childProcess.kill("SIGTERM");
548
- }
549
- } catch {
550
- }
574
+ if (this.childProcess && !this.childProcess.killed && this.childProcess.pid) {
575
+ console.log("[DesktopDriver] Terminating spawned desktop process tree...");
576
+ const pid = this.childProcess.pid;
577
+ await new Promise((resolve) => {
578
+ (0, import_tree_kill.default)(pid, "SIGTERM", () => resolve());
579
+ });
551
580
  this.childProcess = null;
552
581
  }
553
582
  }
@@ -628,62 +657,65 @@ function generateMockIpcScript(registry) {
628
657
  }
629
658
  };
630
659
 
631
- // Legacy fallback for generic window.external
632
- if (!window.external) {
633
- window.external = {};
634
- }
635
-
636
- window.external.sendMessage = (msg) => {
637
- try {
638
- const parsed = JSON.parse(msg);
639
- const action = parsed.Action || parsed.action;
640
- const id = parsed.Id || parsed.id;
641
-
642
- const mock = window.__visualRunnerMocks[action];
660
+ // Safe fallback bridge for hybrid webviews (Photino / CEF / WebView2)
661
+ try {
662
+ if (!window.external) {
663
+ (window as any).external = {};
664
+ }
665
+ (window.external as any).sendMessage = (msg) => {
666
+ try {
667
+ const parsed = typeof msg === 'string' ? JSON.parse(msg) : msg;
668
+ const action = parsed.Action || parsed.action;
669
+ const id = parsed.Id || parsed.id;
670
+ const mock = (window as any).__visualRunnerMocks[action];
643
671
 
644
- if (mock) {
645
- const response = { Id: id, Type: mock.type || 'SUCCESS', Data: mock.data };
646
- setTimeout(() => {
647
- const cb = window.__mockCallback;
648
- if (cb) cb(JSON.stringify(response));
649
- }, mock.delayMs || 20);
650
- } else {
651
- setTimeout(() => {
652
- const cb = window.__mockCallback;
653
- if (cb) cb(JSON.stringify({ Id: id, Type: 'SUCCESS', Data: null }));
654
- }, 20);
672
+ if (mock) {
673
+ const response = { Id: id, Type: mock.type || 'SUCCESS', Data: mock.data };
674
+ setTimeout(() => {
675
+ const cb = (window as any).__mockCallback;
676
+ if (typeof cb === 'function') cb(JSON.stringify(response));
677
+ }, mock.delayMs || 20);
678
+ } else {
679
+ setTimeout(() => {
680
+ const cb = (window as any).__mockCallback;
681
+ if (typeof cb === 'function') cb(JSON.stringify({ Id: id, Type: 'SUCCESS', Data: null }));
682
+ }, 20);
683
+ }
684
+ } catch (e) {
685
+ console.error('[Mock IPC] Failed to process message:', e);
655
686
  }
656
- } catch (e) {
657
- console.error('[Mock IPC] Failed to process legacy message:', e);
658
- }
659
- };
687
+ };
660
688
 
661
- window.external.receiveMessage = (callback) => {
662
- window.__mockCallback = callback;
663
- };
689
+ (window.external as any).receiveMessage = (callback) => {
690
+ (window as any).__mockCallback = callback;
691
+ };
692
+ } catch {
693
+ // Ignored if window.external is read-only in strict Chromium sandboxes
694
+ }
664
695
 
665
- console.log('[Visual Runner] Mock IPC bridge initialized with', Object.keys(window.__visualRunnerMocks).length, 'mocked actions');
696
+ console.log('[Visual Runner] Mock IPC bridge initialized with', Object.keys((window as any).__visualRunnerMocks).length, 'mocked actions');
666
697
  })();
698
+
667
699
  `;
668
700
  }
669
701
 
670
702
  // src/shared/lib/config.ts
671
- var import_fs4 = __toESM(require("fs"));
672
703
  var import_path4 = __toESM(require("path"));
704
+ var import_fs4 = __toESM(require("fs"));
673
705
  function loadConfig(cwd = process.cwd()) {
674
- const configPath = import_path4.default.join(cwd, "agent-lens.json");
675
- if (import_fs4.default.existsSync(configPath)) {
706
+ const jsonConfigPath = import_path4.default.join(cwd, "agent-lens.json");
707
+ if (import_fs4.default.existsSync(jsonConfigPath)) {
676
708
  try {
677
- const raw = import_fs4.default.readFileSync(configPath, "utf8");
709
+ const raw = import_fs4.default.readFileSync(jsonConfigPath, "utf-8");
678
710
  return JSON.parse(raw);
679
711
  } catch (e) {
680
- console.warn(`\u26A0\uFE0F Warning: Failed to parse agent-lens.json:`, e);
712
+ console.warn(`\u26A0\uFE0F [Config] Failed to parse agent-lens.json: ${e.message}`);
681
713
  }
682
714
  }
683
- const pkgPath = import_path4.default.join(cwd, "package.json");
684
- if (import_fs4.default.existsSync(pkgPath)) {
715
+ const packageJsonPath = import_path4.default.join(cwd, "package.json");
716
+ if (import_fs4.default.existsSync(packageJsonPath)) {
685
717
  try {
686
- const raw = import_fs4.default.readFileSync(pkgPath, "utf8");
718
+ const raw = import_fs4.default.readFileSync(packageJsonPath, "utf-8");
687
719
  const pkg = JSON.parse(raw);
688
720
  if (pkg.agentLens && typeof pkg.agentLens === "object") {
689
721
  return pkg.agentLens;
@@ -693,78 +725,60 @@ function loadConfig(cwd = process.cwd()) {
693
725
  }
694
726
  return {};
695
727
  }
696
- function resolveWwwrootDir(customPath, cwd = process.cwd()) {
697
- if (customPath) {
698
- return import_path4.default.resolve(cwd, customPath);
728
+ function detectStartCwd(providedCwd) {
729
+ if (providedCwd) {
730
+ const resolved = import_path4.default.resolve(process.cwd(), providedCwd);
731
+ if (import_fs4.default.existsSync(resolved)) {
732
+ return providedCwd;
733
+ }
699
734
  }
700
- const standardDirs = [
701
- "dist",
702
- "build",
703
- "wwwroot",
704
- "Frontend/dist",
705
- "frontend/dist",
706
- "client/dist",
707
- "web/dist",
708
- "ui/dist"
709
- ];
710
- for (const rel of standardDirs) {
711
- const candidate = import_path4.default.resolve(cwd, rel);
712
- if (import_fs4.default.existsSync(candidate) && import_fs4.default.existsSync(import_path4.default.join(candidate, "index.html"))) {
713
- return candidate;
735
+ const rootPkgPath = import_path4.default.join(process.cwd(), "package.json");
736
+ let rootHasDevScript = false;
737
+ if (import_fs4.default.existsSync(rootPkgPath)) {
738
+ try {
739
+ const rootPkg = JSON.parse(import_fs4.default.readFileSync(rootPkgPath, "utf-8"));
740
+ rootHasDevScript = Boolean(rootPkg.scripts?.dev || rootPkg.scripts?.start);
741
+ } catch {
714
742
  }
715
743
  }
716
- const foundDeepWwwroot = findDeepIndexHtmlDir(cwd, 4);
717
- if (foundDeepWwwroot) {
718
- return foundDeepWwwroot;
744
+ if (rootHasDevScript) {
745
+ return void 0;
719
746
  }
720
- for (const rel of standardDirs) {
721
- const candidate = import_path4.default.resolve(cwd, rel);
722
- if (import_fs4.default.existsSync(candidate)) {
723
- return candidate;
747
+ const candidates = ["Frontend", "frontend", "client", "web", "ui", "apps/web", "src/frontend"];
748
+ for (const candidate of candidates) {
749
+ const candidatePkg = import_path4.default.join(process.cwd(), candidate, "package.json");
750
+ if (import_fs4.default.existsSync(candidatePkg)) {
751
+ return `./${candidate}`;
724
752
  }
725
753
  }
726
- return import_path4.default.resolve(cwd, "dist");
754
+ return void 0;
727
755
  }
728
- function detectStartCwd(customCwd, rootCwd = process.cwd()) {
729
- if (customCwd) {
730
- return import_path4.default.resolve(rootCwd, customCwd);
731
- }
732
- const subdirectories = ["Frontend", "frontend", "client", "web", "ui", "app"];
733
- for (const sub of subdirectories) {
734
- const subPkg = import_path4.default.join(rootCwd, sub, "package.json");
735
- if (import_fs4.default.existsSync(subPkg)) {
736
- try {
737
- const json = JSON.parse(import_fs4.default.readFileSync(subPkg, "utf8"));
738
- if (json.scripts && (json.scripts.dev || json.scripts.start || json.scripts.build)) {
739
- return import_path4.default.join(rootCwd, sub);
740
- }
741
- } catch {
742
- }
756
+ function resolveWwwrootDir(customDir) {
757
+ if (customDir) {
758
+ return import_path4.default.resolve(process.cwd(), customDir);
759
+ }
760
+ const candidates = [
761
+ "dist",
762
+ "build",
763
+ "out",
764
+ "wwwroot",
765
+ "Frontend/dist",
766
+ "frontend/dist",
767
+ "client/dist"
768
+ ];
769
+ for (const c of candidates) {
770
+ const candidatePath = import_path4.default.resolve(process.cwd(), c);
771
+ if (import_fs4.default.existsSync(candidatePath) && import_fs4.default.existsSync(import_path4.default.join(candidatePath, "index.html"))) {
772
+ return candidatePath;
743
773
  }
744
774
  }
745
- return rootCwd;
746
- }
747
- function findDeepIndexHtmlDir(dir, maxDepth, currentDepth = 0) {
748
- if (currentDepth > maxDepth || !import_fs4.default.existsSync(dir)) return null;
749
- try {
750
- const entries = import_fs4.default.readdirSync(dir, { withFileTypes: true });
751
- for (const entry of entries) {
752
- if (entry.isDirectory()) {
753
- if (entry.name === "node_modules" || entry.name === ".git" || entry.name === "artifacts") {
754
- continue;
755
- }
756
- const subDir = import_path4.default.join(dir, entry.name);
757
- if (entry.name === "wwwroot" && import_fs4.default.existsSync(import_path4.default.join(subDir, "index.html"))) {
758
- return subDir;
759
- }
760
- const found = findDeepIndexHtmlDir(subDir, maxDepth, currentDepth + 1);
761
- if (found) return found;
762
- }
775
+ for (const c of candidates) {
776
+ const candidatePath = import_path4.default.resolve(process.cwd(), c);
777
+ if (import_fs4.default.existsSync(candidatePath)) {
778
+ return candidatePath;
763
779
  }
764
- } catch {
765
- return null;
766
780
  }
767
- return null;
781
+ return import_path4.default.resolve(process.cwd(), "dist");
768
782
  }
769
783
 
770
784
  // src/shared/drivers/previewDriver.ts
@@ -791,6 +805,7 @@ var PreviewDriver = class {
791
805
  serverPort = 0;
792
806
  options;
793
807
  _baseUrl = "";
808
+ initialRouteMocks = [];
794
809
  mockRegistry;
795
810
  constructor(options2) {
796
811
  this.options = options2 || {};
@@ -847,7 +862,7 @@ var PreviewDriver = class {
847
862
  this._baseUrl = targetUrl;
848
863
  console.log(`\u{1F310} [PreviewDriver] Connecting directly to live URL: ${targetUrl}`);
849
864
  }
850
- this.browser = await import_playwright.chromium.launch({
865
+ this.browser = await chromium.launch({
851
866
  headless: !this.options.headed,
852
867
  args: ["--no-sandbox", "--disable-setuid-sandbox"]
853
868
  });
@@ -860,9 +875,41 @@ var PreviewDriver = class {
860
875
  await this.context.addInitScript(mockScript);
861
876
  }
862
877
  this.page = await this.context.newPage();
878
+ if (this.initialRouteMocks.length > 0) {
879
+ for (const entry of this.initialRouteMocks) {
880
+ await this.addRouteMock(entry);
881
+ }
882
+ }
863
883
  await this.page.goto(targetUrl, { waitUntil: "domcontentloaded" });
864
884
  return { page: this.page, context: this.context, browser: this.browser };
865
885
  }
886
+ async addRouteMock(entry) {
887
+ if (!this.page) {
888
+ this.initialRouteMocks.push(entry);
889
+ return;
890
+ }
891
+ await this.page.route(entry.url, async (route) => {
892
+ const req = route.request();
893
+ if (entry.method && req.method().toUpperCase() !== entry.method.toUpperCase()) {
894
+ return route.continue();
895
+ }
896
+ if (entry.delayMs) {
897
+ await new Promise((r) => setTimeout(r, entry.delayMs));
898
+ }
899
+ const isJson = typeof entry.body === "object" && entry.body !== null;
900
+ await route.fulfill({
901
+ status: entry.status ?? 200,
902
+ contentType: isJson ? "application/json" : "text/plain; charset=utf-8",
903
+ body: isJson ? JSON.stringify(entry.body) : String(entry.body ?? ""),
904
+ headers: entry.headers
905
+ });
906
+ });
907
+ }
908
+ async setupRouteMocks(routes) {
909
+ for (const r of routes) {
910
+ await this.addRouteMock(r);
911
+ }
912
+ }
866
913
  async updateMockIpc(action, data, options2) {
867
914
  if (!this.page) {
868
915
  throw new Error("PreviewDriver not started. Call start() first.");
@@ -907,104 +954,75 @@ var PreviewDriver = class {
907
954
 
908
955
  // src/shared/lib/processManager.ts
909
956
  var import_child_process2 = require("child_process");
910
- var import_tree_kill = __toESM(require("tree-kill"));
957
+ var import_tree_kill2 = __toESM(require("tree-kill"));
911
958
  var ProcessManager = class {
912
- child = null;
913
- stderrOutput = "";
914
- stdoutOutput = "";
915
- hasExited = false;
916
- exitCode = null;
917
- /**
918
- * Spawns a background process (e.g. dev server, backend, or app)
919
- */
959
+ childProcess = null;
960
+ isStopped = false;
920
961
  async start(command, options2) {
921
- this.stderrOutput = "";
922
- this.stdoutOutput = "";
923
- this.hasExited = false;
924
- this.exitCode = null;
925
962
  const cwd = options2?.cwd || process.cwd();
926
- const env = { ...process.env, ...options2?.env };
927
963
  console.log(`\u{1F680} [ProcessManager] Starting command: "${command}" in ${cwd}`);
928
- this.child = (0, import_child_process2.spawn)(command, {
964
+ this.childProcess = (0, import_child_process2.spawn)(command, {
929
965
  cwd,
930
- env,
931
- shell: options2?.shell ?? true,
966
+ env: { ...process.env, ...options2?.env },
967
+ shell: true,
932
968
  stdio: ["ignore", "pipe", "pipe"]
933
969
  });
934
- this.child.stdout?.on("data", (chunk) => {
935
- const str = chunk.toString();
936
- this.stdoutOutput += str;
937
- });
938
- this.child.stderr?.on("data", (chunk) => {
939
- const str = chunk.toString();
940
- this.stderrOutput += str;
970
+ this.childProcess.stdout?.on("data", (chunk) => {
971
+ const line = chunk.toString().trim();
972
+ if (line) {
973
+ }
941
974
  });
942
- this.child.on("exit", (code) => {
943
- this.hasExited = true;
944
- this.exitCode = code;
975
+ this.childProcess.stderr?.on("data", (chunk) => {
976
+ const line = chunk.toString().trim();
977
+ if (line && !line.includes("ExperimentalWarning")) {
978
+ }
945
979
  });
946
- this.child.on("error", (err) => {
947
- console.error(`\u274C [ProcessManager] Failed to start command: "${command}":`, err.message);
980
+ this.childProcess.on("exit", (code, signal) => {
981
+ if (!this.isStopped && code !== 0 && code !== null) {
982
+ console.warn(`\u26A0\uFE0F [ProcessManager] Subprocess exited prematurely with code ${code}, signal ${signal}`);
983
+ }
948
984
  });
949
985
  }
950
- /**
951
- * Polls a URL until it starts responding or until timeout is reached.
952
- */
953
986
  async waitForUrl(url, timeoutMs = 3e4) {
987
+ console.log(`\u23F3 [ProcessManager] Waiting for ${url} to respond...`);
954
988
  const startTime = Date.now();
955
- console.log(`\u23F3 [ProcessManager] Waiting for URL to become available: ${url} (timeout: ${timeoutMs / 1e3}s)...`);
956
989
  while (Date.now() - startTime < timeoutMs) {
957
- if (this.hasExited && this.exitCode !== 0) {
990
+ if (this.childProcess && this.childProcess.exitCode !== null) {
958
991
  throw new Error(
959
- `[ProcessManager] Process exited prematurely with code ${this.exitCode}.
960
- Stderr:
961
- ${this.stderrOutput.trim() || "(no stderr)"}
962
- Stdout:
963
- ${this.stdoutOutput.slice(-500).trim()}`
992
+ `[ProcessManager] Server process exited with code ${this.childProcess.exitCode} while waiting for ${url}`
964
993
  );
965
994
  }
966
995
  try {
967
- const response = await fetch(url, { method: "GET", signal: AbortSignal.timeout(2e3) });
996
+ const response = await fetch(url, { signal: AbortSignal.timeout(1e3) });
968
997
  if (response.status) {
969
- console.log(`\u2705 [ProcessManager] Server responded with status ${response.status} at ${url}`);
970
- return;
998
+ console.log(`\u2705 [ProcessManager] Target ${url} is ready (status: ${response.status})!`);
999
+ return true;
971
1000
  }
972
1001
  } catch {
973
1002
  }
974
- await new Promise((resolve) => setTimeout(resolve, 350));
1003
+ await new Promise((r) => setTimeout(r, 500));
975
1004
  }
976
- throw new Error(
977
- `[ProcessManager] Timeout (${timeoutMs / 1e3}s) waiting for server at ${url}.
978
- Last stdout:
979
- ${this.stdoutOutput.slice(-500).trim()}
980
- Last stderr:
981
- ${this.stderrOutput.trim()}`
982
- );
1005
+ throw new Error(`[ProcessManager] Timeout after ${timeoutMs}ms waiting for ${url} to respond.`);
983
1006
  }
984
- /**
985
- * Gracefully and forcefully kills the process and all its children.
986
- */
987
1007
  async stop() {
988
- if (!this.child || !this.child.pid || this.hasExited) {
989
- this.child = null;
1008
+ if (this.isStopped || !this.childProcess || !this.childProcess.pid) {
990
1009
  return;
991
1010
  }
992
- const pid = this.child.pid;
993
- console.log(`\u{1F6D1} [ProcessManager] Terminating process tree (PID: ${pid})...`);
1011
+ this.isStopped = true;
1012
+ const pid = this.childProcess.pid;
1013
+ console.log(`\u{1F6D1} [ProcessManager] Terminating process tree for PID ${pid}...`);
994
1014
  await new Promise((resolve) => {
995
- (0, import_tree_kill.default)(pid, "SIGKILL", (err) => {
1015
+ (0, import_tree_kill2.default)(pid, "SIGTERM", (err) => {
996
1016
  if (err) {
997
- if (process.platform === "win32") {
998
- try {
999
- (0, import_child_process2.spawn)("taskkill", ["/pid", String(pid), "/T", "/F"], { stdio: "ignore" });
1000
- } catch {
1001
- }
1017
+ try {
1018
+ (0, import_tree_kill2.default)(pid, "SIGKILL");
1019
+ } catch {
1002
1020
  }
1003
1021
  }
1004
1022
  resolve();
1005
1023
  });
1006
1024
  });
1007
- this.child = null;
1025
+ this.childProcess = null;
1008
1026
  }
1009
1027
  };
1010
1028
 
@@ -1075,9 +1093,16 @@ async function runVisualScenario(options2) {
1075
1093
  console.log(`\u{1F4E6} [Mock IPC] Applying ${mergedMocks.length} mocks`);
1076
1094
  previewDriver.mockRegistry.setBatch(mergedMocks);
1077
1095
  }
1096
+ if (scenario.mockRoutes && scenario.mockRoutes.length > 0) {
1097
+ console.log(`\u{1F310} [Mock Network] Queuing ${scenario.mockRoutes.length} route mock(s)`);
1098
+ await previewDriver.setupRouteMocks(scenario.mockRoutes);
1099
+ }
1078
1100
  const res = await previewDriver.start(currentViewport);
1079
1101
  page = res.page;
1080
1102
  context = res.context;
1103
+ if (scenario.mockRoutes && scenario.mockRoutes.length > 0) {
1104
+ await previewDriver.setupRouteMocks(scenario.mockRoutes);
1105
+ }
1081
1106
  }
1082
1107
  consoleTracker.attach(page);
1083
1108
  console.log(`\u{1F50D} [Console Tracker] Attached \u2014 errors and warnings will be captured
@@ -1223,6 +1248,25 @@ async function runVisualScenario(options2) {
1223
1248
  console.log(`\u{1F4E6} [Mock IPC] Set ${action} -> ${typeof data === "string" ? data : JSON.stringify(data).slice(0, 80)}...`);
1224
1249
  await previewDriver.updateMockIpc(action, data, mockOptions);
1225
1250
  },
1251
+ setMockRoute: async (url, body, routeOptions) => {
1252
+ if (targetMode === "desktop") {
1253
+ console.log(`\u26A0\uFE0F [Mock Route] setMockRoute ignored in desktop mode`);
1254
+ return;
1255
+ }
1256
+ if (!previewDriver) {
1257
+ console.log(`\u26A0\uFE0F [Mock Route] PreviewDriver not available`);
1258
+ return;
1259
+ }
1260
+ console.log(`\u{1F310} [Mock Route] Intercepting ${routeOptions?.method || "ALL"} ${url} -> ${routeOptions?.status ?? 200}`);
1261
+ await previewDriver.addRouteMock({
1262
+ url,
1263
+ body,
1264
+ method: routeOptions?.method,
1265
+ status: routeOptions?.status,
1266
+ delayMs: routeOptions?.delayMs,
1267
+ headers: routeOptions?.headers
1268
+ });
1269
+ },
1226
1270
  getConsoleErrors: () => consoleTracker.getErrors(),
1227
1271
  getConsoleWarnings: () => consoleTracker.getWarnings(),
1228
1272
  hasConsoleErrors: () => consoleTracker.hasErrors,
@@ -1415,6 +1459,7 @@ function parseViewportPresets(raw) {
1415
1459
  }
1416
1460
  return presets.length > 0 ? presets : [VIEWPORT_PRESETS.DEFAULT, PRESET_MAP.mobile];
1417
1461
  }
1462
+ var CANDIDATE_PORTS = [5173, 3e3, 4321, 4200, 8080, 8e3, 3001];
1418
1463
  async function isPortResponding(url) {
1419
1464
  try {
1420
1465
  const res = await fetch(url, { signal: AbortSignal.timeout(800) });
@@ -1423,17 +1468,24 @@ async function isPortResponding(url) {
1423
1468
  return false;
1424
1469
  }
1425
1470
  }
1471
+ async function detectActiveDevServer() {
1472
+ for (const port of CANDIDATE_PORTS) {
1473
+ const url = `http://localhost:${port}`;
1474
+ if (await isPortResponding(url)) {
1475
+ return url;
1476
+ }
1477
+ }
1478
+ return null;
1479
+ }
1426
1480
  async function runQuickSnap(options2) {
1427
1481
  let targetUrl = options2.url;
1428
1482
  let useStaticPreview = false;
1429
1483
  let wwwrootDir;
1430
1484
  if (!targetUrl && !options2.start) {
1431
- if (await isPortResponding("http://localhost:5173")) {
1432
- targetUrl = "http://localhost:5173";
1433
- console.log(`\u{1F310} [Quick Snap] Detected active dev server on http://localhost:5173`);
1434
- } else if (await isPortResponding("http://localhost:3000")) {
1435
- targetUrl = "http://localhost:3000";
1436
- console.log(`\u{1F310} [Quick Snap] Detected active dev server on http://localhost:3000`);
1485
+ const activeUrl = await detectActiveDevServer();
1486
+ if (activeUrl) {
1487
+ targetUrl = activeUrl;
1488
+ console.log(`\u{1F310} [Quick Snap] Detected active dev server on ${targetUrl}`);
1437
1489
  } else {
1438
1490
  const candidateDir = resolveWwwrootDir();
1439
1491
  if (import_fs7.default.existsSync(candidateDir) && import_fs7.default.existsSync(import_path7.default.join(candidateDir, "index.html"))) {
@@ -1442,7 +1494,7 @@ async function runQuickSnap(options2) {
1442
1494
  console.log(`\u{1F4E6} [Quick Snap] No active server found. Detected built static directory at "${candidateDir}". Launching static preview...`);
1443
1495
  } else {
1444
1496
  console.error(`
1445
- \u274C [Quick Snap] No active server found on http://localhost:5173 or :3000, and no static build found.`);
1497
+ \u274C [Quick Snap] No active server found on common ports (${CANDIDATE_PORTS.join(", ")}), and no static build found.`);
1446
1498
  console.log(`\u{1F4A1} Suggested actions:`);
1447
1499
  console.log(` 1. Pass a start command: npx agent-lens snap --start="npm run dev"`);
1448
1500
  console.log(` 2. Specify your URL: npx agent-lens snap --url=http://localhost:8080`);
@@ -1583,15 +1635,19 @@ for (const arg of effectiveArgs) {
1583
1635
  } else if (arg.startsWith("--selector=")) {
1584
1636
  options.selector = arg.split("=")[1];
1585
1637
  } else if (arg.startsWith("--viewports=")) {
1586
- options.viewports = arg.split("=")[1].split(",").map((v) => v.trim()).filter(Boolean);
1638
+ const raw = arg.slice("--viewports=".length);
1639
+ options.viewports = raw.split(",").map((v) => v.trim()).filter(Boolean);
1587
1640
  } else if (arg.startsWith("--wait=")) {
1588
- options.waitMs = parseInt(arg.split("=")[1], 10);
1641
+ options.waitMs = parseInt(arg.slice("--wait=".length), 10);
1589
1642
  } else if (arg.startsWith("--name=")) {
1590
- options.name = arg.split("=")[1];
1591
- } else if (arg.startsWith("--exe=") || arg.startsWith("--executable=")) {
1592
- options.exe = arg.split("=")[1];
1643
+ options.name = arg.slice("--name=".length);
1644
+ } else if (arg.startsWith("--exe=")) {
1645
+ options.exe = arg.slice("--exe=".length);
1646
+ } else if (arg.startsWith("--executable=")) {
1647
+ options.exe = arg.slice("--executable=".length);
1593
1648
  } else if (arg.startsWith("--clean=") || arg.startsWith("--cleanup=")) {
1594
- const rawPaths = arg.split("=")[1];
1649
+ const prefix = arg.startsWith("--clean=") ? "--clean=" : "--cleanup=";
1650
+ const rawPaths = arg.slice(prefix.length);
1595
1651
  options.clean = rawPaths.split(",").map((p) => p.trim()).filter(Boolean);
1596
1652
  } else if (arg.startsWith("--dir=")) {
1597
1653
  options.dir = arg.split("=")[1];
@@ -1777,9 +1833,8 @@ async function main() {
1777
1833
  const buildCmd = typeof options.build === "string" ? options.build : fileConfig.buildCommand || "npm run build";
1778
1834
  console.log(`
1779
1835
  \u{1F528} [Build] Running build process: "${buildCmd}"...`);
1780
- const { execSync } = require("child_process");
1781
1836
  try {
1782
- execSync(buildCmd, { stdio: "inherit", cwd: options.startCwd || process.cwd() });
1837
+ (0, import_child_process3.execSync)(buildCmd, { stdio: "inherit", cwd: options.startCwd || process.cwd() });
1783
1838
  } catch (e) {
1784
1839
  console.error(`\u274C Build failed: ${buildCmd}`);
1785
1840
  process.exit(1);