redeploy 0.2.43__tar.gz → 0.2.45__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 (184) hide show
  1. {redeploy-0.2.43 → redeploy-0.2.45}/PKG-INFO +8 -6
  2. {redeploy-0.2.43 → redeploy-0.2.45}/README.md +5 -5
  3. {redeploy-0.2.43 → redeploy-0.2.45}/pyproject.toml +2 -1
  4. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/__init__.py +1 -1
  5. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/device_map.py +39 -3
  6. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/hardware.py +2 -126
  7. redeploy-0.2.45/redeploy/config_apply/__init__.py +15 -0
  8. redeploy-0.2.45/redeploy/config_apply/applier.py +72 -0
  9. redeploy-0.2.45/redeploy/config_apply/loader.py +17 -0
  10. redeploy-0.2.45/redeploy/integrations/__init__.py +1 -0
  11. redeploy-0.2.45/redeploy/integrations/op3_bridge.py +116 -0
  12. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy.egg-info/PKG-INFO +8 -6
  13. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy.egg-info/SOURCES.txt +5 -0
  14. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy.egg-info/requires.txt +3 -0
  15. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/apply/__init__.py +0 -0
  16. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/apply/exceptions.py +0 -0
  17. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/apply/executor.py +0 -0
  18. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/apply/handlers.py +0 -0
  19. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/apply/progress.py +0 -0
  20. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/apply/rollback.py +0 -0
  21. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/apply/state.py +0 -0
  22. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/apply/state_apply.py +0 -0
  23. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/apply/utils/__init__.py +0 -0
  24. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/apply/utils/run_container_build.py +0 -0
  25. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/audit.py +0 -0
  26. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/blueprint/__init__.py +0 -0
  27. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/blueprint/extractor.py +0 -0
  28. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/blueprint/generators/__init__.py +0 -0
  29. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/blueprint/generators/docker_compose.py +0 -0
  30. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/blueprint/generators/migration.py +0 -0
  31. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/__init__.py +0 -0
  32. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/__init__.py +0 -0
  33. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/audit.py +0 -0
  34. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/blueprint.py +0 -0
  35. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/detect.py +0 -0
  36. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/devices.py +0 -0
  37. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/diagnose.py +0 -0
  38. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/diff.py +0 -0
  39. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/exec_.py +0 -0
  40. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/export.py +0 -0
  41. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/import_.py +0 -0
  42. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/init.py +0 -0
  43. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/inspect.py +0 -0
  44. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/patterns.py +0 -0
  45. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/plan_apply.py +0 -0
  46. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/plugin.py +0 -0
  47. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/probe.py +0 -0
  48. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/push.py +0 -0
  49. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/state.py +0 -0
  50. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/status.py +0 -0
  51. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/target.py +0 -0
  52. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/version/__init__.py +0 -0
  53. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/version/commands.py +0 -0
  54. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/version/helpers.py +0 -0
  55. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/version/monorepo.py +0 -0
  56. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/version/release.py +0 -0
  57. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/version/scanner.py +0 -0
  58. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/commands/workflow.py +0 -0
  59. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/core.py +0 -0
  60. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli/display.py +0 -0
  61. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/cli.py +0 -0
  62. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/data_sync.py +0 -0
  63. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/detect/__init__.py +0 -0
  64. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/detect/builtin/__init__.py +0 -0
  65. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/detect/detector.py +0 -0
  66. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/detect/hardware.py +0 -0
  67. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/detect/hardware_rules.py +0 -0
  68. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/detect/probes.py +0 -0
  69. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/detect/remote.py +0 -0
  70. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/detect/templates.py +0 -0
  71. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/detect/workflow.py +0 -0
  72. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/discovery.py +0 -0
  73. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/dsl/__init__.py +0 -0
  74. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/dsl/loader.py +0 -0
  75. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/dsl/parser.py +0 -0
  76. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/dsl_python/__init__.py +0 -0
  77. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/dsl_python/context.py +0 -0
  78. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/dsl_python/decorators.py +0 -0
  79. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/dsl_python/docker_steps.py +0 -0
  80. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/dsl_python/exceptions.py +0 -0
  81. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/dsl_python/runner.py +0 -0
  82. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/dsl_python/steps.py +0 -0
  83. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/fleet.py +0 -0
  84. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/hardware/__init__.py +0 -0
  85. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/hardware/config_txt.py +0 -0
  86. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/hardware/data/__init__.py +0 -0
  87. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/hardware/data/hyperpixel.py +0 -0
  88. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/hardware/data/official.py +0 -0
  89. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/hardware/data/waveshare.py +0 -0
  90. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/hardware/fixes.py +0 -0
  91. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/hardware/kiosk/__init__.py +0 -0
  92. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/hardware/kiosk/autostart.py +0 -0
  93. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/hardware/kiosk/browsers.py +0 -0
  94. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/hardware/kiosk/compositors.py +0 -0
  95. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/hardware/kiosk/output_profiles.py +0 -0
  96. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/hardware/panels.py +0 -0
  97. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/hardware/raspi_config.py +0 -0
  98. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/iac/__init__.py +0 -0
  99. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/iac/base.py +0 -0
  100. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/iac/docker_compose.py +0 -0
  101. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/iac/parsers/__init__.py +0 -0
  102. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/iac/parsers/compose.py +0 -0
  103. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/iac/registry.py +0 -0
  104. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/markpact/__init__.py +0 -0
  105. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/markpact/compiler.py +0 -0
  106. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/markpact/models.py +0 -0
  107. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/markpact/parser.py +0 -0
  108. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/models.py +0 -0
  109. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/observe.py +0 -0
  110. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/parse.py +0 -0
  111. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/patterns.py +0 -0
  112. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/plan/__init__.py +0 -0
  113. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/plan/planner.py +0 -0
  114. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/plugins/__init__.py +0 -0
  115. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/plugins/builtin/__init__.py +0 -0
  116. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/plugins/builtin/browser_reload.py +0 -0
  117. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/plugins/builtin/hardware_diagnostic.py +0 -0
  118. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/plugins/builtin/notify.py +0 -0
  119. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/plugins/builtin/process_control.py +0 -0
  120. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/plugins/builtin/systemd_reload.py +0 -0
  121. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/spec_loader.py +0 -0
  122. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/ssh.py +0 -0
  123. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/steps.py +0 -0
  124. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/__init__.py +0 -0
  125. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_audit.py +0 -0
  126. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_cli.py +0 -0
  127. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_data_sync.py +0 -0
  128. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_discovery.py +0 -0
  129. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_dsl.py +0 -0
  130. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_examples.py +0 -0
  131. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_executor.py +0 -0
  132. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_fleet.py +0 -0
  133. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_git_integration.py +0 -0
  134. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_iac.py +0 -0
  135. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_manifest.py +0 -0
  136. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_markpact_compiler.py +0 -0
  137. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_markpact_parser.py +0 -0
  138. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_models.py +0 -0
  139. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_observe.py +0 -0
  140. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_parse.py +0 -0
  141. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_patterns.py +0 -0
  142. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_planner_edge.py +0 -0
  143. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_plugins.py +0 -0
  144. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_probes.py +0 -0
  145. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_public_api.py +0 -0
  146. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_resume.py +0 -0
  147. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_spec_loader.py +0 -0
  148. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_ssh.py +0 -0
  149. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_templates.py +0 -0
  150. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_verify.py +0 -0
  151. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_version.py +0 -0
  152. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_version_changelog.py +0 -0
  153. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_version_cli.py +0 -0
  154. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_version_manifest.py +0 -0
  155. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_version_sources.py +0 -0
  156. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/tests/test_workflow.py +0 -0
  157. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/verify.py +0 -0
  158. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/version/__init__.py +0 -0
  159. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/version/bump.py +0 -0
  160. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/version/changelog.py +0 -0
  161. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/version/commits.py +0 -0
  162. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/version/diff.py +0 -0
  163. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/version/git_integration.py +0 -0
  164. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/version/git_transaction.py +0 -0
  165. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/version/manifest.py +0 -0
  166. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/version/sources/__init__.py +0 -0
  167. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/version/sources/base.py +0 -0
  168. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/version/sources/json_.py +0 -0
  169. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/version/sources/plain.py +0 -0
  170. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/version/sources/regex.py +0 -0
  171. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/version/sources/toml_.py +0 -0
  172. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/version/sources/yaml_.py +0 -0
  173. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/version/transaction.py +0 -0
  174. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy/version.py +0 -0
  175. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy.egg-info/dependency_links.txt +0 -0
  176. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy.egg-info/entry_points.txt +0 -0
  177. {redeploy-0.2.43 → redeploy-0.2.45}/redeploy.egg-info/top_level.txt +0 -0
  178. {redeploy-0.2.43 → redeploy-0.2.45}/setup.cfg +0 -0
  179. {redeploy-0.2.43 → redeploy-0.2.45}/tests/test_hardware_config.py +0 -0
  180. {redeploy-0.2.43 → redeploy-0.2.45}/tests/test_hardware_kiosk.py +0 -0
  181. {redeploy-0.2.43 → redeploy-0.2.45}/tests/test_hardware_rules.py +0 -0
  182. {redeploy-0.2.43 → redeploy-0.2.45}/tests/test_iac.py +0 -0
  183. {redeploy-0.2.43 → redeploy-0.2.45}/tests/test_parse.py +0 -0
  184. {redeploy-0.2.43 → redeploy-0.2.45}/tests/test_redeploy.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: redeploy
3
- Version: 0.2.43
3
+ Version: 0.2.45
4
4
  Summary: Infrastructure migration toolkit: detect → plan → apply
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -22,23 +22,25 @@ Requires-Dist: ruff>=0.4; extra == "dev"
22
22
  Requires-Dist: goal>=2.1.0; extra == "dev"
23
23
  Requires-Dist: costs>=0.1.20; extra == "dev"
24
24
  Requires-Dist: pfix>=0.1.60; extra == "dev"
25
+ Provides-Extra: op3
26
+ Requires-Dist: op3>=0.1.4; extra == "op3"
25
27
 
26
28
  # redeploy
27
29
 
28
30
 
29
31
  ## AI Cost Tracking
30
32
 
31
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.43-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
32
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-21.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.45-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-23.2h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
33
35
 
34
- - 🤖 **LLM usage:** $7.5000 (64 commits)
35
- - 👤 **Human dev:** ~$2099 (21.0h @ $100/h, 30min dedup)
36
+ - 🤖 **LLM usage:** $7.5000 (66 commits)
37
+ - 👤 **Human dev:** ~$2318 (23.2h @ $100/h, 30min dedup)
36
38
 
37
39
  Generated on 2026-04-21 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
38
40
 
39
41
  ---
40
42
 
41
- ![PyPI](https://img.shields.io/badge/pypi-redeploy-blue) ![Version](https://img.shields.io/badge/version-0.2.43-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.45-blue) ![Python](https://img.shields.io/badge/python-3.10+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
42
44
 
43
45
  Infrastructure migration and device deploy toolkit — VPS, Raspberry Pi kiosk, Podman Quadlet, k3s.
44
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.43-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-21.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.45-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-23.2h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
8
8
 
9
- - 🤖 **LLM usage:** $7.5000 (64 commits)
10
- - 👤 **Human dev:** ~$2099 (21.0h @ $100/h, 30min dedup)
9
+ - 🤖 **LLM usage:** $7.5000 (66 commits)
10
+ - 👤 **Human dev:** ~$2318 (23.2h @ $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.43-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.45-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.43"
7
+ version = "0.2.45"
8
8
  description = "Infrastructure migration toolkit: detect → plan → apply"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -32,6 +32,7 @@ dev = ["pytest>=8.0", "ruff>=0.4",
32
32
  "costs>=0.1.20",
33
33
  "pfix>=0.1.60",
34
34
  ]
35
+ op3 = ["op3>=0.1.4"]
35
36
 
36
37
  [tool.ruff]
37
38
  line-length = 100
@@ -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.43"
10
+ __version__ = "0.2.45"
11
11
 
12
12
  # ── Core models ───────────────────────────────────────────────────────────────
13
13
  from .models import ( # noqa: F401
@@ -50,10 +50,16 @@ from rich.table import Table
50
50
  @click.option("--apply-config", "apply_config", default=None,
51
51
  type=click.Path(exists=True, dir_okay=False),
52
52
  help="Apply hardware/infra settings from YAML config file to the remote host")
53
+ @click.option(
54
+ "--query", "query_expr",
55
+ default=None,
56
+ metavar="EXPR",
57
+ help="Extract specific values using JMESPath query (e.g. 'hardware.drm_outputs[0].transform', 'host')",
58
+ )
53
59
  @click.option("--ssh-key", default=None, type=click.Path(), help="SSH private key path")
54
60
  def device_map_cmd(
55
61
  host, name, tags, save, out_path, output_fmt,
56
- no_infra, list_saved, show_file, diff_files, ssh_key, apply_config,
62
+ no_infra, list_saved, show_file, diff_files, ssh_key, apply_config, query_expr,
57
63
  ):
58
64
  """Generate a full standardized device snapshot (hardware + infra + diagnostics).
59
65
 
@@ -105,6 +111,12 @@ def device_map_cmd(
105
111
  console.print("[red]✗ HOST required (or use --list / --show / --diff)[/red]")
106
112
  sys.exit(1)
107
113
 
114
+ # ── --apply-config ─────────────────────────────────────────────────────────
115
+ if apply_config:
116
+ from ...config_apply import apply_config_file
117
+ apply_config_file(apply_config, ssh_key=ssh_key, console=console)
118
+ return
119
+
108
120
  # ── probe ─────────────────────────────────────────────────────────────────
109
121
  from ...detect.hardware import probe_hardware
110
122
  from ...detect.remote import RemoteProbe
@@ -195,7 +207,7 @@ def _apply_config_from_file(console, config_path, ssh_key):
195
207
  import yaml
196
208
  import json as _json
197
209
 
198
- from ...remote import Remote
210
+ from ...detect.remote import RemoteProbe
199
211
 
200
212
  with open(config_path) as f:
201
213
  raw = f.read()
@@ -214,7 +226,7 @@ def _apply_config_from_file(console, config_path, ssh_key):
214
226
  console.print(f"[cyan]→ Applying config from {config_path} to {host}[/cyan]")
215
227
 
216
228
  # Connect to remote
217
- p = Remote(host, ssh_key=ssh_key)
229
+ p = RemoteProbe(host, ssh_key=ssh_key)
218
230
 
219
231
  applied = 0
220
232
 
@@ -321,6 +333,30 @@ def _update_kanshi_from_cfg(console, p, dsi_outputs: list):
321
333
  p.run("pkill -SIGUSR1 kanshi 2>/dev/null || true")
322
334
 
323
335
 
336
+ def _execute_query_device_map(console, dm, query_expr, output_fmt):
337
+ """Execute JMESPath query on DeviceMap model and output result."""
338
+ import jmespath
339
+ import json as _json
340
+
341
+ data = dm.model_dump(mode="json")
342
+
343
+ try:
344
+ result = jmespath.search(query_expr, data)
345
+ except jmespath.exceptions.JMESPathError as e:
346
+ console.print(f"[red]✗ JMESPath error:[/red] {e}")
347
+ sys.exit(1)
348
+
349
+ if result is None:
350
+ console.print("[dim]No match found for query[/dim]")
351
+ return
352
+
353
+ if output_fmt == "json":
354
+ click.echo(_json.dumps(result, indent=2, default=str))
355
+ else:
356
+ import yaml
357
+ click.echo(yaml.safe_dump(result, sort_keys=False, default_flow_style=False))
358
+
359
+
324
360
  # ── rendering helpers ─────────────────────────────────────────────────────────
325
361
 
326
362
 
@@ -254,131 +254,6 @@ def _apply_transform(console, p, hw, transform):
254
254
  )
255
255
 
256
256
 
257
- def _apply_config(console, p, config_path):
258
- """Apply hardware settings from a YAML/JSON config file to the remote host.
259
-
260
- Reads drm_outputs[].transform entries and applies them via wlr-randr + kanshi.
261
- Also reads backlights[].brightness / bl_power for backlight settings.
262
- """
263
- import yaml
264
- import json as _json
265
-
266
- with open(config_path) as f:
267
- raw = f.read()
268
-
269
- try:
270
- cfg = yaml.safe_load(raw)
271
- except Exception:
272
- cfg = _json.loads(raw)
273
-
274
- applied = 0
275
-
276
- # ── display transforms ────────────────────────────────────────────────────
277
- for output in cfg.get("drm_outputs", []):
278
- connector = output.get("connector", "")
279
- transform = output.get("transform", "normal")
280
- if not connector:
281
- continue
282
- if "DSI" not in connector and "HDMI" not in connector:
283
- continue
284
-
285
- # skip defaults we don't need to touch
286
- if transform == "normal" and output.get("enabled") in (None, "enabled"):
287
- console.print(f"[dim] skip {connector}: transform=normal (default)[/dim]")
288
- continue
289
-
290
- console.print(f"[cyan]→ {connector}: transform={transform}[/cyan]")
291
-
292
- # Apply via wlr-randr
293
- wlr_cmd = (
294
- f"WAYLAND_DISPLAY=wayland-0 XDG_RUNTIME_DIR=/run/user/$(id -u) "
295
- f"wlr-randr --output {connector} --transform {transform} 2>&1"
296
- )
297
- r = p.run(wlr_cmd)
298
- if r.ok:
299
- console.print(f"[green] ✓ wlr-randr applied[/green]")
300
- else:
301
- console.print(f"[yellow] ⚠ wlr-randr: {r.out.strip() or 'no output'}[/yellow]")
302
-
303
- applied += 1
304
-
305
- # ── persist all DSI transforms in kanshi ─────────────────────────────────
306
- dsi_outputs = [
307
- o for o in cfg.get("drm_outputs", [])
308
- if "DSI" in o.get("connector", "")
309
- ]
310
- if dsi_outputs:
311
- _update_kanshi_from_cfg(console, p, dsi_outputs)
312
-
313
- # ── backlight ─────────────────────────────────────────────────────────────
314
- for bl in cfg.get("backlights", []):
315
- name = bl.get("name", "")
316
- brightness = bl.get("brightness")
317
- bl_power = bl.get("bl_power")
318
- if not name:
319
- continue
320
- if brightness is not None:
321
- r = p.run(f"echo {brightness} | sudo tee /sys/class/backlight/{name}/brightness > /dev/null")
322
- if r.ok:
323
- console.print(f"[green] ✓ backlight {name}: brightness={brightness}[/green]")
324
- if bl_power is not None:
325
- r = p.run(f"echo {bl_power} | sudo tee /sys/class/backlight/{name}/bl_power > /dev/null")
326
- if r.ok:
327
- console.print(f"[green] ✓ backlight {name}: bl_power={bl_power}[/green]")
328
- applied += 1
329
-
330
- if applied == 0:
331
- console.print("[yellow]Nothing to apply — no relevant settings found in config[/yellow]")
332
- else:
333
- console.print(f"\n[bold green]✓ Config applied from {config_path}[/bold green]")
334
-
335
-
336
- def _update_kanshi_from_cfg(console, p, dsi_outputs: list):
337
- """Rebuild kanshi profile from drm_outputs list and write to ~/.config/kanshi/config."""
338
- import re as _re
339
-
340
- kanshi_cfg_path = "~/.config/kanshi/config"
341
- read_r = p.run(f"cat {kanshi_cfg_path} 2>/dev/null")
342
- current = read_r.out if read_r.ok else ""
343
-
344
- for output in dsi_outputs:
345
- connector = output.get("connector", "")
346
- transform = output.get("transform", "normal")
347
-
348
- output_line_pat = _re.compile(
349
- rf'(\s*output\s+{_re.escape(connector)}\s+enable)(\s+transform\s+\S+)?'
350
- )
351
- if _re.search(output_line_pat, current):
352
- if transform == "normal":
353
- # Remove transform clause
354
- current = _re.sub(output_line_pat, r'\1', current)
355
- else:
356
- current = _re.sub(output_line_pat, rf'\1 transform {transform}', current)
357
- elif current.strip():
358
- # output line exists but without "enable"
359
- current = _re.sub(
360
- rf'(\s*output\s+{_re.escape(connector)}\b)',
361
- rf'\1 transform {transform}' if transform != "normal" else r'\1',
362
- current,
363
- )
364
- else:
365
- # No config at all — create minimal profile
366
- current = (
367
- f"profile waveshare-only {{\n"
368
- f" output {connector} enable"
369
- + (f" transform {transform}" if transform != "normal" else "")
370
- + "\n"
371
- + " output HDMI-A-2 disable\n"
372
- + "}\n"
373
- )
374
-
375
- escaped = current.replace("'", "'\\''")
376
- write_r = p.run(f"mkdir -p ~/.config/kanshi && printf '%s' '{escaped}' > {kanshi_cfg_path}")
377
- if write_r.ok:
378
- console.print(f"[green] ✓ kanshi config updated ({kanshi_cfg_path})[/green]")
379
- p.run("pkill -SIGUSR1 kanshi 2>/dev/null || true")
380
-
381
-
382
257
  def _execute_query(hw, query_expr, output_fmt):
383
258
  """Execute JMESPath query on hardware model and output result."""
384
259
  import jmespath
@@ -551,7 +426,8 @@ def hardware(host, output_fmt, show_fix, apply_fix_component, panel_id, list_pan
551
426
 
552
427
  # --apply-config: load YAML/JSON file and push settings to device
553
428
  if apply_config is not None:
554
- _apply_config(console, p, apply_config)
429
+ from ...config_apply import apply_config_file
430
+ apply_config_file(apply_config, host=host, ssh_key=ssh_key, console=console)
555
431
  return
556
432
 
557
433
  # --set-transform: apply rotation immediately and update kanshi config
@@ -0,0 +1,15 @@
1
+ """Shared config-apply layer for ``--apply-config`` across CLI commands.
2
+
3
+ Unifies the duplicated load-host-connect-apply logic found in
4
+ ``hardware``, ``device-map``, and ``blueprint`` commands.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ from .applier import apply_config_dict, apply_config_file
9
+ from .loader import load_config_file
10
+
11
+ __all__ = [
12
+ "apply_config_dict",
13
+ "apply_config_file",
14
+ "load_config_file",
15
+ ]
@@ -0,0 +1,72 @@
1
+ """Apply a declarative config dict or file to a remote host."""
2
+ from __future__ import annotations
3
+
4
+ from pathlib import Path
5
+
6
+ from rich.console import Console
7
+
8
+ from ..apply.state_apply import apply_state, ApplyResult
9
+ from ..detect.remote import RemoteProbe
10
+ from .loader import load_config_file
11
+
12
+
13
+ def _normalize_hardware(data: dict) -> dict:
14
+ """Flatten nested hardware configs for state_apply handlers.
15
+
16
+ DeviceMap and DeviceBlueprint nest hardware under a ``hardware`` key,
17
+ while ``redeploy hardware`` produces a flat file. This helper makes
18
+ both shapes palatable for :func:`apply_state`.
19
+ """
20
+ if "drm_outputs" in data or "backlights" in data:
21
+ return data
22
+ hw = data.get("hardware")
23
+ if isinstance(hw, dict):
24
+ return hw
25
+ return data
26
+
27
+
28
+ def apply_config_dict(data: dict, probe: RemoteProbe, console: Console) -> ApplyResult:
29
+ """Apply *data* to the host behind *probe*.
30
+
31
+ Delegates to :func:`redeploy.apply.state_apply.apply_state` after
32
+ normalising nested ``hardware`` sections.
33
+ """
34
+ normalized = _normalize_hardware(data)
35
+ return apply_state(normalized, probe, console)
36
+
37
+
38
+ def apply_config_file(
39
+ path: str | Path,
40
+ *,
41
+ host: str | None = None,
42
+ ssh_key: str | None = None,
43
+ console: Console | None = None,
44
+ ) -> ApplyResult:
45
+ """Load *path* and apply its hardware/infra settings to the remote host.
46
+
47
+ Parameters
48
+ ----------
49
+ path:
50
+ Local YAML/JSON file (DeviceMap, DeviceBlueprint, or flat hardware dump).
51
+ host:
52
+ Optional SSH target. Falls back to ``data["host"]``.
53
+ ssh_key:
54
+ Private-key path for the SSH connection.
55
+ console:
56
+ Rich console for progress output. Created automatically when ``None``.
57
+
58
+ Raises
59
+ ------
60
+ ValueError
61
+ If no host can be determined (neither *host* nor ``data["host"]``).
62
+ """
63
+ data = load_config_file(path)
64
+ _host = host or data.get("host")
65
+ if not _host:
66
+ raise ValueError("No host specified and no 'host' field in config file")
67
+
68
+ if console is None:
69
+ console = Console()
70
+
71
+ probe = RemoteProbe(_host, ssh_key=ssh_key)
72
+ return apply_config_dict(data, probe, console)
@@ -0,0 +1,17 @@
1
+ """Load a declarative config file (YAML or JSON)."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ from pathlib import Path
6
+
7
+ import yaml
8
+
9
+
10
+ def load_config_file(path: str | Path) -> dict:
11
+ """Read *path* and return a dict (YAML or JSON auto-detected)."""
12
+ path = Path(path)
13
+ raw = path.read_text(encoding="utf-8")
14
+ try:
15
+ return yaml.safe_load(raw) or {}
16
+ except Exception:
17
+ return json.loads(raw)
@@ -0,0 +1 @@
1
+ """Redeploy integrations."""
@@ -0,0 +1,116 @@
1
+ """Bridge between redeploy and op3.
2
+
3
+ All adapters go through here. This module isolates op3 usage so that redeploy
4
+ remains runnable when op3 is not installed.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import os
9
+ from typing import TYPE_CHECKING
10
+
11
+ if TYPE_CHECKING:
12
+ from opstree.probes.context import SSHContext
13
+ from opstree.snapshot.model import Snapshot
14
+ from redeploy.models import InfraState, HardwareInfo
15
+
16
+ OP3_ENABLED_ENV = "REDEPLOY_USE_OP3"
17
+
18
+
19
+ def op3_enabled() -> bool:
20
+ """Check whether the user wants to use op3."""
21
+ return os.environ.get(OP3_ENABLED_ENV, "0").lower() in ("1", "true", "yes")
22
+
23
+
24
+ def op3_available() -> bool:
25
+ """Check whether op3 is installed."""
26
+ try:
27
+ import opstree # noqa: F401
28
+ return True
29
+ except ImportError:
30
+ return False
31
+
32
+
33
+ def should_use_op3() -> bool:
34
+ """Use op3 only when both flag is on and library is available."""
35
+ return op3_enabled() and op3_available()
36
+
37
+
38
+ def make_op3_context_from_ssh_client(ssh_client) -> "SSHContext":
39
+ """Convert redeploy.SshClient -> opstree.SSHContext."""
40
+ from opstree.probes.context import SSHContext
41
+
42
+ return SSHContext(
43
+ target=ssh_client.target,
44
+ host=ssh_client.host,
45
+ user=ssh_client.user,
46
+ ssh_key=ssh_client.ssh_key,
47
+ )
48
+
49
+
50
+ def snapshot_to_infra_state(snapshot: "Snapshot") -> "InfraState":
51
+ """Convert opstree.Snapshot -> redeploy.InfraState (backward compat)."""
52
+ from redeploy.models import InfraState
53
+
54
+ runtime_data = snapshot.layer("runtime.container")
55
+ service_data = snapshot.layer("service.containers")
56
+ endpoint_data = snapshot.layer("endpoint.http")
57
+
58
+ # TODO: full mapping once layer schemas stabilise
59
+ return InfraState(
60
+ runtime=runtime_data.data if runtime_data else {},
61
+ services=service_data.data if service_data else {},
62
+ endpoints=endpoint_data.data if endpoint_data else {},
63
+ )
64
+
65
+
66
+ def snapshot_to_hardware_info(snapshot: "Snapshot") -> "HardwareInfo":
67
+ """Convert opstree.Snapshot -> redeploy.HardwareInfo."""
68
+ from redeploy.models import HardwareInfo, DrmOutput, BacklightInfo
69
+
70
+ physical = snapshot.layer("physical.display")
71
+ os_kernel = snapshot.layer("os.kernel")
72
+ os_config = snapshot.layer("os.config")
73
+
74
+ if physical is None:
75
+ return HardwareInfo()
76
+
77
+ # Map DRM outputs (op3 field names differ slightly from redeploy)
78
+ drm_raw = physical.data.get("drm_outputs", [])
79
+ drm_outputs = []
80
+ for d in drm_raw:
81
+ drm_outputs.append(DrmOutput(
82
+ name=d.get("name", ""),
83
+ connector=d.get("connector", ""),
84
+ status=d.get("status", "unknown"),
85
+ enabled=d.get("enabled", "unknown"),
86
+ modes=d.get("modes", []),
87
+ transform=d.get("transform", "normal"),
88
+ position=d.get("position", "0,0"),
89
+ scale=d.get("scale", "1.0"),
90
+ edid_bytes=d.get("edid_bytes", 0),
91
+ power_state=d.get("dpms"),
92
+ sysfs_path=f"/sys/class/drm/{d.get('name', '')}",
93
+ ))
94
+
95
+ # Map backlights
96
+ bl_raw = physical.data.get("backlights", [])
97
+ backlights = []
98
+ for b in bl_raw:
99
+ backlights.append(BacklightInfo(
100
+ name=b.get("name", ""),
101
+ brightness=b.get("brightness", 0),
102
+ max_brightness=b.get("max_brightness", 255),
103
+ bl_power=b.get("bl_power", 0),
104
+ display_name=b.get("display_name"),
105
+ sysfs_path=f"/sys/class/backlight/{b.get('name', '')}",
106
+ ))
107
+
108
+ return HardwareInfo(
109
+ board=physical.data.get("board_model"),
110
+ kernel=os_kernel.data.get("version") if os_kernel else None,
111
+ config_txt=os_config.data.get("config_txt", "") if os_config else "",
112
+ config_txt_path=os_config.data.get("config_txt_path", "/boot/firmware/config.txt") if os_config else "/boot/firmware/config.txt",
113
+ drm_outputs=drm_outputs,
114
+ backlights=backlights,
115
+ framebuffers=physical.data.get("framebuffers", []),
116
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: redeploy
3
- Version: 0.2.43
3
+ Version: 0.2.45
4
4
  Summary: Infrastructure migration toolkit: detect → plan → apply
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -22,23 +22,25 @@ Requires-Dist: ruff>=0.4; extra == "dev"
22
22
  Requires-Dist: goal>=2.1.0; extra == "dev"
23
23
  Requires-Dist: costs>=0.1.20; extra == "dev"
24
24
  Requires-Dist: pfix>=0.1.60; extra == "dev"
25
+ Provides-Extra: op3
26
+ Requires-Dist: op3>=0.1.4; extra == "op3"
25
27
 
26
28
  # redeploy
27
29
 
28
30
 
29
31
  ## AI Cost Tracking
30
32
 
31
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.43-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
32
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-21.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.45-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-23.2h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
33
35
 
34
- - 🤖 **LLM usage:** $7.5000 (64 commits)
35
- - 👤 **Human dev:** ~$2099 (21.0h @ $100/h, 30min dedup)
36
+ - 🤖 **LLM usage:** $7.5000 (66 commits)
37
+ - 👤 **Human dev:** ~$2318 (23.2h @ $100/h, 30min dedup)
36
38
 
37
39
  Generated on 2026-04-21 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
38
40
 
39
41
  ---
40
42
 
41
- ![PyPI](https://img.shields.io/badge/pypi-redeploy-blue) ![Version](https://img.shields.io/badge/version-0.2.43-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.45-blue) ![Python](https://img.shields.io/badge/python-3.10+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
42
44
 
43
45
  Infrastructure migration and device deploy toolkit — VPS, Raspberry Pi kiosk, Podman Quadlet, k3s.
44
46
 
@@ -68,6 +68,9 @@ redeploy/cli/commands/version/helpers.py
68
68
  redeploy/cli/commands/version/monorepo.py
69
69
  redeploy/cli/commands/version/release.py
70
70
  redeploy/cli/commands/version/scanner.py
71
+ redeploy/config_apply/__init__.py
72
+ redeploy/config_apply/applier.py
73
+ redeploy/config_apply/loader.py
71
74
  redeploy/detect/__init__.py
72
75
  redeploy/detect/detector.py
73
76
  redeploy/detect/hardware.py
@@ -107,6 +110,8 @@ redeploy/iac/docker_compose.py
107
110
  redeploy/iac/registry.py
108
111
  redeploy/iac/parsers/__init__.py
109
112
  redeploy/iac/parsers/compose.py
113
+ redeploy/integrations/__init__.py
114
+ redeploy/integrations/op3_bridge.py
110
115
  redeploy/markpact/__init__.py
111
116
  redeploy/markpact/compiler.py
112
117
  redeploy/markpact/models.py
@@ -17,3 +17,6 @@ ruff>=0.4
17
17
  goal>=2.1.0
18
18
  costs>=0.1.20
19
19
  pfix>=0.1.60
20
+
21
+ [op3]
22
+ op3>=0.1.4
File without changes