SDK
Apps do Miblo: declarar, configurações, supervisor e sandbox
Transforme um programa em app do Miblo: declare, o esquema de configurações, settings.get, request e onChange, a rotação, o supervisor e a sandbox dos apps da comunidade.
O que é um app
Um app é um programa que diz ao Miblo o nome dele e as configurações de que precisa. A pessoa preenche essas configurações no app Miblo (aba Apps) ou no terminal, nunca num arquivo ou numa variável de ambiente, e o Miblo cuida de rodar o programa. Os apps oficiais (miblo-apps) usam exatamente as mesmas rotas que você vai usar: não há porta especial.
Um app é feito de três peças:
declarar o app (nome, categoria, promessa, configurações, uma prévia);
ler as configurações e reagir quando a pessoa muda alguma;
desenhar a tela com
screen.card()ouscreen.layer()(A tela App).
app()
const app = miblo.app({ id: 'clima', tool: 'clima' });app = miblo.app("clima", "clima")id: o id do app, letras minúsculas, números e-, até 48 caracteres. Use o mesmo domiblo apps add --id.tool: o nome do seu programa (as regras dotoolde uma sessão). As configurações de um app pertencem ao programa que o declarou, por esse nome: outro programa recebenot_your_app. Um app instalado da loja pertence ao nome do pacote.
declare()
await app.declare({
name: { pt: 'Clima', en: 'Weather' },
category: 'daily',
promise: { pt: 'Vai chover?', en: 'Will it rain?' },
settings: [
{ key: 'city', label: { pt: 'Cidade', en: 'City' }, type: 'text', required: true, max: 40 },
{ key: 'units', label: { pt: 'Unidade', en: 'Units' }, type: 'select', options: ['°C', '°F'], default: '°C' },
{ key: 'peek', label: { pt: 'Avisar antes da chuva', en: 'Heads-up before rain' }, type: 'toggle', default: true },
],
preview: { v: 1, title: 'Clima', icon: 'cloud', items: [{ t: 'big', value: '24°', label: 'Recife' }] },
});app.declare(
{"pt": "Clima", "en": "Weather"},
settings=[
{"key": "city", "label": {"pt": "Cidade", "en": "City"}, "type": "text", "required": True, "max": 40},
{"key": "units", "label": {"pt": "Unidade", "en": "Units"}, "type": "select", "options": ["°C", "°F"], "default": "°C"},
{"key": "peek", "label": {"pt": "Avisar antes da chuva", "en": "Heads-up before rain"}, "type": "toggle", "default": True},
],
category="daily",
promise={"pt": "Vai chover?", "en": "Will it rain?"},
preview={"v": 1, "title": "Clima", "icon": "cloud", "items": [{"t": "big", "value": "24°", "label": "Recife"}]},
)| Campo | Regras |
|---|---|
name | Um texto ou { pt, en }, até 24 caracteres. Obrigatório. |
settings | O esquema das configurações (abaixo), até 12. |
category | Um nome curto para a aba Apps. A loja usa computer, money, daily, news-dev, sport, delights e other. |
promise | Uma linha, { pt, en } ou texto, até 100 caracteres: a pergunta que o app responde. |
preview | Um card com dados de exemplo, mostrado na aba Apps antes de o app rodar. |
Devolve { ok, id }. Declarar de novo substitui o esquema e mantém cada valor salvo que continua válido nele: pode declarar a cada início do programa. No máximo 32 apps por computador (too_many_apps).
O esquema das configurações
Cada configuração é { key, label, type, default?, required? } mais os campos do tipo.
type | Campos extras | Valor |
|---|---|---|
text | max (1 a 200, padrão 100) | Um texto de uma linha |
number | min?, max? | Um número |
select | options: 1 a 20 opções (até 40 caracteres cada, sem repetir) | Uma das opções |
toggle | (nenhum; padrão false) | true ou false |
url | (nenhum) | Um endereço http:// ou https:// de até 512 caracteres |
time | (nenhum) | Um horário HH:MM (24 h); aceita 22:00, 7:30 ou 22h30 e guarda sempre HH:MM. O app Miblo mostra um seletor de horário |
connect | provider, clientId, scopes, clientSecret?, help? | A conta conectada (veja Conectar contas) |
key: começa com uma letra minúscula, depois letras, números e_, até 32 caracteres, sem repetir.label: um texto ou{ pt, en }, até 40 caracteres.required:truefaz o app aparecer como "precisa de configuração" enquanto não houver valor.default: precisa ser um valor válido do próprio tipo.group:"advanced"dobra a configuração sob "Avançado" no app Miblo, para as que quase ninguém muda.placeholder(text,number,url,time): um exemplo do que escrever, texto ou{ pt, en }de até 40 caracteres, mostrado no campo vazio. Onde existe uma lista, declare umselect(carregue a lista da fonte e guarde por 7 dias); texto livre só onde nenhuma lista cabe, e então com o exemplo.seed: um valor que o seu programa traz das configurações de uma versão anterior (um texto antigo repartido em configurações novas). O Miblo salva uma vez, só onde a pessoa ainda não tem valor, nunca guarda no esquema e ignora um que não seja válido. Um texto salvo cujas partes são todas opções de umselectde várias escolhas que o substituiu passa sozinho.Nada de segredos. Não existe tipo "senha", e uma chave ou um rótulo que pareça um segredo (
token,password,senha,api_key,secret,chave,cookie,pin...) é recusado. Os apps leem fontes públicas, sem conta.
Ler as configurações
settings.get()
const { values, missing, rev } = await app.settings.get();s = app.settings.get() # {"id", "values", "missing", "rev"}values: os padrões combinados com o que a pessoa salvou; uma configuração sem nenhum dos dois énull.missing: as configurações obrigatórias ainda sem valor.rev: muda quando os valores mudam.
settings.request()
Pede à pessoa agora, com um motivo de até 80 caracteres.
if (missing.length) await app.settings.request(missing, 'Para mostrar a previsão');if s["missing"]:
app.settings.request(s["missing"], "Para mostrar a previsão")O que acontece: o app Miblo mostra um cartão de configuração com o formulário e o motivo, o celular diz "Configure o app Clima no computador", e a tela do app no Miblo mostra "Configure no app Miblo" no lugar do card. O pedido fica valendo até os valores mudarem. Devolve { ok, needsSetup }. O motivo não pode começar como um aviso do Miblo.
settings.onChange() e settings.wait()
const stop = app.settings.onChange((values) => redraw(values)); // a pessoa salvou: redesenhe já
// ...
stop();stop = app.settings.on_change(lambda values: redraw(values)) # numa thread em segundo plano
# ...
stop()onChange(cb, { onError, retryMs })chamacb(values, answer)a cada mudança (não para os valores atuais) e devolvestop(). Erros vão paraonErrore ele continua tentando a cadaretryMs(padrão 5000). Em Python:on_change(cb, on_error=None, retry=5.0), comcb(values).wait(rev?)é a peça de baixo: espera os valores mudarem a partir derev, no máximo 20 s, e devolve{ values, missing, rev }. No máximo 16 esperas ao mesmo tempo (busy).
Os valores ficam neste computador (<dados do Miblo>/apps/<id>.json, só o seu usuário lê). Pelo terminal: miblo apps config <id> chave=valor.
Quando a tela do app aparece
O rodízio: com vários apps, a tela App mostra um por vez,
miblo apps rotation <segundos>cada (padrão 15;0: só o fixado).Quando:
miblo apps when idle(padrão: só quando nenhuma sessão de IA está rodando, esperando ou precisando de você há--idle-aftersegundos, 60 por padrão, de 10 a 3600),rotation(sempre no rodízio) oumanual(só fixado ou num peek). A pessoa escolhe; um app não muda isso.Fixar e peek continuam valendo em qualquer escolha. Os avisos do Miblo sempre vêm primeiro.
O supervisor
miblo apps add "<comando>" registra o seu programa e miblo apps enable <id> o mantém rodando: o Miblo o inicia com o token, o reinicia quando termina e guarda a saída (miblo apps logs <id>). O passo a passo está em Instalação.
| Comando | O que faz |
|---|---|
miblo apps list [--json] | Todos os apps, ligados ou não, e quem precisa de configuração |
miblo apps enable / disable <id> | Liga ou desliga |
miblo apps show <id> | Estado, reinícios, comando ou hosts |
miblo apps logs <id> | A saída do programa (e o que a sandbox bloqueou) |
miblo apps config <id> [chave=valor ...] | Mostra ou muda as configurações |
miblo apps rotation [segundos] | O tempo de cada tela no rodízio |
miblo apps when [rotation|idle|manual] | Quando os apps aparecem |
miblo apps install <id> | Instala um app da loja (assinado) |
miblo apps remove <id> | Desliga, tira a tela e apaga as configurações |
miblo screen status / clear | Mostra ou limpa a tela App |
Conectar contas
Com a próxima versão do Miblo, um app pode ler dados da própria conta Google ou Microsoft da pessoa (o app oficial Agenda lê a agenda assim) sem nunca ver uma senha ou um refresh token: quem faz o login é a ponte do Miblo, com OAuth 2.0 (Authorization Code + PKCE, como um app de desktop), e o seu programa recebe só tokens de acesso de curta duração.
await app.declare({ name: 'Agenda', settings: [
{ key: 'google', label: { pt: 'Conectar Google', en: 'Connect Google' }, type: 'connect', provider: 'google',
clientId: '<seu client id de desktop>.apps.googleusercontent.com', clientSecret: '<o secret de app instalado do Google>',
scopes: ['https://www.googleapis.com/auth/calendar.readonly'],
help: { pt: 'Os eventos aparecem no Miblo em até 30 s', en: 'Events show on Miblo within 30 s' } },
] });
const { url } = await app.connect.start('google'); // o endereço de login (o app Miblo tem o botão)
const { accessToken, expiresAt, account } = await app.connect.token('google');
await fetch('https://www.googleapis.com/calendar/v3/users/me/calendarList', { headers: { authorization: `Bearer ${accessToken}` } });
await app.connect.off('google'); // desconectaurl = app.connect.start("google")["url"]
token = app.connect.token("google")["accessToken"] # MibloError "not_connected" até a pessoa conectar
app.connect.off("google")A configuração
connectdiz onde entrar:provider(googleoumicrosoft, uma configuração para cada),clientId(o seu cliente OAuth: um cliente "Desktop app" do Google ou um cliente público do Azure),scopes(1 a 8), oclientSecretdo Google quando houver (o Google dá um aos clientes de desktop e documenta que não é confidencial; nunca para a Microsoft) e uma linhahelpopcional (80 caracteres, embaixo do botão). Nunca é obrigatória nem tem padrão; o valor é a conta conectada (o e-mail),''até lá, e só a ponte escreve nele. Por issosettings.wait()acorda o programa assim que a pessoa conecta ou desconecta.connect.start(provider)devolve{ provider, url }, o endereço de login. A ponte espera um único retorno em até 5 minutos numa porta local própria, confere ostate, troca o código com o verificador PKCE e confere o id_token. O navegador mostra "Conectado como ...". No máximo 4 logins ao mesmo tempo (busy).connect.token(provider)devolve{ provider, accessToken, expiresAt, account }, um token com pelo menos um minuto de vida, renovado quando preciso.not_connectedaté a pessoa conectar (e de novo se o provedor revogar o acesso);unavailablecomretryAfterquando o provedor não responde: tente depois, continua conectado.connect.off(provider)esquece a conta neste computador (miblo apps removetambém).Quem pode: só o programa que declarou o app (
not_your_app), como as configurações.Onde ficam os tokens: o refresh token fica cifrado (AES-256-GCM, com uma chave derivada da chave da ponte, presa ao app e ao provedor) em
<dados do Miblo>/apps/<id>/connect-<provider>.json; os tokens de acesso ficam só na memória da ponte. Nada vai para o miblo.ai.Para a pessoa: a aba Apps do app Miblo mostra o botão "Conectar Google" e depois "Conectado como ... · Desconectar". No terminal:
miblo apps connect <id> googleemiblo apps disconnect <id> google.
A sandbox dos apps da comunidade
Um app da loja pode trazer código escrito por outra pessoa. Ele só chega a um computador depois da análise automática, da revisão por IA e da aprovação humana (Publicar na loja), e roda numa sandbox:
Arquivos: lê só o próprio pacote, a própria pasta de estado e o runtime do Miblo; escreve só na pasta de estado. Usa o modelo de permissões do Node (
--permission).Sem processos nem código nativo: nada de processos filhos, worker threads, addons nativos ou WASI.
Módulos bloqueados:
http,https,http2,net,tls,dgram,dns,child_process,cluster,worker_threads,vm,module,wasi,inspector,repl,v8,trace_eventsesqlite. O app importa só arquivos do próprio pacote e os outros módulos internos do Node; código que ele grava na pasta de estado não pode ser importado.Rede só pelo
fetchdo Miblo, e só para os hosts declarados emnetwork: https na porta padrão, redirecionamentos seguidos com a mesma regra, 10 s por chamada, 256 KB por resposta, 30 chamadas por minuto.WebSocket,EventSourceeXMLHttpRequestnão existem.O token do SDK sai do ambiente antes de o app carregar; só o cliente o guarda.
Tudo que é bloqueado vira uma linha
blocked: ...emmiblo apps logs <id>, mesmo que o app trate o erro.A loja tem a palavra final: a cada início e a cada hora o pacote é conferido de novo; um app que a loja deixou de servir ou escondeu é desligado.
Precisa do Node 22.15+, 23.5+ ou 24+. Num Node mais antigo, o app não roda (estado
unsupported, com o motivo no log).
O que a sandbox não garante: a rede é contida pelo fetch substituído e pelo controle de módulos, não pelo sistema operacional, e um app pode gastar CPU e memória e mandar aos hosts dele o que o Node dá a qualquer programa (hora, idioma, o nome do computador). Por isso a revisão humana vem antes de cada versão.