SDK
Publica una app del Desk en la tienda de apps, paso a paso
Cómo publicar una app en miblo.ai/apps: qué contiene un envío, el paquete de código, el análisis automático, la revisión de IA, la aprobación humana y una lista final.
El proceso de un vistazo
Publicar pone tu app en la tienda de apps del Desk, miblo.ai/apps, para que cualquiera la instale en su Miblo con un clic. El camino:
La construyes y la pruebas en tu computador: en el simulador (
miblo apps dev) y en tu Miblo real, con el botón Enviar al Miblo (Desarrollar).La empaquetas:
miblo apps pack my-appcorre las verificaciones de la tienda de apps del Desk y escribemy-app.zip(y, con una carpetastore/,my-app-store.zipcon los textos, el ícono y las capturas también).La envías en miblo.ai/es/apps/enviar, partiendo del paquete: suelta el zip en el primer paso y su
manifest.jsoncompleta el resto (nombre, promesa, ajustes, direcciones, alertas y la tarjeta de vista previa). Luego el listado (ícono, descripción, capturas), los ajustes (leídos del manifest) y una revisión de todo.El análisis automático verifica el paquete de inmediato y rechaza lo que no puede pasar, con el motivo.
La revisión de IA escribe un informe de seguridad sobre el código (unos minutos después).
Una persona la aprueba, en un plazo de 7 días. Ninguna app entra a la tienda de apps del Desk sin la aprobación de un humano; recibes la respuesta por correo.
miblo.ai firma el paquete con la clave de apps, y quien lo instala verifica la firma antes de que corra nada.
Toda app es un programa
Toda app de la comunidad lleva un paquete de código: un manifest.json y un main.js que Miblo ejecuta en el sandbox en el computador de quien la instala. Un envío sin código se rechaza (code_required). Una app puede dibujar una pantalla (una página web, la forma estándar desde Miblo 1.45, o las tarjetas obsoletas, efectos, animaciones y gráficos en vivo) o, con "screen": false en su manifest, solo enviar alertas.
Qué contiene un envío
| Campo | Reglas |
|---|---|
| Código | Obligatorio: el zip del paquete de código, de hasta 2 MB (miblo apps pack lo verifica y lo escribe). Su id debe ser igual a la dirección. |
Dirección (id) | De 3 a 32 caracteres: letras minúsculas, dígitos y guiones simples, empezando con una letra (sin --, sin - al final). Ninguna de enviar, submit, editar, edit, novo, new, admin, api, apps, app, miblo, miblo-plus, plus, studio, package, media, busca, search, categoria, category, en, pt, oficial, official, denunciar, report, meus, mine; nunca empezando con miblo-; nunca conteniendo 13; nunca el id de una app oficial. Es la dirección de la app y su nombre de instalación, y nunca cambia. miblo apps check todavía no aplica estas reglas: verifica tú mismo tu id contra ellas. |
| Nombre | De 2 a 40 caracteres, limpiado como las cadenas de una tarjeta. |
| Categoría | Mi computador, Dinero, El día a día, Noticias y dev, Deporte, Delicias u Otros. |
| Versión | 1.0 o 1.0.0 (dos o tres números de hasta 3 dígitos cada uno). Cada revisión necesita una versión mayor que la publicada: la misma (version_unchanged) o una menor (version_older) se rechaza. |
| Promesa | Una frase, de hasta 120 caracteres, en portugués y/o inglés: lo que la app hace por quien la instala. Obligatoria (viene del manifest). |
| Descripción | Hasta 2000 caracteres cada una en portugués y en inglés, Markdown simple; al menos una de las dos. |
| Instrucciones | Opcional: cómo usarla y configurarla, hasta 2000 caracteres. |
| Qué cambió | Hasta 500 caracteres: se convierte en el changelog de la versión en la página de la app. |
| Enlace del programa | Opcional: una dirección https:// (sin usuario ni contraseña), de hasta 300 caracteres. |
| Usa Miblo+ | Opcional: márcalo cuando la app use funciones de Miblo+. |
| Ícono | Obligatorio: un PNG cuadrado de hasta 64 KB, rehecho a 128 × 128. Cualquier editor exporta uno (dibújalo a 128 × 128 y expórtalo como PNG), o pon icon.png en la carpeta store/ de la app (Desarrollar). |
| Capturas de pantalla | Opcionales: hasta 6 PNG de hasta 512 KB, rehechos a hasta 960 px (uno rehecho de más de 1,5 MB se rechaza por demasiado detallado). El botón Hacer capturas de pantalla del simulador (o miblo apps shots) hace las de 960 px. |
| Vista previa de la tienda | Opcional: la tarjeta con datos de ejemplo que se muestra en la página de la app y en Miblo Hub antes de instalar. Por defecto el preview del manifest; puedes editarla en el paso de revisión, y agregar hasta 4 PNG de fondo (espacios 0 a 3, hasta 1 MB cada uno, convertidos al formato de Miblo: hasta 60000 bytes de imagen). Verificada con las reglas del Miblo Desk (La pantalla de apps). Sin una, la tienda de apps del Desk muestra el ícono y las capturas. Una app solo de alertas no tiene. |
| Ajustes | Leídos del settings del manifest (Apps); ninguno puede pedir una clave, un token o una contraseña. Para cambiarlos, edita el manifest y empaqueta de nuevo. |
El servidor decodifica y rehace cada imagen; nada del archivo original se sirve jamás. Hasta 10 envíos al día por miembro, y un envío en revisión a la vez por miembro: mientras una de tus apps espera revisión, una app nueva o una revisión de otra app se rechaza (pending_exists); una revisión nueva de la misma app reemplaza a la pendiente. Cuando envías, el equipo de Miblo recibe un aviso por correo.
El paquete de código
Un zip de Node.js plano, ESM, sin dependencias:
my-app.zip
├── manifest.json
├── main.js
├── lib/source.js (optional: other .js and .json files)
└── README.md (optional)manifest.json
{
"id": "tide",
"api": 5,
"name": { "pt": "Maré", "en": "Tide" },
"category": "daily",
"promise": { "pt": "A maré está subindo?", "en": "Is the tide coming in?" },
"settings": [
{ "key": "port", "label": { "pt": "Porto", "en": "Port" }, "type": "text", "required": true, "max": 40, "placeholder": { "pt": "Santos", "en": "Brighton" } }
],
"network": ["api.example-tides.com"],
"intervalMs": 600000,
"preview": { "v": 1, "title": "Tide", "items": [{ "t": "big", "value": "1.2 m", "label": "rising" }] }
}| Campo | Reglas |
|---|---|
id | La dirección del envío (las reglas de arriba). |
api | La API de apps para la que se escribió la app: obligatorio, hoy 5 (Compatibilidad). Una app del Desk de página necesita 4 o más nuevo y debería decir 5 (Miblo 1.46). |
name | Una cadena o { pt, en }, cortada a 40 caracteres. |
promise | Obligatorio: una cadena o { pt, en }, una línea, cortada a 120 caracteres. |
category | Una de computer, money, daily, news-dev, sport, delights, other (otro valor se descarta). La categoría de la tienda de apps del Desk es la que eliges en el formulario. |
preview | Una tarjeta con datos de ejemplo (La pantalla de apps), sin marco de fondo: lo que Miblo Hub muestra antes de que la app dibuje por primera vez, y la vista previa de la tienda de apps del Desk por defecto. No en una app solo de alertas. |
settings | El esquema de ajustes: lo que la persona completa en Miblo Hub. |
network | Los únicos hosts a los que la app puede llegar: nombres públicos (sin IP, puerto ni nombres locales como .local), hasta 16. [] significa sin conexión. Debe ser igual a los hosts escritos en el código: un host usado pero no declarado se rechaza, y también uno declarado pero nunca usado. |
intervalMs | Cada cuánto se llama a run(): de 10 s (10000) a 24 h (86400000). |
display, page | "display": "page" y "page": "page.html" hacen una app del Desk de página: un archivo .html del paquete, api 4 o más nuevo, nunca solo de alertas. |
render | Opcional (Miblo 1.46), solo apps del Desk de página: auto (por defecto), realtime, buffer1, buffer2 o buffer3, cuánto por adelantado se dibuja la página. Cualquier otro valor, o en una app del Desk que no es de página, se rechaza. |
screen | false para una app solo de alertas (sin tarjeta, sin marcos, sin vista previa), con sus tipos de alerta en alerts. |
alerts | De qué tratan sus alertas, o sus tipos de alerta (Alertas). |
sessions | true para leer las sesiones de IA de la persona, una vez que lo permite (necesita Miblo 1.34) (Apps). |
La tienda de apps del Desk reescribe manifest.json a partir de estos campos cuando reempaqueta el código: cualquier otro campo (dwell, previews...) se descarta.
main.js
// main.js: called every intervalMs, or right after the person changes a setting.
export async function run(ctx) {
const t = ctx.lang === 'pt' ? { up: 'subindo', down: 'descendo', at: 'às' } : { up: 'rising', down: 'falling', at: 'at' };
const port = encodeURIComponent(ctx.settings.port);
try {
const data = await ctx.fetchJson(`https://api.example-tides.com/v1/port/${port}`);
ctx.state.last = { now: data.now, rising: data.next > data.now, at: ctx.now() }; // in memory only
} catch (e) {
// Nothing yet: a waiting card (the log says "offline: <error>"). Else the last reading stays.
if (!ctx.state.last) return { card: { title: 'Tide', items: [{ t: 'text', value: '...', color: 'grey' }] }, offline: true, error: e.message };
ctx.log(`kept the last reading (${e.message})`);
}
const { now, rising, at } = ctx.state.last;
return {
card: { title: 'Tide', items: [
{ t: 'big', value: `${now.toFixed(1)} m`, label: rising ? t.up : t.down, color: 'blue' },
{ t: 'text', value: `${t.at} ${ctx.timeOf(at)}`, color: 'grey' },
] },
peek: false, // true asks for a peek (once every 10 min)
};
}run(ctx) recibe:
ctx. | Qué es |
|---|---|
settings | Los valores de los ajustes (los valores por defecto más lo que la persona guardó). |
state | Un objeto que se conserva de una llamada a la siguiente, solo en memoria: vuelve a empezar vacío cuando la app se reinicia (Miblo se reinicia, una actualización). Guarda lo que deba durar con cached o en stateDir. |
lang, locale | pt o en, y la configuración regional correspondiente (pt-BR, en-US) para fechas y números. |
now(), timeOf(ms), sleep(ms) | La hora (epoch ms), una hora formateada (14:05) y una pausa. |
fetchJson(url, options), fetchText(url, options) | Consultan solo por https:// y solo desde los hosts de network: 10 s, 256 KB por respuesta, 30 llamadas por minuto, como máximo 3 redirecciones restringidas a los mismos hosts. |
cached(key, ttlMs, fetcher) | Lo que devolvió fetcher(), guardado como JSON en la carpeta de estado de la app durante ttlMs; se consulta de nuevo después, y se usa una copia más vieja cuando la consulta falla. Claves: letras, dígitos, ., _, - (hasta 100). |
stateDir | La carpeta donde la app puede escribir archivos (path.join(ctx.stateDir, 'x.json')). |
log(text) | Una línea en el log de la app (miblo apps logs <id>, el log de la app del simulador). |
alerts | Las alertas de la app: send, trySend, test, status (Alertas). |
app | El cliente de la app: app.settings, app.connect (cuentas), app.alert. |
screen, frame(slot, image) | La pantalla de la app (screen.push(id, value) alimenta un gráfico en vivo; screen.animate, screen.mesh...) y una subida de su propio marco de fondo (el desgaste de la flash lo presupuestas tú). Nada en una app solo de alertas. |
sessions() | Las sesiones de IA de la persona (un arreglo), para una app cuyo manifest dice "sessions": true, una vez que la persona lo permite (Miblo 1.34; se rechaza con sessions_off o not_declared). |
cpuTimes(), topProcess() | Siempre null para una app de la comunidad: los procesos del computador están fuera de su alcance (solo las apps oficiales los leen). |
fetchJson(url, options) y fetchText(url, options) aceptan { timeoutMs, headers }. headers solo puede llevar authorization (el token de acceso de ctx.app.connect.token(): Bearer ...), accept, accept-language, if-none-match e if-modified-since; cualquier otro se rechaza por su nombre. Los hosts siguen siendo los de network.
Una respuesta que no es 2xx se convierte en un error HTTP 403 from www.googleapis.com/youtube/v3/channels?part=statistics&key=***: <un extracto de la respuesta>, con status y host: el extracto tiene como máximo 300 caracteres, sin claves, tokens ni contraseñas (de la dirección y del cuerpo). Cuando run() falla, el log dice por qué (el campo de una tarjeta rechazada, el archivo y la línea de una excepción), y la app lo intenta de nuevo en 5 s, luego 10, 20, 40..., nunca más allá de su propio intervalMs; tras un 4xx (una clave incorrecta, una cuota agotada), al menos 10 s. Una ejecución buena reinicia la cuenta.
Y devuelve:
| Campo | Qué hace |
|---|---|
data | Para una app del Desk de página (Miblo 1.45): JSON de hasta 16 KB, que Miblo le entrega a la página en vez de una tarjeta. |
card | (Obsoleto desde Miblo 1.45.) La tarjeta que se dibuja (sin v, Miblo lo agrega). Cada cadena se hace dibujable con las fuentes de Miblo. |
peek | true (10 s) o un número de 1 a 10: pide traer la pantalla al frente (una vez cada 10 minutos). |
frame, plain | frame: { slot, image: { rgba, w, h } } escribe un fondo antes de la tarjeta (dibuja la tarjeta encima con bg: 'frame:N'); plain es la tarjeta que se usa si se rechaza el marco. |
alerts | Una lista de alertas ({ type, title, text?, key? }), como máximo 3 por ejecución; cada una se envía como ctx.alerts.trySend() (un rechazo va al log). |
offline, error | offline: true escribe offline: <error> en el log (la tarjeta que devuelves igual se dibuja). |
declare | Una nueva lista de ajustes, declarada de nuevo (una lista que depende de lo que la app consultó). |
Miblo declara la app, lee los ajustes, pide los que faltan y dibuja la tarjeta por ti. Un error lanzado desde run() va al log y la app se vuelve a llamar 5 s después.
Las apps oficiales también pueden exportar options, suggest y migrate desde main.js (listas consultadas una vez por semana, autocompletado, traslado de ajustes antiguos); al main.js de una app de la comunidad solo se le llama run(ctx).
Reglas de archivos
Solo
.js,.jsony.md; como máximo 64 archivos, 8 carpetas de profundidad, nombres de hasta 64 letras, dígitos,.,_o-, sin dos nombres que difieran solo en mayúsculas.Sin
package.jsony sinnode_modules: el runtime de Miblo es el único "paquete".El zip hasta 2 MB, todo descomprimido hasta 2 MB, cada script hasta 512 KB.
main.jsdebe exportarrun(ctx).
Apps del Desk de página
La tienda de apps del Desk acepta apps del Desk de página (API de apps 4 o 5, Apps del Desk de página). Necesitan un Miblo Desk con firmware 1.45 o más nuevo: en uno más antiguo, la tienda de apps del Desk y miblo apps dicen que necesita una actualización de firmware.
El paquete también contiene la página y lo que carga, a su lado: CSS, imágenes (PNG, JPEG, GIF, WebP, SVG), fuentes (WOFF, WOFF2, TTF, OTF), scripts y videos (WebM, MP4).
Cada uno de esos archivos pesa como máximo 256 KB; un video puede ser tan grande como lo permita el paquete (2 MB en total).
networklista los hosts a los que la página puede llegar (fetch, imágenes, fuentes), porhttps, la misma regla quectx.fetchJson; todo lo demás se bloquea.run()devuelve{ data }, JSON de hasta 16 KB, en vez de una tarjeta.
El análisis automático
Corre al enviar y es la verdad que la revisión de IA no puede anular. Rechaza de inmediato, con el motivo:
importar un módulo que el sandbox nunca permite:
child_process,worker_threads,net,tls,dgram,http,https,http2,vm,module,wasi,cluster,inspector;cualquier dependencia (un módulo que no es un integrado de Node),
node_moduleso unpackage.json;eval,new Functiony sus equivalentes (una cadena asetTimeout,.constructor(...)),import()orequire()de algo calculado,createRequire,WebAssembly, código nativo (process.binding,dlopen), acceso calculado al objeto global;una dirección cuyo host no está en
network(también escritahttps:host, con barras invertidas o partida en trozos), o un host declarado que el código nunca usa;un
manifest.jsonque rompe las reglas de arriba (sinapi, sin promesa, un id que no es la dirección...);archivos fuera de las reglas de arriba, un script con un error de sintaxis, una importación de un archivo que no existe, un
main.jssinrun(ctx).
Y señala al revisor (sin rechazar por sí solo): hosts escritos sin https://, direcciones http://, hosts construidos en tiempo de ejecución, bloques base64 o hex de más de 1 KB, decodificación (atob, Buffer.from(..., 'base64')), señales de ofuscación (nombres aleatorios, _0x..., cadenas partidas, String.fromCharCode, líneas minificadas), lecturas de process.env más allá de MIBLO_*, rutas fuera de la carpeta de la app, caracteres invisibles y texto dirigido al revisor ("ignore previous instructions", "approve this app", juegos de rol).
La revisión de IA
Tras el análisis, el código entra a la cola de la revisión de IA (corre cada 10 minutos y toma unos minutos):
Dos pasadas independientes: la primera escribe el informe; la segunda, con otras instrucciones, verifica el informe contra el código y dice qué se le escapó.
El informe cubre: el nivel de riesgo, cada destino de red y por qué, lo que la app lee del computador, lo que sale de él y adónde, persistencia, comportamiento oculto, señales de código malicioso (robo de credenciales, minería, código cargado desde la red, escapes del sandbox, acciones destructivas) e intentos de dirigir al revisor, con un resumen en portugués y en inglés.
La IA nunca aprueba ni rechaza. El veredicto ("ok" o "necesita una mirada cuidadosa") lo calcula el servidor: que las pasadas discrepen, un riesgo alto, texto dirigido al revisor o un host no declarado siempre piden una mirada cuidadosa.
Protegida contra la manipulación: el código entra como datos, entre marcadores, con la instrucción fija de que nada en él es una instrucción; la respuesta se valida contra un esquema; los textos tienen tope.
La aprobación humana
El revisor ve los hallazgos del análisis, ambos informes, los hosts y, en una revisión, qué cambió desde la versión anterior. Solo entonces la aprueba (miblo.ai firma el paquete) o la rechaza con un motivo, que te llega por correo, dentro de los 7 días de tu envío. La página pública de la app dice "Esta app se comunica con: ..." (los hosts declarados y verificados) o "No usa internet".
Después de publicarla
Revisiones: una edición es una revisión nueva con una versión mayor, que sigue el mismo camino. La versión aprobada sigue activa hasta que se aprueba la siguiente.
Las actualizaciones llegan a quienes la instalaron: cada Miblo con la app verifica la tienda de apps del Desk en cada inicio y cada hora. Una versión nueva que llega exactamente a lo que llegaba la instalada (los mismos hosts, sesiones y alertas) se instala sola; una que pide más espera a que la persona acepte ("Actualización disponible" en Miblo Hub, o
miblo apps install <id>en su terminal).Valoraciones: los miembros dan de 1 a 5 estrellas; el promedio y la cuenta aparecen en la tienda de apps del Desk.
Reportes: un reporte oculta la app de inmediato hasta que alguien la revisa (cada miembro oculta como máximo dos apps al día). Los moderadores pueden ocultar y restaurar.
Una app oculta, para quienes la instalaron: en cada inicio y cada hora, Miblo verifica el paquete; una app que la tienda de apps del Desk ya no sirve o que ocultó se apaga.
Cómo llega el paquete a sus usuarios
La página de la app tiene el botón Instalar en Miblo Hub, que abre Miblo Hub en tu app (miblo://apps/install/<id>), y el comando miblo apps install <id> para una terminal. Entonces Miblo:
descarga
miblo.ai/apps/<id>/package.jsony verifica su firma con la clave de apps (separada de la clave de las actualizaciones), rechazando un paquete sin una válida;descarga el zip por https (sin redirecciones, nunca más de 2 MB) y verifica su SHA-256 firmado;
verifica cada archivo, lo descomprime en
<datos de Miblo>/apps/<id>/pkgy enciende la app en el sandbox.
Prueba en tu computador antes de enviar
Ejecuta la app exactamente como la ejecutará Miblo, en el mismo sandbox, con el simulador:
miblo apps dev my-app # the app in the sandbox, its screen, settings, alerts and log in your browser
miblo apps check my-app # the store's scan, offline
miblo apps pack my-app # the zip the store takesEl simulador ejecuta
main.jscomo se ejecuta una app instalada: lee solo su propia carpeta, llega solo a los hosts denetwork(todo lo demás esblocked: <host>en el log), recibe elctxcompleto de arriba y se le llama cadaintervalMso cuando guardas sus ajustes.Con un Miblo emparejado, Enviar al Miblo (o
miblo apps send my-app, desde tu propia terminal) la ejecuta en tu Miblo real, igualmente en el sandbox.Sin Miblo instalado, el starter hace lo mismo con
node sim.mjs dev my-app(Desarrollar).
Compatibilidad
Una app no lleva el SDK: lo recibe del Miblo en el que corre (el ctx que recibe su run(), el cliente que contiene, el puente detrás de ellos). Por eso el SDK solo crece, y manifest.json dice para qué API de apps se escribió la app ("api": 5).
Qué es aditivo (sin nivel nuevo, toda app sigue funcionando): un miembro nuevo de
ctx, un campo opcional nuevo, un elemento o efecto de tarjeta nuevo, un tipo de ajuste opcional nuevo, un límite más holgado.Qué sube el nivel: quitar algo, renombrarlo, cambiar lo que significa o cómo se comporta, o hacer un límite más estricto. Miblo entonces habla un nivel nuevo y deja de ejecutar el antiguo.
Cómo se tratan las apps antiguas: una app por debajo del nivel más antiguo que Miblo ejecuta necesita una actualización de su autor: Miblo no la instala, una copia instalada no corre (la pestaña Apps,
miblo apps listy Miblo Remote lo dicen), y la tienda de apps del Desk la oculta de sus listas y rechaza una versión suya. Una app por encima del nivel que habla este Miblo necesita un Miblo más nuevo (miblo update). Una app publicada antes del campo (sinapi) cuenta como nivel 2 y sigue funcionando.miblo apps newescribe el nivel actual;miblo apps check, el simulador y la tienda de apps del Desk lo exigen.
Lista de verificación antes de enviar
Miblo+ está activo en tu cuenta, y ninguna de tus otras apps está esperando revisión.
El
idsigue las reglas de la tienda de apps del Desk (de 3 a 32 caracteres, sin palabra reservada, sinmiblo-, sin13) y es igual al del manifest.manifest.jsondice"api": 5, la API de apps para la que se escribió la app.La versión es mayor que la publicada.
La app responde una pregunta y su promesa (hasta 120 caracteres) dice cuál, en portugués y en inglés.
La tarjeta cabe: título hasta 24, etiquetas hasta 16,
bighasta 10,texthasta 40 caracteres, hasta 4 elementos, ninguna de las frases de Miblo.Los ajustes no piden ningún secreto, y los obligatorios tienen un motivo claro.
networklista exactamente los hosts que usa el código ([]cuando no tiene conexión), todos porhttps://.Ningún módulo bloqueado, ninguna dependencia, ningún
eval, ningún texto para el revisor.intervalMses al menos 10000, y la app pide un vistazo solo cuando pasa algo importante.La app funciona sin conexión (muestra sus últimos valores y desde cuándo) y sin configurar.
Probada en el simulador, con el log de la app limpio;
miblo apps checkpasa.Un ícono PNG de 128 × 128, una descripción en al menos un idioma y el "qué cambió" listos; capturas de pantalla si las tienes.