trmsg 1.0.0__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 (38) hide show
  1. trmsg-1.0.0/LICENSE +5 -0
  2. trmsg-1.0.0/PKG-INFO +188 -0
  3. trmsg-1.0.0/README.md +144 -0
  4. trmsg-1.0.0/cli/__init__.py +0 -0
  5. trmsg-1.0.0/cli/commands/__init__.py +0 -0
  6. trmsg-1.0.0/cli/commands/auth.py +85 -0
  7. trmsg-1.0.0/cli/commands/social.py +111 -0
  8. trmsg-1.0.0/cli/config.py +83 -0
  9. trmsg-1.0.0/cli/games/__init__.py +0 -0
  10. trmsg-1.0.0/cli/main.py +112 -0
  11. trmsg-1.0.0/cli/network/__init__.py +0 -0
  12. trmsg-1.0.0/cli/network/client.py +94 -0
  13. trmsg-1.0.0/cli/ui/__init__.py +0 -0
  14. trmsg-1.0.0/cli/ui/chat_ui.py +860 -0
  15. trmsg-1.0.0/cli/ui/theme.py +89 -0
  16. trmsg-1.0.0/pyproject.toml +57 -0
  17. trmsg-1.0.0/server/__init__.py +0 -0
  18. trmsg-1.0.0/server/ai/__init__.py +0 -0
  19. trmsg-1.0.0/server/ai/gemini.py +66 -0
  20. trmsg-1.0.0/server/api/__init__.py +0 -0
  21. trmsg-1.0.0/server/api/endpoints.py +700 -0
  22. trmsg-1.0.0/server/auth/__init__.py +0 -0
  23. trmsg-1.0.0/server/auth/auth.py +55 -0
  24. trmsg-1.0.0/server/config.py +29 -0
  25. trmsg-1.0.0/server/database/__init__.py +0 -0
  26. trmsg-1.0.0/server/database/db.py +276 -0
  27. trmsg-1.0.0/server/games/__init__.py +0 -0
  28. trmsg-1.0.0/server/games/engine.py +203 -0
  29. trmsg-1.0.0/server/main.py +35 -0
  30. trmsg-1.0.0/server/websocket/__init__.py +0 -0
  31. trmsg-1.0.0/server/websocket/manager.py +643 -0
  32. trmsg-1.0.0/setup.cfg +4 -0
  33. trmsg-1.0.0/trmsg.egg-info/PKG-INFO +188 -0
  34. trmsg-1.0.0/trmsg.egg-info/SOURCES.txt +36 -0
  35. trmsg-1.0.0/trmsg.egg-info/dependency_links.txt +1 -0
  36. trmsg-1.0.0/trmsg.egg-info/entry_points.txt +3 -0
  37. trmsg-1.0.0/trmsg.egg-info/requires.txt +16 -0
  38. trmsg-1.0.0/trmsg.egg-info/top_level.txt +2 -0
trmsg-1.0.0/LICENSE ADDED
@@ -0,0 +1,5 @@
1
+ MIT License
2
+ Copyright (c) 2025 trmsg Contributors
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
5
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED.
trmsg-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,188 @@
1
+ Metadata-Version: 2.4
2
+ Name: trmsg
3
+ Version: 1.0.0
4
+ Summary: Terminal messaging platform — chat, share files, play games, all from your CLI
5
+ Author: trmsg Contributors
6
+ License: MIT License
7
+ Copyright (c) 2025 trmsg Contributors
8
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
9
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED.
11
+
12
+ Project-URL: Homepage, https://github.com/yourusername/trmsg
13
+ Project-URL: Repository, https://github.com/yourusername/trmsg
14
+ Keywords: chat,terminal,cli,messaging,websocket,real-time,games,ai
15
+ Classifier: Development Status :: 4 - Beta
16
+ Classifier: Environment :: Console
17
+ Classifier: Intended Audience :: End Users/Desktop
18
+ Classifier: License :: OSI Approved :: MIT License
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Topic :: Communications :: Chat
24
+ Classifier: Topic :: Terminals
25
+ Requires-Python: >=3.10
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Requires-Dist: click>=8.1.7
29
+ Requires-Dist: rich>=13.7.0
30
+ Requires-Dist: websockets>=12.0
31
+ Requires-Dist: httpx>=0.27.0
32
+ Requires-Dist: python-jose>=3.3.0
33
+ Requires-Dist: pydantic>=2.6.0
34
+ Provides-Extra: server
35
+ Requires-Dist: fastapi>=0.110.0; extra == "server"
36
+ Requires-Dist: uvicorn[standard]>=0.29.0; extra == "server"
37
+ Requires-Dist: sqlalchemy[asyncio]>=2.0.28; extra == "server"
38
+ Requires-Dist: aiosqlite>=0.20.0; extra == "server"
39
+ Requires-Dist: bcrypt>=4.1.2; extra == "server"
40
+ Requires-Dist: python-multipart>=0.0.9; extra == "server"
41
+ Requires-Dist: pydantic-settings>=2.2.1; extra == "server"
42
+ Requires-Dist: python-jose>=3.3.0; extra == "server"
43
+ Dynamic: license-file
44
+
45
+ # ⚡ trmsg
46
+
47
+ **Terminal messaging. Chat. Share files. Play games. All from your CLI.**
48
+
49
+ ```bash
50
+ pip install trmsg
51
+ ```
52
+
53
+ ---
54
+
55
+ ## Features
56
+
57
+ | Feature | Command |
58
+ |---|---|
59
+ | đŸ’Ŧ Real-time chat | `trmsg chat` |
60
+ | 📁 File sharing (200MB) | `/sendfile photo.jpg` |
61
+ | đŸ‘Ĩ Friends & DMs | `/add username` |
62
+ | 🤖 AI Assistant (Gemini) | `/ai what is python?` |
63
+ | đŸ’Ŗ Self-destruct messages | `/burn 30 secret message` |
64
+ | 🎮 TicTacToe | `/game ttt opponent` |
65
+ | â™Ÿī¸ Chess | `/game chess opponent` |
66
+ | 🧠 Quiz Battle | `/game quiz` |
67
+ | 🏆 Leaderboard | `/leaderboard` |
68
+ | 📊 Polls | `/poll "Q?" A \| B \| C` |
69
+ | 😀 Reactions | `/react 5 👍` |
70
+ | â†Šī¸ Reply to messages | `/reply 5 good point!` |
71
+ | 🔔 Keyword alerts | `/alert homework` |
72
+ | 🌙 Do Not Disturb | `/dnd 11pm-7am` |
73
+ | 🎨 Themes | `/theme matrix` |
74
+ | 🔗 Invite links | `/invite create` |
75
+ | 📋 Message search | `/search-msg keyword` |
76
+ | đŸ’ģ Code sharing | `/code python print("hi")` |
77
+ | đŸ“Ŗ Announcements | `/announce Important update!` |
78
+ | 👑 Roles | Owner / Admin / VIP / Member |
79
+
80
+ ---
81
+
82
+ ## Quick Start
83
+
84
+ ```bash
85
+ # Install
86
+ pip install trmsg
87
+
88
+ # Connect to a server
89
+ trmsg config
90
+
91
+ # Register
92
+ trmsg register
93
+
94
+ # Chat!
95
+ trmsg chat
96
+ ```
97
+
98
+ ---
99
+
100
+ ## Self-Host Server
101
+
102
+ ```bash
103
+ pip install "trmsg[server]"
104
+
105
+ # Setup
106
+ echo "SECRET_KEY=$(python3 -c 'import secrets; print(secrets.token_hex(32))')" > .env
107
+ echo "DATABASE_URL=sqlite+aiosqlite:///./trmsg.db" >> .env
108
+ echo "GEMINI_API_KEY=your-key-here" >> .env # optional AI
109
+ mkdir uploads
110
+
111
+ # Start
112
+ trmsg-server
113
+ ```
114
+
115
+ Friends connect: `trmsg config` → enter your IP → `trmsg register` → `trmsg chat`
116
+
117
+ ---
118
+
119
+ ## AI Commands
120
+
121
+ ```bash
122
+ /ai what is async/await?
123
+ /ai summarize # summarize last 50 messages
124
+ /ai translate Japanese hello # translate text
125
+ /ai explain def foo(): pass # explain code
126
+ /ai roast janak # friendly roast 😈
127
+ ```
128
+
129
+ Powered by **Gemini 1.5 Flash** (free). Add `GEMINI_API_KEY` to server `.env`.
130
+
131
+ ---
132
+
133
+ ## Game Commands
134
+
135
+ ```bash
136
+ /game ttt rohan # TicTacToe vs rohan
137
+ /game chess priya # Chess vs priya
138
+ /game quiz # Quiz battle (room plays together)
139
+
140
+ /move <id> 5 # TTT: pick cell 1-9
141
+ /move <id> e2e4 # Chess: algebraic notation
142
+ /answer B # Quiz: answer A/B/C/D
143
+
144
+ /leaderboard # Overall rankings
145
+ /leaderboard ttt # Game-specific rankings
146
+ /mystats # Your personal stats
147
+ ```
148
+
149
+ ---
150
+
151
+ ## All CLI Commands
152
+
153
+ ```bash
154
+ trmsg register trmsg login trmsg logout
155
+ trmsg chat trmsg chat <user> (DM someone)
156
+ trmsg friends trmsg users trmsg rooms
157
+ trmsg add <user> trmsg whois <user> trmsg stats
158
+ trmsg leaderboard trmsg profile trmsg config
159
+ ```
160
+
161
+ ---
162
+
163
+ ## Themes
164
+
165
+ ```bash
166
+ /theme cyberpunk # neon green (default)
167
+ /theme matrix # matrix falling code style
168
+ /theme ocean # cool blue tones
169
+ /theme sunset # warm red/yellow
170
+ /theme hacker # deep green hacker
171
+ /theme minimal # clean white
172
+ ```
173
+
174
+ ---
175
+
176
+ ## Deploy to Render (Free)
177
+
178
+ 1. Push to GitHub
179
+ 2. Render → New Web Service → connect repo
180
+ 3. Build: `pip install ".[server]"`
181
+ 4. Start: `trmsg-server`
182
+ 5. Env vars: `SECRET_KEY`, `DATABASE_URL`, `GEMINI_API_KEY`
183
+
184
+ Share URL → friends install trmsg → connect → chat! 🎉
185
+
186
+ ---
187
+
188
+ *Built with FastAPI ¡ SQLAlchemy ¡ WebSockets ¡ Rich ¡ Click ¡ Gemini AI*
trmsg-1.0.0/README.md ADDED
@@ -0,0 +1,144 @@
1
+ # ⚡ trmsg
2
+
3
+ **Terminal messaging. Chat. Share files. Play games. All from your CLI.**
4
+
5
+ ```bash
6
+ pip install trmsg
7
+ ```
8
+
9
+ ---
10
+
11
+ ## Features
12
+
13
+ | Feature | Command |
14
+ |---|---|
15
+ | đŸ’Ŧ Real-time chat | `trmsg chat` |
16
+ | 📁 File sharing (200MB) | `/sendfile photo.jpg` |
17
+ | đŸ‘Ĩ Friends & DMs | `/add username` |
18
+ | 🤖 AI Assistant (Gemini) | `/ai what is python?` |
19
+ | đŸ’Ŗ Self-destruct messages | `/burn 30 secret message` |
20
+ | 🎮 TicTacToe | `/game ttt opponent` |
21
+ | â™Ÿī¸ Chess | `/game chess opponent` |
22
+ | 🧠 Quiz Battle | `/game quiz` |
23
+ | 🏆 Leaderboard | `/leaderboard` |
24
+ | 📊 Polls | `/poll "Q?" A \| B \| C` |
25
+ | 😀 Reactions | `/react 5 👍` |
26
+ | â†Šī¸ Reply to messages | `/reply 5 good point!` |
27
+ | 🔔 Keyword alerts | `/alert homework` |
28
+ | 🌙 Do Not Disturb | `/dnd 11pm-7am` |
29
+ | 🎨 Themes | `/theme matrix` |
30
+ | 🔗 Invite links | `/invite create` |
31
+ | 📋 Message search | `/search-msg keyword` |
32
+ | đŸ’ģ Code sharing | `/code python print("hi")` |
33
+ | đŸ“Ŗ Announcements | `/announce Important update!` |
34
+ | 👑 Roles | Owner / Admin / VIP / Member |
35
+
36
+ ---
37
+
38
+ ## Quick Start
39
+
40
+ ```bash
41
+ # Install
42
+ pip install trmsg
43
+
44
+ # Connect to a server
45
+ trmsg config
46
+
47
+ # Register
48
+ trmsg register
49
+
50
+ # Chat!
51
+ trmsg chat
52
+ ```
53
+
54
+ ---
55
+
56
+ ## Self-Host Server
57
+
58
+ ```bash
59
+ pip install "trmsg[server]"
60
+
61
+ # Setup
62
+ echo "SECRET_KEY=$(python3 -c 'import secrets; print(secrets.token_hex(32))')" > .env
63
+ echo "DATABASE_URL=sqlite+aiosqlite:///./trmsg.db" >> .env
64
+ echo "GEMINI_API_KEY=your-key-here" >> .env # optional AI
65
+ mkdir uploads
66
+
67
+ # Start
68
+ trmsg-server
69
+ ```
70
+
71
+ Friends connect: `trmsg config` → enter your IP → `trmsg register` → `trmsg chat`
72
+
73
+ ---
74
+
75
+ ## AI Commands
76
+
77
+ ```bash
78
+ /ai what is async/await?
79
+ /ai summarize # summarize last 50 messages
80
+ /ai translate Japanese hello # translate text
81
+ /ai explain def foo(): pass # explain code
82
+ /ai roast janak # friendly roast 😈
83
+ ```
84
+
85
+ Powered by **Gemini 1.5 Flash** (free). Add `GEMINI_API_KEY` to server `.env`.
86
+
87
+ ---
88
+
89
+ ## Game Commands
90
+
91
+ ```bash
92
+ /game ttt rohan # TicTacToe vs rohan
93
+ /game chess priya # Chess vs priya
94
+ /game quiz # Quiz battle (room plays together)
95
+
96
+ /move <id> 5 # TTT: pick cell 1-9
97
+ /move <id> e2e4 # Chess: algebraic notation
98
+ /answer B # Quiz: answer A/B/C/D
99
+
100
+ /leaderboard # Overall rankings
101
+ /leaderboard ttt # Game-specific rankings
102
+ /mystats # Your personal stats
103
+ ```
104
+
105
+ ---
106
+
107
+ ## All CLI Commands
108
+
109
+ ```bash
110
+ trmsg register trmsg login trmsg logout
111
+ trmsg chat trmsg chat <user> (DM someone)
112
+ trmsg friends trmsg users trmsg rooms
113
+ trmsg add <user> trmsg whois <user> trmsg stats
114
+ trmsg leaderboard trmsg profile trmsg config
115
+ ```
116
+
117
+ ---
118
+
119
+ ## Themes
120
+
121
+ ```bash
122
+ /theme cyberpunk # neon green (default)
123
+ /theme matrix # matrix falling code style
124
+ /theme ocean # cool blue tones
125
+ /theme sunset # warm red/yellow
126
+ /theme hacker # deep green hacker
127
+ /theme minimal # clean white
128
+ ```
129
+
130
+ ---
131
+
132
+ ## Deploy to Render (Free)
133
+
134
+ 1. Push to GitHub
135
+ 2. Render → New Web Service → connect repo
136
+ 3. Build: `pip install ".[server]"`
137
+ 4. Start: `trmsg-server`
138
+ 5. Env vars: `SECRET_KEY`, `DATABASE_URL`, `GEMINI_API_KEY`
139
+
140
+ Share URL → friends install trmsg → connect → chat! 🎉
141
+
142
+ ---
143
+
144
+ *Built with FastAPI ¡ SQLAlchemy ¡ WebSockets ¡ Rich ¡ Click ¡ Gemini AI*
File without changes
File without changes
@@ -0,0 +1,85 @@
1
+ """trmsg - Auth Commands"""
2
+ from rich.prompt import Prompt, Confirm
3
+ from rich.panel import Panel
4
+ from cli.config import config
5
+ from cli.network.client import APIClient, APIError
6
+ from cli.ui.theme import print_banner, print_error, print_success, print_info, console
7
+
8
+ async def register_command():
9
+ print_banner(config.theme)
10
+ console.print(Panel.fit("[bold bright_green]Create Account[/bold bright_green]", border_style="bright_green"))
11
+ api = APIClient()
12
+ try:
13
+ username = Prompt.ask("[cyan]Username[/cyan]").strip()
14
+ password = Prompt.ask("[cyan]Password[/cyan]", password=True)
15
+ confirm = Prompt.ask("[cyan]Confirm password[/cyan]", password=True)
16
+ if password != confirm: print_error("Passwords don't match!"); return
17
+ if len(password) < 8: print_error("Password must be 8+ characters!"); return
18
+ email = Prompt.ask("[cyan]Email[/cyan] [dim](optional)[/dim]", default="").strip() or None
19
+ display_name = Prompt.ask("[cyan]Display name[/cyan]", default=username).strip()
20
+ with console.status("[green]Creating account...[/green]"):
21
+ result = await api.post("/api/v1/users/register", {"username":username,"password":password,"email":email,"display_name":display_name})
22
+ config.token = result["token"]; config.username = result["username"]; config.avatar_color = result.get("avatar_color","#00ff88")
23
+ print_success(f"Welcome to trmsg, [bold]{result['username']}[/bold]! ⚡")
24
+ print_info("Run [bold]trmsg chat[/bold] to start!")
25
+ except APIError as e: print_error(f"Registration failed: {e.message}")
26
+ finally: await api.close()
27
+
28
+ async def login_command():
29
+ print_banner(config.theme)
30
+ if config.is_authenticated():
31
+ print_info(f"Already logged in as [bold]{config.username}[/bold]")
32
+ if not Confirm.ask("Login as different user?"): return
33
+ api = APIClient()
34
+ try:
35
+ username = Prompt.ask("[cyan]Username[/cyan]").strip()
36
+ password = Prompt.ask("[cyan]Password[/cyan]", password=True)
37
+ with console.status("[green]Logging in...[/green]"):
38
+ result = await api.post("/api/v1/users/login", {"username":username,"password":password})
39
+ config.token = result["token"]; config.username = result["username"]
40
+ config.avatar_color = result.get("avatar_color","#00ff88")
41
+ if result.get("theme"): config.theme = result["theme"]
42
+ print_success(f"Welcome back, [bold]{result.get('display_name',username)}[/bold]! ⚡")
43
+ print_info("Run [bold]trmsg chat[/bold] to start!")
44
+ except APIError as e: print_error(f"Login failed: {e.message}")
45
+ finally: await api.close()
46
+
47
+ def logout_command():
48
+ if not config.is_authenticated(): print_info("Not logged in."); return
49
+ username = config.username; config.clear_auth()
50
+ print_success(f"Logged out [bold]{username}[/bold]. Goodbye! 👋")
51
+
52
+ async def profile_command():
53
+ api = APIClient()
54
+ try:
55
+ me = await api.get("/api/v1/users/me")
56
+ s = await api.get(f"/api/v1/users/{me['username']}/stats")
57
+ console.print(f"\n[bold cyan]Your Profile[/bold cyan]")
58
+ console.print(f" Username: [bold]{me['username']}[/bold]")
59
+ console.print(f" Display name: {me.get('display_name','—')}")
60
+ console.print(f" Bio: {me.get('bio','—')}")
61
+ console.print(f" Status: {me.get('status','offline')}")
62
+ console.print(f" Role: {me.get('role','member')}")
63
+ console.print(f" Score: 🏆 {s.get('score',0)} (Rank #{s.get('rank','?')})")
64
+ console.print(f" Messages: {s.get('total_messages',0)}")
65
+ console.print(f" Theme: {me.get('theme','cyberpunk')}")
66
+ console.print(f" Server: {config.server_url}\n")
67
+ if Confirm.ask("Edit profile?"):
68
+ display_name = Prompt.ask("Display name", default=me.get("display_name","")).strip()
69
+ bio = Prompt.ask("Bio", default=me.get("bio","")).strip()
70
+ with console.status("[green]Updating...[/green]"):
71
+ await api.patch("/api/v1/users/me", {"display_name":display_name,"bio":bio})
72
+ print_success("Profile updated!")
73
+ except APIError as e: print_error(f"Failed: {e.message}")
74
+ finally: await api.close()
75
+
76
+ def configure_command():
77
+ console.print("\n[bold cyan]trmsg Configuration[/bold cyan]\n")
78
+ server = Prompt.ask("[cyan]Server URL[/cyan]", default=config.server_url)
79
+ config.server_url = server.rstrip("/")
80
+ from cli.ui.theme import THEMES
81
+ theme = Prompt.ask("[cyan]Theme[/cyan]", default=config.theme, choices=list(THEMES.keys()))
82
+ config.theme = theme
83
+ dl_dir = Prompt.ask("[cyan]Download directory[/cyan]", default=str(config.download_dir))
84
+ config.download_dir = dl_dir
85
+ print_success("Configuration saved!")
@@ -0,0 +1,111 @@
1
+ """trmsg - Social Commands"""
2
+ from rich.table import Table
3
+ from cli.network.client import APIClient, APIError
4
+ from cli.ui.theme import print_error, print_success, print_info, get_status_icon, get_role_badge, format_timestamp, console
5
+
6
+ async def cmd_add(username):
7
+ api = APIClient()
8
+ try:
9
+ with console.status(f"[green]Sending to {username}...[/green]"):
10
+ await api.post(f"/api/v1/friends/add/{username}")
11
+ print_success(f"Friend request sent to [bold]{username}[/bold] 📨")
12
+ except APIError as e: print_error(f"Failed: {e.message}")
13
+ finally: await api.close()
14
+
15
+ async def cmd_friends():
16
+ api = APIClient()
17
+ try:
18
+ with console.status("[green]Loading...[/green]"): r = await api.get("/api/v1/friends/list")
19
+ friends = r.get("friends",[])
20
+ if not friends: print_info("No friends yet. Use [bold]trmsg add <username>[/bold]"); return
21
+ t = Table(title="đŸ‘Ĩ Friends",border_style="bright_black",header_style="bold cyan")
22
+ t.add_column("",width=3); t.add_column("Username",style="bright_cyan"); t.add_column("Display Name"); t.add_column("Status"); t.add_column("Last Seen",style="dim")
23
+ for f in friends:
24
+ icon = get_status_icon(f.get("status","offline"))
25
+ t.add_row(icon, f["username"], f.get("display_name",""), f.get("status_message","") or "", format_timestamp(f.get("last_seen","")) if f.get("last_seen") else "—")
26
+ console.print(t)
27
+ except APIError as e: print_error(f"Failed: {e.message}")
28
+ finally: await api.close()
29
+
30
+ async def cmd_users():
31
+ api = APIClient()
32
+ try:
33
+ with console.status("[green]Fetching...[/green]"): r = await api.get("/api/v1/users/online")
34
+ users = r.get("users",[]); count = r.get("count",0)
35
+ if not users: print_info("No users online."); return
36
+ t = Table(title=f"đŸŸĸ Online ({count})",border_style="green",header_style="bold green")
37
+ t.add_column("",width=3); t.add_column("",width=3); t.add_column("Username",style="bright_cyan"); t.add_column("Display Name"); t.add_column("Score",justify="right",style="yellow")
38
+ for u in users:
39
+ icon = get_status_icon(u.get("status","online"))
40
+ badge = get_role_badge(u.get("role","member"))
41
+ t.add_row(icon, badge, u["username"], u.get("display_name",""), str(u.get("score",0)))
42
+ console.print(t)
43
+ except APIError as e: print_error(f"Failed: {e.message}")
44
+ finally: await api.close()
45
+
46
+ async def cmd_leaderboard():
47
+ api = APIClient()
48
+ try:
49
+ with console.status("[green]Loading...[/green]"): r = await api.get("/api/v1/games/leaderboard")
50
+ t = Table(title="🏆 Leaderboard",border_style="yellow",header_style="bold yellow")
51
+ t.add_column("Rank",width=5); t.add_column("",width=3); t.add_column("Username",style="bright_cyan"); t.add_column("Score",justify="right",style="bright_yellow"); t.add_column("Status",width=3)
52
+ medals = ["đŸĨ‡","đŸĨˆ","đŸĨ‰"]
53
+ for u in r.get("overall",[]):
54
+ rank = medals[u["rank"]-1] if u["rank"]<=3 else f"#{u['rank']}"
55
+ status = "[green]●[/green]" if u.get("is_online") else "[dim]○[/dim]"
56
+ t.add_row(rank, "", u["username"], str(u["score"]), status)
57
+ console.print(t)
58
+ except APIError as e: print_error(f"Failed: {e.message}")
59
+ finally: await api.close()
60
+
61
+ async def cmd_stats():
62
+ api = APIClient()
63
+ try:
64
+ with console.status("[green]Fetching...[/green]"): s = await api.get("/api/v1/stats")
65
+ console.print("\n[bold cyan]── trmsg Server Stats ──[/bold cyan]")
66
+ console.print(f" đŸ‘Ĩ Users: [bold]{s.get('users',0)}[/bold]")
67
+ console.print(f" đŸŸĸ Online: [bold bright_green]{s.get('online_now',0)}[/bold bright_green]")
68
+ console.print(f" đŸ’Ŧ Rooms: [bold]{s.get('rooms',0)}[/bold]")
69
+ console.print(f" 📨 Messages: [bold]{s.get('messages',0)}[/bold]")
70
+ console.print(f" 📎 Files: [bold]{s.get('files',0)}[/bold]")
71
+ console.print(f" 🎮 Games: [bold]{s.get('games_played',0)}[/bold]\n")
72
+ except APIError as e: print_error(f"Failed: {e.message}")
73
+ finally: await api.close()
74
+
75
+ async def cmd_whois(username):
76
+ api = APIClient()
77
+ try:
78
+ with console.status(f"[green]Looking up {username}...[/green]"):
79
+ u = await api.get(f"/api/v1/users/{username}")
80
+ s = await api.get(f"/api/v1/users/{username}/stats")
81
+ icon = get_status_icon(u.get("status","offline"))
82
+ badge = get_role_badge(u.get("role","member"))
83
+ console.print(f"\n[bold cyan]── {u['username']} {badge} ──[/bold cyan]")
84
+ console.print(f" Display: {u.get('display_name',u['username'])}")
85
+ console.print(f" Status: {icon} {u.get('status','offline')}")
86
+ if u.get("status_message"): console.print(f" Message: {u['status_message']}")
87
+ if u.get("bio"): console.print(f" Bio: {u['bio']}")
88
+ console.print(f" Score: 🏆 {s.get('score',0)} (Rank #{s.get('rank','?')})")
89
+ console.print(f" Messages: {s.get('total_messages',0)} | Files: {s.get('total_files',0)}")
90
+ gs = s.get("game_stats",{})
91
+ if gs:
92
+ for game, stat in gs.items():
93
+ console.print(f" {game.upper()}: W:{stat['wins']} L:{stat['losses']} Score:{stat['score']}")
94
+ console.print(f" Joined: {u.get('created_at','')[:10]}\n")
95
+ except APIError as e: print_error(f"Failed: {e.message}")
96
+ finally: await api.close()
97
+
98
+ async def cmd_rooms():
99
+ api = APIClient()
100
+ try:
101
+ with console.status("[green]Loading...[/green]"): r = await api.get("/api/v1/rooms")
102
+ rooms = r.get("rooms",[])
103
+ if not rooms: print_info("No public rooms."); return
104
+ t = Table(title="đŸ“ĸ Public Rooms",border_style="bright_black",header_style="bold cyan")
105
+ t.add_column("Icon",width=4); t.add_column("Room",style="bright_cyan"); t.add_column("Category"); t.add_column("Description"); t.add_column("đŸŸĸ",justify="right"); t.add_column("🔒",width=3)
106
+ for rm in rooms:
107
+ t.add_row(rm.get("icon","đŸ’Ŧ"), f"#{rm['name']}", rm.get("category","") or "", rm.get("description","") or "", str(rm.get("online",0)), "🔒" if rm.get("has_password") else "")
108
+ console.print(t)
109
+ console.print("[dim] Join: trmsg chat → /join <room>[/dim]\n")
110
+ except APIError as e: print_error(f"Failed: {e.message}")
111
+ finally: await api.close()
@@ -0,0 +1,83 @@
1
+ """trmsg - CLI Config"""
2
+ import json
3
+ from pathlib import Path
4
+ from typing import Optional
5
+
6
+ class CLIConfig:
7
+ CONFIG_DIR = Path.home() / ".trmsg"
8
+ CONFIG_FILE = CONFIG_DIR / "config.json"
9
+ TOKEN_FILE = CONFIG_DIR / "token"
10
+ HISTORY_FILE= CONFIG_DIR / "history"
11
+
12
+ def __init__(self):
13
+ self.CONFIG_DIR.mkdir(parents=True, exist_ok=True)
14
+ self._data = self._load()
15
+
16
+ def _load(self):
17
+ if self.CONFIG_FILE.exists():
18
+ try: return json.loads(self.CONFIG_FILE.read_text())
19
+ except: return {}
20
+ return {}
21
+
22
+ def _save(self):
23
+ self.CONFIG_FILE.write_text(json.dumps(self._data, indent=2))
24
+
25
+ @property
26
+ def server_url(self): return self._data.get("server_url", "http://localhost:8000")
27
+ @server_url.setter
28
+ def server_url(self, v): self._data["server_url"] = v.rstrip("/"); self._save()
29
+
30
+ @property
31
+ def ws_url(self): return self.server_url.replace("http://","ws://").replace("https://","wss://")
32
+
33
+ @property
34
+ def username(self): return self._data.get("username")
35
+ @username.setter
36
+ def username(self, v): self._data["username"] = v; self._save()
37
+
38
+ @property
39
+ def avatar_color(self): return self._data.get("avatar_color","#00ff88")
40
+ @avatar_color.setter
41
+ def avatar_color(self, v): self._data["avatar_color"] = v; self._save()
42
+
43
+ @property
44
+ def theme(self): return self._data.get("theme","cyberpunk")
45
+ @theme.setter
46
+ def theme(self, v): self._data["theme"] = v; self._save()
47
+
48
+ @property
49
+ def token(self):
50
+ if self.TOKEN_FILE.exists(): return self.TOKEN_FILE.read_text().strip() or None
51
+ return None
52
+ @token.setter
53
+ def token(self, v):
54
+ if v: self.TOKEN_FILE.write_text(v); self.TOKEN_FILE.chmod(0o600)
55
+ elif self.TOKEN_FILE.exists(): self.TOKEN_FILE.unlink()
56
+
57
+ @property
58
+ def download_dir(self):
59
+ p = Path(self._data.get("download_dir", str(Path.home()/"Downloads"/"trmsg")))
60
+ p.mkdir(parents=True, exist_ok=True)
61
+ return p
62
+ @download_dir.setter
63
+ def download_dir(self, v): self._data["download_dir"] = v; self._save()
64
+
65
+ def add_history(self, text: str):
66
+ if not self.HISTORY_FILE.exists(): h = []
67
+ else:
68
+ try: h = json.loads(self.HISTORY_FILE.read_text())
69
+ except: h = []
70
+ if text and (not h or h[-1] != text):
71
+ h.append(text)
72
+ if len(h) > 200: h = h[-200:]
73
+ self.HISTORY_FILE.write_text(json.dumps(h))
74
+
75
+ def is_authenticated(self): return bool(self.token and self.username)
76
+
77
+ def clear_auth(self):
78
+ self.token = None
79
+ self._data.pop("username", None)
80
+ self._data.pop("avatar_color", None)
81
+ self._save()
82
+
83
+ config = CLIConfig()
File without changes