feat: add 'hermes claw migrate' command + migration docs
- Add hermes_cli/claw.py with full CLI migration handler:
- hermes claw migrate (interactive migration with confirmation)
- --dry-run, --preset, --overwrite, --skill-conflict flags
- --source for custom OpenClaw path
- --yes to skip confirmation
- Clean formatted output matching setup wizard style
- Fix Python 3.11+ @dataclass compatibility bug in dynamic module loading:
- Register module in sys.modules before exec_module()
- Fixes both setup.py (PR #981) and new claw.py
- Add 16 tests in tests/hermes_cli/test_claw.py covering:
- Script discovery (project root, installed, missing)
- Command routing
- Dry-run, execute, cancellation, error handling
- Preset/secrets behavior, report formatting
- Documentation updates:
- README.md: Add 'hermes claw migrate' to Getting Started, new Migration section
- docs/migration/openclaw.md: Full migration guide with all options
- SKILL.md: Add CLI Command section at top of openclaw-migration skill
2026-03-12 08:20:12 -07:00
|
|
|
"""hermes claw — OpenClaw migration commands.
|
|
|
|
|
|
|
|
|
|
Usage:
|
2026-04-11 11:21:54 -07:00
|
|
|
hermes claw migrate # Preview then migrate (always shows preview first)
|
|
|
|
|
hermes claw migrate --dry-run # Preview only, no changes
|
|
|
|
|
hermes claw migrate --yes # Skip confirmation prompt
|
feat: add 'hermes claw migrate' command + migration docs
- Add hermes_cli/claw.py with full CLI migration handler:
- hermes claw migrate (interactive migration with confirmation)
- --dry-run, --preset, --overwrite, --skill-conflict flags
- --source for custom OpenClaw path
- --yes to skip confirmation
- Clean formatted output matching setup wizard style
- Fix Python 3.11+ @dataclass compatibility bug in dynamic module loading:
- Register module in sys.modules before exec_module()
- Fixes both setup.py (PR #981) and new claw.py
- Add 16 tests in tests/hermes_cli/test_claw.py covering:
- Script discovery (project root, installed, missing)
- Command routing
- Dry-run, execute, cancellation, error handling
- Preset/secrets behavior, report formatting
- Documentation updates:
- README.md: Add 'hermes claw migrate' to Getting Started, new Migration section
- docs/migration/openclaw.md: Full migration guide with all options
- SKILL.md: Add CLI Command section at top of openclaw-migration skill
2026-03-12 08:20:12 -07:00
|
|
|
hermes claw migrate --preset full --overwrite # Full migration, overwrite conflicts
|
2026-03-30 17:39:08 -07:00
|
|
|
hermes claw cleanup # Archive leftover OpenClaw directories
|
|
|
|
|
hermes claw cleanup --dry-run # Preview what would be archived
|
feat: add 'hermes claw migrate' command + migration docs
- Add hermes_cli/claw.py with full CLI migration handler:
- hermes claw migrate (interactive migration with confirmation)
- --dry-run, --preset, --overwrite, --skill-conflict flags
- --source for custom OpenClaw path
- --yes to skip confirmation
- Clean formatted output matching setup wizard style
- Fix Python 3.11+ @dataclass compatibility bug in dynamic module loading:
- Register module in sys.modules before exec_module()
- Fixes both setup.py (PR #981) and new claw.py
- Add 16 tests in tests/hermes_cli/test_claw.py covering:
- Script discovery (project root, installed, missing)
- Command routing
- Dry-run, execute, cancellation, error handling
- Preset/secrets behavior, report formatting
- Documentation updates:
- README.md: Add 'hermes claw migrate' to Getting Started, new Migration section
- docs/migration/openclaw.md: Full migration guide with all options
- SKILL.md: Add CLI Command section at top of openclaw-migration skill
2026-03-12 08:20:12 -07:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import importlib.util
|
|
|
|
|
import logging
|
2026-04-12 10:34:38 +08:00
|
|
|
import subprocess
|
feat: add 'hermes claw migrate' command + migration docs
- Add hermes_cli/claw.py with full CLI migration handler:
- hermes claw migrate (interactive migration with confirmation)
- --dry-run, --preset, --overwrite, --skill-conflict flags
- --source for custom OpenClaw path
- --yes to skip confirmation
- Clean formatted output matching setup wizard style
- Fix Python 3.11+ @dataclass compatibility bug in dynamic module loading:
- Register module in sys.modules before exec_module()
- Fixes both setup.py (PR #981) and new claw.py
- Add 16 tests in tests/hermes_cli/test_claw.py covering:
- Script discovery (project root, installed, missing)
- Command routing
- Dry-run, execute, cancellation, error handling
- Preset/secrets behavior, report formatting
- Documentation updates:
- README.md: Add 'hermes claw migrate' to Getting Started, new Migration section
- docs/migration/openclaw.md: Full migration guide with all options
- SKILL.md: Add CLI Command section at top of openclaw-migration skill
2026-03-12 08:20:12 -07:00
|
|
|
import sys
|
2026-03-30 17:39:08 -07:00
|
|
|
from datetime import datetime
|
feat: add 'hermes claw migrate' command + migration docs
- Add hermes_cli/claw.py with full CLI migration handler:
- hermes claw migrate (interactive migration with confirmation)
- --dry-run, --preset, --overwrite, --skill-conflict flags
- --source for custom OpenClaw path
- --yes to skip confirmation
- Clean formatted output matching setup wizard style
- Fix Python 3.11+ @dataclass compatibility bug in dynamic module loading:
- Register module in sys.modules before exec_module()
- Fixes both setup.py (PR #981) and new claw.py
- Add 16 tests in tests/hermes_cli/test_claw.py covering:
- Script discovery (project root, installed, missing)
- Command routing
- Dry-run, execute, cancellation, error handling
- Preset/secrets behavior, report formatting
- Documentation updates:
- README.md: Add 'hermes claw migrate' to Getting Started, new Migration section
- docs/migration/openclaw.md: Full migration guide with all options
- SKILL.md: Add CLI Command section at top of openclaw-migration skill
2026-03-12 08:20:12 -07:00
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
from hermes_cli.config import get_hermes_home, get_config_path, load_config, save_config
|
2026-03-30 17:34:43 -07:00
|
|
|
from hermes_constants import get_optional_skills_dir
|
feat: add 'hermes claw migrate' command + migration docs
- Add hermes_cli/claw.py with full CLI migration handler:
- hermes claw migrate (interactive migration with confirmation)
- --dry-run, --preset, --overwrite, --skill-conflict flags
- --source for custom OpenClaw path
- --yes to skip confirmation
- Clean formatted output matching setup wizard style
- Fix Python 3.11+ @dataclass compatibility bug in dynamic module loading:
- Register module in sys.modules before exec_module()
- Fixes both setup.py (PR #981) and new claw.py
- Add 16 tests in tests/hermes_cli/test_claw.py covering:
- Script discovery (project root, installed, missing)
- Command routing
- Dry-run, execute, cancellation, error handling
- Preset/secrets behavior, report formatting
- Documentation updates:
- README.md: Add 'hermes claw migrate' to Getting Started, new Migration section
- docs/migration/openclaw.md: Full migration guide with all options
- SKILL.md: Add CLI Command section at top of openclaw-migration skill
2026-03-12 08:20:12 -07:00
|
|
|
from hermes_cli.setup import (
|
|
|
|
|
Colors,
|
|
|
|
|
color,
|
|
|
|
|
print_header,
|
|
|
|
|
print_info,
|
|
|
|
|
print_success,
|
|
|
|
|
print_error,
|
|
|
|
|
prompt_yes_no,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
PROJECT_ROOT = Path(__file__).parent.parent.resolve()
|
|
|
|
|
|
|
|
|
|
_OPENCLAW_SCRIPT = (
|
2026-03-30 17:34:43 -07:00
|
|
|
get_optional_skills_dir(PROJECT_ROOT / "optional-skills")
|
feat: add 'hermes claw migrate' command + migration docs
- Add hermes_cli/claw.py with full CLI migration handler:
- hermes claw migrate (interactive migration with confirmation)
- --dry-run, --preset, --overwrite, --skill-conflict flags
- --source for custom OpenClaw path
- --yes to skip confirmation
- Clean formatted output matching setup wizard style
- Fix Python 3.11+ @dataclass compatibility bug in dynamic module loading:
- Register module in sys.modules before exec_module()
- Fixes both setup.py (PR #981) and new claw.py
- Add 16 tests in tests/hermes_cli/test_claw.py covering:
- Script discovery (project root, installed, missing)
- Command routing
- Dry-run, execute, cancellation, error handling
- Preset/secrets behavior, report formatting
- Documentation updates:
- README.md: Add 'hermes claw migrate' to Getting Started, new Migration section
- docs/migration/openclaw.md: Full migration guide with all options
- SKILL.md: Add CLI Command section at top of openclaw-migration skill
2026-03-12 08:20:12 -07:00
|
|
|
/ "migration"
|
|
|
|
|
/ "openclaw-migration"
|
|
|
|
|
/ "scripts"
|
|
|
|
|
/ "openclaw_to_hermes.py"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Fallback: user may have installed the skill from the Hub
|
|
|
|
|
_OPENCLAW_SCRIPT_INSTALLED = (
|
|
|
|
|
get_hermes_home()
|
|
|
|
|
/ "skills"
|
|
|
|
|
/ "migration"
|
|
|
|
|
/ "openclaw-migration"
|
|
|
|
|
/ "scripts"
|
|
|
|
|
/ "openclaw_to_hermes.py"
|
|
|
|
|
)
|
|
|
|
|
|
2026-03-30 17:39:08 -07:00
|
|
|
# Known OpenClaw directory names (current + legacy)
|
feat: rebrand OpenClaw references to Hermes during migration
- Add rebrand_text() that replaces OpenClaw, Open Claw, Open-Claw,
ClawdBot, and MoltBot with Hermes (case-insensitive, word-boundary)
- Apply rebranding to memory entries (MEMORY.md, USER.md, daily memory)
- Apply rebranding to SOUL.md and workspace instructions via new
transform parameter on copy_file()
- Fix moldbot -> moltbot typo across codebase (claw.py, migration
script, docs, tests)
- Add unit tests for rebrand_text and integration tests for memory
and soul migration rebranding
2026-04-11 23:47:37 -07:00
|
|
|
_OPENCLAW_DIR_NAMES = (".openclaw", ".clawdbot", ".moltbot")
|
2026-03-30 17:39:08 -07:00
|
|
|
|
2026-04-12 16:40:10 -07:00
|
|
|
def _detect_openclaw_processes() -> list[str]:
|
|
|
|
|
"""Detect running OpenClaw processes and services.
|
|
|
|
|
|
|
|
|
|
Returns a list of human-readable descriptions of what was found.
|
|
|
|
|
An empty list means nothing was detected.
|
|
|
|
|
"""
|
|
|
|
|
found: list[str] = []
|
|
|
|
|
|
|
|
|
|
# -- systemd service (Linux) ------------------------------------------
|
|
|
|
|
if sys.platform != "win32":
|
|
|
|
|
try:
|
|
|
|
|
result = subprocess.run(
|
|
|
|
|
["systemctl", "--user", "is-active", "openclaw-gateway.service"],
|
|
|
|
|
capture_output=True, text=True, timeout=5,
|
|
|
|
|
)
|
|
|
|
|
if result.stdout.strip() == "active":
|
|
|
|
|
found.append("systemd service: openclaw-gateway.service")
|
|
|
|
|
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
# -- process scan ------------------------------------------------------
|
2026-04-12 10:34:38 +08:00
|
|
|
if sys.platform == "win32":
|
|
|
|
|
try:
|
2026-04-12 10:48:27 +08:00
|
|
|
for exe in ("openclaw.exe", "clawd.exe"):
|
|
|
|
|
result = subprocess.run(
|
|
|
|
|
["tasklist", "/FI", f"IMAGENAME eq {exe}"],
|
2026-04-12 16:40:10 -07:00
|
|
|
capture_output=True, text=True, timeout=5,
|
2026-04-12 10:48:27 +08:00
|
|
|
)
|
|
|
|
|
if exe in result.stdout.lower():
|
2026-04-12 16:40:10 -07:00
|
|
|
found.append(f"process: {exe}")
|
2026-04-12 10:48:27 +08:00
|
|
|
|
2026-04-12 16:40:10 -07:00
|
|
|
# Node.js-hosted OpenClaw — tasklist doesn't show command lines,
|
|
|
|
|
# so fall back to PowerShell.
|
2026-04-12 10:48:27 +08:00
|
|
|
ps_cmd = (
|
|
|
|
|
'Get-CimInstance Win32_Process -Filter "Name = \'node.exe\'" | '
|
|
|
|
|
'Where-Object { $_.CommandLine -match "openclaw|clawd" } | '
|
|
|
|
|
'Select-Object -First 1 ProcessId'
|
|
|
|
|
)
|
2026-04-12 10:34:38 +08:00
|
|
|
result = subprocess.run(
|
2026-04-12 10:48:27 +08:00
|
|
|
["powershell", "-NoProfile", "-Command", ps_cmd],
|
2026-04-12 16:40:10 -07:00
|
|
|
capture_output=True, text=True, timeout=5,
|
2026-04-12 10:34:38 +08:00
|
|
|
)
|
2026-04-12 16:40:10 -07:00
|
|
|
if result.stdout.strip():
|
|
|
|
|
found.append(f"node.exe process with openclaw in command line (PID {result.stdout.strip()})")
|
2026-04-12 10:34:38 +08:00
|
|
|
except Exception:
|
2026-04-12 16:40:10 -07:00
|
|
|
pass
|
|
|
|
|
else:
|
2026-04-12 10:34:38 +08:00
|
|
|
try:
|
2026-04-12 16:40:10 -07:00
|
|
|
result = subprocess.run(
|
|
|
|
|
["pgrep", "-f", "openclaw"],
|
|
|
|
|
capture_output=True, text=True, timeout=3,
|
|
|
|
|
)
|
2026-04-12 10:34:38 +08:00
|
|
|
if result.returncode == 0:
|
2026-04-12 16:40:10 -07:00
|
|
|
pids = result.stdout.strip().split()
|
|
|
|
|
found.append(f"openclaw process(es) (PIDs: {', '.join(pids)})")
|
2026-04-12 10:34:38 +08:00
|
|
|
except (FileNotFoundError, subprocess.TimeoutExpired):
|
2026-04-12 16:40:10 -07:00
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
return found
|
2026-04-12 10:34:38 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _warn_if_openclaw_running(auto_yes: bool) -> None:
|
|
|
|
|
"""Warn if OpenClaw is still running before migration.
|
|
|
|
|
|
|
|
|
|
Telegram, Discord, and Slack only allow one active connection per bot
|
|
|
|
|
token. Migrating while OpenClaw is running causes both to fight for the
|
|
|
|
|
same token.
|
|
|
|
|
"""
|
2026-04-12 16:40:10 -07:00
|
|
|
running = _detect_openclaw_processes()
|
|
|
|
|
if not running:
|
2026-04-12 10:34:38 +08:00
|
|
|
return
|
|
|
|
|
|
|
|
|
|
print()
|
2026-04-12 16:40:10 -07:00
|
|
|
print_error("OpenClaw appears to be running:")
|
|
|
|
|
for detail in running:
|
|
|
|
|
print_info(f" * {detail}")
|
2026-04-12 10:34:38 +08:00
|
|
|
print_info(
|
|
|
|
|
"Messaging platforms (Telegram, Discord, Slack) only allow one "
|
|
|
|
|
"active session per bot token. If you continue, both OpenClaw and "
|
|
|
|
|
"Hermes may try to use the same token, causing disconnects."
|
|
|
|
|
)
|
|
|
|
|
print_info("Recommendation: stop OpenClaw before migrating.")
|
|
|
|
|
print()
|
2026-04-12 10:48:27 +08:00
|
|
|
if auto_yes:
|
|
|
|
|
return
|
|
|
|
|
if not sys.stdin.isatty():
|
|
|
|
|
print_info("Non-interactive session — continuing to preview only.")
|
|
|
|
|
return
|
|
|
|
|
if not prompt_yes_no("Continue anyway?", default=False):
|
2026-04-12 10:34:38 +08:00
|
|
|
print_info("Migration cancelled. Stop OpenClaw and try again.")
|
|
|
|
|
sys.exit(0)
|
|
|
|
|
|
|
|
|
|
|
2026-04-11 14:47:03 -07:00
|
|
|
def _warn_if_gateway_running(auto_yes: bool) -> None:
|
|
|
|
|
"""Check if a Hermes gateway is running with connected platforms.
|
|
|
|
|
|
|
|
|
|
Migrating bot tokens while the gateway is polling will cause conflicts
|
|
|
|
|
(e.g. Telegram 409 "terminated by other getUpdates request"). Warn the
|
|
|
|
|
user and let them decide whether to continue.
|
|
|
|
|
"""
|
|
|
|
|
from gateway.status import get_running_pid, read_runtime_status
|
|
|
|
|
|
|
|
|
|
if not get_running_pid():
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
data = read_runtime_status() or {}
|
|
|
|
|
platforms = data.get("platforms") or {}
|
|
|
|
|
connected = [name for name, info in platforms.items()
|
|
|
|
|
if isinstance(info, dict) and info.get("state") == "connected"]
|
|
|
|
|
if not connected:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
print()
|
|
|
|
|
print_error(
|
|
|
|
|
"Hermes gateway is running with active connections: "
|
|
|
|
|
+ ", ".join(connected)
|
|
|
|
|
)
|
|
|
|
|
print_info(
|
|
|
|
|
"Migrating bot tokens while the gateway is active will cause "
|
|
|
|
|
"conflicts (Telegram, Discord, and Slack only allow one active "
|
|
|
|
|
"session per token)."
|
|
|
|
|
)
|
|
|
|
|
print_info("Recommendation: stop the gateway first with 'hermes stop'.")
|
|
|
|
|
print()
|
|
|
|
|
if not auto_yes and not prompt_yes_no("Continue anyway?", default=False):
|
|
|
|
|
print_info("Migration cancelled. Stop the gateway and try again.")
|
|
|
|
|
sys.exit(0)
|
|
|
|
|
|
2026-04-12 00:27:18 -07:00
|
|
|
# State files commonly found in OpenClaw workspace directories — listed
|
|
|
|
|
# during cleanup to help the user decide whether to archive
|
2026-03-30 17:39:08 -07:00
|
|
|
_WORKSPACE_STATE_GLOBS = (
|
|
|
|
|
"*/todo.json",
|
|
|
|
|
"*/sessions/*",
|
|
|
|
|
"*/memory/*.json",
|
|
|
|
|
"*/logs/*",
|
|
|
|
|
)
|
|
|
|
|
|
feat: add 'hermes claw migrate' command + migration docs
- Add hermes_cli/claw.py with full CLI migration handler:
- hermes claw migrate (interactive migration with confirmation)
- --dry-run, --preset, --overwrite, --skill-conflict flags
- --source for custom OpenClaw path
- --yes to skip confirmation
- Clean formatted output matching setup wizard style
- Fix Python 3.11+ @dataclass compatibility bug in dynamic module loading:
- Register module in sys.modules before exec_module()
- Fixes both setup.py (PR #981) and new claw.py
- Add 16 tests in tests/hermes_cli/test_claw.py covering:
- Script discovery (project root, installed, missing)
- Command routing
- Dry-run, execute, cancellation, error handling
- Preset/secrets behavior, report formatting
- Documentation updates:
- README.md: Add 'hermes claw migrate' to Getting Started, new Migration section
- docs/migration/openclaw.md: Full migration guide with all options
- SKILL.md: Add CLI Command section at top of openclaw-migration skill
2026-03-12 08:20:12 -07:00
|
|
|
|
|
|
|
|
def _find_migration_script() -> Path | None:
|
|
|
|
|
"""Find the openclaw_to_hermes.py script in known locations."""
|
|
|
|
|
for candidate in [_OPENCLAW_SCRIPT, _OPENCLAW_SCRIPT_INSTALLED]:
|
|
|
|
|
if candidate.exists():
|
|
|
|
|
return candidate
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _load_migration_module(script_path: Path):
|
|
|
|
|
"""Dynamically load the migration script as a module."""
|
|
|
|
|
spec = importlib.util.spec_from_file_location("openclaw_to_hermes", script_path)
|
|
|
|
|
if spec is None or spec.loader is None:
|
|
|
|
|
return None
|
|
|
|
|
mod = importlib.util.module_from_spec(spec)
|
|
|
|
|
# Register in sys.modules so @dataclass can resolve the module
|
|
|
|
|
# (Python 3.11+ requires this for dynamically loaded modules)
|
|
|
|
|
sys.modules[spec.name] = mod
|
|
|
|
|
try:
|
|
|
|
|
spec.loader.exec_module(mod)
|
|
|
|
|
except Exception:
|
|
|
|
|
sys.modules.pop(spec.name, None)
|
|
|
|
|
raise
|
|
|
|
|
return mod
|
|
|
|
|
|
|
|
|
|
|
2026-03-30 17:39:08 -07:00
|
|
|
def _find_openclaw_dirs() -> list[Path]:
|
|
|
|
|
"""Find all OpenClaw directories on disk."""
|
|
|
|
|
found = []
|
|
|
|
|
for name in _OPENCLAW_DIR_NAMES:
|
|
|
|
|
candidate = Path.home() / name
|
|
|
|
|
if candidate.is_dir():
|
|
|
|
|
found.append(candidate)
|
|
|
|
|
return found
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _scan_workspace_state(source_dir: Path) -> list[tuple[Path, str]]:
|
2026-04-12 00:27:18 -07:00
|
|
|
"""Scan an OpenClaw directory for workspace state files.
|
2026-03-30 17:39:08 -07:00
|
|
|
|
|
|
|
|
Returns a list of (path, description) tuples.
|
|
|
|
|
"""
|
|
|
|
|
findings: list[tuple[Path, str]] = []
|
|
|
|
|
|
|
|
|
|
# Direct state files in the root
|
|
|
|
|
for name in ("todo.json", "sessions", "logs"):
|
|
|
|
|
candidate = source_dir / name
|
|
|
|
|
if candidate.exists():
|
|
|
|
|
kind = "directory" if candidate.is_dir() else "file"
|
|
|
|
|
findings.append((candidate, f"Root {kind}: {name}"))
|
|
|
|
|
|
|
|
|
|
# State files inside workspace directories
|
|
|
|
|
for child in sorted(source_dir.iterdir()):
|
|
|
|
|
if not child.is_dir() or child.name.startswith("."):
|
|
|
|
|
continue
|
|
|
|
|
# Check for workspace-like subdirectories
|
|
|
|
|
for state_name in ("todo.json", "sessions", "logs", "memory"):
|
|
|
|
|
state_path = child / state_name
|
|
|
|
|
if state_path.exists():
|
|
|
|
|
kind = "directory" if state_path.is_dir() else "file"
|
|
|
|
|
rel = state_path.relative_to(source_dir)
|
|
|
|
|
findings.append((state_path, f"Workspace {kind}: {rel}"))
|
|
|
|
|
|
|
|
|
|
return findings
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _archive_directory(source_dir: Path, dry_run: bool = False) -> Path:
|
|
|
|
|
"""Rename an OpenClaw directory to .pre-migration.
|
|
|
|
|
|
|
|
|
|
Returns the archive path.
|
|
|
|
|
"""
|
|
|
|
|
timestamp = datetime.now().strftime("%Y%m%d")
|
|
|
|
|
archive_name = f"{source_dir.name}.pre-migration"
|
|
|
|
|
archive_path = source_dir.parent / archive_name
|
|
|
|
|
|
|
|
|
|
# If archive already exists, add timestamp
|
|
|
|
|
if archive_path.exists():
|
|
|
|
|
archive_name = f"{source_dir.name}.pre-migration-{timestamp}"
|
|
|
|
|
archive_path = source_dir.parent / archive_name
|
|
|
|
|
|
|
|
|
|
# If still exists (multiple runs same day), add counter
|
|
|
|
|
counter = 2
|
|
|
|
|
while archive_path.exists():
|
|
|
|
|
archive_name = f"{source_dir.name}.pre-migration-{timestamp}-{counter}"
|
|
|
|
|
archive_path = source_dir.parent / archive_name
|
|
|
|
|
counter += 1
|
|
|
|
|
|
|
|
|
|
if not dry_run:
|
|
|
|
|
source_dir.rename(archive_path)
|
|
|
|
|
|
|
|
|
|
return archive_path
|
|
|
|
|
|
|
|
|
|
|
feat: add 'hermes claw migrate' command + migration docs
- Add hermes_cli/claw.py with full CLI migration handler:
- hermes claw migrate (interactive migration with confirmation)
- --dry-run, --preset, --overwrite, --skill-conflict flags
- --source for custom OpenClaw path
- --yes to skip confirmation
- Clean formatted output matching setup wizard style
- Fix Python 3.11+ @dataclass compatibility bug in dynamic module loading:
- Register module in sys.modules before exec_module()
- Fixes both setup.py (PR #981) and new claw.py
- Add 16 tests in tests/hermes_cli/test_claw.py covering:
- Script discovery (project root, installed, missing)
- Command routing
- Dry-run, execute, cancellation, error handling
- Preset/secrets behavior, report formatting
- Documentation updates:
- README.md: Add 'hermes claw migrate' to Getting Started, new Migration section
- docs/migration/openclaw.md: Full migration guide with all options
- SKILL.md: Add CLI Command section at top of openclaw-migration skill
2026-03-12 08:20:12 -07:00
|
|
|
def claw_command(args):
|
|
|
|
|
"""Route hermes claw subcommands."""
|
|
|
|
|
action = getattr(args, "claw_action", None)
|
|
|
|
|
|
|
|
|
|
if action == "migrate":
|
|
|
|
|
_cmd_migrate(args)
|
2026-03-30 17:39:08 -07:00
|
|
|
elif action in ("cleanup", "clean"):
|
|
|
|
|
_cmd_cleanup(args)
|
feat: add 'hermes claw migrate' command + migration docs
- Add hermes_cli/claw.py with full CLI migration handler:
- hermes claw migrate (interactive migration with confirmation)
- --dry-run, --preset, --overwrite, --skill-conflict flags
- --source for custom OpenClaw path
- --yes to skip confirmation
- Clean formatted output matching setup wizard style
- Fix Python 3.11+ @dataclass compatibility bug in dynamic module loading:
- Register module in sys.modules before exec_module()
- Fixes both setup.py (PR #981) and new claw.py
- Add 16 tests in tests/hermes_cli/test_claw.py covering:
- Script discovery (project root, installed, missing)
- Command routing
- Dry-run, execute, cancellation, error handling
- Preset/secrets behavior, report formatting
- Documentation updates:
- README.md: Add 'hermes claw migrate' to Getting Started, new Migration section
- docs/migration/openclaw.md: Full migration guide with all options
- SKILL.md: Add CLI Command section at top of openclaw-migration skill
2026-03-12 08:20:12 -07:00
|
|
|
else:
|
2026-03-30 17:39:08 -07:00
|
|
|
print("Usage: hermes claw <command> [options]")
|
feat: add 'hermes claw migrate' command + migration docs
- Add hermes_cli/claw.py with full CLI migration handler:
- hermes claw migrate (interactive migration with confirmation)
- --dry-run, --preset, --overwrite, --skill-conflict flags
- --source for custom OpenClaw path
- --yes to skip confirmation
- Clean formatted output matching setup wizard style
- Fix Python 3.11+ @dataclass compatibility bug in dynamic module loading:
- Register module in sys.modules before exec_module()
- Fixes both setup.py (PR #981) and new claw.py
- Add 16 tests in tests/hermes_cli/test_claw.py covering:
- Script discovery (project root, installed, missing)
- Command routing
- Dry-run, execute, cancellation, error handling
- Preset/secrets behavior, report formatting
- Documentation updates:
- README.md: Add 'hermes claw migrate' to Getting Started, new Migration section
- docs/migration/openclaw.md: Full migration guide with all options
- SKILL.md: Add CLI Command section at top of openclaw-migration skill
2026-03-12 08:20:12 -07:00
|
|
|
print()
|
|
|
|
|
print("Commands:")
|
|
|
|
|
print(" migrate Migrate settings from OpenClaw to Hermes")
|
2026-03-30 17:39:08 -07:00
|
|
|
print(" cleanup Archive leftover OpenClaw directories after migration")
|
feat: add 'hermes claw migrate' command + migration docs
- Add hermes_cli/claw.py with full CLI migration handler:
- hermes claw migrate (interactive migration with confirmation)
- --dry-run, --preset, --overwrite, --skill-conflict flags
- --source for custom OpenClaw path
- --yes to skip confirmation
- Clean formatted output matching setup wizard style
- Fix Python 3.11+ @dataclass compatibility bug in dynamic module loading:
- Register module in sys.modules before exec_module()
- Fixes both setup.py (PR #981) and new claw.py
- Add 16 tests in tests/hermes_cli/test_claw.py covering:
- Script discovery (project root, installed, missing)
- Command routing
- Dry-run, execute, cancellation, error handling
- Preset/secrets behavior, report formatting
- Documentation updates:
- README.md: Add 'hermes claw migrate' to Getting Started, new Migration section
- docs/migration/openclaw.md: Full migration guide with all options
- SKILL.md: Add CLI Command section at top of openclaw-migration skill
2026-03-12 08:20:12 -07:00
|
|
|
print()
|
2026-03-30 17:39:08 -07:00
|
|
|
print("Run 'hermes claw <command> --help' for options.")
|
feat: add 'hermes claw migrate' command + migration docs
- Add hermes_cli/claw.py with full CLI migration handler:
- hermes claw migrate (interactive migration with confirmation)
- --dry-run, --preset, --overwrite, --skill-conflict flags
- --source for custom OpenClaw path
- --yes to skip confirmation
- Clean formatted output matching setup wizard style
- Fix Python 3.11+ @dataclass compatibility bug in dynamic module loading:
- Register module in sys.modules before exec_module()
- Fixes both setup.py (PR #981) and new claw.py
- Add 16 tests in tests/hermes_cli/test_claw.py covering:
- Script discovery (project root, installed, missing)
- Command routing
- Dry-run, execute, cancellation, error handling
- Preset/secrets behavior, report formatting
- Documentation updates:
- README.md: Add 'hermes claw migrate' to Getting Started, new Migration section
- docs/migration/openclaw.md: Full migration guide with all options
- SKILL.md: Add CLI Command section at top of openclaw-migration skill
2026-03-12 08:20:12 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _cmd_migrate(args):
|
|
|
|
|
"""Run the OpenClaw → Hermes migration."""
|
2026-03-29 22:49:34 -07:00
|
|
|
# Check current and legacy OpenClaw directories
|
|
|
|
|
explicit_source = getattr(args, "source", None)
|
|
|
|
|
if explicit_source:
|
|
|
|
|
source_dir = Path(explicit_source)
|
|
|
|
|
else:
|
|
|
|
|
source_dir = Path.home() / ".openclaw"
|
|
|
|
|
if not source_dir.is_dir():
|
|
|
|
|
# Try legacy directory names
|
feat: rebrand OpenClaw references to Hermes during migration
- Add rebrand_text() that replaces OpenClaw, Open Claw, Open-Claw,
ClawdBot, and MoltBot with Hermes (case-insensitive, word-boundary)
- Apply rebranding to memory entries (MEMORY.md, USER.md, daily memory)
- Apply rebranding to SOUL.md and workspace instructions via new
transform parameter on copy_file()
- Fix moldbot -> moltbot typo across codebase (claw.py, migration
script, docs, tests)
- Add unit tests for rebrand_text and integration tests for memory
and soul migration rebranding
2026-04-11 23:47:37 -07:00
|
|
|
for legacy in (".clawdbot", ".moltbot"):
|
2026-03-29 22:49:34 -07:00
|
|
|
candidate = Path.home() / legacy
|
|
|
|
|
if candidate.is_dir():
|
|
|
|
|
source_dir = candidate
|
|
|
|
|
break
|
feat: add 'hermes claw migrate' command + migration docs
- Add hermes_cli/claw.py with full CLI migration handler:
- hermes claw migrate (interactive migration with confirmation)
- --dry-run, --preset, --overwrite, --skill-conflict flags
- --source for custom OpenClaw path
- --yes to skip confirmation
- Clean formatted output matching setup wizard style
- Fix Python 3.11+ @dataclass compatibility bug in dynamic module loading:
- Register module in sys.modules before exec_module()
- Fixes both setup.py (PR #981) and new claw.py
- Add 16 tests in tests/hermes_cli/test_claw.py covering:
- Script discovery (project root, installed, missing)
- Command routing
- Dry-run, execute, cancellation, error handling
- Preset/secrets behavior, report formatting
- Documentation updates:
- README.md: Add 'hermes claw migrate' to Getting Started, new Migration section
- docs/migration/openclaw.md: Full migration guide with all options
- SKILL.md: Add CLI Command section at top of openclaw-migration skill
2026-03-12 08:20:12 -07:00
|
|
|
dry_run = getattr(args, "dry_run", False)
|
|
|
|
|
preset = getattr(args, "preset", "full")
|
|
|
|
|
overwrite = getattr(args, "overwrite", False)
|
|
|
|
|
migrate_secrets = getattr(args, "migrate_secrets", False)
|
|
|
|
|
workspace_target = getattr(args, "workspace_target", None)
|
|
|
|
|
skill_conflict = getattr(args, "skill_conflict", "skip")
|
|
|
|
|
|
|
|
|
|
# If using the "full" preset, secrets are included by default
|
|
|
|
|
if preset == "full":
|
|
|
|
|
migrate_secrets = True
|
|
|
|
|
|
|
|
|
|
print()
|
|
|
|
|
print(
|
|
|
|
|
color(
|
|
|
|
|
"┌─────────────────────────────────────────────────────────┐",
|
|
|
|
|
Colors.MAGENTA,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
print(
|
|
|
|
|
color(
|
|
|
|
|
"│ ⚕ Hermes — OpenClaw Migration │",
|
|
|
|
|
Colors.MAGENTA,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
print(
|
|
|
|
|
color(
|
|
|
|
|
"└─────────────────────────────────────────────────────────┘",
|
|
|
|
|
Colors.MAGENTA,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Check source directory
|
|
|
|
|
if not source_dir.is_dir():
|
|
|
|
|
print()
|
|
|
|
|
print_error(f"OpenClaw directory not found: {source_dir}")
|
|
|
|
|
print_info("Make sure your OpenClaw installation is at the expected path.")
|
chore: fix 154 f-strings, simplify getattr/URL patterns, remove dead code (#3119)
Three categories of cleanup, all zero-behavioral-change:
1. F-strings without placeholders (154 fixes across 29 files)
- Converted f'...' to '...' where no {expression} was present
- Heaviest files: run_agent.py (24), cli.py (20), honcho_integration/cli.py (34)
2. Simplify defensive patterns in run_agent.py
- Added explicit self._is_anthropic_oauth = False in __init__ (before
the api_mode branch that conditionally sets it)
- Replaced 7x getattr(self, '_is_anthropic_oauth', False) with direct
self._is_anthropic_oauth (attribute always initialized now)
- Added _is_openrouter_url() and _is_anthropic_url() helper methods
- Replaced 3 inline 'openrouter' in self._base_url_lower checks
3. Remove dead code in small files
- hermes_cli/claw.py: removed unused 'total' computation
- tools/fuzzy_match.py: removed unused strip_indent() function and
pattern_stripped variable
Full test suite: 6184 passed, 0 failures
E2E PTY: banner clean, tool calls work, zero garbled ANSI
2026-03-25 19:47:58 -07:00
|
|
|
print_info("You can specify a custom path: hermes claw migrate --source /path/to/.openclaw")
|
feat: add 'hermes claw migrate' command + migration docs
- Add hermes_cli/claw.py with full CLI migration handler:
- hermes claw migrate (interactive migration with confirmation)
- --dry-run, --preset, --overwrite, --skill-conflict flags
- --source for custom OpenClaw path
- --yes to skip confirmation
- Clean formatted output matching setup wizard style
- Fix Python 3.11+ @dataclass compatibility bug in dynamic module loading:
- Register module in sys.modules before exec_module()
- Fixes both setup.py (PR #981) and new claw.py
- Add 16 tests in tests/hermes_cli/test_claw.py covering:
- Script discovery (project root, installed, missing)
- Command routing
- Dry-run, execute, cancellation, error handling
- Preset/secrets behavior, report formatting
- Documentation updates:
- README.md: Add 'hermes claw migrate' to Getting Started, new Migration section
- docs/migration/openclaw.md: Full migration guide with all options
- SKILL.md: Add CLI Command section at top of openclaw-migration skill
2026-03-12 08:20:12 -07:00
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# Find the migration script
|
|
|
|
|
script_path = _find_migration_script()
|
|
|
|
|
if not script_path:
|
|
|
|
|
print()
|
|
|
|
|
print_error("Migration script not found.")
|
|
|
|
|
print_info("Expected at one of:")
|
|
|
|
|
print_info(f" {_OPENCLAW_SCRIPT}")
|
|
|
|
|
print_info(f" {_OPENCLAW_SCRIPT_INSTALLED}")
|
|
|
|
|
print_info("Make sure the openclaw-migration skill is installed.")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# Show what we're doing
|
|
|
|
|
hermes_home = get_hermes_home()
|
2026-04-11 11:21:54 -07:00
|
|
|
auto_yes = getattr(args, "yes", False)
|
feat: add 'hermes claw migrate' command + migration docs
- Add hermes_cli/claw.py with full CLI migration handler:
- hermes claw migrate (interactive migration with confirmation)
- --dry-run, --preset, --overwrite, --skill-conflict flags
- --source for custom OpenClaw path
- --yes to skip confirmation
- Clean formatted output matching setup wizard style
- Fix Python 3.11+ @dataclass compatibility bug in dynamic module loading:
- Register module in sys.modules before exec_module()
- Fixes both setup.py (PR #981) and new claw.py
- Add 16 tests in tests/hermes_cli/test_claw.py covering:
- Script discovery (project root, installed, missing)
- Command routing
- Dry-run, execute, cancellation, error handling
- Preset/secrets behavior, report formatting
- Documentation updates:
- README.md: Add 'hermes claw migrate' to Getting Started, new Migration section
- docs/migration/openclaw.md: Full migration guide with all options
- SKILL.md: Add CLI Command section at top of openclaw-migration skill
2026-03-12 08:20:12 -07:00
|
|
|
print()
|
|
|
|
|
print_header("Migration Settings")
|
|
|
|
|
print_info(f"Source: {source_dir}")
|
|
|
|
|
print_info(f"Target: {hermes_home}")
|
|
|
|
|
print_info(f"Preset: {preset}")
|
|
|
|
|
print_info(f"Overwrite: {'yes' if overwrite else 'no (skip conflicts)'}")
|
|
|
|
|
print_info(f"Secrets: {'yes (allowlisted only)' if migrate_secrets else 'no'}")
|
|
|
|
|
if skill_conflict != "skip":
|
|
|
|
|
print_info(f"Skill conflicts: {skill_conflict}")
|
|
|
|
|
if workspace_target:
|
|
|
|
|
print_info(f"Workspace: {workspace_target}")
|
|
|
|
|
print()
|
|
|
|
|
|
2026-04-12 10:34:38 +08:00
|
|
|
# Check if OpenClaw is still running — migrating tokens while both are
|
|
|
|
|
# active will cause conflicts (e.g. Telegram 409).
|
|
|
|
|
_warn_if_openclaw_running(auto_yes)
|
|
|
|
|
|
|
|
|
|
# Check if a Hermes gateway is running with connected platforms.
|
2026-04-11 14:47:03 -07:00
|
|
|
_warn_if_gateway_running(auto_yes)
|
|
|
|
|
|
feat: add 'hermes claw migrate' command + migration docs
- Add hermes_cli/claw.py with full CLI migration handler:
- hermes claw migrate (interactive migration with confirmation)
- --dry-run, --preset, --overwrite, --skill-conflict flags
- --source for custom OpenClaw path
- --yes to skip confirmation
- Clean formatted output matching setup wizard style
- Fix Python 3.11+ @dataclass compatibility bug in dynamic module loading:
- Register module in sys.modules before exec_module()
- Fixes both setup.py (PR #981) and new claw.py
- Add 16 tests in tests/hermes_cli/test_claw.py covering:
- Script discovery (project root, installed, missing)
- Command routing
- Dry-run, execute, cancellation, error handling
- Preset/secrets behavior, report formatting
- Documentation updates:
- README.md: Add 'hermes claw migrate' to Getting Started, new Migration section
- docs/migration/openclaw.md: Full migration guide with all options
- SKILL.md: Add CLI Command section at top of openclaw-migration skill
2026-03-12 08:20:12 -07:00
|
|
|
# Ensure config.yaml exists before migration tries to read it
|
|
|
|
|
config_path = get_config_path()
|
|
|
|
|
if not config_path.exists():
|
|
|
|
|
save_config(load_config())
|
|
|
|
|
|
2026-04-11 11:21:54 -07:00
|
|
|
# Load the migration module
|
feat: add 'hermes claw migrate' command + migration docs
- Add hermes_cli/claw.py with full CLI migration handler:
- hermes claw migrate (interactive migration with confirmation)
- --dry-run, --preset, --overwrite, --skill-conflict flags
- --source for custom OpenClaw path
- --yes to skip confirmation
- Clean formatted output matching setup wizard style
- Fix Python 3.11+ @dataclass compatibility bug in dynamic module loading:
- Register module in sys.modules before exec_module()
- Fixes both setup.py (PR #981) and new claw.py
- Add 16 tests in tests/hermes_cli/test_claw.py covering:
- Script discovery (project root, installed, missing)
- Command routing
- Dry-run, execute, cancellation, error handling
- Preset/secrets behavior, report formatting
- Documentation updates:
- README.md: Add 'hermes claw migrate' to Getting Started, new Migration section
- docs/migration/openclaw.md: Full migration guide with all options
- SKILL.md: Add CLI Command section at top of openclaw-migration skill
2026-03-12 08:20:12 -07:00
|
|
|
try:
|
|
|
|
|
mod = _load_migration_module(script_path)
|
|
|
|
|
if mod is None:
|
|
|
|
|
print_error("Could not load migration script.")
|
|
|
|
|
return
|
2026-04-11 11:21:54 -07:00
|
|
|
except Exception as e:
|
|
|
|
|
print()
|
|
|
|
|
print_error(f"Could not load migration script: {e}")
|
|
|
|
|
logger.debug("OpenClaw migration error", exc_info=True)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
selected = mod.resolve_selected_options(None, None, preset=preset)
|
|
|
|
|
ws_target = Path(workspace_target).resolve() if workspace_target else None
|
|
|
|
|
|
|
|
|
|
# ── Phase 1: Always preview first ──────────────────────────
|
|
|
|
|
try:
|
|
|
|
|
preview = mod.Migrator(
|
|
|
|
|
source_root=source_dir.resolve(),
|
|
|
|
|
target_root=hermes_home.resolve(),
|
|
|
|
|
execute=False,
|
|
|
|
|
workspace_target=ws_target,
|
|
|
|
|
overwrite=overwrite,
|
|
|
|
|
migrate_secrets=migrate_secrets,
|
|
|
|
|
output_dir=None,
|
|
|
|
|
selected_options=selected,
|
|
|
|
|
preset_name=preset,
|
|
|
|
|
skill_conflict_mode=skill_conflict,
|
|
|
|
|
)
|
|
|
|
|
preview_report = preview.migrate()
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print()
|
|
|
|
|
print_error(f"Migration preview failed: {e}")
|
|
|
|
|
logger.debug("OpenClaw migration preview error", exc_info=True)
|
|
|
|
|
return
|
feat: add 'hermes claw migrate' command + migration docs
- Add hermes_cli/claw.py with full CLI migration handler:
- hermes claw migrate (interactive migration with confirmation)
- --dry-run, --preset, --overwrite, --skill-conflict flags
- --source for custom OpenClaw path
- --yes to skip confirmation
- Clean formatted output matching setup wizard style
- Fix Python 3.11+ @dataclass compatibility bug in dynamic module loading:
- Register module in sys.modules before exec_module()
- Fixes both setup.py (PR #981) and new claw.py
- Add 16 tests in tests/hermes_cli/test_claw.py covering:
- Script discovery (project root, installed, missing)
- Command routing
- Dry-run, execute, cancellation, error handling
- Preset/secrets behavior, report formatting
- Documentation updates:
- README.md: Add 'hermes claw migrate' to Getting Started, new Migration section
- docs/migration/openclaw.md: Full migration guide with all options
- SKILL.md: Add CLI Command section at top of openclaw-migration skill
2026-03-12 08:20:12 -07:00
|
|
|
|
2026-04-11 11:21:54 -07:00
|
|
|
preview_summary = preview_report.get("summary", {})
|
|
|
|
|
preview_count = preview_summary.get("migrated", 0)
|
feat: add 'hermes claw migrate' command + migration docs
- Add hermes_cli/claw.py with full CLI migration handler:
- hermes claw migrate (interactive migration with confirmation)
- --dry-run, --preset, --overwrite, --skill-conflict flags
- --source for custom OpenClaw path
- --yes to skip confirmation
- Clean formatted output matching setup wizard style
- Fix Python 3.11+ @dataclass compatibility bug in dynamic module loading:
- Register module in sys.modules before exec_module()
- Fixes both setup.py (PR #981) and new claw.py
- Add 16 tests in tests/hermes_cli/test_claw.py covering:
- Script discovery (project root, installed, missing)
- Command routing
- Dry-run, execute, cancellation, error handling
- Preset/secrets behavior, report formatting
- Documentation updates:
- README.md: Add 'hermes claw migrate' to Getting Started, new Migration section
- docs/migration/openclaw.md: Full migration guide with all options
- SKILL.md: Add CLI Command section at top of openclaw-migration skill
2026-03-12 08:20:12 -07:00
|
|
|
|
2026-04-11 11:21:54 -07:00
|
|
|
if preview_count == 0:
|
|
|
|
|
print()
|
|
|
|
|
print_info("Nothing to migrate from OpenClaw.")
|
|
|
|
|
_print_migration_report(preview_report, dry_run=True)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
print()
|
|
|
|
|
print_header(f"Migration Preview — {preview_count} item(s) would be imported")
|
|
|
|
|
print_info("No changes have been made yet. Review the list below:")
|
|
|
|
|
_print_migration_report(preview_report, dry_run=True)
|
|
|
|
|
|
|
|
|
|
# If --dry-run, stop here
|
|
|
|
|
if dry_run:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# ── Phase 2: Confirm and execute ───────────────────────────
|
|
|
|
|
print()
|
|
|
|
|
if not auto_yes:
|
|
|
|
|
if not sys.stdin.isatty():
|
|
|
|
|
print_info("Non-interactive session — preview only.")
|
|
|
|
|
print_info("To execute, re-run with: hermes claw migrate --yes")
|
|
|
|
|
return
|
|
|
|
|
if not prompt_yes_no("Proceed with migration?", default=True):
|
|
|
|
|
print_info("Migration cancelled.")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
try:
|
feat: add 'hermes claw migrate' command + migration docs
- Add hermes_cli/claw.py with full CLI migration handler:
- hermes claw migrate (interactive migration with confirmation)
- --dry-run, --preset, --overwrite, --skill-conflict flags
- --source for custom OpenClaw path
- --yes to skip confirmation
- Clean formatted output matching setup wizard style
- Fix Python 3.11+ @dataclass compatibility bug in dynamic module loading:
- Register module in sys.modules before exec_module()
- Fixes both setup.py (PR #981) and new claw.py
- Add 16 tests in tests/hermes_cli/test_claw.py covering:
- Script discovery (project root, installed, missing)
- Command routing
- Dry-run, execute, cancellation, error handling
- Preset/secrets behavior, report formatting
- Documentation updates:
- README.md: Add 'hermes claw migrate' to Getting Started, new Migration section
- docs/migration/openclaw.md: Full migration guide with all options
- SKILL.md: Add CLI Command section at top of openclaw-migration skill
2026-03-12 08:20:12 -07:00
|
|
|
migrator = mod.Migrator(
|
|
|
|
|
source_root=source_dir.resolve(),
|
|
|
|
|
target_root=hermes_home.resolve(),
|
2026-04-11 11:21:54 -07:00
|
|
|
execute=True,
|
feat: add 'hermes claw migrate' command + migration docs
- Add hermes_cli/claw.py with full CLI migration handler:
- hermes claw migrate (interactive migration with confirmation)
- --dry-run, --preset, --overwrite, --skill-conflict flags
- --source for custom OpenClaw path
- --yes to skip confirmation
- Clean formatted output matching setup wizard style
- Fix Python 3.11+ @dataclass compatibility bug in dynamic module loading:
- Register module in sys.modules before exec_module()
- Fixes both setup.py (PR #981) and new claw.py
- Add 16 tests in tests/hermes_cli/test_claw.py covering:
- Script discovery (project root, installed, missing)
- Command routing
- Dry-run, execute, cancellation, error handling
- Preset/secrets behavior, report formatting
- Documentation updates:
- README.md: Add 'hermes claw migrate' to Getting Started, new Migration section
- docs/migration/openclaw.md: Full migration guide with all options
- SKILL.md: Add CLI Command section at top of openclaw-migration skill
2026-03-12 08:20:12 -07:00
|
|
|
workspace_target=ws_target,
|
|
|
|
|
overwrite=overwrite,
|
|
|
|
|
migrate_secrets=migrate_secrets,
|
|
|
|
|
output_dir=None,
|
|
|
|
|
selected_options=selected,
|
|
|
|
|
preset_name=preset,
|
|
|
|
|
skill_conflict_mode=skill_conflict,
|
|
|
|
|
)
|
|
|
|
|
report = migrator.migrate()
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print()
|
|
|
|
|
print_error(f"Migration failed: {e}")
|
|
|
|
|
logger.debug("OpenClaw migration error", exc_info=True)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# Print results
|
2026-04-11 11:21:54 -07:00
|
|
|
_print_migration_report(report, dry_run=False)
|
feat: add 'hermes claw migrate' command + migration docs
- Add hermes_cli/claw.py with full CLI migration handler:
- hermes claw migrate (interactive migration with confirmation)
- --dry-run, --preset, --overwrite, --skill-conflict flags
- --source for custom OpenClaw path
- --yes to skip confirmation
- Clean formatted output matching setup wizard style
- Fix Python 3.11+ @dataclass compatibility bug in dynamic module loading:
- Register module in sys.modules before exec_module()
- Fixes both setup.py (PR #981) and new claw.py
- Add 16 tests in tests/hermes_cli/test_claw.py covering:
- Script discovery (project root, installed, missing)
- Command routing
- Dry-run, execute, cancellation, error handling
- Preset/secrets behavior, report formatting
- Documentation updates:
- README.md: Add 'hermes claw migrate' to Getting Started, new Migration section
- docs/migration/openclaw.md: Full migration guide with all options
- SKILL.md: Add CLI Command section at top of openclaw-migration skill
2026-03-12 08:20:12 -07:00
|
|
|
|
2026-04-12 00:27:18 -07:00
|
|
|
# Source directory is left untouched — archiving is not the migration
|
|
|
|
|
# tool's responsibility. Users who want to clean up can run
|
|
|
|
|
# 'hermes claw cleanup' separately.
|
2026-03-30 17:39:08 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _cmd_cleanup(args):
|
|
|
|
|
"""Archive leftover OpenClaw directories after migration.
|
|
|
|
|
|
|
|
|
|
Scans for OpenClaw directories that still exist after migration and offers
|
2026-04-12 00:27:18 -07:00
|
|
|
to rename them to .pre-migration to free disk space.
|
2026-03-30 17:39:08 -07:00
|
|
|
"""
|
|
|
|
|
dry_run = getattr(args, "dry_run", False)
|
|
|
|
|
auto_yes = getattr(args, "yes", False)
|
|
|
|
|
explicit_source = getattr(args, "source", None)
|
|
|
|
|
|
|
|
|
|
print()
|
|
|
|
|
print(
|
|
|
|
|
color(
|
|
|
|
|
"┌─────────────────────────────────────────────────────────┐",
|
|
|
|
|
Colors.MAGENTA,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
print(
|
|
|
|
|
color(
|
|
|
|
|
"│ ⚕ Hermes — OpenClaw Cleanup │",
|
|
|
|
|
Colors.MAGENTA,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
print(
|
|
|
|
|
color(
|
|
|
|
|
"└─────────────────────────────────────────────────────────┘",
|
|
|
|
|
Colors.MAGENTA,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Find OpenClaw directories
|
|
|
|
|
if explicit_source:
|
|
|
|
|
dirs_to_check = [Path(explicit_source)]
|
|
|
|
|
else:
|
|
|
|
|
dirs_to_check = _find_openclaw_dirs()
|
|
|
|
|
|
|
|
|
|
if not dirs_to_check:
|
|
|
|
|
print()
|
|
|
|
|
print_success("No OpenClaw directories found. Nothing to clean up.")
|
|
|
|
|
return
|
2026-04-12 16:40:10 -07:00
|
|
|
|
|
|
|
|
# Warn if OpenClaw is still running — archiving while the service is
|
|
|
|
|
# active causes it to recreate an empty skeleton directory (#8502).
|
|
|
|
|
running = _detect_openclaw_processes()
|
2026-04-12 22:20:24 +03:00
|
|
|
if running:
|
|
|
|
|
print()
|
2026-04-12 16:40:10 -07:00
|
|
|
print_error("OpenClaw appears to be still running:")
|
|
|
|
|
for detail in running:
|
|
|
|
|
print_info(f" * {detail}")
|
|
|
|
|
print_info(
|
|
|
|
|
"Archiving .openclaw/ while the service is active may cause it to "
|
|
|
|
|
"immediately recreate an empty skeleton directory, destroying your config."
|
|
|
|
|
)
|
|
|
|
|
print_info("Stop OpenClaw first: systemctl --user stop openclaw-gateway.service")
|
2026-04-12 22:20:24 +03:00
|
|
|
print()
|
|
|
|
|
if not auto_yes:
|
2026-04-12 16:40:10 -07:00
|
|
|
if not sys.stdin.isatty():
|
|
|
|
|
print_info("Non-interactive session — aborting. Stop OpenClaw and re-run.")
|
|
|
|
|
return
|
2026-04-12 22:20:24 +03:00
|
|
|
if not prompt_yes_no("Proceed anyway?", default=False):
|
|
|
|
|
print_info("Aborted. Stop OpenClaw first, then re-run: hermes claw cleanup")
|
|
|
|
|
return
|
2026-03-30 17:39:08 -07:00
|
|
|
|
|
|
|
|
total_archived = 0
|
|
|
|
|
|
|
|
|
|
for source_dir in dirs_to_check:
|
|
|
|
|
print()
|
|
|
|
|
print_header(f"Found: {source_dir}")
|
|
|
|
|
|
|
|
|
|
# Scan for state files
|
|
|
|
|
state_files = _scan_workspace_state(source_dir)
|
|
|
|
|
|
|
|
|
|
# Show directory stats
|
|
|
|
|
try:
|
|
|
|
|
workspace_dirs = [
|
|
|
|
|
d for d in source_dir.iterdir()
|
|
|
|
|
if d.is_dir() and not d.name.startswith(".")
|
|
|
|
|
and any((d / name).exists() for name in ("todo.json", "SOUL.md", "MEMORY.md", "USER.md"))
|
|
|
|
|
]
|
|
|
|
|
except OSError:
|
|
|
|
|
workspace_dirs = []
|
|
|
|
|
|
|
|
|
|
if workspace_dirs:
|
|
|
|
|
print_info(f"Workspace directories: {len(workspace_dirs)}")
|
|
|
|
|
for ws in workspace_dirs[:5]:
|
|
|
|
|
items = []
|
|
|
|
|
if (ws / "todo.json").exists():
|
|
|
|
|
items.append("todo.json")
|
|
|
|
|
if (ws / "sessions").is_dir():
|
|
|
|
|
items.append("sessions/")
|
|
|
|
|
if (ws / "SOUL.md").exists():
|
|
|
|
|
items.append("SOUL.md")
|
|
|
|
|
if (ws / "MEMORY.md").exists():
|
|
|
|
|
items.append("MEMORY.md")
|
|
|
|
|
detail = ", ".join(items) if items else "empty"
|
|
|
|
|
print(f" {ws.name}/ ({detail})")
|
|
|
|
|
if len(workspace_dirs) > 5:
|
|
|
|
|
print(f" ... and {len(workspace_dirs) - 5} more")
|
|
|
|
|
|
|
|
|
|
if state_files:
|
|
|
|
|
print()
|
2026-04-12 00:27:18 -07:00
|
|
|
print(color(f" {len(state_files)} state file(s) found:", Colors.YELLOW))
|
2026-03-30 17:39:08 -07:00
|
|
|
for path, desc in state_files[:8]:
|
|
|
|
|
print(f" {desc}")
|
|
|
|
|
if len(state_files) > 8:
|
|
|
|
|
print(f" ... and {len(state_files) - 8} more")
|
|
|
|
|
|
|
|
|
|
print()
|
|
|
|
|
|
|
|
|
|
if dry_run:
|
|
|
|
|
archive_path = _archive_directory(source_dir, dry_run=True)
|
|
|
|
|
print_info(f"Would archive: {source_dir} → {archive_path}")
|
2026-04-11 11:21:54 -07:00
|
|
|
elif not auto_yes and not sys.stdin.isatty():
|
|
|
|
|
print_info(f"Non-interactive session — would archive: {source_dir}")
|
|
|
|
|
print_info("To execute, re-run with: hermes claw cleanup --yes")
|
2026-03-30 17:39:08 -07:00
|
|
|
else:
|
|
|
|
|
if auto_yes or prompt_yes_no(f"Archive {source_dir}?", default=True):
|
|
|
|
|
try:
|
|
|
|
|
archive_path = _archive_directory(source_dir)
|
|
|
|
|
print_success(f"Archived: {source_dir} → {archive_path}")
|
|
|
|
|
total_archived += 1
|
|
|
|
|
except OSError as e:
|
|
|
|
|
print_error(f"Could not archive: {e}")
|
|
|
|
|
print_info(f"Try manually: mv {source_dir} {source_dir}.pre-migration")
|
|
|
|
|
else:
|
|
|
|
|
print_info("Skipped.")
|
|
|
|
|
|
|
|
|
|
# Summary
|
|
|
|
|
print()
|
|
|
|
|
if dry_run:
|
|
|
|
|
print_info(f"Dry run complete. {len(dirs_to_check)} directory(ies) would be archived.")
|
|
|
|
|
print_info("Run without --dry-run to archive them.")
|
|
|
|
|
elif total_archived:
|
|
|
|
|
print_success(f"Cleaned up {total_archived} OpenClaw directory(ies).")
|
|
|
|
|
print_info("Directories were renamed, not deleted. You can undo by renaming them back.")
|
|
|
|
|
else:
|
|
|
|
|
print_info("No directories were archived.")
|
|
|
|
|
|
feat: add 'hermes claw migrate' command + migration docs
- Add hermes_cli/claw.py with full CLI migration handler:
- hermes claw migrate (interactive migration with confirmation)
- --dry-run, --preset, --overwrite, --skill-conflict flags
- --source for custom OpenClaw path
- --yes to skip confirmation
- Clean formatted output matching setup wizard style
- Fix Python 3.11+ @dataclass compatibility bug in dynamic module loading:
- Register module in sys.modules before exec_module()
- Fixes both setup.py (PR #981) and new claw.py
- Add 16 tests in tests/hermes_cli/test_claw.py covering:
- Script discovery (project root, installed, missing)
- Command routing
- Dry-run, execute, cancellation, error handling
- Preset/secrets behavior, report formatting
- Documentation updates:
- README.md: Add 'hermes claw migrate' to Getting Started, new Migration section
- docs/migration/openclaw.md: Full migration guide with all options
- SKILL.md: Add CLI Command section at top of openclaw-migration skill
2026-03-12 08:20:12 -07:00
|
|
|
|
|
|
|
|
def _print_migration_report(report: dict, dry_run: bool):
|
|
|
|
|
"""Print a formatted migration report."""
|
|
|
|
|
summary = report.get("summary", {})
|
|
|
|
|
migrated = summary.get("migrated", 0)
|
|
|
|
|
skipped = summary.get("skipped", 0)
|
|
|
|
|
conflicts = summary.get("conflict", 0)
|
|
|
|
|
errors = summary.get("error", 0)
|
|
|
|
|
|
|
|
|
|
print()
|
|
|
|
|
if dry_run:
|
|
|
|
|
print_header("Dry Run Results")
|
|
|
|
|
print_info("No files were modified. This is a preview of what would happen.")
|
|
|
|
|
else:
|
|
|
|
|
print_header("Migration Results")
|
|
|
|
|
|
|
|
|
|
print()
|
|
|
|
|
|
|
|
|
|
# Detailed items
|
|
|
|
|
items = report.get("items", [])
|
|
|
|
|
if items:
|
|
|
|
|
# Group by status
|
|
|
|
|
migrated_items = [i for i in items if i.get("status") == "migrated"]
|
|
|
|
|
skipped_items = [i for i in items if i.get("status") == "skipped"]
|
|
|
|
|
conflict_items = [i for i in items if i.get("status") == "conflict"]
|
|
|
|
|
error_items = [i for i in items if i.get("status") == "error"]
|
|
|
|
|
|
|
|
|
|
if migrated_items:
|
|
|
|
|
label = "Would migrate" if dry_run else "Migrated"
|
|
|
|
|
print(color(f" ✓ {label}:", Colors.GREEN))
|
|
|
|
|
for item in migrated_items:
|
|
|
|
|
kind = item.get("kind", "unknown")
|
|
|
|
|
dest = item.get("destination", "")
|
|
|
|
|
if dest:
|
|
|
|
|
dest_short = str(dest).replace(str(Path.home()), "~")
|
|
|
|
|
print(f" {kind:<22s} → {dest_short}")
|
|
|
|
|
else:
|
|
|
|
|
print(f" {kind}")
|
|
|
|
|
print()
|
|
|
|
|
|
|
|
|
|
if conflict_items:
|
chore: fix 154 f-strings, simplify getattr/URL patterns, remove dead code (#3119)
Three categories of cleanup, all zero-behavioral-change:
1. F-strings without placeholders (154 fixes across 29 files)
- Converted f'...' to '...' where no {expression} was present
- Heaviest files: run_agent.py (24), cli.py (20), honcho_integration/cli.py (34)
2. Simplify defensive patterns in run_agent.py
- Added explicit self._is_anthropic_oauth = False in __init__ (before
the api_mode branch that conditionally sets it)
- Replaced 7x getattr(self, '_is_anthropic_oauth', False) with direct
self._is_anthropic_oauth (attribute always initialized now)
- Added _is_openrouter_url() and _is_anthropic_url() helper methods
- Replaced 3 inline 'openrouter' in self._base_url_lower checks
3. Remove dead code in small files
- hermes_cli/claw.py: removed unused 'total' computation
- tools/fuzzy_match.py: removed unused strip_indent() function and
pattern_stripped variable
Full test suite: 6184 passed, 0 failures
E2E PTY: banner clean, tool calls work, zero garbled ANSI
2026-03-25 19:47:58 -07:00
|
|
|
print(color(" ⚠ Conflicts (skipped — use --overwrite to force):", Colors.YELLOW))
|
feat: add 'hermes claw migrate' command + migration docs
- Add hermes_cli/claw.py with full CLI migration handler:
- hermes claw migrate (interactive migration with confirmation)
- --dry-run, --preset, --overwrite, --skill-conflict flags
- --source for custom OpenClaw path
- --yes to skip confirmation
- Clean formatted output matching setup wizard style
- Fix Python 3.11+ @dataclass compatibility bug in dynamic module loading:
- Register module in sys.modules before exec_module()
- Fixes both setup.py (PR #981) and new claw.py
- Add 16 tests in tests/hermes_cli/test_claw.py covering:
- Script discovery (project root, installed, missing)
- Command routing
- Dry-run, execute, cancellation, error handling
- Preset/secrets behavior, report formatting
- Documentation updates:
- README.md: Add 'hermes claw migrate' to Getting Started, new Migration section
- docs/migration/openclaw.md: Full migration guide with all options
- SKILL.md: Add CLI Command section at top of openclaw-migration skill
2026-03-12 08:20:12 -07:00
|
|
|
for item in conflict_items:
|
|
|
|
|
kind = item.get("kind", "unknown")
|
|
|
|
|
reason = item.get("reason", "already exists")
|
|
|
|
|
print(f" {kind:<22s} {reason}")
|
|
|
|
|
print()
|
|
|
|
|
|
|
|
|
|
if skipped_items:
|
chore: fix 154 f-strings, simplify getattr/URL patterns, remove dead code (#3119)
Three categories of cleanup, all zero-behavioral-change:
1. F-strings without placeholders (154 fixes across 29 files)
- Converted f'...' to '...' where no {expression} was present
- Heaviest files: run_agent.py (24), cli.py (20), honcho_integration/cli.py (34)
2. Simplify defensive patterns in run_agent.py
- Added explicit self._is_anthropic_oauth = False in __init__ (before
the api_mode branch that conditionally sets it)
- Replaced 7x getattr(self, '_is_anthropic_oauth', False) with direct
self._is_anthropic_oauth (attribute always initialized now)
- Added _is_openrouter_url() and _is_anthropic_url() helper methods
- Replaced 3 inline 'openrouter' in self._base_url_lower checks
3. Remove dead code in small files
- hermes_cli/claw.py: removed unused 'total' computation
- tools/fuzzy_match.py: removed unused strip_indent() function and
pattern_stripped variable
Full test suite: 6184 passed, 0 failures
E2E PTY: banner clean, tool calls work, zero garbled ANSI
2026-03-25 19:47:58 -07:00
|
|
|
print(color(" ─ Skipped:", Colors.DIM))
|
feat: add 'hermes claw migrate' command + migration docs
- Add hermes_cli/claw.py with full CLI migration handler:
- hermes claw migrate (interactive migration with confirmation)
- --dry-run, --preset, --overwrite, --skill-conflict flags
- --source for custom OpenClaw path
- --yes to skip confirmation
- Clean formatted output matching setup wizard style
- Fix Python 3.11+ @dataclass compatibility bug in dynamic module loading:
- Register module in sys.modules before exec_module()
- Fixes both setup.py (PR #981) and new claw.py
- Add 16 tests in tests/hermes_cli/test_claw.py covering:
- Script discovery (project root, installed, missing)
- Command routing
- Dry-run, execute, cancellation, error handling
- Preset/secrets behavior, report formatting
- Documentation updates:
- README.md: Add 'hermes claw migrate' to Getting Started, new Migration section
- docs/migration/openclaw.md: Full migration guide with all options
- SKILL.md: Add CLI Command section at top of openclaw-migration skill
2026-03-12 08:20:12 -07:00
|
|
|
for item in skipped_items:
|
|
|
|
|
kind = item.get("kind", "unknown")
|
|
|
|
|
reason = item.get("reason", "")
|
|
|
|
|
print(f" {kind:<22s} {reason}")
|
|
|
|
|
print()
|
|
|
|
|
|
|
|
|
|
if error_items:
|
chore: fix 154 f-strings, simplify getattr/URL patterns, remove dead code (#3119)
Three categories of cleanup, all zero-behavioral-change:
1. F-strings without placeholders (154 fixes across 29 files)
- Converted f'...' to '...' where no {expression} was present
- Heaviest files: run_agent.py (24), cli.py (20), honcho_integration/cli.py (34)
2. Simplify defensive patterns in run_agent.py
- Added explicit self._is_anthropic_oauth = False in __init__ (before
the api_mode branch that conditionally sets it)
- Replaced 7x getattr(self, '_is_anthropic_oauth', False) with direct
self._is_anthropic_oauth (attribute always initialized now)
- Added _is_openrouter_url() and _is_anthropic_url() helper methods
- Replaced 3 inline 'openrouter' in self._base_url_lower checks
3. Remove dead code in small files
- hermes_cli/claw.py: removed unused 'total' computation
- tools/fuzzy_match.py: removed unused strip_indent() function and
pattern_stripped variable
Full test suite: 6184 passed, 0 failures
E2E PTY: banner clean, tool calls work, zero garbled ANSI
2026-03-25 19:47:58 -07:00
|
|
|
print(color(" ✗ Errors:", Colors.RED))
|
feat: add 'hermes claw migrate' command + migration docs
- Add hermes_cli/claw.py with full CLI migration handler:
- hermes claw migrate (interactive migration with confirmation)
- --dry-run, --preset, --overwrite, --skill-conflict flags
- --source for custom OpenClaw path
- --yes to skip confirmation
- Clean formatted output matching setup wizard style
- Fix Python 3.11+ @dataclass compatibility bug in dynamic module loading:
- Register module in sys.modules before exec_module()
- Fixes both setup.py (PR #981) and new claw.py
- Add 16 tests in tests/hermes_cli/test_claw.py covering:
- Script discovery (project root, installed, missing)
- Command routing
- Dry-run, execute, cancellation, error handling
- Preset/secrets behavior, report formatting
- Documentation updates:
- README.md: Add 'hermes claw migrate' to Getting Started, new Migration section
- docs/migration/openclaw.md: Full migration guide with all options
- SKILL.md: Add CLI Command section at top of openclaw-migration skill
2026-03-12 08:20:12 -07:00
|
|
|
for item in error_items:
|
|
|
|
|
kind = item.get("kind", "unknown")
|
|
|
|
|
reason = item.get("reason", "unknown error")
|
|
|
|
|
print(f" {kind:<22s} {reason}")
|
|
|
|
|
print()
|
|
|
|
|
|
|
|
|
|
# Summary line
|
|
|
|
|
parts = []
|
|
|
|
|
if migrated:
|
|
|
|
|
action = "would migrate" if dry_run else "migrated"
|
|
|
|
|
parts.append(f"{migrated} {action}")
|
|
|
|
|
if conflicts:
|
|
|
|
|
parts.append(f"{conflicts} conflict(s)")
|
|
|
|
|
if skipped:
|
|
|
|
|
parts.append(f"{skipped} skipped")
|
|
|
|
|
if errors:
|
|
|
|
|
parts.append(f"{errors} error(s)")
|
|
|
|
|
|
|
|
|
|
if parts:
|
|
|
|
|
print_info(f"Summary: {', '.join(parts)}")
|
|
|
|
|
else:
|
|
|
|
|
print_info("Nothing to migrate.")
|
|
|
|
|
|
|
|
|
|
# Output directory
|
|
|
|
|
output_dir = report.get("output_dir")
|
|
|
|
|
if output_dir:
|
|
|
|
|
print_info(f"Full report saved to: {output_dir}")
|
|
|
|
|
|
|
|
|
|
if dry_run:
|
|
|
|
|
print()
|
|
|
|
|
print_info("To execute the migration, run without --dry-run:")
|
|
|
|
|
print_info(f" hermes claw migrate --preset {report.get('preset', 'full')}")
|
|
|
|
|
elif migrated:
|
|
|
|
|
print()
|
|
|
|
|
print_success("Migration complete!")
|
fix(claw): warn when API keys are skipped during OpenClaw migration (#1580)
* fix: prevent infinite 400 failure loop on context overflow (#1630)
When a gateway session exceeds the model's context window, Anthropic may
return a generic 400 invalid_request_error with just 'Error' as the
message. This bypassed the phrase-based context-length detection,
causing the agent to treat it as a non-retryable client error. Worse,
the failed user message was still persisted to the transcript, making
the session even larger on each attempt — creating an infinite loop.
Three-layer fix:
1. run_agent.py — Fallback heuristic: when a 400 error has a very short
generic message AND the session is large (>40% of context or >80
messages), treat it as a probable context overflow and trigger
compression instead of aborting.
2. run_agent.py + gateway/run.py — Don't persist failed messages:
when the agent returns failed=True before generating any response,
skip writing the user's message to the transcript/DB. This prevents
the session from growing on each failure.
3. gateway/run.py — Smarter error messages: detect context-overflow
failures and suggest /compact or /reset specifically, instead of a
generic 'try again' that will fail identically.
* fix(skills): detect prompt injection patterns and block cache file reads
Adds two security layers to prevent prompt injection via skills hub
cache files (#1558):
1. read_file: blocks direct reads of ~/.hermes/skills/.hub/ directory
(index-cache, catalog files). The 3.5MB clawhub_catalog_v1.json
was the original injection vector — untrusted skill descriptions
in the catalog contained adversarial text that the model executed.
2. skill_view: warns when skills are loaded from outside the trusted
~/.hermes/skills/ directory, and detects common injection patterns
in skill content ("ignore previous instructions", "<system>", etc.).
Cherry-picked from PR #1562 by ygd58.
* fix(tools): chunk long messages in send_message_tool before dispatch (#1552)
Long messages sent via send_message tool or cron delivery silently
failed when exceeding platform limits. Gateway adapters handle this
via truncate_message(), but the standalone senders in send_message_tool
bypassed that entirely.
- Apply truncate_message() chunking in _send_to_platform() before
dispatching to individual platform senders
- Remove naive message[i:i+2000] character split in _send_discord()
in favor of centralized smart splitting
- Attach media files to last chunk only for Telegram
- Add regression tests for chunking and media placement
Cherry-picked from PR #1557 by llbn.
* fix(approval): show full command in dangerous command approval (#1553)
Previously the command was truncated to 80 chars in CLI (with a
[v]iew full option), 500 chars in Discord embeds, and missing entirely
in Telegram/Slack approval messages. Now the full command is always
displayed everywhere:
- CLI: removed 80-char truncation and [v]iew full menu option
- Gateway (TG/Slack): approval_required message includes full command
in a code block
- Discord: embed shows full command up to 4096-char limit
- Windows: skip SIGALRM-based test timeout (Unix-only)
- Updated tests: replaced view-flow tests with direct approval tests
Cherry-picked from PR #1566 by crazywriter1.
* fix(cli): flush stdout during agent loop to prevent macOS display freeze (#1624)
The interrupt polling loop in chat() waited on the queue without
invalidating the prompt_toolkit renderer. On macOS, the StdoutProxy
buffer only flushed on input events, causing the CLI to appear frozen
during tool execution until the user typed a key.
Fix: call _invalidate() on each queue timeout (every ~100ms, throttled
to 150ms) to force the renderer to flush buffered agent output.
* fix(claw): warn when API keys are skipped during OpenClaw migration (#1580)
When --migrate-secrets is not passed (the default), API keys like
OPENROUTER_API_KEY are silently skipped with no warning. Users don't
realize their keys weren't migrated until the agent fails to connect.
Add a post-migration warning with actionable instructions: either
re-run with --migrate-secrets or add the key manually via
hermes config set.
Cherry-picked from PR #1593 by ygd58.
---------
Co-authored-by: buray <ygd58@users.noreply.github.com>
Co-authored-by: lbn <llbn@users.noreply.github.com>
Co-authored-by: crazywriter1 <53251494+crazywriter1@users.noreply.github.com>
2026-03-17 02:10:36 -07:00
|
|
|
# Warn if API keys were skipped (migrate_secrets not enabled)
|
|
|
|
|
skipped_keys = [
|
|
|
|
|
i for i in report.get("items", [])
|
|
|
|
|
if i.get("kind") == "provider-keys" and i.get("status") == "skipped"
|
|
|
|
|
]
|
|
|
|
|
if skipped_keys:
|
|
|
|
|
print()
|
|
|
|
|
print(color(" ⚠ API keys were NOT migrated (secrets migration is disabled by default).", Colors.YELLOW))
|
|
|
|
|
print(color(" Your OPENROUTER_API_KEY and other provider keys must be added manually.", Colors.YELLOW))
|
|
|
|
|
print()
|
|
|
|
|
print_info("To migrate API keys, re-run with:")
|
|
|
|
|
print_info(" hermes claw migrate --migrate-secrets")
|
|
|
|
|
print()
|
|
|
|
|
print_info("Or add your key manually:")
|
|
|
|
|
print_info(" hermes config set OPENROUTER_API_KEY sk-or-v1-...")
|