Ícono de la app Calíope

Cómo está hecha Calíope

La pila, las fronteras y lo que se dejó fuera a propósito.

Esta página cuenta cómo está armada Calíope: sobre qué frameworks se apoya, dónde está la frontera con el motor, qué permite el sandbox y qué es lo que la app no hace a propósito. Cada cifra sale del árbol de código, a octubre de 2026.

Una app, tres binarios

Calíope se distribuye como una sola app, org.caliope.caliope, con compra universal: una compra cubre todos los dispositivos Apple en los que corre. Detrás de esa sola app hay tres binarios: uno para macOS, uno para iPhone, iPad y Apple Vision Pro (el mismo target, compilado para iOS y para visionOS — la app de Apple Watch viaja embebida en la compilación de iOS) y uno para Apple TV.

La mayor parte del código no se escribe tres veces. Sources/Shared tiene 367 archivos Swift y 277 vistas SwiftUI que compilan tanto en el binario del Mac como en el de iOS. Los widgets son extensiones de la misma app y leen lo que la app publica a través de un App Group; la app escribe, el widget sólo lee.

SwiftUI de punta a punta

Cada ventana es SwiftUI nativo — sin web views, sin emulación multiplataforma. Esa frase está en la portada y es literalmente cierta: las 277 vistas compartidas son SwiftUI, y donde un Mac y un iPad necesitan comportarse distinto, la diferencia se decide al compilar, dentro del mismo archivo, no en una capa de compatibilidad en tiempo de ejecución.

La frontera con el motor

Todo lo que habla con un motor de base de datos vive detrás de 16 protocolos en Sources/Models/Database/Protocols/, agrupados por sub-área. Cada motor tiene una implementación de esos protocolos, y nada por encima de la frontera — ni una vista, ni el orquestador — escribe SQL ni conoce los códigos de error de un motor. Sumar un motor es implementar los protocolos, no editar a quien los llama.

Calíope habla con nueve motores, y el código que habla con ellos sale de dos sitios.

  • Siete hablan por un cliente que escribí yo. Seis, a partir del protocolo que publica su fabricante: MySQL y MariaDB por su protocolo cliente/servidor, PostgreSQL por su protocolo frontend/backend, SQL Server por TDS, MongoDB por OP_MSG y BSON, ClickHouse por su interfaz HTTP o HTTPS, y Redis por RESP2 y RESP3. Oracle Database habla TNS: Oracle no publica su especificación, así que escribo a partir del código que declara como referencia pública, su propio driver de Python. Cada uno implementa lo que Calíope usa y nada más; las siete capas de protocolo caben en unas 18.700 líneas.
  • Dos no tienen driver: el motor mismo corre dentro de la app. SQLite es la biblioteca que trae el sistema, y el editor SQLite habla con ella por su API en C. DuckDB 1.5.5 es el código de DuckDB Labs, compilado dentro de la app como paquete local con una capa fina de Swift sobre su API en C, y no descarga ninguna extensión — las que vienen dentro son todas las que hay.

El código de red corre sobre SwiftNIO 2.101.0, la biblioteca de red de código abierto de Apple; la excepción es el cliente de ClickHouse, escrito sobre URLSession (versiones a octubre de 2026). Las consultas con parámetros usan la sentencia preparada del protocolo de cada motor —COM_STMT_PREPARE y COM_STMT_EXECUTE en MySQL y MariaDB, el protocolo extendido (Parse, Bind, Execute) en PostgreSQL, sp_executesql por RPC en SQL Server, binds :1 en la llamada de ejecución en Oracle Database, parámetros tipados {nombre:Tipo} en ClickHouse—: los valores viajan por el protocolo de red y nunca se concatenan en el SQL. Lo que escribes en el editor se envía tal como lo escribiste.

El cliente de MongoDB cubre OP_MSG, BSON, SCRAM-SHA-1 y SCRAM-SHA-256, y la resolución SRV de mongodb+srv:// con el DNS del sistema, y admite MongoDB 4.0 o posterior. MongoDB queda fuera de la frontera con el motor a propósito: ninguna vista toca esa capa de protocolo, y toda la capa de MongoDB está diseñada para poder quitarse sin tocar el lado relacional. Redis también vive fuera, con su propia sesión.

Por qué escribo los drivers

Una app de bases de datos cuyos drivers escribe todos una sola persona merece una pregunta: ¿quién los mantiene funcionando cuando un servidor cambia? Ésta es mi respuesta, y lo que puedes comprobar.

No es nuevo para mí. Antes de Calíope escribí gb.mysql, el componente de MySQL del lenguaje Gambas; Gambas lo incorporó en 2008 y lo sigue incluyendo con mi nombre. La propia Calíope nació ese mismo año como una app en Qt que hablaba con MySQL y MariaDB llamando directamente a su API en C, a través de una capa de base de datos que escribí yo; su código está en la página de versiones anteriores en Qt. De todo este oficio, la capa entre una aplicación y su base de datos es lo que llevo más tiempo haciendo.

Escribo uno cuando depender de otro es el riesgo mayor. Calíope usó un driver de MongoDB de la comunidad hasta la versión 1.5 incluida. Cuando su desarrollo se detuvo, lo sustituí por uno mío.

Un driver estrecho es un driver pequeño. Un driver de propósito general tiene que servir a todas las aplicaciones; los míos sirven a una. Por eso son lo bastante pequeños como para que sepa qué hace cada parte.

Ser dueño del cliente es ser dueño de su comportamiento. Todos los motores de servidor tienen la misma política de TLS, verificada por omisión, y el mismo túnel SSH, y sus errores se clasifican igual: una respuesta del servidor nunca se confunde con una conexión perdida.

Se prueban contra servidores reales, en contenedores: MySQL 8.0, 8.4 y 26.7, MariaDB en su última versión (la que haya cuando actualizo los servidores de prueba), PostgreSQL 17 con PostGIS 3.5, SQL Server 2025, Oracle Database 26ai Free, ClickHouse 26.8, MongoDB 8.2 y Redis 7.4.

En MySQL y MariaDB, al driver de la comunidad le faltaban cosas que se notan. Un CALL a un procedimiento que devuelve filas fallaba con el error 1312, porque el driver nunca le pedía al servidor varios resultados; LOAD DATA LOCAL INFILE fallaba con un mensaje que apuntaba a la configuración del servidor; cada sentencia sin filas costaba un segundo viaje sólo para saber cuántas filas había cambiado; los avisos que devuelve el servidor —un texto truncado a 255 caracteres, un valor fuera de rango ajustado— no te llegaban nunca; y de las cinco maneras en que una cuenta puede autenticarse hablaba dos, así que una cuenta con ed25519, PARSEC, PAM, LDAP o IAM de Aurora no entraba, con un error que parecía de contraseña. Mi cliente pide todo eso desde el primer paquete: un CALL enseña sus filas, LOAD DATA LOCAL INFILE abre un selector y envía sólo el archivo que eliges, las filas afectadas y los avisos llegan en la propia respuesta del servidor, y habla las cinco, la contraseña en claro sólo por TLS.

En PostgreSQL el driver estaba bien cuidado, y aun así lo sustituí. Cada consulta del editor empezaba con una sonda para saber el id del proceso del servidor, y detener una era pg_cancel_backend desde otra conexión, que exige ser el mismo usuario o superusuario; si una transacción seguía abierta se averiguaba preguntándole la hora al servidor dos veces. Los resultados llegaban en binario, cableado en el driver, y hacía falta una tabla de tipos mía para leerlos, con los intervalos, los arrays, los rangos, los tipos compuestos y los dominios sin cubrir. Y lo que yo usaba de él era su API vieja, marcada como obsoleta en 75 sitios. El protocolo entrega todo eso gratis: el id del proceso y una clave de cancelación al conectar, el estado de la transacción en cada respuesta, y cada valor como el mismo texto que enseña psql. Mi cliente se autentica con SCRAM-SHA-256, md5 o la contraseña en claro, ésta sólo por TLS; Kerberos y SSPI, no. postgres-nio tiene desarrollo activo, y ése nunca fue el problema: lo que gané al escribir el mío es control, no menos mantenimiento.

De qué se escribió cada uno de los siete clientes, qué habla y qué deja fuera está en la página de los drivers.

SSH sin ssh

El túnel SSH está escrito dentro de la app, sobre SwiftNIO SSH 0.15.0 y swift-crypto 4.5.2. Sin depender del ssh del sistema. Las claves descifradas nunca se escriben en disco.

Calíope lee por sí misma las claves privadas OpenSSH, acepta ed25519 y ECDSA P-256, P-384 y P-521, verifica la clave del host antes de enviar nada más y abre un canal direct-tcpip hasta la base de datos. De tu lado escucha en 127.0.0.1, en un puerto elegido al abrir el túnel, y la conexión a la base pasa por ese proxy local. Una clave protegida con frase de paso se descifra en memoria, y ahí se queda.

TLS se verifica por omisión. Activar SSL significa que el certificado del servidor se comprueba contra las raíces de confianza del sistema; una CA que aportes pasa a verificar sólo el certificado (sin comprobar el nombre del host); y saltarse la verificación es una opción explícita, con un aviso visible. La misma política (TLSBuilder) se aplica a MySQL, MariaDB, PostgreSQL, SQL Server, Oracle Database, MongoDB y Redis. ClickHouse, por HTTPS, ofrece las mismas tres opciones con su propia política sobre URLSession, y con una CA tuya sigue comprobando el nombre contra el host del perfil, también por un túnel SSH. Oracle Database, por TCPS, sigue la misma política, salvo que con una CA tuya sigue comprobando el nombre contra el host del perfil, incluso por un túnel SSH; y un perfil con certificado de cliente se rechaza.

Dentro del sandbox

Calíope corre con el App Sandbox activo, como exige la Mac App Store, y Caliope.entitlements pide exactamente esto:

  • com.apple.security.app-sandbox
  • com.apple.security.network.client — las conexiones salientes a tus servidores
  • com.apple.security.network.server — los escuchas locales: el proxy del túnel y el servidor MCP
  • com.apple.security.device.audio-input — el dictado, que corre en el dispositivo
  • com.apple.security.files.user-selected.read-write — los archivos .sql que abres y las carpetas que eliges para los respaldos
  • com.apple.security.application-groups — el contenedor compartido del que leen los widgets
  • com.apple.security.print — imprimir el editor SQL, el registro de consultas, un diagrama ER o un reporte
  • iCloud: CloudDocuments, CloudKit y el almacén clave-valor, para la sincronización que puedes activar

Y lo que no está: ni acceso a todo el sistema de archivos, ni excepción a la validación de bibliotecas, ni JIT.

IA y MCP en tus términos

El asistente de IA tiene tres clases de proveedor. Apple Intelligence corre a través del framework FoundationModels de Apple, en el dispositivo, sin clave y sin cuenta. Ollama corre en tu propia máquina, contra los modelos que instalaste ahí. Los proveedores en la nube funcionan con tu propia clave. La app decide cómo hablar con un proveedor por las capacidades que declara, no por su nombre. Es un asistente, no un agente.

El servidor MCP, en macOS, le da la vuelta y deja que un cliente de IA externo — Cursor, Claude Desktop, cualquiera que hable MCP — le pregunte a Calíope por tus bases de datos, dentro de unos límites. Viene apagado. Escucha sólo en 127.0.0.1, en el puerto 41500 salvo que lo cambies, y esa dirección no se puede configurar. Cada petición necesita un token Bearer que Calíope genera (32 bytes aleatorios) y guarda en el Llavero. El cliente ve sólo las conexiones que expones explícitamente, y esa lista nace vacía. Sólo acepta lecturas: una sentencia por llamada, revisada por una lista blanca de sentencias —no una transacción de sólo lectura— que también rechaza FOR UPDATE y LOCK IN SHARE MODE, y los resultados se cortan a 200 filas; la página del MCP recomienda exponer un usuario de base de datos sin privilegios de escritura. Cinco herramientas, JSON-RPC 2.0 sobre una sola ruta POST /mcp, versión de protocolo 2025-06-18. Ninguno de los dos consentimientos — ni el interruptor ni la lista expuesta — se sincroniza a otro dispositivo ni viaja en un respaldo de ajustes. La página del servidor MCP lista las cinco herramientas y explica cómo apuntarle un cliente.

Lo que a propósito no hace

  • Ni Windows ni Linux. Calíope está escrita para las plataformas de Apple y ahí se queda.
  • Ni Oracle Database más allá de un cliente: AWR, SQL Trace y la edición de PL/SQL se quedan fuera, y sólo está medido en 26ai. Los motores de hoy son MySQL, MariaDB, Amazon Aurora, PostgreSQL, SQL Server, Oracle Database, ClickHouse, Redis, SQLite, DuckDB y MongoDB.
  • Ni espacios de equipo. No hay servidor mío ni cuentas; tus perfiles y tu historial son tuyos, y la sincronización por iCloud es una opción entre tus propios dispositivos.

Si necesitas alguna de esas tres cosas, la comparación honesta nombra las herramientas que lo hacen bien.