refactor: extract shared helpers to deduplicate repeated code patterns (#7917)
* refactor: add shared helper modules for code deduplication
New modules:
- gateway/platforms/helpers.py: MessageDeduplicator, TextBatchAggregator,
strip_markdown, ThreadParticipationTracker, redact_phone
- hermes_cli/cli_output.py: print_info/success/warning/error, prompt helpers
- tools/path_security.py: validate_within_dir, has_traversal_component
- utils.py additions: safe_json_loads, read_json_file, read_jsonl,
append_jsonl, env_str/lower/int/bool helpers
- hermes_constants.py additions: get_config_path, get_skills_dir,
get_logs_dir, get_env_path
* refactor: migrate gateway adapters to shared helpers
- MessageDeduplicator: discord, slack, dingtalk, wecom, weixin, mattermost
- strip_markdown: bluebubbles, feishu, sms
- redact_phone: sms, signal
- ThreadParticipationTracker: discord, matrix
- _acquire/_release_platform_lock: telegram, discord, slack, whatsapp,
signal, weixin
Net -316 lines across 19 files.
* refactor: migrate CLI modules to shared helpers
- tools_config.py: use cli_output print/prompt + curses_radiolist (-117 lines)
- setup.py: use cli_output print helpers + curses_radiolist (-101 lines)
- mcp_config.py: use cli_output prompt (-15 lines)
- memory_setup.py: use curses_radiolist (-86 lines)
Net -263 lines across 5 files.
* refactor: migrate to shared utility helpers
- safe_json_loads: agent/display.py (4 sites)
- get_config_path: skill_utils.py, hermes_logging.py, hermes_time.py
- get_skills_dir: skill_utils.py, prompt_builder.py
- Token estimation dedup: skills_tool.py imports from model_metadata
- Path security: skills_tool, cronjob_tools, skill_manager_tool, credential_files
- Non-atomic YAML writes: doctor.py, config.py now use atomic_yaml_write
- Platform dict: new platforms.py, skills_config + tools_config derive from it
- Anthropic key: new get_anthropic_key() in auth.py, used by doctor/status/config/main
* test: update tests for shared helper migrations
- test_dingtalk: use _dedup.is_duplicate() instead of _is_duplicate()
- test_mattermost: use _dedup instead of _seen_posts/_prune_seen
- test_signal: import redact_phone from helpers instead of signal
- test_discord_connect: _platform_lock_identity instead of _token_lock_identity
- test_telegram_conflict: updated lock error message format
- test_skill_manager_tool: 'escapes' instead of 'boundary' in error msgs
2026-04-11 13:59:52 -07:00
|
|
|
"""Shared CLI output helpers for Hermes CLI modules.
|
|
|
|
|
|
|
|
|
|
Extracts the identical ``print_info/success/warning/error`` and ``prompt()``
|
|
|
|
|
functions previously duplicated across setup.py, tools_config.py,
|
|
|
|
|
mcp_config.py, and memory_setup.py.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from hermes_cli.colors import Colors, color
|
2026-05-24 17:15:03 -06:00
|
|
|
from hermes_cli.secret_prompt import masked_secret_prompt
|
refactor: extract shared helpers to deduplicate repeated code patterns (#7917)
* refactor: add shared helper modules for code deduplication
New modules:
- gateway/platforms/helpers.py: MessageDeduplicator, TextBatchAggregator,
strip_markdown, ThreadParticipationTracker, redact_phone
- hermes_cli/cli_output.py: print_info/success/warning/error, prompt helpers
- tools/path_security.py: validate_within_dir, has_traversal_component
- utils.py additions: safe_json_loads, read_json_file, read_jsonl,
append_jsonl, env_str/lower/int/bool helpers
- hermes_constants.py additions: get_config_path, get_skills_dir,
get_logs_dir, get_env_path
* refactor: migrate gateway adapters to shared helpers
- MessageDeduplicator: discord, slack, dingtalk, wecom, weixin, mattermost
- strip_markdown: bluebubbles, feishu, sms
- redact_phone: sms, signal
- ThreadParticipationTracker: discord, matrix
- _acquire/_release_platform_lock: telegram, discord, slack, whatsapp,
signal, weixin
Net -316 lines across 19 files.
* refactor: migrate CLI modules to shared helpers
- tools_config.py: use cli_output print/prompt + curses_radiolist (-117 lines)
- setup.py: use cli_output print helpers + curses_radiolist (-101 lines)
- mcp_config.py: use cli_output prompt (-15 lines)
- memory_setup.py: use curses_radiolist (-86 lines)
Net -263 lines across 5 files.
* refactor: migrate to shared utility helpers
- safe_json_loads: agent/display.py (4 sites)
- get_config_path: skill_utils.py, hermes_logging.py, hermes_time.py
- get_skills_dir: skill_utils.py, prompt_builder.py
- Token estimation dedup: skills_tool.py imports from model_metadata
- Path security: skills_tool, cronjob_tools, skill_manager_tool, credential_files
- Non-atomic YAML writes: doctor.py, config.py now use atomic_yaml_write
- Platform dict: new platforms.py, skills_config + tools_config derive from it
- Anthropic key: new get_anthropic_key() in auth.py, used by doctor/status/config/main
* test: update tests for shared helper migrations
- test_dingtalk: use _dedup.is_duplicate() instead of _is_duplicate()
- test_mattermost: use _dedup instead of _seen_posts/_prune_seen
- test_signal: import redact_phone from helpers instead of signal
- test_discord_connect: _platform_lock_identity instead of _token_lock_identity
- test_telegram_conflict: updated lock error message format
- test_skill_manager_tool: 'escapes' instead of 'boundary' in error msgs
2026-04-11 13:59:52 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
# ─── Print Helpers ────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def print_info(text: str) -> None:
|
|
|
|
|
"""Print a dim informational message."""
|
|
|
|
|
print(color(f" {text}", Colors.DIM))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def print_success(text: str) -> None:
|
|
|
|
|
"""Print a green success message with ✓ prefix."""
|
|
|
|
|
print(color(f"✓ {text}", Colors.GREEN))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def print_warning(text: str) -> None:
|
|
|
|
|
"""Print a yellow warning message with ⚠ prefix."""
|
|
|
|
|
print(color(f"⚠ {text}", Colors.YELLOW))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def print_error(text: str) -> None:
|
|
|
|
|
"""Print a red error message with ✗ prefix."""
|
|
|
|
|
print(color(f"✗ {text}", Colors.RED))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def print_header(text: str) -> None:
|
|
|
|
|
"""Print a bold yellow header."""
|
|
|
|
|
print(color(f"\n {text}", Colors.YELLOW))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ─── Input Prompts ────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def prompt(
|
|
|
|
|
question: str,
|
|
|
|
|
default: str | None = None,
|
|
|
|
|
password: bool = False,
|
|
|
|
|
) -> str:
|
|
|
|
|
"""Prompt the user for input with optional default and password masking.
|
|
|
|
|
|
|
|
|
|
Replaces the four independent ``_prompt()`` / ``prompt()`` implementations
|
|
|
|
|
in setup.py, tools_config.py, mcp_config.py, and memory_setup.py.
|
|
|
|
|
|
|
|
|
|
Returns the user's input (stripped), or *default* if the user presses Enter.
|
|
|
|
|
Returns empty string on Ctrl-C or EOF.
|
|
|
|
|
"""
|
|
|
|
|
suffix = f" [{default}]" if default else ""
|
|
|
|
|
display = color(f" {question}{suffix}: ", Colors.YELLOW)
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
if password:
|
2026-05-24 17:15:03 -06:00
|
|
|
value = masked_secret_prompt(display)
|
refactor: extract shared helpers to deduplicate repeated code patterns (#7917)
* refactor: add shared helper modules for code deduplication
New modules:
- gateway/platforms/helpers.py: MessageDeduplicator, TextBatchAggregator,
strip_markdown, ThreadParticipationTracker, redact_phone
- hermes_cli/cli_output.py: print_info/success/warning/error, prompt helpers
- tools/path_security.py: validate_within_dir, has_traversal_component
- utils.py additions: safe_json_loads, read_json_file, read_jsonl,
append_jsonl, env_str/lower/int/bool helpers
- hermes_constants.py additions: get_config_path, get_skills_dir,
get_logs_dir, get_env_path
* refactor: migrate gateway adapters to shared helpers
- MessageDeduplicator: discord, slack, dingtalk, wecom, weixin, mattermost
- strip_markdown: bluebubbles, feishu, sms
- redact_phone: sms, signal
- ThreadParticipationTracker: discord, matrix
- _acquire/_release_platform_lock: telegram, discord, slack, whatsapp,
signal, weixin
Net -316 lines across 19 files.
* refactor: migrate CLI modules to shared helpers
- tools_config.py: use cli_output print/prompt + curses_radiolist (-117 lines)
- setup.py: use cli_output print helpers + curses_radiolist (-101 lines)
- mcp_config.py: use cli_output prompt (-15 lines)
- memory_setup.py: use curses_radiolist (-86 lines)
Net -263 lines across 5 files.
* refactor: migrate to shared utility helpers
- safe_json_loads: agent/display.py (4 sites)
- get_config_path: skill_utils.py, hermes_logging.py, hermes_time.py
- get_skills_dir: skill_utils.py, prompt_builder.py
- Token estimation dedup: skills_tool.py imports from model_metadata
- Path security: skills_tool, cronjob_tools, skill_manager_tool, credential_files
- Non-atomic YAML writes: doctor.py, config.py now use atomic_yaml_write
- Platform dict: new platforms.py, skills_config + tools_config derive from it
- Anthropic key: new get_anthropic_key() in auth.py, used by doctor/status/config/main
* test: update tests for shared helper migrations
- test_dingtalk: use _dedup.is_duplicate() instead of _is_duplicate()
- test_mattermost: use _dedup instead of _seen_posts/_prune_seen
- test_signal: import redact_phone from helpers instead of signal
- test_discord_connect: _platform_lock_identity instead of _token_lock_identity
- test_telegram_conflict: updated lock error message format
- test_skill_manager_tool: 'escapes' instead of 'boundary' in error msgs
2026-04-11 13:59:52 -07:00
|
|
|
else:
|
|
|
|
|
value = input(display)
|
|
|
|
|
value = value.strip()
|
|
|
|
|
return value if value else (default or "")
|
|
|
|
|
except (KeyboardInterrupt, EOFError):
|
|
|
|
|
print()
|
|
|
|
|
return ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def prompt_yes_no(question: str, default: bool = True) -> bool:
|
|
|
|
|
"""Prompt for a yes/no answer. Returns bool."""
|
|
|
|
|
hint = "Y/n" if default else "y/N"
|
|
|
|
|
answer = prompt(f"{question} ({hint})")
|
|
|
|
|
if not answer:
|
|
|
|
|
return default
|
|
|
|
|
return answer.lower().startswith("y")
|