Arquitetura

O aipager é um programa asyncio de um processo só que roda em segundo plano. Ele liga três mundos: o seu chat do Telegram, os processos do Claude Code que você roda sob o dtach, e o sistema de hooks do Claude Code. Tudo passa por um único event loop: sem pools de workers, sem banco de dados, sem filas entre processos.

O original no GitHub ↗Esta página é uma tradução da página em inglês; se as duas não baterem, quem vale é a página em inglês. Inglês

O aipager é um programa asyncio de um processo só que roda em segundo plano. Ele liga três mundos: o seu chat do Telegram, os processos do Claude Code que você roda sob o dtach, e o sistema de hooks do Claude Code. Tudo passa por um único event loop: sem pools de workers, sem banco de dados, sem filas entre processos.

Diagrama das partes#

Loading diagram…

Tudo o que está no diagrama, menos o Telegram, roda neste computador. O aipager também fala com alguns outros servidores: o túnel do Cloudflare do Mini App (e o GitHub, uma vez, para o cloudflared), o telegram.org para o script do Mini App, a verificação de versões quando você pede uma atualização, o download do modelo de voz na primeira vez que você usa áudios, e o destino dos relatórios de problemas quando você envia um. Todos estão em Modelo de segurança → conexões de rede.

Como o processo funciona#

Uma única chamada asyncio.run em aipager/cli/daemon.py toca tudo. Dentro dela:

  • TelegramBot (aipager/bot/): tem um Application do python-telegram-bot. Recebe as atualizações do Telegram, entrega cada uma ao tratador de mensagens, botões ou áudios, e mantém em dia os cartões de atividade, a barra de status fixada e as respostas.
  • HookReceiver (aipager/dtach/hook_receiver.py): abre um socket datagram do Unix em $XDG_RUNTIME_DIR/aipager.sock, lê o JSON que o aipager-hook e o aipager-statusline mandam, e distribui o trabalho pelo hook_event_name (ou type, para as mensagens do próprio aipager; veja eventos de hook).
  • SessionMonitor (aipager/session_monitor.py): acorda a cada 2 s para olhar /tmp/claude-dtach-*.sock, acertar as sessões que encontra com a lista de sessões, e rodar os vigias: um cartão de atividade que parou de andar, uma mensagem que o Claude Code nunca recebeu, um pedido de permissão sem notícias por 5 minutos que a conversa salva mostra como acabado (AIPAGER_INTERACTIVE_TIMEOUT), uma tarefa cujo fim não foi visto.
  • SessionRegistry (aipager/state.py): os objetos TrackedSession pelo nome da sessão. Salvos em ~/.claude/aipager-sessions.json (de forma atômica) ao desligar e sempre que muda algo que precisa sobreviver a um reinício.
  • Servidor do Mini App (aipager/miniapp/server.py): um app aiohttp em 127.0.0.1 na porta MINIAPP_PORT (padrão 8765), que enquanto está ligado (o padrão) chega ao Telegram por um túnel do Cloudflare gerenciado. Serve o painel do /app.

Tudo isso roda como tarefas no mesmo loop. O que travaria o loop roda em threads à parte: transformar áudio em texto, montar e enviar um relatório de problemas, verificar atualizações e planejar reinícios, baixar o script do Mini App e o cloudflared, e a verificação de login ao iniciar.

Ordem de inicialização#

De aipager/cli/daemon.py:

  1. Passa uma configuração antiga (config.env, team.yaml) para aipager.yaml, uma vez só, e tira os arquivos velhos do caminho.
  2. Não inicia se outro aipager responder no socket; depois pega a trava do aipager (~/.local/share/aipager/daemon.lock).
  3. Confere o token do bot com o Telegram (getMe).
  4. Acerta os arquivos do próprio Claude Code: adiciona qualquer hook do aipager ou a linha de status que falte em ~/.claude/settings.json, e responde às perguntas da primeira execução que um usuário do Telegram não conseguiria responder (veja Modelo de segurança).
  5. SessionRegistry.load(): lê ~/.claude/aipager-sessions.json. Se o arquivo não existir ou não puder ser lido, começa com a lista vazia.
  6. Descobre o endereço do Mini App (sua própria URL, ou o Tailscale), depois bot.start(): começa a receber as atualizações.
  7. HookReceiver.start(): apaga um arquivo de socket que tenha sobrado, abre e escuta.
  8. bot.recover_sessions(): recupera os cartões e os pedidos das sessões que estavam trabalhando ou esperando quando o aipager parou (veja comandos → respostas).
  9. SessionMonitor.start(): começa o tique de 2 s.
  10. O servidor do Mini App inicia, depois o túnel (quando você não definiu uma URL própria), e os botões 📱 App são publicados.
  11. O aipager espera sinais: SIGINT ou SIGTERM o param, SIGUSR1 recarrega os chats e as regras (uso em equipe → recarregar a configuração sem reiniciar).

Ordem de desligamento#

  1. SIGINT ou SIGTERM ligam o evento stop.
  2. Um /update em andamento ganha um pouco de tempo e depois o instalador dele é parado (veja comandos → atualizar).
  3. registry.save().
  4. O túnel para, depois o servidor do Mini App.
  5. session_monitor.stop(), hook_receiver.stop() (fecha o socket e apaga o arquivo dele).
  6. bot.stop(): cancela as animações e para o Application.
  7. registry.save() de novo, para um cartão cujo envio ainda estava em andamento.

O reinício feito pelo Telegram depende de todo esse caminho terminar antes de o substituto abrir o socket. O que você vê está em comandos do bot → reiniciar.

Onde ficam os arquivos e os sockets#

CaminhoPara quêQuem
$XDG_RUNTIME_DIR/aipager.sockSocket datagram do Unix para os eventos de hook (/tmp/aipager.sock sem $XDG_RUNTIME_DIR; $AIPAGER_SOCKET_PATH vale mais que os dois)aipager (abre)
aipager-reply-<id>.sock (ao lado do socket de controle)Um socket de resposta de vida curta para cada pedido de permissão que o hook esperaaipager-hook
aipager-flood-mute.json, aipager-flood-backoff.json (ao lado do socket de controle)O estado atual dos limites de velocidade do Telegram, para o aipager status e o doctoraipager
aipager-modelswitch-<name>.json (ao lado do socket de controle)Marca uma troca de modelo que o aipager digitou, por 30 s (hooks)aipager (escreve), aipager-hook (pega)
/tmp/claude-dtach-<name>.sockSocket de controle do dtach de cada sessãodtach
/tmp/claude-status-<name>.jsonDados da linha de status de cada sessãoaipager-statusline
/tmp/claude-notes-<name>/Uma nota de permissões para cada mensagem do Telegram que ainda não foi pega, e uma marca turn-open que o hook mantém enquanto há uma tarefa em andamento (uma mensagem que entra na tarefa só pode deixar as regras dela mais rígidas)aipager (escreve as notas), aipager-hook (consome as notas; escreve turn-open, e os dois a apagam)
/tmp/claude-policy-<name>.jsonA cópia de referência das permissões da tarefa em andamentoaipager-hook (escreve ao pegar a mensagem), lida pela verificação PreToolUse
/tmp/claude-policy-.floor-<uid>.jsonO piso de segurança, a lista própria do aipager mais a sua seção safety:, para os casos de reserva do hookaipager
/tmp/claude-reply-<name>.txtA mensagem que você respondeu, quando você responde a uma mensagem anterioraipager
/tmp/aipager-files/<chat>/Arquivos e áudios enviados pelo Telegram, uma pasta privada por chat (0700, arquivos 0600)aipager
/tmp/aipager.logA saída de um reinício próprio quando não há serviçoaipager
~/.claude/aipager-sessions.jsonO estado duradouro da lista de sessõesaipager
~/.claude/aipager-audit.jsonlRegistro de permitir, negar e respostasaipager (só acrescenta)
~/.claude/aipager-flood-state.jsonO histórico dos limites de velocidade do Telegram de cada chat, que sobrevive aos reinícios (solução de problemas)aipager
~/.claude/aipager-tunnel.json, aipager-cloudflared.logO túnel do Mini App que o aipager mantém: o processo dele, as portas locais e um hash do endereço (nunca o endereço), para o próximo aipager poder assumi-lo depois de um reinício planejado (permissões 600); os avisos do cloudflaredaipager; o registro também é removido pelo guardião do túnel mantido e por aipager uninstall
~/.claude/aipager-pending-users.jsonPessoas que chamaram o bot num grupo onde ele não trabalha, ou que não são membros de um grupo onde ele trabalha, para o aipager config poder adicioná-las (permissões 0600, substituído inteiro a cada escrita, as 200 mais recentes vistas nos últimos 30 dias)aipager (escreve), aipager config (lê)
~/aipager-users/<label>/As pastas raiz padrão dos membros (permissões 0700), onde os membros user e read_only criam as sessões deles; o aipager uninstall as mantémaipager (cria no primeiro /new ou “Nova sessão” do Mini App de um membro)
~/.claude/settings.jsonA configuração de hooks do Claude Codeaipager config / aipager setup; a cada início o aipager adiciona qualquer hook ou linha de status que falte e define skipDangerousModePermissionPrompt (Modelo de segurança)
~/.claude/settings.json.bak.*Cópias de segurança antes de cada reescritaaipager config, aipager setup, aipager uninstall
~/.claude.jsonO arquivo do próprio Claude Code: o aipager marca como confiável a pasta onde uma sessão começa logo antes de ela começar (inclusive a pasta pessoal, nunca / nem uma pasta acima da pasta pessoal), para a pergunta de confiança do Claude Code não travar uma sessão criada pelo Telegram. Nada é marcado quando o aipager inicia. Nunca é reescrito quando não dá para lê-lo inteiro ou quando um Claude em execução o mudou nesse meio-tempoaipager
~/.claude/.credentials.json → .credentials.json.staleO arquivo de login do Claude Code, tirado do caminho quando uma sessão inicia com CLAUDE_CODE_OAUTH_TOKEN definido e o arquivo só tem um login vencido ou vazio (senão ele ganharia do token); coloque de volta para desfazeraipager
~/.config/aipager/aipager.yamlToken do bot, chats, membros e funções, configurações do Mini Appaipager config (permissões 600); o aipager move a entrada de um grupo quando o Telegram o transforma em supergrupo, e guarda uma cópia aipager.yaml.bak.<time> quando passa para lá configurações antigas do Mini App
~/.config/aipager/policy.yamlRegras por função e regras de segurança adicionaisvocê (conferido com aipager policy validate); um exemplo comentado é escrito uma vez quando uma instalação antiga é migrada
~/.config/aipager/preferences.jsonO que foi escolhido em /settings para cada chat e cada sessãoaipager
~/.config/aipager/daemon.envCredencial do Claude para as sessões iniciadasvocê / aipager service install / aipager doctor --fix (permissões 600)
~/.config/aipager/config.env, team.yaml → *.bak.<time>, *.retired.<time>A configuração de uma instalação antiga: copiada e depois tirada do caminho quando o aipager.yaml já a temaipager, aipager config, aipager setup
~/.config/aipager/keyboard.jsonMudanças opcionais do tecladovocê
~/.local/share/aipager/sessions/Uma pasta por sessão com o SESSION.md dela (quem pode falar com ela, e as regras), refeita a cada inícioaipager
~/.local/share/aipager/webapp-sdk/, cloudflared/O script do Mini App do Telegram e o binário cloudflared conferidoaipager
~/.local/share/aipager/daemon.lock, update.lock, update-restart.jsonUm aipager de cada vez; uma atualização de cada vez; a atualização a anunciar depois do reinícioaipager
~/.local/share/aipager/install.json, running.jsonQuando o aipager rodou aqui pela primeira vez; se está rodando agora (para diferenciar uma queda de uma parada normal)aipager
~/.local/state/aipager/reports.jsonRelatórios de problemas: os fingerprints dos erros registrados, as contagens e o estado das sugestões (permissões 600); veja relatórios de problemasaipager
~/.local/state/aipager/report-sends.jsonOs relatórios enviados hoje (o limite diário)aipager, aipager report
~/.local/state/aipager/languages.jsonO estado de idioma (permissões 600): quem já usava o aipager antes de existirem idiomas, o último idioma do Telegram visto de cada pessoa, se a oferta única de idioma e a linha de primeiro contato já foram enviadas, e um hash de cada descrição do bot que o aipager definiu; veja Idiomasaipager
~/.config/systemd/user/aipager.service (Linux), ~/Library/LaunchAgents/com.aipager.daemon.plist (macOS), e uma cópia .bak.<time> do que uma instalação substitui (no Linux só se for diferente)O serviço em segundo planoaipager service install
~/Library/Logs/aipager.log (macOS)O log do serviço do launchd, lido pelo aipager logslaunchd, para o serviço

Além disso, o aipager cria uma pasta nova para uma sessão quando você pede (Nova pasta no /new ou no Mini App), e os instaladores e o modelo de voz escrevem os próprios arquivos quando você atualiza ou usa a voz. Tudo fica na sua pasta pessoal, em /tmp, ou ao lado do socket de controle. O aipager nunca pega permissões a mais; veja Modelo de segurança.

Uma instância isolada (para testes)#

Defina AIPAGER_INSTANCE_DIR como uma pasta absoluta que seja sua e todos os arquivos compartilhados da tabela acima vão para lá: o socket de controle (e os sockets de resposta, os arquivos de sinal dos limites de velocidade e a marca de troca de modelo ao lado dele), os sockets claude-dtach-*, os arquivos claude-policy-*, claude-notes-*, claude-status-* e claude-reply-*, o arquivo do piso de segurança, a pasta de downloads aipager-files e o log do reinício próprio. As sessões que a instância inicia herdam a configuração, então os hooks delas só falam com essa instância, e as regras de segurança protegem do mesmo jeito os arquivos movidos. A instância não lê o .env de um checkout.

Os arquivos da sua pasta pessoal não mudam de lugar, então aponte o HOME também para outra pasta. O aipager start se recusa a iniciar quando a pasta da instância está definida e o HOME é a sua pasta pessoal de verdade, quando a pasta não é um caminho absoluto para uma pasta sua, ou quando ela é longa demais para o caminho de um socket.

O /update e o /restart não ficam isolados: eles agem sobre o pacote aipager instalado e o serviço aipager.service, então nunca os mande para uma instância de teste.

AIPAGER_TELEGRAM_API_BASE (por exemplo http://127.0.0.1:41234) manda todos os pedidos à Bot API e os downloads de arquivos para esse endereço em vez de https://api.telegram.org. Precisa ser este computador (localhost ou um endereço loopback), a não ser que você defina AIPAGER_TELEGRAM_API_ALLOW_REMOTE=1, para um erro de digitação não poder mandar o token do bot para outro lugar.

As duas configurações existem para os testes de ponta a ponta poderem rodar um segundo aipager contra um Telegram de mentira local, ao lado do seu de verdade. Sem nenhuma das duas, todos os caminhos e endereços são os da tabela acima.

Por que o dtach#

O dtach dá a cada sessão do Claude Code um PTY de verdade sem prendê-la a um terminal que precisa ficar aberto. O aipager inicia uma sessão com dtach -n e digita nela com dtach -p (mensagens, Escape, as teclas que respondem a uma caixa de diálogo); ele nunca lê a tela, porque os hooks e a conversa salva dizem o que acontece. Você pode se conectar de forma interativa de qualquer shell com aipager session <name> para ter acesso direto. Essa conexão acompanha a sessão num reinício feito pelo Telegram (/mode para o Modo Acesso total ou saindo dele, /restart, Reiniciar): quando o socket da sessão some, ela espera até 15 s pelo mesmo socket voltar e se reconecta sozinha. Uma sessão que terminou de verdade volta para o shell depois da espera (Ctrl-C encerra antes). A conexão roda com a tecla de desconexão do dtach desligada, então Ctrl-\ chega ao Claude; para sair, feche o terminal ou o painel do tmux e a sessão continua rodando.

O resultado: o Claude Code roda como se você digitasse num terminal, mas esse terminal pode ir e vir sem atrapalhar a sessão em andamento. O aipager encontra as sessões olhando /tmp/claude-dtach-*.sock a cada tique de 2 s do vigia. Uma sessão encontrada pertence ao chat para o qual o nome dela foi criado, e uma criada no terminal pertence ao chat privado do dono numa instalação com chat privado e grupo.

Veja também#