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.
Files changed (443) hide show
  1. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/PKG-INFO +1 -1
  2. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/docs/web-session-interface.md +17 -7
  3. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/package-lock.json +29 -0
  4. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/package.json +2 -0
  5. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/components/AppShell.tsx +3 -0
  6. remo_cli-2.2.0rc13/frontend/src/components/PairToSync.css +71 -0
  7. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/components/PairToSync.tsx +16 -6
  8. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/components/SettingsPage.tsx +38 -0
  9. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/components/TerminalCard.tsx +40 -4
  10. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/components/TopBar.tsx +25 -6
  11. remo_cli-2.2.0rc13/frontend/src/state/latency.test.ts +35 -0
  12. remo_cli-2.2.0rc13/frontend/src/state/latency.ts +69 -0
  13. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/state/settings.ts +15 -0
  14. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/terminal/GhosttyRenderer.ts +27 -1
  15. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/terminal/RendererAdapter.ts +6 -0
  16. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/terminal/TerminalConnection.ts +37 -0
  17. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/terminal/XtermRenderer.ts +53 -1
  18. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/terminal/keymap.test.ts +21 -1
  19. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/terminal/keymap.ts +14 -0
  20. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/pyproject.toml +1 -1
  21. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/uv.lock +1 -1
  22. remo_cli-2.2.0rc11/frontend/src/components/PairToSync.css +0 -53
  23. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.claude/skills/speckit-analyze/SKILL.md +0 -0
  24. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.claude/skills/speckit-checklist/SKILL.md +0 -0
  25. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.claude/skills/speckit-clarify/SKILL.md +0 -0
  26. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.claude/skills/speckit-constitution/SKILL.md +0 -0
  27. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.claude/skills/speckit-converge/SKILL.md +0 -0
  28. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.claude/skills/speckit-git-commit/SKILL.md +0 -0
  29. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.claude/skills/speckit-git-feature/SKILL.md +0 -0
  30. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.claude/skills/speckit-git-initialize/SKILL.md +0 -0
  31. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.claude/skills/speckit-git-remote/SKILL.md +0 -0
  32. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.claude/skills/speckit-git-validate/SKILL.md +0 -0
  33. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.claude/skills/speckit-implement/SKILL.md +0 -0
  34. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.claude/skills/speckit-plan/SKILL.md +0 -0
  35. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.claude/skills/speckit-specify/SKILL.md +0 -0
  36. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.claude/skills/speckit-tasks/SKILL.md +0 -0
  37. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.claude/skills/speckit-taskstoissues/SKILL.md +0 -0
  38. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.devcontainer/devcontainer.json +0 -0
  39. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.dockerignore +0 -0
  40. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.github/dependabot.yml +0 -0
  41. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.github/workflows/ci.yml +0 -0
  42. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.github/workflows/provision.yml +0 -0
  43. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.github/workflows/release.yml +0 -0
  44. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.github/workflows/smoke-test-cleanup.yml +0 -0
  45. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.github/workflows/smoke-test.yml +0 -0
  46. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.github/workflows/teardown.yml +0 -0
  47. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.gitignore +0 -0
  48. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.maverick/checkpoints/feature/implementation_complete.json +0 -0
  49. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.maverick/checkpoints/feature/phase_${{ index }}_complete.json +0 -0
  50. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.maverick/checkpoints/feature/validation_complete.json +0 -0
  51. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/extensions/.registry +0 -0
  52. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/extensions/git/README.md +0 -0
  53. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/extensions/git/commands/speckit.git.commit.md +0 -0
  54. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/extensions/git/commands/speckit.git.feature.md +0 -0
  55. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/extensions/git/commands/speckit.git.initialize.md +0 -0
  56. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/extensions/git/commands/speckit.git.remote.md +0 -0
  57. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/extensions/git/commands/speckit.git.validate.md +0 -0
  58. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/extensions/git/config-template.yml +0 -0
  59. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/extensions/git/extension.yml +0 -0
  60. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/extensions/git/git-config.yml +0 -0
  61. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/extensions/git/scripts/bash/auto-commit.sh +0 -0
  62. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/extensions/git/scripts/bash/create-new-feature.sh +0 -0
  63. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/extensions/git/scripts/bash/git-common.sh +0 -0
  64. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/extensions/git/scripts/bash/initialize-repo.sh +0 -0
  65. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/extensions/git/scripts/powershell/auto-commit.ps1 +0 -0
  66. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/extensions/git/scripts/powershell/create-new-feature.ps1 +0 -0
  67. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/extensions/git/scripts/powershell/git-common.ps1 +0 -0
  68. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/extensions/git/scripts/powershell/initialize-repo.ps1 +0 -0
  69. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/extensions.yml +0 -0
  70. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/feature.json +0 -0
  71. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/init-options.json +0 -0
  72. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/integration.json +0 -0
  73. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/integrations/claude.manifest.json +0 -0
  74. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/integrations/speckit.manifest.json +0 -0
  75. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/memory/constitution.md +0 -0
  76. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/scripts/bash/check-prerequisites.sh +0 -0
  77. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/scripts/bash/common.sh +0 -0
  78. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/scripts/bash/create-new-feature.sh +0 -0
  79. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/scripts/bash/setup-plan.sh +0 -0
  80. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/scripts/bash/setup-tasks.sh +0 -0
  81. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/scripts/bash/update-agent-context.sh +0 -0
  82. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/templates/agent-file-template.md +0 -0
  83. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/templates/checklist-template.md +0 -0
  84. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/templates/constitution-template.md +0 -0
  85. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/templates/plan-template.md +0 -0
  86. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/templates/spec-template.md +0 -0
  87. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/templates/tasks-template.md +0 -0
  88. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/workflows/speckit/workflow.yml +0 -0
  89. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/.specify/workflows/workflow-registry.json +0 -0
  90. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/AGENTS.md +0 -0
  91. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/CLAUDE.md +0 -0
  92. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/CONTRIBUTING.md +0 -0
  93. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/Dockerfile +0 -0
  94. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/LICENSE +0 -0
  95. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/README.md +0 -0
  96. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/.ansible-lint +0 -0
  97. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/README.md +0 -0
  98. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/ansible.cfg +0 -0
  99. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/aws_configure.yml +0 -0
  100. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/aws_resize.yml +0 -0
  101. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/aws_site.yml +0 -0
  102. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/aws_teardown.yml +0 -0
  103. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/group_vars/all.yml +0 -0
  104. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/group_vars/incus_containers.yml +0 -0
  105. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/group_vars/proxmox_containers.yml +0 -0
  106. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/hetzner_configure.yml +0 -0
  107. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/hetzner_provision.yml +0 -0
  108. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/hetzner_resize.yml +0 -0
  109. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/hetzner_site.yml +0 -0
  110. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/hetzner_teardown.yml +0 -0
  111. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/incus_bootstrap.yml +0 -0
  112. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/incus_configure.yml +0 -0
  113. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/incus_provision.yml +0 -0
  114. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/incus_resize.yml +0 -0
  115. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/incus_site.yml +0 -0
  116. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/incus_teardown.yml +0 -0
  117. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/inventory/hosts.yml +0 -0
  118. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/inventory/incus_containers.yml +0 -0
  119. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/proxmox_bootstrap.yml +0 -0
  120. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/proxmox_configure.yml +0 -0
  121. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/proxmox_provision.yml +0 -0
  122. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/proxmox_resize.yml +0 -0
  123. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/proxmox_site.yml +0 -0
  124. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/proxmox_teardown.yml +0 -0
  125. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/requirements.yml +0 -0
  126. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/aws_server/defaults/main.yml +0 -0
  127. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/aws_server/tasks/ebs.yml +0 -0
  128. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/aws_server/tasks/ec2.yml +0 -0
  129. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/aws_server/tasks/main.yml +0 -0
  130. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/aws_server/tasks/patch_manager.yml +0 -0
  131. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/aws_server/tasks/preflight.yml +0 -0
  132. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/aws_server/tasks/security_group.yml +0 -0
  133. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/deacon/defaults/main.yml +0 -0
  134. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/deacon/tasks/main.yml +0 -0
  135. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/devcontainers/tasks/main.yml +0 -0
  136. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/docker/handlers/main.yml +0 -0
  137. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/docker/tasks/main.yml +0 -0
  138. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/fzf/tasks/main.yml +0 -0
  139. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/github_cli/tasks/main.yml +0 -0
  140. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/hetzner_server/defaults/main.yml +0 -0
  141. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/hetzner_server/tasks/main.yml +0 -0
  142. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/incus_bootstrap/defaults/main.yml +0 -0
  143. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/incus_bootstrap/handlers/main.yml +0 -0
  144. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/incus_bootstrap/tasks/main.yml +0 -0
  145. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/incus_container/defaults/main.yml +0 -0
  146. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/incus_container/handlers/main.yml +0 -0
  147. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/incus_container/meta/main.yml +0 -0
  148. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/incus_container/tasks/main.yml +0 -0
  149. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/incus_container/tasks/preflight.yml +0 -0
  150. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/incus_container_teardown/defaults/main.yml +0 -0
  151. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/incus_container_teardown/meta/main.yml +0 -0
  152. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/incus_container_teardown/tasks/main.yml +0 -0
  153. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/nodejs/defaults/main.yml +0 -0
  154. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/nodejs/tasks/main.yml +0 -0
  155. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/proxmox_bootstrap/defaults/main.yml +0 -0
  156. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/proxmox_bootstrap/meta/main.yml +0 -0
  157. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/proxmox_bootstrap/tasks/main.yml +0 -0
  158. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/proxmox_container/defaults/main.yml +0 -0
  159. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/proxmox_container/handlers/main.yml +0 -0
  160. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/proxmox_container/meta/main.yml +0 -0
  161. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/proxmox_container/tasks/main.yml +0 -0
  162. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/proxmox_container/tasks/preflight.yml +0 -0
  163. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/proxmox_container_teardown/defaults/main.yml +0 -0
  164. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/proxmox_container_teardown/meta/main.yml +0 -0
  165. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/proxmox_container_teardown/tasks/main.yml +0 -0
  166. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/user_setup/defaults/main.yml +0 -0
  167. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/user_setup/tasks/main.yml +0 -0
  168. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/user_setup/templates/devshell.sh.j2 +0 -0
  169. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/user_setup/templates/project-launch.sh.j2 +0 -0
  170. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/user_setup/templates/project-menu.sh.j2 +0 -0
  171. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/user_setup/templates/remo-host.sh.j2 +0 -0
  172. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/zellij/defaults/main.yml +0 -0
  173. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/zellij/tasks/main.yml +0 -0
  174. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/zellij/templates/config.kdl.j2 +0 -0
  175. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/roles/zellij/templates/layouts/minimal.kdl.j2 +0 -0
  176. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/ansible/tasks/configure_dev_tools.yml +0 -0
  177. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/docker/Dockerfile +0 -0
  178. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/docker/compose.example.yml +0 -0
  179. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/docker/entrypoint.sh +0 -0
  180. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/docs/aws.md +0 -0
  181. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/docs/hetzner.md +0 -0
  182. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/docs/incus.md +0 -0
  183. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/docs/install.sh +0 -0
  184. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/docs/proxmox.md +0 -0
  185. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/docs/remo-web-speckit-input.md +0 -0
  186. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/docs/remo-web.html +0 -0
  187. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/index.html +0 -0
  188. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/public/README.md +0 -0
  189. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/public/dist__ghostty-vt.wasm +0 -0
  190. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/public/ghostty-vt.wasm +0 -0
  191. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/scripts/copy-ghostty-wasm.mjs +0 -0
  192. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/api/client.ts +0 -0
  193. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/components/AppRoot.tsx +0 -0
  194. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/components/AppShell.css +0 -0
  195. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/components/AwaitingAdoption.css +0 -0
  196. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/components/AwaitingAdoption.tsx +0 -0
  197. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/components/OfflineOverlay.css +0 -0
  198. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/components/OfflineOverlay.tsx +0 -0
  199. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/components/SessionRail.css +0 -0
  200. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/components/SessionRail.tsx +0 -0
  201. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/components/SettingsPage.css +0 -0
  202. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/components/ShortcutsModal.css +0 -0
  203. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/components/ShortcutsModal.tsx +0 -0
  204. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/components/TerminalCard.css +0 -0
  205. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/components/TopBar.css +0 -0
  206. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/components/WorkspacePane.css +0 -0
  207. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/components/WorkspacePane.tsx +0 -0
  208. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/components/providerMeta.ts +0 -0
  209. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/components/railModel.ts +0 -0
  210. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/lib/clipboard.ts +0 -0
  211. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/lib/fullscreen.ts +0 -0
  212. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/main.tsx +0 -0
  213. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/state/discovery.ts +0 -0
  214. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/state/fonts.ts +0 -0
  215. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/state/health.ts +0 -0
  216. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/state/useConsoleKeyboard.ts +0 -0
  217. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/state/workspace.test.ts +0 -0
  218. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/state/workspace.ts +0 -0
  219. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/terminal/.gitkeep +0 -0
  220. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/terminal/TerminalConnection.test.ts +0 -0
  221. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/terminal/defaultRenderer.ts +0 -0
  222. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/test/setup.ts +0 -0
  223. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/theme/fonts.ts +0 -0
  224. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/src/theme/tokens.css +0 -0
  225. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/tsconfig.json +0 -0
  226. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/frontend/vite.config.ts +0 -0
  227. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/install.sh +0 -0
  228. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/maverick.yaml +0 -0
  229. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/playwright.config.ts +0 -0
  230. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/project-menu.sh +0 -0
  231. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/remo +0 -0
  232. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/run.sh +0 -0
  233. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/scripts/setup-aws-oidc.sh +0 -0
  234. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/001-bootstrap-incus-host/checklists/requirements.md +0 -0
  235. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/001-bootstrap-incus-host/checklists/validation.md +0 -0
  236. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/001-bootstrap-incus-host/contracts/playbook-interface.yaml +0 -0
  237. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/001-bootstrap-incus-host/contracts/role-interface.yaml +0 -0
  238. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/001-bootstrap-incus-host/data-model.md +0 -0
  239. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/001-bootstrap-incus-host/plan.md +0 -0
  240. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/001-bootstrap-incus-host/quickstart.md +0 -0
  241. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/001-bootstrap-incus-host/research.md +0 -0
  242. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/001-bootstrap-incus-host/spec.md +0 -0
  243. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/001-bootstrap-incus-host/tasks.md +0 -0
  244. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/002-incus-container-support/checklists/requirements.md +0 -0
  245. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/002-incus-container-support/contracts/playbook-interface.yaml +0 -0
  246. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/002-incus-container-support/contracts/role-interface.yaml +0 -0
  247. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/002-incus-container-support/data-model.md +0 -0
  248. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/002-incus-container-support/plan.md +0 -0
  249. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/002-incus-container-support/quickstart.md +0 -0
  250. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/002-incus-container-support/research.md +0 -0
  251. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/002-incus-container-support/spec.md +0 -0
  252. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/002-incus-container-support/tasks.md +0 -0
  253. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/003-python-cli-rewrite/checklists/requirements.md +0 -0
  254. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/003-python-cli-rewrite/data-model.md +0 -0
  255. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/003-python-cli-rewrite/plan.md +0 -0
  256. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/003-python-cli-rewrite/quickstart.md +0 -0
  257. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/003-python-cli-rewrite/research.md +0 -0
  258. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/003-python-cli-rewrite/spec.md +0 -0
  259. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/003-python-cli-rewrite/tasks.md +0 -0
  260. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/004-proxmox-container-support/spec.md +0 -0
  261. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/005-provider-snapshots/checklists/requirements.md +0 -0
  262. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/005-provider-snapshots/contracts/cli-surface.md +0 -0
  263. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/005-provider-snapshots/data-model.md +0 -0
  264. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/005-provider-snapshots/plan.md +0 -0
  265. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/005-provider-snapshots/quickstart.md +0 -0
  266. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/005-provider-snapshots/research.md +0 -0
  267. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/005-provider-snapshots/spec.md +0 -0
  268. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/005-provider-snapshots/tasks.md +0 -0
  269. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/010-web-session-interface/checklists/requirements.md +0 -0
  270. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/010-web-session-interface/contracts/remo-host-protocol.md +0 -0
  271. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/010-web-session-interface/contracts/rest-api.md +0 -0
  272. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/010-web-session-interface/contracts/terminal-websocket.md +0 -0
  273. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/010-web-session-interface/data-model.md +0 -0
  274. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/010-web-session-interface/plan.md +0 -0
  275. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/010-web-session-interface/quickstart.md +0 -0
  276. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/010-web-session-interface/research.md +0 -0
  277. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/010-web-session-interface/spec.md +0 -0
  278. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/010-web-session-interface/tasks.md +0 -0
  279. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/011-web-adopt/checklists/requirements.md +0 -0
  280. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/011-web-adopt/contracts/cli-web-adopt.md +0 -0
  281. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/011-web-adopt/contracts/setup-api.md +0 -0
  282. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/011-web-adopt/data-model.md +0 -0
  283. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/011-web-adopt/plan.md +0 -0
  284. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/011-web-adopt/quickstart-results.md +0 -0
  285. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/011-web-adopt/quickstart.md +0 -0
  286. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/011-web-adopt/research.md +0 -0
  287. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/011-web-adopt/spec.md +0 -0
  288. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/011-web-adopt/tasks.md +0 -0
  289. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/012-web-adopt-pairing/contracts/cli-web-adopt.md +0 -0
  290. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/012-web-adopt-pairing/contracts/pairing-api.md +0 -0
  291. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/012-web-adopt-pairing/contracts/setup-api.md +0 -0
  292. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/012-web-adopt-pairing/data-model.md +0 -0
  293. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/012-web-adopt-pairing/plan.md +0 -0
  294. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/012-web-adopt-pairing/quickstart.md +0 -0
  295. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/012-web-adopt-pairing/research.md +0 -0
  296. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/012-web-adopt-pairing/spec.md +0 -0
  297. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/specs/012-web-adopt-pairing/tasks.md +0 -0
  298. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/__init__.py +0 -0
  299. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/__main__.py +0 -0
  300. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/cli/__init__.py +0 -0
  301. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/cli/cp.py +0 -0
  302. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/cli/main.py +0 -0
  303. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/cli/providers/__init__.py +0 -0
  304. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/cli/providers/aws.py +0 -0
  305. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/cli/providers/hetzner.py +0 -0
  306. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/cli/providers/incus.py +0 -0
  307. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/cli/providers/proxmox.py +0 -0
  308. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/cli/shell.py +0 -0
  309. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/cli/web.py +0 -0
  310. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/core/__init__.py +0 -0
  311. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/core/ansible_runner.py +0 -0
  312. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/core/completion.py +0 -0
  313. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/core/config.py +0 -0
  314. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/core/known_hosts.py +0 -0
  315. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/core/output.py +0 -0
  316. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/core/picker.py +0 -0
  317. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/core/remo_host_client.py +0 -0
  318. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/core/rsync.py +0 -0
  319. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/core/snapshot.py +0 -0
  320. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/core/ssh.py +0 -0
  321. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/core/validation.py +0 -0
  322. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/core/version.py +0 -0
  323. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/core/web_adopt.py +0 -0
  324. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/models/__init__.py +0 -0
  325. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/models/capability.py +0 -0
  326. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/models/discovery.py +0 -0
  327. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/models/host.py +0 -0
  328. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/models/session_target.py +0 -0
  329. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/models/snapshot.py +0 -0
  330. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/providers/__init__.py +0 -0
  331. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/providers/aws.py +0 -0
  332. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/providers/hetzner.py +0 -0
  333. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/providers/incus.py +0 -0
  334. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/providers/proxmox.py +0 -0
  335. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/web/__init__.py +0 -0
  336. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/web/api/__init__.py +0 -0
  337. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/web/api/hosts.py +0 -0
  338. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/web/api/pairing.py +0 -0
  339. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/web/api/setup.py +0 -0
  340. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/web/api/terminals.py +0 -0
  341. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/web/app.py +0 -0
  342. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/web/check.py +0 -0
  343. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/web/config.py +0 -0
  344. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/web/discovery.py +0 -0
  345. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/web/health.py +0 -0
  346. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/web/logging_config.py +0 -0
  347. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/web/models.py +0 -0
  348. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/web/operator_auth.py +0 -0
  349. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/web/pairing.py +0 -0
  350. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/web/ssh_master.py +0 -0
  351. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/web/state.py +0 -0
  352. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/web/terminal.py +0 -0
  353. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/web/terminal_registry.py +0 -0
  354. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/src/remo_cli/web/tokens.py +0 -0
  355. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/__init__.py +0 -0
  356. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/ansible/__init__.py +0 -0
  357. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/ansible/test_remo_host_idempotency.py +0 -0
  358. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/conftest.py +0 -0
  359. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/e2e/fixtures.ts +0 -0
  360. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/e2e/ghostty-shell-io.spec.ts +0 -0
  361. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/e2e/ghostty-tui-compatibility.spec.ts +0 -0
  362. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/e2e/keyboard-routing.spec.ts +0 -0
  363. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/e2e/mobile.spec.ts +0 -0
  364. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/e2e/reconnect.spec.ts +0 -0
  365. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/e2e/workspace-layout.spec.ts +0 -0
  366. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/image/__init__.py +0 -0
  367. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/image/test_docker_image.py +0 -0
  368. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/integration/__init__.py +0 -0
  369. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/integration/_nine_terminal_fixture.py +0 -0
  370. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/integration/orbstack-cloud-init.yml +0 -0
  371. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/integration/orbstack.sh +0 -0
  372. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/integration/test_nine_terminals.py +0 -0
  373. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/integration/test_nine_terminals_soak.py +0 -0
  374. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/integration/test_remo_host_e2e.py +0 -0
  375. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/integration/test_security_rejections.py +0 -0
  376. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/integration/test_terminal_attach.py +0 -0
  377. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/integration/test_web_adopt_e2e.py +0 -0
  378. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/integration/test_web_cli_parity.py +0 -0
  379. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/perf/__init__.py +0 -0
  380. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/perf/test_latency.py +0 -0
  381. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/__init__.py +0 -0
  382. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/cli/__init__.py +0 -0
  383. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/cli/providers/test_aws_snapshot.py +0 -0
  384. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/cli/providers/test_hetzner_snapshot.py +0 -0
  385. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/cli/providers/test_incus_snapshot.py +0 -0
  386. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/cli/providers/test_proxmox_snapshot.py +0 -0
  387. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/cli/test_cp.py +0 -0
  388. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/cli/test_main.py +0 -0
  389. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/cli/test_shell.py +0 -0
  390. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/cli/test_web_adopt_cmd.py +0 -0
  391. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/core/__init__.py +0 -0
  392. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/core/test_ansible_runner.py +0 -0
  393. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/core/test_config.py +0 -0
  394. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/core/test_known_hosts.py +0 -0
  395. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/core/test_output.py +0 -0
  396. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/core/test_picker.py +0 -0
  397. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/core/test_remo_host_client.py +0 -0
  398. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/core/test_rsync.py +0 -0
  399. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/core/test_snapshot.py +0 -0
  400. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/core/test_ssh.py +0 -0
  401. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/core/test_ssh_controlpath.py +0 -0
  402. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/core/test_ssh_identity_opts.py +0 -0
  403. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/core/test_validation.py +0 -0
  404. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/core/test_version.py +0 -0
  405. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/core/test_web_adopt_authorize.py +0 -0
  406. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/core/test_web_adopt_code.py +0 -0
  407. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/core/test_web_adopt_payload.py +0 -0
  408. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/core/test_web_adopt_trust.py +0 -0
  409. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/core/test_web_push.py +0 -0
  410. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/models/__init__.py +0 -0
  411. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/models/test_capability.py +0 -0
  412. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/models/test_discovery.py +0 -0
  413. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/models/test_session_target.py +0 -0
  414. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/providers/__init__.py +0 -0
  415. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/providers/test_aws_snapshot.py +0 -0
  416. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/providers/test_hetzner_snapshot.py +0 -0
  417. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/providers/test_incus_snapshot.py +0 -0
  418. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/providers/test_proxmox_devcontainer_runtime.py +0 -0
  419. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/providers/test_proxmox_snapshot.py +0 -0
  420. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/test_ansible_templates.py +0 -0
  421. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/test_host_model.py +0 -0
  422. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/web/__init__.py +0 -0
  423. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/web/_pairing_support.py +0 -0
  424. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/web/conftest.py +0 -0
  425. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/web/test_backpressure.py +0 -0
  426. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/web/test_check.py +0 -0
  427. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/web/test_csp.py +0 -0
  428. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/web/test_discovery.py +0 -0
  429. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/web/test_health_states.py +0 -0
  430. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/web/test_hosts_mapping.py +0 -0
  431. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/web/test_lazy_import.py +0 -0
  432. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/web/test_log_redaction.py +0 -0
  433. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/web/test_mint_gating.py +0 -0
  434. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/web/test_operator_auth.py +0 -0
  435. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/web/test_pairing.py +0 -0
  436. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/web/test_pairing_origin.py +0 -0
  437. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/web/test_setup_api.py +0 -0
  438. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/web/test_setup_auth.py +0 -0
  439. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/web/test_setup_dormancy.py +0 -0
  440. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/web/test_state.py +0 -0
  441. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/web/test_terminal_resize.py +0 -0
  442. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/web/test_terminals_api.py +0 -0
  443. {remo_cli-2.2.0rc11 → remo_cli-2.2.0rc13}/tests/unit/web/test_tokens.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: remo-cli
3
- Version: 2.2.0rc11
3
+ Version: 2.2.0rc13
4
4
  Summary: Remote development environment CLI
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -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), and a **Nerd Font uploader**.
134
- Because a browser can't read fonts installed on the instance, uploading a patched Nerd Font once
135
- registers it via the `FontFace` API (persisted in IndexedDB) and offers it as a terminal font that's
136
- how Powerline/Git/devicon glyphs in a prompt or Zellij status bar render. Font changes apply live to
137
- every open terminal. The top bar also shows a health indicator (from `GET /api/v1/ready`) and an
138
- offline overlay if the service becomes unreachable (terminals reattach automatically when it returns).
139
- Press **?** for the keyboard-shortcut reference.
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
- // Dashboard "Pair CLI to sync" affordance (012-web-adopt-pairing, US4 / FR-017).
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 the popover mints, closing it ends the session
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="topbar-btn"
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
- Pair CLI to sync
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="topbar-btn"
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 &lt;url&gt;</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
- }, HOVER_FOCUS_DELAY_MS);
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={healthDetail ?? HEALTH_LABEL[health]}>
86
- <span className="topbar-health-dot" style={{ background: HEALTH_COLOR[health] }} />
87
- <span>{HEALTH_LABEL[health]}</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
+ }