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.
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/PKG-INFO +1 -1
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/docs/web-session-interface.md +6 -2
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/api/client.ts +85 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/AppShell.css +7 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/AppShell.tsx +38 -5
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/ShortcutsModal.tsx +2 -1
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/TerminalCard.css +50 -7
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/TerminalCard.tsx +75 -26
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/WorkspacePane.tsx +33 -6
- remo_cli-2.2.0rc6/frontend/src/lib/fullscreen.ts +38 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/state/useConsoleKeyboard.ts +26 -2
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/state/workspace.ts +58 -9
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/pyproject.toml +1 -1
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/uv.lock +1 -1
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.claude/skills/speckit-analyze/SKILL.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.claude/skills/speckit-checklist/SKILL.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.claude/skills/speckit-clarify/SKILL.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.claude/skills/speckit-constitution/SKILL.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.claude/skills/speckit-converge/SKILL.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.claude/skills/speckit-git-commit/SKILL.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.claude/skills/speckit-git-feature/SKILL.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.claude/skills/speckit-git-initialize/SKILL.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.claude/skills/speckit-git-remote/SKILL.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.claude/skills/speckit-git-validate/SKILL.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.claude/skills/speckit-implement/SKILL.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.claude/skills/speckit-plan/SKILL.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.claude/skills/speckit-specify/SKILL.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.claude/skills/speckit-tasks/SKILL.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.claude/skills/speckit-taskstoissues/SKILL.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.devcontainer/devcontainer.json +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.dockerignore +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.github/dependabot.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.github/workflows/ci.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.github/workflows/provision.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.github/workflows/release.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.github/workflows/smoke-test-cleanup.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.github/workflows/smoke-test.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.github/workflows/teardown.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.gitignore +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.maverick/checkpoints/feature/implementation_complete.json +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.maverick/checkpoints/feature/phase_${{ index }}_complete.json +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.maverick/checkpoints/feature/validation_complete.json +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/extensions/.registry +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/extensions/git/README.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/extensions/git/commands/speckit.git.commit.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/extensions/git/commands/speckit.git.feature.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/extensions/git/commands/speckit.git.initialize.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/extensions/git/commands/speckit.git.remote.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/extensions/git/commands/speckit.git.validate.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/extensions/git/config-template.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/extensions/git/extension.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/extensions/git/git-config.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/extensions/git/scripts/bash/auto-commit.sh +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/extensions/git/scripts/bash/create-new-feature.sh +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/extensions/git/scripts/bash/git-common.sh +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/extensions/git/scripts/bash/initialize-repo.sh +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/extensions/git/scripts/powershell/auto-commit.ps1 +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/extensions/git/scripts/powershell/create-new-feature.ps1 +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/extensions/git/scripts/powershell/git-common.ps1 +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/extensions/git/scripts/powershell/initialize-repo.ps1 +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/extensions.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/feature.json +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/init-options.json +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/integration.json +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/integrations/claude.manifest.json +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/integrations/speckit.manifest.json +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/memory/constitution.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/scripts/bash/check-prerequisites.sh +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/scripts/bash/common.sh +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/scripts/bash/create-new-feature.sh +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/scripts/bash/setup-plan.sh +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/scripts/bash/setup-tasks.sh +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/scripts/bash/update-agent-context.sh +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/templates/agent-file-template.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/templates/checklist-template.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/templates/constitution-template.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/templates/plan-template.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/templates/spec-template.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/templates/tasks-template.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/workflows/speckit/workflow.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/.specify/workflows/workflow-registry.json +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/AGENTS.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/CLAUDE.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/CONTRIBUTING.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/Dockerfile +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/LICENSE +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/README.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/.ansible-lint +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/README.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/ansible.cfg +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/aws_configure.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/aws_resize.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/aws_site.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/aws_teardown.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/group_vars/all.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/group_vars/incus_containers.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/group_vars/proxmox_containers.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/hetzner_configure.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/hetzner_provision.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/hetzner_resize.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/hetzner_site.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/hetzner_teardown.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/incus_bootstrap.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/incus_configure.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/incus_provision.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/incus_resize.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/incus_site.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/incus_teardown.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/inventory/hosts.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/inventory/incus_containers.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/proxmox_bootstrap.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/proxmox_configure.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/proxmox_provision.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/proxmox_resize.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/proxmox_site.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/proxmox_teardown.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/requirements.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/aws_server/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/aws_server/tasks/ebs.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/aws_server/tasks/ec2.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/aws_server/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/aws_server/tasks/patch_manager.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/aws_server/tasks/preflight.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/aws_server/tasks/security_group.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/deacon/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/deacon/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/devcontainers/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/docker/handlers/main.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/docker/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/fzf/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/github_cli/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/hetzner_server/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/hetzner_server/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/incus_bootstrap/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/incus_bootstrap/handlers/main.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/incus_bootstrap/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/incus_container/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/incus_container/handlers/main.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/incus_container/meta/main.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/incus_container/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/incus_container/tasks/preflight.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/incus_container_teardown/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/incus_container_teardown/meta/main.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/incus_container_teardown/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/nodejs/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/nodejs/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/proxmox_bootstrap/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/proxmox_bootstrap/meta/main.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/proxmox_bootstrap/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/proxmox_container/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/proxmox_container/handlers/main.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/proxmox_container/meta/main.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/proxmox_container/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/proxmox_container/tasks/preflight.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/proxmox_container_teardown/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/proxmox_container_teardown/meta/main.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/proxmox_container_teardown/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/user_setup/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/user_setup/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/user_setup/templates/devshell.sh.j2 +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/user_setup/templates/project-launch.sh.j2 +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/user_setup/templates/project-menu.sh.j2 +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/user_setup/templates/remo-host.sh.j2 +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/zellij/defaults/main.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/zellij/tasks/main.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/zellij/templates/config.kdl.j2 +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/roles/zellij/templates/layouts/minimal.kdl.j2 +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/ansible/tasks/configure_dev_tools.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/docker/Dockerfile +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/docker/compose.example.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/docker/entrypoint.sh +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/docs/aws.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/docs/hetzner.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/docs/incus.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/docs/install.sh +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/docs/proxmox.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/docs/remo-web-speckit-input.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/docs/remo-web.html +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/index.html +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/package-lock.json +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/package.json +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/public/README.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/public/dist__ghostty-vt.wasm +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/public/ghostty-vt.wasm +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/scripts/copy-ghostty-wasm.mjs +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/AppRoot.tsx +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/AwaitingAdoption.css +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/AwaitingAdoption.tsx +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/OfflineOverlay.css +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/OfflineOverlay.tsx +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/PairToSync.css +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/PairToSync.tsx +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/SessionRail.css +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/SessionRail.tsx +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/SettingsPage.css +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/SettingsPage.tsx +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/ShortcutsModal.css +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/TopBar.css +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/TopBar.tsx +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/WorkspacePane.css +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/providerMeta.ts +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/components/railModel.ts +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/lib/clipboard.ts +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/main.tsx +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/state/discovery.ts +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/state/fonts.ts +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/state/health.ts +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/state/settings.ts +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/terminal/.gitkeep +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/terminal/GhosttyRenderer.ts +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/terminal/RendererAdapter.ts +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/terminal/TerminalConnection.ts +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/terminal/XtermRenderer.ts +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/terminal/defaultRenderer.ts +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/theme/fonts.ts +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/src/theme/tokens.css +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/tsconfig.json +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/frontend/vite.config.ts +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/install.sh +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/maverick.yaml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/playwright.config.ts +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/project-menu.sh +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/remo +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/run.sh +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/scripts/setup-aws-oidc.sh +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/001-bootstrap-incus-host/checklists/requirements.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/001-bootstrap-incus-host/checklists/validation.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/001-bootstrap-incus-host/contracts/playbook-interface.yaml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/001-bootstrap-incus-host/contracts/role-interface.yaml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/001-bootstrap-incus-host/data-model.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/001-bootstrap-incus-host/plan.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/001-bootstrap-incus-host/quickstart.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/001-bootstrap-incus-host/research.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/001-bootstrap-incus-host/spec.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/001-bootstrap-incus-host/tasks.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/002-incus-container-support/checklists/requirements.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/002-incus-container-support/contracts/playbook-interface.yaml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/002-incus-container-support/contracts/role-interface.yaml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/002-incus-container-support/data-model.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/002-incus-container-support/plan.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/002-incus-container-support/quickstart.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/002-incus-container-support/research.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/002-incus-container-support/spec.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/002-incus-container-support/tasks.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/003-python-cli-rewrite/checklists/requirements.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/003-python-cli-rewrite/data-model.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/003-python-cli-rewrite/plan.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/003-python-cli-rewrite/quickstart.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/003-python-cli-rewrite/research.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/003-python-cli-rewrite/spec.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/003-python-cli-rewrite/tasks.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/004-proxmox-container-support/spec.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/005-provider-snapshots/checklists/requirements.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/005-provider-snapshots/contracts/cli-surface.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/005-provider-snapshots/data-model.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/005-provider-snapshots/plan.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/005-provider-snapshots/quickstart.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/005-provider-snapshots/research.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/005-provider-snapshots/spec.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/005-provider-snapshots/tasks.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/010-web-session-interface/checklists/requirements.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/010-web-session-interface/contracts/remo-host-protocol.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/010-web-session-interface/contracts/rest-api.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/010-web-session-interface/contracts/terminal-websocket.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/010-web-session-interface/data-model.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/010-web-session-interface/plan.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/010-web-session-interface/quickstart.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/010-web-session-interface/research.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/010-web-session-interface/spec.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/010-web-session-interface/tasks.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/011-web-adopt/checklists/requirements.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/011-web-adopt/contracts/cli-web-adopt.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/011-web-adopt/contracts/setup-api.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/011-web-adopt/data-model.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/011-web-adopt/plan.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/011-web-adopt/quickstart-results.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/011-web-adopt/quickstart.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/011-web-adopt/research.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/011-web-adopt/spec.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/011-web-adopt/tasks.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/012-web-adopt-pairing/contracts/cli-web-adopt.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/012-web-adopt-pairing/contracts/pairing-api.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/012-web-adopt-pairing/contracts/setup-api.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/012-web-adopt-pairing/data-model.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/012-web-adopt-pairing/plan.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/012-web-adopt-pairing/quickstart.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/012-web-adopt-pairing/research.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/012-web-adopt-pairing/spec.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/specs/012-web-adopt-pairing/tasks.md +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/__init__.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/__main__.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/cli/__init__.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/cli/cp.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/cli/main.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/cli/providers/__init__.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/cli/providers/aws.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/cli/providers/hetzner.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/cli/providers/incus.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/cli/providers/proxmox.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/cli/shell.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/cli/web.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/core/__init__.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/core/ansible_runner.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/core/completion.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/core/config.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/core/known_hosts.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/core/output.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/core/picker.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/core/remo_host_client.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/core/rsync.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/core/snapshot.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/core/ssh.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/core/validation.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/core/version.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/core/web_adopt.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/models/__init__.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/models/capability.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/models/discovery.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/models/host.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/models/session_target.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/models/snapshot.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/providers/__init__.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/providers/aws.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/providers/hetzner.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/providers/incus.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/providers/proxmox.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/web/__init__.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/web/api/__init__.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/web/api/hosts.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/web/api/pairing.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/web/api/setup.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/web/api/terminals.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/web/app.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/web/check.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/web/config.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/web/discovery.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/web/health.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/web/logging_config.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/web/models.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/web/operator_auth.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/web/pairing.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/web/ssh_master.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/web/state.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/web/terminal.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/web/terminal_registry.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/src/remo_cli/web/tokens.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/__init__.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/ansible/__init__.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/ansible/test_remo_host_idempotency.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/conftest.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/e2e/fixtures.ts +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/e2e/ghostty-shell-io.spec.ts +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/e2e/ghostty-tui-compatibility.spec.ts +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/e2e/keyboard-routing.spec.ts +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/e2e/mobile.spec.ts +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/e2e/reconnect.spec.ts +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/e2e/workspace-layout.spec.ts +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/image/__init__.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/image/test_docker_image.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/integration/__init__.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/integration/_nine_terminal_fixture.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/integration/orbstack-cloud-init.yml +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/integration/orbstack.sh +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/integration/test_nine_terminals.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/integration/test_nine_terminals_soak.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/integration/test_remo_host_e2e.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/integration/test_security_rejections.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/integration/test_terminal_attach.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/integration/test_web_adopt_e2e.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/integration/test_web_cli_parity.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/perf/__init__.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/perf/test_latency.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/__init__.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/cli/__init__.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/cli/providers/test_aws_snapshot.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/cli/providers/test_hetzner_snapshot.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/cli/providers/test_incus_snapshot.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/cli/providers/test_proxmox_snapshot.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/cli/test_cp.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/cli/test_main.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/cli/test_shell.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/cli/test_web_adopt_cmd.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/core/__init__.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/core/test_ansible_runner.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/core/test_config.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/core/test_known_hosts.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/core/test_output.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/core/test_picker.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/core/test_remo_host_client.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/core/test_rsync.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/core/test_snapshot.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/core/test_ssh.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/core/test_ssh_controlpath.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/core/test_ssh_identity_opts.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/core/test_validation.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/core/test_version.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/core/test_web_adopt_authorize.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/core/test_web_adopt_code.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/core/test_web_adopt_payload.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/core/test_web_adopt_trust.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/core/test_web_push.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/models/__init__.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/models/test_capability.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/models/test_discovery.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/models/test_session_target.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/providers/__init__.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/providers/test_aws_snapshot.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/providers/test_hetzner_snapshot.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/providers/test_incus_snapshot.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/providers/test_proxmox_devcontainer_runtime.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/providers/test_proxmox_snapshot.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/test_ansible_templates.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/test_host_model.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/web/__init__.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/web/_pairing_support.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/web/conftest.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/web/test_backpressure.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/web/test_check.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/web/test_csp.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/web/test_discovery.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/web/test_health_states.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/web/test_hosts_mapping.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/web/test_lazy_import.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/web/test_log_redaction.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/web/test_mint_gating.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/web/test_operator_auth.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/web/test_pairing.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/web/test_pairing_origin.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/web/test_setup_api.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/web/test_setup_auth.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/web/test_setup_dormancy.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/web/test_state.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/web/test_terminal_resize.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/web/test_terminals_api.py +0 -0
- {remo_cli-2.2.0rc4 → remo_cli-2.2.0rc6}/tests/unit/web/test_tokens.py +0 -0
|
@@ -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
|
-
|
|
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
|
-
|
|
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: "
|
|
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
|
-
|
|
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
|
-
|
|
148
|
-
|
|
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
|
-
.
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
-
/**
|
|
57
|
-
|
|
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
|
-
|
|
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
|
-
{
|
|
313
|
+
{needsManualReconnect && (
|
|
302
314
|
<button
|
|
303
315
|
type="button"
|
|
304
|
-
className="tc-btn"
|
|
305
|
-
|
|
316
|
+
className="tc-btn tc-btn--accent"
|
|
317
|
+
data-testid={`terminal-reconnect-${target.id}`}
|
|
306
318
|
onClick={(e) => {
|
|
307
319
|
e.stopPropagation();
|
|
308
|
-
|
|
320
|
+
handleReconnect();
|
|
309
321
|
}}
|
|
310
322
|
>
|
|
311
|
-
|
|
323
|
+
↻ Reconnect
|
|
312
324
|
</button>
|
|
313
325
|
)}
|
|
314
|
-
{
|
|
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=
|
|
318
|
-
data-testid={`terminal-
|
|
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
|
-
|
|
340
|
+
onNormal();
|
|
322
341
|
}}
|
|
323
342
|
>
|
|
324
|
-
|
|
343
|
+
▭
|
|
325
344
|
</button>
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
6
|
-
//
|
|
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) {
|