title: "Référence CLI" description: "Piloter Mercy SF depuis un autre programme : chaque commande, sa sortie JSON, les codes de sortie et des exemples travaillés."
Référence CLI, piloter Mercy SF depuis un autre programme
mercy-cli a un mode non interactif : des arguments en entrée, un objet JSON en sortie. Il est destiné aux tableaux de bord, aux superviseurs et aux scripts.
Cette page est la promesse. Les commandes et les noms de champs ci-dessous ne changeront pas sans préavis. Tout le reste de la CLI, le menu, ses formulations, la mise en page de sa sortie, peut changer à tout moment, alors ne construisez rien dessus.
Lancée sans argument, le menu interactif démarre exactement comme avant.
Chaque réponse porte "api". Aujourd'hui c'est 1. Ajouter un champ ne le change pas, un appelant qui ne lit que les champs qu'il connaît n'est pas affecté. Il change quand un champ est retiré ou que son sens change, vous pouvez donc refuser de tourner face à une forme pour laquelle vous n'avez pas été construit.
Se connecter
Les comptes créés via l'authentification unique S&F n'ont besoin de rien de plus. Un compte qui appartient à un serveur et n'a jamais fait partie de l'authentification unique n'existe pas de son point de vue, et a besoin de --server :
mercy-cli --status --user <account> --server https://f1.sfgame.net/--server fonctionne avec toutes les commandes ci-dessous.
Sortie
Toutes les commandes sauf --start impriment du JSON et rien d'autre, donc --json est déjà la valeur par défaut et le passer ne change rien. Il existe pour les appelants qui préfèrent le dire plutôt que de compter sur un défaut, et pour --start, qui sinon écrit un journal lisible par un humain.
Lire
Lister les personnages d'un compte
mercy-cli --characters --user <account>{
"ok": true,
"characters": [
{ "character": "Hero", "server": "https://s1.sfgame.net/", "level": 345 },
{ "character": "Alt", "server": "https://s42.sfgame.net/", "level": 88 }
]
}Une seule connexion couvre tous les personnages du compte, tous serveurs confondus. level vaut null si l'état de ce personnage n'a pas pu être récupéré, l'entrée est tout de même listée, parce que savoir qu'il existe est l'objet de l'appel.
Lire l'état d'un personnage
mercy-cli --status --user <account> --character <name>--character peut être omis quand le compte n'en a qu'un.
{
"ok": true,
"character": "Hero",
"server": "https://s1.sfgame.net/",
"level": 345,
"class": "DemonHunter",
"race": "DarkElf",
"experience": { "current": 740300000, "next_level": 769800000 },
"silver": 725000000,
"mushrooms": 8,
"honor": 12043,
"rank": 1800,
"action": "CityGuard",
"attributes": {
"strength": { "base": 4077, "bonus": 1200 },
"dexterity": { "base": 1000, "bonus": 300 },
"intelligence": { "base": 900, "bonus": 250 },
"constitution": { "base": 3000, "bonus": 800 },
"luck": { "base": 700, "bonus": 150 }
},
"equipment": [
{
"slot": "Weapon",
"model_id": 55,
"color": 1,
"class": "DemonHunter",
"epic": true,
"legendary": false,
"armor": 0,
"min_damage": 900,
"max_damage": 1400,
"upgrades": 3,
"attributes": { "strength": 420, "constitution": 210 },
"rune": { "type": "FireDamage", "value": 60 },
"enchantment": "SwordOfVengeance",
"gem": { "type": "Strength", "value": 300 }
}
],
"arena": {
"fights_for_xp": 7,
"next_free_fight": "2026-08-06T12:04:11+02:00",
"opponents": [50099, 34316, 13668]
},
"tavern": {
"thirst_for_adventure_sec": 3600,
"beer_drunk": 2,
"mushroom_skip_allowed": true,
"quests": [
{ "length_sec": 600, "silver": 120000, "experience": 45000,
"location": "Wolves", "item": true }
]
},
"guild": {
"name": "Blvck Clover",
"rank": 12,
"honor": 90000,
"members": [
{ "name": "Hero", "level": 345, "last_online": "2026-08-06T09:12:00+02:00" }
]
},
"scrapbook_items": 1102
}Tout provient d'une seule récupération d'état, il n'y a donc pas de sections à demander et rien à économiser en demandant moins.
guild vaut null quand le personnage n'est dans aucune. scrapbook_items vaut null tant que l'album n'a pas été lu au moins une fois. Un emplacement d'équipement vide est omis plutôt que listé comme null.
À propos des noms d'objets : le protocole porte un identifiant de modèle et un emplacement, pas un nom d'affichage, et c'est donc ce que vous obtenez. C'est aussi par là que l'illustration est adressée, un tableau de bord peut donc afficher exactement l'image que l'application de bureau affiche. Inventer un nom ici serait une supposition invérifiable.
Répéter une lecture à intervalle régulier
mercy-cli --status --user <account> --character <name> --watch 60Une connexion, puis un objet toutes les 60 secondes jusqu'à l'arrêt du processus. Utilisez ceci plutôt que de relancer la commande en boucle : chaque exécution est une nouvelle connexion, ce qui est la façon la plus coûteuse de surveiller un nombre et la plus voyante du côté du serveur. Le plancher est de 30 secondes. Un tour raté est imprimé et la boucle continue, un observateur qui abandonne au premier hoquet ressemble exactement à un observateur où tout va bien.
Ce qu'il affrontera ensuite, et pourquoi
mercy-cli --plan --user <account> --character <name>{
"ok": true,
"outcome": "planned",
"reason": "Next: Someone — 3 scrapbook items, 72% odds",
"candidates": [
{
"name": "Someone",
"level": 340,
"source": "pool",
"missing_items": 3,
"rank_gain": 12,
"favourite": false,
"win_chance": 0.72,
"item_score": 300.0,
"xp_score": 60.0,
"rank_score": 25.0,
"base": 385.0,
"score": 277.2
}
]
}La même décision que celle affichée par l'application : les candidats notés sur les objets, l'XP quotidienne et le rang, puis multipliés par les probabilités. outcome vaut planned, waiting, blocked ou empty, et reason dit lequel en une phrase.
Cela ne dépense ni combat ni champignon. Cela parcourt en revanche le serveur pour trouver des candidats, ce n'est donc pas gratuit en requêtes, ne le mettez pas dans une boucle serrée.
Répéter un combat
mercy-cli --simulate --user <account> --character <name> --against <player>{ "ok": true, "against": "Someone", "against_level": 340, "win_chance": 0.63, "iterations": 1000 }De l'arithmétique. Une consultation part vers le serveur pour savoir qui c'est ; le combat lui-même n'est jamais envoyé. win_chance est le nombre de victoires sur les combats tranchés, une simulation qui ne parvient pas à conclure rend donc compte de moins de combats qu'elle n'en a lancés.
Ce qui est porté
mercy-cli --inventory --user <account> --character <name>{ "ok": true, "carried": 12, "backpack": [ { "slot_index": 0, "model_id": 55 } ] }Une seule liste, parce que le jeu n'en tient qu'une. Les objets ont la même forme que dans --status, plus slot_index.
Album
mercy-cli --scrapbook --user <account> --character <name>{ "ok": true, "unlocked": true, "owned": 1102, "monsters": 214 }unlocked vaut false en dessous du niveau qui donne un album, et owned y vaut null. Ce n'est pas une erreur.
Combats enregistrés
mercy-cli --history --user <account> --character <name> --limit 50{ "ok": true, "total": 4210, "returned": 50, "battles": [] }Les plus récents d'abord. Ils sont enregistrés par l'installation qui a fait tourner le bot, pas récupérés depuis le serveur, un processus qui vient de démarrer n'a donc rien à montrer, et ce n'est pas un défaut. total est ce qui existe, returned est ce que --limit a laissé.
Agir
Faire tourner le bot
mercy-cli --start --user <account> --character <name>Tourne jusqu'à l'arrêt du processus, en journalisant au fil de l'eau. --character est obligatoire ici : lister et lire un état sont inoffensifs, mais démarrer le bot fait jouer, et se tromper de personnage n'est pas quelque chose que l'on peut défaire.
--all fait tourner à la place tous les personnages du compte dans ce même processus. C'est une connexion pour le compte plutôt qu'une par personnage, ce qui coûte moins cher et empêche les personnages d'invalider mutuellement leur session.
Sur Ctrl-C (ou SIGTERM), le coup en cours a le droit de se terminer avant que le processus ne s'arrête, plutôt que d'être coupé au milieu d'une requête.
Des événements plutôt que de la prose
mercy-cli --start --user <account> --character <name> --eventsUn objet JSON par ligne, un superviseur lit donc ligne par ligne :
{"at":"2026-08-06T17:22:04+02:00","api":1,"event":"arena_win","character":"Hero"}
{"at":"2026-08-06T17:22:31+02:00","api":1,"event":"quest_done","character":"Hero"}event vaut l'un de started, stopping, stopped, arena_win, arena_loss, dungeon_win, dungeon_loss, quest_done, scrapbook_win, scrapbook_loss, scrapbook_items (qui porte un count).
Cette liste va s'allonger. Traitez un événement que vous ne reconnaissez pas comme un événement à ignorer, pas comme une erreur, c'est toute la raison pour laquelle les noms sont écrits en toutes lettres au lieu d'être ce que Rust a imprimé.
Utilisez ceci plutôt que de lire le journal humain. Ce journal est écrit pour une personne et peut être reformulé à tout moment ; le parser signifie que votre outil casse quand quelqu'un améliore une phrase.
Ramasser ce qui attend
mercy-cli --claim --user <account> --character <name>Calendrier, tâches quotidiennes, déblocages en attente. --character est obligatoire, pour la même raison que --start.
Lire ou changer la configuration
mercy-cli --config --user <account> --character <name>
mercy-cli --config --user <account> --character <name> --set auto_arena=false{ "ok": true, "settable": ["auto_arena"], "changed": [ { "key": "auto_arena", "from": true, "to": false } ] }La lecture renvoie toute la configuration sous config. Les noms de champs à l'intérieur de ce bloc ne font pas partie de cette promesse, seules les clés listées dans settable en font partie. La configuration compte bien plus de cent champs et grossit à chaque version ; les promettre toutes reviendrait à ne jamais pouvoir remanier les réglages.
--set accepte true/false, on/off, yes/no, 1/0, exige toujours --character (un interrupteur appartient à un personnage, et « le seul » est une supposition acceptable pour lire et fausse pour écrire), et peut être répété. La valeur est relue depuis le disque ensuite et l'appel échoue si elle n'a pas atterri, car signaler un changement qui n'a pas eu lieu est le seul résultat dont on ne peut pas se remettre.
Exploitation
Quelle version est-ce
mercy-cli --version{ "ok": true, "version": "2.12.0", "api": 1 }La seule commande qui n'a besoin ni d'un mot de passe ni du réseau. Un superviseur doit pouvoir demander à quoi il parle avant d'avoir des identifiants pour lui parler.
Est-ce joignable
mercy-cli --health --user <account> --character <name>{ "ok": true, "character": "Hero", "level": 345, "round_trip_ms": 412 }Se connecter, récupérer, rapporter, sortir. Délibérément minuscule : une vérification de santé qui renvoie une fiche de personnage complète invite à la parser, et alors la vérification devient une interface à part entière.
Le mot de passe
Il n'y a pas de drapeau --password, exprès. Les arguments de ligne de commande sont lisibles par tous les autres processus de la machine, ps, /proc, le gestionnaire de tâches, donc un tableau de bord qui ferait tourner ceci pour plusieurs personnes livrerait tous leurs mots de passe de jeu à quiconque a un shell sur cette machine.
Deux voies à la place :
MERCY_SF_PASSWORD='…' mercy-cli --status --user someoneprintf '%s' "$password" | mercy-cli --status --user someone --password-stdin--password-stdin lit la première ligne de l'entrée standard, ce qui garde aussi le secret hors de l'environnement.
Codes de sortie
| Code | Signification |
|---|---|
0 | Succès, et pour --help |
1 | Ça a tourné et échoué, la raison est dans error, et ok vaut false |
2 | L'appel était erroné : option inconnue, valeur manquante, aucune action. Sur stderr |
La différence entre 1 et 2 compte pour un superviseur : 2 échouera de la même façon à chaque fois et demande un humain, 1 peut très bien marcher à la prochaine tentative.
Erreurs
Les échecs sont aussi du JSON, un seul parseur gère donc les deux :
{ "ok": false, "error": "no character called 'Typo' on this account" }ok est présent dans chaque réponse. Vérifiez-le avant toute chose.
Causes fréquentes :
| Message | Que faire |
|---|---|
--user is required | --user manque (toute action sauf --version) |
--start needs --character (or --all) | Nommez la cible ; elle ne sera pas devinée |
this account has N characters — name one with --character | Il y en a plusieurs, choisissez-en un |
no character called '…' on this account | Faute de frappe ; --characters liste les noms valides |
no password: set MERCY_SF_PASSWORD or pass --password-stdin | Choisissez l'une des deux voies pour le mot de passe |
login failed: … | Vérifiez les identifiants ou le serveur ; les comptes hors authentification unique ont besoin de --server |
'…' is not settable from here | Pas sur la liste autorisée ; --config sans --set imprime settable |
'…' did not save — it still reads back unchanged | Le changement n'a pas atterri ; vérifiez les droits d'écriture |
Recettes
Ramasser sur chaque compte une fois par jour
for account in alpha beta gamma; do
MERCY_SF_PASSWORD="$(pass mercy/$account)" \
mercy-cli --claim --user "$account" --character Hero || echo "failed: $account"
doneGarder une tuile de tableau de bord à jour
MERCY_SF_PASSWORD='…' mercy-cli --status --user someone --character Hero --watch 60Une connexion, un objet par minute. Ne relancez pas ceci depuis cron chaque minute à la place, ce serait une nouvelle connexion à chaque fois.
Enregistrer les événements dans un fichier
MERCY_SF_PASSWORD='…' mercy-cli --start --user someone --character Hero --events \
>> events.ndjsonVérifier à quoi vous parlez avant de vous connecter
api=$(mercy-cli --version | jq -r .api)
[ "$api" = "1" ] || { echo "unknown contract version: $api"; exit 1; }En service systemd
[Service]
Environment=MERCY_SF_PASSWORD=…
ExecStart=/usr/local/bin/mercy-cli --start --user someone --all --events
Restart=on-failure
KillSignal=SIGTERMSIGTERM laisse le coup en cours se terminer. Merci de ne pas lui envoyer SIGKILL.
Ce que cela ne fait pas encore
Démarrer plusieurs personnages et les arrêter individuellement (--all les démarre tous et les arrête tous ensemble), un canal de contrôle vers un processus en cours, et changer quoi que ce soit au-delà des interrupteurs de modules.
Si vous avez besoin de l'un d'eux, dites lequel, il est plus facile de construire la bonne chose que de deviner et de la voir inutilisée.