redeploy 0.2.69__tar.gz → 0.2.71__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.
- {redeploy-0.2.69 → redeploy-0.2.71}/PKG-INFO +45 -6
- {redeploy-0.2.69 → redeploy-0.2.71}/README.md +44 -5
- {redeploy-0.2.69 → redeploy-0.2.71}/pyproject.toml +1 -1
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/__init__.py +1 -1
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/apply/handlers.py +8 -14
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/cli/__init__.py +2 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/cli/commands/exec_.py +4 -11
- redeploy-0.2.71/redeploy/cli/commands/gh_workflow.py +291 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/cli/commands/hardware.py +2 -68
- redeploy-0.2.71/redeploy/config_apply/handlers/__init__.py +10 -0
- redeploy-0.2.71/redeploy/config_apply/handlers/display.py +125 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/detect/probes.py +7 -1
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/heal/runner.py +19 -2
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/markpact/__init__.py +2 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/markpact/parser.py +30 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/mcp_server.py +42 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/tests/test_heal.py +56 -0
- redeploy-0.2.71/redeploy/tests/test_security_hardening.py +93 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy.egg-info/PKG-INFO +45 -6
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy.egg-info/SOURCES.txt +5 -0
- redeploy-0.2.71/tests/test_cli_gh_workflow.py +132 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/analyze/__init__.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/analyze/checkers/__init__.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/analyze/preflight_schema.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/analyze/spec_analyzer.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/apply/__init__.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/apply/exceptions.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/apply/executor.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/apply/progress.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/apply/rollback.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/apply/state.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/apply/state_apply.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/apply/utils/__init__.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/apply/utils/run_container_build.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/audit.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/blueprint/__init__.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/blueprint/extractor.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/blueprint/generators/__init__.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/blueprint/generators/docker_compose.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/blueprint/generators/migration.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/blueprint/sources/__init__.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/blueprint/sources/compose.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/blueprint/sources/hardware.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/blueprint/sources/infra.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/blueprint/sources/migration.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/cli/commands/__init__.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/cli/commands/audit.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/cli/commands/blueprint.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/cli/commands/bump_fix.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/cli/commands/detect.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/cli/commands/device_map.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/cli/commands/device_map_renderers.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/cli/commands/devices.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/cli/commands/diagnose.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/cli/commands/diff.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/cli/commands/export.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/cli/commands/import_.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/cli/commands/init.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/cli/commands/inspect.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/cli/commands/lint.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/cli/commands/mcp_cmd.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/cli/commands/patterns.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/cli/commands/plan_apply.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/cli/commands/plugin.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/cli/commands/probe.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/cli/commands/prompt_cmd.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/cli/commands/push.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/cli/commands/state.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/cli/commands/status.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/cli/commands/target.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/cli/commands/version/__init__.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/cli/commands/version/commands.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/cli/commands/version/helpers.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/cli/commands/version/monorepo.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/cli/commands/version/release.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/cli/commands/version/scanner.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/cli/commands/version/utils/__init__.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/cli/commands/version/utils/changelog_config.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/cli/commands/version/utils/git_config.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/cli/commands/workflow.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/cli/core.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/cli/display.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/cli/query.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/cli.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/config_apply/__init__.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/config_apply/applier.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/config_apply/loader.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/data_sync.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/detect/__init__.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/detect/builtin/__init__.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/detect/detector.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/detect/hardware.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/detect/hardware_rules.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/detect/remote.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/detect/templates.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/detect/workflow.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/discovery.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/dsl/__init__.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/dsl/loader.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/dsl/parser.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/dsl_python/__init__.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/dsl_python/context.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/dsl_python/decorators.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/dsl_python/docker_steps.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/dsl_python/exceptions.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/dsl_python/runner.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/dsl_python/steps.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/fleet.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/hardware/__init__.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/hardware/config_txt.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/hardware/data/__init__.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/hardware/data/hyperpixel.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/hardware/data/official.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/hardware/data/waveshare.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/hardware/fixes.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/hardware/kiosk/__init__.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/hardware/kiosk/autostart.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/hardware/kiosk/browsers.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/hardware/kiosk/compositors.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/hardware/kiosk/output_profiles.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/hardware/panels.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/hardware/raspi_config.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/heal/__init__.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/heal/decider.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/heal/hint_provider.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/heal/log_writer.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/heal/loop_detector.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/heal.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/iac/__init__.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/iac/base.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/iac/config_hints.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/iac/docker_compose.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/iac/parsers/__init__.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/iac/parsers/compose.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/iac/registry.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/integrations/__init__.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/integrations/op3_bridge.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/markpact/compiler.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/markpact/models.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/models/__init__.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/models/blueprint.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/models/devices.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/models/enums.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/models/hardware.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/models/infra.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/models/manifest.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/models/persisted.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/models/pipeline.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/models/plan.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/models/spec.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/observe.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/parse.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/patterns.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/plan/__init__.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/plan/planner.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/plugins/__init__.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/plugins/builtin/__init__.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/plugins/builtin/browser_reload.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/plugins/builtin/hardware_diagnostic.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/plugins/builtin/notify.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/plugins/builtin/process_control.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/plugins/builtin/systemd_reload.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/schema.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/spec_loader.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/ssh.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/steps/__init__.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/steps/docker.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/steps/generic.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/steps/hardware.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/steps/k3s.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/steps/kiosk.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/steps/podman.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/steps/process.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/steps/scm.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/steps/transfer.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/tests/__init__.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/tests/test_analyze.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/tests/test_audit.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/tests/test_cli.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/tests/test_data_sync.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/tests/test_discovery.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/tests/test_dsl.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/tests/test_examples.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/tests/test_executor.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/tests/test_fleet.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/tests/test_git_integration.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/tests/test_heal_decider.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/tests/test_hooks.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/tests/test_iac.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/tests/test_iac_compose_contract.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/tests/test_iac_docker_compose_contract.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/tests/test_manifest.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/tests/test_markpact_compiler.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/tests/test_markpact_parser.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/tests/test_models.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/tests/test_observe.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/tests/test_parse.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/tests/test_patterns.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/tests/test_planner_edge.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/tests/test_plugins.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/tests/test_probes.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/tests/test_public_api.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/tests/test_resume.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/tests/test_spec_loader.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/tests/test_ssh.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/tests/test_steps_registry.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/tests/test_templates.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/tests/test_verify.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/tests/test_version.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/tests/test_version_changelog.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/tests/test_version_cli.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/tests/test_version_manifest.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/tests/test_version_sources.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/tests/test_workflow.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/verify.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/version/__init__.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/version/bump.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/version/changelog.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/version/commits.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/version/diff.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/version/git_integration.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/version/git_transaction.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/version/manifest.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/version/sources/__init__.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/version/sources/base.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/version/sources/json_.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/version/sources/plain.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/version/sources/regex.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/version/sources/toml_.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/version/sources/yaml_.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/version/transaction.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy/version.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy.egg-info/dependency_links.txt +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy.egg-info/entry_points.txt +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy.egg-info/requires.txt +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/redeploy.egg-info/top_level.txt +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/setup.cfg +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/tests/test_blueprint_sources.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/tests/test_blueprint_sources_compose.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/tests/test_cli_query.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/tests/test_example_argocd_flux_plugin.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/tests/test_example_gitops_ci_plugin.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/tests/test_example_helm_kustomize_plugin.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/tests/test_hardware_config.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/tests/test_hardware_kiosk.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/tests/test_hardware_rules.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/tests/test_heal_decider.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/tests/test_heal_loop_detector.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/tests/test_iac.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/tests/test_iac_config_hints.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/tests/test_iac_plugin_loading.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/tests/test_import_plugin_template.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/tests/test_parse.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/tests/test_prompt_llm.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/tests/test_redeploy.py +0 -0
- {redeploy-0.2.69 → redeploy-0.2.71}/tests/test_steps_kiosk.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: redeploy
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.71
|
|
4
4
|
Summary: Infrastructure migration toolkit: detect → plan → apply
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -32,17 +32,17 @@ Requires-Dist: mcp>=1.0; extra == "mcp"
|
|
|
32
32
|
|
|
33
33
|
## AI Cost Tracking
|
|
34
34
|
|
|
35
|
-
     
|
|
36
|
+
  
|
|
37
37
|
|
|
38
|
-
- 🤖 **LLM usage:** $7.5000 (
|
|
39
|
-
- 👤 **Human dev:** ~$
|
|
38
|
+
- 🤖 **LLM usage:** $7.5000 (92 commits)
|
|
39
|
+
- 👤 **Human dev:** ~$3734 (37.3h @ $100/h, 30min dedup)
|
|
40
40
|
|
|
41
41
|
Generated on 2026-04-23 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
42
42
|
|
|
43
43
|
---
|
|
44
44
|
|
|
45
|
-
    
|
|
46
46
|
|
|
47
47
|
Infrastructure migration and device deploy toolkit — VPS, Raspberry Pi kiosk, Podman Quadlet, k3s.
|
|
48
48
|
|
|
@@ -57,6 +57,7 @@ redeploy prompt → NLP → command via LLM (natural language interface)
|
|
|
57
57
|
redeploy mcp → start MCP server (Claude Desktop / VS Code / remote API)
|
|
58
58
|
redeploy scan → find devices on LAN (device registry)
|
|
59
59
|
redeploy target → deploy to named device (fleet)
|
|
60
|
+
redeploy gh-workflow → analyze/run GitHub Actions workflows on demand
|
|
60
61
|
```
|
|
61
62
|
|
|
62
63
|
## Install
|
|
@@ -247,6 +248,44 @@ redeploy run --env rpi5 --detect # deploy to rpi5 with live probe
|
|
|
247
248
|
redeploy run --dry-run # uses .env DEPLOY_* vars if no redeploy.yaml
|
|
248
249
|
```
|
|
249
250
|
|
|
251
|
+
### `redeploy gh-workflow [list|analyze|run]`
|
|
252
|
+
|
|
253
|
+
Analyze and trigger GitHub Actions workflows from your repo on demand.
|
|
254
|
+
|
|
255
|
+
Prerequisites:
|
|
256
|
+
- GitHub CLI installed: `gh`
|
|
257
|
+
- Authenticated session: `gh auth login`
|
|
258
|
+
- Workflow must define `workflow_dispatch` under `on:` to be runnable manually
|
|
259
|
+
|
|
260
|
+
Common usage:
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
# List all workflow files and dispatch readiness
|
|
264
|
+
redeploy gh-workflow list
|
|
265
|
+
|
|
266
|
+
# Analyze one workflow (triggers/jobs + hint if not dispatchable)
|
|
267
|
+
redeploy gh-workflow analyze version-drift
|
|
268
|
+
|
|
269
|
+
# Analyze all workflows in a custom repo path
|
|
270
|
+
redeploy gh-workflow analyze --repo-root /path/to/repo
|
|
271
|
+
|
|
272
|
+
# Trigger workflow_dispatch run on demand
|
|
273
|
+
redeploy gh-workflow run version-drift --ref main
|
|
274
|
+
|
|
275
|
+
# Pass workflow inputs (repeat --field)
|
|
276
|
+
redeploy gh-workflow run release --field env=prod --field force=true
|
|
277
|
+
|
|
278
|
+
# Trigger and wait for completion (non-zero exit when workflow fails)
|
|
279
|
+
redeploy gh-workflow run version-drift --watch
|
|
280
|
+
|
|
281
|
+
# Preview gh command without executing
|
|
282
|
+
redeploy gh-workflow run version-drift --dry-run
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Notes:
|
|
286
|
+
- `redeploy workflow ...` is for workflows from `redeploy.css`.
|
|
287
|
+
- `redeploy gh-workflow ...` is for GitHub Actions in `.github/workflows/`.
|
|
288
|
+
|
|
250
289
|
### Generic pipeline hooks (recommended)
|
|
251
290
|
|
|
252
291
|
Use top-level `hooks:` in your migration spec to run custom actions in specific phases.
|
|
@@ -3,17 +3,17 @@
|
|
|
3
3
|
|
|
4
4
|
## AI Cost Tracking
|
|
5
5
|
|
|
6
|
-
     
|
|
7
|
+
  
|
|
8
8
|
|
|
9
|
-
- 🤖 **LLM usage:** $7.5000 (
|
|
10
|
-
- 👤 **Human dev:** ~$
|
|
9
|
+
- 🤖 **LLM usage:** $7.5000 (92 commits)
|
|
10
|
+
- 👤 **Human dev:** ~$3734 (37.3h @ $100/h, 30min dedup)
|
|
11
11
|
|
|
12
12
|
Generated on 2026-04-23 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
13
13
|
|
|
14
14
|
---
|
|
15
15
|
|
|
16
|
-
    
|
|
17
17
|
|
|
18
18
|
Infrastructure migration and device deploy toolkit — VPS, Raspberry Pi kiosk, Podman Quadlet, k3s.
|
|
19
19
|
|
|
@@ -28,6 +28,7 @@ redeploy prompt → NLP → command via LLM (natural language interface)
|
|
|
28
28
|
redeploy mcp → start MCP server (Claude Desktop / VS Code / remote API)
|
|
29
29
|
redeploy scan → find devices on LAN (device registry)
|
|
30
30
|
redeploy target → deploy to named device (fleet)
|
|
31
|
+
redeploy gh-workflow → analyze/run GitHub Actions workflows on demand
|
|
31
32
|
```
|
|
32
33
|
|
|
33
34
|
## Install
|
|
@@ -218,6 +219,44 @@ redeploy run --env rpi5 --detect # deploy to rpi5 with live probe
|
|
|
218
219
|
redeploy run --dry-run # uses .env DEPLOY_* vars if no redeploy.yaml
|
|
219
220
|
```
|
|
220
221
|
|
|
222
|
+
### `redeploy gh-workflow [list|analyze|run]`
|
|
223
|
+
|
|
224
|
+
Analyze and trigger GitHub Actions workflows from your repo on demand.
|
|
225
|
+
|
|
226
|
+
Prerequisites:
|
|
227
|
+
- GitHub CLI installed: `gh`
|
|
228
|
+
- Authenticated session: `gh auth login`
|
|
229
|
+
- Workflow must define `workflow_dispatch` under `on:` to be runnable manually
|
|
230
|
+
|
|
231
|
+
Common usage:
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
# List all workflow files and dispatch readiness
|
|
235
|
+
redeploy gh-workflow list
|
|
236
|
+
|
|
237
|
+
# Analyze one workflow (triggers/jobs + hint if not dispatchable)
|
|
238
|
+
redeploy gh-workflow analyze version-drift
|
|
239
|
+
|
|
240
|
+
# Analyze all workflows in a custom repo path
|
|
241
|
+
redeploy gh-workflow analyze --repo-root /path/to/repo
|
|
242
|
+
|
|
243
|
+
# Trigger workflow_dispatch run on demand
|
|
244
|
+
redeploy gh-workflow run version-drift --ref main
|
|
245
|
+
|
|
246
|
+
# Pass workflow inputs (repeat --field)
|
|
247
|
+
redeploy gh-workflow run release --field env=prod --field force=true
|
|
248
|
+
|
|
249
|
+
# Trigger and wait for completion (non-zero exit when workflow fails)
|
|
250
|
+
redeploy gh-workflow run version-drift --watch
|
|
251
|
+
|
|
252
|
+
# Preview gh command without executing
|
|
253
|
+
redeploy gh-workflow run version-drift --dry-run
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Notes:
|
|
257
|
+
- `redeploy workflow ...` is for workflows from `redeploy.css`.
|
|
258
|
+
- `redeploy gh-workflow ...` is for GitHub Actions in `.github/workflows/`.
|
|
259
|
+
|
|
221
260
|
### Generic pipeline hooks (recommended)
|
|
222
261
|
|
|
223
262
|
Use top-level `hooks:` in your migration spec to run custom actions in specific phases.
|
|
@@ -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.
|
|
10
|
+
__version__ = "0.2.71"
|
|
11
11
|
|
|
12
12
|
# ── Core models ───────────────────────────────────────────────────────────────
|
|
13
13
|
from .models import ( # noqa: F401
|
|
@@ -350,7 +350,7 @@ def _resolve_command_ref(command_ref: str, step: MigrationStep, plan: MigrationP
|
|
|
350
350
|
- "#section-id" - script from section in current spec file
|
|
351
351
|
- "#kiosk-browser-configuration-script" - markpact:ref block
|
|
352
352
|
"""
|
|
353
|
-
from ..markpact
|
|
353
|
+
from ..markpact import resolve_script_ref
|
|
354
354
|
|
|
355
355
|
# Parse command_ref
|
|
356
356
|
if "#" in command_ref:
|
|
@@ -367,21 +367,15 @@ def _resolve_command_ref(command_ref: str, step: MigrationStep, plan: MigrationP
|
|
|
367
367
|
if not file_path.exists():
|
|
368
368
|
raise StepError(step, f"Command ref file not found: {file_path}")
|
|
369
369
|
|
|
370
|
-
# Extract script from markdown - try both methods:
|
|
371
|
-
# 1. First try markpact:ref codeblock (new format)
|
|
372
|
-
# 2. Then try section heading (old format)
|
|
373
370
|
markdown_content = file_path.read_text(encoding="utf-8")
|
|
371
|
+
result = resolve_script_ref(markdown_content, section_id, language="bash")
|
|
374
372
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
if script is None:
|
|
383
|
-
raise StepError(step, f"Could not find bash script for ref '{section_id}' in {file_path} (tried markpact:ref and section heading)")
|
|
384
|
-
|
|
373
|
+
if result is None:
|
|
374
|
+
raise StepError(
|
|
375
|
+
step,
|
|
376
|
+
f"Could not find bash script for ref '{section_id}' in {file_path} (tried markpact:ref and section heading)"
|
|
377
|
+
)
|
|
378
|
+
script, _ = result
|
|
385
379
|
return script
|
|
386
380
|
|
|
387
381
|
|
|
@@ -45,6 +45,7 @@ from .commands.probe import probe
|
|
|
45
45
|
from .commands.target import target
|
|
46
46
|
from .commands.version import version_cmd
|
|
47
47
|
from .commands.workflow import workflow_cmd
|
|
48
|
+
from .commands.gh_workflow import gh_workflow_cmd
|
|
48
49
|
from .commands.exec_ import exec_cmd, exec_multi_cmd
|
|
49
50
|
from .commands.plan_apply import plan, apply, migrate, run
|
|
50
51
|
from .commands.state import state_cmd
|
|
@@ -91,6 +92,7 @@ cli.add_command(status)
|
|
|
91
92
|
cli.add_command(target)
|
|
92
93
|
cli.add_command(version_cmd)
|
|
93
94
|
cli.add_command(workflow_cmd)
|
|
95
|
+
cli.add_command(gh_workflow_cmd)
|
|
94
96
|
cli.add_command(bump_cmd, name="bump")
|
|
95
97
|
cli.add_command(fix_cmd, name="fix")
|
|
96
98
|
cli.add_command(prompt_cmd, name="prompt")
|
|
@@ -202,19 +202,12 @@ def _extract_all_scripts(md_path, ref_list, console) -> list[tuple[str, str, str
|
|
|
202
202
|
|
|
203
203
|
def _extract_script_for_ref(md_content, ref_id) -> tuple[str, str] | None:
|
|
204
204
|
"""Extract script for a single ref."""
|
|
205
|
-
from ...markpact
|
|
206
|
-
|
|
207
|
-
# Try markpact:ref first
|
|
208
|
-
script = extract_script_by_ref(md_content, ref_id, language="bash")
|
|
209
|
-
lookup_method = "markpact:ref"
|
|
210
|
-
|
|
211
|
-
if script is None and ref_id.startswith("#"):
|
|
212
|
-
script = extract_script_from_markdown(md_content, ref_id[1:], language="bash")
|
|
213
|
-
lookup_method = "section"
|
|
205
|
+
from ...markpact import resolve_script_ref
|
|
214
206
|
|
|
215
|
-
|
|
207
|
+
result = resolve_script_ref(md_content, ref_id, language="bash")
|
|
208
|
+
if result is None:
|
|
216
209
|
return None
|
|
217
|
-
return
|
|
210
|
+
return result
|
|
218
211
|
|
|
219
212
|
|
|
220
213
|
def _execute_single_script(ref_id, script, host, timeout) -> bool:
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"""gh-workflow command - inspect and trigger GitHub Actions workflows on demand."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import shutil
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import click
|
|
11
|
+
import yaml
|
|
12
|
+
from rich.console import Console
|
|
13
|
+
from rich.table import Table
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _find_repo_root(start: Path) -> Path | None:
|
|
17
|
+
cur = start.resolve()
|
|
18
|
+
for p in [cur, *cur.parents]:
|
|
19
|
+
if (p / ".git").exists() or (p / ".github" / "workflows").exists():
|
|
20
|
+
return p
|
|
21
|
+
return None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _resolve_repo_root(repo_root_opt: str | None) -> Path:
|
|
25
|
+
root = Path(repo_root_opt).resolve() if repo_root_opt else _find_repo_root(Path.cwd())
|
|
26
|
+
if not root:
|
|
27
|
+
raise click.ClickException(
|
|
28
|
+
"Could not detect repository root. Use --repo-root PATH."
|
|
29
|
+
)
|
|
30
|
+
return root
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _workflows_dir(repo_root: Path) -> Path:
|
|
34
|
+
wf_dir = repo_root / ".github" / "workflows"
|
|
35
|
+
if not wf_dir.exists():
|
|
36
|
+
raise click.ClickException(f"No workflows directory found: {wf_dir}")
|
|
37
|
+
return wf_dir
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _load_yaml(path: Path) -> dict:
|
|
41
|
+
try:
|
|
42
|
+
raw = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
|
|
43
|
+
except Exception as exc:
|
|
44
|
+
raise click.ClickException(f"Failed to parse workflow YAML: {path}: {exc}") from exc
|
|
45
|
+
if not isinstance(raw, dict):
|
|
46
|
+
raise click.ClickException(f"Workflow file must contain a YAML mapping: {path}")
|
|
47
|
+
return raw
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _on_section(raw: dict) -> object:
|
|
51
|
+
# PyYAML can parse key "on" as boolean True (YAML 1.1).
|
|
52
|
+
return raw.get("on", raw.get(True))
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _trigger_list(on_value: object) -> list[str]:
|
|
56
|
+
if isinstance(on_value, str):
|
|
57
|
+
return [on_value]
|
|
58
|
+
if isinstance(on_value, list):
|
|
59
|
+
return [str(x) for x in on_value]
|
|
60
|
+
if isinstance(on_value, dict):
|
|
61
|
+
return [str(k) for k in on_value.keys()]
|
|
62
|
+
return []
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _has_workflow_dispatch(on_value: object) -> bool:
|
|
66
|
+
if isinstance(on_value, str):
|
|
67
|
+
return on_value == "workflow_dispatch"
|
|
68
|
+
if isinstance(on_value, list):
|
|
69
|
+
return "workflow_dispatch" in on_value
|
|
70
|
+
if isinstance(on_value, dict):
|
|
71
|
+
return "workflow_dispatch" in on_value
|
|
72
|
+
return False
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _collect_workflow_meta(path: Path) -> dict:
|
|
76
|
+
raw = _load_yaml(path)
|
|
77
|
+
on_value = _on_section(raw)
|
|
78
|
+
jobs = raw.get("jobs", {})
|
|
79
|
+
job_names = list(jobs.keys()) if isinstance(jobs, dict) else []
|
|
80
|
+
return {
|
|
81
|
+
"file": path.name,
|
|
82
|
+
"name": str(raw.get("name") or path.stem),
|
|
83
|
+
"triggers": _trigger_list(on_value),
|
|
84
|
+
"dispatch": _has_workflow_dispatch(on_value),
|
|
85
|
+
"jobs": job_names,
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _resolve_workflow_identifier(wf_dir: Path, workflow: str) -> Path:
|
|
90
|
+
candidate = wf_dir / workflow
|
|
91
|
+
if candidate.exists() and candidate.is_file():
|
|
92
|
+
return candidate
|
|
93
|
+
|
|
94
|
+
for ext in (".yml", ".yaml"):
|
|
95
|
+
p = wf_dir / f"{workflow}{ext}"
|
|
96
|
+
if p.exists() and p.is_file():
|
|
97
|
+
return p
|
|
98
|
+
|
|
99
|
+
for p in sorted(wf_dir.glob("*.y*ml")):
|
|
100
|
+
meta = _collect_workflow_meta(p)
|
|
101
|
+
if meta["name"] == workflow or p.stem == workflow:
|
|
102
|
+
return p
|
|
103
|
+
|
|
104
|
+
available = ", ".join(sorted(p.name for p in wf_dir.glob("*.y*ml")))
|
|
105
|
+
raise click.ClickException(
|
|
106
|
+
f"Workflow not found: {workflow}. Available files: {available}"
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _gh_available() -> bool:
|
|
111
|
+
return shutil.which("gh") is not None
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _parse_fields(fields: tuple[str, ...]) -> list[str]:
|
|
115
|
+
parsed: list[str] = []
|
|
116
|
+
for item in fields:
|
|
117
|
+
if "=" not in item:
|
|
118
|
+
raise click.ClickException(
|
|
119
|
+
f"Invalid --field '{item}'. Expected KEY=VALUE."
|
|
120
|
+
)
|
|
121
|
+
parsed.append(item)
|
|
122
|
+
return parsed
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@click.group("gh-workflow")
|
|
126
|
+
def gh_workflow_cmd():
|
|
127
|
+
"""Inspect and run GitHub Actions workflows on demand."""
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@gh_workflow_cmd.command("list")
|
|
131
|
+
@click.option("--repo-root", default=None, type=click.Path(file_okay=False, path_type=str),
|
|
132
|
+
help="Repository root (auto-detected if omitted)")
|
|
133
|
+
def gh_workflow_list(repo_root: str | None):
|
|
134
|
+
"""List workflow files and whether they are dispatchable."""
|
|
135
|
+
console = Console()
|
|
136
|
+
root = _resolve_repo_root(repo_root)
|
|
137
|
+
wf_dir = _workflows_dir(root)
|
|
138
|
+
files = sorted(wf_dir.glob("*.y*ml"))
|
|
139
|
+
if not files:
|
|
140
|
+
console.print(f"[yellow]No workflow files found in {wf_dir}[/yellow]")
|
|
141
|
+
return
|
|
142
|
+
|
|
143
|
+
table = Table(title=f"GitHub Workflows ({root.name})")
|
|
144
|
+
table.add_column("File", style="cyan")
|
|
145
|
+
table.add_column("Name")
|
|
146
|
+
table.add_column("Dispatch", justify="center")
|
|
147
|
+
table.add_column("Triggers")
|
|
148
|
+
table.add_column("Jobs", justify="right")
|
|
149
|
+
|
|
150
|
+
for path in files:
|
|
151
|
+
meta = _collect_workflow_meta(path)
|
|
152
|
+
dispatch = "yes" if meta["dispatch"] else "no"
|
|
153
|
+
triggers = ", ".join(meta["triggers"]) if meta["triggers"] else "-"
|
|
154
|
+
table.add_row(meta["file"], meta["name"], dispatch, triggers, str(len(meta["jobs"])))
|
|
155
|
+
|
|
156
|
+
console.print(table)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@gh_workflow_cmd.command("analyze")
|
|
160
|
+
@click.argument("workflow", required=False, default=None)
|
|
161
|
+
@click.option("--repo-root", default=None, type=click.Path(file_okay=False, path_type=str),
|
|
162
|
+
help="Repository root (auto-detected if omitted)")
|
|
163
|
+
def gh_workflow_analyze(workflow: str | None, repo_root: str | None):
|
|
164
|
+
"""Analyze one workflow (or all workflows) for triggers/jobs/dispatch readiness."""
|
|
165
|
+
console = Console()
|
|
166
|
+
root = _resolve_repo_root(repo_root)
|
|
167
|
+
wf_dir = _workflows_dir(root)
|
|
168
|
+
|
|
169
|
+
selected: list[Path]
|
|
170
|
+
if workflow:
|
|
171
|
+
selected = [_resolve_workflow_identifier(wf_dir, workflow)]
|
|
172
|
+
else:
|
|
173
|
+
selected = sorted(wf_dir.glob("*.y*ml"))
|
|
174
|
+
|
|
175
|
+
for path in selected:
|
|
176
|
+
meta = _collect_workflow_meta(path)
|
|
177
|
+
console.print(f"[bold]{meta['name']}[/bold] [dim]({meta['file']})[/dim]")
|
|
178
|
+
console.print(f" dispatchable: {'yes' if meta['dispatch'] else 'no'}")
|
|
179
|
+
console.print(
|
|
180
|
+
" triggers: "
|
|
181
|
+
+ (", ".join(meta["triggers"]) if meta["triggers"] else "none")
|
|
182
|
+
)
|
|
183
|
+
if meta["jobs"]:
|
|
184
|
+
console.print(" jobs: " + ", ".join(meta["jobs"]))
|
|
185
|
+
else:
|
|
186
|
+
console.print(" jobs: none")
|
|
187
|
+
if not meta["dispatch"]:
|
|
188
|
+
console.print(
|
|
189
|
+
" [yellow]hint:[/yellow] add 'workflow_dispatch:' under 'on:' to run on demand"
|
|
190
|
+
)
|
|
191
|
+
console.print()
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
@gh_workflow_cmd.command("run")
|
|
195
|
+
@click.argument("workflow")
|
|
196
|
+
@click.option("--repo-root", default=None, type=click.Path(file_okay=False, path_type=str),
|
|
197
|
+
help="Repository root (auto-detected if omitted)")
|
|
198
|
+
@click.option("--ref", default=None, help="Git ref (branch/tag/SHA) for workflow_dispatch")
|
|
199
|
+
@click.option(
|
|
200
|
+
"--field",
|
|
201
|
+
"fields",
|
|
202
|
+
multiple=True,
|
|
203
|
+
help="workflow_dispatch input as KEY=VALUE (repeatable)",
|
|
204
|
+
)
|
|
205
|
+
@click.option("--watch", is_flag=True, help="Watch the newly started run until completion")
|
|
206
|
+
@click.option("--dry-run", is_flag=True, help="Print commands without executing")
|
|
207
|
+
def gh_workflow_run(
|
|
208
|
+
workflow: str,
|
|
209
|
+
repo_root: str | None,
|
|
210
|
+
ref: str | None,
|
|
211
|
+
fields: tuple[str, ...],
|
|
212
|
+
watch: bool,
|
|
213
|
+
dry_run: bool,
|
|
214
|
+
):
|
|
215
|
+
"""Trigger a GitHub Actions workflow_dispatch run on demand via gh CLI."""
|
|
216
|
+
console = Console()
|
|
217
|
+
root = _resolve_repo_root(repo_root)
|
|
218
|
+
wf_dir = _workflows_dir(root)
|
|
219
|
+
wf_path = _resolve_workflow_identifier(wf_dir, workflow)
|
|
220
|
+
meta = _collect_workflow_meta(wf_path)
|
|
221
|
+
|
|
222
|
+
if not meta["dispatch"]:
|
|
223
|
+
raise click.ClickException(
|
|
224
|
+
f"Workflow '{meta['file']}' is not dispatchable (missing workflow_dispatch trigger)."
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
if not _gh_available():
|
|
228
|
+
raise click.ClickException(
|
|
229
|
+
"GitHub CLI 'gh' is required. Install gh and run 'gh auth login'."
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
parsed_fields = _parse_fields(fields)
|
|
233
|
+
|
|
234
|
+
run_cmd: list[str] = ["gh", "workflow", "run", wf_path.name]
|
|
235
|
+
if ref:
|
|
236
|
+
run_cmd.extend(["--ref", ref])
|
|
237
|
+
for item in parsed_fields:
|
|
238
|
+
run_cmd.extend(["-f", item])
|
|
239
|
+
|
|
240
|
+
console.print(f"[bold]workflow:[/bold] {meta['name']} ({wf_path.name})")
|
|
241
|
+
console.print("[bold]command:[/bold] " + " ".join(run_cmd))
|
|
242
|
+
|
|
243
|
+
if dry_run:
|
|
244
|
+
return
|
|
245
|
+
|
|
246
|
+
result = subprocess.run(run_cmd, cwd=str(root), capture_output=True, text=True)
|
|
247
|
+
if result.returncode != 0:
|
|
248
|
+
msg = result.stderr.strip() or result.stdout.strip() or "gh workflow run failed"
|
|
249
|
+
raise click.ClickException(msg)
|
|
250
|
+
|
|
251
|
+
if result.stdout.strip():
|
|
252
|
+
console.print(result.stdout.strip())
|
|
253
|
+
|
|
254
|
+
if not watch:
|
|
255
|
+
return
|
|
256
|
+
|
|
257
|
+
list_cmd = [
|
|
258
|
+
"gh",
|
|
259
|
+
"run",
|
|
260
|
+
"list",
|
|
261
|
+
"--workflow",
|
|
262
|
+
wf_path.name,
|
|
263
|
+
"--limit",
|
|
264
|
+
"1",
|
|
265
|
+
"--json",
|
|
266
|
+
"databaseId,url,status,conclusion",
|
|
267
|
+
]
|
|
268
|
+
list_res = subprocess.run(list_cmd, cwd=str(root), capture_output=True, text=True)
|
|
269
|
+
if list_res.returncode != 0:
|
|
270
|
+
msg = list_res.stderr.strip() or list_res.stdout.strip() or "gh run list failed"
|
|
271
|
+
raise click.ClickException(msg)
|
|
272
|
+
|
|
273
|
+
try:
|
|
274
|
+
entries = json.loads(list_res.stdout)
|
|
275
|
+
except Exception as exc:
|
|
276
|
+
raise click.ClickException(f"Could not parse gh run list output: {exc}") from exc
|
|
277
|
+
|
|
278
|
+
if not entries:
|
|
279
|
+
raise click.ClickException("No run found to watch.")
|
|
280
|
+
|
|
281
|
+
run_id = str(entries[0].get("databaseId", "")).strip()
|
|
282
|
+
run_url = str(entries[0].get("url", "")).strip()
|
|
283
|
+
if run_url:
|
|
284
|
+
console.print(f"[bold]run:[/bold] {run_url}")
|
|
285
|
+
if not run_id:
|
|
286
|
+
raise click.ClickException("Run ID missing from gh run list output.")
|
|
287
|
+
|
|
288
|
+
watch_cmd = ["gh", "run", "watch", run_id, "--exit-status"]
|
|
289
|
+
watch_res = subprocess.run(watch_cmd, cwd=str(root))
|
|
290
|
+
if watch_res.returncode != 0:
|
|
291
|
+
sys.exit(watch_res.returncode)
|
|
@@ -186,79 +186,13 @@ def _print_diagnostics(console, hw, show_fix):
|
|
|
186
186
|
|
|
187
187
|
def _apply_transform(console, p, hw, transform):
|
|
188
188
|
"""Apply display transform via wlr-randr and persist in kanshi config."""
|
|
189
|
-
# Find DSI output name (e.g. "DSI-2")
|
|
190
189
|
dsi = next((o for o in hw.drm_outputs if "DSI" in o.name), None)
|
|
191
190
|
if not dsi:
|
|
192
191
|
console.print("[red]✗ No DSI output found on this host[/red]")
|
|
193
192
|
sys.exit(1)
|
|
194
193
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
console.print(f"[cyan]→ Setting transform {transform} on {output_name}[/cyan]")
|
|
198
|
-
|
|
199
|
-
# 1. Apply immediately via wlr-randr
|
|
200
|
-
wlr_cmd = (
|
|
201
|
-
f"WAYLAND_DISPLAY=wayland-0 XDG_RUNTIME_DIR=/run/user/$(id -u) "
|
|
202
|
-
f"wlr-randr --output {output_name} --transform {transform} 2>&1"
|
|
203
|
-
)
|
|
204
|
-
r = p.run(wlr_cmd)
|
|
205
|
-
if r.ok:
|
|
206
|
-
console.print(f"[green] ✓ wlr-randr transform applied[/green]")
|
|
207
|
-
else:
|
|
208
|
-
console.print(f"[yellow] ⚠ wlr-randr failed (compositor may not be running): {r.out.strip()}[/yellow]")
|
|
209
|
-
|
|
210
|
-
# 2. Persist in kanshi config — update or create transform line
|
|
211
|
-
kanshi_cfg_path = "~/.config/kanshi/config"
|
|
212
|
-
# Read current config
|
|
213
|
-
read_r = p.run(f"cat {kanshi_cfg_path} 2>/dev/null")
|
|
214
|
-
import re as _re
|
|
215
|
-
|
|
216
|
-
if read_r.ok and read_r.out.strip():
|
|
217
|
-
current = read_r.out
|
|
218
|
-
output_line_pat = _re.compile(
|
|
219
|
-
rf'(\s*output\s+{_re.escape(output_name)}\s+enable)(\s+transform\s+\S+)?'
|
|
220
|
-
)
|
|
221
|
-
if _re.search(output_line_pat, current):
|
|
222
|
-
# Replace or add transform on the existing enable line
|
|
223
|
-
new_cfg = _re.sub(
|
|
224
|
-
output_line_pat,
|
|
225
|
-
rf'\1 transform {transform}',
|
|
226
|
-
current,
|
|
227
|
-
)
|
|
228
|
-
else:
|
|
229
|
-
# Output line without "enable" — just append transform param
|
|
230
|
-
new_cfg = _re.sub(
|
|
231
|
-
rf'(\s*output\s+{_re.escape(output_name)}\b)',
|
|
232
|
-
rf'\1 transform {transform}',
|
|
233
|
-
current,
|
|
234
|
-
)
|
|
235
|
-
else:
|
|
236
|
-
# No kanshi config — create minimal one
|
|
237
|
-
new_cfg = (
|
|
238
|
-
f"profile waveshare-only {{\n"
|
|
239
|
-
f" output {output_name} enable transform {transform}\n"
|
|
240
|
-
f"}}\n"
|
|
241
|
-
)
|
|
242
|
-
|
|
243
|
-
# Write updated config
|
|
244
|
-
escaped = new_cfg.replace("'", "'\\''")
|
|
245
|
-
write_r = p.run(f"mkdir -p ~/.config/kanshi && printf '%s' '{escaped}' > {kanshi_cfg_path}")
|
|
246
|
-
if write_r.ok:
|
|
247
|
-
console.print(f"[green] ✓ kanshi config updated ({kanshi_cfg_path})[/green]")
|
|
248
|
-
else:
|
|
249
|
-
console.print(f"[red] ✗ failed to write kanshi config: {write_r.out}[/red]")
|
|
250
|
-
sys.exit(1)
|
|
251
|
-
|
|
252
|
-
# 3. Reload kanshi if running
|
|
253
|
-
pkill_r = p.run("pkill -SIGUSR1 kanshi 2>/dev/null || pkill kanshi 2>/dev/null; sleep 1; "
|
|
254
|
-
"WAYLAND_DISPLAY=wayland-0 XDG_RUNTIME_DIR=/run/user/$(id -u) kanshi &")
|
|
255
|
-
if pkill_r.ok:
|
|
256
|
-
console.print("[green] ✓ kanshi reloaded[/green]")
|
|
257
|
-
|
|
258
|
-
console.print(
|
|
259
|
-
f"\n[bold green]✓ Transform {transform} applied to {output_name}[/bold green]\n"
|
|
260
|
-
f" [dim]Persistent: {kanshi_cfg_path}[/dim]"
|
|
261
|
-
)
|
|
194
|
+
from ...config_apply.handlers.display import apply_display_transform
|
|
195
|
+
apply_display_transform(console, p, dsi.connector, transform)
|
|
262
196
|
|
|
263
197
|
|
|
264
198
|
def _execute_query(hw, query_expr, output_fmt):
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Hardware/infrastructure config-apply handlers.
|
|
2
|
+
|
|
3
|
+
Each handler receives a ``(console, probe, value)`` triple and performs
|
|
4
|
+
the remote mutation + local reporting.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from .display import apply_display_transform
|
|
9
|
+
|
|
10
|
+
__all__ = ["apply_display_transform"]
|