@joeytheman/gemini-mcp-tool 2.2.0 → 2.3.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.
Files changed (107) hide show
  1. package/README.md +10 -7
  2. package/dist/__tests__/agyModelSteps.test.d.ts +2 -0
  3. package/dist/__tests__/agyModelSteps.test.d.ts.map +1 -0
  4. package/dist/__tests__/agyModelSteps.test.js +58 -0
  5. package/dist/__tests__/agyModelSteps.test.js.map +1 -0
  6. package/dist/__tests__/capture-script.chromium.test.d.ts +2 -0
  7. package/dist/__tests__/capture-script.chromium.test.d.ts.map +1 -0
  8. package/dist/__tests__/capture-script.chromium.test.js +235 -0
  9. package/dist/__tests__/capture-script.chromium.test.js.map +1 -0
  10. package/dist/__tests__/layout-snippet.chromium.test.d.ts +2 -0
  11. package/dist/__tests__/layout-snippet.chromium.test.d.ts.map +1 -0
  12. package/dist/__tests__/layout-snippet.chromium.test.js +333 -0
  13. package/dist/__tests__/layout-snippet.chromium.test.js.map +1 -0
  14. package/dist/__tests__/layout-snippet.test.d.ts +2 -0
  15. package/dist/__tests__/layout-snippet.test.d.ts.map +1 -0
  16. package/dist/__tests__/layout-snippet.test.js +12 -0
  17. package/dist/__tests__/layout-snippet.test.js.map +1 -0
  18. package/dist/__tests__/live-pass.test.js +531 -7
  19. package/dist/__tests__/live-pass.test.js.map +1 -1
  20. package/dist/__tests__/screen-review.test.js +111 -2
  21. package/dist/__tests__/screen-review.test.js.map +1 -1
  22. package/dist/__tests__/server-progress.test.d.ts +2 -0
  23. package/dist/__tests__/server-progress.test.d.ts.map +1 -0
  24. package/dist/__tests__/server-progress.test.js +63 -0
  25. package/dist/__tests__/server-progress.test.js.map +1 -0
  26. package/dist/__tests__/server.test.js +1 -8
  27. package/dist/__tests__/server.test.js.map +1 -1
  28. package/dist/__tests__/simple-tools.test.js +8 -3
  29. package/dist/__tests__/simple-tools.test.js.map +1 -1
  30. package/dist/__tests__/tools.test.js +29 -1
  31. package/dist/__tests__/tools.test.js.map +1 -1
  32. package/dist/__tests__/utils/agyExecutor.test.js +176 -61
  33. package/dist/__tests__/utils/agyExecutor.test.js.map +1 -1
  34. package/dist/__tests__/utils/commandExecutor.test.js +155 -1
  35. package/dist/__tests__/utils/commandExecutor.test.js.map +1 -1
  36. package/dist/__tests__/utils/progressUpdates.test.d.ts +2 -0
  37. package/dist/__tests__/utils/progressUpdates.test.d.ts.map +1 -0
  38. package/dist/__tests__/utils/progressUpdates.test.js +62 -0
  39. package/dist/__tests__/utils/progressUpdates.test.js.map +1 -0
  40. package/dist/constants.d.ts +33 -10
  41. package/dist/constants.d.ts.map +1 -1
  42. package/dist/constants.js +42 -10
  43. package/dist/constants.js.map +1 -1
  44. package/dist/index.js +19 -94
  45. package/dist/index.js.map +1 -1
  46. package/dist/layoutCheckSnippet.generated.d.ts +2 -0
  47. package/dist/layoutCheckSnippet.generated.d.ts.map +1 -0
  48. package/dist/layoutCheckSnippet.generated.js +3 -0
  49. package/dist/layoutCheckSnippet.generated.js.map +1 -0
  50. package/dist/tools/ask-gemini.tool.d.ts.map +1 -1
  51. package/dist/tools/ask-gemini.tool.js +6 -5
  52. package/dist/tools/ask-gemini.tool.js.map +1 -1
  53. package/dist/tools/brainstorm.tool.d.ts.map +1 -1
  54. package/dist/tools/brainstorm.tool.js +2 -1
  55. package/dist/tools/brainstorm.tool.js.map +1 -1
  56. package/dist/tools/live-pass.tool.d.ts +24 -1
  57. package/dist/tools/live-pass.tool.d.ts.map +1 -1
  58. package/dist/tools/live-pass.tool.js +174 -52
  59. package/dist/tools/live-pass.tool.js.map +1 -1
  60. package/dist/tools/registry.d.ts +2 -2
  61. package/dist/tools/registry.d.ts.map +1 -1
  62. package/dist/tools/registry.js +2 -2
  63. package/dist/tools/registry.js.map +1 -1
  64. package/dist/tools/screen-review.tool.d.ts +3 -0
  65. package/dist/tools/screen-review.tool.d.ts.map +1 -1
  66. package/dist/tools/screen-review.tool.js +42 -8
  67. package/dist/tools/screen-review.tool.js.map +1 -1
  68. package/dist/tools/simple-tools.d.ts.map +1 -1
  69. package/dist/tools/simple-tools.js +6 -2
  70. package/dist/tools/simple-tools.js.map +1 -1
  71. package/dist/utils/agyExecutor.d.ts +9 -1
  72. package/dist/utils/agyExecutor.d.ts.map +1 -1
  73. package/dist/utils/agyExecutor.js +54 -48
  74. package/dist/utils/agyExecutor.js.map +1 -1
  75. package/dist/utils/agyModelSteps.d.ts +5 -0
  76. package/dist/utils/agyModelSteps.d.ts.map +1 -0
  77. package/dist/utils/agyModelSteps.js +33 -0
  78. package/dist/utils/agyModelSteps.js.map +1 -0
  79. package/dist/utils/artifactFile.d.ts +6 -0
  80. package/dist/utils/artifactFile.d.ts.map +1 -0
  81. package/dist/utils/artifactFile.js +55 -0
  82. package/dist/utils/artifactFile.js.map +1 -0
  83. package/dist/utils/captureScript.d.ts +24 -0
  84. package/dist/utils/captureScript.d.ts.map +1 -0
  85. package/dist/utils/captureScript.js +83 -0
  86. package/dist/utils/captureScript.js.map +1 -0
  87. package/dist/utils/commandExecutor.d.ts +7 -1
  88. package/dist/utils/commandExecutor.d.ts.map +1 -1
  89. package/dist/utils/commandExecutor.js +154 -22
  90. package/dist/utils/commandExecutor.js.map +1 -1
  91. package/dist/utils/gitCheckout.d.ts +14 -0
  92. package/dist/utils/gitCheckout.d.ts.map +1 -0
  93. package/dist/utils/gitCheckout.js +36 -0
  94. package/dist/utils/gitCheckout.js.map +1 -0
  95. package/dist/utils/layoutCheck.d.ts +58 -0
  96. package/dist/utils/layoutCheck.d.ts.map +1 -0
  97. package/dist/utils/layoutCheck.js +84 -0
  98. package/dist/utils/layoutCheck.js.map +1 -0
  99. package/dist/utils/logger.d.ts +0 -1
  100. package/dist/utils/logger.d.ts.map +1 -1
  101. package/dist/utils/logger.js +0 -6
  102. package/dist/utils/logger.js.map +1 -1
  103. package/dist/utils/progressUpdates.d.ts +14 -0
  104. package/dist/utils/progressUpdates.d.ts.map +1 -0
  105. package/dist/utils/progressUpdates.js +40 -0
  106. package/dist/utils/progressUpdates.js.map +1 -0
  107. package/package.json +13 -11
@@ -0,0 +1,55 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { isWithin } from './fileReferences.js';
4
+ import { LIVE_PASS } from '../constants.js';
5
+ export const IMAGE_FILE = /\.(png|jpe?g)$/i;
6
+ export function hasImageSignature(file) {
7
+ const extension = path.extname(file).toLowerCase();
8
+ if (extension !== '.png' && extension !== '.jpg' && extension !== '.jpeg')
9
+ return false;
10
+ let handle;
11
+ try {
12
+ handle = fs.openSync(file, 'r');
13
+ }
14
+ catch {
15
+ return false;
16
+ }
17
+ try {
18
+ const header = Buffer.alloc(24);
19
+ const size = fs.readSync(handle, header, 0, header.length, 0);
20
+ if (extension === '.png') {
21
+ return size === header.length
22
+ && header.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]))
23
+ && header.readUInt32BE(8) === 13
24
+ && header.toString('ascii', 12, 16) === 'IHDR'
25
+ && header.readUInt32BE(16) > 0
26
+ && header.readUInt32BE(20) > 0;
27
+ }
28
+ return size >= 3 && header[0] === 0xff && header[1] === 0xd8 && header[2] === 0xff;
29
+ }
30
+ catch {
31
+ return false;
32
+ }
33
+ finally {
34
+ fs.closeSync(handle);
35
+ }
36
+ }
37
+ /** Whether a file a pass wrote is inside `artifactDir`, present, non-empty and written during this run. */
38
+ export function checkArtifactFile(absFile, artifactDir, runStartMs) {
39
+ if (!isWithin(artifactDir, absFile))
40
+ return 'OUTSIDE';
41
+ try {
42
+ const stat = fs.statSync(absFile);
43
+ if (!stat.isFile())
44
+ return 'MISSING';
45
+ if (stat.size === 0)
46
+ return 'EMPTY';
47
+ if (stat.mtimeMs < runStartMs - LIVE_PASS.FRESHNESS_TOLERANCE_MS)
48
+ return 'STALE';
49
+ return 'OK';
50
+ }
51
+ catch {
52
+ return 'MISSING';
53
+ }
54
+ }
55
+ //# sourceMappingURL=artifactFile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"artifactFile.js","sourceRoot":"","sources":["../../src/utils/artifactFile.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAI5C,MAAM,CAAC,MAAM,UAAU,GAAG,iBAAiB,CAAC;AAE5C,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;IACnD,IAAI,SAAS,KAAK,MAAM,IAAI,SAAS,KAAK,MAAM,IAAI,SAAS,KAAK,OAAO;QAAE,OAAO,KAAK,CAAC;IAExF,IAAI,MAAc,CAAC;IACnB,IAAI,CAAC;QACH,MAAM,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAClC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAChC,MAAM,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QAC9D,IAAI,SAAS,KAAK,MAAM,EAAE,CAAC;YACzB,OAAO,IAAI,KAAK,MAAM,CAAC,MAAM;mBACxB,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;mBAC5E,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,EAAE;mBAC7B,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,EAAE,EAAE,EAAE,CAAC,KAAK,MAAM;mBAC3C,MAAM,CAAC,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC;mBAC3B,MAAM,CAAC,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;QACnC,CAAC;QACD,OAAO,IAAI,IAAI,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;IACrF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IACvB,CAAC;AACH,CAAC;AAED,2GAA2G;AAC3G,MAAM,UAAU,iBAAiB,CAAC,OAAe,EAAE,WAAmB,EAAE,UAAkB;IACxF,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;QAAE,OAAO,SAAS,CAAC;IACtD,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAClC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;YAAE,OAAO,SAAS,CAAC;QACrC,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC;YAAE,OAAO,OAAO,CAAC;QACpC,IAAI,IAAI,CAAC,OAAO,GAAG,UAAU,GAAG,SAAS,CAAC,sBAAsB;YAAE,OAAO,OAAO,CAAC;QACjF,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC"}
@@ -0,0 +1,24 @@
1
+ export interface Viewport {
2
+ width: number;
3
+ height: number;
4
+ }
5
+ /**
6
+ * With a shown aria-modal sheet open, only the scrollers inside it count, so a
7
+ * sheet over a long screen is still shot at the viewport. `shown` and the modal
8
+ * lookup match src/layout-check.snippet.js, so both agree on which sheet counts.
9
+ */
10
+ export declare const SCROLL_OVERFLOW_SNIPPET = "() => {\n const shown = (el) => {\n const r = el.getBoundingClientRect();\n return r.width > 0 && r.height > 0 && getComputedStyle(el).visibility !== 'hidden' && !el.closest('[aria-hidden=\"true\"],[inert]');\n };\n const scrolls = (el) => el.tagName !== 'TEXTAREA' && /^(auto|scroll)$/.test(getComputedStyle(el).overflowY) && shown(el);\n const modal = [...document.querySelectorAll('[aria-modal=\"true\"]')].find(shown);\n let overflow = modal ? 0 : (document.scrollingElement || document.documentElement).scrollHeight - innerHeight;\n for (const el of modal ? [modal, ...modal.querySelectorAll('*')] : document.querySelectorAll('*')) {\n const extra = el.scrollHeight - el.clientHeight;\n if (extra > overflow && scrolls(el)) overflow = extra;\n }\n return overflow;\n }";
11
+ export declare function parseViewport(viewport: string): Viewport | null;
12
+ export declare function saveLayoutFunction(key: string): string;
13
+ /**
14
+ * The text of one target's capture file, run by @playwright/mcp's
15
+ * browser_run_code_unsafe as `(<text>)(page)` in a vm context that holds only
16
+ * `page` and a few timer and web globals: it must stay one expression, with no
17
+ * `require`, `fs` or `process`, and no trailing `;` or line comment.
18
+ */
19
+ export declare function buildCaptureScript(config: {
20
+ viewport: Viewport;
21
+ frameFile: string;
22
+ key: string;
23
+ }): string;
24
+ //# sourceMappingURL=captureScript.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"captureScript.d.ts","sourceRoot":"","sources":["../../src/utils/captureScript.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,uzBAahC,CAAC;AAEL,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAG/D;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAgDzG"}
@@ -0,0 +1,83 @@
1
+ import { LAYOUT_CHECK_SNIPPET, LIVE_PASS } from '../constants.js';
2
+ /**
3
+ * With a shown aria-modal sheet open, only the scrollers inside it count, so a
4
+ * sheet over a long screen is still shot at the viewport. `shown` and the modal
5
+ * lookup match src/layout-check.snippet.js, so both agree on which sheet counts.
6
+ */
7
+ export const SCROLL_OVERFLOW_SNIPPET = `() => {
8
+ const shown = (el) => {
9
+ const r = el.getBoundingClientRect();
10
+ return r.width > 0 && r.height > 0 && getComputedStyle(el).visibility !== 'hidden' && !el.closest('[aria-hidden="true"],[inert]');
11
+ };
12
+ const scrolls = (el) => el.tagName !== 'TEXTAREA' && /^(auto|scroll)$/.test(getComputedStyle(el).overflowY) && shown(el);
13
+ const modal = [...document.querySelectorAll('[aria-modal="true"]')].find(shown);
14
+ let overflow = modal ? 0 : (document.scrollingElement || document.documentElement).scrollHeight - innerHeight;
15
+ for (const el of modal ? [modal, ...modal.querySelectorAll('*')] : document.querySelectorAll('*')) {
16
+ const extra = el.scrollHeight - el.clientHeight;
17
+ if (extra > overflow && scrolls(el)) overflow = extra;
18
+ }
19
+ return overflow;
20
+ }`;
21
+ export function parseViewport(viewport) {
22
+ const match = /^\s*([1-9]\d*)\s*x\s*([1-9]\d*)\s*$/i.exec(viewport);
23
+ return match ? { width: Number(match[1]), height: Number(match[2]) } : null;
24
+ }
25
+ export function saveLayoutFunction(key) {
26
+ return `() => window.__livePassLayout?.[${JSON.stringify(key)}]`;
27
+ }
28
+ /**
29
+ * The text of one target's capture file, run by @playwright/mcp's
30
+ * browser_run_code_unsafe as `(<text>)(page)` in a vm context that holds only
31
+ * `page` and a few timer and web globals: it must stay one expression, with no
32
+ * `require`, `fs` or `process`, and no trailing `;` or line comment.
33
+ */
34
+ export function buildCaptureScript(config) {
35
+ const capture = JSON.stringify({
36
+ ...config,
37
+ settleMs: LIVE_PASS.CAPTURE_SETTLE_MS,
38
+ maxHeight: Math.max(LIVE_PASS.CAPTURE_MAX_HEIGHT, config.viewport.height),
39
+ maxGrows: LIVE_PASS.CAPTURE_MAX_GROWS,
40
+ });
41
+ return `async (page) => {
42
+ const { viewport, frameFile, key, settleMs, maxHeight, maxGrows } = ${capture};
43
+ const settle = () => new Promise((resolve) => setTimeout(resolve, settleMs));
44
+ const firstLine = (error) => String((error && error.message) || error).split('\\n')[0];
45
+ const overflow = () => page.evaluate(${SCROLL_OVERFLOW_SNIPPET});
46
+ const shoot = async () => {
47
+ let height = viewport.height;
48
+ for (let grown = 0, last = Infinity; grown < maxGrows && height < maxHeight; grown++) {
49
+ const by = await overflow();
50
+ if (by <= 1 || by >= last) break;
51
+ height = Math.min(height + Math.ceil(by), maxHeight);
52
+ await page.setViewportSize({ width: viewport.width, height });
53
+ await settle();
54
+ last = by;
55
+ }
56
+ await page.screenshot({ path: frameFile, type: 'png', scale: 'css' });
57
+ const saved = 'saved ' + frameFile + ' at ' + viewport.width + 'x' + height;
58
+ try {
59
+ await page.evaluate((key) => {
60
+ const reports = (window.__livePassLayout = window.__livePassLayout || {});
61
+ delete reports[key];
62
+ reports[key] = (${LAYOUT_CHECK_SNIPPET})();
63
+ }, key);
64
+ return saved;
65
+ } catch (error) {
66
+ return saved + '; layout not measured: ' + firstLine(error);
67
+ }
68
+ };
69
+ const restore = () => page.setViewportSize(viewport).then(() => '', (error) => '; viewport not restored: ' + firstLine(error));
70
+ await page.setViewportSize(viewport);
71
+ await settle();
72
+ let result;
73
+ try {
74
+ result = await shoot();
75
+ } catch (error) {
76
+ await restore();
77
+ throw error;
78
+ }
79
+ return result + (await restore());
80
+ }
81
+ `;
82
+ }
83
+ //# sourceMappingURL=captureScript.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"captureScript.js","sourceRoot":"","sources":["../../src/utils/captureScript.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAOlE;;;;GAIG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG;;;;;;;;;;;;;IAanC,CAAC;AAEL,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,MAAM,KAAK,GAAG,sCAAsC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACpE,OAAO,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AAC9E,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,GAAW;IAC5C,OAAO,mCAAmC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC;AACnE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAA8D;IAC/F,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC;QAC7B,GAAG,MAAM;QACT,QAAQ,EAAE,SAAS,CAAC,iBAAiB;QACrC,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,kBAAkB,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;QACzE,QAAQ,EAAE,SAAS,CAAC,iBAAiB;KACtC,CAAC,CAAC;IACH,OAAO;wEAC+D,OAAO;;;yCAGtC,uBAAuB;;;;;;;;;;;;;;;;;0BAiBtC,oBAAoB;;;;;;;;;;;;;;;;;;;CAmB7C,CAAC;AACF,CAAC"}
@@ -1,2 +1,8 @@
1
- export declare function executeCommand(command: string, args: string[], onProgress?: (newOutput: string) => void, cwd?: string): Promise<string>;
1
+ export interface CommandLimits {
2
+ /** Stop the process this long after spawn (SIGTERM then SIGKILL on POSIX; taskkill on Windows). */
3
+ killAfterMs?: number;
4
+ /** Stop the process when the caller's request is cancelled. */
5
+ signal?: AbortSignal;
6
+ }
7
+ export declare function executeCommand(command: string, args: string[], onProgress?: (newOutput: string) => void, cwd?: string, limits?: CommandLimits): Promise<string>;
2
8
  //# sourceMappingURL=commandExecutor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"commandExecutor.d.ts","sourceRoot":"","sources":["../../src/utils/commandExecutor.ts"],"names":[],"mappings":"AAGA,wBAAsB,cAAc,CAClC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,EACxC,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,CAAC,CA2FjB"}
1
+ {"version":3,"file":"commandExecutor.d.ts","sourceRoot":"","sources":["../../src/utils/commandExecutor.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,aAAa;IAC5B,mGAAmG;IACnG,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+DAA+D;IAC/D,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,wBAAsB,cAAc,CAClC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,EACxC,GAAG,CAAC,EAAE,MAAM,EACZ,MAAM,GAAE,aAAkB,GACzB,OAAO,CAAC,MAAM,CAAC,CAmNjB"}
@@ -1,21 +1,140 @@
1
1
  import { spawn } from "child_process";
2
2
  import { Logger } from "./logger.js";
3
- export async function executeCommand(command, args, onProgress, cwd) {
3
+ import { AGY_LIMITS, ERROR_MESSAGES } from "../constants.js";
4
+ // Node clamps longer delays to 1 ms, so long limits need more than one timer.
5
+ const MAX_TIMER_DELAY_MS = 2 ** 31 - 1;
6
+ export async function executeCommand(command, args, onProgress, cwd, limits = {}) {
4
7
  return new Promise((resolve, reject) => {
8
+ if (limits.signal?.aborted) {
9
+ reject(new Error(ERROR_MESSAGES.AGY_CANCELLED));
10
+ return;
11
+ }
5
12
  const startTime = Date.now();
6
13
  Logger.commandExecution(command, args, startTime);
14
+ // Give limited POSIX commands their own process group. agy's MCP children
15
+ // inherit it, so a stop can reach them even if agy ignores SIGTERM.
16
+ const grouped = process.platform !== "win32" &&
17
+ (limits.killAfterMs !== undefined || limits.signal !== undefined);
7
18
  const childProcess = spawn(command, args, {
8
19
  env: process.env,
9
20
  shell: false,
10
21
  stdio: ["ignore", "pipe", "pipe"],
11
22
  ...(cwd && { cwd }),
23
+ ...(grouped && { detached: true }),
12
24
  });
13
25
  // Use array buffers for O(n) performance instead of O(n²) string concatenation
14
26
  const stdoutChunks = [];
15
27
  const stderrChunks = [];
16
28
  let isResolved = false;
29
+ let stopError;
17
30
  let resourceExhausted = false;
31
+ let ceilingTimer;
32
+ let graceTimer;
33
+ let closeTimer;
34
+ const settle = () => {
35
+ isResolved = true;
36
+ clearTimeout(ceilingTimer);
37
+ clearTimeout(graceTimer);
38
+ clearTimeout(closeTimer);
39
+ limits.signal?.removeEventListener("abort", onAbort);
40
+ };
41
+ const signalProcessGroup = (signal) => {
42
+ try {
43
+ if (grouped && childProcess.pid !== undefined) {
44
+ process.kill(-childProcess.pid, signal);
45
+ }
46
+ else {
47
+ childProcess.kill(signal);
48
+ }
49
+ }
50
+ catch {
51
+ // A process can exit between the timer and the signal. If the group
52
+ // signal failed for another reason, still try to stop agy itself.
53
+ if (grouped)
54
+ childProcess.kill(signal);
55
+ }
56
+ };
57
+ const signalWindowsTree = () => {
58
+ if (childProcess.pid === undefined) {
59
+ childProcess.kill("SIGKILL");
60
+ return;
61
+ }
62
+ // Windows has no negative-PID group signal. Kill the tree while the
63
+ // parent PID still exists, before its children can become orphaned.
64
+ const killer = spawn("taskkill", ["/PID", String(childProcess.pid), "/T", "/F"], {
65
+ shell: false,
66
+ stdio: "ignore",
67
+ windowsHide: true,
68
+ });
69
+ killer.on("error", (error) => {
70
+ Logger.error("Could not stop the Windows process tree:", error);
71
+ childProcess.kill("SIGKILL");
72
+ });
73
+ killer.on("close", (code) => {
74
+ if (code !== 0 && !isResolved) {
75
+ Logger.error(`taskkill could not stop the Windows process tree (exit ${code})`);
76
+ childProcess.kill("SIGKILL");
77
+ }
78
+ });
79
+ };
80
+ const finishStopped = (code) => {
81
+ if (isResolved)
82
+ return;
83
+ settle();
84
+ Logger.commandComplete(startTime, code);
85
+ reject(stopError);
86
+ };
87
+ const forceStop = () => {
88
+ if (isResolved)
89
+ return;
90
+ if (process.platform === "win32")
91
+ signalWindowsTree();
92
+ else
93
+ signalProcessGroup("SIGKILL");
94
+ // A detached descendant can keep inherited pipes open after agy dies,
95
+ // so bound the wait for close.
96
+ closeTimer = setTimeout(() => {
97
+ childProcess.stdout.destroy?.();
98
+ childProcess.stderr.destroy?.();
99
+ finishStopped(childProcess.exitCode ?? null);
100
+ }, AGY_LIMITS.KILL_CLOSE_WAIT_MS);
101
+ };
102
+ const stop = (error) => {
103
+ if (isResolved || stopError)
104
+ return;
105
+ stopError = error;
106
+ clearTimeout(ceilingTimer);
107
+ limits.signal?.removeEventListener("abort", onAbort);
108
+ Logger.error(error.message);
109
+ if (process.platform === "win32")
110
+ signalWindowsTree();
111
+ else
112
+ signalProcessGroup("SIGTERM");
113
+ graceTimer = setTimeout(forceStop, AGY_LIMITS.KILL_GRACE_MS);
114
+ };
115
+ function onAbort() {
116
+ stop(new Error(ERROR_MESSAGES.AGY_CANCELLED));
117
+ }
118
+ if (limits.killAfterMs !== undefined) {
119
+ const limitMs = limits.killAfterMs;
120
+ const deadline = Date.now() + limitMs;
121
+ const scheduleCeiling = () => {
122
+ const remaining = deadline - Date.now();
123
+ if (remaining <= 0) {
124
+ stop(new Error(`${ERROR_MESSAGES.AGY_HUNG}: limit ${Math.round(limitMs / 1000)}s`));
125
+ }
126
+ else {
127
+ ceilingTimer = setTimeout(scheduleCeiling, Math.min(remaining, MAX_TIMER_DELAY_MS));
128
+ }
129
+ };
130
+ scheduleCeiling();
131
+ }
132
+ limits.signal?.addEventListener("abort", onAbort, { once: true });
133
+ if (limits.signal?.aborted)
134
+ onAbort();
18
135
  childProcess.stdout.on("data", (data) => {
136
+ if (stopError)
137
+ return;
19
138
  const chunk = data.toString();
20
139
  stdoutChunks.push(chunk);
21
140
  // Report immediately if callback provided (no substring calculation needed)
@@ -25,6 +144,8 @@ export async function executeCommand(command, args, onProgress, cwd) {
25
144
  });
26
145
  // CLI level errors
27
146
  childProcess.stderr.on("data", (data) => {
147
+ if (stopError)
148
+ return;
28
149
  const chunk = data.toString();
29
150
  stderrChunks.push(chunk);
30
151
  // Surface resource/quota failures from the backend without attempting
@@ -54,32 +175,43 @@ export async function executeCommand(command, args, onProgress, cwd) {
54
175
  });
55
176
  childProcess.on("error", (error) => {
56
177
  if (!isResolved) {
57
- isResolved = true;
178
+ if (stopError)
179
+ return;
180
+ settle();
58
181
  Logger.error(`Process error:`, error);
182
+ Logger.commandComplete(startTime, null);
59
183
  reject(new Error(`Failed to spawn command: ${error.message}`));
60
184
  }
61
185
  });
62
186
  childProcess.on("close", (code) => {
63
- if (!isResolved) {
64
- isResolved = true;
65
- // Join array buffers efficiently
66
- const stdout = stdoutChunks.join('');
67
- const stderr = stderrChunks.join('');
68
- // Treat an exit-0 run as failed only when it reported quota exhaustion
69
- // AND produced no usable stdout — so a quota error with empty output is
70
- // surfaced (not silently "recovered"), while a run that still returned a
71
- // real answer is kept.
72
- const quotaFailure = resourceExhausted && stdout.trim() === "";
73
- if (code === 0 && !quotaFailure) {
74
- Logger.commandComplete(startTime, code, stdout.length);
75
- resolve(stdout.trim());
76
- }
77
- else {
78
- Logger.commandComplete(startTime, code);
79
- Logger.error(`Failed with exit code ${code}`);
80
- const errorMessage = stderr.trim() || (resourceExhausted ? "RESOURCE_EXHAUSTED" : "Unknown error");
81
- reject(new Error(`Command failed with exit code ${code}: ${errorMessage}`));
82
- }
187
+ if (isResolved)
188
+ return;
189
+ if (stopError) {
190
+ // A parent can exit before its children. Clear any survivors from its
191
+ // process group before reporting that the request has stopped.
192
+ if (grouped && childProcess.pid !== undefined)
193
+ signalProcessGroup("SIGKILL");
194
+ finishStopped(code);
195
+ return;
196
+ }
197
+ settle();
198
+ // Join array buffers efficiently
199
+ const stdout = stdoutChunks.join('');
200
+ const stderr = stderrChunks.join('');
201
+ // Treat an exit-0 run as failed only when it reported quota exhaustion
202
+ // AND produced no usable stdout — so a quota error with empty output is
203
+ // surfaced (not silently "recovered"), while a run that still returned a
204
+ // real answer is kept.
205
+ const quotaFailure = resourceExhausted && stdout.trim() === "";
206
+ if (code === 0 && !quotaFailure) {
207
+ Logger.commandComplete(startTime, code, stdout.length);
208
+ resolve(stdout.trim());
209
+ }
210
+ else {
211
+ Logger.commandComplete(startTime, code);
212
+ Logger.error(`Failed with exit code ${code}`);
213
+ const errorMessage = stderr.trim() || (resourceExhausted ? "RESOURCE_EXHAUSTED" : "Unknown error");
214
+ reject(new Error(`Command failed with exit code ${code}: ${errorMessage}`));
83
215
  }
84
216
  });
85
217
  });
@@ -1 +1 @@
1
- {"version":3,"file":"commandExecutor.js","sourceRoot":"","sources":["../../src/utils/commandExecutor.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"commandExecutor.js","sourceRoot":"","sources":["../../src/utils/commandExecutor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ export interface Checkout {
2
+ /** Null on a detached HEAD. */
3
+ branch: string | null;
4
+ head: string;
5
+ }
6
+ /** The branch and commit checked out in `directory`, or null when it is not a git checkout with a commit. */
7
+ export declare function readCheckout(directory: string): Promise<Checkout | null>;
8
+ /**
9
+ * The merge base of `ref` and HEAD in `directory`. Throws with git's own reason
10
+ * when the ref does not resolve; `--end-of-options` keeps a ref spelled like an
11
+ * option from being read as one.
12
+ */
13
+ export declare function resolveMergeBase(directory: string, ref: string): Promise<string>;
14
+ //# sourceMappingURL=gitCheckout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gitCheckout.d.ts","sourceRoot":"","sources":["../../src/utils/gitCheckout.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,QAAQ;IACvB,+BAA+B;IAC/B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;CACd;AAMD,6GAA6G;AAC7G,wBAAsB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAS9E;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQtF"}
@@ -0,0 +1,36 @@
1
+ import { execFile } from 'child_process';
2
+ import { promisify } from 'util';
3
+ const execFileAsync = promisify(execFile);
4
+ const GIT_TIMEOUT_MS = 10_000;
5
+ function git(directory, args) {
6
+ return execFileAsync('git', args, { cwd: directory, timeout: GIT_TIMEOUT_MS });
7
+ }
8
+ /** The branch and commit checked out in `directory`, or null when it is not a git checkout with a commit. */
9
+ export async function readCheckout(directory) {
10
+ try {
11
+ const { stdout } = await git(directory, ['rev-parse', 'HEAD', '--abbrev-ref', 'HEAD']);
12
+ const [head, branch] = stdout.trim().split('\n');
13
+ if (!head || !branch)
14
+ return null;
15
+ return { head, branch: branch === 'HEAD' ? null : branch };
16
+ }
17
+ catch {
18
+ return null;
19
+ }
20
+ }
21
+ /**
22
+ * The merge base of `ref` and HEAD in `directory`. Throws with git's own reason
23
+ * when the ref does not resolve; `--end-of-options` keeps a ref spelled like an
24
+ * option from being read as one.
25
+ */
26
+ export async function resolveMergeBase(directory, ref) {
27
+ try {
28
+ const { stdout } = await git(directory, ['merge-base', '--end-of-options', ref, 'HEAD']);
29
+ return stdout.trim();
30
+ }
31
+ catch (error) {
32
+ const stderr = error.stderr?.trim();
33
+ throw new Error(stderr || error.message);
34
+ }
35
+ }
36
+ //# sourceMappingURL=gitCheckout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gitCheckout.js","sourceRoot":"","sources":["../../src/utils/gitCheckout.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AAEjC,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAC1C,MAAM,cAAc,GAAG,MAAM,CAAC;AAQ9B,SAAS,GAAG,CAAC,SAAiB,EAAE,IAAc;IAC5C,OAAO,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC,CAAC;AACjF,CAAC;AAED,6GAA6G;AAC7G,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,SAAiB;IAClD,IAAI,CAAC;QACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,CAAC,SAAS,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,CAAC,CAAC,CAAC;QACvF,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACjD,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC;QAClC,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IAC7D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,SAAiB,EAAE,GAAW;IACnE,IAAI,CAAC;QACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,CAAC,SAAS,EAAE,CAAC,YAAY,EAAE,kBAAkB,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC;QACzF,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC;IACvB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,MAAM,GAAI,KAA6B,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC;QAC7D,MAAM,IAAI,KAAK,CAAC,MAAM,IAAK,KAAe,CAAC,OAAO,CAAC,CAAC;IACtD,CAAC;AACH,CAAC"}
@@ -0,0 +1,58 @@
1
+ import { z } from 'zod';
2
+ /** `dev-overlay` marks a frame a framework dev overlay (such as Expo's error overlay) covered; its other entries are suspect. */
3
+ export declare const LAYOUT_ENTRY_KINDS: readonly ["dev-overlay", "page-scroll-x", "text-spill", "text-clipped", "control-covered", "control-offscreen"];
4
+ export declare const layoutReportSchema: z.ZodObject<{
5
+ check: z.ZodLiteral<"layout-check/1">;
6
+ viewport: z.ZodObject<{
7
+ width: z.ZodNumber;
8
+ height: z.ZodNumber;
9
+ }, z.core.$strip>;
10
+ found: z.ZodNumber;
11
+ entries: z.ZodArray<z.ZodObject<{
12
+ text: z.ZodString;
13
+ testID: z.ZodNullable<z.ZodString>;
14
+ rect: z.ZodObject<{
15
+ x: z.ZodNumber;
16
+ y: z.ZodNumber;
17
+ width: z.ZodNumber;
18
+ height: z.ZodNumber;
19
+ }, z.core.$strip>;
20
+ kind: z.ZodEnum<{
21
+ "dev-overlay": "dev-overlay";
22
+ "page-scroll-x": "page-scroll-x";
23
+ "text-spill": "text-spill";
24
+ "text-clipped": "text-clipped";
25
+ "control-covered": "control-covered";
26
+ "control-offscreen": "control-offscreen";
27
+ }>;
28
+ }, z.core.$strip>>;
29
+ clamped: z.ZodArray<z.ZodObject<{
30
+ text: z.ZodString;
31
+ testID: z.ZodNullable<z.ZodString>;
32
+ rect: z.ZodObject<{
33
+ x: z.ZodNumber;
34
+ y: z.ZodNumber;
35
+ width: z.ZodNumber;
36
+ height: z.ZodNumber;
37
+ }, z.core.$strip>;
38
+ kind: z.ZodLiteral<"text-clamped">;
39
+ }, z.core.$strip>>;
40
+ }, z.core.$strip>;
41
+ export type LayoutReport = z.infer<typeof layoutReportSchema>;
42
+ export type LayoutEntry = LayoutReport['entries'][number] | LayoutReport['clamped'][number];
43
+ export type LayoutCheckStatus = 'OK' | 'MISSING' | 'STALE' | 'OUTSIDE' | 'MALFORMED' | 'MISMATCHED';
44
+ export interface LayoutCheck {
45
+ file: string;
46
+ status: LayoutCheckStatus;
47
+ report?: LayoutReport;
48
+ problem?: string;
49
+ }
50
+ export declare function layoutFileName(frameFileName: string): string;
51
+ /**
52
+ * Read one frame's layout file. Never throws: a file that cannot be trusted is
53
+ * reported with its status and treated by the report as "not run". A file
54
+ * measured at another width than the frame's describes a different layout.
55
+ */
56
+ export declare function readLayoutCheck(file: string, artifactDir: string, runStartMs: number, expectedWidth?: number): LayoutCheck;
57
+ export declare function formatLayoutEntry(entry: LayoutEntry): string;
58
+ //# sourceMappingURL=layoutCheck.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layoutCheck.d.ts","sourceRoot":"","sources":["../../src/utils/layoutCheck.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAmBxB,iIAAiI;AACjI,eAAO,MAAM,kBAAkB,iHAOrB,CAAC;AAEX,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAM7B,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAC9D,MAAM,MAAM,WAAW,GAAG,YAAY,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,GAAG,YAAY,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;AAE5F,MAAM,MAAM,iBAAiB,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,WAAW,GAAG,YAAY,CAAC;AAEpG,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,iBAAiB,CAAC;IAC1B,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,cAAc,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAE5D;AAQD;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,aAAa,CAAC,EAAE,MAAM,GACrB,WAAW,CA8Bb;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAI5D"}