redeploy 0.2.28__tar.gz → 0.2.30__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 (140) hide show
  1. {redeploy-0.2.28 → redeploy-0.2.30}/PKG-INFO +6 -6
  2. {redeploy-0.2.28 → redeploy-0.2.30}/README.md +5 -5
  3. {redeploy-0.2.28 → redeploy-0.2.30}/pyproject.toml +1 -1
  4. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/__init__.py +1 -1
  5. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/commands/devices.py +14 -4
  6. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/discovery.py +44 -3
  7. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy.egg-info/PKG-INFO +6 -6
  8. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/apply/__init__.py +0 -0
  9. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/apply/exceptions.py +0 -0
  10. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/apply/executor.py +0 -0
  11. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/apply/handlers.py +0 -0
  12. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/apply/progress.py +0 -0
  13. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/apply/rollback.py +0 -0
  14. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/apply/state.py +0 -0
  15. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/audit.py +0 -0
  16. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/__init__.py +0 -0
  17. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/commands/__init__.py +0 -0
  18. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/commands/audit.py +0 -0
  19. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/commands/detect.py +0 -0
  20. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/commands/diagnose.py +0 -0
  21. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/commands/diff.py +0 -0
  22. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/commands/exec_.py +0 -0
  23. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/commands/export.py +0 -0
  24. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/commands/import_.py +0 -0
  25. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/commands/init.py +0 -0
  26. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/commands/inspect.py +0 -0
  27. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/commands/patterns.py +0 -0
  28. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/commands/plan_apply.py +0 -0
  29. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/commands/plugin.py +0 -0
  30. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/commands/probe.py +0 -0
  31. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/commands/state.py +0 -0
  32. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/commands/status.py +0 -0
  33. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/commands/target.py +0 -0
  34. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/commands/version.py +0 -0
  35. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/commands/workflow.py +0 -0
  36. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/core.py +0 -0
  37. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/display.py +0 -0
  38. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli.py +0 -0
  39. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/data_sync.py +0 -0
  40. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/detect/__init__.py +0 -0
  41. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/detect/detector.py +0 -0
  42. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/detect/probes.py +0 -0
  43. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/detect/remote.py +0 -0
  44. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/detect/templates.py +0 -0
  45. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/detect/workflow.py +0 -0
  46. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/dsl/__init__.py +0 -0
  47. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/dsl/loader.py +0 -0
  48. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/dsl/parser.py +0 -0
  49. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/dsl_python/__init__.py +0 -0
  50. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/dsl_python/context.py +0 -0
  51. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/dsl_python/decorators.py +0 -0
  52. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/dsl_python/docker_steps.py +0 -0
  53. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/dsl_python/exceptions.py +0 -0
  54. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/dsl_python/runner.py +0 -0
  55. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/dsl_python/steps.py +0 -0
  56. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/fleet.py +0 -0
  57. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/iac/__init__.py +0 -0
  58. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/iac/base.py +0 -0
  59. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/iac/docker_compose.py +0 -0
  60. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/iac/parsers/__init__.py +0 -0
  61. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/iac/parsers/compose.py +0 -0
  62. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/iac/registry.py +0 -0
  63. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/markpact/__init__.py +0 -0
  64. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/markpact/compiler.py +0 -0
  65. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/markpact/models.py +0 -0
  66. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/markpact/parser.py +0 -0
  67. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/models.py +0 -0
  68. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/observe.py +0 -0
  69. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/parse.py +0 -0
  70. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/patterns.py +0 -0
  71. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/plan/__init__.py +0 -0
  72. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/plan/planner.py +0 -0
  73. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/plugins/__init__.py +0 -0
  74. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/plugins/builtin/__init__.py +0 -0
  75. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/plugins/builtin/browser_reload.py +0 -0
  76. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/plugins/builtin/notify.py +0 -0
  77. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/plugins/builtin/systemd_reload.py +0 -0
  78. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/spec_loader.py +0 -0
  79. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/ssh.py +0 -0
  80. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/steps.py +0 -0
  81. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/__init__.py +0 -0
  82. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_audit.py +0 -0
  83. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_cli.py +0 -0
  84. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_data_sync.py +0 -0
  85. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_discovery.py +0 -0
  86. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_dsl.py +0 -0
  87. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_examples.py +0 -0
  88. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_executor.py +0 -0
  89. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_fleet.py +0 -0
  90. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_git_integration.py +0 -0
  91. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_iac.py +0 -0
  92. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_manifest.py +0 -0
  93. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_markpact_compiler.py +0 -0
  94. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_markpact_parser.py +0 -0
  95. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_models.py +0 -0
  96. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_observe.py +0 -0
  97. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_parse.py +0 -0
  98. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_patterns.py +0 -0
  99. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_planner_edge.py +0 -0
  100. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_plugins.py +0 -0
  101. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_probes.py +0 -0
  102. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_public_api.py +0 -0
  103. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_resume.py +0 -0
  104. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_spec_loader.py +0 -0
  105. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_ssh.py +0 -0
  106. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_templates.py +0 -0
  107. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_verify.py +0 -0
  108. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_version.py +0 -0
  109. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_version_changelog.py +0 -0
  110. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_version_cli.py +0 -0
  111. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_version_manifest.py +0 -0
  112. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_version_sources.py +0 -0
  113. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_workflow.py +0 -0
  114. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/verify.py +0 -0
  115. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/version/__init__.py +0 -0
  116. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/version/bump.py +0 -0
  117. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/version/changelog.py +0 -0
  118. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/version/commits.py +0 -0
  119. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/version/diff.py +0 -0
  120. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/version/git_integration.py +0 -0
  121. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/version/git_transaction.py +0 -0
  122. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/version/manifest.py +0 -0
  123. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/version/sources/__init__.py +0 -0
  124. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/version/sources/base.py +0 -0
  125. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/version/sources/json_.py +0 -0
  126. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/version/sources/plain.py +0 -0
  127. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/version/sources/regex.py +0 -0
  128. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/version/sources/toml_.py +0 -0
  129. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/version/sources/yaml_.py +0 -0
  130. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/version/transaction.py +0 -0
  131. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/version.py +0 -0
  132. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy.egg-info/SOURCES.txt +0 -0
  133. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy.egg-info/dependency_links.txt +0 -0
  134. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy.egg-info/entry_points.txt +0 -0
  135. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy.egg-info/requires.txt +0 -0
  136. {redeploy-0.2.28 → redeploy-0.2.30}/redeploy.egg-info/top_level.txt +0 -0
  137. {redeploy-0.2.28 → redeploy-0.2.30}/setup.cfg +0 -0
  138. {redeploy-0.2.28 → redeploy-0.2.30}/tests/test_iac.py +0 -0
  139. {redeploy-0.2.28 → redeploy-0.2.30}/tests/test_parse.py +0 -0
  140. {redeploy-0.2.28 → redeploy-0.2.30}/tests/test_redeploy.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: redeploy
3
- Version: 0.2.28
3
+ Version: 0.2.30
4
4
  Summary: Infrastructure migration toolkit: detect → plan → apply
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -27,17 +27,17 @@ Requires-Dist: pfix>=0.1.60; extra == "dev"
27
27
 
28
28
  ## AI Cost Tracking
29
29
 
30
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.28-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
31
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.20-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-14.5h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
30
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.30-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
31
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-15.0h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
32
32
 
33
- - 🤖 **LLM usage:** $7.2000 (48 commits)
34
- - 👤 **Human dev:** ~$1450 (14.5h @ $100/h, 30min dedup)
33
+ - 🤖 **LLM usage:** $7.5000 (50 commits)
34
+ - 👤 **Human dev:** ~$1505 (15.0h @ $100/h, 30min dedup)
35
35
 
36
36
  Generated on 2026-04-21 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
37
37
 
38
38
  ---
39
39
 
40
- ![PyPI](https://img.shields.io/badge/pypi-redeploy-blue) ![Version](https://img.shields.io/badge/version-0.2.28-blue) ![Python](https://img.shields.io/badge/python-3.10+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
40
+ ![PyPI](https://img.shields.io/badge/pypi-redeploy-blue) ![Version](https://img.shields.io/badge/version-0.2.30-blue) ![Python](https://img.shields.io/badge/python-3.10+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
41
41
 
42
42
  Infrastructure migration and device deploy toolkit — VPS, Raspberry Pi kiosk, Podman Quadlet, k3s.
43
43
 
@@ -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.28-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.20-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-14.5h-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.30-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-15.0h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
8
8
 
9
- - 🤖 **LLM usage:** $7.2000 (48 commits)
10
- - 👤 **Human dev:** ~$1450 (14.5h @ $100/h, 30min dedup)
9
+ - 🤖 **LLM usage:** $7.5000 (50 commits)
10
+ - 👤 **Human dev:** ~$1505 (15.0h @ $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.28-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.30-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.28"
7
+ version = "0.2.30"
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.28"
10
+ __version__ = "0.2.30"
11
11
 
12
12
  # ── Core models ───────────────────────────────────────────────────────────────
13
13
  from .models import ( # noqa: F401
@@ -13,9 +13,10 @@ from rich.table import Table
13
13
  @click.command()
14
14
  @click.option("--tag", default=None, help="Filter by tag")
15
15
  @click.option("--strategy", default=None, help="Filter by strategy")
16
+ @click.option("--rpi", is_flag=True, help="Show only Raspberry Pi devices")
16
17
  @click.option("--reachable", is_flag=True, help="Show only recently-seen devices")
17
18
  @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
18
- def devices(tag, strategy, reachable, as_json):
19
+ def devices(tag, strategy, rpi, reachable, as_json):
19
20
  """List known devices from ~/.config/redeploy/devices.yaml.
20
21
 
21
22
  \b
@@ -35,6 +36,8 @@ def devices(tag, strategy, reachable, as_json):
35
36
  devs = [d for d in devs if tag in d.tags]
36
37
  if strategy:
37
38
  devs = [d for d in devs if d.strategy == strategy]
39
+ if rpi:
40
+ devs = [d for d in devs if "raspberry-pi" in d.tags]
38
41
  if reachable:
39
42
  devs = [d for d in devs if d.is_reachable]
40
43
 
@@ -64,12 +67,16 @@ def devices(tag, strategy, reachable, as_json):
64
67
  for d in devs:
65
68
  seen = d.last_seen.strftime("%m-%d %H:%M") if d.last_seen else "never"
66
69
  ssh = "[green]✓[/green]" if d.last_ssh_ok else "[red]✗[/red]"
70
+ tags_str = ",".join(d.tags) or "—"
71
+ # Highlight raspberry-pi tag
72
+ if "raspberry-pi" in d.tags:
73
+ tags_str = tags_str.replace("raspberry-pi", "[bold magenta]raspberry-pi[/bold magenta]")
67
74
  t.add_row(
68
75
  d.id,
69
76
  d.host,
70
77
  d.strategy,
71
78
  d.app or "—",
72
- ",".join(d.tags) or "—",
79
+ tags_str,
73
80
  seen,
74
81
  ssh,
75
82
  )
@@ -130,17 +137,20 @@ def scan(subnet, ssh_users, ssh_port, ping, no_mdns, timeout, no_save):
130
137
  )
131
138
 
132
139
  ssh_ok = [h for h in found if h.ssh_ok]
133
- console.print(f" found {len(found)} host(s), {len(ssh_ok)} SSH-accessible\n")
140
+ rpi_count = sum(1 for h in found if h.is_raspberry_pi)
141
+ console.print(f" found {len(found)} host(s), {len(ssh_ok)} SSH-accessible, {rpi_count} Raspberry Pi\n")
134
142
 
135
143
  t = Table(show_header=True, box=None, padding=(0, 2))
136
144
  t.add_column("IP")
137
145
  t.add_column("Hostname", style="dim")
138
146
  t.add_column("MAC", style="dim")
139
147
  t.add_column("SSH user", style="cyan")
148
+ t.add_column("RPi", style="bold magenta")
140
149
  t.add_column("Source", style="dim")
141
150
  for h in found:
142
151
  ssh_col = f"[green]{h.ssh_user}[/green]" if h.ssh_ok else "[red]✗[/red]"
143
- t.add_row(h.ip, h.hostname or "—", h.mac or "—", ssh_col, h.source)
152
+ rpi_col = "🍓" if h.is_raspberry_pi else "—"
153
+ t.add_row(h.ip, h.hostname or "—", h.mac or "—", ssh_col, rpi_col, h.source)
144
154
  console.print(t)
145
155
 
146
156
  if not no_save and ssh_ok:
@@ -38,6 +38,24 @@ class DiscoveredHost:
38
38
  ssh_user: str = ""
39
39
  source: str = "unknown"
40
40
  ports_open: list[int] = field(default_factory=list)
41
+ is_raspberry_pi: bool = False
42
+
43
+
44
+ # ── Raspberry Pi identification ─────────────────────────────────────────────────
45
+
46
+ _RPI_MAC_PREFIXES = [
47
+ "b8:27:eb", # Raspberry Pi Foundation
48
+ "dc:a6:32", # Raspberry Pi Trading
49
+ "e4:5f:01", # Raspberry Pi Trading
50
+ "28:cd:c1", # Raspberry Pi 5
51
+ ]
52
+
53
+ def _is_raspberry_pi_mac(mac: str) -> bool:
54
+ """Check if MAC address belongs to Raspberry Pi."""
55
+ if not mac:
56
+ return False
57
+ mac_prefix = mac[:8].lower()
58
+ return mac_prefix in _RPI_MAC_PREFIXES
41
59
 
42
60
 
43
61
  # ── Individual scanners ───────────────────────────────────────────────────────
@@ -89,7 +107,12 @@ def _scan_arp_cache() -> list[DiscoveredHost]:
89
107
  parts = line.split()
90
108
  if len(parts) >= 5 and re.match(r"\d+\.\d+\.\d+\.\d+", parts[0]):
91
109
  mac = parts[4] if len(parts) > 4 else ""
92
- results.append(DiscoveredHost(ip=parts[0], mac=mac, source="arp"))
110
+ results.append(DiscoveredHost(
111
+ ip=parts[0],
112
+ mac=mac,
113
+ source="arp",
114
+ is_raspberry_pi=_is_raspberry_pi_mac(mac)
115
+ ))
93
116
  if results:
94
117
  logger.debug(f"arp (ip neigh): {len(results)} hosts")
95
118
  return results
@@ -101,7 +124,13 @@ def _scan_arp_cache() -> list[DiscoveredHost]:
101
124
  # ? (192.168.1.1) at aa:bb:cc:dd:ee:ff [ether] on eth0
102
125
  m = re.search(r"\((\d+\.\d+\.\d+\.\d+)\).*?([0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f:]+)", line, re.I)
103
126
  if m:
104
- results.append(DiscoveredHost(ip=m.group(1), mac=m.group(2), source="arp"))
127
+ mac = m.group(2)
128
+ results.append(DiscoveredHost(
129
+ ip=m.group(1),
130
+ mac=mac,
131
+ source="arp",
132
+ is_raspberry_pi=_is_raspberry_pi_mac(mac)
133
+ ))
105
134
  logger.debug(f"arp: {len(results)} hosts")
106
135
  return results
107
136
 
@@ -250,6 +279,8 @@ def _merge(hosts: list[DiscoveredHost]) -> list[DiscoveredHost]:
250
279
  existing.ssh_user = existing.ssh_user or h.ssh_user
251
280
  if existing.source == "unknown":
252
281
  existing.source = h.source
282
+ if h.is_raspberry_pi:
283
+ existing.is_raspberry_pi = True
253
284
  else:
254
285
  by_ip[h.ip] = h
255
286
  return list(by_ip.values())
@@ -302,6 +333,11 @@ def discover(
302
333
 
303
334
  found = _merge(found)
304
335
 
336
+ # Log Raspberry Pi devices
337
+ rpi_devices = [h for h in found if h.is_raspberry_pi]
338
+ if rpi_devices:
339
+ logger.info(f"Found {len(rpi_devices)} Raspberry Pi device(s): {', '.join(h.ip for h in rpi_devices)}")
340
+
305
341
  # SSH probe
306
342
  if probe_ssh and found:
307
343
  logger.info(f"SSH probe: {len(found)} hosts ({timeout}s timeout each)")
@@ -339,9 +375,14 @@ def update_registry(
339
375
  existing.hostname = h.hostname
340
376
  if h.ssh_ok:
341
377
  existing.last_ssh_ok = now
378
+ if h.is_raspberry_pi and "raspberry-pi" not in existing.tags:
379
+ existing.tags.append("raspberry-pi")
342
380
  reg.upsert(existing)
343
381
  elif h.ssh_ok:
344
382
  # Only auto-add SSH-accessible devices
383
+ tags = ["discovered"]
384
+ if h.is_raspberry_pi:
385
+ tags.append("raspberry-pi")
345
386
  reg.upsert(KnownDevice(
346
387
  id=device_id,
347
388
  host=device_id,
@@ -352,7 +393,7 @@ def update_registry(
352
393
  last_seen=now,
353
394
  last_ssh_ok=now,
354
395
  source=h.source,
355
- tags=["discovered"],
396
+ tags=tags,
356
397
  ))
357
398
 
358
399
  if save:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: redeploy
3
- Version: 0.2.28
3
+ Version: 0.2.30
4
4
  Summary: Infrastructure migration toolkit: detect → plan → apply
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -27,17 +27,17 @@ Requires-Dist: pfix>=0.1.60; extra == "dev"
27
27
 
28
28
  ## AI Cost Tracking
29
29
 
30
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.28-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
31
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.20-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-14.5h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
30
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.30-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
31
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-15.0h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
32
32
 
33
- - 🤖 **LLM usage:** $7.2000 (48 commits)
34
- - 👤 **Human dev:** ~$1450 (14.5h @ $100/h, 30min dedup)
33
+ - 🤖 **LLM usage:** $7.5000 (50 commits)
34
+ - 👤 **Human dev:** ~$1505 (15.0h @ $100/h, 30min dedup)
35
35
 
36
36
  Generated on 2026-04-21 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
37
37
 
38
38
  ---
39
39
 
40
- ![PyPI](https://img.shields.io/badge/pypi-redeploy-blue) ![Version](https://img.shields.io/badge/version-0.2.28-blue) ![Python](https://img.shields.io/badge/python-3.10+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
40
+ ![PyPI](https://img.shields.io/badge/pypi-redeploy-blue) ![Version](https://img.shields.io/badge/version-0.2.30-blue) ![Python](https://img.shields.io/badge/python-3.10+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
41
41
 
42
42
  Infrastructure migration and device deploy toolkit — VPS, Raspberry Pi kiosk, Podman Quadlet, k3s.
43
43
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes