remo-cli 4.2.0__tar.gz → 4.3.1__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.
- remo_cli-4.3.1/.release-please-manifest.json +3 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/AGENTS.md +7 -3
- {remo_cli-4.2.0 → remo_cli-4.3.1}/CHANGELOG.md +19 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/CLAUDE.md +7 -3
- {remo_cli-4.2.0 → remo_cli-4.3.1}/PKG-INFO +55 -1
- {remo_cli-4.2.0 → remo_cli-4.3.1}/README.md +54 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/user_setup/tasks/main.yml +14 -0
- remo_cli-4.3.1/ansible/ssh_configure.yml +240 -0
- remo_cli-4.3.1/docs/examples/orbstack-cloud-init.yaml +120 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/docs/web-session-interface.md +9 -2
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/package-lock.json +24 -24
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/theme/terminalThemes.test.ts +38 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/theme/terminalThemes.ts +28 -5
- {remo_cli-4.2.0 → remo_cli-4.3.1}/pyproject.toml +1 -1
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/014-register-ssh-host/spec.md +3 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/cli/added.py +71 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/cli/main.py +2 -1
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/core/known_hosts.py +51 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/core/ssh.py +18 -1
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/core/web_adopt.py +44 -9
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/models/host.py +16 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/providers/added.py +135 -1
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/web/check.py +4 -1
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/web/config.py +25 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/web/discovery.py +36 -7
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/web/terminal.py +4 -1
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/ansible/test_apt_sources_repair.py +1 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/ansible/test_authorized_keys_bootstrap.py +6 -2
- remo_cli-4.3.1/tests/ansible/test_ssh_configure_connection.py +203 -0
- remo_cli-4.3.1/tests/ansible/test_user_setup_docker_group.py +88 -0
- remo_cli-4.3.1/tests/unit/cli/test_configure_cmd.py +115 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/cli/test_main.py +1 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/core/test_ssh_identity_opts.py +66 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/core/test_web_adopt_trust.py +156 -1
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/providers/test_added_add.py +30 -0
- remo_cli-4.3.1/tests/unit/providers/test_added_configure.py +226 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/providers/test_added_provider_guard.py +79 -1
- remo_cli-4.3.1/tests/unit/test_docs_examples.py +80 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/test_host_ssh_type.py +19 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/web/test_discovery.py +76 -1
- remo_cli-4.3.1/tests/unit/web/test_service_identity_selection.py +95 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/uv.lock +51 -54
- remo_cli-4.2.0/.release-please-manifest.json +0 -3
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.claude/skills/release/SKILL.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.claude/skills/speckit-analyze/SKILL.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.claude/skills/speckit-checklist/SKILL.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.claude/skills/speckit-clarify/SKILL.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.claude/skills/speckit-constitution/SKILL.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.claude/skills/speckit-converge/SKILL.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.claude/skills/speckit-git-commit/SKILL.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.claude/skills/speckit-git-feature/SKILL.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.claude/skills/speckit-git-initialize/SKILL.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.claude/skills/speckit-git-remote/SKILL.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.claude/skills/speckit-git-validate/SKILL.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.claude/skills/speckit-implement/SKILL.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.claude/skills/speckit-plan/SKILL.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.claude/skills/speckit-specify/SKILL.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.claude/skills/speckit-tasks/SKILL.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.claude/skills/speckit-taskstoissues/SKILL.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.devcontainer/devcontainer.json +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.dockerignore +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.github/CODEOWNERS +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.github/dependabot.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.github/workflows/ci.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.github/workflows/codeql.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.github/workflows/dependency-review.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.github/workflows/dev-build.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.github/workflows/provision.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.github/workflows/release-please.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.github/workflows/release.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.github/workflows/smoke-test-cleanup.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.github/workflows/smoke-test.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.github/workflows/teardown.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.gitignore +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.maverick/checkpoints/feature/implementation_complete.json +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.maverick/checkpoints/feature/phase_${{ index }}_complete.json +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.maverick/checkpoints/feature/validation_complete.json +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.specify/extensions/.registry +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.specify/extensions/git/README.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.specify/extensions/git/commands/speckit.git.commit.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.specify/extensions/git/commands/speckit.git.feature.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.specify/extensions/git/commands/speckit.git.initialize.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.specify/extensions/git/commands/speckit.git.remote.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.specify/extensions/git/commands/speckit.git.validate.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.specify/extensions/git/config-template.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.specify/extensions/git/extension.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.specify/extensions/git/git-config.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.specify/extensions/git/scripts/bash/auto-commit.sh +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.specify/extensions/git/scripts/bash/create-new-feature.sh +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.specify/extensions/git/scripts/bash/git-common.sh +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.specify/extensions/git/scripts/bash/initialize-repo.sh +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.specify/extensions/git/scripts/powershell/auto-commit.ps1 +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.specify/extensions/git/scripts/powershell/create-new-feature.ps1 +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.specify/extensions/git/scripts/powershell/git-common.ps1 +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.specify/extensions/git/scripts/powershell/initialize-repo.ps1 +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.specify/extensions.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.specify/feature.json +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.specify/init-options.json +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.specify/integration.json +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.specify/integrations/claude.manifest.json +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.specify/integrations/speckit.manifest.json +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.specify/memory/constitution.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.specify/scripts/bash/check-prerequisites.sh +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.specify/scripts/bash/common.sh +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.specify/scripts/bash/create-new-feature.sh +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.specify/scripts/bash/setup-plan.sh +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.specify/scripts/bash/setup-tasks.sh +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.specify/scripts/bash/update-agent-context.sh +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.specify/templates/agent-file-template.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.specify/templates/checklist-template.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.specify/templates/constitution-template.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.specify/templates/plan-template.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.specify/templates/spec-template.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.specify/templates/tasks-template.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.specify/workflows/speckit/workflow.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/.specify/workflows/workflow-registry.json +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/CONTRIBUTING.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/Dockerfile +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/LICENSE +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/SECURITY.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/.ansible-lint +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/README.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/ansible.cfg +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/aws_configure.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/aws_resize.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/aws_site.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/aws_teardown.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/group_vars/all.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/group_vars/incus_containers.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/group_vars/proxmox_containers.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/hetzner_configure.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/hetzner_provision.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/hetzner_resize.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/hetzner_site.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/hetzner_teardown.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/incus_bootstrap.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/incus_configure.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/incus_provision.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/incus_resize.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/incus_site.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/incus_teardown.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/inventory/hosts.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/inventory/incus_containers.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/proxmox_bootstrap.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/proxmox_configure.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/proxmox_provision.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/proxmox_resize.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/proxmox_site.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/proxmox_teardown.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/requirements.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/aws_server/defaults/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/aws_server/tasks/ebs.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/aws_server/tasks/ec2.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/aws_server/tasks/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/aws_server/tasks/patch_manager.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/aws_server/tasks/preflight.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/aws_server/tasks/security_group.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/deacon/defaults/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/deacon/tasks/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/devcontainers/tasks/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/docker/handlers/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/docker/tasks/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/fzf/tasks/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/github_cli/tasks/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/hetzner_server/defaults/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/hetzner_server/tasks/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/incus_bootstrap/defaults/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/incus_bootstrap/handlers/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/incus_bootstrap/tasks/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/incus_container/defaults/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/incus_container/handlers/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/incus_container/meta/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/incus_container/tasks/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/incus_container/tasks/preflight.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/incus_container_teardown/defaults/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/incus_container_teardown/meta/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/incus_container_teardown/tasks/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/nodejs/defaults/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/nodejs/files/nodesource-repo.gpg.key +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/nodejs/tasks/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/proxmox_bootstrap/defaults/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/proxmox_bootstrap/meta/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/proxmox_bootstrap/tasks/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/proxmox_container/defaults/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/proxmox_container/handlers/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/proxmox_container/meta/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/proxmox_container/tasks/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/proxmox_container/tasks/preflight.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/proxmox_container_teardown/defaults/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/proxmox_container_teardown/meta/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/proxmox_container_teardown/tasks/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/user_setup/defaults/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/user_setup/templates/devshell.sh.j2 +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/user_setup/templates/project-launch.sh.j2 +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/user_setup/templates/project-menu.sh.j2 +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/user_setup/templates/remo-host.sh.j2 +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/zellij/defaults/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/zellij/tasks/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/zellij/templates/config.kdl.j2 +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/roles/zellij/templates/layouts/minimal.kdl.j2 +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/tasks/configure_dev_tools.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/ansible/tasks/repair_apt_sources.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/docker/Dockerfile +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/docker/compose.example.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/docker/entrypoint.sh +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/docs/aws.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/docs/feature-history.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/docs/hetzner.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/docs/incus.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/docs/install.sh +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/docs/maintaining-claude-md.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/docs/maintaining-generated-types.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/docs/providers.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/docs/proxmox.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/docs/remo-web-speckit-input.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/docs/remo-web.html +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/index.html +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/package.json +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/scripts/check-types-fresh.mjs +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/scripts/generate-frame-types.mjs +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/scripts/generate-schema-types.mjs +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/api/client.test.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/api/client.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/api/generated/README.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/api/generated/openapi.json +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/api/generated/schema.d.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/api/generated/terminal-frames.d.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/api/generated/terminal-frames.json +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/api/generated.test.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/components/AppRoot.tsx +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/components/AppShell.css +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/components/AppShell.tsx +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/components/AwaitingAdoption.css +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/components/AwaitingAdoption.tsx +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/components/OfflineOverlay.css +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/components/OfflineOverlay.tsx +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/components/PairToSync.css +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/components/PairToSync.tsx +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/components/SessionRail.css +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/components/SessionRail.tsx +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/components/SettingsPage.css +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/components/SettingsPage.tsx +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/components/ShortcutsModal.css +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/components/ShortcutsModal.tsx +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/components/TerminalCard.css +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/components/TerminalCard.test.tsx +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/components/TerminalCard.tsx +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/components/TopBar.css +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/components/TopBar.tsx +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/components/WorkspacePane.css +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/components/WorkspacePane.test.tsx +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/components/WorkspacePane.tsx +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/components/masterLayout.test.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/components/masterLayout.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/components/providerMeta.test.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/components/providerMeta.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/components/railModel.test.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/components/railModel.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/lib/clipboard.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/lib/fullscreen.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/main.tsx +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/state/discovery.test.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/state/discovery.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/state/fonts.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/state/health.test.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/state/health.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/state/latency.test.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/state/latency.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/state/settings.test.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/state/settings.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/state/useConsoleKeyboard.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/state/workspace.test.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/state/workspace.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/terminal/.gitkeep +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/terminal/RendererAdapter.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/terminal/TerminalConnection.test.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/terminal/TerminalConnection.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/terminal/XtermRenderer.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/terminal/defaultRenderer.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/terminal/keymap.test.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/terminal/keymap.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/test/setup.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/theme/fonts.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/src/theme/tokens.css +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/tsconfig.json +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/frontend/vite.config.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/install.sh +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/maverick.yaml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/playwright.config.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/project-menu.sh +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/release-please-config.json +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/remo +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/remo.svg +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/run.sh +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/scripts/export_openapi.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/scripts/palette-check.sh +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/scripts/setup-aws-oidc.sh +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/001-bootstrap-incus-host/checklists/requirements.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/001-bootstrap-incus-host/checklists/validation.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/001-bootstrap-incus-host/contracts/playbook-interface.yaml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/001-bootstrap-incus-host/contracts/role-interface.yaml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/001-bootstrap-incus-host/data-model.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/001-bootstrap-incus-host/plan.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/001-bootstrap-incus-host/quickstart.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/001-bootstrap-incus-host/research.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/001-bootstrap-incus-host/spec.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/001-bootstrap-incus-host/tasks.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/002-incus-container-support/checklists/requirements.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/002-incus-container-support/contracts/playbook-interface.yaml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/002-incus-container-support/contracts/role-interface.yaml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/002-incus-container-support/data-model.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/002-incus-container-support/plan.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/002-incus-container-support/quickstart.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/002-incus-container-support/research.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/002-incus-container-support/spec.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/002-incus-container-support/tasks.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/003-python-cli-rewrite/checklists/requirements.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/003-python-cli-rewrite/data-model.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/003-python-cli-rewrite/plan.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/003-python-cli-rewrite/quickstart.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/003-python-cli-rewrite/research.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/003-python-cli-rewrite/spec.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/003-python-cli-rewrite/tasks.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/004-proxmox-container-support/spec.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/005-provider-snapshots/checklists/requirements.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/005-provider-snapshots/contracts/cli-surface.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/005-provider-snapshots/data-model.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/005-provider-snapshots/plan.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/005-provider-snapshots/quickstart.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/005-provider-snapshots/research.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/005-provider-snapshots/spec.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/005-provider-snapshots/tasks.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/010-web-session-interface/checklists/requirements.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/010-web-session-interface/contracts/remo-host-protocol.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/010-web-session-interface/contracts/rest-api.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/010-web-session-interface/contracts/terminal-websocket.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/010-web-session-interface/data-model.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/010-web-session-interface/plan.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/010-web-session-interface/quickstart.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/010-web-session-interface/research.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/010-web-session-interface/spec.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/010-web-session-interface/tasks.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/011-web-adopt/checklists/requirements.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/011-web-adopt/contracts/cli-web-adopt.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/011-web-adopt/contracts/setup-api.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/011-web-adopt/data-model.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/011-web-adopt/plan.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/011-web-adopt/quickstart-results.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/011-web-adopt/quickstart.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/011-web-adopt/research.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/011-web-adopt/spec.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/011-web-adopt/tasks.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/012-web-adopt-pairing/contracts/cli-web-adopt.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/012-web-adopt-pairing/contracts/pairing-api.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/012-web-adopt-pairing/contracts/setup-api.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/012-web-adopt-pairing/data-model.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/012-web-adopt-pairing/plan.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/012-web-adopt-pairing/quickstart.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/012-web-adopt-pairing/research.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/012-web-adopt-pairing/spec.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/012-web-adopt-pairing/tasks.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/013-managed-instance-tags/contracts/cli-sync.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/013-managed-instance-tags/contracts/marker-commands.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/013-managed-instance-tags/data-model.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/013-managed-instance-tags/plan.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/013-managed-instance-tags/quickstart.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/013-managed-instance-tags/research.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/013-managed-instance-tags/spec.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/013-managed-instance-tags/tasks.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/014-register-ssh-host/contracts/add-command.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/014-register-ssh-host/contracts/remove-command.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/014-register-ssh-host/data-model.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/014-register-ssh-host/plan.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/014-register-ssh-host/quickstart.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/014-register-ssh-host/research.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/014-register-ssh-host/tasks.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/015-registry-v2/checklists/requirements.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/015-registry-v2/contracts/mirror-payload-v2.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/015-registry-v2/contracts/registry-accessor-api.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/015-registry-v2/contracts/registry-file-v2.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/015-registry-v2/data-model.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/015-registry-v2/plan.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/015-registry-v2/quickstart.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/015-registry-v2/research.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/015-registry-v2/spec.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/015-registry-v2/tasks.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/016-sync-reconcile/checklists/requirements.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/016-sync-reconcile/contracts/cli-sync.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/016-sync-reconcile/contracts/provider-probe.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/016-sync-reconcile/data-model.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/016-sync-reconcile/plan.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/016-sync-reconcile/quickstart.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/016-sync-reconcile/research.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/016-sync-reconcile/spec.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/016-sync-reconcile/tasks.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/017-web-adopt-simplify/checklists/requirements.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/017-web-adopt-simplify/contracts/cli-web-push.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/017-web-adopt-simplify/contracts/cli-web-status.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/017-web-adopt-simplify/contracts/revocation.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/017-web-adopt-simplify/contracts/setup-status-marker.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/017-web-adopt-simplify/data-model.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/017-web-adopt-simplify/plan.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/017-web-adopt-simplify/quickstart.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/017-web-adopt-simplify/research.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/017-web-adopt-simplify/spec.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/017-web-adopt-simplify/tasks.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/018-provider-abstraction/baseline-notes.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/018-provider-abstraction/checklists/requirements.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/018-provider-abstraction/contracts/cli-surface.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/018-provider-abstraction/contracts/errors.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/018-provider-abstraction/contracts/lifecycle-templates.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/018-provider-abstraction/contracts/provider-protocol.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/018-provider-abstraction/contracts/sync-merge.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/018-provider-abstraction/data-model.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/018-provider-abstraction/plan.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/018-provider-abstraction/quickstart.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/018-provider-abstraction/research.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/018-provider-abstraction/spec.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/018-provider-abstraction/tasks.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/019-hygiene-deps-docs/checklists/requirements.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/019-hygiene-deps-docs/contracts/cli-surface-delta.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/019-hygiene-deps-docs/contracts/docs-structure-check.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/019-hygiene-deps-docs/data-model.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/019-hygiene-deps-docs/plan.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/019-hygiene-deps-docs/quickstart.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/019-hygiene-deps-docs/research.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/019-hygiene-deps-docs/spec.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/019-hygiene-deps-docs/tasks.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/020-openapi-type-generation/checklists/requirements.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/020-openapi-type-generation/contracts/drift-checks.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/020-openapi-type-generation/contracts/terminal-frames-v1.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/020-openapi-type-generation/data-model.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/020-openapi-type-generation/plan.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/020-openapi-type-generation/quickstart.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/020-openapi-type-generation/research.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/020-openapi-type-generation/spec.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/020-openapi-type-generation/tasks.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/021-cli-plane-separation/contracts/cli-surface.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/021-cli-plane-separation/contracts/descriptor-schema.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/021-cli-plane-separation/data-model.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/021-cli-plane-separation/plan.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/021-cli-plane-separation/quickstart.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/021-cli-plane-separation/research.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/021-cli-plane-separation/spec.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/specs/021-cli-plane-separation/tasks.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/__init__.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/__main__.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/cli/__init__.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/cli/cp.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/cli/providers/__init__.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/cli/providers/factory.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/cli/shell.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/cli/web.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/core/__init__.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/core/ansible_runner.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/core/completion.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/core/config.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/core/errors.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/core/lifecycle.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/core/output.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/core/picker.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/core/provider_protocol.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/core/provider_registry.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/core/reconcile.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/core/registry.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/core/remo_host_client.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/core/rsync.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/core/snapshot.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/core/validation.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/core/version.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/core/web_drift.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/models/__init__.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/models/capability.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/models/discovery.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/models/session_target.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/models/snapshot.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/providers/__init__.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/providers/aws.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/providers/aws_descriptor.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/providers/builtin.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/providers/hetzner.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/providers/hetzner_descriptor.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/providers/incus.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/providers/incus_descriptor.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/providers/proxmox.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/providers/proxmox_descriptor.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/web/__init__.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/web/api/__init__.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/web/api/hosts.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/web/api/pairing.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/web/api/setup.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/web/api/terminals.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/web/app.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/web/frames.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/web/health.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/web/logging_config.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/web/models.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/web/operator_auth.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/web/pairing.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/web/ssh_master.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/web/state.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/web/terminal_registry.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/src/remo_cli/web/tokens.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/__init__.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/ansible/__init__.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/ansible/test_remo_host_idempotency.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/conftest.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/e2e/fixtures.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/e2e/keyboard-routing.spec.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/e2e/mobile.spec.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/e2e/reconnect.spec.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/e2e/terminal-shell-io.spec.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/e2e/terminal-tui-compatibility.spec.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/e2e/workspace-layout.spec.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/image/__init__.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/image/test_docker_image.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/integration/__init__.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/integration/_nine_terminal_fixture.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/integration/fish_completion.sh +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/integration/orbstack-cloud-init.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/integration/orbstack.sh +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/integration/test_nine_terminals.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/integration/test_nine_terminals_soak.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/integration/test_registry_concurrency.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/integration/test_remo_host_e2e.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/integration/test_security_rejections.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/integration/test_setup_payload_versions.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/integration/test_sync_reconcile.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/integration/test_terminal_attach.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/integration/test_web_adopt_e2e.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/integration/test_web_cli_parity.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/perf/__init__.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/perf/test_latency.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/perf/test_registry_perf.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/__init__.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/cli/__init__.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/cli/providers/test_aws_snapshot.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/cli/providers/test_hetzner_snapshot.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/cli/providers/test_incus_snapshot.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/cli/providers/test_incus_sync_all.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/cli/providers/test_proxmox_snapshot.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/cli/providers/test_proxmox_sync_all.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/cli/surface_baseline.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/cli/test_add_cmd.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/cli/test_cli_uniformity.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/cli/test_cp.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/cli/test_remove_cmd.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/cli/test_shell.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/cli/test_shell_added.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/cli/test_startup_imports.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/cli/test_surface_preservation.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/cli/test_web_adopt_cmd.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/core/__init__.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/core/test_ansible_runner.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/core/test_completion.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/core/test_config.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/core/test_errors.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/core/test_known_hosts.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/core/test_lifecycle.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/core/test_migration_tagging_notice.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/core/test_output.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/core/test_picker.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/core/test_provider_registry.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/core/test_reconcile.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/core/test_registry_format.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/core/test_registry_locking.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/core/test_registry_migration.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/core/test_remo_host_client.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/core/test_rsync.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/core/test_snapshot.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/core/test_ssh.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/core/test_ssh_added.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/core/test_ssh_controlpath.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/core/test_validation.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/core/test_version.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/core/test_web_adopt_authorize.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/core/test_web_adopt_code.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/core/test_web_adopt_payload.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/core/test_web_drift.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/core/test_web_push.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/core/test_web_push_self_heal.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/models/__init__.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/models/test_capability.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/models/test_discovery.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/models/test_session_target.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/providers/__init__.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/providers/fake_provider.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/providers/test_added_update_remove.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/providers/test_added_verify.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/providers/test_aws_provider_protocol.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/providers/test_aws_snapshot.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/providers/test_aws_sync.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/providers/test_aws_upgrade_resize.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/providers/test_hetzner_entry_adapters.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/providers/test_hetzner_http.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/providers/test_hetzner_label.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/providers/test_hetzner_resize.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/providers/test_hetzner_snapshot.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/providers/test_hetzner_sync.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/providers/test_host_user_consistency.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/providers/test_incus_entry_adapters.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/providers/test_incus_marker.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/providers/test_incus_resize.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/providers/test_incus_snapshot.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/providers/test_incus_sync.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/providers/test_missing_sdk.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/providers/test_provider_conformance.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/providers/test_provider_registry_entries.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/providers/test_proxmox_devcontainer_runtime.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/providers/test_proxmox_marker.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/providers/test_proxmox_provider_protocol.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/providers/test_proxmox_resize.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/providers/test_proxmox_snapshot.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/providers/test_proxmox_sync.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/test_ansible_templates.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/test_architecture.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/test_docs_structure.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/test_host_model.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/test_lockfile_version.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/test_schema_drift.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/web/__init__.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/web/_pairing_support.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/web/conftest.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/web/fixtures/hosts_response.json +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/web/fixtures/sessions_response.json +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/web/test_backpressure.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/web/test_check.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/web/test_csp.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/web/test_discovery_refresh_force.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/web/test_frames.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/web/test_health_states.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/web/test_hosts_mapping.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/web/test_lazy_import.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/web/test_log_redaction.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/web/test_mint_gating.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/web/test_operator_auth.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/web/test_pairing.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/web/test_pairing_origin.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/web/test_payload_equivalence.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/web/test_registry_readonly.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/web/test_setup_api.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/web/test_setup_auth.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/web/test_setup_dormancy.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/web/test_state.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/web/test_terminal_resize.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/web/test_terminals_api.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.1}/tests/unit/web/test_tokens.py +0 -0
|
@@ -31,6 +31,7 @@ Principles I, IV, and VIII are machine-enforced — see [Quality Gates](#quality
|
|
|
31
31
|
- `core/registry.py`: stdlib `json` (format), `fcntl` (advisory locking via a `registry.lock` sidecar), `os.replace` (atomic writes). No new runtime deps. Setup API mirror payload moved to v2 (`contracts/mirror-payload-v2.md`) with a `payload_versions` capability handshake; an upgraded service still accepts v1 payloads. (015-registry-v2)
|
|
32
32
|
- `core/reconcile.py`: provider-agnostic sync-reconcile engine (`SyncScope`, `DiscoveredHost`, `ProbeResult`, `build_plan` (pure), `render_plan`, the consent gate, `apply_plan` via the existing `mutate_registry()`, and the `run_sync` driver). No new runtime deps — stdlib only, built on `core/registry.py`/`core/output.py` as-is. (016-sync-reconcile)
|
|
33
33
|
- `core/web_adopt.py` is now the single unified push engine (`run_push`; `run_adopt` is a thin deprecated alias) — first push adopts, later pushes re-sync, plus best-effort `remo-web@` revocation on removal, `--force` full re-authorization, and multi-workstation flap detection against the service's mirror-generation marker. `core/web_drift.py`: new stdlib-only offline registry-vs-push-cache diff (`diff_registry_against_cache`, `select_deployment`, `render_drift`) powering `remo web status` and the shared `out_of_date_notice()`/`emit_out_of_date_notice()` post-mutation nudge (importable without the `web` extra). Push cache bumped to `cache_version: 3` (`~/.config/remo/web-service.json`: per-deployment `{mirror_generation, instances}`, each instance retaining a non-secret connection tuple for revocation). Service side: `web/api/setup.py` writes/serves a `web-identity/mirror-meta.json` marker (generation + last-push descriptor) additively on `/setup/{status,registry}`; `web/state.py` mode detection fixed so a personal `~/.ssh/id_*` no longer forces `mount_configured` (non-writable `REMO_HOME` is the authoritative signal) with a new `REMO_WEB_MODE` override in `web/config.py`. No new runtime deps; no registry schema change. (017-web-adopt-simplify)
|
|
34
|
+
- No new runtime deps and no registry schema change. `ansible/ssh_configure.yml` is the first playbook to plumb an SSH **port** at all (`ansible_port` appeared nowhere in the repo before) and the first to take an identity from the registry rather than hardcoding `~/.ssh/id_rsa`; both arrive as namespaced `remo_ssh_*` extra-vars so no `ansible_*` name is ever emitted at extra-var precedence. `core/known_hosts.py` gains `guard_added_ssh_host_only`; `core/web_adopt.py` gains `known_hosts_lookup_key` and a `port=` parameter on `scan_and_verify_host_key`. Host key checking is inherited from `ansible.cfg` (disabled repo-wide) — a play-level override is impossible, since `ssh_common_args` is appended after `ssh_args` and ssh honours the first occurrence. (022-configure-added-hosts)
|
|
34
35
|
- No new runtime deps or registry schema change. `pyproject.toml`'s existing `hcloud`/`boto3`/`httpx2` entries gained consumer-attribution comments; `tests/unit/test_docs_structure.py` is a new stdlib-only pytest module (parses the structure diagram below, no new dependency). (019-hygiene-deps-docs)
|
|
35
36
|
- No new runtime deps; no registry schema change (the `host_user`/`node_user` JSON keys already matched the new flag spellings). Superseded for Proxmox by the `--node-user` → `--host-user` rename: both host-scoped providers now spell the node/host login `--host-user`/`host_user` on the CLI, in the provider kwargs and in `registry.json`; `core/registry.py` still reads a legacy `proxmox.node_user` key so pre-rename registries load unchanged and migrate on the next write. `core/provider_registry.py` gains `ArgumentSpec` (positional-argument metadata) and `CommandSpec.target`; `ProviderDescriptor.update_options` is replaced by `upgrade_options`/`resize_dimensions`/`resize_options`/`tag_options`/`host_commands`. `cli/providers/factory.py` gains `_build_upgrade`/`_build_resize`/`_build_tag`/`_build_host_group` (dropping `_build_update` and flat-mounted `bootstrap`). (021-cli-plane-separation)
|
|
36
37
|
|
|
@@ -48,7 +49,7 @@ src/remo_cli/ # Python CLI package (src layout, hatchling build)
|
|
|
48
49
|
│ ├── main.py # Root CLI group; mounts one group per remo_cli.core.provider_registry.all_descriptors()
|
|
49
50
|
│ ├── shell.py # remo shell — registry-dispatched update_entry() (delegates to upgrade()); prompt names the exact `remo <type> upgrade <name>` it will run; unknown type/ssh handled explicitly (no silent no-op)
|
|
50
51
|
│ ├── cp.py # remo cp
|
|
51
|
-
│ ├── added.py # remo add / remo remove — provider-neutral SSH host registration (
|
|
52
|
+
│ ├── added.py # remo add / remo remove / remo configure — provider-neutral SSH host registration (014) and configure (022)
|
|
52
53
|
│ ├── web.py # remo web {serve,check,push,status,adopt} — serve/check lazy-import remo_cli.web.* (NFR-008); push/status/adopt use core/web_adopt + core/web_drift only (adopt is a deprecated alias for push)
|
|
53
54
|
│ └── providers/
|
|
54
55
|
│ └── factory.py # build_provider_group(descriptor) generates create/destroy/upgrade/resize/list/info/sync/snapshot for every provider, plus tag (iff supports_managed_marker) and a host subgroup (iff host_commands non-empty) — the four hand-written per-provider CLI modules are gone
|
|
@@ -61,7 +62,7 @@ src/remo_cli/ # Python CLI package (src layout, hatchling build)
|
|
|
61
62
|
│ ├── hetzner_descriptor.py # metadata-only ProviderDescriptor declaration, no SDK imports
|
|
62
63
|
│ ├── aws_descriptor.py # metadata-only ProviderDescriptor declaration, no SDK imports
|
|
63
64
|
│ ├── proxmox_descriptor.py # metadata-only ProviderDescriptor declaration, no SDK imports
|
|
64
|
-
│ ├── added.py # Business logic for remo add / remo remove
|
|
65
|
+
│ ├── added.py # Business logic for remo add / remo remove / remo configure — registration (014) + generic configure play (022)
|
|
65
66
|
│ └── builtin.py # registers the four built-in descriptors; lazily auto-imported by provider_registry on first lookup
|
|
66
67
|
├── core/ # Shared utilities (no provider knowledge)
|
|
67
68
|
│ ├── config.py # REMO_HOME, paths, read-only registry accessor
|
|
@@ -133,6 +134,9 @@ ansible/ # Ansible playbooks (invoked by Python via subprocess
|
|
|
133
134
|
│ └── templates/
|
|
134
135
|
│ └── remo-host.sh.j2 # Versioned `remo-host` command (capabilities/sessions/attach)
|
|
135
136
|
├── incus_bootstrap.yml
|
|
137
|
+
├── ssh_configure.yml # Generic configure play for `remo add` hosts (022): maps remo_ssh_*
|
|
138
|
+
│ # -> ansible_port/ansible_ssh_private_key_file, binds remo_user to the
|
|
139
|
+
│ # registered account, reuses tasks/configure_dev_tools.yml
|
|
136
140
|
└── requirements.yml
|
|
137
141
|
|
|
138
142
|
scripts/ # Repo-root utility scripts (not part of the installed package)
|
|
@@ -315,9 +319,9 @@ checking nothing.
|
|
|
315
319
|
- Ansible 2.14+ / YAML: Follow standard conventions plus Constitution principles
|
|
316
320
|
|
|
317
321
|
## Recent Changes
|
|
322
|
+
- 022-configure-added-hosts: Made manually-added SSH hosts first-class by giving them the one thing `remo add` (014) never did — a configure path. New provider-neutral `remo configure NAME` runs a new generic `ansible/ssh_configure.yml` against a registry `type="ssh"` entry, so an added host finally gets `remo-host`/zellij/`project-launch` and stops sitting in the `remo web` rail permanently badged `no_remo_host` with zero session targets. **No refactor of the four provider plays**: the role list was already shared via `tasks/configure_dev_tools.yml`, so a fifth playbook inherits the whole toolchain — the change is purely additive. The new play is two plays: a `localhost` play that asserts (never defaults) `remo_ssh_host`/`remo_ssh_user`/`remo_ssh_port` and `add_host`s them onto `ansible_host`/`ansible_user`/`ansible_port`/`ansible_ssh_private_key_file`, then a play targeting that named group (NOT `hosts: all`, which would pick up `inventory/hosts.yml`'s `hetzner_server` and whose zero-host case exits rc 0 — a silent false success). Four decisions the tests pin, each defending a *silent* failure: the identity is `| default(omit)`, never `'~/.ssh/id_rsa'` (aws_configure.yml's anti-pattern — a path default offers the WRONG key); it is paired with an explicit `-o IdentitiesOnly=yes`, which ansible-core does not add for `private_key_file` (without it a busy agent hits MaxAuthTries and dies "Too many authentication failures" while the passed key is never tried); both `raw` pre-flight probes (python3, `sudo -n true`) set `become: false`, since `ansible.cfg` sets `become = True` globally and a sudo probe run THROUGH sudo proves nothing; and `remo_user` is bound to `remo_ssh_user` rather than `group_vars/all.yml`'s `"remo"` — everything `user_setup` installs lands in `/home/{{ remo_user }}/.local/bin` and discovery logs in as the REGISTERED user, so the default would install `remo-host` into a second account and leave the console still reporting `no_remo_host` after a "successful" configure (it also skips the UID-1000 displacement, which only fires when 1000's holder is someone else). Root is refused (UID-1000 pin), the prompt defaults to no, and remo never reboots a host it did not provision — it reports via the task NAME, since `_filter_line` suppresses `debug` bodies. `core/known_hosts.py` gains `guard_added_ssh_host_only`, the mirror of `guard_not_added_ssh_host`: a provider host is refused naming `remo <type> upgrade NAME`, because the five configure plays are not interchangeable and a mis-target would succeed while configuring wrongly. **Bug fix in the same change** (`core/web_adopt.py`): `remo web push` hardcoded port 22 in `scan_and_verify_host_key`'s `ssh-keyscan` and `_lookup_trusted_keys`' `ssh-keygen -F`, so an added host on another port was reported `skipped/unreachable` — `authorize_service_key` never ran — with a remediation blaming a host that was reachable, or, worse, a different machine answering on 22 had its keys pushed. New `known_hosts_lookup_key()` follows OpenSSH's record form (bare for 22, `[host]:port` otherwise; verified against the real binary — neither direction is forgiving), and `-p` is omitted for 22 so every provider host's argv is byte-identical. `providers/added.py`'s `_reject_unsafe_field` now also rejects Jinja delimiters (registry values become extra-vars, which Ansible templates on the CONTROL NODE), re-checked on the configure read path so pre-existing entries are covered. `web/discovery.py`'s `no_remo_host` remediation now names the real command per host type instead of "re-run configure", which for an added host pointed at nothing. No registry schema change; no new runtime deps.
|
|
318
323
|
- 021-cli-plane-separation: Split the three-intent `remo <provider> update` verb into three single-intent verbs — `upgrade` (in-instance configure play only, zero provider-side writes, SC-001), `resize` (resource change only — the factory rejects a dimensionless invocation with a `PreconditionError` listing that provider's dimension flags, before dispatching to the provider), and `tag` (managed-marker write only, generated iff `supports_managed_marker`; read-before-write no-op when already tagged, `OperationFailedError` — not `create`'s warn-and-continue — on write failure, SC-002) — and moved `bootstrap` off the flat instance plane into a new descriptor-driven `host` subgroup (`remo {incus,proxmox} host bootstrap`, generated iff `host_commands` is non-empty; `CommandSpec` gained a `target: ArgumentSpec | None` field so host commands take the host positionally — incus optional/defaults to localhost, proxmox required). `core/provider_registry.py`: `ProviderDescriptor.update_options` removed, replaced by `upgrade_options`/`resize_dimensions`/`resize_options`/`tag_options`/`host_commands`; new `ArgumentSpec` dataclass. `cli/providers/factory.py`: `_build_update` replaced by `_build_upgrade`/`_build_resize`/`_build_tag`/`_build_host_group`; `_instance_argument` grew a `param` kwarg so the new verbs take `NAME` positionally (matching the provider impl's `name` kwarg) while `create`/`destroy`/`info` keep `--name`; `_resolve_entry_for_destroy`'s user-hint lookup is now descriptor-driven (reads `kwargs` under the `registry_fields` JSON key ending in `_user`) instead of a `"user"` literal. Every provider module split `update()` into `upgrade()`/`resize()`/`tag()` (marker-supporting providers only) reusing existing private helpers unchanged; `update_entry()` (the `remo shell` tools-update path) now delegates to `upgrade()`. Renamed `--user` → `--host-user` (incus) / `--node-user` (proxmox) everywhere it appears (`remo add --user`, a different flag, is unchanged). Made every printed remedy truthful (SC-003): the registry-migration tagging notice and `sync`'s "Mark permanently:" line now recommend `tag` (previously wrongly recommended `sync`/`update`, which never wrote the marker); `remo shell`'s version-mismatch prompt now names the exact `remo <type> upgrade <name>` command it will run. Breaking change, no shim, no registry schema change: `remo <type> update`, flat `bootstrap`, and the old `--user` spelling are all Click "unknown command"/"no such option" errors post-change. FakeProvider's descriptor gained `upgrade_options`/`resize_dimensions`/`tag_options`/one `host_commands` entry, proving the full new surface is descriptor-only (SC-005).
|
|
319
324
|
- 020-openapi-type-generation: Made the FastAPI service the machine-checked source of truth for every shape the browser console consumes, and made drift a build failure with an actionable message — three moves, in dependency order. **(1) Contract completeness, declaration-only** (not one serialized byte moves): `web/api/hosts.py` annotates `InstanceOut.status`/`SessionTargetOut.zellij_state`/`devcontainer_running` with the existing closed domain enums (dropping the `.value` unwrapping — a `str`-Enum serializes identically) and adds `KnownProviderType(str, Enum)` (`incus`/`hetzner`/`aws`/`proxmox`, fixed to the built-in set per FR-004a — never derived from the live `provider_registry`, so a third-party provider install can't perturb the artifact) typed `KnownProviderType | str` (`anyOf[$ref, string]`, keeping the wire field open); new `ErrorEnvelope`/`HealthResponse`/`ReadinessResponse`/`MintPairingResponse`/`DetailResponse` response models declare what four previously-untyped routes (`/health`, `/ready`, `/pairing/mint`, `POST /terminals`) already return, with the `{"error": {...}}` envelope declared only on routes that actually return it (never on `pairing.py`, whose 403 is `{"detail": ...}`). **(2) Generate and consume**: new stdlib `scripts/export_openapi.py` exports `frontend/src/api/generated/openapi.json` (`create_app().openapi()`, `sort_keys=True` + trailing newline for byte-reproducibility) and `terminal-frames.json` (a new `web/frames.py`: six Pydantic control-frame models + `InboundFrame`/`OutboundFrame` discriminated unions, wrapped via `TypeAdapter(...).json_schema()` in a `{protocol: "remo-terminal.v1", frame_version: 1, ...}` envelope that versions independently of the REST contract); `openapi-typescript` v7 (exact-pinned frontend devDependency) generates `schema.d.ts`, and a small synthetic-OpenAPI wrapper (`frontend/scripts/generate-frame-types.mjs`) generates `terminal-frames.d.ts` (openapi-typescript itself only accepts genuine OpenAPI documents). `web/api/terminals.py`'s five ad-hoc `{"v": 1, ...}` WS control-frame dict literals are gone, replaced by typed `frames.py` model construction/validation — `_handle_control`'s silent-drop behavior for malformed JSON, non-object payloads, and unknown frame types is preserved exactly (the one place this refactor could have changed runtime behavior; dedicated tests cover it). `frontend/src/api/client.ts` replaced ~12 hand-declared service-shaped interfaces with `components["schemas"][...]` aliases (console-owned shapes — `ApiError`, `ServiceStatus`, the forward-auth re-auth path — stay hand-written); `frontend/src/components/providerMeta.ts` now keys a `Record<InstanceStatus | KnownProviderType, ...>` off the generated unions, so a new enum member is a compile error at the presentation mapping while an off-union runtime value still renders via a preserved fallback (FR-013a — deleting that fallback to "achieve exhaustiveness" is explicitly wrong). **(3) Gate it**: three drift checks modeled on `tests/unit/test_docs_structure.py`'s established shape (name the artifact, group findings by kind, close with the exact regeneration command and a dependency-bump note) — `tests/unit/test_schema_drift.py` (REST + frame freshness against the live Python app, `test` CI job) and `frontend/scripts/check-types-fresh.mjs` (regenerated `.d.ts` vs. checked-in, `frontend` CI job) — fail, never skip, and never write a tracked file. New `docs/maintaining-generated-types.md` documents the four generated artifacts (never hand-edited; internal build input, no external compatibility promise, FR-029) and how to read a failure. No registry schema change; no new service runtime dependency.
|
|
320
|
-
- 019-hygiene-deps-docs: Repository hygiene pass across dependencies, dead code, and documentation — no behavior change except one deliberate CLI break. Annotated every dependency whose necessity isn't visible from `src/remo_cli/` imports alone (`hcloud`/`boto3` in `pyproject.toml` now name their real consumers — the Ansible `hetzner.hcloud`/`amazon.aws`/`community.aws` collections — and stay unconditional pending issue #94; the `httpx2` dev-extra comment clarifies it's a real pydantic package, not a typo) and annotated two currently-unreachable missing-boto3 code paths in `providers/aws.py` rather than deleting them. Removed the phantom `remo init` command from every doc surface (README, docs/aws.md, install.sh, CONTRIBUTING.md) — Ansible collections install automatically on first provider command via `core/ansible_runner.py::_ensure_collections`. Corrected this file's structure diagram, Commands, and Active Technologies sections to match the real tree (removed two phantom entries, documented 13 previously-undocumented modules, dropped two nonexistent `--extra` install commands) and rewrote `AGENTS.md`, which had described an unrelated project. Added `tests/unit/test_docs_structure.py`, a new CI-gating pytest module (stdlib-only, no new dependency) that parses both files' structure diagrams and fails the build on drift, naming the offending paths; `docs/maintaining-claude-md.md` documents the fix procedure. Deleted the dead `providers/proxmox.py::_parse_pct_json` (zero call sites) and its orphaned `import json`. Consolidated all four of `providers/hetzner.py`'s hand-rolled `urllib.request.Request` call sites onto the module's existing `_hetzner_api()` helper, preserving each site's distinct raise-vs-swallow/timeout/message-text contract exactly (`tests/unit/providers/test_hetzner_http.py` characterizes and pins the behavior). **Breaking change**: removed the `--yes`/`-y` flag from `remo <provider> create` on all four providers — it never had any effect (creation has no confirmation prompt); `--yes` is unchanged on `destroy`, `sync`, `snapshot restore`, `snapshot delete`, and `remo remove`.
|
|
321
325
|
Only the newest three entries live here — `update-agent-context.sh` discards the rest on every run. The complete history is archived in [`docs/feature-history.md`](docs/feature-history.md); move displaced entries there rather than letting the generator drop them.
|
|
322
326
|
|
|
323
327
|
|
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [4.3.1](https://github.com/get2knowio/remo/compare/v4.3.0...v4.3.1) (2026-08-05)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* three defects found while building `remo configure` ([#144](https://github.com/get2knowio/remo/issues/144)) ([608a914](https://github.com/get2knowio/remo/commit/608a9146560b9e1bf0e45811ce76a0c517dafa45))
|
|
9
|
+
|
|
10
|
+
## [4.3.0](https://github.com/get2knowio/remo/compare/v4.2.0...v4.3.0) (2026-08-05)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* **cli:** configure manually-added SSH hosts with `remo configure` ([#143](https://github.com/get2knowio/remo/issues/143)) ([4a34f70](https://github.com/get2knowio/remo/commit/4a34f70674f9afd85f92529ac119cc97a015f3a0))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Bug Fixes
|
|
19
|
+
|
|
20
|
+
* **web:** stop three light themes printing invisible text ([#141](https://github.com/get2knowio/remo/issues/141)) ([a1c2bea](https://github.com/get2knowio/remo/commit/a1c2bea06c13503fe6c623f2f18483c862c20b46))
|
|
21
|
+
|
|
3
22
|
## [4.2.0](https://github.com/get2knowio/remo/compare/v4.1.0...v4.2.0) (2026-08-05)
|
|
4
23
|
|
|
5
24
|
|
|
@@ -31,6 +31,7 @@ Principles I, IV, and VIII are machine-enforced — see [Quality Gates](#quality
|
|
|
31
31
|
- `core/registry.py`: stdlib `json` (format), `fcntl` (advisory locking via a `registry.lock` sidecar), `os.replace` (atomic writes). No new runtime deps. Setup API mirror payload moved to v2 (`contracts/mirror-payload-v2.md`) with a `payload_versions` capability handshake; an upgraded service still accepts v1 payloads. (015-registry-v2)
|
|
32
32
|
- `core/reconcile.py`: provider-agnostic sync-reconcile engine (`SyncScope`, `DiscoveredHost`, `ProbeResult`, `build_plan` (pure), `render_plan`, the consent gate, `apply_plan` via the existing `mutate_registry()`, and the `run_sync` driver). No new runtime deps — stdlib only, built on `core/registry.py`/`core/output.py` as-is. (016-sync-reconcile)
|
|
33
33
|
- `core/web_adopt.py` is now the single unified push engine (`run_push`; `run_adopt` is a thin deprecated alias) — first push adopts, later pushes re-sync, plus best-effort `remo-web@` revocation on removal, `--force` full re-authorization, and multi-workstation flap detection against the service's mirror-generation marker. `core/web_drift.py`: new stdlib-only offline registry-vs-push-cache diff (`diff_registry_against_cache`, `select_deployment`, `render_drift`) powering `remo web status` and the shared `out_of_date_notice()`/`emit_out_of_date_notice()` post-mutation nudge (importable without the `web` extra). Push cache bumped to `cache_version: 3` (`~/.config/remo/web-service.json`: per-deployment `{mirror_generation, instances}`, each instance retaining a non-secret connection tuple for revocation). Service side: `web/api/setup.py` writes/serves a `web-identity/mirror-meta.json` marker (generation + last-push descriptor) additively on `/setup/{status,registry}`; `web/state.py` mode detection fixed so a personal `~/.ssh/id_*` no longer forces `mount_configured` (non-writable `REMO_HOME` is the authoritative signal) with a new `REMO_WEB_MODE` override in `web/config.py`. No new runtime deps; no registry schema change. (017-web-adopt-simplify)
|
|
34
|
+
- No new runtime deps and no registry schema change. `ansible/ssh_configure.yml` is the first playbook to plumb an SSH **port** at all (`ansible_port` appeared nowhere in the repo before) and the first to take an identity from the registry rather than hardcoding `~/.ssh/id_rsa`; both arrive as namespaced `remo_ssh_*` extra-vars so no `ansible_*` name is ever emitted at extra-var precedence. `core/known_hosts.py` gains `guard_added_ssh_host_only`; `core/web_adopt.py` gains `known_hosts_lookup_key` and a `port=` parameter on `scan_and_verify_host_key`. Host key checking is inherited from `ansible.cfg` (disabled repo-wide) — a play-level override is impossible, since `ssh_common_args` is appended after `ssh_args` and ssh honours the first occurrence. (022-configure-added-hosts)
|
|
34
35
|
- No new runtime deps or registry schema change. `pyproject.toml`'s existing `hcloud`/`boto3`/`httpx2` entries gained consumer-attribution comments; `tests/unit/test_docs_structure.py` is a new stdlib-only pytest module (parses the structure diagram below, no new dependency). (019-hygiene-deps-docs)
|
|
35
36
|
- No new runtime deps; no registry schema change (the `host_user`/`node_user` JSON keys already matched the new flag spellings). Superseded for Proxmox by the `--node-user` → `--host-user` rename: both host-scoped providers now spell the node/host login `--host-user`/`host_user` on the CLI, in the provider kwargs and in `registry.json`; `core/registry.py` still reads a legacy `proxmox.node_user` key so pre-rename registries load unchanged and migrate on the next write. `core/provider_registry.py` gains `ArgumentSpec` (positional-argument metadata) and `CommandSpec.target`; `ProviderDescriptor.update_options` is replaced by `upgrade_options`/`resize_dimensions`/`resize_options`/`tag_options`/`host_commands`. `cli/providers/factory.py` gains `_build_upgrade`/`_build_resize`/`_build_tag`/`_build_host_group` (dropping `_build_update` and flat-mounted `bootstrap`). (021-cli-plane-separation)
|
|
36
37
|
|
|
@@ -48,7 +49,7 @@ src/remo_cli/ # Python CLI package (src layout, hatchling build)
|
|
|
48
49
|
│ ├── main.py # Root CLI group; mounts one group per remo_cli.core.provider_registry.all_descriptors()
|
|
49
50
|
│ ├── shell.py # remo shell — registry-dispatched update_entry() (delegates to upgrade()); prompt names the exact `remo <type> upgrade <name>` it will run; unknown type/ssh handled explicitly (no silent no-op)
|
|
50
51
|
│ ├── cp.py # remo cp
|
|
51
|
-
│ ├── added.py # remo add / remo remove — provider-neutral SSH host registration (
|
|
52
|
+
│ ├── added.py # remo add / remo remove / remo configure — provider-neutral SSH host registration (014) and configure (022)
|
|
52
53
|
│ ├── web.py # remo web {serve,check,push,status,adopt} — serve/check lazy-import remo_cli.web.* (NFR-008); push/status/adopt use core/web_adopt + core/web_drift only (adopt is a deprecated alias for push)
|
|
53
54
|
│ └── providers/
|
|
54
55
|
│ └── factory.py # build_provider_group(descriptor) generates create/destroy/upgrade/resize/list/info/sync/snapshot for every provider, plus tag (iff supports_managed_marker) and a host subgroup (iff host_commands non-empty) — the four hand-written per-provider CLI modules are gone
|
|
@@ -61,7 +62,7 @@ src/remo_cli/ # Python CLI package (src layout, hatchling build)
|
|
|
61
62
|
│ ├── hetzner_descriptor.py # metadata-only ProviderDescriptor declaration, no SDK imports
|
|
62
63
|
│ ├── aws_descriptor.py # metadata-only ProviderDescriptor declaration, no SDK imports
|
|
63
64
|
│ ├── proxmox_descriptor.py # metadata-only ProviderDescriptor declaration, no SDK imports
|
|
64
|
-
│ ├── added.py # Business logic for remo add / remo remove
|
|
65
|
+
│ ├── added.py # Business logic for remo add / remo remove / remo configure — registration (014) + generic configure play (022)
|
|
65
66
|
│ └── builtin.py # registers the four built-in descriptors; lazily auto-imported by provider_registry on first lookup
|
|
66
67
|
├── core/ # Shared utilities (no provider knowledge)
|
|
67
68
|
│ ├── config.py # REMO_HOME, paths, read-only registry accessor
|
|
@@ -134,6 +135,9 @@ ansible/ # Ansible playbooks (invoked by Python via subprocess
|
|
|
134
135
|
│ └── templates/
|
|
135
136
|
│ └── remo-host.sh.j2 # Versioned `remo-host` command (capabilities/sessions/attach)
|
|
136
137
|
├── incus_bootstrap.yml
|
|
138
|
+
├── ssh_configure.yml # Generic configure play for `remo add` hosts (022): maps remo_ssh_*
|
|
139
|
+
│ # -> ansible_port/ansible_ssh_private_key_file, binds remo_user to the
|
|
140
|
+
│ # registered account, reuses tasks/configure_dev_tools.yml
|
|
137
141
|
└── requirements.yml
|
|
138
142
|
|
|
139
143
|
scripts/ # Repo-root utility scripts (not part of the installed package)
|
|
@@ -316,9 +320,9 @@ checking nothing.
|
|
|
316
320
|
- Ansible 2.14+ / YAML: Follow standard conventions plus Constitution principles
|
|
317
321
|
|
|
318
322
|
## Recent Changes
|
|
323
|
+
- 022-configure-added-hosts: Made manually-added SSH hosts first-class by giving them the one thing `remo add` (014) never did — a configure path. New provider-neutral `remo configure NAME` runs a new generic `ansible/ssh_configure.yml` against a registry `type="ssh"` entry, so an added host finally gets `remo-host`/zellij/`project-launch` and stops sitting in the `remo web` rail permanently badged `no_remo_host` with zero session targets. **No refactor of the four provider plays**: the role list was already shared via `tasks/configure_dev_tools.yml`, so a fifth playbook inherits the whole toolchain — the change is purely additive. The new play is two plays: a `localhost` play that asserts (never defaults) `remo_ssh_host`/`remo_ssh_user`/`remo_ssh_port` and `add_host`s them onto `ansible_host`/`ansible_user`/`ansible_port`/`ansible_ssh_private_key_file`, then a play targeting that named group (NOT `hosts: all`, which would pick up `inventory/hosts.yml`'s `hetzner_server` and whose zero-host case exits rc 0 — a silent false success). Four decisions the tests pin, each defending a *silent* failure: the identity is `| default(omit)`, never `'~/.ssh/id_rsa'` (aws_configure.yml's anti-pattern — a path default offers the WRONG key); it is paired with an explicit `-o IdentitiesOnly=yes`, which ansible-core does not add for `private_key_file` (without it a busy agent hits MaxAuthTries and dies "Too many authentication failures" while the passed key is never tried); both `raw` pre-flight probes (python3, `sudo -n true`) set `become: false`, since `ansible.cfg` sets `become = True` globally and a sudo probe run THROUGH sudo proves nothing; and `remo_user` is bound to `remo_ssh_user` rather than `group_vars/all.yml`'s `"remo"` — everything `user_setup` installs lands in `/home/{{ remo_user }}/.local/bin` and discovery logs in as the REGISTERED user, so the default would install `remo-host` into a second account and leave the console still reporting `no_remo_host` after a "successful" configure (it also skips the UID-1000 displacement, which only fires when 1000's holder is someone else). Root is refused (UID-1000 pin), the prompt defaults to no, and remo never reboots a host it did not provision — it reports via the task NAME, since `_filter_line` suppresses `debug` bodies. `core/known_hosts.py` gains `guard_added_ssh_host_only`, the mirror of `guard_not_added_ssh_host`: a provider host is refused naming `remo <type> upgrade NAME`, because the five configure plays are not interchangeable and a mis-target would succeed while configuring wrongly. **Bug fix in the same change** (`core/web_adopt.py`): `remo web push` hardcoded port 22 in `scan_and_verify_host_key`'s `ssh-keyscan` and `_lookup_trusted_keys`' `ssh-keygen -F`, so an added host on another port was reported `skipped/unreachable` — `authorize_service_key` never ran — with a remediation blaming a host that was reachable, or, worse, a different machine answering on 22 had its keys pushed. New `known_hosts_lookup_key()` follows OpenSSH's record form (bare for 22, `[host]:port` otherwise; verified against the real binary — neither direction is forgiving), and `-p` is omitted for 22 so every provider host's argv is byte-identical. `providers/added.py`'s `_reject_unsafe_field` now also rejects Jinja delimiters (registry values become extra-vars, which Ansible templates on the CONTROL NODE), re-checked on the configure read path so pre-existing entries are covered. `web/discovery.py`'s `no_remo_host` remediation now names the real command per host type instead of "re-run configure", which for an added host pointed at nothing. No registry schema change; no new runtime deps.
|
|
319
324
|
- 021-cli-plane-separation: Split the three-intent `remo <provider> update` verb into three single-intent verbs — `upgrade` (in-instance configure play only, zero provider-side writes, SC-001), `resize` (resource change only — the factory rejects a dimensionless invocation with a `PreconditionError` listing that provider's dimension flags, before dispatching to the provider), and `tag` (managed-marker write only, generated iff `supports_managed_marker`; read-before-write no-op when already tagged, `OperationFailedError` — not `create`'s warn-and-continue — on write failure, SC-002) — and moved `bootstrap` off the flat instance plane into a new descriptor-driven `host` subgroup (`remo {incus,proxmox} host bootstrap`, generated iff `host_commands` is non-empty; `CommandSpec` gained a `target: ArgumentSpec | None` field so host commands take the host positionally — incus optional/defaults to localhost, proxmox required). `core/provider_registry.py`: `ProviderDescriptor.update_options` removed, replaced by `upgrade_options`/`resize_dimensions`/`resize_options`/`tag_options`/`host_commands`; new `ArgumentSpec` dataclass. `cli/providers/factory.py`: `_build_update` replaced by `_build_upgrade`/`_build_resize`/`_build_tag`/`_build_host_group`; `_instance_argument` grew a `param` kwarg so the new verbs take `NAME` positionally (matching the provider impl's `name` kwarg) while `create`/`destroy`/`info` keep `--name`; `_resolve_entry_for_destroy`'s user-hint lookup is now descriptor-driven (reads `kwargs` under the `registry_fields` JSON key ending in `_user`) instead of a `"user"` literal. Every provider module split `update()` into `upgrade()`/`resize()`/`tag()` (marker-supporting providers only) reusing existing private helpers unchanged; `update_entry()` (the `remo shell` tools-update path) now delegates to `upgrade()`. Renamed `--user` → `--host-user` (incus) / `--node-user` (proxmox) everywhere it appears (`remo add --user`, a different flag, is unchanged). Made every printed remedy truthful (SC-003): the registry-migration tagging notice and `sync`'s "Mark permanently:" line now recommend `tag` (previously wrongly recommended `sync`/`update`, which never wrote the marker); `remo shell`'s version-mismatch prompt now names the exact `remo <type> upgrade <name>` command it will run. Breaking change, no shim, no registry schema change: `remo <type> update`, flat `bootstrap`, and the old `--user` spelling are all Click "unknown command"/"no such option" errors post-change. FakeProvider's descriptor gained `upgrade_options`/`resize_dimensions`/`tag_options`/one `host_commands` entry, proving the full new surface is descriptor-only (SC-005).
|
|
320
325
|
- 020-openapi-type-generation: Made the FastAPI service the machine-checked source of truth for every shape the browser console consumes, and made drift a build failure with an actionable message — three moves, in dependency order. **(1) Contract completeness, declaration-only** (not one serialized byte moves): `web/api/hosts.py` annotates `InstanceOut.status`/`SessionTargetOut.zellij_state`/`devcontainer_running` with the existing closed domain enums (dropping the `.value` unwrapping — a `str`-Enum serializes identically) and adds `KnownProviderType(str, Enum)` (`incus`/`hetzner`/`aws`/`proxmox`, fixed to the built-in set per FR-004a — never derived from the live `provider_registry`, so a third-party provider install can't perturb the artifact) typed `KnownProviderType | str` (`anyOf[$ref, string]`, keeping the wire field open); new `ErrorEnvelope`/`HealthResponse`/`ReadinessResponse`/`MintPairingResponse`/`DetailResponse` response models declare what four previously-untyped routes (`/health`, `/ready`, `/pairing/mint`, `POST /terminals`) already return, with the `{"error": {...}}` envelope declared only on routes that actually return it (never on `pairing.py`, whose 403 is `{"detail": ...}`). **(2) Generate and consume**: new stdlib `scripts/export_openapi.py` exports `frontend/src/api/generated/openapi.json` (`create_app().openapi()`, `sort_keys=True` + trailing newline for byte-reproducibility) and `terminal-frames.json` (a new `web/frames.py`: six Pydantic control-frame models + `InboundFrame`/`OutboundFrame` discriminated unions, wrapped via `TypeAdapter(...).json_schema()` in a `{protocol: "remo-terminal.v1", frame_version: 1, ...}` envelope that versions independently of the REST contract); `openapi-typescript` v7 (exact-pinned frontend devDependency) generates `schema.d.ts`, and a small synthetic-OpenAPI wrapper (`frontend/scripts/generate-frame-types.mjs`) generates `terminal-frames.d.ts` (openapi-typescript itself only accepts genuine OpenAPI documents). `web/api/terminals.py`'s five ad-hoc `{"v": 1, ...}` WS control-frame dict literals are gone, replaced by typed `frames.py` model construction/validation — `_handle_control`'s silent-drop behavior for malformed JSON, non-object payloads, and unknown frame types is preserved exactly (the one place this refactor could have changed runtime behavior; dedicated tests cover it). `frontend/src/api/client.ts` replaced ~12 hand-declared service-shaped interfaces with `components["schemas"][...]` aliases (console-owned shapes — `ApiError`, `ServiceStatus`, the forward-auth re-auth path — stay hand-written); `frontend/src/components/providerMeta.ts` now keys a `Record<InstanceStatus | KnownProviderType, ...>` off the generated unions, so a new enum member is a compile error at the presentation mapping while an off-union runtime value still renders via a preserved fallback (FR-013a — deleting that fallback to "achieve exhaustiveness" is explicitly wrong). **(3) Gate it**: three drift checks modeled on `tests/unit/test_docs_structure.py`'s established shape (name the artifact, group findings by kind, close with the exact regeneration command and a dependency-bump note) — `tests/unit/test_schema_drift.py` (REST + frame freshness against the live Python app, `test` CI job) and `frontend/scripts/check-types-fresh.mjs` (regenerated `.d.ts` vs. checked-in, `frontend` CI job) — fail, never skip, and never write a tracked file. New `docs/maintaining-generated-types.md` documents the four generated artifacts (never hand-edited; internal build input, no external compatibility promise, FR-029) and how to read a failure. No registry schema change; no new service runtime dependency.
|
|
321
|
-
- 019-hygiene-deps-docs: Repository hygiene pass across dependencies, dead code, and documentation — no behavior change except one deliberate CLI break. Annotated every dependency whose necessity isn't visible from `src/remo_cli/` imports alone (`hcloud`/`boto3` in `pyproject.toml` now name their real consumers — the Ansible `hetzner.hcloud`/`amazon.aws`/`community.aws` collections — and stay unconditional pending issue #94; the `httpx2` dev-extra comment clarifies it's a real pydantic package, not a typo) and annotated two currently-unreachable missing-boto3 code paths in `providers/aws.py` rather than deleting them. Removed the phantom `remo init` command from every doc surface (README, docs/aws.md, install.sh, CONTRIBUTING.md) — Ansible collections install automatically on first provider command via `core/ansible_runner.py::_ensure_collections`. Corrected this file's structure diagram, Commands, and Active Technologies sections to match the real tree (removed two phantom entries, documented 13 previously-undocumented modules, dropped two nonexistent `--extra` install commands) and rewrote `AGENTS.md`, which had described an unrelated project. Added `tests/unit/test_docs_structure.py`, a new CI-gating pytest module (stdlib-only, no new dependency) that parses both files' structure diagrams and fails the build on drift, naming the offending paths; `docs/maintaining-claude-md.md` documents the fix procedure. Deleted the dead `providers/proxmox.py::_parse_pct_json` (zero call sites) and its orphaned `import json`. Consolidated all four of `providers/hetzner.py`'s hand-rolled `urllib.request.Request` call sites onto the module's existing `_hetzner_api()` helper, preserving each site's distinct raise-vs-swallow/timeout/message-text contract exactly (`tests/unit/providers/test_hetzner_http.py` characterizes and pins the behavior). **Breaking change**: removed the `--yes`/`-y` flag from `remo <provider> create` on all four providers — it never had any effect (creation has no confirmation prompt); `--yes` is unchanged on `destroy`, `sync`, `snapshot restore`, `snapshot delete`, and `remo remove`.
|
|
322
326
|
Only the newest three entries live here — `update-agent-context.sh` discards the rest on every run. The complete history is archived in [`docs/feature-history.md`](docs/feature-history.md); move displaced entries there rather than letting the generator drop them.
|
|
323
327
|
|
|
324
328
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: remo-cli
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.3.1
|
|
4
4
|
Summary: Remote development environment CLI
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -286,11 +286,63 @@ as any other host.
|
|
|
286
286
|
|
|
287
287
|
```bash
|
|
288
288
|
remo add mybox 10.0.0.5 --port 2222 --identity ~/.ssh/mybox_ed25519 --verify
|
|
289
|
+
remo configure mybox # install the dev tools (see below)
|
|
289
290
|
remo shell mybox # open a shell over direct SSH
|
|
290
291
|
remo cp ./deploy.sh mybox:/tmp/ # copy a file up
|
|
291
292
|
remo remove mybox # deregister when you're done
|
|
292
293
|
```
|
|
293
294
|
|
|
295
|
+
### Install the dev tools: `remo configure`
|
|
296
|
+
|
|
297
|
+
`add` only *registers* a host — it installs nothing. Until you configure it, an
|
|
298
|
+
added host has no `remo-host`, no zellij and no project launcher, so `remo web`
|
|
299
|
+
lists it with an **"update req."** badge and no sessions you can open.
|
|
300
|
+
|
|
301
|
+
```bash
|
|
302
|
+
remo configure NAME [--only TOOL] [--skip TOOL] [--yes] [-v]
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
This runs the same Ansible play the providers use, so the host ends up with
|
|
306
|
+
Docker, Node.js, zellij, fzf, the GitHub CLI, the devcontainers CLI and
|
|
307
|
+
`remo-host`. It is idempotent — re-run it any time to refresh the tools, which
|
|
308
|
+
is also how you update a host after upgrading `remo` itself.
|
|
309
|
+
|
|
310
|
+
Two things worth knowing before the first run:
|
|
311
|
+
|
|
312
|
+
- It configures **the account you registered** and grants that account
|
|
313
|
+
passwordless sudo. It also apt-upgrades the system. You are asked to confirm
|
|
314
|
+
(defaulting to *no*); `--yes` skips the prompt.
|
|
315
|
+
- The host must be Debian/Ubuntu with `python3` and sudo available. Registering
|
|
316
|
+
as `root` is refused — the workspace account is pinned to UID 1000, which
|
|
317
|
+
would break the host.
|
|
318
|
+
|
|
319
|
+
Unlike the provider `create` path, remo **never reboots** a host it did not
|
|
320
|
+
provision; if package updates need one, it tells you and leaves the decision to
|
|
321
|
+
you. For a provider-managed host use `remo <provider> upgrade NAME` instead —
|
|
322
|
+
`remo configure` refuses those and names the right command.
|
|
323
|
+
|
|
324
|
+
```bash
|
|
325
|
+
remo configure mybox # first-time setup, or refresh the tools
|
|
326
|
+
remo configure mybox --skip docker # everything except Docker
|
|
327
|
+
remo web push # authorize the web service on it
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
#### Example: an OrbStack VM
|
|
331
|
+
|
|
332
|
+
[`docs/examples/orbstack-cloud-init.yaml`](docs/examples/orbstack-cloud-init.yaml)
|
|
333
|
+
prepares an [OrbStack](https://orbstack.dev) VM on a Mac so `remo add` and
|
|
334
|
+
`remo configure` work first time:
|
|
335
|
+
|
|
336
|
+
```bash
|
|
337
|
+
orb create ubuntu remo-mbp -c docs/examples/orbstack-cloud-init.yaml
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
It installs only what Ansible needs to connect and hand over — `remo configure`
|
|
341
|
+
does the rest. It deliberately creates no user: OrbStack already makes one named
|
|
342
|
+
after your macOS account holding UID 1000, and registering *that* user is what
|
|
343
|
+
puts `remo-host` in the home directory the web service logs into, while skipping
|
|
344
|
+
`user_setup`'s UID-1000 reassignment entirely.
|
|
345
|
+
|
|
294
346
|
`remo remove NAME [--yes]` deregisters an added host by deleting **only** the
|
|
295
347
|
local registry entry — it makes no connection to and no change on the remote
|
|
296
348
|
environment (unlike a provider `destroy`, which tears down infrastructure). It
|
|
@@ -323,6 +375,8 @@ remo add NAME [user@]host[:port] # Register a single SSH host
|
|
|
323
375
|
remo add NAME host --port 2222 --identity ~/.ssh/key # Custom port + key
|
|
324
376
|
remo add NAME host --verify # Fail-closed SSH reachability check first
|
|
325
377
|
remo add NAME host --user alice # Override default SSH user (default: remo)
|
|
378
|
+
remo configure NAME # Install/refresh the dev tools on an added host
|
|
379
|
+
remo configure NAME --skip docker # Skip a tool (repeatable; --only is the inverse)
|
|
326
380
|
remo remove NAME [--yes] # Deregister an added host (local-only)
|
|
327
381
|
|
|
328
382
|
# Hetzner Cloud
|
|
@@ -260,11 +260,63 @@ as any other host.
|
|
|
260
260
|
|
|
261
261
|
```bash
|
|
262
262
|
remo add mybox 10.0.0.5 --port 2222 --identity ~/.ssh/mybox_ed25519 --verify
|
|
263
|
+
remo configure mybox # install the dev tools (see below)
|
|
263
264
|
remo shell mybox # open a shell over direct SSH
|
|
264
265
|
remo cp ./deploy.sh mybox:/tmp/ # copy a file up
|
|
265
266
|
remo remove mybox # deregister when you're done
|
|
266
267
|
```
|
|
267
268
|
|
|
269
|
+
### Install the dev tools: `remo configure`
|
|
270
|
+
|
|
271
|
+
`add` only *registers* a host — it installs nothing. Until you configure it, an
|
|
272
|
+
added host has no `remo-host`, no zellij and no project launcher, so `remo web`
|
|
273
|
+
lists it with an **"update req."** badge and no sessions you can open.
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
remo configure NAME [--only TOOL] [--skip TOOL] [--yes] [-v]
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
This runs the same Ansible play the providers use, so the host ends up with
|
|
280
|
+
Docker, Node.js, zellij, fzf, the GitHub CLI, the devcontainers CLI and
|
|
281
|
+
`remo-host`. It is idempotent — re-run it any time to refresh the tools, which
|
|
282
|
+
is also how you update a host after upgrading `remo` itself.
|
|
283
|
+
|
|
284
|
+
Two things worth knowing before the first run:
|
|
285
|
+
|
|
286
|
+
- It configures **the account you registered** and grants that account
|
|
287
|
+
passwordless sudo. It also apt-upgrades the system. You are asked to confirm
|
|
288
|
+
(defaulting to *no*); `--yes` skips the prompt.
|
|
289
|
+
- The host must be Debian/Ubuntu with `python3` and sudo available. Registering
|
|
290
|
+
as `root` is refused — the workspace account is pinned to UID 1000, which
|
|
291
|
+
would break the host.
|
|
292
|
+
|
|
293
|
+
Unlike the provider `create` path, remo **never reboots** a host it did not
|
|
294
|
+
provision; if package updates need one, it tells you and leaves the decision to
|
|
295
|
+
you. For a provider-managed host use `remo <provider> upgrade NAME` instead —
|
|
296
|
+
`remo configure` refuses those and names the right command.
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
remo configure mybox # first-time setup, or refresh the tools
|
|
300
|
+
remo configure mybox --skip docker # everything except Docker
|
|
301
|
+
remo web push # authorize the web service on it
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
#### Example: an OrbStack VM
|
|
305
|
+
|
|
306
|
+
[`docs/examples/orbstack-cloud-init.yaml`](docs/examples/orbstack-cloud-init.yaml)
|
|
307
|
+
prepares an [OrbStack](https://orbstack.dev) VM on a Mac so `remo add` and
|
|
308
|
+
`remo configure` work first time:
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
orb create ubuntu remo-mbp -c docs/examples/orbstack-cloud-init.yaml
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
It installs only what Ansible needs to connect and hand over — `remo configure`
|
|
315
|
+
does the rest. It deliberately creates no user: OrbStack already makes one named
|
|
316
|
+
after your macOS account holding UID 1000, and registering *that* user is what
|
|
317
|
+
puts `remo-host` in the home directory the web service logs into, while skipping
|
|
318
|
+
`user_setup`'s UID-1000 reassignment entirely.
|
|
319
|
+
|
|
268
320
|
`remo remove NAME [--yes]` deregisters an added host by deleting **only** the
|
|
269
321
|
local registry entry — it makes no connection to and no change on the remote
|
|
270
322
|
environment (unlike a provider `destroy`, which tears down infrastructure). It
|
|
@@ -297,6 +349,8 @@ remo add NAME [user@]host[:port] # Register a single SSH host
|
|
|
297
349
|
remo add NAME host --port 2222 --identity ~/.ssh/key # Custom port + key
|
|
298
350
|
remo add NAME host --verify # Fail-closed SSH reachability check first
|
|
299
351
|
remo add NAME host --user alice # Override default SSH user (default: remo)
|
|
352
|
+
remo configure NAME # Install/refresh the dev tools on an added host
|
|
353
|
+
remo configure NAME --skip docker # Skip a tool (repeatable; --only is the inverse)
|
|
300
354
|
remo remove NAME [--yes] # Deregister an added host (local-only)
|
|
301
355
|
|
|
302
356
|
# Hetzner Cloud
|
|
@@ -243,11 +243,25 @@
|
|
|
243
243
|
- user_setup_cloud_keys.content | default('') | length > 0
|
|
244
244
|
# --- end cloud-injected key bootstrap --------------------------------------
|
|
245
245
|
|
|
246
|
+
# The docker group only exists if the `docker` role ran. Skipping that role
|
|
247
|
+
# (`--skip docker`, or `configure_docker=false`) previously made the next task
|
|
248
|
+
# fail outright with "Group docker does not exist", taking the whole configure
|
|
249
|
+
# down after it had already changed the host — so `--skip docker` was, in
|
|
250
|
+
# effect, not a supported option on any provider. Gate on the group rather than
|
|
251
|
+
# on `configure_docker` so a host where Docker was installed by other means
|
|
252
|
+
# (or by an earlier run) still gets the membership.
|
|
253
|
+
- name: Check whether a docker group exists
|
|
254
|
+
ansible.builtin.command: getent group docker
|
|
255
|
+
register: user_setup_docker_group
|
|
256
|
+
changed_when: false
|
|
257
|
+
failed_when: false
|
|
258
|
+
|
|
246
259
|
- name: Add remo user to docker group
|
|
247
260
|
ansible.builtin.user:
|
|
248
261
|
name: "{{ remo_user }}"
|
|
249
262
|
groups: docker
|
|
250
263
|
append: true
|
|
264
|
+
when: user_setup_docker_group.rc | default(1) == 0
|
|
251
265
|
|
|
252
266
|
- name: Configure passwordless sudo for remo user
|
|
253
267
|
ansible.builtin.lineinfile:
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
---
|
|
2
|
+
# Generic SSH Configure Playbook — install/refresh remo's dev tools on a host
|
|
3
|
+
# that NO provider manages (registry type="ssh", registered via `remo add`).
|
|
4
|
+
#
|
|
5
|
+
# Caller: src/remo_cli/providers/added.py::configure (`remo configure NAME`)
|
|
6
|
+
#
|
|
7
|
+
# Usage:
|
|
8
|
+
# ./run.sh ssh_configure.yml \
|
|
9
|
+
# -e remo_ssh_host=10.0.0.5 -e remo_ssh_user=remo -e remo_ssh_port=2222 \
|
|
10
|
+
# -e remo_ssh_identity=~/.ssh/k
|
|
11
|
+
#
|
|
12
|
+
# Required variables (the CLI ALWAYS passes all three; none is defaulted here on
|
|
13
|
+
# purpose — a missing port must fail loudly, never quietly fall back to 22):
|
|
14
|
+
# remo_ssh_host: address of the registered host
|
|
15
|
+
# remo_ssh_user: SSH user, which is ALSO the account this play configures
|
|
16
|
+
# remo_ssh_port: SSH port
|
|
17
|
+
#
|
|
18
|
+
# Optional variables:
|
|
19
|
+
# remo_ssh_identity: private key path. `~` is expanded by the ssh connection
|
|
20
|
+
# plugin (os.path.expanduser), so no client-side expansion is needed.
|
|
21
|
+
# configure_docker / configure_user_setup / configure_nodejs /
|
|
22
|
+
# configure_devcontainers / configure_github_cli / configure_fzf /
|
|
23
|
+
# configure_zellij: per-tool toggles — see tasks/configure_dev_tools.yml
|
|
24
|
+
#
|
|
25
|
+
# Host key checking is INHERITED from ansible.cfg, which disables it repo-wide
|
|
26
|
+
# (host_key_checking=False AND StrictHostKeyChecking=no in ssh_args). That is a
|
|
27
|
+
# deliberate inheritance, not an oversight: ssh_common_args is appended AFTER
|
|
28
|
+
# ssh_args and ssh honours the first occurrence of an option, so a play-level
|
|
29
|
+
# override here would be silently ignored. Tightening it is a repo-wide change
|
|
30
|
+
# to ansible.cfg across every playbook, not a per-play special case.
|
|
31
|
+
|
|
32
|
+
- name: Build the inventory for the registered SSH host
|
|
33
|
+
hosts: localhost
|
|
34
|
+
connection: local
|
|
35
|
+
gather_facts: false
|
|
36
|
+
become: false
|
|
37
|
+
|
|
38
|
+
tasks:
|
|
39
|
+
- name: Validate the connection variables supplied by the CLI
|
|
40
|
+
ansible.builtin.assert:
|
|
41
|
+
that:
|
|
42
|
+
- remo_ssh_host is defined and (remo_ssh_host | string | length) > 0
|
|
43
|
+
- remo_ssh_user is defined and (remo_ssh_user | string | length) > 0
|
|
44
|
+
- remo_ssh_port is defined
|
|
45
|
+
- (remo_ssh_port | int) > 0
|
|
46
|
+
- (remo_ssh_port | int) < 65536
|
|
47
|
+
fail_msg: >-
|
|
48
|
+
ssh_configure.yml needs remo_ssh_host, remo_ssh_user and remo_ssh_port.
|
|
49
|
+
Run it through 'remo configure <name>' rather than directly.
|
|
50
|
+
quiet: true
|
|
51
|
+
|
|
52
|
+
- name: Refuse to reconfigure the root account
|
|
53
|
+
ansible.builtin.assert:
|
|
54
|
+
that:
|
|
55
|
+
- remo_ssh_user != 'root'
|
|
56
|
+
fail_msg: >-
|
|
57
|
+
remo configures the SSH user you registered as the workspace account,
|
|
58
|
+
and user_setup pins that account to UID 1000 — doing that to root
|
|
59
|
+
would break the host. Re-register with a normal user:
|
|
60
|
+
remo add <name> {{ remo_ssh_host | default('<host>') }} --user <user>
|
|
61
|
+
quiet: true
|
|
62
|
+
|
|
63
|
+
# Fixed, colon-free name: add_host runs its `name` through parse_address(),
|
|
64
|
+
# so an IP-with-port name would set ansible_ssh_port behind our back.
|
|
65
|
+
- name: Add the registered SSH host to the in-memory inventory
|
|
66
|
+
ansible.builtin.add_host:
|
|
67
|
+
name: remo_ssh_target
|
|
68
|
+
groups:
|
|
69
|
+
- remo_ssh_hosts
|
|
70
|
+
ansible_host: "{{ remo_ssh_host }}"
|
|
71
|
+
ansible_user: "{{ remo_ssh_user }}"
|
|
72
|
+
ansible_port: "{{ remo_ssh_port | int }}"
|
|
73
|
+
ansible_python_interpreter: /usr/bin/python3
|
|
74
|
+
# `omit`, NOT a '~/.ssh/id_rsa' fallback. "No identity stored" means
|
|
75
|
+
# "let ssh/agent resolve the key"; defaulting to a path here is exactly
|
|
76
|
+
# how aws_configure.yml can silently offer the WRONG key.
|
|
77
|
+
ansible_ssh_private_key_file: "{{ remo_ssh_identity | default(omit) }}"
|
|
78
|
+
# ansible-core emits `-o IdentityFile=` for private_key_file but NOT
|
|
79
|
+
# `-o IdentitiesOnly=yes` (that pairing exists only on the `private_key`
|
|
80
|
+
# branch of connection/ssh.py). Without this, ssh offers every agent key
|
|
81
|
+
# first and a busy agent hits MaxAuthTries — "Too many authentication
|
|
82
|
+
# failures" — while the key you passed is never tried. core/ssh.py pairs
|
|
83
|
+
# them for this exact reason; match it.
|
|
84
|
+
ansible_ssh_common_args: >-
|
|
85
|
+
{{ '-o IdentitiesOnly=yes'
|
|
86
|
+
if (remo_ssh_identity | default('') | length) > 0
|
|
87
|
+
else omit }}
|
|
88
|
+
|
|
89
|
+
- name: Configure a manually-registered SSH host with development tools
|
|
90
|
+
hosts: remo_ssh_hosts
|
|
91
|
+
become: true
|
|
92
|
+
# Facts are gathered explicitly in pre_tasks, AFTER the interpreter probe: the
|
|
93
|
+
# implicit gather runs a Python module, so on a host with no python3 it fails
|
|
94
|
+
# first, with a message that never names the actual problem.
|
|
95
|
+
gather_facts: false
|
|
96
|
+
|
|
97
|
+
vars:
|
|
98
|
+
# The account remo configures IS the account you registered. Everything
|
|
99
|
+
# user_setup installs lands in /home/{{ remo_user }}/.local/bin — including
|
|
100
|
+
# remo-host, which `remo web` discovery invokes over SSH as the REGISTERED
|
|
101
|
+
# user. Inventing a second 'remo' user here would leave discovery reporting
|
|
102
|
+
# no_remo_host after a "successful" configure. For the default `remo add`
|
|
103
|
+
# user this is a no-op: remo_user is 'remo' either way. It also skips the
|
|
104
|
+
# UID-1000 displacement in user_setup, which only fires when the holder of
|
|
105
|
+
# UID 1000 is someone other than remo_user.
|
|
106
|
+
remo_user: "{{ remo_ssh_user }}"
|
|
107
|
+
# No cloud provider injected a key on this host, so there is nothing to
|
|
108
|
+
# bootstrap FROM — and root's authorized_keys on a machine remo does not own
|
|
109
|
+
# belongs to its owner, not to a workspace account (#121). With remo_user
|
|
110
|
+
# set above, the connecting user's own authorized_keys is already non-empty,
|
|
111
|
+
# so the first-contact gate would decline anyway; false makes it structural.
|
|
112
|
+
user_setup_bootstrap_cloud_keys: false
|
|
113
|
+
|
|
114
|
+
pre_tasks:
|
|
115
|
+
# `raw` needs no interpreter on the target, so it can diagnose the absence
|
|
116
|
+
# of one. `become: false` is mandatory on both probes: ansible.cfg sets
|
|
117
|
+
# become=True globally, so without it they run THROUGH sudo and prove
|
|
118
|
+
# nothing — the sudo probe would report success on a host where the user
|
|
119
|
+
# cannot in fact sudo, because the connection itself would have failed.
|
|
120
|
+
- name: Probe for a Python 3 interpreter
|
|
121
|
+
ansible.builtin.raw: command -v python3
|
|
122
|
+
become: false
|
|
123
|
+
register: remo_python_probe
|
|
124
|
+
changed_when: false
|
|
125
|
+
failed_when: false
|
|
126
|
+
|
|
127
|
+
- name: Fail with actionable guidance when the host has no Python 3
|
|
128
|
+
ansible.builtin.fail:
|
|
129
|
+
msg: >-
|
|
130
|
+
No python3 on {{ ansible_host }}:{{ ansible_port }} — Ansible needs a
|
|
131
|
+
Python 3 interpreter on the target. Install it and retry:
|
|
132
|
+
ssh -p {{ ansible_port }} {{ ansible_user }}@{{ ansible_host }}
|
|
133
|
+
'sudo apt-get update && sudo apt-get install -y python3'
|
|
134
|
+
when: (remo_python_probe.rc | default(1)) != 0
|
|
135
|
+
|
|
136
|
+
- name: Probe for passwordless sudo
|
|
137
|
+
ansible.builtin.raw: sudo -n true
|
|
138
|
+
become: false
|
|
139
|
+
register: remo_sudo_probe
|
|
140
|
+
changed_when: false
|
|
141
|
+
failed_when: false
|
|
142
|
+
|
|
143
|
+
- name: Fail with actionable guidance when the SSH user cannot sudo
|
|
144
|
+
ansible.builtin.fail:
|
|
145
|
+
msg: >-
|
|
146
|
+
'{{ ansible_user }}' cannot sudo without a password on
|
|
147
|
+
{{ ansible_host }}. remo installs packages and system services, so it
|
|
148
|
+
needs passwordless sudo. Grant it once, then retry:
|
|
149
|
+
echo '{{ ansible_user }} ALL=(ALL) NOPASSWD:ALL' | sudo tee
|
|
150
|
+
/etc/sudoers.d/{{ ansible_user }} && sudo chmod 440
|
|
151
|
+
/etc/sudoers.d/{{ ansible_user }}
|
|
152
|
+
when: (remo_sudo_probe.rc | default(1)) != 0
|
|
153
|
+
|
|
154
|
+
- name: Gather facts
|
|
155
|
+
ansible.builtin.setup:
|
|
156
|
+
|
|
157
|
+
- name: Fail on a non-apt host before any role starts changing it
|
|
158
|
+
ansible.builtin.assert:
|
|
159
|
+
that:
|
|
160
|
+
- ansible_os_family | default('') == 'Debian'
|
|
161
|
+
fail_msg: >-
|
|
162
|
+
{{ ansible_host }} runs {{ ansible_distribution | default('an unknown
|
|
163
|
+
distribution') }} {{ ansible_distribution_version | default('') }};
|
|
164
|
+
every remo role is apt-based (Ubuntu and Debian are supported).
|
|
165
|
+
Nothing was changed.
|
|
166
|
+
quiet: true
|
|
167
|
+
|
|
168
|
+
- name: Wait for apt to be ready
|
|
169
|
+
ansible.builtin.shell: |
|
|
170
|
+
while fuser /var/lib/dpkg/lock-frontend /var/lib/apt/lists/lock /var/cache/apt/archives/lock >/dev/null 2>&1; do
|
|
171
|
+
sleep 5
|
|
172
|
+
done
|
|
173
|
+
args:
|
|
174
|
+
executable: /bin/bash
|
|
175
|
+
register: apt_lock_result
|
|
176
|
+
changed_when: false
|
|
177
|
+
retries: 30
|
|
178
|
+
delay: 10
|
|
179
|
+
until: apt_lock_result.rc | default(1) == 0
|
|
180
|
+
|
|
181
|
+
# Unwedge apt before the first apt call: a sources line naming a keyring
|
|
182
|
+
# that no longer exists makes apt refuse to read ANY source list, which
|
|
183
|
+
# would fail the cache update below and leave the host unrepairable (#120).
|
|
184
|
+
- name: Repair apt sources referencing a missing keyring
|
|
185
|
+
ansible.builtin.include_tasks: tasks/repair_apt_sources.yml
|
|
186
|
+
|
|
187
|
+
- name: Update apt cache
|
|
188
|
+
ansible.builtin.apt:
|
|
189
|
+
update_cache: true
|
|
190
|
+
cache_valid_time: 3600
|
|
191
|
+
|
|
192
|
+
- name: Upgrade all packages
|
|
193
|
+
ansible.builtin.apt:
|
|
194
|
+
upgrade: dist
|
|
195
|
+
autoremove: true
|
|
196
|
+
|
|
197
|
+
- name: Check if reboot is required
|
|
198
|
+
ansible.builtin.stat:
|
|
199
|
+
path: /var/run/reboot-required
|
|
200
|
+
register: reboot_required_file
|
|
201
|
+
|
|
202
|
+
tasks:
|
|
203
|
+
- name: Configure dev tools
|
|
204
|
+
ansible.builtin.include_tasks: tasks/configure_dev_tools.yml
|
|
205
|
+
|
|
206
|
+
post_tasks:
|
|
207
|
+
# Deliberately NOT ansible.builtin.reboot: hetzner/aws reboot because remo
|
|
208
|
+
# created and solely owns those VMs. This host is the user's own machine and
|
|
209
|
+
# may be running their other work; remo has no API to bring it back, and no
|
|
210
|
+
# way to notice if it doesn't. Report and let them choose.
|
|
211
|
+
#
|
|
212
|
+
# The message lives in the task NAME, not the debug body: the non-verbose
|
|
213
|
+
# output filter (core/ansible_runner.py::_filter_line) suppresses debug msg
|
|
214
|
+
# bodies entirely but does print task names — and clears them on `skipping:`,
|
|
215
|
+
# so this line appears exactly when a reboot really is pending.
|
|
216
|
+
- name: "Reboot pending — remo did NOT reboot this host (it is yours, not remo-provisioned); run 'sudo reboot' when convenient"
|
|
217
|
+
ansible.builtin.debug:
|
|
218
|
+
msg: >-
|
|
219
|
+
Package updates need a reboot:
|
|
220
|
+
ssh -p {{ ansible_port }} {{ ansible_user }}@{{ ansible_host }} 'sudo reboot'
|
|
221
|
+
when: reboot_required_file.stat.exists | default(false)
|
|
222
|
+
|
|
223
|
+
- name: Display completion message
|
|
224
|
+
ansible.builtin.debug:
|
|
225
|
+
msg: |
|
|
226
|
+
Host configuration complete!
|
|
227
|
+
|
|
228
|
+
Configured account: {{ remo_user }}
|
|
229
|
+
Workspace: {{ dev_workspace_dir }}
|
|
230
|
+
|
|
231
|
+
Installed:
|
|
232
|
+
- Docker (with docker-compose plugin)
|
|
233
|
+
- User '{{ remo_user }}' with sudo NOPASSWD access and docker group
|
|
234
|
+
- Node.js LTS (v{{ nodejs_version }})
|
|
235
|
+
- fzf, @devcontainers/cli, GitHub CLI (gh), Zellij
|
|
236
|
+
- remo-host, which is what makes this host appear in 'remo web'
|
|
237
|
+
|
|
238
|
+
Connect with:
|
|
239
|
+
remo shell
|
|
240
|
+
ssh -p {{ ansible_port }} {{ ansible_user }}@{{ ansible_host }}
|