craftdriver 1.6.0 → 1.8.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 (229) hide show
  1. package/CHANGELOG.md +35 -6
  2. package/README.md +17 -6
  3. package/dist/cli/agentSession.d.ts +62 -0
  4. package/dist/cli/agentSession.d.ts.map +1 -0
  5. package/dist/cli/agentSession.js +100 -0
  6. package/dist/cli/agentSession.js.map +1 -0
  7. package/dist/cli/artifactPaths.d.ts +42 -0
  8. package/dist/cli/artifactPaths.d.ts.map +1 -0
  9. package/dist/cli/artifactPaths.js +144 -0
  10. package/dist/cli/artifactPaths.js.map +1 -0
  11. package/dist/cli/bounds.d.ts +88 -0
  12. package/dist/cli/bounds.d.ts.map +1 -0
  13. package/dist/cli/bounds.js +128 -0
  14. package/dist/cli/bounds.js.map +1 -0
  15. package/dist/cli/client.d.ts +3 -0
  16. package/dist/cli/client.d.ts.map +1 -1
  17. package/dist/cli/client.js +24 -8
  18. package/dist/cli/client.js.map +1 -1
  19. package/dist/cli/daemon.d.ts +3 -0
  20. package/dist/cli/daemon.d.ts.map +1 -1
  21. package/dist/cli/daemon.js +152 -39
  22. package/dist/cli/daemon.js.map +1 -1
  23. package/dist/cli/defaults.d.ts +23 -3
  24. package/dist/cli/defaults.d.ts.map +1 -1
  25. package/dist/cli/defaults.js +50 -5
  26. package/dist/cli/defaults.js.map +1 -1
  27. package/dist/cli/dispatcher.d.ts +54 -11
  28. package/dist/cli/dispatcher.d.ts.map +1 -1
  29. package/dist/cli/dispatcher.js +851 -54
  30. package/dist/cli/dispatcher.js.map +1 -1
  31. package/dist/cli/index.d.ts.map +1 -1
  32. package/dist/cli/index.js +305 -56
  33. package/dist/cli/index.js.map +1 -1
  34. package/dist/cli/init.d.ts +14 -14
  35. package/dist/cli/init.d.ts.map +1 -1
  36. package/dist/cli/init.js +328 -180
  37. package/dist/cli/init.js.map +1 -1
  38. package/dist/cli/journal.d.ts +153 -0
  39. package/dist/cli/journal.d.ts.map +1 -0
  40. package/dist/cli/journal.js +252 -0
  41. package/dist/cli/journal.js.map +1 -0
  42. package/dist/cli/lineReader.d.ts +33 -0
  43. package/dist/cli/lineReader.d.ts.map +1 -0
  44. package/dist/cli/lineReader.js +66 -0
  45. package/dist/cli/lineReader.js.map +1 -0
  46. package/dist/cli/locatorCandidates.d.ts +44 -0
  47. package/dist/cli/locatorCandidates.d.ts.map +1 -0
  48. package/dist/cli/locatorCandidates.js +190 -0
  49. package/dist/cli/locatorCandidates.js.map +1 -0
  50. package/dist/cli/mcp/artifacts.d.ts +39 -1
  51. package/dist/cli/mcp/artifacts.d.ts.map +1 -1
  52. package/dist/cli/mcp/artifacts.js +104 -9
  53. package/dist/cli/mcp/artifacts.js.map +1 -1
  54. package/dist/cli/mcp/bounds.d.ts +65 -0
  55. package/dist/cli/mcp/bounds.d.ts.map +1 -0
  56. package/dist/cli/mcp/bounds.js +124 -0
  57. package/dist/cli/mcp/bounds.js.map +1 -0
  58. package/dist/cli/mcp/params.d.ts +38 -0
  59. package/dist/cli/mcp/params.d.ts.map +1 -0
  60. package/dist/cli/mcp/params.js +174 -0
  61. package/dist/cli/mcp/params.js.map +1 -0
  62. package/dist/cli/mcp/server.d.ts +94 -16
  63. package/dist/cli/mcp/server.d.ts.map +1 -1
  64. package/dist/cli/mcp/server.js +304 -150
  65. package/dist/cli/mcp/server.js.map +1 -1
  66. package/dist/cli/mcp/tools.d.ts +62 -17
  67. package/dist/cli/mcp/tools.d.ts.map +1 -1
  68. package/dist/cli/mcp/tools.js +373 -158
  69. package/dist/cli/mcp/tools.js.map +1 -1
  70. package/dist/cli/pageSemantics.d.ts +42 -0
  71. package/dist/cli/pageSemantics.d.ts.map +1 -0
  72. package/dist/cli/pageSemantics.js +194 -0
  73. package/dist/cli/pageSemantics.js.map +1 -0
  74. package/dist/cli/parseArgs.d.ts +6 -18
  75. package/dist/cli/parseArgs.d.ts.map +1 -1
  76. package/dist/cli/parseArgs.js +689 -45
  77. package/dist/cli/parseArgs.js.map +1 -1
  78. package/dist/cli/protocol.d.ts +8 -0
  79. package/dist/cli/protocol.d.ts.map +1 -1
  80. package/dist/cli/sessionRegistry.d.ts +42 -0
  81. package/dist/cli/sessionRegistry.d.ts.map +1 -0
  82. package/dist/cli/sessionRegistry.js +99 -0
  83. package/dist/cli/sessionRegistry.js.map +1 -0
  84. package/dist/cli/snapshot.d.ts +100 -18
  85. package/dist/cli/snapshot.d.ts.map +1 -1
  86. package/dist/cli/snapshot.js +248 -102
  87. package/dist/cli/snapshot.js.map +1 -1
  88. package/dist/cli/stateStore.d.ts +50 -0
  89. package/dist/cli/stateStore.d.ts.map +1 -0
  90. package/dist/cli/stateStore.js +122 -0
  91. package/dist/cli/stateStore.js.map +1 -0
  92. package/dist/index.d.ts +2 -1
  93. package/dist/index.d.ts.map +1 -1
  94. package/dist/index.js +2 -0
  95. package/dist/index.js.map +1 -1
  96. package/dist/lib/bidi/index.d.ts +1 -1
  97. package/dist/lib/bidi/index.d.ts.map +1 -1
  98. package/dist/lib/bidi/index.js +2 -2
  99. package/dist/lib/bidi/index.js.map +1 -1
  100. package/dist/lib/bidi/logs.d.ts +3 -0
  101. package/dist/lib/bidi/logs.d.ts.map +1 -1
  102. package/dist/lib/bidi/logs.js +7 -0
  103. package/dist/lib/bidi/logs.js.map +1 -1
  104. package/dist/lib/bidi/network.d.ts +14 -1
  105. package/dist/lib/bidi/network.d.ts.map +1 -1
  106. package/dist/lib/bidi/network.js +45 -7
  107. package/dist/lib/bidi/network.js.map +1 -1
  108. package/dist/lib/bidi/storage.d.ts +30 -6
  109. package/dist/lib/bidi/storage.d.ts.map +1 -1
  110. package/dist/lib/bidi/storage.js +190 -45
  111. package/dist/lib/bidi/storage.js.map +1 -1
  112. package/dist/lib/bidi/types.d.ts +7 -2
  113. package/dist/lib/bidi/types.d.ts.map +1 -1
  114. package/dist/lib/browser.d.ts +38 -3
  115. package/dist/lib/browser.d.ts.map +1 -1
  116. package/dist/lib/browser.js +196 -30
  117. package/dist/lib/browser.js.map +1 -1
  118. package/dist/lib/browserContext.d.ts +54 -18
  119. package/dist/lib/browserContext.d.ts.map +1 -1
  120. package/dist/lib/browserContext.js +295 -92
  121. package/dist/lib/browserContext.js.map +1 -1
  122. package/dist/lib/by.d.ts +3 -3
  123. package/dist/lib/by.d.ts.map +1 -1
  124. package/dist/lib/by.js +7 -3
  125. package/dist/lib/by.js.map +1 -1
  126. package/dist/lib/driver.d.ts +7 -0
  127. package/dist/lib/driver.d.ts.map +1 -1
  128. package/dist/lib/driver.js +12 -0
  129. package/dist/lib/driver.js.map +1 -1
  130. package/dist/lib/errors.d.ts +15 -0
  131. package/dist/lib/errors.d.ts.map +1 -1
  132. package/dist/lib/errors.js +15 -0
  133. package/dist/lib/errors.js.map +1 -1
  134. package/dist/lib/initScript.d.ts +9 -0
  135. package/dist/lib/initScript.d.ts.map +1 -0
  136. package/dist/lib/initScript.js +14 -0
  137. package/dist/lib/initScript.js.map +1 -0
  138. package/dist/lib/page.d.ts +22 -0
  139. package/dist/lib/page.d.ts.map +1 -1
  140. package/dist/lib/page.js +48 -0
  141. package/dist/lib/page.js.map +1 -1
  142. package/dist/lib/secureFile.d.ts +8 -0
  143. package/dist/lib/secureFile.d.ts.map +1 -0
  144. package/dist/lib/secureFile.js +67 -0
  145. package/dist/lib/secureFile.js.map +1 -0
  146. package/dist/lib/sessionStateValidation.d.ts +35 -0
  147. package/dist/lib/sessionStateValidation.d.ts.map +1 -0
  148. package/dist/lib/sessionStateValidation.js +223 -0
  149. package/dist/lib/sessionStateValidation.js.map +1 -0
  150. package/dist/lib/timing.d.ts +8 -0
  151. package/dist/lib/timing.d.ts.map +1 -1
  152. package/dist/lib/timing.js +8 -0
  153. package/dist/lib/timing.js.map +1 -1
  154. package/dist/lib/tracing.d.ts +2 -1
  155. package/dist/lib/tracing.d.ts.map +1 -1
  156. package/dist/lib/tracing.js +12 -1
  157. package/dist/lib/tracing.js.map +1 -1
  158. package/dist/lib/visual/antialias.d.ts +41 -0
  159. package/dist/lib/visual/antialias.d.ts.map +1 -0
  160. package/dist/lib/visual/antialias.js +126 -0
  161. package/dist/lib/visual/antialias.js.map +1 -0
  162. package/dist/lib/visual/codec.d.ts +24 -0
  163. package/dist/lib/visual/codec.d.ts.map +1 -0
  164. package/dist/lib/visual/codec.js +11 -0
  165. package/dist/lib/visual/codec.js.map +1 -0
  166. package/dist/lib/visual/compare.d.ts +38 -0
  167. package/dist/lib/visual/compare.d.ts.map +1 -0
  168. package/dist/lib/visual/compare.js +209 -0
  169. package/dist/lib/visual/compare.js.map +1 -0
  170. package/dist/lib/visual/diff.d.ts +18 -0
  171. package/dist/lib/visual/diff.d.ts.map +1 -0
  172. package/dist/lib/visual/diff.js +23 -0
  173. package/dist/lib/visual/diff.js.map +1 -0
  174. package/dist/lib/visual/expectScreenshot.d.ts +65 -0
  175. package/dist/lib/visual/expectScreenshot.d.ts.map +1 -0
  176. package/dist/lib/visual/expectScreenshot.js +287 -0
  177. package/dist/lib/visual/expectScreenshot.js.map +1 -0
  178. package/dist/lib/visual/index.d.ts +10 -0
  179. package/dist/lib/visual/index.d.ts.map +1 -0
  180. package/dist/lib/visual/index.js +9 -0
  181. package/dist/lib/visual/index.js.map +1 -0
  182. package/dist/lib/visual/pixelMath.d.ts +32 -0
  183. package/dist/lib/visual/pixelMath.d.ts.map +1 -0
  184. package/dist/lib/visual/pixelMath.js +70 -0
  185. package/dist/lib/visual/pixelMath.js.map +1 -0
  186. package/dist/lib/visual/pngHeader.d.ts +27 -0
  187. package/dist/lib/visual/pngHeader.d.ts.map +1 -0
  188. package/dist/lib/visual/pngHeader.js +63 -0
  189. package/dist/lib/visual/pngHeader.js.map +1 -0
  190. package/dist/lib/visual/pngjsCodec.d.ts +22 -0
  191. package/dist/lib/visual/pngjsCodec.d.ts.map +1 -0
  192. package/dist/lib/visual/pngjsCodec.js +70 -0
  193. package/dist/lib/visual/pngjsCodec.js.map +1 -0
  194. package/dist/lib/visual/types.d.ts +89 -0
  195. package/dist/lib/visual/types.d.ts.map +1 -0
  196. package/dist/lib/visual/types.js +20 -0
  197. package/dist/lib/visual/types.js.map +1 -0
  198. package/dist/lib/visual/visualError.d.ts +32 -0
  199. package/dist/lib/visual/visualError.d.ts.map +1 -0
  200. package/dist/lib/visual/visualError.js +59 -0
  201. package/dist/lib/visual/visualError.js.map +1 -0
  202. package/dist/lib/wait.d.ts.map +1 -1
  203. package/dist/lib/wait.js +7 -1
  204. package/dist/lib/wait.js.map +1 -1
  205. package/docs/agents.md +56 -61
  206. package/docs/api-reference.md +16 -1
  207. package/docs/browser-api.md +24 -4
  208. package/docs/browser-context.md +18 -12
  209. package/docs/cli.md +346 -26
  210. package/docs/error-codes.md +6 -4
  211. package/docs/getting-started.md +6 -1
  212. package/docs/index.md +1 -1
  213. package/docs/mcp.md +129 -62
  214. package/docs/public/examples/agent-actions.html +48 -0
  215. package/docs/public/examples/agent-debug.html +86 -0
  216. package/docs/public/examples/agent-late-mutation.html +18 -0
  217. package/docs/public/examples/agent-ref-shift.html +33 -0
  218. package/docs/recipes/login-once-reuse-session.md +32 -4
  219. package/docs/screenshots.md +15 -6
  220. package/docs/session-management.md +64 -3
  221. package/docs/visual-testing.md +254 -0
  222. package/docs/why-craftdriver.md +4 -3
  223. package/package.json +5 -1
  224. package/skills/craftdriver/SKILL.md +59 -75
  225. package/skills/craftdriver/agents/openai.yaml +4 -0
  226. package/skills/craftdriver/cheatsheet.md +18 -4
  227. package/skills/craftdriver/cli.md +194 -68
  228. package/skills/craftdriver/patterns.md +14 -8
  229. package/skills/craftdriver/workflow.md +149 -0
@@ -1 +1 @@
1
- {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../../src/cli/mcp/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAGxD,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,qEAAqE;IACrE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,4DAA4D;IAC5D,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC;IAC9F,iFAAiF;IACjF,QAAQ,EAAE,OAAO,CAAC;IAClB,mEAAmE;IACnE,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAUD,eAAO,MAAM,KAAK,EAAE,OAAO,EAqP1B,CAAC;AAIF,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAEzD;AAED,6EAA6E;AAC7E,wBAAsB,OAAO,CAC3B,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,OAAO,EACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,OAAO,CAAC,OAAO,CAAC,CAGlB"}
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../../src/cli/mcp/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,KAAK,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAClF,OAAO,EAA+B,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAE3E;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,wDAAwD;IACxD,YAAY,EAAE,OAAO,CAAC;IACtB,yDAAyD;IACzD,eAAe,EAAE,OAAO,CAAC;IACzB,0EAA0E;IAC1E,cAAc,EAAE,OAAO,CAAC;IACxB,kEAAkE;IAClE,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,UAAU,CAAC;IACnB,WAAW,EAAE,eAAe,CAAC;IAC7B,gDAAgD;IAChD,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC;CAC/F;AAoCD,eAAO,MAAM,KAAK,EAAE,OAAO,EA4a1B,CAAC;AAIF,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAEzD;AAED,+EAA+E;AAC/E,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAErE;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEtF;AAED,gFAAgF;AAChF,wBAAsB,OAAO,CAC3B,OAAO,EAAE,kBAAkB,EAC3B,IAAI,EAAE,OAAO,EACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,OAAO,CAAC,OAAO,CAAC,CAElB;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,kBAAkB,EAC3B,IAAI,EAAE,OAAO,EACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,OAAO,CAAC,mBAAmB,CAAC,CAE9B"}
@@ -1,94 +1,201 @@
1
- import { dispatch } from '../dispatcher.js';
2
- const STRING_SELECTOR = {
1
+ import { toInputSchema, validateArgs } from './params.js';
2
+ const SELECTOR = {
3
3
  type: 'string',
4
+ required: true,
4
5
  description: "CSS by default. Switch kind with a prefix: 'role=button[name=Submit]', " +
5
6
  "'text=Sign In', 'text*=Sign', 'label=Email', 'placeholder=...', " +
6
- "'testid=...', 'xpath=...', 'id=...', 'name=...'.",
7
+ "'testid=...', 'xpath=...', 'id=...', 'name=...', or 'ref=eN' from a snapshot.",
7
8
  };
9
+ const OPTIONAL_SELECTOR = { ...SELECTOR, required: false };
10
+ const TIMEOUT = { type: 'number', min: 0, max: 300_000, description: 'Milliseconds.' };
11
+ /** A page action that changes what the user sees. */
12
+ function mutating(title, openWorld = true) {
13
+ return {
14
+ title,
15
+ readOnlyHint: false,
16
+ destructiveHint: false,
17
+ idempotentHint: false,
18
+ openWorldHint: openWorld,
19
+ };
20
+ }
21
+ /** An inspection that changes nothing. */
22
+ function readOnly(title, openWorld = true) {
23
+ return {
24
+ title,
25
+ readOnlyHint: true,
26
+ destructiveHint: false,
27
+ idempotentHint: true,
28
+ openWorldHint: openWorld,
29
+ };
30
+ }
8
31
  export const TOOLS = [
32
+ // ---- navigation and core actions ---------------------------------------
9
33
  {
10
34
  name: 'browser_navigate',
11
35
  description: 'Navigate the active page to a URL. Waits for load.',
12
- inputSchema: {
13
- type: 'object',
14
- properties: { url: { type: 'string' } },
15
- required: ['url'],
16
- },
17
- mutating: true,
36
+ params: { url: { type: 'string', required: true, maxLength: 2048 } },
37
+ annotations: mutating('Navigate'),
18
38
  toDispatch: (a) => ({ cmd: 'go', args: { url: a.url } }),
19
39
  },
20
40
  {
21
41
  name: 'browser_click',
22
42
  description: 'Click an element. Auto-waits for visible+enabled. Returns NO_MATCH immediately ' +
23
- 'when the selector matches zero elements at t=0.',
24
- inputSchema: {
25
- type: 'object',
26
- properties: { selector: STRING_SELECTOR, timeout_ms: { type: 'number' } },
27
- required: ['selector'],
43
+ 'when the selector matches zero elements at t=0. Set double for a double-click.',
44
+ params: {
45
+ selector: SELECTOR,
46
+ double: { type: 'boolean', description: 'Double-click instead of single.' },
47
+ timeout_ms: TIMEOUT,
28
48
  },
29
- mutating: true,
30
- toDispatch: (a) => ({ cmd: 'click', args: { selector: a.selector, timeout: a.timeout_ms } }),
49
+ annotations: mutating('Click'),
50
+ toDispatch: (a) => ({
51
+ cmd: a.double === true ? 'dblclick' : 'click',
52
+ args: { selector: a.selector, timeout: a.timeout_ms },
53
+ }),
31
54
  },
32
55
  {
33
56
  name: 'browser_fill',
34
- description: 'Fill an input/textarea/select. The library auto-clears before typing. ' +
35
- 'For checkboxes prefer browser_click with the matching label.',
36
- inputSchema: {
37
- type: 'object',
38
- properties: {
39
- selector: STRING_SELECTOR,
40
- value: { type: 'string' },
41
- timeout_ms: { type: 'number' },
42
- },
43
- required: ['selector', 'value'],
57
+ description: 'Fill an input/textarea. Clears first, then enters the value with real key events. ' +
58
+ 'For checkboxes use browser_element with action=check.',
59
+ params: {
60
+ selector: SELECTOR,
61
+ value: { type: 'string', required: true },
62
+ timeout_ms: TIMEOUT,
44
63
  },
45
- mutating: true,
64
+ annotations: mutating('Fill field'),
46
65
  toDispatch: (a) => ({
47
66
  cmd: 'fill',
48
67
  args: { selector: a.selector, value: a.value, timeout: a.timeout_ms },
49
68
  }),
50
69
  },
70
+ {
71
+ name: 'browser_type',
72
+ description: 'Type text into whatever currently holds focus. No selector — use browser_fill to ' +
73
+ 'target a field, or browser_element with action=focus first to append.',
74
+ params: { text: { type: 'string', required: true } },
75
+ annotations: mutating('Type into focus'),
76
+ toDispatch: (a) => ({ cmd: 'type', args: { text: a.text } }),
77
+ },
78
+ {
79
+ name: 'browser_element',
80
+ description: 'Act on one element: dblclick, focus, scroll, clear, check, uncheck, or select. ' +
81
+ 'check/uncheck are idempotent and read the state back. select matches an <option> ' +
82
+ 'by its value attribute, not its label.',
83
+ params: {
84
+ action: {
85
+ type: 'string',
86
+ required: true,
87
+ enum: ['dblclick', 'focus', 'scroll', 'clear', 'check', 'uncheck', 'select'],
88
+ },
89
+ selector: SELECTOR,
90
+ value: { type: 'string', description: 'Option value; required for action=select.' },
91
+ timeout_ms: TIMEOUT,
92
+ },
93
+ annotations: mutating('Element action'),
94
+ toDispatch: (a) => ({
95
+ cmd: a.action,
96
+ args: {
97
+ selector: a.selector,
98
+ ...(a.value !== undefined ? { value: a.value } : {}),
99
+ timeout: a.timeout_ms,
100
+ },
101
+ }),
102
+ },
51
103
  {
52
104
  name: 'browser_press',
53
- description: 'Press a keyboard key (e.g. "Enter", "Tab", "Control+A"). Optional selector ' +
54
- 'focuses an element first.',
55
- inputSchema: {
56
- type: 'object',
57
- properties: { key: { type: 'string' }, selector: STRING_SELECTOR },
58
- required: ['key'],
59
- },
60
- mutating: true,
105
+ description: 'Press a key (e.g. "Enter", "Tab", "Control+A"). Optional selector focuses first.',
106
+ params: { key: { type: 'string', required: true, maxLength: 64 }, selector: OPTIONAL_SELECTOR },
107
+ annotations: mutating('Press key'),
61
108
  toDispatch: (a) => ({
62
109
  cmd: 'press',
63
110
  args: { key: a.key, ...(a.selector ? { selector: a.selector } : {}) },
64
111
  }),
65
112
  },
113
+ {
114
+ name: 'browser_key',
115
+ description: 'Low-level keyboard control: hold a key down, release it, or press it. Use for ' +
116
+ 'modifier combinations that browser_press cannot express.',
117
+ params: {
118
+ action: { type: 'string', required: true, enum: ['press', 'down', 'up'] },
119
+ key: { type: 'string', required: true, maxLength: 64 },
120
+ selector: OPTIONAL_SELECTOR,
121
+ },
122
+ annotations: mutating('Keyboard'),
123
+ toDispatch: (a) => ({
124
+ cmd: 'key',
125
+ args: { action: a.action, key: a.key, ...(a.selector ? { selector: a.selector } : {}) },
126
+ }),
127
+ },
128
+ {
129
+ name: 'browser_mouse',
130
+ description: 'Move, click, press, release, or wheel the mouse, by element or by coordinate. ' +
131
+ 'Use for drag, hover-reveal, and canvas interactions.',
132
+ params: {
133
+ action: { type: 'string', required: true, enum: ['move', 'click', 'down', 'up', 'wheel'] },
134
+ selector: OPTIONAL_SELECTOR,
135
+ x: { type: 'number' },
136
+ y: { type: 'number' },
137
+ button: { type: 'string', enum: ['left', 'middle', 'right'] },
138
+ delta_x: { type: 'number' },
139
+ delta_y: { type: 'number' },
140
+ },
141
+ annotations: mutating('Mouse'),
142
+ toDispatch: (a) => ({
143
+ cmd: 'mouse',
144
+ args: {
145
+ action: a.action,
146
+ ...(a.selector ? { selector: a.selector } : {}),
147
+ x: a.x,
148
+ y: a.y,
149
+ button: a.button,
150
+ deltaX: a.delta_x,
151
+ deltaY: a.delta_y,
152
+ },
153
+ }),
154
+ },
66
155
  {
67
156
  name: 'browser_hover',
68
157
  description: 'Hover over an element.',
69
- inputSchema: {
70
- type: 'object',
71
- properties: { selector: STRING_SELECTOR, timeout_ms: { type: 'number' } },
72
- required: ['selector'],
73
- },
74
- mutating: true,
158
+ params: { selector: SELECTOR, timeout_ms: TIMEOUT },
159
+ annotations: mutating('Hover'),
75
160
  toDispatch: (a) => ({ cmd: 'hover', args: { selector: a.selector, timeout: a.timeout_ms } }),
76
161
  },
162
+ {
163
+ name: 'browser_upload',
164
+ description: 'Set files on a file input. Paths must already exist and are never echoed back. ' +
165
+ 'Bounded in count.',
166
+ params: {
167
+ selector: SELECTOR,
168
+ files: { type: 'string[]', required: true, maxItems: 10, maxLength: 4096 },
169
+ },
170
+ annotations: { ...mutating('Upload files'), openWorldHint: true },
171
+ toDispatch: (a) => ({ cmd: 'upload', args: { selector: a.selector, files: a.files } }),
172
+ },
173
+ {
174
+ name: 'browser_dialog',
175
+ description: 'Inspect, accept, or dismiss a native dialog (alert/confirm/prompt). An open dialog ' +
176
+ 'blocks page script, so handle it before anything else.',
177
+ params: {
178
+ action: { type: 'string', required: true, enum: ['inspect', 'accept', 'dismiss'] },
179
+ text: { type: 'string', description: 'Prompt answer; used with accept.' },
180
+ },
181
+ annotations: mutating('Dialog'),
182
+ toDispatch: (a) => ({
183
+ cmd: 'dialog',
184
+ args: { action: a.action, ...(a.text !== undefined ? { text: a.text } : {}) },
185
+ }),
186
+ },
187
+ // ---- inspection ---------------------------------------------------------
77
188
  {
78
189
  name: 'browser_find',
79
- description: 'Locate elements without acting on them. Returns up to `limit` matches with ' +
80
- 'tag, text snippet, and visibility. Use `all: true` to enumerate.',
81
- inputSchema: {
82
- type: 'object',
83
- properties: {
84
- selector: STRING_SELECTOR,
85
- all: { type: 'boolean' },
86
- limit: { type: 'number' },
87
- offset: { type: 'number' },
88
- },
89
- required: ['selector'],
190
+ description: 'Locate elements without acting on them. Returns up to `limit` matches with tag, ' +
191
+ 'text snippet, and visibility. Use `all` to enumerate.',
192
+ params: {
193
+ selector: SELECTOR,
194
+ all: { type: 'boolean' },
195
+ limit: { type: 'number', min: 1, max: 500, integer: true },
196
+ offset: { type: 'number', min: 0, integer: true },
90
197
  },
91
- mutating: false,
198
+ annotations: readOnly('Find elements'),
92
199
  toDispatch: (a) => ({
93
200
  cmd: 'find',
94
201
  args: { selector: a.selector, all: a.all, limit: a.limit, offset: a.offset },
@@ -96,55 +203,27 @@ export const TOOLS = [
96
203
  },
97
204
  {
98
205
  name: 'browser_exists',
99
- description: 'Zero-wait probe: returns { exists, count } immediately. Call this BEFORE ' +
100
- 'browser_click/wait when you are guessing a selector — it costs one BiDi ' +
101
- "roundtrip instead of the full 5 s timeout.",
102
- inputSchema: {
103
- type: 'object',
104
- properties: { selector: STRING_SELECTOR },
105
- required: ['selector'],
106
- },
107
- mutating: false,
206
+ description: 'Zero-wait probe: returns { exists, count } immediately. Call this BEFORE clicking or ' +
207
+ 'waiting when guessing a selector — one roundtrip instead of the full timeout.',
208
+ params: { selector: SELECTOR },
209
+ annotations: readOnly('Probe selector'),
108
210
  toDispatch: (a) => ({ cmd: 'exists', args: { selector: a.selector } }),
109
211
  },
110
- {
111
- name: 'browser_wait',
112
- description: 'Wait for a condition: selector to become visible/hidden/attached/detached, ' +
113
- 'or a load state (load|domcontentloaded|networkidle).',
114
- inputSchema: {
115
- type: 'object',
116
- properties: {
117
- target: { type: 'string' },
118
- kind: { type: 'string', enum: ['selector', 'load'] },
119
- state: { type: 'string' },
120
- timeout_ms: { type: 'number' },
121
- },
122
- required: ['target'],
123
- },
124
- mutating: false,
125
- toDispatch: (a) => ({
126
- cmd: 'wait',
127
- args: { target: a.target, kind: a.kind ?? 'selector', state: a.state, timeout: a.timeout_ms },
128
- }),
129
- },
130
212
  {
131
213
  name: 'browser_read',
132
- description: 'Read text/attr/value/is-state from an element, or page body text when no ' +
133
- 'selector is given. `kind` defaults to "text".',
134
- inputSchema: {
135
- type: 'object',
136
- properties: {
137
- selector: STRING_SELECTOR,
138
- kind: { type: 'string', enum: ['text', 'attr', 'value', 'is'] },
139
- name: { type: 'string', description: 'attribute name when kind=attr' },
140
- what: {
141
- type: 'string',
142
- enum: ['visible', 'enabled', 'checked'],
143
- description: 'state when kind=is',
144
- },
214
+ description: 'Read text/attr/value/is-state from an element, or page body text when no selector ' +
215
+ 'is given. `kind` defaults to "text".',
216
+ params: {
217
+ selector: OPTIONAL_SELECTOR,
218
+ kind: { type: 'string', enum: ['text', 'attr', 'value', 'is'] },
219
+ name: { type: 'string', description: 'Attribute name when kind=attr.' },
220
+ what: {
221
+ type: 'string',
222
+ enum: ['visible', 'enabled', 'checked'],
223
+ description: 'State when kind=is.',
145
224
  },
146
225
  },
147
- mutating: false,
226
+ annotations: readOnly('Read element'),
148
227
  toDispatch: (a) => {
149
228
  const kind = a.kind ?? 'text';
150
229
  if (kind === 'attr')
@@ -157,88 +236,203 @@ export const TOOLS = [
157
236
  },
158
237
  },
159
238
  {
160
- name: 'browser_pages',
161
- description: 'List open pages with id/url/title. Use for tab/popup-aware flows.',
162
- inputSchema: { type: 'object', properties: {} },
163
- mutating: false,
164
- toDispatch: () => ({ cmd: 'pages', args: {} }),
239
+ name: 'browser_wait',
240
+ description: 'Wait for a selector to become visible/hidden/attached/detached, or for a load state ' +
241
+ '(load|domcontentloaded|networkidle).',
242
+ params: {
243
+ target: { type: 'string', required: true },
244
+ kind: { type: 'string', enum: ['selector', 'load'] },
245
+ state: { type: 'string', maxLength: 32 },
246
+ timeout_ms: TIMEOUT,
247
+ },
248
+ annotations: readOnly('Wait for condition'),
249
+ toDispatch: (a) => ({
250
+ cmd: 'wait',
251
+ args: { target: a.target, kind: a.kind ?? 'selector', state: a.state, timeout: a.timeout_ms },
252
+ }),
165
253
  },
166
254
  {
167
255
  name: 'browser_snapshot',
168
- description: 'Sanitized accessibility-tree summary of the active page: one line ' +
169
- 'per visible interactive element with role, accessible name, and a ' +
170
- 'stable ref (`e1`, `e2`, …). Use `ref=eN` as the selector for ' +
171
- 'subsequent tool calls refs are auto-resolved without DOM hunting ' +
172
- 'and invalidate on the next snapshot or navigation. Cheaper and ' +
173
- 'more reliable than browser_screenshot for understanding the page.',
174
- inputSchema: { type: 'object', properties: {} },
175
- mutating: false,
256
+ description: 'Sanitized accessibility-tree summary of the active page: one line per visible ' +
257
+ 'interactive element with role, accessible name, and a stable ref (e1, e2, …). Use ' +
258
+ '`ref=eN` as a selector for later calls. A ref binds to one element and fails ' +
259
+ 'STALE_REF rather than drifting; it is exploration state and never belongs in a test.',
260
+ params: {},
261
+ annotations: readOnly('Page snapshot'),
176
262
  toDispatch: () => ({ cmd: 'snapshot', args: {} }),
177
263
  },
264
+ {
265
+ name: 'browser_locators',
266
+ description: 'Turn an element into durable selectors for a committed test, ordered by resilience ' +
267
+ '(role+name, label, test id, unique text, minimal CSS) and each re-checked against ' +
268
+ 'the live page. Use this to convert a snapshot ref into something a test can keep. ' +
269
+ 'No candidate ever contains a ref.',
270
+ params: {
271
+ selector: SELECTOR,
272
+ limit: { type: 'number', min: 1, max: 20, integer: true },
273
+ },
274
+ annotations: readOnly('Durable selectors'),
275
+ toDispatch: (a) => ({ cmd: 'locators', args: { selector: a.selector, limit: a.limit } }),
276
+ },
178
277
  {
179
278
  name: 'browser_screenshot',
180
- description: 'Capture a PNG of the active page or a specific element. Always written ' +
181
- 'to disk under the per-session artifact directory; the response carries ' +
182
- 'the absolute path and byte count (zero image tokens). Pass `path` to ' +
183
- 'choose your own destination.',
184
- inputSchema: {
185
- type: 'object',
186
- properties: {
187
- selector: STRING_SELECTOR,
188
- full_page: { type: 'boolean' },
189
- path: {
190
- type: 'string',
191
- description: 'Absolute path to write the PNG to. If omitted, an artifact ' +
192
- 'path is auto-allocated.',
193
- },
194
- },
279
+ description: 'Capture a PNG of the active page or one element. Written to disk under the ' +
280
+ 'per-session artifact directory; the response carries the path and byte count, not ' +
281
+ 'image tokens.',
282
+ // No destination parameter: the description promises the per-session
283
+ // artifact directory, and a caller-supplied path made that promise false
284
+ // — it could write a PNG anywhere the process could reach. The server
285
+ // allocates the path instead.
286
+ params: {
287
+ selector: OPTIONAL_SELECTOR,
288
+ full_page: { type: 'boolean' },
195
289
  },
196
- mutating: false,
197
- returnsImage: true,
290
+ annotations: { ...readOnly('Screenshot'), idempotentHint: false },
198
291
  toDispatch: (a) => ({
199
292
  cmd: 'screenshot',
200
293
  args: {
201
294
  ...(a.selector ? { selector: a.selector } : {}),
202
295
  fullPage: a.full_page,
203
- ...(a.path ? { path: a.path } : {}),
204
296
  },
205
297
  }),
206
298
  },
207
299
  {
208
- name: 'browser_trace',
209
- description: 'Start or stop browser tracing. Stop with path to create a zip playable at player.vibium.dev.',
210
- inputSchema: {
211
- type: 'object',
212
- properties: {
213
- action: { type: 'string', enum: ['start', 'stop'] },
214
- out_dir: { type: 'string', description: 'Raw trace directory; required for start.' },
215
- path: { type: 'string', description: 'Vibium-compatible zip path; used for stop.' },
216
- title: { type: 'string', description: 'Viewer title; used for start.' },
300
+ name: 'browser_status',
301
+ description: 'Report whether a browser is up and which URL is active.',
302
+ params: {},
303
+ annotations: readOnly('Status', false),
304
+ toDispatch: () => ({ cmd: 'status', args: {} }),
305
+ },
306
+ // ---- pages, evidence, and artifacts -------------------------------------
307
+ {
308
+ name: 'browser_page',
309
+ description: 'List, open, select, or close a tab. A tab the application opens is listed but never ' +
310
+ 'selected implicitly. Switching or closing clears refs, so snapshot again afterwards.',
311
+ params: {
312
+ action: { type: 'string', required: true, enum: ['list', 'open', 'select', 'close'] },
313
+ target: { type: 'string', maxLength: 128, description: 'Index or page id.' },
314
+ url: { type: 'string', maxLength: 2048, description: 'Used with action=open.' },
315
+ },
316
+ annotations: mutating('Tabs'),
317
+ toDispatch: (a) => ({
318
+ cmd: 'page',
319
+ args: {
320
+ action: a.action,
321
+ ...(a.target !== undefined ? { target: a.target } : {}),
322
+ ...(a.url !== undefined ? { url: a.url } : {}),
323
+ },
324
+ }),
325
+ },
326
+ {
327
+ name: 'browser_logs',
328
+ description: 'Console and network history for this session, captured from launch — so an error ' +
329
+ 'thrown during the first navigation is still answerable. Every result carries a ' +
330
+ 'cursor; pass it back as `since` for only what is new. kind=error covers both ' +
331
+ 'uncaught exceptions and console.error. Network rows are summaries: no bodies, ' +
332
+ 'cookies or headers.',
333
+ params: {
334
+ action: { type: 'string', enum: ['list', 'wait', 'clear'] },
335
+ kind: {
336
+ type: 'string',
337
+ maxLength: 64,
338
+ description: 'Comma-separated: console, error, request, response.',
217
339
  },
218
- required: ['action'],
340
+ level: { type: 'string', maxLength: 32 },
341
+ contains: { type: 'string', maxLength: 512 },
342
+ since: { type: 'number', min: 0, integer: true },
343
+ limit: { type: 'number', min: 1, max: 500, integer: true },
344
+ timeout_ms: TIMEOUT,
219
345
  },
220
- mutating: false,
221
- toDispatch: (a) => a.action === 'start'
222
- ? { cmd: 'trace-start', args: { outDir: a.out_dir, title: a.title } }
223
- : { cmd: 'trace-stop', args: { path: a.path } },
346
+ annotations: readOnly('Console and network'),
347
+ toDispatch: (a) => ({
348
+ cmd: 'logs',
349
+ args: {
350
+ action: a.action ?? 'list',
351
+ kind: a.kind,
352
+ level: a.level,
353
+ contains: a.contains,
354
+ since: a.since,
355
+ limit: a.limit,
356
+ timeout: a.timeout_ms,
357
+ },
358
+ }),
224
359
  },
225
360
  {
226
- name: 'browser_status',
227
- description: 'Report whether a browser is up and which URL is active.',
228
- inputSchema: { type: 'object', properties: {} },
229
- mutating: false,
230
- toDispatch: () => ({ cmd: 'status', args: {} }),
361
+ name: 'browser_mock',
362
+ description: 'Serve a fixed response for matching requests, or block them, so an error path can be ' +
363
+ 'driven without changing the application. Validated before installation and bounded ' +
364
+ 'in count. Clear them when done — they outlive the call that added them.',
365
+ params: {
366
+ action: { type: 'string', enum: ['add', 'block', 'list', 'remove', 'clear'] },
367
+ pattern: { type: 'string', maxLength: 500, description: 'URL pattern, e.g. **/api/*.' },
368
+ status: { type: 'number', min: 100, max: 599, integer: true },
369
+ body: { type: 'string', maxLength: 64 * 1024 },
370
+ content_type: { type: 'string', maxLength: 128 },
371
+ id: { type: 'string', maxLength: 128, description: 'Used with action=remove.' },
372
+ },
373
+ annotations: mutating('Network mocks'),
374
+ toDispatch: (a) => ({
375
+ cmd: 'mock',
376
+ args: {
377
+ action: a.action ?? 'list',
378
+ pattern: a.pattern,
379
+ status: a.status,
380
+ body: a.body,
381
+ contentType: a.content_type,
382
+ id: a.id,
383
+ },
384
+ }),
231
385
  },
386
+ {
387
+ name: 'browser_state',
388
+ description: 'Save or restore cookies and local storage, so a login is captured once instead of ' +
389
+ 'replayed. Navigate to the site BEFORE loading: local storage only restores onto its ' +
390
+ 'own origin, and loading onto a blank page would silently drop it. Names are bare ' +
391
+ '(no paths); values are never printed.',
392
+ params: {
393
+ action: { type: 'string', enum: ['save', 'load', 'list'] },
394
+ name: { type: 'string', maxLength: 64 },
395
+ session_storage: { type: 'boolean', description: 'Include sessionStorage on save.' },
396
+ },
397
+ annotations: { ...mutating('Login state'), destructiveHint: true },
398
+ toDispatch: (a) => ({
399
+ cmd: 'state',
400
+ args: {
401
+ action: a.action ?? 'list',
402
+ ...(a.name !== undefined ? { name: a.name } : {}),
403
+ sessionStorage: a.session_storage,
404
+ },
405
+ }),
406
+ },
407
+ {
408
+ name: 'browser_trace',
409
+ description: 'Record actions, console, network and screenshots to a file for a failure a snapshot ' +
410
+ 'cannot explain. One trace at a time; output lands in an owned directory. Stop with ' +
411
+ 'zip for an archive playable at player.vibium.dev.',
412
+ params: {
413
+ action: { type: 'string', enum: ['start', 'stop', 'status'] },
414
+ name: { type: 'string', maxLength: 64, description: 'Bare name; used with start.' },
415
+ zip: { type: 'boolean', description: 'Also write an archive; used with stop.' },
416
+ no_screenshots: { type: 'boolean', description: 'Skip screenshots; used with start.' },
417
+ },
418
+ annotations: mutating('Tracing'),
419
+ toDispatch: (a) => ({
420
+ cmd: 'trace',
421
+ args: {
422
+ action: a.action ?? 'status',
423
+ ...(a.name !== undefined ? { name: a.name } : {}),
424
+ zip: a.zip,
425
+ noScreenshots: a.no_screenshots,
426
+ },
427
+ }),
428
+ },
429
+ // ---- escape hatch -------------------------------------------------------
232
430
  {
233
431
  name: 'browser_advanced_eval',
234
- description: 'Evaluate arbitrary JavaScript in the page. LAST RESORT — prefer ' +
235
- 'browser_find/click/fill when possible. Costs tokens and bypasses auto-wait.',
236
- inputSchema: {
237
- type: 'object',
238
- properties: { js: { type: 'string' } },
239
- required: ['js'],
240
- },
241
- mutating: true,
432
+ description: 'Evaluate arbitrary JavaScript in the page. LAST RESORT — prefer the targeted tools. ' +
433
+ 'Costs tokens and bypasses auto-wait.',
434
+ params: { js: { type: 'string', required: true, maxLength: 32 * 1024 } },
435
+ annotations: { ...mutating('Evaluate JavaScript'), destructiveHint: true },
242
436
  toDispatch: (a) => ({ cmd: 'eval', args: { js: a.js } }),
243
437
  },
244
438
  ];
@@ -246,9 +440,30 @@ const BY_NAME = new Map(TOOLS.map((t) => [t.name, t]));
246
440
  export function getTool(name) {
247
441
  return BY_NAME.get(name);
248
442
  }
249
- /** Run a tool against the dispatcher. Throws CraftdriverError on failure. */
250
- export async function runTool(ctx, tool, args) {
251
- const mapped = tool.toDispatch(args);
252
- return dispatch(ctx, mapped.cmd, mapped.args);
443
+ /** The advertised schema for a tool, derived from its parameter descriptor. */
444
+ export function inputSchemaFor(tool) {
445
+ return toInputSchema(tool.params);
446
+ }
447
+ /**
448
+ * Validate arguments against the tool's descriptor.
449
+ *
450
+ * Separate from dispatch so the server can reject before touching the session
451
+ * queue: an invalid call should not wait behind a slow browser action to be
452
+ * told it was malformed.
453
+ */
454
+ export function validateToolArgs(tool, args) {
455
+ return validateArgs(tool.name, tool.params, args);
456
+ }
457
+ /** Map a tool to a dispatcher command and run it through the shared session. */
458
+ export async function runTool(session, tool, args) {
459
+ return session.run(tool.toDispatch(args));
460
+ }
461
+ /**
462
+ * As {@link runTool}, but keeps the post-action snapshot the session captured
463
+ * in the same operation. The MCP adapter renders that as the second content
464
+ * block; it does not compute or own it.
465
+ */
466
+ export async function runToolDetailed(session, tool, args) {
467
+ return session.runDetailed(tool.toDispatch(args));
253
468
  }
254
469
  //# sourceMappingURL=tools.js.map