remo-cli 2.2.0rc4__tar.gz → 2.2.0rc6__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 (435) hide show
  1. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/PKG-INFO +1 -1
  2. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/docs/web-session-interface.md +6 -2
  3. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/api/client.ts +85 -0
  4. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/AppShell.css +7 -0
  5. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/AppShell.tsx +38 -5
  6. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/ShortcutsModal.tsx +2 -1
  7. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/TerminalCard.css +50 -7
  8. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/TerminalCard.tsx +75 -26
  9. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/WorkspacePane.tsx +33 -6
  10. remo_cli-2.2.0rc6/frontend/src/lib/fullscreen.ts +38 -0
  11. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/state/useConsoleKeyboard.ts +26 -2
  12. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/state/workspace.ts +58 -9
  13. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/pyproject.toml +1 -1
  14. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/uv.lock +1 -1
  15. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.claude/skills/speckit-analyze/SKILL.md +0 -0
  16. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.claude/skills/speckit-checklist/SKILL.md +0 -0
  17. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.claude/skills/speckit-clarify/SKILL.md +0 -0
  18. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.claude/skills/speckit-constitution/SKILL.md +0 -0
  19. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.claude/skills/speckit-converge/SKILL.md +0 -0
  20. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.claude/skills/speckit-git-commit/SKILL.md +0 -0
  21. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.claude/skills/speckit-git-feature/SKILL.md +0 -0
  22. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.claude/skills/speckit-git-initialize/SKILL.md +0 -0
  23. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.claude/skills/speckit-git-remote/SKILL.md +0 -0
  24. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.claude/skills/speckit-git-validate/SKILL.md +0 -0
  25. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.claude/skills/speckit-implement/SKILL.md +0 -0
  26. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.claude/skills/speckit-plan/SKILL.md +0 -0
  27. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.claude/skills/speckit-specify/SKILL.md +0 -0
  28. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.claude/skills/speckit-tasks/SKILL.md +0 -0
  29. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.claude/skills/speckit-taskstoissues/SKILL.md +0 -0
  30. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.devcontainer/devcontainer.json +0 -0
  31. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.dockerignore +0 -0
  32. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.github/dependabot.yml +0 -0
  33. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.github/workflows/ci.yml +0 -0
  34. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.github/workflows/provision.yml +0 -0
  35. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.github/workflows/release.yml +0 -0
  36. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.github/workflows/smoke-test-cleanup.yml +0 -0
  37. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.github/workflows/smoke-test.yml +0 -0
  38. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.github/workflows/teardown.yml +0 -0
  39. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.gitignore +0 -0
  40. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.maverick/checkpoints/feature/implementation_complete.json +0 -0
  41. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.maverick/checkpoints/feature/phase_${{ index }}_complete.json +0 -0
  42. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.maverick/checkpoints/feature/validation_complete.json +0 -0
  43. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/extensions/.registry +0 -0
  44. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/extensions/git/README.md +0 -0
  45. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/extensions/git/commands/speckit.git.commit.md +0 -0
  46. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/extensions/git/commands/speckit.git.feature.md +0 -0
  47. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/extensions/git/commands/speckit.git.initialize.md +0 -0
  48. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/extensions/git/commands/speckit.git.remote.md +0 -0
  49. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/extensions/git/commands/speckit.git.validate.md +0 -0
  50. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/extensions/git/config-template.yml +0 -0
  51. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/extensions/git/extension.yml +0 -0
  52. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/extensions/git/git-config.yml +0 -0
  53. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/extensions/git/scripts/bash/auto-commit.sh +0 -0
  54. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/extensions/git/scripts/bash/create-new-feature.sh +0 -0
  55. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/extensions/git/scripts/bash/git-common.sh +0 -0
  56. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/extensions/git/scripts/bash/initialize-repo.sh +0 -0
  57. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/extensions/git/scripts/powershell/auto-commit.ps1 +0 -0
  58. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/extensions/git/scripts/powershell/create-new-feature.ps1 +0 -0
  59. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/extensions/git/scripts/powershell/git-common.ps1 +0 -0
  60. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/extensions/git/scripts/powershell/initialize-repo.ps1 +0 -0
  61. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/extensions.yml +0 -0
  62. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/feature.json +0 -0
  63. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/init-options.json +0 -0
  64. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/integration.json +0 -0
  65. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/integrations/claude.manifest.json +0 -0
  66. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/integrations/speckit.manifest.json +0 -0
  67. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/memory/constitution.md +0 -0
  68. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/scripts/bash/check-prerequisites.sh +0 -0
  69. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/scripts/bash/common.sh +0 -0
  70. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/scripts/bash/create-new-feature.sh +0 -0
  71. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/scripts/bash/setup-plan.sh +0 -0
  72. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/scripts/bash/setup-tasks.sh +0 -0
  73. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/scripts/bash/update-agent-context.sh +0 -0
  74. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/templates/agent-file-template.md +0 -0
  75. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/templates/checklist-template.md +0 -0
  76. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/templates/constitution-template.md +0 -0
  77. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/templates/plan-template.md +0 -0
  78. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/templates/spec-template.md +0 -0
  79. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/templates/tasks-template.md +0 -0
  80. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/workflows/speckit/workflow.yml +0 -0
  81. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/workflows/workflow-registry.json +0 -0
  82. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/AGENTS.md +0 -0
  83. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/CLAUDE.md +0 -0
  84. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/CONTRIBUTING.md +0 -0
  85. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/Dockerfile +0 -0
  86. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/LICENSE +0 -0
  87. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/README.md +0 -0
  88. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/.ansible-lint +0 -0
  89. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/README.md +0 -0
  90. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/ansible.cfg +0 -0
  91. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/aws_configure.yml +0 -0
  92. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/aws_resize.yml +0 -0
  93. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/aws_site.yml +0 -0
  94. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/aws_teardown.yml +0 -0
  95. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/group_vars/all.yml +0 -0
  96. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/group_vars/incus_containers.yml +0 -0
  97. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/group_vars/proxmox_containers.yml +0 -0
  98. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/hetzner_configure.yml +0 -0
  99. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/hetzner_provision.yml +0 -0
  100. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/hetzner_resize.yml +0 -0
  101. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/hetzner_site.yml +0 -0
  102. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/hetzner_teardown.yml +0 -0
  103. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/incus_bootstrap.yml +0 -0
  104. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/incus_configure.yml +0 -0
  105. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/incus_provision.yml +0 -0
  106. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/incus_resize.yml +0 -0
  107. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/incus_site.yml +0 -0
  108. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/incus_teardown.yml +0 -0
  109. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/inventory/hosts.yml +0 -0
  110. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/inventory/incus_containers.yml +0 -0
  111. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/proxmox_bootstrap.yml +0 -0
  112. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/proxmox_configure.yml +0 -0
  113. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/proxmox_provision.yml +0 -0
  114. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/proxmox_resize.yml +0 -0
  115. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/proxmox_site.yml +0 -0
  116. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/proxmox_teardown.yml +0 -0
  117. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/requirements.yml +0 -0
  118. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/aws_server/defaults/main.yml +0 -0
  119. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/aws_server/tasks/ebs.yml +0 -0
  120. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/aws_server/tasks/ec2.yml +0 -0
  121. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/aws_server/tasks/main.yml +0 -0
  122. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/aws_server/tasks/patch_manager.yml +0 -0
  123. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/aws_server/tasks/preflight.yml +0 -0
  124. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/aws_server/tasks/security_group.yml +0 -0
  125. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/deacon/defaults/main.yml +0 -0
  126. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/deacon/tasks/main.yml +0 -0
  127. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/devcontainers/tasks/main.yml +0 -0
  128. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/docker/handlers/main.yml +0 -0
  129. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/docker/tasks/main.yml +0 -0
  130. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/fzf/tasks/main.yml +0 -0
  131. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/github_cli/tasks/main.yml +0 -0
  132. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/hetzner_server/defaults/main.yml +0 -0
  133. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/hetzner_server/tasks/main.yml +0 -0
  134. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/incus_bootstrap/defaults/main.yml +0 -0
  135. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/incus_bootstrap/handlers/main.yml +0 -0
  136. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/incus_bootstrap/tasks/main.yml +0 -0
  137. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/incus_container/defaults/main.yml +0 -0
  138. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/incus_container/handlers/main.yml +0 -0
  139. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/incus_container/meta/main.yml +0 -0
  140. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/incus_container/tasks/main.yml +0 -0
  141. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/incus_container/tasks/preflight.yml +0 -0
  142. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/incus_container_teardown/defaults/main.yml +0 -0
  143. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/incus_container_teardown/meta/main.yml +0 -0
  144. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/incus_container_teardown/tasks/main.yml +0 -0
  145. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/nodejs/defaults/main.yml +0 -0
  146. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/nodejs/tasks/main.yml +0 -0
  147. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/proxmox_bootstrap/defaults/main.yml +0 -0
  148. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/proxmox_bootstrap/meta/main.yml +0 -0
  149. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/proxmox_bootstrap/tasks/main.yml +0 -0
  150. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/proxmox_container/defaults/main.yml +0 -0
  151. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/proxmox_container/handlers/main.yml +0 -0
  152. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/proxmox_container/meta/main.yml +0 -0
  153. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/proxmox_container/tasks/main.yml +0 -0
  154. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/proxmox_container/tasks/preflight.yml +0 -0
  155. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/proxmox_container_teardown/defaults/main.yml +0 -0
  156. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/proxmox_container_teardown/meta/main.yml +0 -0
  157. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/proxmox_container_teardown/tasks/main.yml +0 -0
  158. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/user_setup/defaults/main.yml +0 -0
  159. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/user_setup/tasks/main.yml +0 -0
  160. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/user_setup/templates/devshell.sh.j2 +0 -0
  161. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/user_setup/templates/project-launch.sh.j2 +0 -0
  162. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/user_setup/templates/project-menu.sh.j2 +0 -0
  163. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/user_setup/templates/remo-host.sh.j2 +0 -0
  164. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/zellij/defaults/main.yml +0 -0
  165. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/zellij/tasks/main.yml +0 -0
  166. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/zellij/templates/config.kdl.j2 +0 -0
  167. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/zellij/templates/layouts/minimal.kdl.j2 +0 -0
  168. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/tasks/configure_dev_tools.yml +0 -0
  169. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/docker/Dockerfile +0 -0
  170. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/docker/compose.example.yml +0 -0
  171. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/docker/entrypoint.sh +0 -0
  172. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/docs/aws.md +0 -0
  173. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/docs/hetzner.md +0 -0
  174. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/docs/incus.md +0 -0
  175. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/docs/install.sh +0 -0
  176. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/docs/proxmox.md +0 -0
  177. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/docs/remo-web-speckit-input.md +0 -0
  178. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/docs/remo-web.html +0 -0
  179. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/index.html +0 -0
  180. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/package-lock.json +0 -0
  181. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/package.json +0 -0
  182. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/public/README.md +0 -0
  183. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/public/dist__ghostty-vt.wasm +0 -0
  184. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/public/ghostty-vt.wasm +0 -0
  185. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/scripts/copy-ghostty-wasm.mjs +0 -0
  186. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/AppRoot.tsx +0 -0
  187. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/AwaitingAdoption.css +0 -0
  188. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/AwaitingAdoption.tsx +0 -0
  189. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/OfflineOverlay.css +0 -0
  190. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/OfflineOverlay.tsx +0 -0
  191. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/PairToSync.css +0 -0
  192. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/PairToSync.tsx +0 -0
  193. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/SessionRail.css +0 -0
  194. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/SessionRail.tsx +0 -0
  195. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/SettingsPage.css +0 -0
  196. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/SettingsPage.tsx +0 -0
  197. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/ShortcutsModal.css +0 -0
  198. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/TopBar.css +0 -0
  199. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/TopBar.tsx +0 -0
  200. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/WorkspacePane.css +0 -0
  201. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/providerMeta.ts +0 -0
  202. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/railModel.ts +0 -0
  203. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/lib/clipboard.ts +0 -0
  204. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/main.tsx +0 -0
  205. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/state/discovery.ts +0 -0
  206. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/state/fonts.ts +0 -0
  207. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/state/health.ts +0 -0
  208. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/state/settings.ts +0 -0
  209. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/terminal/.gitkeep +0 -0
  210. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/terminal/GhosttyRenderer.ts +0 -0
  211. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/terminal/RendererAdapter.ts +0 -0
  212. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/terminal/TerminalConnection.ts +0 -0
  213. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/terminal/XtermRenderer.ts +0 -0
  214. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/terminal/defaultRenderer.ts +0 -0
  215. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/theme/fonts.ts +0 -0
  216. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/theme/tokens.css +0 -0
  217. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/tsconfig.json +0 -0
  218. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/vite.config.ts +0 -0
  219. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/install.sh +0 -0
  220. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/maverick.yaml +0 -0
  221. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/playwright.config.ts +0 -0
  222. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/project-menu.sh +0 -0
  223. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/remo +0 -0
  224. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/run.sh +0 -0
  225. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/scripts/setup-aws-oidc.sh +0 -0
  226. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/001-bootstrap-incus-host/checklists/requirements.md +0 -0
  227. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/001-bootstrap-incus-host/checklists/validation.md +0 -0
  228. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/001-bootstrap-incus-host/contracts/playbook-interface.yaml +0 -0
  229. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/001-bootstrap-incus-host/contracts/role-interface.yaml +0 -0
  230. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/001-bootstrap-incus-host/data-model.md +0 -0
  231. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/001-bootstrap-incus-host/plan.md +0 -0
  232. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/001-bootstrap-incus-host/quickstart.md +0 -0
  233. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/001-bootstrap-incus-host/research.md +0 -0
  234. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/001-bootstrap-incus-host/spec.md +0 -0
  235. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/001-bootstrap-incus-host/tasks.md +0 -0
  236. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/002-incus-container-support/checklists/requirements.md +0 -0
  237. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/002-incus-container-support/contracts/playbook-interface.yaml +0 -0
  238. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/002-incus-container-support/contracts/role-interface.yaml +0 -0
  239. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/002-incus-container-support/data-model.md +0 -0
  240. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/002-incus-container-support/plan.md +0 -0
  241. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/002-incus-container-support/quickstart.md +0 -0
  242. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/002-incus-container-support/research.md +0 -0
  243. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/002-incus-container-support/spec.md +0 -0
  244. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/002-incus-container-support/tasks.md +0 -0
  245. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/003-python-cli-rewrite/checklists/requirements.md +0 -0
  246. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/003-python-cli-rewrite/data-model.md +0 -0
  247. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/003-python-cli-rewrite/plan.md +0 -0
  248. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/003-python-cli-rewrite/quickstart.md +0 -0
  249. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/003-python-cli-rewrite/research.md +0 -0
  250. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/003-python-cli-rewrite/spec.md +0 -0
  251. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/003-python-cli-rewrite/tasks.md +0 -0
  252. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/004-proxmox-container-support/spec.md +0 -0
  253. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/005-provider-snapshots/checklists/requirements.md +0 -0
  254. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/005-provider-snapshots/contracts/cli-surface.md +0 -0
  255. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/005-provider-snapshots/data-model.md +0 -0
  256. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/005-provider-snapshots/plan.md +0 -0
  257. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/005-provider-snapshots/quickstart.md +0 -0
  258. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/005-provider-snapshots/research.md +0 -0
  259. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/005-provider-snapshots/spec.md +0 -0
  260. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/005-provider-snapshots/tasks.md +0 -0
  261. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/010-web-session-interface/checklists/requirements.md +0 -0
  262. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/010-web-session-interface/contracts/remo-host-protocol.md +0 -0
  263. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/010-web-session-interface/contracts/rest-api.md +0 -0
  264. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/010-web-session-interface/contracts/terminal-websocket.md +0 -0
  265. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/010-web-session-interface/data-model.md +0 -0
  266. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/010-web-session-interface/plan.md +0 -0
  267. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/010-web-session-interface/quickstart.md +0 -0
  268. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/010-web-session-interface/research.md +0 -0
  269. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/010-web-session-interface/spec.md +0 -0
  270. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/010-web-session-interface/tasks.md +0 -0
  271. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/011-web-adopt/checklists/requirements.md +0 -0
  272. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/011-web-adopt/contracts/cli-web-adopt.md +0 -0
  273. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/011-web-adopt/contracts/setup-api.md +0 -0
  274. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/011-web-adopt/data-model.md +0 -0
  275. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/011-web-adopt/plan.md +0 -0
  276. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/011-web-adopt/quickstart-results.md +0 -0
  277. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/011-web-adopt/quickstart.md +0 -0
  278. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/011-web-adopt/research.md +0 -0
  279. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/011-web-adopt/spec.md +0 -0
  280. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/011-web-adopt/tasks.md +0 -0
  281. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/012-web-adopt-pairing/contracts/cli-web-adopt.md +0 -0
  282. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/012-web-adopt-pairing/contracts/pairing-api.md +0 -0
  283. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/012-web-adopt-pairing/contracts/setup-api.md +0 -0
  284. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/012-web-adopt-pairing/data-model.md +0 -0
  285. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/012-web-adopt-pairing/plan.md +0 -0
  286. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/012-web-adopt-pairing/quickstart.md +0 -0
  287. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/012-web-adopt-pairing/research.md +0 -0
  288. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/012-web-adopt-pairing/spec.md +0 -0
  289. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/012-web-adopt-pairing/tasks.md +0 -0
  290. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/__init__.py +0 -0
  291. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/__main__.py +0 -0
  292. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/cli/__init__.py +0 -0
  293. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/cli/cp.py +0 -0
  294. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/cli/main.py +0 -0
  295. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/cli/providers/__init__.py +0 -0
  296. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/cli/providers/aws.py +0 -0
  297. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/cli/providers/hetzner.py +0 -0
  298. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/cli/providers/incus.py +0 -0
  299. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/cli/providers/proxmox.py +0 -0
  300. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/cli/shell.py +0 -0
  301. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/cli/web.py +0 -0
  302. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/core/__init__.py +0 -0
  303. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/core/ansible_runner.py +0 -0
  304. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/core/completion.py +0 -0
  305. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/core/config.py +0 -0
  306. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/core/known_hosts.py +0 -0
  307. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/core/output.py +0 -0
  308. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/core/picker.py +0 -0
  309. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/core/remo_host_client.py +0 -0
  310. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/core/rsync.py +0 -0
  311. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/core/snapshot.py +0 -0
  312. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/core/ssh.py +0 -0
  313. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/core/validation.py +0 -0
  314. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/core/version.py +0 -0
  315. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/core/web_adopt.py +0 -0
  316. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/models/__init__.py +0 -0
  317. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/models/capability.py +0 -0
  318. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/models/discovery.py +0 -0
  319. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/models/host.py +0 -0
  320. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/models/session_target.py +0 -0
  321. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/models/snapshot.py +0 -0
  322. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/providers/__init__.py +0 -0
  323. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/providers/aws.py +0 -0
  324. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/providers/hetzner.py +0 -0
  325. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/providers/incus.py +0 -0
  326. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/providers/proxmox.py +0 -0
  327. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/web/__init__.py +0 -0
  328. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/web/api/__init__.py +0 -0
  329. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/web/api/hosts.py +0 -0
  330. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/web/api/pairing.py +0 -0
  331. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/web/api/setup.py +0 -0
  332. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/web/api/terminals.py +0 -0
  333. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/web/app.py +0 -0
  334. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/web/check.py +0 -0
  335. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/web/config.py +0 -0
  336. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/web/discovery.py +0 -0
  337. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/web/health.py +0 -0
  338. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/web/logging_config.py +0 -0
  339. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/web/models.py +0 -0
  340. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/web/operator_auth.py +0 -0
  341. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/web/pairing.py +0 -0
  342. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/web/ssh_master.py +0 -0
  343. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/web/state.py +0 -0
  344. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/web/terminal.py +0 -0
  345. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/web/terminal_registry.py +0 -0
  346. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/web/tokens.py +0 -0
  347. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/__init__.py +0 -0
  348. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/ansible/__init__.py +0 -0
  349. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/ansible/test_remo_host_idempotency.py +0 -0
  350. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/conftest.py +0 -0
  351. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/e2e/fixtures.ts +0 -0
  352. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/e2e/ghostty-shell-io.spec.ts +0 -0
  353. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/e2e/ghostty-tui-compatibility.spec.ts +0 -0
  354. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/e2e/keyboard-routing.spec.ts +0 -0
  355. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/e2e/mobile.spec.ts +0 -0
  356. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/e2e/reconnect.spec.ts +0 -0
  357. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/e2e/workspace-layout.spec.ts +0 -0
  358. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/image/__init__.py +0 -0
  359. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/image/test_docker_image.py +0 -0
  360. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/integration/__init__.py +0 -0
  361. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/integration/_nine_terminal_fixture.py +0 -0
  362. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/integration/orbstack-cloud-init.yml +0 -0
  363. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/integration/orbstack.sh +0 -0
  364. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/integration/test_nine_terminals.py +0 -0
  365. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/integration/test_nine_terminals_soak.py +0 -0
  366. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/integration/test_remo_host_e2e.py +0 -0
  367. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/integration/test_security_rejections.py +0 -0
  368. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/integration/test_terminal_attach.py +0 -0
  369. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/integration/test_web_adopt_e2e.py +0 -0
  370. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/integration/test_web_cli_parity.py +0 -0
  371. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/perf/__init__.py +0 -0
  372. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/perf/test_latency.py +0 -0
  373. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/__init__.py +0 -0
  374. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/cli/__init__.py +0 -0
  375. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/cli/providers/test_aws_snapshot.py +0 -0
  376. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/cli/providers/test_hetzner_snapshot.py +0 -0
  377. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/cli/providers/test_incus_snapshot.py +0 -0
  378. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/cli/providers/test_proxmox_snapshot.py +0 -0
  379. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/cli/test_cp.py +0 -0
  380. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/cli/test_main.py +0 -0
  381. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/cli/test_shell.py +0 -0
  382. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/cli/test_web_adopt_cmd.py +0 -0
  383. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/core/__init__.py +0 -0
  384. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/core/test_ansible_runner.py +0 -0
  385. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/core/test_config.py +0 -0
  386. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/core/test_known_hosts.py +0 -0
  387. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/core/test_output.py +0 -0
  388. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/core/test_picker.py +0 -0
  389. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/core/test_remo_host_client.py +0 -0
  390. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/core/test_rsync.py +0 -0
  391. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/core/test_snapshot.py +0 -0
  392. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/core/test_ssh.py +0 -0
  393. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/core/test_ssh_controlpath.py +0 -0
  394. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/core/test_ssh_identity_opts.py +0 -0
  395. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/core/test_validation.py +0 -0
  396. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/core/test_version.py +0 -0
  397. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/core/test_web_adopt_authorize.py +0 -0
  398. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/core/test_web_adopt_code.py +0 -0
  399. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/core/test_web_adopt_payload.py +0 -0
  400. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/core/test_web_adopt_trust.py +0 -0
  401. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/core/test_web_push.py +0 -0
  402. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/models/__init__.py +0 -0
  403. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/models/test_capability.py +0 -0
  404. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/models/test_discovery.py +0 -0
  405. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/models/test_session_target.py +0 -0
  406. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/providers/__init__.py +0 -0
  407. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/providers/test_aws_snapshot.py +0 -0
  408. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/providers/test_hetzner_snapshot.py +0 -0
  409. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/providers/test_incus_snapshot.py +0 -0
  410. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/providers/test_proxmox_devcontainer_runtime.py +0 -0
  411. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/providers/test_proxmox_snapshot.py +0 -0
  412. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/test_ansible_templates.py +0 -0
  413. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/test_host_model.py +0 -0
  414. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/web/__init__.py +0 -0
  415. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/web/_pairing_support.py +0 -0
  416. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/web/conftest.py +0 -0
  417. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/web/test_backpressure.py +0 -0
  418. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/web/test_check.py +0 -0
  419. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/web/test_csp.py +0 -0
  420. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/web/test_discovery.py +0 -0
  421. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/web/test_health_states.py +0 -0
  422. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/web/test_hosts_mapping.py +0 -0
  423. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/web/test_lazy_import.py +0 -0
  424. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/web/test_log_redaction.py +0 -0
  425. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/web/test_mint_gating.py +0 -0
  426. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/web/test_operator_auth.py +0 -0
  427. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/web/test_pairing.py +0 -0
  428. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/web/test_pairing_origin.py +0 -0
  429. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/web/test_setup_api.py +0 -0
  430. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/web/test_setup_auth.py +0 -0
  431. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/web/test_setup_dormancy.py +0 -0
  432. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/web/test_state.py +0 -0
  433. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/web/test_terminal_resize.py +0 -0
  434. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/web/test_terminals_api.py +0 -0
  435. {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/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.0rc4
3
+ Version: 2.2.0rc6
4
4
  Summary: Remote development environment CLI
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -102,8 +102,12 @@ The SPA is a two-pane **web console**:
102
102
  row (or its `+` button) **adds** it to a responsive grid (1/2/3 columns by count). Clicking a grid
103
103
  tile solos it; **Esc** collapses the grid back to the focused terminal; number keys **1–9** jump to
104
104
  the numbered sessions (⌘ 1–9 add to the grid). Hidden terminals stay connected and keep their
105
- scrollback. Each terminal header shows `provider · instance · region`, connection state, and
106
- reconnect/close controls.
105
+ scrollback. Each terminal header shows `provider · instance · region`, connection state, and a
106
+ window-control cluster of the display modes — **▭ Normal** (single view), **⊞ Grid** (the grid, when
107
+ one is available), **⤢ Fullscreen** (the terminal fills the whole window — shell chrome hidden, plus
108
+ best-effort browser fullscreen), and **✕ Close** — with the current mode shown active. Press **f** to
109
+ toggle fullscreen on the focused terminal; **Esc** exits it. Fullscreen is a presentation overlay: it
110
+ never disturbs the single/grid layout underneath, so exiting returns to exactly where you were.
107
111
 
108
112
  **Session-row glyphs** (also shown in the rail legend):
109
113
 
@@ -101,11 +101,82 @@ interface ErrorEnvelope {
101
101
  error: TypedError;
102
102
  }
103
103
 
104
+ // ---- Forward-auth (SSO proxy) re-authentication ----
105
+ //
106
+ // When remo-web is deployed behind a whole-app forward-auth proxy (Traefik
107
+ // ForwardAuth + an OIDC IdP such as Authentik — e.g. a Hola app), an
108
+ // unauthenticated or expired-session request is answered with a 302 to the
109
+ // cross-origin IdP (`https://auth.example.com/application/o/authorize/...`). A
110
+ // same-origin `fetch()` cannot complete that SSO round-trip, and remo-web's
111
+ // strict `connect-src 'self'` CSP blocks following the redirect at all — so the
112
+ // only way to restore the session is a TOP-LEVEL navigation, which re-triggers
113
+ // the proxy's SSO flow (the browser CAN follow it through the IdP and back).
114
+ //
115
+ // `request()` uses `redirect: "manual"` so such a redirect surfaces as an opaque
116
+ // response (`response.type === "opaqueredirect"`, status 0) instead of throwing
117
+ // on the blocked cross-origin follow; we then reload the document to re-auth. A
118
+ // sessionStorage cooldown prevents a reload loop when auth genuinely can't
119
+ // complete. With no proxy (REMO_WEB_OPERATOR_AUTH=none) there are no redirects,
120
+ // so this path never fires.
121
+
122
+ const _REAUTH_KEY = "remo:last-reauth";
123
+ const _REAUTH_COOLDOWN_MS = 10_000;
124
+
125
+ /**
126
+ * Handle a forward-auth challenge on an XHR by re-authenticating through a
127
+ * top-level navigation. Never returns normally: it either navigates the whole
128
+ * document (throwing to halt the caller before the navigation lands) or, if we
129
+ * already tried to re-auth within the cooldown, throws a clear `auth_required`
130
+ * ApiError instead of looping.
131
+ */
132
+ function reauthenticate(): never {
133
+ let last = 0;
134
+ try {
135
+ last = Number(sessionStorage.getItem(_REAUTH_KEY) ?? 0) || 0;
136
+ } catch {
137
+ last = 0;
138
+ }
139
+ const now = Date.now();
140
+ if (now - last < _REAUTH_COOLDOWN_MS) {
141
+ // We just reloaded to re-auth and are being challenged again — the SSO
142
+ // round-trip isn't restoring a usable session. Stop reloading; surface a
143
+ // clear error rather than looping.
144
+ throw new ApiError({
145
+ code: "auth_required",
146
+ message: "Sign-in is required, but the access proxy did not restore a session.",
147
+ retryable: false,
148
+ remediation:
149
+ "Sign in through your access proxy and reload. If this repeats, the " +
150
+ "forward-auth proxy may be misconfigured (its session cookie is not reaching this app).",
151
+ });
152
+ }
153
+ try {
154
+ sessionStorage.setItem(_REAUTH_KEY, String(now));
155
+ } catch {
156
+ // sessionStorage unavailable — navigate anyway.
157
+ }
158
+ // A full document request re-runs the proxy's SSO redirect chain (IdP round
159
+ // trip), unlike a fetch which cannot. The SPA reloads authenticated and
160
+ // subsequent XHRs return 200.
161
+ window.location.assign(window.location.href);
162
+ // location.assign() schedules the navigation asynchronously and lets sync
163
+ // code keep running; throw so the caller never treats this as data.
164
+ throw new ApiError({
165
+ code: "auth_challenge",
166
+ message: "Re-authenticating…",
167
+ retryable: false,
168
+ remediation: "",
169
+ });
170
+ }
171
+
104
172
  async function request<T>(path: string, init?: RequestInit): Promise<T> {
105
173
  let response: Response;
106
174
  try {
107
175
  response = await fetch(path, {
108
176
  ...init,
177
+ // Catch a forward-auth proxy's cross-origin SSO redirect as an opaque
178
+ // response instead of a thrown CSP-blocked follow (see reauthenticate()).
179
+ redirect: "manual",
109
180
  headers: {
110
181
  "Content-Type": "application/json",
111
182
  ...(init?.headers ?? {}),
@@ -123,6 +194,14 @@ async function request<T>(path: string, init?: RequestInit): Promise<T> {
123
194
  });
124
195
  }
125
196
 
197
+ // A forward-auth session lapse: the proxy answered with a cross-origin 3xx,
198
+ // now an opaque redirect. Re-authenticate via a top-level navigation rather
199
+ // than surfacing a bogus "network_error"/CSP failure. The API itself never
200
+ // issues same-origin redirects, so this is unambiguously a proxy challenge.
201
+ if (response.type === "opaqueredirect") {
202
+ reauthenticate();
203
+ }
204
+
126
205
  if (!response.ok) {
127
206
  let envelope: ErrorEnvelope | undefined;
128
207
  try {
@@ -355,6 +434,12 @@ export async function closeTerminal(terminalId: string): Promise<void> {
355
434
  *
356
435
  * This function only constructs and returns the socket; connection-lifecycle
357
436
  * and control-frame handling live in `terminal/TerminalConnection.ts`.
437
+ *
438
+ * Forward-auth note: a raw WebSocket upgrade cannot itself distinguish a proxy
439
+ * SSO redirect/401 from an ordinary failure. It does not need to — every attach
440
+ * (and reconnect) first calls `createTerminal()`, which goes through
441
+ * `request()` and so triggers the top-level SSO re-auth (see `reauthenticate`)
442
+ * whenever the session has lapsed, before/at the point the socket is opened.
358
443
  */
359
444
  export function openTerminalSocket(terminalId: string, token: string): WebSocket {
360
445
  const wsProtocol = window.location.protocol === "https:" ? "wss:" : "ws:";
@@ -32,6 +32,13 @@
32
32
  display: flex;
33
33
  }
34
34
 
35
+ /* Fullscreen terminal: the shell collapses to just the pane. The rail/divider/
36
+ * backbar are already withheld from render (see AppShell); here we drop the top
37
+ * bar so the maximized terminal fills the whole viewport. */
38
+ .app-shell--maximized .topbar {
39
+ display: none;
40
+ }
41
+
35
42
  .app-rail {
36
43
  flex: none;
37
44
  flex-direction: column;
@@ -5,6 +5,7 @@
5
5
 
6
6
  import { useCallback, useEffect, useMemo, useState, type PointerEvent } from "react";
7
7
  import type { SessionTarget } from "../api/client";
8
+ import { exitBrowserFullscreen } from "../lib/fullscreen";
8
9
  import { useDiscovery } from "../state/discovery";
9
10
  import { useHealth } from "../state/health";
10
11
  import { settingsActions, useSettings } from "../state/settings";
@@ -42,6 +43,36 @@ export function AppShell(): JSX.Element {
42
43
  return () => window.removeEventListener("resize", onResize);
43
44
  }, []);
44
45
 
46
+ // A terminal is fullscreen when the overlay id still resolves to an attached
47
+ // card; that both hides the shell chrome (class below) and drives browser
48
+ // fullscreen. Entering browser fullscreen is requested from the click/keypress
49
+ // gesture (WorkspacePane / keyboard); here we own the two exit paths:
50
+ // (a) whenever the overlay clears through ANY path, leave browser fullscreen;
51
+ // (b) when the user leaves browser fullscreen natively (Esc/F11), clear the
52
+ // overlay so the shell chrome comes back.
53
+ const maximized =
54
+ workspace.maximizedId !== null && workspace.attached.includes(workspace.maximizedId);
55
+ const restore = workspace.restore;
56
+
57
+ useEffect(() => {
58
+ if (!maximized) {
59
+ exitBrowserFullscreen();
60
+ }
61
+ }, [maximized]);
62
+
63
+ useEffect(() => {
64
+ if (typeof document === "undefined") {
65
+ return undefined;
66
+ }
67
+ const onChange = (): void => {
68
+ if (!document.fullscreenElement && maximized) {
69
+ restore();
70
+ }
71
+ };
72
+ document.addEventListener("fullscreenchange", onChange);
73
+ return () => document.removeEventListener("fullscreenchange", onChange);
74
+ }, [maximized, restore]);
75
+
45
76
  const filters: RailFilters = useMemo(
46
77
  () => ({ search, providerFilter, sessionOnly }),
47
78
  [search, providerFilter, sessionOnly],
@@ -136,9 +167,11 @@ export function AppShell(): JSX.Element {
136
167
  );
137
168
 
138
169
  const paneHasContent = workspace.attached.length > 0;
139
- const railHidden = narrow ? paneHasContent : settings.railCollapsed;
170
+ // Fullscreen collapses the shell to just the maximized terminal: rail and
171
+ // divider gone, top bar hidden by the .app-shell--maximized class.
172
+ const railHidden = maximized || (narrow ? paneHasContent : settings.railCollapsed);
140
173
  const paneHidden = narrow && !paneHasContent;
141
- const showDivider = !narrow && !settings.railCollapsed;
174
+ const showDivider = !narrow && !settings.railCollapsed && !maximized;
142
175
 
143
176
  // "Loading" until the first discovery has produced instances — either before
144
177
  // the first poll (lastRefreshedAt null) or while a refresh is still in flight
@@ -151,7 +184,7 @@ export function AppShell(): JSX.Element {
151
184
  const noCredentials = health.checks.ssh_identity === "missing";
152
185
 
153
186
  return (
154
- <div className="app-shell">
187
+ <div className={`app-shell${maximized ? " app-shell--maximized" : ""}`}>
155
188
  <TopBar
156
189
  showRailToggle={!narrow}
157
190
  railCollapsed={settings.railCollapsed}
@@ -165,7 +198,7 @@ export function AppShell(): JSX.Element {
165
198
  onShortcuts={onToggleShortcuts}
166
199
  />
167
200
 
168
- {discovery.isRefreshing && (
201
+ {discovery.isRefreshing && !maximized && (
169
202
  <div className="app-refresh-bar">
170
203
  <div className="app-refresh-bar-fill" />
171
204
  </div>
@@ -203,7 +236,7 @@ export function AppShell(): JSX.Element {
203
236
  )}
204
237
 
205
238
  <div className="app-pane" style={{ display: paneHidden ? "none" : "flex" }}>
206
- {narrow && paneHasContent && (
239
+ {narrow && paneHasContent && !maximized && (
207
240
  <div className="app-backbar">
208
241
  <button
209
242
  type="button"
@@ -9,7 +9,8 @@ interface ShortcutsModalProps {
9
9
  const SHORTCUTS: { desc: string; key: string }[] = [
10
10
  { desc: "Open session 1–9 (single)", key: "1 – 9" },
11
11
  { desc: "Add session to grid", key: "⌘ 1–9" },
12
- { desc: "Collapse grid to focused", key: "esc" },
12
+ { desc: "Fullscreen focused terminal", key: "f" },
13
+ { desc: "Exit fullscreen / collapse grid", key: "esc" },
13
14
  { desc: "Show this panel", key: "?" },
14
15
  ];
15
16
 
@@ -141,11 +141,49 @@
141
141
  background: var(--bg-btn-hover);
142
142
  }
143
143
 
144
- .terminal-card--grid .tc-btn--close {
144
+ /* Window-control cluster: a tight row of square, icon-only mode buttons. */
145
+ .tc-winctl {
146
+ display: flex;
147
+ align-items: center;
148
+ gap: 5px;
149
+ }
150
+
151
+ .tc-btn--icon {
152
+ width: 26px;
153
+ height: 26px;
154
+ padding: 0;
155
+ display: inline-flex;
156
+ align-items: center;
157
+ justify-content: center;
158
+ font-size: 13px;
159
+ line-height: 1;
160
+ }
161
+
162
+ .terminal-card--grid .tc-btn--icon {
145
163
  width: 22px;
146
164
  height: 22px;
147
- padding: 0;
148
- font-size: 10px;
165
+ font-size: 11px;
166
+ }
167
+
168
+ /* Current display mode: highlighted (and disabled — you can't re-enter it). */
169
+ .tc-btn--active {
170
+ border-color: color-mix(in oklch, var(--accent) 55%, var(--border-strong));
171
+ background: color-mix(in oklch, var(--accent) 18%, transparent);
172
+ color: color-mix(in oklch, var(--accent) 82%, white);
173
+ }
174
+
175
+ /* Unavailable modes (e.g. Grid with no grid to show) read as inert. */
176
+ .tc-btn--icon:disabled {
177
+ cursor: default;
178
+ }
179
+ .tc-btn--icon:disabled:not(.tc-btn--active) {
180
+ opacity: 0.4;
181
+ }
182
+ .tc-btn--icon:disabled:hover {
183
+ background: var(--bg-btn);
184
+ }
185
+ .tc-btn--active:disabled:hover {
186
+ background: color-mix(in oklch, var(--accent) 18%, transparent);
149
187
  }
150
188
 
151
189
  .tc-btn--accent {
@@ -193,9 +231,14 @@
193
231
  padding: 8px 13px;
194
232
  font-size: 13px;
195
233
  }
196
- .terminal-card--grid .tc-btn--close {
197
- width: 32px;
198
- height: 32px;
199
- font-size: 13px;
234
+ .tc-btn--icon,
235
+ .terminal-card--grid .tc-btn--icon {
236
+ width: 34px;
237
+ height: 34px;
238
+ padding: 0;
239
+ font-size: 15px;
240
+ }
241
+ .tc-winctl {
242
+ gap: 7px;
200
243
  }
201
244
  }
@@ -38,6 +38,9 @@ const STATE_LABELS: Record<TerminalConnectionState, string> = {
38
38
  };
39
39
 
40
40
  export type TerminalCardMode = "single" | "grid";
41
+ /** Which of the mutually-exclusive display modes this card is currently in.
42
+ * Drives the window-control cluster's active/disabled state. */
43
+ export type TerminalViewState = "normal" | "grid" | "fullscreen";
41
44
 
42
45
  interface TerminalCardProps {
43
46
  target: SessionTarget;
@@ -50,11 +53,17 @@ interface TerminalCardProps {
50
53
  isFocused: boolean;
51
54
  /** 1-based position label shown on a grid tile. */
52
55
  num?: number;
56
+ /** The display mode this card is currently in (window-control cluster state). */
57
+ viewState: TerminalViewState;
53
58
  onClose: () => void;
54
59
  /** Grid tile clicked → solo it (single view). */
55
60
  onSolo?: () => void;
56
- /** Single view "Back to grid" omitted when there's no grid to return to. */
57
- onBackToGrid?: () => void;
61
+ /** Window-control "Normal": solo this terminal into the single view. */
62
+ onNormal: () => void;
63
+ /** Window-control "Grid": show the grid — omitted when none is available. */
64
+ onGrid?: () => void;
65
+ /** Window-control "Fullscreen": enter fullscreen, or exit if already in it. */
66
+ onToggleFullscreen: () => void;
58
67
  /** Called when the user clicks into the surface (focus this terminal). */
59
68
  onFocusRequest?: () => void;
60
69
  /** Called when output arrives while this card is hidden (rail activity dot). */
@@ -80,9 +89,12 @@ export function TerminalCard({
80
89
  isVisible,
81
90
  isFocused,
82
91
  num,
92
+ viewState,
83
93
  onClose,
84
94
  onSolo,
85
- onBackToGrid,
95
+ onNormal,
96
+ onGrid,
97
+ onToggleFullscreen,
86
98
  onFocusRequest,
87
99
  onActivity,
88
100
  onEnded,
@@ -298,44 +310,81 @@ export function TerminalCard({
298
310
  {STATE_LABELS[connectionState]}
299
311
  </span>
300
312
  <div className="terminal-card-controls">
301
- {mode === "single" && onBackToGrid && (
313
+ {needsManualReconnect && (
302
314
  <button
303
315
  type="button"
304
- className="tc-btn"
305
- title="Return to the grid you came from"
316
+ className="tc-btn tc-btn--accent"
317
+ data-testid={`terminal-reconnect-${target.id}`}
306
318
  onClick={(e) => {
307
319
  e.stopPropagation();
308
- onBackToGrid();
320
+ handleReconnect();
309
321
  }}
310
322
  >
311
- Grid
323
+ Reconnect
312
324
  </button>
313
325
  )}
314
- {needsManualReconnect && (
326
+ {/* Window-control cluster: mutually-exclusive display modes (the
327
+ * current one shown active + disabled) plus close. Fullscreen toggles.
328
+ * Icons only; the label is the tooltip. */}
329
+ <div className="tc-winctl" role="group" aria-label="Terminal display mode">
315
330
  <button
316
331
  type="button"
317
- className="tc-btn tc-btn--accent"
318
- data-testid={`terminal-reconnect-${target.id}`}
332
+ className={`tc-btn tc-btn--icon${viewState === "normal" ? " tc-btn--active" : ""}`}
333
+ data-testid={`terminal-normal-${target.id}`}
334
+ title="Normal (single view)"
335
+ aria-label="Normal view"
336
+ aria-pressed={viewState === "normal"}
337
+ disabled={viewState === "normal"}
319
338
  onClick={(e) => {
320
339
  e.stopPropagation();
321
- handleReconnect();
340
+ onNormal();
322
341
  }}
323
342
  >
324
- ↻ Reconnect
343
+
325
344
  </button>
326
- )}
327
- <button
328
- type="button"
329
- className="tc-btn tc-btn--close"
330
- data-testid={`terminal-close-${target.id}`}
331
- title="Close terminal — remote Zellij session stays alive"
332
- onClick={(e) => {
333
- e.stopPropagation();
334
- handleClose();
335
- }}
336
- >
337
- {mode === "grid" ? "✕" : "Close"}
338
- </button>
345
+ <button
346
+ type="button"
347
+ className={`tc-btn tc-btn--icon${viewState === "grid" ? " tc-btn--active" : ""}`}
348
+ data-testid={`terminal-grid-${target.id}`}
349
+ title="Grid view"
350
+ aria-label="Grid view"
351
+ aria-pressed={viewState === "grid"}
352
+ disabled={viewState === "grid" || !onGrid}
353
+ onClick={(e) => {
354
+ e.stopPropagation();
355
+ onGrid?.();
356
+ }}
357
+ >
358
+
359
+ </button>
360
+ <button
361
+ type="button"
362
+ className={`tc-btn tc-btn--icon${viewState === "fullscreen" ? " tc-btn--active" : ""}`}
363
+ data-testid={`terminal-fullscreen-${target.id}`}
364
+ title={viewState === "fullscreen" ? "Exit fullscreen" : "Fullscreen"}
365
+ aria-label={viewState === "fullscreen" ? "Exit fullscreen" : "Fullscreen"}
366
+ aria-pressed={viewState === "fullscreen"}
367
+ onClick={(e) => {
368
+ e.stopPropagation();
369
+ onToggleFullscreen();
370
+ }}
371
+ >
372
+ {viewState === "fullscreen" ? "⤡" : "⤢"}
373
+ </button>
374
+ <button
375
+ type="button"
376
+ className="tc-btn tc-btn--icon tc-btn--close"
377
+ data-testid={`terminal-close-${target.id}`}
378
+ title="Close terminal — remote Zellij session stays alive"
379
+ aria-label="Close terminal"
380
+ onClick={(e) => {
381
+ e.stopPropagation();
382
+ handleClose();
383
+ }}
384
+ >
385
+
386
+ </button>
387
+ </div>
339
388
  </div>
340
389
  </header>
341
390
 
@@ -4,6 +4,7 @@
4
4
 
5
5
  import { useMemo } from "react";
6
6
  import type { SessionTarget } from "../api/client";
7
+ import { requestBrowserFullscreen } from "../lib/fullscreen";
7
8
  import { useWorkspace } from "../state/workspace";
8
9
  import { TerminalCard } from "./TerminalCard";
9
10
  import "./WorkspacePane.css";
@@ -43,7 +44,7 @@ export function WorkspacePane({
43
44
  narrow,
44
45
  }: WorkspacePaneProps): JSX.Element {
45
46
  const workspace = useWorkspace();
46
- const { attached, visible, focusedId, prevGrid } = workspace;
47
+ const { attached, visible, focusedId, prevGrid, maximizedId } = workspace;
47
48
 
48
49
  // Only attached ids that still resolve to a live target get a card.
49
50
  const attachedTargets = useMemo(
@@ -70,18 +71,40 @@ export function WorkspacePane({
70
71
  );
71
72
  }
72
73
 
73
- const mode = visible.length <= 1 ? "single" : "grid";
74
+ // Fullscreen is an orthogonal overlay: it only takes effect when it still
75
+ // resolves to an attached card. When active, that one card fills the pane and
76
+ // the single↔grid layout underneath is left untouched (so exiting restores it).
77
+ const maximized =
78
+ maximizedId !== null && attached.includes(maximizedId) ? maximizedId : null;
79
+
80
+ // Cards render as single (full-bleed) while a card is maximized; otherwise the
81
+ // usual single↔grid split by how many are visible.
82
+ const mode = maximized || visible.length <= 1 ? "single" : "grid";
74
83
  const cols = gridColumns(visible.length, narrow);
75
84
  const rows = Math.max(1, Math.ceil(visible.length / cols));
76
- const canBackToGrid = (prevGrid ?? []).filter((id) => attached.includes(id)).length > 1;
85
+ // The Grid control is available when a grid can be shown — either the visible
86
+ // set is already a grid (fullscreen opened over one) or a grid was remembered.
87
+ const canGrid =
88
+ visible.filter((id) => attached.includes(id)).length > 1 ||
89
+ (prevGrid ?? []).filter((id) => attached.includes(id)).length > 1;
77
90
 
78
91
  // 1-based position for each visible id (grid tile number badge).
79
92
  const visibleIndex = new Map(visible.map((id, i) => [id, i + 1]));
80
93
 
94
+ const toggleFullscreen = (id: string): void => {
95
+ if (maximized === id) {
96
+ workspace.restore();
97
+ } else {
98
+ workspace.maximize(id);
99
+ // Request from within this click gesture so the browser allows it.
100
+ requestBrowserFullscreen();
101
+ }
102
+ };
103
+
81
104
  return (
82
105
  <main className="workspace" data-testid="workspace">
83
106
  <div
84
- className={`workspace-body workspace-body--${mode}`}
107
+ className={`workspace-body workspace-body--${mode}${maximized ? " workspace-body--maximized" : ""}`}
85
108
  style={
86
109
  mode === "grid"
87
110
  ? {
@@ -92,7 +115,8 @@ export function WorkspacePane({
92
115
  }
93
116
  >
94
117
  {attachedTargets.map((target) => {
95
- const isVisible = visible.includes(target.id);
118
+ const isVisible = maximized ? target.id === maximized : visible.includes(target.id);
119
+ const viewState = maximized === target.id ? "fullscreen" : mode === "grid" ? "grid" : "normal";
96
120
  return (
97
121
  <TerminalCard
98
122
  key={target.id}
@@ -102,9 +126,12 @@ export function WorkspacePane({
102
126
  isVisible={isVisible}
103
127
  isFocused={focusedId === target.id}
104
128
  num={visibleIndex.get(target.id)}
129
+ viewState={viewState}
105
130
  onClose={() => workspace.closeTerm(target.id)}
106
131
  onSolo={mode === "grid" ? () => workspace.soloTile(target.id) : undefined}
107
- onBackToGrid={mode === "single" && canBackToGrid ? workspace.backToGrid : undefined}
132
+ onNormal={() => workspace.soloTile(target.id)}
133
+ onGrid={canGrid ? workspace.backToGrid : undefined}
134
+ onToggleFullscreen={() => toggleFullscreen(target.id)}
108
135
  onFocusRequest={() => workspace.setFocused(target.id)}
109
136
  onActivity={() => workspace.markUnread(target.id)}
110
137
  onEnded={() => onTerminalEnded(target)}
@@ -0,0 +1,38 @@
1
+ // Best-effort browser (F11-style) fullscreen helpers.
2
+ //
3
+ // These are a thin, defensive wrapper over the Fullscreen API. The web-app's
4
+ // "fullscreen" terminal mode is primarily an *app-viewport* takeover (the shell
5
+ // hides its chrome via a CSS class keyed on `workspace.maximizedId`); requesting
6
+ // true browser fullscreen on top of that is a best-effort enhancement:
7
+ //
8
+ // - `requestBrowserFullscreen()` MUST be called from within a user-gesture
9
+ // handler (a click or keydown) — browsers reject it otherwise. On rejection
10
+ // the app-viewport takeover still stands, so the caller needs no fallback.
11
+ // - Exiting is centralized in AppShell (an effect keyed on `maximizedId`), so
12
+ // every exit path — the restore button, `f`, Esc, closing the card, or
13
+ // opening another session — funnels through `exitBrowserFullscreen()`.
14
+ //
15
+ // Both calls are idempotent: they guard on `document.fullscreenElement`, so
16
+ // double-invoking (e.g. our exit effect firing after a native Esc already left
17
+ // fullscreen) is a no-op and never loops.
18
+
19
+ export function requestBrowserFullscreen(): void {
20
+ if (typeof document === "undefined") {
21
+ return;
22
+ }
23
+ const el = document.documentElement;
24
+ if (!document.fullscreenElement && el.requestFullscreen) {
25
+ // Swallow rejections: no gesture / disallowed by the browser just means we
26
+ // stay in the app-viewport takeover.
27
+ void el.requestFullscreen().catch(() => {});
28
+ }
29
+ }
30
+
31
+ export function exitBrowserFullscreen(): void {
32
+ if (typeof document === "undefined") {
33
+ return;
34
+ }
35
+ if (document.fullscreenElement && document.exitFullscreen) {
36
+ void document.exitFullscreen().catch(() => {});
37
+ }
38
+ }
@@ -2,14 +2,16 @@
2
2
  //
3
3
  // 1–9 open that numbered session solo (single view)
4
4
  // ⌘/Ctrl/Shift 1–9 add/toggle that session in the grid
5
- // Esc close an open overlay, else collapse the grid to the
6
- // focused terminal
5
+ // f toggle fullscreen on the focused terminal
6
+ // Esc close an open overlay, else exit fullscreen, else collapse
7
+ // the grid to the focused terminal
7
8
  // ? toggle the shortcuts panel
8
9
  //
9
10
  // Ignored while typing in an <input>/<textarea> (e.g. the rail search).
10
11
 
11
12
  import { useEffect } from "react";
12
13
  import type { SessionTarget } from "../api/client";
14
+ import { requestBrowserFullscreen } from "../lib/fullscreen";
13
15
  import type { UseWorkspaceResult } from "./workspace";
14
16
 
15
17
  export interface ConsoleKeyboardConfig {
@@ -45,6 +47,11 @@ export function useConsoleKeyboard(config: ConsoleKeyboardConfig): void {
45
47
  if (onEscapeOverlay()) {
46
48
  return;
47
49
  }
50
+ // Exit fullscreen before touching the single/grid layout.
51
+ if (workspace.maximizedId) {
52
+ workspace.restore();
53
+ return;
54
+ }
48
55
  // Collapse a grid to just the focused terminal.
49
56
  if (workspace.visible.length > 1 && workspace.focusedId) {
50
57
  workspace.soloTile(workspace.focusedId);
@@ -52,6 +59,23 @@ export function useConsoleKeyboard(config: ConsoleKeyboardConfig): void {
52
59
  return;
53
60
  }
54
61
 
62
+ // Fullscreen the focused terminal (toggle). Requesting browser fullscreen
63
+ // here works because a keydown is a user gesture.
64
+ if (e.key === "f" || e.key === "F") {
65
+ const id = workspace.focusedId;
66
+ if (!id) {
67
+ return;
68
+ }
69
+ e.preventDefault();
70
+ if (workspace.maximizedId === id) {
71
+ workspace.restore();
72
+ } else {
73
+ workspace.maximize(id);
74
+ requestBrowserFullscreen();
75
+ }
76
+ return;
77
+ }
78
+
55
79
  if (/^[1-9]$/.test(e.key)) {
56
80
  const target = flatOpenable[Number(e.key) - 1];
57
81
  if (!target) {