refactor: remove remaining redundant local imports (comprehensive sweep)

Full AST-based scan of all .py files to find every case where a module
or name is imported locally inside a function body but is already
available at module level.  This is the second pass — the first commit
handled the known cases from the lint report; this one catches
everything else.

Files changed (19):

  cli.py                — 16 removals: time as _time/_t/_tmod (×10),
                           re / re as _re (×2), os as _os, sys,
                           partial os from combo import,
                           from model_tools import get_tool_definitions
  gateway/run.py        —  8 removals: MessageEvent as _ME /
                           MessageType as _MT (×3), os as _os2,
                           MessageEvent+MessageType (×2), Platform,
                           BasePlatformAdapter as _BaseAdapter
  run_agent.py          —  6 removals: get_hermes_home as _ghh,
                           partial (contextlib, os as _os),
                           cleanup_vm, cleanup_browser,
                           set_interrupt as _sif (×2),
                           partial get_toolset_for_tool
  hermes_cli/main.py    —  4 removals: get_hermes_home, time as _time,
                           logging as _log, shutil
  hermes_cli/config.py  —  1 removal:  get_hermes_home as _ghome
  hermes_cli/runtime_provider.py
                        —  1 removal:  load_config as _load_bedrock_config
  hermes_cli/setup.py   —  2 removals: importlib.util (×2)
  hermes_cli/nous_subscription.py
                        —  1 removal:  from hermes_cli.config import load_config
  hermes_cli/tools_config.py
                        —  1 removal:  from hermes_cli.config import load_config, save_config
  cron/scheduler.py     —  3 removals: concurrent.futures, json as _json,
                           from hermes_cli.config import load_config
  batch_runner.py       —  1 removal:  list_distributions as get_all_dists
                           (kept print_distribution_info, not at top level)
  tools/send_message_tool.py
                        —  2 removals: import os (×2)
  tools/skills_tool.py  —  1 removal:  logging as _logging
  tools/browser_camofox.py
                        —  1 removal:  from hermes_cli.config import load_config
  tools/image_generation_tool.py
                        —  1 removal:  import fal_client
  environments/tool_context.py
                        —  1 removal:  concurrent.futures
  gateway/platforms/bluebubbles.py
                        —  1 removal:  httpx as _httpx
  gateway/platforms/whatsapp.py
                        —  1 removal:  import asyncio
  tui_gateway/server.py —  2 removals: from datetime import datetime,
                           import time

All alias references (_time, _t, _tmod, _re, _os, _os2, _json, _ghh,
_ghome, _sif, _ME, _MT, _BaseAdapter, _load_bedrock_config, _httpx,
_logging, _log, get_all_dists) updated to use the top-level names.
This commit is contained in:
alt-glitch 2026-04-21 12:46:31 +05:30 committed by Teknium
parent 1010e5fa3c
commit 28b3f49aaa
19 changed files with 43 additions and 101 deletions

View file

@ -1190,12 +1190,12 @@ def main(
""" """
# Handle list distributions # Handle list distributions
if list_distributions: if list_distributions:
from toolset_distributions import list_distributions as get_all_dists, print_distribution_info from toolset_distributions import print_distribution_info
print("📊 Available Toolset Distributions") print("📊 Available Toolset Distributions")
print("=" * 70) print("=" * 70)
all_dists = get_all_dists() all_dists = list_distributions()
for dist_name in sorted(all_dists.keys()): for dist_name in sorted(all_dists.keys()):
print_distribution_info(dist_name) print_distribution_info(dist_name)

53
cli.py
View file

@ -7159,7 +7159,6 @@ class HermesCLI:
# Refresh the agent's tool list so the model can call new tools # Refresh the agent's tool list so the model can call new tools
if self.agent is not None: if self.agent is not None:
from model_tools import get_tool_definitions
self.agent.tools = get_tool_definitions( self.agent.tools = get_tool_definitions(
enabled_toolsets=self.agent.enabled_toolsets enabled_toolsets=self.agent.enabled_toolsets
if hasattr(self.agent, "enabled_toolsets") else None, if hasattr(self.agent, "enabled_toolsets") else None,
@ -7242,7 +7241,6 @@ class HermesCLI:
full history of tool calls (not just the current one in the spinner). full history of tool calls (not just the current one in the spinner).
""" """
if event_type == "tool.completed": if event_type == "tool.completed":
import time as _time
self._tool_start_time = 0.0 self._tool_start_time = 0.0
# Print stacked scrollback line for "all" / "new" modes # Print stacked scrollback line for "all" / "new" modes
if function_name and self.tool_progress_mode in ("all", "new"): if function_name and self.tool_progress_mode in ("all", "new"):
@ -7271,7 +7269,6 @@ class HermesCLI:
if event_type != "tool.started": if event_type != "tool.started":
return return
if function_name and not function_name.startswith("_"): if function_name and not function_name.startswith("_"):
import time as _time
from agent.display import get_tool_emoji from agent.display import get_tool_emoji
emoji = get_tool_emoji(function_name) emoji = get_tool_emoji(function_name)
label = preview or function_name label = preview or function_name
@ -7280,7 +7277,7 @@ class HermesCLI:
if _pl > 0 and len(label) > _pl: if _pl > 0 and len(label) > _pl:
label = label[:_pl - 3] + "..." label = label[:_pl - 3] + "..."
self._spinner_text = f"{emoji} {label}" self._spinner_text = f"{emoji} {label}"
self._tool_start_time = _time.monotonic() self._tool_start_time = time.monotonic()
# Store args for stacked scrollback line on completion # Store args for stacked scrollback line on completion
self._pending_tool_info.setdefault(function_name, []).append( self._pending_tool_info.setdefault(function_name, []).append(
function_args if function_args is not None else {} function_args if function_args is not None else {}
@ -7538,7 +7535,6 @@ class HermesCLI:
try: try:
from tools.tts_tool import text_to_speech_tool from tools.tts_tool import text_to_speech_tool
from tools.voice_mode import play_audio_file from tools.voice_mode import play_audio_file
import re
# Strip markdown and non-speech content for cleaner TTS # Strip markdown and non-speech content for cleaner TTS
tts_text = text[:4000] if len(text) > 4000 else text tts_text = text[:4000] if len(text) > 4000 else text
@ -8374,8 +8370,7 @@ class HermesCLI:
try: try:
_dbg = _hermes_home / "interrupt_debug.log" _dbg = _hermes_home / "interrupt_debug.log"
with open(_dbg, "a") as _f: with open(_dbg, "a") as _f:
import time as _t _f.write(f"{time.strftime('%H:%M:%S')} interrupt fired: msg={str(interrupt_msg)[:60]!r}, "
_f.write(f"{_t.strftime('%H:%M:%S')} interrupt fired: msg={str(interrupt_msg)[:60]!r}, "
f"children={len(self.agent._active_children)}, " f"children={len(self.agent._active_children)}, "
f"parent._interrupt={self.agent._interrupt_requested}\n") f"parent._interrupt={self.agent._interrupt_requested}\n")
for _ci, _ch in enumerate(self.agent._active_children): for _ci, _ch in enumerate(self.agent._active_children):
@ -8451,9 +8446,8 @@ class HermesCLI:
# buffer so tool/status lines render ABOVE our response box. # buffer so tool/status lines render ABOVE our response box.
# The flush pushes data into the renderer queue; the short # The flush pushes data into the renderer queue; the short
# sleep lets the renderer actually paint it before we draw. # sleep lets the renderer actually paint it before we draw.
import time as _time
sys.stdout.flush() sys.stdout.flush()
_time.sleep(0.15) time.sleep(0.15)
# Update history with full conversation # Update history with full conversation
self.conversation_history = result.get("messages", self.conversation_history) if result else self.conversation_history self.conversation_history = result.get("messages", self.conversation_history) if result else self.conversation_history
@ -9119,8 +9113,7 @@ class HermesCLI:
try: try:
_dbg = _hermes_home / "interrupt_debug.log" _dbg = _hermes_home / "interrupt_debug.log"
with open(_dbg, "a") as _f: with open(_dbg, "a") as _f:
import time as _t _f.write(f"{time.strftime('%H:%M:%S')} ENTER: queued interrupt msg={str(payload)[:60]!r}, "
_f.write(f"{_t.strftime('%H:%M:%S')} ENTER: queued interrupt msg={str(payload)[:60]!r}, "
f"agent_running={self._agent_running}\n") f"agent_running={self._agent_running}\n")
except Exception: except Exception:
pass pass
@ -9268,8 +9261,7 @@ class HermesCLI:
2. Interrupt the running agent (first press) 2. Interrupt the running agent (first press)
3. Force exit (second press within 2s, or when idle) 3. Force exit (second press within 2s, or when idle)
""" """
import time as _time now = time.time()
now = _time.time()
# Cancel active voice recording. # Cancel active voice recording.
# Run cancel() in a background thread to prevent blocking the # Run cancel() in a background thread to prevent blocking the
@ -9377,12 +9369,11 @@ class HermesCLI:
@kb.add('c-z') @kb.add('c-z')
def handle_ctrl_z(event): def handle_ctrl_z(event):
"""Handle Ctrl+Z - suspend process to background (Unix only).""" """Handle Ctrl+Z - suspend process to background (Unix only)."""
import sys
if sys.platform == 'win32': if sys.platform == 'win32':
_cprint(f"\n{_DIM}Suspend (Ctrl+Z) is not supported on Windows.{_RST}") _cprint(f"\n{_DIM}Suspend (Ctrl+Z) is not supported on Windows.{_RST}")
event.app.invalidate() event.app.invalidate()
return return
import os, signal as _sig import signal as _sig
from prompt_toolkit.application import run_in_terminal from prompt_toolkit.application import run_in_terminal
from hermes_cli.skin_engine import get_active_skin from hermes_cli.skin_engine import get_active_skin
agent_name = get_active_skin().get_branding("agent_name", "Hermes Agent") agent_name = get_active_skin().get_branding("agent_name", "Hermes Agent")
@ -9696,31 +9687,29 @@ class HermesCLI:
# extra instructions (sudo countdown, approval navigation, clarify). # extra instructions (sudo countdown, approval navigation, clarify).
# The agent-running interrupt hint is now an inline placeholder above. # The agent-running interrupt hint is now an inline placeholder above.
def get_hint_text(): def get_hint_text():
import time as _time
if cli_ref._sudo_state: if cli_ref._sudo_state:
remaining = max(0, int(cli_ref._sudo_deadline - _time.monotonic())) remaining = max(0, int(cli_ref._sudo_deadline - time.monotonic()))
return [ return [
('class:hint', ' password hidden · Enter to skip'), ('class:hint', ' password hidden · Enter to skip'),
('class:clarify-countdown', f' ({remaining}s)'), ('class:clarify-countdown', f' ({remaining}s)'),
] ]
if cli_ref._secret_state: if cli_ref._secret_state:
remaining = max(0, int(cli_ref._secret_deadline - _time.monotonic())) remaining = max(0, int(cli_ref._secret_deadline - time.monotonic()))
return [ return [
('class:hint', ' secret hidden · Enter to skip'), ('class:hint', ' secret hidden · Enter to skip'),
('class:clarify-countdown', f' ({remaining}s)'), ('class:clarify-countdown', f' ({remaining}s)'),
] ]
if cli_ref._approval_state: if cli_ref._approval_state:
remaining = max(0, int(cli_ref._approval_deadline - _time.monotonic())) remaining = max(0, int(cli_ref._approval_deadline - time.monotonic()))
return [ return [
('class:hint', ' ↑/↓ to select, Enter to confirm'), ('class:hint', ' ↑/↓ to select, Enter to confirm'),
('class:clarify-countdown', f' ({remaining}s)'), ('class:clarify-countdown', f' ({remaining}s)'),
] ]
if cli_ref._clarify_state: if cli_ref._clarify_state:
remaining = max(0, int(cli_ref._clarify_deadline - _time.monotonic())) remaining = max(0, int(cli_ref._clarify_deadline - time.monotonic()))
countdown = f' ({remaining}s)' if cli_ref._clarify_deadline else '' countdown = f' ({remaining}s)' if cli_ref._clarify_deadline else ''
if cli_ref._clarify_freetext: if cli_ref._clarify_freetext:
return [ return [
@ -10268,22 +10257,20 @@ class HermesCLI:
app._on_resize = _resize_clear_ghosts app._on_resize = _resize_clear_ghosts
def spinner_loop(): def spinner_loop():
import time as _time
last_idle_refresh = 0.0 last_idle_refresh = 0.0
while not self._should_exit: while not self._should_exit:
if not self._app: if not self._app:
_time.sleep(0.1) time.sleep(0.1)
continue continue
if self._command_running: if self._command_running:
self._invalidate(min_interval=0.1) self._invalidate(min_interval=0.1)
_time.sleep(0.1) time.sleep(0.1)
else: else:
now = _time.monotonic() now = time.monotonic()
if now - last_idle_refresh >= 1.0: if now - last_idle_refresh >= 1.0:
last_idle_refresh = now last_idle_refresh = now
self._invalidate(min_interval=1.0) self._invalidate(min_interval=1.0)
_time.sleep(0.2) time.sleep(0.2)
spinner_thread = threading.Thread(target=spinner_loop, daemon=True) spinner_thread = threading.Thread(target=spinner_loop, daemon=True)
spinner_thread.start() spinner_thread.start()
@ -10352,8 +10339,7 @@ class HermesCLI:
continue continue
# Expand paste references back to full content # Expand paste references back to full content
import re as _re _paste_ref_re = re.compile(r'\[Pasted text #\d+: \d+ lines \u2192 (.+?)\]')
_paste_ref_re = _re.compile(r'\[Pasted text #\d+: \d+ lines \u2192 (.+?)\]')
paste_refs = list(_paste_ref_re.finditer(user_input)) if isinstance(user_input, str) else [] paste_refs = list(_paste_ref_re.finditer(user_input)) if isinstance(user_input, str) else []
if paste_refs: if paste_refs:
user_input = self._expand_paste_references(user_input) user_input = self._expand_paste_references(user_input)
@ -10445,13 +10431,12 @@ class HermesCLI:
try: try:
if getattr(self, "agent", None) and getattr(self, "_agent_running", False): if getattr(self, "agent", None) and getattr(self, "_agent_running", False):
self.agent.interrupt(f"received signal {signum}") self.agent.interrupt(f"received signal {signum}")
import time as _t
try: try:
_grace = float(os.getenv("HERMES_SIGTERM_GRACE", "1.5")) _grace = float(os.getenv("HERMES_SIGTERM_GRACE", "1.5"))
except (TypeError, ValueError): except (TypeError, ValueError):
_grace = 1.5 _grace = 1.5
if _grace > 0: if _grace > 0:
_t.sleep(_grace) time.sleep(_grace)
except Exception: except Exception:
pass # never block signal handling pass # never block signal handling
raise KeyboardInterrupt() raise KeyboardInterrupt()
@ -10484,8 +10469,7 @@ class HermesCLI:
# uv-managed Python, fd 0 can be invalid or unregisterable with the # uv-managed Python, fd 0 can be invalid or unregisterable with the
# asyncio selector, causing "KeyError: '0 is not registered'" (#6393). # asyncio selector, causing "KeyError: '0 is not registered'" (#6393).
try: try:
import os as _os os.fstat(0)
_os.fstat(0)
except OSError: except OSError:
print( print(
"Error: stdin (fd 0) is not available.\n" "Error: stdin (fd 0) is not available.\n"
@ -10778,13 +10762,12 @@ def main(
_agent = getattr(cli, "agent", None) _agent = getattr(cli, "agent", None)
if _agent is not None: if _agent is not None:
_agent.interrupt(f"received signal {signum}") _agent.interrupt(f"received signal {signum}")
import time as _t
try: try:
_grace = float(os.getenv("HERMES_SIGTERM_GRACE", "1.5")) _grace = float(os.getenv("HERMES_SIGTERM_GRACE", "1.5"))
except (TypeError, ValueError): except (TypeError, ValueError):
_grace = 1.5 _grace = 1.5
if _grace > 0: if _grace > 0:
_t.sleep(_grace) time.sleep(_grace)
except Exception: except Exception:
pass # never block signal handling pass # never block signal handling
raise KeyboardInterrupt() raise KeyboardInterrupt()

View file

@ -422,7 +422,6 @@ def _deliver_result(job: dict, content: str, adapters=None, loop=None) -> Option
# prevent "coroutine was never awaited" RuntimeWarning, then retry in a # prevent "coroutine was never awaited" RuntimeWarning, then retry in a
# fresh thread that has no running loop. # fresh thread that has no running loop.
coro.close() coro.close()
import concurrent.futures
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool: with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
future = pool.submit(asyncio.run, _send_to_platform(platform, pconfig, chat_id, cleaned_delivery_content, thread_id=thread_id, media_files=media_files)) future = pool.submit(asyncio.run, _send_to_platform(platform, pconfig, chat_id, cleaned_delivery_content, thread_id=thread_id, media_files=media_files))
result = future.result(timeout=30) result = future.result(timeout=30)
@ -810,14 +809,13 @@ def run_job(job: dict) -> tuple[bool, str, str, Optional[str]]:
prefill_messages = None prefill_messages = None
prefill_file = os.getenv("HERMES_PREFILL_MESSAGES_FILE", "") or _cfg.get("prefill_messages_file", "") prefill_file = os.getenv("HERMES_PREFILL_MESSAGES_FILE", "") or _cfg.get("prefill_messages_file", "")
if prefill_file: if prefill_file:
import json as _json
pfpath = Path(prefill_file).expanduser() pfpath = Path(prefill_file).expanduser()
if not pfpath.is_absolute(): if not pfpath.is_absolute():
pfpath = _hermes_home / pfpath pfpath = _hermes_home / pfpath
if pfpath.exists(): if pfpath.exists():
try: try:
with open(pfpath, "r", encoding="utf-8") as _pf: with open(pfpath, "r", encoding="utf-8") as _pf:
prefill_messages = _json.load(_pf) prefill_messages = json.load(_pf)
if not isinstance(prefill_messages, list): if not isinstance(prefill_messages, list):
prefill_messages = None prefill_messages = None
except Exception as e: except Exception as e:
@ -1085,7 +1083,6 @@ def tick(verbose: bool = True, adapters=None, loop=None) -> int:
logger.warning("Invalid HERMES_CRON_MAX_PARALLEL value; defaulting to unbounded") logger.warning("Invalid HERMES_CRON_MAX_PARALLEL value; defaulting to unbounded")
if _max_workers is None: if _max_workers is None:
try: try:
from hermes_cli.config import load_config
_ucfg = load_config() or {} _ucfg = load_config() or {}
_cfg_par = ( _cfg_par = (
_ucfg.get("cron", {}) if isinstance(_ucfg, dict) else {} _ucfg.get("cron", {}) if isinstance(_ucfg, dict) else {}

View file

@ -53,7 +53,6 @@ def _run_tool_in_thread(tool_name: str, arguments: Dict[str, Any], task_id: str)
try: try:
loop = asyncio.get_running_loop() loop = asyncio.get_running_loop()
# We're in an async context -- need to run in thread # We're in an async context -- need to run in thread
import concurrent.futures
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool: with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
future = pool.submit( future = pool.submit(
handle_function_call, tool_name, arguments, task_id handle_function_call, tool_name, arguments, task_id

View file

@ -75,7 +75,7 @@ def _redact(text: str) -> str:
def check_bluebubbles_requirements() -> bool: def check_bluebubbles_requirements() -> bool:
try: try:
import aiohttp # noqa: F401 import aiohttp # noqa: F401
import httpx as _httpx # noqa: F401 import httpx # noqa: F401
except ImportError: except ImportError:
return False return False
return True return True

View file

@ -399,7 +399,6 @@ class WhatsAppAdapter(BasePlatformAdapter):
# Check if bridge is already running and connected # Check if bridge is already running and connected
import aiohttp import aiohttp
import asyncio
try: try:
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:
async with session.get( async with session.get(

View file

@ -3274,10 +3274,9 @@ class GatewayRunner:
return "Usage: /queue <prompt>" return "Usage: /queue <prompt>"
adapter = self.adapters.get(source.platform) adapter = self.adapters.get(source.platform)
if adapter: if adapter:
from gateway.platforms.base import MessageEvent as _ME, MessageType as _MT queued_event = MessageEvent(
queued_event = _ME(
text=queued_text, text=queued_text,
message_type=_MT.TEXT, message_type=MessageType.TEXT,
source=event.source, source=event.source,
message_id=event.message_id, message_id=event.message_id,
channel_prompt=event.channel_prompt, channel_prompt=event.channel_prompt,
@ -3299,10 +3298,9 @@ class GatewayRunner:
# Agent hasn't started yet — queue as turn-boundary fallback. # Agent hasn't started yet — queue as turn-boundary fallback.
adapter = self.adapters.get(source.platform) adapter = self.adapters.get(source.platform)
if adapter: if adapter:
from gateway.platforms.base import MessageEvent as _ME, MessageType as _MT queued_event = MessageEvent(
queued_event = _ME(
text=steer_text, text=steer_text,
message_type=_MT.TEXT, message_type=MessageType.TEXT,
source=event.source, source=event.source,
message_id=event.message_id, message_id=event.message_id,
channel_prompt=event.channel_prompt, channel_prompt=event.channel_prompt,
@ -3322,10 +3320,9 @@ class GatewayRunner:
# Running agent is missing or lacks steer() — fall back to queue. # Running agent is missing or lacks steer() — fall back to queue.
adapter = self.adapters.get(source.platform) adapter = self.adapters.get(source.platform)
if adapter: if adapter:
from gateway.platforms.base import MessageEvent as _ME, MessageType as _MT queued_event = MessageEvent(
queued_event = _ME(
text=steer_text, text=steer_text,
message_type=_MT.TEXT, message_type=MessageType.TEXT,
source=event.source, source=event.source,
message_id=event.message_id, message_id=event.message_id,
channel_prompt=event.channel_prompt, channel_prompt=event.channel_prompt,
@ -3857,9 +3854,7 @@ class GatewayRunner:
for i, path in enumerate(event.media_urls): for i, path in enumerate(event.media_urls):
mtype = event.media_types[i] if i < len(event.media_types) else "" mtype = event.media_types[i] if i < len(event.media_types) else ""
if mtype in ("", "application/octet-stream"): if mtype in ("", "application/octet-stream"):
import os as _os2 _ext = os.path.splitext(path)[1].lower()
_ext = _os2.path.splitext(path)[1].lower()
if _ext in _TEXT_EXTENSIONS: if _ext in _TEXT_EXTENSIONS:
mtype = "text/plain" mtype = "text/plain"
else: else:
@ -8302,7 +8297,6 @@ class GatewayRunner:
if not adapter: if not adapter:
return return
try: try:
from gateway.platforms.base import MessageEvent, MessageType
synth_event = MessageEvent( synth_event = MessageEvent(
text=synth_text, text=synth_text,
message_type=MessageType.TEXT, message_type=MessageType.TEXT,
@ -8407,7 +8401,6 @@ class GatewayRunner:
break break
if adapter and source.chat_id: if adapter and source.chat_id:
try: try:
from gateway.platforms.base import MessageEvent, MessageType
synth_event = MessageEvent( synth_event = MessageEvent(
text=synth_text, text=synth_text,
message_type=MessageType.TEXT, message_type=MessageType.TEXT,
@ -8929,7 +8922,6 @@ class GatewayRunner:
if _streaming_enabled: if _streaming_enabled:
try: try:
from gateway.stream_consumer import GatewayStreamConsumer, StreamConsumerConfig from gateway.stream_consumer import GatewayStreamConsumer, StreamConsumerConfig
from gateway.config import Platform
_adapter = self.adapters.get(source.platform) _adapter = self.adapters.get(source.platform)
if _adapter: if _adapter:
_adapter_supports_edit = getattr(_adapter, "SUPPORTS_MESSAGE_EDITING", True) _adapter_supports_edit = getattr(_adapter, "SUPPORTS_MESSAGE_EDITING", True)
@ -9279,8 +9271,7 @@ class GatewayRunner:
# Skip tool progress for platforms that don't support message # Skip tool progress for platforms that don't support message
# editing (e.g. iMessage/BlueBubbles) — each progress update # editing (e.g. iMessage/BlueBubbles) — each progress update
# would become a separate message bubble, which is noisy. # would become a separate message bubble, which is noisy.
from gateway.platforms.base import BasePlatformAdapter as _BaseAdapter if type(adapter).edit_message is BasePlatformAdapter.edit_message:
if type(adapter).edit_message is _BaseAdapter.edit_message:
while not progress_queue.empty(): while not progress_queue.empty():
try: try:
progress_queue.get_nowait() progress_queue.get_nowait()

View file

@ -2605,8 +2605,7 @@ def migrate_config(interactive: bool = True, quiet: bool = False) -> Dict[str, A
# Scan ``$HERMES_HOME/plugins/`` for currently installed user plugins. # Scan ``$HERMES_HOME/plugins/`` for currently installed user plugins.
grandfathered: List[str] = [] grandfathered: List[str] = []
try: try:
from hermes_constants import get_hermes_home as _ghome user_plugins_dir = get_hermes_home() / "plugins"
user_plugins_dir = _ghome() / "plugins"
if user_plugins_dir.is_dir(): if user_plugins_dir.is_dir():
for child in sorted(user_plugins_dir.iterdir()): for child in sorted(user_plugins_dir.iterdir()):
if not child.is_dir(): if not child.is_dir():

View file

@ -4319,8 +4319,6 @@ def _gateway_prompt(prompt_text: str, default: str = "", timeout: float = 300.0)
tmp.replace(prompt_path) tmp.replace(prompt_path)
# Poll for response # Poll for response
import time as _time
deadline = _time.monotonic() + timeout deadline = _time.monotonic() + timeout
while _time.monotonic() < deadline: while _time.monotonic() < deadline:
if response_path.exists(): if response_path.exists():
@ -5214,8 +5212,6 @@ def _install_hangup_protection(gateway_mode: bool = False):
# (2) Mirror output to update.log and wrap stdio for broken-pipe # (2) Mirror output to update.log and wrap stdio for broken-pipe
# tolerance. Any failure here is non-fatal; we just skip the wrap. # tolerance. Any failure here is non-fatal; we just skip the wrap.
try: try:
from hermes_cli.config import get_hermes_home
logs_dir = get_hermes_home() / "logs" logs_dir = get_hermes_home() / "logs"
logs_dir.mkdir(parents=True, exist_ok=True) logs_dir.mkdir(parents=True, exist_ok=True)
log_path = logs_dir / "update.log" log_path = logs_dir / "update.log"
@ -5791,8 +5787,6 @@ def _cmd_update_impl(args, gateway_mode: bool):
# Verify the service actually survived the # Verify the service actually survived the
# restart. systemctl restart returns 0 even # restart. systemctl restart returns 0 even
# if the new process crashes immediately. # if the new process crashes immediately.
import time as _time
_time.sleep(3) _time.sleep(3)
verify = subprocess.run( verify = subprocess.run(
scope_cmd + ["is-active", svc_name], scope_cmd + ["is-active", svc_name],
@ -7679,9 +7673,7 @@ Examples:
) )
cmd_info["setup_fn"](plugin_parser) cmd_info["setup_fn"](plugin_parser)
except Exception as _exc: except Exception as _exc:
import logging as _log logging.getLogger(__name__).debug("Plugin CLI discovery failed: %s", _exc)
_log.getLogger(__name__).debug("Plugin CLI discovery failed: %s", _exc)
# ========================================================================= # =========================================================================
# memory command # memory command
@ -8145,8 +8137,6 @@ Examples:
# Launch hermes --resume <id> by replacing the current process # Launch hermes --resume <id> by replacing the current process
print(f"Resuming session: {selected_id}") print(f"Resuming session: {selected_id}")
import shutil
hermes_bin = shutil.which("hermes") hermes_bin = shutil.which("hermes")
if hermes_bin: if hermes_bin:
os.execvp(hermes_bin, ["hermes", "--resume", selected_id]) os.execvp(hermes_bin, ["hermes", "--resume", selected_id])

View file

@ -586,7 +586,6 @@ def get_gateway_eligible_tools(
return [], [], [] return [], [], []
if config is None: if config is None:
from hermes_cli.config import load_config
config = load_config() or {} config = load_config() or {}
# Quick provider check without the heavy get_nous_subscription_features call # Quick provider check without the heavy get_nous_subscription_features call

View file

@ -906,8 +906,7 @@ def resolve_runtime_provider(
code="no_aws_credentials", code="no_aws_credentials",
) )
# Read bedrock-specific config from config.yaml # Read bedrock-specific config from config.yaml
from hermes_cli.config import load_config as _load_bedrock_config _bedrock_cfg = load_config().get("bedrock", {})
_bedrock_cfg = _load_bedrock_config().get("bedrock", {})
# Region priority: config.yaml bedrock.region → env var → us-east-1 # Region priority: config.yaml bedrock.region → env var → us-east-1
region = (_bedrock_cfg.get("region") or "").strip() or resolve_bedrock_region() region = (_bedrock_cfg.get("region") or "").strip() or resolve_bedrock_region()
auth_source = resolve_aws_auth_env_var() or "aws-sdk-default-chain" auth_source = resolve_aws_auth_env_var() or "aws-sdk-default-chain"

View file

@ -434,7 +434,6 @@ def _print_setup_summary(config: dict, hermes_home):
tool_status.append(("Text-to-Speech (Google Gemini)", True, None)) tool_status.append(("Text-to-Speech (Google Gemini)", True, None))
elif tts_provider == "neutts": elif tts_provider == "neutts":
try: try:
import importlib.util
neutts_ok = importlib.util.find_spec("neutts") is not None neutts_ok = importlib.util.find_spec("neutts") is not None
except Exception: except Exception:
neutts_ok = False neutts_ok = False
@ -963,7 +962,6 @@ def _setup_tts_provider(config: dict):
if selected == "neutts": if selected == "neutts":
# Check if already installed # Check if already installed
try: try:
import importlib.util
already_installed = importlib.util.find_spec("neutts") is not None already_installed = importlib.util.find_spec("neutts") is not None
except Exception: except Exception:
already_installed = False already_installed = False

View file

@ -1186,7 +1186,6 @@ def _configure_simple_requirements(ts_key: str):
if api_key and api_key.strip(): if api_key and api_key.strip():
save_env_value("OPENAI_API_KEY", api_key.strip()) save_env_value("OPENAI_API_KEY", api_key.strip())
# Save vision base URL to config (not .env — only secrets go there) # Save vision base URL to config (not .env — only secrets go there)
from hermes_cli.config import load_config, save_config
_cfg = load_config() _cfg = load_config()
_aux = _cfg.setdefault("auxiliary", {}).setdefault("vision", {}) _aux = _cfg.setdefault("auxiliary", {}).setdefault("vision", {})
_aux["base_url"] = base_url _aux["base_url"] = base_url

View file

@ -1453,11 +1453,10 @@ class AIAgent:
if _mp and _mp.is_available(): if _mp and _mp.is_available():
self._memory_manager.add_provider(_mp) self._memory_manager.add_provider(_mp)
if self._memory_manager.providers: if self._memory_manager.providers:
from hermes_constants import get_hermes_home as _ghh
_init_kwargs = { _init_kwargs = {
"session_id": self.session_id, "session_id": self.session_id,
"platform": platform or "cli", "platform": platform or "cli",
"hermes_home": str(_ghh()), "hermes_home": str(get_hermes_home()),
"agent_context": "primary", "agent_context": "primary",
} }
# Thread session title for memory provider scoping # Thread session title for memory provider scoping
@ -2777,10 +2776,10 @@ class AIAgent:
prompt = self._SKILL_REVIEW_PROMPT prompt = self._SKILL_REVIEW_PROMPT
def _run_review(): def _run_review():
import contextlib, os as _os import contextlib
review_agent = None review_agent = None
try: try:
with open(_os.devnull, "w") as _devnull, \ with open(os.devnull, "w") as _devnull, \
contextlib.redirect_stdout(_devnull), \ contextlib.redirect_stdout(_devnull), \
contextlib.redirect_stderr(_devnull): contextlib.redirect_stderr(_devnull):
review_agent = AIAgent( review_agent = AIAgent(
@ -3852,14 +3851,12 @@ class AIAgent:
# 2. Clean terminal sandbox environments # 2. Clean terminal sandbox environments
try: try:
from tools.terminal_tool import cleanup_vm
cleanup_vm(task_id) cleanup_vm(task_id)
except Exception: except Exception:
pass pass
# 3. Clean browser daemon sessions # 3. Clean browser daemon sessions
try: try:
from tools.browser_tool import cleanup_browser
cleanup_browser(task_id) cleanup_browser(task_id)
except Exception: except Exception:
pass pass
@ -7777,8 +7774,7 @@ class AIAgent:
# the tool returns True on the next poll. # the tool returns True on the next poll.
if self._interrupt_requested: if self._interrupt_requested:
try: try:
from tools.interrupt import set_interrupt as _sif _set_interrupt(True, _worker_tid)
_sif(True, _worker_tid)
except Exception: except Exception:
pass pass
# Set the activity callback on THIS worker thread so # Set the activity callback on THIS worker thread so
@ -7809,8 +7805,7 @@ class AIAgent:
with self._tool_worker_threads_lock: with self._tool_worker_threads_lock:
self._tool_worker_threads.discard(_worker_tid) self._tool_worker_threads.discard(_worker_tid)
try: try:
from tools.interrupt import set_interrupt as _sif _set_interrupt(False, _worker_tid)
_sif(False, _worker_tid)
except Exception: except Exception:
pass pass
@ -11864,7 +11859,7 @@ def main(
# Handle tool listing # Handle tool listing
if list_tools: if list_tools:
from model_tools import get_all_tool_names, get_toolset_for_tool, get_available_toolsets from model_tools import get_all_tool_names, get_available_toolsets
from toolsets import get_all_toolsets, get_toolset_info from toolsets import get_all_toolsets, get_toolset_info
print("📋 Available Tools & Toolsets:") print("📋 Available Tools & Toolsets:")

View file

@ -543,7 +543,6 @@ def camofox_vision(question: str, annotate: bool = False,
) )
try: try:
from hermes_cli.config import load_config
_cfg = load_config() _cfg = load_config()
_vision_cfg = _cfg.get("auxiliary", {}).get("vision", {}) _vision_cfg = _cfg.get("auxiliary", {}).get("vision", {})
_vision_timeout = float(_vision_cfg.get("timeout", 120)) _vision_timeout = float(_vision_cfg.get("timeout", 120))

View file

@ -742,7 +742,7 @@ def check_image_generation_requirements() -> bool:
try: try:
if not check_fal_api_key(): if not check_fal_api_key():
return False return False
import fal_client # noqa: F401 — SDK presence check fal_client # noqa: F401 — SDK presence check
return True return True
except ImportError: except ImportError:
return False return False

View file

@ -226,7 +226,6 @@ def _handle_send(args):
# Weixin can be configured purely via .env; synthesize a pconfig so # Weixin can be configured purely via .env; synthesize a pconfig so
# send_message and cron delivery work without a gateway.yaml entry. # send_message and cron delivery work without a gateway.yaml entry.
if platform_name == "weixin": if platform_name == "weixin":
import os
wx_token = os.getenv("WEIXIN_TOKEN", "").strip() wx_token = os.getenv("WEIXIN_TOKEN", "").strip()
wx_account = os.getenv("WEIXIN_ACCOUNT_ID", "").strip() wx_account = os.getenv("WEIXIN_ACCOUNT_ID", "").strip()
if wx_token and wx_account: if wx_token and wx_account:
@ -254,7 +253,6 @@ def _handle_send(args):
if not chat_id: if not chat_id:
home = config.get_home_channel(platform) home = config.get_home_channel(platform)
if not home and platform_name == "weixin": if not home and platform_name == "weixin":
import os
wx_home = os.getenv("WEIXIN_HOME_CHANNEL", "").strip() wx_home = os.getenv("WEIXIN_HOME_CHANNEL", "").strip()
if wx_home: if wx_home:
from gateway.config import HomeChannel from gateway.config import HomeChannel

View file

@ -975,8 +975,7 @@ def skill_view(name: str, file_path: str = None, task_id: str = None) -> str:
_warnings.append(f"skill file is outside the trusted skills directory (~/.hermes/skills/): {skill_md}") _warnings.append(f"skill file is outside the trusted skills directory (~/.hermes/skills/): {skill_md}")
if _injection_detected: if _injection_detected:
_warnings.append("skill content contains patterns that may indicate prompt injection") _warnings.append("skill content contains patterns that may indicate prompt injection")
import logging as _logging logging.getLogger(__name__).warning("Skill security warning for '%s': %s", name, "; ".join(_warnings))
_logging.getLogger(__name__).warning("Skill security warning for '%s': %s", name, "; ".join(_warnings))
parsed_frontmatter: Dict[str, Any] = {} parsed_frontmatter: Dict[str, Any] = {}
try: try:

View file

@ -1608,7 +1608,6 @@ def _(rid, params: dict) -> dict:
if err: if err:
return err return err
try: try:
from datetime import datetime
from hermes_cli.clipboard import has_clipboard_image, save_clipboard_image from hermes_cli.clipboard import has_clipboard_image, save_clipboard_image
except Exception as e: except Exception as e:
return _err(rid, 5027, f"clipboard unavailable: {e}") return _err(rid, 5027, f"clipboard unavailable: {e}")
@ -2687,7 +2686,6 @@ def _(rid, params: dict) -> dict:
def _(rid, params: dict) -> dict: def _(rid, params: dict) -> dict:
days = params.get("days", 30) days = params.get("days", 30)
try: try:
import time
cutoff = time.time() - days * 86400 cutoff = time.time() - days * 86400
rows = [s for s in _get_db().list_sessions_rich(limit=500) if (s.get("started_at") or 0) >= cutoff] rows = [s for s in _get_db().list_sessions_rich(limit=500) if (s.get("started_at") or 0) >= cutoff]
return _ok(rid, {"days": days, "sessions": len(rows), "messages": sum(s.get("message_count", 0) for s in rows)}) return _ok(rid, {"days": days, "sessions": len(rows), "messages": sum(s.get("message_count", 0) for s in rows)})