Como o app macOS do lemon-code conversa com o CLI por JSON-RPC sobre stdio, sem reimplementar o agente e sem tocar no banco do core
English summary: How lemon-code's native macOS app sits on top of the CLI through a JSON-RPC 2.0 contract over stdio, with capability negotiation, degraded mode and a hard rule against writing to the core database.
Publicado: 2 de novembro de 2026
Tempo de leitura: 9 min
Série: lemon-code em 30 dias, parte 7 de 13
O primeiro commit do app macOS do lemon-code, em 16 de maio de 2026, não tinha nenhuma tela. A mensagem era "define app contract and execution rules". A janela só apareceu no commit seguinte.
Essa ordem foi a decisão mais importante do app. Este post mostra como um app SwiftUI pode ficar por cima de um CLI sem reescrever o agente em Swift, e as regras que mantêm os dois em pé.
Pense no painel de um carro. Ele mostra velocidade, combustível e alertas, e tem botões que mandam comandos. Mas ele não queima gasolina. Se você trocar o painel, o motor continua igual.
O app é o painel. O CLI é o motor. A regra número um do repositório do app diz isso com outras palavras: manter o lemon-code como único motor de execução.
As operações do agente e as mudanças no estado do core passam pelo CLI; a UI do app não abre o SQLite. O terminal embutido é separado: o app inicia o processo filho e controla o PTY usando a configuração devolvida pelo CLI.
O contrato é JSON-RPC 2.0, uma mensagem por linha, sobre a entrada e saída padrão do processo do CLI. O app envia lc.app.initialize sem parâmetros; a resposta traz contractVersion (app.v2), sessionId, serverInfo e capabilities.
{"jsonrpc":"2.0","id":1,"method":"lc.app.initialize"}
O app inicia o processo do CLI, escreve uma linha e lê a resposta correspondente. A lógica do agente continua no CLI; a interface Swift também é dona do PTY do terminal embutido.
Produtos gratuitos e pagos para transformar ideias em uma base que você consegue executar.
13 produtos disponíveisContinue explorando tópicos similares
Um lock momentâneo no SQLite abria o circuit breaker do meu app, que passava a mostrar dados de fallback e um aviso que tapava o campo de digitação. A correção coube em cinco regras.
O lemon-code tem seis ADRs com o número 0049, todas criadas no mesmo dia. Esse erro ensina mais sobre memória de projeto com vários agentes do que qualquer ADR bem numerada.
Eu pagava API para tarefas que o Claude Code ou o Codex, já logados na minha máquina, resolveriam. A regra CLI-first do lemon-code cabe em quatro linhas de precedência.
Checklist prático para validar segurança, custos, avaliações e observabilidade antes de colocar seu app com IA em produção.
Templates testados em produção, usados por desenvolvedores. Economize semanas de setup no seu próximo projeto.
O contrato separa dois planos:
lc.app.*: projetos, threads, aprovações, review, automações, instalação.Um exemplo concreto é o chat. O lc.app.chat.send executa o prompt pelo mesmo controlador do CLI, grava as mensagens na sessão e devolve a procedência da rota: provider e modelo pedidos, os realmente usados, as tentativas em ordem e se houve fallback. O lc.app.chat.cancel cancela só a requisição com aquele requestId, e o cancelamento é terminal: não dispara outra tentativa de fallback.
Outro exemplo é o terminal embutido. O CLI devolve só a configuração (diretório, shell de login, argumentos, ambiente) e o app é dono do PTY e do processo filho. Cada lado fica com o que sabe fazer.
Aqui está o erro que a ADR 0058 corrigiu. O risco era o app interpretar um initialize bem-sucedido como suporte a qualquer feature opcional. Com um CLI mais antigo, o app mostrava um botão que o core não sabia executar.
A regra hoje:
lc.app.getCapabilities anuncia cada feature opcional junto com os métodos que a implementam.agentRooms: true se o conjunto mínimo de métodos não estiver registrado.Um teste de integridade no CLI impede anunciar uma capability sem método correspondente.
Se o backend não estiver rodando, o app abre em modo degradado. Se a conexão JSON-RPC falhar, aparece um aviso no topo que pode ser fechado e não bloqueia a digitação. Parece detalhe. Na prática, é o que separa um app que você usa todo dia de um que você fecha irritado.
Com dados do Git: o app tem 152 commits entre 16 de maio e 12 de outubro de 2026. Requer macOS 13 e Swift 5.9, e roda com swift build e swift run LemonCodeApp.
Um app nativo por cima de um CLI funciona quando o contrato vem primeiro e o app aceita ser painel. O motor fica num lugar só, testado por CLI, TUI e RPC. O SwiftUI cuida do que faz bem: janelas, atalhos, terminal embutido e notificações.
Na quarta, o assunto é observabilidade: como manter o Collector sob seu controle e deixar o conteúdo da conversa fora da telemetria.
Se o seu time quer um app desktop por cima de uma ferramenta de linha de comando existente, desenhar esse contrato é um trabalho que faço na LEMON.DEV.