@biggora/claude-plugins 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (265) hide show
  1. package/README.md +13 -4
  2. package/package.json +1 -1
  3. package/registry/registry.json +334 -244
  4. package/specs/coding.md +30 -0
  5. package/specs/pod.md +2 -0
  6. package/src/commands/skills/add.js +63 -7
  7. package/src/commands/skills/list.js +23 -52
  8. package/src/commands/skills/remove.js +26 -27
  9. package/src/commands/skills/resolve.js +155 -0
  10. package/src/commands/skills/update.js +58 -74
  11. package/src/skills/captcha/README.md +221 -0
  12. package/src/skills/captcha/SKILL.md +355 -0
  13. package/src/skills/captcha/references/captcha-types.md +254 -0
  14. package/src/skills/captcha/references/services.md +172 -0
  15. package/src/skills/captcha/references/stealth.md +238 -0
  16. package/src/skills/captcha/scripts/solve_captcha.py +323 -0
  17. package/src/skills/captcha/scripts/solve_image_grid.py +350 -0
  18. package/src/skills/google-merchant-api/SKILL.md +581 -0
  19. package/src/skills/google-merchant-api/references/accounts.md +247 -0
  20. package/src/skills/google-merchant-api/references/content-api-legacy.md +216 -0
  21. package/src/skills/google-merchant-api/references/datasources.md +233 -0
  22. package/src/skills/google-merchant-api/references/inventories.md +201 -0
  23. package/src/skills/google-merchant-api/references/migration.md +267 -0
  24. package/src/skills/google-merchant-api/references/products.md +316 -0
  25. package/src/skills/google-merchant-api/references/promotions.md +201 -0
  26. package/src/skills/google-merchant-api/references/reports.md +240 -0
  27. package/src/skills/lv-aggregators-api/SKILL.md +113 -0
  28. package/src/skills/lv-aggregators-api/references/integration-guide.md +368 -0
  29. package/src/skills/lv-aggregators-api/references/kurpirkt.md +103 -0
  30. package/src/skills/lv-aggregators-api/references/salidzini.md +122 -0
  31. package/src/skills/nest-best-practices/SKILL.md +251 -0
  32. package/src/skills/nest-best-practices/references/best-practices-request-lifecycle.md +158 -0
  33. package/src/skills/nest-best-practices/references/cli-monorepo.md +106 -0
  34. package/src/skills/nest-best-practices/references/cli-overview.md +157 -0
  35. package/src/skills/nest-best-practices/references/core-controllers.md +165 -0
  36. package/src/skills/nest-best-practices/references/core-dependency-injection.md +179 -0
  37. package/src/skills/nest-best-practices/references/core-middleware.md +139 -0
  38. package/src/skills/nest-best-practices/references/core-modules.md +138 -0
  39. package/src/skills/nest-best-practices/references/core-providers.md +188 -0
  40. package/src/skills/nest-best-practices/references/faq-raw-body-hybrid.md +122 -0
  41. package/src/skills/nest-best-practices/references/fundamentals-circular-dependency.md +89 -0
  42. package/src/skills/nest-best-practices/references/fundamentals-custom-decorators.md +107 -0
  43. package/src/skills/nest-best-practices/references/fundamentals-dynamic-modules.md +125 -0
  44. package/src/skills/nest-best-practices/references/fundamentals-exception-filters.md +202 -0
  45. package/src/skills/nest-best-practices/references/fundamentals-execution-context.md +107 -0
  46. package/src/skills/nest-best-practices/references/fundamentals-guards.md +136 -0
  47. package/src/skills/nest-best-practices/references/fundamentals-interceptors.md +187 -0
  48. package/src/skills/nest-best-practices/references/fundamentals-lazy-loading.md +89 -0
  49. package/src/skills/nest-best-practices/references/fundamentals-lifecycle-events.md +87 -0
  50. package/src/skills/nest-best-practices/references/fundamentals-module-reference.md +107 -0
  51. package/src/skills/nest-best-practices/references/fundamentals-pipes.md +197 -0
  52. package/src/skills/nest-best-practices/references/fundamentals-provider-scopes.md +92 -0
  53. package/src/skills/nest-best-practices/references/fundamentals-testing.md +142 -0
  54. package/src/skills/nest-best-practices/references/graphql-overview.md +233 -0
  55. package/src/skills/nest-best-practices/references/graphql-resolvers-mutations.md +199 -0
  56. package/src/skills/nest-best-practices/references/graphql-scalars-unions-enums.md +180 -0
  57. package/src/skills/nest-best-practices/references/graphql-subscriptions.md +228 -0
  58. package/src/skills/nest-best-practices/references/microservices-grpc.md +175 -0
  59. package/src/skills/nest-best-practices/references/microservices-overview.md +221 -0
  60. package/src/skills/nest-best-practices/references/microservices-transports.md +119 -0
  61. package/src/skills/nest-best-practices/references/openapi-swagger.md +207 -0
  62. package/src/skills/nest-best-practices/references/recipes-authentication.md +97 -0
  63. package/src/skills/nest-best-practices/references/recipes-cqrs.md +176 -0
  64. package/src/skills/nest-best-practices/references/recipes-crud-generator.md +87 -0
  65. package/src/skills/nest-best-practices/references/recipes-documentation.md +93 -0
  66. package/src/skills/nest-best-practices/references/recipes-mongoose.md +153 -0
  67. package/src/skills/nest-best-practices/references/recipes-prisma.md +98 -0
  68. package/src/skills/nest-best-practices/references/recipes-terminus.md +148 -0
  69. package/src/skills/nest-best-practices/references/recipes-typeorm.md +122 -0
  70. package/src/skills/nest-best-practices/references/security-authorization.md +196 -0
  71. package/src/skills/nest-best-practices/references/security-cors-helmet-rate-limiting.md +204 -0
  72. package/src/skills/nest-best-practices/references/security-encryption-hashing.md +93 -0
  73. package/src/skills/nest-best-practices/references/techniques-caching.md +142 -0
  74. package/src/skills/nest-best-practices/references/techniques-compression-streaming-sse.md +194 -0
  75. package/src/skills/nest-best-practices/references/techniques-configuration.md +132 -0
  76. package/src/skills/nest-best-practices/references/techniques-database.md +153 -0
  77. package/src/skills/nest-best-practices/references/techniques-events.md +163 -0
  78. package/src/skills/nest-best-practices/references/techniques-fastify.md +137 -0
  79. package/src/skills/nest-best-practices/references/techniques-file-upload.md +140 -0
  80. package/src/skills/nest-best-practices/references/techniques-http-module.md +176 -0
  81. package/src/skills/nest-best-practices/references/techniques-logging.md +146 -0
  82. package/src/skills/nest-best-practices/references/techniques-mvc-serve-static.md +132 -0
  83. package/src/skills/nest-best-practices/references/techniques-queues.md +162 -0
  84. package/src/skills/nest-best-practices/references/techniques-serialization.md +158 -0
  85. package/src/skills/nest-best-practices/references/techniques-sessions-cookies.md +167 -0
  86. package/src/skills/nest-best-practices/references/techniques-task-scheduling.md +166 -0
  87. package/src/skills/nest-best-practices/references/techniques-validation.md +126 -0
  88. package/src/skills/nest-best-practices/references/techniques-versioning.md +153 -0
  89. package/src/skills/nest-best-practices/references/websockets-advanced.md +96 -0
  90. package/src/skills/nest-best-practices/references/websockets-gateways.md +215 -0
  91. package/src/skills/tailwindcss-best-practices/SKILL.md +180 -0
  92. package/src/skills/tailwindcss-best-practices/references/best-practices-utility-patterns.md +87 -0
  93. package/src/skills/tailwindcss-best-practices/references/core-installation.md +109 -0
  94. package/src/skills/tailwindcss-best-practices/references/core-preflight.md +200 -0
  95. package/src/skills/tailwindcss-best-practices/references/core-responsive.md +163 -0
  96. package/src/skills/tailwindcss-best-practices/references/core-source-detection.md +114 -0
  97. package/src/skills/tailwindcss-best-practices/references/core-theme.md +108 -0
  98. package/src/skills/tailwindcss-best-practices/references/core-utility-classes.md +59 -0
  99. package/src/skills/tailwindcss-best-practices/references/core-variants.md +204 -0
  100. package/src/skills/tailwindcss-best-practices/references/effects-form-controls.md +76 -0
  101. package/src/skills/tailwindcss-best-practices/references/effects-mask.md +91 -0
  102. package/src/skills/tailwindcss-best-practices/references/effects-scroll-snap.md +59 -0
  103. package/src/skills/tailwindcss-best-practices/references/effects-text-shadow.md +78 -0
  104. package/src/skills/tailwindcss-best-practices/references/effects-transition-animation.md +80 -0
  105. package/src/skills/tailwindcss-best-practices/references/effects-visibility-interactivity.md +82 -0
  106. package/src/skills/tailwindcss-best-practices/references/features-content-detection.md +175 -0
  107. package/src/skills/tailwindcss-best-practices/references/features-custom-styles.md +203 -0
  108. package/src/skills/tailwindcss-best-practices/references/features-dark-mode.md +137 -0
  109. package/src/skills/tailwindcss-best-practices/references/features-functions-directives.md +241 -0
  110. package/src/skills/tailwindcss-best-practices/references/features-upgrade.md +160 -0
  111. package/src/skills/tailwindcss-best-practices/references/layout-aspect-ratio.md +39 -0
  112. package/src/skills/tailwindcss-best-practices/references/layout-columns.md +80 -0
  113. package/src/skills/tailwindcss-best-practices/references/layout-display.md +110 -0
  114. package/src/skills/tailwindcss-best-practices/references/layout-flexbox.md +112 -0
  115. package/src/skills/tailwindcss-best-practices/references/layout-grid.md +87 -0
  116. package/src/skills/tailwindcss-best-practices/references/layout-height.md +97 -0
  117. package/src/skills/tailwindcss-best-practices/references/layout-inset.md +103 -0
  118. package/src/skills/tailwindcss-best-practices/references/layout-logical-properties.md +92 -0
  119. package/src/skills/tailwindcss-best-practices/references/layout-margin.md +126 -0
  120. package/src/skills/tailwindcss-best-practices/references/layout-min-max-sizing.md +63 -0
  121. package/src/skills/tailwindcss-best-practices/references/layout-object-fit-position.md +64 -0
  122. package/src/skills/tailwindcss-best-practices/references/layout-overflow.md +57 -0
  123. package/src/skills/tailwindcss-best-practices/references/layout-padding.md +77 -0
  124. package/src/skills/tailwindcss-best-practices/references/layout-position.md +85 -0
  125. package/src/skills/tailwindcss-best-practices/references/layout-tables.md +67 -0
  126. package/src/skills/tailwindcss-best-practices/references/layout-width.md +102 -0
  127. package/src/skills/tailwindcss-best-practices/references/transform-base.md +68 -0
  128. package/src/skills/tailwindcss-best-practices/references/transform-rotate.md +70 -0
  129. package/src/skills/tailwindcss-best-practices/references/transform-scale.md +83 -0
  130. package/src/skills/tailwindcss-best-practices/references/transform-skew.md +62 -0
  131. package/src/skills/tailwindcss-best-practices/references/transform-translate.md +77 -0
  132. package/src/skills/tailwindcss-best-practices/references/typography-font-text.md +142 -0
  133. package/src/skills/tailwindcss-best-practices/references/typography-list-style.md +65 -0
  134. package/src/skills/tailwindcss-best-practices/references/typography-text-align.md +60 -0
  135. package/src/skills/tailwindcss-best-practices/references/visual-background.md +76 -0
  136. package/src/skills/tailwindcss-best-practices/references/visual-border.md +108 -0
  137. package/src/skills/tailwindcss-best-practices/references/visual-effects.md +111 -0
  138. package/src/skills/tailwindcss-best-practices/references/visual-svg.md +82 -0
  139. package/src/skills/test-mobile-app/SKILL.md +11 -6
  140. package/src/skills/test-mobile-app/scripts/analyze_apk.py +15 -4
  141. package/src/skills/test-mobile-app/scripts/check_environment.py +5 -5
  142. package/src/skills/test-mobile-app/scripts/run_tests.py +1 -1
  143. package/src/skills/test-web-ui/SKILL.md +264 -84
  144. package/src/skills/test-web-ui/scripts/discover.py +25 -12
  145. package/src/skills/test-web-ui/scripts/run_tests.py +3 -2
  146. package/src/skills/typescript-expert/SKILL.md +145 -0
  147. package/src/skills/typescript-expert/commands/typescript-fix.md +65 -0
  148. package/src/skills/typescript-expert/references/advanced-conditional-types.md +190 -0
  149. package/src/skills/typescript-expert/references/advanced-decorators.md +243 -0
  150. package/src/skills/typescript-expert/references/advanced-mapped-types.md +223 -0
  151. package/src/skills/typescript-expert/references/advanced-template-literals.md +209 -0
  152. package/src/skills/typescript-expert/references/advanced-type-guards.md +308 -0
  153. package/src/skills/typescript-expert/references/best-practices-patterns.md +313 -0
  154. package/src/skills/typescript-expert/references/best-practices-performance.md +185 -0
  155. package/src/skills/typescript-expert/references/best-practices-tsconfig.md +242 -0
  156. package/src/skills/typescript-expert/references/core-generics.md +246 -0
  157. package/src/skills/typescript-expert/references/core-interfaces-types.md +231 -0
  158. package/src/skills/typescript-expert/references/core-type-system.md +261 -0
  159. package/src/skills/typescript-expert/references/core-utility-types.md +235 -0
  160. package/src/skills/typescript-expert/references/features-ts5x.md +370 -0
  161. package/src/skills/vite-best-practices/SKILL.md +115 -0
  162. package/src/skills/vite-best-practices/references/build-and-ssr.md +255 -0
  163. package/src/skills/vite-best-practices/references/core-config.md +231 -0
  164. package/src/skills/vite-best-practices/references/core-features.md +222 -0
  165. package/src/skills/vite-best-practices/references/core-plugin-api.md +294 -0
  166. package/src/skills/vite-best-practices/references/environment-api.md +108 -0
  167. package/src/skills/vite-best-practices/references/rolldown-migration.md +242 -0
  168. package/codex-cli-workspace/iteration-1/benchmark.json +0 -122
  169. package/codex-cli-workspace/iteration-1/eval-1-ci-integration/eval_metadata.json +0 -13
  170. package/codex-cli-workspace/iteration-1/eval-1-ci-integration/with_skill/grading.json +0 -52
  171. package/codex-cli-workspace/iteration-1/eval-1-ci-integration/with_skill/outputs/response.md +0 -163
  172. package/codex-cli-workspace/iteration-1/eval-1-ci-integration/with_skill/timing.json +0 -5
  173. package/codex-cli-workspace/iteration-1/eval-1-ci-integration/without_skill/grading.json +0 -58
  174. package/codex-cli-workspace/iteration-1/eval-1-ci-integration/without_skill/outputs/response.md +0 -151
  175. package/codex-cli-workspace/iteration-1/eval-1-ci-integration/without_skill/timing.json +0 -5
  176. package/codex-cli-workspace/iteration-1/eval-2-mcp-server-config/eval_metadata.json +0 -13
  177. package/codex-cli-workspace/iteration-1/eval-2-mcp-server-config/with_skill/grading.json +0 -52
  178. package/codex-cli-workspace/iteration-1/eval-2-mcp-server-config/with_skill/outputs/response.md +0 -86
  179. package/codex-cli-workspace/iteration-1/eval-2-mcp-server-config/with_skill/timing.json +0 -5
  180. package/codex-cli-workspace/iteration-1/eval-2-mcp-server-config/without_skill/grading.json +0 -58
  181. package/codex-cli-workspace/iteration-1/eval-2-mcp-server-config/without_skill/outputs/response.md +0 -164
  182. package/codex-cli-workspace/iteration-1/eval-2-mcp-server-config/without_skill/timing.json +0 -5
  183. package/codex-cli-workspace/iteration-1/eval-3-profiles-troubleshooting/eval_metadata.json +0 -13
  184. package/codex-cli-workspace/iteration-1/eval-3-profiles-troubleshooting/with_skill/grading.json +0 -52
  185. package/codex-cli-workspace/iteration-1/eval-3-profiles-troubleshooting/with_skill/outputs/response.md +0 -130
  186. package/codex-cli-workspace/iteration-1/eval-3-profiles-troubleshooting/with_skill/timing.json +0 -5
  187. package/codex-cli-workspace/iteration-1/eval-3-profiles-troubleshooting/without_skill/grading.json +0 -64
  188. package/codex-cli-workspace/iteration-1/eval-3-profiles-troubleshooting/without_skill/outputs/response.md +0 -209
  189. package/codex-cli-workspace/iteration-1/eval-3-profiles-troubleshooting/without_skill/timing.json +0 -5
  190. package/codex-cli-workspace/iteration-1/review.html +0 -1325
  191. package/gemini-cli-workspace/iteration-1/benchmark.json +0 -86
  192. package/gemini-cli-workspace/iteration-1/eval-1-cicd-setup/eval_metadata.json +0 -37
  193. package/gemini-cli-workspace/iteration-1/eval-1-cicd-setup/with_skill/grading.json +0 -37
  194. package/gemini-cli-workspace/iteration-1/eval-1-cicd-setup/with_skill/outputs/response.md +0 -401
  195. package/gemini-cli-workspace/iteration-1/eval-1-cicd-setup/with_skill/timing.json +0 -5
  196. package/gemini-cli-workspace/iteration-1/eval-1-cicd-setup/without_skill/grading.json +0 -37
  197. package/gemini-cli-workspace/iteration-1/eval-1-cicd-setup/without_skill/outputs/response.md +0 -405
  198. package/gemini-cli-workspace/iteration-1/eval-1-cicd-setup/without_skill/timing.json +0 -5
  199. package/gemini-cli-workspace/iteration-1/eval-2-mcp-server-config/eval_metadata.json +0 -37
  200. package/gemini-cli-workspace/iteration-1/eval-2-mcp-server-config/with_skill/grading.json +0 -37
  201. package/gemini-cli-workspace/iteration-1/eval-2-mcp-server-config/with_skill/outputs/response.md +0 -212
  202. package/gemini-cli-workspace/iteration-1/eval-2-mcp-server-config/with_skill/timing.json +0 -5
  203. package/gemini-cli-workspace/iteration-1/eval-2-mcp-server-config/without_skill/grading.json +0 -37
  204. package/gemini-cli-workspace/iteration-1/eval-2-mcp-server-config/without_skill/outputs/response.md +0 -427
  205. package/gemini-cli-workspace/iteration-1/eval-2-mcp-server-config/without_skill/timing.json +0 -5
  206. package/gemini-cli-workspace/iteration-1/eval-3-custom-slash-command/eval_metadata.json +0 -32
  207. package/gemini-cli-workspace/iteration-1/eval-3-custom-slash-command/with_skill/grading.json +0 -32
  208. package/gemini-cli-workspace/iteration-1/eval-3-custom-slash-command/with_skill/outputs/response.md +0 -171
  209. package/gemini-cli-workspace/iteration-1/eval-3-custom-slash-command/with_skill/timing.json +0 -5
  210. package/gemini-cli-workspace/iteration-1/eval-3-custom-slash-command/without_skill/grading.json +0 -32
  211. package/gemini-cli-workspace/iteration-1/eval-3-custom-slash-command/without_skill/outputs/response.md +0 -199
  212. package/gemini-cli-workspace/iteration-1/eval-3-custom-slash-command/without_skill/timing.json +0 -5
  213. package/gemini-cli-workspace/iteration-1/review.html +0 -1325
  214. package/gemini-cli-workspace/iteration-2/benchmark.json +0 -173
  215. package/gemini-cli-workspace/iteration-2/benchmark.md +0 -28
  216. package/gemini-cli-workspace/iteration-2/eval-1-cicd-setup/eval_metadata.json +0 -37
  217. package/gemini-cli-workspace/iteration-2/eval-1-cicd-setup/with_skill/grading.json +0 -37
  218. package/gemini-cli-workspace/iteration-2/eval-1-cicd-setup/with_skill/outputs/response.md +0 -195
  219. package/gemini-cli-workspace/iteration-2/eval-1-cicd-setup/with_skill/timing.json +0 -5
  220. package/gemini-cli-workspace/iteration-2/eval-1-cicd-setup/without_skill/grading.json +0 -37
  221. package/gemini-cli-workspace/iteration-2/eval-1-cicd-setup/without_skill/outputs/response.md +0 -377
  222. package/gemini-cli-workspace/iteration-2/eval-1-cicd-setup/without_skill/timing.json +0 -5
  223. package/gemini-cli-workspace/iteration-2/eval-2-mcp-server-config/eval_metadata.json +0 -37
  224. package/gemini-cli-workspace/iteration-2/eval-2-mcp-server-config/with_skill/grading.json +0 -37
  225. package/gemini-cli-workspace/iteration-2/eval-2-mcp-server-config/with_skill/outputs/response.md +0 -127
  226. package/gemini-cli-workspace/iteration-2/eval-2-mcp-server-config/with_skill/timing.json +0 -5
  227. package/gemini-cli-workspace/iteration-2/eval-2-mcp-server-config/without_skill/grading.json +0 -37
  228. package/gemini-cli-workspace/iteration-2/eval-2-mcp-server-config/without_skill/outputs/response.md +0 -164
  229. package/gemini-cli-workspace/iteration-2/eval-2-mcp-server-config/without_skill/timing.json +0 -5
  230. package/gemini-cli-workspace/iteration-2/eval-3-custom-slash-command/eval_metadata.json +0 -32
  231. package/gemini-cli-workspace/iteration-2/eval-3-custom-slash-command/with_skill/grading.json +0 -32
  232. package/gemini-cli-workspace/iteration-2/eval-3-custom-slash-command/with_skill/outputs/response.md +0 -91
  233. package/gemini-cli-workspace/iteration-2/eval-3-custom-slash-command/with_skill/timing.json +0 -5
  234. package/gemini-cli-workspace/iteration-2/eval-3-custom-slash-command/without_skill/grading.json +0 -32
  235. package/gemini-cli-workspace/iteration-2/eval-3-custom-slash-command/without_skill/outputs/response.md +0 -112
  236. package/gemini-cli-workspace/iteration-2/eval-3-custom-slash-command/without_skill/timing.json +0 -5
  237. package/gemini-cli-workspace/iteration-2/eval-viewer.html +0 -1325
  238. package/screen-recording-workspace/evals.json +0 -41
  239. package/screen-recording-workspace/iteration-1/benchmark.json +0 -102
  240. package/screen-recording-workspace/iteration-1/eval-0-fullscreen/eval_metadata.json +0 -31
  241. package/screen-recording-workspace/iteration-1/eval-0-fullscreen/with_skill/grading.json +0 -11
  242. package/screen-recording-workspace/iteration-1/eval-0-fullscreen/with_skill/outputs/demo.mp4 +0 -0
  243. package/screen-recording-workspace/iteration-1/eval-0-fullscreen/with_skill/timing.json +0 -5
  244. package/screen-recording-workspace/iteration-1/eval-0-fullscreen/without_skill/grading.json +0 -11
  245. package/screen-recording-workspace/iteration-1/eval-0-fullscreen/without_skill/outputs/demo.mp4 +0 -0
  246. package/screen-recording-workspace/iteration-1/eval-0-fullscreen/without_skill/timing.json +0 -5
  247. package/screen-recording-workspace/iteration-1/eval-1-region-audio/eval_metadata.json +0 -31
  248. package/screen-recording-workspace/iteration-1/eval-1-region-audio/with_skill/grading.json +0 -11
  249. package/screen-recording-workspace/iteration-1/eval-1-region-audio/with_skill/outputs/region_capture.mp4 +0 -0
  250. package/screen-recording-workspace/iteration-1/eval-1-region-audio/with_skill/timing.json +0 -5
  251. package/screen-recording-workspace/iteration-1/eval-1-region-audio/without_skill/grading.json +0 -11
  252. package/screen-recording-workspace/iteration-1/eval-1-region-audio/without_skill/outputs/region_capture.mp4 +0 -0
  253. package/screen-recording-workspace/iteration-1/eval-1-region-audio/without_skill/timing.json +0 -5
  254. package/screen-recording-workspace/iteration-1/eval-2-python-fallback/eval_metadata.json +0 -31
  255. package/screen-recording-workspace/iteration-1/eval-2-python-fallback/with_skill/grading.json +0 -11
  256. package/screen-recording-workspace/iteration-1/eval-2-python-fallback/with_skill/outputs/fallback_recording.mp4 +0 -0
  257. package/screen-recording-workspace/iteration-1/eval-2-python-fallback/with_skill/timing.json +0 -5
  258. package/screen-recording-workspace/iteration-1/eval-2-python-fallback/without_skill/grading.json +0 -11
  259. package/screen-recording-workspace/iteration-1/eval-2-python-fallback/without_skill/outputs/fallback_recording.mp4 +0 -0
  260. package/screen-recording-workspace/iteration-1/eval-2-python-fallback/without_skill/outputs/record_screen.py +0 -67
  261. package/screen-recording-workspace/iteration-1/eval-2-python-fallback/without_skill/timing.json +0 -5
  262. package/screen-recording-workspace/iteration-1/review.html +0 -1325
  263. package/src/skills/codex-cli/evals/evals.json +0 -47
  264. package/src/skills/gemini-cli/evals/evals.json +0 -46
  265. package/src/skills/tm-search/evals/evals.json +0 -23
@@ -0,0 +1,323 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ CAPTCHA solver — submits to 2captcha, CapMonster, or Anti-Captcha and returns a token.
4
+
5
+ No external dependencies required (uses Python stdlib only).
6
+
7
+ Usage:
8
+ python solve_captcha.py --type recaptcha-v2 --sitekey KEY --pageurl URL
9
+ python solve_captcha.py --type recaptcha-v3 --sitekey KEY --pageurl URL --action verify
10
+ python solve_captcha.py --type hcaptcha --sitekey KEY --pageurl URL
11
+ python solve_captcha.py --type turnstile --sitekey KEY --pageurl URL
12
+ python solve_captcha.py --type image --image path/to/captcha.png
13
+
14
+ Environment variables:
15
+ CAPTCHA_API_KEY — API key for the solving service
16
+ CAPTCHA_SERVICE — "2captcha" (default) | "capmonster" | "anticaptcha"
17
+
18
+ Output (JSON to stdout):
19
+ {"success": true, "token": "03AGdBq25...", "type": "recaptcha-v2"}
20
+ {"success": false, "error": "ERROR_ZERO_BALANCE"}
21
+ """
22
+
23
+ import argparse
24
+ import base64
25
+ import json
26
+ import os
27
+ import sys
28
+ import time
29
+ import urllib.error
30
+ import urllib.parse
31
+ import urllib.request
32
+
33
+
34
+ # Base URLs for each service — all support the same 2captcha-compatible API
35
+ SERVICE_URLS = {
36
+ "2captcha": "https://2captcha.com",
37
+ "capmonster": "https://api.capmonster.cloud",
38
+ "anticaptcha": "https://api.anti-captcha.com",
39
+ }
40
+
41
+ # How long to wait before first poll (solvers need processing time)
42
+ INITIAL_WAIT = 15 # seconds
43
+
44
+ # Interval between poll attempts
45
+ POLL_INTERVAL = 5 # seconds
46
+
47
+
48
+ # ---------------------------------------------------------------------------
49
+ # HTTP helpers (stdlib only)
50
+ # ---------------------------------------------------------------------------
51
+
52
+ def _post(url, data):
53
+ """HTTP POST with form-encoded body. Returns response text."""
54
+ payload = urllib.parse.urlencode(data).encode()
55
+ req = urllib.request.Request(url, data=payload, method="POST")
56
+ req.add_header("Content-Type", "application/x-www-form-urlencoded")
57
+ try:
58
+ with urllib.request.urlopen(req, timeout=30) as resp:
59
+ return resp.read().decode()
60
+ except urllib.error.HTTPError as e:
61
+ raise RuntimeError(f"HTTP {e.code}: {e.read().decode()}")
62
+
63
+
64
+ def _get(url):
65
+ """HTTP GET. Returns response text."""
66
+ try:
67
+ with urllib.request.urlopen(url, timeout=30) as resp:
68
+ return resp.read().decode()
69
+ except urllib.error.HTTPError as e:
70
+ raise RuntimeError(f"HTTP {e.code}: {e.read().decode()}")
71
+
72
+
73
+ # ---------------------------------------------------------------------------
74
+ # Task submission
75
+ # ---------------------------------------------------------------------------
76
+
77
+ def _submit(api_key, service, data):
78
+ """Submit a task. Returns the task ID string."""
79
+ base = SERVICE_URLS.get(service, SERVICE_URLS["2captcha"])
80
+ data = {**data, "key": api_key, "json": "1"}
81
+ resp = _post(f"{base}/in.php", data)
82
+ result = json.loads(resp)
83
+ if result.get("status") != 1:
84
+ raise RuntimeError(result.get("request", str(result)))
85
+ return str(result["request"])
86
+
87
+
88
+ def submit_recaptcha_v2(api_key, sitekey, pageurl, service="2captcha", invisible=False):
89
+ data = {
90
+ "method": "userrecaptcha",
91
+ "googlekey": sitekey,
92
+ "pageurl": pageurl,
93
+ }
94
+ if invisible:
95
+ data["invisible"] = "1"
96
+ return _submit(api_key, service, data)
97
+
98
+
99
+ def submit_recaptcha_v3(api_key, sitekey, pageurl, action="verify",
100
+ min_score=0.5, service="2captcha"):
101
+ return _submit(api_key, service, {
102
+ "method": "userrecaptcha",
103
+ "version": "v3",
104
+ "googlekey": sitekey,
105
+ "pageurl": pageurl,
106
+ "action": action,
107
+ "min_score": str(min_score),
108
+ })
109
+
110
+
111
+ def submit_hcaptcha(api_key, sitekey, pageurl, service="2captcha"):
112
+ return _submit(api_key, service, {
113
+ "method": "hcaptcha",
114
+ "sitekey": sitekey,
115
+ "pageurl": pageurl,
116
+ })
117
+
118
+
119
+ def submit_turnstile(api_key, sitekey, pageurl, service="2captcha"):
120
+ return _submit(api_key, service, {
121
+ "method": "turnstile",
122
+ "sitekey": sitekey,
123
+ "pageurl": pageurl,
124
+ })
125
+
126
+
127
+ def submit_image(api_key, image_source, service="2captcha"):
128
+ """image_source: file path or raw base64 string."""
129
+ if os.path.isfile(image_source):
130
+ with open(image_source, "rb") as f:
131
+ img_b64 = base64.b64encode(f.read()).decode()
132
+ else:
133
+ img_b64 = image_source # assume already base64
134
+ return _submit(api_key, service, {
135
+ "method": "base64",
136
+ "body": img_b64,
137
+ })
138
+
139
+
140
+ # ---------------------------------------------------------------------------
141
+ # Polling
142
+ # ---------------------------------------------------------------------------
143
+
144
+ def poll(api_key, task_id, service="2captcha", timeout=120):
145
+ """Poll until solved. Returns the token string."""
146
+ base = SERVICE_URLS.get(service, SERVICE_URLS["2captcha"])
147
+ url = (
148
+ f"{base}/res.php"
149
+ f"?key={urllib.parse.quote(api_key)}"
150
+ f"&action=get"
151
+ f"&id={urllib.parse.quote(task_id)}"
152
+ f"&json=1"
153
+ )
154
+
155
+ print(f"[captcha] Waiting {INITIAL_WAIT}s for solver...", file=sys.stderr)
156
+ time.sleep(INITIAL_WAIT)
157
+
158
+ deadline = time.time() + timeout
159
+ while time.time() < deadline:
160
+ resp = _get(url)
161
+ result = json.loads(resp)
162
+
163
+ if result.get("status") == 1:
164
+ return result["request"]
165
+
166
+ if result.get("request") == "CAPCHA_NOT_READY":
167
+ remaining = int(deadline - time.time())
168
+ print(f"[captcha] Not ready yet, polling again in {POLL_INTERVAL}s "
169
+ f"({remaining}s remaining)...", file=sys.stderr)
170
+ time.sleep(POLL_INTERVAL)
171
+ continue
172
+
173
+ # Any other response is an error
174
+ raise RuntimeError(result.get("request", str(result)))
175
+
176
+ raise TimeoutError(f"CAPTCHA not solved within {timeout}s. "
177
+ "Try increasing --timeout or check service status.")
178
+
179
+
180
+ # ---------------------------------------------------------------------------
181
+ # High-level solve()
182
+ # ---------------------------------------------------------------------------
183
+
184
+ def solve(captcha_type, api_key, service="2captcha", **kwargs):
185
+ """
186
+ Solve a CAPTCHA and return the token string.
187
+
188
+ captcha_type: recaptcha-v2 | recaptcha-v3 | hcaptcha | turnstile | image
189
+ Required kwargs by type:
190
+ recaptcha-v2/v3, hcaptcha, turnstile: sitekey, pageurl
191
+ image: image (file path or base64)
192
+ Optional:
193
+ invisible (bool) — for recaptcha-v2
194
+ action (str) — for recaptcha-v3, default "verify"
195
+ min_score (float) — for recaptcha-v3, default 0.5
196
+ timeout (int) — max seconds to wait, default 120
197
+ """
198
+ dispatch = {
199
+ "recaptcha-v2": lambda: submit_recaptcha_v2(
200
+ api_key, kwargs["sitekey"], kwargs["pageurl"],
201
+ service=service, invisible=kwargs.get("invisible", False)
202
+ ),
203
+ "recaptcha2": lambda: submit_recaptcha_v2(
204
+ api_key, kwargs["sitekey"], kwargs["pageurl"],
205
+ service=service, invisible=kwargs.get("invisible", False)
206
+ ),
207
+ "recaptcha-v3": lambda: submit_recaptcha_v3(
208
+ api_key, kwargs["sitekey"], kwargs["pageurl"],
209
+ action=kwargs.get("action", "verify"),
210
+ min_score=kwargs.get("min_score", 0.5),
211
+ service=service
212
+ ),
213
+ "recaptcha3": lambda: submit_recaptcha_v3(
214
+ api_key, kwargs["sitekey"], kwargs["pageurl"],
215
+ action=kwargs.get("action", "verify"),
216
+ min_score=kwargs.get("min_score", 0.5),
217
+ service=service
218
+ ),
219
+ "hcaptcha": lambda: submit_hcaptcha(
220
+ api_key, kwargs["sitekey"], kwargs["pageurl"], service=service
221
+ ),
222
+ "turnstile": lambda: submit_turnstile(
223
+ api_key, kwargs["sitekey"], kwargs["pageurl"], service=service
224
+ ),
225
+ "image": lambda: submit_image(api_key, kwargs["image"], service=service),
226
+ }
227
+
228
+ submit_fn = dispatch.get(captcha_type)
229
+ if submit_fn is None:
230
+ raise ValueError(
231
+ f"Unknown CAPTCHA type: {captcha_type!r}. "
232
+ f"Choose from: {', '.join(sorted(set(dispatch.keys())))}"
233
+ )
234
+
235
+ task_id = submit_fn()
236
+ print(f"[captcha] Task submitted (id={task_id})", file=sys.stderr)
237
+ return poll(api_key, task_id, service=service, timeout=kwargs.get("timeout", 120))
238
+
239
+
240
+ # ---------------------------------------------------------------------------
241
+ # CLI
242
+ # ---------------------------------------------------------------------------
243
+
244
+ def main():
245
+ parser = argparse.ArgumentParser(
246
+ description="Solve a CAPTCHA via a third-party service and print the token as JSON.",
247
+ formatter_class=argparse.RawDescriptionHelpFormatter,
248
+ epilog=__doc__,
249
+ )
250
+ parser.add_argument(
251
+ "--type", required=True,
252
+ choices=["recaptcha-v2", "recaptcha-v3", "hcaptcha", "turnstile", "image"],
253
+ help="CAPTCHA type",
254
+ )
255
+ parser.add_argument("--sitekey", help="CAPTCHA sitekey (recaptcha/hcaptcha/turnstile)")
256
+ parser.add_argument("--pageurl", help="Full URL of the page with the CAPTCHA")
257
+ parser.add_argument("--image", help="Image file path or base64 string (for --type image)")
258
+ parser.add_argument(
259
+ "--api-key",
260
+ default=os.environ.get("CAPTCHA_API_KEY"),
261
+ help="Solving service API key (default: $CAPTCHA_API_KEY)",
262
+ )
263
+ parser.add_argument(
264
+ "--service",
265
+ default=os.environ.get("CAPTCHA_SERVICE", "2captcha"),
266
+ choices=list(SERVICE_URLS.keys()),
267
+ help="Solving service to use (default: 2captcha)",
268
+ )
269
+ parser.add_argument("--action", default="verify", help="reCAPTCHA v3 action name")
270
+ parser.add_argument("--min-score", type=float, default=0.5,
271
+ help="reCAPTCHA v3 minimum acceptable score (0.1–0.9)")
272
+ parser.add_argument("--invisible", action="store_true",
273
+ help="Use invisible reCAPTCHA v2 mode")
274
+ parser.add_argument("--timeout", type=int, default=120,
275
+ help="Max seconds to wait for a solution (default: 120)")
276
+
277
+ args = parser.parse_args()
278
+
279
+ if not args.api_key:
280
+ out = {"success": False, "error":
281
+ "No API key provided. Use --api-key or set CAPTCHA_API_KEY env var."}
282
+ print(json.dumps(out))
283
+ sys.exit(1)
284
+
285
+ # Validate required args per type
286
+ needs_sitekey = args.type in ("recaptcha-v2", "recaptcha-v3", "hcaptcha", "turnstile")
287
+ if needs_sitekey and not args.sitekey:
288
+ print(json.dumps({"success": False,
289
+ "error": f"--sitekey is required for --type {args.type}"}))
290
+ sys.exit(1)
291
+ if needs_sitekey and not args.pageurl:
292
+ print(json.dumps({"success": False,
293
+ "error": f"--pageurl is required for --type {args.type}"}))
294
+ sys.exit(1)
295
+ if args.type == "image" and not args.image:
296
+ print(json.dumps({"success": False,
297
+ "error": "--image is required for --type image"}))
298
+ sys.exit(1)
299
+
300
+ try:
301
+ token = solve(
302
+ args.type,
303
+ args.api_key,
304
+ service=args.service,
305
+ sitekey=args.sitekey,
306
+ pageurl=args.pageurl,
307
+ image=args.image,
308
+ action=args.action,
309
+ min_score=args.min_score,
310
+ invisible=args.invisible,
311
+ timeout=args.timeout,
312
+ )
313
+ print(json.dumps({"success": True, "token": token, "type": args.type}))
314
+ except TimeoutError as e:
315
+ print(json.dumps({"success": False, "error": str(e)}))
316
+ sys.exit(1)
317
+ except Exception as e:
318
+ print(json.dumps({"success": False, "error": str(e)}))
319
+ sys.exit(1)
320
+
321
+
322
+ if __name__ == "__main__":
323
+ main()
@@ -0,0 +1,350 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Solve reCAPTCHA v2 image grid challenges using vision AI.
4
+
5
+ After clicking the checkbox, if an image grid appears ("select all traffic
6
+ lights"), this script screenshots the grid, sends it to Claude or GPT-4V,
7
+ gets back which cells to click, and handles multiple rounds automatically.
8
+
9
+ Usage in your Playwright script:
10
+ from solve_image_grid import solve_image_grid
11
+
12
+ # Click the checkbox first
13
+ page.frame_locator('iframe[title="reCAPTCHA"]') \\
14
+ .locator('.recaptcha-checkbox-border').click()
15
+
16
+ # Solve the grid (if it appears)
17
+ solved = solve_image_grid(page)
18
+
19
+ Standalone test (opens the Google demo):
20
+ python solve_image_grid.py
21
+
22
+ Requirements:
23
+ pip install playwright anthropic # Claude (recommended)
24
+ # or: pip install playwright openai # GPT-4V fallback
25
+
26
+ Environment:
27
+ ANTHROPIC_API_KEY — Claude API key (preferred, better spatial reasoning)
28
+ OPENAI_API_KEY — OpenAI API key (fallback)
29
+ """
30
+
31
+ import base64
32
+ import json
33
+ import os
34
+ import re
35
+ import sys
36
+ import time
37
+ import random
38
+
39
+ # ---------------------------------------------------------------------------
40
+ # Timing constants
41
+ # ---------------------------------------------------------------------------
42
+
43
+ CELL_CLICK_DELAY = (0.25, 0.65) # random pause between cell clicks (seconds)
44
+ VERIFY_WAIT_SEC = 2.5 # pause after clicking Verify before checking result
45
+ ROUND_DELAY = (1.5, 3.0) # pause between rounds
46
+
47
+ # ---------------------------------------------------------------------------
48
+ # Selectors
49
+ # ---------------------------------------------------------------------------
50
+
51
+ CHECKBOX_IFRAME = 'iframe[title="reCAPTCHA"]'
52
+
53
+ # The challenge iframe may have slightly different titles across reCAPTCHA versions
54
+ CHALLENGE_IFRAME_SELECTORS = [
55
+ 'iframe[title*="recaptcha challenge"]',
56
+ 'iframe[src*="recaptcha/api2/bframe"]',
57
+ 'iframe[src*="recaptcha/enterprise/bframe"]',
58
+ ]
59
+
60
+ # Selectors *inside* the challenge iframe
61
+ TASK_TEXT_SEL = '.rc-imageselect-desc-no-canonical, .rc-imageselect-desc'
62
+ GRID_SEL = '.rc-imageselect-table-33, .rc-imageselect-table-44, table.rc-imageselect-table'
63
+ CELL_SEL = 'td.rc-imageselect-tile, .rc-imageselect-tile'
64
+ VERIFY_BTN = '#recaptcha-verify-button'
65
+
66
+
67
+ # ---------------------------------------------------------------------------
68
+ # Vision AI: ask which cells match the task
69
+ # ---------------------------------------------------------------------------
70
+
71
+ def _prompt(task_text: str, grid_size: int) -> str:
72
+ n = grid_size * grid_size
73
+ return (
74
+ f'This is a reCAPTCHA image grid ({grid_size}×{grid_size}, {n} cells total).\n'
75
+ f'Task: "{task_text}"\n\n'
76
+ f'Number the cells 1–{n} left-to-right, top-to-bottom (row 1 first).\n'
77
+ f'Return ONLY a JSON array of the cell numbers that match the task.\n'
78
+ f'Examples: [2,5,8] or [] (empty if none match)\n'
79
+ f'No explanation — just the array.'
80
+ )
81
+
82
+
83
+ def _call_claude(image_b64: str, task_text: str, grid_size: int) -> list:
84
+ import anthropic
85
+ client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
86
+ msg = client.messages.create(
87
+ model="claude-opus-4-6",
88
+ max_tokens=64,
89
+ messages=[{
90
+ "role": "user",
91
+ "content": [
92
+ {
93
+ "type": "image",
94
+ "source": {
95
+ "type": "base64",
96
+ "media_type": "image/png",
97
+ "data": image_b64,
98
+ },
99
+ },
100
+ {"type": "text", "text": _prompt(task_text, grid_size)},
101
+ ],
102
+ }],
103
+ )
104
+ return _parse_response(msg.content[0].text, grid_size)
105
+
106
+
107
+ def _call_openai(image_b64: str, task_text: str, grid_size: int) -> list:
108
+ from openai import OpenAI
109
+ client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
110
+ resp = client.chat.completions.create(
111
+ model="gpt-4o",
112
+ max_tokens=64,
113
+ messages=[{
114
+ "role": "user",
115
+ "content": [
116
+ {
117
+ "type": "image_url",
118
+ "image_url": {"url": f"data:image/png;base64,{image_b64}"},
119
+ },
120
+ {"type": "text", "text": _prompt(task_text, grid_size)},
121
+ ],
122
+ }],
123
+ )
124
+ return _parse_response(resp.choices[0].message.content, grid_size)
125
+
126
+
127
+ def _parse_response(raw: str, grid_size: int) -> list:
128
+ """Extract a valid list of cell ints from the AI's raw response."""
129
+ match = re.search(r'\[.*?\]', raw.strip(), re.DOTALL)
130
+ if not match:
131
+ return []
132
+ try:
133
+ cells = json.loads(match.group())
134
+ n = grid_size * grid_size
135
+ return [c for c in cells if isinstance(c, int) and 1 <= c <= n]
136
+ except (json.JSONDecodeError, TypeError):
137
+ return []
138
+
139
+
140
+ def ask_vision_ai(image_b64: str, task_text: str, grid_size: int) -> list:
141
+ """
142
+ Ask the available vision AI which cells match the task.
143
+ Tries Claude first, falls back to GPT-4V.
144
+ Returns a list of 1-indexed cell numbers.
145
+ """
146
+ if os.environ.get("ANTHROPIC_API_KEY"):
147
+ try:
148
+ cells = _call_claude(image_b64, task_text, grid_size)
149
+ print(f"[grid] Claude selected: {cells}", file=sys.stderr)
150
+ return cells
151
+ except Exception as e:
152
+ print(f"[grid] Claude error: {e} — trying OpenAI...", file=sys.stderr)
153
+
154
+ if os.environ.get("OPENAI_API_KEY"):
155
+ cells = _call_openai(image_b64, task_text, grid_size)
156
+ print(f"[grid] GPT-4V selected: {cells}", file=sys.stderr)
157
+ return cells
158
+
159
+ raise RuntimeError(
160
+ "No vision AI key found. Set ANTHROPIC_API_KEY or OPENAI_API_KEY."
161
+ )
162
+
163
+
164
+ # ---------------------------------------------------------------------------
165
+ # Playwright helpers
166
+ # ---------------------------------------------------------------------------
167
+
168
+ def _find_challenge_frame(page):
169
+ """Return the challenge FrameLocator if visible, else None."""
170
+ for sel in CHALLENGE_IFRAME_SELECTORS:
171
+ try:
172
+ frame = page.frame_locator(sel)
173
+ if frame.locator(TASK_TEXT_SEL).is_visible(timeout=1500):
174
+ return frame
175
+ except Exception:
176
+ continue
177
+ return None
178
+
179
+
180
+ def _challenge_visible(page) -> bool:
181
+ for sel in CHALLENGE_IFRAME_SELECTORS:
182
+ try:
183
+ if page.locator(sel).is_visible(timeout=400):
184
+ return True
185
+ except Exception:
186
+ pass
187
+ return False
188
+
189
+
190
+ def _get_task(frame) -> str:
191
+ try:
192
+ return frame.locator(TASK_TEXT_SEL).first.inner_text(timeout=3000).strip()
193
+ except Exception:
194
+ return ""
195
+
196
+
197
+ def _get_grid_size(frame) -> int:
198
+ try:
199
+ if frame.locator('.rc-imageselect-table-44').count() > 0:
200
+ return 4
201
+ except Exception:
202
+ pass
203
+ return 3
204
+
205
+
206
+ def _screenshot_grid(frame) -> str:
207
+ """Screenshot just the image grid and return as base64 PNG."""
208
+ try:
209
+ png = frame.locator(GRID_SEL).first.screenshot()
210
+ except Exception:
211
+ png = frame.locator("body").screenshot()
212
+ return base64.b64encode(png).decode()
213
+
214
+
215
+ def _click_cells(frame, cell_numbers: list, grid_size: int) -> None:
216
+ """
217
+ Click the specified cells (1-indexed, left-to-right top-to-bottom).
218
+
219
+ Uses element-based clicking via frame_locator rather than raw pixel
220
+ coordinates — more reliable across different viewport sizes and DPIs.
221
+ """
222
+ if not cell_numbers:
223
+ return
224
+ cells = frame.locator(CELL_SEL).all()
225
+ for num in cell_numbers:
226
+ idx = num - 1
227
+ if idx < len(cells):
228
+ try:
229
+ cells[idx].click()
230
+ time.sleep(random.uniform(*CELL_CLICK_DELAY))
231
+ except Exception as e:
232
+ print(f"[grid] Cell {num} click failed: {e}", file=sys.stderr)
233
+
234
+
235
+ # ---------------------------------------------------------------------------
236
+ # Main public function
237
+ # ---------------------------------------------------------------------------
238
+
239
+ def solve_image_grid(page, max_rounds: int = 6) -> bool:
240
+ """
241
+ Solve a reCAPTCHA v2 image grid challenge on the given Playwright page.
242
+
243
+ Call this after clicking the reCAPTCHA checkbox. If a grid challenge
244
+ appears, the function loops through rounds (new grids keep appearing
245
+ until Google is satisfied) and clicks the correct cells each time.
246
+
247
+ If the checkbox passed without triggering a grid (common on trusted IPs
248
+ and during testing), the function returns True immediately.
249
+
250
+ Args:
251
+ page: Playwright Page object
252
+ max_rounds: Give up after this many rounds (default: 6)
253
+
254
+ Returns:
255
+ True — challenge closed (solved or no challenge appeared)
256
+ False — failed to solve within max_rounds or API error
257
+ """
258
+ # Wait briefly for the challenge to appear
259
+ print("[grid] Checking for image challenge...", file=sys.stderr)
260
+ for _ in range(8):
261
+ if _find_challenge_frame(page):
262
+ break
263
+ time.sleep(0.7)
264
+ else:
265
+ print("[grid] No grid challenge — checkbox solved directly.", file=sys.stderr)
266
+ return True
267
+
268
+ for round_num in range(1, max_rounds + 1):
269
+ frame = _find_challenge_frame(page)
270
+ if not frame:
271
+ print("[grid] Challenge closed — done.", file=sys.stderr)
272
+ return True
273
+
274
+ task_text = _get_task(frame)
275
+ grid_size = _get_grid_size(frame)
276
+ print(f"[grid] Round {round_num}: '{task_text}' ({grid_size}×{grid_size})",
277
+ file=sys.stderr)
278
+
279
+ image_b64 = _screenshot_grid(frame)
280
+
281
+ try:
282
+ cells = ask_vision_ai(image_b64, task_text, grid_size)
283
+ except Exception as e:
284
+ print(f"[grid] Vision AI failed: {e}", file=sys.stderr)
285
+ return False
286
+
287
+ _click_cells(frame, cells, grid_size)
288
+
289
+ # Small pause before clicking Verify (mimics human review time)
290
+ time.sleep(random.uniform(0.6, 1.4))
291
+
292
+ try:
293
+ frame.locator(VERIFY_BTN).click()
294
+ except Exception as e:
295
+ print(f"[grid] Verify click failed: {e}", file=sys.stderr)
296
+ return False
297
+
298
+ time.sleep(VERIFY_WAIT_SEC + random.uniform(0, 1))
299
+
300
+ if not _challenge_visible(page):
301
+ print("[grid] Challenge dismissed — solved.", file=sys.stderr)
302
+ return True
303
+
304
+ print("[grid] New round appeared.", file=sys.stderr)
305
+
306
+ print(f"[grid] Gave up after {max_rounds} rounds.", file=sys.stderr)
307
+ return False
308
+
309
+
310
+ # ---------------------------------------------------------------------------
311
+ # Standalone test
312
+ # ---------------------------------------------------------------------------
313
+
314
+ if __name__ == "__main__":
315
+ from playwright.sync_api import sync_playwright
316
+
317
+ if not (os.environ.get("ANTHROPIC_API_KEY") or os.environ.get("OPENAI_API_KEY")):
318
+ print("ERROR: set ANTHROPIC_API_KEY or OPENAI_API_KEY", file=sys.stderr)
319
+ sys.exit(1)
320
+
321
+ with sync_playwright() as p:
322
+ browser = p.chromium.launch(headless=False)
323
+ context = browser.new_context(
324
+ viewport={"width": 1280, "height": 800},
325
+ user_agent=(
326
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
327
+ "AppleWebKit/537.36 (KHTML, like Gecko) "
328
+ "Chrome/124.0.0.0 Safari/537.36"
329
+ ),
330
+ )
331
+ context.add_init_script(
332
+ "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"
333
+ )
334
+ page = context.new_page()
335
+ page.goto("https://www.google.com/recaptcha/api2/demo", wait_until="networkidle")
336
+
337
+ print("[main] Clicking checkbox...")
338
+ page.frame_locator(CHECKBOX_IFRAME) \
339
+ .locator(".recaptcha-checkbox-border").click()
340
+
341
+ ok = solve_image_grid(page)
342
+ print(f"[main] Grid result: {'SOLVED' if ok else 'FAILED'}")
343
+
344
+ if ok:
345
+ page.click("#recaptcha-demo-submit")
346
+ page.wait_for_timeout(3000)
347
+
348
+ page.screenshot(path="grid_result.png")
349
+ print("[main] Screenshot saved to grid_result.png")
350
+ browser.close()