bps-kit 1.0.1 → 1.0.2

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 (368) hide show
  1. package/package.json +1 -1
  2. package/templates/.agents/agents/backend-specialist.md +263 -0
  3. package/templates/.agents/agents/code-archaeologist.md +106 -0
  4. package/templates/.agents/agents/database-architect.md +226 -0
  5. package/templates/.agents/agents/debugger.md +225 -0
  6. package/templates/.agents/agents/devops-engineer.md +242 -0
  7. package/templates/.agents/agents/documentation-writer.md +104 -0
  8. package/templates/.agents/agents/explorer-agent.md +73 -0
  9. package/templates/.agents/agents/frontend-specialist.md +593 -0
  10. package/templates/.agents/agents/game-developer.md +162 -0
  11. package/templates/.agents/agents/mobile-developer.md +377 -0
  12. package/templates/.agents/agents/orchestrator.md +416 -0
  13. package/templates/.agents/agents/penetration-tester.md +188 -0
  14. package/templates/.agents/agents/performance-optimizer.md +187 -0
  15. package/templates/.agents/agents/product-manager.md +112 -0
  16. package/templates/.agents/agents/product-owner.md +95 -0
  17. package/templates/.agents/agents/project-planner.md +406 -0
  18. package/templates/.agents/agents/qa-automation-engineer.md +103 -0
  19. package/templates/.agents/agents/security-auditor.md +170 -0
  20. package/templates/.agents/agents/seo-specialist.md +111 -0
  21. package/templates/.agents/agents/test-engineer.md +158 -0
  22. package/templates/.agents/rules/GEMINI.md +219 -0
  23. package/templates/.agents/scripts/auto_preview.py +148 -0
  24. package/templates/.agents/scripts/checklist.py +217 -0
  25. package/templates/.agents/scripts/session_manager.py +120 -0
  26. package/templates/.agents/scripts/verify_all.py +327 -0
  27. package/templates/.agents/workflows/brainstorm.md +113 -0
  28. package/templates/.agents/workflows/create.md +59 -0
  29. package/templates/.agents/workflows/debug.md +103 -0
  30. package/templates/.agents/workflows/deploy.md +176 -0
  31. package/templates/.agents/workflows/enhance.md +63 -0
  32. package/templates/.agents/workflows/orchestrate.md +237 -0
  33. package/templates/.agents/workflows/plan.md +89 -0
  34. package/templates/.agents/workflows/preview.md +81 -0
  35. package/templates/.agents/workflows/setup-brain.md +39 -0
  36. package/templates/.agents/workflows/status.md +86 -0
  37. package/templates/.agents/workflows/test.md +144 -0
  38. package/templates/.agents/workflows/ui-ux-pro-max.md +296 -0
  39. package/templates/skills_normal/api-patterns/scripts/api_validator.py +211 -0
  40. package/templates/skills_normal/database-design/scripts/schema_validator.py +172 -0
  41. package/templates/skills_normal/frontend-design/scripts/accessibility_checker.py +183 -0
  42. package/templates/skills_normal/frontend-design/scripts/ux_audit.py +722 -0
  43. package/templates/skills_normal/git-pushing/scripts/smart_commit.sh +19 -0
  44. package/templates/skills_normal/lint-and-validate/scripts/lint_runner.py +184 -0
  45. package/templates/skills_normal/lint-and-validate/scripts/type_coverage.py +173 -0
  46. package/templates/skills_normal/performance-profiling/scripts/lighthouse_audit.py +76 -0
  47. package/templates/skills_normal/senior-fullstack/scripts/code_quality_analyzer.py +114 -0
  48. package/templates/skills_normal/senior-fullstack/scripts/fullstack_scaffolder.py +114 -0
  49. package/templates/skills_normal/senior-fullstack/scripts/project_scaffolder.py +114 -0
  50. package/templates/skills_normal/seo-fundamentals/scripts/seo_checker.py +219 -0
  51. package/templates/skills_normal/testing-patterns/scripts/test_runner.py +219 -0
  52. package/templates/skills_normal/vulnerability-scanner/scripts/security_scan.py +458 -0
  53. package/templates/vault/007/scripts/config.py +472 -0
  54. package/templates/vault/007/scripts/full_audit.py +1306 -0
  55. package/templates/vault/007/scripts/quick_scan.py +481 -0
  56. package/templates/vault/007/scripts/requirements.txt +26 -0
  57. package/templates/vault/007/scripts/scanners/__init__.py +0 -0
  58. package/templates/vault/007/scripts/scanners/dependency_scanner.py +1305 -0
  59. package/templates/vault/007/scripts/scanners/injection_scanner.py +1104 -0
  60. package/templates/vault/007/scripts/scanners/secrets_scanner.py +1008 -0
  61. package/templates/vault/007/scripts/score_calculator.py +693 -0
  62. package/templates/vault/agent-orchestrator/scripts/match_skills.py +329 -0
  63. package/templates/vault/agent-orchestrator/scripts/orchestrate.py +304 -0
  64. package/templates/vault/agent-orchestrator/scripts/requirements.txt +1 -0
  65. package/templates/vault/agent-orchestrator/scripts/scan_registry.py +508 -0
  66. package/templates/vault/ai-studio-image/scripts/config.py +613 -0
  67. package/templates/vault/ai-studio-image/scripts/generate.py +630 -0
  68. package/templates/vault/ai-studio-image/scripts/prompt_engine.py +424 -0
  69. package/templates/vault/ai-studio-image/scripts/requirements.txt +4 -0
  70. package/templates/vault/ai-studio-image/scripts/templates.py +349 -0
  71. package/templates/vault/android_ui_verification/scripts/verify_ui.sh +32 -0
  72. package/templates/vault/apify-audience-analysis/reference/scripts/run_actor.js +363 -0
  73. package/templates/vault/apify-brand-reputation-monitoring/reference/scripts/run_actor.js +363 -0
  74. package/templates/vault/apify-competitor-intelligence/reference/scripts/run_actor.js +363 -0
  75. package/templates/vault/apify-content-analytics/reference/scripts/run_actor.js +363 -0
  76. package/templates/vault/apify-ecommerce/reference/scripts/package.json +3 -0
  77. package/templates/vault/apify-ecommerce/reference/scripts/run_actor.js +369 -0
  78. package/templates/vault/apify-influencer-discovery/reference/scripts/run_actor.js +363 -0
  79. package/templates/vault/apify-lead-generation/reference/scripts/run_actor.js +363 -0
  80. package/templates/vault/apify-market-research/reference/scripts/run_actor.js +363 -0
  81. package/templates/vault/apify-trend-analysis/reference/scripts/run_actor.js +363 -0
  82. package/templates/vault/apify-ultimate-scraper/reference/scripts/run_actor.js +363 -0
  83. package/templates/vault/audio-transcriber/scripts/install-requirements.sh +190 -0
  84. package/templates/vault/audio-transcriber/scripts/transcribe.py +486 -0
  85. package/templates/vault/claude-monitor/scripts/api_bench.py +240 -0
  86. package/templates/vault/claude-monitor/scripts/config.py +69 -0
  87. package/templates/vault/claude-monitor/scripts/health_check.py +362 -0
  88. package/templates/vault/claude-monitor/scripts/monitor.py +296 -0
  89. package/templates/vault/content-creator/scripts/brand_voice_analyzer.py +185 -0
  90. package/templates/vault/content-creator/scripts/seo_optimizer.py +419 -0
  91. package/templates/vault/context-agent/scripts/active_context.py +227 -0
  92. package/templates/vault/context-agent/scripts/compressor.py +149 -0
  93. package/templates/vault/context-agent/scripts/config.py +69 -0
  94. package/templates/vault/context-agent/scripts/context_loader.py +155 -0
  95. package/templates/vault/context-agent/scripts/context_manager.py +302 -0
  96. package/templates/vault/context-agent/scripts/models.py +103 -0
  97. package/templates/vault/context-agent/scripts/project_registry.py +132 -0
  98. package/templates/vault/context-agent/scripts/requirements.txt +6 -0
  99. package/templates/vault/context-agent/scripts/search.py +115 -0
  100. package/templates/vault/context-agent/scripts/session_parser.py +206 -0
  101. package/templates/vault/context-agent/scripts/session_summary.py +319 -0
  102. package/templates/vault/context-guardian/scripts/context_snapshot.py +229 -0
  103. package/templates/vault/docx/ooxml/scripts/pack.py +159 -0
  104. package/templates/vault/docx/ooxml/scripts/unpack.py +29 -0
  105. package/templates/vault/docx/ooxml/scripts/validate.py +69 -0
  106. package/templates/vault/docx/ooxml/scripts/validation/__init__.py +15 -0
  107. package/templates/vault/docx/ooxml/scripts/validation/base.py +951 -0
  108. package/templates/vault/docx/ooxml/scripts/validation/docx.py +274 -0
  109. package/templates/vault/docx/ooxml/scripts/validation/pptx.py +315 -0
  110. package/templates/vault/docx/ooxml/scripts/validation/redlining.py +279 -0
  111. package/templates/vault/docx/scripts/__init__.py +1 -0
  112. package/templates/vault/docx/scripts/document.py +1276 -0
  113. package/templates/vault/docx/scripts/templates/comments.xml +3 -0
  114. package/templates/vault/docx/scripts/templates/commentsExtended.xml +3 -0
  115. package/templates/vault/docx/scripts/templates/commentsExtensible.xml +3 -0
  116. package/templates/vault/docx/scripts/templates/commentsIds.xml +3 -0
  117. package/templates/vault/docx/scripts/templates/people.xml +3 -0
  118. package/templates/vault/docx/scripts/utilities.py +374 -0
  119. package/templates/vault/docx-official/ooxml/scripts/pack.py +159 -0
  120. package/templates/vault/docx-official/ooxml/scripts/unpack.py +29 -0
  121. package/templates/vault/docx-official/ooxml/scripts/validate.py +69 -0
  122. package/templates/vault/docx-official/ooxml/scripts/validation/__init__.py +15 -0
  123. package/templates/vault/docx-official/ooxml/scripts/validation/base.py +951 -0
  124. package/templates/vault/docx-official/ooxml/scripts/validation/docx.py +274 -0
  125. package/templates/vault/docx-official/ooxml/scripts/validation/pptx.py +315 -0
  126. package/templates/vault/docx-official/ooxml/scripts/validation/redlining.py +279 -0
  127. package/templates/vault/docx-official/scripts/__init__.py +1 -0
  128. package/templates/vault/docx-official/scripts/document.py +1276 -0
  129. package/templates/vault/docx-official/scripts/templates/comments.xml +3 -0
  130. package/templates/vault/docx-official/scripts/templates/commentsExtended.xml +3 -0
  131. package/templates/vault/docx-official/scripts/templates/commentsExtensible.xml +3 -0
  132. package/templates/vault/docx-official/scripts/templates/commentsIds.xml +3 -0
  133. package/templates/vault/docx-official/scripts/templates/people.xml +3 -0
  134. package/templates/vault/docx-official/scripts/utilities.py +374 -0
  135. package/templates/vault/geo-fundamentals/scripts/geo_checker.py +289 -0
  136. package/templates/vault/helm-chart-scaffolding/scripts/validate-chart.sh +244 -0
  137. package/templates/vault/i18n-localization/scripts/i18n_checker.py +241 -0
  138. package/templates/vault/instagram/scripts/account_setup.py +233 -0
  139. package/templates/vault/instagram/scripts/analyze.py +221 -0
  140. package/templates/vault/instagram/scripts/api_client.py +444 -0
  141. package/templates/vault/instagram/scripts/auth.py +411 -0
  142. package/templates/vault/instagram/scripts/comments.py +160 -0
  143. package/templates/vault/instagram/scripts/config.py +111 -0
  144. package/templates/vault/instagram/scripts/db.py +467 -0
  145. package/templates/vault/instagram/scripts/export.py +138 -0
  146. package/templates/vault/instagram/scripts/governance.py +233 -0
  147. package/templates/vault/instagram/scripts/hashtags.py +114 -0
  148. package/templates/vault/instagram/scripts/insights.py +170 -0
  149. package/templates/vault/instagram/scripts/media.py +65 -0
  150. package/templates/vault/instagram/scripts/messages.py +103 -0
  151. package/templates/vault/instagram/scripts/profile.py +58 -0
  152. package/templates/vault/instagram/scripts/publish.py +449 -0
  153. package/templates/vault/instagram/scripts/requirements.txt +5 -0
  154. package/templates/vault/instagram/scripts/run_all.py +189 -0
  155. package/templates/vault/instagram/scripts/schedule.py +189 -0
  156. package/templates/vault/instagram/scripts/serve_api.py +234 -0
  157. package/templates/vault/instagram/scripts/templates.py +155 -0
  158. package/templates/vault/junta-leiloeiros/scripts/db.py +216 -0
  159. package/templates/vault/junta-leiloeiros/scripts/export.py +137 -0
  160. package/templates/vault/junta-leiloeiros/scripts/requirements.txt +15 -0
  161. package/templates/vault/junta-leiloeiros/scripts/run_all.py +190 -0
  162. package/templates/vault/junta-leiloeiros/scripts/scraper/__init__.py +4 -0
  163. package/templates/vault/junta-leiloeiros/scripts/scraper/base_scraper.py +209 -0
  164. package/templates/vault/junta-leiloeiros/scripts/scraper/generic_scraper.py +110 -0
  165. package/templates/vault/junta-leiloeiros/scripts/scraper/jucap.py +110 -0
  166. package/templates/vault/junta-leiloeiros/scripts/scraper/juceac.py +72 -0
  167. package/templates/vault/junta-leiloeiros/scripts/scraper/juceal.py +72 -0
  168. package/templates/vault/junta-leiloeiros/scripts/scraper/juceb.py +68 -0
  169. package/templates/vault/junta-leiloeiros/scripts/scraper/jucec.py +63 -0
  170. package/templates/vault/junta-leiloeiros/scripts/scraper/jucema.py +211 -0
  171. package/templates/vault/junta-leiloeiros/scripts/scraper/jucemg.py +218 -0
  172. package/templates/vault/junta-leiloeiros/scripts/scraper/jucep.py +70 -0
  173. package/templates/vault/junta-leiloeiros/scripts/scraper/jucepa.py +74 -0
  174. package/templates/vault/junta-leiloeiros/scripts/scraper/jucepar.py +80 -0
  175. package/templates/vault/junta-leiloeiros/scripts/scraper/jucepe.py +78 -0
  176. package/templates/vault/junta-leiloeiros/scripts/scraper/jucepi.py +69 -0
  177. package/templates/vault/junta-leiloeiros/scripts/scraper/jucer.py +256 -0
  178. package/templates/vault/junta-leiloeiros/scripts/scraper/jucerja.py +170 -0
  179. package/templates/vault/junta-leiloeiros/scripts/scraper/jucern.py +71 -0
  180. package/templates/vault/junta-leiloeiros/scripts/scraper/jucesc.py +89 -0
  181. package/templates/vault/junta-leiloeiros/scripts/scraper/jucesp.py +233 -0
  182. package/templates/vault/junta-leiloeiros/scripts/scraper/jucetins.py +134 -0
  183. package/templates/vault/junta-leiloeiros/scripts/scraper/jucis_df.py +63 -0
  184. package/templates/vault/junta-leiloeiros/scripts/scraper/jucisrs.py +299 -0
  185. package/templates/vault/junta-leiloeiros/scripts/scraper/states.py +99 -0
  186. package/templates/vault/junta-leiloeiros/scripts/serve_api.py +164 -0
  187. package/templates/vault/junta-leiloeiros/scripts/web_scraper_fallback.py +233 -0
  188. package/templates/vault/last30days/scripts/last30days.py +521 -0
  189. package/templates/vault/last30days/scripts/lib/__init__.py +1 -0
  190. package/templates/vault/last30days/scripts/lib/cache.py +152 -0
  191. package/templates/vault/last30days/scripts/lib/dates.py +124 -0
  192. package/templates/vault/last30days/scripts/lib/dedupe.py +120 -0
  193. package/templates/vault/last30days/scripts/lib/env.py +149 -0
  194. package/templates/vault/last30days/scripts/lib/http.py +152 -0
  195. package/templates/vault/last30days/scripts/lib/models.py +175 -0
  196. package/templates/vault/last30days/scripts/lib/normalize.py +160 -0
  197. package/templates/vault/last30days/scripts/lib/openai_reddit.py +230 -0
  198. package/templates/vault/last30days/scripts/lib/reddit_enrich.py +232 -0
  199. package/templates/vault/last30days/scripts/lib/render.py +383 -0
  200. package/templates/vault/last30days/scripts/lib/schema.py +336 -0
  201. package/templates/vault/last30days/scripts/lib/score.py +311 -0
  202. package/templates/vault/last30days/scripts/lib/ui.py +324 -0
  203. package/templates/vault/last30days/scripts/lib/websearch.py +401 -0
  204. package/templates/vault/last30days/scripts/lib/xai_x.py +217 -0
  205. package/templates/vault/leiloeiro-avaliacao/scripts/governance.py +106 -0
  206. package/templates/vault/leiloeiro-avaliacao/scripts/requirements.txt +1 -0
  207. package/templates/vault/leiloeiro-edital/scripts/governance.py +106 -0
  208. package/templates/vault/leiloeiro-edital/scripts/requirements.txt +1 -0
  209. package/templates/vault/leiloeiro-ia/scripts/governance.py +106 -0
  210. package/templates/vault/leiloeiro-ia/scripts/requirements.txt +1 -0
  211. package/templates/vault/leiloeiro-juridico/scripts/governance.py +106 -0
  212. package/templates/vault/leiloeiro-juridico/scripts/requirements.txt +1 -0
  213. package/templates/vault/leiloeiro-mercado/scripts/governance.py +106 -0
  214. package/templates/vault/leiloeiro-mercado/scripts/requirements.txt +1 -0
  215. package/templates/vault/leiloeiro-risco/scripts/governance.py +106 -0
  216. package/templates/vault/leiloeiro-risco/scripts/requirements.txt +1 -0
  217. package/templates/vault/loki-mode/examples/todo-app-generated/backend/src/db/database.ts +24 -0
  218. package/templates/vault/loki-mode/examples/todo-app-generated/backend/src/db/db.ts +35 -0
  219. package/templates/vault/loki-mode/examples/todo-app-generated/backend/src/db/index.ts +2 -0
  220. package/templates/vault/loki-mode/examples/todo-app-generated/backend/src/db/migrations.ts +31 -0
  221. package/templates/vault/loki-mode/examples/todo-app-generated/backend/src/db/schema.sql +8 -0
  222. package/templates/vault/loki-mode/examples/todo-app-generated/backend/src/index.ts +44 -0
  223. package/templates/vault/loki-mode/examples/todo-app-generated/backend/src/routes/todos.ts +155 -0
  224. package/templates/vault/loki-mode/examples/todo-app-generated/backend/src/types/index.ts +35 -0
  225. package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/App.css +384 -0
  226. package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/App.tsx +81 -0
  227. package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/api/todos.ts +57 -0
  228. package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/components/ConfirmDialog.tsx +26 -0
  229. package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/components/EmptyState.tsx +8 -0
  230. package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/components/TodoForm.tsx +43 -0
  231. package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/components/TodoItem.tsx +36 -0
  232. package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/components/TodoList.tsx +27 -0
  233. package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/hooks/useTodos.ts +81 -0
  234. package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/index.css +48 -0
  235. package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/main.tsx +10 -0
  236. package/templates/vault/loki-mode/examples/todo-app-generated/frontend/src/vite-env.d.ts +1 -0
  237. package/templates/vault/loki-mode/scripts/export-to-vibe-kanban.sh +178 -0
  238. package/templates/vault/loki-mode/scripts/loki-wrapper.sh +281 -0
  239. package/templates/vault/loki-mode/scripts/take-screenshots.js +55 -0
  240. package/templates/vault/matematico-tao/scripts/complexity_analyzer.py +544 -0
  241. package/templates/vault/matematico-tao/scripts/dependency_graph.py +538 -0
  242. package/templates/vault/mcp-builder/scripts/connections.py +151 -0
  243. package/templates/vault/mcp-builder/scripts/evaluation.py +373 -0
  244. package/templates/vault/mcp-builder/scripts/example_evaluation.xml +22 -0
  245. package/templates/vault/mcp-builder/scripts/requirements.txt +2 -0
  246. package/templates/vault/mobile-design/scripts/mobile_audit.py +670 -0
  247. package/templates/vault/notebooklm/scripts/__init__.py +81 -0
  248. package/templates/vault/notebooklm/scripts/ask_question.py +256 -0
  249. package/templates/vault/notebooklm/scripts/auth_manager.py +358 -0
  250. package/templates/vault/notebooklm/scripts/browser_session.py +255 -0
  251. package/templates/vault/notebooklm/scripts/browser_utils.py +107 -0
  252. package/templates/vault/notebooklm/scripts/cleanup_manager.py +302 -0
  253. package/templates/vault/notebooklm/scripts/config.py +44 -0
  254. package/templates/vault/notebooklm/scripts/notebook_manager.py +410 -0
  255. package/templates/vault/notebooklm/scripts/run.py +102 -0
  256. package/templates/vault/notebooklm/scripts/setup_environment.py +204 -0
  257. package/templates/vault/pdf/scripts/check_bounding_boxes.py +70 -0
  258. package/templates/vault/pdf/scripts/check_bounding_boxes_test.py +226 -0
  259. package/templates/vault/pdf/scripts/check_fillable_fields.py +12 -0
  260. package/templates/vault/pdf/scripts/convert_pdf_to_images.py +35 -0
  261. package/templates/vault/pdf/scripts/create_validation_image.py +41 -0
  262. package/templates/vault/pdf/scripts/extract_form_field_info.py +152 -0
  263. package/templates/vault/pdf/scripts/fill_fillable_fields.py +114 -0
  264. package/templates/vault/pdf/scripts/fill_pdf_form_with_annotations.py +108 -0
  265. package/templates/vault/pdf-official/scripts/check_bounding_boxes.py +70 -0
  266. package/templates/vault/pdf-official/scripts/check_bounding_boxes_test.py +226 -0
  267. package/templates/vault/pdf-official/scripts/check_fillable_fields.py +12 -0
  268. package/templates/vault/pdf-official/scripts/convert_pdf_to_images.py +35 -0
  269. package/templates/vault/pdf-official/scripts/create_validation_image.py +41 -0
  270. package/templates/vault/pdf-official/scripts/extract_form_field_info.py +152 -0
  271. package/templates/vault/pdf-official/scripts/fill_fillable_fields.py +114 -0
  272. package/templates/vault/pdf-official/scripts/fill_pdf_form_with_annotations.py +108 -0
  273. package/templates/vault/planning-with-files/scripts/check-complete.sh +44 -0
  274. package/templates/vault/planning-with-files/scripts/init-session.sh +120 -0
  275. package/templates/vault/pptx/ooxml/scripts/pack.py +159 -0
  276. package/templates/vault/pptx/ooxml/scripts/unpack.py +29 -0
  277. package/templates/vault/pptx/ooxml/scripts/validate.py +69 -0
  278. package/templates/vault/pptx/ooxml/scripts/validation/__init__.py +15 -0
  279. package/templates/vault/pptx/ooxml/scripts/validation/base.py +951 -0
  280. package/templates/vault/pptx/ooxml/scripts/validation/docx.py +274 -0
  281. package/templates/vault/pptx/ooxml/scripts/validation/pptx.py +315 -0
  282. package/templates/vault/pptx/ooxml/scripts/validation/redlining.py +279 -0
  283. package/templates/vault/pptx/scripts/html2pptx.js +979 -0
  284. package/templates/vault/pptx/scripts/inventory.py +1020 -0
  285. package/templates/vault/pptx/scripts/rearrange.py +231 -0
  286. package/templates/vault/pptx/scripts/replace.py +385 -0
  287. package/templates/vault/pptx/scripts/thumbnail.py +450 -0
  288. package/templates/vault/pptx-official/ooxml/scripts/pack.py +159 -0
  289. package/templates/vault/pptx-official/ooxml/scripts/unpack.py +29 -0
  290. package/templates/vault/pptx-official/ooxml/scripts/validate.py +69 -0
  291. package/templates/vault/pptx-official/ooxml/scripts/validation/__init__.py +15 -0
  292. package/templates/vault/pptx-official/ooxml/scripts/validation/base.py +951 -0
  293. package/templates/vault/pptx-official/ooxml/scripts/validation/docx.py +274 -0
  294. package/templates/vault/pptx-official/ooxml/scripts/validation/pptx.py +315 -0
  295. package/templates/vault/pptx-official/ooxml/scripts/validation/redlining.py +279 -0
  296. package/templates/vault/pptx-official/scripts/html2pptx.js +979 -0
  297. package/templates/vault/pptx-official/scripts/inventory.py +1020 -0
  298. package/templates/vault/pptx-official/scripts/rearrange.py +231 -0
  299. package/templates/vault/pptx-official/scripts/replace.py +385 -0
  300. package/templates/vault/pptx-official/scripts/thumbnail.py +450 -0
  301. package/templates/vault/product-manager-toolkit/scripts/customer_interview_analyzer.py +441 -0
  302. package/templates/vault/product-manager-toolkit/scripts/rice_prioritizer.py +296 -0
  303. package/templates/vault/prompt-engineering-patterns/scripts/optimize-prompt.py +279 -0
  304. package/templates/vault/scripts/.skill_cache.json +7538 -0
  305. package/templates/vault/scripts/skill_search.py +228 -0
  306. package/templates/vault/senior-architect/scripts/architecture_diagram_generator.py +114 -0
  307. package/templates/vault/senior-architect/scripts/dependency_analyzer.py +114 -0
  308. package/templates/vault/senior-architect/scripts/project_architect.py +114 -0
  309. package/templates/vault/shopify-development/scripts/requirements.txt +19 -0
  310. package/templates/vault/shopify-development/scripts/shopify_graphql.py +428 -0
  311. package/templates/vault/shopify-development/scripts/shopify_init.py +441 -0
  312. package/templates/vault/shopify-development/scripts/tests/test_shopify_init.py +379 -0
  313. package/templates/vault/skill-creator/scripts/init_skill.py +303 -0
  314. package/templates/vault/skill-creator/scripts/package_skill.py +110 -0
  315. package/templates/vault/skill-creator/scripts/quick_validate.py +95 -0
  316. package/templates/vault/skill-installer/scripts/detect_skills.py +318 -0
  317. package/templates/vault/skill-installer/scripts/install_skill.py +1708 -0
  318. package/templates/vault/skill-installer/scripts/package_skill.py +417 -0
  319. package/templates/vault/skill-installer/scripts/requirements.txt +1 -0
  320. package/templates/vault/skill-installer/scripts/validate_skill.py +430 -0
  321. package/templates/vault/skill-sentinel/scripts/analyzers/__init__.py +13 -0
  322. package/templates/vault/skill-sentinel/scripts/analyzers/code_quality.py +247 -0
  323. package/templates/vault/skill-sentinel/scripts/analyzers/cross_skill.py +134 -0
  324. package/templates/vault/skill-sentinel/scripts/analyzers/dependencies.py +121 -0
  325. package/templates/vault/skill-sentinel/scripts/analyzers/documentation.py +189 -0
  326. package/templates/vault/skill-sentinel/scripts/analyzers/governance_audit.py +153 -0
  327. package/templates/vault/skill-sentinel/scripts/analyzers/performance.py +164 -0
  328. package/templates/vault/skill-sentinel/scripts/analyzers/security.py +189 -0
  329. package/templates/vault/skill-sentinel/scripts/config.py +158 -0
  330. package/templates/vault/skill-sentinel/scripts/cost_optimizer.py +146 -0
  331. package/templates/vault/skill-sentinel/scripts/db.py +354 -0
  332. package/templates/vault/skill-sentinel/scripts/governance.py +58 -0
  333. package/templates/vault/skill-sentinel/scripts/recommender.py +228 -0
  334. package/templates/vault/skill-sentinel/scripts/report_generator.py +224 -0
  335. package/templates/vault/skill-sentinel/scripts/requirements.txt +1 -0
  336. package/templates/vault/skill-sentinel/scripts/run_audit.py +290 -0
  337. package/templates/vault/skill-sentinel/scripts/scanner.py +271 -0
  338. package/templates/vault/stability-ai/scripts/config.py +266 -0
  339. package/templates/vault/stability-ai/scripts/generate.py +687 -0
  340. package/templates/vault/stability-ai/scripts/requirements.txt +4 -0
  341. package/templates/vault/stability-ai/scripts/styles.py +174 -0
  342. package/templates/vault/telegram/assets/boilerplate/nodejs/src/bot-client.ts +86 -0
  343. package/templates/vault/telegram/assets/boilerplate/nodejs/src/handlers.ts +79 -0
  344. package/templates/vault/telegram/assets/boilerplate/nodejs/src/index.ts +32 -0
  345. package/templates/vault/telegram/scripts/send_message.py +143 -0
  346. package/templates/vault/telegram/scripts/setup_project.py +103 -0
  347. package/templates/vault/telegram/scripts/test_bot.py +144 -0
  348. package/templates/vault/typescript-expert/scripts/ts_diagnostic.py +203 -0
  349. package/templates/vault/ui-ux-pro-max/scripts/__pycache__/core.cpython-314.pyc +0 -0
  350. package/templates/vault/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-314.pyc +0 -0
  351. package/templates/vault/ui-ux-pro-max/scripts/core.py +257 -0
  352. package/templates/vault/ui-ux-pro-max/scripts/design_system.py +487 -0
  353. package/templates/vault/ui-ux-pro-max/scripts/search.py +76 -0
  354. package/templates/vault/videodb/scripts/ws_listener.py +204 -0
  355. package/templates/vault/web-artifacts-builder/scripts/bundle-artifact.sh +54 -0
  356. package/templates/vault/web-artifacts-builder/scripts/init-artifact.sh +322 -0
  357. package/templates/vault/web-artifacts-builder/scripts/shadcn-components.tar.gz +0 -0
  358. package/templates/vault/webapp-testing/scripts/with_server.py +106 -0
  359. package/templates/vault/whatsapp-cloud-api/assets/boilerplate/nodejs/src/index.ts +125 -0
  360. package/templates/vault/whatsapp-cloud-api/assets/boilerplate/nodejs/src/template-manager.ts +67 -0
  361. package/templates/vault/whatsapp-cloud-api/assets/boilerplate/nodejs/src/types.ts +216 -0
  362. package/templates/vault/whatsapp-cloud-api/assets/boilerplate/nodejs/src/webhook-handler.ts +173 -0
  363. package/templates/vault/whatsapp-cloud-api/assets/boilerplate/nodejs/src/whatsapp-client.ts +193 -0
  364. package/templates/vault/whatsapp-cloud-api/scripts/send_test_message.py +137 -0
  365. package/templates/vault/whatsapp-cloud-api/scripts/setup_project.py +118 -0
  366. package/templates/vault/whatsapp-cloud-api/scripts/validate_config.py +190 -0
  367. package/templates/vault/youtube-summarizer/scripts/extract-transcript.py +65 -0
  368. package/templates/vault/youtube-summarizer/scripts/install-dependencies.sh +28 -0
@@ -0,0 +1,152 @@
1
+ import json
2
+ import sys
3
+
4
+ from pypdf import PdfReader
5
+
6
+
7
+ # Extracts data for the fillable form fields in a PDF and outputs JSON that
8
+ # Claude uses to fill the fields. See forms.md.
9
+
10
+
11
+ # This matches the format used by PdfReader `get_fields` and `update_page_form_field_values` methods.
12
+ def get_full_annotation_field_id(annotation):
13
+ components = []
14
+ while annotation:
15
+ field_name = annotation.get('/T')
16
+ if field_name:
17
+ components.append(field_name)
18
+ annotation = annotation.get('/Parent')
19
+ return ".".join(reversed(components)) if components else None
20
+
21
+
22
+ def make_field_dict(field, field_id):
23
+ field_dict = {"field_id": field_id}
24
+ ft = field.get('/FT')
25
+ if ft == "/Tx":
26
+ field_dict["type"] = "text"
27
+ elif ft == "/Btn":
28
+ field_dict["type"] = "checkbox" # radio groups handled separately
29
+ states = field.get("/_States_", [])
30
+ if len(states) == 2:
31
+ # "/Off" seems to always be the unchecked value, as suggested by
32
+ # https://opensource.adobe.com/dc-acrobat-sdk-docs/standards/pdfstandards/pdf/PDF32000_2008.pdf#page=448
33
+ # It can be either first or second in the "/_States_" list.
34
+ if "/Off" in states:
35
+ field_dict["checked_value"] = states[0] if states[0] != "/Off" else states[1]
36
+ field_dict["unchecked_value"] = "/Off"
37
+ else:
38
+ print(f"Unexpected state values for checkbox `${field_id}`. Its checked and unchecked values may not be correct; if you're trying to check it, visually verify the results.")
39
+ field_dict["checked_value"] = states[0]
40
+ field_dict["unchecked_value"] = states[1]
41
+ elif ft == "/Ch":
42
+ field_dict["type"] = "choice"
43
+ states = field.get("/_States_", [])
44
+ field_dict["choice_options"] = [{
45
+ "value": state[0],
46
+ "text": state[1],
47
+ } for state in states]
48
+ else:
49
+ field_dict["type"] = f"unknown ({ft})"
50
+ return field_dict
51
+
52
+
53
+ # Returns a list of fillable PDF fields:
54
+ # [
55
+ # {
56
+ # "field_id": "name",
57
+ # "page": 1,
58
+ # "type": ("text", "checkbox", "radio_group", or "choice")
59
+ # // Per-type additional fields described in forms.md
60
+ # },
61
+ # ]
62
+ def get_field_info(reader: PdfReader):
63
+ fields = reader.get_fields()
64
+
65
+ field_info_by_id = {}
66
+ possible_radio_names = set()
67
+
68
+ for field_id, field in fields.items():
69
+ # Skip if this is a container field with children, except that it might be
70
+ # a parent group for radio button options.
71
+ if field.get("/Kids"):
72
+ if field.get("/FT") == "/Btn":
73
+ possible_radio_names.add(field_id)
74
+ continue
75
+ field_info_by_id[field_id] = make_field_dict(field, field_id)
76
+
77
+ # Bounding rects are stored in annotations in page objects.
78
+
79
+ # Radio button options have a separate annotation for each choice;
80
+ # all choices have the same field name.
81
+ # See https://westhealth.github.io/exploring-fillable-forms-with-pdfrw.html
82
+ radio_fields_by_id = {}
83
+
84
+ for page_index, page in enumerate(reader.pages):
85
+ annotations = page.get('/Annots', [])
86
+ for ann in annotations:
87
+ field_id = get_full_annotation_field_id(ann)
88
+ if field_id in field_info_by_id:
89
+ field_info_by_id[field_id]["page"] = page_index + 1
90
+ field_info_by_id[field_id]["rect"] = ann.get('/Rect')
91
+ elif field_id in possible_radio_names:
92
+ try:
93
+ # ann['/AP']['/N'] should have two items. One of them is '/Off',
94
+ # the other is the active value.
95
+ on_values = [v for v in ann["/AP"]["/N"] if v != "/Off"]
96
+ except KeyError:
97
+ continue
98
+ if len(on_values) == 1:
99
+ rect = ann.get("/Rect")
100
+ if field_id not in radio_fields_by_id:
101
+ radio_fields_by_id[field_id] = {
102
+ "field_id": field_id,
103
+ "type": "radio_group",
104
+ "page": page_index + 1,
105
+ "radio_options": [],
106
+ }
107
+ # Note: at least on macOS 15.7, Preview.app doesn't show selected
108
+ # radio buttons correctly. (It does if you remove the leading slash
109
+ # from the value, but that causes them not to appear correctly in
110
+ # Chrome/Firefox/Acrobat/etc).
111
+ radio_fields_by_id[field_id]["radio_options"].append({
112
+ "value": on_values[0],
113
+ "rect": rect,
114
+ })
115
+
116
+ # Some PDFs have form field definitions without corresponding annotations,
117
+ # so we can't tell where they are. Ignore these fields for now.
118
+ fields_with_location = []
119
+ for field_info in field_info_by_id.values():
120
+ if "page" in field_info:
121
+ fields_with_location.append(field_info)
122
+ else:
123
+ print(f"Unable to determine location for field id: {field_info.get('field_id')}, ignoring")
124
+
125
+ # Sort by page number, then Y position (flipped in PDF coordinate system), then X.
126
+ def sort_key(f):
127
+ if "radio_options" in f:
128
+ rect = f["radio_options"][0]["rect"] or [0, 0, 0, 0]
129
+ else:
130
+ rect = f.get("rect") or [0, 0, 0, 0]
131
+ adjusted_position = [-rect[1], rect[0]]
132
+ return [f.get("page"), adjusted_position]
133
+
134
+ sorted_fields = fields_with_location + list(radio_fields_by_id.values())
135
+ sorted_fields.sort(key=sort_key)
136
+
137
+ return sorted_fields
138
+
139
+
140
+ def write_field_info(pdf_path: str, json_output_path: str):
141
+ reader = PdfReader(pdf_path)
142
+ field_info = get_field_info(reader)
143
+ with open(json_output_path, "w") as f:
144
+ json.dump(field_info, f, indent=2)
145
+ print(f"Wrote {len(field_info)} fields to {json_output_path}")
146
+
147
+
148
+ if __name__ == "__main__":
149
+ if len(sys.argv) != 3:
150
+ print("Usage: extract_form_field_info.py [input pdf] [output json]")
151
+ sys.exit(1)
152
+ write_field_info(sys.argv[1], sys.argv[2])
@@ -0,0 +1,114 @@
1
+ import json
2
+ import sys
3
+
4
+ from pypdf import PdfReader, PdfWriter
5
+
6
+ from extract_form_field_info import get_field_info
7
+
8
+
9
+ # Fills fillable form fields in a PDF. See forms.md.
10
+
11
+
12
+ def fill_pdf_fields(input_pdf_path: str, fields_json_path: str, output_pdf_path: str):
13
+ with open(fields_json_path) as f:
14
+ fields = json.load(f)
15
+ # Group by page number.
16
+ fields_by_page = {}
17
+ for field in fields:
18
+ if "value" in field:
19
+ field_id = field["field_id"]
20
+ page = field["page"]
21
+ if page not in fields_by_page:
22
+ fields_by_page[page] = {}
23
+ fields_by_page[page][field_id] = field["value"]
24
+
25
+ reader = PdfReader(input_pdf_path)
26
+
27
+ has_error = False
28
+ field_info = get_field_info(reader)
29
+ fields_by_ids = {f["field_id"]: f for f in field_info}
30
+ for field in fields:
31
+ existing_field = fields_by_ids.get(field["field_id"])
32
+ if not existing_field:
33
+ has_error = True
34
+ print(f"ERROR: `{field['field_id']}` is not a valid field ID")
35
+ elif field["page"] != existing_field["page"]:
36
+ has_error = True
37
+ print(f"ERROR: Incorrect page number for `{field['field_id']}` (got {field['page']}, expected {existing_field['page']})")
38
+ else:
39
+ if "value" in field:
40
+ err = validation_error_for_field_value(existing_field, field["value"])
41
+ if err:
42
+ print(err)
43
+ has_error = True
44
+ if has_error:
45
+ sys.exit(1)
46
+
47
+ writer = PdfWriter(clone_from=reader)
48
+ for page, field_values in fields_by_page.items():
49
+ writer.update_page_form_field_values(writer.pages[page - 1], field_values, auto_regenerate=False)
50
+
51
+ # This seems to be necessary for many PDF viewers to format the form values correctly.
52
+ # It may cause the viewer to show a "save changes" dialog even if the user doesn't make any changes.
53
+ writer.set_need_appearances_writer(True)
54
+
55
+ with open(output_pdf_path, "wb") as f:
56
+ writer.write(f)
57
+
58
+
59
+ def validation_error_for_field_value(field_info, field_value):
60
+ field_type = field_info["type"]
61
+ field_id = field_info["field_id"]
62
+ if field_type == "checkbox":
63
+ checked_val = field_info["checked_value"]
64
+ unchecked_val = field_info["unchecked_value"]
65
+ if field_value != checked_val and field_value != unchecked_val:
66
+ return f'ERROR: Invalid value "{field_value}" for checkbox field "{field_id}". The checked value is "{checked_val}" and the unchecked value is "{unchecked_val}"'
67
+ elif field_type == "radio_group":
68
+ option_values = [opt["value"] for opt in field_info["radio_options"]]
69
+ if field_value not in option_values:
70
+ return f'ERROR: Invalid value "{field_value}" for radio group field "{field_id}". Valid values are: {option_values}'
71
+ elif field_type == "choice":
72
+ choice_values = [opt["value"] for opt in field_info["choice_options"]]
73
+ if field_value not in choice_values:
74
+ return f'ERROR: Invalid value "{field_value}" for choice field "{field_id}". Valid values are: {choice_values}'
75
+ return None
76
+
77
+
78
+ # pypdf (at least version 5.7.0) has a bug when setting the value for a selection list field.
79
+ # In _writer.py around line 966:
80
+ #
81
+ # if field.get(FA.FT, "/Tx") == "/Ch" and field_flags & FA.FfBits.Combo == 0:
82
+ # txt = "\n".join(annotation.get_inherited(FA.Opt, []))
83
+ #
84
+ # The problem is that for selection lists, `get_inherited` returns a list of two-element lists like
85
+ # [["value1", "Text 1"], ["value2", "Text 2"], ...]
86
+ # This causes `join` to throw a TypeError because it expects an iterable of strings.
87
+ # The horrible workaround is to patch `get_inherited` to return a list of the value strings.
88
+ # We call the original method and adjust the return value only if the argument to `get_inherited`
89
+ # is `FA.Opt` and if the return value is a list of two-element lists.
90
+ def monkeypatch_pydpf_method():
91
+ from pypdf.generic import DictionaryObject
92
+ from pypdf.constants import FieldDictionaryAttributes
93
+
94
+ original_get_inherited = DictionaryObject.get_inherited
95
+
96
+ def patched_get_inherited(self, key: str, default = None):
97
+ result = original_get_inherited(self, key, default)
98
+ if key == FieldDictionaryAttributes.Opt:
99
+ if isinstance(result, list) and all(isinstance(v, list) and len(v) == 2 for v in result):
100
+ result = [r[0] for r in result]
101
+ return result
102
+
103
+ DictionaryObject.get_inherited = patched_get_inherited
104
+
105
+
106
+ if __name__ == "__main__":
107
+ if len(sys.argv) != 4:
108
+ print("Usage: fill_fillable_fields.py [input pdf] [field_values.json] [output pdf]")
109
+ sys.exit(1)
110
+ monkeypatch_pydpf_method()
111
+ input_pdf = sys.argv[1]
112
+ fields_json = sys.argv[2]
113
+ output_pdf = sys.argv[3]
114
+ fill_pdf_fields(input_pdf, fields_json, output_pdf)
@@ -0,0 +1,108 @@
1
+ import json
2
+ import sys
3
+
4
+ from pypdf import PdfReader, PdfWriter
5
+ from pypdf.annotations import FreeText
6
+
7
+
8
+ # Fills a PDF by adding text annotations defined in `fields.json`. See forms.md.
9
+
10
+
11
+ def transform_coordinates(bbox, image_width, image_height, pdf_width, pdf_height):
12
+ """Transform bounding box from image coordinates to PDF coordinates"""
13
+ # Image coordinates: origin at top-left, y increases downward
14
+ # PDF coordinates: origin at bottom-left, y increases upward
15
+ x_scale = pdf_width / image_width
16
+ y_scale = pdf_height / image_height
17
+
18
+ left = bbox[0] * x_scale
19
+ right = bbox[2] * x_scale
20
+
21
+ # Flip Y coordinates for PDF
22
+ top = pdf_height - (bbox[1] * y_scale)
23
+ bottom = pdf_height - (bbox[3] * y_scale)
24
+
25
+ return left, bottom, right, top
26
+
27
+
28
+ def fill_pdf_form(input_pdf_path, fields_json_path, output_pdf_path):
29
+ """Fill the PDF form with data from fields.json"""
30
+
31
+ # `fields.json` format described in forms.md.
32
+ with open(fields_json_path, "r") as f:
33
+ fields_data = json.load(f)
34
+
35
+ # Open the PDF
36
+ reader = PdfReader(input_pdf_path)
37
+ writer = PdfWriter()
38
+
39
+ # Copy all pages to writer
40
+ writer.append(reader)
41
+
42
+ # Get PDF dimensions for each page
43
+ pdf_dimensions = {}
44
+ for i, page in enumerate(reader.pages):
45
+ mediabox = page.mediabox
46
+ pdf_dimensions[i + 1] = [mediabox.width, mediabox.height]
47
+
48
+ # Process each form field
49
+ annotations = []
50
+ for field in fields_data["form_fields"]:
51
+ page_num = field["page_number"]
52
+
53
+ # Get page dimensions and transform coordinates.
54
+ page_info = next(p for p in fields_data["pages"] if p["page_number"] == page_num)
55
+ image_width = page_info["image_width"]
56
+ image_height = page_info["image_height"]
57
+ pdf_width, pdf_height = pdf_dimensions[page_num]
58
+
59
+ transformed_entry_box = transform_coordinates(
60
+ field["entry_bounding_box"],
61
+ image_width, image_height,
62
+ pdf_width, pdf_height
63
+ )
64
+
65
+ # Skip empty fields
66
+ if "entry_text" not in field or "text" not in field["entry_text"]:
67
+ continue
68
+ entry_text = field["entry_text"]
69
+ text = entry_text["text"]
70
+ if not text:
71
+ continue
72
+
73
+ font_name = entry_text.get("font", "Arial")
74
+ font_size = str(entry_text.get("font_size", 14)) + "pt"
75
+ font_color = entry_text.get("font_color", "000000")
76
+
77
+ # Font size/color seems to not work reliably across viewers:
78
+ # https://github.com/py-pdf/pypdf/issues/2084
79
+ annotation = FreeText(
80
+ text=text,
81
+ rect=transformed_entry_box,
82
+ font=font_name,
83
+ font_size=font_size,
84
+ font_color=font_color,
85
+ border_color=None,
86
+ background_color=None,
87
+ )
88
+ annotations.append(annotation)
89
+ # page_number is 0-based for pypdf
90
+ writer.add_annotation(page_number=page_num - 1, annotation=annotation)
91
+
92
+ # Save the filled PDF
93
+ with open(output_pdf_path, "wb") as output:
94
+ writer.write(output)
95
+
96
+ print(f"Successfully filled PDF form and saved to {output_pdf_path}")
97
+ print(f"Added {len(annotations)} text annotations")
98
+
99
+
100
+ if __name__ == "__main__":
101
+ if len(sys.argv) != 4:
102
+ print("Usage: fill_pdf_form_with_annotations.py [input pdf] [fields.json] [output pdf]")
103
+ sys.exit(1)
104
+ input_pdf = sys.argv[1]
105
+ fields_json = sys.argv[2]
106
+ output_pdf = sys.argv[3]
107
+
108
+ fill_pdf_form(input_pdf, fields_json, output_pdf)
@@ -0,0 +1,70 @@
1
+ from dataclasses import dataclass
2
+ import json
3
+ import sys
4
+
5
+
6
+ # Script to check that the `fields.json` file that Claude creates when analyzing PDFs
7
+ # does not have overlapping bounding boxes. See forms.md.
8
+
9
+
10
+ @dataclass
11
+ class RectAndField:
12
+ rect: list[float]
13
+ rect_type: str
14
+ field: dict
15
+
16
+
17
+ # Returns a list of messages that are printed to stdout for Claude to read.
18
+ def get_bounding_box_messages(fields_json_stream) -> list[str]:
19
+ messages = []
20
+ fields = json.load(fields_json_stream)
21
+ messages.append(f"Read {len(fields['form_fields'])} fields")
22
+
23
+ def rects_intersect(r1, r2):
24
+ disjoint_horizontal = r1[0] >= r2[2] or r1[2] <= r2[0]
25
+ disjoint_vertical = r1[1] >= r2[3] or r1[3] <= r2[1]
26
+ return not (disjoint_horizontal or disjoint_vertical)
27
+
28
+ rects_and_fields = []
29
+ for f in fields["form_fields"]:
30
+ rects_and_fields.append(RectAndField(f["label_bounding_box"], "label", f))
31
+ rects_and_fields.append(RectAndField(f["entry_bounding_box"], "entry", f))
32
+
33
+ has_error = False
34
+ for i, ri in enumerate(rects_and_fields):
35
+ # This is O(N^2); we can optimize if it becomes a problem.
36
+ for j in range(i + 1, len(rects_and_fields)):
37
+ rj = rects_and_fields[j]
38
+ if ri.field["page_number"] == rj.field["page_number"] and rects_intersect(ri.rect, rj.rect):
39
+ has_error = True
40
+ if ri.field is rj.field:
41
+ messages.append(f"FAILURE: intersection between label and entry bounding boxes for `{ri.field['description']}` ({ri.rect}, {rj.rect})")
42
+ else:
43
+ messages.append(f"FAILURE: intersection between {ri.rect_type} bounding box for `{ri.field['description']}` ({ri.rect}) and {rj.rect_type} bounding box for `{rj.field['description']}` ({rj.rect})")
44
+ if len(messages) >= 20:
45
+ messages.append("Aborting further checks; fix bounding boxes and try again")
46
+ return messages
47
+ if ri.rect_type == "entry":
48
+ if "entry_text" in ri.field:
49
+ font_size = ri.field["entry_text"].get("font_size", 14)
50
+ entry_height = ri.rect[3] - ri.rect[1]
51
+ if entry_height < font_size:
52
+ has_error = True
53
+ messages.append(f"FAILURE: entry bounding box height ({entry_height}) for `{ri.field['description']}` is too short for the text content (font size: {font_size}). Increase the box height or decrease the font size.")
54
+ if len(messages) >= 20:
55
+ messages.append("Aborting further checks; fix bounding boxes and try again")
56
+ return messages
57
+
58
+ if not has_error:
59
+ messages.append("SUCCESS: All bounding boxes are valid")
60
+ return messages
61
+
62
+ if __name__ == "__main__":
63
+ if len(sys.argv) != 2:
64
+ print("Usage: check_bounding_boxes.py [fields.json]")
65
+ sys.exit(1)
66
+ # Input file should be in the `fields.json` format described in forms.md.
67
+ with open(sys.argv[1]) as f:
68
+ messages = get_bounding_box_messages(f)
69
+ for msg in messages:
70
+ print(msg)
@@ -0,0 +1,226 @@
1
+ import unittest
2
+ import json
3
+ import io
4
+ from check_bounding_boxes import get_bounding_box_messages
5
+
6
+
7
+ # Currently this is not run automatically in CI; it's just for documentation and manual checking.
8
+ class TestGetBoundingBoxMessages(unittest.TestCase):
9
+
10
+ def create_json_stream(self, data):
11
+ """Helper to create a JSON stream from data"""
12
+ return io.StringIO(json.dumps(data))
13
+
14
+ def test_no_intersections(self):
15
+ """Test case with no bounding box intersections"""
16
+ data = {
17
+ "form_fields": [
18
+ {
19
+ "description": "Name",
20
+ "page_number": 1,
21
+ "label_bounding_box": [10, 10, 50, 30],
22
+ "entry_bounding_box": [60, 10, 150, 30]
23
+ },
24
+ {
25
+ "description": "Email",
26
+ "page_number": 1,
27
+ "label_bounding_box": [10, 40, 50, 60],
28
+ "entry_bounding_box": [60, 40, 150, 60]
29
+ }
30
+ ]
31
+ }
32
+
33
+ stream = self.create_json_stream(data)
34
+ messages = get_bounding_box_messages(stream)
35
+ self.assertTrue(any("SUCCESS" in msg for msg in messages))
36
+ self.assertFalse(any("FAILURE" in msg for msg in messages))
37
+
38
+ def test_label_entry_intersection_same_field(self):
39
+ """Test intersection between label and entry of the same field"""
40
+ data = {
41
+ "form_fields": [
42
+ {
43
+ "description": "Name",
44
+ "page_number": 1,
45
+ "label_bounding_box": [10, 10, 60, 30],
46
+ "entry_bounding_box": [50, 10, 150, 30] # Overlaps with label
47
+ }
48
+ ]
49
+ }
50
+
51
+ stream = self.create_json_stream(data)
52
+ messages = get_bounding_box_messages(stream)
53
+ self.assertTrue(any("FAILURE" in msg and "intersection" in msg for msg in messages))
54
+ self.assertFalse(any("SUCCESS" in msg for msg in messages))
55
+
56
+ def test_intersection_between_different_fields(self):
57
+ """Test intersection between bounding boxes of different fields"""
58
+ data = {
59
+ "form_fields": [
60
+ {
61
+ "description": "Name",
62
+ "page_number": 1,
63
+ "label_bounding_box": [10, 10, 50, 30],
64
+ "entry_bounding_box": [60, 10, 150, 30]
65
+ },
66
+ {
67
+ "description": "Email",
68
+ "page_number": 1,
69
+ "label_bounding_box": [40, 20, 80, 40], # Overlaps with Name's boxes
70
+ "entry_bounding_box": [160, 10, 250, 30]
71
+ }
72
+ ]
73
+ }
74
+
75
+ stream = self.create_json_stream(data)
76
+ messages = get_bounding_box_messages(stream)
77
+ self.assertTrue(any("FAILURE" in msg and "intersection" in msg for msg in messages))
78
+ self.assertFalse(any("SUCCESS" in msg for msg in messages))
79
+
80
+ def test_different_pages_no_intersection(self):
81
+ """Test that boxes on different pages don't count as intersecting"""
82
+ data = {
83
+ "form_fields": [
84
+ {
85
+ "description": "Name",
86
+ "page_number": 1,
87
+ "label_bounding_box": [10, 10, 50, 30],
88
+ "entry_bounding_box": [60, 10, 150, 30]
89
+ },
90
+ {
91
+ "description": "Email",
92
+ "page_number": 2,
93
+ "label_bounding_box": [10, 10, 50, 30], # Same coordinates but different page
94
+ "entry_bounding_box": [60, 10, 150, 30]
95
+ }
96
+ ]
97
+ }
98
+
99
+ stream = self.create_json_stream(data)
100
+ messages = get_bounding_box_messages(stream)
101
+ self.assertTrue(any("SUCCESS" in msg for msg in messages))
102
+ self.assertFalse(any("FAILURE" in msg for msg in messages))
103
+
104
+ def test_entry_height_too_small(self):
105
+ """Test that entry box height is checked against font size"""
106
+ data = {
107
+ "form_fields": [
108
+ {
109
+ "description": "Name",
110
+ "page_number": 1,
111
+ "label_bounding_box": [10, 10, 50, 30],
112
+ "entry_bounding_box": [60, 10, 150, 20], # Height is 10
113
+ "entry_text": {
114
+ "font_size": 14 # Font size larger than height
115
+ }
116
+ }
117
+ ]
118
+ }
119
+
120
+ stream = self.create_json_stream(data)
121
+ messages = get_bounding_box_messages(stream)
122
+ self.assertTrue(any("FAILURE" in msg and "height" in msg for msg in messages))
123
+ self.assertFalse(any("SUCCESS" in msg for msg in messages))
124
+
125
+ def test_entry_height_adequate(self):
126
+ """Test that adequate entry box height passes"""
127
+ data = {
128
+ "form_fields": [
129
+ {
130
+ "description": "Name",
131
+ "page_number": 1,
132
+ "label_bounding_box": [10, 10, 50, 30],
133
+ "entry_bounding_box": [60, 10, 150, 30], # Height is 20
134
+ "entry_text": {
135
+ "font_size": 14 # Font size smaller than height
136
+ }
137
+ }
138
+ ]
139
+ }
140
+
141
+ stream = self.create_json_stream(data)
142
+ messages = get_bounding_box_messages(stream)
143
+ self.assertTrue(any("SUCCESS" in msg for msg in messages))
144
+ self.assertFalse(any("FAILURE" in msg for msg in messages))
145
+
146
+ def test_default_font_size(self):
147
+ """Test that default font size is used when not specified"""
148
+ data = {
149
+ "form_fields": [
150
+ {
151
+ "description": "Name",
152
+ "page_number": 1,
153
+ "label_bounding_box": [10, 10, 50, 30],
154
+ "entry_bounding_box": [60, 10, 150, 20], # Height is 10
155
+ "entry_text": {} # No font_size specified, should use default 14
156
+ }
157
+ ]
158
+ }
159
+
160
+ stream = self.create_json_stream(data)
161
+ messages = get_bounding_box_messages(stream)
162
+ self.assertTrue(any("FAILURE" in msg and "height" in msg for msg in messages))
163
+ self.assertFalse(any("SUCCESS" in msg for msg in messages))
164
+
165
+ def test_no_entry_text(self):
166
+ """Test that missing entry_text doesn't cause height check"""
167
+ data = {
168
+ "form_fields": [
169
+ {
170
+ "description": "Name",
171
+ "page_number": 1,
172
+ "label_bounding_box": [10, 10, 50, 30],
173
+ "entry_bounding_box": [60, 10, 150, 20] # Small height but no entry_text
174
+ }
175
+ ]
176
+ }
177
+
178
+ stream = self.create_json_stream(data)
179
+ messages = get_bounding_box_messages(stream)
180
+ self.assertTrue(any("SUCCESS" in msg for msg in messages))
181
+ self.assertFalse(any("FAILURE" in msg for msg in messages))
182
+
183
+ def test_multiple_errors_limit(self):
184
+ """Test that error messages are limited to prevent excessive output"""
185
+ fields = []
186
+ # Create many overlapping fields
187
+ for i in range(25):
188
+ fields.append({
189
+ "description": f"Field{i}",
190
+ "page_number": 1,
191
+ "label_bounding_box": [10, 10, 50, 30], # All overlap
192
+ "entry_bounding_box": [20, 15, 60, 35] # All overlap
193
+ })
194
+
195
+ data = {"form_fields": fields}
196
+
197
+ stream = self.create_json_stream(data)
198
+ messages = get_bounding_box_messages(stream)
199
+ # Should abort after ~20 messages
200
+ self.assertTrue(any("Aborting" in msg for msg in messages))
201
+ # Should have some FAILURE messages but not hundreds
202
+ failure_count = sum(1 for msg in messages if "FAILURE" in msg)
203
+ self.assertGreater(failure_count, 0)
204
+ self.assertLess(len(messages), 30) # Should be limited
205
+
206
+ def test_edge_touching_boxes(self):
207
+ """Test that boxes touching at edges don't count as intersecting"""
208
+ data = {
209
+ "form_fields": [
210
+ {
211
+ "description": "Name",
212
+ "page_number": 1,
213
+ "label_bounding_box": [10, 10, 50, 30],
214
+ "entry_bounding_box": [50, 10, 150, 30] # Touches at x=50
215
+ }
216
+ ]
217
+ }
218
+
219
+ stream = self.create_json_stream(data)
220
+ messages = get_bounding_box_messages(stream)
221
+ self.assertTrue(any("SUCCESS" in msg for msg in messages))
222
+ self.assertFalse(any("FAILURE" in msg for msg in messages))
223
+
224
+
225
+ if __name__ == '__main__':
226
+ unittest.main()
@@ -0,0 +1,12 @@
1
+ import sys
2
+ from pypdf import PdfReader
3
+
4
+
5
+ # Script for Claude to run to determine whether a PDF has fillable form fields. See forms.md.
6
+
7
+
8
+ reader = PdfReader(sys.argv[1])
9
+ if (reader.get_fields()):
10
+ print("This PDF has fillable form fields")
11
+ else:
12
+ print("This PDF does not have fillable form fields; you will need to visually determine where to enter data")