Guía Completa de Construcción de CLI

En este artículo, voy a abordar una guía completa sobre cómo construir una CLI (Command Line Interface) profesional, que sea fácil de usar y, principalmente, fácil de integrar por otras aplicaciones. Si nunca has construido una CLI antes, no te preocupes — vamos a empezar desde cero.

¿Qué es una CLI?

CLI significa Command Line Interface (Interfaz de Línea de Comandos). Es ese programa que utilizas escribiendo comandos en la terminal, como git, npm, docker, pip, entre otros. A diferencia de los programas con interfaz gráfica (GUI), las CLIs se controlan por texto, lo que las hace extremadamente potentes, automatizables y fáciles de integrar con otros sistemas.

Entendiendo el ARGV

Antes que nada, necesitas entender qué es el argv. La respuesta es simple: argv es un array de strings que contiene los argumentos pasados al programa. Todo lo que se escribió en la terminal se separa por espacios y se transforma en este array.

  • argv[0] → nombre del programa (o del runtime + script)
  • argv[1] → primer argumento
  • argv[2] → segundo argumento
  • Y así sucesivamente…

Cómo funciona argv en diferentes lenguajes

En lenguajes interpretados (como Python), el argv[0] es el nombre de tu script, y los argumentos comienzan a partir de argv[1].

En lenguajes compilados (como C, C++, Go, Rust), el argv[0] es el nombre del ejecutable compilado.

Ejemplo en Python

import sys
print(sys.argv)

Ejecuta:

python3 script.py arg1 arg2 arg3

Salida:

['script.py', 'arg1', 'arg2', 'arg3']

Ejemplo en C

#include <stdio.h>

int main(int argc, char *argv[]) {
    for (int i = 0; i < argc; i++) {
        printf("argv[%d] = %s\n", i, argv[i]);
    }
    return 0;
}

Compila y ejecuta:

gcc programa.c -o programa
./programa arg1 arg2 arg3

Salida:

argv[0] = ./programa
argv[1] = arg1
argv[2] = arg2
argv[3] = arg3

Construyendo tu propia CLI

Para construir una CLI sólida y profesional, sigue los siguientes pasos:

1. Define las Actions (Acciones)

El primer argumento (después del nombre del programa) debe ser la action, es decir, la acción que el usuario desea ejecutar.

mi_programa <action> [flags y argumentos]

Usemos git como ejemplo. Tiene varias actions como commit, pull, push, rebase, etc. Mira el siguiente comando:

git commit -m "mensaje del commit"

En este ejemplo: - commit → es la action - -m → es una flag - "mensaje del commit" → es el valor de la flag

Consejo para principiantes: Piensa en la action como una función, y en las flags como los parámetros de esa función.

Cómo implementar actions

La forma más sencilla de implementar actions es usar una estructura condicional para verificar el primer argumento:

Ejemplo en Python:

import sys

def action_hello():
    print("¡Hola! ¡Bienvenido a mi CLI!")

def action_version():
    print("mi_programa v1.0.0")

if len(sys.argv) < 2:
    print("Uso: mi_programa <action>")
    print("Usa 'mi_programa help' para ver las acciones disponibles.")
    sys.exit(1)

action = sys.argv[1]

if action == "hello":
    action_hello()
elif action == "version":
    action_version()
else:
    print(f"Acción desconocida: {action}")
    print("Usa 'mi_programa help' para ver las acciones disponibles.")

2. Define las Flags

Ahora que has separado cada acción posible de tu CLI, debes definir las flags que cada action puede recibir. Las flags modifican el comportamiento de una action.

Existen dos tipos de flags:

Boolean Flags (flags sin valor)

Son flags que no reciben valor — solo indican que algo debe ser activado. Generalmente comienzan con --.

git push --force

Aquí, --force es una flag booleana que indica que el push debe ser forzado.

Value Flags (flags con valor)

Son flags que reciben un valor inmediatamente después. Pueden usar - (forma corta) o -- (forma larga).

git commit -m "mensaje del commit"
git commit --message "mensaje del commit"

Aquí, -m (o --message) es una flag que recibe el mensaje como valor.

Cómo implementar flags

Ejemplo en Python:

import sys

def parse_flags(args):
    """Analiza los argumentos y retorna un diccionario de flags."""
    flags = {}
    i = 0
    while i < len(args):
        if args[i].startswith("--"):
            flag_name = args[i][2:]  # Elimina el prefijo --
            # Verifica si es una boolean flag o una value flag
            if i + 1 < len(args) and not args[i + 1].startswith("-"):
                flags[flag_name] = args[i + 1]
                i += 2
            else:
                flags[flag_name] = True
                i += 1
        elif args[i].startswith("-"):
            flag_name = args[i][1:]  # Elimina el prefijo -
            if i + 1 < len(args) and not args[i + 1].startswith("-"):
                flags[flag_name] = args[i + 1]
                i += 2
            else:
                flags[flag_name] = True
                i += 1
        else:
            i += 1
    return flags

# Ejemplo de uso:
# mi_programa commit -m "mensaje" --force
action = sys.argv[1]
flags = parse_flags(sys.argv[2:])
print(f"Action: {action}")
print(f"Flags: {flags}")

3. Define un Help (Ayuda)

Todo programa de línea de comandos necesita una acción help. Debe listar todas las acciones y flags disponibles, con una descripción clara de cada una.

mi_programa help

Ejemplo de salida:

mi_programa - Una herramienta increíble para gestionar tareas

Uso: mi_programa <action> [flags]

Acciones disponibles:
  help                Muestra este mensaje de ayuda
  version             Muestra la versión del programa
  add                 Añade una nueva tarea
    --title <texto>     Título de la tarea (obligatorio)
    --priority <n>      Prioridad de 1 a 5 (por defecto: 3)
  list                Lista todas las tareas
    --filter <status>   Filtra por estado (pendiente, completada)
    --limit <n>         Limita el número de resultados
  done                Marca una tarea como completada
    --id <id>           ID de la tarea (obligatorio)

Ejemplos:
  mi_programa add --title "Comprar café" --priority 1
  mi_programa list --filter pendiente
  mi_programa done --id 42

Consejo: Siempre incluye ejemplos prácticos en la ayuda. Esto ayuda mucho a los principiantes.

4. Define un Version (Versión)

Crea una action llamada version que muestre la versión actual de tu CLI. Esto es esencial para debugging y para que los usuarios sepan qué versión están usando.

mi_programa version

Ejemplo de salida:

mi_programa v1.0.0

Consejo: Sigue el estándar Semantic Versioning (SemVer) para numerar tus versiones: MAJOR.MINOR.PATCH (ej: 1.2.3).

5. Define Stored Configs (Configuraciones Almacenadas)

A menudo es conveniente que tu CLI almacene configuraciones del usuario para que no necesite escribirlas cada vez. Esto es muy común — piensa en git config o en npm config.

Ejemplo:

mi_programa config-user --username "Mateus" --email "mateus@gmail.com" --password "contraseña"

Una vez configurado, en las siguientes acciones puedes solicitar solo la flag identificadora:

mi_programa set-profile-picture --username "Mateus" --picture foto.png

¿Dónde almacenar las configuraciones?

Es muy recomendable que guardes todos los datos en una sola ubicación, preferiblemente en una carpeta oculta en el home del usuario (los directorios que comienzan con . son ocultos en Linux/macOS).

Ejemplo: las configuraciones podrían guardarse en el archivo ~/.mi_programa/config.json:

{
    "username": "Mateus",
    "email": "mateus@gmail.com",
    "password": "test"
}

Formas comunes de almacenamiento: - JSON → simple y ampliamente soportado - SQLite → ideal para datos más complejos o relacionales - TOML/YAML → bueno para archivos de configuración legibles - Archivos de texto → para datos muy simples

6. Tratamiento de Errores

Una CLI profesional debe tratar los errores de forma clara y amigable. Nunca dejes que el programa “se rompa” con un error incomprensible.

Buenas prácticas:

import sys

if len(sys.argv) < 2:
    print("Error: ninguna acción proporcionada.")
    print("Uso: mi_programa <action> [flags]")
    print("Usa 'mi_programa help' para más información.")
    sys.exit(1)

Consejos para buenos errores: - Di qué salió mal - Di por qué salió mal (si es posible) - Sugiere cómo solucionarlo

Ejemplo:

Error: la flag --title es obligatoria para la acción 'add'.
Uso: mi_programa add --title "Mi tarea"

7. Códigos de Salida (Exit Codes)

Los programas de línea de comandos comunican el éxito o el fracaso a través de códigos de salida. Esto es fundamental para que otros programas y scripts puedan saber si tu CLI se ejecutó correctamente.

  • Código 0éxito
  • Código 1 o superior → error
import sys

# Éxito
sys.exit(0)

# Error genérico
sys.exit(1)

Esto permite que otros programas usen tu CLI así:

mi_programa add --title "Tarea" && echo "¡Éxito!" || echo "¡Falló!"

8. Salida Estructurada (Output para Otras Aplicaciones)

Si tu CLI puede ser utilizada por otros programas (¡y debería serlo!), considera ofrecer salida en formatos estructurados como JSON.

mi_programa list --output json

Salida normal (para humanos):

ID  | Título          | Estado
1   | Comprar café    | pendiente
2   | Estudiar CLI    | completada

Salida JSON (para programas):

[
    {"id": 1, "titulo": "Comprar café", "status": "pendiente"},
    {"id": 2, "titulo": "Estudiar CLI", "status": "completada"}
]

Esto hace que tu CLI sea fácilmente integrable con otros sistemas, scripts y pipelines.

Consejos Extras

  1. Nunca mezcles convenciones de nomenclatura. Si una action se llama set-profile-picture (separando por -), usa el mismo patrón en todas las actions. Esto facilita la legibilidad y la intuición de uso.

  2. Si la información es sensible, encriptala. Nunca almacenes contraseñas en texto plano. Usa alguna forma de encriptación y requiere la contraseña de desencriptación como argumento o configuración almacenada.

  3. Añade colores al output. Los colores hacen que la salida sea mucho más legible. Por ejemplo, errores en rojo, éxito en verde, advertencias en amarillo. En la mayoría de los lenguajes existen bibliotecas para esto (como colorama en Python o chalk en Node.js).

  4. Soporta auto-completado. Si es posible, permite que el usuario configure auto-completado en el shell (Bash, Zsh, Fish). Esto mejora drásticamente la experiencia de uso.

  5. Escribe una buena documentación. Además de la ayuda integrada, considera mantener un README o una página de documentación con ejemplos detallados.

  6. Prueba tu CLI. Escribe tests automatizados para cada action y para combinaciones de flags. Esto evita regresiones y garantiza que todo funcione como se espera.

Conclusión

Construir una CLI profesional va más allá de solo leer argumentos de la terminal. Implica pensar en la experiencia del usuario, en la consistencia de los comandos, en el tratamiento de errores y en la integrabilidad con otros sistemas. Siguiendo los pasos de esta guía, tendrás una base sólida para crear CLIs que sean agradables de usar y fáciles de mantener.

Recuerda: una buena CLI es aquella que el usuario puede usar sin necesidad de consultar la documentación en todo momento. Invierte en buenos mensajes de ayuda, nombres de acciones intuitivos y mensajes de error claros.