Files
hermes-agent/hermes_cli/subcommands/codex_runtime.py
teknium1 d09183d8d3 feat(cli): add hermes codex-runtime migrate [--dry-run] [--json]
The managed-block marker and the docs have referred to `hermes codex-runtime
migrate` all along, but no such CLI subcommand existed: the only way to run the
~/.codex/config.toml migration outside a chat session was to import the private
hermes_cli.codex_runtime_plugin_migration.migrate (#79023). The new subcommand
group (hermes_cli/subcommands/codex_runtime.py, registered like the other
groups) calls the same migrate() the /codex-runtime slash command uses, on the
selected profile home, with --dry-run (no write) and --json (full report incl.
preserved_user_servers and errors); exit code 1 when the report has errors.

Tests: one invariant for the same-name table (single header, valid TOML, user
command kept, report lists the name) and one for the CLI dry-run/json path.
Docs: conflict policy + command in the codex runtime guide and CLI reference.
2026-09-18 23:38:13 -07:00

56 lines
2.7 KiB
Python

"""``hermes codex-runtime`` — noninteractive counterpart of the ``/codex-runtime`` slash command.
``hermes codex-runtime migrate [--dry-run] [--json]`` runs the same ``~/.codex/config.toml``
migration the slash command triggers when the codex app-server runtime is enabled, on the
selected profile home (``hermes -p NAME codex-runtime migrate``), so automation no longer has to
import ``hermes_cli.codex_runtime_plugin_migration`` directly (issue #79023).
"""
from __future__ import annotations
import argparse
import dataclasses
import json
def cmd_codex_runtime_migrate(args: argparse.Namespace) -> int:
"""Project Hermes MCP servers (+ codex plugins) into ~/.codex/config.toml; 1 on any error."""
from hermes_cli.codex_runtime_plugin_migration import migrate
from hermes_cli.config import load_config
report = migrate(load_config(), dry_run=args.dry_run)
if args.json:
payload = dataclasses.asdict(report)
payload["target_path"] = str(report.target_path)
print(json.dumps(payload, indent=2, sort_keys=True))
else:
print(report.summary())
return 1 if report.errors else 0
def build_codex_runtime_parser(subparsers) -> None:
"""Attach the ``codex-runtime`` subcommand (``migrate`` action) to ``subparsers``."""
parser = subparsers.add_parser(
"codex-runtime", help="Manage the optional codex app-server runtime (migrate MCP config)",
description="Noninteractive counterpart of the /codex-runtime slash command. Toggling the "
"runtime itself stays in the chat command (`/codex-runtime on|off`); `migrate` "
"re-projects Hermes' mcp_servers + installed codex plugins into the managed block of "
"~/.codex/config.toml for the selected profile.")
actions = parser.add_subparsers(dest="codex_runtime_action")
migrate_parser = actions.add_parser(
"migrate", help="Regenerate the hermes-managed block in codex's config.toml",
description="Idempotent: replaces the managed block, keeps user text verbatim, skips Hermes "
"servers whose name the user already declares outside the block, validates the result "
"as TOML before writing atomically.")
migrate_parser.add_argument(
"--dry-run", action="store_true", help="Report what would be written without touching config.toml")
migrate_parser.add_argument(
"--json", action="store_true", help="Print the migration report as JSON (for automation)")
migrate_parser.set_defaults(func=cmd_codex_runtime_migrate)
def _print_help(args): # noqa: ANN001 — bare `hermes codex-runtime` lists the actions
parser.print_help()
return 0
parser.set_defaults(func=_print_help)