wasat 0.0.1__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.
- wasat-0.0.1/PKG-INFO +146 -0
- wasat-0.0.1/README.md +120 -0
- wasat-0.0.1/pyproject.toml +108 -0
- wasat-0.0.1/src/wasat/__init__.py +51 -0
- wasat-0.0.1/src/wasat/__main__.py +78 -0
- wasat-0.0.1/src/wasat/client.py +436 -0
- wasat-0.0.1/src/wasat/exceptions.py +28 -0
- wasat-0.0.1/src/wasat/py.typed +0 -0
- wasat-0.0.1/src/wasat/response.py +171 -0
- wasat-0.0.1/src/wasat/status.py +134 -0
- wasat-0.0.1/src/wasat/trust.py +179 -0
- wasat-0.0.1/src/wasat/uri.py +172 -0
wasat-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: wasat
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A async client library for the Gemini protocol
|
|
5
|
+
Keywords: API,async,client,library,Gemini
|
|
6
|
+
Author: Dave Pearson
|
|
7
|
+
Author-email: Dave Pearson <davep@davep.org>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Requires-Python: >=3.12
|
|
19
|
+
Project-URL: Homepage, https://wasat.davep.dev/
|
|
20
|
+
Project-URL: Repository, https://github.com/davep/wasat
|
|
21
|
+
Project-URL: Documentation, https://wasat.davep.dev/
|
|
22
|
+
Project-URL: Source, https://github.com/davep/wasat
|
|
23
|
+
Project-URL: Issues, https://github.com/davep/wasat/issues
|
|
24
|
+
Project-URL: Discussions, https://github.com/davep/wasat/discussions
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# Wasat: Async Gemini Protocol Client Library
|
|
28
|
+
|
|
29
|
+
Wasat is an object-oriented, fully type-hinted asynchronous client library for the [Gemini Protocol](gemini://geminiprotocol.net), built with zero external dependencies.
|
|
30
|
+
|
|
31
|
+
## Features
|
|
32
|
+
|
|
33
|
+
- **Async All the Way**: Built on top of Python's standard `asyncio` loop with streaming/chunking support.
|
|
34
|
+
- **Type Safe**: Fully typed API utilizing Python 3.14 standards (strictly avoiding `Any`).
|
|
35
|
+
- **TOFU (Trust-On-First-Use) Support**: Secure by default with a built-in file-based TOFU store and custom interactive trust confirmation hooks.
|
|
36
|
+
- **Auto Redirect Handling**: Automatically handles temporary and permanent redirects (with protection against loops and infinite redirect limits), caching permanent redirects locally.
|
|
37
|
+
- **Client Authentication**: Native support for client TLS certificates.
|
|
38
|
+
- **Zero-Dependency**: Runs purely on Python's standard library.
|
|
39
|
+
- **CLI Utility**: Includes a `wasat` command-line interface out of the box.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
44
|
+
|
|
45
|
+
You can install `wasat` directly in your project virtual environment using `uv` or `pip`:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
uv pip install -e .
|
|
49
|
+
# or
|
|
50
|
+
pip install -e .
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Quick Start
|
|
56
|
+
|
|
57
|
+
### 1. Make a Simple Request
|
|
58
|
+
Use `Client` with standard async context managers to query a Gemini capsule and decode the response:
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
import asyncio
|
|
62
|
+
from wasat import Client, WasatError
|
|
63
|
+
|
|
64
|
+
async def main():
|
|
65
|
+
# 'tofu' mode is ideal for standard Gemini capsules (self-signed certs)
|
|
66
|
+
client = Client(verify_mode="tofu")
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
# Perform the request (automatically resolves host, port, TLS, and redirects)
|
|
70
|
+
async with await client.request("gemini://geminiprotocol.net/") as response:
|
|
71
|
+
print(f"Status: {response.status.value} ({response.status.name})")
|
|
72
|
+
print(f"MIME type: {response.mime_type}")
|
|
73
|
+
|
|
74
|
+
# Fetch the decoded body text
|
|
75
|
+
body = await response.text()
|
|
76
|
+
print(body)
|
|
77
|
+
|
|
78
|
+
except WasatError as e:
|
|
79
|
+
print(f"Request failed: {e}")
|
|
80
|
+
|
|
81
|
+
if __name__ == "__main__":
|
|
82
|
+
asyncio.run(main())
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### 2. Streaming Chunk Responses
|
|
86
|
+
For large files or media streams, read the response body in chunks to prevent exhausting memory:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
async with await client.request("gemini://example.com/large-file.txt") as response:
|
|
90
|
+
if response.status.is_success:
|
|
91
|
+
async for chunk in response.iter_chunks(chunk_size=1024):
|
|
92
|
+
# Process each chunk as it arrives
|
|
93
|
+
sys.stdout.buffer.write(chunk)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### 3. Client Certificate Authentication
|
|
97
|
+
If a server requires client auth (status code `60`), supply your certificate files to the client configuration:
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
client = Client(
|
|
101
|
+
verify_mode="tofu",
|
|
102
|
+
client_cert="/path/to/client.crt",
|
|
103
|
+
client_key="/path/to/client.key" # Optional if key is embedded in cert
|
|
104
|
+
)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### 4. Interactive TOFU Confirmation
|
|
108
|
+
Implement a custom asynchronous callback to prompt the user before trusting new self-signed certificates:
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
import sys
|
|
112
|
+
|
|
113
|
+
async def confirm_cert(host: str, port: int, fingerprint: str) -> bool:
|
|
114
|
+
print(f"New certificate encountered for {host}:{port}")
|
|
115
|
+
print(f"Fingerprint: sha256:{fingerprint}")
|
|
116
|
+
response = input("Do you trust this certificate? [y/N]: ").strip().lower()
|
|
117
|
+
return response == "y"
|
|
118
|
+
|
|
119
|
+
client = Client(
|
|
120
|
+
verify_mode="tofu",
|
|
121
|
+
on_new_certificate=confirm_cert
|
|
122
|
+
)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Command Line Interface (CLI)
|
|
128
|
+
|
|
129
|
+
Wasat comes with a command-line interface to fetch Gemini capsules from your shell:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
# Basic fetch using the entrypoint script (uses TOFU)
|
|
133
|
+
uv run wasat gemini://geminiprotocol.net/
|
|
134
|
+
|
|
135
|
+
# Alternatively, execute the package directly using python -m
|
|
136
|
+
uv run python -m wasat gemini://geminiprotocol.net/
|
|
137
|
+
|
|
138
|
+
# Fetch a local or custom port capsule
|
|
139
|
+
uv run wasat gemini://localhost:1965/index.gmi
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## License
|
|
143
|
+
|
|
144
|
+
MIT
|
|
145
|
+
|
|
146
|
+
[//]: # (README.md ends here)
|
wasat-0.0.1/README.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Wasat: Async Gemini Protocol Client Library
|
|
2
|
+
|
|
3
|
+
Wasat is an object-oriented, fully type-hinted asynchronous client library for the [Gemini Protocol](gemini://geminiprotocol.net), built with zero external dependencies.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Async All the Way**: Built on top of Python's standard `asyncio` loop with streaming/chunking support.
|
|
8
|
+
- **Type Safe**: Fully typed API utilizing Python 3.14 standards (strictly avoiding `Any`).
|
|
9
|
+
- **TOFU (Trust-On-First-Use) Support**: Secure by default with a built-in file-based TOFU store and custom interactive trust confirmation hooks.
|
|
10
|
+
- **Auto Redirect Handling**: Automatically handles temporary and permanent redirects (with protection against loops and infinite redirect limits), caching permanent redirects locally.
|
|
11
|
+
- **Client Authentication**: Native support for client TLS certificates.
|
|
12
|
+
- **Zero-Dependency**: Runs purely on Python's standard library.
|
|
13
|
+
- **CLI Utility**: Includes a `wasat` command-line interface out of the box.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
You can install `wasat` directly in your project virtual environment using `uv` or `pip`:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
uv pip install -e .
|
|
23
|
+
# or
|
|
24
|
+
pip install -e .
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
### 1. Make a Simple Request
|
|
32
|
+
Use `Client` with standard async context managers to query a Gemini capsule and decode the response:
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
import asyncio
|
|
36
|
+
from wasat import Client, WasatError
|
|
37
|
+
|
|
38
|
+
async def main():
|
|
39
|
+
# 'tofu' mode is ideal for standard Gemini capsules (self-signed certs)
|
|
40
|
+
client = Client(verify_mode="tofu")
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
# Perform the request (automatically resolves host, port, TLS, and redirects)
|
|
44
|
+
async with await client.request("gemini://geminiprotocol.net/") as response:
|
|
45
|
+
print(f"Status: {response.status.value} ({response.status.name})")
|
|
46
|
+
print(f"MIME type: {response.mime_type}")
|
|
47
|
+
|
|
48
|
+
# Fetch the decoded body text
|
|
49
|
+
body = await response.text()
|
|
50
|
+
print(body)
|
|
51
|
+
|
|
52
|
+
except WasatError as e:
|
|
53
|
+
print(f"Request failed: {e}")
|
|
54
|
+
|
|
55
|
+
if __name__ == "__main__":
|
|
56
|
+
asyncio.run(main())
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 2. Streaming Chunk Responses
|
|
60
|
+
For large files or media streams, read the response body in chunks to prevent exhausting memory:
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
async with await client.request("gemini://example.com/large-file.txt") as response:
|
|
64
|
+
if response.status.is_success:
|
|
65
|
+
async for chunk in response.iter_chunks(chunk_size=1024):
|
|
66
|
+
# Process each chunk as it arrives
|
|
67
|
+
sys.stdout.buffer.write(chunk)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### 3. Client Certificate Authentication
|
|
71
|
+
If a server requires client auth (status code `60`), supply your certificate files to the client configuration:
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
client = Client(
|
|
75
|
+
verify_mode="tofu",
|
|
76
|
+
client_cert="/path/to/client.crt",
|
|
77
|
+
client_key="/path/to/client.key" # Optional if key is embedded in cert
|
|
78
|
+
)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### 4. Interactive TOFU Confirmation
|
|
82
|
+
Implement a custom asynchronous callback to prompt the user before trusting new self-signed certificates:
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
import sys
|
|
86
|
+
|
|
87
|
+
async def confirm_cert(host: str, port: int, fingerprint: str) -> bool:
|
|
88
|
+
print(f"New certificate encountered for {host}:{port}")
|
|
89
|
+
print(f"Fingerprint: sha256:{fingerprint}")
|
|
90
|
+
response = input("Do you trust this certificate? [y/N]: ").strip().lower()
|
|
91
|
+
return response == "y"
|
|
92
|
+
|
|
93
|
+
client = Client(
|
|
94
|
+
verify_mode="tofu",
|
|
95
|
+
on_new_certificate=confirm_cert
|
|
96
|
+
)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Command Line Interface (CLI)
|
|
102
|
+
|
|
103
|
+
Wasat comes with a command-line interface to fetch Gemini capsules from your shell:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
# Basic fetch using the entrypoint script (uses TOFU)
|
|
107
|
+
uv run wasat gemini://geminiprotocol.net/
|
|
108
|
+
|
|
109
|
+
# Alternatively, execute the package directly using python -m
|
|
110
|
+
uv run python -m wasat gemini://geminiprotocol.net/
|
|
111
|
+
|
|
112
|
+
# Fetch a local or custom port capsule
|
|
113
|
+
uv run wasat gemini://localhost:1965/index.gmi
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## License
|
|
117
|
+
|
|
118
|
+
MIT
|
|
119
|
+
|
|
120
|
+
[//]: # (README.md ends here)
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "wasat"
|
|
3
|
+
version = "0.0.1"
|
|
4
|
+
description = "A async client library for the Gemini protocol"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Dave Pearson", email = "davep@davep.org" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.12"
|
|
10
|
+
dependencies = []
|
|
11
|
+
license = "MIT"
|
|
12
|
+
keywords = [
|
|
13
|
+
"API",
|
|
14
|
+
"async",
|
|
15
|
+
"client",
|
|
16
|
+
"library",
|
|
17
|
+
"Gemini",
|
|
18
|
+
]
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Development Status :: 3 - Alpha",
|
|
21
|
+
"Operating System :: OS Independent",
|
|
22
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
23
|
+
"Programming Language :: Python :: 3",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Programming Language :: Python :: 3.13",
|
|
26
|
+
"Programming Language :: Python :: 3.14",
|
|
27
|
+
"Topic :: Software Development :: Libraries",
|
|
28
|
+
"Typing :: Typed",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://wasat.davep.dev/"
|
|
33
|
+
Repository = "https://github.com/davep/wasat"
|
|
34
|
+
Documentation = "https://wasat.davep.dev/"
|
|
35
|
+
Source = "https://github.com/davep/wasat"
|
|
36
|
+
Issues = "https://github.com/davep/wasat/issues"
|
|
37
|
+
Discussions = "https://github.com/davep/wasat/discussions"
|
|
38
|
+
|
|
39
|
+
[project.scripts]
|
|
40
|
+
wasat = "wasat.__main__:main"
|
|
41
|
+
|
|
42
|
+
[build-system]
|
|
43
|
+
requires = ["uv_build>=0.11.21,<0.12.0"]
|
|
44
|
+
build-backend = "uv_build"
|
|
45
|
+
|
|
46
|
+
[[tool.uv.index]]
|
|
47
|
+
name = "testpypi"
|
|
48
|
+
url = "https://test.pypi.org/simple/"
|
|
49
|
+
publish-url = "https://test.pypi.org/legacy/"
|
|
50
|
+
explicit = true
|
|
51
|
+
|
|
52
|
+
[dependency-groups]
|
|
53
|
+
dev = [
|
|
54
|
+
"codespell>=2.4.2",
|
|
55
|
+
"mypy>=2.1.0",
|
|
56
|
+
"pre-commit>=4.6.0",
|
|
57
|
+
"ruff>=0.15.17",
|
|
58
|
+
]
|
|
59
|
+
docs = [
|
|
60
|
+
"mkdocs>=1.6.1,<2",
|
|
61
|
+
"mkdocs-material>=9.7.6",
|
|
62
|
+
"mkdocstrings[python]>=1.0.4",
|
|
63
|
+
]
|
|
64
|
+
test = [
|
|
65
|
+
"pytest>=9.1.0",
|
|
66
|
+
"pytest-cov>=7.1.0",
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
[tool.pyright]
|
|
70
|
+
venvPath="."
|
|
71
|
+
venv=".venv"
|
|
72
|
+
exclude=[".venv"]
|
|
73
|
+
|
|
74
|
+
[tool.ruff.lint]
|
|
75
|
+
select = [
|
|
76
|
+
# pycodestyle
|
|
77
|
+
"E",
|
|
78
|
+
# Pyflakes
|
|
79
|
+
"F",
|
|
80
|
+
# pyupgrade
|
|
81
|
+
"UP",
|
|
82
|
+
# flake8-bugbear
|
|
83
|
+
"B",
|
|
84
|
+
# flake8-simplify
|
|
85
|
+
"SIM",
|
|
86
|
+
# isort
|
|
87
|
+
"I",
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
[tool.ruff.lint.pycodestyle]
|
|
91
|
+
max-line-length = 120
|
|
92
|
+
|
|
93
|
+
[tool.coverage.run]
|
|
94
|
+
omit = [
|
|
95
|
+
"tests/*"
|
|
96
|
+
]
|
|
97
|
+
|
|
98
|
+
[tool.coverage.report]
|
|
99
|
+
exclude_lines = [
|
|
100
|
+
"pragma: no cover",
|
|
101
|
+
"def __repr__",
|
|
102
|
+
"raise AssertionError",
|
|
103
|
+
"raise NotImplementedError",
|
|
104
|
+
"if __name__ == .__main__.:",
|
|
105
|
+
"if TYPE_CHECKING:",
|
|
106
|
+
"class .*\\bProtocol\\):",
|
|
107
|
+
"@(abc\\.)?abstractmethod",
|
|
108
|
+
]
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Wasat: An asynchronous, type-hinted client library for the Gemini Protocol."""
|
|
2
|
+
|
|
3
|
+
##############################################################################
|
|
4
|
+
# Python imports.
|
|
5
|
+
from importlib.metadata import version
|
|
6
|
+
|
|
7
|
+
######################################################################
|
|
8
|
+
# Main library information.
|
|
9
|
+
__author__ = "Dave Pearson"
|
|
10
|
+
__copyright__ = "Copyright 2026, Dave Pearson"
|
|
11
|
+
__credits__ = ["Dave Pearson"]
|
|
12
|
+
__maintainer__ = "Dave Pearson"
|
|
13
|
+
__email__ = "davep@davep.org"
|
|
14
|
+
__version__: str = version("wasat")
|
|
15
|
+
__licence__ = "MIT"
|
|
16
|
+
|
|
17
|
+
##############################################################################
|
|
18
|
+
# Local imports.
|
|
19
|
+
from .client import Client
|
|
20
|
+
from .exceptions import (
|
|
21
|
+
ConnectionError,
|
|
22
|
+
ProtocolError,
|
|
23
|
+
RedirectError,
|
|
24
|
+
SecurityError,
|
|
25
|
+
URIError,
|
|
26
|
+
WasatError,
|
|
27
|
+
)
|
|
28
|
+
from .response import Response
|
|
29
|
+
from .status import StatusCode
|
|
30
|
+
from .trust import FileTrustStore, TrustStore
|
|
31
|
+
from .uri import GEMINI_DEFAULT_PORT, GeminiURI
|
|
32
|
+
|
|
33
|
+
##############################################################################
|
|
34
|
+
# Exports.
|
|
35
|
+
__all__ = [
|
|
36
|
+
"Client",
|
|
37
|
+
"Response",
|
|
38
|
+
"StatusCode",
|
|
39
|
+
"GeminiURI",
|
|
40
|
+
"GEMINI_DEFAULT_PORT",
|
|
41
|
+
"TrustStore",
|
|
42
|
+
"FileTrustStore",
|
|
43
|
+
"WasatError",
|
|
44
|
+
"URIError",
|
|
45
|
+
"ProtocolError",
|
|
46
|
+
"ConnectionError",
|
|
47
|
+
"SecurityError",
|
|
48
|
+
"RedirectError",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
### __init__.py ends here
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Entry point for executing the wasat package directly."""
|
|
2
|
+
|
|
3
|
+
##############################################################################
|
|
4
|
+
# Python imports.
|
|
5
|
+
from argparse import ArgumentParser, Namespace
|
|
6
|
+
from asyncio import run
|
|
7
|
+
from sys import exit, stderr
|
|
8
|
+
|
|
9
|
+
##############################################################################
|
|
10
|
+
# Local imports.
|
|
11
|
+
from . import Client, WasatError, __version__
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
##############################################################################
|
|
15
|
+
def get_args() -> Namespace:
|
|
16
|
+
"""Parse command-line arguments.
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
Namespace: Parsed command-line arguments.
|
|
20
|
+
"""
|
|
21
|
+
parser = ArgumentParser(
|
|
22
|
+
prog="wasat",
|
|
23
|
+
description="An asynchronous client library and CLI for the Gemini protocol.",
|
|
24
|
+
)
|
|
25
|
+
parser.add_argument(
|
|
26
|
+
"url",
|
|
27
|
+
help="The Gemini URL to request.",
|
|
28
|
+
)
|
|
29
|
+
parser.add_argument(
|
|
30
|
+
"--version",
|
|
31
|
+
action="version",
|
|
32
|
+
version=f"%(prog)s {__version__}",
|
|
33
|
+
)
|
|
34
|
+
parser.add_argument(
|
|
35
|
+
"-v",
|
|
36
|
+
"--verbose",
|
|
37
|
+
action="store_true",
|
|
38
|
+
help="Enable verbose output.",
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
return parser.parse_args()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
##############################################################################
|
|
45
|
+
async def run_cli() -> None:
|
|
46
|
+
"""Run the Wasat CLI asynchronously."""
|
|
47
|
+
args = get_args()
|
|
48
|
+
|
|
49
|
+
try:
|
|
50
|
+
async with await Client(verify_mode="tofu").request(args.url) as response:
|
|
51
|
+
if args.verbose or not response.status.is_success:
|
|
52
|
+
print("--- Gemini Response ---")
|
|
53
|
+
print(f"Status: {response.status.value} ({response.status.name})")
|
|
54
|
+
print(f"Meta: {response.meta}")
|
|
55
|
+
print("-----------------------")
|
|
56
|
+
if response.status.is_success:
|
|
57
|
+
print(await response.text())
|
|
58
|
+
else:
|
|
59
|
+
exit(1)
|
|
60
|
+
except WasatError as e:
|
|
61
|
+
print(f"Error: {e}", file=stderr)
|
|
62
|
+
exit(1)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
##############################################################################
|
|
66
|
+
def main() -> None:
|
|
67
|
+
"""CLI entry point."""
|
|
68
|
+
try:
|
|
69
|
+
run(run_cli())
|
|
70
|
+
except KeyboardInterrupt:
|
|
71
|
+
exit(130)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
##############################################################################
|
|
75
|
+
if __name__ == "__main__":
|
|
76
|
+
main()
|
|
77
|
+
|
|
78
|
+
### __main__.py ends here
|