termchat-client 0.2.2__tar.gz → 0.2.3__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 (33) hide show
  1. {termchat_client-0.2.2/termchat_client.egg-info → termchat_client-0.2.3}/PKG-INFO +4 -4
  2. {termchat_client-0.2.2 → termchat_client-0.2.3}/README.md +3 -3
  3. {termchat_client-0.2.2 → termchat_client-0.2.3}/pyproject.toml +1 -1
  4. {termchat_client-0.2.2 → termchat_client-0.2.3}/termchat/__init__.py +1 -1
  5. {termchat_client-0.2.2 → termchat_client-0.2.3}/termchat/__pycache__/cli.cpython-313.pyc +0 -0
  6. termchat_client-0.2.3/termchat/__pycache__/client.cpython-313.pyc +0 -0
  7. termchat_client-0.2.3/termchat/__pycache__/config.cpython-313.pyc +0 -0
  8. {termchat_client-0.2.2 → termchat_client-0.2.3}/termchat/cli.py +11 -2
  9. {termchat_client-0.2.2 → termchat_client-0.2.3}/termchat/client.py +45 -13
  10. {termchat_client-0.2.2 → termchat_client-0.2.3}/termchat/config.py +20 -8
  11. {termchat_client-0.2.2 → termchat_client-0.2.3/termchat_client.egg-info}/PKG-INFO +4 -4
  12. {termchat_client-0.2.2 → termchat_client-0.2.3}/tests/test_config.py +9 -2
  13. termchat_client-0.2.2/termchat/__pycache__/client.cpython-313.pyc +0 -0
  14. termchat_client-0.2.2/termchat/__pycache__/config.cpython-313.pyc +0 -0
  15. {termchat_client-0.2.2 → termchat_client-0.2.3}/LICENSE +0 -0
  16. {termchat_client-0.2.2 → termchat_client-0.2.3}/MANIFEST.in +0 -0
  17. {termchat_client-0.2.2 → termchat_client-0.2.3}/setup.cfg +0 -0
  18. {termchat_client-0.2.2 → termchat_client-0.2.3}/termchat/__main__.py +0 -0
  19. {termchat_client-0.2.2 → termchat_client-0.2.3}/termchat/__pycache__/__init__.cpython-313.pyc +0 -0
  20. {termchat_client-0.2.2 → termchat_client-0.2.3}/termchat/__pycache__/__main__.cpython-313.pyc +0 -0
  21. {termchat_client-0.2.2 → termchat_client-0.2.3}/termchat/__pycache__/ai.cpython-313.pyc +0 -0
  22. {termchat_client-0.2.2 → termchat_client-0.2.3}/termchat/__pycache__/viewer.cpython-313.pyc +0 -0
  23. {termchat_client-0.2.2 → termchat_client-0.2.3}/termchat/ai.py +0 -0
  24. {termchat_client-0.2.2 → termchat_client-0.2.3}/termchat/viewer.py +0 -0
  25. {termchat_client-0.2.2 → termchat_client-0.2.3}/termchat_client.egg-info/SOURCES.txt +0 -0
  26. {termchat_client-0.2.2 → termchat_client-0.2.3}/termchat_client.egg-info/dependency_links.txt +0 -0
  27. {termchat_client-0.2.2 → termchat_client-0.2.3}/termchat_client.egg-info/entry_points.txt +0 -0
  28. {termchat_client-0.2.2 → termchat_client-0.2.3}/termchat_client.egg-info/requires.txt +0 -0
  29. {termchat_client-0.2.2 → termchat_client-0.2.3}/termchat_client.egg-info/top_level.txt +0 -0
  30. {termchat_client-0.2.2 → termchat_client-0.2.3}/tests/test_ai.py +0 -0
  31. {termchat_client-0.2.2 → termchat_client-0.2.3}/tests/test_cli.py +0 -0
  32. {termchat_client-0.2.2 → termchat_client-0.2.3}/tests/test_server.py +0 -0
  33. {termchat_client-0.2.2 → termchat_client-0.2.3}/tests/test_viewer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: termchat-client
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: Lightweight, non-intrusive terminal chat client connecting to an online chat server with real-time text, images, and AI assistant.
5
5
  Author-email: TermChat Team <info@termchat.dev>
6
6
  License-Expression: MIT
@@ -104,7 +104,7 @@ pip install termchat-client
104
104
 
105
105
  ## 🚀 Quickstart (Zero-Config)
106
106
 
107
- Because the client comes pre-configured with the live server hosted in Singapore (`https://termchat-live.onrender.com`), you can chat immediately:
107
+ The client comes pre-configured with the live custom domain (`https://termchat.cliy.me/`) and automatic failover fallback to the Singapore Render server (`https://termchat-live.onrender.com`), so you can chat immediately:
108
108
 
109
109
  ```bash
110
110
  pip install --upgrade termchat-client
@@ -126,7 +126,7 @@ termchat --name Alice
126
126
  ```
127
127
  Or specify a custom server if self-hosting:
128
128
  ```bash
129
- termchat --server https://termchat-live.onrender.com --name Alice
129
+ termchat --server https://termchat.cliy.me --name Alice
130
130
  ```
131
131
  Or via Python module:
132
132
  ```bash
@@ -136,7 +136,7 @@ python -m termchat --name Alice
136
136
  ### 2. Save Your Preferences Once
137
137
  Save your online server URL or default name so you never need to type them again:
138
138
  ```bash
139
- termchat --set-server https://termchat-live.onrender.com
139
+ termchat --set-server https://termchat.cliy.me
140
140
  termchat --set-name Alice
141
141
  ```
142
142
  Once saved, simply run:
@@ -62,7 +62,7 @@ pip install termchat-client
62
62
 
63
63
  ## 🚀 Quickstart (Zero-Config)
64
64
 
65
- Because the client comes pre-configured with the live server hosted in Singapore (`https://termchat-live.onrender.com`), you can chat immediately:
65
+ The client comes pre-configured with the live custom domain (`https://termchat.cliy.me/`) and automatic failover fallback to the Singapore Render server (`https://termchat-live.onrender.com`), so you can chat immediately:
66
66
 
67
67
  ```bash
68
68
  pip install --upgrade termchat-client
@@ -84,7 +84,7 @@ termchat --name Alice
84
84
  ```
85
85
  Or specify a custom server if self-hosting:
86
86
  ```bash
87
- termchat --server https://termchat-live.onrender.com --name Alice
87
+ termchat --server https://termchat.cliy.me --name Alice
88
88
  ```
89
89
  Or via Python module:
90
90
  ```bash
@@ -94,7 +94,7 @@ python -m termchat --name Alice
94
94
  ### 2. Save Your Preferences Once
95
95
  Save your online server URL or default name so you never need to type them again:
96
96
  ```bash
97
- termchat --set-server https://termchat-live.onrender.com
97
+ termchat --set-server https://termchat.cliy.me
98
98
  termchat --set-name Alice
99
99
  ```
100
100
  Once saved, simply run:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "termchat-client"
7
- version = "0.2.2"
7
+ version = "0.2.3"
8
8
 
9
9
  description = "Lightweight, non-intrusive terminal chat client connecting to an online chat server with real-time text, images, and AI assistant."
10
10
 
@@ -3,7 +3,7 @@ termchat - Lightweight, non-intrusive terminal chat client with AI Assistant.
3
3
  Connects standard terminal input to your online Render/cloud server and receives text, images & AI responses.
4
4
  """
5
5
 
6
- __version__ = "0.2.2"
6
+ __version__ = "0.2.3"
7
7
 
8
8
  __author__ = "TermChat Contributors"
9
9
  __license__ = "MIT"
@@ -11,6 +11,8 @@ from termchat.ai import query_llm
11
11
  from termchat.client import start
12
12
  from termchat.config import (
13
13
  DEFAULT_FALLBACK_SERVER,
14
+ PRIMARY_DEFAULT_SERVER,
15
+ get_default_servers,
14
16
  get_saved_ai_key,
15
17
  get_saved_server_url,
16
18
  get_saved_username,
@@ -98,8 +100,14 @@ def main():
98
100
  sys.exit(0)
99
101
 
100
102
 
101
- # Determine server URL (defaults directly to live Singapore Render server)
102
- server_url = (args.server or get_saved_server_url() or DEFAULT_FALLBACK_SERVER).strip()
103
+ # Determine server URL and fallback failover
104
+ primary_srv, fallback_srv = get_default_servers()
105
+ if args.server:
106
+ server_url = args.server.strip()
107
+ fallback_url = None
108
+ else:
109
+ server_url = primary_srv
110
+ fallback_url = fallback_srv
103
111
 
104
112
  # Determine username
105
113
  username = args.name or get_saved_username()
@@ -107,6 +115,7 @@ def main():
107
115
  start(
108
116
  server_url=server_url,
109
117
  username=username,
118
+ fallback_url=fallback_url,
110
119
  auto_open=args.open,
111
120
  no_thumb=args.no_thumb,
112
121
  )
@@ -29,11 +29,10 @@ class TerminalClient:
29
29
  self,
30
30
  server_url: str,
31
31
  username: str,
32
+ fallback_url: Optional[str] = None,
32
33
  auto_open_images: bool = False,
33
34
  show_thumbnails: bool = True,
34
35
  ):
35
- self.raw_server_url = server_url
36
- self.ws_url, self.http_base_url = normalize_urls(server_url)
37
36
  self.username = username
38
37
  self.auto_open_images = auto_open_images
39
38
  self.show_thumbnails = show_thumbnails
@@ -41,12 +40,24 @@ class TerminalClient:
41
40
  self.websocket = None
42
41
  self.last_downloaded_image: Optional[Path] = None
43
42
 
43
+ self.server_candidates = [server_url.strip()]
44
+ if fallback_url and fallback_url.strip() != server_url.strip():
45
+ self.server_candidates.append(fallback_url.strip())
46
+ self.server_index = 0
47
+ self._apply_server(self.server_candidates[0])
48
+
49
+ def _apply_server(self, server_url: str):
50
+ self.raw_server_url = server_url
51
+ self.ws_url, self.http_base_url = normalize_urls(server_url)
52
+
44
53
  def print_welcome(self):
45
54
  """Prints a simple standard-line connection notice."""
46
55
  print(f"\n--- termchat connected ---")
47
- print(f"User: {self.username}")
48
- print(f"Server: {self.http_base_url}")
49
- print(f"Web UI: {self.http_base_url} (open in browser to chat or send images)")
56
+ print(f"User: {self.username}")
57
+ print(f"Server: {self.http_base_url}")
58
+ print(f"Web UI: {self.http_base_url} (open in browser to chat or send images)")
59
+ if len(self.server_candidates) > 1:
60
+ print(f"Fallback: {self.server_candidates[1]}")
50
61
  print(f"Commands: /help, /open (view last image), /quit")
51
62
  print(f"---------------------------\n")
52
63
 
@@ -199,7 +210,7 @@ class TerminalClient:
199
210
  print(f"[Input error: {e}]")
200
211
 
201
212
  async def run(self):
202
- """Main client loop with automatic reconnect."""
213
+ """Main client loop with automatic reconnect and fallback server failover."""
203
214
  self.print_welcome()
204
215
  session = PromptSession()
205
216
 
@@ -209,19 +220,20 @@ class TerminalClient:
209
220
  "client_type": "terminal",
210
221
  "device_info": device,
211
222
  })
212
- full_ws = f"{self.ws_url}?{query_params}"
213
223
 
214
224
  with patch_stdout():
215
225
  while self.running:
226
+ full_ws = f"{self.ws_url}?{query_params}"
216
227
  try:
217
228
  print(f"Connecting to {self.http_base_url}...")
218
229
  async with websockets.connect(
219
230
  full_ws,
220
231
  ping_interval=25,
221
232
  ping_timeout=15,
233
+ open_timeout=8,
222
234
  ) as ws:
223
235
  self.websocket = ws
224
- print("Connected! Start typing text below (Press Enter to send):\n")
236
+ print(f"Connected to {self.http_base_url}! Start typing text below (Press Enter to send):\n")
225
237
 
226
238
  recv_task = asyncio.create_task(self._receive_loop(ws))
227
239
  input_task = asyncio.create_task(self._input_loop(session))
@@ -241,16 +253,34 @@ class TerminalClient:
241
253
  if not self.running:
242
254
  break
243
255
 
244
- except (ConnectionRefusedError, OSError) as e:
256
+ except (ConnectionRefusedError, OSError, websockets.exceptions.WebSocketException, asyncio.TimeoutError) as e:
245
257
  if not self.running:
246
258
  break
247
- print(f"Could not connect to {self.http_base_url}. Retrying in 3s... (Ctrl+C to exit)")
248
- await asyncio.sleep(3)
259
+ if len(self.server_candidates) > 1:
260
+ next_idx = (self.server_index + 1) % len(self.server_candidates)
261
+ next_server = self.server_candidates[next_idx]
262
+ print(f"\n[Warning] {self.http_base_url} unreachable ({e}).")
263
+ print(f"[Failover] Switching to fallback server: {next_server}...")
264
+ self.server_index = next_idx
265
+ self._apply_server(next_server)
266
+ await asyncio.sleep(1)
267
+ else:
268
+ print(f"Could not connect to {self.http_base_url}. Retrying in 3s... (Ctrl+C to exit)")
269
+ await asyncio.sleep(3)
249
270
  except Exception as e:
250
271
  if not self.running:
251
272
  break
252
- print(f"Connection error: {e}. Retrying in 3s...")
253
- await asyncio.sleep(3)
273
+ if len(self.server_candidates) > 1:
274
+ next_idx = (self.server_index + 1) % len(self.server_candidates)
275
+ next_server = self.server_candidates[next_idx]
276
+ print(f"\n[Warning] Connection issue on {self.http_base_url}: {e}")
277
+ print(f"[Failover] Switching to fallback server: {next_server}...")
278
+ self.server_index = next_idx
279
+ self._apply_server(next_server)
280
+ await asyncio.sleep(1)
281
+ else:
282
+ print(f"Connection error: {e}. Retrying in 3s...")
283
+ await asyncio.sleep(3)
254
284
 
255
285
  print("\nExiting termchat. Goodbye!")
256
286
 
@@ -258,12 +288,14 @@ class TerminalClient:
258
288
  def start(
259
289
  server_url: str,
260
290
  username: str,
291
+ fallback_url: Optional[str] = None,
261
292
  auto_open: bool = False,
262
293
  no_thumb: bool = False,
263
294
  ):
264
295
  client = TerminalClient(
265
296
  server_url=server_url,
266
297
  username=username,
298
+ fallback_url=fallback_url,
267
299
  auto_open_images=auto_open,
268
300
  show_thumbnails=not no_thumb,
269
301
  )
@@ -7,10 +7,12 @@ and URL normalization for WebSocket and HTTP endpoints.
7
7
  import json
8
8
  import os
9
9
  from pathlib import Path
10
- from typing import Dict, Optional
10
+ from typing import Dict, Optional, Tuple
11
11
 
12
12
  CONFIG_FILE = Path.home() / ".termchat.json"
13
- DEFAULT_FALLBACK_SERVER = "https://termchat-live.onrender.com"
13
+ PRIMARY_DEFAULT_SERVER = "https://termchat.cliy.me"
14
+ FALLBACK_DEFAULT_SERVER = "https://termchat-live.onrender.com"
15
+ DEFAULT_FALLBACK_SERVER = FALLBACK_DEFAULT_SERVER
14
16
 
15
17
 
16
18
 
@@ -34,20 +36,30 @@ def save_config(config: Dict[str, str]):
34
36
  print(f"[Warning] Could not save config file: {e}")
35
37
 
36
38
 
37
- def get_saved_server_url() -> str:
38
- """Retrieves server URL from ENV, config file, or falls back to DEFAULT_FALLBACK_SERVER."""
39
+ def get_default_servers() -> Tuple[str, Optional[str]]:
40
+ """
41
+ Returns (primary_server, fallback_server).
42
+ If custom server is configured via ENV or ~/.termchat.json, returns (custom, None).
43
+ Otherwise returns (PRIMARY_DEFAULT_SERVER, FALLBACK_DEFAULT_SERVER).
44
+ """
39
45
  # 1. Environment variable takes highest priority
40
46
  env_url = os.environ.get("TERMCHAT_SERVER")
41
47
  if env_url:
42
- return env_url.strip()
48
+ return env_url.strip(), None
43
49
 
44
50
  # 2. Config file
45
51
  cfg = load_config()
46
52
  if "server" in cfg and cfg["server"]:
47
- return cfg["server"].strip()
53
+ return cfg["server"].strip(), None
48
54
 
49
- # 3. Default to public online Render server (Singapore)
50
- return DEFAULT_FALLBACK_SERVER
55
+ # 3. Default primary custom domain with Render Singapore fallback
56
+ return PRIMARY_DEFAULT_SERVER, FALLBACK_DEFAULT_SERVER
57
+
58
+
59
+ def get_saved_server_url() -> str:
60
+ """Retrieves server URL from ENV, config file, or falls back to PRIMARY_DEFAULT_SERVER."""
61
+ primary, _ = get_default_servers()
62
+ return primary
51
63
 
52
64
 
53
65
  def set_saved_server_url(url: str):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: termchat-client
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: Lightweight, non-intrusive terminal chat client connecting to an online chat server with real-time text, images, and AI assistant.
5
5
  Author-email: TermChat Team <info@termchat.dev>
6
6
  License-Expression: MIT
@@ -104,7 +104,7 @@ pip install termchat-client
104
104
 
105
105
  ## 🚀 Quickstart (Zero-Config)
106
106
 
107
- Because the client comes pre-configured with the live server hosted in Singapore (`https://termchat-live.onrender.com`), you can chat immediately:
107
+ The client comes pre-configured with the live custom domain (`https://termchat.cliy.me/`) and automatic failover fallback to the Singapore Render server (`https://termchat-live.onrender.com`), so you can chat immediately:
108
108
 
109
109
  ```bash
110
110
  pip install --upgrade termchat-client
@@ -126,7 +126,7 @@ termchat --name Alice
126
126
  ```
127
127
  Or specify a custom server if self-hosting:
128
128
  ```bash
129
- termchat --server https://termchat-live.onrender.com --name Alice
129
+ termchat --server https://termchat.cliy.me --name Alice
130
130
  ```
131
131
  Or via Python module:
132
132
  ```bash
@@ -136,7 +136,7 @@ python -m termchat --name Alice
136
136
  ### 2. Save Your Preferences Once
137
137
  Save your online server URL or default name so you never need to type them again:
138
138
  ```bash
139
- termchat --set-server https://termchat-live.onrender.com
139
+ termchat --set-server https://termchat.cliy.me
140
140
  termchat --set-name Alice
141
141
  ```
142
142
  Once saved, simply run:
@@ -1,5 +1,8 @@
1
1
  from termchat.config import (
2
2
  DEFAULT_FALLBACK_SERVER,
3
+ FALLBACK_DEFAULT_SERVER,
4
+ PRIMARY_DEFAULT_SERVER,
5
+ get_default_servers,
3
6
  get_saved_server_url,
4
7
  normalize_urls,
5
8
  )
@@ -8,8 +11,12 @@ from termchat.config import (
8
11
  def test_get_saved_server_url_default(monkeypatch):
9
12
  monkeypatch.delenv("TERMCHAT_SERVER", raising=False)
10
13
  monkeypatch.setattr("termchat.config.load_config", lambda: {})
11
- assert get_saved_server_url() == DEFAULT_FALLBACK_SERVER
12
- assert "termchat-live.onrender.com" in get_saved_server_url()
14
+ primary, fallback = get_default_servers()
15
+ assert primary == PRIMARY_DEFAULT_SERVER
16
+ assert "termchat.cliy.me" in primary
17
+ assert fallback == FALLBACK_DEFAULT_SERVER
18
+ assert "termchat-live.onrender.com" in fallback
19
+ assert get_saved_server_url() == PRIMARY_DEFAULT_SERVER
13
20
 
14
21
 
15
22
  def test_normalize_urls_https():
File without changes