Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ ignored/
.worktrees/
environments/benchmarks/evals/

# Web UI build output
hermes_cli/web_dist/

# Release script temp files
.release_notes.md
mini-swe-agent/
Expand Down
4 changes: 4 additions & 0 deletions cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -4971,6 +4971,10 @@ def process_command(self, command: str) -> bool:
self._handle_paste_command()
elif canonical == "image":
self._handle_image_command(cmd_original)
elif canonical == "reload":
from hermes_cli.config import reload_env
count = reload_env()
print(f" Reloaded .env ({count} var(s) updated)")
elif canonical == "reload-mcp":
with self._busy_command(self._slow_command_status(cmd_original)):
self._reload_mcp()
Expand Down
1 change: 1 addition & 0 deletions hermes_cli/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ class CommandDef:
CommandDef("cron", "Manage scheduled tasks", "Tools & Skills",
cli_only=True, args_hint="[subcommand]",
subcommands=("list", "add", "create", "edit", "pause", "resume", "run", "remove")),
CommandDef("reload", "Reload .env variables into the running session", "Tools & Skills"),
CommandDef("reload-mcp", "Reload MCP servers from config", "Tools & Skills",
aliases=("reload_mcp",)),
CommandDef("browser", "Connect browser tools to your live Chrome via CDP", "Tools & Skills",
Expand Down
45 changes: 45 additions & 0 deletions hermes_cli/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2497,6 +2497,51 @@ def save_env_value_secure(key: str, value: str) -> Dict[str, Any]:
}


def delete_env_value(key: str) -> bool:
"""Remove a key from ~/.hermes/.env. Returns True if the key was found and removed."""
env_path = get_env_path()
if not env_path.exists():
return False

read_kw = {"encoding": "utf-8", "errors": "replace"} if _IS_WINDOWS else {}
write_kw = {"encoding": "utf-8"} if _IS_WINDOWS else {}

with open(env_path, **read_kw) as f:
lines = f.readlines()

new_lines = [l for l in lines if not l.strip().startswith(f"{key}=")]
if len(new_lines) == len(lines):
return False

fd, tmp_path = tempfile.mkstemp(dir=str(env_path.parent), suffix='.tmp', prefix='.env_')
try:
with os.fdopen(fd, 'w', **write_kw) as f:
f.writelines(new_lines)
f.flush()
os.fsync(f.fileno())
os.replace(tmp_path, env_path)
except BaseException:
try:
os.unlink(tmp_path)
except OSError:
pass
raise
_secure_file(env_path)

os.environ.pop(key, None)
return True


def reload_env() -> int:
"""Re-read ~/.hermes/.env into os.environ. Returns count of vars updated."""
env_vars = load_env()
count = 0
for key, value in env_vars.items():
if os.environ.get(key) != value:
os.environ[key] = value
count += 1
return count


def get_env_value(key: str) -> Optional[str]:
"""Get a value from ~/.hermes/.env or environment."""
Expand Down
95 changes: 92 additions & 3 deletions hermes_cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2890,7 +2890,23 @@ def _update_via_zip(args):
check=True,
)
_install_python_dependencies_with_optional_fallback(pip_cmd)


# Build web UI frontend (optional — requires npm)
web_dir = PROJECT_ROOT / "web"
if (web_dir / "package.json").exists():
import shutil as _shutil
if _shutil.which("npm"):
print("→ Building web UI...")
r1 = subprocess.run(["npm", "install", "--silent"], cwd=web_dir, capture_output=True)
if r1.returncode == 0:
r2 = subprocess.run(["npm", "run", "build"], cwd=web_dir, capture_output=True)
if r2.returncode == 0:
print(" ✓ Web UI built")
else:
print(" ⚠ Web UI build failed (hermes web will not be available)")
else:
print(" ⚠ Web UI npm install failed (hermes web will not be available)")

# Sync skills
try:
from tools.skills_sync import sync_skills
Expand Down Expand Up @@ -3637,7 +3653,23 @@ def cmd_update(args):
if shutil.which("npm"):
print("→ Updating Node.js dependencies...")
subprocess.run(["npm", "install", "--silent"], cwd=PROJECT_ROOT, check=False)


# Build web UI frontend (optional — requires npm)
web_dir = PROJECT_ROOT / "web"
if (web_dir / "package.json").exists():
import shutil as _shutil_web
if _shutil_web.which("npm"):
print("→ Building web UI...")
r1 = subprocess.run(["npm", "install", "--silent"], cwd=web_dir, capture_output=True)
if r1.returncode == 0:
r2 = subprocess.run(["npm", "run", "build"], cwd=web_dir, capture_output=True)
if r2.returncode == 0:
print(" ✓ Web UI built")
else:
print(" ⚠ Web UI build failed (hermes web will not be available)")
else:
print(" ⚠ Web UI npm install failed (hermes web will not be available)")

print()
print("✓ Code updated!")

Expand Down Expand Up @@ -3899,7 +3931,7 @@ def _coalesce_session_name_args(argv: list) -> list:
"chat", "model", "gateway", "setup", "whatsapp", "login", "logout", "auth",
"status", "cron", "doctor", "config", "pairing", "skills", "tools",
"mcp", "sessions", "insights", "version", "update", "uninstall",
"profile",
"profile", "web",
}
_SESSION_FLAGS = {"-c", "--continue", "-r", "--resume"}

Expand Down Expand Up @@ -4171,6 +4203,50 @@ def cmd_profile(args):
sys.exit(1)


def cmd_web(args):
"""Start the web UI server."""
try:
import fastapi # noqa: F401
import uvicorn # noqa: F401
except ImportError:
print("Web UI dependencies not installed.")
print("Install them with: pip install hermes-agent[web]")
sys.exit(1)

web_dist = PROJECT_ROOT / "hermes_cli" / "web_dist"
web_src = PROJECT_ROOT / "web"
if not web_dist.exists() and (web_src / "package.json").exists():
import shutil
npm = shutil.which("npm")
if npm:
import subprocess
print("→ Web UI not built yet — building now...")
r1 = subprocess.run([npm, "install", "--silent"], cwd=web_src, capture_output=True)
if r1.returncode == 0:
r2 = subprocess.run([npm, "run", "build"], cwd=web_src, capture_output=True)
if r2.returncode == 0:
print(" ✓ Web UI built")
else:
print(" ✗ Web UI build failed")
print(" Run manually: cd web && npm install && npm run build")
sys.exit(1)
else:
print(" ✗ npm install failed")
print(" Run manually: cd web && npm install && npm run build")
sys.exit(1)
else:
print("Web UI frontend not built and npm is not available.")
print("Install Node.js, then run: cd web && npm install && npm run build")
sys.exit(1)

from hermes_cli.web_server import start_server
start_server(
host=args.host,
port=args.port,
open_browser=not args.no_open,
)


def cmd_completion(args):
"""Print shell completion script."""
from hermes_cli.profiles import generate_bash_completion, generate_zsh_completion
Expand Down Expand Up @@ -5578,6 +5654,19 @@ def cmd_acp(args):
)
completion_parser.set_defaults(func=cmd_completion)

# =========================================================================
# web command
# =========================================================================
web_parser = subparsers.add_parser(
"web",
help="Start the web UI dashboard",
description="Launch the Hermes Agent web dashboard for managing config, API keys, and sessions",
)
web_parser.add_argument("--port", type=int, default=9119, help="Port (default 9119)")
web_parser.add_argument("--host", default="127.0.0.1", help="Host (default 127.0.0.1)")
web_parser.add_argument("--no-open", action="store_true", help="Don't open browser automatically")
web_parser.set_defaults(func=cmd_web)

# =========================================================================
# logs command
# =========================================================================
Expand Down
Loading
Loading