# Rainbow CLI NG — Guide de scripting JavaScript

## Vue d'ensemble

Le mode shell de Rainbow CLI NG inclut un runner de scripts JavaScript. Un script `.js` s'exécute dans un contexte sandboxé (Node.js `vm`) où les commandes du CLI sont exposées comme des fonctions async.

**Atouts par rapport au mini-langage `.rbw` :**
- Top-level `await` natif
- Toute la puissance de JavaScript : boucles, conditions, fonctions, regex, `Array.filter/map/reduce`…
- Interpolation sans ambiguïté via les template literals : `` `user:context ${d.email}` ``
- Gestion d'erreurs native avec `try/catch`
- `console.log`, `JSON`, `Math` et les globals JS standard disponibles

---

## Lancer un script

```bash
# 1. Entrer en mode shell
rbw

# 2. Lancer le script depuis le prompt
rbw[net] › run ./mon-script.js
rbw[net] › run /chemin/absolu/script.js
```

> Les scripts `.js` sont détectés à l'extension. Les fichiers `.rbw` utilisent le mini-langage.

---

## API disponible dans le script

### `run(cmd)` — exécuter une commande (silencieux par défaut)

```javascript
const result = await run('commande [options]')
```

| Paramètre | Description |
|---|---|
| `cmd` | String — toute commande du CLI sans le préfixe `rbw` |
| Retour | Objet JSON résultat, ou `null` en cas d'erreur |

`run()` est **silencieux par défaut** : aucune sortie n'est affichée. Le résultat est uniquement disponible dans la variable JavaScript.

```javascript
// Données accessibles directement
const me    = await run('whoami')
const users = await run('users --limit 10')
const app   = await run('application get 5bc50eea4ca684544b694001')
```

---

### `runVerbose(cmd)` — exécuter avec affichage

Affiche `› cmd` et le tableau de résultats dans le terminal.

```javascript
await runVerbose('user:context alice@example.com')
// Affiche :  › user:context alice@example.com
//            [tableau des résultats]
```

---

### `runSilent(cmd)` — alias explicite pour run()

Équivalent à `run(cmd)`. Permet d'indiquer explicitement l'intention de silence.

```javascript
const devs = await runSilent('report:developers --filter external')
```

---

### `status` et `ok` — code de retour

Mis à jour après chaque `run()` / `runVerbose()` / `runSilent()`.

| Variable | Type | Valeur |
|---|---|---|
| `status` | `number` | `0` = succès, `1` = échec |
| `ok` | `boolean` | `true` si `status === 0` |

```javascript
const me = await run('whoami')
if (!ok) {
    log('Connexion échouée — status :', status)
    stop()
}
log('Connecté :', me.data.loginEmail)
```

---

### `log(...args)` et `echo(...args)`

Affiche un message dans le terminal (alias de `console.log` avec indentation).

```javascript
log('Traitement en cours…')
log(`${devs.data.length} développeurs trouvés`)
echo('Profile :', vars.get('profile'))
```

---

### `profile(name)` — changer de profil

Change le profil actif **pour la session** (sans modifier la configuration).

```javascript
profile('prod')
const apps = await run('applications')
profile('sandbox')
```

---

### `onError(mode)` — comportement sur erreur

| Mode | Comportement |
|---|---|
| `'continue'` | Continuer malgré les erreurs (défaut) |
| `'stop'`     | Arrêter le script dès la première erreur |

```javascript
onError('stop')  // équivalent de set -e en bash

const me = await run('whoami')
// Si run() échoue et on-error=stop → script stoppé automatiquement
```

---

### `stop(msg?)` — arrêt propre

Arrête le script immédiatement sans lever d'exception fatale. Le shell continue normalement.

```javascript
const me = await run('whoami')
if (!ok) stop('Impossible de se connecter')

// Condition de sortie anticipée
if (devs.data.length === 0) stop('Aucun développeur — rien à faire')
```

---

### `vars` — accès aux variables du shell

Le `Map` des variables partagé avec la session interactive.

| Variable pré-remplie | Contenu |
|---|---|
| `vars.get('profile')` | Nom du profil actif |
| `vars.get('host')` | Serveur cible (ex: `openrainbow.com`) |
| `vars.get('?')` | Code de retour de la dernière commande |

```javascript
log('Profile :', vars.get('profile'))
log('Host    :', vars.get('host'))

// Lire/écrire des variables personnalisées
vars.set('compteur', 0)
vars.set('compteur', vars.get('compteur') + 1)
```

---

## Structure des résultats

La plupart des commandes retournent un objet paginé :

```javascript
{
  data:   [...],   // tableau de résultats (ou objet unique)
  total:  N,       // nombre total d'éléments
  offset: 0,
  limit:  50,
}
```

Pour les commandes retournant un objet unique (ex: `whoami`) :

```javascript
const me = await run('whoami')
me.data.loginEmail     // email
me.data.companyId      // ID de la company
me.data.roles          // tableau de rôles
```

Pour les commandes retournant une liste :

```javascript
const users = await run('users --limit 100')
users.data             // tableau d'utilisateurs
users.total            // nombre total
users.data[0].id       // ID du premier utilisateur
users.data.filter(u => u.roles.includes('admin'))
```

---

## Globals JavaScript disponibles

| Global | Description |
|---|---|
| `console` | `console.log`, `console.error`, etc. |
| `JSON` | `JSON.parse`, `JSON.stringify` |
| `Math` | `Math.round`, `Math.floor`, etc. |
| `parseInt`, `parseFloat`, `isNaN`, `isFinite` | Conversion numérique |
| `setTimeout`, `clearTimeout` | Timers |
| `setInterval`, `clearInterval` | Intervalles |
| `Promise`, `Error` | Primitives async |

> `require`, `import`, `process`, `fs` et les modules Node.js ne sont **pas** disponibles (sandbox).

---

## Gestion des erreurs

### Vérifier `ok` après chaque commande

```javascript
const result = await run('commande')
if (!ok) {
    log('Erreur sur la commande — status :', status)
}
```

### `try/catch` natif JavaScript

```javascript
try {
    const ctx = await run(`user:context ${d.email} --pdf ./out/${d.email}.pdf`)
} catch (err) {
    log('Exception :', err.message)
}
```

### `onError('stop')` — fail-fast

```javascript
onError('stop')
const me    = await run('whoami')            // si échec → stop
const devs  = await run('report:developers') // si échec → stop
// Garantit que toutes les données sont disponibles avant de continuer
```

---

## Interruption Ctrl+C

Pendant l'exécution d'un script, **Ctrl+C** interrompt le script proprement :
- La commande `run()` en cours se termine (la requête HTTP complète)
- Le script s'arrête avant le prochain `run()`
- **Le shell continue** — pas de sortie du processus

```
rbw[net] › run ./long-script.js
  › report:developers --filter external
^C
  Ctrl+C — script interrompu après la commande en cours…

  Script JS terminé.

rbw[net] ›    ← shell toujours actif
```

---

## Exemples complets

### Audit rapide — connexion et résumé

```javascript
// audit.js
const me = await run('whoami')
if (!ok) stop('Connexion échouée')

log('=== Audit Rainbow ===')
log('Connecté :', me.data.loginEmail)
log('Profile  :', vars.get('profile'))
log('Host     :', vars.get('host'))

const devs = await run('report:developers --filter external')
const apps = await run('applications --limit 1')

log(`\nDéveloppeurs externes : ${devs.total}`)
log(`Applications totales  : ${apps.total}`)
```

---

### Export de contextes en PDF

```javascript
// export_contexts.js
import { mkdirSync } from 'fs'  // Non disponible en sandbox — utiliser runVerbose ci-dessous

onError('continue')

const devs = await run('report:developers --filter external --activity some')
if (!ok || devs.data.length === 0) stop('Aucun développeur avec des applications')

log(`Export PDF pour ${devs.data.length} développeur(s)…\n`)

let ok_count = 0, fail_count = 0

for (const d of devs.data) {
    const filename = `./rapports/${d.email}.pdf`
    await run(`user:context ${d.email} --pdf ${filename}`)
    if (ok) {
        ok_count++
        log(`✓ ${d.email}`)
    } else {
        fail_count++
        log(`✗ ${d.email} (erreur)`)
    }
}

log(`\nTerminé : ${ok_count} succès, ${fail_count} échec(s)`)
```

> **Note** : créer le répertoire `./rapports/` avant de lancer le script.

---

### Rapport multi-profils

```javascript
// multi_profile_report.js
const profiles = ['sandbox', 'prod']
const results  = {}

for (const p of profiles) {
    profile(p)
    log(`\n--- Profil : ${p} ---`)

    const me = await run('whoami')
    if (!ok) { log('Connexion échouée'); continue }

    const devs = await run('report:developers --filter external')
    results[p] = {
        host:  vars.get('host'),
        total: devs?.total ?? 0,
    }
    log(`Développeurs externes : ${devs?.total ?? '?'}`)
}

log('\n=== Résumé ===')
for (const [p, r] of Object.entries(results)) {
    log(`${p.padEnd(12)} ${r.host.padEnd(30)} ${r.total} devs`)
}
```

---

### Vérifier les applications PayAsYouGo sans paiement

```javascript
// check_payments.js
onError('stop')

const me = await run('whoami')
if (!ok) stop('Connexion impossible')

// Développeurs PayAsYouGo sans moyen de paiement
const risky = await run('report:developers --filter external --pm-status any')
if (!ok) stop('Impossible de récupérer les développeurs')

const noPay = risky.data.filter(d => d.pmStatus === 'none' || d.pmStatus === 'expired')

if (noPay.length === 0) {
    log('✓ Aucun développeur PayAsYouGo sans paiement valide')
    stop()
}

log(`⚠ ${noPay.length} développeur(s) sans paiement valide :`)
for (const d of noPay) {
    log(`  ${d.email.padEnd(40)} ${d.companyName.padEnd(30)} pmStatus=${d.pmStatus}`)
}
```

---

## Bonnes pratiques

- **Toujours vérifier `ok`** après les commandes critiques, ou utiliser `onError('stop')`
- **Utiliser `runVerbose()`** pour les commandes dont vous voulez voir le résultat en temps réel
- **Préférer les template literals** pour construire les commandes dynamiques
- **Créer les répertoires** de sortie avant de lancer un script qui génère des fichiers
- **Stopper tôt** avec `stop()` si les pré-conditions ne sont pas remplies
- **Tester en sandbox** avant de lancer sur le profil production
