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>
101 lines
3.2 KiB
Python
101 lines
3.2 KiB
Python
import shutil
|
|
|
|
from argparse import ArgumentParser
|
|
from asyncio import run
|
|
from inspect import signature
|
|
from typing import Callable
|
|
|
|
from sanic import Sanic
|
|
from sanic.application.logo import get_logo
|
|
from sanic.cli.base import (
|
|
SanicArgumentParser,
|
|
SanicHelpFormatter,
|
|
)
|
|
|
|
|
|
def make_executor_parser(parser: ArgumentParser) -> None:
|
|
parser.add_argument(
|
|
"command",
|
|
help="Command to execute",
|
|
)
|
|
|
|
|
|
class ExecutorSubParser(ArgumentParser):
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
if not self.description:
|
|
self.description = ""
|
|
self.description = get_logo(True) + self.description
|
|
|
|
|
|
class Executor:
|
|
def __init__(self, app: Sanic, kwargs: dict) -> None:
|
|
self.app = app
|
|
self.kwargs = kwargs
|
|
self.commands = self._make_commands()
|
|
self.parser = self._make_parser()
|
|
|
|
def run(self, command: str, args: list[str]) -> None:
|
|
if command == "exec":
|
|
args = ["--help"]
|
|
parsed_args = self.parser.parse_args(args)
|
|
if command not in self.commands:
|
|
raise ValueError(f"Unknown command: {command}")
|
|
parsed_kwargs = vars(parsed_args)
|
|
parsed_kwargs.pop("command")
|
|
run(self.commands[command](**parsed_kwargs))
|
|
|
|
def _make_commands(self) -> dict[str, Callable]:
|
|
commands = {c.name: c.func for c in self.app._future_commands}
|
|
return commands
|
|
|
|
def _make_parser(self) -> SanicArgumentParser:
|
|
width = shutil.get_terminal_size().columns
|
|
parser = SanicArgumentParser(
|
|
prog="sanic",
|
|
description=get_logo(True),
|
|
formatter_class=lambda prog: SanicHelpFormatter(
|
|
prog,
|
|
max_help_position=36 if width > 96 else 24,
|
|
indent_increment=4,
|
|
width=None,
|
|
),
|
|
)
|
|
|
|
subparsers = parser.add_subparsers(
|
|
dest="command",
|
|
title=" Commands",
|
|
parser_class=ExecutorSubParser,
|
|
)
|
|
for command in self.app._future_commands:
|
|
sub = subparsers.add_parser(
|
|
command.name,
|
|
help=command.func.__doc__ or f"Execute {command.name}",
|
|
formatter_class=SanicHelpFormatter,
|
|
)
|
|
self._add_arguments(sub, command.func)
|
|
|
|
return parser
|
|
|
|
def _add_arguments(self, parser: ArgumentParser, func: Callable) -> None:
|
|
sig = signature(func)
|
|
for param in sig.parameters.values():
|
|
kwargs = {}
|
|
if param.default is not param.empty:
|
|
kwargs["default"] = param.default
|
|
# In Python 3.14+, argparse validates help strings and rejects
|
|
# non-string types. Convert annotations to string representation.
|
|
help_text = None
|
|
if param.annotation is not param.empty:
|
|
if isinstance(param.annotation, str):
|
|
help_text = param.annotation
|
|
else:
|
|
help_text = getattr(
|
|
param.annotation, "__name__", str(param.annotation)
|
|
)
|
|
parser.add_argument(
|
|
f"--{param.name}",
|
|
help=help_text,
|
|
**kwargs,
|
|
)
|