Serein
Rust로 만든 가볍고 성능 좋은 Discord 전용 네이티브 클라이언트
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기full project, blueprints, tutorials, resources of messaging app
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
README 전문 보기 ↗A self-hosted enterprise AI-assistant platform — realtime chat where everyone gets a personal AI that acts for them via governed third-party connectors (MCP).
A high-performance monorepo with four microservices, a Flutter mobile client, and a Next.js web client — delivering secure, low-latency 1-on-1 and group chat, WebRTC calls, a RAG-backed AI assistant with long-term memory, and an enterprise foundation (workspace, departments, role-based access control) where the assistant can take real actions through governed MCP connectors (Notion, Google Workspace, custom servers).
New here? ONBOARDING.md is the one page for getting this running and contributing to it — including which branch to clone, which is not
main. Architecture diagrams live in docs/architecture.md; how environments are kept apart, in docs/environments.md.Product direction: PON is evolving from a chat app into a self-hosted, single-tenant-per-deployment B2B AI-assistant platform (one deployment = one company). The vision, revised roadmap, and current build state live in docs/superpowers/PON-ENTERPRISE-HANDOFF.md.
PON is a modular, full-stack platform composed of four backend microservices and two clients interacting asynchronously:
| Layer | Technology | Responsibility |
|---|---|---|
auth-service | NestJS · TypeScript | Identity & tokens, OTP/OAuth, enterprise RBAC (workspace, departments, roles, permission matrix embedded in the JWT) |
chat-service | Spring Boot 3 · Java 21 | Realtime message delivery (WebSocket/STOMP, Redis-bridged across instances), presence, chat REST/CRUD, attachments (GridFS), calls signalling, reminders, FCM push, external-bot bridge (Bot Factory personal assistants) |
ai-service | NestJS · TypeScript | AI assistant pipeline (Anthropic Claude with per-message model routing), agentic tool loop, long-term memory, hybrid RAG, skills, digest/scheduler, usage quota; merges per-user MCP tools into the agent |
connector-service | NestJS · TypeScript | MCP connectors: OAuth to third parties, AES-256-GCM token vault, MCP client, governed tool exposure (workspace/personal + capability gates) |
client | Flutter 3 · Dart | Cross-platform mobile app (Android, iOS) using Riverpod & GoRouter |
web | Next.js 16 · React 19 · Tailwind 4 | Responsive web client — shadcn/ui, TanStack Query (server data), Zustand (auth), next-intl, STOMP |
platform database): Holds canonical user profiles, conversations, messages, and knowledge base metadata.ai.direct → queue ai.requests, ai-service consumes. 30-second message TTL, dead-lettered to ai.requests.dlq.ai:response:{conversationId}), Knowledge Base job channels (kb:process, kb:delete), and the cross-instance STOMP fan-out bridge (stomp:broadcast) so realtime works with more than one chat-service instance.knowledge (KB document chunks) and ai_memory (long-term memory facts).connector-service, :3003): users connect third-party accounts via OAuth; tokens are stored in an AES-256-GCM vault. The assistant gains live tools (mcp__<provider>__<tool>) — governed by capability (workspace vs personal connectors, an admin allow-list, custom-MCP = admin-only, sensitive-action gating). ┌─────────────────────────────────┐
│ Flutter / Web Client │
│ Riverpod · STOMP · WebRTC │
└────────┬─────────────────┬──────┘
│ REST/HTTP │ WS/STOMP (port 8080)
▼ ▼
┌──────────────────┐ ┌─────────────────────────────────┐
│ auth-service │ │ chat-service │
│ NestJS (3001) │ │ Spring Boot 3 (8080) │
└────────┬─────────┘ └────────┬───────────┬────▲───────┘
│ │ REST/ │ │ STOMP
│ JWT │ GridFS │ │ Broadcast
│ Sign ▼ │ │
│ ┌──────────────┐ │ │
│ │ MongoDB │ │ │
│ │ (db:platform)│ │ │
│ └──────────────┘ │ │
│ │ │ Redis pub/sub
│ │ │ ai:response:{id}
│ RabbitMQ (5672) │ ┌─┴────────────┐
│ ┌──────────────┐ │ │ Redis (6379) │
│ │ ai.requests │◄──┘ │ ai:response │
│ │ queue (AMQP) │ │ kb:process │
│ └──────┬───────┘ │ presence │
│ │ └──────────────┘
└──────────────────────────────►│ (JWT validate) ▲
▼ │
┌─────────────────────────────────┐
│ ai-service │
│ NestJS (Claude + Voyage AI) │
└──────┬──────────────────┬───────┘
│ Embedding │ Vector Search
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Voyage AI │ │ Qdrant DB │
│ Embeddings │ │ (port 6333) │
└──────────────┘ └──────────────┘
@AI in conversation → Client sends message to chat-service → chat-service persists the message and publishes a job (with the last 20 messages of context) to the RabbitMQ ai.requests queue → ai-service consumes the job via AMQP, recalls relevant memory facts and KB chunks from Qdrant, routes the request to a model tier (Haiku / Sonnet / Opus by message size and history), calls the Anthropic Claude Streaming API → stream chunks are published to Redis ai:response:{conversationId} → chat-service listens on that Redis channel and forwards each chunk down the STOMP socket to the client in real time.chat-service stores the file in GridFS and publishes metadata to Redis kb:process → ai-service downloads the file, extracts text, chunks it, requests Voyage AI embeddings (voyage-4-lite, 1024 dims by default — KB_EMBEDDING_MODEL), and upserts them to Qdrant (scanned PDFs / images go through Claude vision first) → ai-service updates the status to done and notifies the client over WebSocket.connector-service (:3003) runs OAuth and stores tokens in the encrypted vault → during an AI request, ai-service asks connector-service's internal API for the user's permitted tools and merges them into the agent loop → when the model calls a tool, ai-service proxies it back through connector-service, which enforces RBAC (capability + allow-list + sensitive-action gates) and executes it against the third party (remote MCP for Notion/custom, REST adapter for Google).platform/
├── apps/
│ ├── server/
│ │ ├── auth-service/ # NestJS (:3001) — identity, tokens, enterprise RBAC
│ │ │ └── src/modules/ # auth (password/OTP/OAuth/OIDC SSO), users, friends,
│ │ │ # workspace (depts/roles/members), admin, audit,
│ │ │ # ai-context, notifications, firebase, Email
│ │ ├── chat-service/ # Spring Boot 3 (:8080) — messaging engine
│ │ │ └── src/main/java/com/platform/chatservice/
│ │ │ ├── config/ # STOMP, security, RabbitMQ, Redis
│ │ │ ├── controller/ # REST: messages, conversations, uploads, KB, calls,
│ │ │ │ # reminders, external bots, AI memory/persona, usage
│ │ │ ├── websocket/ # STOMP handlers
│ │ │ ├── service/ # messages, AI publish/stream, cluster broker, FCM,
│ │ │ │ # BotFactoryClient/ExternalBotService, reminders, calls
│ │ │ ├── security/ # JWT + STOMP channel interceptor
│ │ │ └── model/ repository/ dto/ domain/ exception/
│ │ ├── ai-service/ # NestJS (:3002) — AI pipeline
│ │ │ └── src/
│ │ │ ├── ai/ # Claude streaming, model router, agentic tool loop
│ │ │ ├── memory/ # long-term facts (MongoDB + Qdrant ai_memory)
│ │ │ ├── kb/ # extraction, vision, chunking, embeddings, rerank
│ │ │ ├── tools/ # static tools + MCP connector client
│ │ │ ├── skills/ persona/ ai-context/ settings/
│ │ │ ├── scheduler/ # daily digest cron (reminders are swept by chat-service)
│ │ │ ├── call/ # AI notetaker summaries
│ │ │ ├── usage/ retention/ session/ conversation/ bot/
│ │ │ ├── rabbitmq/ redis/
│ │ │ └── eval/ # eval gate for AI regressions (ai-eval.yml)
│ │ └── connector-service/ # NestJS (:3003) — MCP connectors
│ │ └── src/
│ │ ├── catalog/ # built-in connectors (Notion, Gmail, Calendar, Drive)
│ │ ├── oauth/ # OAuth start/callback per provider
│ │ ├── vault/ # AES-256-GCM token vault
│ │ ├── mcp/ adapters/ # remote-MCP + Google REST adapters
│ │ ├── connections/ # user connections + custom MCP CRUD
│ │ ├── directory/ bot/ audit/
│ │ └── internal/ # service-to-service tools API (ai-service)
│ ├── client/ # Flutter 3 — Android / iOS (Riverpod, GoRouter)
│ │ └── lib/
│ │ ├── core/ # api, config, l10n, providers, router, services, theme, utils, widgets
│ │ ├── features/ # auth, home, chat, friends, profile, settings, notifications,
│ │ │ # assistant, ai_hub, ai_context, skills, integrations,
│ │ │ # reminders, admin, help
│ │ └── l10n/ # app_*.arb (7 languages)
│ └── web/ # Next.js 16 — web client
│ ├── app/ # (auth), (legal), (main) route groups + api/
│ ├── components/ # ui (shadcn), chat, call, ai, admin, integrations, layout, …
│ ├── lib/ # api, stomp, store (Zustand), webrtc, hooks, auth, …
│ └── messages/ # next-intl *.json (7 languages)
├── packages/
│ └── database/src/
│ ├── mongo/ # shared Mongoose schemas (User, Workspace, Department, Role…)
│ ├── redis/ # shared Redis module
│ ├── rbac/ # capability catalog + preset role matrix (single source of truth)
│ └── auth/ # shared JwtAuthGuard + @RequirePermission (all NestJS services)
├── infra/docker-compose/
│ ├── compose.yml # local: Mongo, Redis, RabbitMQ, Qdrant, Jaeger + 4 services
│ ├── compose.mini.yml # production host: 4 services + Qdrant + Caddy + cloudflared
│ ├── compose.prod.yml # self-host: full stack incl. data + web behind Caddy
│ ├── Caddyfile, Caddyfile.mini
│ └── bootstrap.sh # self-host: create .env + generate secrets
├── scripts/
│ ├── ci/ # check-env-parity, check-env-leaks, check-dev-only
│ ├── mini/up.sh # start the production host + Cloudflare Tunnel
│ └── migrations/
├── docs/ # api-spec, architecture, environments, observability,
│ # decisions, roadmap, superpowers/ (specs, plans, handoffs)
└── pnpm-workspace.yaml
Full instructions — prerequisites, which branch to clone, what to ask the owner for, the feature workflow — are in ONBOARDING.md. The short version:
git clone https://github.com/MITOM06/platform.git && cd platform
git checkout dev # dev = main + the local-env commits; feature branches cut from here
pnpm install
cp infra/docker-compose/.env.example infra/docker-compose/.env
cp apps/server/auth-service/.env.example apps/server/auth-service/.env
cp apps/web/.env.example apps/web/.env.local
./scripts/dev/up.sh --seed # whole stack in Docker, seeded, web on :3000
Editing one backend service with hot reload instead? That is path B in
ONBOARDING — start the infrastructure by name, because compose.yml holds
the four services too and a bare up -d starts them as well:
docker compose -f infra/docker-compose/compose.yml up -d \
mongo mongo-setup redis rabbitmq qdrant jaeger
Each service's .env.example is the source of truth for its variables — they are
kept in sync with the code by scripts/ci/check-env-parity.sh, so read those
rather than a list in this file.
| Deployment | Compose file | Env template |
|---|---|---|
| Local development | infra/docker-compose/compose.yml | infra/docker-compose/.env.example |
| Production (single host + tunnel) | infra/docker-compose/compose.mini.yml | infra/docker-compose/.env.mini.example |
| Self-host (one company, one stack) | infra/docker-compose/compose.prod.yml | infra/docker-compose/.env.example + infra/docker-compose/bootstrap.sh |
Local and production differ by configuration only; how a build moves between them — one variable per client — is docs/environments.md.
Two invariants worth knowing before you start: JWT_ACCESS_SECRET must be
identical across all four services, and MongoDB is on port 27018 locally, not
27017.
End-to-end distributed tracing is wired through chat-service and ai-service via OpenTelemetry. A single @AI request produces a unified trace spanning chat-service (publish) → RabbitMQ → ai-service (agentic loop) → Redis → chat-service (STOMP deliver), all visible in Jaeger at http://localhost:16686.
See docs/observability.md for the full propagation protocol, span names, and environment toggles.
Three deployment shapes, all from the same commit — they differ by configuration only (docs/environments.md):
| Backend | Web | |
|---|---|---|
| This project today | one host behind a Cloudflare Tunnel, compose.mini.yml — the four services + Qdrant + Caddy; Mongo/Redis/RabbitMQ stay managed (arm64 images built by .github/workflows/build-mini-images.yml) | Vercel, auto-deploy on push to main |
| Cloud Run | .github/workflows/deploy.yml — still triggers on push to main, no longer the primary backend | Vercel |
| Self-host (a customer) | compose.prod.yml — the whole stack, data included, behind Caddy on one domain | served by the same Caddy |
Deployment model (by design): PON is self-hosted, one deployment per company. Each customer
runs their own instance; companies are isolated at the infrastructure level (no shared multi-tenant
database). One deployment = one Workspace. This is the strongest isolation and the basis of the
enterprise security posture — not a limitation. A turnkey self-host kit (bootstrap runbook + optional
Helm) is on the roadmap; today the docker compose stack + per-service .env is the deployment unit.
Operational notes:
WORKSPACE_NAME + BOOTSTRAP_OWNER_EMAIL; the first boot seeds the
workspace, preset roles, and assigns the Owner.AI_MONTHLY_TOKEN_LIMIT (default 500,000 tokens). Adjust
in apps/server/ai-service/.env to match your Anthropic plan.ANTHROPIC_SIMPLE_MODEL / ANTHROPIC_MID_MODEL / ANTHROPIC_COMPLEX_MODEL
(defaults claude-haiku-4-5 / claude-sonnet-4-6 / claude-opus-4-8); set
ANTHROPIC_ROUTER_ENABLED=false to always use ANTHROPIC_MODEL.@AI / PON AI to any direct or group conversation. Responses are streamed token-by-token directly to the UI.MEMORY_EXTRACT_EVERY) the assistant extracts facts about the user, deduplicates them, and stores them in MongoDB + Qdrant (ai_memory); the most relevant facts are recalled per request with recency decay, and expire after 180 days (AI_MEMORY_TTL_DAYS).COHERE_API_KEY), after a low cosine recall floor (≥ 0.2, KB_SCORE_THRESHOLD) — voyage-4-lite scores real answers as low as ~0.26, so a precision cut on raw cosine drops them.@AI.Full build state and roadmap (P0–P8 enterprise foundation, connectors, group bot, self-host kit, SSO — all complete) live in docs/superpowers/PON-ENTERPRISE-HANDOFF.md.
MIT © Trần Phúc Khang — see LICENSE.