Sections

Guide utilisateur

Référence config.json

Tous les champs du fichier config.json de Runyard, avec des exemples.

Runyard lit un unique fichier JSON qui décrit vos outils. Cette page documente chaque champ.

Le fichier de configuration se trouve par défaut à ~/Library/Application Support/Runyard/config.json. Vous pouvez le déplacer vers un dossier synchronisé. Voyez Synchroniser entre plusieurs Mac.

Structure de premier niveau

{
  "tools": [ /* ... */ ],
  "paths": ["/opt/homebrew/bin"],
  "advanced": { /* réglages temporels optionnels */ }
}
Champ Type Valeur par défaut Description
tools tableau [] Liste des définitions d'outils (voir ci-dessous).
paths string[] [] Répertoires ajoutés en tête du PATH lors du lancement de toute commande. Listés par ordre de priorité. ~ est remplacé par votre dossier personnel.
advanced objet - Réglages temporels (voir Réglages avancés).

Lancer à l'ouverture de session n'est pas stocké dans config.json. Activez l'option dans Réglages → General → Launch Runyard at login ; macOS conserve le choix dans la liste système des éléments d'ouverture de session.

Définition d'outil

Chaque entrée de tools représente un outil.

Champ Type Valeur par défaut Description
name string requis Nom affiché dans le menu.
type string requis "service", "shortcut", "group" ou "healthCheck" (voir Types d'outils).
directory string requis* Service uniquement. Racine du projet (~ est développé). Les commandes s'exécutent depuis ce dossier sauf si workingDir est défini. Ignoré sur les autres types d'outils.
startCommands tableau requis* Processus à lancer (voir Commandes de démarrage).
stopCommands tableau - Commandes d'arrêt personnalisées (voir Commandes d'arrêt).
actions tableau - Éléments de menu supplémentaires (voir Actions).
tools tableau - Outils imbriqués dans un groupe. Services et raccourcis uniquement ; pas de groupes imbriqués.
autoStart booléen false (service) / true (vérification d'état) Démarre automatiquement cet outil au lancement de Runyard. Par défaut false pour les services ; les vérifications d'état sont à true par défaut (elles sondent sauf si vous mettez false).
keepSystemAwake booléen false Lorsque le service tourne, empêche le système de se mettre en veille. Voir Garder éveillé.
keepDisplayAwake booléen false Lorsque le service tourne, empêche aussi l'écran de se mettre en veille. N'a aucun effet si keepSystemAwake n'est pas également true.
installCommand objet - Commande à exécuter si le chemin de marquage est absent (voir Commande d'installation).
paths string[] - Entrées PATH supplémentaires pour cet outil.
pathsOverride booléen false Si true, paths remplace entièrement les chemins globaux au lieu de les fusionner.

* directory et startCommands sont requis pour les outils service. directory est réservé aux services : il est ignoré sur les outils shortcut, group et healthCheck, et retiré du fichier lors de l'enregistrement.

Types d'outils

Type Apparence Champs requis
"service" En-tête en gras + Start/Stop + pastille d'état + journaux + actions. directory, startCommands
"shortcut" En-tête en gras + liste d'actions. Aucune gestion de processus. actions (au moins une)
"group" Sous-menu avec outils imbriqués et/ou actions. actions OU tools (au moins un)
"healthCheck" Pastille d'état + sondage HTTP/TCP périodique (voir Health Checks). http OU tcp (exactement un)

Les services imbriqués dans un groupe se comportent exactement comme les services de premier niveau. Les groupes imbriqués ne sont pas autorisés.

Commandes de démarrage

Chaque entrée de startCommands est un processus à lancer.

Champ Type Valeur par défaut Description
label string requis Nom unique de ce processus.
command string requis Exécutable à lancer (par exemple npm, docker-compose).
args string[] [] Arguments passés à la commande.
workingDir string dossier de l'outil Répertoire de travail, relatif à directory.
startupCheck string - URL HTTP à interroger jusqu'à ce qu'elle renvoie un 2xx ou 3xx pendant le démarrage du service.
startupFallbackPort number - Port de repli si la détection automatique échoue. Également utilisé comme indice si plusieurs ports sont détectés.
startupRequestTimeout number global Délai HTTP par requête (en secondes) pour le sondage de démarrage. Remplace advanced.startupRequestTimeout uniquement pour ce processus.
waitFor string - Étiquette d'un autre processus qui doit être en bon état avant que celui-ci ne démarre.

args est une liste d'arguments distincts, pas une ligne de commande. Chaque entrée est transmise à la commande telle quelle, comme un seul argument. Runyard ne la passe jamais par un shell, donc il ne sépare jamais une entrée sur les espaces. Placez chaque option ou valeur dans sa propre entrée : ["run", "dev"], et non ["run dev"]. Une entrée peut contenir des espaces lorsqu'elle doit parvenir au programme comme un seul argument (par exemple "--command=zsh -lc 'npm start'"). Cela s'applique à args partout où il apparaît : commandes de démarrage, commandes d'arrêt, actions de type commande et commande d'installation.

Vérifications de démarrage indépendantes du port

Si startupCheck est une URL locale sans port explicite (par exemple http://localhost/api/health), Runyard y injecte le port détecté au démarrage. Votre configuration fonctionne donc même si le serveur de développement choisit un port différent à chaque lancement.

Une URL avec un port explicite (par exemple http://localhost:3001/health) est utilisée telle quelle.

Dépendances de processus avec waitFor

"startCommands": [
  { "label": "Backend",  "command": "npm", "args": ["run", "dev"], "startupFallbackPort": 3001 },
  { "label": "Frontend", "command": "npm", "args": ["run", "dev"], "workingDir": "frontend",
    "startupFallbackPort": 5173, "waitFor": "Backend" }
]

Frontend ne démarre pas tant que la vérification de démarrage de Backend n'a pas réussi.

Commandes d'arrêt

Optionnelles. Lorsque stopCommands est défini, Runyard exécute chaque commande séquentiellement au lieu d'envoyer SIGTERM. Une fois terminées, tout processus résiduel est forcé à se fermer, par sécurité.

Utilise la même forme que startCommands. Les champs comme startupCheck, startupFallbackPort et waitFor sont ignorés.

"stopCommands": [
  { "label": "Compose Down", "command": "docker-compose", "args": ["down"] }
]

En l'absence de stopCommands, l'arrêt par défaut est SIGTERM → délai de grâce → SIGKILL.

Actions

Les actions ajoutent des éléments personnalisés au menu d'un outil. Le champ requis type sélectionne le genre d'action : "url", "command", "reveal", "applescript", "applescriptFile" ou "healthCheck". Le champ de charge utile correspondant à ce type est obligatoire ; tout autre champ de charge utile est rejeté par la validation.

Champ Type Valeur par défaut Description
label string requis Texte de l'élément de menu.
type string requis L'une de "url", "command", "reveal", "applescript", "applescriptFile", "healthCheck".
url string - URL à ouvrir dans le navigateur par défaut.
command string - Commande shell à exécuter.
args string[] [] Arguments pour la commande shell.
workingDir string dossier de l'outil Répertoire de travail pour la commande. Sur un service, relatif au directory de l'outil ; sur un shortcut/group (sans directory), utilisez un chemin absolu ou ~.
reveal string - Chemin à ouvrir dans le Finder. Sur un service, un chemin relatif est résolu par rapport au directory de l'outil ; sur un shortcut/group, utilisez un chemin absolu ou ~.
applescript string - Code AppleScript inline.
applescriptFile string - Chemin vers un fichier .applescript.
showWhen string "running" "always", "running" ou "stopped" (service uniquement).
confirm boolean false Si true, Runyard demande une confirmation avant d'exécuter l'action.

Exemples d'actions

Ouvrir une URL :

{ "label": "Ouvrir le frontend", "type": "url", "url": "http://localhost:5173" }

Exécuter une commande shell :

{ "label": "Amorcer la base", "type": "command", "command": "npm", "args": ["run", "db:seed"] }

Ouvrir un fichier dans une application précise :

{ "label": "Éditer README", "type": "command", "command": "open", "args": ["-a", "TextEdit", "./README.md"], "showWhen": "always" }

Ouvrir un projet dans Cursor :

{ "label": "Ouvrir dans Cursor", "type": "command", "command": "cursor", "args": ["."], "showWhen": "always" }

Afficher un dossier dans le Finder :

{ "label": "Ouvrir le projet", "type": "reveal", "reveal": ".", "showWhen": "always" }
{ "label": "Ouvrir les logs du backend", "type": "reveal", "reveal": "backend/logs", "showWhen": "always" }

AppleScript inline :

{ "label": "Activer Safari", "type": "applescript", "applescript": "tell application \"Safari\" to activate", "showWhen": "always" }

AppleScript multiligne (utilisez \n) :

{
  "label": "Mettre Safari au premier plan",
  "type": "applescript",
  "applescript": "try\ntell application \"System Events\" to tell process \"Safari\"\nset frontmost to true\nend tell\nend try",
  "showWhen": "always"
}

AppleScript depuis un fichier :

{ "label": "Déployer", "type": "applescriptFile", "applescriptFile": "scripts/deploy.applescript", "showWhen": "always" }

Espaces réservés de port

Les URL d'action prennent en charge deux espaces réservés :

{ "label": "API Docs", "type": "url", "url": "http://localhost:{{port:Backend}}/api/docs" }

Si le port n'est pas encore connu (outil non démarré), l'espace réservé reste en place et l'URL ne s'ouvre pas.

Confirmation avant exécution

Placez confirm à true sur une action et Runyard affichera une alerte de confirmation avant de l'exécuter. Pratique pour les actions destructives : tuer des serveurs de dev, vider un cache, supprimer une base de données.

{
  "label": "Tuer les serveurs dev résiduels",
  "type": "command",
  "command": "pkill",
  "args": ["-f", "next dev"],
  "confirm": true,
  "showWhen": "always"
}

Le bouton principal de l'alerte exécute l'action ; le bouton par défaut est Annuler, pour qu'un appui accidentel sur Retour ne déclenche rien.

Comportement de showWhen

Valeur Affiché quand
"running" (par défaut) L'outil est en cours d'exécution.
"stopped" L'outil est arrêté ou en erreur.
"always" Toujours affiché.

showWhen ne s'applique qu'aux outils service. Pour shortcut et group, les actions sont toujours visibles.

Commande d'installation

Exécutée une fois avant le premier démarrage, si un fichier de marquage est absent. Utile pour npm install et équivalents.

"installCommand": {
  "command": "npm",
  "args": ["install"],
  "markerPath": "node_modules",
  "enabled": true
}
Champ Type Valeur par défaut Description
command string requis Exécutable.
args string[] [] Arguments.
markerPath string "node_modules" Chemin (relatif au dossier de l'outil) vérifié avant l'exécution. S'il existe, l'installation est sautée.
enabled boolean true Quand false, la commande d'installation est conservée dans le JSON mais ignorée au runtime. Permet de désactiver temporairement l'étape d'installation sans perdre la configuration.

Comportement à trois états

Configuration État dans l'interface Runtime
installCommand absent « Activer l'installation automatique » désactivé, champs vides/placeholders Jamais configuré ; aucune installation
"installCommand": { …, "enabled": false } « Activer l'installation automatique » désactivé, champs remplis et grisés Configuré mais ignoré ; données utilisateur conservées
"installCommand": { …, "enabled": true } (ou enabled omis) « Activer l'installation automatique » activé, champs remplis Configuré et exécuté au démarrage si le marqueur est absent

Le commutateur Réglages → Tools → Commande d'installation se lie à enabled. Le désactiver ne supprime pas le reste de la configuration ; il définit simplement enabled: false.

Réglages avancés

Tous les champs sont optionnels. Les valeurs par défaut sont indiquées.

"advanced": {
  "startupTimeout": 30.0,
  "startupPollInterval": 1.0,
  "startupRequestTimeout": 5.0,
  "sigTermGracePeriod": 3.0,
  "installTimeout": 300.0,
  "stopCommandTimeout": 30.0,
  "logMaxFileSizeMB": 5,
  "logKeepRotations": 3,
  "logMaxAgeDays": 30
}
Champ Valeur par défaut Description
startupTimeout 30.0 Secondes totales d'attente pour que la vérification de démarrage réussisse avant de marquer un processus en erreur.
startupPollInterval 1.0 Secondes entre deux sondages de démarrage.
startupRequestTimeout 5.0 Secondes d'attente pour une seule requête HTTP de sondage de démarrage. Peut être remplacé par processus via startupRequestTimeout.
sigTermGracePeriod 3.0 Secondes entre SIGTERM et SIGKILL lors de l'arrêt d'un processus sans stopCommands.
installTimeout 300.0 Secondes d'attente pour la commande d'installation avant abandon.
stopCommandTimeout 30.0 Secondes d'attente pour chaque entrée de stopCommands.
logMaxFileSizeMB 5 Taille (Mo) qu'un journal peut atteindre avant que Runyard ne le fasse tourner. Les fichiers archivés sont renommés *.log.1, *.log.2.gz, etc., et compressés en gzip à partir de la deuxième rotation. Plage : 1–100.
logKeepRotations 3 Nombre d'archives conservées sur le disque pour chaque journal. Les plus anciennes sont supprimées à la prochaine rotation. Plage : 1–20.
logMaxAgeDays 30 Les journaux archivés plus vieux que cette valeur sont supprimés au démarrage de l'application. Le fichier .log en cours n'est jamais supprimé par âge. Plage : 1–365.

Chemins et résolution du PATH

Les commandes s'exécutent via /usr/bin/env avec un PATH construit sur mesure. Les règles :

Configuration type :

{
  "paths": ["~/.nvm/versions/node/v22.0.0/bin", "/opt/homebrew/bin"]
}

Pour les utilisateurs d'asdf, ajoutez ~/.asdf/shims (et /opt/homebrew/bin si asdf a été installé via Homebrew) :

{
  "name": "Mon application Phoenix",
  "type": "service",
  "directory": "~/Code/mon-app",
  "paths": ["~/.asdf/shims", "/opt/homebrew/bin"],
  "startCommands": [
    { "label": "Server", "command": "mix", "args": ["phx.server"],
      "startupCheck": "http://localhost:4000/health", "startupFallbackPort": 4000 }
  ]
}

Vérifications d'état

Une vérification d'état est un type d'outil qui interroge un point d'extrémité arbitraire à son propre intervalle. Les vérifications d'état sont indépendantes de tout service géré par Runyard : vous pouvez interroger une API distante, une base de données locale, ou n'importe quoi qui parle HTTP ou accepte une connexion TCP.

Les vérifications d'état apparaissent dans le popover sous forme de cartes repliables propres (lorsqu'elles sont définies au niveau racine) ou de rangées compactes (lorsqu'elles sont imbriquées dans un groupe). La pastille d'état et le point d'état en direct vous indiquent ce qui se passe :

Lorsqu'une vérification d'état est en échec, un petit triangle d'avertissement rouge apparaît à côté de l'icône Runyard dans la barre de menus. Les groupes qui contiennent la vérification d'état en échec basculent aussi leur pastille agrégée en état d'avertissement.

Schéma d'une vérification d'état

{
  "name": "Production API",
  "type": "healthCheck",
  "autoStart": true,            // optionnel, par défaut true
  "interval": 30,               // optionnel, secondes; défaut 30, minimum 5
  "failureThreshold": 2,        // optionnel, vérifications consécutives échouées avant de marquer en échec; défaut 2, minimum 1

  // Vérification HTTP (mutuellement exclusive avec tcp)
  "http": {
    "url": "https://api.example.com/health",
    "expectStatus": 200,                    // optionnel; int ou [int]; défaut 200
    "expectBodyContains": "ok",             // optionnel; sous-chaîne sensible à la casse
    "requestTimeout": 5                     // optionnel; secondes; défaut 5
  },

  // Vérification TCP (mutuellement exclusive avec http)
  "tcp": {
    "host": "db.internal",
    "port": 5432,
    "connectTimeout": 3                     // optionnel; secondes; défaut 3
  }
}

Une vérification d'état doit spécifier exactement un des deux : http ou tcp.

Seuil d'échec

Une vérification d'état bascule en échec après failureThreshold vérifications consécutives échouées (défaut 2), et revient à saine après 1 vérification réussie. Le seuil évite les oscillations sur des coupures réseau transitoires tout en faisant remonter les vraies pannes en un seul intervalle. Définissez failureThreshold: 1 pour une vérification d'état agressive qui bascule à la première erreur, ou une valeur plus élevée pour plus de tolérance.

Mettre en pause / reprendre depuis le popover

Utilisez Pause / Resume pour suspendre ou reprendre le sondage. Sur une carte de niveau racine, le bouton vit dans la barre de boutons en pied de carte. Sur une rangée imbriquée dans un groupe, c'est un bouton-icône à droite de la rangée. Pause et Resume sont des contrôles d'exécution uniquement : ils ne modifient pas config.json, donc la vérification d'état respecte le champ autoStart au prochain lancement. Le popover ne se ferme pas.

Contrôles par vérification d'état

Une carte de vérification d'état au niveau racine affiche :

Cliquez-droit sur une rangée imbriquée pour copier l'URL du point d'extrémité (http://...) ou hôte:port dans le presse-papiers.

Statistiques de latence (p50 / p95 / moyenne)

Survolez la pastille d'état ou le sparkline pour ouvrir le popover des statistiques : p50 (médiane), p95 (queue lente), latence moyenne, et taux d'échec. Les statistiques sont calculées sur une fenêtre glissante d'une heure des sondages récents. La fenêtre est fixe et non configurable, choisie pour fournir suffisamment d'échantillons pour un p95 significatif à tout intervalle de sondage raisonnable (5 s à 60 s), tout en bornant l'utilisation mémoire.

La fenêtre est conservée en mémoire seulement et se réinitialise quand Runyard se ferme. Tant que cinq sondages réussis ne sont pas accumulés après un (re)démarrage ou après une longue période hors ligne, le popover affiche une vue « peu d'échantillons » avec la latence la plus récente au lieu du résumé complet.

Vérifications d'état à l'intérieur des groupes

Le tableau tools d'un outil de type group peut contenir des entrées healthCheck aux côtés de services et de raccourcis. Les vérifications d'état imbriquées en échec font basculer la pastille agrégée du groupe en état d'avertissement. Les rangées imbriquées s'affichent de manière compacte sans le sparkline. Promouvez-en une au niveau racine si vous voulez le sparkline + l'UI complète de la carte.

Action healthCheck ponctuelle

Le tableau actions de tout outil peut inclure une action de type healthCheck. Cliquer dessus lance une vérification ad-hoc et met à jour l'étiquette de l'action en ligne pendant ~3 secondes avec le résultat.

"actions": [
  {
    "label": "Verify Staging",
    "type": "healthCheck",
    "healthCheck": {
      "http": {
        "url": "https://staging.example.com/health",
        "expectStatus": 200
      }
    }
  }
]

La forme de l'action healthCheck reflète le bloc http ou tcp d'une vérification d'état, moins les champs d'exécution (interval, autoStart).

Garder éveillé

Activez keepSystemAwake: true sur un service pour empêcher macOS de se mettre en veille tant que ce service fonctionne. Runyard lance un caffeinate -i -w <pid> par processus de démarrage actif, donc le système reste éveillé tant qu'au moins un processus du service est vivant. Chaque assertion par processus se libère dès que son PID surveillé disparaît (arrêt propre, SIGKILL, ou crash), et l'assertion globale du service ne tombe que lorsque tous les processus de démarrage ont quitté. L'ordre des startCommands n'a aucune importance.

Ajoutez keepDisplayAwake: true pour aussi passer -d, qui empêche l'écran de s'assombrir ou de se verrouiller. C'est une option à activer manuellement, car elle accélère la décharge de la batterie.

{
  "name": "Big Build",
  "type": "service",
  "directory": "~/Code/big-build",
  "keepSystemAwake": true,        // système éveillé pendant l'exécution
  "keepDisplayAwake": false,      // l'écran s'assombrit normalement (défaut)
  "startCommands": [
    { "label": "Build", "command": "npm", "args": ["run", "build"] }
  ]
}

Les mêmes champs fonctionnent sur les outils healthCheck. Une vérification n'a pas de PID, donc Runyard maintient un caffeinate -i indéfini tant que la vérification est active (non mise en pause) et le libère dès que vous la mettez en pause, la retirez de la config, ou désactivez le champ.

{
  "name": "Prod Status",
  "type": "healthCheck",
  "keepSystemAwake": true,        // maintient caffeinate pendant le sondage
  "interval": 30,
  "http": { "url": "https://api.example.com/healthz", "expectStatus": 200 }
}

La même infrastructure caffeinate pilote aussi le bouton Garder éveillé du popover, un filet de sécurité manuel que vous pouvez activer sans modifier la configuration. Les activations manuelles, liées à un service et liées à une vérification peuvent toutes être actives en même temps ; chacune maintient sa propre assertion d'alimentation indépendante.

Limitation : macOS ne garde pas votre Mac éveillé lorsque le capot est fermé, quel que soit l'outil anti-veille utilisé. C'est une contrainte de sécurité thermique qui s'applique à tout outil basé sur caffeinate.

Popover de la barre de menus

Le popover est l'unique UI de barre de menus. Quelques clés UserDefaults pilotent son comportement :

Clé UserDefaults Type Défaut Description
collapsed.{toolName} Bool false État réduit par carte. Écrit quand vous cliquez sur le chevron d'une carte ou sur le bouton Tout réduire / Tout déplier de l'en-tête. Restauré entre les lancements.
popoverShowSummary Bool true Cache ou affiche la ligne N running · M errors dans l'en-tête du popover.
popoverDefaultState String "lastUsed" État de réduction par défaut appliqué à chaque carte de premier niveau à chaque ouverture du popover. Défini par le sélecteur segmenté de Réglages → General → Popover. "expanded" force toutes les cartes ouvertes à chaque ouverture ; "collapsed" les force fermées ; "lastUsed" conserve la mémoire par carte. Les réductions manuelles pendant une session sont conservées jusqu'à la prochaine ouverture, puis réinitialisées (sauf en mode "lastUsed").

Ces clés vivent dans ~/Library/Preferences/ca.bonevil.runyard.plist. Modifiez-les avec defaults write ca.bonevil.runyard <clé> <valeur> si vous devez réinitialiser la mise en page depuis la ligne de commande.

Le popover est persistant par défaut. Il ne se ferme que sur Échap, un clic à l'extérieur, ou la perte de focus.

Exemple complet

{
  "paths": ["~/.nvm/versions/node/v22.0.0/bin", "/opt/homebrew/bin"],
  "tools": [
    {
      "name": "MyApp",
      "type": "service",
      "directory": "~/Code/my-app",
      "autoStart": true,
      "installCommand": { "command": "npm", "args": ["install"] },
      "startCommands": [
        { "label": "Backend",  "command": "npm", "args": ["run", "dev"],
          "startupCheck": "http://localhost/api/health", "startupFallbackPort": 3001 },
        { "label": "Frontend", "command": "npm", "args": ["run", "dev"],
          "workingDir": "frontend", "startupFallbackPort": 5173, "waitFor": "Backend" }
      ],
      "actions": [
        { "label": "Ouvrir le frontend",  "type": "url",     "url": "http://localhost:{{port}}/" },
        { "label": "API Docs",            "type": "url",     "url": "http://localhost:{{port:Backend}}/api/docs" },
        { "label": "Amorcer la base",     "type": "command", "command": "npm", "args": ["run", "db:seed"] },
        { "label": "Ouvrir dans Cursor",  "type": "command", "command": "cursor", "args": ["."], "showWhen": "always" },
        { "label": "Ouvrir le projet",    "type": "reveal",  "reveal": ".", "showWhen": "always" }
      ]
    },
    {
      "name": "Liens rapides",
      "type": "shortcut",
      "actions": [
        { "label": "Grafana", "type": "url", "url": "https://grafana.example.com" },
        { "label": "Jira",    "type": "url", "url": "https://jira.example.com" }
      ]
    },
    {
      "name": "API de production",
      "type": "healthCheck",
      "interval": 30,
      "failureThreshold": 2,
      "http": {
        "url": "https://api.example.com/health",
        "expectStatus": 200,
        "expectBodyContains": "ok"
      }
    }
  ],
  "advanced": {
    "startupTimeout": 45.0,
    "startupPollInterval": 1.0
  }
}