tabwright 1.0.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 (599) hide show
  1. package/LICENSE +21 -0
  2. package/bin.js +4 -0
  3. package/dist/a11y-client.js +204 -0
  4. package/dist/agent-skills/tabwright/SKILL.md +119 -0
  5. package/dist/aria-snapshot.d.ts +262 -0
  6. package/dist/aria-snapshot.d.ts.map +1 -0
  7. package/dist/aria-snapshot.js +1256 -0
  8. package/dist/aria-snapshot.js.map +1 -0
  9. package/dist/aria-snapshot.test.d.ts +2 -0
  10. package/dist/aria-snapshot.test.d.ts.map +1 -0
  11. package/dist/aria-snapshot.test.js +270 -0
  12. package/dist/aria-snapshot.test.js.map +1 -0
  13. package/dist/aria-snapshot.unit.test.d.ts +2 -0
  14. package/dist/aria-snapshot.unit.test.d.ts.map +1 -0
  15. package/dist/aria-snapshot.unit.test.js +542 -0
  16. package/dist/aria-snapshot.unit.test.js.map +1 -0
  17. package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.d.ts +5 -0
  18. package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.d.ts.map +1 -0
  19. package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.js +5 -0
  20. package/dist/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.js.map +1 -0
  21. package/dist/bippy.js +894 -0
  22. package/dist/browser-config.d.ts +13 -0
  23. package/dist/browser-config.d.ts.map +1 -0
  24. package/dist/browser-config.js +126 -0
  25. package/dist/browser-config.js.map +1 -0
  26. package/dist/browser-install.d.ts +16 -0
  27. package/dist/browser-install.d.ts.map +1 -0
  28. package/dist/browser-install.js +238 -0
  29. package/dist/browser-install.js.map +1 -0
  30. package/dist/browser-launch.d.ts +17 -0
  31. package/dist/browser-launch.d.ts.map +1 -0
  32. package/dist/browser-launch.js +44 -0
  33. package/dist/browser-launch.js.map +1 -0
  34. package/dist/builtin-capabilities.d.ts +28 -0
  35. package/dist/builtin-capabilities.d.ts.map +1 -0
  36. package/dist/builtin-capabilities.js +2673 -0
  37. package/dist/builtin-capabilities.js.map +1 -0
  38. package/dist/capability-agent-skill.d.ts +60 -0
  39. package/dist/capability-agent-skill.d.ts.map +1 -0
  40. package/dist/capability-agent-skill.js +225 -0
  41. package/dist/capability-agent-skill.js.map +1 -0
  42. package/dist/capability-auth-state.d.ts +34 -0
  43. package/dist/capability-auth-state.d.ts.map +1 -0
  44. package/dist/capability-auth-state.js +157 -0
  45. package/dist/capability-auth-state.js.map +1 -0
  46. package/dist/capability-auth.d.ts +36 -0
  47. package/dist/capability-auth.d.ts.map +1 -0
  48. package/dist/capability-auth.js +198 -0
  49. package/dist/capability-auth.js.map +1 -0
  50. package/dist/capability-cli-confirmation.test.d.ts +2 -0
  51. package/dist/capability-cli-confirmation.test.d.ts.map +1 -0
  52. package/dist/capability-cli-confirmation.test.js +79 -0
  53. package/dist/capability-cli-confirmation.test.js.map +1 -0
  54. package/dist/capability-options.d.ts +20 -0
  55. package/dist/capability-options.d.ts.map +1 -0
  56. package/dist/capability-options.js +31 -0
  57. package/dist/capability-options.js.map +1 -0
  58. package/dist/capability-options.test.d.ts +2 -0
  59. package/dist/capability-options.test.d.ts.map +1 -0
  60. package/dist/capability-options.test.js +81 -0
  61. package/dist/capability-options.test.js.map +1 -0
  62. package/dist/capability-package.d.ts +27 -0
  63. package/dist/capability-package.d.ts.map +1 -0
  64. package/dist/capability-package.js +444 -0
  65. package/dist/capability-package.js.map +1 -0
  66. package/dist/capability-package.test.d.ts +2 -0
  67. package/dist/capability-package.test.d.ts.map +1 -0
  68. package/dist/capability-package.test.js +179 -0
  69. package/dist/capability-package.test.js.map +1 -0
  70. package/dist/capability-registry.d.ts +262 -0
  71. package/dist/capability-registry.d.ts.map +1 -0
  72. package/dist/capability-registry.js +931 -0
  73. package/dist/capability-registry.js.map +1 -0
  74. package/dist/capability-registry.test.d.ts +2 -0
  75. package/dist/capability-registry.test.d.ts.map +1 -0
  76. package/dist/capability-registry.test.js +1683 -0
  77. package/dist/capability-registry.test.js.map +1 -0
  78. package/dist/capability-runner.d.ts +99 -0
  79. package/dist/capability-runner.d.ts.map +1 -0
  80. package/dist/capability-runner.js +608 -0
  81. package/dist/capability-runner.js.map +1 -0
  82. package/dist/capability-search-relevance.test.d.ts +2 -0
  83. package/dist/capability-search-relevance.test.d.ts.map +1 -0
  84. package/dist/capability-search-relevance.test.js +67 -0
  85. package/dist/capability-search-relevance.test.js.map +1 -0
  86. package/dist/capability-studio.d.ts +11 -0
  87. package/dist/capability-studio.d.ts.map +1 -0
  88. package/dist/capability-studio.js +593 -0
  89. package/dist/capability-studio.js.map +1 -0
  90. package/dist/capability-studio.test.d.ts +2 -0
  91. package/dist/capability-studio.test.d.ts.map +1 -0
  92. package/dist/capability-studio.test.js +41 -0
  93. package/dist/capability-studio.test.js.map +1 -0
  94. package/dist/cdp-log.d.ts +19 -0
  95. package/dist/cdp-log.d.ts.map +1 -0
  96. package/dist/cdp-log.js +83 -0
  97. package/dist/cdp-log.js.map +1 -0
  98. package/dist/cdp-log.test.d.ts +2 -0
  99. package/dist/cdp-log.test.d.ts.map +1 -0
  100. package/dist/cdp-log.test.js +109 -0
  101. package/dist/cdp-log.test.js.map +1 -0
  102. package/dist/cdp-relay.d.ts +19 -0
  103. package/dist/cdp-relay.d.ts.map +1 -0
  104. package/dist/cdp-relay.js +2375 -0
  105. package/dist/cdp-relay.js.map +1 -0
  106. package/dist/cdp-session.d.ts +37 -0
  107. package/dist/cdp-session.d.ts.map +1 -0
  108. package/dist/cdp-session.js +36 -0
  109. package/dist/cdp-session.js.map +1 -0
  110. package/dist/cdp-types.d.ts +65 -0
  111. package/dist/cdp-types.d.ts.map +1 -0
  112. package/dist/cdp-types.js +91 -0
  113. package/dist/cdp-types.js.map +1 -0
  114. package/dist/channel-owner-inspect.test.d.ts +2 -0
  115. package/dist/channel-owner-inspect.test.d.ts.map +1 -0
  116. package/dist/channel-owner-inspect.test.js +75 -0
  117. package/dist/channel-owner-inspect.test.js.map +1 -0
  118. package/dist/chrome-discovery.d.ts +65 -0
  119. package/dist/chrome-discovery.d.ts.map +1 -0
  120. package/dist/chrome-discovery.js +173 -0
  121. package/dist/chrome-discovery.js.map +1 -0
  122. package/dist/chrome-discovery.test.d.ts +2 -0
  123. package/dist/chrome-discovery.test.d.ts.map +1 -0
  124. package/dist/chrome-discovery.test.js +41 -0
  125. package/dist/chrome-discovery.test.js.map +1 -0
  126. package/dist/clean-html.d.ts +11 -0
  127. package/dist/clean-html.d.ts.map +1 -0
  128. package/dist/clean-html.js +106 -0
  129. package/dist/clean-html.js.map +1 -0
  130. package/dist/cli-help.test.d.ts +2 -0
  131. package/dist/cli-help.test.d.ts.map +1 -0
  132. package/dist/cli-help.test.js +98 -0
  133. package/dist/cli-help.test.js.map +1 -0
  134. package/dist/cli.d.ts +3 -0
  135. package/dist/cli.d.ts.map +1 -0
  136. package/dist/cli.js +2709 -0
  137. package/dist/cli.js.map +1 -0
  138. package/dist/cloud-client.d.ts +56 -0
  139. package/dist/cloud-client.d.ts.map +1 -0
  140. package/dist/cloud-client.js +127 -0
  141. package/dist/cloud-client.js.map +1 -0
  142. package/dist/create-logger.d.ts +9 -0
  143. package/dist/create-logger.d.ts.map +1 -0
  144. package/dist/create-logger.js +27 -0
  145. package/dist/create-logger.js.map +1 -0
  146. package/dist/debugger-api.md +458 -0
  147. package/dist/debugger-examples-types.d.ts +24 -0
  148. package/dist/debugger-examples-types.d.ts.map +1 -0
  149. package/dist/debugger-examples-types.js +2 -0
  150. package/dist/debugger-examples-types.js.map +1 -0
  151. package/dist/debugger-examples.d.ts +6 -0
  152. package/dist/debugger-examples.d.ts.map +1 -0
  153. package/dist/debugger-examples.js +53 -0
  154. package/dist/debugger-examples.js.map +1 -0
  155. package/dist/debugger.d.ts +381 -0
  156. package/dist/debugger.d.ts.map +1 -0
  157. package/dist/debugger.js +630 -0
  158. package/dist/debugger.js.map +1 -0
  159. package/dist/diff-utils.d.ts +20 -0
  160. package/dist/diff-utils.d.ts.map +1 -0
  161. package/dist/diff-utils.js +50 -0
  162. package/dist/diff-utils.js.map +1 -0
  163. package/dist/diff-utils.test.d.ts +2 -0
  164. package/dist/diff-utils.test.d.ts.map +1 -0
  165. package/dist/diff-utils.test.js +137 -0
  166. package/dist/diff-utils.test.js.map +1 -0
  167. package/dist/doctor.d.ts +44 -0
  168. package/dist/doctor.d.ts.map +1 -0
  169. package/dist/doctor.js +246 -0
  170. package/dist/doctor.js.map +1 -0
  171. package/dist/doctor.test.d.ts +2 -0
  172. package/dist/doctor.test.d.ts.map +1 -0
  173. package/dist/doctor.test.js +208 -0
  174. package/dist/doctor.test.js.map +1 -0
  175. package/dist/editor-api.md +374 -0
  176. package/dist/editor-examples.d.ts +11 -0
  177. package/dist/editor-examples.d.ts.map +1 -0
  178. package/dist/editor-examples.js +124 -0
  179. package/dist/editor-examples.js.map +1 -0
  180. package/dist/editor.d.ts +203 -0
  181. package/dist/editor.d.ts.map +1 -0
  182. package/dist/editor.js +363 -0
  183. package/dist/editor.js.map +1 -0
  184. package/dist/env-compat.d.ts +2 -0
  185. package/dist/env-compat.d.ts.map +1 -0
  186. package/dist/env-compat.js +27 -0
  187. package/dist/env-compat.js.map +1 -0
  188. package/dist/executor.d.ts +229 -0
  189. package/dist/executor.d.ts.map +1 -0
  190. package/dist/executor.js +1465 -0
  191. package/dist/executor.js.map +1 -0
  192. package/dist/executor.unit.test.d.ts +2 -0
  193. package/dist/executor.unit.test.d.ts.map +1 -0
  194. package/dist/executor.unit.test.js +143 -0
  195. package/dist/executor.unit.test.js.map +1 -0
  196. package/dist/extension/_locales/en/messages.json +278 -0
  197. package/dist/extension/_locales/zh_CN/messages.json +256 -0
  198. package/dist/extension/assets/options-CYH96Lcx.css +1349 -0
  199. package/dist/extension/background.js +3994 -0
  200. package/dist/extension/icons/SolarCursorSquareBold.png +0 -0
  201. package/dist/extension/icons/icon-black-128.png +0 -0
  202. package/dist/extension/icons/icon-black-16.png +0 -0
  203. package/dist/extension/icons/icon-black-32.png +0 -0
  204. package/dist/extension/icons/icon-black-48.png +0 -0
  205. package/dist/extension/icons/icon-gray-128.png +0 -0
  206. package/dist/extension/icons/icon-gray-16.png +0 -0
  207. package/dist/extension/icons/icon-gray-32.png +0 -0
  208. package/dist/extension/icons/icon-gray-48.png +0 -0
  209. package/dist/extension/icons/icon-green-128.png +0 -0
  210. package/dist/extension/icons/icon-green-16.png +0 -0
  211. package/dist/extension/icons/icon-green-32.png +0 -0
  212. package/dist/extension/icons/icon-green-48.png +0 -0
  213. package/dist/extension/icons/tabwright-icon-black.png +0 -0
  214. package/dist/extension/icons/tabwright-icon-gray-disabled.png +0 -0
  215. package/dist/extension/icons/tabwright-icon-gray-disabled.svg +1 -0
  216. package/dist/extension/manifest.json +54 -0
  217. package/dist/extension/options.js +16145 -0
  218. package/dist/extension/rrweb-recorder.js +12952 -0
  219. package/dist/extension/src/options.html +165 -0
  220. package/dist/extension/src/prism-bash.min.js +1 -0
  221. package/dist/extension/src/prism.min.js +1 -0
  222. package/dist/extension/src/welcome.html +396 -0
  223. package/dist/extension-connection.test.d.ts +2 -0
  224. package/dist/extension-connection.test.d.ts.map +1 -0
  225. package/dist/extension-connection.test.js +708 -0
  226. package/dist/extension-connection.test.js.map +1 -0
  227. package/dist/extension-icon-warning.test.d.ts +2 -0
  228. package/dist/extension-icon-warning.test.d.ts.map +1 -0
  229. package/dist/extension-icon-warning.test.js +33 -0
  230. package/dist/extension-icon-warning.test.js.map +1 -0
  231. package/dist/ghost-browser.d.ts +62 -0
  232. package/dist/ghost-browser.d.ts.map +1 -0
  233. package/dist/ghost-browser.js +107 -0
  234. package/dist/ghost-browser.js.map +1 -0
  235. package/dist/ghost-cursor-client.js +374 -0
  236. package/dist/ghost-cursor-controller.d.ts +46 -0
  237. package/dist/ghost-cursor-controller.d.ts.map +1 -0
  238. package/dist/ghost-cursor-controller.js +98 -0
  239. package/dist/ghost-cursor-controller.js.map +1 -0
  240. package/dist/ghost-cursor.d.ts +27 -0
  241. package/dist/ghost-cursor.d.ts.map +1 -0
  242. package/dist/ghost-cursor.js +79 -0
  243. package/dist/ghost-cursor.js.map +1 -0
  244. package/dist/htmlrewrite.d.ts +8 -0
  245. package/dist/htmlrewrite.d.ts.map +1 -0
  246. package/dist/htmlrewrite.js +339 -0
  247. package/dist/htmlrewrite.js.map +1 -0
  248. package/dist/htmlrewrite.test.d.ts +2 -0
  249. package/dist/htmlrewrite.test.d.ts.map +1 -0
  250. package/dist/htmlrewrite.test.js +7975 -0
  251. package/dist/htmlrewrite.test.js.map +1 -0
  252. package/dist/index.d.ts +20 -0
  253. package/dist/index.d.ts.map +1 -0
  254. package/dist/index.js +11 -0
  255. package/dist/index.js.map +1 -0
  256. package/dist/kill-port.d.ts +29 -0
  257. package/dist/kill-port.d.ts.map +1 -0
  258. package/dist/kill-port.js +190 -0
  259. package/dist/kill-port.js.map +1 -0
  260. package/dist/kitty-graphics.d.ts +22 -0
  261. package/dist/kitty-graphics.d.ts.map +1 -0
  262. package/dist/kitty-graphics.js +71 -0
  263. package/dist/kitty-graphics.js.map +1 -0
  264. package/dist/kitty-graphics.test.d.ts +2 -0
  265. package/dist/kitty-graphics.test.d.ts.map +1 -0
  266. package/dist/kitty-graphics.test.js +125 -0
  267. package/dist/kitty-graphics.test.js.map +1 -0
  268. package/dist/locator-selector.test.d.ts +2 -0
  269. package/dist/locator-selector.test.d.ts.map +1 -0
  270. package/dist/locator-selector.test.js +98 -0
  271. package/dist/locator-selector.test.js.map +1 -0
  272. package/dist/mcp-client.d.ts +20 -0
  273. package/dist/mcp-client.d.ts.map +1 -0
  274. package/dist/mcp-client.js +56 -0
  275. package/dist/mcp-client.js.map +1 -0
  276. package/dist/mcp.d.ts +6 -0
  277. package/dist/mcp.d.ts.map +1 -0
  278. package/dist/mcp.js +511 -0
  279. package/dist/mcp.js.map +1 -0
  280. package/dist/on-mouse-action.test.d.ts +2 -0
  281. package/dist/on-mouse-action.test.d.ts.map +1 -0
  282. package/dist/on-mouse-action.test.js +180 -0
  283. package/dist/on-mouse-action.test.js.map +1 -0
  284. package/dist/options-page.test.d.ts +2 -0
  285. package/dist/options-page.test.d.ts.map +1 -0
  286. package/dist/options-page.test.js +773 -0
  287. package/dist/options-page.test.js.map +1 -0
  288. package/dist/package-paths.d.ts +4 -0
  289. package/dist/package-paths.d.ts.map +1 -0
  290. package/dist/package-paths.js +30 -0
  291. package/dist/package-paths.js.map +1 -0
  292. package/dist/page-markdown.d.ts +40 -0
  293. package/dist/page-markdown.d.ts.map +1 -0
  294. package/dist/page-markdown.js +181 -0
  295. package/dist/page-markdown.js.map +1 -0
  296. package/dist/performance-examples.d.ts +5 -0
  297. package/dist/performance-examples.d.ts.map +1 -0
  298. package/dist/performance-examples.js +112 -0
  299. package/dist/performance-examples.js.map +1 -0
  300. package/dist/performance-profiling.md +417 -0
  301. package/dist/playwright-import.d.ts +19 -0
  302. package/dist/playwright-import.d.ts.map +1 -0
  303. package/dist/playwright-import.js +40 -0
  304. package/dist/playwright-import.js.map +1 -0
  305. package/dist/popup-relocation.test.d.ts +7 -0
  306. package/dist/popup-relocation.test.d.ts.map +1 -0
  307. package/dist/popup-relocation.test.js +139 -0
  308. package/dist/popup-relocation.test.js.map +1 -0
  309. package/dist/product-paths.d.ts +9 -0
  310. package/dist/product-paths.d.ts.map +1 -0
  311. package/dist/product-paths.js +24 -0
  312. package/dist/product-paths.js.map +1 -0
  313. package/dist/product-paths.test.d.ts +2 -0
  314. package/dist/product-paths.test.d.ts.map +1 -0
  315. package/dist/product-paths.test.js +39 -0
  316. package/dist/product-paths.test.js.map +1 -0
  317. package/dist/prompt.md +1004 -0
  318. package/dist/protocol.d.ts +248 -0
  319. package/dist/protocol.d.ts.map +1 -0
  320. package/dist/protocol.js +59 -0
  321. package/dist/protocol.js.map +1 -0
  322. package/dist/protocol.test.d.ts +2 -0
  323. package/dist/protocol.test.d.ts.map +1 -0
  324. package/dist/protocol.test.js +31 -0
  325. package/dist/protocol.test.js.map +1 -0
  326. package/dist/react-source.d.ts +57 -0
  327. package/dist/react-source.d.ts.map +1 -0
  328. package/dist/react-source.js +254 -0
  329. package/dist/react-source.js.map +1 -0
  330. package/dist/readability.js +1674 -0
  331. package/dist/relay-client.d.ts +104 -0
  332. package/dist/relay-client.d.ts.map +1 -0
  333. package/dist/relay-client.js +426 -0
  334. package/dist/relay-client.js.map +1 -0
  335. package/dist/relay-client.test.d.ts +2 -0
  336. package/dist/relay-client.test.d.ts.map +1 -0
  337. package/dist/relay-client.test.js +305 -0
  338. package/dist/relay-client.test.js.map +1 -0
  339. package/dist/relay-core.test.d.ts +2 -0
  340. package/dist/relay-core.test.d.ts.map +1 -0
  341. package/dist/relay-core.test.js +1427 -0
  342. package/dist/relay-core.test.js.map +1 -0
  343. package/dist/relay-navigation.test.d.ts +2 -0
  344. package/dist/relay-navigation.test.d.ts.map +1 -0
  345. package/dist/relay-navigation.test.js +661 -0
  346. package/dist/relay-navigation.test.js.map +1 -0
  347. package/dist/relay-session.test.d.ts +2 -0
  348. package/dist/relay-session.test.d.ts.map +1 -0
  349. package/dist/relay-session.test.js +1121 -0
  350. package/dist/relay-session.test.js.map +1 -0
  351. package/dist/relay-state.d.ts +178 -0
  352. package/dist/relay-state.d.ts.map +1 -0
  353. package/dist/relay-state.js +346 -0
  354. package/dist/relay-state.js.map +1 -0
  355. package/dist/relay-state.test.d.ts +2 -0
  356. package/dist/relay-state.test.d.ts.map +1 -0
  357. package/dist/relay-state.test.js +594 -0
  358. package/dist/relay-state.test.js.map +1 -0
  359. package/dist/replay-ai-index.d.ts +99 -0
  360. package/dist/replay-ai-index.d.ts.map +1 -0
  361. package/dist/replay-ai-index.js +677 -0
  362. package/dist/replay-ai-index.js.map +1 -0
  363. package/dist/replay-ai-index.test.d.ts +2 -0
  364. package/dist/replay-ai-index.test.d.ts.map +1 -0
  365. package/dist/replay-ai-index.test.js +255 -0
  366. package/dist/replay-ai-index.test.js.map +1 -0
  367. package/dist/replay-cli.test.d.ts +2 -0
  368. package/dist/replay-cli.test.d.ts.map +1 -0
  369. package/dist/replay-cli.test.js +275 -0
  370. package/dist/replay-cli.test.js.map +1 -0
  371. package/dist/replay-eval.d.ts +44 -0
  372. package/dist/replay-eval.d.ts.map +1 -0
  373. package/dist/replay-eval.js +902 -0
  374. package/dist/replay-eval.js.map +1 -0
  375. package/dist/replay-handoff.d.ts +29 -0
  376. package/dist/replay-handoff.d.ts.map +1 -0
  377. package/dist/replay-handoff.js +83 -0
  378. package/dist/replay-handoff.js.map +1 -0
  379. package/dist/replay-handoff.test.d.ts +2 -0
  380. package/dist/replay-handoff.test.d.ts.map +1 -0
  381. package/dist/replay-handoff.test.js +123 -0
  382. package/dist/replay-handoff.test.js.map +1 -0
  383. package/dist/replay-workflow-compiler.d.ts +44 -0
  384. package/dist/replay-workflow-compiler.d.ts.map +1 -0
  385. package/dist/replay-workflow-compiler.js +601 -0
  386. package/dist/replay-workflow-compiler.js.map +1 -0
  387. package/dist/replay-workflow-compiler.test.d.ts +2 -0
  388. package/dist/replay-workflow-compiler.test.d.ts.map +1 -0
  389. package/dist/replay-workflow-compiler.test.js +223 -0
  390. package/dist/replay-workflow-compiler.test.js.map +1 -0
  391. package/dist/rrweb-recording-relay.d.ts +56 -0
  392. package/dist/rrweb-recording-relay.d.ts.map +1 -0
  393. package/dist/rrweb-recording-relay.js +299 -0
  394. package/dist/rrweb-recording-relay.js.map +1 -0
  395. package/dist/rrweb-recording-relay.test.d.ts +2 -0
  396. package/dist/rrweb-recording-relay.test.d.ts.map +1 -0
  397. package/dist/rrweb-recording-relay.test.js +85 -0
  398. package/dist/rrweb-recording-relay.test.js.map +1 -0
  399. package/dist/rrweb-recording.d.ts +104 -0
  400. package/dist/rrweb-recording.d.ts.map +1 -0
  401. package/dist/rrweb-recording.js +180 -0
  402. package/dist/rrweb-recording.js.map +1 -0
  403. package/dist/scoped-fs.d.ts +95 -0
  404. package/dist/scoped-fs.d.ts.map +1 -0
  405. package/dist/scoped-fs.js +358 -0
  406. package/dist/scoped-fs.js.map +1 -0
  407. package/dist/scoped-fs.test.d.ts +5 -0
  408. package/dist/scoped-fs.test.d.ts.map +1 -0
  409. package/dist/scoped-fs.test.js +73 -0
  410. package/dist/scoped-fs.test.js.map +1 -0
  411. package/dist/selector-generator.js +7378 -0
  412. package/dist/skill-stub.test.d.ts +2 -0
  413. package/dist/skill-stub.test.d.ts.map +1 -0
  414. package/dist/skill-stub.test.js +22 -0
  415. package/dist/skill-stub.test.js.map +1 -0
  416. package/dist/snapshot-tools.test.d.ts +2 -0
  417. package/dist/snapshot-tools.test.d.ts.map +1 -0
  418. package/dist/snapshot-tools.test.js +856 -0
  419. package/dist/snapshot-tools.test.js.map +1 -0
  420. package/dist/start-relay-server.d.ts +6 -0
  421. package/dist/start-relay-server.d.ts.map +1 -0
  422. package/dist/start-relay-server.js +57 -0
  423. package/dist/start-relay-server.js.map +1 -0
  424. package/dist/styles-api.md +124 -0
  425. package/dist/styles-examples.d.ts +8 -0
  426. package/dist/styles-examples.d.ts.map +1 -0
  427. package/dist/styles-examples.js +64 -0
  428. package/dist/styles-examples.js.map +1 -0
  429. package/dist/styles.d.ts +27 -0
  430. package/dist/styles.d.ts.map +1 -0
  431. package/dist/styles.js +231 -0
  432. package/dist/styles.js.map +1 -0
  433. package/dist/tabwright-agent-skill.d.ts +30 -0
  434. package/dist/tabwright-agent-skill.d.ts.map +1 -0
  435. package/dist/tabwright-agent-skill.js +74 -0
  436. package/dist/tabwright-agent-skill.js.map +1 -0
  437. package/dist/tabwright-agent-skill.test.d.ts +2 -0
  438. package/dist/tabwright-agent-skill.test.d.ts.map +1 -0
  439. package/dist/tabwright-agent-skill.test.js +51 -0
  440. package/dist/tabwright-agent-skill.test.js.map +1 -0
  441. package/dist/test-declarations.d.ts +13 -0
  442. package/dist/test-declarations.d.ts.map +1 -0
  443. package/dist/test-declarations.js +2 -0
  444. package/dist/test-declarations.js.map +1 -0
  445. package/dist/test-utils.d.ts +69 -0
  446. package/dist/test-utils.d.ts.map +1 -0
  447. package/dist/test-utils.js +448 -0
  448. package/dist/test-utils.js.map +1 -0
  449. package/dist/test-utils.test.d.ts +2 -0
  450. package/dist/test-utils.test.d.ts.map +1 -0
  451. package/dist/test-utils.test.js +55 -0
  452. package/dist/test-utils.test.js.map +1 -0
  453. package/dist/utils.d.ts +26 -0
  454. package/dist/utils.d.ts.map +1 -0
  455. package/dist/utils.js +60 -0
  456. package/dist/utils.js.map +1 -0
  457. package/dist/wait-for-page-load.d.ts +16 -0
  458. package/dist/wait-for-page-load.d.ts.map +1 -0
  459. package/dist/wait-for-page-load.js +146 -0
  460. package/dist/wait-for-page-load.js.map +1 -0
  461. package/dist/workflow-capability.d.ts +72 -0
  462. package/dist/workflow-capability.d.ts.map +1 -0
  463. package/dist/workflow-capability.js +316 -0
  464. package/dist/workflow-capability.js.map +1 -0
  465. package/dist/workflow-script-flow.test.d.ts +2 -0
  466. package/dist/workflow-script-flow.test.d.ts.map +1 -0
  467. package/dist/workflow-script-flow.test.js +285 -0
  468. package/dist/workflow-script-flow.test.js.map +1 -0
  469. package/package.json +80 -0
  470. package/src/__snapshots__/x.com.processed.html +1003 -0
  471. package/src/__snapshots__/x.com.processed.withStyles.html +2720 -0
  472. package/src/a11y-client.ts +241 -0
  473. package/src/agent-skills/conan-config/SKILL.md +141 -0
  474. package/src/agent-skills/conan-config/agents/openai.yaml +4 -0
  475. package/src/aria-snapshot.test.ts +311 -0
  476. package/src/aria-snapshot.ts +1681 -0
  477. package/src/aria-snapshot.unit.test.ts +615 -0
  478. package/src/aria-snapshots/github-interactive.txt +193 -0
  479. package/src/aria-snapshots/github-raw.txt +309 -0
  480. package/src/aria-snapshots/hackernews-interactive.txt +477 -0
  481. package/src/aria-snapshots/hackernews-raw.txt +428 -0
  482. package/src/aria-snapshots/prosemirror-interactive.txt +66 -0
  483. package/src/aria-snapshots/prosemirror-raw.txt +145 -0
  484. package/src/assets/aria-labels-hacker-news.png +0 -0
  485. package/src/assets/aria-labels-old-reddit.png +0 -0
  486. package/src/assets/cursors/screen-studio/pointer-macos-tahoe-data-url.ts +5 -0
  487. package/src/assets/cursors/screen-studio/pointer-macos-tahoe.svg +18 -0
  488. package/src/assets/x.com.html +32946 -0
  489. package/src/browser-config.ts +179 -0
  490. package/src/browser-install.ts +284 -0
  491. package/src/browser-launch.ts +75 -0
  492. package/src/builtin-capabilities.ts +2765 -0
  493. package/src/capability-agent-skill.ts +305 -0
  494. package/src/capability-auth-state.ts +197 -0
  495. package/src/capability-auth.ts +249 -0
  496. package/src/capability-cli-confirmation.test.ts +90 -0
  497. package/src/capability-options.test.ts +89 -0
  498. package/src/capability-options.ts +57 -0
  499. package/src/capability-package.test.ts +204 -0
  500. package/src/capability-package.ts +555 -0
  501. package/src/capability-registry.test.ts +1905 -0
  502. package/src/capability-registry.ts +1234 -0
  503. package/src/capability-runner.ts +801 -0
  504. package/src/capability-search-relevance.test.ts +73 -0
  505. package/src/capability-studio.test.ts +45 -0
  506. package/src/capability-studio.ts +641 -0
  507. package/src/cdp-log.test.ts +131 -0
  508. package/src/cdp-log.ts +112 -0
  509. package/src/cdp-relay.ts +2944 -0
  510. package/src/cdp-session.ts +77 -0
  511. package/src/cdp-types.ts +158 -0
  512. package/src/channel-owner-inspect.test.ts +94 -0
  513. package/src/chrome-discovery.test.ts +50 -0
  514. package/src/chrome-discovery.ts +220 -0
  515. package/src/clean-html.ts +141 -0
  516. package/src/cli-help.test.ts +123 -0
  517. package/src/cli.ts +3181 -0
  518. package/src/cloud-client.ts +179 -0
  519. package/src/create-logger.ts +38 -0
  520. package/src/debugger-examples-types.ts +16 -0
  521. package/src/debugger-examples.ts +66 -0
  522. package/src/debugger.ts +708 -0
  523. package/src/diff-utils.test.ts +152 -0
  524. package/src/diff-utils.ts +70 -0
  525. package/src/doctor.test.ts +244 -0
  526. package/src/doctor.ts +321 -0
  527. package/src/editor-examples.ts +158 -0
  528. package/src/editor.ts +426 -0
  529. package/src/env-compat.ts +28 -0
  530. package/src/executor.ts +1824 -0
  531. package/src/executor.unit.test.ts +168 -0
  532. package/src/extension-connection.test.ts +855 -0
  533. package/src/extension-icon-warning.test.ts +45 -0
  534. package/src/ghost-browser.ts +149 -0
  535. package/src/ghost-cursor-client.ts +494 -0
  536. package/src/ghost-cursor-controller.ts +129 -0
  537. package/src/ghost-cursor.ts +123 -0
  538. package/src/htmlrewrite.test.ts +8008 -0
  539. package/src/htmlrewrite.ts +390 -0
  540. package/src/index.ts +53 -0
  541. package/src/kill-port.ts +219 -0
  542. package/src/kitty-graphics.test.ts +139 -0
  543. package/src/kitty-graphics.ts +79 -0
  544. package/src/locator-selector.test.ts +119 -0
  545. package/src/mcp-client.ts +78 -0
  546. package/src/mcp.ts +629 -0
  547. package/src/on-mouse-action.test.ts +226 -0
  548. package/src/options-page.test.ts +839 -0
  549. package/src/package-paths.ts +38 -0
  550. package/src/page-markdown.ts +240 -0
  551. package/src/performance-examples.ts +186 -0
  552. package/src/playwright-import.ts +59 -0
  553. package/src/popup-relocation.test.ts +163 -0
  554. package/src/product-paths.test.ts +47 -0
  555. package/src/product-paths.ts +28 -0
  556. package/src/protocol.test.ts +52 -0
  557. package/src/protocol.ts +351 -0
  558. package/src/react-source.ts +379 -0
  559. package/src/relay-client.test.ts +375 -0
  560. package/src/relay-client.ts +572 -0
  561. package/src/relay-core.test.ts +1603 -0
  562. package/src/relay-navigation.test.ts +790 -0
  563. package/src/relay-session.test.ts +1375 -0
  564. package/src/relay-state.test.ts +729 -0
  565. package/src/relay-state.ts +561 -0
  566. package/src/replay-ai-index.test.ts +278 -0
  567. package/src/replay-ai-index.ts +872 -0
  568. package/src/replay-cli.test.ts +325 -0
  569. package/src/replay-eval.ts +1039 -0
  570. package/src/replay-handoff.test.ts +163 -0
  571. package/src/replay-handoff.ts +109 -0
  572. package/src/replay-workflow-compiler.test.ts +250 -0
  573. package/src/replay-workflow-compiler.ts +700 -0
  574. package/src/resource.md +409 -0
  575. package/src/rrweb-recording-relay.test.ts +99 -0
  576. package/src/rrweb-recording-relay.ts +400 -0
  577. package/src/rrweb-recording.ts +322 -0
  578. package/src/scoped-fs.test.ts +80 -0
  579. package/src/scoped-fs.ts +420 -0
  580. package/src/skill-stub.test.ts +24 -0
  581. package/src/skill.md +1413 -0
  582. package/src/snapshot-tools.test.ts +1017 -0
  583. package/src/snapshots/hacker-news-accessibility-full.md +81 -0
  584. package/src/snapshots/hacker-news-accessibility-interactive.md +43 -0
  585. package/src/snapshots/page-markdown-output.txt +16 -0
  586. package/src/snapshots/shadcn-ui-accessibility-full.md +201 -0
  587. package/src/snapshots/shadcn-ui-accessibility-interactive.md +109 -0
  588. package/src/start-relay-server.ts +69 -0
  589. package/src/styles-examples.ts +84 -0
  590. package/src/styles.ts +343 -0
  591. package/src/tabwright-agent-skill.test.ts +61 -0
  592. package/src/tabwright-agent-skill.ts +105 -0
  593. package/src/test-declarations.ts +13 -0
  594. package/src/test-utils.test.ts +70 -0
  595. package/src/test-utils.ts +566 -0
  596. package/src/utils.ts +78 -0
  597. package/src/wait-for-page-load.ts +198 -0
  598. package/src/workflow-capability.ts +417 -0
  599. package/src/workflow-script-flow.test.ts +388 -0
@@ -0,0 +1,1234 @@
1
+ import crypto from 'node:crypto'
2
+ import fs from 'node:fs'
3
+ import path from 'node:path'
4
+ import { z } from 'zod'
5
+ import { getTabwrightProjectDataDir, getTabwrightUserDataDir } from './product-paths.js'
6
+
7
+ export type CapabilityStatus = 'draft' | 'trusted' | 'disabled'
8
+ export type CapabilityLocation = 'project' | 'user'
9
+ export type CapabilityRuntime = 'browser' | 'node'
10
+ export type CapabilitySideEffect = 'read' | 'write' | 'dangerous'
11
+ export type CapabilityAuthType = 'none' | 'cookie' | 'token' | 'custom'
12
+ export type CapabilityAuthRefresh = 'none' | 'manual' | 'from-browser'
13
+ export type CapabilityRoutingHint = 'search-first' | 'exact-match-direct-run'
14
+
15
+ export interface CapabilityAuthConfig {
16
+ type: CapabilityAuthType
17
+ refresh: CapabilityAuthRefresh
18
+ secretKey?: string
19
+ browserUrls: string[]
20
+ requiredCookieNames: string[]
21
+ failureSignals: string[]
22
+ }
23
+
24
+ export interface CapabilityExample {
25
+ description?: string
26
+ input?: unknown
27
+ output?: unknown
28
+ }
29
+
30
+ export interface CapabilityOperation {
31
+ title: string
32
+ description: string
33
+ match: string[]
34
+ routingHint: CapabilityRoutingHint
35
+ inputSchema: Record<string, unknown>
36
+ outputSchema: Record<string, unknown>
37
+ permissions?: string[]
38
+ sideEffect: CapabilitySideEffect
39
+ requiresConfirmation: boolean
40
+ }
41
+
42
+ export interface ResolvedCapabilityOperation extends CapabilityOperation {
43
+ id?: string
44
+ confirmationToken: string
45
+ }
46
+
47
+ export interface CapabilityManifest {
48
+ schemaVersion: 1
49
+ id: string
50
+ title: string
51
+ description: string
52
+ whenToUse: string[]
53
+ whenNotToUse: string[]
54
+ tags: string[]
55
+ match: string[]
56
+ // Agents may skip search/describe only when this is exact-match-direct-run and autonomy is allowed; a URL alone is not enough.
57
+ routingHint: CapabilityRoutingHint
58
+ inputSchema: Record<string, unknown>
59
+ outputSchema: Record<string, unknown>
60
+ permissions: string[]
61
+ sideEffect: CapabilitySideEffect
62
+ requiresConfirmation: boolean
63
+ operations: Record<string, CapabilityOperation>
64
+ auth: CapabilityAuthConfig
65
+ examples: CapabilityExample[]
66
+ entry: string
67
+ runtime: CapabilityRuntime
68
+ status: CapabilityStatus
69
+ createdBy: 'user' | 'ai'
70
+ createdAt?: string
71
+ updatedAt?: string
72
+ }
73
+
74
+ export interface CapabilityRecord {
75
+ manifest: CapabilityManifest
76
+ dir: string
77
+ manifestPath: string
78
+ scriptPath: string
79
+ location: CapabilityLocation
80
+ }
81
+
82
+ export interface CapabilityRunRecord {
83
+ id: string
84
+ operation?: string
85
+ status: 'success' | 'error'
86
+ url?: string
87
+ durationMs: number
88
+ inputHash: string
89
+ error?: string
90
+ contract?: CapabilityRunContract
91
+ createdAt: string
92
+ }
93
+
94
+ export type CapabilityContractCheckStatus = 'passed' | 'failed' | 'not-applicable' | 'unknown'
95
+
96
+ export interface CapabilityContractFailure {
97
+ kind: 'output-schema' | 'undeclared-host'
98
+ message: string
99
+ }
100
+
101
+ export interface CapabilityRunContract {
102
+ schemaVersion: 1
103
+ fingerprint: string
104
+ status: 'passed' | 'failed' | 'unknown'
105
+ failures: CapabilityContractFailure[]
106
+ output: {
107
+ status: CapabilityContractCheckStatus
108
+ errors: string[]
109
+ }
110
+ network: {
111
+ status: CapabilityContractCheckStatus
112
+ observedHosts: string[]
113
+ undeclaredHosts: string[]
114
+ }
115
+ trust: {
116
+ before: CapabilityStatus
117
+ after: CapabilityStatus
118
+ downgraded: boolean
119
+ }
120
+ }
121
+
122
+ export interface CapabilityContractHealth {
123
+ state: 'healthy' | 'drifted' | 'unknown'
124
+ checkedAt?: string
125
+ reasons: string[]
126
+ }
127
+
128
+ export interface CapabilityLifecycle {
129
+ stage: 'drafted' | 'validated' | 'trusted' | 'drifted' | 'disabled'
130
+ nextAction: 'validate' | 'trust' | 'run' | 'repair' | 'enable'
131
+ nextCommand: string
132
+ contractHealth: CapabilityContractHealth
133
+ }
134
+
135
+ export interface ValidationResult {
136
+ valid: boolean
137
+ errors: string[]
138
+ }
139
+
140
+ export interface CapabilitySearchResult {
141
+ capability: CapabilityRecord
142
+ score: number
143
+ reasons: string[]
144
+ }
145
+
146
+ interface ScoredCapabilitySearchResult extends CapabilitySearchResult {
147
+ matchedTokenCount: number
148
+ }
149
+
150
+ export interface CapabilityRouteResult {
151
+ capability: CapabilityRecord
152
+ operation?: string
153
+ input: Record<string, unknown>
154
+ command: string
155
+ shellCommand: string
156
+ commandWarning: string
157
+ executionHint: {
158
+ routeCanRunSandboxed: boolean
159
+ runRequiresEscalatedSandbox: boolean
160
+ commandMustStartWith: string
161
+ reason: string
162
+ }
163
+ reasons: string[]
164
+ matchedText: string
165
+ }
166
+
167
+ export type CapabilityManifestPatch = Partial<Omit<CapabilityManifest, 'schemaVersion' | 'id' | 'createdAt'>>
168
+
169
+ const CapabilityStatusSchema = z.enum(['draft', 'trusted', 'disabled'])
170
+ const CapabilityRuntimeSchema = z.enum(['browser', 'node'])
171
+ const CapabilitySideEffectSchema = z.enum(['read', 'write', 'dangerous'])
172
+ const CapabilityRoutingHintSchema = z.enum(['search-first', 'exact-match-direct-run'])
173
+ const CapabilityAuthConfigSchema = z
174
+ .object({
175
+ type: z.enum(['none', 'cookie', 'token', 'custom']).default('none'),
176
+ refresh: z.enum(['none', 'manual', 'from-browser']).default('none'),
177
+ secretKey: z.string().optional(),
178
+ browserUrls: z.array(z.string()).default([]),
179
+ requiredCookieNames: z.array(z.string()).default([]),
180
+ failureSignals: z.array(z.string()).default([]),
181
+ })
182
+ .default({
183
+ type: 'none',
184
+ refresh: 'none',
185
+ browserUrls: [],
186
+ requiredCookieNames: [],
187
+ failureSignals: [],
188
+ })
189
+ const CapabilityExampleSchema = z
190
+ .object({
191
+ description: z.string().optional(),
192
+ input: z.unknown().optional(),
193
+ output: z.unknown().optional(),
194
+ })
195
+ .passthrough()
196
+ const CapabilityOperationSchema = z
197
+ .object({
198
+ title: z.string().default(''),
199
+ description: z.string().default(''),
200
+ match: z.array(z.string()).default([]),
201
+ routingHint: CapabilityRoutingHintSchema.default('search-first'),
202
+ inputSchema: z.record(z.string(), z.unknown()).default({ type: 'object', properties: {} }),
203
+ outputSchema: z.record(z.string(), z.unknown()).default({ type: 'object', properties: {} }),
204
+ permissions: z.array(z.string()).optional(),
205
+ sideEffect: CapabilitySideEffectSchema.default('read'),
206
+ requiresConfirmation: z.boolean().default(false),
207
+ })
208
+ .passthrough()
209
+ const CapabilityRunRecordSchema = z.object({
210
+ id: z.string(),
211
+ operation: z.string().optional(),
212
+ status: z.enum(['success', 'error']),
213
+ url: z.string().optional(),
214
+ durationMs: z.number(),
215
+ inputHash: z.string(),
216
+ error: z.string().optional(),
217
+ contract: z
218
+ .object({
219
+ schemaVersion: z.literal(1),
220
+ fingerprint: z.string(),
221
+ status: z.enum(['passed', 'failed', 'unknown']),
222
+ failures: z.array(
223
+ z.object({
224
+ kind: z.enum(['output-schema', 'undeclared-host']),
225
+ message: z.string(),
226
+ }),
227
+ ),
228
+ output: z.object({
229
+ status: z.enum(['passed', 'failed', 'not-applicable', 'unknown']),
230
+ errors: z.array(z.string()),
231
+ }),
232
+ network: z.object({
233
+ status: z.enum(['passed', 'failed', 'not-applicable', 'unknown']),
234
+ observedHosts: z.array(z.string()),
235
+ undeclaredHosts: z.array(z.string()),
236
+ }),
237
+ trust: z.object({
238
+ before: CapabilityStatusSchema,
239
+ after: CapabilityStatusSchema,
240
+ downgraded: z.boolean(),
241
+ }),
242
+ })
243
+ .optional(),
244
+ createdAt: z.string(),
245
+ })
246
+
247
+ const CapabilityManifestSchema = z
248
+ .object({
249
+ schemaVersion: z.literal(1),
250
+ id: z.string().regex(/^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/),
251
+ title: z.string().min(1),
252
+ description: z.string().default(''),
253
+ whenToUse: z.array(z.string()).default([]),
254
+ whenNotToUse: z.array(z.string()).default([]),
255
+ tags: z.array(z.string()).default([]),
256
+ match: z.array(z.string()).default([]),
257
+ routingHint: CapabilityRoutingHintSchema.default('search-first'),
258
+ inputSchema: z.record(z.string(), z.unknown()).default({ type: 'object', properties: {} }),
259
+ outputSchema: z.record(z.string(), z.unknown()).default({ type: 'object', properties: {} }),
260
+ permissions: z.array(z.string()).default([]),
261
+ sideEffect: CapabilitySideEffectSchema.default('read'),
262
+ requiresConfirmation: z.boolean().default(false),
263
+ operations: z
264
+ .record(z.string().regex(/^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/), CapabilityOperationSchema)
265
+ .default({}),
266
+ auth: CapabilityAuthConfigSchema,
267
+ examples: z.array(CapabilityExampleSchema).default([]),
268
+ entry: z.string().default('script.js'),
269
+ runtime: CapabilityRuntimeSchema.default('browser'),
270
+ status: CapabilityStatusSchema.default('draft'),
271
+ createdBy: z.enum(['user', 'ai']).default('user'),
272
+ createdAt: z.string().optional(),
273
+ updatedAt: z.string().optional(),
274
+ })
275
+ .passthrough()
276
+
277
+ export function parseCapabilityManifest(value: unknown): CapabilityManifest {
278
+ return CapabilityManifestSchema.parse(value)
279
+ }
280
+
281
+ export function getUserCapabilitiesDir(): string {
282
+ return path.join(getTabwrightUserDataDir(), 'capabilities')
283
+ }
284
+
285
+ export function getProjectCapabilitiesDir(options: { cwd?: string } = {}): string {
286
+ return path.join(getTabwrightProjectDataDir({ cwd: options.cwd }), 'capabilities')
287
+ }
288
+
289
+ export function getCapabilityRoots(
290
+ options: { cwd?: string } = {},
291
+ ): Array<{ dir: string; location: CapabilityLocation }> {
292
+ return [
293
+ { dir: getProjectCapabilitiesDir({ cwd: options.cwd }), location: 'project' },
294
+ { dir: getUserCapabilitiesDir(), location: 'user' },
295
+ ]
296
+ }
297
+
298
+ export function validateCapabilityId(id: string): void {
299
+ if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/.test(id)) {
300
+ throw new Error(`Invalid capability id "${id}". Use kebab-case letters and numbers only.`)
301
+ }
302
+ }
303
+
304
+ export function getCapabilityDir(options: { id: string; location: CapabilityLocation; cwd?: string }): string {
305
+ validateCapabilityId(options.id)
306
+ const root =
307
+ options.location === 'project' ? getProjectCapabilitiesDir({ cwd: options.cwd }) : getUserCapabilitiesDir()
308
+ return path.join(root, options.id)
309
+ }
310
+
311
+ function readJsonFile(filePath: string): unknown {
312
+ const content = fs.readFileSync(filePath, 'utf-8')
313
+ return JSON.parse(content)
314
+ }
315
+
316
+ function writeJsonFile(options: { filePath: string; value: unknown }): void {
317
+ fs.writeFileSync(options.filePath, `${JSON.stringify(options.value, null, 2)}\n`)
318
+ }
319
+
320
+ function resolveCapabilityEntry(options: { dir: string; entry: string }): string {
321
+ const resolvedDir = path.resolve(options.dir)
322
+ const scriptPath = path.resolve(resolvedDir, options.entry)
323
+ if (scriptPath !== resolvedDir && !scriptPath.startsWith(`${resolvedDir}${path.sep}`)) {
324
+ throw new Error(`Capability entry must stay inside capability directory: ${options.entry}`)
325
+ }
326
+ return scriptPath
327
+ }
328
+
329
+ function parseManifest(options: { manifestPath: string }): CapabilityManifest {
330
+ const raw = readJsonFile(options.manifestPath)
331
+ return parseCapabilityManifest(raw)
332
+ }
333
+
334
+ export function readCapability(options: { dir: string; location: CapabilityLocation }): CapabilityRecord {
335
+ const manifestPath = path.join(options.dir, 'capability.json')
336
+ const manifest = parseManifest({ manifestPath })
337
+ const scriptPath = resolveCapabilityEntry({ dir: options.dir, entry: manifest.entry })
338
+ return {
339
+ manifest,
340
+ dir: options.dir,
341
+ manifestPath,
342
+ scriptPath,
343
+ location: options.location,
344
+ }
345
+ }
346
+
347
+ export function listCapabilities(options: { cwd?: string } = {}): CapabilityRecord[] {
348
+ return getCapabilityRoots({ cwd: options.cwd }).flatMap((root) => {
349
+ if (!fs.existsSync(root.dir)) {
350
+ return []
351
+ }
352
+ return fs
353
+ .readdirSync(root.dir, { withFileTypes: true })
354
+ .filter((entry) => {
355
+ return entry.isDirectory()
356
+ })
357
+ .flatMap((entry) => {
358
+ const dir = path.join(root.dir, entry.name)
359
+ const manifestPath = path.join(dir, 'capability.json')
360
+ if (!fs.existsSync(manifestPath)) {
361
+ return []
362
+ }
363
+ try {
364
+ return [readCapability({ dir, location: root.location })]
365
+ } catch {
366
+ return []
367
+ }
368
+ })
369
+ })
370
+ }
371
+
372
+ export function findCapability(options: { id: string; cwd?: string }): CapabilityRecord | null {
373
+ validateCapabilityId(options.id)
374
+ return (
375
+ getCapabilityRoots({ cwd: options.cwd })
376
+ .map((root) => {
377
+ const dir = path.join(root.dir, options.id)
378
+ if (!fs.existsSync(path.join(dir, 'capability.json'))) {
379
+ return null
380
+ }
381
+ return readCapability({ dir, location: root.location })
382
+ })
383
+ .find((record) => {
384
+ return record !== null
385
+ }) || null
386
+ )
387
+ }
388
+
389
+ export function requireCapability(options: { id: string; cwd?: string }): CapabilityRecord {
390
+ const capability = findCapability(options)
391
+ if (!capability) {
392
+ throw new Error(`Capability not found: ${options.id}`)
393
+ }
394
+ return capability
395
+ }
396
+
397
+ export function capabilityMatchesText(options: { capability: CapabilityManifest; text: string }): boolean {
398
+ return matchesCapabilityPatterns({ patterns: options.capability.match, text: options.text })
399
+ }
400
+
401
+ export function capabilityOperationMatchesText(options: {
402
+ operation: ResolvedCapabilityOperation
403
+ text: string
404
+ }): boolean {
405
+ return matchesCapabilityPatterns({ patterns: options.operation.match, text: options.text })
406
+ }
407
+
408
+ function matchesCapabilityPatterns(options: { patterns: string[]; text: string }): boolean {
409
+ if (options.patterns.length === 0) {
410
+ return true
411
+ }
412
+ return options.patterns.some((pattern) => {
413
+ const regex = new RegExp(`^${pattern.split('*').map(escapeRegex).join('.*')}$`)
414
+ return regex.test(options.text)
415
+ })
416
+ }
417
+
418
+ export function capabilityMatchesUrl(options: { capability: CapabilityManifest; url: string }): boolean {
419
+ return capabilityMatchesText({ capability: options.capability, text: options.url })
420
+ }
421
+
422
+ function escapeRegex(value: string): string {
423
+ return value.replace(/[|\\{}()[\]^$+?.]/g, '\\$&')
424
+ }
425
+
426
+ export function getCapabilityOperations(capability: CapabilityRecord): ResolvedCapabilityOperation[] {
427
+ const operations = Object.entries(capability.manifest.operations)
428
+ if (operations.length === 0) {
429
+ return [
430
+ {
431
+ id: undefined,
432
+ title: capability.manifest.title,
433
+ description: capability.manifest.description,
434
+ match: capability.manifest.match,
435
+ routingHint: capability.manifest.routingHint,
436
+ inputSchema: capability.manifest.inputSchema,
437
+ outputSchema: capability.manifest.outputSchema,
438
+ permissions: capability.manifest.permissions,
439
+ sideEffect: capability.manifest.sideEffect,
440
+ requiresConfirmation: capability.manifest.requiresConfirmation,
441
+ confirmationToken: capability.manifest.id,
442
+ },
443
+ ]
444
+ }
445
+ return operations.map(([id, operation]) => {
446
+ return {
447
+ ...operation,
448
+ id,
449
+ permissions: operation.permissions || capability.manifest.permissions,
450
+ confirmationToken: `${capability.manifest.id}:${id}`,
451
+ }
452
+ })
453
+ }
454
+
455
+ export function getCapabilitySafetySummary(capability: CapabilityRecord): {
456
+ sideEffect: CapabilitySideEffect | 'mixed'
457
+ requiresConfirmation: boolean
458
+ } {
459
+ const operations = getCapabilityOperations(capability)
460
+ const sideEffects = new Set(
461
+ operations.map((operation) => {
462
+ return operation.sideEffect
463
+ }),
464
+ )
465
+ return {
466
+ sideEffect: sideEffects.size === 1 ? operations[0]?.sideEffect || capability.manifest.sideEffect : 'mixed',
467
+ requiresConfirmation: operations.some((operation) => {
468
+ return operation.requiresConfirmation
469
+ }),
470
+ }
471
+ }
472
+
473
+ export function resolveCapabilityOperation(options: {
474
+ capability: CapabilityRecord
475
+ input: unknown
476
+ }): ResolvedCapabilityOperation {
477
+ const operations = getCapabilityOperations(options.capability)
478
+ if (operations.length === 1 && operations[0]?.id === undefined) {
479
+ return operations[0]
480
+ }
481
+ if (!isPlainObject(options.input) || typeof options.input.action !== 'string') {
482
+ throw new Error(
483
+ `Capability ${options.capability.manifest.id} requires input.action. Use one of: ${operations
484
+ .map((operation) => {
485
+ return operation.id
486
+ })
487
+ .join(', ')}`,
488
+ )
489
+ }
490
+ const action = options.input.action
491
+ const operation = operations.find((candidate) => {
492
+ return candidate.id === action
493
+ })
494
+ if (!operation) {
495
+ throw new Error(
496
+ `Unsupported capability action "${action}". Use one of: ${operations
497
+ .map((candidate) => {
498
+ return candidate.id
499
+ })
500
+ .join(', ')}`,
501
+ )
502
+ }
503
+ return operation
504
+ }
505
+
506
+ export function createCapability(options: {
507
+ id: string
508
+ title?: string
509
+ description?: string
510
+ location?: CapabilityLocation
511
+ cwd?: string
512
+ overwrite?: boolean
513
+ createdBy?: 'user' | 'ai'
514
+ runtime?: CapabilityRuntime
515
+ }): CapabilityRecord {
516
+ validateCapabilityId(options.id)
517
+ const location = options.location || 'user'
518
+ const dir = getCapabilityDir({ id: options.id, location, cwd: options.cwd })
519
+ if (fs.existsSync(dir) && !options.overwrite) {
520
+ throw new Error(`Capability already exists: ${options.id}`)
521
+ }
522
+
523
+ fs.mkdirSync(dir, { recursive: true })
524
+ const now = new Date().toISOString()
525
+ const manifest: CapabilityManifest = {
526
+ schemaVersion: 1,
527
+ id: options.id,
528
+ title: options.title || options.id,
529
+ description: options.description || '',
530
+ whenToUse: options.description ? [options.description] : [],
531
+ whenNotToUse: [],
532
+ tags: [],
533
+ match: [],
534
+ routingHint: 'search-first',
535
+ inputSchema: { type: 'object', properties: {}, required: [] },
536
+ outputSchema: { type: 'object', properties: {} },
537
+ permissions: options.runtime === 'node' ? ['network'] : ['browser.read'],
538
+ sideEffect: 'read',
539
+ requiresConfirmation: false,
540
+ operations: {},
541
+ auth: {
542
+ type: 'none',
543
+ refresh: 'none',
544
+ browserUrls: [],
545
+ requiredCookieNames: [],
546
+ failureSignals: [],
547
+ },
548
+ examples: [],
549
+ entry: 'script.js',
550
+ runtime: options.runtime || 'browser',
551
+ status: 'draft',
552
+ createdBy: options.createdBy || 'user',
553
+ createdAt: now,
554
+ updatedAt: now,
555
+ }
556
+
557
+ writeJsonFile({ filePath: path.join(dir, 'capability.json'), value: manifest })
558
+ fs.writeFileSync(path.join(dir, 'script.js'), getDefaultCapabilityScript({ runtime: manifest.runtime }))
559
+ fs.writeFileSync(path.join(dir, 'README.md'), `# ${manifest.title}\n\n${manifest.description}\n`)
560
+ return readCapability({ dir, location })
561
+ }
562
+
563
+ export function updateCapabilityManifest(options: {
564
+ id: string
565
+ cwd?: string
566
+ patch: Partial<Omit<CapabilityManifest, 'schemaVersion' | 'id' | 'createdAt'>>
567
+ allowUnvalidatedTrust?: boolean
568
+ }): CapabilityRecord {
569
+ const capability = requireCapability({ id: options.id, cwd: options.cwd })
570
+ const changesContract = Object.keys(options.patch).some((key) => {
571
+ return key !== 'status' && key !== 'updatedAt'
572
+ })
573
+ if (options.patch.status === 'trusted' && !options.allowUnvalidatedTrust) {
574
+ if (changesContract) {
575
+ throw new Error(
576
+ `Capability ${capability.manifest.id} cannot change its contract and become trusted in the same update. Save it as draft, validate the current contract, then trust it.`,
577
+ )
578
+ }
579
+ if (getCapabilityContractHealth(capability).state !== 'healthy') {
580
+ throw new Error(
581
+ `Capability ${capability.manifest.id} has no passing conformance evidence for its current contract. Run it with --force after repairing it before trusting it.`,
582
+ )
583
+ }
584
+ }
585
+ const nextStatus: CapabilityStatus = (() => {
586
+ if (options.patch.status) {
587
+ return options.patch.status
588
+ }
589
+ if (changesContract && capability.manifest.status === 'trusted') {
590
+ return 'draft'
591
+ }
592
+ return capability.manifest.status
593
+ })()
594
+ const nextManifest: CapabilityManifest = {
595
+ ...capability.manifest,
596
+ ...options.patch,
597
+ id: capability.manifest.id,
598
+ schemaVersion: 1,
599
+ status: nextStatus,
600
+ createdAt: capability.manifest.createdAt,
601
+ updatedAt: new Date().toISOString(),
602
+ }
603
+ writeJsonFile({ filePath: capability.manifestPath, value: CapabilityManifestSchema.parse(nextManifest) })
604
+ return readCapability({ dir: capability.dir, location: capability.location })
605
+ }
606
+
607
+ export function updateCapabilityScript(options: { id: string; cwd?: string; source: string }): CapabilityRecord {
608
+ const capability = requireCapability({ id: options.id, cwd: options.cwd })
609
+ fs.writeFileSync(capability.scriptPath, options.source)
610
+ const nextStatus: CapabilityStatus = capability.manifest.status === 'trusted' ? 'draft' : capability.manifest.status
611
+ return updateCapabilityManifest({
612
+ id: options.id,
613
+ cwd: options.cwd,
614
+ patch: { status: nextStatus },
615
+ })
616
+ }
617
+
618
+ export function readCapabilityScript(options: { id: string; cwd?: string }): string {
619
+ const capability = requireCapability({ id: options.id, cwd: options.cwd })
620
+ return fs.readFileSync(capability.scriptPath, 'utf-8')
621
+ }
622
+
623
+ export function readCapabilitySecrets(options: { capability: CapabilityRecord }): Record<string, unknown> {
624
+ const secretsPath = path.join(options.capability.dir, 'secrets.json')
625
+ if (!fs.existsSync(secretsPath)) {
626
+ return {}
627
+ }
628
+ const parsed = JSON.parse(fs.readFileSync(secretsPath, 'utf-8'))
629
+ if (!isPlainObject(parsed)) {
630
+ throw new Error(`Capability secrets.json must contain an object: ${secretsPath}`)
631
+ }
632
+ return parsed
633
+ }
634
+
635
+ export function writeCapabilitySecrets(options: {
636
+ capability: CapabilityRecord
637
+ secrets: Record<string, unknown>
638
+ }): void {
639
+ const secretsPath = path.join(options.capability.dir, 'secrets.json')
640
+ fs.mkdirSync(options.capability.dir, { recursive: true })
641
+ fs.writeFileSync(secretsPath, `${JSON.stringify(options.secrets, null, 2)}\n`, { mode: 0o600 })
642
+ if (process.platform === 'win32') {
643
+ return
644
+ }
645
+ fs.chmodSync(secretsPath, 0o600)
646
+ }
647
+
648
+ export function validateJsonAgainstSchema(options: {
649
+ schema: Record<string, unknown>
650
+ value: unknown
651
+ label: string
652
+ }): ValidationResult {
653
+ const errors: string[] = []
654
+ const type = typeof options.schema.type === 'string' ? options.schema.type : undefined
655
+ if (type && !matchesJsonType({ value: options.value, type })) {
656
+ errors.push(`${options.label} must be ${type}`)
657
+ return { valid: errors.length === 0, errors }
658
+ }
659
+
660
+ if (type === 'object' || options.schema.properties || options.schema.required) {
661
+ if (!isPlainObject(options.value)) {
662
+ errors.push(`${options.label} must be object`)
663
+ return { valid: false, errors }
664
+ }
665
+ const objectValue = options.value
666
+
667
+ const required = Array.isArray(options.schema.required)
668
+ ? options.schema.required.filter((value): value is string => {
669
+ return typeof value === 'string'
670
+ })
671
+ : []
672
+ const properties = isPlainObject(options.schema.properties) ? options.schema.properties : {}
673
+
674
+ errors.push(
675
+ ...required
676
+ .filter((key) => {
677
+ return !(key in objectValue)
678
+ })
679
+ .map((key) => {
680
+ return `${options.label}.${key} is required`
681
+ }),
682
+ )
683
+
684
+ errors.push(
685
+ ...Object.entries(properties).flatMap(([key, rawPropertySchema]) => {
686
+ if (!(key in objectValue)) {
687
+ return []
688
+ }
689
+ if (!isPlainObject(rawPropertySchema)) {
690
+ return []
691
+ }
692
+ const propertyType = typeof rawPropertySchema.type === 'string' ? rawPropertySchema.type : undefined
693
+ if (!propertyType) {
694
+ return []
695
+ }
696
+ if (matchesJsonType({ value: objectValue[key], type: propertyType })) {
697
+ return []
698
+ }
699
+ return [`${options.label}.${key} must be ${propertyType}`]
700
+ }),
701
+ )
702
+ }
703
+
704
+ return { valid: errors.length === 0, errors }
705
+ }
706
+
707
+ function matchesJsonType(options: { value: unknown; type: string }): boolean {
708
+ if (options.type === 'array') {
709
+ return Array.isArray(options.value)
710
+ }
711
+ if (options.type === 'object') {
712
+ return isPlainObject(options.value)
713
+ }
714
+ if (options.type === 'integer') {
715
+ return Number.isInteger(options.value)
716
+ }
717
+ if (options.type === 'number') {
718
+ return typeof options.value === 'number'
719
+ }
720
+ if (options.type === 'string') {
721
+ return typeof options.value === 'string'
722
+ }
723
+ if (options.type === 'boolean') {
724
+ return typeof options.value === 'boolean'
725
+ }
726
+ if (options.type === 'null') {
727
+ return options.value === null
728
+ }
729
+ return true
730
+ }
731
+
732
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
733
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
734
+ }
735
+
736
+ export function appendCapabilityRun(options: { capability: CapabilityRecord; record: CapabilityRunRecord }): void {
737
+ fs.appendFileSync(path.join(options.capability.dir, 'runs.jsonl'), `${JSON.stringify(options.record)}\n`)
738
+ }
739
+
740
+ export function readCapabilityRuns(options: { capability: CapabilityRecord; limit?: number }): CapabilityRunRecord[] {
741
+ const runsPath = path.join(options.capability.dir, 'runs.jsonl')
742
+ if (!fs.existsSync(runsPath)) {
743
+ return []
744
+ }
745
+ const lines = fs
746
+ .readFileSync(runsPath, 'utf-8')
747
+ .split(/\r?\n/)
748
+ .filter((line) => {
749
+ return line.trim().length > 0
750
+ })
751
+ const selectedLines = typeof options.limit === 'number' ? lines.slice(-options.limit) : lines
752
+ return selectedLines.flatMap((line) => {
753
+ const value: unknown = (() => {
754
+ try {
755
+ return JSON.parse(line)
756
+ } catch {
757
+ return null
758
+ }
759
+ })()
760
+ const parsed = CapabilityRunRecordSchema.safeParse(value)
761
+ if (!parsed.success) {
762
+ return []
763
+ }
764
+ return [parsed.data]
765
+ })
766
+ }
767
+
768
+ export function getCapabilityContractFingerprint(capability: CapabilityRecord): string {
769
+ const { status: _status, createdAt: _createdAt, updatedAt: _updatedAt, ...contractManifest } = capability.manifest
770
+ const script = fs.readFileSync(capability.scriptPath, 'utf-8')
771
+ return crypto
772
+ .createHash('sha256')
773
+ .update(JSON.stringify({ manifest: contractManifest, script }))
774
+ .digest('hex')
775
+ }
776
+
777
+ export function getCapabilityContractHealth(capability: CapabilityRecord): CapabilityContractHealth {
778
+ const fingerprintResult: { success: true; fingerprint: string } | { success: false; reason: string } = (() => {
779
+ try {
780
+ return { success: true, fingerprint: getCapabilityContractFingerprint(capability) }
781
+ } catch (error: unknown) {
782
+ const message = error instanceof Error ? error.message : String(error)
783
+ return { success: false, reason: `Cannot validate the capability entry script: ${message}` }
784
+ }
785
+ })()
786
+ if (!fingerprintResult.success) {
787
+ return {
788
+ state: 'drifted',
789
+ reasons: [fingerprintResult.reason],
790
+ }
791
+ }
792
+ const fingerprint = fingerprintResult.fingerprint
793
+ const latestContractRun = readCapabilityRuns({ capability, limit: 100 })
794
+ .reverse()
795
+ .find((run) => {
796
+ return run.contract?.fingerprint === fingerprint && run.contract.status !== 'unknown'
797
+ })
798
+
799
+ if (!latestContractRun?.contract) {
800
+ return {
801
+ state: 'unknown',
802
+ reasons: ['No conformance evidence exists for the current script and contract.'],
803
+ }
804
+ }
805
+
806
+ if (latestContractRun.contract.status === 'failed') {
807
+ return {
808
+ state: 'drifted',
809
+ checkedAt: latestContractRun.createdAt,
810
+ reasons: latestContractRun.contract.failures.map((failure) => {
811
+ return failure.message
812
+ }),
813
+ }
814
+ }
815
+
816
+ return {
817
+ state: 'healthy',
818
+ checkedAt: latestContractRun.createdAt,
819
+ reasons: [],
820
+ }
821
+ }
822
+
823
+ export function getCapabilityLifecycle(capability: CapabilityRecord): CapabilityLifecycle {
824
+ const contractHealth = getCapabilityContractHealth(capability)
825
+ const stage: CapabilityLifecycle['stage'] = (() => {
826
+ if (capability.manifest.status === 'disabled') {
827
+ return 'disabled'
828
+ }
829
+ if (contractHealth.state === 'drifted') {
830
+ return 'drifted'
831
+ }
832
+ if (capability.manifest.status === 'trusted') {
833
+ return 'trusted'
834
+ }
835
+ if (contractHealth.state === 'healthy') {
836
+ return 'validated'
837
+ }
838
+ return 'drafted'
839
+ })()
840
+ const nextAction: CapabilityLifecycle['nextAction'] = (() => {
841
+ if (stage === 'disabled') {
842
+ return 'enable'
843
+ }
844
+ if (stage === 'drifted') {
845
+ return 'repair'
846
+ }
847
+ if (stage === 'validated') {
848
+ return 'trust'
849
+ }
850
+ if (stage === 'trusted') {
851
+ return 'run'
852
+ }
853
+ return 'validate'
854
+ })()
855
+ const nextCommand: string = (() => {
856
+ if (nextAction === 'enable') {
857
+ return `tabwright capability draft ${capability.manifest.id}`
858
+ }
859
+ if (nextAction === 'repair') {
860
+ return `tabwright capability show ${capability.manifest.id}`
861
+ }
862
+ if (nextAction === 'trust') {
863
+ return `tabwright capability trust ${capability.manifest.id}`
864
+ }
865
+
866
+ const exampleInput = capability.manifest.examples.find((example) => {
867
+ return isPlainObject(example.input)
868
+ })?.input
869
+ const input = isPlainObject(exampleInput) ? exampleInput : {}
870
+ const operation: ResolvedCapabilityOperation | undefined = (() => {
871
+ try {
872
+ return resolveCapabilityOperation({ capability, input })
873
+ } catch {
874
+ return undefined
875
+ }
876
+ })()
877
+ const args = [
878
+ 'tabwright',
879
+ 'capability',
880
+ 'run',
881
+ capability.manifest.id,
882
+ ...(capability.manifest.runtime === 'browser' ? ['--browser', 'user'] : []),
883
+ '--input-json',
884
+ quoteShell(JSON.stringify(input)),
885
+ ...(nextAction === 'validate' ? ['--force'] : []),
886
+ ...(operation?.requiresConfirmation ? ['--confirm', operation.confirmationToken] : []),
887
+ '--json',
888
+ ]
889
+ return args.join(' ')
890
+ })()
891
+
892
+ return { stage, nextAction, nextCommand, contractHealth }
893
+ }
894
+
895
+ export function getCapabilityAutonomy(
896
+ capability: CapabilityRecord,
897
+ operation?: ResolvedCapabilityOperation,
898
+ ): { allowed: boolean; reasons: string[] } {
899
+ const contractHealth = getCapabilityContractHealth(capability)
900
+ const operations = operation ? [operation] : getCapabilityOperations(capability)
901
+ const blockers = [
902
+ capability.manifest.status === 'trusted' ? '' : `status is ${capability.manifest.status}`,
903
+ contractHealth.state === 'drifted' ? 'current contract failed conformance' : '',
904
+ ...operations.flatMap((candidate) => {
905
+ return [
906
+ candidate.sideEffect === 'read'
907
+ ? ''
908
+ : `${candidate.id ? `operation ${candidate.id} ` : ''}sideEffect is ${candidate.sideEffect}`,
909
+ candidate.requiresConfirmation
910
+ ? `${candidate.id ? `operation ${candidate.id} ` : ''}requires confirmation`
911
+ : '',
912
+ ]
913
+ }),
914
+ ].filter((reason) => {
915
+ return reason.length > 0
916
+ })
917
+ return {
918
+ allowed: blockers.length === 0,
919
+ reasons: blockers.length === 0 ? ['trusted read-only capability'] : blockers,
920
+ }
921
+ }
922
+
923
+ export function toCapabilityContract(capability: CapabilityRecord): Record<string, unknown> {
924
+ const safety = getCapabilitySafetySummary(capability)
925
+ const operations = Object.fromEntries(
926
+ getCapabilityOperations(capability)
927
+ .filter((operation) => {
928
+ return operation.id !== undefined
929
+ })
930
+ .map((operation) => {
931
+ return [
932
+ operation.id,
933
+ {
934
+ title: operation.title,
935
+ description: operation.description,
936
+ match: operation.match,
937
+ routingHint: operation.routingHint,
938
+ inputSchema: operation.inputSchema,
939
+ outputSchema: operation.outputSchema,
940
+ permissions: operation.permissions,
941
+ sideEffect: operation.sideEffect,
942
+ requiresConfirmation: operation.requiresConfirmation,
943
+ confirmationToken: operation.confirmationToken,
944
+ autonomousInvocation: getCapabilityAutonomy(capability, operation),
945
+ },
946
+ ]
947
+ }),
948
+ )
949
+ return {
950
+ ...toCapabilitySummary(capability),
951
+ whenToUse: capability.manifest.whenToUse,
952
+ whenNotToUse: capability.manifest.whenNotToUse,
953
+ tags: capability.manifest.tags,
954
+ sideEffect: safety.sideEffect,
955
+ requiresConfirmation: safety.requiresConfirmation,
956
+ auth: capability.manifest.auth,
957
+ examples: capability.manifest.examples,
958
+ autonomousInvocation: getCapabilityAutonomy(capability),
959
+ operations,
960
+ recentRuns: readCapabilityRuns({ capability, limit: 5 }),
961
+ lifecycle: getCapabilityLifecycle(capability),
962
+ }
963
+ }
964
+
965
+ export function searchCapabilities(options: { query: string; cwd?: string; limit?: number }): CapabilitySearchResult[] {
966
+ const tokens = tokenizeSearchQuery(options.query)
967
+ if (options.query.trim() && tokens.length === 0) {
968
+ return []
969
+ }
970
+ const results = dedupeCapabilitiesById(listCapabilities({ cwd: options.cwd }))
971
+ .map((capability) => {
972
+ return scoreCapabilitySearch({ capability, tokens })
973
+ })
974
+ .filter((result) => {
975
+ if (tokens.length === 0) {
976
+ return true
977
+ }
978
+ if (tokens.length === 1) {
979
+ return result.score > 0
980
+ }
981
+ return result.matchedTokenCount >= 2
982
+ })
983
+ .sort((left, right) => {
984
+ return right.score - left.score || left.capability.manifest.id.localeCompare(right.capability.manifest.id)
985
+ })
986
+ const limitedResults = typeof options.limit === 'number' ? results.slice(0, options.limit) : results
987
+ return limitedResults.map((result) => {
988
+ return {
989
+ capability: result.capability,
990
+ score: result.score,
991
+ reasons: result.reasons,
992
+ }
993
+ })
994
+ }
995
+
996
+ export function routeCapabilities(options: { task: string; cwd?: string; limit?: number }): CapabilityRouteResult[] {
997
+ const task = options.task.trim()
998
+ if (!task) {
999
+ return []
1000
+ }
1001
+ const candidates = getRouteCandidateTexts(task)
1002
+ const routes = dedupeCapabilitiesById(listCapabilities({ cwd: options.cwd })).flatMap((capability) => {
1003
+ return getCapabilityOperations(capability)
1004
+ .filter((operation) => {
1005
+ return operation.routingHint === 'exact-match-direct-run'
1006
+ })
1007
+ .filter((operation) => {
1008
+ return getCapabilityAutonomy(capability, operation).allowed
1009
+ })
1010
+ .flatMap((operation) => {
1011
+ const matchedText = candidates.find((candidate) => {
1012
+ return operation.match.length > 0 && capabilityOperationMatchesText({ operation, text: candidate })
1013
+ })
1014
+ if (!matchedText) {
1015
+ return []
1016
+ }
1017
+ const input = buildRouteInput({ capability, operation, task, matchedText })
1018
+ const shellCommand = buildCapabilityRunCommand({ id: capability.manifest.id, input })
1019
+ return [
1020
+ {
1021
+ capability,
1022
+ operation: operation.id,
1023
+ input,
1024
+ command: shellCommand,
1025
+ shellCommand,
1026
+ commandWarning: buildCapabilityCommandWarning({ id: capability.manifest.id }),
1027
+ executionHint: {
1028
+ routeCanRunSandboxed: true,
1029
+ runRequiresEscalatedSandbox: true,
1030
+ commandMustStartWith: 'tabwright capability run ',
1031
+ reason:
1032
+ 'capability run writes runs.jsonl and may write artifacts under ~/.tabwright/capabilities; use escalated/unsandboxed execution in Codex sandbox',
1033
+ },
1034
+ reasons: [
1035
+ `${operation.id ? `operation: ${operation.id}, ` : ''}routingHint: exact-match-direct-run`,
1036
+ 'autonomousInvocation: trusted read-only capability',
1037
+ `matched: ${matchedText}`,
1038
+ ],
1039
+ matchedText,
1040
+ },
1041
+ ]
1042
+ })
1043
+ })
1044
+ return typeof options.limit === 'number' ? routes.slice(0, options.limit) : routes
1045
+ }
1046
+
1047
+ function dedupeCapabilitiesById(capabilities: CapabilityRecord[]): CapabilityRecord[] {
1048
+ return capabilities.filter((capability, index) => {
1049
+ return (
1050
+ capabilities.findIndex((candidate) => {
1051
+ return candidate.manifest.id === capability.manifest.id
1052
+ }) === index
1053
+ )
1054
+ })
1055
+ }
1056
+
1057
+ function getRouteCandidateTexts(task: string): string[] {
1058
+ const urls = task.match(/https?:\/\/[^\s<>"']+/g) || []
1059
+ return Array.from(
1060
+ new Set(
1061
+ [task, ...urls].map((value) => {
1062
+ return value.replace(/[),,。;;]+$/g, '')
1063
+ }),
1064
+ ),
1065
+ )
1066
+ }
1067
+
1068
+ function buildRouteInput(options: {
1069
+ capability: CapabilityRecord
1070
+ operation: ResolvedCapabilityOperation
1071
+ task: string
1072
+ matchedText: string
1073
+ }): Record<string, unknown> {
1074
+ const actionInput = options.operation.id ? { action: options.operation.id } : {}
1075
+ if (
1076
+ options.matchedText.startsWith('http') &&
1077
+ hasInputProperty({ schema: options.operation.inputSchema, name: 'url' })
1078
+ ) {
1079
+ return { ...actionInput, url: options.matchedText }
1080
+ }
1081
+ if (hasInputProperty({ schema: options.operation.inputSchema, name: 'query' })) {
1082
+ return { ...actionInput, query: options.task }
1083
+ }
1084
+ return actionInput
1085
+ }
1086
+
1087
+ function hasInputProperty(options: { schema: Record<string, unknown>; name: string }): boolean {
1088
+ const properties = isPlainObject(options.schema.properties) ? options.schema.properties : {}
1089
+ return properties[options.name] !== undefined
1090
+ }
1091
+
1092
+ export function buildCapabilityRunCommand(options: { id: string; input: Record<string, unknown> }): string {
1093
+ return `tabwright capability run ${options.id} --input-json ${quoteShell(JSON.stringify(options.input))} --json`
1094
+ }
1095
+
1096
+ function buildCapabilityCommandWarning(options: { id: string }): string {
1097
+ return `${options.id} is a Tabwright capability id, not a shell command. Do not run "${options.id} ..."; run shellCommand exactly.`
1098
+ }
1099
+
1100
+ function quoteShell(value: string): string {
1101
+ return `'${value.replace(/'/g, "'\\''")}'`
1102
+ }
1103
+
1104
+ export function toCapabilitySummary(capability: CapabilityRecord): Record<string, unknown> {
1105
+ const safety = getCapabilitySafetySummary(capability)
1106
+ return {
1107
+ id: capability.manifest.id,
1108
+ title: capability.manifest.title,
1109
+ description: capability.manifest.description,
1110
+ status: capability.manifest.status,
1111
+ runtime: capability.manifest.runtime,
1112
+ match: capability.manifest.match,
1113
+ routingHint: capability.manifest.routingHint,
1114
+ permissions: capability.manifest.permissions,
1115
+ sideEffect: safety.sideEffect,
1116
+ requiresConfirmation: safety.requiresConfirmation,
1117
+ operations: capability.manifest.operations,
1118
+ whenToUse: capability.manifest.whenToUse,
1119
+ whenNotToUse: capability.manifest.whenNotToUse,
1120
+ tags: capability.manifest.tags,
1121
+ auth: capability.manifest.auth,
1122
+ inputSchema: capability.manifest.inputSchema,
1123
+ outputSchema: capability.manifest.outputSchema,
1124
+ location: capability.location,
1125
+ dir: capability.dir,
1126
+ }
1127
+ }
1128
+
1129
+ const CAPABILITY_SEARCH_STOP_WORDS = new Set([
1130
+ 'a',
1131
+ 'an',
1132
+ 'and',
1133
+ 'for',
1134
+ 'in',
1135
+ 'of',
1136
+ 'on',
1137
+ 'only',
1138
+ 'or',
1139
+ 'please',
1140
+ 'read',
1141
+ 'the',
1142
+ 'to',
1143
+ 'use',
1144
+ 'with',
1145
+ ])
1146
+
1147
+ function tokenizeSearchQuery(query: string): string[] {
1148
+ return query
1149
+ .toLowerCase()
1150
+ .split(/[^a-z0-9\u4e00-\u9fff]+/)
1151
+ .map((token) => {
1152
+ return token.trim()
1153
+ })
1154
+ .filter((token) => {
1155
+ return token.length > 0 && !CAPABILITY_SEARCH_STOP_WORDS.has(token)
1156
+ })
1157
+ }
1158
+
1159
+ function scoreCapabilitySearch(options: {
1160
+ capability: CapabilityRecord
1161
+ tokens: string[]
1162
+ }): ScoredCapabilitySearchResult {
1163
+ const operations = Object.entries(options.capability.manifest.operations)
1164
+ const weightedFields: Array<{ label: string; weight: number; values: string[] }> = [
1165
+ { label: 'id', weight: 5, values: [options.capability.manifest.id] },
1166
+ { label: 'title', weight: 6, values: [options.capability.manifest.title] },
1167
+ { label: 'description', weight: 4, values: [options.capability.manifest.description] },
1168
+ { label: 'whenToUse', weight: 8, values: options.capability.manifest.whenToUse },
1169
+ { label: 'tags', weight: 6, values: options.capability.manifest.tags },
1170
+ { label: 'match', weight: 3, values: options.capability.manifest.match },
1171
+ {
1172
+ label: 'operations',
1173
+ weight: 6,
1174
+ values: operations.flatMap(([id, operation]) => {
1175
+ return [id, operation.title, operation.description, ...operation.match]
1176
+ }),
1177
+ },
1178
+ ]
1179
+
1180
+ const matches = weightedFields.flatMap((field) => {
1181
+ const text = field.values.join(' ').toLowerCase()
1182
+ const matchedTokens = options.tokens.filter((token) => {
1183
+ return text.includes(token)
1184
+ })
1185
+ if (matchedTokens.length === 0) {
1186
+ return []
1187
+ }
1188
+ return [
1189
+ {
1190
+ score: matchedTokens.length * field.weight,
1191
+ reason: `${field.label}: ${matchedTokens.join(', ')}`,
1192
+ matchedTokens,
1193
+ },
1194
+ ]
1195
+ })
1196
+
1197
+ const negativeMatches = options.capability.manifest.whenNotToUse.flatMap((text) => {
1198
+ const lowerText = text.toLowerCase()
1199
+ const matchedTokens = options.tokens.filter((token) => {
1200
+ return lowerText.includes(token)
1201
+ })
1202
+ if (matchedTokens.length === 0) {
1203
+ return []
1204
+ }
1205
+ return [matchedTokens.length * 3]
1206
+ })
1207
+
1208
+ return {
1209
+ capability: options.capability,
1210
+ score:
1211
+ matches.reduce((total, match) => {
1212
+ return total + match.score
1213
+ }, 0) -
1214
+ negativeMatches.reduce((total, score) => {
1215
+ return total + score
1216
+ }, 0),
1217
+ reasons: matches.map((match) => {
1218
+ return match.reason
1219
+ }),
1220
+ matchedTokenCount: new Set(
1221
+ matches.flatMap((match) => {
1222
+ return match.matchedTokens
1223
+ }),
1224
+ ).size,
1225
+ }
1226
+ }
1227
+
1228
+ function getDefaultCapabilityScript(options: { runtime: CapabilityRuntime }): string {
1229
+ if (options.runtime === 'node') {
1230
+ return ['return {', ' input,', ' hasSecrets: Object.keys(secrets).length > 0,', '}', ''].join('\n')
1231
+ }
1232
+
1233
+ return ['const currentUrl = page.url()', '', 'return {', ' currentUrl,', ' input,', '}', ''].join('\n')
1234
+ }