@caddis/cli 0.0.0 → 0.2.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 (513) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +163 -1
  3. package/bundles/antigravity-plugin/agents/code-reviewer.md +47 -0
  4. package/bundles/antigravity-plugin/agents/preflight.md +53 -0
  5. package/bundles/antigravity-plugin/guard_agy.py +338 -0
  6. package/bundles/antigravity-plugin/hooks.json +36 -0
  7. package/bundles/antigravity-plugin/mcp_config.json +8 -0
  8. package/bundles/antigravity-plugin/mcp_ping_server.py +55 -0
  9. package/bundles/antigravity-plugin/plugin.json +5 -0
  10. package/bundles/antigravity-plugin/session_end_agy.py +57 -0
  11. package/bundles/antigravity-plugin/skills/_registry.md +115 -0
  12. package/bundles/antigravity-plugin/skills/add-rules/SKILL.md +45 -0
  13. package/bundles/antigravity-plugin/skills/api-design/SKILL.md +249 -0
  14. package/bundles/antigravity-plugin/skills/backend-development/SKILL.md +305 -0
  15. package/bundles/antigravity-plugin/skills/best-practices/SKILL.md +500 -0
  16. package/bundles/antigravity-plugin/skills/best-practices/agents/best-practices-referencer.md +263 -0
  17. package/bundles/antigravity-plugin/skills/best-practices/agents/codebase-context-builder.md +326 -0
  18. package/bundles/antigravity-plugin/skills/best-practices/agents/task-intent-analyzer.md +245 -0
  19. package/bundles/antigravity-plugin/skills/best-practices/references/anti-patterns.md +571 -0
  20. package/bundles/antigravity-plugin/skills/best-practices/references/before-after-examples.md +1114 -0
  21. package/bundles/antigravity-plugin/skills/best-practices/references/best-practices-guide.md +513 -0
  22. package/bundles/antigravity-plugin/skills/best-practices/references/common-workflows.md +692 -0
  23. package/bundles/antigravity-plugin/skills/best-practices/references/prompt-patterns.md +547 -0
  24. package/bundles/antigravity-plugin/skills/brainstorming/SKILL.md +57 -0
  25. package/bundles/antigravity-plugin/skills/ci-cd-pipeline/SKILL.md +315 -0
  26. package/bundles/antigravity-plugin/skills/code-documentation/SKILL.md +271 -0
  27. package/bundles/antigravity-plugin/skills/code-review/SKILL.md +122 -0
  28. package/bundles/antigravity-plugin/skills/codebase-audit/SKILL.md +204 -0
  29. package/bundles/antigravity-plugin/skills/context-curator/SKILL.md +157 -0
  30. package/bundles/antigravity-plugin/skills/cross-review/SKILL.md +40 -0
  31. package/bundles/antigravity-plugin/skills/css-architecture/SKILL.md +305 -0
  32. package/bundles/antigravity-plugin/skills/css-architecture/references/RESPONSIVE-DESIGN.md +604 -0
  33. package/bundles/antigravity-plugin/skills/database-design/SKILL.md +177 -0
  34. package/bundles/antigravity-plugin/skills/db-diagram/SKILL.md +148 -0
  35. package/bundles/antigravity-plugin/skills/db-diagram/scripts/sql_to_graph.py +1212 -0
  36. package/bundles/antigravity-plugin/skills/digress/SKILL.md +61 -0
  37. package/bundles/antigravity-plugin/skills/draw-io/SKILL.md +162 -0
  38. package/bundles/antigravity-plugin/skills/draw-io/references/aws-icons.md +677 -0
  39. package/bundles/antigravity-plugin/skills/draw-io/references/layout-guidelines.md +142 -0
  40. package/bundles/antigravity-plugin/skills/draw-io/references/troubleshooting.md +118 -0
  41. package/bundles/antigravity-plugin/skills/draw-io/references/workflows.md +103 -0
  42. package/bundles/antigravity-plugin/skills/draw-io/scripts/convert-drawio-to-png.sh +25 -0
  43. package/bundles/antigravity-plugin/skills/draw-io/scripts/find_aws_icon.py +79 -0
  44. package/bundles/antigravity-plugin/skills/error-handling/SKILL.md +260 -0
  45. package/bundles/antigravity-plugin/skills/excalidraw-db/SKILL.md +38 -0
  46. package/bundles/antigravity-plugin/skills/fastapi-dev/SKILL.md +300 -0
  47. package/bundles/antigravity-plugin/skills/feature-plan/SKILL.md +198 -0
  48. package/bundles/antigravity-plugin/skills/frontend-design/SKILL.md +193 -0
  49. package/bundles/antigravity-plugin/skills/frontend-design/references/my-tech-stack.md +127 -0
  50. package/bundles/antigravity-plugin/skills/gh-cli/SKILL.md +195 -0
  51. package/bundles/antigravity-plugin/skills/git-commit/SKILL.md +285 -0
  52. package/bundles/antigravity-plugin/skills/golden-plan/SKILL.md +577 -0
  53. package/bundles/antigravity-plugin/skills/handoff/SKILL.md +100 -0
  54. package/bundles/antigravity-plugin/skills/implement/SKILL.md +114 -0
  55. package/bundles/antigravity-plugin/skills/javascript-typescript/SKILL.md +142 -0
  56. package/bundles/antigravity-plugin/skills/kb/SKILL.md +60 -0
  57. package/bundles/antigravity-plugin/skills/mermaid-db/SKILL.md +33 -0
  58. package/bundles/antigravity-plugin/skills/mermaid-diagrams/SKILL.md +236 -0
  59. package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/ENHANCEMENTS.md +264 -0
  60. package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/MERMAID-SUMMARY.md +137 -0
  61. package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/advanced-features.md +556 -0
  62. package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/architecture-diagrams.md +192 -0
  63. package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/c4-diagrams.md +410 -0
  64. package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/class-diagrams.md +361 -0
  65. package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/erd-diagrams.md +510 -0
  66. package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/flowcharts.md +450 -0
  67. package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/sequence-diagrams.md +394 -0
  68. package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/troubleshooting.md +335 -0
  69. package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/workflows.md +418 -0
  70. package/bundles/antigravity-plugin/skills/migrate-dir/SKILL.md +68 -0
  71. package/bundles/antigravity-plugin/skills/mockup/SKILL.md +242 -0
  72. package/bundles/antigravity-plugin/skills/particle-art/SKILL.md +243 -0
  73. package/bundles/antigravity-plugin/skills/particle-art/references/canvas-utils.ts +171 -0
  74. package/bundles/antigravity-plugin/skills/particle-art/references/dot-field.template.tsx +203 -0
  75. package/bundles/antigravity-plugin/skills/particle-art/references/flow-field.template.tsx +263 -0
  76. package/bundles/antigravity-plugin/skills/particle-art/references/node-shape.template.tsx +261 -0
  77. package/bundles/antigravity-plugin/skills/particle-art/references/shape-sampler.ts +281 -0
  78. package/bundles/antigravity-plugin/skills/particle-art/references/stipple-morph.template.tsx +167 -0
  79. package/bundles/antigravity-plugin/skills/particle-art/references/stipple.template.tsx +175 -0
  80. package/bundles/antigravity-plugin/skills/particle-art/references/trail-ghost.template.tsx +266 -0
  81. package/bundles/antigravity-plugin/skills/particle-art/references/usage-examples.md +320 -0
  82. package/bundles/antigravity-plugin/skills/playwright/API_REFERENCE.md +653 -0
  83. package/bundles/antigravity-plugin/skills/playwright/SKILL.md +454 -0
  84. package/bundles/antigravity-plugin/skills/playwright/lib/helpers.js +441 -0
  85. package/bundles/antigravity-plugin/skills/playwright/package.json +26 -0
  86. package/bundles/antigravity-plugin/skills/playwright/run.js +228 -0
  87. package/bundles/antigravity-plugin/skills/prd/SKILL.md +107 -0
  88. package/bundles/antigravity-plugin/skills/preflight/SKILL.md +435 -0
  89. package/bundles/antigravity-plugin/skills/python/SKILL.md +388 -0
  90. package/bundles/antigravity-plugin/skills/react-best-practices/SKILL.md +269 -0
  91. package/bundles/antigravity-plugin/skills/react-dev/README.md +404 -0
  92. package/bundles/antigravity-plugin/skills/react-dev/SKILL.md +459 -0
  93. package/bundles/antigravity-plugin/skills/react-dev/examples/generic-components.md +579 -0
  94. package/bundles/antigravity-plugin/skills/react-dev/examples/server-components.md +579 -0
  95. package/bundles/antigravity-plugin/skills/react-dev/references/event-handlers.md +574 -0
  96. package/bundles/antigravity-plugin/skills/react-dev/references/hooks.md +456 -0
  97. package/bundles/antigravity-plugin/skills/react-dev/references/react-19-patterns.md +638 -0
  98. package/bundles/antigravity-plugin/skills/react-dev/references/react-router.md +1002 -0
  99. package/bundles/antigravity-plugin/skills/react-dev/references/tanstack-router.md +587 -0
  100. package/bundles/antigravity-plugin/skills/refactoring/SKILL.md +486 -0
  101. package/bundles/antigravity-plugin/skills/resume/SKILL.md +36 -0
  102. package/bundles/antigravity-plugin/skills/security-review/SKILL.md +196 -0
  103. package/bundles/antigravity-plugin/skills/setup-project-ai/SKILL.md +61 -0
  104. package/bundles/antigravity-plugin/skills/ship/SKILL.md +107 -0
  105. package/bundles/antigravity-plugin/skills/ship-merge/SKILL.md +103 -0
  106. package/bundles/antigravity-plugin/skills/ship-pr/SKILL.md +102 -0
  107. package/bundles/antigravity-plugin/skills/skill-creator/LICENSE.txt +202 -0
  108. package/bundles/antigravity-plugin/skills/skill-creator/SKILL.md +491 -0
  109. package/bundles/antigravity-plugin/skills/skill-creator/agents/analyzer.md +274 -0
  110. package/bundles/antigravity-plugin/skills/skill-creator/agents/comparator.md +202 -0
  111. package/bundles/antigravity-plugin/skills/skill-creator/agents/grader.md +223 -0
  112. package/bundles/antigravity-plugin/skills/skill-creator/assets/eval_review.html +146 -0
  113. package/bundles/antigravity-plugin/skills/skill-creator/eval-viewer/generate_review.py +471 -0
  114. package/bundles/antigravity-plugin/skills/skill-creator/eval-viewer/viewer.html +1325 -0
  115. package/bundles/antigravity-plugin/skills/skill-creator/references/schemas.md +430 -0
  116. package/bundles/antigravity-plugin/skills/skill-creator/scripts/__init__.py +0 -0
  117. package/bundles/antigravity-plugin/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  118. package/bundles/antigravity-plugin/skills/skill-creator/scripts/generate_report.py +326 -0
  119. package/bundles/antigravity-plugin/skills/skill-creator/scripts/improve_description.py +247 -0
  120. package/bundles/antigravity-plugin/skills/skill-creator/scripts/package_skill.py +136 -0
  121. package/bundles/antigravity-plugin/skills/skill-creator/scripts/quick_validate.py +103 -0
  122. package/bundles/antigravity-plugin/skills/skill-creator/scripts/run_eval.py +310 -0
  123. package/bundles/antigravity-plugin/skills/skill-creator/scripts/run_loop.py +328 -0
  124. package/bundles/antigravity-plugin/skills/skill-creator/scripts/utils.py +47 -0
  125. package/bundles/antigravity-plugin/skills/sql/SKILL.md +321 -0
  126. package/bundles/antigravity-plugin/skills/tdd/SKILL.md +37 -0
  127. package/bundles/antigravity-plugin/skills/tdd-workflow/SKILL.md +188 -0
  128. package/bundles/antigravity-plugin/skills/technical-writing/SKILL.md +286 -0
  129. package/bundles/antigravity-plugin/skills/test-strategy/SKILL.md +155 -0
  130. package/bundles/antigravity-plugin/skills/ui-brief/SKILL.md +84 -0
  131. package/bundles/antigravity-plugin/skills/ui-review/SKILL.md +176 -0
  132. package/bundles/antigravity-plugin/skills/ui-review/references/framework-fixes.md +471 -0
  133. package/bundles/antigravity-plugin/skills/ui-review/references/visual-checklist.md +236 -0
  134. package/bundles/antigravity-plugin/skills/usage-review/SKILL.md +77 -0
  135. package/bundles/antigravity-plugin/skills/use-model/SKILL.md +64 -0
  136. package/bundles/antigravity-plugin/skills/using-git-worktrees/SKILL.md +217 -0
  137. package/bundles/antigravity-plugin/skills/version/SKILL.md +18 -0
  138. package/bundles/antigravity-plugin/skills/warm-editorial-ui/DESIGN_TOKENS.md +487 -0
  139. package/bundles/antigravity-plugin/skills/warm-editorial-ui/IMPLEMENTATION_GUIDE.md +177 -0
  140. package/bundles/antigravity-plugin/skills/warm-editorial-ui/SKILL.md +732 -0
  141. package/bundles/antigravity-plugin/skills/webapp-testing/LICENSE.txt +202 -0
  142. package/bundles/antigravity-plugin/skills/webapp-testing/SKILL.md +97 -0
  143. package/bundles/antigravity-plugin/skills/webapp-testing/examples/console_logging.py +35 -0
  144. package/bundles/antigravity-plugin/skills/webapp-testing/examples/element_discovery.py +40 -0
  145. package/bundles/antigravity-plugin/skills/webapp-testing/examples/static_html_automation.py +33 -0
  146. package/bundles/antigravity-plugin/skills/webapp-testing/scripts/with_server.py +106 -0
  147. package/bundles/antigravity-plugin/skills/windows-deployment/SKILL.md +880 -0
  148. package/bundles/antigravity-plugin/skills/writing-plans/SKILL.md +384 -0
  149. package/bundles/antigravity-plugin/statusline-command-agy.sh +91 -0
  150. package/bundles/antigravity-plugin/warm_start_agy.py +149 -0
  151. package/bundles/antigravity-plugin-extras/plugin.json +5 -0
  152. package/bundles/antigravity-plugin-extras/skills/_registry.md +154 -0
  153. package/bundles/antigravity-plugin-extras/skills/agent-md-refactor/SKILL.md +356 -0
  154. package/bundles/antigravity-plugin-extras/skills/agent-orchestration/SKILL.md +517 -0
  155. package/bundles/antigravity-plugin-extras/skills/algorithmic-art/SKILL.md +293 -0
  156. package/bundles/antigravity-plugin-extras/skills/anchor-review/SKILL.md +135 -0
  157. package/bundles/antigravity-plugin-extras/skills/api-client-patterns/SKILL.md +367 -0
  158. package/bundles/antigravity-plugin-extras/skills/architecture-design/SKILL.md +244 -0
  159. package/bundles/antigravity-plugin-extras/skills/architecture-diagram/SKILL.md +156 -0
  160. package/bundles/antigravity-plugin-extras/skills/architecture-document/SKILL.md +321 -0
  161. package/bundles/antigravity-plugin-extras/skills/artifacts-builder/LICENSE.txt +202 -0
  162. package/bundles/antigravity-plugin-extras/skills/artifacts-builder/SKILL.md +74 -0
  163. package/bundles/antigravity-plugin-extras/skills/artifacts-builder/scripts/bundle-artifact.sh +54 -0
  164. package/bundles/antigravity-plugin-extras/skills/artifacts-builder/scripts/init-artifact.sh +322 -0
  165. package/bundles/antigravity-plugin-extras/skills/artifacts-builder/scripts/shadcn-components.tar.gz +0 -0
  166. package/bundles/antigravity-plugin-extras/skills/ascii-art/SKILL.md +144 -0
  167. package/bundles/antigravity-plugin-extras/skills/asking-questions/SKILL.md +81 -0
  168. package/bundles/antigravity-plugin-extras/skills/aws-agentic-ai/SKILL.md +118 -0
  169. package/bundles/antigravity-plugin-extras/skills/aws-agentic-ai/cross-service/credential-management.md +450 -0
  170. package/bundles/antigravity-plugin-extras/skills/aws-agentic-ai/services/browser/README.md +363 -0
  171. package/bundles/antigravity-plugin-extras/skills/aws-agentic-ai/services/code-interpreter/README.md +232 -0
  172. package/bundles/antigravity-plugin-extras/skills/aws-agentic-ai/services/gateway/README.md +100 -0
  173. package/bundles/antigravity-plugin-extras/skills/aws-agentic-ai/services/gateway/deploy-template.sh +117 -0
  174. package/bundles/antigravity-plugin-extras/skills/aws-agentic-ai/services/gateway/deployment-strategies.md +416 -0
  175. package/bundles/antigravity-plugin-extras/skills/aws-agentic-ai/services/gateway/troubleshooting-guide.md +384 -0
  176. package/bundles/antigravity-plugin-extras/skills/aws-agentic-ai/services/gateway/validate-deployment.sh +186 -0
  177. package/bundles/antigravity-plugin-extras/skills/aws-agentic-ai/services/identity/README.md +186 -0
  178. package/bundles/antigravity-plugin-extras/skills/aws-agentic-ai/services/memory/README.md +253 -0
  179. package/bundles/antigravity-plugin-extras/skills/aws-agentic-ai/services/observability/README.md +517 -0
  180. package/bundles/antigravity-plugin-extras/skills/aws-agentic-ai/services/runtime/README.md +215 -0
  181. package/bundles/antigravity-plugin-extras/skills/aws-cdk-development/SKILL.md +279 -0
  182. package/bundles/antigravity-plugin-extras/skills/aws-cdk-development/references/cdk-patterns.md +431 -0
  183. package/bundles/antigravity-plugin-extras/skills/aws-cdk-development/scripts/validate-stack.sh +240 -0
  184. package/bundles/antigravity-plugin-extras/skills/aws-cost-operations/SKILL.md +318 -0
  185. package/bundles/antigravity-plugin-extras/skills/aws-cost-operations/references/cloudwatch-alarms.md +567 -0
  186. package/bundles/antigravity-plugin-extras/skills/aws-cost-operations/references/operations-patterns.md +394 -0
  187. package/bundles/antigravity-plugin-extras/skills/aws-serverless-eda/SKILL.md +758 -0
  188. package/bundles/antigravity-plugin-extras/skills/aws-serverless-eda/references/deployment-best-practices.md +830 -0
  189. package/bundles/antigravity-plugin-extras/skills/aws-serverless-eda/references/eda-patterns.md +1002 -0
  190. package/bundles/antigravity-plugin-extras/skills/aws-serverless-eda/references/observability-best-practices.md +770 -0
  191. package/bundles/antigravity-plugin-extras/skills/aws-serverless-eda/references/performance-optimization.md +671 -0
  192. package/bundles/antigravity-plugin-extras/skills/aws-serverless-eda/references/security-best-practices.md +625 -0
  193. package/bundles/antigravity-plugin-extras/skills/aws-serverless-eda/references/serverless-patterns.md +838 -0
  194. package/bundles/antigravity-plugin-extras/skills/backend-to-frontend-handoff/SKILL.md +139 -0
  195. package/bundles/antigravity-plugin-extras/skills/banner-design/SKILL.md +188 -0
  196. package/bundles/antigravity-plugin-extras/skills/brand-design/SKILL.md +298 -0
  197. package/bundles/antigravity-plugin-extras/skills/brand-guidelines/LICENSE.txt +202 -0
  198. package/bundles/antigravity-plugin-extras/skills/brand-guidelines/SKILL.md +118 -0
  199. package/bundles/antigravity-plugin-extras/skills/brand-voice/SKILL.md +93 -0
  200. package/bundles/antigravity-plugin-extras/skills/building-ai-agent-on-cloudflare/SKILL.md +392 -0
  201. package/bundles/antigravity-plugin-extras/skills/building-ai-agent-on-cloudflare/references/agent-patterns.md +461 -0
  202. package/bundles/antigravity-plugin-extras/skills/building-ai-agent-on-cloudflare/references/examples.md +188 -0
  203. package/bundles/antigravity-plugin-extras/skills/building-ai-agent-on-cloudflare/references/state-patterns.md +360 -0
  204. package/bundles/antigravity-plugin-extras/skills/building-ai-agent-on-cloudflare/references/troubleshooting.md +362 -0
  205. package/bundles/antigravity-plugin-extras/skills/building-mcp-server-on-cloudflare/SKILL.md +266 -0
  206. package/bundles/antigravity-plugin-extras/skills/building-mcp-server-on-cloudflare/references/examples.md +115 -0
  207. package/bundles/antigravity-plugin-extras/skills/building-mcp-server-on-cloudflare/references/oauth-setup.md +338 -0
  208. package/bundles/antigravity-plugin-extras/skills/building-mcp-server-on-cloudflare/references/troubleshooting.md +317 -0
  209. package/bundles/antigravity-plugin-extras/skills/caching-patterns/SKILL.md +385 -0
  210. package/bundles/antigravity-plugin-extras/skills/canvas-design/LICENSE.txt +202 -0
  211. package/bundles/antigravity-plugin-extras/skills/canvas-design/SKILL.md +91 -0
  212. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/ArsenalSC-OFL.txt +93 -0
  213. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/ArsenalSC-Regular.ttf +0 -0
  214. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/BigShoulders-Bold.ttf +0 -0
  215. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/BigShoulders-OFL.txt +93 -0
  216. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/BigShoulders-Regular.ttf +0 -0
  217. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Boldonse-OFL.txt +93 -0
  218. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Boldonse-Regular.ttf +0 -0
  219. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/BricolageGrotesque-Bold.ttf +0 -0
  220. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/BricolageGrotesque-OFL.txt +93 -0
  221. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/BricolageGrotesque-Regular.ttf +0 -0
  222. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/CrimsonPro-Bold.ttf +0 -0
  223. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/CrimsonPro-Italic.ttf +0 -0
  224. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/CrimsonPro-OFL.txt +93 -0
  225. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/CrimsonPro-Regular.ttf +0 -0
  226. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/DMMono-OFL.txt +93 -0
  227. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/DMMono-Regular.ttf +0 -0
  228. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/EricaOne-OFL.txt +94 -0
  229. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/EricaOne-Regular.ttf +0 -0
  230. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/GeistMono-Bold.ttf +0 -0
  231. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/GeistMono-OFL.txt +93 -0
  232. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/GeistMono-Regular.ttf +0 -0
  233. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Gloock-OFL.txt +93 -0
  234. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Gloock-Regular.ttf +0 -0
  235. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/IBMPlexMono-Bold.ttf +0 -0
  236. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/IBMPlexMono-OFL.txt +93 -0
  237. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/IBMPlexMono-Regular.ttf +0 -0
  238. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/IBMPlexSerif-Bold.ttf +0 -0
  239. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/IBMPlexSerif-BoldItalic.ttf +0 -0
  240. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/IBMPlexSerif-Italic.ttf +0 -0
  241. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/IBMPlexSerif-Regular.ttf +0 -0
  242. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/InstrumentSans-Bold.ttf +0 -0
  243. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/InstrumentSans-BoldItalic.ttf +0 -0
  244. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/InstrumentSans-Italic.ttf +0 -0
  245. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/InstrumentSans-OFL.txt +93 -0
  246. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/InstrumentSans-Regular.ttf +0 -0
  247. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/InstrumentSerif-Italic.ttf +0 -0
  248. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/InstrumentSerif-Regular.ttf +0 -0
  249. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Italiana-OFL.txt +93 -0
  250. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Italiana-Regular.ttf +0 -0
  251. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/JetBrainsMono-Bold.ttf +0 -0
  252. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/JetBrainsMono-OFL.txt +93 -0
  253. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/JetBrainsMono-Regular.ttf +0 -0
  254. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Jura-Light.ttf +0 -0
  255. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Jura-Medium.ttf +0 -0
  256. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Jura-OFL.txt +93 -0
  257. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/LibreBaskerville-OFL.txt +93 -0
  258. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/LibreBaskerville-Regular.ttf +0 -0
  259. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Lora-Bold.ttf +0 -0
  260. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Lora-BoldItalic.ttf +0 -0
  261. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Lora-Italic.ttf +0 -0
  262. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Lora-OFL.txt +93 -0
  263. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Lora-Regular.ttf +0 -0
  264. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/NationalPark-Bold.ttf +0 -0
  265. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/NationalPark-OFL.txt +93 -0
  266. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/NationalPark-Regular.ttf +0 -0
  267. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/NothingYouCouldDo-OFL.txt +93 -0
  268. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/NothingYouCouldDo-Regular.ttf +0 -0
  269. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Outfit-Bold.ttf +0 -0
  270. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Outfit-OFL.txt +93 -0
  271. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Outfit-Regular.ttf +0 -0
  272. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/PixelifySans-Medium.ttf +0 -0
  273. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/PixelifySans-OFL.txt +93 -0
  274. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/PoiretOne-OFL.txt +93 -0
  275. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/PoiretOne-Regular.ttf +0 -0
  276. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/RedHatMono-Bold.ttf +0 -0
  277. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/RedHatMono-OFL.txt +93 -0
  278. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/RedHatMono-Regular.ttf +0 -0
  279. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Silkscreen-OFL.txt +93 -0
  280. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Silkscreen-Regular.ttf +0 -0
  281. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/SmoochSans-Medium.ttf +0 -0
  282. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/SmoochSans-OFL.txt +93 -0
  283. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Tektur-Medium.ttf +0 -0
  284. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Tektur-OFL.txt +93 -0
  285. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/Tektur-Regular.ttf +0 -0
  286. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/WorkSans-Bold.ttf +0 -0
  287. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/WorkSans-BoldItalic.ttf +0 -0
  288. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/WorkSans-Italic.ttf +0 -0
  289. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/WorkSans-OFL.txt +93 -0
  290. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/WorkSans-Regular.ttf +0 -0
  291. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/YoungSerif-OFL.txt +93 -0
  292. package/bundles/antigravity-plugin-extras/skills/canvas-design/canvas-fonts/YoungSerif-Regular.ttf +0 -0
  293. package/bundles/antigravity-plugin-extras/skills/changelog-generator/SKILL.md +105 -0
  294. package/bundles/antigravity-plugin-extras/skills/code-explainer/SKILL.md +91 -0
  295. package/bundles/antigravity-plugin-extras/skills/competitive-ads-extractor/SKILL.md +294 -0
  296. package/bundles/antigravity-plugin-extras/skills/component-testing/SKILL.md +310 -0
  297. package/bundles/antigravity-plugin-extras/skills/content-research-writer/SKILL.md +539 -0
  298. package/bundles/antigravity-plugin-extras/skills/cross-review/SKILL.md +51 -0
  299. package/bundles/antigravity-plugin-extras/skills/data-analysis/SKILL.md +111 -0
  300. package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/README.md +248 -0
  301. package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/SKILL.md +203 -0
  302. package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/assets/adapter.py.template +44 -0
  303. package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/assets/api-router.py.template +43 -0
  304. package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/assets/contract-test.py.template +384 -0
  305. package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/assets/display-dto.py.template +39 -0
  306. package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/assets/frontend-service.ts.template +46 -0
  307. package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/assets/frontend-types.ts.template +38 -0
  308. package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/assets/ingestion-model.py.template +42 -0
  309. package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/assets/mapping-doc.md.template +46 -0
  310. package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/assets/normalizer.py.template +48 -0
  311. package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/assets/prompts/extract_requirements.md +36 -0
  312. package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/assets/prompts/extract_ui_demand.md +35 -0
  313. package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/assets/prompts/reconcile_mapping.md +66 -0
  314. package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/assets/query-catalog.md.template +62 -0
  315. package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/references/consumer-driven-contracts.md +131 -0
  316. package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/references/drift-check-catalog.md +305 -0
  317. package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/references/layer-responsibilities.md +333 -0
  318. package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/scripts/discover_connections.py +170 -0
  319. package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/scripts/discover_sources.py +161 -0
  320. package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/scripts/drift_check.py +171 -0
  321. package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/scripts/extract_schema.py +1363 -0
  322. package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/scripts/generate_gap_report.py +81 -0
  323. package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/scripts/generate_mapping_doc.py +294 -0
  324. package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/scripts/ts_dto_compare.py +214 -0
  325. package/bundles/antigravity-plugin-extras/skills/data-contract-pipeline/scripts/validate_lineage.py +51 -0
  326. package/bundles/antigravity-plugin-extras/skills/data-loader/SKILL.md +753 -0
  327. package/bundles/antigravity-plugin-extras/skills/data-validation/SKILL.md +220 -0
  328. package/bundles/antigravity-plugin-extras/skills/db-testing/SKILL.md +117 -0
  329. package/bundles/antigravity-plugin-extras/skills/deploy-local/SKILL.md +259 -0
  330. package/bundles/antigravity-plugin-extras/skills/design-md/SKILL.md +167 -0
  331. package/bundles/antigravity-plugin-extras/skills/design-system-tokens/SKILL.md +240 -0
  332. package/bundles/antigravity-plugin-extras/skills/design-system-tokens/references/DESIGN-SYSTEM-TEMPLATE.md +577 -0
  333. package/bundles/antigravity-plugin-extras/skills/developer-growth-analysis/SKILL.md +323 -0
  334. package/bundles/antigravity-plugin-extras/skills/doc-coauthoring/SKILL.md +378 -0
  335. package/bundles/antigravity-plugin-extras/skills/documentation-analyzer/SKILL.md +78 -0
  336. package/bundles/antigravity-plugin-extras/skills/domain-name-brainstormer/SKILL.md +213 -0
  337. package/bundles/antigravity-plugin-extras/skills/enterprise-dashboard-aesthetic-system/SKILL.md +120 -0
  338. package/bundles/antigravity-plugin-extras/skills/excalidraw/SKILL.md +193 -0
  339. package/bundles/antigravity-plugin-extras/skills/excalidraw/references/element-types.md +497 -0
  340. package/bundles/antigravity-plugin-extras/skills/excalidraw/references/excalidraw-schema.md +356 -0
  341. package/bundles/antigravity-plugin-extras/skills/excalidraw/references/reliable-diagrams.md +115 -0
  342. package/bundles/antigravity-plugin-extras/skills/excalidraw/scripts/README.md +60 -0
  343. package/bundles/antigravity-plugin-extras/skills/excalidraw/scripts/add-arrow.py +354 -0
  344. package/bundles/antigravity-plugin-extras/skills/excalidraw/scripts/add-icon-to-diagram.py +412 -0
  345. package/bundles/antigravity-plugin-extras/skills/excalidraw/scripts/split-excalidraw-library.py +183 -0
  346. package/bundles/antigravity-plugin-extras/skills/excalidraw/templates/business-flow-swimlane-template.excalidraw +334 -0
  347. package/bundles/antigravity-plugin-extras/skills/excalidraw/templates/class-diagram-template.excalidraw +558 -0
  348. package/bundles/antigravity-plugin-extras/skills/excalidraw/templates/data-flow-diagram-template.excalidraw +279 -0
  349. package/bundles/antigravity-plugin-extras/skills/excalidraw/templates/er-diagram-template.excalidraw +662 -0
  350. package/bundles/antigravity-plugin-extras/skills/excalidraw/templates/flowchart-template.excalidraw +179 -0
  351. package/bundles/antigravity-plugin-extras/skills/excalidraw/templates/mindmap-template.excalidraw +244 -0
  352. package/bundles/antigravity-plugin-extras/skills/excalidraw/templates/relationship-template.excalidraw +145 -0
  353. package/bundles/antigravity-plugin-extras/skills/excalidraw/templates/reliable-flowchart.excalidraw +1 -0
  354. package/bundles/antigravity-plugin-extras/skills/excalidraw/templates/sequence-diagram-template.excalidraw +509 -0
  355. package/bundles/antigravity-plugin-extras/skills/file-organizer/SKILL.md +434 -0
  356. package/bundles/antigravity-plugin-extras/skills/game-changing-features/README.md +221 -0
  357. package/bundles/antigravity-plugin-extras/skills/game-changing-features/SKILL.md +265 -0
  358. package/bundles/antigravity-plugin-extras/skills/github-issues/SKILL.md +359 -0
  359. package/bundles/antigravity-plugin-extras/skills/github-issues/references/templates.md +90 -0
  360. package/bundles/antigravity-plugin-extras/skills/golden-nuggets/SKILL.md +192 -0
  361. package/bundles/antigravity-plugin-extras/skills/humanizer/SKILL.md +194 -0
  362. package/bundles/antigravity-plugin-extras/skills/image-enhancer/SKILL.md +100 -0
  363. package/bundles/antigravity-plugin-extras/skills/intent-writer/SKILL.md +201 -0
  364. package/bundles/antigravity-plugin-extras/skills/internal-comms/LICENSE.txt +202 -0
  365. package/bundles/antigravity-plugin-extras/skills/internal-comms/SKILL.md +124 -0
  366. package/bundles/antigravity-plugin-extras/skills/internal-comms/examples/3p-updates.md +47 -0
  367. package/bundles/antigravity-plugin-extras/skills/internal-comms/examples/company-newsletter.md +65 -0
  368. package/bundles/antigravity-plugin-extras/skills/internal-comms/examples/faq-answers.md +30 -0
  369. package/bundles/antigravity-plugin-extras/skills/internal-comms/examples/general-comms.md +16 -0
  370. package/bundles/antigravity-plugin-extras/skills/invoice-organizer/SKILL.md +447 -0
  371. package/bundles/antigravity-plugin-extras/skills/jira-issues/SKILL.md +182 -0
  372. package/bundles/antigravity-plugin-extras/skills/job-application/SKILL.md +188 -0
  373. package/bundles/antigravity-plugin-extras/skills/lead-research-assistant/SKILL.md +200 -0
  374. package/bundles/antigravity-plugin-extras/skills/llm-application-dev/SKILL.md +217 -0
  375. package/bundles/antigravity-plugin-extras/skills/mcp-builder/LICENSE.txt +202 -0
  376. package/bundles/antigravity-plugin-extras/skills/mcp-builder/SKILL.md +100 -0
  377. package/bundles/antigravity-plugin-extras/skills/mcp-builder/references/evaluation.md +602 -0
  378. package/bundles/antigravity-plugin-extras/skills/mcp-builder/references/mcp_best_practices.md +915 -0
  379. package/bundles/antigravity-plugin-extras/skills/mcp-builder/references/node_mcp_server.md +916 -0
  380. package/bundles/antigravity-plugin-extras/skills/mcp-builder/references/python_mcp_server.md +752 -0
  381. package/bundles/antigravity-plugin-extras/skills/mcp-builder/scripts/connections.py +151 -0
  382. package/bundles/antigravity-plugin-extras/skills/mcp-builder/scripts/evaluation.py +373 -0
  383. package/bundles/antigravity-plugin-extras/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
  384. package/bundles/antigravity-plugin-extras/skills/mcp-builder/scripts/requirements.txt +2 -0
  385. package/bundles/antigravity-plugin-extras/skills/meeting-insights-analyzer/SKILL.md +328 -0
  386. package/bundles/antigravity-plugin-extras/skills/monorepo/SKILL.md +267 -0
  387. package/bundles/antigravity-plugin-extras/skills/naming-analyzer/SKILL.md +376 -0
  388. package/bundles/antigravity-plugin-extras/skills/nextjs-app-router/SKILL.md +271 -0
  389. package/bundles/antigravity-plugin-extras/skills/notebooklm/AUTHENTICATION.md +154 -0
  390. package/bundles/antigravity-plugin-extras/skills/notebooklm/SKILL.md +270 -0
  391. package/bundles/antigravity-plugin-extras/skills/notebooklm/references/api_reference.md +309 -0
  392. package/bundles/antigravity-plugin-extras/skills/notebooklm/references/troubleshooting.md +376 -0
  393. package/bundles/antigravity-plugin-extras/skills/notebooklm/references/usage_patterns.md +338 -0
  394. package/bundles/antigravity-plugin-extras/skills/notebooklm/scripts/__init__.py +81 -0
  395. package/bundles/antigravity-plugin-extras/skills/notebooklm/scripts/ask_question.py +256 -0
  396. package/bundles/antigravity-plugin-extras/skills/notebooklm/scripts/auth_manager.py +358 -0
  397. package/bundles/antigravity-plugin-extras/skills/notebooklm/scripts/browser_session.py +255 -0
  398. package/bundles/antigravity-plugin-extras/skills/notebooklm/scripts/browser_utils.py +107 -0
  399. package/bundles/antigravity-plugin-extras/skills/notebooklm/scripts/cleanup_manager.py +302 -0
  400. package/bundles/antigravity-plugin-extras/skills/notebooklm/scripts/config.py +44 -0
  401. package/bundles/antigravity-plugin-extras/skills/notebooklm/scripts/notebook_manager.py +410 -0
  402. package/bundles/antigravity-plugin-extras/skills/notebooklm/scripts/run.py +102 -0
  403. package/bundles/antigravity-plugin-extras/skills/notebooklm/scripts/setup_environment.py +204 -0
  404. package/bundles/antigravity-plugin-extras/skills/observability/SKILL.md +371 -0
  405. package/bundles/antigravity-plugin-extras/skills/onboard-project/SKILL.md +192 -0
  406. package/bundles/antigravity-plugin-extras/skills/performance-testing/SKILL.md +226 -0
  407. package/bundles/antigravity-plugin-extras/skills/pipeline-state/SKILL.md +208 -0
  408. package/bundles/antigravity-plugin-extras/skills/plantuml/SKILL.md +228 -0
  409. package/bundles/antigravity-plugin-extras/skills/popular-web-designs/SKILL.md +142 -0
  410. package/bundles/antigravity-plugin-extras/skills/popular-web-designs/templates/linear.app.md +196 -0
  411. package/bundles/antigravity-plugin-extras/skills/popular-web-designs/templates/stripe.md +214 -0
  412. package/bundles/antigravity-plugin-extras/skills/popular-web-designs/templates/vercel.md +218 -0
  413. package/bundles/antigravity-plugin-extras/skills/prd-to-code/SKILL.md +127 -0
  414. package/bundles/antigravity-plugin-extras/skills/raffle-winner-picker/SKILL.md +159 -0
  415. package/bundles/antigravity-plugin-extras/skills/react-useeffect/README.md +320 -0
  416. package/bundles/antigravity-plugin-extras/skills/react-useeffect/SKILL.md +53 -0
  417. package/bundles/antigravity-plugin-extras/skills/react-useeffect/alternatives.md +258 -0
  418. package/bundles/antigravity-plugin-extras/skills/react-useeffect/anti-patterns.md +290 -0
  419. package/bundles/antigravity-plugin-extras/skills/receiving-code-review/SKILL.md +213 -0
  420. package/bundles/antigravity-plugin-extras/skills/relay/SKILL.md +246 -0
  421. package/bundles/antigravity-plugin-extras/skills/requesting-code-review/SKILL.md +105 -0
  422. package/bundles/antigravity-plugin-extras/skills/requesting-code-review/code-reviewer.md +146 -0
  423. package/bundles/antigravity-plugin-extras/skills/responsive-mobile-native/SKILL.md +350 -0
  424. package/bundles/antigravity-plugin-extras/skills/responsive-mobile-native/references/pwa-checklist.md +226 -0
  425. package/bundles/antigravity-plugin-extras/skills/responsive-mobile-native/references/touch-and-interaction.md +265 -0
  426. package/bundles/antigravity-plugin-extras/skills/responsive-mobile-native/references/transformation-catalogue.md +326 -0
  427. package/bundles/antigravity-plugin-extras/skills/schema-migration/SKILL.md +275 -0
  428. package/bundles/antigravity-plugin-extras/skills/setup-project-ai/SKILL.md +120 -0
  429. package/bundles/antigravity-plugin-extras/skills/shadcn-radix/SKILL.md +330 -0
  430. package/bundles/antigravity-plugin-extras/skills/sketch/SKILL.md +104 -0
  431. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/LICENSE.txt +202 -0
  432. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/SKILL.md +646 -0
  433. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/core/color_palettes.py +302 -0
  434. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/core/easing.py +230 -0
  435. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/core/frame_composer.py +469 -0
  436. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/core/gif_builder.py +246 -0
  437. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/core/typography.py +357 -0
  438. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/core/validators.py +264 -0
  439. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/core/visual_effects.py +494 -0
  440. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/requirements.txt +4 -0
  441. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/templates/bounce.py +106 -0
  442. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/templates/explode.py +331 -0
  443. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/templates/fade.py +329 -0
  444. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/templates/flip.py +291 -0
  445. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/templates/kaleidoscope.py +211 -0
  446. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/templates/morph.py +329 -0
  447. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/templates/move.py +293 -0
  448. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/templates/pulse.py +268 -0
  449. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/templates/shake.py +127 -0
  450. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/templates/slide.py +291 -0
  451. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/templates/spin.py +269 -0
  452. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/templates/wiggle.py +300 -0
  453. package/bundles/antigravity-plugin-extras/skills/slack-gif-creator/templates/zoom.py +312 -0
  454. package/bundles/antigravity-plugin-extras/skills/slides/SKILL.md +38 -0
  455. package/bundles/antigravity-plugin-extras/skills/state-tracking/SKILL.md +131 -0
  456. package/bundles/antigravity-plugin-extras/skills/streamlit-dev/SKILL.md +268 -0
  457. package/bundles/antigravity-plugin-extras/skills/svg-create/SKILL.md +251 -0
  458. package/bundles/antigravity-plugin-extras/skills/theme-factory/LICENSE.txt +202 -0
  459. package/bundles/antigravity-plugin-extras/skills/theme-factory/SKILL.md +59 -0
  460. package/bundles/antigravity-plugin-extras/skills/theme-factory/theme-showcase.pdf +0 -0
  461. package/bundles/antigravity-plugin-extras/skills/theme-factory/themes/arctic-frost.md +19 -0
  462. package/bundles/antigravity-plugin-extras/skills/theme-factory/themes/botanical-garden.md +19 -0
  463. package/bundles/antigravity-plugin-extras/skills/theme-factory/themes/desert-rose.md +19 -0
  464. package/bundles/antigravity-plugin-extras/skills/theme-factory/themes/forest-canopy.md +19 -0
  465. package/bundles/antigravity-plugin-extras/skills/theme-factory/themes/golden-hour.md +19 -0
  466. package/bundles/antigravity-plugin-extras/skills/theme-factory/themes/midnight-galaxy.md +19 -0
  467. package/bundles/antigravity-plugin-extras/skills/theme-factory/themes/modern-minimalist.md +19 -0
  468. package/bundles/antigravity-plugin-extras/skills/theme-factory/themes/ocean-depths.md +19 -0
  469. package/bundles/antigravity-plugin-extras/skills/theme-factory/themes/sunset-boulevard.md +19 -0
  470. package/bundles/antigravity-plugin-extras/skills/theme-factory/themes/tech-innovation.md +19 -0
  471. package/bundles/antigravity-plugin-extras/skills/ui-styling-patterns/LICENSE.txt +202 -0
  472. package/bundles/antigravity-plugin-extras/skills/ui-styling-patterns/SKILL.md +320 -0
  473. package/bundles/antigravity-plugin-extras/skills/ui-testing/SKILL.md +660 -0
  474. package/bundles/antigravity-plugin-extras/skills/ui-ux-intelligence/SKILL.md +660 -0
  475. package/bundles/antigravity-plugin-extras/skills/ui-ux-intelligence/data/_sync_all.py +414 -0
  476. package/bundles/antigravity-plugin-extras/skills/ui-ux-intelligence/data/app-interface.csv +31 -0
  477. package/bundles/antigravity-plugin-extras/skills/ui-ux-intelligence/data/charts.csv +26 -0
  478. package/bundles/antigravity-plugin-extras/skills/ui-ux-intelligence/data/colors.csv +162 -0
  479. package/bundles/antigravity-plugin-extras/skills/ui-ux-intelligence/data/design.csv +1776 -0
  480. package/bundles/antigravity-plugin-extras/skills/ui-ux-intelligence/data/draft.csv +1779 -0
  481. package/bundles/antigravity-plugin-extras/skills/ui-ux-intelligence/data/google-fonts.csv +1924 -0
  482. package/bundles/antigravity-plugin-extras/skills/ui-ux-intelligence/data/icons.csv +106 -0
  483. package/bundles/antigravity-plugin-extras/skills/ui-ux-intelligence/data/landing.csv +35 -0
  484. package/bundles/antigravity-plugin-extras/skills/ui-ux-intelligence/data/products.csv +162 -0
  485. package/bundles/antigravity-plugin-extras/skills/ui-ux-intelligence/data/react-performance.csv +45 -0
  486. package/bundles/antigravity-plugin-extras/skills/ui-ux-intelligence/data/styles.csv +85 -0
  487. package/bundles/antigravity-plugin-extras/skills/ui-ux-intelligence/data/typography.csv +74 -0
  488. package/bundles/antigravity-plugin-extras/skills/ui-ux-intelligence/data/ui-reasoning.csv +162 -0
  489. package/bundles/antigravity-plugin-extras/skills/ui-ux-intelligence/data/ux-guidelines.csv +100 -0
  490. package/bundles/antigravity-plugin-extras/skills/ui-ux-intelligence/scripts/core.py +247 -0
  491. package/bundles/antigravity-plugin-extras/skills/ui-ux-intelligence/scripts/design_system.py +1067 -0
  492. package/bundles/antigravity-plugin-extras/skills/ui-ux-intelligence/scripts/search.py +114 -0
  493. package/bundles/antigravity-plugin-extras/skills/understand-anything/SKILL.md +140 -0
  494. package/bundles/antigravity-plugin-extras/skills/understand-anything/references/commands.md +252 -0
  495. package/bundles/antigravity-plugin-extras/skills/understand-anything/references/graph-schema.md +116 -0
  496. package/bundles/antigravity-plugin-extras/skills/understand-anything/references/pipeline.md +331 -0
  497. package/bundles/antigravity-plugin-extras/skills/update-readme/SKILL.md +74 -0
  498. package/bundles/antigravity-plugin-extras/skills/verification-loop/SKILL.md +209 -0
  499. package/bundles/antigravity-plugin-extras/skills/video-downloader/SKILL.md +107 -0
  500. package/bundles/antigravity-plugin-extras/skills/webapp-development/SKILL.md +288 -0
  501. package/bundles/antigravity-plugin-extras/skills/word-cloud/SKILL.md +339 -0
  502. package/bundles/antigravity-plugin-extras/skills/word-cloud/agents/agent-interface.md +175 -0
  503. package/bundles/antigravity-plugin-extras/skills/word-cloud/references/html.md +384 -0
  504. package/bundles/antigravity-plugin-extras/skills/word-cloud/references/nlp-keywords.md +127 -0
  505. package/bundles/antigravity-plugin-extras/skills/word-cloud/references/palettes.md +149 -0
  506. package/bundles/antigravity-plugin-extras/skills/word-cloud/references/python.md +249 -0
  507. package/bundles/antigravity-plugin-extras/skills/word-cloud/references/react.md +427 -0
  508. package/bundles/antigravity-plugin-extras/skills/word-cloud/references/shapes.md +135 -0
  509. package/bundles/antigravity-plugin-extras/skills/word-cloud/references/stopwords.md +115 -0
  510. package/bundles/antigravity-plugin-extras/skills/youtube-transcript/SKILL.md +416 -0
  511. package/bundles/manifest.json +7 -0
  512. package/dist/cli.js +5492 -0
  513. package/package.json +61 -4
@@ -0,0 +1,1363 @@
1
+ """
2
+ Unified Schema Extractor — Reads any structured data source and generates Pydantic models.
3
+
4
+ Supported formats:
5
+ - JSON (.json) — recursive nesting, list-of-objects, Field(alias=...)
6
+ - Markdown (.md) — tables, bold KV, code blocks, bullet KV, outer wrapper strip
7
+ - CSV/TSV (.csv/.tsv) — column headers, type inference from sample rows
8
+ - XLSX (.xlsx/.xls) — openpyxl, same column logic as CSV
9
+ - YAML (.yaml/.yml) — safe_load, delegates to dict logic
10
+ - Plain text (.txt/.log) — Key: Value, Key = Value, [Section] headers
11
+ - DB table (--format db) — SQLAlchemy inspect + data sampling + embedded format detection
12
+
13
+ DB discovery & sampling:
14
+ --discover — enumerate all tables/views with columns, PKs, FKs
15
+ --schema <name> — target a specific DB schema (e.g., 'dbo', 'public')
16
+ --sample N — fetch N rows for type inference and embedded format detection
17
+ Embedded formats detected: JSON, XML, YAML, markdown, pipe-delimited in string columns
18
+
19
+ Schema evolution:
20
+ --save-baseline snap.json — snapshot current schema for later comparison
21
+ --diff snap.json — compare current extraction vs baseline
22
+
23
+ Usage:
24
+ python extract_schema.py data.json
25
+ python extract_schema.py report.md --class-name NetworkReport
26
+ python extract_schema.py data.csv --output models/ingestion/report.py
27
+ python extract_schema.py data.json --save-baseline baseline.json
28
+ python extract_schema.py data_v2.json --diff baseline.json
29
+ python extract_schema.py --format db --connection-string "mssql+pyodbc://..." --discover
30
+ python extract_schema.py --format db --connection-string "..." --table users --sample 50
31
+ python extract_schema.py --format db --connection-string "..." --table orders --schema dbo
32
+
33
+ Part of the data-contract-pipeline skill.
34
+ """
35
+
36
+ from __future__ import annotations
37
+
38
+ import csv
39
+ import io
40
+ import json
41
+ import re
42
+ import sys
43
+ from dataclasses import dataclass, field
44
+ from datetime import datetime, timezone
45
+ from pathlib import Path
46
+ from typing import Any
47
+
48
+ # ---------------------------------------------------------------------------
49
+ # Data structures
50
+ # ---------------------------------------------------------------------------
51
+
52
+
53
+ @dataclass
54
+ class ExtractedField:
55
+ name: str
56
+ python_type: str
57
+ source_pattern: str
58
+ example_value: str = ""
59
+ section: str = "Root"
60
+
61
+
62
+ @dataclass
63
+ class ExtractedSection:
64
+ name: str
65
+ fields: list[ExtractedField] = field(default_factory=list)
66
+ table_rows_model: str | None = None
67
+
68
+
69
+ @dataclass
70
+ class SchemaChange:
71
+ field_name: str
72
+ change_type: str # added, removed, type_changed
73
+ old_value: str = ""
74
+ new_value: str = ""
75
+
76
+
77
+ # ---------------------------------------------------------------------------
78
+ # Shared helpers
79
+ # ---------------------------------------------------------------------------
80
+
81
+
82
+ def to_snake_case(name: str) -> str:
83
+ name = name.replace("**", "").strip().rstrip(":")
84
+ s = re.sub(r"[^a-zA-Z0-9]", "_", name)
85
+ s = re.sub(r"(.)([A-Z][a-z]+)", r"\1_\2", s)
86
+ s = re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", s)
87
+ s = re.sub(r"_+", "_", s).strip("_")
88
+ return s.lower()
89
+
90
+
91
+ def to_class_name(name: str) -> str:
92
+ snake = to_snake_case(name)
93
+ return "".join(word.capitalize() for word in snake.split("_"))
94
+
95
+
96
+ def infer_type_from_value(value: Any) -> str:
97
+ if value is None:
98
+ return "Any | None"
99
+ if isinstance(value, bool):
100
+ return "bool"
101
+ if isinstance(value, int):
102
+ return "int"
103
+ if isinstance(value, float):
104
+ return "float"
105
+ if isinstance(value, list):
106
+ if not value:
107
+ return "list"
108
+ types = {infer_type_from_value(v) for v in value}
109
+ return f"list[{types.pop()}]" if len(types) == 1 else "list"
110
+ if isinstance(value, dict):
111
+ return "dict"
112
+ v = str(value).strip()
113
+ if not v or v.lower() in ("n/a", "not available", "none", "null", "-"):
114
+ return "str | None"
115
+ if v.lower() in ("true", "false", "yes", "no"):
116
+ return "bool"
117
+ try:
118
+ int(v.replace(",", ""))
119
+ return "int"
120
+ except ValueError:
121
+ pass
122
+ try:
123
+ float(v.replace(",", ""))
124
+ return "float"
125
+ except ValueError:
126
+ pass
127
+ return "str"
128
+
129
+
130
+ # ---------------------------------------------------------------------------
131
+ # Format detection
132
+ # ---------------------------------------------------------------------------
133
+
134
+ FORMAT_MAP = {
135
+ ".json": "json",
136
+ ".md": "md",
137
+ ".markdown": "md",
138
+ ".csv": "csv",
139
+ ".tsv": "csv",
140
+ ".xlsx": "xlsx",
141
+ ".xls": "xlsx",
142
+ ".yaml": "yaml",
143
+ ".yml": "yaml",
144
+ ".txt": "text",
145
+ ".log": "text",
146
+ }
147
+
148
+
149
+ def detect_format(path: Path | None, explicit: str | None) -> str:
150
+ if explicit and explicit != "auto":
151
+ return explicit
152
+ if path:
153
+ return FORMAT_MAP.get(path.suffix.lower(), "text")
154
+ return "text"
155
+
156
+
157
+ # ---------------------------------------------------------------------------
158
+ # JSON extractor
159
+ # ---------------------------------------------------------------------------
160
+
161
+
162
+ def extract_json_fields(
163
+ data: Any, section: str = "Root"
164
+ ) -> list[ExtractedSection]:
165
+ sections: list[ExtractedSection] = []
166
+ if isinstance(data, list):
167
+ data = data[0] if data else {}
168
+ if not isinstance(data, dict):
169
+ return sections
170
+
171
+ root = ExtractedSection(name=section)
172
+ for key, value in data.items():
173
+ snake = to_snake_case(key)
174
+ if not snake or not snake.replace("_", "").isalnum():
175
+ continue
176
+
177
+ if isinstance(value, dict) and value:
178
+ child_class = to_class_name(key)
179
+ child_sections = extract_json_fields(value, section=key)
180
+ sections.extend(child_sections)
181
+ root.fields.append(
182
+ ExtractedField(
183
+ name=snake,
184
+ python_type=child_class,
185
+ source_pattern="json_nested",
186
+ example_value=f"{len(value)} keys",
187
+ section=section,
188
+ )
189
+ )
190
+ elif isinstance(value, list) and value and isinstance(value[0], dict):
191
+ row_class = to_class_name(key) + "Item"
192
+ row_sections = extract_json_fields(value[0], section=f"{key} (Item)")
193
+ for rs in row_sections:
194
+ if rs.name == f"{key} (Item)":
195
+ rs.name = f"{key} (Row Model)"
196
+ sections.extend(row_sections)
197
+ root.fields.append(
198
+ ExtractedField(
199
+ name=snake,
200
+ python_type=f"list[{row_class}]",
201
+ source_pattern="json_list",
202
+ example_value=f"{len(value)} items",
203
+ section=section,
204
+ )
205
+ )
206
+ else:
207
+ example = str(value)[:80] if value is not None else "null"
208
+ root.fields.append(
209
+ ExtractedField(
210
+ name=snake,
211
+ python_type=infer_type_from_value(value),
212
+ source_pattern="json_key",
213
+ example_value=example,
214
+ section=section,
215
+ )
216
+ )
217
+
218
+ if root.fields:
219
+ sections.insert(0, root)
220
+ return sections
221
+
222
+
223
+ # ---------------------------------------------------------------------------
224
+ # Markdown extractor
225
+ # ---------------------------------------------------------------------------
226
+
227
+
228
+ def strip_outer_table_wrapper(text: str) -> str:
229
+ """Strip single-cell table wrapper (| col | / | --- | header) from LLM outputs."""
230
+ lines = text.strip().split("\n")
231
+ if len(lines) < 3:
232
+ return text
233
+ header = lines[0].strip()
234
+ if not re.match(r"^\|[^|]+\|\s*$", header):
235
+ return text
236
+ separator = lines[1].strip()
237
+ if not re.match(r"^\|[-\s:]+\|\s*$", separator):
238
+ return text
239
+ content_lines = lines[2:]
240
+ if content_lines:
241
+ first = content_lines[0].strip()
242
+ if first.startswith("|"):
243
+ content_lines[0] = first[1:].lstrip()
244
+ return "\n".join(content_lines)
245
+
246
+
247
+ def parse_markdown_tables(text: str) -> list[dict]:
248
+ tables: list[dict] = []
249
+ lines = text.split("\n")
250
+ i = 0
251
+ while i < len(lines) - 1:
252
+ line = lines[i].strip()
253
+ # Separator regex: dash MUST be first in char class to avoid range
254
+ if re.match(r"^\|[-\s:]+\|[-\s:|]+\|$", line.replace(" ", "")):
255
+ if i > 0 and "|" in lines[i - 1]:
256
+ header_row = lines[i - 1].strip()
257
+ data_rows: list[str] = []
258
+ j = i + 1
259
+ while j < len(lines) and "|" in lines[j] and lines[j].strip():
260
+ data_rows.append(lines[j].strip())
261
+ j += 1
262
+ header_cells = [
263
+ c.strip()
264
+ for c in header_row.strip().strip("|").split("|")
265
+ if c.strip()
266
+ ]
267
+ tables.append(
268
+ {
269
+ "header_row": header_row,
270
+ "data_rows": data_rows,
271
+ "column_count": len(header_cells),
272
+ }
273
+ )
274
+ i = j
275
+ continue
276
+ i += 1
277
+ return tables
278
+
279
+
280
+ def extract_bold_kv(text: str) -> list[ExtractedField]:
281
+ fields: list[ExtractedField] = []
282
+ for m in re.finditer(
283
+ r"\*\*(.+?)\*\*\s*:?\s*(.+?)(?:\s{2,}|$)", text, re.MULTILINE
284
+ ):
285
+ key = m.group(1).strip().rstrip(":")
286
+ value = m.group(2).strip()
287
+ if value.startswith("**") or value.startswith("#"):
288
+ continue
289
+ snake = to_snake_case(key)
290
+ if not snake or not snake.replace("_", "").isalnum():
291
+ continue
292
+ fields.append(
293
+ ExtractedField(
294
+ name=snake,
295
+ python_type=infer_type_from_value(value),
296
+ source_pattern="bold_kv",
297
+ example_value=value[:80],
298
+ )
299
+ )
300
+ return fields
301
+
302
+
303
+ def extract_code_block_kv(block: str) -> list[ExtractedField]:
304
+ fields: list[ExtractedField] = []
305
+ seen: set[str] = set()
306
+ for line in block.split("\n"):
307
+ line = line.strip()
308
+ if not line or line.startswith("\u2501") or line.startswith("---"):
309
+ continue
310
+ for seg in line.split("|"):
311
+ seg = seg.strip()
312
+ m = re.match(r"^([A-Za-z][A-Za-z0-9 _/#()-]+?)\s*:\s*(.+)$", seg)
313
+ if m:
314
+ snake = to_snake_case(m.group(1).strip())
315
+ if (
316
+ snake
317
+ and snake.replace("_", "").isalnum()
318
+ and snake not in seen
319
+ ):
320
+ seen.add(snake)
321
+ fields.append(
322
+ ExtractedField(
323
+ name=snake,
324
+ python_type=infer_type_from_value(
325
+ m.group(2).strip()
326
+ ),
327
+ source_pattern="code_block_kv",
328
+ example_value=m.group(2).strip()[:80],
329
+ )
330
+ )
331
+ return fields
332
+
333
+
334
+ def extract_bullet_kv(
335
+ text: str, already: set[str] | None = None
336
+ ) -> list[ExtractedField]:
337
+ """Extract non-bold bullet KV pairs. Bold bullets are handled by bold_kv."""
338
+ if already is None:
339
+ already = set()
340
+ fields: list[ExtractedField] = []
341
+ seen: set[str] = set()
342
+ for m in re.finditer(
343
+ r"^[-*]\s+(?!\*\*)([^*:]+?)\s*:\s*(.+)$", text, re.MULTILINE
344
+ ):
345
+ snake = to_snake_case(m.group(1).strip())
346
+ if (
347
+ snake
348
+ and snake.replace("_", "").isalnum()
349
+ and snake not in seen
350
+ and snake not in already
351
+ ):
352
+ seen.add(snake)
353
+ fields.append(
354
+ ExtractedField(
355
+ name=snake,
356
+ python_type=infer_type_from_value(m.group(2).strip()),
357
+ source_pattern="bullet_kv",
358
+ example_value=m.group(2).strip()[:80],
359
+ )
360
+ )
361
+ return fields
362
+
363
+
364
+ def extract_md_sections(text: str) -> list[tuple[str, str]]:
365
+ sections: list[tuple[str, str]] = []
366
+ matches = list(re.finditer(r"^#{1,3}\s+(.+)$", text, re.MULTILINE))
367
+ if not matches:
368
+ return [("Root", text)]
369
+ if matches[0].start() > 0:
370
+ sections.append(("Root", text[: matches[0].start()]))
371
+ for i, m in enumerate(matches):
372
+ heading = re.sub(r"^\d+\.\s*", "", m.group(1).strip())
373
+ end = matches[i + 1].start() if i + 1 < len(matches) else len(text)
374
+ sections.append((heading, text[m.end() : end].strip()))
375
+ return sections
376
+
377
+
378
+ def extract_markdown(text: str) -> list[ExtractedSection]:
379
+ text = strip_outer_table_wrapper(text)
380
+ result: list[ExtractedSection] = []
381
+ for heading, content in extract_md_sections(text):
382
+ section = ExtractedSection(name=heading)
383
+
384
+ # Markdown tables
385
+ for table in parse_markdown_tables(content):
386
+ if table["column_count"] == 2:
387
+ for row in table["data_rows"]:
388
+ cells = [
389
+ c.strip().replace("**", "")
390
+ for c in row.strip().strip("|").split("|")
391
+ ]
392
+ if len(cells) >= 2:
393
+ snake = to_snake_case(cells[0])
394
+ if snake and snake.replace("_", "").isalnum():
395
+ section.fields.append(
396
+ ExtractedField(
397
+ name=snake,
398
+ python_type=infer_type_from_value(
399
+ cells[1].strip()
400
+ ),
401
+ source_pattern="md_table_kv",
402
+ example_value=cells[1].strip()[:80],
403
+ )
404
+ )
405
+ elif table["column_count"] >= 3:
406
+ headers = [
407
+ c.strip().replace("**", "")
408
+ for c in table["header_row"].strip().strip("|").split("|")
409
+ if c.strip()
410
+ ]
411
+ first_vals = (
412
+ [
413
+ c.strip().replace("**", "")
414
+ for c in table["data_rows"][0]
415
+ .strip()
416
+ .strip("|")
417
+ .split("|")
418
+ if c.strip()
419
+ ]
420
+ if table["data_rows"]
421
+ else []
422
+ )
423
+ row_fields = []
424
+ for idx, h in enumerate(headers):
425
+ snake = to_snake_case(h)
426
+ if snake and snake.replace("_", "").isalnum():
427
+ val = first_vals[idx] if idx < len(first_vals) else ""
428
+ row_fields.append(
429
+ ExtractedField(
430
+ name=snake,
431
+ python_type=infer_type_from_value(val),
432
+ source_pattern="md_table_multi",
433
+ example_value=val[:80],
434
+ )
435
+ )
436
+ row_class = to_class_name(heading) + "Row"
437
+ result.append(
438
+ ExtractedSection(
439
+ name=f"{heading} (Row Model)", fields=row_fields
440
+ )
441
+ )
442
+ section.fields.append(
443
+ ExtractedField(
444
+ name=to_snake_case(heading) + "_rows",
445
+ python_type=f"list[{row_class}]",
446
+ source_pattern="md_table_multi",
447
+ example_value=f"{len(table['data_rows'])} rows",
448
+ )
449
+ )
450
+
451
+ # Code blocks
452
+ for block in re.findall(r"```[^\n]*\n(.*?)```", content, re.DOTALL):
453
+ section.fields.extend(extract_code_block_kv(block))
454
+
455
+ # Bold + bullet KV (after removing tables and code blocks)
456
+ clean = re.sub(r"```.*?```", "", content, flags=re.DOTALL)
457
+ clean = re.sub(r"^\|.+\|$", "", clean, flags=re.MULTILINE)
458
+ bold_fields = extract_bold_kv(clean)
459
+ section.fields.extend(bold_fields)
460
+ bold_names = {f.name for f in bold_fields}
461
+ section.fields.extend(extract_bullet_kv(clean, already=bold_names))
462
+
463
+ if section.fields:
464
+ if heading == "Root":
465
+ result.insert(0, section)
466
+ else:
467
+ result.append(section)
468
+ return result
469
+
470
+
471
+ # ---------------------------------------------------------------------------
472
+ # CSV extractor
473
+ # ---------------------------------------------------------------------------
474
+
475
+
476
+ def extract_csv(
477
+ text: str, delimiter: str | None = None
478
+ ) -> list[ExtractedSection]:
479
+ if delimiter is None:
480
+ try:
481
+ dialect = csv.Sniffer().sniff(text[:4096])
482
+ delimiter = dialect.delimiter
483
+ except csv.Error:
484
+ delimiter = ","
485
+
486
+ reader = csv.reader(io.StringIO(text), delimiter=delimiter)
487
+ rows = list(reader)
488
+ if len(rows) < 2:
489
+ return []
490
+
491
+ headers = rows[0]
492
+ data_rows = rows[1:101] # sample up to 100 rows for type inference
493
+
494
+ section = ExtractedSection(name="Root")
495
+ for col_idx, header in enumerate(headers):
496
+ snake = to_snake_case(header)
497
+ if not snake or not snake.replace("_", "").isalnum():
498
+ snake = f"col_{col_idx}"
499
+
500
+ sample_values = [
501
+ r[col_idx]
502
+ for r in data_rows
503
+ if col_idx < len(r) and r[col_idx].strip()
504
+ ]
505
+ if sample_values:
506
+ types = {infer_type_from_value(v) for v in sample_values[:20]}
507
+ if len(types) == 1:
508
+ py_type = types.pop()
509
+ elif types <= {"int", "float"}:
510
+ py_type = "float"
511
+ elif "str | None" in types:
512
+ py_type = "str | None"
513
+ else:
514
+ py_type = "str"
515
+ example = sample_values[0][:80]
516
+ else:
517
+ py_type = "str | None"
518
+ example = ""
519
+
520
+ section.fields.append(
521
+ ExtractedField(
522
+ name=snake,
523
+ python_type=py_type,
524
+ source_pattern="csv_col",
525
+ example_value=example,
526
+ )
527
+ )
528
+ return [section]
529
+
530
+
531
+ # ---------------------------------------------------------------------------
532
+ # XLSX extractor
533
+ # ---------------------------------------------------------------------------
534
+
535
+
536
+ def extract_xlsx(path: Path) -> list[ExtractedSection]:
537
+ try:
538
+ import openpyxl
539
+ except ImportError:
540
+ print(
541
+ "Error: openpyxl required for xlsx. Install: pip install openpyxl",
542
+ file=sys.stderr,
543
+ )
544
+ sys.exit(1)
545
+
546
+ wb = openpyxl.load_workbook(path, read_only=True, data_only=True)
547
+ all_sections: list[ExtractedSection] = []
548
+
549
+ for sheet_name in wb.sheetnames:
550
+ ws = wb[sheet_name]
551
+ rows = list(ws.iter_rows(values_only=True))
552
+ if len(rows) < 2:
553
+ continue
554
+
555
+ headers = [
556
+ str(c) if c else f"col_{i}" for i, c in enumerate(rows[0])
557
+ ]
558
+ data_rows = rows[1:101]
559
+
560
+ section = ExtractedSection(
561
+ name=sheet_name if len(wb.sheetnames) > 1 else "Root"
562
+ )
563
+ for col_idx, header in enumerate(headers):
564
+ snake = to_snake_case(header)
565
+ if not snake or not snake.replace("_", "").isalnum():
566
+ snake = f"col_{col_idx}"
567
+
568
+ samples = [
569
+ r[col_idx]
570
+ for r in data_rows
571
+ if col_idx < len(r) and r[col_idx] is not None
572
+ ]
573
+ if samples:
574
+ py_type = infer_type_from_value(samples[0])
575
+ example = str(samples[0])[:80]
576
+ else:
577
+ py_type = "str | None"
578
+ example = ""
579
+
580
+ section.fields.append(
581
+ ExtractedField(
582
+ name=snake,
583
+ python_type=py_type,
584
+ source_pattern="xlsx_col",
585
+ example_value=example,
586
+ )
587
+ )
588
+ all_sections.append(section)
589
+
590
+ wb.close()
591
+ return all_sections
592
+
593
+
594
+ # ---------------------------------------------------------------------------
595
+ # YAML extractor
596
+ # ---------------------------------------------------------------------------
597
+
598
+
599
+ def extract_yaml(text: str) -> list[ExtractedSection]:
600
+ try:
601
+ import yaml
602
+ except ImportError:
603
+ print(
604
+ "Error: PyYAML required. Install: pip install pyyaml",
605
+ file=sys.stderr,
606
+ )
607
+ sys.exit(1)
608
+ data = yaml.safe_load(text)
609
+ return extract_json_fields(data)
610
+
611
+
612
+ # ---------------------------------------------------------------------------
613
+ # Plain text extractor
614
+ # ---------------------------------------------------------------------------
615
+
616
+
617
+ def extract_text(text: str) -> list[ExtractedSection]:
618
+ sections: list[ExtractedSection] = []
619
+ current_section = "Root"
620
+ current_fields: list[ExtractedField] = []
621
+
622
+ for line in text.split("\n"):
623
+ line = line.strip()
624
+ if not line:
625
+ continue
626
+
627
+ # INI-style section header
628
+ sec_match = re.match(r"^\[(.+)\]$", line)
629
+ if sec_match:
630
+ if current_fields:
631
+ sections.append(
632
+ ExtractedSection(
633
+ name=current_section, fields=current_fields
634
+ )
635
+ )
636
+ current_section = sec_match.group(1).strip()
637
+ current_fields = []
638
+ continue
639
+
640
+ # Key: Value or Key = Value
641
+ kv_match = re.match(
642
+ r"^([A-Za-z][A-Za-z0-9 _.-]+?)\s*[:=]\s*(.+)$", line
643
+ )
644
+ if kv_match:
645
+ snake = to_snake_case(kv_match.group(1).strip())
646
+ value = kv_match.group(2).strip()
647
+ if snake and snake.replace("_", "").isalnum():
648
+ current_fields.append(
649
+ ExtractedField(
650
+ name=snake,
651
+ python_type=infer_type_from_value(value),
652
+ source_pattern="text_kv",
653
+ example_value=value[:80],
654
+ )
655
+ )
656
+
657
+ if current_fields:
658
+ sections.append(
659
+ ExtractedSection(name=current_section, fields=current_fields)
660
+ )
661
+ return sections
662
+
663
+
664
+ # ---------------------------------------------------------------------------
665
+ # DB table extractor
666
+ # ---------------------------------------------------------------------------
667
+
668
+ SQL_TYPE_MAP = {
669
+ # SQL Server
670
+ "INTEGER": "int",
671
+ "BIGINT": "int",
672
+ "SMALLINT": "int",
673
+ "TINYINT": "int",
674
+ "FLOAT": "float",
675
+ "REAL": "float",
676
+ "DECIMAL": "float",
677
+ "NUMERIC": "float",
678
+ "MONEY": "float",
679
+ "SMALLMONEY": "float",
680
+ "BIT": "bool",
681
+ "VARCHAR": "str",
682
+ "NVARCHAR": "str",
683
+ "CHAR": "str",
684
+ "NCHAR": "str",
685
+ "TEXT": "str",
686
+ "NTEXT": "str",
687
+ "DATE": "str",
688
+ "DATETIME": "str",
689
+ "DATETIME2": "str",
690
+ "SMALLDATETIME": "str",
691
+ "TIME": "str",
692
+ "DATETIMEOFFSET": "str",
693
+ "UNIQUEIDENTIFIER": "str",
694
+ "VARBINARY": "bytes",
695
+ "BINARY": "bytes",
696
+ "IMAGE": "bytes",
697
+ # PostgreSQL
698
+ "SERIAL": "int",
699
+ "BIGSERIAL": "int",
700
+ "SMALLSERIAL": "int",
701
+ "BOOLEAN": "bool",
702
+ "DOUBLE_PRECISION": "float",
703
+ "JSONB": "dict",
704
+ "JSON": "dict",
705
+ "UUID": "str",
706
+ "BYTEA": "bytes",
707
+ "TIMESTAMPTZ": "str",
708
+ "TIMESTAMP": "str",
709
+ "INTERVAL": "str",
710
+ "CITEXT": "str",
711
+ "INET": "str",
712
+ "MACADDR": "str",
713
+ "ARRAY": "list",
714
+ "HSTORE": "dict",
715
+ # MySQL
716
+ "TINYTEXT": "str",
717
+ "MEDIUMTEXT": "str",
718
+ "LONGTEXT": "str",
719
+ "MEDIUMINT": "int",
720
+ "ENUM": "str",
721
+ "SET": "str",
722
+ "BLOB": "bytes",
723
+ "MEDIUMBLOB": "bytes",
724
+ "LONGBLOB": "bytes",
725
+ "TINYBLOB": "bytes",
726
+ # SQLite
727
+ "REAL": "float",
728
+ }
729
+
730
+
731
+ def _detect_embedded_format(value: str) -> str | None:
732
+ """Detect structured data embedded in a string column value."""
733
+ v = value.strip()
734
+ if not v:
735
+ return None
736
+ if (v.startswith("{") and v.endswith("}")) or (
737
+ v.startswith("[") and v.endswith("]")
738
+ ):
739
+ try:
740
+ json.loads(v)
741
+ return "json"
742
+ except (json.JSONDecodeError, ValueError):
743
+ pass
744
+ if v.startswith("<?xml") or (v.startswith("<") and ">" in v):
745
+ return "xml"
746
+ if v.startswith("---\n") or re.match(r"^[a-zA-Z_]+\s*:", v):
747
+ try:
748
+ import yaml
749
+
750
+ yaml.safe_load(v)
751
+ return "yaml"
752
+ except Exception:
753
+ pass
754
+ if "|" in v and v.count("|") >= 3:
755
+ return "pipe-delimited"
756
+ if v.startswith("#") or v.startswith("**") or re.search(r"\[.+\]\(.+\)", v):
757
+ return "markdown"
758
+ return None
759
+
760
+
761
+ def _sample_rows(
762
+ engine: Any, table_name: str, schema: str | None, limit: int = 50
763
+ ) -> list[dict]:
764
+ """Fetch sample rows from a table for type inference and format detection."""
765
+ from sqlalchemy import text
766
+
767
+ qualified = f'"{schema}"."{table_name}"' if schema else f'"{table_name}"'
768
+ query = text(f"SELECT * FROM {qualified} ORDER BY 1 LIMIT :lim")
769
+ try:
770
+ with engine.connect() as conn:
771
+ result = conn.execute(query, {"lim": limit})
772
+ columns = list(result.keys())
773
+ return [dict(zip(columns, row)) for row in result.fetchall()]
774
+ except Exception:
775
+ # MSSQL uses TOP instead of LIMIT
776
+ query_mssql = text(
777
+ f"SELECT TOP(:lim) * FROM {qualified} ORDER BY 1"
778
+ )
779
+ try:
780
+ with engine.connect() as conn:
781
+ result = conn.execute(query_mssql, {"lim": limit})
782
+ columns = list(result.keys())
783
+ return [dict(zip(columns, row)) for row in result.fetchall()]
784
+ except Exception:
785
+ return []
786
+
787
+
788
+ def discover_db_tables(
789
+ connection_string: str, schema: str | None = None
790
+ ) -> list[dict]:
791
+ """Discover all tables and views in a database schema.
792
+
793
+ Returns a list of dicts with keys: name, type (table/view), column_count,
794
+ row_count_estimate, columns (list of column info dicts).
795
+ """
796
+ try:
797
+ from sqlalchemy import create_engine, text
798
+ from sqlalchemy import inspect as sa_inspect
799
+ except ImportError:
800
+ print(
801
+ "Error: sqlalchemy required. Install: pip install sqlalchemy",
802
+ file=sys.stderr,
803
+ )
804
+ sys.exit(1)
805
+
806
+ engine = create_engine(connection_string)
807
+ inspector = sa_inspect(engine)
808
+
809
+ tables = []
810
+ # Get tables
811
+ for table_name in inspector.get_table_names(schema=schema):
812
+ columns = inspector.get_columns(table_name, schema=schema)
813
+ pk = inspector.get_pk_constraint(table_name, schema=schema)
814
+ fks = inspector.get_foreign_keys(table_name, schema=schema)
815
+ tables.append({
816
+ "name": table_name,
817
+ "schema": schema,
818
+ "type": "table",
819
+ "column_count": len(columns),
820
+ "columns": [
821
+ {
822
+ "name": c["name"],
823
+ "sql_type": type(c["type"]).__name__.upper(),
824
+ "nullable": c.get("nullable", True),
825
+ "is_pk": c["name"] in (pk.get("constrained_columns", []) if pk else []),
826
+ }
827
+ for c in columns
828
+ ],
829
+ "primary_key": pk.get("constrained_columns", []) if pk else [],
830
+ "foreign_keys": [
831
+ {
832
+ "columns": fk["constrained_columns"],
833
+ "referred_table": fk["referred_table"],
834
+ "referred_columns": fk["referred_columns"],
835
+ }
836
+ for fk in fks
837
+ ],
838
+ })
839
+
840
+ # Get views
841
+ for view_name in inspector.get_view_names(schema=schema):
842
+ columns = inspector.get_columns(view_name, schema=schema)
843
+ tables.append({
844
+ "name": view_name,
845
+ "schema": schema,
846
+ "type": "view",
847
+ "column_count": len(columns),
848
+ "columns": [
849
+ {
850
+ "name": c["name"],
851
+ "sql_type": type(c["type"]).__name__.upper(),
852
+ "nullable": c.get("nullable", True),
853
+ "is_pk": False,
854
+ }
855
+ for c in columns
856
+ ],
857
+ "primary_key": [],
858
+ "foreign_keys": [],
859
+ })
860
+
861
+ engine.dispose()
862
+ return tables
863
+
864
+
865
+ def extract_db_table(
866
+ connection_string: str,
867
+ table_name: str,
868
+ schema: str | None = None,
869
+ sample: int = 0,
870
+ ) -> list[ExtractedSection]:
871
+ """Extract schema from a DB table with optional data sampling.
872
+
873
+ Args:
874
+ connection_string: SQLAlchemy connection string
875
+ table_name: Table or view name
876
+ schema: Optional DB schema (e.g., 'dbo', 'public')
877
+ sample: Number of rows to sample for type inference and format detection.
878
+ 0 = metadata only (original behavior).
879
+ """
880
+ try:
881
+ from sqlalchemy import create_engine
882
+ from sqlalchemy import inspect as sa_inspect
883
+ except ImportError:
884
+ print(
885
+ "Error: sqlalchemy required. Install: pip install sqlalchemy",
886
+ file=sys.stderr,
887
+ )
888
+ sys.exit(1)
889
+
890
+ engine = create_engine(connection_string)
891
+ inspector = sa_inspect(engine)
892
+
893
+ columns = inspector.get_columns(table_name, schema=schema)
894
+ pk = inspector.get_pk_constraint(table_name, schema=schema)
895
+ pk_cols = pk.get("constrained_columns", []) if pk else []
896
+ fks = inspector.get_foreign_keys(table_name, schema=schema)
897
+
898
+ # Sample rows if requested
899
+ sampled_rows: list[dict] = []
900
+ if sample > 0:
901
+ sampled_rows = _sample_rows(engine, table_name, schema, limit=sample)
902
+
903
+ section = ExtractedSection(name="Root")
904
+ embedded_sections: list[ExtractedSection] = []
905
+
906
+ for col in columns:
907
+ snake = to_snake_case(col["name"])
908
+ sql_type = type(col["type"]).__name__.upper()
909
+ py_type = SQL_TYPE_MAP.get(sql_type, "Any")
910
+ if col.get("nullable", True):
911
+ py_type = f"{py_type} | None"
912
+
913
+ # Build example from sampled data
914
+ example = f"SQL: {sql_type}"
915
+ embedded_fmt = None
916
+ if sampled_rows:
917
+ sample_vals = [
918
+ row.get(col["name"])
919
+ for row in sampled_rows
920
+ if row.get(col["name"]) is not None
921
+ ]
922
+ if sample_vals:
923
+ example = str(sample_vals[0])[:80]
924
+ # Check for embedded structured data in string columns
925
+ if sql_type in (
926
+ "VARCHAR", "NVARCHAR", "TEXT", "NTEXT",
927
+ "MEDIUMTEXT", "LONGTEXT", "CITEXT",
928
+ ):
929
+ for sv in sample_vals[:10]:
930
+ fmt = _detect_embedded_format(str(sv))
931
+ if fmt:
932
+ embedded_fmt = fmt
933
+ break
934
+
935
+ source_pattern = "db_column"
936
+ if col["name"] in pk_cols:
937
+ source_pattern = "db_column_pk"
938
+ if embedded_fmt:
939
+ source_pattern = f"db_column_embedded_{embedded_fmt}"
940
+ example = f"[embedded {embedded_fmt}] {example}"
941
+ # Parse the embedded content to extract nested schema
942
+ if embedded_fmt == "json" and sampled_rows:
943
+ for sv in sample_vals[:1]:
944
+ try:
945
+ parsed = json.loads(str(sv))
946
+ nested = extract_json_fields(
947
+ parsed, section=f"{col['name']} (Embedded JSON)"
948
+ )
949
+ embedded_sections.extend(nested)
950
+ except (json.JSONDecodeError, ValueError):
951
+ pass
952
+
953
+ section.fields.append(
954
+ ExtractedField(
955
+ name=snake,
956
+ python_type=py_type,
957
+ source_pattern=source_pattern,
958
+ example_value=example,
959
+ )
960
+ )
961
+
962
+ # Add FK annotations as comments in the section
963
+ for fk in fks:
964
+ fk_desc = (
965
+ f"FK: {','.join(fk['constrained_columns'])} -> "
966
+ f"{fk['referred_table']}({','.join(fk['referred_columns'])})"
967
+ )
968
+ section.fields.append(
969
+ ExtractedField(
970
+ name=f"_fk_{fk['referred_table']}",
971
+ python_type="# relationship",
972
+ source_pattern="db_foreign_key",
973
+ example_value=fk_desc,
974
+ )
975
+ )
976
+
977
+ engine.dispose()
978
+ result = []
979
+ if section.fields:
980
+ result.append(section)
981
+ result.extend(embedded_sections)
982
+ return result
983
+
984
+
985
+ # ---------------------------------------------------------------------------
986
+ # Deduplication
987
+ # ---------------------------------------------------------------------------
988
+
989
+
990
+ def deduplicate_fields(
991
+ fields: list[ExtractedField],
992
+ ) -> list[ExtractedField]:
993
+ seen: set[str] = set()
994
+ result: list[ExtractedField] = []
995
+ for f in fields:
996
+ if f.name not in seen:
997
+ seen.add(f.name)
998
+ result.append(f)
999
+ return result
1000
+
1001
+
1002
+ # ---------------------------------------------------------------------------
1003
+ # Model code generation (shared across all formats)
1004
+ # ---------------------------------------------------------------------------
1005
+
1006
+
1007
+ def generate_model_source(
1008
+ sections: list[ExtractedSection], root_class_name: str = "GeneratedPayload"
1009
+ ) -> str:
1010
+ lines = [
1011
+ '"""',
1012
+ "Auto-generated ingestion model.",
1013
+ "Generated by: extract_schema.py",
1014
+ "",
1015
+ "Review and adjust:",
1016
+ " - Field names and types",
1017
+ " - Which sections become nested models vs flat fields",
1018
+ " - Optional fields and defaults",
1019
+ '"""',
1020
+ "",
1021
+ "from __future__ import annotations",
1022
+ "",
1023
+ "from pydantic import BaseModel, ConfigDict, Field",
1024
+ "",
1025
+ ]
1026
+
1027
+ row_models: list[ExtractedSection] = []
1028
+ content_sections: list[ExtractedSection] = []
1029
+ for s in sections:
1030
+ if s.name.endswith("(Row Model)"):
1031
+ row_models.append(s)
1032
+ else:
1033
+ content_sections.append(s)
1034
+
1035
+ # Nested/row models first
1036
+ for rm in row_models:
1037
+ cname = to_class_name(rm.name.replace(" (Row Model)", "")) + "Row"
1038
+ if "(Item)" in rm.name:
1039
+ cname = (
1040
+ to_class_name(
1041
+ rm.name.replace(" (Item)", "").replace(
1042
+ " (Row Model)", ""
1043
+ )
1044
+ )
1045
+ + "Item"
1046
+ )
1047
+ rm_fields = deduplicate_fields(rm.fields)
1048
+ lines += [
1049
+ "",
1050
+ f"class {cname}(BaseModel):",
1051
+ f' """Row from {rm.name.replace(" (Row Model)", "")}."""',
1052
+ "",
1053
+ ]
1054
+ if not rm_fields:
1055
+ lines.append(" pass")
1056
+ else:
1057
+ for f in rm_fields:
1058
+ comment = (
1059
+ f" # e.g., {f.example_value}" if f.example_value else ""
1060
+ )
1061
+ lines.append(f" {f.name}: {f.python_type}{comment}")
1062
+ lines.append("")
1063
+
1064
+ # Root model
1065
+ all_fields: list[ExtractedField] = []
1066
+ section_markers: list[tuple[int, str]] = []
1067
+ for cs in content_sections:
1068
+ cs_fields = deduplicate_fields(cs.fields)
1069
+ if cs_fields:
1070
+ section_markers.append((len(all_fields), cs.name))
1071
+ all_fields.extend(cs_fields)
1072
+ all_fields = deduplicate_fields(all_fields)
1073
+
1074
+ lines += [
1075
+ "",
1076
+ f"class {root_class_name}(BaseModel):",
1077
+ f' """1:1 representation of source payload."""',
1078
+ "",
1079
+ ' model_config = ConfigDict(extra="allow")',
1080
+ "",
1081
+ ]
1082
+
1083
+ if not all_fields:
1084
+ lines.append(" pass")
1085
+ else:
1086
+ marker_idx = 0
1087
+ for i, f in enumerate(all_fields):
1088
+ if marker_idx < len(section_markers):
1089
+ fidx, sname = section_markers[marker_idx]
1090
+ if i >= fidx:
1091
+ lines.append(f" # --- {sname} ---")
1092
+ marker_idx += 1
1093
+ comment = (
1094
+ f" # e.g., {f.example_value}" if f.example_value else ""
1095
+ )
1096
+ lines.append(f" {f.name}: {f.python_type}{comment}")
1097
+
1098
+ return "\n".join(lines) + "\n"
1099
+
1100
+
1101
+ # ---------------------------------------------------------------------------
1102
+ # Schema evolution
1103
+ # ---------------------------------------------------------------------------
1104
+
1105
+
1106
+ def sections_to_schema_dict(
1107
+ sections: list[ExtractedSection], source: str, fmt: str
1108
+ ) -> dict:
1109
+ fields = []
1110
+ for s in sections:
1111
+ for f in s.fields:
1112
+ fields.append(
1113
+ {
1114
+ "name": f.name,
1115
+ "type": f.python_type,
1116
+ "source_pattern": f.source_pattern,
1117
+ "section": s.name,
1118
+ }
1119
+ )
1120
+ return {
1121
+ "extracted_at": datetime.now(timezone.utc).isoformat(),
1122
+ "source_file": source,
1123
+ "format": fmt,
1124
+ "field_count": len(fields),
1125
+ "fields": fields,
1126
+ }
1127
+
1128
+
1129
+ def save_baseline(
1130
+ sections: list[ExtractedSection], path: Path, source: str, fmt: str
1131
+ ) -> None:
1132
+ schema = sections_to_schema_dict(sections, source, fmt)
1133
+ path.parent.mkdir(parents=True, exist_ok=True)
1134
+ path.write_text(json.dumps(schema, indent=2), encoding="utf-8")
1135
+ print(
1136
+ f"Baseline saved: {path} ({schema['field_count']} fields)",
1137
+ file=sys.stderr,
1138
+ )
1139
+
1140
+
1141
+ def diff_baseline(
1142
+ sections: list[ExtractedSection], baseline_path: Path
1143
+ ) -> list[SchemaChange]:
1144
+ baseline = json.loads(baseline_path.read_text(encoding="utf-8"))
1145
+ old_fields = {f["name"]: f["type"] for f in baseline["fields"]}
1146
+ new_fields: dict[str, str] = {}
1147
+ for s in sections:
1148
+ for f in s.fields:
1149
+ if f.name not in new_fields:
1150
+ new_fields[f.name] = f.python_type
1151
+
1152
+ changes: list[SchemaChange] = []
1153
+ for name in sorted(set(old_fields) | set(new_fields)):
1154
+ if name not in old_fields:
1155
+ changes.append(
1156
+ SchemaChange(name, "added", new_value=new_fields[name])
1157
+ )
1158
+ elif name not in new_fields:
1159
+ changes.append(
1160
+ SchemaChange(name, "removed", old_value=old_fields[name])
1161
+ )
1162
+ elif old_fields[name] != new_fields[name]:
1163
+ changes.append(
1164
+ SchemaChange(
1165
+ name,
1166
+ "type_changed",
1167
+ old_value=old_fields[name],
1168
+ new_value=new_fields[name],
1169
+ )
1170
+ )
1171
+ return changes
1172
+
1173
+
1174
+ # ---------------------------------------------------------------------------
1175
+ # CLI
1176
+ # ---------------------------------------------------------------------------
1177
+
1178
+
1179
+ def main() -> None:
1180
+ import argparse
1181
+
1182
+ parser = argparse.ArgumentParser(
1183
+ description="Unified schema extractor — any format to Pydantic"
1184
+ )
1185
+ parser.add_argument(
1186
+ "source",
1187
+ type=Path,
1188
+ nargs="?",
1189
+ default=None,
1190
+ help="Path to source data file",
1191
+ )
1192
+ parser.add_argument(
1193
+ "--format",
1194
+ dest="fmt",
1195
+ default="auto",
1196
+ choices=["auto", "json", "md", "csv", "xlsx", "yaml", "db", "text"],
1197
+ help="Source format (default: auto-detect from extension)",
1198
+ )
1199
+ parser.add_argument(
1200
+ "--class-name",
1201
+ default="GeneratedPayload",
1202
+ help="Root class name",
1203
+ )
1204
+ parser.add_argument(
1205
+ "--output",
1206
+ type=Path,
1207
+ default=None,
1208
+ help="Output .py file path",
1209
+ )
1210
+ parser.add_argument(
1211
+ "--show-sections",
1212
+ action="store_true",
1213
+ help="Print extracted sections instead of code",
1214
+ )
1215
+ parser.add_argument(
1216
+ "--save-baseline",
1217
+ type=Path,
1218
+ default=None,
1219
+ help="Save schema snapshot to JSON",
1220
+ )
1221
+ parser.add_argument(
1222
+ "--diff",
1223
+ type=Path,
1224
+ default=None,
1225
+ help="Diff current schema against baseline JSON",
1226
+ )
1227
+ parser.add_argument(
1228
+ "--connection-string",
1229
+ default=None,
1230
+ help="DB connection string (for --format db)",
1231
+ )
1232
+ parser.add_argument(
1233
+ "--table",
1234
+ default=None,
1235
+ help="DB table name (for --format db)",
1236
+ )
1237
+ parser.add_argument(
1238
+ "--schema",
1239
+ default=None,
1240
+ help="DB schema name, e.g. 'dbo', 'public' (for --format db)",
1241
+ )
1242
+ parser.add_argument(
1243
+ "--discover",
1244
+ action="store_true",
1245
+ help="Discover all tables/views in the database (for --format db)",
1246
+ )
1247
+ parser.add_argument(
1248
+ "--sample",
1249
+ type=int,
1250
+ default=0,
1251
+ help="Number of rows to sample for type inference and embedded format detection (for --format db, default: 0 = metadata only)",
1252
+ )
1253
+ args = parser.parse_args()
1254
+
1255
+ fmt = detect_format(args.source, args.fmt)
1256
+
1257
+ if fmt == "db":
1258
+ if not args.connection_string:
1259
+ print(
1260
+ "Error: --connection-string required for db format",
1261
+ file=sys.stderr,
1262
+ )
1263
+ sys.exit(1)
1264
+
1265
+ # Discovery mode: list all tables and their schemas
1266
+ if args.discover:
1267
+ tables = discover_db_tables(args.connection_string, schema=args.schema)
1268
+ print(f"Discovered {len(tables)} tables/views:\n")
1269
+ for t in tables:
1270
+ pk_str = f" PK: {', '.join(t['primary_key'])}" if t['primary_key'] else ""
1271
+ fk_str = f" FKs: {len(t['foreign_keys'])}" if t['foreign_keys'] else ""
1272
+ print(f" [{t['type'].upper()}] {t['name']} ({t['column_count']} columns){pk_str}{fk_str}")
1273
+ for col in t['columns']:
1274
+ pk_marker = " [PK]" if col['is_pk'] else ""
1275
+ null_marker = " NULL" if col['nullable'] else " NOT NULL"
1276
+ print(f" {col['name']}: {col['sql_type']}{null_marker}{pk_marker}")
1277
+ if t['foreign_keys']:
1278
+ for fk in t['foreign_keys']:
1279
+ print(f" FK: {','.join(fk['columns'])} -> {fk['referred_table']}({','.join(fk['referred_columns'])})")
1280
+ print()
1281
+ sys.exit(0)
1282
+
1283
+ if not args.table:
1284
+ print(
1285
+ "Error: --table required for db format (or use --discover to list tables)",
1286
+ file=sys.stderr,
1287
+ )
1288
+ sys.exit(1)
1289
+ sections = extract_db_table(
1290
+ args.connection_string, args.table,
1291
+ schema=args.schema, sample=args.sample,
1292
+ )
1293
+ source_label = f"{args.table} (db)"
1294
+ else:
1295
+ if not args.source or not args.source.exists():
1296
+ print(
1297
+ f"Error: File not found: {args.source}", file=sys.stderr
1298
+ )
1299
+ sys.exit(1)
1300
+ source_label = str(args.source)
1301
+
1302
+ if fmt == "xlsx":
1303
+ sections = extract_xlsx(args.source)
1304
+ else:
1305
+ text = args.source.read_text(encoding="utf-8")
1306
+ extractors = {
1307
+ "json": lambda t: extract_json_fields(json.loads(t)),
1308
+ "md": extract_markdown,
1309
+ "csv": extract_csv,
1310
+ "yaml": extract_yaml,
1311
+ "text": extract_text,
1312
+ }
1313
+ sections = extractors[fmt](text)
1314
+
1315
+ if args.save_baseline:
1316
+ save_baseline(sections, args.save_baseline, source_label, fmt)
1317
+
1318
+ if args.diff:
1319
+ if not args.diff.exists():
1320
+ print(
1321
+ f"Error: Baseline not found: {args.diff}", file=sys.stderr
1322
+ )
1323
+ sys.exit(1)
1324
+ changes = diff_baseline(sections, args.diff)
1325
+ if not changes:
1326
+ print("No schema changes detected.")
1327
+ else:
1328
+ print(f"{len(changes)} schema change(s) detected:\n")
1329
+ for c in changes:
1330
+ if c.change_type == "added":
1331
+ print(f" + ADDED {c.field_name}: {c.new_value}")
1332
+ elif c.change_type == "removed":
1333
+ print(f" - REMOVED {c.field_name}: {c.old_value}")
1334
+ else:
1335
+ print(
1336
+ f" ~ CHANGED {c.field_name}: {c.old_value} -> {c.new_value}"
1337
+ )
1338
+ sys.exit(0)
1339
+
1340
+ if args.show_sections:
1341
+ print(f"Extracted {len(sections)} sections:\n")
1342
+ for s in sections:
1343
+ print(f" [{s.name}] -- {len(s.fields)} fields")
1344
+ for f in s.fields:
1345
+ print(
1346
+ f" {f.name}: {f.python_type} ({f.source_pattern}) = {f.example_value!r}"
1347
+ )
1348
+ print()
1349
+ sys.exit(0)
1350
+
1351
+ source_code = generate_model_source(
1352
+ sections, root_class_name=args.class_name
1353
+ )
1354
+ if args.output:
1355
+ args.output.parent.mkdir(parents=True, exist_ok=True)
1356
+ args.output.write_text(source_code, encoding="utf-8")
1357
+ print(f"Written to {args.output}")
1358
+ else:
1359
+ print(source_code)
1360
+
1361
+
1362
+ if __name__ == "__main__":
1363
+ main()