LibreOffice

Un modèle pilote LibreOffice 25.2.3.2 par le Hub pour construire dans Calc une feuille de ventes, avec des formules et un en-tête, sans aucun document ouvert, et l’enregistrer en fichier .ods. Le premier script a fonctionné. Cette page donne le texte, le script, les réponses du Hub et la capture.

La fenêtre de LibreOffice Writer avec une lettre d'une page, en français, du Hub à GIMP, Krita, Blender, Inkscape et LibreOffice, avec le logo et le titre LibreModular-MCP au-dessus du texte.
Une lettre du Hub aux cinq applications, mise en page dans Writer par un script, avec le logo au-dessus du titre. Lettre en PDF

Démarrer LibreOffice et son pont

LibreOffice et son pont ne démarrent jamais sur la parole du modèle : l’opérateur démarre les deux. Un seul lanceur le fait, d’après le guide, et rend la main aussitôt :

libremodular-launch libreoffice --bridge-file /usr/share/libremodular-mcp/bridges/uno_bridge.py

L’exécution n’avait pas d’écran, un serveur X virtuel en a tenu lieu, et elle a réglé dans config.toml les ports 2102 et 9314, et non les valeurs par défaut 2002 et 9304. Le lanceur a imprimé :

LibreOffice started (process 612876), listening for UNO on 127.0.0.1:2102
bridge started (process 612877), listening on 127.0.0.1:9314
a script runs on the current document of its target's family, or with doc set to None when none is open
stop the bridge with 'libremodular-launch libreoffice --stop'; LibreOffice is yours to close

LibreOffice s’est mis à l’écoute d’UNO 1,3 s après le lancement, et le Hub a été démarré ensuite. get_state sur hub a listé les trois cibles qui partagent le pont, libreoffice-writer, libreoffice-calc et libreoffice-impress, connectées, avec la version que LibreOffice déclarait :

Réponse du Hub
{
  "app": "hub",
  "app_version": "0.1.0",
  "bridge_version": null,
  "captured_at": "2026-10-21T12:48:38.198Z",
  "connected": true,
  "content_origin": "hub",
  "screenshot": null,
  "state": {
    "modules": [
      {
        "app_version": "25.2.3.2",
        "bridge_version": "1",
        "connected": true,
        "mode": "uno",
        "name": "libreoffice-writer"
      },
      {
        "app_version": "25.2.3.2",
        "bridge_version": "1",
        "connected": true,
        "mode": "uno",
        "name": "libreoffice-calc"
      },
      {
        "app_version": "25.2.3.2",
        "bridge_version": "1",
        "connected": true,
        "mode": "uno",
        "name": "libreoffice-impress"
      }
    ]
  },
  "warnings": []
}

Texte donné au modèle

Texte donné au modèle
In LibreOffice Calc, build a quarterly sales sheet named “Sales”. Rows: Tent (120, 150, 310, 90), Stove (80, 60, 140, 200), Lantern (45, 70, 65, 120), for the columns Q1 to Q4. Add a Total column and a Total row, computed with formulas. Header row in bold white on dark blue (#1c385c). Save it as sales.ods in your working directory.

Avant d’écrire, le modèle a appelé get_state sur libreoffice-calc. active valait null et documents était vide, avec un avertissement disant que LibreOffice n’avait aucun document Calc courant. Ce n’est pas une erreur : un script s’exécute alors avec doc à None et peut créer le premier document.

Script écrit

Un modèle, Claude Sonnet 5.5, a écrit ce script à partir du guide et de cet état. Ce n’est pas un script tiré des tests du projet. Il crée le document quand doc vaut None, et il enregistre avec storeToURL dans le répertoire de travail.

python · Script écrit
import os

import uno

if doc is None:
    doc = desktop.loadComponentFromURL("private:factory/scalc", "_blank", 0, ())
sheet = doc.Sheets.getByIndex(0)
sheet.Name = "Sales"

header = ["Product", "Q1", "Q2", "Q3", "Q4", "Total"]
data = [("Tent", 120, 150, 310, 90), ("Stove", 80, 60, 140, 200), ("Lantern", 45, 70, 65, 120)]
for column, title in enumerate(header):
    cell = sheet.getCellByPosition(column, 0)
    cell.setString(title)
    cell.CharWeight = 150.0
    cell.CharColor = 0xFFFFFF
    cell.CellBackColor = 0x1C385C
    if column > 0:
        cell.HoriJustify = uno.Enum("com.sun.star.table.CellHoriJustify", "RIGHT")
for row, (name, *values) in enumerate(data, start=1):
    sheet.getCellByPosition(0, row).setString(name)
    for column, value in enumerate(values, start=1):
        sheet.getCellByPosition(column, row).setValue(value)
    sheet.getCellByPosition(5, row).setFormula(f"=SUM(B{row + 1}:E{row + 1})")
last = len(data) + 1
sheet.getCellByPosition(0, last).setString("Total")
for column in range(1, 6):
    letter = "ABCDEF"[column]
    sheet.getCellByPosition(column, last).setFormula(f"=SUM({letter}2:{letter}{last})")
for column in range(0, 6):
    sheet.getCellByPosition(column, last).CharWeight = 150.0
    sheet.Columns.getByIndex(column).Width = 2600

path = os.path.join(LMM_WORK_DIR, "sales.ods")
properties = (uno.createUnoStruct("com.sun.star.beans.PropertyValue"),)
properties[0].Name = "FilterName"
properties[0].Value = "calc8"
doc.storeToURL(uno.systemPathToFileUrl(path), properties)
result = {"sheet": sheet.Name, "grand_total": sheet.getCellByPosition(5, last).getValue(), "ods_bytes": os.path.getsize(path)}

La réponse du Hub

Réponse du Hub
{
  "app": "libreoffice-calc",
  "duration_ms": 555,
  "error": null,
  "result": {
    "grand_total": 1450.0,
    "ods_bytes": 13472,
    "sheet": "Sales"
  },
  "status": "ok",
  "stderr": "",
  "stdout": "",
  "truncated": false
}

Le total général, calculé par la formule, est 1 450 : 670 pour la tente, 480 pour le réchaud et 300 pour la lanterne. Le fichier .ods que le script a enregistré fait 13 472 octets. get_state a ensuite décrit la feuille et renvoyé une capture :

Réponse du Hub
{
  "app": "libreoffice-calc",
  "app_version": "25.2.3.2",
  "bridge_version": "1",
  "captured_at": "2026-10-21T12:48:39.480Z",
  "connected": true,
  "content_origin": "document",
  "screenshot": "$XDG_RUNTIME_DIR/libremodular-mcp/captures/libreoffice-calc-20261021T124838664Z.png",
  "state": {
    "active": {
      "sheets": [
        {
          "last_column": 5,
          "last_row": 4,
          "name": "Sales"
        }
      ]
    },
    "documents": [
      {
        "family": "calc",
        "title": "Untitled 1"
      }
    ]
  },
  "warnings": []
}

L’image produite

La capture est une page exportée par le filtre PNG de LibreOffice, de 828 par 1 170 pixels, avec le tableau en haut et le reste blanc. Cette image garde ses 230 pixels du haut. L’en-tête est en blanc gras sur #1c385c, et la dernière ligne et la dernière colonne contiennent les totaux.

Un tableau intitulé Sales, avec l'en-tête Product, Q1 à Q4 et Total sur une bande bleu foncé, trois lignes de produits et une ligne Total en gras.
Le haut de la capture de la feuille Sales, telle que le Hub l'a exportée.
La fenêtre de LibreOffice Calc : un tableau des ventes avec Product, Q1 à Q4 et Total sur une bande bleu foncé, trois produits, et dessous un graphique en barres des quatre trimestres.
Le tableau des ventes de la recette et son graphique en barres, faits par le même script dans Calc.

Guide de référence dans le dépôt →

SuiteCompatibilité →