termchat-client 0.2.1__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 (34) hide show
  1. {termchat_client-0.2.1/termchat_client.egg-info → termchat_client-0.2.3}/PKG-INFO +26 -6
  2. {termchat_client-0.2.1 → termchat_client-0.2.3}/README.md +25 -5
  3. {termchat_client-0.2.1 → termchat_client-0.2.3}/pyproject.toml +1 -1
  4. {termchat_client-0.2.1 → termchat_client-0.2.3}/termchat/__init__.py +1 -1
  5. {termchat_client-0.2.1 → termchat_client-0.2.3}/termchat/__pycache__/__init__.cpython-313.pyc +0 -0
  6. termchat_client-0.2.3/termchat/__pycache__/cli.cpython-313.pyc +0 -0
  7. termchat_client-0.2.3/termchat/__pycache__/client.cpython-313.pyc +0 -0
  8. termchat_client-0.2.3/termchat/__pycache__/config.cpython-313.pyc +0 -0
  9. {termchat_client-0.2.1 → termchat_client-0.2.3}/termchat/cli.py +12 -17
  10. {termchat_client-0.2.1 → termchat_client-0.2.3}/termchat/client.py +45 -13
  11. {termchat_client-0.2.1 → termchat_client-0.2.3}/termchat/config.py +20 -7
  12. {termchat_client-0.2.1 → termchat_client-0.2.3/termchat_client.egg-info}/PKG-INFO +26 -6
  13. {termchat_client-0.2.1 → termchat_client-0.2.3}/tests/test_config.py +19 -1
  14. termchat_client-0.2.1/termchat/__pycache__/cli.cpython-313.pyc +0 -0
  15. termchat_client-0.2.1/termchat/__pycache__/client.cpython-313.pyc +0 -0
  16. termchat_client-0.2.1/termchat/__pycache__/config.cpython-313.pyc +0 -0
  17. {termchat_client-0.2.1 → termchat_client-0.2.3}/LICENSE +0 -0
  18. {termchat_client-0.2.1 → termchat_client-0.2.3}/MANIFEST.in +0 -0
  19. {termchat_client-0.2.1 → termchat_client-0.2.3}/setup.cfg +0 -0
  20. {termchat_client-0.2.1 → termchat_client-0.2.3}/termchat/__main__.py +0 -0
  21. {termchat_client-0.2.1 → termchat_client-0.2.3}/termchat/__pycache__/__main__.cpython-313.pyc +0 -0
  22. {termchat_client-0.2.1 → termchat_client-0.2.3}/termchat/__pycache__/ai.cpython-313.pyc +0 -0
  23. {termchat_client-0.2.1 → termchat_client-0.2.3}/termchat/__pycache__/viewer.cpython-313.pyc +0 -0
  24. {termchat_client-0.2.1 → termchat_client-0.2.3}/termchat/ai.py +0 -0
  25. {termchat_client-0.2.1 → termchat_client-0.2.3}/termchat/viewer.py +0 -0
  26. {termchat_client-0.2.1 → termchat_client-0.2.3}/termchat_client.egg-info/SOURCES.txt +0 -0
  27. {termchat_client-0.2.1 → termchat_client-0.2.3}/termchat_client.egg-info/dependency_links.txt +0 -0
  28. {termchat_client-0.2.1 → termchat_client-0.2.3}/termchat_client.egg-info/entry_points.txt +0 -0
  29. {termchat_client-0.2.1 → termchat_client-0.2.3}/termchat_client.egg-info/requires.txt +0 -0
  30. {termchat_client-0.2.1 → termchat_client-0.2.3}/termchat_client.egg-info/top_level.txt +0 -0
  31. {termchat_client-0.2.1 → termchat_client-0.2.3}/tests/test_ai.py +0 -0
  32. {termchat_client-0.2.1 → termchat_client-0.2.3}/tests/test_cli.py +0 -0
  33. {termchat_client-0.2.1 → termchat_client-0.2.3}/tests/test_server.py +0 -0
  34. {termchat_client-0.2.1 → 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.1
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
@@ -102,21 +102,41 @@ pip install termchat-client
102
102
 
103
103
  ---
104
104
 
105
+ ## 🚀 Quickstart (Zero-Config)
106
+
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
+
109
+ ```bash
110
+ pip install --upgrade termchat-client
111
+ termchat
112
+ ```
113
+
114
+ Or ask the free AI assistant right away:
115
+ ```bash
116
+ termchat ai "What is the difference between a process and a thread?"
117
+ ```
118
+
119
+ ---
120
+
105
121
  ## 🚀 Terminal Client Usage
106
122
 
107
123
  ### 1. Run via CLI
108
124
  ```bash
109
- termchat --server https://my-chat.onrender.com --name Alice
125
+ termchat --name Alice
126
+ ```
127
+ Or specify a custom server if self-hosting:
128
+ ```bash
129
+ termchat --server https://termchat.cliy.me --name Alice
110
130
  ```
111
131
  Or via Python module:
112
132
  ```bash
113
- python -m termchat --server https://my-chat.onrender.com --name Alice
133
+ python -m termchat --name Alice
114
134
  ```
115
135
 
116
- ### 2. Save Your Server Once
117
- Save your online server URL so you never need to type it again:
136
+ ### 2. Save Your Preferences Once
137
+ Save your online server URL or default name so you never need to type them again:
118
138
  ```bash
119
- termchat --set-server https://my-chat.onrender.com
139
+ termchat --set-server https://termchat.cliy.me
120
140
  termchat --set-name Alice
121
141
  ```
122
142
  Once saved, simply run:
@@ -60,21 +60,41 @@ pip install termchat-client
60
60
 
61
61
  ---
62
62
 
63
+ ## 🚀 Quickstart (Zero-Config)
64
+
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
+
67
+ ```bash
68
+ pip install --upgrade termchat-client
69
+ termchat
70
+ ```
71
+
72
+ Or ask the free AI assistant right away:
73
+ ```bash
74
+ termchat ai "What is the difference between a process and a thread?"
75
+ ```
76
+
77
+ ---
78
+
63
79
  ## 🚀 Terminal Client Usage
64
80
 
65
81
  ### 1. Run via CLI
66
82
  ```bash
67
- termchat --server https://my-chat.onrender.com --name Alice
83
+ termchat --name Alice
84
+ ```
85
+ Or specify a custom server if self-hosting:
86
+ ```bash
87
+ termchat --server https://termchat.cliy.me --name Alice
68
88
  ```
69
89
  Or via Python module:
70
90
  ```bash
71
- python -m termchat --server https://my-chat.onrender.com --name Alice
91
+ python -m termchat --name Alice
72
92
  ```
73
93
 
74
- ### 2. Save Your Server Once
75
- Save your online server URL so you never need to type it again:
94
+ ### 2. Save Your Preferences Once
95
+ Save your online server URL or default name so you never need to type them again:
76
96
  ```bash
77
- termchat --set-server https://my-chat.onrender.com
97
+ termchat --set-server https://termchat.cliy.me
78
98
  termchat --set-name Alice
79
99
  ```
80
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.1"
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.1"
6
+ __version__ = "0.2.3"
7
7
 
8
8
  __author__ = "TermChat Contributors"
9
9
  __license__ = "MIT"
@@ -10,6 +10,9 @@ from termchat import __version__
10
10
  from termchat.ai import query_llm
11
11
  from termchat.client import start
12
12
  from termchat.config import (
13
+ DEFAULT_FALLBACK_SERVER,
14
+ PRIMARY_DEFAULT_SERVER,
15
+ get_default_servers,
13
16
  get_saved_ai_key,
14
17
  get_saved_server_url,
15
18
  get_saved_username,
@@ -97,23 +100,14 @@ def main():
97
100
  sys.exit(0)
98
101
 
99
102
 
100
- # Determine server URL
101
- server_url = args.server or get_saved_server_url()
102
- if not server_url:
103
- print("Welcome to termchat!")
104
- try:
105
- val = input("Enter your online chat server URL (e.g. https://my-app.onrender.com): ").strip()
106
- except (KeyboardInterrupt, EOFError):
107
- print("\nAborted.")
108
- sys.exit(0)
109
-
110
- if not val:
111
- print("Error: A server URL is required.")
112
- sys.exit(1)
113
-
114
- set_saved_server_url(val)
115
- server_url = val
116
- print(f"Server saved to ~/.termchat.json. Next time you can just type 'termchat'!\n")
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
117
111
 
118
112
  # Determine username
119
113
  username = args.name or get_saved_username()
@@ -121,6 +115,7 @@ def main():
121
115
  start(
122
116
  server_url=server_url,
123
117
  username=username,
118
+ fallback_url=fallback_url,
124
119
  auto_open=args.open,
125
120
  no_thumb=args.no_thumb,
126
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,19 +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() -> Optional[str]:
38
- """Retrieves server URL from ENV, config file, or None."""
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
54
+
55
+ # 3. Default primary custom domain with Render Singapore fallback
56
+ return PRIMARY_DEFAULT_SERVER, FALLBACK_DEFAULT_SERVER
57
+
48
58
 
49
- return None
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
50
63
 
51
64
 
52
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.1
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
@@ -102,21 +102,41 @@ pip install termchat-client
102
102
 
103
103
  ---
104
104
 
105
+ ## 🚀 Quickstart (Zero-Config)
106
+
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
+
109
+ ```bash
110
+ pip install --upgrade termchat-client
111
+ termchat
112
+ ```
113
+
114
+ Or ask the free AI assistant right away:
115
+ ```bash
116
+ termchat ai "What is the difference between a process and a thread?"
117
+ ```
118
+
119
+ ---
120
+
105
121
  ## 🚀 Terminal Client Usage
106
122
 
107
123
  ### 1. Run via CLI
108
124
  ```bash
109
- termchat --server https://my-chat.onrender.com --name Alice
125
+ termchat --name Alice
126
+ ```
127
+ Or specify a custom server if self-hosting:
128
+ ```bash
129
+ termchat --server https://termchat.cliy.me --name Alice
110
130
  ```
111
131
  Or via Python module:
112
132
  ```bash
113
- python -m termchat --server https://my-chat.onrender.com --name Alice
133
+ python -m termchat --name Alice
114
134
  ```
115
135
 
116
- ### 2. Save Your Server Once
117
- Save your online server URL so you never need to type it again:
136
+ ### 2. Save Your Preferences Once
137
+ Save your online server URL or default name so you never need to type them again:
118
138
  ```bash
119
- termchat --set-server https://my-chat.onrender.com
139
+ termchat --set-server https://termchat.cliy.me
120
140
  termchat --set-name Alice
121
141
  ```
122
142
  Once saved, simply run:
@@ -1,4 +1,22 @@
1
- from termchat.config import normalize_urls
1
+ from termchat.config import (
2
+ DEFAULT_FALLBACK_SERVER,
3
+ FALLBACK_DEFAULT_SERVER,
4
+ PRIMARY_DEFAULT_SERVER,
5
+ get_default_servers,
6
+ get_saved_server_url,
7
+ normalize_urls,
8
+ )
9
+
10
+
11
+ def test_get_saved_server_url_default(monkeypatch):
12
+ monkeypatch.delenv("TERMCHAT_SERVER", raising=False)
13
+ monkeypatch.setattr("termchat.config.load_config", lambda: {})
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
2
20
 
3
21
 
4
22
  def test_normalize_urls_https():
File without changes