lupaxa-chat-client 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,24 @@
1
+ # OS
2
+ .DS_Store
3
+
4
+ # Cursor
5
+ .cursor/
6
+ .superpowers/
7
+ .worktrees/
8
+ cursor-docs
9
+
10
+ # Make
11
+ .makefiles/
12
+
13
+ # MkDocs
14
+ site/
15
+
16
+ # Python
17
+ .venv/
18
+ __pycache__/
19
+ .mypy_cache/
20
+ .ruff_cache/
21
+ .pytest_cache/
22
+ dist/
23
+ build/
24
+ *.egg-info/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) The Lupaxa Project
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.
@@ -0,0 +1,134 @@
1
+ Metadata-Version: 2.5
2
+ Name: lupaxa-chat-client
3
+ Version: 0.1.0
4
+ Summary: A terminal client for the chat server, keeping server text separate from what you are typing.
5
+ Project-URL: Homepage, https://github.com/lupaxa-after-hours/chat-client
6
+ Project-URL: Repository, https://github.com/lupaxa-after-hours/chat-client
7
+ Project-URL: Issues, https://github.com/lupaxa-after-hours/chat-client/issues
8
+ Author: The Lupaxa Project
9
+ License: MIT License
10
+
11
+ Copyright (c) The Lupaxa Project
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining a copy
14
+ of this software and associated documentation files (the "Software"), to deal
15
+ in the Software without restriction, including without limitation the rights
16
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ copies of the Software, and to permit persons to whom the Software is
18
+ furnished to do so, subject to the following conditions:
19
+
20
+ The above copyright notice and this permission notice shall be included in all
21
+ copies or substantial portions of the Software.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
+ SOFTWARE.
30
+ License-File: LICENCE
31
+ Keywords: chat,client,terminal
32
+ Classifier: Development Status :: 3 - Alpha
33
+ Classifier: Environment :: Console
34
+ Classifier: Intended Audience :: Developers
35
+ Classifier: License :: OSI Approved :: MIT License
36
+ Classifier: Operating System :: OS Independent
37
+ Classifier: Programming Language :: Python :: 3
38
+ Classifier: Programming Language :: Python :: 3.10
39
+ Classifier: Programming Language :: Python :: 3.11
40
+ Classifier: Programming Language :: Python :: 3.12
41
+ Classifier: Programming Language :: Python :: 3.13
42
+ Classifier: Programming Language :: Python :: 3.14
43
+ Classifier: Topic :: Communications :: Chat
44
+ Requires-Python: >=3.10
45
+ Requires-Dist: asyncssh>=2.14
46
+ Provides-Extra: dev
47
+ Requires-Dist: bump-my-version>=1.2.0; extra == 'dev'
48
+ Requires-Dist: hatch>=1.12.0; extra == 'dev'
49
+ Requires-Dist: mypy>=1.12.0; extra == 'dev'
50
+ Requires-Dist: pip-audit>=2.7.0; extra == 'dev'
51
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
52
+ Requires-Dist: pytest>=8.0; extra == 'dev'
53
+ Requires-Dist: ruff>=0.6.0; extra == 'dev'
54
+ Provides-Extra: test
55
+ Requires-Dist: pytest-cov>=5.0; extra == 'test'
56
+ Requires-Dist: pytest>=8.0; extra == 'test'
57
+ Description-Content-Type: text/markdown
58
+
59
+ <p align="center">
60
+ <a href="https://github.com/lupaxa-after-hours">
61
+ <img src="https://raw.githubusercontent.com/the-lupaxa-project/brand-assets/master/logos/organisations/after-hours/readme-logo.png" alt="After Hours" />
62
+ </a>
63
+ </p>
64
+
65
+ <h1 align="center">Chat Client</h1>
66
+
67
+ A terminal client for the [chat server](https://github.com/lupaxa-after-hours/chat-server).
68
+ Text from the server stays in the upper part of the screen. The line you are
69
+ typing stays at the bottom, and new text does not clear it.
70
+
71
+ The bottom line is the prompt the server just sent: the name prompt, a
72
+ password prompt, or the player's own prompt. The two lines above it are the
73
+ last two lines you sent, still showing the prompt that was in use then. A
74
+ bar separates that area from the history. The server's prompt is not copied
75
+ into the history.
76
+
77
+ ## Install
78
+
79
+ ```bash
80
+ pip install lupaxa-chat-client
81
+ ```
82
+
83
+ Requires Python 3.10+.
84
+
85
+ > **Note:** `chat-client` only runs in a terminal. It stops before connecting
86
+ > when standard input or standard output is not a terminal.
87
+
88
+ ## Run
89
+
90
+ The host and the port are required. Use the port the server is listening on.
91
+
92
+ ```bash
93
+ chat-client telnet 127.0.0.1 9000
94
+ chat-client ssh 127.0.0.1 9022
95
+ ```
96
+
97
+ Telnet and SSH both show the server's normal text. On telnet the line is
98
+ edited locally and sent when you press Enter. On SSH each character is sent
99
+ as you type, and the server echoes it onto the prompt. SSH signs in as the
100
+ user `chat`.
101
+
102
+ The first SSH host key is saved in `~/.chat-client/known_hosts`. A key that
103
+ no longer matches is refused.
104
+
105
+ ## The Screen
106
+
107
+ Server lines wrap to the width of the terminal. Narrowing or widening the
108
+ window wraps them again from the original lines. Page Up keeps the same rows
109
+ on screen while more text arrives. End, or Page Down back to the bottom,
110
+ shows the newest lines.
111
+
112
+ A password prompt hides the characters you type. Ctrl-C leaves and restores
113
+ the terminal.
114
+
115
+ ## Keys
116
+
117
+ Scrolling and editing stay on this client. They are not sent to the server.
118
+
119
+ | Key | What it Does |
120
+ | -------------- | ----------------------------------------------- |
121
+ | Page Up | Scroll the history up by one screen. |
122
+ | Page Down | Scroll the history down by one screen. |
123
+ | End | Jump back to the newest server lines. |
124
+ | Left and Right | Move the cursor along the line you are typing. |
125
+ | Backspace | Delete the character before the cursor. |
126
+ | Delete | Delete the character at the cursor. |
127
+ | Enter | Send the line. |
128
+ | Ctrl-C | Leave and restore the terminal. |
129
+
130
+ Up and Down do not recall earlier lines. Ctrl-D does not quit.
131
+
132
+ <a href="https://github.com/the-lupaxa-project">
133
+ <img src="https://raw.githubusercontent.com/the-lupaxa-project/brand-assets/master/logos/components/footer-for-child-orgs.svg" alt="The Lupaxa Project Footer" width="100%" />
134
+ </a>
@@ -0,0 +1,76 @@
1
+ <p align="center">
2
+ <a href="https://github.com/lupaxa-after-hours">
3
+ <img src="https://raw.githubusercontent.com/the-lupaxa-project/brand-assets/master/logos/organisations/after-hours/readme-logo.png" alt="After Hours" />
4
+ </a>
5
+ </p>
6
+
7
+ <h1 align="center">Chat Client</h1>
8
+
9
+ A terminal client for the [chat server](https://github.com/lupaxa-after-hours/chat-server).
10
+ Text from the server stays in the upper part of the screen. The line you are
11
+ typing stays at the bottom, and new text does not clear it.
12
+
13
+ The bottom line is the prompt the server just sent: the name prompt, a
14
+ password prompt, or the player's own prompt. The two lines above it are the
15
+ last two lines you sent, still showing the prompt that was in use then. A
16
+ bar separates that area from the history. The server's prompt is not copied
17
+ into the history.
18
+
19
+ ## Install
20
+
21
+ ```bash
22
+ pip install lupaxa-chat-client
23
+ ```
24
+
25
+ Requires Python 3.10+.
26
+
27
+ > **Note:** `chat-client` only runs in a terminal. It stops before connecting
28
+ > when standard input or standard output is not a terminal.
29
+
30
+ ## Run
31
+
32
+ The host and the port are required. Use the port the server is listening on.
33
+
34
+ ```bash
35
+ chat-client telnet 127.0.0.1 9000
36
+ chat-client ssh 127.0.0.1 9022
37
+ ```
38
+
39
+ Telnet and SSH both show the server's normal text. On telnet the line is
40
+ edited locally and sent when you press Enter. On SSH each character is sent
41
+ as you type, and the server echoes it onto the prompt. SSH signs in as the
42
+ user `chat`.
43
+
44
+ The first SSH host key is saved in `~/.chat-client/known_hosts`. A key that
45
+ no longer matches is refused.
46
+
47
+ ## The Screen
48
+
49
+ Server lines wrap to the width of the terminal. Narrowing or widening the
50
+ window wraps them again from the original lines. Page Up keeps the same rows
51
+ on screen while more text arrives. End, or Page Down back to the bottom,
52
+ shows the newest lines.
53
+
54
+ A password prompt hides the characters you type. Ctrl-C leaves and restores
55
+ the terminal.
56
+
57
+ ## Keys
58
+
59
+ Scrolling and editing stay on this client. They are not sent to the server.
60
+
61
+ | Key | What it Does |
62
+ | -------------- | ----------------------------------------------- |
63
+ | Page Up | Scroll the history up by one screen. |
64
+ | Page Down | Scroll the history down by one screen. |
65
+ | End | Jump back to the newest server lines. |
66
+ | Left and Right | Move the cursor along the line you are typing. |
67
+ | Backspace | Delete the character before the cursor. |
68
+ | Delete | Delete the character at the cursor. |
69
+ | Enter | Send the line. |
70
+ | Ctrl-C | Leave and restore the terminal. |
71
+
72
+ Up and Down do not recall earlier lines. Ctrl-D does not quit.
73
+
74
+ <a href="https://github.com/the-lupaxa-project">
75
+ <img src="https://raw.githubusercontent.com/the-lupaxa-project/brand-assets/master/logos/components/footer-for-child-orgs.svg" alt="The Lupaxa Project Footer" width="100%" />
76
+ </a>
@@ -0,0 +1,23 @@
1
+ <p align="center">
2
+ <a href="https://github.com/lupaxa-after-hours">
3
+ <img src="https://raw.githubusercontent.com/the-lupaxa-project/brand-assets/master/logos/organisations/after-hours/readme-logo.png" alt="Organisation Logo" />
4
+ </a>
5
+ </p>
6
+
7
+ <h1 align="center">Core Documentation</h1>
8
+
9
+ The following documents define the standards that apply across **The Lupaxa Project**:
10
+
11
+ | Document | Purpose |
12
+ | :------------------------------------ | :---------------------------------------------------------------- |
13
+ | [Code of Conduct](CODE_OF_CONDUCT.md) | Expected behaviour and reporting procedures. |
14
+ | [How to Contribute](CONTRIBUTING.md) | Development workflow, coding standards, and contribution process. |
15
+ | [Governance](GOVERNANCE.md) | Project governance, roles, and decision-making. |
16
+ | [Maintainers](MAINTAINERS.md) | Maintainer responsibilities and expectations. |
17
+ | [Projects Overview](PROJECTS.md) | Overview of organisations, repositories, and project structure. |
18
+ | [Security Policy](SECURITY.md) | Responsible disclosure and secure development practices. |
19
+ | [Support Guide](SUPPORT.md) | How to obtain help and support. |
20
+
21
+ <a href="https://github.com/the-lupaxa-project">
22
+ <img src="https://raw.githubusercontent.com/the-lupaxa-project/brand-assets/master/logos/components/footer-for-child-orgs.svg" alt="The Lupaxa Project Footer" width="100%" />
23
+ </a>
@@ -0,0 +1,107 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.18"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "lupaxa-chat-client"
7
+ dynamic = ["version"]
8
+ description = "A terminal client for the chat server, keeping server text separate from what you are typing."
9
+ readme = "README.md"
10
+ license = { file = "LICENCE" }
11
+ requires-python = ">=3.10"
12
+ authors = [
13
+ { name = "The Lupaxa Project" }
14
+ ]
15
+ keywords = ["chat", "client", "terminal"]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Environment :: Console",
19
+ "Intended Audience :: Developers",
20
+ "License :: OSI Approved :: MIT License",
21
+ "Operating System :: OS Independent",
22
+ "Programming Language :: Python :: 3",
23
+ "Programming Language :: Python :: 3.10",
24
+ "Programming Language :: Python :: 3.11",
25
+ "Programming Language :: Python :: 3.12",
26
+ "Programming Language :: Python :: 3.13",
27
+ "Programming Language :: Python :: 3.14",
28
+ "Topic :: Communications :: Chat",
29
+ ]
30
+ dependencies = [
31
+ "asyncssh>=2.14",
32
+ ]
33
+
34
+ [project.scripts]
35
+ chat-client = "lupaxa.chat_client.cli:main"
36
+
37
+ [project.urls]
38
+ Homepage = "https://github.com/lupaxa-after-hours/chat-client"
39
+ Repository = "https://github.com/lupaxa-after-hours/chat-client"
40
+ Issues = "https://github.com/lupaxa-after-hours/chat-client/issues"
41
+
42
+ [project.optional-dependencies]
43
+ dev = [
44
+ "pytest>=8.0",
45
+ "pytest-cov>=5.0",
46
+ "ruff>=0.6.0",
47
+ "mypy>=1.12.0",
48
+ "hatch>=1.12.0",
49
+ "bump-my-version>=1.2.0",
50
+ "pip-audit>=2.7.0",
51
+ ]
52
+ test = [
53
+ "pytest>=8.0",
54
+ "pytest-cov>=5.0",
55
+ ]
56
+
57
+ [tool.hatch.version]
58
+ path = "src/lupaxa/chat_client/version.py"
59
+
60
+ [tool.hatch.build.targets.sdist]
61
+ include = [
62
+ "src/**",
63
+ "README.md",
64
+ "LICENCE",
65
+ ]
66
+
67
+ [tool.hatch.build.targets.wheel]
68
+ packages = ["src/lupaxa"]
69
+
70
+ [tool.ruff]
71
+ line-length = 100
72
+ target-version = "py310"
73
+
74
+ [tool.ruff.lint]
75
+ select = ["E", "F", "I", "B", "S", "UP", "SIM", "C4", "ISC", "D", "ANN", "PT"]
76
+ ignore = [
77
+ "D200", "D201", "D202", "D203", "D204", "D205", "D206", "D207",
78
+ "D208", "D209", "D210", "D211", "D212",
79
+ ]
80
+
81
+ [tool.ruff.lint.per-file-ignores]
82
+ "tests/**/*.py" = ["S101", "ANN001", "ANN201"]
83
+
84
+ [tool.ruff.lint.pydocstyle]
85
+ convention = "numpy"
86
+
87
+ [tool.ruff.format]
88
+ quote-style = "double"
89
+
90
+ [tool.pyright]
91
+ typeCheckingMode = "standard"
92
+ enableTypeIgnoreComments = true
93
+ pythonVersion = "3.10"
94
+
95
+ [tool.mypy]
96
+ python_version = "3.10"
97
+ files = ["src", "tests"]
98
+ mypy_path = "src"
99
+ explicit_package_bases = true
100
+ namespace_packages = true
101
+ warn_unused_configs = true
102
+ ignore_missing_imports = true
103
+
104
+ [tool.pytest.ini_options]
105
+ addopts = "-ra -q --disable-warnings"
106
+ testpaths = ["tests"]
107
+ pythonpath = ["src"]
@@ -0,0 +1,7 @@
1
+ """Terminal client for the chat server."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .version import __version__, get_version
6
+
7
+ __all__ = ["__version__", "get_version"]
@@ -0,0 +1,99 @@
1
+ """Connect server bytes and keys to the screen."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable
6
+
7
+ from .ssh import EchoStripper
8
+ from .telnet import TelnetParser
9
+ from .terminal import Screen
10
+
11
+ Transport = TelnetParser | EchoStripper
12
+
13
+
14
+ class App:
15
+ """One session. Exit always restores the terminal once."""
16
+
17
+ def __init__(self, screen: Screen, transport: Transport, restore: Callable[[], None]) -> None:
18
+ """Keep the screen, the transport, and the restore callback."""
19
+ self.screen = screen
20
+ self._transport = transport
21
+ self._restore = restore
22
+ self.outgoing: list[bytes] = []
23
+ self.exited = False
24
+ self.restored = False
25
+
26
+ def server_data(self, data: bytes) -> None:
27
+ """Append visible server text and apply echo or the hidden flag."""
28
+ text = self._transport.feed(data)
29
+ self.screen.set_hidden(self._transport.hidden)
30
+ if isinstance(self._transport, EchoStripper):
31
+ echoed = self._transport.take_echo()
32
+ if echoed:
33
+ self.screen.echo(echoed)
34
+ if text:
35
+ self.screen.add_server_text(text)
36
+
37
+ def seal_prompt(self) -> None:
38
+ """Drop the idle server prompt from the next line of history."""
39
+ self.screen.seal_prompt()
40
+
41
+ def key(self, name: str) -> None:
42
+ """Edit, send, or quit. Ctrl-C restores and adds no failure line."""
43
+ if self.exited:
44
+ return
45
+ if name == "ctrl-c":
46
+ self._exit()
47
+ return
48
+ if name == "ctrl-d":
49
+ return
50
+ if isinstance(self._transport, TelnetParser):
51
+ if name == "enter":
52
+ line = self.screen.key(name)
53
+ self.outgoing.append(self._transport.send_line(line or ""))
54
+ return
55
+ data = self._transport.send_key(name)
56
+ if data:
57
+ self.outgoing.append(data)
58
+ self.screen.key(name)
59
+ return
60
+ if name in {"pageup", "pagedown", "end", "left", "right", "up", "down"}:
61
+ self.screen.key(name)
62
+ return
63
+ if name == "backspace":
64
+ self.outgoing.append(self._transport.send_erase())
65
+ return
66
+ if name == "delete":
67
+ if self.screen.cursor_at_end():
68
+ self.outgoing.append(self._transport.send_erase())
69
+ else:
70
+ self.screen.key("delete")
71
+ return
72
+ if name == "enter":
73
+ self.screen.key(name)
74
+ enter = self._transport.send_enter()
75
+ if enter:
76
+ self.outgoing.append(enter)
77
+ return
78
+ data = self._transport.send_key(name)
79
+ if data:
80
+ self.outgoing.append(data)
81
+
82
+ def fail(self, message: str) -> None:
83
+ """Show ``message`` above the bar, then restore and exit."""
84
+ if self.exited:
85
+ return
86
+ self.screen.add_server_text(message + "\n")
87
+ self._exit()
88
+
89
+ def resize(self, columns: int, rows: int) -> None:
90
+ """Resize the screen and send the telnet window size when that transport is in use."""
91
+ self.screen.resize(columns, rows)
92
+ if isinstance(self._transport, TelnetParser):
93
+ self.outgoing.append(self._transport.window_size(columns, rows))
94
+
95
+ def _exit(self) -> None:
96
+ if not self.restored:
97
+ self._restore()
98
+ self.restored = True
99
+ self.exited = True
@@ -0,0 +1,26 @@
1
+ """Command line for telnet and SSH."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import sys
7
+ from collections.abc import Callable
8
+
9
+
10
+ def main(argv: list[str] | None = None, run: Callable[[str, str, int], int] | None = None) -> int:
11
+ """Parse the command. Return 1 when stdin or stdout is not a terminal."""
12
+ parser = argparse.ArgumentParser(prog="chat-client")
13
+ commands = parser.add_subparsers(dest="command", required=True)
14
+ for name in ("telnet", "ssh"):
15
+ sub = commands.add_parser(name)
16
+ sub.add_argument("host")
17
+ sub.add_argument("port", type=int)
18
+ args = parser.parse_args(argv)
19
+ if not sys.stdin.isatty() or not sys.stdout.isatty():
20
+ print("chat-client needs a terminal.", file=sys.stderr)
21
+ return 1
22
+ if run is None:
23
+ from .session import run_client
24
+
25
+ return run_client(str(args.command), str(args.host), int(args.port))
26
+ return run(str(args.command), str(args.host), int(args.port))