npm package: @fixweb/utils-cli
Description: Declarative CLI framework with YAML-based commands and interactive TUI
Author: Fixweb
Latest version: 1.1.2

# @fixweb/utils-cli

Framework CLI déclaratif avec support YAML et TUI interactif.

## Installation

```bash
npm install @fixweb/utils-cli
```

## Démarrage rapide

```javascript
// index.js
require("@fixweb/utils-cli").run("./menu.yml");
```

```yaml
# menu.yml
title: Mon CLI
version: 1.0.0

menu:
  - "Commandes"
  - hello: ["echo", "Hello World"]
  - greet:
      desc: "Dit bonjour"
      exec: ["echo", "Bonjour!"]
```

```bash
node index.js
```

---

## Référence YAML complète

### Structure racine

```yaml
title: string           # Titre affiché en banner figlet (optionnel)
version: string         # Version affichée sous le banner (optionnel)
modules: string         # Chemin vers le dossier des modules (optionnel)
config: string          # Chemin vers le dossier de config (optionnel)

vars:                   # Variables globales (optionnel)
  KEY: "value"

menu:                   # Menu principal (requis) — tableau d'éléments
  - ...
```

### Éléments du menu

Le tableau `menu` accepte 6 types d'éléments :

#### 1. Label (string) — Titre de section

```yaml
menu:
  - "Ma Section"
```

Affiche un titre de section dans le TUI. Toutes les commandes qui suivent sont regroupées sous ce label.

#### 2. Commande courte — `name: [cmd, ...args]`

```yaml
menu:
  - hello: ["echo", "Hello!"]
  - list-files: ["ls", "-la"]
```

Syntaxe minimale : `clé: [tableau_argv]`. Le nom de la commande est la clé. `exec` est implicite.

#### 3. Commande étendue — `name: { desc, exec, ... }`

```yaml
menu:
  - deploy:
      desc: "Déployer l'application"     # Description (optionnel)
      exec: ["./deploy.sh", "{{env}}"]   # Commande à exécuter (tableau argv)
      async: true                         # Mode async avec spinner (optionnel, défaut: false)
      inline: true                        # Reste dans le TUI (optionnel, défaut: false)
      prompts:                            # Prompts interactifs avant exécution (optionnel)
        - ...
```

#### 4. Sous-menu — `name: { desc, items: [...] }`

```yaml
menu:
  - Outils:
      desc: "Outils divers"
      items:
        - tool1: ["echo", "Tool 1"]
        - tool2:
            desc: "Second outil"
            exec: ["echo", "Tool 2"]
        - SousMenu:
            desc: "Imbriqué"
            items:
              - subtool: ["echo", "Subtool"]
```

Les sous-menus sont récursifs — on peut imbriquer autant de niveaux que nécessaire.

#### 5. Steps — `name: { desc, steps: [...], mode }`

```yaml
menu:
  - build-all:
      desc: "Build everything"
      mode: parallel            # "parallel" (défaut) ou "sequential"
      steps:
        - name: "Frontend"
          exec: ["npm", "run", "build:front"]
        - name: "Backend"
          exec: ["npm", "run", "build:back"]
```

#### 6. Include — `{ include: path }`

```yaml
menu:
  - include: ./other-menu.yml
  - include: menus/*.yml
  - include: modules/*/commands.yml
```

Le fichier inclus doit contenir une clé `menu:` avec un tableau d'éléments.

---

## Modes d'exécution

| Propriété | Valeur | Comportement |
|-----------|--------|-------------|
| _(défaut)_ | — | Prend le contrôle du terminal (stdin/stdout hérités) |
| `async: true` | — | Spinner animé, sortie capturée, résumé affiché |
| `inline: true` | — | Comme async mais reste dans le TUI sans le quitter |

---

## Prompts interactifs

Ajoutez `prompts` à une commande pour demander des valeurs à l'utilisateur avant exécution.

### Types de prompts

| Type | Propriétés | Description |
|------|-----------|-------------|
| `select` | `id`, `message`, `choices`, `defaultValue?` | Liste de choix (flèches + Enter) |
| `input` | `id`, `message`, `defaultValue?` | Saisie texte libre |
| `confirm` | `id`, `message`, `defaultValue?` | Oui/Non (Y/n) |
| `password` | `id`, `message` | Saisie masquée (****) |

### Propriétés d'un prompt

```yaml
prompts:
  - id: env               # Identifiant (requis) — utilisable comme {{env}} dans exec
    type: select           # Type (requis) — select | input | confirm | password
    message: "Environnement ?"  # Message affiché (optionnel)
    choices: [dev, staging, prod]  # Choix (requis pour select)
    defaultValue: "dev"    # Valeur par défaut (optionnel)
```

### Exemple complet avec prompts

```yaml
- deploy:
    desc: "Déployer l'application"
    prompts:
      - id: env
        type: select
        message: "Environnement ?"
        choices: [dev, staging, prod]
      - id: version
        type: input
        message: "Version ?"
        defaultValue: "1.0.0"
      - id: confirm
        type: confirm
        message: "Déployer {{env}} v{{version}} ?"
    exec: ["./deploy.sh", "{{env}}", "{{version}}"]
```

Les valeurs des prompts sont injectées comme variables `{{id}}` dans `exec` et dans les `message` des prompts suivants.

---

## Variables

### Définition

```yaml
vars:
  PROJECT_DIR: /opt/myproject
  LOG_FILE: /var/log/app.log
  ENV: production
```

### Syntaxes supportées

```yaml
# Syntaxe moustache (recommandée)
- logs: ["tail", "-f", "{{LOG_FILE}}"]

# Syntaxe shell
- logs: ["tail", "-f", "${LOG_FILE}"]

# Avec valeur par défaut
- logs: ["tail", "-f", "${LOG_FILE:-/tmp/app.log}"]
```

### Variables système (automatiques)

| Variable | Description |
|----------|-------------|
| `CLI_DIR` | Dossier racine du CLI |
| `MODULES_DIR` | Dossier des modules |
| `CONFIG_DIR` | Dossier de config |
| `MODULE_DIR` | Dossier du module courant |

---

## Navigation TUI

### Clavier

| Touche | Action |
|--------|--------|
| `↑` `↓` | Naviguer dans le menu |
| `→` `Enter` | Entrer / Exécuter |
| `←` | Retour au menu parent |
| `q` `Ctrl+C` | Quitter |

### Souris

| Action | Effet |
|--------|-------|
| Molette haut/bas | Naviguer dans le menu |
| Clic sur un item | Le sélectionner |
| Clic sur l'item sélectionné | L'activer |

### Indicateurs visuels

- `○` Action/commande
- `●` Sous-menu
- `▶` Élément sélectionné

---

## API JavaScript

### Simple

```javascript
require("@fixweb/utils-cli").run("./menu.yml");
```

### Avancée

```javascript
const { createCLI } = require("@fixweb/utils-cli");

const cli = createCLI({
  name: "mycli",
  version: "1.0.0",
  title: "My CLI",
  menu: [
    "Commands",
    { hello: ["echo", "Hello"] },
    { deploy: { desc: "Deploy", exec: ["./deploy.sh"], prompts: [
      { id: "env", type: "select", message: "Env?", choices: ["dev", "prod"] }
    ]}}
  ],
  vars: { APP_DIR: "/opt/app" },
  paths: { cliDir: __dirname }
});

cli.run();
```

### Avec gestion d'erreurs

```javascript
require("@fixweb/utils-cli").runSafe("./menu.yml");
```

---

## Structure de projet recommandée

```
my-cli/
├── index.js              # Point d'entrée : require("@fixweb/utils-cli").run("./menu.yml")
├── menu.yml              # Menu principal
├── modules/              # Modules optionnels
│   ├── database/
│   │   ├── commands.yml
│   │   └── scripts/
│   │       └── backup.sh
│   └── docker/
│       ├── commands.yml
│       └── scripts/
│           └── restart.sh
└── menus/                # Menus additionnels optionnels
    ├── admin.yml
    └── dev.yml
```

```yaml
# menu.yml
title: Mon Projet
version: 1.0.0
modules: ./modules

menu:
  - "Projet"
  - status: ["git", "status"]
  - build: 
      desc: "Build le projet"
      async: true
      exec: ["npm", "run", "build"]

  - include: modules/*/commands.yml
  - include: menus/*.yml
```

---

## Exemples complets

### CLI DevOps

```yaml
title: DevOps CLI
version: 2.0.0

vars:
  STACK: myapp
  COMPOSE: docker-compose.yml

menu:
  - "Docker"
  - up:
      desc: "Démarrer les services"
      exec: ["docker", "compose", "-f", "{{COMPOSE}}", "up", "-d"]
  - down:
      desc: "Arrêter les services"
      exec: ["docker", "compose", "-f", "{{COMPOSE}}", "down"]
  - logs:
      desc: "Voir les logs d'un service"
      prompts:
        - id: service
          type: select
          message: "Service ?"
          choices: [api, web, db, redis]
      exec: ["docker", "compose", "logs", "-f", "{{service}}"]

  - "Déploiement"
  - deploy:
      desc: "Déployer en production"
      mode: sequential
      steps:
        - name: "Tests"
          exec: ["npm", "test"]
        - name: "Build"
          exec: ["npm", "run", "build"]
        - name: "Push"
          exec: ["docker", "push", "registry.example.com/{{STACK}}"]
  
  - "Monitoring"
  - Statuts:
      desc: "Statut des services"
      items:
        - ps: 
            desc: "Conteneurs actifs"
            inline: true
            exec: ["docker", "compose", "ps"]
        - disk:
            desc: "Espace disque"
            inline: true
            exec: ["df", "-h"]
```

### CLI Monorepo

```yaml
title: Monorepo
version: 1.0.0

vars:
  PACKAGES: ./packages

menu:
  - "Développement"
  - dev:
      desc: "Lancer le dev server"
      prompts:
        - id: pkg
          type: select
          message: "Package ?"
          choices: [frontend, backend, shared]
      exec: ["npm", "run", "dev", "--workspace", "{{pkg}}"]

  - build-all:
      desc: "Build tous les packages"
      mode: parallel
      steps:
        - name: "shared"
          exec: ["npm", "run", "build", "--workspace", "shared"]
        - name: "frontend"
          exec: ["npm", "run", "build", "--workspace", "frontend"]
        - name: "backend"
          exec: ["npm", "run", "build", "--workspace", "backend"]

  - "Git"
  - commit:
      desc: "Commit conventionnel"
      prompts:
        - id: type
          type: select
          message: "Type ?"
          choices: [feat, fix, chore, docs, refactor, test]
        - id: scope
          type: input
          message: "Scope ?"
        - id: msg
          type: input
          message: "Message ?"
      exec: ["git", "commit", "-m", "{{type}}({{scope}}): {{msg}}"]
```

---

## Sécurité

- Les commandes sont exécutées via `spawn` sans shell (pas d'injection possible)
- Les arguments sont sanitisés automatiquement
- Variables d'environnement filtrées (seules les vars système sûres + vars utilisateur sont transmises)
- `exec` est un **tableau** `[cmd, arg1, arg2]`, jamais une string (pas d'interprétation shell)

---

## Compatibilité

| Plateforme | Support |
|-----------|---------|
| macOS / Linux | Complet |
| Windows Terminal + PowerShell 7+ | Complet |
| Windows PowerShell 5.1 | Partiel (pas de TUI interactif) |
| CI/CD (non-TTY) | Fallback automatique (mode non-interactif) |

Variables d'environnement :
- `NO_COLOR=1` — Désactive les couleurs ANSI
- `FORCE_COLOR=1` — Force les couleurs ANSI

Requires Node.js >= 18.

---

## Licence

MIT