CLI

@agreely/cli est la barrière de consentement sous forme d'outil en ligne de commande. Un seul binaire, deux modes :

  • Les humains obtiennent une sortie en couleur et un assistant interactif (à un TTY).
  • Les agents obtiennent du JSON pur sur stdout et des codes de sortie stables (un tube ou --json).

C'est une fine coquille par-dessus @agreely/sdk : elle ne réimplémente jamais la logique HTTP, de décision ou de normalisation. Elle résout l'authentification, choisit un mode, appelle le SDK, et mappe le résultat vers un code de sortie.

Paquet @agreely/cli v0.1.0, binaire agreely, ESM, Node >=18.

Installer

npm install -g @agreely/cli
agreely --help
agreely --version    # -> 0.1.0

Agent-native : une variable d'environnement + --json

export AGREELY_API_KEY=agr_live_xxx          # le seul réglage dont un agent a besoin

agreely check cust-42 "Email Address" "Marketing Outreach" --json
# -> {"decision":"allow","status":"active","consentRef":"0x…"}   exit 0

Définissez la clé une fois via AGREELY_API_KEY (aucune invite, aucun trousseau requis), passez --json (ou redirigez simplement par un tube : un stdout qui n'est pas un TTY déclenche automatiquement le mode agent), et branchez sur le code de sortie. category / purpose sont envoyés bruts; le serveur les normalise.

Drapeaux globaux

Acceptés avant ou après la sous-commande :

  • --json : force le mode agent (JSON pur sur stdout).
  • --api-key <key> : déconseillé, visible dans ps.
  • --base-url <url> : remplace l'URL de base de l'API.

Variables d'environnement

  • AGREELY_API_KEY : le chemin d'authentification agent.
  • AGREELY_BASE_URL : l'URL de base de l'API.
  • AGREELY_RPC_URL : le repli RPC pour verify --onchain.
  • XDG_CONFIG_HOME : relocalise le dossier de configuration.

Le tableau des codes de sortie (le contrat agent)

Le code de sortie est le contrat. Il sépare proprement une panne d'un refus et une vérification échouée d'une panne, ce qui est tout l'intérêt d'une barrière de consentement agent-native.

code signification
0 succès, ou une vérification ALLOW
10 une vérification DENY propre, un négatif attendu, pas une erreur
2 erreur d'usage : arguments mauvais/manquants, entrée invalide, une erreur de validation serveur, une erreur de config, et une ressource introuvable
3 authentification : la clé est absente, invalide, révoquée ou dépourvue de la portée requise
4 indisponible : une panne d'Agreely (distincte d'un refus), et une expiration de sondage côté client
5 limite de débit : la fenêtre par entreprise a été dépassée
6 vérification échouée : un reçu a été vérifié et n'a pas été validé (une falsification). Un verdict, pas une erreur
1 un échec inattendu / non catégorisé

check résout ALLOW vers 0 et DENY vers 10. La CLI fonctionne en refus par défaut : lors d'une panne, le SDK lève une exception et la CLI sort avec 4, de sorte qu'un appelant peut toujours distinguer « panne » de « refusé ». Le JSON d'un DENY va tout de même sur stdout; une véritable erreur garde stdout propre et écrit une enveloppe {"error":{"code","message"}} sur stderr.

Modes (détectés automatiquement)

condition mode comportement
stdout est un TTY et pas de --json humain couleurs, l'assistant, confirmations
stdout n'est pas un TTY, ou --json agent jamais d'invite, JSON pur sur stdout, journaux/erreurs sur stderr

En mode agent, stdout ne transporte que du JSON pur et rien d'autre; chaque journal, indice ou erreur va sur stderr, sous l'enveloppe {"error":{"code","message"}} en cas d'erreur. En mode humain, la sortie est colorée avec assistants et confirmations, et une erreur s'affiche en rouge sous la forme x message. picocolors désactive automatiquement la couleur hors TTY. Un argument requis manquant en mode agent est une erreur claire assortie d'une sortie d'usage (2); il ne reste jamais bloqué à attendre une invite.

Commandes

agreely check <customerId> <category> <purpose>
agreely check --batch <file.json>
agreely catalog
agreely whoami
agreely requests [--status <s>] [--cursor <id>]
agreely request create [--customer <id>] [--to <email>] [--item <item>]... [--valid-until <YYYY-MM-DD>] [--idempotency-key <key>]
agreely request show <requestId>
agreely request cancel <requestId>
agreely request wait <requestId> [--interval <ms>] [--timeout <ms>]
agreely manual-consent create [--customer <id>] [--document-version <id>] [--effective-date <YYYY-MM-DD>] [--valid-until <YYYY-MM-DD>] [--item <item>]... [--pdf <path>] [--upload]
agreely manual-consent claim-link [--customer <id>] [--reference <ref>]
agreely manual-consent revoke <consentRef> [--reason <text>]
agreely manual-consent erase <consentRef> [--reason <text>]
agreely relationship end <customerRef> --reason <text>
agreely verify <receipt.json> [--ipfs] [--onchain] [--rpc-url <url>] [--did-doc <file>]...
agreely login
agreely config set [--api-key <k>] [--base-url <url>]

check

Décision synchrone. category / purpose sont envoyés bruts (le serveur normalise). Sortie 0 = ALLOW, 10 = DENY, 4 = panne.

agreely check cust-42 "Email Address" "Marketing Outreach" --json
# {"decision":"allow","status":"active","consentRef":"0x…"}      exit 0
# {"decision":"deny","status":"revoked","consentRef":"0x…"}      exit 10

En mode humain : OK ALLOW … ou DENY …. En mode agent, le JSON est {decision, status, consentRef?}.

Mode lot : --batch <file.json> lit un tableau JSON d'objets {customerRef, category, purpose}, appelle checkBatch() une fois, et imprime une table de décisions (humain) ou un tableau JSON (agent, un objet {customerRef, category, purpose, decision, status, consentRef?} par cellule). La sortie est 0 quand toutes les cellules autorisent, 10 dès qu'une refuse, 4 en cas de panne. C'est l'outil pour protéger l'affichage d'une liste (N clients x M champs = N x M vérifications) en un seul aller-retour.

agreely check --batch ./cells.json --json
# [{"customerRef":"cust-42","category":"Email Address","purpose":"Marketing Outreach","decision":"allow","status":"active","consentRef":"0x…"}]

catalog

Liste le catalogue actif de l'entreprise. JSON {catalog: entries}.

whoami

Vérifie la clé côté serveur en appelant GET /v1/whoami : la commande rapporte les portées réellement vérifiées par le serveur, et non une déduction locale. Une clé invalide sort avec 3. La commande n'imprime jamais le secret (masqué : les 8 premiers + les 4 derniers caractères). JSON : {authenticated, apiKeyMasked, apiKeySource, baseUrl, scopes}.

requests

Liste les demandes, pagination par curseur. --status accepte pending|approved|refused|expired|revoked_before_action (une valeur invalide est une erreur d'usage). Le JSON émet la page brute {items, nextCursor}.

request create

Double mode. Non interactif lorsque le mode agent est actif ou qu'un drapeau scriptable est présent : la CLI valide alors (client requis, courriel valide, date YYYY-MM-DD, au moins un item). À un TTY humain sans drapeau, un assistant guide la sélection dans le catalogue, recueille le client, le destinataire et la date d'expiration, valide chacun, et confirme avant d'émettre.

agreely request create \
  --customer cust-42 --to [email protected] \
  --item "Email Address:Marketing Outreach" --item 4b082452-… \
  --valid-until 2030-01-01 --idempotency-key issue-2026-001 --json
# -> l'IssuedRequest brut

Un --item est soit l'id d'une entrée de catalogue, soit category:purpose. Réutilisez --idempotency-key pour rendre une nouvelle tentative sûre : un rejeu renvoie la demande d'origine, sans double émission ni double courriel.

request show

requestId doit correspondre à ^0x[0-9a-f]{64}$. JSON : l'enregistrement brut.

request cancel

Annule une demande en attente par son requestId (portée issue). L'appel est idempotent : une demande déjà terminale n'est pas une erreur, la sortie reste 0 avec cancelled: false. JSON : {requestId, status, cancelled}.

agreely request cancel 0x… --json
# {"requestId":"0x…","status":"revoked_before_action","cancelled":true}   exit 0

request wait

Sonde jusqu'à ce que la demande soit réglée (approved|refused|expired|revoked_before_action). Une expiration sort avec 4. --interval vaut 2000 ms par défaut, --timeout vaut 120000 ms. JSON : l'enregistrement réglé brut.

agreely request wait 0x… --timeout 300000 --json

Portée attest. Enregistre un consentement obtenu hors ligne (papier ou PDF signé).

create : le PDF est haché localement (sha256); par défaut seul pdfSha256 ("0x" + hash) est envoyé. Les octets bruts (base64) ne quittent la machine qu'avec --upload. Tous les éléments sont requis : --customer, --document-version, --effective-date, --valid-until, au moins un --item, et --pdf. JSON : le ManualConsentResult brut.

agreely manual-consent create \
  --customer cust-42 --document-version dv-2026-05 \
  --effective-date 2026-05-01 --valid-until 2031-05-01 \
  --item "Email Address:Marketing Outreach" \
  --pdf ./signed.pdf --json
# le hash seul part; ajoutez --upload pour envoyer aussi les octets

claim-link : --customer requis. JSON : le ClaimLink brut {claimUrl, token, expiresAt}.

revoke : consentRef correspond à ^0x[0-9a-f]+$ (insensible à la casse). JSON : la révocation brute {consentRef, revoked, alreadyRevoked}.

erase : efface (crypto-broyage) un consentement manuel. JSON : l'effacement brut {consentRef, erased, alreadyErased}.

relationship end

Portée relationship. Atteste qu'une relation client est terminée (Loi 25 art. 23, « les fins sont accomplies »). <customerRef> est votre propre référence client (la même que pour check), rattachée à l'entreprise de la clé. --reason est requis : un motif absent ou vide sort en erreur, jamais une fin silencieuse. L'appel est idempotent (re-finir une relation déjà terminée est un succès; endedAt et endedBy d'origine sont préservés). C'est une surcouche de cycle de vie : elle ne révoque ni n'efface aucun consentement. JSON : {customerRef, status, endedAt, endedBy}.

agreely relationship end cust-42 --reason "Compte fermé, fins accomplies" --json
# {"customerRef":"cust-42","status":"ended","endedAt":"2026-06-26T15:04:05Z","endedBy":"company"}   exit 0

verify

Vérification de reçu hors ligne d'abord. Une fine enveloppe autour de la méthode statique Agreely.verifyReceipt du SDK, qui expose les mêmes statuts honnêtes : elle ne redérive pas les verdicts.

En mode humain, verify imprime une matrice d'honnêteté à cinq lignes, companySignature, citizenAssertion, disclosureCopy, documentAnchor et cellLabelBinding, chacune pass / fail / unavailable / unsupported / skipped, plus un verdict global (VERIFIED / PARTIAL / UNAVAILABLE / FAILED) et des notes. La ligne cellLabelBinding est le champ à lire avant de faire confiance à une étiquette affichée. En mode agent, elle émet le ReceiptVerification brut.

agreely verify ./receipt.json --json

Drapeaux :

  • --ipfs : active la vérification disclosureCopy (récupère la copie IPFS et la compare à disclosureHash); autrement skipped.
  • --onchain : active la vérification documentAnchor en chaîne et exige une URL RPC via --rpc-url ou AGREELY_RPC_URL (sinon erreur d'usage).
  • --did-doc <file> (répétable) : installe un résolveur de document DID local, rendant verify entièrement hors réseau. Un DID inconnu se résout alors vers unavailable (sortie 4) plutôt que d'atteindre le réseau. Sans --did-doc, les vérifications de signature et d'assertion effectuent par défaut une seule résolution DID par HTTPS.

Sortie 4 (indisponible) contre sortie 6 (échec)

C'est le cœur honnête du vérificateur. Un DID irrésoluble (le DID de l'émetteur ou du citoyen ne peut être résolu) est sortie 4 (indisponible), pas sortie 6. La sortie 6 est réservée à un échec cryptographique actif : une falsification ou une mauvaise clé. Un reçu valide, durant une panne de résolution DID, ne doit jamais ressembler à une contrefaçon.

login

Interactif uniquement. En mode agent, login échoue et vous renvoie vers config set ou AGREELY_API_KEY. Il demande la clé et une URL de base optionnelle, vérifie la clé contre l'API en direct avant de la persister (il ne stocke jamais une clé qui ne fonctionne pas), puis la range dans le trousseau du système d'exploitation, avec repli vers le fichier de configuration 0600.

config set

Non interactif : il ne demande jamais rien et ne vérifie rien. Il exige au moins un drapeau parmi --api-key et --base-url, et stocke les identifiants.

agreely config set --api-key agr_live_xxx --base-url https://api.agreely.ca

Préséance d'authentification

La CLI ne demande jamais de clé. Ordre de résolution :

--api-key flag  (déconseillé, visible dans `ps`)
  > AGREELY_API_KEY env      (le chemin agent : une variable, aucune invite)
    > trousseau du SE (keytar, service « agreely »)
      > ${XDG_CONFIG_HOME|~/.config}/agreely/config.json   (mode 0600)

Une clé absente lève une erreur d'usage (sortie 2) : « No API key. Set AGREELY_API_KEY, run 'agreely login', or pass --api-key. » L'URL de base par défaut affichée est https://api.agreely.ca.

URL de base : --base-url > AGREELY_BASE_URL > configuration stockée > valeur par défaut du SDK. Si le module natif optionnel keytar est indisponible, login / config set se rabattent sur le fichier de configuration 0600.

La dégradation est omise (à dessein)

La CLI fonctionne en refus par défaut et mappe une panne vers le code de sortie 4. La politique de dégradation (ouverture en cas de panne / double barrière / bris de glace) est intentionnellement omise de la CLI v1 : c'est une préoccupation d'intégration application/SDK de longue durée (avec un collecteur d'audit et une fenêtre bornée), pas quelque chose qu'une invocation ponctuelle de CLI peut persister ou auditer sensément. Configurez-la là où le SDK est intégré.

Ensuite