@robbiesrobotics/alice-agents 1.5.7 → 1.5.9

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 (581) hide show
  1. package/README.md +5 -2
  2. package/bin/alice-cloud.cjs +71 -55
  3. package/package.json +1 -1
  4. package/templates/skills/acculynx/SKILL.md +183 -0
  5. package/templates/skills/acculynx/references/analysis_template.py +116 -0
  6. package/templates/skills/acculynx/references/dashboard_page.tsx +641 -0
  7. package/templates/skills/claude-code/SKILL.md +2 -2
  8. package/templates/skills/coding-agent/SKILL.md +68 -0
  9. package/templates/skills/crawl4ai/SKILL.md +119 -0
  10. package/templates/skills/crawl4ai/scripts/crwl +3 -0
  11. package/templates/workspaces/accuscope/AGENTS.md +38 -0
  12. package/templates/workspaces/accuscope/FEEDBACK.md +27 -0
  13. package/templates/workspaces/accuscope/HEARTBEAT.md +26 -0
  14. package/templates/workspaces/accuscope/IDENTITY.md +48 -0
  15. package/templates/workspaces/accuscope/LEARNINGS.md +46 -0
  16. package/templates/workspaces/accuscope/MEMORY.md +47 -0
  17. package/templates/workspaces/accuscope/PLAYBOOK.md +65 -0
  18. package/templates/workspaces/accuscope/SOUL.md +40 -0
  19. package/templates/workspaces/accuscope/TOOLS.md +63 -0
  20. package/templates/workspaces/accuscope/USER.md +39 -0
  21. package/templates/workspaces/aiden/AGENTS.md +52 -0
  22. package/templates/workspaces/aiden/FEEDBACK.md +12 -0
  23. package/templates/workspaces/aiden/HEARTBEAT.md +9 -0
  24. package/templates/workspaces/aiden/IDENTITY.md +6 -0
  25. package/templates/workspaces/aiden/LEARNINGS.md +6 -0
  26. package/templates/workspaces/aiden/MEMORY.md +22 -0
  27. package/templates/workspaces/aiden/PLAYBOOK.md +16 -0
  28. package/templates/workspaces/aiden/SOUL.md +1 -1
  29. package/templates/workspaces/aiden/USER.md +17 -0
  30. package/templates/workspaces/alex/AGENTS.md +52 -0
  31. package/templates/workspaces/alex/FEEDBACK.md +11 -0
  32. package/templates/workspaces/alex/HEARTBEAT.md +9 -0
  33. package/templates/workspaces/alex/IDENTITY.md +6 -0
  34. package/templates/workspaces/alex/LEARNINGS.md +5 -0
  35. package/templates/workspaces/alex/MEMORY.md +22 -0
  36. package/templates/workspaces/alex/PLAYBOOK.md +16 -0
  37. package/templates/workspaces/alex/SOUL.md +1 -1
  38. package/templates/workspaces/alex/USER.md +13 -0
  39. package/templates/workspaces/aria/AGENTS.md +18 -0
  40. package/templates/workspaces/aria/FEEDBACK.md +12 -0
  41. package/templates/workspaces/aria/HEARTBEAT.md +32 -0
  42. package/templates/workspaces/aria/IDENTITY.md +12 -0
  43. package/templates/workspaces/aria/LEARNINGS.md +31 -0
  44. package/templates/workspaces/aria/MEMORY.md +29 -0
  45. package/templates/workspaces/aria/PLAYBOOK.md +71 -0
  46. package/templates/workspaces/aria/SOUL.md +57 -0
  47. package/templates/workspaces/aria/TOOLS.md +47 -0
  48. package/templates/workspaces/aria/USER.md +18 -0
  49. package/templates/workspaces/audrey/AGENTS.md +59 -0
  50. package/templates/workspaces/audrey/FEEDBACK.md +11 -0
  51. package/templates/workspaces/audrey/HEARTBEAT.md +9 -0
  52. package/templates/workspaces/audrey/IDENTITY.md +6 -0
  53. package/templates/workspaces/audrey/LEARNINGS.md +5 -0
  54. package/templates/workspaces/audrey/MEMORY.md +22 -0
  55. package/templates/workspaces/audrey/PLAYBOOK.md +16 -0
  56. package/templates/workspaces/audrey/SOUL.md +1 -1
  57. package/templates/workspaces/audrey/TOOLS.md +15 -0
  58. package/templates/workspaces/audrey/USER.md +13 -0
  59. package/templates/workspaces/avery/AGENTS.md +52 -0
  60. package/templates/workspaces/avery/FEEDBACK.md +12 -0
  61. package/templates/workspaces/avery/HEARTBEAT.md +5 -0
  62. package/templates/workspaces/avery/IDENTITY.md +6 -0
  63. package/templates/workspaces/avery/LEARNINGS.md +6 -0
  64. package/templates/workspaces/avery/MEMORY.md +22 -0
  65. package/templates/workspaces/avery/PLAYBOOK.md +16 -0
  66. package/templates/workspaces/avery/SOUL.md +1 -1
  67. package/templates/workspaces/avery/USER.md +17 -0
  68. package/templates/workspaces/avery/skills/claude-code/SKILL.md +38 -0
  69. package/templates/workspaces/avery/skills/claude-code/claude_code +55 -0
  70. package/templates/workspaces/caleb/AGENTS.md +52 -0
  71. package/templates/workspaces/caleb/FEEDBACK.md +11 -0
  72. package/templates/workspaces/caleb/HEARTBEAT.md +9 -0
  73. package/templates/workspaces/caleb/IDENTITY.md +6 -0
  74. package/templates/workspaces/caleb/LEARNINGS.md +5 -0
  75. package/templates/workspaces/caleb/MEMORY.md +22 -0
  76. package/templates/workspaces/caleb/PLAYBOOK.md +16 -0
  77. package/templates/workspaces/caleb/SOUL.md +1 -1
  78. package/templates/workspaces/caleb/TOOLS.md +30 -0
  79. package/templates/workspaces/caleb/USER.md +13 -0
  80. package/templates/workspaces/clara/AGENTS.md +59 -0
  81. package/templates/workspaces/clara/FEEDBACK.md +12 -0
  82. package/templates/workspaces/clara/HEARTBEAT.md +5 -0
  83. package/templates/workspaces/clara/IDENTITY.md +6 -0
  84. package/templates/workspaces/clara/LEARNINGS.md +6 -0
  85. package/templates/workspaces/clara/MEMORY.md +22 -0
  86. package/templates/workspaces/clara/PLAYBOOK.md +16 -0
  87. package/templates/workspaces/clara/SOUL.md +1 -1
  88. package/templates/workspaces/clara/TOOLS.md +15 -0
  89. package/templates/workspaces/clara/USER.md +17 -0
  90. package/templates/workspaces/daphne/AGENTS.md +59 -0
  91. package/templates/workspaces/daphne/FEEDBACK.md +18 -0
  92. package/templates/workspaces/daphne/HEARTBEAT.md +5 -0
  93. package/templates/workspaces/daphne/IDENTITY.md +6 -0
  94. package/templates/workspaces/daphne/LEARNINGS.md +6 -0
  95. package/templates/workspaces/daphne/MEMORY.md +22 -0
  96. package/templates/workspaces/daphne/PLAYBOOK.md +48 -0
  97. package/templates/workspaces/daphne/SOUL.md +1 -1
  98. package/templates/workspaces/daphne/TOOLS.md +15 -0
  99. package/templates/workspaces/daphne/USER.md +17 -0
  100. package/templates/workspaces/darius/AGENTS.md +52 -0
  101. package/templates/workspaces/darius/FEEDBACK.md +12 -0
  102. package/templates/workspaces/darius/HEARTBEAT.md +5 -0
  103. package/templates/workspaces/darius/IDENTITY.md +6 -0
  104. package/templates/workspaces/darius/LEARNINGS.md +6 -0
  105. package/templates/workspaces/darius/MEMORY.md +22 -0
  106. package/templates/workspaces/darius/PLAYBOOK.md +16 -0
  107. package/templates/workspaces/darius/SOUL.md +1 -1
  108. package/templates/workspaces/darius/USER.md +17 -0
  109. package/templates/workspaces/darius/skills/claude-code/SKILL.md +38 -0
  110. package/templates/workspaces/darius/skills/claude-code/claude_code +55 -0
  111. package/templates/workspaces/devon/AGENTS.md +52 -0
  112. package/templates/workspaces/devon/FEEDBACK.md +11 -0
  113. package/templates/workspaces/devon/HEARTBEAT.md +5 -0
  114. package/templates/workspaces/devon/IDENTITY.md +6 -0
  115. package/templates/workspaces/devon/LEARNINGS.md +11 -0
  116. package/templates/workspaces/devon/MEMORY.md +22 -0
  117. package/templates/workspaces/devon/PLAYBOOK.md +16 -0
  118. package/templates/workspaces/devon/SOUL.md +1 -1
  119. package/templates/workspaces/devon/USER.md +13 -0
  120. package/templates/workspaces/devon/check_github.py +12 -0
  121. package/templates/workspaces/devon/check_mc_env.py +30 -0
  122. package/templates/workspaces/devon/check_sb.py +34 -0
  123. package/templates/workspaces/devon/check_vercel.py +12 -0
  124. package/templates/workspaces/devon/get_mc_files.py +17 -0
  125. package/templates/workspaces/devon/write_heartbeat.py +67 -0
  126. package/templates/workspaces/dylan/.env.example +33 -0
  127. package/templates/workspaces/dylan/00007_verify_licenses_table.sql +100 -0
  128. package/templates/workspaces/dylan/AGENTS.md +52 -0
  129. package/templates/workspaces/dylan/FEEDBACK.md +28 -0
  130. package/templates/workspaces/dylan/HEARTBEAT.md +5 -0
  131. package/templates/workspaces/dylan/IDENTITY.md +6 -0
  132. package/templates/workspaces/dylan/LEARNINGS.md +70 -0
  133. package/templates/workspaces/dylan/MEMORY.md +22 -0
  134. package/templates/workspaces/dylan/PLAYBOOK.md +16 -0
  135. package/templates/workspaces/dylan/SOUL.md +1 -1
  136. package/templates/workspaces/dylan/STRIPE_PIPELINE.md +185 -0
  137. package/templates/workspaces/dylan/USER.md +17 -0
  138. package/templates/workspaces/dylan/n8n-stripe-welcome-workflow.json +123 -0
  139. package/templates/workspaces/dylan/skills/claude-code/SKILL.md +38 -0
  140. package/templates/workspaces/dylan/skills/claude-code/claude_code +55 -0
  141. package/templates/workspaces/dylan/stripe-webhook-handler.py +433 -0
  142. package/templates/workspaces/dylan/test_mock_webhook.py +103 -0
  143. package/templates/workspaces/elena/AGENTS.md +59 -0
  144. package/templates/workspaces/elena/FEEDBACK.md +11 -0
  145. package/templates/workspaces/elena/HEARTBEAT.md +9 -0
  146. package/templates/workspaces/elena/IDENTITY.md +6 -0
  147. package/templates/workspaces/elena/LEARNINGS.md +5 -0
  148. package/templates/workspaces/elena/MEMORY.md +22 -0
  149. package/templates/workspaces/elena/PLAYBOOK.md +16 -0
  150. package/templates/workspaces/elena/SOUL.md +1 -1
  151. package/templates/workspaces/elena/TOOLS.md +15 -0
  152. package/templates/workspaces/elena/USER.md +13 -0
  153. package/templates/workspaces/eva/AGENTS.md +59 -0
  154. package/templates/workspaces/eva/FEEDBACK.md +11 -0
  155. package/templates/workspaces/eva/HEARTBEAT.md +9 -0
  156. package/templates/workspaces/eva/IDENTITY.md +6 -0
  157. package/templates/workspaces/eva/LEARNINGS.md +5 -0
  158. package/templates/workspaces/eva/MEMORY.md +22 -0
  159. package/templates/workspaces/eva/PLAYBOOK.md +16 -0
  160. package/templates/workspaces/eva/SOUL.md +1 -1
  161. package/templates/workspaces/eva/TOOLS.md +15 -0
  162. package/templates/workspaces/eva/USER.md +13 -0
  163. package/templates/workspaces/felix/AGENTS.md +52 -0
  164. package/templates/workspaces/felix/FEEDBACK.md +11 -0
  165. package/templates/workspaces/felix/HEARTBEAT.md +5 -0
  166. package/templates/workspaces/felix/IDENTITY.md +6 -0
  167. package/templates/workspaces/felix/LEARNINGS.md +17 -0
  168. package/templates/workspaces/felix/MEMORY.md +22 -0
  169. package/templates/workspaces/felix/PLAYBOOK.md +16 -0
  170. package/templates/workspaces/felix/SOUL.md +1 -1
  171. package/templates/workspaces/felix/USER.md +13 -0
  172. package/templates/workspaces/felix/fidelia-psychology.html +1594 -0
  173. package/templates/workspaces/felix/task.txt +164 -0
  174. package/templates/workspaces/hannah/AGENTS.md +59 -0
  175. package/templates/workspaces/hannah/FEEDBACK.md +12 -0
  176. package/templates/workspaces/hannah/HEARTBEAT.md +5 -0
  177. package/templates/workspaces/hannah/IDENTITY.md +6 -0
  178. package/templates/workspaces/hannah/LEARNINGS.md +6 -0
  179. package/templates/workspaces/hannah/MEMORY.md +22 -0
  180. package/templates/workspaces/hannah/PLAYBOOK.md +16 -0
  181. package/templates/workspaces/hannah/SOUL.md +1 -1
  182. package/templates/workspaces/hannah/TOOLS.md +15 -0
  183. package/templates/workspaces/hannah/USER.md +17 -0
  184. package/templates/workspaces/isaac/AGENTS.md +52 -0
  185. package/templates/workspaces/isaac/FEEDBACK.md +12 -0
  186. package/templates/workspaces/isaac/HEARTBEAT.md +9 -0
  187. package/templates/workspaces/isaac/IDENTITY.md +6 -0
  188. package/templates/workspaces/isaac/LEARNINGS.md +6 -0
  189. package/templates/workspaces/isaac/MEMORY.md +22 -0
  190. package/templates/workspaces/isaac/PLAYBOOK.md +16 -0
  191. package/templates/workspaces/isaac/SOUL.md +1 -1
  192. package/templates/workspaces/isaac/USER.md +17 -0
  193. package/templates/workspaces/isaac/skills/claude-code/SKILL.md +38 -0
  194. package/templates/workspaces/isaac/skills/claude-code/claude_code +55 -0
  195. package/templates/workspaces/logan/AGENTS.md +59 -0
  196. package/templates/workspaces/logan/FEEDBACK.md +11 -0
  197. package/templates/workspaces/logan/HEARTBEAT.md +9 -0
  198. package/templates/workspaces/logan/IDENTITY.md +6 -0
  199. package/templates/workspaces/logan/LEARNINGS.md +5 -0
  200. package/templates/workspaces/logan/MEMORY.md +22 -0
  201. package/templates/workspaces/logan/PLAYBOOK.md +16 -0
  202. package/templates/workspaces/logan/SOUL.md +1 -1
  203. package/templates/workspaces/logan/TOOLS.md +15 -0
  204. package/templates/workspaces/logan/USER.md +13 -0
  205. package/templates/workspaces/maxxipro/AGENTS.md +29 -0
  206. package/templates/workspaces/maxxipro/FEEDBACK.md +19 -0
  207. package/templates/workspaces/maxxipro/HEARTBEAT.md +22 -0
  208. package/templates/workspaces/maxxipro/IDENTITY.md +35 -0
  209. package/templates/workspaces/maxxipro/KNOWLEDGE.md +335 -0
  210. package/templates/workspaces/maxxipro/LEARNINGS.md +47 -0
  211. package/templates/workspaces/maxxipro/MEMORY.md +60 -0
  212. package/templates/workspaces/maxxipro/OUTREACH_TEMPLATES.md +143 -0
  213. package/templates/workspaces/maxxipro/PLAYBOOK.md +81 -0
  214. package/templates/workspaces/maxxipro/SOUL.md +146 -0
  215. package/templates/workspaces/maxxipro/TOOLS.md +81 -0
  216. package/templates/workspaces/maxxipro/USER.md +40 -0
  217. package/templates/workspaces/morgan/AGENTS.md +59 -0
  218. package/templates/workspaces/morgan/FEEDBACK.md +19 -0
  219. package/templates/workspaces/morgan/HEARTBEAT.md +5 -0
  220. package/templates/workspaces/morgan/IDENTITY.md +6 -0
  221. package/templates/workspaces/morgan/LEARNINGS.md +18 -0
  222. package/templates/workspaces/morgan/MEMORY.md +22 -0
  223. package/templates/workspaces/morgan/PLAYBOOK.md +16 -0
  224. package/templates/workspaces/morgan/SOUL.md +1 -1
  225. package/templates/workspaces/morgan/TOOLS.md +15 -0
  226. package/templates/workspaces/morgan/USER.md +13 -0
  227. package/templates/workspaces/nadia/AGENTS.md +59 -0
  228. package/templates/workspaces/nadia/FEEDBACK.md +12 -0
  229. package/templates/workspaces/nadia/HEARTBEAT.md +5 -0
  230. package/templates/workspaces/nadia/IDENTITY.md +6 -0
  231. package/templates/workspaces/nadia/LEARNINGS.md +6 -0
  232. package/templates/workspaces/nadia/MEMORY.md +22 -0
  233. package/templates/workspaces/nadia/PLAYBOOK.md +16 -0
  234. package/templates/workspaces/nadia/SOUL.md +1 -1
  235. package/templates/workspaces/nadia/TOOLS.md +15 -0
  236. package/templates/workspaces/nadia/USER.md +13 -0
  237. package/templates/workspaces/nate/AGENTS.md +24 -0
  238. package/templates/workspaces/nate/FEEDBACK.md +12 -0
  239. package/templates/workspaces/nate/HEARTBEAT.md +33 -0
  240. package/templates/workspaces/nate/IDENTITY.md +15 -0
  241. package/templates/workspaces/nate/LEARNINGS.md +33 -0
  242. package/templates/workspaces/nate/MEMORY.md +39 -0
  243. package/templates/workspaces/nate/PLAYBOOK.md +160 -0
  244. package/templates/workspaces/nate/SOUL.md +50 -0
  245. package/templates/workspaces/nate/TOOLS.md +111 -0
  246. package/templates/workspaces/nate/USER.md +32 -0
  247. package/templates/workspaces/olivia/.last-openclaw-version +1 -0
  248. package/templates/workspaces/olivia/.npmrc.tmp +0 -0
  249. package/templates/workspaces/olivia/AGENTS.md +77 -0
  250. package/templates/workspaces/olivia/ALPHA_CODING_BENCHMARK.txt +148 -0
  251. package/templates/workspaces/olivia/ALPHA_MODEL_GUIDE.md +393 -0
  252. package/templates/workspaces/olivia/FEEDBACK.md +13 -0
  253. package/templates/workspaces/olivia/HEADTOHEAD_BENCHMARK.txt +1289 -0
  254. package/templates/workspaces/olivia/HEARTBEAT.md +267 -0
  255. package/templates/workspaces/olivia/IDENTITY.md +6 -0
  256. package/templates/workspaces/olivia/LEARNINGS.md +708 -0
  257. package/templates/workspaces/olivia/MEMORY.md +202 -0
  258. package/templates/workspaces/olivia/MISSION_CONTROL_DESIGN_SPEC_v1.md +1143 -0
  259. package/templates/workspaces/olivia/MVP-COMPLETION-SUMMARY.md +175 -0
  260. package/templates/workspaces/olivia/NETWORK_IMPLEMENTATION_PLAN.md +1556 -0
  261. package/templates/workspaces/olivia/NEW_NODES_BENCHMARK.txt +947 -0
  262. package/templates/workspaces/olivia/PLAYBOOK.md +42 -0
  263. package/templates/workspaces/olivia/SELF-HEALING-COMPLETE.md +150 -0
  264. package/templates/workspaces/olivia/SOUL.md +8 -8
  265. package/templates/workspaces/olivia/TOOLS.md +15 -0
  266. package/templates/workspaces/olivia/USER.md +17 -0
  267. package/templates/workspaces/olivia/alicefleet-supabase-credentials.md +50 -0
  268. package/templates/workspaces/olivia/dzombo-copy-rewrite.md +115 -0
  269. package/templates/workspaces/olivia/dzombo-implementation-plan.md +1248 -0
  270. package/templates/workspaces/olivia/fidelia-psychology.html +1594 -0
  271. package/templates/workspaces/olivia/lead_debug.png +0 -0
  272. package/templates/workspaces/olivia/minimatch-10.2.4.tgz +0 -0
  273. package/templates/workspaces/olivia/operation-bllm-research.md +157 -0
  274. package/templates/workspaces/olivia/qa-audit-mission-control-v2.md +538 -0
  275. package/templates/workspaces/olivia/roofmaxx_logo.svg +1 -0
  276. package/templates/workspaces/olivia/roofmaxx_social.jpg +0 -0
  277. package/templates/workspaces/olivia/skills/1password/SKILL.md +53 -0
  278. package/templates/workspaces/olivia/skills/1password/_meta.json +6 -0
  279. package/templates/workspaces/olivia/skills/afrexai-recruiting-engine/README.md +57 -0
  280. package/templates/workspaces/olivia/skills/afrexai-recruiting-engine/SKILL.md +534 -0
  281. package/templates/workspaces/olivia/skills/afrexai-recruiting-engine/_meta.json +6 -0
  282. package/templates/workspaces/olivia/skills/agent-security/SKILL.md +69 -0
  283. package/templates/workspaces/olivia/skills/agent-security/_meta.json +6 -0
  284. package/templates/workspaces/olivia/skills/agentic-security-audit/SKILL.md +855 -0
  285. package/templates/workspaces/olivia/skills/agentic-security-audit/_meta.json +6 -0
  286. package/templates/workspaces/olivia/skills/ai-automation-consulting/SKILL.md +67 -0
  287. package/templates/workspaces/olivia/skills/ai-automation-consulting/_meta.json +6 -0
  288. package/templates/workspaces/olivia/skills/ai-automation-consulting/skill.json +12 -0
  289. package/templates/workspaces/olivia/skills/ai-presentation-maker/SKILL.md +1104 -0
  290. package/templates/workspaces/olivia/skills/ai-presentation-maker/_meta.json +6 -0
  291. package/templates/workspaces/olivia/skills/ai-productivity-audit/SKILL.md +181 -0
  292. package/templates/workspaces/olivia/skills/ai-productivity-audit/_meta.json +6 -0
  293. package/templates/workspaces/olivia/skills/ai-researcher/README.md +31 -0
  294. package/templates/workspaces/olivia/skills/ai-researcher/SKILL.md +59 -0
  295. package/templates/workspaces/olivia/skills/ai-researcher/_meta.json +6 -0
  296. package/templates/workspaces/olivia/skills/ai-seo-writer/README.md +19 -0
  297. package/templates/workspaces/olivia/skills/ai-seo-writer/SKILL.md +100 -0
  298. package/templates/workspaces/olivia/skills/ai-seo-writer/_meta.json +6 -0
  299. package/templates/workspaces/olivia/skills/analytics-tracking-2/SKILL.md +309 -0
  300. package/templates/workspaces/olivia/skills/analytics-tracking-2/_meta.json +6 -0
  301. package/templates/workspaces/olivia/skills/api-doc-writer/SKILL.md +232 -0
  302. package/templates/workspaces/olivia/skills/api-doc-writer/_meta.json +6 -0
  303. package/templates/workspaces/olivia/skills/api-generator/SKILL.md +49 -0
  304. package/templates/workspaces/olivia/skills/api-generator/_meta.json +6 -0
  305. package/templates/workspaces/olivia/skills/api-generator/tips.md +10 -0
  306. package/templates/workspaces/olivia/skills/apple-notes/SKILL.md +50 -0
  307. package/templates/workspaces/olivia/skills/apple-notes/_meta.json +6 -0
  308. package/templates/workspaces/olivia/skills/apple-reminders/SKILL.md +67 -0
  309. package/templates/workspaces/olivia/skills/apple-reminders/_meta.json +6 -0
  310. package/templates/workspaces/olivia/skills/automation-workflows/SKILL.md +267 -0
  311. package/templates/workspaces/olivia/skills/automation-workflows/_meta.json +6 -0
  312. package/templates/workspaces/olivia/skills/autoresearch/SKILL.md +46 -0
  313. package/templates/workspaces/olivia/skills/autoresearch/aria_write.py +148 -0
  314. package/templates/workspaces/olivia/skills/autoresearch/autoresearch.py +75 -0
  315. package/templates/workspaces/olivia/skills/azure-devops/SKILL.md +115 -0
  316. package/templates/workspaces/olivia/skills/azure-devops/_meta.json +6 -0
  317. package/templates/workspaces/olivia/skills/blogwatcher/SKILL.md +46 -0
  318. package/templates/workspaces/olivia/skills/blogwatcher/_meta.json +6 -0
  319. package/templates/workspaces/olivia/skills/blucli/SKILL.md +27 -0
  320. package/templates/workspaces/olivia/skills/blucli/_meta.json +6 -0
  321. package/templates/workspaces/olivia/skills/check-analytics/SKILL.md +92 -0
  322. package/templates/workspaces/olivia/skills/check-analytics/_meta.json +6 -0
  323. package/templates/workspaces/olivia/skills/cloud-architect/SKILL.md +89 -0
  324. package/templates/workspaces/olivia/skills/cloud-architect/_meta.json +6 -0
  325. package/templates/workspaces/olivia/skills/cloud-infra-automation/SKILL.md +50 -0
  326. package/templates/workspaces/olivia/skills/cloud-infra-automation/_meta.json +6 -0
  327. package/templates/workspaces/olivia/skills/cloud-storage/SKILL.md +61 -0
  328. package/templates/workspaces/olivia/skills/cloud-storage/_meta.json +6 -0
  329. package/templates/workspaces/olivia/skills/cloud-storage/auth.md +97 -0
  330. package/templates/workspaces/olivia/skills/cloud-storage/costs.md +88 -0
  331. package/templates/workspaces/olivia/skills/cloud-storage/providers.md +55 -0
  332. package/templates/workspaces/olivia/skills/copywriting-pro/SKILL.md +107 -0
  333. package/templates/workspaces/olivia/skills/copywriting-pro/_meta.json +6 -0
  334. package/templates/workspaces/olivia/skills/data-analyst-pro/SKILL.md +21 -0
  335. package/templates/workspaces/olivia/skills/data-analyst-pro/_meta.json +6 -0
  336. package/templates/workspaces/olivia/skills/database-designer/README.md +388 -0
  337. package/templates/workspaces/olivia/skills/database-designer/SKILL.md +66 -0
  338. package/templates/workspaces/olivia/skills/database-designer/_meta.json +6 -0
  339. package/templates/workspaces/olivia/skills/database-designer/index_optimizer.py +926 -0
  340. package/templates/workspaces/olivia/skills/database-designer/migration_generator.py +1199 -0
  341. package/templates/workspaces/olivia/skills/database-designer/schema_analyzer.py +982 -0
  342. package/templates/workspaces/olivia/skills/deploy-agent/SKILL.md +255 -0
  343. package/templates/workspaces/olivia/skills/deploy-agent/_meta.json +6 -0
  344. package/templates/workspaces/olivia/skills/devops-automation-pack/SKILL.md +72 -0
  345. package/templates/workspaces/olivia/skills/devops-automation-pack/_meta.json +6 -0
  346. package/templates/workspaces/olivia/skills/devops-automation-pack/deploy.sh +0 -0
  347. package/templates/workspaces/olivia/skills/financial-analysis-agent/SKILL.md +489 -0
  348. package/templates/workspaces/olivia/skills/financial-analysis-agent/_meta.json +6 -0
  349. package/templates/workspaces/olivia/skills/gdpr-compliance-tracker/README.md +72 -0
  350. package/templates/workspaces/olivia/skills/gdpr-compliance-tracker/SKILL.md +226 -0
  351. package/templates/workspaces/olivia/skills/gdpr-compliance-tracker/_meta.json +6 -0
  352. package/templates/workspaces/olivia/skills/gifgrep/SKILL.md +47 -0
  353. package/templates/workspaces/olivia/skills/gifgrep/_meta.json +6 -0
  354. package/templates/workspaces/olivia/skills/github/SKILL.md +47 -0
  355. package/templates/workspaces/olivia/skills/github/_meta.json +6 -0
  356. package/templates/workspaces/olivia/skills/gog/SKILL.md +36 -0
  357. package/templates/workspaces/olivia/skills/gog/_meta.json +6 -0
  358. package/templates/workspaces/olivia/skills/growth-strategy-hub/SKILL.md +135 -0
  359. package/templates/workspaces/olivia/skills/growth-strategy-hub/_meta.json +6 -0
  360. package/templates/workspaces/olivia/skills/growth-strategy-hub/metadata.json +4 -0
  361. package/templates/workspaces/olivia/skills/hetzner-cloud/SKILL.md +130 -0
  362. package/templates/workspaces/olivia/skills/hetzner-cloud/_meta.json +6 -0
  363. package/templates/workspaces/olivia/skills/himalaya/SKILL.md +217 -0
  364. package/templates/workspaces/olivia/skills/himalaya/_meta.json +6 -0
  365. package/templates/workspaces/olivia/skills/hotel-recommendation/SKILL.md +117 -0
  366. package/templates/workspaces/olivia/skills/hotel-recommendation/_meta.json +6 -0
  367. package/templates/workspaces/olivia/skills/hr-policy-generator/SKILL.md +54 -0
  368. package/templates/workspaces/olivia/skills/hr-policy-generator/_meta.json +6 -0
  369. package/templates/workspaces/olivia/skills/human-writing/SKILL.md +41 -0
  370. package/templates/workspaces/olivia/skills/human-writing/_meta.json +6 -0
  371. package/templates/workspaces/olivia/skills/imsg/SKILL.md +25 -0
  372. package/templates/workspaces/olivia/skills/imsg/_meta.json +6 -0
  373. package/templates/workspaces/olivia/skills/in-depth-research/SKILL.md +124 -0
  374. package/templates/workspaces/olivia/skills/in-depth-research/_meta.json +6 -0
  375. package/templates/workspaces/olivia/skills/in-depth-research/methodology.md +75 -0
  376. package/templates/workspaces/olivia/skills/in-depth-research/output-formats.md +168 -0
  377. package/templates/workspaces/olivia/skills/in-depth-research/sources.md +80 -0
  378. package/templates/workspaces/olivia/skills/javascript-skills/README.md +71 -0
  379. package/templates/workspaces/olivia/skills/javascript-skills/SKILL.md +746 -0
  380. package/templates/workspaces/olivia/skills/javascript-skills/_meta.json +6 -0
  381. package/templates/workspaces/olivia/skills/leadership-strategy-playbook/SKILL.md +147 -0
  382. package/templates/workspaces/olivia/skills/leadership-strategy-playbook/_meta.json +6 -0
  383. package/templates/workspaces/olivia/skills/market-research-agent/README.md +29 -0
  384. package/templates/workspaces/olivia/skills/market-research-agent/SKILL.md +52 -0
  385. package/templates/workspaces/olivia/skills/market-research-agent/_meta.json +6 -0
  386. package/templates/workspaces/olivia/skills/marketing-analytics/SKILL.md +74 -0
  387. package/templates/workspaces/olivia/skills/marketing-analytics/_meta.json +6 -0
  388. package/templates/workspaces/olivia/skills/marketing-master-io/SKILL.md +125 -0
  389. package/templates/workspaces/olivia/skills/marketing-master-io/_meta.json +6 -0
  390. package/templates/workspaces/olivia/skills/marketing-strategy-pmm/SKILL.md +398 -0
  391. package/templates/workspaces/olivia/skills/marketing-strategy-pmm/_meta.json +6 -0
  392. package/templates/workspaces/olivia/skills/meta-ads-analytics/SKILL.md +53 -0
  393. package/templates/workspaces/olivia/skills/meta-ads-analytics/_meta.json +6 -0
  394. package/templates/workspaces/olivia/skills/obsidian/SKILL.md +55 -0
  395. package/templates/workspaces/olivia/skills/obsidian/_meta.json +6 -0
  396. package/templates/workspaces/olivia/skills/openclaw-accounting/SKILL.md +125 -0
  397. package/templates/workspaces/olivia/skills/openclaw-accounting/_meta.json +6 -0
  398. package/templates/workspaces/olivia/skills/openclaw-security-toolkit/CHANGELOG.md +35 -0
  399. package/templates/workspaces/olivia/skills/openclaw-security-toolkit/CHANNELLOG.md +73 -0
  400. package/templates/workspaces/olivia/skills/openclaw-security-toolkit/README.md +161 -0
  401. package/templates/workspaces/olivia/skills/openclaw-security-toolkit/SKILL.md +130 -0
  402. package/templates/workspaces/olivia/skills/openclaw-security-toolkit/_meta.json +6 -0
  403. package/templates/workspaces/olivia/skills/openclaw-security-toolkit/config.json +36 -0
  404. package/templates/workspaces/olivia/skills/openclaw-security-toolkit/metadata.json +19 -0
  405. package/templates/workspaces/olivia/skills/openhue/SKILL.md +30 -0
  406. package/templates/workspaces/olivia/skills/openhue/_meta.json +6 -0
  407. package/templates/workspaces/olivia/skills/orgx-operations-agent/SKILL.md +41 -0
  408. package/templates/workspaces/olivia/skills/orgx-operations-agent/_meta.json +6 -0
  409. package/templates/workspaces/olivia/skills/outreach/SKILL.md +84 -0
  410. package/templates/workspaces/olivia/skills/outreach/_meta.json +6 -0
  411. package/templates/workspaces/olivia/skills/outreach/by-type.md +166 -0
  412. package/templates/workspaces/olivia/skills/outreach/templates.md +154 -0
  413. package/templates/workspaces/olivia/skills/outreach/tracking.md +145 -0
  414. package/templates/workspaces/olivia/skills/persona-hr-coordinator/SKILL.md +38 -0
  415. package/templates/workspaces/olivia/skills/persona-hr-coordinator/_meta.json +6 -0
  416. package/templates/workspaces/olivia/skills/personal-productivity/SKILL.md +161 -0
  417. package/templates/workspaces/olivia/skills/personal-productivity/_meta.json +6 -0
  418. package/templates/workspaces/olivia/skills/personal-productivity/index.js +363 -0
  419. package/templates/workspaces/olivia/skills/personal-productivity/package.json +15 -0
  420. package/templates/workspaces/olivia/skills/personal-travel/README.md +34 -0
  421. package/templates/workspaces/olivia/skills/personal-travel/SKILL.md +46 -0
  422. package/templates/workspaces/olivia/skills/personal-travel/_meta.json +6 -0
  423. package/templates/workspaces/olivia/skills/presentation-html-generator-skill/SKILL.md +185 -0
  424. package/templates/workspaces/olivia/skills/presentation-html-generator-skill/_meta.json +6 -0
  425. package/templates/workspaces/olivia/skills/product-manager/SKILL.md +77 -0
  426. package/templates/workspaces/olivia/skills/product-manager/_meta.json +6 -0
  427. package/templates/workspaces/olivia/skills/quant-strategy/SKILL.md +28 -0
  428. package/templates/workspaces/olivia/skills/quant-strategy/_meta.json +6 -0
  429. package/templates/workspaces/olivia/skills/sales-pipeline-tracker/README.md +29 -0
  430. package/templates/workspaces/olivia/skills/sales-pipeline-tracker/SKILL.md +45 -0
  431. package/templates/workspaces/olivia/skills/sales-pipeline-tracker/_meta.json +6 -0
  432. package/templates/workspaces/olivia/skills/security-auditor/SKILL.md +399 -0
  433. package/templates/workspaces/olivia/skills/security-auditor/_meta.json +6 -0
  434. package/templates/workspaces/olivia/skills/security-hardening/SKILL.md +296 -0
  435. package/templates/workspaces/olivia/skills/security-hardening/_meta.json +6 -0
  436. package/templates/workspaces/olivia/skills/security-scanner/SKILL.md +67 -0
  437. package/templates/workspaces/olivia/skills/security-scanner/_meta.json +6 -0
  438. package/templates/workspaces/olivia/skills/seo-optimization/SKILL.md +31 -0
  439. package/templates/workspaces/olivia/skills/seo-optimization/_meta.json +6 -0
  440. package/templates/workspaces/olivia/skills/service-booking/SKILL.md +193 -0
  441. package/templates/workspaces/olivia/skills/service-booking/_meta.json +6 -0
  442. package/templates/workspaces/olivia/skills/sme-hr-automation/SKILL.md +131 -0
  443. package/templates/workspaces/olivia/skills/sme-hr-automation/_meta.json +6 -0
  444. package/templates/workspaces/olivia/skills/social-media-scheduler/README.md +29 -0
  445. package/templates/workspaces/olivia/skills/social-media-scheduler/SKILL.md +49 -0
  446. package/templates/workspaces/olivia/skills/social-media-scheduler/_meta.json +6 -0
  447. package/templates/workspaces/olivia/skills/sonoscli/SKILL.md +26 -0
  448. package/templates/workspaces/olivia/skills/sonoscli/_meta.json +6 -0
  449. package/templates/workspaces/olivia/skills/strategy-advisor/SKILL.md +33 -0
  450. package/templates/workspaces/olivia/skills/strategy-advisor/_meta.json +6 -0
  451. package/templates/workspaces/olivia/skills/summarize/SKILL.md +49 -0
  452. package/templates/workspaces/olivia/skills/summarize/_meta.json +6 -0
  453. package/templates/workspaces/olivia/skills/things-mac/SKILL.md +61 -0
  454. package/templates/workspaces/olivia/skills/things-mac/_meta.json +6 -0
  455. package/templates/workspaces/olivia/skills/travel-itinerary-planner/SKILL.md +121 -0
  456. package/templates/workspaces/olivia/skills/travel-itinerary-planner/_meta.json +6 -0
  457. package/templates/workspaces/olivia/skills/travel-manager/SKILL.md +36 -0
  458. package/templates/workspaces/olivia/skills/travel-manager/_meta.json +6 -0
  459. package/templates/workspaces/olivia/skills/travel-planning/SKILL.md +238 -0
  460. package/templates/workspaces/olivia/skills/travel-planning/_meta.json +6 -0
  461. package/templates/workspaces/olivia/skills/travel-planning/booking-guide.md +91 -0
  462. package/templates/workspaces/olivia/skills/travel-planning/memory-template.md +111 -0
  463. package/templates/workspaces/olivia/skills/travel-planning/multi-city.md +131 -0
  464. package/templates/workspaces/olivia/skills/travel-planning/packing-templates.md +155 -0
  465. package/templates/workspaces/olivia/skills/travel-planning/setup.md +66 -0
  466. package/templates/workspaces/olivia/skills/update-it-all/SKILL.md +143 -0
  467. package/templates/workspaces/olivia/skills/update-it-all/_meta.json +6 -0
  468. package/templates/workspaces/olivia/skills/voice/SKILL.md +62 -0
  469. package/templates/workspaces/olivia/skills/weather/SKILL.md +49 -0
  470. package/templates/workspaces/olivia/skills/weather/_meta.json +6 -0
  471. package/templates/workspaces/olivia/skills/web-researcher/SKILL.md +21 -0
  472. package/templates/workspaces/olivia/skills/web-researcher/_meta.json +6 -0
  473. package/templates/workspaces/olivia/skills/website-seo/SKILL.md +284 -0
  474. package/templates/workspaces/olivia/skills/website-seo/_meta.json +6 -0
  475. package/templates/workspaces/olivia/stripe-welcome-n8n.json +103 -0
  476. package/templates/workspaces/olivia/test2.wav.wav +0 -0
  477. package/templates/workspaces/olivia/test_speech.json +1 -0
  478. package/templates/workspaces/olivia/test_speech.srt +0 -0
  479. package/templates/workspaces/olivia/test_speech.tsv +1 -0
  480. package/templates/workspaces/olivia/test_speech.txt +0 -0
  481. package/templates/workspaces/olivia/test_speech.vtt +2 -0
  482. package/templates/workspaces/owen/AGENTS.md +59 -0
  483. package/templates/workspaces/owen/FEEDBACK.md +12 -0
  484. package/templates/workspaces/owen/HEARTBEAT.md +5 -0
  485. package/templates/workspaces/owen/IDENTITY.md +6 -0
  486. package/templates/workspaces/owen/LEARNINGS.md +46 -0
  487. package/templates/workspaces/owen/MEMORY.md +22 -0
  488. package/templates/workspaces/owen/PLAYBOOK.md +16 -0
  489. package/templates/workspaces/owen/SOUL.md +1 -1
  490. package/templates/workspaces/owen/TOOLS.md +15 -0
  491. package/templates/workspaces/owen/USER.md +17 -0
  492. package/templates/workspaces/parker/AGENTS.md +59 -0
  493. package/templates/workspaces/parker/FEEDBACK.md +11 -0
  494. package/templates/workspaces/parker/HEARTBEAT.md +5 -0
  495. package/templates/workspaces/parker/IDENTITY.md +6 -0
  496. package/templates/workspaces/parker/LEARNINGS.md +17 -0
  497. package/templates/workspaces/parker/MEMORY.md +22 -0
  498. package/templates/workspaces/parker/PLAYBOOK.md +16 -0
  499. package/templates/workspaces/parker/SOUL.md +1 -1
  500. package/templates/workspaces/parker/TOOLS.md +15 -0
  501. package/templates/workspaces/parker/USER.md +13 -0
  502. package/templates/workspaces/quinn/AGENTS.md +52 -0
  503. package/templates/workspaces/quinn/FEEDBACK.md +11 -0
  504. package/templates/workspaces/quinn/HEARTBEAT.md +5 -0
  505. package/templates/workspaces/quinn/IDENTITY.md +6 -0
  506. package/templates/workspaces/quinn/LEARNINGS.md +35 -0
  507. package/templates/workspaces/quinn/MEMORY.md +22 -0
  508. package/templates/workspaces/quinn/PLAYBOOK.md +16 -0
  509. package/templates/workspaces/quinn/SOUL.md +1 -1
  510. package/templates/workspaces/quinn/USER.md +17 -0
  511. package/templates/workspaces/quinn/alice-login-page.png +0 -0
  512. package/templates/workspaces/rowan/AGENTS.md +59 -0
  513. package/templates/workspaces/rowan/FEEDBACK.md +12 -0
  514. package/templates/workspaces/rowan/HEARTBEAT.md +5 -0
  515. package/templates/workspaces/rowan/IDENTITY.md +6 -0
  516. package/templates/workspaces/rowan/LEARNINGS.md +12 -0
  517. package/templates/workspaces/rowan/MEMORY.md +22 -0
  518. package/templates/workspaces/rowan/PLAYBOOK.md +16 -0
  519. package/templates/workspaces/rowan/SOUL.md +1 -1
  520. package/templates/workspaces/rowan/USER.md +17 -0
  521. package/templates/workspaces/selena/AGENTS.md +59 -0
  522. package/templates/workspaces/selena/FEEDBACK.md +12 -0
  523. package/templates/workspaces/selena/HEARTBEAT.md +5 -0
  524. package/templates/workspaces/selena/IDENTITY.md +6 -0
  525. package/templates/workspaces/selena/LEARNINGS.md +24 -0
  526. package/templates/workspaces/selena/MEMORY.md +22 -0
  527. package/templates/workspaces/selena/PLAYBOOK.md +16 -0
  528. package/templates/workspaces/selena/SOUL.md +1 -1
  529. package/templates/workspaces/selena/USER.md +17 -0
  530. package/templates/workspaces/selena/kids-ai-security-compliance-plan.md +791 -0
  531. package/templates/workspaces/selena/kidspark-coppa-compliance-audit.md +866 -0
  532. package/templates/workspaces/sloane/AGENTS.md +59 -0
  533. package/templates/workspaces/sloane/FEEDBACK.md +12 -0
  534. package/templates/workspaces/sloane/HEARTBEAT.md +9 -0
  535. package/templates/workspaces/sloane/IDENTITY.md +6 -0
  536. package/templates/workspaces/sloane/LEARNINGS.md +6 -0
  537. package/templates/workspaces/sloane/MEMORY.md +22 -0
  538. package/templates/workspaces/sloane/PLAYBOOK.md +16 -0
  539. package/templates/workspaces/sloane/SOUL.md +1 -1
  540. package/templates/workspaces/sloane/TOOLS.md +15 -0
  541. package/templates/workspaces/sloane/USER.md +13 -0
  542. package/templates/workspaces/smoketestagent/AGENTS.md +52 -0
  543. package/templates/workspaces/smoketestagent/FEEDBACK.md +3 -0
  544. package/templates/workspaces/smoketestagent/HEARTBEAT.md +14 -0
  545. package/templates/workspaces/smoketestagent/IDENTITY.md +6 -0
  546. package/templates/workspaces/smoketestagent/LEARNINGS.md +3 -0
  547. package/templates/workspaces/smoketestagent/MEMORY.md +24 -0
  548. package/templates/workspaces/smoketestagent/PLAYBOOK.md +7 -0
  549. package/templates/workspaces/smoketestagent/SOUL.md +32 -0
  550. package/templates/workspaces/smoketestagent/TOOLS.md +13 -0
  551. package/templates/workspaces/smoketestagent/USER.md +5 -0
  552. package/templates/workspaces/sophie/AGENTS.md +59 -0
  553. package/templates/workspaces/sophie/FEEDBACK.md +12 -0
  554. package/templates/workspaces/sophie/HEARTBEAT.md +9 -0
  555. package/templates/workspaces/sophie/IDENTITY.md +6 -0
  556. package/templates/workspaces/sophie/LEARNINGS.md +6 -0
  557. package/templates/workspaces/sophie/MEMORY.md +22 -0
  558. package/templates/workspaces/sophie/PLAYBOOK.md +16 -0
  559. package/templates/workspaces/sophie/SOUL.md +1 -1
  560. package/templates/workspaces/sophie/TOOLS.md +15 -0
  561. package/templates/workspaces/sophie/USER.md +17 -0
  562. package/templates/workspaces/tommy/AGENTS.md +59 -0
  563. package/templates/workspaces/tommy/FEEDBACK.md +12 -0
  564. package/templates/workspaces/tommy/HEARTBEAT.md +9 -0
  565. package/templates/workspaces/tommy/IDENTITY.md +6 -0
  566. package/templates/workspaces/tommy/LEARNINGS.md +6 -0
  567. package/templates/workspaces/tommy/MEMORY.md +22 -0
  568. package/templates/workspaces/tommy/PLAYBOOK.md +16 -0
  569. package/templates/workspaces/tommy/SOUL.md +1 -1
  570. package/templates/workspaces/tommy/TOOLS.md +15 -0
  571. package/templates/workspaces/tommy/USER.md +17 -0
  572. package/templates/workspaces/uma/AGENTS.md +59 -0
  573. package/templates/workspaces/uma/FEEDBACK.md +11 -0
  574. package/templates/workspaces/uma/HEARTBEAT.md +5 -0
  575. package/templates/workspaces/uma/IDENTITY.md +6 -0
  576. package/templates/workspaces/uma/LEARNINGS.md +11 -0
  577. package/templates/workspaces/uma/MEMORY.md +22 -0
  578. package/templates/workspaces/uma/PLAYBOOK.md +16 -0
  579. package/templates/workspaces/uma/SOUL.md +1 -1
  580. package/templates/workspaces/uma/TOOLS.md +15 -0
  581. package/templates/workspaces/uma/USER.md +13 -0
@@ -0,0 +1,1199 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Database Migration Generator
4
+
5
+ Generates safe migration scripts between schema versions:
6
+ - Compares current and target schemas
7
+ - Generates ALTER TABLE statements for schema changes
8
+ - Implements zero-downtime migration strategies (expand-contract pattern)
9
+ - Creates rollback scripts for all changes
10
+ - Generates validation queries to verify migrations
11
+ - Handles complex changes like table splits/merges
12
+
13
+ Input: Current schema JSON + Target schema JSON
14
+ Output: Migration SQL + Rollback SQL + Validation queries + Execution plan
15
+
16
+ Usage:
17
+ python migration_generator.py --current current_schema.json --target target_schema.json --output migration.sql
18
+ python migration_generator.py --current current.json --target target.json --format json
19
+ python migration_generator.py --current current.json --target target.json --zero-downtime
20
+ python migration_generator.py --current current.json --target target.json --validate-only
21
+ """
22
+
23
+ import argparse
24
+ import json
25
+ import re
26
+ import sys
27
+ from collections import defaultdict, OrderedDict
28
+ from typing import Dict, List, Set, Tuple, Optional, Any, Union
29
+ from dataclasses import dataclass, asdict
30
+ from datetime import datetime
31
+ import hashlib
32
+
33
+
34
+ @dataclass
35
+ class Column:
36
+ name: str
37
+ data_type: str
38
+ nullable: bool = True
39
+ primary_key: bool = False
40
+ unique: bool = False
41
+ foreign_key: Optional[str] = None
42
+ default_value: Optional[str] = None
43
+ check_constraint: Optional[str] = None
44
+
45
+
46
+ @dataclass
47
+ class Table:
48
+ name: str
49
+ columns: Dict[str, Column]
50
+ primary_key: List[str]
51
+ foreign_keys: Dict[str, str] # column -> referenced_table.column
52
+ unique_constraints: List[List[str]]
53
+ check_constraints: Dict[str, str]
54
+ indexes: List[Dict[str, Any]]
55
+
56
+
57
+ @dataclass
58
+ class MigrationStep:
59
+ step_id: str
60
+ step_type: str
61
+ table: str
62
+ description: str
63
+ sql_forward: str
64
+ sql_rollback: str
65
+ validation_sql: Optional[str] = None
66
+ dependencies: List[str] = None
67
+ risk_level: str = "LOW" # LOW, MEDIUM, HIGH
68
+ estimated_time: Optional[str] = None
69
+ zero_downtime_phase: Optional[str] = None # EXPAND, CONTRACT, or None
70
+
71
+
72
+ @dataclass
73
+ class MigrationPlan:
74
+ migration_id: str
75
+ created_at: str
76
+ source_schema_hash: str
77
+ target_schema_hash: str
78
+ steps: List[MigrationStep]
79
+ summary: Dict[str, Any]
80
+ execution_order: List[str]
81
+ rollback_order: List[str]
82
+
83
+
84
+ @dataclass
85
+ class ValidationCheck:
86
+ check_id: str
87
+ check_type: str
88
+ table: str
89
+ description: str
90
+ sql_query: str
91
+ expected_result: Any
92
+ critical: bool = True
93
+
94
+
95
+ class SchemaComparator:
96
+ """Compares two schema versions and identifies differences."""
97
+
98
+ def __init__(self):
99
+ self.current_schema: Dict[str, Table] = {}
100
+ self.target_schema: Dict[str, Table] = {}
101
+ self.changes: Dict[str, List[Dict[str, Any]]] = {
102
+ 'tables_added': [],
103
+ 'tables_dropped': [],
104
+ 'tables_renamed': [],
105
+ 'columns_added': [],
106
+ 'columns_dropped': [],
107
+ 'columns_modified': [],
108
+ 'columns_renamed': [],
109
+ 'constraints_added': [],
110
+ 'constraints_dropped': [],
111
+ 'indexes_added': [],
112
+ 'indexes_dropped': []
113
+ }
114
+
115
+ def load_schemas(self, current_data: Dict[str, Any], target_data: Dict[str, Any]):
116
+ """Load current and target schemas."""
117
+ self.current_schema = self._parse_schema(current_data)
118
+ self.target_schema = self._parse_schema(target_data)
119
+
120
+ def _parse_schema(self, schema_data: Dict[str, Any]) -> Dict[str, Table]:
121
+ """Parse schema JSON into Table objects."""
122
+ tables = {}
123
+
124
+ if 'tables' not in schema_data:
125
+ return tables
126
+
127
+ for table_name, table_def in schema_data['tables'].items():
128
+ columns = {}
129
+ primary_key = table_def.get('primary_key', [])
130
+ foreign_keys = {}
131
+
132
+ # Parse columns
133
+ for col_name, col_def in table_def.get('columns', {}).items():
134
+ column = Column(
135
+ name=col_name,
136
+ data_type=col_def.get('type', 'VARCHAR(255)'),
137
+ nullable=col_def.get('nullable', True),
138
+ primary_key=col_name in primary_key,
139
+ unique=col_def.get('unique', False),
140
+ foreign_key=col_def.get('foreign_key'),
141
+ default_value=col_def.get('default'),
142
+ check_constraint=col_def.get('check_constraint')
143
+ )
144
+ columns[col_name] = column
145
+
146
+ if column.foreign_key:
147
+ foreign_keys[col_name] = column.foreign_key
148
+
149
+ table = Table(
150
+ name=table_name,
151
+ columns=columns,
152
+ primary_key=primary_key,
153
+ foreign_keys=foreign_keys,
154
+ unique_constraints=table_def.get('unique_constraints', []),
155
+ check_constraints=table_def.get('check_constraints', {}),
156
+ indexes=table_def.get('indexes', [])
157
+ )
158
+ tables[table_name] = table
159
+
160
+ return tables
161
+
162
+ def compare_schemas(self) -> Dict[str, List[Dict[str, Any]]]:
163
+ """Compare schemas and identify all changes."""
164
+ self._compare_tables()
165
+ self._compare_columns()
166
+ self._compare_constraints()
167
+ self._compare_indexes()
168
+ return self.changes
169
+
170
+ def _compare_tables(self):
171
+ """Compare table-level changes."""
172
+ current_tables = set(self.current_schema.keys())
173
+ target_tables = set(self.target_schema.keys())
174
+
175
+ # Tables added
176
+ for table_name in target_tables - current_tables:
177
+ self.changes['tables_added'].append({
178
+ 'table': table_name,
179
+ 'definition': self.target_schema[table_name]
180
+ })
181
+
182
+ # Tables dropped
183
+ for table_name in current_tables - target_tables:
184
+ self.changes['tables_dropped'].append({
185
+ 'table': table_name,
186
+ 'definition': self.current_schema[table_name]
187
+ })
188
+
189
+ # Tables renamed (heuristic based on column similarity)
190
+ self._detect_renamed_tables(current_tables - target_tables, target_tables - current_tables)
191
+
192
+ def _detect_renamed_tables(self, dropped_tables: Set[str], added_tables: Set[str]):
193
+ """Detect renamed tables based on column similarity."""
194
+ if not dropped_tables or not added_tables:
195
+ return
196
+
197
+ # Calculate similarity scores
198
+ similarity_scores = []
199
+ for dropped_table in dropped_tables:
200
+ for added_table in added_tables:
201
+ score = self._calculate_table_similarity(dropped_table, added_table)
202
+ if score > 0.7: # High similarity threshold
203
+ similarity_scores.append((score, dropped_table, added_table))
204
+
205
+ # Sort by similarity and identify renames
206
+ similarity_scores.sort(reverse=True)
207
+ used_tables = set()
208
+
209
+ for score, old_name, new_name in similarity_scores:
210
+ if old_name not in used_tables and new_name not in used_tables:
211
+ self.changes['tables_renamed'].append({
212
+ 'old_name': old_name,
213
+ 'new_name': new_name,
214
+ 'similarity_score': score
215
+ })
216
+ used_tables.add(old_name)
217
+ used_tables.add(new_name)
218
+
219
+ # Remove from added/dropped lists
220
+ self.changes['tables_added'] = [t for t in self.changes['tables_added'] if t['table'] != new_name]
221
+ self.changes['tables_dropped'] = [t for t in self.changes['tables_dropped'] if t['table'] != old_name]
222
+
223
+ def _calculate_table_similarity(self, table1_name: str, table2_name: str) -> float:
224
+ """Calculate similarity between two tables based on columns."""
225
+ table1 = self.current_schema[table1_name]
226
+ table2 = self.target_schema[table2_name]
227
+
228
+ cols1 = set(table1.columns.keys())
229
+ cols2 = set(table2.columns.keys())
230
+
231
+ if not cols1 and not cols2:
232
+ return 1.0
233
+ elif not cols1 or not cols2:
234
+ return 0.0
235
+
236
+ intersection = len(cols1.intersection(cols2))
237
+ union = len(cols1.union(cols2))
238
+
239
+ return intersection / union
240
+
241
+ def _compare_columns(self):
242
+ """Compare column-level changes."""
243
+ common_tables = set(self.current_schema.keys()).intersection(set(self.target_schema.keys()))
244
+
245
+ for table_name in common_tables:
246
+ current_table = self.current_schema[table_name]
247
+ target_table = self.target_schema[table_name]
248
+
249
+ current_columns = set(current_table.columns.keys())
250
+ target_columns = set(target_table.columns.keys())
251
+
252
+ # Columns added
253
+ for col_name in target_columns - current_columns:
254
+ self.changes['columns_added'].append({
255
+ 'table': table_name,
256
+ 'column': col_name,
257
+ 'definition': target_table.columns[col_name]
258
+ })
259
+
260
+ # Columns dropped
261
+ for col_name in current_columns - target_columns:
262
+ self.changes['columns_dropped'].append({
263
+ 'table': table_name,
264
+ 'column': col_name,
265
+ 'definition': current_table.columns[col_name]
266
+ })
267
+
268
+ # Columns modified
269
+ for col_name in current_columns.intersection(target_columns):
270
+ current_col = current_table.columns[col_name]
271
+ target_col = target_table.columns[col_name]
272
+
273
+ if self._columns_different(current_col, target_col):
274
+ self.changes['columns_modified'].append({
275
+ 'table': table_name,
276
+ 'column': col_name,
277
+ 'current_definition': current_col,
278
+ 'target_definition': target_col,
279
+ 'changes': self._describe_column_changes(current_col, target_col)
280
+ })
281
+
282
+ def _columns_different(self, col1: Column, col2: Column) -> bool:
283
+ """Check if two columns have different definitions."""
284
+ return (col1.data_type != col2.data_type or
285
+ col1.nullable != col2.nullable or
286
+ col1.default_value != col2.default_value or
287
+ col1.unique != col2.unique or
288
+ col1.foreign_key != col2.foreign_key or
289
+ col1.check_constraint != col2.check_constraint)
290
+
291
+ def _describe_column_changes(self, current_col: Column, target_col: Column) -> List[str]:
292
+ """Describe specific changes between column definitions."""
293
+ changes = []
294
+
295
+ if current_col.data_type != target_col.data_type:
296
+ changes.append(f"type: {current_col.data_type} -> {target_col.data_type}")
297
+
298
+ if current_col.nullable != target_col.nullable:
299
+ changes.append(f"nullable: {current_col.nullable} -> {target_col.nullable}")
300
+
301
+ if current_col.default_value != target_col.default_value:
302
+ changes.append(f"default: {current_col.default_value} -> {target_col.default_value}")
303
+
304
+ if current_col.unique != target_col.unique:
305
+ changes.append(f"unique: {current_col.unique} -> {target_col.unique}")
306
+
307
+ if current_col.foreign_key != target_col.foreign_key:
308
+ changes.append(f"foreign_key: {current_col.foreign_key} -> {target_col.foreign_key}")
309
+
310
+ return changes
311
+
312
+ def _compare_constraints(self):
313
+ """Compare constraint changes."""
314
+ common_tables = set(self.current_schema.keys()).intersection(set(self.target_schema.keys()))
315
+
316
+ for table_name in common_tables:
317
+ current_table = self.current_schema[table_name]
318
+ target_table = self.target_schema[table_name]
319
+
320
+ # Compare primary keys
321
+ if current_table.primary_key != target_table.primary_key:
322
+ if current_table.primary_key:
323
+ self.changes['constraints_dropped'].append({
324
+ 'table': table_name,
325
+ 'constraint_type': 'PRIMARY_KEY',
326
+ 'columns': current_table.primary_key
327
+ })
328
+
329
+ if target_table.primary_key:
330
+ self.changes['constraints_added'].append({
331
+ 'table': table_name,
332
+ 'constraint_type': 'PRIMARY_KEY',
333
+ 'columns': target_table.primary_key
334
+ })
335
+
336
+ # Compare unique constraints
337
+ current_unique = set(tuple(uc) for uc in current_table.unique_constraints)
338
+ target_unique = set(tuple(uc) for uc in target_table.unique_constraints)
339
+
340
+ for constraint in target_unique - current_unique:
341
+ self.changes['constraints_added'].append({
342
+ 'table': table_name,
343
+ 'constraint_type': 'UNIQUE',
344
+ 'columns': list(constraint)
345
+ })
346
+
347
+ for constraint in current_unique - target_unique:
348
+ self.changes['constraints_dropped'].append({
349
+ 'table': table_name,
350
+ 'constraint_type': 'UNIQUE',
351
+ 'columns': list(constraint)
352
+ })
353
+
354
+ # Compare check constraints
355
+ current_checks = set(current_table.check_constraints.items())
356
+ target_checks = set(target_table.check_constraints.items())
357
+
358
+ for name, condition in target_checks - current_checks:
359
+ self.changes['constraints_added'].append({
360
+ 'table': table_name,
361
+ 'constraint_type': 'CHECK',
362
+ 'constraint_name': name,
363
+ 'condition': condition
364
+ })
365
+
366
+ for name, condition in current_checks - target_checks:
367
+ self.changes['constraints_dropped'].append({
368
+ 'table': table_name,
369
+ 'constraint_type': 'CHECK',
370
+ 'constraint_name': name,
371
+ 'condition': condition
372
+ })
373
+
374
+ def _compare_indexes(self):
375
+ """Compare index changes."""
376
+ common_tables = set(self.current_schema.keys()).intersection(set(self.target_schema.keys()))
377
+
378
+ for table_name in common_tables:
379
+ current_indexes = {idx['name']: idx for idx in self.current_schema[table_name].indexes}
380
+ target_indexes = {idx['name']: idx for idx in self.target_schema[table_name].indexes}
381
+
382
+ current_names = set(current_indexes.keys())
383
+ target_names = set(target_indexes.keys())
384
+
385
+ # Indexes added
386
+ for idx_name in target_names - current_names:
387
+ self.changes['indexes_added'].append({
388
+ 'table': table_name,
389
+ 'index': target_indexes[idx_name]
390
+ })
391
+
392
+ # Indexes dropped
393
+ for idx_name in current_names - target_names:
394
+ self.changes['indexes_dropped'].append({
395
+ 'table': table_name,
396
+ 'index': current_indexes[idx_name]
397
+ })
398
+
399
+
400
+ class MigrationGenerator:
401
+ """Generates migration steps from schema differences."""
402
+
403
+ def __init__(self, zero_downtime: bool = False):
404
+ self.zero_downtime = zero_downtime
405
+ self.migration_steps: List[MigrationStep] = []
406
+ self.step_counter = 0
407
+
408
+ # Data type conversion safety
409
+ self.safe_type_conversions = {
410
+ ('VARCHAR(50)', 'VARCHAR(100)'): True, # Expanding varchar
411
+ ('INT', 'BIGINT'): True, # Expanding integer
412
+ ('DECIMAL(10,2)', 'DECIMAL(12,2)'): True, # Expanding decimal precision
413
+ }
414
+
415
+ self.risky_type_conversions = {
416
+ ('VARCHAR(100)', 'VARCHAR(50)'): 'Data truncation possible',
417
+ ('BIGINT', 'INT'): 'Data loss possible for large values',
418
+ ('TEXT', 'VARCHAR(255)'): 'Data truncation possible'
419
+ }
420
+
421
+ def generate_migration(self, changes: Dict[str, List[Dict[str, Any]]]) -> MigrationPlan:
422
+ """Generate complete migration plan from schema changes."""
423
+ self.migration_steps = []
424
+ self.step_counter = 0
425
+
426
+ # Generate steps in dependency order
427
+ self._generate_table_creation_steps(changes['tables_added'])
428
+ self._generate_column_addition_steps(changes['columns_added'])
429
+ self._generate_constraint_addition_steps(changes['constraints_added'])
430
+ self._generate_index_addition_steps(changes['indexes_added'])
431
+ self._generate_column_modification_steps(changes['columns_modified'])
432
+ self._generate_table_rename_steps(changes['tables_renamed'])
433
+ self._generate_index_removal_steps(changes['indexes_dropped'])
434
+ self._generate_constraint_removal_steps(changes['constraints_dropped'])
435
+ self._generate_column_removal_steps(changes['columns_dropped'])
436
+ self._generate_table_removal_steps(changes['tables_dropped'])
437
+
438
+ # Create migration plan
439
+ migration_id = self._generate_migration_id(changes)
440
+ execution_order = [step.step_id for step in self.migration_steps]
441
+ rollback_order = list(reversed(execution_order))
442
+
443
+ return MigrationPlan(
444
+ migration_id=migration_id,
445
+ created_at=datetime.now().isoformat(),
446
+ source_schema_hash=self._calculate_changes_hash(changes),
447
+ target_schema_hash="", # Would be calculated from target schema
448
+ steps=self.migration_steps,
449
+ summary=self._generate_summary(changes),
450
+ execution_order=execution_order,
451
+ rollback_order=rollback_order
452
+ )
453
+
454
+ def _generate_step_id(self) -> str:
455
+ """Generate unique step ID."""
456
+ self.step_counter += 1
457
+ return f"step_{self.step_counter:03d}"
458
+
459
+ def _generate_table_creation_steps(self, tables_added: List[Dict[str, Any]]):
460
+ """Generate steps for creating new tables."""
461
+ for table_info in tables_added:
462
+ table = table_info['definition']
463
+ step = self._create_table_step(table)
464
+ self.migration_steps.append(step)
465
+
466
+ def _create_table_step(self, table: Table) -> MigrationStep:
467
+ """Create migration step for table creation."""
468
+ columns_sql = []
469
+
470
+ for col_name, column in table.columns.items():
471
+ col_sql = f"{col_name} {column.data_type}"
472
+
473
+ if not column.nullable:
474
+ col_sql += " NOT NULL"
475
+
476
+ if column.default_value:
477
+ col_sql += f" DEFAULT {column.default_value}"
478
+
479
+ if column.unique:
480
+ col_sql += " UNIQUE"
481
+
482
+ columns_sql.append(col_sql)
483
+
484
+ # Add primary key
485
+ if table.primary_key:
486
+ pk_sql = f"PRIMARY KEY ({', '.join(table.primary_key)})"
487
+ columns_sql.append(pk_sql)
488
+
489
+ # Add foreign keys
490
+ for col_name, ref in table.foreign_keys.items():
491
+ fk_sql = f"FOREIGN KEY ({col_name}) REFERENCES {ref}"
492
+ columns_sql.append(fk_sql)
493
+
494
+ create_sql = f"CREATE TABLE {table.name} (\n " + ",\n ".join(columns_sql) + "\n);"
495
+ drop_sql = f"DROP TABLE IF EXISTS {table.name};"
496
+
497
+ return MigrationStep(
498
+ step_id=self._generate_step_id(),
499
+ step_type="CREATE_TABLE",
500
+ table=table.name,
501
+ description=f"Create table {table.name} with {len(table.columns)} columns",
502
+ sql_forward=create_sql,
503
+ sql_rollback=drop_sql,
504
+ validation_sql=f"SELECT COUNT(*) FROM information_schema.tables WHERE table_name = '{table.name}';",
505
+ risk_level="LOW"
506
+ )
507
+
508
+ def _generate_column_addition_steps(self, columns_added: List[Dict[str, Any]]):
509
+ """Generate steps for adding columns."""
510
+ for col_info in columns_added:
511
+ if self.zero_downtime:
512
+ # For zero-downtime, add columns as nullable first
513
+ step = self._add_column_zero_downtime_step(col_info)
514
+ else:
515
+ step = self._add_column_step(col_info)
516
+ self.migration_steps.append(step)
517
+
518
+ def _add_column_step(self, col_info: Dict[str, Any]) -> MigrationStep:
519
+ """Create step for adding a column."""
520
+ table = col_info['table']
521
+ column = col_info['definition']
522
+
523
+ col_sql = f"{column.name} {column.data_type}"
524
+
525
+ if not column.nullable:
526
+ if column.default_value:
527
+ col_sql += f" DEFAULT {column.default_value} NOT NULL"
528
+ else:
529
+ # This is risky - adding NOT NULL without default
530
+ col_sql += " NOT NULL"
531
+ elif column.default_value:
532
+ col_sql += f" DEFAULT {column.default_value}"
533
+
534
+ add_sql = f"ALTER TABLE {table} ADD COLUMN {col_sql};"
535
+ drop_sql = f"ALTER TABLE {table} DROP COLUMN {column.name};"
536
+
537
+ risk_level = "HIGH" if not column.nullable and not column.default_value else "LOW"
538
+
539
+ return MigrationStep(
540
+ step_id=self._generate_step_id(),
541
+ step_type="ADD_COLUMN",
542
+ table=table,
543
+ description=f"Add column {column.name} to {table}",
544
+ sql_forward=add_sql,
545
+ sql_rollback=drop_sql,
546
+ validation_sql=f"SELECT COUNT(*) FROM information_schema.columns WHERE table_name = '{table}' AND column_name = '{column.name}';",
547
+ risk_level=risk_level
548
+ )
549
+
550
+ def _add_column_zero_downtime_step(self, col_info: Dict[str, Any]) -> MigrationStep:
551
+ """Create zero-downtime step for adding column."""
552
+ table = col_info['table']
553
+ column = col_info['definition']
554
+
555
+ # Phase 1: Add as nullable with default if needed
556
+ col_sql = f"{column.name} {column.data_type}"
557
+ if column.default_value:
558
+ col_sql += f" DEFAULT {column.default_value}"
559
+
560
+ add_sql = f"ALTER TABLE {table} ADD COLUMN {col_sql};"
561
+
562
+ # If column should be NOT NULL, handle in separate phase
563
+ if not column.nullable:
564
+ # Add comment about needing follow-up step
565
+ add_sql += f"\n-- Follow-up needed: Add NOT NULL constraint after data population"
566
+
567
+ drop_sql = f"ALTER TABLE {table} DROP COLUMN {column.name};"
568
+
569
+ return MigrationStep(
570
+ step_id=self._generate_step_id(),
571
+ step_type="ADD_COLUMN_ZD",
572
+ table=table,
573
+ description=f"Add column {column.name} to {table} (zero-downtime phase 1)",
574
+ sql_forward=add_sql,
575
+ sql_rollback=drop_sql,
576
+ validation_sql=f"SELECT COUNT(*) FROM information_schema.columns WHERE table_name = '{table}' AND column_name = '{column.name}';",
577
+ risk_level="LOW",
578
+ zero_downtime_phase="EXPAND"
579
+ )
580
+
581
+ def _generate_column_modification_steps(self, columns_modified: List[Dict[str, Any]]):
582
+ """Generate steps for modifying columns."""
583
+ for col_info in columns_modified:
584
+ if self.zero_downtime:
585
+ steps = self._modify_column_zero_downtime_steps(col_info)
586
+ self.migration_steps.extend(steps)
587
+ else:
588
+ step = self._modify_column_step(col_info)
589
+ self.migration_steps.append(step)
590
+
591
+ def _modify_column_step(self, col_info: Dict[str, Any]) -> MigrationStep:
592
+ """Create step for modifying a column."""
593
+ table = col_info['table']
594
+ column = col_info['column']
595
+ current_def = col_info['current_definition']
596
+ target_def = col_info['target_definition']
597
+ changes = col_info['changes']
598
+
599
+ alter_statements = []
600
+ rollback_statements = []
601
+
602
+ # Handle different types of changes
603
+ if current_def.data_type != target_def.data_type:
604
+ alter_statements.append(f"ALTER COLUMN {column} TYPE {target_def.data_type}")
605
+ rollback_statements.append(f"ALTER COLUMN {column} TYPE {current_def.data_type}")
606
+
607
+ if current_def.nullable != target_def.nullable:
608
+ if target_def.nullable:
609
+ alter_statements.append(f"ALTER COLUMN {column} DROP NOT NULL")
610
+ rollback_statements.append(f"ALTER COLUMN {column} SET NOT NULL")
611
+ else:
612
+ alter_statements.append(f"ALTER COLUMN {column} SET NOT NULL")
613
+ rollback_statements.append(f"ALTER COLUMN {column} DROP NOT NULL")
614
+
615
+ if current_def.default_value != target_def.default_value:
616
+ if target_def.default_value:
617
+ alter_statements.append(f"ALTER COLUMN {column} SET DEFAULT {target_def.default_value}")
618
+ else:
619
+ alter_statements.append(f"ALTER COLUMN {column} DROP DEFAULT")
620
+
621
+ if current_def.default_value:
622
+ rollback_statements.append(f"ALTER COLUMN {column} SET DEFAULT {current_def.default_value}")
623
+ else:
624
+ rollback_statements.append(f"ALTER COLUMN {column} DROP DEFAULT")
625
+
626
+ # Build SQL
627
+ alter_sql = f"ALTER TABLE {table}\n " + ",\n ".join(alter_statements) + ";"
628
+ rollback_sql = f"ALTER TABLE {table}\n " + ",\n ".join(rollback_statements) + ";"
629
+
630
+ # Assess risk
631
+ risk_level = self._assess_column_modification_risk(current_def, target_def)
632
+
633
+ return MigrationStep(
634
+ step_id=self._generate_step_id(),
635
+ step_type="MODIFY_COLUMN",
636
+ table=table,
637
+ description=f"Modify column {column}: {', '.join(changes)}",
638
+ sql_forward=alter_sql,
639
+ sql_rollback=rollback_sql,
640
+ validation_sql=f"SELECT data_type, is_nullable FROM information_schema.columns WHERE table_name = '{table}' AND column_name = '{column}';",
641
+ risk_level=risk_level
642
+ )
643
+
644
+ def _modify_column_zero_downtime_steps(self, col_info: Dict[str, Any]) -> List[MigrationStep]:
645
+ """Create zero-downtime steps for column modification."""
646
+ table = col_info['table']
647
+ column = col_info['column']
648
+ current_def = col_info['current_definition']
649
+ target_def = col_info['target_definition']
650
+
651
+ steps = []
652
+
653
+ # For zero-downtime, use expand-contract pattern
654
+ temp_column = f"{column}_new"
655
+
656
+ # Step 1: Add new column
657
+ step1 = MigrationStep(
658
+ step_id=self._generate_step_id(),
659
+ step_type="ADD_TEMP_COLUMN",
660
+ table=table,
661
+ description=f"Add temporary column {temp_column} for zero-downtime migration",
662
+ sql_forward=f"ALTER TABLE {table} ADD COLUMN {temp_column} {target_def.data_type};",
663
+ sql_rollback=f"ALTER TABLE {table} DROP COLUMN {temp_column};",
664
+ zero_downtime_phase="EXPAND"
665
+ )
666
+ steps.append(step1)
667
+
668
+ # Step 2: Copy data
669
+ step2 = MigrationStep(
670
+ step_id=self._generate_step_id(),
671
+ step_type="COPY_COLUMN_DATA",
672
+ table=table,
673
+ description=f"Copy data from {column} to {temp_column}",
674
+ sql_forward=f"UPDATE {table} SET {temp_column} = {column};",
675
+ sql_rollback=f"UPDATE {table} SET {temp_column} = NULL;",
676
+ zero_downtime_phase="EXPAND"
677
+ )
678
+ steps.append(step2)
679
+
680
+ # Step 3: Drop old column
681
+ step3 = MigrationStep(
682
+ step_id=self._generate_step_id(),
683
+ step_type="DROP_OLD_COLUMN",
684
+ table=table,
685
+ description=f"Drop original column {column}",
686
+ sql_forward=f"ALTER TABLE {table} DROP COLUMN {column};",
687
+ sql_rollback=f"ALTER TABLE {table} ADD COLUMN {column} {current_def.data_type};",
688
+ zero_downtime_phase="CONTRACT"
689
+ )
690
+ steps.append(step3)
691
+
692
+ # Step 4: Rename new column
693
+ step4 = MigrationStep(
694
+ step_id=self._generate_step_id(),
695
+ step_type="RENAME_COLUMN",
696
+ table=table,
697
+ description=f"Rename {temp_column} to {column}",
698
+ sql_forward=f"ALTER TABLE {table} RENAME COLUMN {temp_column} TO {column};",
699
+ sql_rollback=f"ALTER TABLE {table} RENAME COLUMN {column} TO {temp_column};",
700
+ zero_downtime_phase="CONTRACT"
701
+ )
702
+ steps.append(step4)
703
+
704
+ return steps
705
+
706
+ def _assess_column_modification_risk(self, current: Column, target: Column) -> str:
707
+ """Assess risk level of column modification."""
708
+ if current.data_type != target.data_type:
709
+ conversion_key = (current.data_type, target.data_type)
710
+ if conversion_key in self.risky_type_conversions:
711
+ return "HIGH"
712
+ elif conversion_key not in self.safe_type_conversions:
713
+ return "MEDIUM"
714
+
715
+ if current.nullable and not target.nullable:
716
+ return "HIGH" # Adding NOT NULL constraint
717
+
718
+ return "LOW"
719
+
720
+ def _generate_constraint_addition_steps(self, constraints_added: List[Dict[str, Any]]):
721
+ """Generate steps for adding constraints."""
722
+ for constraint_info in constraints_added:
723
+ step = self._add_constraint_step(constraint_info)
724
+ self.migration_steps.append(step)
725
+
726
+ def _add_constraint_step(self, constraint_info: Dict[str, Any]) -> MigrationStep:
727
+ """Create step for adding constraint."""
728
+ table = constraint_info['table']
729
+ constraint_type = constraint_info['constraint_type']
730
+
731
+ if constraint_type == 'PRIMARY_KEY':
732
+ columns = constraint_info['columns']
733
+ constraint_name = f"pk_{table}"
734
+ add_sql = f"ALTER TABLE {table} ADD CONSTRAINT {constraint_name} PRIMARY KEY ({', '.join(columns)});"
735
+ drop_sql = f"ALTER TABLE {table} DROP CONSTRAINT {constraint_name};"
736
+ description = f"Add primary key on {', '.join(columns)}"
737
+
738
+ elif constraint_type == 'UNIQUE':
739
+ columns = constraint_info['columns']
740
+ constraint_name = f"uq_{table}_{'_'.join(columns)}"
741
+ add_sql = f"ALTER TABLE {table} ADD CONSTRAINT {constraint_name} UNIQUE ({', '.join(columns)});"
742
+ drop_sql = f"ALTER TABLE {table} DROP CONSTRAINT {constraint_name};"
743
+ description = f"Add unique constraint on {', '.join(columns)}"
744
+
745
+ elif constraint_type == 'CHECK':
746
+ constraint_name = constraint_info['constraint_name']
747
+ condition = constraint_info['condition']
748
+ add_sql = f"ALTER TABLE {table} ADD CONSTRAINT {constraint_name} CHECK ({condition});"
749
+ drop_sql = f"ALTER TABLE {table} DROP CONSTRAINT {constraint_name};"
750
+ description = f"Add check constraint: {condition}"
751
+
752
+ else:
753
+ return None
754
+
755
+ return MigrationStep(
756
+ step_id=self._generate_step_id(),
757
+ step_type="ADD_CONSTRAINT",
758
+ table=table,
759
+ description=description,
760
+ sql_forward=add_sql,
761
+ sql_rollback=drop_sql,
762
+ risk_level="MEDIUM" # Constraints can fail if data doesn't comply
763
+ )
764
+
765
+ def _generate_index_addition_steps(self, indexes_added: List[Dict[str, Any]]):
766
+ """Generate steps for adding indexes."""
767
+ for index_info in indexes_added:
768
+ step = self._add_index_step(index_info)
769
+ self.migration_steps.append(step)
770
+
771
+ def _add_index_step(self, index_info: Dict[str, Any]) -> MigrationStep:
772
+ """Create step for adding index."""
773
+ table = index_info['table']
774
+ index = index_info['index']
775
+
776
+ unique_keyword = "UNIQUE " if index.get('unique', False) else ""
777
+ columns_sql = ', '.join(index['columns'])
778
+
779
+ create_sql = f"CREATE {unique_keyword}INDEX {index['name']} ON {table} ({columns_sql});"
780
+ drop_sql = f"DROP INDEX {index['name']};"
781
+
782
+ return MigrationStep(
783
+ step_id=self._generate_step_id(),
784
+ step_type="ADD_INDEX",
785
+ table=table,
786
+ description=f"Create index {index['name']} on ({columns_sql})",
787
+ sql_forward=create_sql,
788
+ sql_rollback=drop_sql,
789
+ estimated_time="1-5 minutes depending on table size",
790
+ risk_level="LOW"
791
+ )
792
+
793
+ def _generate_table_rename_steps(self, tables_renamed: List[Dict[str, Any]]):
794
+ """Generate steps for renaming tables."""
795
+ for rename_info in tables_renamed:
796
+ step = self._rename_table_step(rename_info)
797
+ self.migration_steps.append(step)
798
+
799
+ def _rename_table_step(self, rename_info: Dict[str, Any]) -> MigrationStep:
800
+ """Create step for renaming table."""
801
+ old_name = rename_info['old_name']
802
+ new_name = rename_info['new_name']
803
+
804
+ rename_sql = f"ALTER TABLE {old_name} RENAME TO {new_name};"
805
+ rollback_sql = f"ALTER TABLE {new_name} RENAME TO {old_name};"
806
+
807
+ return MigrationStep(
808
+ step_id=self._generate_step_id(),
809
+ step_type="RENAME_TABLE",
810
+ table=old_name,
811
+ description=f"Rename table {old_name} to {new_name}",
812
+ sql_forward=rename_sql,
813
+ sql_rollback=rollback_sql,
814
+ validation_sql=f"SELECT COUNT(*) FROM information_schema.tables WHERE table_name = '{new_name}';",
815
+ risk_level="LOW"
816
+ )
817
+
818
+ def _generate_column_removal_steps(self, columns_dropped: List[Dict[str, Any]]):
819
+ """Generate steps for removing columns."""
820
+ for col_info in columns_dropped:
821
+ step = self._drop_column_step(col_info)
822
+ self.migration_steps.append(step)
823
+
824
+ def _drop_column_step(self, col_info: Dict[str, Any]) -> MigrationStep:
825
+ """Create step for dropping column."""
826
+ table = col_info['table']
827
+ column = col_info['definition']
828
+
829
+ drop_sql = f"ALTER TABLE {table} DROP COLUMN {column.name};"
830
+
831
+ # Recreate column for rollback
832
+ col_sql = f"{column.name} {column.data_type}"
833
+ if not column.nullable:
834
+ col_sql += " NOT NULL"
835
+ if column.default_value:
836
+ col_sql += f" DEFAULT {column.default_value}"
837
+
838
+ add_sql = f"ALTER TABLE {table} ADD COLUMN {col_sql};"
839
+
840
+ return MigrationStep(
841
+ step_id=self._generate_step_id(),
842
+ step_type="DROP_COLUMN",
843
+ table=table,
844
+ description=f"Drop column {column.name} from {table}",
845
+ sql_forward=drop_sql,
846
+ sql_rollback=add_sql,
847
+ risk_level="HIGH" # Data loss risk
848
+ )
849
+
850
+ def _generate_constraint_removal_steps(self, constraints_dropped: List[Dict[str, Any]]):
851
+ """Generate steps for removing constraints."""
852
+ for constraint_info in constraints_dropped:
853
+ step = self._drop_constraint_step(constraint_info)
854
+ if step:
855
+ self.migration_steps.append(step)
856
+
857
+ def _drop_constraint_step(self, constraint_info: Dict[str, Any]) -> Optional[MigrationStep]:
858
+ """Create step for dropping constraint."""
859
+ table = constraint_info['table']
860
+ constraint_type = constraint_info['constraint_type']
861
+
862
+ if constraint_type == 'PRIMARY_KEY':
863
+ constraint_name = f"pk_{table}"
864
+ drop_sql = f"ALTER TABLE {table} DROP CONSTRAINT {constraint_name};"
865
+ columns = constraint_info['columns']
866
+ add_sql = f"ALTER TABLE {table} ADD CONSTRAINT {constraint_name} PRIMARY KEY ({', '.join(columns)});"
867
+ description = f"Drop primary key constraint"
868
+
869
+ elif constraint_type == 'UNIQUE':
870
+ columns = constraint_info['columns']
871
+ constraint_name = f"uq_{table}_{'_'.join(columns)}"
872
+ drop_sql = f"ALTER TABLE {table} DROP CONSTRAINT {constraint_name};"
873
+ add_sql = f"ALTER TABLE {table} ADD CONSTRAINT {constraint_name} UNIQUE ({', '.join(columns)});"
874
+ description = f"Drop unique constraint on {', '.join(columns)}"
875
+
876
+ elif constraint_type == 'CHECK':
877
+ constraint_name = constraint_info['constraint_name']
878
+ condition = constraint_info.get('condition', '')
879
+ drop_sql = f"ALTER TABLE {table} DROP CONSTRAINT {constraint_name};"
880
+ add_sql = f"ALTER TABLE {table} ADD CONSTRAINT {constraint_name} CHECK ({condition});"
881
+ description = f"Drop check constraint {constraint_name}"
882
+
883
+ else:
884
+ return None
885
+
886
+ return MigrationStep(
887
+ step_id=self._generate_step_id(),
888
+ step_type="DROP_CONSTRAINT",
889
+ table=table,
890
+ description=description,
891
+ sql_forward=drop_sql,
892
+ sql_rollback=add_sql,
893
+ risk_level="MEDIUM"
894
+ )
895
+
896
+ def _generate_index_removal_steps(self, indexes_dropped: List[Dict[str, Any]]):
897
+ """Generate steps for removing indexes."""
898
+ for index_info in indexes_dropped:
899
+ step = self._drop_index_step(index_info)
900
+ self.migration_steps.append(step)
901
+
902
+ def _drop_index_step(self, index_info: Dict[str, Any]) -> MigrationStep:
903
+ """Create step for dropping index."""
904
+ table = index_info['table']
905
+ index = index_info['index']
906
+
907
+ drop_sql = f"DROP INDEX {index['name']};"
908
+
909
+ # Recreate for rollback
910
+ unique_keyword = "UNIQUE " if index.get('unique', False) else ""
911
+ columns_sql = ', '.join(index['columns'])
912
+ create_sql = f"CREATE {unique_keyword}INDEX {index['name']} ON {table} ({columns_sql});"
913
+
914
+ return MigrationStep(
915
+ step_id=self._generate_step_id(),
916
+ step_type="DROP_INDEX",
917
+ table=table,
918
+ description=f"Drop index {index['name']}",
919
+ sql_forward=drop_sql,
920
+ sql_rollback=create_sql,
921
+ risk_level="LOW"
922
+ )
923
+
924
+ def _generate_table_removal_steps(self, tables_dropped: List[Dict[str, Any]]):
925
+ """Generate steps for removing tables."""
926
+ for table_info in tables_dropped:
927
+ step = self._drop_table_step(table_info)
928
+ self.migration_steps.append(step)
929
+
930
+ def _drop_table_step(self, table_info: Dict[str, Any]) -> MigrationStep:
931
+ """Create step for dropping table."""
932
+ table = table_info['definition']
933
+
934
+ drop_sql = f"DROP TABLE {table.name};"
935
+
936
+ # Would need to recreate entire table for rollback
937
+ # This is simplified - full implementation would generate CREATE TABLE statement
938
+ create_sql = f"-- Recreate table {table.name} (implementation needed)"
939
+
940
+ return MigrationStep(
941
+ step_id=self._generate_step_id(),
942
+ step_type="DROP_TABLE",
943
+ table=table.name,
944
+ description=f"Drop table {table.name}",
945
+ sql_forward=drop_sql,
946
+ sql_rollback=create_sql,
947
+ risk_level="HIGH" # Data loss risk
948
+ )
949
+
950
+ def _generate_migration_id(self, changes: Dict[str, List[Dict[str, Any]]]) -> str:
951
+ """Generate unique migration ID."""
952
+ content = json.dumps(changes, sort_keys=True)
953
+ return hashlib.md5(content.encode()).hexdigest()[:8]
954
+
955
+ def _calculate_changes_hash(self, changes: Dict[str, List[Dict[str, Any]]]) -> str:
956
+ """Calculate hash of changes for versioning."""
957
+ content = json.dumps(changes, sort_keys=True)
958
+ return hashlib.md5(content.encode()).hexdigest()
959
+
960
+ def _generate_summary(self, changes: Dict[str, List[Dict[str, Any]]]) -> Dict[str, Any]:
961
+ """Generate migration summary."""
962
+ summary = {
963
+ "total_steps": len(self.migration_steps),
964
+ "changes_summary": {
965
+ "tables_added": len(changes['tables_added']),
966
+ "tables_dropped": len(changes['tables_dropped']),
967
+ "tables_renamed": len(changes['tables_renamed']),
968
+ "columns_added": len(changes['columns_added']),
969
+ "columns_dropped": len(changes['columns_dropped']),
970
+ "columns_modified": len(changes['columns_modified']),
971
+ "constraints_added": len(changes['constraints_added']),
972
+ "constraints_dropped": len(changes['constraints_dropped']),
973
+ "indexes_added": len(changes['indexes_added']),
974
+ "indexes_dropped": len(changes['indexes_dropped'])
975
+ },
976
+ "risk_assessment": {
977
+ "high_risk_steps": len([s for s in self.migration_steps if s.risk_level == "HIGH"]),
978
+ "medium_risk_steps": len([s for s in self.migration_steps if s.risk_level == "MEDIUM"]),
979
+ "low_risk_steps": len([s for s in self.migration_steps if s.risk_level == "LOW"])
980
+ },
981
+ "zero_downtime": self.zero_downtime
982
+ }
983
+
984
+ return summary
985
+
986
+
987
+ class ValidationGenerator:
988
+ """Generates validation queries for migration verification."""
989
+
990
+ def generate_validations(self, migration_plan: MigrationPlan) -> List[ValidationCheck]:
991
+ """Generate validation checks for migration plan."""
992
+ validations = []
993
+
994
+ for step in migration_plan.steps:
995
+ if step.step_type == "CREATE_TABLE":
996
+ validations.append(self._create_table_validation(step))
997
+ elif step.step_type == "ADD_COLUMN":
998
+ validations.append(self._add_column_validation(step))
999
+ elif step.step_type == "MODIFY_COLUMN":
1000
+ validations.append(self._modify_column_validation(step))
1001
+ elif step.step_type == "ADD_INDEX":
1002
+ validations.append(self._add_index_validation(step))
1003
+
1004
+ return validations
1005
+
1006
+ def _create_table_validation(self, step: MigrationStep) -> ValidationCheck:
1007
+ """Create validation for table creation."""
1008
+ return ValidationCheck(
1009
+ check_id=f"validate_{step.step_id}",
1010
+ check_type="TABLE_EXISTS",
1011
+ table=step.table,
1012
+ description=f"Verify table {step.table} exists",
1013
+ sql_query=f"SELECT COUNT(*) FROM information_schema.tables WHERE table_name = '{step.table}';",
1014
+ expected_result=1
1015
+ )
1016
+
1017
+ def _add_column_validation(self, step: MigrationStep) -> ValidationCheck:
1018
+ """Create validation for column addition."""
1019
+ # Extract column name from SQL
1020
+ column_match = re.search(r'ADD COLUMN (\w+)', step.sql_forward)
1021
+ column_name = column_match.group(1) if column_match else "unknown"
1022
+
1023
+ return ValidationCheck(
1024
+ check_id=f"validate_{step.step_id}",
1025
+ check_type="COLUMN_EXISTS",
1026
+ table=step.table,
1027
+ description=f"Verify column {column_name} exists in {step.table}",
1028
+ sql_query=f"SELECT COUNT(*) FROM information_schema.columns WHERE table_name = '{step.table}' AND column_name = '{column_name}';",
1029
+ expected_result=1
1030
+ )
1031
+
1032
+ def _modify_column_validation(self, step: MigrationStep) -> ValidationCheck:
1033
+ """Create validation for column modification."""
1034
+ return ValidationCheck(
1035
+ check_id=f"validate_{step.step_id}",
1036
+ check_type="COLUMN_MODIFIED",
1037
+ table=step.table,
1038
+ description=f"Verify column modification in {step.table}",
1039
+ sql_query=step.validation_sql or f"SELECT 1;", # Use provided validation or default
1040
+ expected_result=1
1041
+ )
1042
+
1043
+ def _add_index_validation(self, step: MigrationStep) -> ValidationCheck:
1044
+ """Create validation for index addition."""
1045
+ # Extract index name from SQL
1046
+ index_match = re.search(r'INDEX (\w+)', step.sql_forward)
1047
+ index_name = index_match.group(1) if index_match else "unknown"
1048
+
1049
+ return ValidationCheck(
1050
+ check_id=f"validate_{step.step_id}",
1051
+ check_type="INDEX_EXISTS",
1052
+ table=step.table,
1053
+ description=f"Verify index {index_name} exists",
1054
+ sql_query=f"SELECT COUNT(*) FROM information_schema.statistics WHERE index_name = '{index_name}';",
1055
+ expected_result=1
1056
+ )
1057
+
1058
+
1059
+ def format_migration_plan_text(plan: MigrationPlan, validations: List[ValidationCheck] = None) -> str:
1060
+ """Format migration plan as human-readable text."""
1061
+ lines = []
1062
+ lines.append("DATABASE MIGRATION PLAN")
1063
+ lines.append("=" * 50)
1064
+ lines.append(f"Migration ID: {plan.migration_id}")
1065
+ lines.append(f"Created: {plan.created_at}")
1066
+ lines.append(f"Zero Downtime: {plan.summary['zero_downtime']}")
1067
+ lines.append("")
1068
+
1069
+ # Summary
1070
+ summary = plan.summary
1071
+ lines.append("MIGRATION SUMMARY")
1072
+ lines.append("-" * 17)
1073
+ lines.append(f"Total Steps: {summary['total_steps']}")
1074
+
1075
+ changes = summary['changes_summary']
1076
+ for change_type, count in changes.items():
1077
+ if count > 0:
1078
+ lines.append(f"{change_type.replace('_', ' ').title()}: {count}")
1079
+ lines.append("")
1080
+
1081
+ # Risk Assessment
1082
+ risk = summary['risk_assessment']
1083
+ lines.append("RISK ASSESSMENT")
1084
+ lines.append("-" * 15)
1085
+ lines.append(f"High Risk Steps: {risk['high_risk_steps']}")
1086
+ lines.append(f"Medium Risk Steps: {risk['medium_risk_steps']}")
1087
+ lines.append(f"Low Risk Steps: {risk['low_risk_steps']}")
1088
+ lines.append("")
1089
+
1090
+ # Migration Steps
1091
+ lines.append("MIGRATION STEPS")
1092
+ lines.append("-" * 15)
1093
+ for i, step in enumerate(plan.steps, 1):
1094
+ lines.append(f"{i}. {step.description} ({step.risk_level} risk)")
1095
+ lines.append(f" Type: {step.step_type}")
1096
+ if step.zero_downtime_phase:
1097
+ lines.append(f" Phase: {step.zero_downtime_phase}")
1098
+ lines.append(f" Forward SQL: {step.sql_forward}")
1099
+ lines.append(f" Rollback SQL: {step.sql_rollback}")
1100
+ if step.estimated_time:
1101
+ lines.append(f" Estimated Time: {step.estimated_time}")
1102
+ lines.append("")
1103
+
1104
+ # Validation Checks
1105
+ if validations:
1106
+ lines.append("VALIDATION CHECKS")
1107
+ lines.append("-" * 17)
1108
+ for validation in validations:
1109
+ lines.append(f"• {validation.description}")
1110
+ lines.append(f" SQL: {validation.sql_query}")
1111
+ lines.append(f" Expected: {validation.expected_result}")
1112
+ lines.append("")
1113
+
1114
+ return "\n".join(lines)
1115
+
1116
+
1117
+ def main():
1118
+ parser = argparse.ArgumentParser(description="Generate database migration scripts")
1119
+ parser.add_argument("--current", "-c", required=True, help="Current schema JSON file")
1120
+ parser.add_argument("--target", "-t", required=True, help="Target schema JSON file")
1121
+ parser.add_argument("--output", "-o", help="Output file (default: stdout)")
1122
+ parser.add_argument("--format", "-f", choices=["json", "text", "sql"], default="text",
1123
+ help="Output format")
1124
+ parser.add_argument("--zero-downtime", "-z", action="store_true",
1125
+ help="Generate zero-downtime migration strategy")
1126
+ parser.add_argument("--validate-only", "-v", action="store_true",
1127
+ help="Only generate validation queries")
1128
+ parser.add_argument("--include-validations", action="store_true",
1129
+ help="Include validation queries in output")
1130
+
1131
+ args = parser.parse_args()
1132
+
1133
+ try:
1134
+ # Load schemas
1135
+ with open(args.current, 'r') as f:
1136
+ current_schema = json.load(f)
1137
+
1138
+ with open(args.target, 'r') as f:
1139
+ target_schema = json.load(f)
1140
+
1141
+ # Compare schemas
1142
+ comparator = SchemaComparator()
1143
+ comparator.load_schemas(current_schema, target_schema)
1144
+ changes = comparator.compare_schemas()
1145
+
1146
+ if not any(changes.values()):
1147
+ print("No schema changes detected.")
1148
+ return 0
1149
+
1150
+ # Generate migration
1151
+ generator = MigrationGenerator(zero_downtime=args.zero_downtime)
1152
+ migration_plan = generator.generate_migration(changes)
1153
+
1154
+ # Generate validations if requested
1155
+ validations = None
1156
+ if args.include_validations or args.validate_only:
1157
+ validator = ValidationGenerator()
1158
+ validations = validator.generate_validations(migration_plan)
1159
+
1160
+ # Format output
1161
+ if args.validate_only:
1162
+ output = json.dumps([asdict(v) for v in validations], indent=2)
1163
+ elif args.format == "json":
1164
+ result = {"migration_plan": asdict(migration_plan)}
1165
+ if validations:
1166
+ result["validations"] = [asdict(v) for v in validations]
1167
+ output = json.dumps(result, indent=2)
1168
+ elif args.format == "sql":
1169
+ sql_lines = []
1170
+ sql_lines.append("-- Database Migration Script")
1171
+ sql_lines.append(f"-- Migration ID: {migration_plan.migration_id}")
1172
+ sql_lines.append(f"-- Created: {migration_plan.created_at}")
1173
+ sql_lines.append("")
1174
+
1175
+ for step in migration_plan.steps:
1176
+ sql_lines.append(f"-- Step: {step.description}")
1177
+ sql_lines.append(step.sql_forward)
1178
+ sql_lines.append("")
1179
+
1180
+ output = "\n".join(sql_lines)
1181
+ else: # text format
1182
+ output = format_migration_plan_text(migration_plan, validations)
1183
+
1184
+ # Write output
1185
+ if args.output:
1186
+ with open(args.output, 'w') as f:
1187
+ f.write(output)
1188
+ else:
1189
+ print(output)
1190
+
1191
+ return 0
1192
+
1193
+ except Exception as e:
1194
+ print(f"Error: {e}", file=sys.stderr)
1195
+ return 1
1196
+
1197
+
1198
+ if __name__ == "__main__":
1199
+ sys.exit(main())