feat(debug): support /debug [nous|local] in the CLI/TUI slash command
The --nous flag was only wired into the argparse `hermes debug share` subcommand. The /debug slash command (classic CLI + TUI, both via process_command -> _handle_debug_command) built a hardcoded args namespace with no `nous` attribute, so it always took the default paste.rs path. Pass cmd_original through to _handle_debug_command and parse an optional destination word: /debug -> public paste (default, unchanged) /debug nous -> Nous-internal S3 /debug local -> stdout, no upload local wins over nous (never touches the network); unknown words fall back to the default. Add args_hint="[nous|local]" so help/autocomplete surface it. New TestDebugSlashCommand covers the parsing + dispatch.
This commit is contained in:
parent
89653db403
commit
98d550e035
4 changed files with 78 additions and 5 deletions
2
cli.py
2
cli.py
|
|
@ -8385,7 +8385,7 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin):
|
|||
elif canonical == "copy":
|
||||
self._handle_copy_command(cmd_original)
|
||||
elif canonical == "debug":
|
||||
self._handle_debug_command()
|
||||
self._handle_debug_command(cmd_original)
|
||||
elif canonical == "update":
|
||||
if self._handle_update_command():
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -2575,12 +2575,25 @@ class CLICommandsMixin:
|
|||
else:
|
||||
_cprint(f" {_ACCENT}✓ {feature_name} set to {label} (session only){_RST}")
|
||||
|
||||
def _handle_debug_command(self):
|
||||
"""Handle /debug — upload debug report + logs and print paste URLs."""
|
||||
def _handle_debug_command(self, cmd_original: str = ""):
|
||||
"""Handle /debug — upload debug report + logs and print share URLs.
|
||||
|
||||
Accepts optional destination words after the command:
|
||||
|
||||
- ``/debug`` → upload to the public paste service (default)
|
||||
- ``/debug nous`` → upload to Nous-internal storage (private, staff-only)
|
||||
- ``/debug local`` → render the report to stdout, no upload
|
||||
|
||||
``nous`` and ``local`` are mutually exclusive; if both are given,
|
||||
``local`` wins (it never touches the network).
|
||||
"""
|
||||
from hermes_cli.debug import run_debug_share
|
||||
from types import SimpleNamespace
|
||||
|
||||
args = SimpleNamespace(lines=200, expire=7, local=False)
|
||||
words = {w.lower() for w in cmd_original.split()[1:]}
|
||||
local = "local" in words
|
||||
nous = "nous" in words and not local
|
||||
args = SimpleNamespace(lines=200, expire=7, local=local, nous=nous)
|
||||
run_debug_share(args)
|
||||
|
||||
def _handle_update_command(self) -> bool:
|
||||
|
|
|
|||
|
|
@ -246,7 +246,8 @@ COMMAND_REGISTRY: list[CommandDef] = [
|
|||
cli_only=True, args_hint="<path>"),
|
||||
CommandDef("update", "Update Hermes Agent to the latest version", "Info"),
|
||||
CommandDef("version", "Show Hermes Agent version", "Info", aliases=("v",)),
|
||||
CommandDef("debug", "Upload debug report (system info + logs) and get shareable links", "Info"),
|
||||
CommandDef("debug", "Upload debug report (system info + logs) and get shareable links", "Info",
|
||||
args_hint="[nous|local]"),
|
||||
|
||||
# Exit
|
||||
CommandDef("quit", "Exit the CLI (use --delete to also remove session history)", "Exit",
|
||||
|
|
|
|||
|
|
@ -1570,3 +1570,62 @@ class TestRunDebugShareNous:
|
|||
run_debug_share(self._args())
|
||||
paste.assert_not_called()
|
||||
|
||||
|
||||
class TestDebugSlashCommand:
|
||||
"""`/debug [nous|local]` parsing in the CLI/TUI handler.
|
||||
|
||||
The classic CLI and the TUI slash worker both dispatch through
|
||||
``HermesCLI.process_command`` → ``_handle_debug_command(cmd_original)``,
|
||||
which parses an optional destination word and builds the args namespace
|
||||
handed to ``run_debug_share``.
|
||||
"""
|
||||
|
||||
def _handler(self):
|
||||
from hermes_cli.cli_commands_mixin import CLICommandsMixin
|
||||
|
||||
class _Stub(CLICommandsMixin):
|
||||
pass
|
||||
|
||||
return _Stub()._handle_debug_command
|
||||
|
||||
def _captured(self, cmd_original):
|
||||
captured = {}
|
||||
|
||||
def _fake_run(args):
|
||||
captured.update(vars(args))
|
||||
|
||||
with patch("hermes_cli.debug.run_debug_share", _fake_run):
|
||||
self._handler()(cmd_original)
|
||||
return captured
|
||||
|
||||
def test_bare_debug_defaults_to_paste(self):
|
||||
c = self._captured("/debug")
|
||||
assert c["nous"] is False and c["local"] is False
|
||||
assert c["lines"] == 200 and c["expire"] == 7
|
||||
|
||||
def test_nous_word_sets_nous(self):
|
||||
c = self._captured("/debug nous")
|
||||
assert c["nous"] is True and c["local"] is False
|
||||
|
||||
def test_local_word_sets_local(self):
|
||||
c = self._captured("/debug local")
|
||||
assert c["local"] is True and c["nous"] is False
|
||||
|
||||
def test_word_parsing_is_case_insensitive(self):
|
||||
c = self._captured("/debug NOUS")
|
||||
assert c["nous"] is True
|
||||
|
||||
def test_local_wins_over_nous(self):
|
||||
# local never touches the network, so it takes precedence.
|
||||
c = self._captured("/debug nous local")
|
||||
assert c["local"] is True and c["nous"] is False
|
||||
|
||||
def test_unknown_word_falls_back_to_default(self):
|
||||
c = self._captured("/debug paste")
|
||||
assert c["nous"] is False and c["local"] is False
|
||||
|
||||
def test_no_arg_default_keyword(self):
|
||||
# Calling with no cmd_original (legacy callers) must still work.
|
||||
c = self._captured("")
|
||||
assert c["nous"] is False and c["local"] is False
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue