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.
- {termchat_client-0.2.2/termchat_client.egg-info → termchat_client-0.2.3}/PKG-INFO +4 -4
- {termchat_client-0.2.2 → termchat_client-0.2.3}/README.md +3 -3
- {termchat_client-0.2.2 → termchat_client-0.2.3}/pyproject.toml +1 -1
- {termchat_client-0.2.2 → termchat_client-0.2.3}/termchat/__init__.py +1 -1
- {termchat_client-0.2.2 → termchat_client-0.2.3}/termchat/__pycache__/cli.cpython-313.pyc +0 -0
- termchat_client-0.2.3/termchat/__pycache__/client.cpython-313.pyc +0 -0
- termchat_client-0.2.3/termchat/__pycache__/config.cpython-313.pyc +0 -0
- {termchat_client-0.2.2 → termchat_client-0.2.3}/termchat/cli.py +11 -2
- {termchat_client-0.2.2 → termchat_client-0.2.3}/termchat/client.py +45 -13
- {termchat_client-0.2.2 → termchat_client-0.2.3}/termchat/config.py +20 -8
- {termchat_client-0.2.2 → termchat_client-0.2.3/termchat_client.egg-info}/PKG-INFO +4 -4
- {termchat_client-0.2.2 → termchat_client-0.2.3}/tests/test_config.py +9 -2
- termchat_client-0.2.2/termchat/__pycache__/client.cpython-313.pyc +0 -0
- termchat_client-0.2.2/termchat/__pycache__/config.cpython-313.pyc +0 -0
- {termchat_client-0.2.2 → termchat_client-0.2.3}/LICENSE +0 -0
- {termchat_client-0.2.2 → termchat_client-0.2.3}/MANIFEST.in +0 -0
- {termchat_client-0.2.2 → termchat_client-0.2.3}/setup.cfg +0 -0
- {termchat_client-0.2.2 → termchat_client-0.2.3}/termchat/__main__.py +0 -0
- {termchat_client-0.2.2 → termchat_client-0.2.3}/termchat/__pycache__/__init__.cpython-313.pyc +0 -0
- {termchat_client-0.2.2 → termchat_client-0.2.3}/termchat/__pycache__/__main__.cpython-313.pyc +0 -0
- {termchat_client-0.2.2 → termchat_client-0.2.3}/termchat/__pycache__/ai.cpython-313.pyc +0 -0
- {termchat_client-0.2.2 → termchat_client-0.2.3}/termchat/__pycache__/viewer.cpython-313.pyc +0 -0
- {termchat_client-0.2.2 → termchat_client-0.2.3}/termchat/ai.py +0 -0
- {termchat_client-0.2.2 → termchat_client-0.2.3}/termchat/viewer.py +0 -0
- {termchat_client-0.2.2 → termchat_client-0.2.3}/termchat_client.egg-info/SOURCES.txt +0 -0
- {termchat_client-0.2.2 → termchat_client-0.2.3}/termchat_client.egg-info/dependency_links.txt +0 -0
- {termchat_client-0.2.2 → termchat_client-0.2.3}/termchat_client.egg-info/entry_points.txt +0 -0
- {termchat_client-0.2.2 → termchat_client-0.2.3}/termchat_client.egg-info/requires.txt +0 -0
- {termchat_client-0.2.2 → termchat_client-0.2.3}/termchat_client.egg-info/top_level.txt +0 -0
- {termchat_client-0.2.2 → termchat_client-0.2.3}/tests/test_ai.py +0 -0
- {termchat_client-0.2.2 → termchat_client-0.2.3}/tests/test_cli.py +0 -0
- {termchat_client-0.2.2 → termchat_client-0.2.3}/tests/test_server.py +0 -0
- {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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
6
|
+
__version__ = "0.2.3"
|
|
7
7
|
|
|
8
8
|
__author__ = "TermChat Contributors"
|
|
9
9
|
__license__ = "MIT"
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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
|
|
102
|
-
|
|
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:
|
|
48
|
-
print(f"Server:
|
|
49
|
-
print(f"Web UI:
|
|
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
|
-
|
|
248
|
-
|
|
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
|
-
|
|
253
|
-
|
|
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
|
-
|
|
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
|
|
38
|
-
"""
|
|
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
|
|
50
|
-
return
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
12
|
-
assert
|
|
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():
|
|
Binary file
|
|
Binary file
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{termchat_client-0.2.2 → termchat_client-0.2.3}/termchat/__pycache__/__init__.cpython-313.pyc
RENAMED
|
File without changes
|
{termchat_client-0.2.2 → termchat_client-0.2.3}/termchat/__pycache__/__main__.cpython-313.pyc
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{termchat_client-0.2.2 → termchat_client-0.2.3}/termchat_client.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|