remo-cli 4.2.0__tar.gz → 4.3.0__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.0/.release-please-manifest.json +3 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/AGENTS.md +7 -3
- {remo_cli-4.2.0 → remo_cli-4.3.0}/CHANGELOG.md +12 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/CLAUDE.md +7 -3
- {remo_cli-4.2.0 → remo_cli-4.3.0}/PKG-INFO +39 -1
- {remo_cli-4.2.0 → remo_cli-4.3.0}/README.md +38 -0
- remo_cli-4.3.0/ansible/ssh_configure.yml +240 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/docs/web-session-interface.md +9 -2
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/theme/terminalThemes.test.ts +38 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/theme/terminalThemes.ts +28 -5
- {remo_cli-4.2.0 → remo_cli-4.3.0}/pyproject.toml +1 -1
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/014-register-ssh-host/spec.md +3 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/cli/added.py +71 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/cli/main.py +2 -1
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/core/known_hosts.py +51 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/core/web_adopt.py +44 -9
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/providers/added.py +135 -1
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/web/discovery.py +25 -2
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/ansible/test_apt_sources_repair.py +1 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/ansible/test_authorized_keys_bootstrap.py +6 -2
- remo_cli-4.3.0/tests/ansible/test_ssh_configure_connection.py +203 -0
- remo_cli-4.3.0/tests/unit/cli/test_configure_cmd.py +115 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/cli/test_main.py +1 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/core/test_web_adopt_trust.py +156 -1
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/providers/test_added_add.py +30 -0
- remo_cli-4.3.0/tests/unit/providers/test_added_configure.py +226 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/providers/test_added_provider_guard.py +79 -1
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/web/test_discovery.py +44 -1
- {remo_cli-4.2.0 → remo_cli-4.3.0}/uv.lock +1 -1
- remo_cli-4.2.0/.release-please-manifest.json +0 -3
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.claude/skills/release/SKILL.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.claude/skills/speckit-analyze/SKILL.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.claude/skills/speckit-checklist/SKILL.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.claude/skills/speckit-clarify/SKILL.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.claude/skills/speckit-constitution/SKILL.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.claude/skills/speckit-converge/SKILL.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.claude/skills/speckit-git-commit/SKILL.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.claude/skills/speckit-git-feature/SKILL.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.claude/skills/speckit-git-initialize/SKILL.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.claude/skills/speckit-git-remote/SKILL.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.claude/skills/speckit-git-validate/SKILL.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.claude/skills/speckit-implement/SKILL.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.claude/skills/speckit-plan/SKILL.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.claude/skills/speckit-specify/SKILL.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.claude/skills/speckit-tasks/SKILL.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.claude/skills/speckit-taskstoissues/SKILL.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.devcontainer/devcontainer.json +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.dockerignore +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.github/CODEOWNERS +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.github/dependabot.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.github/workflows/ci.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.github/workflows/codeql.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.github/workflows/dependency-review.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.github/workflows/dev-build.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.github/workflows/provision.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.github/workflows/release-please.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.github/workflows/release.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.github/workflows/smoke-test-cleanup.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.github/workflows/smoke-test.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.github/workflows/teardown.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.gitignore +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.maverick/checkpoints/feature/implementation_complete.json +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.maverick/checkpoints/feature/phase_${{ index }}_complete.json +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.maverick/checkpoints/feature/validation_complete.json +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.specify/extensions/.registry +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.specify/extensions/git/README.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.specify/extensions/git/commands/speckit.git.commit.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.specify/extensions/git/commands/speckit.git.feature.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.specify/extensions/git/commands/speckit.git.initialize.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.specify/extensions/git/commands/speckit.git.remote.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.specify/extensions/git/commands/speckit.git.validate.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.specify/extensions/git/config-template.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.specify/extensions/git/extension.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.specify/extensions/git/git-config.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.specify/extensions/git/scripts/bash/auto-commit.sh +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.specify/extensions/git/scripts/bash/create-new-feature.sh +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.specify/extensions/git/scripts/bash/git-common.sh +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.specify/extensions/git/scripts/bash/initialize-repo.sh +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.specify/extensions/git/scripts/powershell/auto-commit.ps1 +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.specify/extensions/git/scripts/powershell/create-new-feature.ps1 +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.specify/extensions/git/scripts/powershell/git-common.ps1 +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.specify/extensions/git/scripts/powershell/initialize-repo.ps1 +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.specify/extensions.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.specify/feature.json +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.specify/init-options.json +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.specify/integration.json +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.specify/integrations/claude.manifest.json +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.specify/integrations/speckit.manifest.json +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.specify/memory/constitution.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.specify/scripts/bash/check-prerequisites.sh +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.specify/scripts/bash/common.sh +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.specify/scripts/bash/create-new-feature.sh +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.specify/scripts/bash/setup-plan.sh +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.specify/scripts/bash/setup-tasks.sh +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.specify/scripts/bash/update-agent-context.sh +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.specify/templates/agent-file-template.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.specify/templates/checklist-template.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.specify/templates/constitution-template.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.specify/templates/plan-template.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.specify/templates/spec-template.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.specify/templates/tasks-template.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.specify/workflows/speckit/workflow.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/.specify/workflows/workflow-registry.json +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/CONTRIBUTING.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/Dockerfile +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/LICENSE +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/SECURITY.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/.ansible-lint +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/README.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/ansible.cfg +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/aws_configure.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/aws_resize.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/aws_site.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/aws_teardown.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/group_vars/all.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/group_vars/incus_containers.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/group_vars/proxmox_containers.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/hetzner_configure.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/hetzner_provision.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/hetzner_resize.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/hetzner_site.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/hetzner_teardown.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/incus_bootstrap.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/incus_configure.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/incus_provision.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/incus_resize.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/incus_site.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/incus_teardown.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/inventory/hosts.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/inventory/incus_containers.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/proxmox_bootstrap.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/proxmox_configure.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/proxmox_provision.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/proxmox_resize.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/proxmox_site.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/proxmox_teardown.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/requirements.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/aws_server/defaults/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/aws_server/tasks/ebs.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/aws_server/tasks/ec2.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/aws_server/tasks/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/aws_server/tasks/patch_manager.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/aws_server/tasks/preflight.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/aws_server/tasks/security_group.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/deacon/defaults/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/deacon/tasks/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/devcontainers/tasks/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/docker/handlers/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/docker/tasks/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/fzf/tasks/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/github_cli/tasks/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/hetzner_server/defaults/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/hetzner_server/tasks/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/incus_bootstrap/defaults/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/incus_bootstrap/handlers/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/incus_bootstrap/tasks/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/incus_container/defaults/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/incus_container/handlers/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/incus_container/meta/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/incus_container/tasks/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/incus_container/tasks/preflight.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/incus_container_teardown/defaults/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/incus_container_teardown/meta/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/incus_container_teardown/tasks/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/nodejs/defaults/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/nodejs/files/nodesource-repo.gpg.key +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/nodejs/tasks/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/proxmox_bootstrap/defaults/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/proxmox_bootstrap/meta/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/proxmox_bootstrap/tasks/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/proxmox_container/defaults/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/proxmox_container/handlers/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/proxmox_container/meta/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/proxmox_container/tasks/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/proxmox_container/tasks/preflight.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/proxmox_container_teardown/defaults/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/proxmox_container_teardown/meta/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/proxmox_container_teardown/tasks/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/user_setup/defaults/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/user_setup/tasks/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/user_setup/templates/devshell.sh.j2 +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/user_setup/templates/project-launch.sh.j2 +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/user_setup/templates/project-menu.sh.j2 +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/user_setup/templates/remo-host.sh.j2 +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/zellij/defaults/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/zellij/tasks/main.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/zellij/templates/config.kdl.j2 +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/roles/zellij/templates/layouts/minimal.kdl.j2 +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/tasks/configure_dev_tools.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/ansible/tasks/repair_apt_sources.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/docker/Dockerfile +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/docker/compose.example.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/docker/entrypoint.sh +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/docs/aws.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/docs/feature-history.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/docs/hetzner.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/docs/incus.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/docs/install.sh +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/docs/maintaining-claude-md.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/docs/maintaining-generated-types.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/docs/providers.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/docs/proxmox.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/docs/remo-web-speckit-input.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/docs/remo-web.html +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/index.html +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/package-lock.json +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/package.json +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/scripts/check-types-fresh.mjs +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/scripts/generate-frame-types.mjs +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/scripts/generate-schema-types.mjs +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/api/client.test.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/api/client.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/api/generated/README.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/api/generated/openapi.json +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/api/generated/schema.d.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/api/generated/terminal-frames.d.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/api/generated/terminal-frames.json +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/api/generated.test.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/components/AppRoot.tsx +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/components/AppShell.css +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/components/AppShell.tsx +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/components/AwaitingAdoption.css +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/components/AwaitingAdoption.tsx +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/components/OfflineOverlay.css +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/components/OfflineOverlay.tsx +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/components/PairToSync.css +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/components/PairToSync.tsx +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/components/SessionRail.css +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/components/SessionRail.tsx +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/components/SettingsPage.css +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/components/SettingsPage.tsx +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/components/ShortcutsModal.css +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/components/ShortcutsModal.tsx +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/components/TerminalCard.css +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/components/TerminalCard.test.tsx +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/components/TerminalCard.tsx +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/components/TopBar.css +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/components/TopBar.tsx +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/components/WorkspacePane.css +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/components/WorkspacePane.test.tsx +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/components/WorkspacePane.tsx +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/components/masterLayout.test.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/components/masterLayout.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/components/providerMeta.test.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/components/providerMeta.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/components/railModel.test.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/components/railModel.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/lib/clipboard.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/lib/fullscreen.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/main.tsx +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/state/discovery.test.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/state/discovery.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/state/fonts.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/state/health.test.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/state/health.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/state/latency.test.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/state/latency.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/state/settings.test.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/state/settings.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/state/useConsoleKeyboard.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/state/workspace.test.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/state/workspace.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/terminal/.gitkeep +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/terminal/RendererAdapter.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/terminal/TerminalConnection.test.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/terminal/TerminalConnection.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/terminal/XtermRenderer.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/terminal/defaultRenderer.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/terminal/keymap.test.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/terminal/keymap.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/test/setup.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/theme/fonts.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/src/theme/tokens.css +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/tsconfig.json +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/frontend/vite.config.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/install.sh +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/maverick.yaml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/playwright.config.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/project-menu.sh +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/release-please-config.json +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/remo +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/remo.svg +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/run.sh +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/scripts/export_openapi.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/scripts/palette-check.sh +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/scripts/setup-aws-oidc.sh +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/001-bootstrap-incus-host/checklists/requirements.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/001-bootstrap-incus-host/checklists/validation.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/001-bootstrap-incus-host/contracts/playbook-interface.yaml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/001-bootstrap-incus-host/contracts/role-interface.yaml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/001-bootstrap-incus-host/data-model.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/001-bootstrap-incus-host/plan.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/001-bootstrap-incus-host/quickstart.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/001-bootstrap-incus-host/research.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/001-bootstrap-incus-host/spec.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/001-bootstrap-incus-host/tasks.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/002-incus-container-support/checklists/requirements.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/002-incus-container-support/contracts/playbook-interface.yaml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/002-incus-container-support/contracts/role-interface.yaml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/002-incus-container-support/data-model.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/002-incus-container-support/plan.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/002-incus-container-support/quickstart.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/002-incus-container-support/research.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/002-incus-container-support/spec.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/002-incus-container-support/tasks.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/003-python-cli-rewrite/checklists/requirements.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/003-python-cli-rewrite/data-model.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/003-python-cli-rewrite/plan.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/003-python-cli-rewrite/quickstart.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/003-python-cli-rewrite/research.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/003-python-cli-rewrite/spec.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/003-python-cli-rewrite/tasks.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/004-proxmox-container-support/spec.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/005-provider-snapshots/checklists/requirements.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/005-provider-snapshots/contracts/cli-surface.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/005-provider-snapshots/data-model.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/005-provider-snapshots/plan.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/005-provider-snapshots/quickstart.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/005-provider-snapshots/research.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/005-provider-snapshots/spec.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/005-provider-snapshots/tasks.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/010-web-session-interface/checklists/requirements.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/010-web-session-interface/contracts/remo-host-protocol.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/010-web-session-interface/contracts/rest-api.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/010-web-session-interface/contracts/terminal-websocket.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/010-web-session-interface/data-model.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/010-web-session-interface/plan.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/010-web-session-interface/quickstart.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/010-web-session-interface/research.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/010-web-session-interface/spec.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/010-web-session-interface/tasks.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/011-web-adopt/checklists/requirements.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/011-web-adopt/contracts/cli-web-adopt.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/011-web-adopt/contracts/setup-api.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/011-web-adopt/data-model.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/011-web-adopt/plan.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/011-web-adopt/quickstart-results.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/011-web-adopt/quickstart.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/011-web-adopt/research.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/011-web-adopt/spec.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/011-web-adopt/tasks.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/012-web-adopt-pairing/contracts/cli-web-adopt.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/012-web-adopt-pairing/contracts/pairing-api.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/012-web-adopt-pairing/contracts/setup-api.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/012-web-adopt-pairing/data-model.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/012-web-adopt-pairing/plan.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/012-web-adopt-pairing/quickstart.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/012-web-adopt-pairing/research.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/012-web-adopt-pairing/spec.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/012-web-adopt-pairing/tasks.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/013-managed-instance-tags/contracts/cli-sync.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/013-managed-instance-tags/contracts/marker-commands.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/013-managed-instance-tags/data-model.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/013-managed-instance-tags/plan.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/013-managed-instance-tags/quickstart.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/013-managed-instance-tags/research.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/013-managed-instance-tags/spec.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/013-managed-instance-tags/tasks.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/014-register-ssh-host/contracts/add-command.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/014-register-ssh-host/contracts/remove-command.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/014-register-ssh-host/data-model.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/014-register-ssh-host/plan.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/014-register-ssh-host/quickstart.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/014-register-ssh-host/research.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/014-register-ssh-host/tasks.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/015-registry-v2/checklists/requirements.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/015-registry-v2/contracts/mirror-payload-v2.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/015-registry-v2/contracts/registry-accessor-api.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/015-registry-v2/contracts/registry-file-v2.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/015-registry-v2/data-model.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/015-registry-v2/plan.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/015-registry-v2/quickstart.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/015-registry-v2/research.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/015-registry-v2/spec.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/015-registry-v2/tasks.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/016-sync-reconcile/checklists/requirements.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/016-sync-reconcile/contracts/cli-sync.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/016-sync-reconcile/contracts/provider-probe.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/016-sync-reconcile/data-model.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/016-sync-reconcile/plan.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/016-sync-reconcile/quickstart.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/016-sync-reconcile/research.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/016-sync-reconcile/spec.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/016-sync-reconcile/tasks.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/017-web-adopt-simplify/checklists/requirements.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/017-web-adopt-simplify/contracts/cli-web-push.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/017-web-adopt-simplify/contracts/cli-web-status.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/017-web-adopt-simplify/contracts/revocation.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/017-web-adopt-simplify/contracts/setup-status-marker.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/017-web-adopt-simplify/data-model.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/017-web-adopt-simplify/plan.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/017-web-adopt-simplify/quickstart.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/017-web-adopt-simplify/research.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/017-web-adopt-simplify/spec.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/017-web-adopt-simplify/tasks.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/018-provider-abstraction/baseline-notes.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/018-provider-abstraction/checklists/requirements.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/018-provider-abstraction/contracts/cli-surface.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/018-provider-abstraction/contracts/errors.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/018-provider-abstraction/contracts/lifecycle-templates.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/018-provider-abstraction/contracts/provider-protocol.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/018-provider-abstraction/contracts/sync-merge.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/018-provider-abstraction/data-model.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/018-provider-abstraction/plan.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/018-provider-abstraction/quickstart.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/018-provider-abstraction/research.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/018-provider-abstraction/spec.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/018-provider-abstraction/tasks.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/019-hygiene-deps-docs/checklists/requirements.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/019-hygiene-deps-docs/contracts/cli-surface-delta.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/019-hygiene-deps-docs/contracts/docs-structure-check.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/019-hygiene-deps-docs/data-model.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/019-hygiene-deps-docs/plan.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/019-hygiene-deps-docs/quickstart.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/019-hygiene-deps-docs/research.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/019-hygiene-deps-docs/spec.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/019-hygiene-deps-docs/tasks.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/020-openapi-type-generation/checklists/requirements.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/020-openapi-type-generation/contracts/drift-checks.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/020-openapi-type-generation/contracts/terminal-frames-v1.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/020-openapi-type-generation/data-model.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/020-openapi-type-generation/plan.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/020-openapi-type-generation/quickstart.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/020-openapi-type-generation/research.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/020-openapi-type-generation/spec.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/020-openapi-type-generation/tasks.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/021-cli-plane-separation/contracts/cli-surface.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/021-cli-plane-separation/contracts/descriptor-schema.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/021-cli-plane-separation/data-model.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/021-cli-plane-separation/plan.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/021-cli-plane-separation/quickstart.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/021-cli-plane-separation/research.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/021-cli-plane-separation/spec.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/specs/021-cli-plane-separation/tasks.md +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/__init__.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/__main__.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/cli/__init__.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/cli/cp.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/cli/providers/__init__.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/cli/providers/factory.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/cli/shell.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/cli/web.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/core/__init__.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/core/ansible_runner.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/core/completion.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/core/config.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/core/errors.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/core/lifecycle.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/core/output.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/core/picker.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/core/provider_protocol.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/core/provider_registry.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/core/reconcile.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/core/registry.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/core/remo_host_client.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/core/rsync.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/core/snapshot.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/core/ssh.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/core/validation.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/core/version.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/core/web_drift.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/models/__init__.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/models/capability.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/models/discovery.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/models/host.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/models/session_target.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/models/snapshot.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/providers/__init__.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/providers/aws.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/providers/aws_descriptor.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/providers/builtin.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/providers/hetzner.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/providers/hetzner_descriptor.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/providers/incus.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/providers/incus_descriptor.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/providers/proxmox.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/providers/proxmox_descriptor.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/web/__init__.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/web/api/__init__.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/web/api/hosts.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/web/api/pairing.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/web/api/setup.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/web/api/terminals.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/web/app.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/web/check.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/web/config.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/web/frames.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/web/health.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/web/logging_config.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/web/models.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/web/operator_auth.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/web/pairing.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/web/ssh_master.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/web/state.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/web/terminal.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/web/terminal_registry.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/src/remo_cli/web/tokens.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/__init__.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/ansible/__init__.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/ansible/test_remo_host_idempotency.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/conftest.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/e2e/fixtures.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/e2e/keyboard-routing.spec.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/e2e/mobile.spec.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/e2e/reconnect.spec.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/e2e/terminal-shell-io.spec.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/e2e/terminal-tui-compatibility.spec.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/e2e/workspace-layout.spec.ts +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/image/__init__.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/image/test_docker_image.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/integration/__init__.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/integration/_nine_terminal_fixture.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/integration/fish_completion.sh +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/integration/orbstack-cloud-init.yml +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/integration/orbstack.sh +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/integration/test_nine_terminals.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/integration/test_nine_terminals_soak.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/integration/test_registry_concurrency.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/integration/test_remo_host_e2e.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/integration/test_security_rejections.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/integration/test_setup_payload_versions.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/integration/test_sync_reconcile.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/integration/test_terminal_attach.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/integration/test_web_adopt_e2e.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/integration/test_web_cli_parity.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/perf/__init__.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/perf/test_latency.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/perf/test_registry_perf.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/__init__.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/cli/__init__.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/cli/providers/test_aws_snapshot.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/cli/providers/test_hetzner_snapshot.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/cli/providers/test_incus_snapshot.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/cli/providers/test_incus_sync_all.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/cli/providers/test_proxmox_snapshot.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/cli/providers/test_proxmox_sync_all.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/cli/surface_baseline.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/cli/test_add_cmd.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/cli/test_cli_uniformity.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/cli/test_cp.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/cli/test_remove_cmd.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/cli/test_shell.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/cli/test_shell_added.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/cli/test_startup_imports.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/cli/test_surface_preservation.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/cli/test_web_adopt_cmd.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/core/__init__.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/core/test_ansible_runner.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/core/test_completion.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/core/test_config.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/core/test_errors.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/core/test_known_hosts.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/core/test_lifecycle.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/core/test_migration_tagging_notice.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/core/test_output.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/core/test_picker.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/core/test_provider_registry.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/core/test_reconcile.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/core/test_registry_format.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/core/test_registry_locking.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/core/test_registry_migration.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/core/test_remo_host_client.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/core/test_rsync.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/core/test_snapshot.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/core/test_ssh.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/core/test_ssh_added.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/core/test_ssh_controlpath.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/core/test_ssh_identity_opts.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/core/test_validation.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/core/test_version.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/core/test_web_adopt_authorize.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/core/test_web_adopt_code.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/core/test_web_adopt_payload.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/core/test_web_drift.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/core/test_web_push.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/core/test_web_push_self_heal.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/models/__init__.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/models/test_capability.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/models/test_discovery.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/models/test_session_target.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/providers/__init__.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/providers/fake_provider.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/providers/test_added_update_remove.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/providers/test_added_verify.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/providers/test_aws_provider_protocol.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/providers/test_aws_snapshot.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/providers/test_aws_sync.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/providers/test_aws_upgrade_resize.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/providers/test_hetzner_entry_adapters.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/providers/test_hetzner_http.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/providers/test_hetzner_label.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/providers/test_hetzner_resize.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/providers/test_hetzner_snapshot.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/providers/test_hetzner_sync.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/providers/test_host_user_consistency.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/providers/test_incus_entry_adapters.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/providers/test_incus_marker.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/providers/test_incus_resize.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/providers/test_incus_snapshot.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/providers/test_incus_sync.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/providers/test_missing_sdk.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/providers/test_provider_conformance.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/providers/test_provider_registry_entries.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/providers/test_proxmox_devcontainer_runtime.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/providers/test_proxmox_marker.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/providers/test_proxmox_provider_protocol.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/providers/test_proxmox_resize.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/providers/test_proxmox_snapshot.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/providers/test_proxmox_sync.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/test_ansible_templates.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/test_architecture.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/test_docs_structure.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/test_host_model.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/test_host_ssh_type.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/test_lockfile_version.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/test_schema_drift.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/web/__init__.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/web/_pairing_support.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/web/conftest.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/web/fixtures/hosts_response.json +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/web/fixtures/sessions_response.json +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/web/test_backpressure.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/web/test_check.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/web/test_csp.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/web/test_discovery_refresh_force.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/web/test_frames.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/web/test_health_states.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/web/test_hosts_mapping.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/web/test_lazy_import.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/web/test_log_redaction.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/web/test_mint_gating.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/web/test_operator_auth.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/web/test_pairing.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/web/test_pairing_origin.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/web/test_payload_equivalence.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/web/test_registry_readonly.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/web/test_setup_api.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/web/test_setup_auth.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/web/test_setup_dormancy.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/web/test_state.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/web/test_terminal_resize.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/tests/unit/web/test_terminals_api.py +0 -0
- {remo_cli-4.2.0 → remo_cli-4.3.0}/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,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [4.3.0](https://github.com/get2knowio/remo/compare/v4.2.0...v4.3.0) (2026-08-05)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **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))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* **web:** stop three light themes printing invisible text ([#141](https://github.com/get2knowio/remo/issues/141)) ([a1c2bea](https://github.com/get2knowio/remo/commit/a1c2bea06c13503fe6c623f2f18483c862c20b46))
|
|
14
|
+
|
|
3
15
|
## [4.2.0](https://github.com/get2knowio/remo/compare/v4.1.0...v4.2.0) (2026-08-05)
|
|
4
16
|
|
|
5
17
|
|
|
@@ -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.0
|
|
4
4
|
Summary: Remote development environment CLI
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -286,11 +286,47 @@ 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
|
+
|
|
294
330
|
`remo remove NAME [--yes]` deregisters an added host by deleting **only** the
|
|
295
331
|
local registry entry — it makes no connection to and no change on the remote
|
|
296
332
|
environment (unlike a provider `destroy`, which tears down infrastructure). It
|
|
@@ -323,6 +359,8 @@ remo add NAME [user@]host[:port] # Register a single SSH host
|
|
|
323
359
|
remo add NAME host --port 2222 --identity ~/.ssh/key # Custom port + key
|
|
324
360
|
remo add NAME host --verify # Fail-closed SSH reachability check first
|
|
325
361
|
remo add NAME host --user alice # Override default SSH user (default: remo)
|
|
362
|
+
remo configure NAME # Install/refresh the dev tools on an added host
|
|
363
|
+
remo configure NAME --skip docker # Skip a tool (repeatable; --only is the inverse)
|
|
326
364
|
remo remove NAME [--yes] # Deregister an added host (local-only)
|
|
327
365
|
|
|
328
366
|
# Hetzner Cloud
|
|
@@ -260,11 +260,47 @@ 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
|
+
|
|
268
304
|
`remo remove NAME [--yes]` deregisters an added host by deleting **only** the
|
|
269
305
|
local registry entry — it makes no connection to and no change on the remote
|
|
270
306
|
environment (unlike a provider `destroy`, which tears down infrastructure). It
|
|
@@ -297,6 +333,8 @@ remo add NAME [user@]host[:port] # Register a single SSH host
|
|
|
297
333
|
remo add NAME host --port 2222 --identity ~/.ssh/key # Custom port + key
|
|
298
334
|
remo add NAME host --verify # Fail-closed SSH reachability check first
|
|
299
335
|
remo add NAME host --user alice # Override default SSH user (default: remo)
|
|
336
|
+
remo configure NAME # Install/refresh the dev tools on an added host
|
|
337
|
+
remo configure NAME --skip docker # Skip a tool (repeatable; --only is the inverse)
|
|
300
338
|
remo remove NAME [--yes] # Deregister an added host (local-only)
|
|
301
339
|
|
|
302
340
|
# Hetzner Cloud
|
|
@@ -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 }}
|
|
@@ -182,7 +182,12 @@ background is `--bg-term`, foreground `--text`, ANSI 1–6 the `--danger`/`--ok`
|
|
|
182
182
|
(a terminal palette must be literal `#rrggbb`; xterm.js's `ITheme` accepts neither `oklch()` nor
|
|
183
183
|
`var()`), so editing a token does not update them. Six curated third-party schemes are also available —
|
|
184
184
|
Catppuccin Mocha/Latte, Dracula, Gruvbox Dark/Light, Solarized Light — and any theme can be used under
|
|
185
|
-
either site mode
|
|
185
|
+
either site mode. Those ports are faithful with one deliberate exception: three of the light variants
|
|
186
|
+
assign a *background* tone to a neutral slot that applications print as **text** (Solarized Light's
|
|
187
|
+
white/brightWhite, Gruvbox Light's black, Catppuccin Latte's white/brightWhite), which made that text
|
|
188
|
+
invisible on the theme's own background. Those five values are replaced — each with another colour from
|
|
189
|
+
the same palette — and a contrast floor in `terminalThemes.test.ts` keeps every slot above 2.0:1. Every
|
|
190
|
+
chromatic slot is untouched; picking one explicitly stops the terminal following the site. The Settings choice is
|
|
186
191
|
the default for every terminal; an individual terminal can override it from the color swatch in its
|
|
187
192
|
header, and clearing that override back to "Default" makes it follow the global choice again. Theme
|
|
188
193
|
changes apply live — the terminal is recolored in place, keeping the connection and scrollback. Because a browser can't read fonts installed on the
|
|
@@ -626,7 +631,9 @@ Your web deployment may now be out of date — run 'remo web status' to see what
|
|
|
626
631
|
```
|
|
627
632
|
|
|
628
633
|
It fires after a successful `remo <provider> create`/`destroy`, an **applied** `remo <provider> sync`
|
|
629
|
-
(the shared reconcile/`run_sync` path), `remo add`,
|
|
634
|
+
(the shared reconcile/`run_sync` path), `remo add`, `remo remove`, and `remo configure` (which does not
|
|
635
|
+
mutate the registry, but is normally the point at which a newly added host first becomes worth
|
|
636
|
+
pushing). It never fires when this
|
|
630
637
|
workstation has never pushed (no cache to be out of date against), on a dry-run or no-op `sync`, or
|
|
631
638
|
on `remo aws stop`/`start`/`reboot` (which don't mutate the registry). The nudge is gated on cache
|
|
632
639
|
existence only — a rare false positive after a no-op mutation is acceptable because `remo web status`
|
|
@@ -76,6 +76,44 @@ describe("terminal themes", () => {
|
|
|
76
76
|
expect(autoTerminalTheme(false).variant).toBe("light");
|
|
77
77
|
});
|
|
78
78
|
|
|
79
|
+
// No theme may print text that is invisible on its own background.
|
|
80
|
+
//
|
|
81
|
+
// Reported from real use twice: Solarized Light's white and Gruvbox Light's
|
|
82
|
+
// black were literally the background colour (1.00:1), and Catppuccin Latte's
|
|
83
|
+
// whites were near enough. Upstream really does assign background tones to
|
|
84
|
+
// those slots — which is fine for a swatch and useless for text — so the
|
|
85
|
+
// fixed values are a deliberate deviation this test defends.
|
|
86
|
+
//
|
|
87
|
+
// `black` is exempt in DARK themes only: sitting near the background is what
|
|
88
|
+
// colour 0 is FOR there, and applications use it as a fill rather than as
|
|
89
|
+
// text. In a LIGHT theme the same slot IS printed as text, so it must be
|
|
90
|
+
// legible — which is exactly the Gruvbox Light bug.
|
|
91
|
+
//
|
|
92
|
+
// The floor is 2.0:1, well below the 3:1 the Remo pair holds to, because this
|
|
93
|
+
// catches INVISIBLE rather than merely quiet. The data splits cleanly: the
|
|
94
|
+
// worst canonical slot across all eight themes is Gruvbox Light's yellow at
|
|
95
|
+
// 2.19, while the five broken ones were all <= 1.91. A floor of 2.0 sits in
|
|
96
|
+
// that gap, so every upstream chromatic value stays untouched.
|
|
97
|
+
//
|
|
98
|
+
// `cursor` is exempt: it is a filled block, not text, and reads at ratios
|
|
99
|
+
// that would be unusable for glyphs.
|
|
100
|
+
it.each(TERMINAL_THEMES)("$label prints nothing invisible", (theme) => {
|
|
101
|
+
const bg = theme.colors.background;
|
|
102
|
+
for (const [slot, hex] of Object.entries(theme.colors)) {
|
|
103
|
+
if (
|
|
104
|
+
["background", "cursor", "cursorAccent", "selectionBackground", "selectionForeground"].includes(
|
|
105
|
+
slot,
|
|
106
|
+
)
|
|
107
|
+
) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (slot === "black" && theme.variant === "dark") {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
expect(contrast(hex, bg), `${theme.id}.${slot} ${hex} on ${bg}`).toBeGreaterThan(2.0);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
79
117
|
// Every colour the Remo pair can PRINT must be legible on its own
|
|
80
118
|
// background. This is the regression guard for a real report: Remo Light
|
|
81
119
|
// shipped `white` at #d5d8db and `brightWhite` at #fbfcfd — 1.27:1 and
|