Cuando tus dedos pulsan Esc: keybindings de Vim para el composer del CLI de Hermes (y el nombre de la rama en tu barra de estado)


Has escrito un prompt largo en Hermes y justo antes de pulsar Enter te das cuenta de una palabra mal escrita en medio. Tus dedos pulsan Esc y luego dd, y el composer no hace absolutamente nada. Con los keybindings emacs por defecto, Esc no es “salir del modo insert”: es una tecla de cancelar/limpiar, y dd simplemente se convierte en dos caracteres de texto. Si llevas una década en vim, ese medio segundo de fricción es difícil de explicar a quien no lo ha vivido.

Ahora hay una respuesta oficial. El composer clásico del CLI puede usar keybindings de vi/vim, y la configuración es más pequeña de lo que esperarías: una clave de config y ningún slash command nuevo.

Activarlo: display.vim_mode

Añade una línea bajo la sección display de ~/.hermes/config.yaml:

display:
  # Solo CLI: keybindings vi/vim en el composer de entrada (Esc → NORMAL, i → INSERT)
  # El modo NORMAL / INSERT / REPLACE en vivo se muestra a la derecha de la barra de estado.
  # Solo config, se lee al arrancar.
  vim_mode: true

Luego abre tu config con hermes config edit (o tu propio editor), guarda y reinicia Hermes.

Una trampa que conviene quitar del medio cuanto antes: no existe ningún slash command /vim. Una iteración temprana de esta función incluía uno con subcomandos on|off|status, pero se retiró antes del merge, y por una razón sensata. Sus vecinos (/statusbar, /battery, /timestamps) alternan elementos de visualización en el momento, mientras que los keybindings son un hábito de edición, y atarlos a la configuración es más coherente que cambiarlos sesión a sesión. Así que si ves /vim on escrito por ahí, ese es el estado intermedio descartado: escribir eso no hace absolutamente nada.

Cómo se siente en la práctica

El comportamiento encaja con lo que esperan tus dedos:

  • Esc entra en modo NORMAL, donde las teclas son comandos y no texto;
  • i (o a, o y compañía) vuelve a modo INSERT para escribir normal;
  • la barra de estado muestra el modo actual en su borde derecho, uno de NORMAL, INSERT o REPLACE. Esto es genuinamente útil, no decoración: en un prompt multilínea, olvidar en qué modo estás es el fastidio clásico, y una etiqueta persistente elimina la mayoría de los momentos de “por qué no pasa nada cuando escribo”.

Esa etiqueta es en vivo: sigue tus pulsaciones de Esc e i en lugar de fijarse al arrancar. Por debajo, la barra de estado la construye dinámicamente a partir del modo de edición actual (_vim_mode_label en cli_status_bar_mixin.py).

¿Y si nunca has usado vim? El valor por defecto es off, lo que mantiene los keybindings emacs estándar de prompt_toolkit: no cambies nada, no notas nada. Esa postura opt-in es justo por lo que esto llegó como un interruptor de configuración.

Un compañero que merece la pena: tu rama git actual en la barra de estado

Ya que estamos con la barra de estado, hay un segundo campo “apagado por defecto, actívalo a propósito”: la rama git actual.

En una máquina con varios repositorios y worktrees, “¿en qué rama está de verdad esta sesión?” es una pregunta frecuente y con consecuencias, sobre todo después de que un agente te haya editado archivos y quieras estar seguro de qué rama se llevó el golpe. Fíjala en la barra de estado y la tendrás siempre a un vistazo:

display:
  status_bar:
    # No vacío = solo se muestran estos campos (se mantiene el orden interno; la
    # config controla la visibilidad, no el orden). Una lista vacía = el conjunto por defecto.
    fields: ["model", "git_branch", "duration"]

Los campos disponibles incluyen model, context_pct, cache_hit, latency, tps, compressions, bg_tasks, bg_subagents, goal, git_branch (se dibuja como ⎇ branch), duration, focus, yolo, battery, title y total_tokens. Dos reglas importan:

  1. Una lista no vacía es una whitelist: solo se muestran los campos que nombres; una lista vacía restaura el conjunto por defecto. El orden sigue siendo interno: la configuración solo decide la visibilidad.
  2. Los terminales estrechos descartan campos automáticamente. Cuando la ventana se queda corta, los campos anchos como context_detail, prompt_elapsed e idle_since se eliminan en lugar de apretarse. Es el comportamiento previsto, no una config rota.

Si lo que quieres es simplemente alternar la barra de estado, /statusbar se encarga; la lista de campos es configuración.

Advertencias

  • Reinicia después de editar. vim_mode se lee al arrancar, así que una edición de config en caliente no cambiará el modo en una sesión que ya está corriendo.
  • Solo toca el composer. El cambio afecta a la caja de entrada donde escribes prompts, no a todas las demás superficies de interacción y, desde luego, no a cómo trabaja el agente.
  • Es específico del CLI. Los keybindings pertenecen al composer clásico del CLI; la app de escritorio y otras superficies TUI tienen su propia lógica de entrada. No esperes que un solo interruptor las cambie todas.
  • No es todo vim. Este es el modo de edición vi de prompt_toolkit: movimiento, borrado, entrada en modo insert; edición cotidiana, no una sesión de Neovim con plugins y registros elaborados.
  • La función tiene precedente. La petición original es el issue #4254, y casi al mismo tiempo GitHub Copilot CLI llevó el modo vim a disponibilidad general (sus notas de la v1.0.84-2 nombran tanto un comando /vim como un ajuste editorMode). Los dos proyectos coinciden en el fondo: los hábitos de edición en terminal merecen respeto.

Si estás ajustando tu setup de CLI, tres artículos relacionados de aquí merecen una lectura: la guía de la command palette con Ctrl+P, el análisis a fondo de las métricas de la barra de estado y quitar el techo de max-turns para sesiones largas. Montar una máquina desde cero empieza en la guía de instalación.

La versión corta

En una frase: ¿quieres teclas de vim? Escribe display.vim_mode: true y reinicia, y no busques un comando /vim porque no existe.

Tres cosas que llevarte:

  1. Apagado por defecto, así que nada cambia a menos que lo pidas; una vez activado, la barra de estado muestra NORMAL / INSERT / REPLACE de forma permanente.
  2. Plantéate añadir git_branch a display.status_bar.fields: en un entorno con varias ramas evita toda una clase de errores tontos.
  3. Este cambio se fusionó después del tag v0.21.2, así que ahora mismo vive en main en lugar de en una versión etiquetada; hermes update trae main, o espera al siguiente release.

Para quien arrastra memoria muscular de vim en el terminal, es un cambio pequeño que usarás varias veces al día.