feat(cli): add interactive TUI chat
Add a Textual chat interface with threaded SSE streaming, slash commands, interrupt handling, and bare nexus dispatch. Package it behind the tui extra, document usage, and cover command routing, dependencies, and headless interaction with tests.
This commit is contained in:
+36
-3
@@ -398,6 +398,24 @@ def cmd_monitor(args) -> int:
|
||||
)
|
||||
|
||||
|
||||
def cmd_tui(args) -> int:
|
||||
"""Interactive Hermes/OpenClaw-style chat TUI (requires nexusos-ai[tui])."""
|
||||
if not sys.stdin.isatty() or not sys.stdout.isatty():
|
||||
print(
|
||||
"The TUI needs a terminal. Use: nexus chat send \"…\"\n"
|
||||
"Or run `nexus` in an interactive shell.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 2
|
||||
try:
|
||||
from .tui_app import run_tui
|
||||
except ImportError as exc:
|
||||
print(str(exc), file=sys.stderr)
|
||||
return 2
|
||||
api = getattr(args, "api_url", None) or settings.api_url
|
||||
return run_tui(api_url=api)
|
||||
|
||||
|
||||
def _target_flag(target: str | None):
|
||||
return {
|
||||
"memory": "--memory",
|
||||
@@ -722,10 +740,20 @@ def _port(value: str) -> int:
|
||||
|
||||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(prog="nexus", description="NexusOS local AI runtime and API client")
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="nexus",
|
||||
description=(
|
||||
"NexusOS local AI runtime and API client. "
|
||||
"With no subcommand, opens the interactive TUI (needs nexusos-ai[tui])."
|
||||
),
|
||||
)
|
||||
parser.add_argument("--version", action="version", version=f"NexusOS {settings.version}")
|
||||
parser.add_argument("--api-url", help="override the NexusOS backend URL for this command")
|
||||
sub = parser.add_subparsers(dest="command", required=True)
|
||||
# Bare `nexus` → TUI. Subcommands remain for scripts and one-shots.
|
||||
sub = parser.add_subparsers(dest="command", required=False)
|
||||
|
||||
p = sub.add_parser("tui", help="interactive chat TUI (default when no subcommand)")
|
||||
p.set_defaults(fn=cmd_tui)
|
||||
|
||||
p = sub.add_parser("init", help="create user state and seed default playbooks"); _add_json(p); p.set_defaults(fn=cmd_init)
|
||||
p = sub.add_parser("paths", help="show resolved package and writable paths"); _add_json(p); p.set_defaults(fn=cmd_paths)
|
||||
@@ -827,7 +855,12 @@ def _normalize_legacy_argv(argv) -> list[str]:
|
||||
|
||||
def main(argv=None) -> int:
|
||||
parser = build_parser()
|
||||
args = parser.parse_args(_normalize_legacy_argv(argv))
|
||||
argv = _normalize_legacy_argv(argv)
|
||||
args = parser.parse_args(argv)
|
||||
if not getattr(args, "command", None):
|
||||
# Bare `nexus` / `ncp` / `nexusos` → interactive TUI.
|
||||
args.command = "tui"
|
||||
args.fn = cmd_tui
|
||||
if args.command == "config":
|
||||
if args.action in ("get", "unset") and not args.key:
|
||||
parser.error(f"config {args.action} requires KEY")
|
||||
|
||||
Reference in New Issue
Block a user