{"id":50564545,"url":"https://github.com/christophevg/roomz","last_synced_at":"2026-06-04T13:30:38.804Z","repository":{"id":358200444,"uuid":"1238529506","full_name":"christophevg/roomz","owner":"christophevg","description":"A real-time chatroom web service with a plugin architecture for extensible functionality.","archived":false,"fork":false,"pushed_at":"2026-06-02T11:01:19.000Z","size":3029,"stargazers_count":0,"open_issues_count":3,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-06-02T11:22:32.887Z","etag":null,"topics":["chat","client","python","socketio"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/christophevg.png","metadata":{"files":{"readme":"README.md","changelog":"HISTORY.md","contributing":"docs/contributing.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-14T07:47:15.000Z","updated_at":"2026-06-02T10:39:17.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/christophevg/roomz","commit_stats":null,"previous_names":["christophevg/roomz"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/christophevg/roomz","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/christophevg%2Froomz","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/christophevg%2Froomz/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/christophevg%2Froomz/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/christophevg%2Froomz/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/christophevg","download_url":"https://codeload.github.com/christophevg/roomz/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/christophevg%2Froomz/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33907693,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-04T02:00:06.755Z","response_time":64,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["chat","client","python","socketio"],"created_at":"2026-06-04T13:30:38.734Z","updated_at":"2026-06-04T13:30:38.795Z","avatar_url":"https://github.com/christophevg.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Roomz\n\n[![PyPI](https://img.shields.io/pypi/v/roomz.svg)][pypi]\n[![Python](https://img.shields.io/pypi/pyversions/roomz.svg)][pypi]\n[![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)][uv]\n[![CI](https://img.shields.io/github/actions/workflow/status/christophevg/roomz/ci.yml.svg)][ci]\n[![Coverage](https://img.shields.io/coveralls/github/christophevg/roomz.svg)][coveralls]\n[![License](https://img.shields.io/github/license/christophevg/roomz.svg)][license]\n[![Agentic](https://img.shields.io/badge/workflow-agentic-blueviolet?style=flat-square)](https://christophe.vg/about/Agentic-Workflow)\n\nA real-time chatroom web service with magic link authentication.\n\n## What is Roomz?\n\nRoomz is a real-time chat application with secure magic link authentication. Built with modern async technology (Quart + SocketIO), it provides seamless real-time messaging with passwordless login.\n\n## Screenshots\n\n| Login | Magic Link | Chat | CLI |\n|-------|-------------|------|-----|\n| ![Login](https://raw.githubusercontent.com/christophevg/roomz/master/media/login.png) | ![Magic Link](https://raw.githubusercontent.com/christophevg/roomz/master/media/magic-link.png) | ![Chat](https://raw.githubusercontent.com/christophevg/roomz/master/media/chat.png) | ![CLI](https://raw.githubusercontent.com/christophevg/roomz/master/media/cli.png) |\n\n## Features\n\n- **Magic Link Authentication**: Passwordless login via email\n- **Instant Messaging**: Messages appear instantly across all connected users\n- **Real-time Updates**: See when users join or leave\n- **Display Names**: Set a custom display name shown as \"Name (email)\" in chat\n- **Responsive Design**: Works on desktop, tablet, and mobile\n- **Connection Status**: Visual indicator shows when disconnected\n- **Accessibility**: Keyboard navigation and screen reader support\n\n## Quick Start\n\n### Prerequisites\n\n- Python 3.10 or higher\n- [uv](https://docs.astral.sh/uv/) package manager\n\n### Installation\n\n```bash\n# Clone or navigate to the project\ncd /path/to/roomz\n\n# Install dependencies\nuv sync\n\n# Install dev dependencies (for testing)\nuv sync --extra dev\n```\n\n### Running the Application\n\n```bash\n# Start the chat server\nuv run gunicorn -k uvicorn.workers.UvicornWorker roomz.server:asgi_app\n\n# Or for development with auto-reload:\nuv run uvicorn roomz.server:asgi_app --reload --host 0.0.0.0 --port 8000\n```\n\nOpen [http://localhost:8000](http://localhost:8000) in your browser.\n\n### Configuration\n\nRoomz uses environment variables for configuration. Create a `.env` file in the project root:\n\n```bash\n# Required: JWT secret key (minimum 32 characters)\n# Generate with: python -c \"import secrets; print(secrets.token_urlsafe(32))\"\nJWT_SECRET_KEY=your-256-bit-secret-key-here\n\n# Required: Comma-separated list of allowed email addresses\nALLOWED_EMAILS=user@example.com,other@example.com\n\n# Email Configuration (choose one)\n# Development: Log magic links to console (default)\nEMAIL_SENDER=console\n\n# Production: Send emails via Resend\n# EMAIL_SENDER=resend\n# RESEND_API_KEY=re_your_api_key_here\n# EMAIL_FROM=noreply@yourdomain.com  # Optional, defaults to no-reply@example.com\n\n# Optional: JWT token expiration in days (default: 30)\nJWT_EXPIRY_DAYS=30\n\n# Optional: Magic link expiration in minutes (default: 15)\nMAGIC_LINK_EXPIRY_MINUTES=15\n\n# Optional: Rate limit for magic link requests per email per hour (default: 5)\nMAGIC_LINK_RATE_LIMIT=5\n```\n\n**Security Notes:**\n- `JWT_SECRET_KEY` must be at least 32 characters (256 bits)\n- `ALLOWED_EMAILS` controls who can authenticate\n- Removing an email from `ALLOWED_EMAILS` immediately revokes their access\n- Session cookies are httpOnly and SameSite=Strict\n\n**Email Setup:**\n- **Development**: Set `EMAIL_SENDER=console` (or leave unset). Magic links are logged to the server console.\n- **Production**: Set `EMAIL_SENDER=resend` and provide `RESEND_API_KEY`. Get your API key from [resend.com](https://resend.com).\n\n### Testing\n\n```bash\n# Run all tests\nuv run pytest tests/ -v\n\n# Run tests with coverage\nuv run pytest --cov=src/roomz --cov-report=term-missing\n\n# Run tests across Python versions\nuv run tox\n```\n\n## How to Use\n\n### Authentication\n\n1. Open the application in your browser\n2. Enter your email address\n3. Click \"Send Magic Link\"\n4. Check the server console for the magic link (development mode)\n5. Click the magic link to authenticate\n6. You're now in the chat!\n\n### Chatting\n\n1. After authentication, you see the chat interface\n2. Type a message in the input field at the bottom\n3. Press **Enter** or click the **Send** button\n4. Your message appears instantly to all connected users\n\n### Multiple Users\n\n1. Open the application in multiple browser tabs or windows\n2. Authenticate in each tab (can use same or different email)\n3. Type messages in any tab\n4. All tabs see the messages instantly\n5. System messages show when users join or leave\n\n## Python Client Library\n\nRoomz includes a Python client library for programmatic access to the chat service.\n\n### Using the CLI\n\nThe easiest way to use Roomz from the command line:\n\n```bash\n# Start the CLI\nuv run roomz-cli\n\n# Or with a custom server\nuv run roomz-cli --server http://your-server:8000\n```\n\n**Commands:**\n- `/login \u003cemail\u003e` - Request a magic link\n- `/token \u003ctoken\u003e` - Connect with magic link token\n- `/name \u003cname\u003e` - Set display name (shown as \"Name (email)\")\n- `/name` - Clear display name (show email only)\n- `/logout` - Disconnect and clear session\n- `/quit` - Exit the CLI\n\n**Features:**\n- Session caching (auto-reconnect on restart)\n- Split-screen TUI with message history\n- Color-coded messages (your messages in green)\n- Multiline support (Enter to send, Ctrl+Enter for new line)\n- Display names (set via `/name` command or config file)\n\n### Using the AsyncClient\n\nFor programmatic access in your Python applications:\n\n```python\nfrom roomz.client import AsyncClient\n\n# Create client with session caching\nclient = AsyncClient(\n  server_url=\"http://localhost:8000\",\n  session_cache_file=\"~/.roomz/session.json\"\n)\n\n# Register event handlers\nclient.on(\"message\", lambda data: print(f\"{data['user']['email']}: {data['content']}\"))\nclient.on(\"user_joined\", lambda data: print(f\"{data['user']['email']} joined\"))\n\n# Request magic link\nawait client.login(\"user@example.com\")\n\n# Connect with magic link token\nawait client.connect(token=\"magic-link-token\")\n\n# Or reconnect with cached session\nawait client.connect()\n\n# Send message\nresult = await client.send(\"Hello, world!\")\nif \"error\" in result:\n  print(f\"Failed: {result['error']}\")\n\n# Disconnect\nawait client.disconnect()\nclient.clear_cached_session()\n```\n\n### Using the SyncClient\n\nFor synchronous applications:\n\n```python\nfrom roomz.client import SyncClient\n\nwith SyncClient(server_url=\"http://localhost:8000\", session_token=\"token\") as client:\n  client.on(\"message\", lambda data: print(data['content']))\n  result = client.send(\"Hello!\")\n```\n\n### Session Caching\n\nThe client can cache session cookies for automatic reconnection:\n\n```python\n# Enable caching (recommended for CLI apps)\nclient = AsyncClient(\n  server_url=\"http://localhost:8000\",\n  session_cache_file=Path.home() / \".roomz\" / \"session.json\"\n)\n\n# Disable caching (default)\nclient = AsyncClient(server_url=\"http://localhost:8000\")\n```\n\n### Display Names\n\nSet a custom display name that appears in chat messages:\n\n**Python Client:**\n```python\n# Set display name\nresult = await client.set_display_name(\"Alice\")\n# Messages show as: \"Alice (alice@example.com)\"\n\n# Clear display name\nawait client.set_display_name(None)\n# Messages show as: \"alice@example.com\"\n```\n\n**Environment Variable:**\n```bash\nexport ROOMZ_DISPLAY_NAME=\"Alice\"\n```\n\n**Config File (`~/.roomz/config.toml`):**\n```toml\n[client]\ndisplay_name = \"Alice\"\n```\n\n**Format:** Messages display as `\"Display Name (email)\"` when set, or just `\"email\"` when not set.\n\n## Technology Stack\n\n| Layer | Technology |\n|-------|------------|\n| Backend | Quart (async Flask), SocketIO |\n| Frontend | Vue 3, Vuetify 4 |\n| Framework | Baseweb |\n| Runtime | Python 3.10+ |\n| Server | Gunicorn + Uvicorn |\n| Auth | Magic links with httpOnly cookies |\n\n## Architecture\n\n```\nBrowser (Vue 3 + Vuetify 4)\n    ↓ HTTP POST /auth/request-magic-link\n    Magic Link Email (or console in dev)\n    ↓ HTTP GET /auth/verify?token=...\n    JWT Cookie Set (httpOnly, SameSite=Strict)\n    ↓ WebSocket with JWT cookie auth\nQuart Server + SocketIO\n    ↓ JWT Validation + ALLOWED_EMAILS check\nConnected Users\n```\n\n**Stateless Authentication**: Sessions use JWT tokens, enabling server restarts without losing sessions. Access is controlled by the `ALLOWED_EMAILS` environment variable.\n\n## Project Structure\n\n```\nroomz/\n├── src/roomz/\n│   ├── server/             # Server application\n│   │   ├── __init__.py     # Quart app + SocketIO + Auth endpoints\n│   │   ├── auth.py         # Magic link and session management\n│   │   ├── models.py       # Session and magic link models\n│   │   ├── components/     # Vue components\n│   │   ├── pages/          # Page modules\n│   │   └── static/         # CSS styles\n│   ├── client/             # Python client library\n│   │   ├── async_client.py # AsyncClient implementation\n│   │   ├── sync_client.py  # SyncClient wrapper\n│   │   ├── events.py       # Event emitter\n│   │   └── exceptions.py   # Client exceptions\n│   └── cli/                # Command-line interface\n│       ├── app_tui.py      # Textual TUI application\n│       └── styles/         # TUI stylesheets\n├── tests/                  # Test suite\n├── analysis/               # Design documents\n├── reporting/              # Task reports\n├── pyproject.toml          # Project configuration\n└── README.md               # This file\n```\n\n## Development\n\nSee [TODO.md](TODO.md) for planned features and [REQUIREMENTS.md](REQUIREMENTS.md) for full requirements list.\n\n## License\n\nMIT License - See [LICENSE](LICENSE) for details.\n\n## Credits\n\nBuilt with:\n- [Baseweb](https://github.com/christophevg/baseweb) — Web framework\n- [Quart](https://pgjones.gitlab.io/quart/) — Async Flask\n- [Socket.IO](https://python-socketio.readthedocs.io/) — Real-time communication\n- [Vue 3](https://vuejs.org/) — Frontend framework\n- [Vuetify 4](https://vuetifyjs.com/) — Material Design components\n\n[pypi]: https://pypi.org/project/roomz/\n[uv]: https://docs.astral.sh/uv/\n[ci]: https://github.com/christophevg/roomz/actions\n[coveralls]: https://coveralls.io/github/christophevg/roomz\n[license]: https://github.com/christophevg/roomz/blob/main/LICENSE","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchristophevg%2Froomz","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchristophevg%2Froomz","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchristophevg%2Froomz/lists"}