59chat 0.1.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.
@@ -0,0 +1,92 @@
1
+ Metadata-Version: 2.4
2
+ Name: 59chat
3
+ Version: 0.1.0
4
+ Summary: 59-second zero-trace terminal chat
5
+ Home-page: https://github.com/yourusername/59chat
6
+ Author: YourName
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: textual>=0.45.0
10
+ Requires-Dist: supabase>=2.3.0
11
+ Requires-Dist: python-dotenv>=1.0.0
12
+ Requires-Dist: pyperclip>=1.8.2
13
+ Dynamic: author
14
+ Dynamic: description
15
+ Dynamic: description-content-type
16
+ Dynamic: home-page
17
+ Dynamic: requires-dist
18
+ Dynamic: requires-python
19
+ Dynamic: summary
20
+
21
+ # Ephemeral Terminal Chat
22
+
23
+ Zero-trace terminal chat application with 59-second disappearing messages.
24
+
25
+ ## Features
26
+
27
+ - Messages disappear after 59 seconds
28
+ - Zero trace - no history kept
29
+ - Fast terminal UI with Textual framework
30
+ - Mono font recommended (set in your terminal, e.g., JetBrains Mono)
31
+ - Supabase backend for real-time sync
32
+ - Share rooms with 6-character room codes
33
+ - Random nickname generation
34
+
35
+ ## Setup
36
+
37
+ ### 1. Supabase Setup
38
+
39
+ 1. Go to [supabase.com](https://supabase.com) and create a free account
40
+ 2. Create a new project
41
+ 3. Go to SQL Editor and run the `supabase_setup.sql` file
42
+ 4. Get your project URL and anon key from Settings > API
43
+
44
+ ### 2. Environment Variables
45
+
46
+ Copy `.env.example` to `.env` and add your Supabase credentials:
47
+
48
+ ```bash
49
+ cp .env.example .env
50
+ ```
51
+
52
+ Edit `.env`:
53
+ ```
54
+ SUPABASE_URL=your_supabase_project_url
55
+ SUPABASE_KEY=your_supabase_anon_key
56
+ ```
57
+
58
+ ### 3. Install Dependencies
59
+
60
+ ```bash
61
+ pip install -r requirements.txt
62
+ ```
63
+
64
+ ### 4. Run
65
+
66
+ ```bash
67
+ python main.py
68
+ ```
69
+
70
+ ## Usage
71
+
72
+ - **Type a message** and press Enter to send
73
+ - **Ctrl+R** - Create new room
74
+ - **Ctrl+C** - Quit
75
+ - Share the **room code** with others to chat
76
+ - Messages auto-delete after 59 seconds
77
+
78
+ ## How It Works
79
+
80
+ - Each room has a unique 6-character code
81
+ - Messages are stored in Supabase with timestamp
82
+ - Auto-cleanup deletes messages older than 59 seconds
83
+ - Terminal polls for new messages every second
84
+ - Zero-trace: no message history retained
85
+
86
+ ## Terminal Share
87
+
88
+ Share your terminal with:
89
+ - tmate: `tmate`
90
+ - warp: Share session feature
91
+ - ssh: Allow remote connections
92
+ - Or just share the room code via any channel
@@ -0,0 +1,11 @@
1
+ MANIFEST.in
2
+ README.md
3
+ main.py
4
+ requirements.txt
5
+ setup.py
6
+ 59chat.egg-info/PKG-INFO
7
+ 59chat.egg-info/SOURCES.txt
8
+ 59chat.egg-info/dependency_links.txt
9
+ 59chat.egg-info/entry_points.txt
10
+ 59chat.egg-info/requires.txt
11
+ 59chat.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ 59chat = main:main_func
@@ -0,0 +1,4 @@
1
+ textual>=0.45.0
2
+ supabase>=2.3.0
3
+ python-dotenv>=1.0.0
4
+ pyperclip>=1.8.2
@@ -0,0 +1 @@
1
+ main
@@ -0,0 +1,5 @@
1
+ include README.md
2
+ include requirements.txt
3
+ exclude .env
4
+ exclude .env.example
5
+ recursive-exclude tests *
59chat-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,92 @@
1
+ Metadata-Version: 2.4
2
+ Name: 59chat
3
+ Version: 0.1.0
4
+ Summary: 59-second zero-trace terminal chat
5
+ Home-page: https://github.com/yourusername/59chat
6
+ Author: YourName
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: textual>=0.45.0
10
+ Requires-Dist: supabase>=2.3.0
11
+ Requires-Dist: python-dotenv>=1.0.0
12
+ Requires-Dist: pyperclip>=1.8.2
13
+ Dynamic: author
14
+ Dynamic: description
15
+ Dynamic: description-content-type
16
+ Dynamic: home-page
17
+ Dynamic: requires-dist
18
+ Dynamic: requires-python
19
+ Dynamic: summary
20
+
21
+ # Ephemeral Terminal Chat
22
+
23
+ Zero-trace terminal chat application with 59-second disappearing messages.
24
+
25
+ ## Features
26
+
27
+ - Messages disappear after 59 seconds
28
+ - Zero trace - no history kept
29
+ - Fast terminal UI with Textual framework
30
+ - Mono font recommended (set in your terminal, e.g., JetBrains Mono)
31
+ - Supabase backend for real-time sync
32
+ - Share rooms with 6-character room codes
33
+ - Random nickname generation
34
+
35
+ ## Setup
36
+
37
+ ### 1. Supabase Setup
38
+
39
+ 1. Go to [supabase.com](https://supabase.com) and create a free account
40
+ 2. Create a new project
41
+ 3. Go to SQL Editor and run the `supabase_setup.sql` file
42
+ 4. Get your project URL and anon key from Settings > API
43
+
44
+ ### 2. Environment Variables
45
+
46
+ Copy `.env.example` to `.env` and add your Supabase credentials:
47
+
48
+ ```bash
49
+ cp .env.example .env
50
+ ```
51
+
52
+ Edit `.env`:
53
+ ```
54
+ SUPABASE_URL=your_supabase_project_url
55
+ SUPABASE_KEY=your_supabase_anon_key
56
+ ```
57
+
58
+ ### 3. Install Dependencies
59
+
60
+ ```bash
61
+ pip install -r requirements.txt
62
+ ```
63
+
64
+ ### 4. Run
65
+
66
+ ```bash
67
+ python main.py
68
+ ```
69
+
70
+ ## Usage
71
+
72
+ - **Type a message** and press Enter to send
73
+ - **Ctrl+R** - Create new room
74
+ - **Ctrl+C** - Quit
75
+ - Share the **room code** with others to chat
76
+ - Messages auto-delete after 59 seconds
77
+
78
+ ## How It Works
79
+
80
+ - Each room has a unique 6-character code
81
+ - Messages are stored in Supabase with timestamp
82
+ - Auto-cleanup deletes messages older than 59 seconds
83
+ - Terminal polls for new messages every second
84
+ - Zero-trace: no message history retained
85
+
86
+ ## Terminal Share
87
+
88
+ Share your terminal with:
89
+ - tmate: `tmate`
90
+ - warp: Share session feature
91
+ - ssh: Allow remote connections
92
+ - Or just share the room code via any channel
59chat-0.1.0/README.md ADDED
@@ -0,0 +1,72 @@
1
+ # Ephemeral Terminal Chat
2
+
3
+ Zero-trace terminal chat application with 59-second disappearing messages.
4
+
5
+ ## Features
6
+
7
+ - Messages disappear after 59 seconds
8
+ - Zero trace - no history kept
9
+ - Fast terminal UI with Textual framework
10
+ - Mono font recommended (set in your terminal, e.g., JetBrains Mono)
11
+ - Supabase backend for real-time sync
12
+ - Share rooms with 6-character room codes
13
+ - Random nickname generation
14
+
15
+ ## Setup
16
+
17
+ ### 1. Supabase Setup
18
+
19
+ 1. Go to [supabase.com](https://supabase.com) and create a free account
20
+ 2. Create a new project
21
+ 3. Go to SQL Editor and run the `supabase_setup.sql` file
22
+ 4. Get your project URL and anon key from Settings > API
23
+
24
+ ### 2. Environment Variables
25
+
26
+ Copy `.env.example` to `.env` and add your Supabase credentials:
27
+
28
+ ```bash
29
+ cp .env.example .env
30
+ ```
31
+
32
+ Edit `.env`:
33
+ ```
34
+ SUPABASE_URL=your_supabase_project_url
35
+ SUPABASE_KEY=your_supabase_anon_key
36
+ ```
37
+
38
+ ### 3. Install Dependencies
39
+
40
+ ```bash
41
+ pip install -r requirements.txt
42
+ ```
43
+
44
+ ### 4. Run
45
+
46
+ ```bash
47
+ python main.py
48
+ ```
49
+
50
+ ## Usage
51
+
52
+ - **Type a message** and press Enter to send
53
+ - **Ctrl+R** - Create new room
54
+ - **Ctrl+C** - Quit
55
+ - Share the **room code** with others to chat
56
+ - Messages auto-delete after 59 seconds
57
+
58
+ ## How It Works
59
+
60
+ - Each room has a unique 6-character code
61
+ - Messages are stored in Supabase with timestamp
62
+ - Auto-cleanup deletes messages older than 59 seconds
63
+ - Terminal polls for new messages every second
64
+ - Zero-trace: no message history retained
65
+
66
+ ## Terminal Share
67
+
68
+ Share your terminal with:
69
+ - tmate: `tmate`
70
+ - warp: Share session feature
71
+ - ssh: Allow remote connections
72
+ - Or just share the room code via any channel
59chat-0.1.0/main.py ADDED
@@ -0,0 +1,288 @@
1
+ import asyncio
2
+ import os
3
+ import sys
4
+ import argparse
5
+ import subprocess
6
+ import json
7
+ import urllib.request
8
+ from datetime import datetime, timezone
9
+ from typing import Optional, Set, List, Dict
10
+ from textual.app import App, ComposeResult
11
+ from textual.widgets import Input, Static, Button, Footer, RichLog
12
+ from textual.containers import Vertical, Horizontal
13
+ from supabase import create_client, Client
14
+ import random
15
+ import string
16
+ import pyperclip
17
+
18
+ # --- CONFIGURATION ---
19
+ VERSION = "0.1.0" # Yeni isim olduğu için v0.1.0'dan başlıyoruz
20
+ APP_NAME = "59chat"
21
+ CMD_NAME = "59chat"
22
+
23
+ SUPABASE_URL = "https://xdqxebyyjxklzisddmwl.supabase.co"
24
+ SUPABASE_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InhkcXhlYnl5anhrbHppc2RkbXdsIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTk4Mzc0NDAsImV4cCI6MjA3NTQxMzQ0MH0.37JtLjN6mGfdac-t-cqNADa8OQlYIgSkZEFSngwxlM0"
25
+
26
+ # Design System
27
+ COLOR_BG = "#000000"
28
+ COLOR_SURFACE = "#0A0A0A"
29
+ COLOR_BORDER = "#1A1A1A"
30
+ COLOR_ACCENT = "#FFFFFF"
31
+ COLOR_TEXT_SECONDARY = "#525252"
32
+ COLOR_SUCCESS = "#22C55E"
33
+ COLOR_ERROR = "#EF4444"
34
+
35
+ CSS = f"""
36
+ Screen {{
37
+ background: {COLOR_BG};
38
+ color: {COLOR_ACCENT};
39
+ }}
40
+
41
+ #header {{
42
+ dock: top;
43
+ height: 3;
44
+ background: {COLOR_SURFACE};
45
+ border-bottom: solid {COLOR_BORDER};
46
+ content-align: center middle;
47
+ text-style: bold;
48
+ }}
49
+
50
+ #chat-log {{
51
+ height: 1fr;
52
+ background: {COLOR_BG};
53
+ border: none;
54
+ padding: 1 1;
55
+ scrollbar-gutter: stable;
56
+ }}
57
+
58
+ #input-area {{
59
+ dock: bottom;
60
+ height: 3;
61
+ background: {COLOR_SURFACE};
62
+ border-top: solid {COLOR_BORDER};
63
+ }}
64
+
65
+ Input {{
66
+ background: {COLOR_SURFACE};
67
+ border: none;
68
+ width: 1fr;
69
+ padding: 0 2;
70
+ color: {COLOR_ACCENT};
71
+ }}
72
+
73
+ Input:focus {{
74
+ border: none;
75
+ }}
76
+
77
+ #send-btn {{
78
+ width: 10;
79
+ background: {COLOR_ACCENT};
80
+ color: {COLOR_BG};
81
+ border: none;
82
+ text-style: bold;
83
+ height: 1;
84
+ margin: 1 1 0 0;
85
+ }}
86
+ """
87
+
88
+ def check_for_updates():
89
+ try:
90
+ url = f"https://pypi.org/pypi/{APP_NAME}/json"
91
+ with urllib.request.urlopen(url, timeout=1.5) as response:
92
+ data = json.load(response)
93
+ latest_version = data['info']['version']
94
+ def v_to_tuple(v): return tuple(map(int, v.split('.')))
95
+ if v_to_tuple(latest_version) > v_to_tuple(VERSION):
96
+ subprocess.check_call([sys.executable, "-m", "pip", "install", "-U", APP_NAME])
97
+ os.execv(sys.executable, ['python'] + sys.argv)
98
+ except Exception:
99
+ pass
100
+
101
+ class FiftyNineChat(App): # Sınıf ismini de güncelledik
102
+ CSS = CSS
103
+
104
+ BINDINGS = [
105
+ ("ctrl+c", "quit", "Exit"),
106
+ ("ctrl+r", "new_room", "New Room"),
107
+ ("ctrl+l", "copy_invite", "Copy Invitation"),
108
+ ]
109
+
110
+ def __init__(self, room_id: Optional[str] = None, is_new: bool = False):
111
+ super().__init__()
112
+ self.supabase: Optional[Client] = None
113
+ self.room_id: str = room_id or ""
114
+ self.nickname: str = ""
115
+ self.running = True
116
+ self.active_users: int = 1
117
+ self.is_new_session = is_new
118
+
119
+ def compose(self) -> ComposeResult:
120
+ yield Static(id="header")
121
+ yield RichLog(id="chat-log", markup=True, wrap=False)
122
+ with Horizontal(id="input-area"):
123
+ yield Input(placeholder="Type message...", id="message-input")
124
+ yield Button("SEND", id="send-btn")
125
+ yield Footer()
126
+
127
+ def on_mount(self) -> None:
128
+ try:
129
+ self.supabase = create_client(SUPABASE_URL, SUPABASE_KEY)
130
+ self.nickname = self._generate_nickname()
131
+ if not self.room_id:
132
+ self.room_id = self._generate_room_id()
133
+ self.is_new_session = True
134
+
135
+ self._update_header()
136
+ asyncio.create_task(self._watch_and_refresh())
137
+ asyncio.create_task(self._mark_as_read())
138
+ asyncio.create_task(self._presence_heartbeat())
139
+
140
+ if self.is_new_session:
141
+ self.action_copy_invite()
142
+
143
+ except Exception as e:
144
+ self.notify(f"Init failed: {e}", severity="error")
145
+
146
+ def _generate_nickname(self) -> str:
147
+ return f"{random.choice(['Cold','Swift','Pure','Thin'])}{random.choice(['Grid','Line','Type','Form'])}"
148
+
149
+ def _generate_room_id(self) -> str:
150
+ return ''.join(random.choices(string.ascii_uppercase + string.digits, k=6))
151
+
152
+ def _update_header(self):
153
+ presence_info = f" [dim]│[/] [bold {COLOR_SUCCESS}]●[/] {self.active_users} ONLINE"
154
+ self.query_one("#header").update(f"ROOM: [bold]{self.room_id}[/] [dim]│[/] USER: [bold]{self.nickname}[/]{presence_info}")
155
+
156
+ async def _presence_heartbeat(self):
157
+ while self.running and self.supabase:
158
+ try:
159
+ res = self.supabase.table('messages').select('nickname').eq('room_id', self.room_id).execute()
160
+ nicknames = {m['nickname'] for m in res.data}
161
+ self.active_users = max(1, len(nicknames))
162
+ self._update_header()
163
+ await asyncio.sleep(10)
164
+ except:
165
+ await asyncio.sleep(10)
166
+
167
+ async def _watch_and_refresh(self):
168
+ while self.running and self.supabase:
169
+ try:
170
+ res = self.supabase.table('messages').select('*').eq('room_id', self.room_id).order('created_at').execute()
171
+ chat_log = self.query_one("#chat-log")
172
+
173
+ now = datetime.now(timezone.utc)
174
+ visible_messages = []
175
+ for m in res.data:
176
+ if m.get('read_at'):
177
+ read_dt = datetime.fromisoformat(m['read_at'].replace('Z', '+00:00'))
178
+ if (now - read_dt).total_seconds() < 59:
179
+ visible_messages.append(m)
180
+ else:
181
+ visible_messages.append(m)
182
+
183
+ chat_log.clear()
184
+
185
+ width = chat_log.content_size.width or 80
186
+ max_msg_width = int(width * 0.8)
187
+
188
+ # --- Hizalanmış Davet Bloğu ---
189
+ invite_cmd = f"pip install -U {APP_NAME} && {CMD_NAME} --join {self.room_id}"
190
+
191
+ chat_log.write(f"[bold {COLOR_TEXT_SECONDARY}]MAGIC JOIN COMMAND:[/]")
192
+ box_top = "┏" + ("━" * (max_msg_width - 2)) + "┓"
193
+ box_mid = "┃ " + invite_cmd.ljust(max_msg_width - 4) + " ┃"
194
+ box_btm = "┗" + ("━" * (max_msg_width - 2)) + "┛"
195
+
196
+ chat_log.write(f"[dim]{box_top}[/]")
197
+ chat_log.write(f"[bold {COLOR_ACCENT}]{box_mid}[/]")
198
+ chat_log.write(f"[dim]{box_btm}[/]")
199
+ chat_log.write(f"[dim]Press Ctrl+L to copy invitation.[/]")
200
+ chat_log.write(f"[dim]{'─' * max_msg_width}[/]\n")
201
+
202
+ for m in visible_messages:
203
+ is_own = m['nickname'] == self.nickname
204
+ time_part = m['created_at'].split('T')[1][:5]
205
+ status = " [dim]○[/]"
206
+ if m.get('read_at'):
207
+ read_dt = datetime.fromisoformat(m['read_at'].replace('Z', '+00:00'))
208
+ rem = max(0, int(59 - (now - read_dt).total_seconds()))
209
+ status = f" [bold {COLOR_SUCCESS}]●[/] [bold {COLOR_ERROR}]{rem}s[/]"
210
+
211
+ user_style = f"bold {COLOR_ACCENT}" if is_own else f"bold {COLOR_TEXT_SECONDARY}"
212
+ indent_size = (width - max_msg_width - 4) if is_own else 0
213
+ indent = " " * indent_size
214
+ prefix = "› " if is_own else ""
215
+
216
+ header = f"{indent}[{user_style}]{prefix}{m['nickname']}[/] [dim]{time_part}[/]{status}"
217
+ content = f"{indent} {m['content']}"
218
+ underline = f"{indent}[dim]{'━' * max_msg_width}[/]]"
219
+
220
+ chat_log.write(header)
221
+ chat_log.write(content)
222
+ chat_log.write(underline)
223
+ chat_log.write("")
224
+
225
+ chat_log.scroll_end(animate=False)
226
+ await asyncio.sleep(0.5)
227
+ except:
228
+ await asyncio.sleep(1)
229
+
230
+ async def _mark_as_read(self):
231
+ while self.running and self.supabase:
232
+ try:
233
+ res = self.supabase.table('messages').select('id, nickname').eq('room_id', self.room_id).is_('read_at', 'null').execute()
234
+ unread = [m['id'] for m in res.data if m['nickname'] != self.nickname]
235
+ if unread:
236
+ now = datetime.now(timezone.utc).isoformat()
237
+ for mid in unread:
238
+ self.supabase.table('messages').update({'read_at': now}).eq('id', mid).execute()
239
+ await asyncio.sleep(1)
240
+ except:
241
+ await asyncio.sleep(2)
242
+
243
+ def _send(self):
244
+ inp = self.query_one(Input)
245
+ content = inp.value.strip()
246
+ if not content or not self.supabase: return
247
+ try:
248
+ self.supabase.table('messages').insert({
249
+ 'room_id': self.room_id,
250
+ 'nickname': self.nickname,
251
+ 'content': content
252
+ }).execute()
253
+ inp.value = ""
254
+ except:
255
+ self.notify("Send failed")
256
+
257
+ def on_input_submitted(self): self._send()
258
+ def on_button_pressed(self): self._send()
259
+
260
+ def action_copy_invite(self):
261
+ invite_cmd = f"pip install -U {APP_NAME} && {CMD_NAME} --join {self.room_id}"
262
+ try:
263
+ pyperclip.copy(invite_cmd)
264
+ self.notify("Invitation copied!", severity="information")
265
+ except:
266
+ self.notify("Failed to copy automatically.", severity="warning")
267
+
268
+ def action_new_room(self):
269
+ self.room_id = self._generate_room_id()
270
+ self.query_one("#chat-log").clear()
271
+ self._update_header()
272
+ self.action_copy_invite()
273
+
274
+ def on_unmount(self) -> None:
275
+ self.running = False
276
+
277
+ def main_func():
278
+ check_for_updates()
279
+ parser = argparse.ArgumentParser()
280
+ parser.add_argument("--join", help="Room ID")
281
+ parser.add_argument("--new", action="store_true", help="Start a new room")
282
+ args = parser.parse_args()
283
+ room = None if args.new else args.join
284
+ app = FiftyNineChat(room_id=room, is_new=args.new)
285
+ app.run()
286
+
287
+ if __name__ == "__main__":
288
+ main_func()
@@ -0,0 +1,4 @@
1
+ textual>=0.45.0
2
+ supabase>=2.3.0
3
+ python-dotenv>=1.0.0
4
+ pyperclip>=1.8.2
59chat-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
59chat-0.1.0/setup.py ADDED
@@ -0,0 +1,25 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ setup(
4
+ name="59chat",
5
+ version="0.1.0",
6
+ author="YourName",
7
+ description="59-second zero-trace terminal chat",
8
+ long_description=open("README.md", encoding="utf-8").read(),
9
+ long_description_content_type="text/markdown",
10
+ url="https://github.com/yourusername/59chat",
11
+ packages=find_packages(),
12
+ py_modules=["main"],
13
+ install_requires=[
14
+ "textual>=0.45.0",
15
+ "supabase>=2.3.0",
16
+ "python-dotenv>=1.0.0",
17
+ "pyperclip>=1.8.2",
18
+ ],
19
+ entry_points={
20
+ "console_scripts": [
21
+ "59chat=main:main_func",
22
+ ],
23
+ },
24
+ python_requires=">=3.10",
25
+ )