chatmux

Servidor MCP local-first que coloca seus próprios chats do LINE e Telegram em uma única camada de dados — um daemon na sua máquina faz login com sua própria conta, armazena mensagens em JSONL + SQLite/FTS5 e as expõe para clientes MCP como Claude Code. Não é um comando npx de uma linha: você clona o repositório e executa o daemon por conta própria.

Documentação

chatmux

Daemon de camada de dados de chat pessoal, local-first. Conecta plataformas de IM (v0.1: LINE) via adaptadores de processo filho, armazena mensagens em JSONL + SQLite/FTS5, expõe ferramentas MCP para clientes de IA.

Os três repositórios

chatmux é o núcleo. Plataformas se conectam abaixo dele, consumidores ficam acima, e ambos os lados dessa fronteira vivem em seus próprios repositórios:

RepositórioFunção
chatmux (este)Daemon central: armazenamento, trilho de segurança, servidor MCP, adaptador LINE
chatmux-adapter-telegramSegundo adaptador de plataforma (Telegram, sessão de usuário MTProto)
chat.nvimConsumidor de referência: leia e responda chats dentro do Neovim

Adaptadores falam o protocolo de adaptador; consumidores falam MCP. Qualquer lado pode ser substituído sem tocar no outro.

Início rápido

1. Instalação

git clone https://github.com/echoedinvoker/chatmux.git
cd chatmux
bun install

2. Decida se quer conectar uma conta já

Sem adapters.json, bun run start inicia o adaptador LINE, o que significa que o passo 3 coloca sua conta LINE em risco — leia o Aviso de Risco de Conta antes de executar. Se preferir explorar primeiro, comece sem nenhum adaptador:

mkdir -p ~/.local/share/chatmux
cat > ~/.local/share/chatmux/adapters.json <<'JSON'
{
  "adapters": [],
  "mcp": { "port": 7717 }
}
JSON
bun run start

O daemon sobe com armazenamento e a interface MCP completa — você pode initialize, listar ferramentas e ler recursos. Simplesmente não há dados de chat por trás deles até que um adaptador seja conectado. Defina CHATMUX_DATA_DIR para manter este teste fora do seu diretório de dados real:

CHATMUX_DATA_DIR=/tmp/chatmux-trial bun run start

Cada entrada em adapters recebe platform, uma string command e um array args (mais opcional cwd e env):

{ "platform": "telegram", "command": "python", "args": ["-m", "chatmux_adapter_telegram"] }

Para Telegram, siga a configuração em chatmux-adapter-telegram — ele tem suas próprias credenciais e fluxo de login, e não envolve LINE.

3. Primeiro login (código QR)

bun run start
# A QR code will appear in the terminal
# Open LINE on your phone → open the QR scanner → scan
#   iOS:     Home → the scan icon
#   Android: Home → Add friends → QR code
# After successful login, authToken is saved for future auto-login

4. Conecte o Claude Code

Registre o endpoint MCP do daemon com o Claude Code:

claude mcp add --transport http chatmux http://127.0.0.1:7717/mcp
claude mcp list   # chatmux: ... - ✔ Connected

O daemon escuta em dois transportes ao mesmo tempo: uma porta TCP em 127.0.0.1 (padrão 7717) para clientes MCP padrão como Claude Code, e um socket unix para consumidores sidecar no mesmo host, como chat.nvim. Use a URL TCP para o Claude Code — a especificação MCP só define transportes stdio e HTTP com streaming, então nenhum cliente MCP aceita um socket unix.

A porta é configurável via CHATMUX_MCP_PORT, ou mcp.port em adapters.json; defina-a como 0 para desabilitar o listener TCP. Veja docs/mcp-interface.md.

Arquitetura

LINE adapter ←── stdio JSON-RPC ──→ core daemon ←── MCP Streamable HTTP ──→ Claude Code
(Node+tsx)        (child process)    (Bun)         (127.0.0.1 TCP / unix)     (MCP client)
                                     ├─ SafetyRail
                                     ├─ Storage (JSONL → SQLite/FTS5)
                                     ├─ Adapter Runner
                                     └─ MCP Server
  • Daemon central (Bun): processo principal que gerencia armazenamento, segurança e servidor MCP
  • Adaptador LINE (Node+tsx): processo filho que conecta ao LINE via slot IOSIPAD
  • Armazenamento: fonte de verdade JSONL append-only + visão consultável SQLite/FTS5
  • Servidor MCP: HTTP com streaming sobre TCP (clientes MCP padrão; loopback por padrão, configurável para containers) + socket unix (sidecars no mesmo host), 8 ferramentas + 4 recursos

Ferramentas MCP

FerramentaDescrição
list_chatsLista chats com pré-visualização da última mensagem, busca, paginação
read_messagesLê mensagens de um chat, paginadas por timestamp
read_eventsSegue o log de eventos a partir de um cursor opaco — retomável, sobrevive a reordenação de backfill e reentrega uma mensagem quando ela é editada ou retraída
search_messagesBusca de texto completo (CJK suportado via FTS5 trigram + fallback LIKE)
send_messageEnvia mensagem através do SafetyRail (limitado por taxa, com rastreamento de erros)
get_mediaCaminho de arquivo local para imagem ou sticker de uma mensagem; baixa e armazena em cache na primeira chamada
probe_latestDiagnóstico, somente leitura: pede ao adaptador as N mensagens mais recentes de um chat sem registrá-las
get_statusStatus do sistema: conexão do adaptador + estatísticas de armazenamento

Recursos MCP

URIDescrição
chat://chatsLista completa de chats
chat://chats/{id}/messagesMensagens recentes de um chat
chat://chats/{id}/infoDetalhes do chat com membros
chat://statusStatus do sistema

Escrevendo um consumidor

O núcleo expõe primitivas, não políticas. Qualquer coisa que decide o que importa — quais chats valem ser mostrados, para onde uma notificação vai, quando ficar em silêncio — pertence a um consumidor, do outro lado da fronteira MCP.

examples/notifier/ é uma referência funcional: ele segue o log de eventos com um cursor persistido e entrega cada mensagem a um hook que você preenche. Seu mcp-client.ts usa fetch puro em vez do SDK TypeScript, então ele também serve como referência de protocolo de rede para consumidores em qualquer linguagem.

Serviço systemd

cp config/chatmux.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now chatmux

Edite WorkingDirectory para apontar para seu clone antes de copiá-lo.

Como ele volta

A unidade vem com Restart=always, não on-failure. Um backend de chat deveria estar lá o dia todo, e há três maneiras de ele parar de estar — ele crasha, algo envia um sinal, ou ele sai limpo — das quais on-failure só se recupera da primeira. systemctl --user stop ainda o para: uma parada que você pediu não é uma falha, sob nenhuma das configurações.

⚠️ Se você está em uma unidade antiga com Restart=on-failure, kill -TERM não irá trazê-lo de volta — e isso não é falta de política de reinício. systemd conta SIGTERM, SIGHUP, SIGINT e SIGPIPE como uma parada intencional, então on-failure deixa o serviço em inactive após qualquer um deles. Apenas kill -9 (SIGKILL) conta como falha ali.

Com o Restart=always que esta unidade agora traz, TERM volta também — medido em 2026-08-02: kill -TERM $MainPID moveu NRestarts 1 → 2 e produziu um novo MainPID dentro do RestartSec de 10s. Isso torna o TERM o teste útil: kill -9 reinicia sob qualquer configuração, então não pode dizer qual está em vigor. Se você quiser confirmar que always está ativo, envie TERM e observe systemctl --user show chatmux -p MainPID,NRestarts mudar.

StartLimitIntervalSec=300 / StartLimitBurst=5 limitam um loop de crash: cinco inícios dentro de cinco minutos e o systemd para de tentar, deixando a unidade em failed para você examinar em vez de reiniciar na mesma parede para sempre. Limpe com systemctl --user reset-failed chatmux.

Containers

Um serviço de usuário systemd é a maneira pretendida de executar chatmux. Se você quiser em um container em vez disso, deploy/container/ é uma referência que constrói e responde — não é uma imagem oficial, e executa zero adaptadores, porque adaptadores mantêm sessões logadas e um container que você reconstrói é o lugar errado para elas.

A única coisa que você não pode pular é CHATMUX_MCP_HOST. O daemon vincula 127.0.0.1 por padrão, que dentro de um container é o loopback do próprio container — uma porta publicada então mapeia para um socket que ninguém está escutando, e cada conexão é recusada enquanto os logs parecem perfeitamente saudáveis. Leia deploy/container/README.md antes de assumir que seu mapeamento de porta está quebrado.

Desenvolvimento

bun run dev     # Start with --watch (auto-reload)
bun test        # Run all tests
bun run start   # Start daemon

Veja docs/ para documentação detalhada de arquitetura e protocolo.

Limitações

Conhecidas e aceitas, com o que tornaria cada uma digna de revisão.

  • A lista de chats limita a 1000, silenciosamente. chat://chats está codificado em esse limite. Consumidores podem detectar um overflow comparando o campo total com o que chegou, então não vai te surpreender sem avisar. Vale aumentar quando um vault se aproxima de ~500 chats, ou na primeira vez que essa verificação de completude disparar.
  • O log JSONL contém histórico duplicado. O backfill re-ingeriu algumas mensagens muitas vezes, deixando o log de eventos várias vezes maior do que as mensagens nele. Isso parou: o crescimento recente é quase inteiramente novas mensagens distintas, e a pior contagem de duplicatas foi congelada em medições repetidas. Não é um problema de correção — a ingestão é idempotente e a projeção SQLite não é afetada — então a correção, se algum dia precisar, é uma compactação pontual em vez de uma mudança de código. Vale fazer se o log passar de ~500 MB, se a contagem de duplicatas começar a subir de novo, ou se o início a frio ficar visivelmente mais lento.
  • Retrações em chats um-a-um do Telegram são perdidas. Retrações em grupo chegam; diretas não, porque o adaptador não pode recuperar o id do chat para esses eventos do seu cache de entidades, e o núcleo não vai combinar uma mensagem só pelo id — essa ambiguidade é exatamente o que a chave de armazenamento foi ampliada para remover. Então uma mensagem que você retraiu no seu celular pode ficar visível aqui. Vale corrigir quando o adaptador puder resolver o id do chat sozinho, ou assim que a precisão de retração importar para um consumidor.
  • Reações não são armazenadas de forma alguma. As plataformas as enviam; nenhuma camada as lê. Nada no núcleo, no esquema ou na superfície MCP representa uma reação, então um consumidor não pode mostrar o que um celular mostra. Vale construir quando reações carregarem significado que você perderia — é armazenamento novo, não um ajuste de exibição.
  • read_receipt é declarado mas nunca emitido. O adaptador LINE anuncia a capacidade e o núcleo está pronto para ingeri-lo; nada constrói o evento. Se o estado de leitura deveria chegar a uma UI é uma questão de produto aberta, não um bug pendente — mas a declaração está errada hoje, então não faça branch em supported_events para este. Vale corrigir assim que qualquer consumidor fizer branch nisso, ou quando essa questão de produto for respondida.

⚠️ Aviso de Risco de Conta

Este projeto usa @evex/linejs, uma biblioteca de cliente LINE não oficial. Usar APIs não oficiais pode violar os Termos de Serviço da LINE. Sua conta LINE pode ser restringida, suspensa ou banida permanentemente. Use por sua conta e risco.

O slot de dispositivo IOSIPAD é usado para evitar interferir com o app LINE do seu telefone, mas a LINE pode mudar sua política de múltiplos dispositivos a qualquer momento.

⚠️ Aviso Legal

Este software é fornecido "como está", sem garantia de qualquer tipo. O autor não é responsável por quaisquer consequências do uso deste software, incluindo, mas não se limitando a restrições de conta, perda de dados ou violações de termos de serviço de terceiros.

Esta é uma ferramenta pessoal para uso pessoal. Não a use para spam, assédio, acesso não autorizado a mensagens de outros ou qualquer atividade ilegal.

🔒 Divulgação de Privacidade

chatmux armazena conteúdo de mensagens descriptografado em texto puro na sua máquina local:

  • ~/.local/share/chatmux/events.jsonl — todos os eventos (append-only)
  • ~/.local/share/chatmux/chatmux.db — banco de dados SQLite com mensagens, contatos, chats
  • ~/.local/share/chatmux/adapters/line/auth.json — token de autenticação LINE
  • ~/.local/share/chatmux/adapters/line/storage.json — armazenamento de chaves E2EE

Esses arquivos são protegidos por permissões de sistema de arquivos (somente proprietário). Não compartilhe esses arquivos. O token de autenticação concede acesso total à sua conta LINE. As chaves E2EE podem descriptografar suas mensagens.

v0.1 não criptografa o banco de dados. A criptografia SQLCipher está planejada para v0.2.

Licença

MIT