chore: rename project coven → hack-house ⛧

Rebrand the Rust client crate (coven/ → hh/, package+binary "hack-house"),
README, CLI strings, and branch (coven → hack-house). Gitea repo renamed
cmd-chat → hack-house to match. Crypto/server logic unchanged; selftest +
golden-vector test still green, binary is now `hack-house`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
leetcrypt
2026-05-30 13:29:14 -07:00
parent 82a04f3e12
commit bb1d662ee1
2730 changed files with 712933 additions and 46 deletions
@@ -0,0 +1,97 @@
import asyncio
from collections.abc import Awaitable, MutableMapping
from typing import Any, Callable
from sanic.exceptions import BadRequest
from sanic.models.protocol_types import TransportProtocol
from sanic.server.websockets.connection import WebSocketConnection
ASGIScope = MutableMapping[str, Any]
ASGIMessage = MutableMapping[str, Any]
ASGISend = Callable[[ASGIMessage], Awaitable[None]]
ASGIReceive = Callable[[], Awaitable[ASGIMessage]]
class MockProtocol: # no cov
def __init__(self, transport: "MockTransport", loop):
self.transport = transport
self._not_paused = asyncio.Event()
self._not_paused.set()
self._complete = asyncio.Event()
def pause_writing(self) -> None:
self._not_paused.clear()
def resume_writing(self) -> None:
self._not_paused.set()
async def complete(self) -> None:
self._not_paused.set()
await self.transport.send(
{"type": "http.response.body", "body": b"", "more_body": False}
)
@property
def is_complete(self) -> bool:
return self._complete.is_set()
async def push_data(self, data: bytes) -> None:
if not self.is_complete:
await self.transport.send(
{"type": "http.response.body", "body": data, "more_body": True}
)
async def drain(self) -> None:
await self._not_paused.wait()
class MockTransport(TransportProtocol): # no cov
_protocol: MockProtocol | None
def __init__(
self, scope: ASGIScope, receive: ASGIReceive, send: ASGISend
) -> None:
self.scope = scope
self._receive = receive
self._send = send
self._protocol = None
self.loop: asyncio.AbstractEventLoop | None = None
def get_protocol(self) -> MockProtocol: # type: ignore
if not self._protocol:
self._protocol = MockProtocol(self, self.loop)
return self._protocol
def get_extra_info(self, info: str, default=None) -> str | bool | None:
if info == "peername":
return self.scope.get("client")
elif info == "sslcontext":
return self.scope.get("scheme") in ["https", "wss"]
return default
def get_websocket_connection(self) -> WebSocketConnection:
try:
return self._websocket_connection
except AttributeError:
raise BadRequest("Improper websocket connection.")
def create_websocket_connection(
self, send: ASGISend, receive: ASGIReceive
) -> WebSocketConnection:
self._websocket_connection = WebSocketConnection(
send, receive, self.scope.get("subprotocols", [])
)
return self._websocket_connection
def add_task(self) -> None:
raise NotImplementedError
async def send(self, data) -> None:
# TODO:
# - Validation on data and that it is formatted properly and is valid
await self._send(data)
async def receive(self) -> ASGIMessage:
return await self._receive()
@@ -0,0 +1,69 @@
from typing import Any, NamedTuple
class REPLLocal(NamedTuple):
var: Any
name: str
desc: str
class REPLContext:
BUILTINS = {
"app": "The Sanic application instance",
"sanic": "The Sanic module",
"do": "An async function to fake a request to the application",
"client": "A client to access the Sanic app instance using httpx",
}
def __init__(self):
self._locals: set[REPLLocal] = set()
def add(
self,
var: Any,
name: str | None = None,
desc: str | None = None,
):
"""Add a local variable to be available in REPL context.
Args:
var (Any): A module, class, object or a class.
name (Optional[str], optional): An alias for the local. Defaults to None.
desc (Optional[str], optional): A brief description for the local. Defaults to None.
""" # noqa: E501
if name is None:
try:
name = var.__name__
except AttributeError:
name = var.__class__.__name__
if desc is None:
try:
desc = var.__doc__ or ""
except AttributeError:
desc = str(type(var))
assert isinstance(desc, str) and isinstance(
name, str
) # Just to make mypy happy
if name in self.BUILTINS:
raise ValueError(f"Cannot override built-in variable: {name}")
desc = self._truncate(desc)
self._locals.add(REPLLocal(var, name, desc))
def __setattr__(self, name: str, value: Any):
if name.startswith("_"):
super().__setattr__(name, value)
else:
self.add(value, name)
def __iter__(self):
return iter(self._locals)
@staticmethod
def _truncate(s: str, limit: int = 40) -> str:
s = s.replace("\n", " ")
return s[:limit] + "..." if len(s) > limit else s
@@ -0,0 +1,80 @@
from collections.abc import Iterable
from pathlib import Path
from typing import Callable, NamedTuple
from sanic.handlers.directory import DirectoryHandler
from sanic.models.handler_types import (
ErrorMiddlewareType,
ListenerType,
MiddlewareType,
SignalHandler,
)
from sanic.types import HashableDict
class FutureRoute(NamedTuple):
handler: str
uri: str
methods: Iterable[str] | None
host: str | list[str]
strict_slashes: bool
stream: bool
version: int | None
name: str
ignore_body: bool
websocket: bool
subprotocols: list[str] | None
unquote: bool
static: bool
version_prefix: str
error_format: str | None
route_context: HashableDict
class FutureListener(NamedTuple):
listener: ListenerType
event: str
priority: int
class FutureMiddleware(NamedTuple):
middleware: MiddlewareType
attach_to: str
class FutureException(NamedTuple):
handler: ErrorMiddlewareType
exceptions: list[BaseException]
class FutureStatic(NamedTuple):
uri: str
file_or_directory: Path
pattern: str
use_modified_since: bool
use_content_range: bool
stream_large_files: bool | int
name: str
host: str | None
strict_slashes: bool | None
content_type: str | None
resource_type: str | None
directory_handler: DirectoryHandler
follow_external_symlink_files: bool
follow_external_symlink_dirs: bool
class FutureSignal(NamedTuple):
handler: SignalHandler
event: str
condition: dict[str, str] | None
exclusive: bool
priority: int
class FutureRegistry(set): ...
class FutureCommand(NamedTuple):
name: str
func: Callable
@@ -0,0 +1,30 @@
from asyncio.events import AbstractEventLoop
from collections.abc import Coroutine
from typing import Any, Callable, TypeVar
import sanic
from sanic import request
from sanic.response import BaseHTTPResponse, HTTPResponse
Sanic = TypeVar("Sanic", bound="sanic.Sanic")
Request = TypeVar("Request", bound="request.Request")
MiddlewareResponse = (
HTTPResponse | None | Coroutine[Any, Any, HTTPResponse | None]
)
RequestMiddlewareType = Callable[[Request], MiddlewareResponse]
ResponseMiddlewareType = Callable[
[Request, BaseHTTPResponse], MiddlewareResponse
]
ErrorMiddlewareType = Callable[
[Request, BaseException], Coroutine[Any, Any, None] | None
]
MiddlewareType = RequestMiddlewareType | ResponseMiddlewareType
ListenerType = (
Callable[[Sanic], Coroutine[Any, Any, None] | None]
| Callable[[Sanic, AbstractEventLoop], Coroutine[Any, Any, None] | None]
)
RouteHandler = Callable[..., Coroutine[Any, Any, HTTPResponse | None]]
SignalHandler = Callable[..., Coroutine[Any, Any, None]]
@@ -0,0 +1,34 @@
from __future__ import annotations
from base64 import b64decode
from dataclasses import dataclass, field
@dataclass()
class Credentials:
auth_type: str | None
token: str | None
_username: str | None = field(default=None)
_password: str | None = field(default=None)
def __post_init__(self):
if self._auth_is_basic:
self._username, self._password = (
b64decode(self.token.encode("utf-8")).decode().split(":")
)
@property
def username(self):
if not self._auth_is_basic:
raise AttributeError("Username is available for Basic Auth only")
return self._username
@property
def password(self):
if not self._auth_is_basic:
raise AttributeError("Password is available for Basic Auth only")
return self._password
@property
def _auth_is_basic(self) -> bool:
return self.auth_type == "Basic"
@@ -0,0 +1,29 @@
from __future__ import annotations
from asyncio import BaseTransport
from typing import TYPE_CHECKING, Protocol
if TYPE_CHECKING:
from sanic.http.constants import HTTP
from sanic.models.asgi import ASGIScope
class HTMLProtocol(Protocol):
def __html__(self) -> str | bytes: ...
def _repr_html_(self) -> str | bytes: ...
class Range(Protocol):
start: int | None
end: int | None
size: int | None
total: int | None
__slots__ = ()
class TransportProtocol(BaseTransport):
scope: ASGIScope
version: HTTP
__slots__ = ()
@@ -0,0 +1,71 @@
from __future__ import annotations
from ssl import SSLContext, SSLObject
from types import SimpleNamespace
from typing import Any
from sanic.models.protocol_types import TransportProtocol
class Signal:
stopped = False
class ConnInfo:
"""
Local and remote addresses and SSL status info.
"""
__slots__ = (
"client_port",
"client",
"client_ip",
"ctx",
"lost",
"peername",
"server_port",
"server",
"server_name",
"sockname",
"ssl",
"cert",
"network_paths",
)
def __init__(self, transport: TransportProtocol, unix=None):
self.ctx = SimpleNamespace()
self.lost = False
self.peername: tuple[str, int] | None = None
self.server = self.client = ""
self.server_port = self.client_port = 0
self.client_ip = ""
self.sockname = addr = transport.get_extra_info("sockname")
self.ssl = False
self.server_name = ""
self.cert: dict[str, Any] = {}
self.network_paths: list[Any] = []
sslobj: SSLObject | None = transport.get_extra_info("ssl_object") # type: ignore
sslctx: SSLContext | None = transport.get_extra_info("ssl_context") # type: ignore
if sslobj:
self.ssl = True
self.server_name = getattr(sslobj, "sanic_server_name", None) or ""
self.cert = dict(getattr(sslobj.context, "sanic", {}))
if sslctx and not self.cert:
self.cert = dict(getattr(sslctx, "sanic", {}))
if isinstance(addr, str): # UNIX socket
self.server = unix or addr
return
# IPv4 (ip, port) or IPv6 (ip, port, flowinfo, scopeid)
if isinstance(addr, tuple):
self.server = addr[0] if len(addr) == 2 else f"[{addr[0]}]"
self.server_port = addr[1]
# self.server gets non-standard port appended
if addr[1] != (443 if self.ssl else 80):
self.server = f"{self.server}:{addr[1]}"
self.peername = addr = transport.get_extra_info("peername")
self.network_paths = transport.get_extra_info("network_paths") # type: ignore
if isinstance(addr, tuple):
self.client = addr[0] if len(addr) == 2 else f"[{addr[0]}]"
self.client_ip = addr[0]
self.client_port = addr[1]