Gérer les workbooks avec git
Sortez le contenu d’un workbook sous forme de fichiers dans votre dépôt local, faites-le passer par les diffs, les revues et les PR de git, puis réécrivez-le tel quel. Les transforms écrits par l’agent du chat sortent au même endroit : le mode de travail « relire le SQL écrit par l’IA avant de l’entériner » fonctionne donc tel quel.
d2b pull --workbook WB # → transforms/ sheets/ charts/ + d2b.jsond2b pull --data customers # also track a small base table as data/customers.csv (export = branch)git add -A && git commit -m "pull from D2B"# ... edit transforms/*.sql|py, sheets/*.json, data/*.csvd2b push --dry-run # what would be sent (only files that changed)d2b push --commit "$(git rev-parse --short HEAD)" # apply the changes → pin the git sha as a named versiongit commit -am "d2b push" # push updates d2b.json (sync hashes) — commit it tooArborescence
Section intitulée « Arborescence »| Répertoire | Contenu | pull | push |
|---|---|---|---|
transforms/ | Transforms SQL / Python (hiérarchie possible, comme agg/monthly.sql) | ✅ | ✅ Seuls les fichiers modifiés sont réexécutés via POST /transforms (compté comme opérations de données). Dans l’ordre des dépendances (l’amont d’abord) |
sheets/ | Sheets d’affichage {"blocks": [...]} | ✅ | ✅ PUT /sheets/{name} |
charts/ | Config + recipe des charts (l’outil et les paramètres de leur génération) | ✅ | ❌ Lecture seule — un chart se régénère depuis sa recipe, et une config corrigée à la main ne peut pas être régénérée. Pour l’historique et la relecture |
data/ | CSV des tables base en opt-in via --data (avec __d2b_row_id) | ✅ export = branch | ✅ 3-way merge cellule par cellule côté serveur, sur la clé d’ID de ligne. Les cellules modifiées des deux côtés partent en queue de conflicts du workbook (la valeur D2B reste) |
--data ne s’applique qu’aux tables de base (dont les lignes sont leur propre source de vérité). La sortie structurée de mode=auto est dérivée (sortie d’un transform) et ne peut pas être branchée — pour faire transiter les lignes d’une table par git, ingérez-la en --mode staged, vérifiez le parse spec puis matérialisez (cela produit une table de base), ou créez-la via l’API de lignes.
d2b.json est le manifeste. L’entrée d’un transform est {name, artifact_name, args, layer, hash} (pour ajouter un transform, écrivez le fichier et cette entrée). hash est le digest de la dernière synchronisation (= la base de merge), maintenu par la CLI — committez le d2b.json d’après push.
Quand vous écrivez une nouvelle entrée à la main, omettez hash (l’omettre — ou null — revient au même) : le premier push l’attribue et le réécrit.
{ "workbook_id": "…", "transforms": { "agg/monthly.sql": { "name": "agg/monthly", "artifact_name": "product_sales", "args": {"src": "sales"}, "layer": null, "hash": "…" } }, "sheets": {"summary.json": {"name": "summary", "hash": "…"}}, "charts": {"trend.json": {"name": "trend", "readonly": true, "hash": "…"}}, "data": {"customers.csv": {"table": "customers", "branch_id": "…", "hash": "…"}}}Synchroniser un espace de travail entier
Section intitulée « Synchroniser un espace de travail entier »Des centaines ou des milliers de classeurs se gèrent comme un dépôt = un espace de travail. Le d2b.json racine (le registre) fixe l’espace de travail, et chaque classeur arrive dans workbooks/<titre>--<8 premiers caractères de son id>/ avec la disposition ci-dessus, son propre d2b.json compris.
d2b pull --workspace WS # première fois : écrit le registre et récupère tous les classeurs de l'espace (en parallèle, --jobs N)d2b pull # ensuite : depuis le registre ; un nouveau classeur apparaît de lui-mêmed2b pull --prune # supprime les répertoires des classeurs sortis de l'espace (la suppression apparaît dans le diff)d2b status --strict # exit 2 quand le registre, les répertoires et le serveur divergent (à rendre obligatoire en CI)d2b push --commit "$(git rev-parse --short HEAD)" # seuls les classeurs modifiés sont envoyés- L’appartenance est un fait du serveur :
pullliste tous les classeurs de l’espace et n’écrit jamais un répertoire que le registre ne connaît pas. Un autre espace de travail ne peut pas être récupéré dans le même dépôt — il n’y a pas d’option pour forcer. - Le répertoire d’un classeur est fixé à son premier pull ; un changement de titre ne le déplace pas. Son id figure dans le nom du répertoire et sur la première ligne de chaque fichier de transformation,
-- d2b ws=… wb=… transform=…(cette ligne n’est jamais envoyée au serveur et ne compte jamais comme une modification). pushn’envoie rien du tout s’il trouve un répertoire inconnu du registre, un fichier dont l’en-tête nomme un autre classeur, ou un manifeste lié à un autreworkbook_id.- Donnez à la CI un PAT épinglé à l’espace de travail (
workspace_idsurPOST /api/control/account/keys, ouresource: "workspace:<id>"surPOST /api/v1/me/tokens) : même mal configurée, elle reçoit un 403 du serveur pour tout autre espace. L’identifiant ded2b logincouvre tout le compte et n’est pas la bonne clé pour la CI. data/(les lignes) reste un opt-in par classeur (d2b pull --data TABLEdansworkbooks/<dir>/).
Un répertoire sans registre garde le comportement à un seul classeur décrit ci-dessus.
Règles de conflit
Section intitulée « Règles de conflit »- Le textuel (transforms / sheets / charts) n’est jamais écrasé en silence (le même contrat que le verrouillage optimiste de l’API de lignes) : si les deux côtés ont changé depuis la dernière synchronisation, pull comme push refusent et renvoient les fichiers concernés en JSON. Le sens de
--forcedépend de la commande : push--forceimpose votre côté local ; pull--forceadopte le côté serveur (vos éditions locales sont perdues) data/diffère : l’édition simultanée n’est pas refusée, le 3-way merge du serveur arbitrant cellule par cellule. Après le push, le CSV est repris depuis le résultat du merge et un nouveau branch est ouvert (les changements côté D2B redescendent aussi en local). Les tables dérivées ne se branchent pas (400). Limite de merge : 100 000 lignes — pour les petites tables type référentiel ou table de correspondance- Un fichier supprimé en local ne supprime rien côté serveur (simple signalement dans
deleted_locally).--prunesupprime les tables de sortie des transforms et les sheets (la suppression d’une table de sortie demandeworkbooks:delete). Pourdata/, seul le suivi est retiré — la table n’est pas supprimée - Les clés de
d2b.jsondoivent être des chemins relatifs normalisés à l’intérieur de leur propre répertoire de section : les chemins absolus,.., les lecteurs Windows et les chemins non normalisés sont refusés au pull comme au push. Un fichier synchronisé qui est un lien symbolique (transforms/*.sql|pyet consorts) est refusé lui aussi — rien n’est lu ni écrit à travers un lien (un lien vers un fichier que la section ne lit jamais est simplement ignoré)
Automatiser l’aller-retour avec GitHub Actions
Section intitulée « Automatiser l’aller-retour avec GitHub Actions »Ni GitHub App ni configuration d’intégration côté D2B : la CLI suffit à boucler l’aller-retour entre le dépôt et le workbook.
d2b github-workflow > .github/workflows/d2b.yml# Secrets: D2B_API_KEY (a workbooks:write PAT). Variables: D2B_BASE_URL- Merge dans
main(avec des fichiers suivis modifiés) →d2b push --commit <sha>→ commit automatique dud2b.jsonmis à jour - Planifié (par défaut toutes les heures) et manuel →
d2b pull→ PR s’il y a un diff (branched2b/pull). Ce que l’agent a changé côté workbook est ainsi relu par un humain puis mergé
Côté SDK, client.transforms.list(wb) / client.sheets.list(wb) / client.charts.list(wb) / client.export.branch(wb, [table]) / client.tables.merge(...) sont la même surface.