remo-cli 2.2.0rc11__tar.gz → 2.2.0rc13__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-2.2.0rc11 → remo_cli-2.2.0rc13}/PKG-INFO +1 -1
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/docs/web-session-interface.md +17 -7
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/package-lock.json +29 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/package.json +2 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/components/AppShell.tsx +3 -0
- remo_cli-2.2.0rc13/frontend/src/components/PairToSync.css +71 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/components/PairToSync.tsx +16 -6
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/components/SettingsPage.tsx +38 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/components/TerminalCard.tsx +40 -4
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/components/TopBar.tsx +25 -6
- remo_cli-2.2.0rc13/frontend/src/state/latency.test.ts +35 -0
- remo_cli-2.2.0rc13/frontend/src/state/latency.ts +69 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/state/settings.ts +15 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/terminal/GhosttyRenderer.ts +27 -1
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/terminal/RendererAdapter.ts +6 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/terminal/TerminalConnection.ts +37 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/terminal/XtermRenderer.ts +53 -1
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/terminal/keymap.test.ts +21 -1
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/terminal/keymap.ts +14 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/pyproject.toml +1 -1
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/uv.lock +1 -1
- remo_cli-2.2.0rc11/frontend/src/components/PairToSync.css +0 -53
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.claude/skills/speckit-analyze/SKILL.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.claude/skills/speckit-checklist/SKILL.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.claude/skills/speckit-clarify/SKILL.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.claude/skills/speckit-constitution/SKILL.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.claude/skills/speckit-converge/SKILL.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.claude/skills/speckit-git-commit/SKILL.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.claude/skills/speckit-git-feature/SKILL.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.claude/skills/speckit-git-initialize/SKILL.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.claude/skills/speckit-git-remote/SKILL.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.claude/skills/speckit-git-validate/SKILL.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.claude/skills/speckit-implement/SKILL.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.claude/skills/speckit-plan/SKILL.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.claude/skills/speckit-specify/SKILL.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.claude/skills/speckit-tasks/SKILL.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.claude/skills/speckit-taskstoissues/SKILL.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.devcontainer/devcontainer.json +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.dockerignore +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.github/dependabot.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.github/workflows/ci.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.github/workflows/provision.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.github/workflows/release.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.github/workflows/smoke-test-cleanup.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.github/workflows/smoke-test.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.github/workflows/teardown.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.gitignore +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.maverick/checkpoints/feature/implementation_complete.json +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.maverick/checkpoints/feature/phase_${{ index }}_complete.json +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.maverick/checkpoints/feature/validation_complete.json +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/extensions/.registry +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/extensions/git/README.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/extensions/git/commands/speckit.git.commit.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/extensions/git/commands/speckit.git.feature.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/extensions/git/commands/speckit.git.initialize.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/extensions/git/commands/speckit.git.remote.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/extensions/git/commands/speckit.git.validate.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/extensions/git/config-template.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/extensions/git/extension.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/extensions/git/git-config.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/extensions/git/scripts/bash/auto-commit.sh +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/extensions/git/scripts/bash/create-new-feature.sh +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/extensions/git/scripts/bash/git-common.sh +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/extensions/git/scripts/bash/initialize-repo.sh +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/extensions/git/scripts/powershell/auto-commit.ps1 +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/extensions/git/scripts/powershell/create-new-feature.ps1 +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/extensions/git/scripts/powershell/git-common.ps1 +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/extensions/git/scripts/powershell/initialize-repo.ps1 +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/extensions.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/feature.json +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/init-options.json +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/integration.json +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/integrations/claude.manifest.json +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/integrations/speckit.manifest.json +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/memory/constitution.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/scripts/bash/check-prerequisites.sh +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/scripts/bash/common.sh +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/scripts/bash/create-new-feature.sh +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/scripts/bash/setup-plan.sh +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/scripts/bash/setup-tasks.sh +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/scripts/bash/update-agent-context.sh +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/templates/agent-file-template.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/templates/checklist-template.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/templates/constitution-template.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/templates/plan-template.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/templates/spec-template.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/templates/tasks-template.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/workflows/speckit/workflow.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/workflows/workflow-registry.json +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/AGENTS.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/CLAUDE.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/CONTRIBUTING.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/Dockerfile +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/LICENSE +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/README.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/.ansible-lint +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/README.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/ansible.cfg +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/aws_configure.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/aws_resize.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/aws_site.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/aws_teardown.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/group_vars/all.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/group_vars/incus_containers.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/group_vars/proxmox_containers.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/hetzner_configure.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/hetzner_provision.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/hetzner_resize.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/hetzner_site.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/hetzner_teardown.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/incus_bootstrap.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/incus_configure.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/incus_provision.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/incus_resize.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/incus_site.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/incus_teardown.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/inventory/hosts.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/inventory/incus_containers.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/proxmox_bootstrap.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/proxmox_configure.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/proxmox_provision.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/proxmox_resize.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/proxmox_site.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/proxmox_teardown.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/requirements.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/aws_server/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/aws_server/tasks/ebs.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/aws_server/tasks/ec2.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/aws_server/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/aws_server/tasks/patch_manager.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/aws_server/tasks/preflight.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/aws_server/tasks/security_group.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/deacon/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/deacon/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/devcontainers/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/docker/handlers/main.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/docker/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/fzf/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/github_cli/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/hetzner_server/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/hetzner_server/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/incus_bootstrap/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/incus_bootstrap/handlers/main.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/incus_bootstrap/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/incus_container/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/incus_container/handlers/main.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/incus_container/meta/main.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/incus_container/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/incus_container/tasks/preflight.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/incus_container_teardown/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/incus_container_teardown/meta/main.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/incus_container_teardown/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/nodejs/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/nodejs/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/proxmox_bootstrap/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/proxmox_bootstrap/meta/main.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/proxmox_bootstrap/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/proxmox_container/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/proxmox_container/handlers/main.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/proxmox_container/meta/main.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/proxmox_container/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/proxmox_container/tasks/preflight.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/proxmox_container_teardown/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/proxmox_container_teardown/meta/main.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/proxmox_container_teardown/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/user_setup/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/user_setup/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/user_setup/templates/devshell.sh.j2 +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/user_setup/templates/project-launch.sh.j2 +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/user_setup/templates/project-menu.sh.j2 +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/user_setup/templates/remo-host.sh.j2 +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/zellij/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/zellij/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/zellij/templates/config.kdl.j2 +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/zellij/templates/layouts/minimal.kdl.j2 +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/tasks/configure_dev_tools.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/docker/Dockerfile +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/docker/compose.example.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/docker/entrypoint.sh +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/docs/aws.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/docs/hetzner.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/docs/incus.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/docs/install.sh +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/docs/proxmox.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/docs/remo-web-speckit-input.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/docs/remo-web.html +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/index.html +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/public/README.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/public/dist__ghostty-vt.wasm +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/public/ghostty-vt.wasm +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/scripts/copy-ghostty-wasm.mjs +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/api/client.ts +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/components/AppRoot.tsx +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/components/AppShell.css +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/components/AwaitingAdoption.css +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/components/AwaitingAdoption.tsx +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/components/OfflineOverlay.css +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/components/OfflineOverlay.tsx +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/components/SessionRail.css +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/components/SessionRail.tsx +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/components/SettingsPage.css +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/components/ShortcutsModal.css +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/components/ShortcutsModal.tsx +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/components/TerminalCard.css +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/components/TopBar.css +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/components/WorkspacePane.css +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/components/WorkspacePane.tsx +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/components/providerMeta.ts +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/components/railModel.ts +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/lib/clipboard.ts +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/lib/fullscreen.ts +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/main.tsx +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/state/discovery.ts +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/state/fonts.ts +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/state/health.ts +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/state/useConsoleKeyboard.ts +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/state/workspace.test.ts +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/state/workspace.ts +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/terminal/.gitkeep +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/terminal/TerminalConnection.test.ts +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/terminal/defaultRenderer.ts +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/test/setup.ts +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/theme/fonts.ts +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/theme/tokens.css +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/tsconfig.json +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/vite.config.ts +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/install.sh +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/maverick.yaml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/playwright.config.ts +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/project-menu.sh +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/remo +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/run.sh +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/scripts/setup-aws-oidc.sh +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/001-bootstrap-incus-host/checklists/requirements.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/001-bootstrap-incus-host/checklists/validation.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/001-bootstrap-incus-host/contracts/playbook-interface.yaml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/001-bootstrap-incus-host/contracts/role-interface.yaml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/001-bootstrap-incus-host/data-model.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/001-bootstrap-incus-host/plan.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/001-bootstrap-incus-host/quickstart.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/001-bootstrap-incus-host/research.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/001-bootstrap-incus-host/spec.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/001-bootstrap-incus-host/tasks.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/002-incus-container-support/checklists/requirements.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/002-incus-container-support/contracts/playbook-interface.yaml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/002-incus-container-support/contracts/role-interface.yaml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/002-incus-container-support/data-model.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/002-incus-container-support/plan.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/002-incus-container-support/quickstart.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/002-incus-container-support/research.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/002-incus-container-support/spec.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/002-incus-container-support/tasks.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/003-python-cli-rewrite/checklists/requirements.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/003-python-cli-rewrite/data-model.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/003-python-cli-rewrite/plan.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/003-python-cli-rewrite/quickstart.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/003-python-cli-rewrite/research.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/003-python-cli-rewrite/spec.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/003-python-cli-rewrite/tasks.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/004-proxmox-container-support/spec.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/005-provider-snapshots/checklists/requirements.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/005-provider-snapshots/contracts/cli-surface.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/005-provider-snapshots/data-model.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/005-provider-snapshots/plan.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/005-provider-snapshots/quickstart.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/005-provider-snapshots/research.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/005-provider-snapshots/spec.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/005-provider-snapshots/tasks.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/010-web-session-interface/checklists/requirements.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/010-web-session-interface/contracts/remo-host-protocol.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/010-web-session-interface/contracts/rest-api.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/010-web-session-interface/contracts/terminal-websocket.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/010-web-session-interface/data-model.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/010-web-session-interface/plan.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/010-web-session-interface/quickstart.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/010-web-session-interface/research.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/010-web-session-interface/spec.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/010-web-session-interface/tasks.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/011-web-adopt/checklists/requirements.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/011-web-adopt/contracts/cli-web-adopt.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/011-web-adopt/contracts/setup-api.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/011-web-adopt/data-model.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/011-web-adopt/plan.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/011-web-adopt/quickstart-results.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/011-web-adopt/quickstart.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/011-web-adopt/research.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/011-web-adopt/spec.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/011-web-adopt/tasks.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/012-web-adopt-pairing/contracts/cli-web-adopt.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/012-web-adopt-pairing/contracts/pairing-api.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/012-web-adopt-pairing/contracts/setup-api.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/012-web-adopt-pairing/data-model.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/012-web-adopt-pairing/plan.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/012-web-adopt-pairing/quickstart.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/012-web-adopt-pairing/research.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/012-web-adopt-pairing/spec.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/012-web-adopt-pairing/tasks.md +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/__init__.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/__main__.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/cli/__init__.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/cli/cp.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/cli/main.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/cli/providers/__init__.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/cli/providers/aws.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/cli/providers/hetzner.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/cli/providers/incus.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/cli/providers/proxmox.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/cli/shell.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/cli/web.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/core/__init__.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/core/ansible_runner.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/core/completion.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/core/config.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/core/known_hosts.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/core/output.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/core/picker.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/core/remo_host_client.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/core/rsync.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/core/snapshot.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/core/ssh.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/core/validation.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/core/version.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/core/web_adopt.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/models/__init__.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/models/capability.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/models/discovery.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/models/host.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/models/session_target.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/models/snapshot.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/providers/__init__.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/providers/aws.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/providers/hetzner.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/providers/incus.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/providers/proxmox.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/web/__init__.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/web/api/__init__.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/web/api/hosts.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/web/api/pairing.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/web/api/setup.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/web/api/terminals.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/web/app.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/web/check.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/web/config.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/web/discovery.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/web/health.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/web/logging_config.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/web/models.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/web/operator_auth.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/web/pairing.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/web/ssh_master.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/web/state.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/web/terminal.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/web/terminal_registry.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/web/tokens.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/__init__.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/ansible/__init__.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/ansible/test_remo_host_idempotency.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/conftest.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/e2e/fixtures.ts +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/e2e/ghostty-shell-io.spec.ts +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/e2e/ghostty-tui-compatibility.spec.ts +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/e2e/keyboard-routing.spec.ts +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/e2e/mobile.spec.ts +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/e2e/reconnect.spec.ts +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/e2e/workspace-layout.spec.ts +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/image/__init__.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/image/test_docker_image.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/integration/__init__.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/integration/_nine_terminal_fixture.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/integration/orbstack-cloud-init.yml +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/integration/orbstack.sh +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/integration/test_nine_terminals.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/integration/test_nine_terminals_soak.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/integration/test_remo_host_e2e.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/integration/test_security_rejections.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/integration/test_terminal_attach.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/integration/test_web_adopt_e2e.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/integration/test_web_cli_parity.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/perf/__init__.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/perf/test_latency.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/__init__.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/cli/__init__.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/cli/providers/test_aws_snapshot.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/cli/providers/test_hetzner_snapshot.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/cli/providers/test_incus_snapshot.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/cli/providers/test_proxmox_snapshot.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/cli/test_cp.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/cli/test_main.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/cli/test_shell.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/cli/test_web_adopt_cmd.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/core/__init__.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/core/test_ansible_runner.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/core/test_config.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/core/test_known_hosts.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/core/test_output.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/core/test_picker.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/core/test_remo_host_client.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/core/test_rsync.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/core/test_snapshot.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/core/test_ssh.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/core/test_ssh_controlpath.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/core/test_ssh_identity_opts.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/core/test_validation.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/core/test_version.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/core/test_web_adopt_authorize.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/core/test_web_adopt_code.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/core/test_web_adopt_payload.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/core/test_web_adopt_trust.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/core/test_web_push.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/models/__init__.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/models/test_capability.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/models/test_discovery.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/models/test_session_target.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/providers/__init__.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/providers/test_aws_snapshot.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/providers/test_hetzner_snapshot.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/providers/test_incus_snapshot.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/providers/test_proxmox_devcontainer_runtime.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/providers/test_proxmox_snapshot.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/test_ansible_templates.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/test_host_model.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/web/__init__.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/web/_pairing_support.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/web/conftest.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/web/test_backpressure.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/web/test_check.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/web/test_csp.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/web/test_discovery.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/web/test_health_states.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/web/test_hosts_mapping.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/web/test_lazy_import.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/web/test_log_redaction.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/web/test_mint_gating.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/web/test_operator_auth.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/web/test_pairing.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/web/test_pairing_origin.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/web/test_setup_api.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/web/test_setup_auth.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/web/test_setup_dormancy.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/web/test_state.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/web/test_terminal_resize.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/web/test_terminals_api.py +0 -0
- {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/web/test_tokens.py +0 -0
|
@@ -116,6 +116,13 @@ The SPA is a two-pane **web console**:
|
|
|
116
116
|
toggle fullscreen on the focused terminal; **Esc** exits it. Fullscreen is a presentation overlay: it
|
|
117
117
|
never disturbs the single/grid layout underneath, so exiting returns to exactly where you were.
|
|
118
118
|
|
|
119
|
+
**Clipboard & links.** Select text and press **⌘C** (macOS) / **Ctrl+Shift+C** (Linux/Windows), or click
|
|
120
|
+
the **⧉ Copy** button that appears on selection, to copy to the system clipboard; bare **Ctrl+C** stays
|
|
121
|
+
SIGINT. **Paste** with ⌘V / Ctrl+V. **http(s) URLs are clickable** and open in a new tab. Remote apps that
|
|
122
|
+
emit **OSC 52** (e.g. Claude Code's copy-on-select) can write to the browser clipboard — best-effort: it
|
|
123
|
+
must traverse Zellij and the browser must permit a gesture-less clipboard write. OSC 52 *reads* are denied
|
|
124
|
+
(a remote app can never read your clipboard). Clipboard access needs a secure context (HTTPS or localhost).
|
|
125
|
+
|
|
119
126
|
**Session-row glyphs** (also shown in the rail legend):
|
|
120
127
|
|
|
121
128
|
| Glyph | Meaning |
|
|
@@ -130,13 +137,16 @@ they can be stale until something else fetches. Git glyphs only appear on instan
|
|
|
130
137
|
`remo-host` new enough to report git status; see [Upgrade compatibility](#upgrade-compatibility).
|
|
131
138
|
|
|
132
139
|
**Settings** (⚙, top bar; stored in this browser only, FR-034): accent color, terminal font, font
|
|
133
|
-
size, program ligatures, grid display mode (actual-size vs scale-to-fit),
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
+
size, program ligatures, grid display mode (actual-size vs scale-to-fit), **focus dwell** (how long the
|
|
141
|
+
pointer rests before focus-follows-mouse fires), terminal engine, a **Nerd Font uploader**, and **Pair
|
|
142
|
+
CLI to sync** (mint a re-sync pairing code). Because a browser can't read fonts installed on the
|
|
143
|
+
instance, uploading a patched Nerd Font once registers it via the `FontFace` API (persisted in
|
|
144
|
+
IndexedDB) and offers it as a terminal font — that's how Powerline/Git/devicon glyphs in a prompt or
|
|
145
|
+
Zellij status bar render. Font changes apply live to every open terminal. The top bar shows the
|
|
146
|
+
**WebSocket round-trip latency** (median across open terminals, with the dot green/yellow/red by
|
|
147
|
+
latency), falling back to the service health status when no terminal is connected; an offline overlay
|
|
148
|
+
appears if the service becomes unreachable (terminals reattach automatically when it returns). Press
|
|
149
|
+
**?** for the keyboard-shortcut reference.
|
|
140
150
|
|
|
141
151
|
All fonts are self-hosted (bundled `@fontsource` assets), never fetched from a CDN, so the restrictive
|
|
142
152
|
same-origin CSP (`default-src 'self'`) is satisfied.
|
|
@@ -13,8 +13,10 @@
|
|
|
13
13
|
"@fontsource/ibm-plex-mono": "^5.2.7",
|
|
14
14
|
"@fontsource/jetbrains-mono": "^5.2.8",
|
|
15
15
|
"@fontsource/source-code-pro": "^5.2.7",
|
|
16
|
+
"@xterm/addon-clipboard": "^0.1.0",
|
|
16
17
|
"@xterm/addon-fit": "^0.10.0",
|
|
17
18
|
"@xterm/addon-ligatures": "^0.9.0",
|
|
19
|
+
"@xterm/addon-web-links": "^0.11.0",
|
|
18
20
|
"@xterm/addon-webgl": "^0.18.0",
|
|
19
21
|
"@xterm/xterm": "^5.5.0",
|
|
20
22
|
"ghostty-web": "0.4.0",
|
|
@@ -1851,6 +1853,18 @@
|
|
|
1851
1853
|
"url": "https://opencollective.com/vitest"
|
|
1852
1854
|
}
|
|
1853
1855
|
},
|
|
1856
|
+
"node_modules/@xterm/addon-clipboard": {
|
|
1857
|
+
"version": "0.1.0",
|
|
1858
|
+
"resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.1.0.tgz",
|
|
1859
|
+
"integrity": "sha512-zdoM7p53T5sv/HbRTyp4hY0kKmEQ3MZvAvEtiXqNIHc/JdpqwByCtsTaQF5DX2n4hYdXRPO4P/eOS0QEhX1nPw==",
|
|
1860
|
+
"license": "MIT",
|
|
1861
|
+
"dependencies": {
|
|
1862
|
+
"js-base64": "^3.7.5"
|
|
1863
|
+
},
|
|
1864
|
+
"peerDependencies": {
|
|
1865
|
+
"@xterm/xterm": "^5.4.0"
|
|
1866
|
+
}
|
|
1867
|
+
},
|
|
1854
1868
|
"node_modules/@xterm/addon-fit": {
|
|
1855
1869
|
"version": "0.10.0",
|
|
1856
1870
|
"resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.10.0.tgz",
|
|
@@ -1876,6 +1890,15 @@
|
|
|
1876
1890
|
"@xterm/xterm": "^5.0.0"
|
|
1877
1891
|
}
|
|
1878
1892
|
},
|
|
1893
|
+
"node_modules/@xterm/addon-web-links": {
|
|
1894
|
+
"version": "0.11.0",
|
|
1895
|
+
"resolved": "https://registry.npmjs.org/@xterm/addon-web-links/-/addon-web-links-0.11.0.tgz",
|
|
1896
|
+
"integrity": "sha512-nIHQ38pQI+a5kXnRaTgwqSHnX7KE6+4SVoceompgHL26unAxdfP6IPqUTSYPQgSwM56hsElfoNrrW5V7BUED/Q==",
|
|
1897
|
+
"license": "MIT",
|
|
1898
|
+
"peerDependencies": {
|
|
1899
|
+
"@xterm/xterm": "^5.0.0"
|
|
1900
|
+
}
|
|
1901
|
+
},
|
|
1879
1902
|
"node_modules/@xterm/addon-webgl": {
|
|
1880
1903
|
"version": "0.18.0",
|
|
1881
1904
|
"resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.18.0.tgz",
|
|
@@ -2611,6 +2634,12 @@
|
|
|
2611
2634
|
"@pkgjs/parseargs": "^0.11.0"
|
|
2612
2635
|
}
|
|
2613
2636
|
},
|
|
2637
|
+
"node_modules/js-base64": {
|
|
2638
|
+
"version": "3.9.1",
|
|
2639
|
+
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.9.1.tgz",
|
|
2640
|
+
"integrity": "sha512-U73qptcvf/HIOauFOmqT3a0mDUp0MYlfd15oqoe9kqZt5XhiXVb+HG09sLvI9PQ9tZIBFS4nlErai8zbWazP0g==",
|
|
2641
|
+
"license": "BSD-3-Clause"
|
|
2642
|
+
},
|
|
2614
2643
|
"node_modules/js-tokens": {
|
|
2615
2644
|
"version": "4.0.0",
|
|
2616
2645
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
|
@@ -20,8 +20,10 @@
|
|
|
20
20
|
"@fontsource/ibm-plex-mono": "^5.2.7",
|
|
21
21
|
"@fontsource/jetbrains-mono": "^5.2.8",
|
|
22
22
|
"@fontsource/source-code-pro": "^5.2.7",
|
|
23
|
+
"@xterm/addon-clipboard": "^0.1.0",
|
|
23
24
|
"@xterm/addon-fit": "^0.10.0",
|
|
24
25
|
"@xterm/addon-ligatures": "^0.9.0",
|
|
26
|
+
"@xterm/addon-web-links": "^0.11.0",
|
|
25
27
|
"@xterm/addon-webgl": "^0.18.0",
|
|
26
28
|
"@xterm/xterm": "^5.5.0",
|
|
27
29
|
"ghostty-web": "0.4.0",
|
|
@@ -8,6 +8,7 @@ import type { SessionTarget } from "../api/client";
|
|
|
8
8
|
import { exitBrowserFullscreen } from "../lib/fullscreen";
|
|
9
9
|
import { useDiscovery } from "../state/discovery";
|
|
10
10
|
import { useHealth } from "../state/health";
|
|
11
|
+
import { useLatency } from "../state/latency";
|
|
11
12
|
import { settingsActions, useSettings } from "../state/settings";
|
|
12
13
|
import { useConsoleKeyboard } from "../state/useConsoleKeyboard";
|
|
13
14
|
import { useWorkspace } from "../state/workspace";
|
|
@@ -25,6 +26,7 @@ const NARROW_BREAKPOINT = 820;
|
|
|
25
26
|
export function AppShell(): JSX.Element {
|
|
26
27
|
const discovery = useDiscovery();
|
|
27
28
|
const health = useHealth();
|
|
29
|
+
const latency = useLatency();
|
|
28
30
|
const settings = useSettings();
|
|
29
31
|
const workspace = useWorkspace();
|
|
30
32
|
|
|
@@ -192,6 +194,7 @@ export function AppShell(): JSX.Element {
|
|
|
192
194
|
openCount={workspace.attached.length}
|
|
193
195
|
health={health.status}
|
|
194
196
|
healthDetail={health.detail}
|
|
197
|
+
latencyMs={latency.rttMs}
|
|
195
198
|
refreshing={discovery.isRefreshing}
|
|
196
199
|
onRefresh={() => void discovery.refresh()}
|
|
197
200
|
onSettings={() => setSettingsOpen(true)}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/* "Pair CLI to sync" — rendered inline inside a Settings section (no longer a
|
|
2
|
+
* top-bar popover), so the minted-code panel expands in place below the button. */
|
|
3
|
+
.pairsync {
|
|
4
|
+
display: flex;
|
|
5
|
+
flex-direction: column;
|
|
6
|
+
align-items: flex-start;
|
|
7
|
+
gap: 10px;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.pairsync-btn {
|
|
11
|
+
padding: 7px 12px;
|
|
12
|
+
border-radius: 7px;
|
|
13
|
+
border: 1px solid var(--border-strong, #444);
|
|
14
|
+
background: var(--bg-btn, #24262c);
|
|
15
|
+
color: var(--text-1, #e6e8ec);
|
|
16
|
+
font-size: 13px;
|
|
17
|
+
font-family: inherit;
|
|
18
|
+
cursor: pointer;
|
|
19
|
+
}
|
|
20
|
+
.pairsync-btn:hover {
|
|
21
|
+
background: var(--bg-btn-hover, #2c2f36);
|
|
22
|
+
}
|
|
23
|
+
.pairsync-btn:disabled {
|
|
24
|
+
opacity: 0.6;
|
|
25
|
+
cursor: default;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.pairsync-popover {
|
|
29
|
+
width: 100%;
|
|
30
|
+
max-width: 380px;
|
|
31
|
+
padding: 12px;
|
|
32
|
+
display: flex;
|
|
33
|
+
flex-direction: column;
|
|
34
|
+
gap: 10px;
|
|
35
|
+
background: var(--bg-header, #1b1d22);
|
|
36
|
+
border: 1px solid var(--border-soft, #333);
|
|
37
|
+
border-radius: 8px;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.pairsync-body {
|
|
41
|
+
margin: 0;
|
|
42
|
+
font-size: 12px;
|
|
43
|
+
line-height: 1.5;
|
|
44
|
+
color: var(--text-dim, #b8bcc4);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.pairsync-body code {
|
|
48
|
+
font-family: var(--font-mono, monospace);
|
|
49
|
+
color: var(--text, #e6e8ec);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.pairsync-error {
|
|
53
|
+
margin: 0;
|
|
54
|
+
font-size: 12px;
|
|
55
|
+
line-height: 1.5;
|
|
56
|
+
color: var(--danger, #e06c6c);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.pairsync-close {
|
|
60
|
+
align-self: flex-end;
|
|
61
|
+
background: none;
|
|
62
|
+
border: none;
|
|
63
|
+
color: var(--text-dim, #b8bcc4);
|
|
64
|
+
font-size: 12px;
|
|
65
|
+
cursor: pointer;
|
|
66
|
+
padding: 2px 4px;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.pairsync-close:hover {
|
|
70
|
+
color: var(--text, #e6e8ec);
|
|
71
|
+
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
//
|
|
1
|
+
// "Pair CLI to sync" affordance (012-web-adopt-pairing, US4 / FR-017), shown in
|
|
2
|
+
// Settings once the service has been adopted.
|
|
2
3
|
//
|
|
3
4
|
// Mints a fresh pairing code (origin="resync") through the same lifecycle and
|
|
4
5
|
// operator-auth gate as the awaiting-adoption page, and offers a Copy button.
|
|
5
6
|
// The code value is held only in a ref and is NEVER rendered into the DOM
|
|
6
|
-
// (FR-015/FR-016); opening
|
|
7
|
-
// best-effort (the idle TTL is the backstop).
|
|
7
|
+
// (FR-015/FR-016); opening mints, closing (or unmounting, e.g. leaving Settings)
|
|
8
|
+
// ends the session best-effort (the idle TTL is the backstop).
|
|
8
9
|
|
|
9
10
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
10
11
|
import { ApiError, endPairing, mintPairingCode } from "../api/client";
|
|
@@ -19,6 +20,9 @@ export function PairToSync(): JSX.Element {
|
|
|
19
20
|
const [copyState, setCopyState] = useState<"idle" | "copied" | "failed">("idle");
|
|
20
21
|
const codeRef = useRef<string | null>(null);
|
|
21
22
|
const copyResetHandle = useRef<ReturnType<typeof setTimeout>>();
|
|
23
|
+
// Track "open" for the unmount cleanup below without stale-closure issues.
|
|
24
|
+
const openRef = useRef(false);
|
|
25
|
+
openRef.current = open;
|
|
22
26
|
|
|
23
27
|
const close = useCallback(() => {
|
|
24
28
|
setOpen(false);
|
|
@@ -46,6 +50,12 @@ export function PairToSync(): JSX.Element {
|
|
|
46
50
|
}, []);
|
|
47
51
|
|
|
48
52
|
useEffect(() => () => clearTimeout(copyResetHandle.current), []);
|
|
53
|
+
// End the pairing session if Settings is closed with a code still live.
|
|
54
|
+
useEffect(() => () => {
|
|
55
|
+
if (openRef.current) {
|
|
56
|
+
endPairing();
|
|
57
|
+
}
|
|
58
|
+
}, []);
|
|
49
59
|
|
|
50
60
|
const onCopy = useCallback(() => {
|
|
51
61
|
const code = codeRef.current;
|
|
@@ -64,12 +74,12 @@ export function PairToSync(): JSX.Element {
|
|
|
64
74
|
<div className="pairsync">
|
|
65
75
|
<button
|
|
66
76
|
type="button"
|
|
67
|
-
className="
|
|
77
|
+
className="pairsync-btn"
|
|
68
78
|
onClick={open ? close : openAndMint}
|
|
69
79
|
data-testid="pair-to-sync"
|
|
70
80
|
title="Mint a pairing code to run `remo web push` from your workstation"
|
|
71
81
|
>
|
|
72
|
-
|
|
82
|
+
{open ? "Cancel" : "Mint pairing code"}
|
|
73
83
|
</button>
|
|
74
84
|
|
|
75
85
|
{open && (
|
|
@@ -88,7 +98,7 @@ export function PairToSync(): JSX.Element {
|
|
|
88
98
|
) : (
|
|
89
99
|
<button
|
|
90
100
|
type="button"
|
|
91
|
-
className="
|
|
101
|
+
className="pairsync-btn"
|
|
92
102
|
onClick={onCopy}
|
|
93
103
|
disabled={mintState !== "ready"}
|
|
94
104
|
data-testid="pairsync-copy"
|
|
@@ -7,13 +7,16 @@ import { listUploadedFonts, registerUploadedFont } from "../state/fonts";
|
|
|
7
7
|
import {
|
|
8
8
|
ACCENT_OPTIONS,
|
|
9
9
|
FONT_OPTIONS,
|
|
10
|
+
MAX_FOCUS_DWELL_MS,
|
|
10
11
|
MAX_TERM_SIZE,
|
|
12
|
+
MIN_FOCUS_DWELL_MS,
|
|
11
13
|
MIN_TERM_SIZE,
|
|
12
14
|
RENDERER_OPTIONS,
|
|
13
15
|
settingsActions,
|
|
14
16
|
useSettings,
|
|
15
17
|
type FontOption,
|
|
16
18
|
} from "../state/settings";
|
|
19
|
+
import { PairToSync } from "./PairToSync";
|
|
17
20
|
import "./SettingsPage.css";
|
|
18
21
|
|
|
19
22
|
interface SettingsPageProps {
|
|
@@ -156,6 +159,30 @@ export function SettingsPage({ onClose }: SettingsPageProps): JSX.Element {
|
|
|
156
159
|
</div>
|
|
157
160
|
</section>
|
|
158
161
|
|
|
162
|
+
{/* Focus-follows-mouse dwell */}
|
|
163
|
+
<section>
|
|
164
|
+
<div className="settings-row2-head">
|
|
165
|
+
<span className="settings-heading">Focus dwell</span>
|
|
166
|
+
<span className="settings-value">
|
|
167
|
+
{settings.focusDwellMs === 0 ? "instant" : `${settings.focusDwellMs} ms`}
|
|
168
|
+
</span>
|
|
169
|
+
</div>
|
|
170
|
+
<p className="settings-sub">
|
|
171
|
+
In a grid, how long the pointer must rest on a tile before it takes focus
|
|
172
|
+
(focus-follows-mouse). Lower is snappier; higher is calmer. 0 = instant.
|
|
173
|
+
</p>
|
|
174
|
+
<input
|
|
175
|
+
type="range"
|
|
176
|
+
min={MIN_FOCUS_DWELL_MS}
|
|
177
|
+
max={MAX_FOCUS_DWELL_MS}
|
|
178
|
+
step={20}
|
|
179
|
+
value={settings.focusDwellMs}
|
|
180
|
+
onChange={(e) => settingsActions.setFocusDwell(Number(e.target.value))}
|
|
181
|
+
className="settings-range"
|
|
182
|
+
data-testid="focus-dwell-range"
|
|
183
|
+
/>
|
|
184
|
+
</section>
|
|
185
|
+
|
|
159
186
|
{/* Size + ligatures */}
|
|
160
187
|
<section className="settings-row2">
|
|
161
188
|
<div className="settings-col">
|
|
@@ -244,6 +271,17 @@ export function SettingsPage({ onClose }: SettingsPageProps): JSX.Element {
|
|
|
244
271
|
})}
|
|
245
272
|
</div>
|
|
246
273
|
</section>
|
|
274
|
+
|
|
275
|
+
{/* Pair CLI to sync (post-adoption re-sync) */}
|
|
276
|
+
<section>
|
|
277
|
+
<div className="settings-heading">Pair CLI to sync</div>
|
|
278
|
+
<p className="settings-sub">
|
|
279
|
+
Mint a one-time code to run <code>remo web push <url></code> from your
|
|
280
|
+
workstation and push registry / host-key updates to this service. The code is copied
|
|
281
|
+
to your clipboard — it is never shown.
|
|
282
|
+
</p>
|
|
283
|
+
<PairToSync />
|
|
284
|
+
</section>
|
|
247
285
|
</div>
|
|
248
286
|
</div>
|
|
249
287
|
</div>
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
type SettingsState,
|
|
20
20
|
type TerminalFontOptions,
|
|
21
21
|
} from "../state/settings";
|
|
22
|
+
import { removeLatency, reportLatency } from "../state/latency";
|
|
22
23
|
import type { RendererAdapter } from "../terminal/RendererAdapter";
|
|
23
24
|
import { createDefaultRenderer } from "../terminal/defaultRenderer";
|
|
24
25
|
import { TerminalConnection, type TerminalConnectionState } from "../terminal/TerminalConnection";
|
|
@@ -28,9 +29,6 @@ const DEFAULT_COLS = 80;
|
|
|
28
29
|
const DEFAULT_ROWS = 24;
|
|
29
30
|
/** How much to shrink the terminal font in a grid tile when "scale to fit". */
|
|
30
31
|
const GRID_FIT_SCALE = 0.8;
|
|
31
|
-
/** Focus-follows-mouse dwell: the pointer must REST on a tile this long before
|
|
32
|
-
* it takes focus, so passing through tiles (or a small drift) doesn't steal it. */
|
|
33
|
-
const HOVER_FOCUS_DELAY_MS = 220;
|
|
34
32
|
|
|
35
33
|
const STATE_LABELS: Record<TerminalConnectionState, string> = {
|
|
36
34
|
connecting: "Connecting…",
|
|
@@ -124,10 +122,15 @@ export function TerminalCard({
|
|
|
124
122
|
const lastSentDimsRef = useRef<{ cols: number; rows: number } | null>(null);
|
|
125
123
|
// Pending focus-follows-mouse dwell timer (cleared if the pointer leaves first).
|
|
126
124
|
const hoverTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
125
|
+
// Latest focus-dwell setting, read inside the timer without re-creating the handler.
|
|
126
|
+
const dwellMsRef = useRef(settings.focusDwellMs);
|
|
127
|
+
dwellMsRef.current = settings.focusDwellMs;
|
|
127
128
|
|
|
128
129
|
const [connectionState, setConnectionState] = useState<TerminalConnectionState>("connecting");
|
|
129
130
|
const [needsManualReconnect, setNeedsManualReconnect] = useState(false);
|
|
130
131
|
const [error, setError] = useState<TypedError | null>(null);
|
|
132
|
+
const [hasSelection, setHasSelection] = useState(false);
|
|
133
|
+
const [copied, setCopied] = useState(false);
|
|
131
134
|
|
|
132
135
|
// dnd-kit reorder: the tile is both a draggable (handle = header grip, below)
|
|
133
136
|
// and a droppable (swap target). Disabled outside a reorderable grid. We use
|
|
@@ -242,7 +245,12 @@ export function TerminalCard({
|
|
|
242
245
|
onStateChange: (state) => {
|
|
243
246
|
setConnectionState(state);
|
|
244
247
|
setNeedsManualReconnect(connectionRef.current?.needsManualReconnect ?? false);
|
|
248
|
+
// Only a connected terminal contributes to the header latency median.
|
|
249
|
+
if (state !== "ready") {
|
|
250
|
+
removeLatency(target.id);
|
|
251
|
+
}
|
|
245
252
|
},
|
|
253
|
+
onLatency: (rttMs) => reportLatency(target.id, rttMs),
|
|
246
254
|
});
|
|
247
255
|
connectionRef.current = connection;
|
|
248
256
|
|
|
@@ -252,6 +260,9 @@ export function TerminalCard({
|
|
|
252
260
|
}
|
|
253
261
|
});
|
|
254
262
|
|
|
263
|
+
// Show/hide the Copy affordance as the terminal selection changes.
|
|
264
|
+
const unsubscribeSelection = adapter.onSelectionChange((has) => setHasSelection(has));
|
|
265
|
+
|
|
255
266
|
// Reflow on every container size change (window resize, rail drag, grid
|
|
256
267
|
// <-> single, tile show/hide). scheduleFit coalesces bursts to one fit per
|
|
257
268
|
// frame and skips the hidden-0x0 case.
|
|
@@ -267,7 +278,9 @@ export function TerminalCard({
|
|
|
267
278
|
fitRafRef.current = null;
|
|
268
279
|
}
|
|
269
280
|
lastSentDimsRef.current = null;
|
|
281
|
+
removeLatency(target.id);
|
|
270
282
|
unsubscribeInput();
|
|
283
|
+
unsubscribeSelection();
|
|
271
284
|
resizeObserver.disconnect();
|
|
272
285
|
void connection.close();
|
|
273
286
|
adapter.dispose();
|
|
@@ -311,6 +324,15 @@ export function TerminalCard({
|
|
|
311
324
|
onFocusRequest?.();
|
|
312
325
|
}, [onFocusRequest]);
|
|
313
326
|
|
|
327
|
+
const handleCopy = useCallback(() => {
|
|
328
|
+
void adapterRef.current?.copySelection().then((ok) => {
|
|
329
|
+
if (ok) {
|
|
330
|
+
setCopied(true);
|
|
331
|
+
setTimeout(() => setCopied(false), 1200);
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
}, []);
|
|
335
|
+
|
|
314
336
|
const clearHoverTimer = useCallback(() => {
|
|
315
337
|
if (hoverTimerRef.current !== null) {
|
|
316
338
|
clearTimeout(hoverTimerRef.current);
|
|
@@ -328,7 +350,7 @@ export function TerminalCard({
|
|
|
328
350
|
hoverTimerRef.current = setTimeout(() => {
|
|
329
351
|
hoverTimerRef.current = null;
|
|
330
352
|
onHoverFocus();
|
|
331
|
-
},
|
|
353
|
+
}, dwellMsRef.current);
|
|
332
354
|
}, [onHoverFocus, clearHoverTimer]);
|
|
333
355
|
|
|
334
356
|
// Cancel a pending dwell when the pointer leaves or the card unmounts.
|
|
@@ -379,6 +401,20 @@ export function TerminalCard({
|
|
|
379
401
|
{STATE_LABELS[connectionState]}
|
|
380
402
|
</span>
|
|
381
403
|
<div className="terminal-card-controls">
|
|
404
|
+
{hasSelection && (
|
|
405
|
+
<button
|
|
406
|
+
type="button"
|
|
407
|
+
className="tc-btn"
|
|
408
|
+
data-testid={`terminal-copy-${target.id}`}
|
|
409
|
+
title="Copy selection (⌘C / Ctrl+Shift+C)"
|
|
410
|
+
onClick={(e) => {
|
|
411
|
+
e.stopPropagation();
|
|
412
|
+
handleCopy();
|
|
413
|
+
}}
|
|
414
|
+
>
|
|
415
|
+
{copied ? "✓ Copied" : "⧉ Copy"}
|
|
416
|
+
</button>
|
|
417
|
+
)}
|
|
382
418
|
{needsManualReconnect && (
|
|
383
419
|
<button
|
|
384
420
|
type="button"
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
// health indicator, refresh, settings, and shortcuts.
|
|
3
3
|
|
|
4
4
|
import type { HealthStatus } from "../state/health";
|
|
5
|
-
import { PairToSync } from "./PairToSync";
|
|
6
5
|
import "./TopBar.css";
|
|
7
6
|
|
|
8
7
|
// "unconfigured" is included for Record exhaustiveness, but in practice the
|
|
@@ -32,11 +31,24 @@ interface TopBarProps {
|
|
|
32
31
|
health: HealthStatus;
|
|
33
32
|
healthDetail: string | null;
|
|
34
33
|
refreshing: boolean;
|
|
34
|
+
/** Median WS round-trip latency (ms) across open terminals, or null if none. */
|
|
35
|
+
latencyMs: number | null;
|
|
35
36
|
onRefresh: () => void;
|
|
36
37
|
onSettings: () => void;
|
|
37
38
|
onShortcuts: () => void;
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
/** Latency → dot color: good (green) / so-so (yellow) / poor (red). */
|
|
42
|
+
function latencyColor(ms: number): string {
|
|
43
|
+
if (ms < 100) {
|
|
44
|
+
return "var(--ok)";
|
|
45
|
+
}
|
|
46
|
+
if (ms < 300) {
|
|
47
|
+
return "var(--warn)";
|
|
48
|
+
}
|
|
49
|
+
return "var(--danger)";
|
|
50
|
+
}
|
|
51
|
+
|
|
40
52
|
export function TopBar({
|
|
41
53
|
showRailToggle,
|
|
42
54
|
railCollapsed,
|
|
@@ -45,10 +57,19 @@ export function TopBar({
|
|
|
45
57
|
health,
|
|
46
58
|
healthDetail,
|
|
47
59
|
refreshing,
|
|
60
|
+
latencyMs,
|
|
48
61
|
onRefresh,
|
|
49
62
|
onSettings,
|
|
50
63
|
onShortcuts,
|
|
51
64
|
}: TopBarProps): JSX.Element {
|
|
65
|
+
// Prefer live WS latency (the real data-path measure); fall back to the
|
|
66
|
+
// health status when no terminal is connected to measure.
|
|
67
|
+
const showLatency = latencyMs != null;
|
|
68
|
+
const dotColor = showLatency ? latencyColor(latencyMs) : HEALTH_COLOR[health];
|
|
69
|
+
const statusLabel = showLatency ? `${Math.round(latencyMs)} ms` : HEALTH_LABEL[health];
|
|
70
|
+
const statusTitle = showLatency
|
|
71
|
+
? "WebSocket round-trip latency (median across open terminals)"
|
|
72
|
+
: (healthDetail ?? HEALTH_LABEL[health]);
|
|
52
73
|
return (
|
|
53
74
|
<header className="topbar">
|
|
54
75
|
{showRailToggle && (
|
|
@@ -82,17 +103,15 @@ export function TopBar({
|
|
|
82
103
|
{openCount} open
|
|
83
104
|
</span>
|
|
84
105
|
|
|
85
|
-
<span className="topbar-health" title={
|
|
86
|
-
<span className="topbar-health-dot" style={{ background:
|
|
87
|
-
<span>{
|
|
106
|
+
<span className="topbar-health" title={statusTitle} data-testid="topbar-status">
|
|
107
|
+
<span className="topbar-health-dot" style={{ background: dotColor }} />
|
|
108
|
+
<span>{statusLabel}</span>
|
|
88
109
|
</span>
|
|
89
110
|
|
|
90
111
|
<button type="button" className="topbar-btn" onClick={onRefresh} data-testid="refresh-button">
|
|
91
112
|
<span className={refreshing ? "rail-spin" : undefined}>⟳</span> Refresh
|
|
92
113
|
</button>
|
|
93
114
|
|
|
94
|
-
<PairToSync />
|
|
95
|
-
|
|
96
115
|
<button
|
|
97
116
|
type="button"
|
|
98
117
|
className="topbar-icon-btn"
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it } from "vitest";
|
|
2
|
+
import { getLatencySnapshot, removeLatency, reportLatency } from "./latency";
|
|
3
|
+
|
|
4
|
+
describe("latency store", () => {
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
// Clear any samples left by a prior test (module singleton).
|
|
7
|
+
for (const id of ["a", "b", "c", "d"]) {
|
|
8
|
+
removeLatency(id);
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("reports the median across connected terminals", () => {
|
|
13
|
+
reportLatency("a", 40);
|
|
14
|
+
reportLatency("b", 80);
|
|
15
|
+
reportLatency("c", 120);
|
|
16
|
+
const snap = getLatencySnapshot();
|
|
17
|
+
expect(snap.rttMs).toBe(80);
|
|
18
|
+
expect(snap.count).toBe(3);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("updates a terminal's latest sample in place", () => {
|
|
22
|
+
reportLatency("a", 40);
|
|
23
|
+
reportLatency("a", 300);
|
|
24
|
+
expect(getLatencySnapshot()).toEqual({ rttMs: 300, count: 1 });
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("drops a terminal's sample on removal; null when none remain", () => {
|
|
28
|
+
reportLatency("a", 40);
|
|
29
|
+
reportLatency("b", 80);
|
|
30
|
+
removeLatency("a");
|
|
31
|
+
expect(getLatencySnapshot()).toEqual({ rttMs: 80, count: 1 });
|
|
32
|
+
removeLatency("b");
|
|
33
|
+
expect(getLatencySnapshot()).toEqual({ rttMs: null, count: 0 });
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// WebSocket latency store (header indicator).
|
|
2
|
+
//
|
|
3
|
+
// Each connected terminal measures its WS round-trip (ping→pong in
|
|
4
|
+
// TerminalConnection) and reports it here keyed by session-target id. The header
|
|
5
|
+
// shows the MEDIAN across open terminals — since every terminal's WebSocket
|
|
6
|
+
// terminates at the same remo-web service, they all measure the same
|
|
7
|
+
// browser↔service path, so the median is a robust single number. Terminals
|
|
8
|
+
// remove their sample when they disconnect/unmount, so the store only ever holds
|
|
9
|
+
// currently-healthy connections (no freshness timer needed).
|
|
10
|
+
//
|
|
11
|
+
// The cached-snapshot pattern keeps `getSnapshot()` referentially stable between
|
|
12
|
+
// mutations (returning a fresh object each call would loop useSyncExternalStore).
|
|
13
|
+
|
|
14
|
+
import { useSyncExternalStore } from "react";
|
|
15
|
+
|
|
16
|
+
const samples = new Map<string, number>();
|
|
17
|
+
const listeners = new Set<() => void>();
|
|
18
|
+
|
|
19
|
+
export interface LatencySnapshot {
|
|
20
|
+
/** Median WS round-trip in ms across open terminals, or null if none. */
|
|
21
|
+
rttMs: number | null;
|
|
22
|
+
/** How many terminals are currently reporting. */
|
|
23
|
+
count: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let snapshot: LatencySnapshot = { rttMs: null, count: 0 };
|
|
27
|
+
|
|
28
|
+
function recompute(): void {
|
|
29
|
+
if (samples.size === 0) {
|
|
30
|
+
snapshot = { rttMs: null, count: 0 };
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const sorted = [...samples.values()].sort((a, b) => a - b);
|
|
34
|
+
const median = sorted[Math.floor(sorted.length / 2)];
|
|
35
|
+
snapshot = { rttMs: median, count: sorted.length };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function emit(): void {
|
|
39
|
+
for (const listener of listeners) {
|
|
40
|
+
listener();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function reportLatency(id: string, rttMs: number): void {
|
|
45
|
+
samples.set(id, rttMs);
|
|
46
|
+
recompute();
|
|
47
|
+
emit();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function removeLatency(id: string): void {
|
|
51
|
+
if (samples.delete(id)) {
|
|
52
|
+
recompute();
|
|
53
|
+
emit();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Test/inspection accessor for the current aggregate. */
|
|
58
|
+
export function getLatencySnapshot(): LatencySnapshot {
|
|
59
|
+
return snapshot;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function subscribe(listener: () => void): () => void {
|
|
63
|
+
listeners.add(listener);
|
|
64
|
+
return () => listeners.delete(listener);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function useLatency(): LatencySnapshot {
|
|
68
|
+
return useSyncExternalStore(subscribe, getLatencySnapshot, getLatencySnapshot);
|
|
69
|
+
}
|