feat: add Docker watch mode for dynamic container sessions

- Add --docker-watch CLI flag to watch for containers with webterm-command label
- Containers with label 'auto' get bash exec, otherwise use label as command
- Dynamic dashboard updates via SSE when containers start/stop
- Add /tiles endpoint for JSON tile list
- Multi-stage Dockerfile for minimal production image
- Update README with docker-watch documentation

The docker watcher monitors Docker events and automatically:
- Adds terminal tiles when labeled containers start
- Removes tiles when containers stop
- Notifies dashboard via SSE for live updates
This commit is contained in:
GitHub Copilot
2026-01-28 12:45:02 +00:00
parent 0fad9e7353
commit 216380405a
16 changed files with 957 additions and 153 deletions
@@ -22,7 +22,9 @@ async def _make_client(server: LocalServer) -> TestClient:
@pytest.mark.asyncio
async def test_websocket_creates_session_on_resize(tmp_path):
config = Config(apps=[App(name="Test", slug="test", path=".", command="echo test", terminal=True)])
config = Config(
apps=[App(name="Test", slug="test", path=".", command="echo test", terminal=True)]
)
config_file = tmp_path / "config.toml"
config_file.write_text("")
server = LocalServer(config_path=str(config_file), config=config)
@@ -78,10 +80,11 @@ async def test_websocket_creates_session_on_resize(tmp_path):
assert called["stdin"] == 1
@pytest.mark.asyncio
async def test_websocket_ping_pong(tmp_path):
config = Config(apps=[App(name="Test", slug="test", path=".", command="echo test", terminal=True)])
config = Config(
apps=[App(name="Test", slug="test", path=".", command="echo test", terminal=True)]
)
config_file = tmp_path / "config.toml"
config_file.write_text("")
server = LocalServer(config_path=str(config_file), config=config)
@@ -102,7 +105,9 @@ async def test_websocket_ping_pong(tmp_path):
@pytest.mark.asyncio
async def test_websocket_ignores_invalid_envelopes(tmp_path):
config = Config(apps=[App(name="Test", slug="test", path=".", command="echo test", terminal=True)])
config = Config(
apps=[App(name="Test", slug="test", path=".", command="echo test", terminal=True)]
)
config_file = tmp_path / "config.toml"
config_file.write_text("")
server = LocalServer(config_path=str(config_file), config=config)