ChaTerminal 0.1.0__py3-none-any.whl
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.
- ChaTerminal/__init__.py +1 -0
- ChaTerminal/__main__.py +4 -0
- ChaTerminal/cli.py +15 -0
- cha_terminal/__init__.py +1 -0
- cha_terminal/client.py +101 -0
- cha_terminal/crypto_utils.py +7 -0
- cha_terminal/server.py +150 -0
- cha_terminal/splash.py +23 -0
- chaterminal-0.1.0.dist-info/METADATA +136 -0
- chaterminal-0.1.0.dist-info/RECORD +14 -0
- chaterminal-0.1.0.dist-info/WHEEL +5 -0
- chaterminal-0.1.0.dist-info/entry_points.txt +2 -0
- chaterminal-0.1.0.dist-info/licenses/LICENSE +21 -0
- chaterminal-0.1.0.dist-info/top_level.txt +2 -0
ChaTerminal/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
ChaTerminal/__main__.py
ADDED
ChaTerminal/cli.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from cha_terminal import server, client
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
def main():
|
|
5
|
+
if len(sys.argv) < 3 or sys.argv[1] != "init":
|
|
6
|
+
print("Usage: ChaTerminal init [server|client]")
|
|
7
|
+
sys.exit(1)
|
|
8
|
+
|
|
9
|
+
mode = sys.argv[2]
|
|
10
|
+
if mode == "server":
|
|
11
|
+
server.main()
|
|
12
|
+
elif mode == "client":
|
|
13
|
+
client.main()
|
|
14
|
+
else:
|
|
15
|
+
print("Usage: ChaTerminal init [server|client]")
|
cha_terminal/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
cha_terminal/client.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import socket
|
|
2
|
+
import threading
|
|
3
|
+
import hashlib
|
|
4
|
+
import readline
|
|
5
|
+
import platform
|
|
6
|
+
import time
|
|
7
|
+
import sys
|
|
8
|
+
from .crypto_utils import encrypt_msg, decrypt_msg
|
|
9
|
+
from .splash import splash
|
|
10
|
+
from colorama import init, Fore, Style
|
|
11
|
+
|
|
12
|
+
COMMANDS = ["/dm", "/kick", "/list", "/me", "/rename", "/help"]
|
|
13
|
+
client = None
|
|
14
|
+
|
|
15
|
+
def completer(text, state):
|
|
16
|
+
options = [cmd for cmd in COMMANDS if cmd.startswith(text)]
|
|
17
|
+
return options[state] if state < len(options) else None
|
|
18
|
+
|
|
19
|
+
def get_user_color(name):
|
|
20
|
+
hash_val = int(hashlib.sha256(name.encode()).hexdigest(), 16)
|
|
21
|
+
colors = [Fore.CYAN, Fore.GREEN, Fore.MAGENTA, Fore.YELLOW, Fore.BLUE, Fore.WHITE]
|
|
22
|
+
return colors[hash_val % len(colors)]
|
|
23
|
+
|
|
24
|
+
def sound_alert():
|
|
25
|
+
try:
|
|
26
|
+
if platform.system() == "Windows":
|
|
27
|
+
import winsound
|
|
28
|
+
winsound.Beep(800, 150)
|
|
29
|
+
else:
|
|
30
|
+
import os
|
|
31
|
+
os.system('printf "\\a"')
|
|
32
|
+
except:
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
def receive():
|
|
36
|
+
while True:
|
|
37
|
+
try:
|
|
38
|
+
msg = client.recv(2048)
|
|
39
|
+
if not msg:
|
|
40
|
+
print("\n[!] Server disconnected.")
|
|
41
|
+
break
|
|
42
|
+
text = decrypt_msg(msg)
|
|
43
|
+
if "[DM from" in text or "kicked" in text:
|
|
44
|
+
sound_alert()
|
|
45
|
+
|
|
46
|
+
if "[DM" in text:
|
|
47
|
+
print(f"\r{Fore.MAGENTA}{text}{Style.RESET_ALL}\n> ", end="")
|
|
48
|
+
elif "[+]" in text:
|
|
49
|
+
print(f"\r{Fore.GREEN}{text}{Style.RESET_ALL}\n> ", end="")
|
|
50
|
+
elif "[-]" in text:
|
|
51
|
+
print(f"\r{Fore.YELLOW}{text}{Style.RESET_ALL}\n> ", end="")
|
|
52
|
+
elif "[!]" in text:
|
|
53
|
+
print(f"\r{Fore.RED}{text}{Style.RESET_ALL}\n> ", end="")
|
|
54
|
+
elif "* " in text:
|
|
55
|
+
print(f"\r{Fore.BLUE}{text}{Style.RESET_ALL}\n> ", end="")
|
|
56
|
+
elif ": " in text:
|
|
57
|
+
timestamp, rest = text.split("] ", 1)
|
|
58
|
+
timestamp += "]"
|
|
59
|
+
name, msg = rest.split(": ", 1)
|
|
60
|
+
color = get_user_color(name.strip())
|
|
61
|
+
print(f"\r{timestamp} {color}{name}{Style.RESET_ALL}: {msg}\n> ", end="")
|
|
62
|
+
else:
|
|
63
|
+
print(f"\r{text}\n> ", end="")
|
|
64
|
+
except Exception as e:
|
|
65
|
+
print(f"\n[!] Connection lost: {e}")
|
|
66
|
+
break
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
client.close()
|
|
70
|
+
except:
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
def send():
|
|
74
|
+
while True:
|
|
75
|
+
try:
|
|
76
|
+
msg = input("> ")
|
|
77
|
+
client.sendall(encrypt_msg(msg))
|
|
78
|
+
except:
|
|
79
|
+
break
|
|
80
|
+
|
|
81
|
+
def main():
|
|
82
|
+
global client
|
|
83
|
+
splash()
|
|
84
|
+
init(autoreset=True)
|
|
85
|
+
readline.set_completer(completer)
|
|
86
|
+
readline.parse_and_bind("tab: complete")
|
|
87
|
+
|
|
88
|
+
SERVER = input("Server IP: ")
|
|
89
|
+
PORT = int(input("Server Port: "))
|
|
90
|
+
username = input("Username: ")
|
|
91
|
+
|
|
92
|
+
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
93
|
+
try:
|
|
94
|
+
client.connect((SERVER, PORT))
|
|
95
|
+
client.sendall(encrypt_msg(username))
|
|
96
|
+
except Exception as e:
|
|
97
|
+
print(f"[!] Could not connect: {e}")
|
|
98
|
+
return
|
|
99
|
+
|
|
100
|
+
threading.Thread(target=receive, daemon=True).start()
|
|
101
|
+
send()
|
cha_terminal/server.py
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import socket
|
|
2
|
+
import threading
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from .crypto_utils import encrypt_msg, decrypt_msg
|
|
5
|
+
from .splash import splash
|
|
6
|
+
|
|
7
|
+
HOST = '0.0.0.0'
|
|
8
|
+
PORT = None
|
|
9
|
+
ADMIN_USERNAME = None
|
|
10
|
+
clients = {}
|
|
11
|
+
usernames = {}
|
|
12
|
+
|
|
13
|
+
def broadcast(message, exclude_socket=None):
|
|
14
|
+
for client in list(clients):
|
|
15
|
+
if client != exclude_socket:
|
|
16
|
+
try:
|
|
17
|
+
client.sendall(encrypt_msg(message))
|
|
18
|
+
except:
|
|
19
|
+
remove_client(client)
|
|
20
|
+
|
|
21
|
+
def remove_client(sock):
|
|
22
|
+
try:
|
|
23
|
+
if sock in clients:
|
|
24
|
+
username = clients[sock]
|
|
25
|
+
del usernames[username]
|
|
26
|
+
del clients[sock]
|
|
27
|
+
sock.close()
|
|
28
|
+
broadcast(f"[-] {username} has left the chat.")
|
|
29
|
+
except Exception as e:
|
|
30
|
+
print(f"[ERROR: remove_client] {e}")
|
|
31
|
+
|
|
32
|
+
def handle_client(client):
|
|
33
|
+
try:
|
|
34
|
+
username = decrypt_msg(client.recv(1024))
|
|
35
|
+
if username in usernames:
|
|
36
|
+
client.sendall(encrypt_msg("[!] Username already taken."))
|
|
37
|
+
client.close()
|
|
38
|
+
return
|
|
39
|
+
|
|
40
|
+
clients[client] = username
|
|
41
|
+
usernames[username] = client
|
|
42
|
+
broadcast(f"[+] {username} joined the chat.")
|
|
43
|
+
|
|
44
|
+
while True:
|
|
45
|
+
raw = client.recv(2048)
|
|
46
|
+
if not raw:
|
|
47
|
+
break
|
|
48
|
+
msg = decrypt_msg(raw)
|
|
49
|
+
timestamp = datetime.now().strftime("[%H:%M]")
|
|
50
|
+
|
|
51
|
+
if msg.startswith("/dm "):
|
|
52
|
+
_, to_user, *message = msg.split()
|
|
53
|
+
message = " ".join(message)
|
|
54
|
+
if to_user in usernames:
|
|
55
|
+
target_socket = usernames[to_user]
|
|
56
|
+
target_socket.sendall(encrypt_msg(f"{timestamp} [DM from {username}] {message}"))
|
|
57
|
+
client.sendall(encrypt_msg(f"{timestamp} [DM to {to_user}] {message}"))
|
|
58
|
+
else:
|
|
59
|
+
client.sendall(encrypt_msg("[!] User not found."))
|
|
60
|
+
|
|
61
|
+
elif msg.startswith("/kick ") and username == ADMIN_USERNAME:
|
|
62
|
+
_, to_kick = msg.split()
|
|
63
|
+
if to_kick in usernames:
|
|
64
|
+
kick_socket = usernames[to_kick]
|
|
65
|
+
kick_socket.sendall(encrypt_msg("[!] You have been kicked by admin."))
|
|
66
|
+
remove_client(kick_socket)
|
|
67
|
+
broadcast(f"[!] {to_kick} was kicked by admin.")
|
|
68
|
+
else:
|
|
69
|
+
client.sendall(encrypt_msg("[!] User not found."))
|
|
70
|
+
|
|
71
|
+
elif msg.startswith("/list"):
|
|
72
|
+
active_users = ", ".join(usernames.keys())
|
|
73
|
+
client.sendall(encrypt_msg(f"[Users Online] {active_users}"))
|
|
74
|
+
|
|
75
|
+
elif msg.startswith("/rename "):
|
|
76
|
+
newname = msg.split()[1]
|
|
77
|
+
if newname in usernames:
|
|
78
|
+
client.sendall(encrypt_msg("[!] Username already taken."))
|
|
79
|
+
else:
|
|
80
|
+
broadcast(f"[!] {username} changed name to {newname}")
|
|
81
|
+
del usernames[username]
|
|
82
|
+
usernames[newname] = client
|
|
83
|
+
clients[client] = newname
|
|
84
|
+
username = newname
|
|
85
|
+
|
|
86
|
+
elif msg.startswith("/help"):
|
|
87
|
+
help_text = (
|
|
88
|
+
"\033[1;36m[ChaTerminal Help Menu]\033[0m\n"
|
|
89
|
+
"\033[1;33m/dm <user> <msg>\033[0m - Send a private message\n"
|
|
90
|
+
"\033[1;33m/kick <user>\033[0m - Kick a user (admin only)\n"
|
|
91
|
+
"\033[1;33m/list\033[0m - List online users\n"
|
|
92
|
+
"\033[1;33m/rename <newname>\033[0m - Change your username\n"
|
|
93
|
+
"\033[1;33m/me <action>\033[0m - Perform an action\n"
|
|
94
|
+
"\033[1;33m/help\033[0m - Show this help menu\n"
|
|
95
|
+
)
|
|
96
|
+
client.sendall(encrypt_msg(help_text))
|
|
97
|
+
|
|
98
|
+
else:
|
|
99
|
+
broadcast(f"{timestamp} {username}: {msg}", exclude_socket=client)
|
|
100
|
+
|
|
101
|
+
except Exception as e:
|
|
102
|
+
print(f"[ERROR: handle_client] {e}")
|
|
103
|
+
finally:
|
|
104
|
+
remove_client(client)
|
|
105
|
+
|
|
106
|
+
def get_local_ip():
|
|
107
|
+
try:
|
|
108
|
+
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
109
|
+
s.connect(("8.8.8.8", 80))
|
|
110
|
+
ip = s.getsockname()[0]
|
|
111
|
+
s.close()
|
|
112
|
+
return ip
|
|
113
|
+
except:
|
|
114
|
+
return "Unavailable"
|
|
115
|
+
|
|
116
|
+
def print_colored_box(lines, border_color="\033[96m", text_color="\033[97m"):
|
|
117
|
+
reset = "\033[0m"
|
|
118
|
+
width = max(len(line) for line in lines) + 4
|
|
119
|
+
print(f"{border_color}┌{'─' * width}┐{reset}")
|
|
120
|
+
for line in lines:
|
|
121
|
+
print(f"{border_color}│{reset} {text_color}{line.ljust(width - 2)}{reset} {border_color}│{reset}")
|
|
122
|
+
print(f"{border_color}└{'─' * width}┘{reset}")
|
|
123
|
+
|
|
124
|
+
def main():
|
|
125
|
+
global PORT, ADMIN_USERNAME
|
|
126
|
+
splash()
|
|
127
|
+
PORT = int(input("Port: "))
|
|
128
|
+
ADMIN_USERNAME = input("Admin Username: ")
|
|
129
|
+
|
|
130
|
+
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
131
|
+
server.bind((HOST, PORT))
|
|
132
|
+
server.listen()
|
|
133
|
+
|
|
134
|
+
local_ip = get_local_ip()
|
|
135
|
+
info_lines = [
|
|
136
|
+
f"ChaTerminal running on local IP: {local_ip}:{PORT}",
|
|
137
|
+
"Other users on the same Wi-Fi/LAN can connect using this IP.",
|
|
138
|
+
"",
|
|
139
|
+
"To allow others to connect over the internet:",
|
|
140
|
+
f" - Use Ngrok: ngrok tcp {PORT}",
|
|
141
|
+
f" - Or LocalXpose: ./lx tcp {PORT}",
|
|
142
|
+
f" - Or port forward your router to {local_ip}",
|
|
143
|
+
"",
|
|
144
|
+
"Then share the public IP/URL and port with clients."
|
|
145
|
+
]
|
|
146
|
+
print_colored_box(info_lines)
|
|
147
|
+
|
|
148
|
+
while True:
|
|
149
|
+
client_socket, addr = server.accept()
|
|
150
|
+
threading.Thread(target=handle_client, args=(client_socket,), daemon=True).start()
|
cha_terminal/splash.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import pyfiglet
|
|
2
|
+
import time
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
def typewriter(text, delay=0.005, beep=False):
|
|
6
|
+
for char in text:
|
|
7
|
+
sys.stdout.write(char)
|
|
8
|
+
sys.stdout.flush()
|
|
9
|
+
if beep and char not in [' ', '\n']:
|
|
10
|
+
sys.stdout.write('\a') # ASCII bell
|
|
11
|
+
sys.stdout.flush()
|
|
12
|
+
time.sleep(delay)
|
|
13
|
+
print()
|
|
14
|
+
|
|
15
|
+
def splash():
|
|
16
|
+
banner = pyfiglet.figlet_format("ChaTerminal", font="slant")
|
|
17
|
+
print("\033[1;32m", end="") # Green
|
|
18
|
+
typewriter(banner, delay=0.002, beep=True)
|
|
19
|
+
|
|
20
|
+
print("\033[1;34m", end="") # Blue
|
|
21
|
+
typewriter("[ secure terminal chat • \033[1;35mChaTerminal\033[1;34m ]", delay=0.01, beep=True)
|
|
22
|
+
|
|
23
|
+
print("\033[0m") # Reset color
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ChaTerminal
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A terminal-based encrypted chat system for LAN and remote connections
|
|
5
|
+
Home-page: https://github.com/Gofaone315/ChaTerminal
|
|
6
|
+
Author: Gofaone Tlalang
|
|
7
|
+
Author-email: gofaonetlalang@gmail.com
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Topic :: Communications :: Chat
|
|
13
|
+
Classifier: Topic :: Security :: Cryptography
|
|
14
|
+
Requires-Python: >=3.6
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: colorama
|
|
18
|
+
Dynamic: author
|
|
19
|
+
Dynamic: author-email
|
|
20
|
+
Dynamic: classifier
|
|
21
|
+
Dynamic: description
|
|
22
|
+
Dynamic: description-content-type
|
|
23
|
+
Dynamic: home-page
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
Dynamic: requires-dist
|
|
26
|
+
Dynamic: requires-python
|
|
27
|
+
Dynamic: summary
|
|
28
|
+
|
|
29
|
+
# ChaTerminal
|
|
30
|
+
|
|
31
|
+
**ChaTerminal** is a cross-platform terminal-based encrypted chat system built with Python. It supports multiple users on a local network or via tunneling tools like Ngrok or LocalXpose, providing a lightweight and interactive chat experience — right from the terminal.
|
|
32
|
+
|
|
33
|
+

|
|
34
|
+

|
|
35
|
+

|
|
36
|
+
|
|
37
|
+
## Features
|
|
38
|
+
|
|
39
|
+
- **Encrypted communication** using custom crypto utilities
|
|
40
|
+
- **Multi-user chat** over LAN or tunneled internet
|
|
41
|
+
- **Color-coded messages** with timestamp formatting
|
|
42
|
+
- **Auto-complete** for commands via `readline` and TAB
|
|
43
|
+
- **Direct messaging** using `/dm <user> <message>`
|
|
44
|
+
- **Username renaming** with `/rename <new_name>`
|
|
45
|
+
- **List online users** using `/list`
|
|
46
|
+
- **Admin controls**: `/kick <user>` (admin-only)
|
|
47
|
+
- **Emote support**: `/me <action>`
|
|
48
|
+
- **Help menu**: `/help` displays command reference
|
|
49
|
+
- **User join/leave notifications**
|
|
50
|
+
- **Sound alert** for DMs and admin actions
|
|
51
|
+
- **Graceful client disconnect and server feedback**
|
|
52
|
+
- **Color personalization** based on username hash
|
|
53
|
+
|
|
54
|
+
## Getting Started
|
|
55
|
+
|
|
56
|
+
### Requirements
|
|
57
|
+
|
|
58
|
+
- Python 3.9+
|
|
59
|
+
- pip packages: `colorama`, `pyfiglet`, `readline` (Linux/macOS), `pyreadline3` (Windows)
|
|
60
|
+
|
|
61
|
+
### Installation
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pip install ChaTerminal
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Running the Server
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
ChaTerminal init server
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
- Enter a port (e.g., `5555`) and the admin username
|
|
74
|
+
- Displays local IP with instructions for LAN or tunneled access
|
|
75
|
+
|
|
76
|
+
### Running the Client
|
|
77
|
+
|
|
78
|
+
In a **new terminal window**:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
ChaTerminal init client
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
- Enter the server IP, port, and a unique username
|
|
85
|
+
|
|
86
|
+
## Commands
|
|
87
|
+
|
|
88
|
+
| Command | Description |
|
|
89
|
+
|---------------------|------------------------------------------|
|
|
90
|
+
| `/dm <user> <msg>` | Send a private message |
|
|
91
|
+
| `/kick <user>` | Kick a user (admin only) |
|
|
92
|
+
| `/list` | View online users |
|
|
93
|
+
| `/rename <name>` | Change your username |
|
|
94
|
+
| `/me <action>` | Send an action/emote (e.g., waves) |
|
|
95
|
+
| `/help` | Show all available commands |
|
|
96
|
+
|
|
97
|
+
## Networking Tips
|
|
98
|
+
|
|
99
|
+
To let others join:
|
|
100
|
+
|
|
101
|
+
- **Local network**: Share the IP and port printed on server start
|
|
102
|
+
- **Ngrok**: `ngrok tcp <port>`
|
|
103
|
+
- **LocalXpose**: `./lx tcp <port>`
|
|
104
|
+
- **Port Forwarding**: Map the server's port on your router
|
|
105
|
+
|
|
106
|
+
## Example
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
# Start server
|
|
110
|
+
$ ChaTerminal init server
|
|
111
|
+
Port: 5555
|
|
112
|
+
Admin Username: admin
|
|
113
|
+
|
|
114
|
+
# Start client
|
|
115
|
+
$ ChaTerminal init client
|
|
116
|
+
Server IP: 192.168.1.100
|
|
117
|
+
Server Port: 5555
|
|
118
|
+
Username: myname
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Security Note
|
|
122
|
+
|
|
123
|
+
All messages are encrypted using the `crypto_utils` module before transmission. Ensure that this module uses secure encryption practices for production deployments.
|
|
124
|
+
|
|
125
|
+
## License
|
|
126
|
+
|
|
127
|
+
This project is licensed under the MIT License. See `LICENSE` for more info.
|
|
128
|
+
|
|
129
|
+
## Author
|
|
130
|
+
|
|
131
|
+
**Gofaone Tlalang**
|
|
132
|
+
GitHub: [@Gofaone315](https://github.com/Gofaone315)
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
**ChaTerminal** – Real-time encrypted chat, right from your terminal.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
ChaTerminal/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
2
|
+
ChaTerminal/__main__.py,sha256=XZQEiEg1VXxCIEX_EL-FGLLl-k4qXjYxhD06UoRRsP0,72
|
|
3
|
+
ChaTerminal/cli.py,sha256=-VloPrDGkSjNYfV6aEB9FI3hTkmtQScUUwyjTbSJnL0,379
|
|
4
|
+
cha_terminal/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
5
|
+
cha_terminal/client.py,sha256=ZMvT2GMRqIC23pHGFpVzwOCG3ZWeY8EYdU_iRwL1Ioc,3002
|
|
6
|
+
cha_terminal/crypto_utils.py,sha256=bO4g6aJx-yY3RiwIJgEY59x2_GjadwRPvMk62gzkkho,179
|
|
7
|
+
cha_terminal/server.py,sha256=pEKDQs9-nAx_Q2o_qau9ojX_nobWXwGerpr_WavUIG8,5521
|
|
8
|
+
cha_terminal/splash.py,sha256=mU1_EiOnX68blw9CF4U9RXThQZzNgOs6JFRx_8_Z324,678
|
|
9
|
+
chaterminal-0.1.0.dist-info/licenses/LICENSE,sha256=GvmOpkMWXCP9-bywb4DwoRxMT_zOWu5cuYilKfdf2vU,1072
|
|
10
|
+
chaterminal-0.1.0.dist-info/METADATA,sha256=tOIohPt-oG1ITlGXQgBhqsn6Yb_VPLiDE2kCNLJi3go,3992
|
|
11
|
+
chaterminal-0.1.0.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
|
|
12
|
+
chaterminal-0.1.0.dist-info/entry_points.txt,sha256=IpdZdr_Me6Gp4uKTnAwdQgvLrfSiK4Fbmlz2UYSU9as,46
|
|
13
|
+
chaterminal-0.1.0.dist-info/top_level.txt,sha256=NHGCl4mRpbXM55jGfq8zvR6xKi1EDjB57lgmitVOrqw,25
|
|
14
|
+
chaterminal-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Gofaone Tlalang
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|