redeploy 0.2.47__tar.gz → 0.2.48__tar.gz

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 (195) hide show
  1. {redeploy-0.2.47 → redeploy-0.2.48}/PKG-INFO +6 -6
  2. {redeploy-0.2.47 → redeploy-0.2.48}/README.md +5 -5
  3. {redeploy-0.2.47 → redeploy-0.2.48}/pyproject.toml +1 -1
  4. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/__init__.py +1 -1
  5. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/__init__.py +3 -0
  6. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/blueprint.py +2 -20
  7. redeploy-0.2.48/redeploy/cli/commands/bump_fix.py +245 -0
  8. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/device_map.py +2 -20
  9. redeploy-0.2.48/redeploy/steps/__init__.py +76 -0
  10. redeploy-0.2.47/redeploy/steps.py → redeploy-0.2.48/redeploy/steps/builtins.py +65 -118
  11. redeploy-0.2.48/redeploy/steps/kiosk.py +49 -0
  12. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy.egg-info/PKG-INFO +6 -6
  13. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy.egg-info/SOURCES.txt +4 -1
  14. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/apply/__init__.py +0 -0
  15. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/apply/exceptions.py +0 -0
  16. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/apply/executor.py +0 -0
  17. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/apply/handlers.py +0 -0
  18. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/apply/progress.py +0 -0
  19. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/apply/rollback.py +0 -0
  20. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/apply/state.py +0 -0
  21. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/apply/state_apply.py +0 -0
  22. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/apply/utils/__init__.py +0 -0
  23. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/apply/utils/run_container_build.py +0 -0
  24. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/audit.py +0 -0
  25. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/blueprint/__init__.py +0 -0
  26. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/blueprint/extractor.py +0 -0
  27. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/blueprint/generators/__init__.py +0 -0
  28. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/blueprint/generators/docker_compose.py +0 -0
  29. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/blueprint/generators/migration.py +0 -0
  30. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/blueprint/sources/__init__.py +0 -0
  31. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/blueprint/sources/compose.py +0 -0
  32. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/blueprint/sources/hardware.py +0 -0
  33. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/blueprint/sources/infra.py +0 -0
  34. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/blueprint/sources/migration.py +0 -0
  35. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/__init__.py +0 -0
  36. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/audit.py +0 -0
  37. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/detect.py +0 -0
  38. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/device_map_renderers.py +0 -0
  39. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/devices.py +0 -0
  40. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/diagnose.py +0 -0
  41. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/diff.py +0 -0
  42. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/exec_.py +0 -0
  43. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/export.py +0 -0
  44. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/hardware.py +0 -0
  45. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/import_.py +0 -0
  46. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/init.py +0 -0
  47. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/inspect.py +0 -0
  48. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/patterns.py +0 -0
  49. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/plan_apply.py +0 -0
  50. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/plugin.py +0 -0
  51. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/probe.py +0 -0
  52. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/push.py +0 -0
  53. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/state.py +0 -0
  54. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/status.py +0 -0
  55. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/target.py +0 -0
  56. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/version/__init__.py +0 -0
  57. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/version/commands.py +0 -0
  58. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/version/helpers.py +0 -0
  59. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/version/monorepo.py +0 -0
  60. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/version/release.py +0 -0
  61. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/version/scanner.py +0 -0
  62. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/commands/workflow.py +0 -0
  63. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/core.py +0 -0
  64. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/display.py +0 -0
  65. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli/query.py +0 -0
  66. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/cli.py +0 -0
  67. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/config_apply/__init__.py +0 -0
  68. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/config_apply/applier.py +0 -0
  69. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/config_apply/loader.py +0 -0
  70. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/data_sync.py +0 -0
  71. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/detect/__init__.py +0 -0
  72. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/detect/builtin/__init__.py +0 -0
  73. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/detect/detector.py +0 -0
  74. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/detect/hardware.py +0 -0
  75. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/detect/hardware_rules.py +0 -0
  76. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/detect/probes.py +0 -0
  77. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/detect/remote.py +0 -0
  78. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/detect/templates.py +0 -0
  79. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/detect/workflow.py +0 -0
  80. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/discovery.py +0 -0
  81. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/dsl/__init__.py +0 -0
  82. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/dsl/loader.py +0 -0
  83. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/dsl/parser.py +0 -0
  84. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/dsl_python/__init__.py +0 -0
  85. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/dsl_python/context.py +0 -0
  86. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/dsl_python/decorators.py +0 -0
  87. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/dsl_python/docker_steps.py +0 -0
  88. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/dsl_python/exceptions.py +0 -0
  89. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/dsl_python/runner.py +0 -0
  90. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/dsl_python/steps.py +0 -0
  91. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/fleet.py +0 -0
  92. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/hardware/__init__.py +0 -0
  93. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/hardware/config_txt.py +0 -0
  94. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/hardware/data/__init__.py +0 -0
  95. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/hardware/data/hyperpixel.py +0 -0
  96. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/hardware/data/official.py +0 -0
  97. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/hardware/data/waveshare.py +0 -0
  98. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/hardware/fixes.py +0 -0
  99. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/hardware/kiosk/__init__.py +0 -0
  100. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/hardware/kiosk/autostart.py +0 -0
  101. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/hardware/kiosk/browsers.py +0 -0
  102. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/hardware/kiosk/compositors.py +0 -0
  103. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/hardware/kiosk/output_profiles.py +0 -0
  104. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/hardware/panels.py +0 -0
  105. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/hardware/raspi_config.py +0 -0
  106. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/heal.py +0 -0
  107. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/iac/__init__.py +0 -0
  108. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/iac/base.py +0 -0
  109. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/iac/docker_compose.py +0 -0
  110. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/iac/parsers/__init__.py +0 -0
  111. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/iac/parsers/compose.py +0 -0
  112. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/iac/registry.py +0 -0
  113. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/integrations/__init__.py +0 -0
  114. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/integrations/op3_bridge.py +0 -0
  115. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/markpact/__init__.py +0 -0
  116. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/markpact/compiler.py +0 -0
  117. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/markpact/models.py +0 -0
  118. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/markpact/parser.py +0 -0
  119. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/models.py +0 -0
  120. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/observe.py +0 -0
  121. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/parse.py +0 -0
  122. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/patterns.py +0 -0
  123. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/plan/__init__.py +0 -0
  124. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/plan/planner.py +0 -0
  125. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/plugins/__init__.py +0 -0
  126. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/plugins/builtin/__init__.py +0 -0
  127. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/plugins/builtin/browser_reload.py +0 -0
  128. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/plugins/builtin/hardware_diagnostic.py +0 -0
  129. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/plugins/builtin/notify.py +0 -0
  130. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/plugins/builtin/process_control.py +0 -0
  131. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/plugins/builtin/systemd_reload.py +0 -0
  132. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/spec_loader.py +0 -0
  133. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/ssh.py +0 -0
  134. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/__init__.py +0 -0
  135. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_audit.py +0 -0
  136. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_cli.py +0 -0
  137. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_data_sync.py +0 -0
  138. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_discovery.py +0 -0
  139. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_dsl.py +0 -0
  140. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_examples.py +0 -0
  141. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_executor.py +0 -0
  142. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_fleet.py +0 -0
  143. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_git_integration.py +0 -0
  144. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_iac.py +0 -0
  145. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_manifest.py +0 -0
  146. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_markpact_compiler.py +0 -0
  147. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_markpact_parser.py +0 -0
  148. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_models.py +0 -0
  149. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_observe.py +0 -0
  150. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_parse.py +0 -0
  151. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_patterns.py +0 -0
  152. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_planner_edge.py +0 -0
  153. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_plugins.py +0 -0
  154. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_probes.py +0 -0
  155. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_public_api.py +0 -0
  156. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_resume.py +0 -0
  157. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_spec_loader.py +0 -0
  158. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_ssh.py +0 -0
  159. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_templates.py +0 -0
  160. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_verify.py +0 -0
  161. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_version.py +0 -0
  162. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_version_changelog.py +0 -0
  163. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_version_cli.py +0 -0
  164. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_version_manifest.py +0 -0
  165. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_version_sources.py +0 -0
  166. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/tests/test_workflow.py +0 -0
  167. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/verify.py +0 -0
  168. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/version/__init__.py +0 -0
  169. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/version/bump.py +0 -0
  170. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/version/changelog.py +0 -0
  171. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/version/commits.py +0 -0
  172. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/version/diff.py +0 -0
  173. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/version/git_integration.py +0 -0
  174. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/version/git_transaction.py +0 -0
  175. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/version/manifest.py +0 -0
  176. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/version/sources/__init__.py +0 -0
  177. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/version/sources/base.py +0 -0
  178. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/version/sources/json_.py +0 -0
  179. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/version/sources/plain.py +0 -0
  180. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/version/sources/regex.py +0 -0
  181. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/version/sources/toml_.py +0 -0
  182. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/version/sources/yaml_.py +0 -0
  183. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/version/transaction.py +0 -0
  184. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy/version.py +0 -0
  185. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy.egg-info/dependency_links.txt +0 -0
  186. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy.egg-info/entry_points.txt +0 -0
  187. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy.egg-info/requires.txt +0 -0
  188. {redeploy-0.2.47 → redeploy-0.2.48}/redeploy.egg-info/top_level.txt +0 -0
  189. {redeploy-0.2.47 → redeploy-0.2.48}/setup.cfg +0 -0
  190. {redeploy-0.2.47 → redeploy-0.2.48}/tests/test_hardware_config.py +0 -0
  191. {redeploy-0.2.47 → redeploy-0.2.48}/tests/test_hardware_kiosk.py +0 -0
  192. {redeploy-0.2.47 → redeploy-0.2.48}/tests/test_hardware_rules.py +0 -0
  193. {redeploy-0.2.47 → redeploy-0.2.48}/tests/test_iac.py +0 -0
  194. {redeploy-0.2.47 → redeploy-0.2.48}/tests/test_parse.py +0 -0
  195. {redeploy-0.2.47 → redeploy-0.2.48}/tests/test_redeploy.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: redeploy
3
- Version: 0.2.47
3
+ Version: 0.2.48
4
4
  Summary: Infrastructure migration toolkit: detect → plan → apply
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -30,17 +30,17 @@ Requires-Dist: op3>=0.1.8; extra == "op3"
30
30
 
31
31
  ## AI Cost Tracking
32
32
 
33
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.47-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
34
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-24.0h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
33
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.48-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
34
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-24.4h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
35
35
 
36
- - 🤖 **LLM usage:** $7.5000 (68 commits)
37
- - 👤 **Human dev:** ~$2398 (24.0h @ $100/h, 30min dedup)
36
+ - 🤖 **LLM usage:** $7.5000 (69 commits)
37
+ - 👤 **Human dev:** ~$2440 (24.4h @ $100/h, 30min dedup)
38
38
 
39
39
  Generated on 2026-04-21 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
40
40
 
41
41
  ---
42
42
 
43
- ![PyPI](https://img.shields.io/badge/pypi-redeploy-blue) ![Version](https://img.shields.io/badge/version-0.2.47-blue) ![Python](https://img.shields.io/badge/python-3.10+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
43
+ ![PyPI](https://img.shields.io/badge/pypi-redeploy-blue) ![Version](https://img.shields.io/badge/version-0.2.48-blue) ![Python](https://img.shields.io/badge/python-3.10+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
44
44
 
45
45
  Infrastructure migration and device deploy toolkit — VPS, Raspberry Pi kiosk, Podman Quadlet, k3s.
46
46
 
@@ -3,17 +3,17 @@
3
3
 
4
4
  ## AI Cost Tracking
5
5
 
6
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.47-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
7
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-24.0h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
6
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.48-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
7
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-24.4h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
8
8
 
9
- - 🤖 **LLM usage:** $7.5000 (68 commits)
10
- - 👤 **Human dev:** ~$2398 (24.0h @ $100/h, 30min dedup)
9
+ - 🤖 **LLM usage:** $7.5000 (69 commits)
10
+ - 👤 **Human dev:** ~$2440 (24.4h @ $100/h, 30min dedup)
11
11
 
12
12
  Generated on 2026-04-21 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
13
13
 
14
14
  ---
15
15
 
16
- ![PyPI](https://img.shields.io/badge/pypi-redeploy-blue) ![Version](https://img.shields.io/badge/version-0.2.47-blue) ![Python](https://img.shields.io/badge/python-3.10+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
16
+ ![PyPI](https://img.shields.io/badge/pypi-redeploy-blue) ![Version](https://img.shields.io/badge/version-0.2.48-blue) ![Python](https://img.shields.io/badge/python-3.10+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
17
17
 
18
18
  Infrastructure migration and device deploy toolkit — VPS, Raspberry Pi kiosk, Podman Quadlet, k3s.
19
19
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "redeploy"
7
- version = "0.2.47"
7
+ version = "0.2.48"
8
8
  description = "Infrastructure migration toolkit: detect → plan → apply"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -7,7 +7,7 @@ Public API (stable from 0.2.0, semver guaranteed)::
7
7
 
8
8
  Everything not listed in ``__all__`` is internal and may change without notice.
9
9
  """
10
- __version__ = "0.2.47"
10
+ __version__ = "0.2.48"
11
11
 
12
12
  # ── Core models ───────────────────────────────────────────────────────────────
13
13
  from .models import ( # noqa: F401
@@ -54,6 +54,7 @@ from .commands.device_map import device_map_cmd
54
54
  from .commands.blueprint import blueprint_cmd
55
55
  from .commands.init import init
56
56
  from .commands.status import status
57
+ from .commands.bump_fix import bump_cmd, fix_cmd
57
58
 
58
59
  # Register commands
59
60
  cli.add_command(audit)
@@ -86,6 +87,8 @@ cli.add_command(status)
86
87
  cli.add_command(target)
87
88
  cli.add_command(version_cmd)
88
89
  cli.add_command(workflow_cmd)
90
+ cli.add_command(bump_cmd, name="bump")
91
+ cli.add_command(fix_cmd, name="fix")
89
92
 
90
93
 
91
94
  # Backward compatibility: _resolve_device for tests
@@ -97,26 +97,8 @@ def capture(host, name, compose_files, migration_file, device_map_file,
97
97
 
98
98
  def _execute_query_blueprint(bp, query_expr, output_fmt):
99
99
  """Execute JMESPath query on DeviceBlueprint model and output result."""
100
- import jmespath
101
- import json as _json
102
-
103
- data = bp.model_dump(mode="json")
104
-
105
- try:
106
- result = jmespath.search(query_expr, data)
107
- except jmespath.exceptions.JMESPathError as e:
108
- print(f"[red]✗ JMESPath error:[/red] {e}")
109
- sys.exit(1)
110
-
111
- if result is None:
112
- print("[dim]No match found for query[/dim]")
113
- return
114
-
115
- if output_fmt == "json":
116
- click.echo(_json.dumps(result, indent=2, default=str))
117
- else:
118
- import yaml
119
- click.echo(yaml.safe_dump(result, sort_keys=False, default_flow_style=False))
100
+ from ...cli.query import execute_query
101
+ execute_query(bp, query_expr, output_fmt, echo=click.echo)
120
102
 
121
103
 
122
104
  # ── twin ──────────────────────────────────────────────────────────────────────
@@ -0,0 +1,245 @@
1
+ """bump and fix commands — version bumping and self-healing deploy."""
2
+ from __future__ import annotations
3
+
4
+ import re
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ import click
9
+ from rich.console import Console
10
+
11
+
12
+ # ── helpers ──────────────────────────────────────────────────────────────────
13
+
14
+ def _find_spec(spec_arg: str) -> Path:
15
+ """Resolve spec path from '.' (use migration.yaml/migration.md) or explicit path."""
16
+ p = Path(spec_arg)
17
+ if p.is_dir():
18
+ for candidate in ["migration.md", "migration.yaml", "migration.yml"]:
19
+ c = p / candidate
20
+ if c.exists():
21
+ return c
22
+ # also check one level up for c2004 project pattern
23
+ for sub in sorted(p.rglob("migration.md"), key=lambda x: len(x.parts)):
24
+ return sub
25
+ for sub in sorted(p.rglob("migration.yaml"), key=lambda x: len(x.parts)):
26
+ return sub
27
+ raise click.ClickException(f"No migration spec found under {spec_arg!r}")
28
+ if not p.exists():
29
+ raise click.ClickException(f"Spec not found: {spec_arg!r}")
30
+ return p
31
+
32
+
33
+ def _find_version_file(spec_path: Path) -> Path | None:
34
+ """Walk up from spec to find VERSION file."""
35
+ for parent in [spec_path.parent, spec_path.parent.parent,
36
+ spec_path.parent.parent.parent, Path.cwd()]:
37
+ v = parent / "VERSION"
38
+ if v.exists():
39
+ return v
40
+ return None
41
+
42
+
43
+ def _bump_patch(version: str) -> str:
44
+ """Bump the patch component: 1.0.30 → 1.0.31."""
45
+ m = re.match(r"^(\d+\.\d+\.)(\d+)(.*)", version.strip())
46
+ if not m:
47
+ raise click.ClickException(f"Cannot parse version: {version!r}")
48
+ return f"{m.group(1)}{int(m.group(2)) + 1}{m.group(3)}"
49
+
50
+
51
+ def _bump_version_file(version_file: Path, console: Console) -> str:
52
+ """Read, increment patch, write back. Returns new version."""
53
+ old = version_file.read_text().strip()
54
+ new = _bump_patch(old)
55
+ version_file.write_text(new + "\n")
56
+ console.print(f"[green]bump[/green] {old} → [bold]{new}[/bold] ({version_file})")
57
+ return new
58
+
59
+
60
+ def _update_migration_header(spec_path: Path, new_version: str) -> None:
61
+ """Update 'version:' line in spec file header if present."""
62
+ text = spec_path.read_text()
63
+ updated = re.sub(
64
+ r"^(version:\s*)[\w\.\-]+",
65
+ lambda m: f"{m.group(1)}{new_version}",
66
+ text, flags=re.MULTILINE
67
+ )
68
+ if updated != text:
69
+ spec_path.write_text(updated)
70
+
71
+
72
+ def _load_dotenv() -> None:
73
+ try:
74
+ from dotenv import load_dotenv
75
+ for candidate in [Path(".env"), Path.home() / ".redeploy" / ".env"]:
76
+ if candidate.exists():
77
+ load_dotenv(candidate, override=False)
78
+ break
79
+ except ImportError:
80
+ pass
81
+
82
+
83
+ # ── bump command ──────────────────────────────────────────────────────────────
84
+
85
+ @click.command("bump")
86
+ @click.argument("spec_or_dir", default=".", metavar="PATH")
87
+ @click.option(
88
+ "--minor", is_flag=True, help="Bump minor instead of patch (1.0.x → 1.1.0)"
89
+ )
90
+ @click.option(
91
+ "--major", is_flag=True, help="Bump major instead of patch (1.x.y → 2.0.0)"
92
+ )
93
+ def bump_cmd(spec_or_dir, minor, major):
94
+ """Bump the project version (patch by default).
95
+
96
+ \b
97
+ PATH is a spec file or directory containing migration.yaml / migration.md.
98
+ '.' uses the current directory.
99
+
100
+ \b
101
+ Examples:
102
+ redeploy bump .
103
+ redeploy bump redeploy/pi109/migration.md
104
+ redeploy bump . --minor
105
+ """
106
+ console = Console()
107
+ spec_path = _find_spec(spec_or_dir)
108
+ version_file = _find_version_file(spec_path)
109
+
110
+ if version_file is None:
111
+ console.print(
112
+ "[yellow]No VERSION file found — creating one alongside spec[/yellow]"
113
+ )
114
+ version_file = spec_path.parent / "VERSION"
115
+ version_file.write_text("1.0.0\n")
116
+
117
+ old = version_file.read_text().strip()
118
+
119
+ if major:
120
+ m = re.match(r"^(\d+)(.*)", old)
121
+ new = f"{int(m.group(1)) + 1}.0.0" if m else old
122
+ elif minor:
123
+ m = re.match(r"^(\d+)\.(\d+)(.*)", old)
124
+ new = f"{m.group(1)}.{int(m.group(2)) + 1}.0" if m else old
125
+ else:
126
+ new = _bump_patch(old)
127
+
128
+ version_file.write_text(new + "\n")
129
+ console.print(f"[green]bump[/green] {old} → [bold]{new}[/bold] ({version_file})")
130
+ _update_migration_header(spec_path, new)
131
+ console.print(f"[dim]updated spec header: {spec_path}[/dim]")
132
+
133
+
134
+ # ── fix command ───────────────────────────────────────────────────────────────
135
+
136
+ @click.command("fix")
137
+ @click.argument("spec_or_dir", default=".", metavar="PATH")
138
+ @click.option(
139
+ "--hint", "-m", default=None, metavar="TEXT",
140
+ help='Describe the problem, e.g. --hint "brak ikon SVG w menu"'
141
+ )
142
+ @click.option(
143
+ "--bump/--no-bump", default=True,
144
+ help="Bump version before deploying (default: on)"
145
+ )
146
+ @click.option(
147
+ "--retries", default=3, show_default=True,
148
+ help="Max LLM self-healing attempts"
149
+ )
150
+ @click.option("--dry-run", is_flag=True, help="Plan only, no apply")
151
+ @click.option(
152
+ "--env", "env_name", default="",
153
+ help="Named environment from redeploy.yaml"
154
+ )
155
+ def fix_cmd(spec_or_dir, hint, bump, retries, dry_run, env_name):
156
+ """Self-healing deploy: bump version, then run with LLM auto-fix on failure.
157
+
158
+ \b
159
+ PATH is a spec file or directory containing migration.yaml / migration.md.
160
+ '.' uses the current directory.
161
+
162
+ \b
163
+ Examples:
164
+ redeploy fix .
165
+ redeploy fix . --hint "brak ikon SVG w menu"
166
+ redeploy fix redeploy/pi109/migration.md --no-bump
167
+ redeploy fix . --hint "chromium nie startuje" --retries 5
168
+ """
169
+ console = Console()
170
+ _load_dotenv()
171
+
172
+ spec_path = _find_spec(spec_or_dir)
173
+
174
+ # Bump version first
175
+ version = ""
176
+ if bump and not dry_run:
177
+ version_file = _find_version_file(spec_path)
178
+ if version_file is None:
179
+ version_file = spec_path.parent / "VERSION"
180
+ version_file.write_text("1.0.0\n")
181
+ version = _bump_version_file(version_file, console)
182
+ _update_migration_header(spec_path, version)
183
+
184
+ if hint:
185
+ console.print(f"[cyan]hint:[/cyan] {hint}")
186
+
187
+ # Load spec + plan (reuse plan_apply helpers)
188
+ from ..core import load_spec_or_exit
189
+ from ...plan import Planner
190
+ from ...plugins import load_user_plugins
191
+ from ...models import ProjectManifest
192
+
193
+ manifest = ProjectManifest.find_and_load(Path.cwd())
194
+ spec = load_spec_or_exit(console, str(spec_path))
195
+
196
+ # Apply manifest overlays if any
197
+ if manifest and env_name:
198
+ env = manifest.environments.get(env_name)
199
+ if env:
200
+ if env.host:
201
+ spec.target.host = env.host
202
+
203
+ console.print(
204
+ f"\n[bold]plan[/bold] [dim]({spec_path})[/dim]"
205
+ )
206
+ planner = Planner.from_spec(spec)
207
+ migration = planner.run()
208
+
209
+ from ..display import print_plan_table
210
+ print_plan_table(console, migration)
211
+
212
+ if dry_run:
213
+ console.print("\n[dim]--dry-run: stopping before apply[/dim]")
214
+ return
215
+
216
+ load_user_plugins()
217
+
218
+ _print_banner(console, hint)
219
+ version = version or _detect_version(spec_path)
220
+
221
+ from ...heal import HealRunner
222
+ runner = HealRunner(
223
+ migration=migration,
224
+ spec_path=spec_path,
225
+ host=migration.host,
226
+ fix_hint=hint or "",
227
+ max_retries=retries,
228
+ dry_run=dry_run,
229
+ console=console,
230
+ version=version,
231
+ )
232
+ ok = runner.run()
233
+ if not ok:
234
+ sys.exit(1)
235
+
236
+
237
+ def _print_banner(console: Console, hint: str | None) -> None:
238
+ console.print("\n[bold green]fix[/bold green] [dim]heal mode: on[/dim]")
239
+ if hint:
240
+ console.print(f"[cyan]user hint:[/cyan] {hint}")
241
+
242
+
243
+ def _detect_version(spec_path: Path) -> str:
244
+ vf = _find_version_file(spec_path)
245
+ return vf.read_text().strip() if vf else ""
@@ -197,26 +197,8 @@ def device_map_cmd(
197
197
 
198
198
  def _execute_query_device_map(console, dm, query_expr, output_fmt):
199
199
  """Execute JMESPath query on DeviceMap model and output result."""
200
- import jmespath
201
- import json as _json
202
-
203
- data = dm.model_dump(mode="json")
204
-
205
- try:
206
- result = jmespath.search(query_expr, data)
207
- except jmespath.exceptions.JMESPathError as e:
208
- console.print(f"[red]✗ JMESPath error:[/red] {e}")
209
- sys.exit(1)
210
-
211
- if result is None:
212
- console.print("[dim]No match found for query[/dim]")
213
- return
214
-
215
- if output_fmt == "json":
216
- click.echo(_json.dumps(result, indent=2, default=str))
217
- else:
218
- import yaml
219
- click.echo(yaml.safe_dump(result, sort_keys=False, default_flow_style=False))
200
+ from ...cli.query import execute_query
201
+ execute_query(dm, query_expr, output_fmt, echo=console.print)
220
202
 
221
203
 
222
204
  # ── rendering helpers ─────────────────────────────────────────────────────────
@@ -0,0 +1,76 @@
1
+ """Step library — pre-defined, reusable named MigrationSteps.
2
+
3
+ Usage in a MigrationSpec::
4
+
5
+ extra_steps:
6
+ - id: flush_k3s_iptables # references a library step by id
7
+ - id: stop_nginx
8
+ - id: http_health_check
9
+ url: https://myapp.example.com/health
10
+
11
+ Usage from Python::
12
+
13
+ from redeploy.steps import StepLibrary
14
+ steps = StepLibrary.get("flush_k3s_iptables")
15
+ """
16
+ from __future__ import annotations
17
+
18
+ from typing import Any, Optional
19
+
20
+ from ..models import MigrationStep
21
+
22
+ from . import builtins, kiosk
23
+
24
+ # Merge domain modules into a single flat registry.
25
+ _LIBRARY: dict[str, MigrationStep] = {
26
+ s.id: s for s in (builtins.ALL + kiosk.ALL)
27
+ }
28
+
29
+
30
+ class StepLibrary:
31
+ """Registry of pre-defined named MigrationSteps.
32
+
33
+ Steps are returned as copies so callers can override individual fields::
34
+
35
+ step = StepLibrary.get("http_health_check")
36
+ step.url = "https://myapp.example.com/health"
37
+ step.expect = "1.0.20"
38
+ """
39
+
40
+ @staticmethod
41
+ def get(step_id: str, **overrides: Any) -> Optional[MigrationStep]:
42
+ """Return a copy of a named step, optionally with field overrides.
43
+
44
+ Returns ``None`` if the step_id is not in the library.
45
+ """
46
+ template = _LIBRARY.get(step_id)
47
+ if template is None:
48
+ return None
49
+ data = template.model_dump()
50
+ data.update(overrides)
51
+ return MigrationStep(**data)
52
+
53
+ @staticmethod
54
+ def list() -> list[str]:
55
+ """Return sorted list of available step IDs."""
56
+ return sorted(_LIBRARY.keys())
57
+
58
+ @staticmethod
59
+ def all() -> dict[str, MigrationStep]:
60
+ """Return full registry (copies)."""
61
+ return {k: v.model_copy() for k, v in _LIBRARY.items()}
62
+
63
+ @staticmethod
64
+ def resolve_from_spec(raw: dict[str, Any]) -> MigrationStep:
65
+ """Resolve a raw dict (from migration YAML extra_steps) to a MigrationStep.
66
+
67
+ If ``id`` matches a library entry and no ``action`` is given, use the
68
+ library template as base and merge the raw dict on top.
69
+ """
70
+ step_id = raw.get("id", "")
71
+ template = _LIBRARY.get(step_id)
72
+ if template:
73
+ data = template.model_dump()
74
+ data.update({k: v for k, v in raw.items() if v is not None})
75
+ return MigrationStep(**data)
76
+ return MigrationStep(**raw)