GIMP
Un modelo maneja GIMP 3.0.4 a través del Hub para dibujar una pancarta en dos capas y exportarla como PNG. Esta página da el texto que se le dio, su script, las respuestas del Hub y la imagen, con el error que ocultó su primer script: se ejecutó sin ningún fallo y exportó un solo color uniforme.

En esta página
Iniciar GIMP con su puente
El puente se ejecuta dentro del Python de GIMP y nunca se inicia solo. Esta ejecución usó el comando de la guía para un GIMP iniciado sin su interfaz. La máquina no tenía pantalla, y GIMP se detiene sin una pantalla X incluso con --no-interface, así que xvfb-run -a le dio una virtual.
LIBREMODULAR_BRIDGE_PORT=9301 \
LIBREMODULAR_TOKEN_PATH=$HOME/.config/libremodular-mcp/token \
xvfb-run -a gimp-3.0 --new-instance --no-data --no-fonts --no-interface --batch-interpreter python-fu-eval \
-b "exec(open('/usr/share/libremodular-mcp/bridges/gimp_bridge.py', encoding='utf-8').read()); serve_main_thread()"
La ejecución en sí usó el puerto 9311, un directorio personal nuevo y un token nuevo. El puerto empezó a escuchar 5,5 s después del inicio. get_state sobre hub enumeró entonces GIMP como conectado, con la versión que informó GIMP:
{
"app": "hub",
"app_version": "0.1.0",
"bridge_version": null,
"captured_at": "2026-10-21T12:47:26.453Z",
"connected": true,
"content_origin": "hub",
"screenshot": null,
"state": {
"modules": [
{
"app_version": "3.0.4",
"bridge_version": "1",
"connected": true,
"mode": "tcp",
"name": "gimp"
}
]
},
"warnings": []
}Texto dado al modelo
Antes de escribir, el modelo llamó a get_state sobre gimp, como pide la guía para el modelo. GIMP no tenía ninguna imagen abierta: "images": [] y "active_image": null.
Script escrito
Un modelo, Claude Sonnet 5.5, escribió este script a partir de la guía y de ese estado vacío. No es un script tomado de las pruebas del proyecto.
import os
image = Gimp.Image.new(800, 450, Gimp.ImageBaseType.RGB)
sky = Gimp.Layer.new(image, "Sky", 800, 450, Gimp.ImageType.RGBA_IMAGE, 100.0, Gimp.LayerMode.NORMAL)
image.insert_layer(sky, None, 0)
Gimp.context_set_foreground(Gimp.color_parse_hex("#1c385c"))
Gimp.context_set_background(Gimp.color_parse_hex("#29a196"))
sky.edit_gradient_fill(Gimp.GradientType.LINEAR, 0.0, False, 1, 0.0, True, 0.0, 0.0, 0.0, 450.0)
sun = Gimp.Layer.new(image, "Sun", 800, 450, Gimp.ImageType.RGBA_IMAGE, 100.0, Gimp.LayerMode.NORMAL)
image.insert_layer(sun, None, 0)
sun.fill(Gimp.FillType.TRANSPARENT)
image.select_ellipse(Gimp.ChannelOps.REPLACE, 533 - 60, 150 - 60, 120, 120)
Gimp.context_set_foreground(Gimp.color_parse_hex("#f2b344"))
sun.fill(Gimp.FillType.FOREGROUND)
Gimp.Selection.none(image)
path = os.path.join(LMM_WORK_DIR, "banner.png")
Gimp.file_save(Gimp.RunMode.NONINTERACTIVE, image, Gio.File.new_for_path(path), None)
result = {"layers": [layer.get_name() for layer in image.get_layers()], "png_bytes": os.path.getsize(path)}Respuesta del Hub
{
"app": "gimp",
"duration_ms": 432,
"error": null,
"result": {
"layers": [
"Sun",
"Sky"
],
"png_bytes": 3594
},
"status": "ok",
"stderr": "",
"stdout": "",
"truncated": false
}status es ok, error es null y result es lo que fijó el script. Nada en la respuesta dice que la imagen esté mal. El modelo leyó el PNG que el script había exportado:

Lo que mostró la primera imagen
La imagen tiene en todas partes el color del círculo. Un segundo script, enviado a la imagen abierta, leyó dos cosas que la respuesta no daba: la selección tras select_ellipse y los colores de la capa Sky en su parte superior y en la inferior.
image = images()[0]
sun = [layer for layer in image.get_layers() if layer.get_name() == "Sun"][0]
sky = [layer for layer in image.get_layers() if layer.get_name() == "Sky"][0]
ok = image.select_ellipse(Gimp.ChannelOps.REPLACE, 473, 90, 120, 120)
print("select_ellipse returned", ok)
print("bounds", Gimp.Selection.bounds(image))
print("is_empty", Gimp.Selection.is_empty(image))
sky_top = sky.get_pixel(5, 5)
sky_bottom = sky.get_pixel(5, 444)
print("sky pixels", sky_top.get_rgba(), sky_bottom.get_rgba()){
"app": "gimp",
"duration_ms": 141,
"error": null,
"result": null,
"status": "ok",
"stderr": "",
"stdout": "select_ellipse returned True\nbounds (True, non_empty=True, x1=473, y1=90, x2=593, y2=210)\nis_empty False\nsky pixels (red=0.011612247675657272, green=0.04091521352529526, blue=0.10702310502529144, alpha=1.0) (red=0.022173885256052017, green=0.35153278708457947, blue=0.3005438446998596, alpha=1.0)\n",
"truncated": false
}La selección era correcta (x1=473, y1=90, x2=593, y2=210, de 120 por 120 píxeles) y el degradado también: el píxel superior de Sky es azul oscuro y el inferior verde azulado, en valores lineales. Así que la capa Sun se había rellenado entera.
Un tercer script midió la causa en una imagen nueva de 100 por 100, con la selección fijada. Rellenó una capa transparente con layer.fill y otra con layer.edit_fill, y luego leyó el alfa de un píxel del centro y de un píxel de la esquina:
image = Gimp.Image.new(100, 100, Gimp.ImageBaseType.RGB)
Gimp.context_set_foreground(Gimp.color_parse_hex("#f2b344"))
image.select_ellipse(Gimp.ChannelOps.REPLACE, 30, 30, 40, 40)
result = {}
for name, method in (("fill", "fill"), ("edit_fill", "edit_fill")):
layer = Gimp.Layer.new(image, name, 100, 100, Gimp.ImageType.RGBA_IMAGE, 100.0, Gimp.LayerMode.NORMAL)
image.insert_layer(layer, None, 0)
layer.fill(Gimp.FillType.TRANSPARENT)
getattr(layer, method)(Gimp.FillType.FOREGROUND)
result[name] = {"alpha_centre": layer.get_pixel(50, 50).get_rgba().alpha, "alpha_corner": layer.get_pixel(2, 2).get_rgba().alpha}
image.delete(){
"app": "gimp",
"duration_ms": 27,
"error": null,
"result": {
"edit_fill": {
"alpha_centre": 1.0,
"alpha_corner": 0.0
},
"fill": {
"alpha_centre": 1.0,
"alpha_corner": 1.0
}
},
"status": "ok",
"stderr": "",
"stdout": "",
"truncated": false
}layer.fill ignora la selección: su píxel de la esquina es opaco. layer.edit_fill la respeta: su píxel de la esquina es transparente.
El script corregido y su respuesta
Solo cambió lo que falló. El script vacía la capa Sun, la rellena de nuevo con edit_fill mientras la selección sigue fijada y vuelve a exportar la imagen sobre banner.png.
import os
image = images()[0]
sun = [layer for layer in image.get_layers() if layer.get_name() == "Sun"][0]
print("is_empty before", Gimp.Selection.is_empty(image))
sun.fill(Gimp.FillType.TRANSPARENT)
Gimp.context_set_foreground(Gimp.color_parse_hex("#f2b344"))
sun.edit_fill(Gimp.FillType.FOREGROUND)
Gimp.Selection.none(image)
inside = sun.get_pixel(533, 150).get_rgba()
outside = sun.get_pixel(5, 5).get_rgba()
path = os.path.join(LMM_WORK_DIR, "banner.png")
Gimp.file_save(Gimp.RunMode.NONINTERACTIVE, image, Gio.File.new_for_path(path), None)
result = {"sun_inside_alpha": inside.alpha, "sun_outside_alpha": outside.alpha, "png_bytes": os.path.getsize(path)}{
"app": "gimp",
"duration_ms": 2246,
"error": null,
"result": {
"png_bytes": 232912,
"sun_inside_alpha": 1.0,
"sun_outside_alpha": 0.0
},
"status": "ok",
"stderr": "",
"stdout": "is_empty before False\n",
"truncated": false
}El archivo exportado pesa 232 912 bytes, frente a 3594 de la imagen uniforme. La capa Sun es opaca en el centro del círculo (sun_inside_alpha) y transparente en el píxel (5, 5) (sun_outside_alpha).
La imagen producida
Este es el banner.png exportado, de 800 por 450 píxeles. Los píxeles de arriba y de abajo leen srgba(28,57,92,1) y srgba(41,160,149,1), a un nivel de #1c385c y #29a196: el degradado lleva tramado, así que una repetición difiere en un nivel en algunos píxeles.
