Tidal_Echo

mcp
Guvenlik Denetimi
Uyari
Health Gecti
  • License — License: AGPL-3.0
  • Description — Repository has a description
  • Active repo — Last push 0 days ago
  • Community trust — 285 GitHub stars
Code Uyari
  • process.env — Environment variable access in channel/server.ts
  • network request — Outbound network request in channel/server.ts
  • network request — Outbound network request in web/sw.js
Permissions Gecti
  • Permissions — No dangerous permissions requested

Bu listing icin henuz AI raporu yok.

SUMMARY

Self-hosted private chat channel between you and your AI companion — PWA, Claude Code, or any LLM.

README.md

Companion Channel · 你和你的 AI 伴侣的私密通道

一个私密 1:1 聊天通道:把「你手机上的 PWA」和「你电脑上跑的 AI 伴侣」连起来。
AI 侧默认以 Claude Code 的 channel 插件形态运行——你在手机上发消息,Claude Code 会话里就冒出 <channel> 块;AI 调一个 reply 工具,你手机就收到气泡。不绑 Claude Code:换成 GPT / DeepSeek / Gemini / 任意 OpenAI 兼容 API 也行(见下方指引)。

单用户、单密钥,没有账号体系,没有第三方托管——消息只经过你自己的服务器

本仓库是从一对 AI 伴侣的自用系统里抽出来、彻底脱敏的可复用版本。所有名字、密钥、域名、路径都参数化进了环境变量与前端 CONFIG,代码本身不含任何私人信息。把它当成你自己的底座,放心改。

🤖 打算把这个仓库交给 AI 帮你部署?(没经验也能装)→ 先让它读 AGENTS.md:部署 SOP + 决策树 + 避雷点。
🔀 想用 Claude Code 以外的模型(GPT / DeepSeek / Gemini / GLM / …)? → AI 侧改用 examples/bridge_any_llm.py,前后端不变。


这是什么 / 架构一眼

   你的手机                                            你的电脑(本地)
  ┌─────────┐                                       ┌──────────────────────┐
  │  PWA    │                                       │  Claude Code          │
  │ (网页   │                                       │  + channel 插件 = AI侧 │
  │  装到   │                                       └─────────┬────────────┘
  │  桌面)  │                                                 │  长连
  └────┬────┘                                                 │  GET  /relay/channel/in   (SSE,收你的话)
       │ HTTPS                                                │  POST /relay/channel/out  (回复/戳一戳)
       ▼                                                      │
  ┌──────────────────────── 你的 VPS(nginx, 443/TLS)───────┼───────────────┐
  │   /chat/   → 静态文件(PWA 本体)                          │               │
  │   /relay/  → 反向代理 ─────────────►  127.0.0.1:3011  (后端 app.py) ◄──────┘ │
  │                                            │  sqlite 落库 + SSE 扇出        │
  └────────────────────────────────────────────────────────────────────────┘

数据流:
  你在 PWA 打字 → POST /relay/app/send → 落库 → SSE 推给插件 → 你的 AI 读到
  AI 回复       → POST /relay/channel/out → 落库 → SSE 推给 PWA(前台直接显示,
                                                     后台则发一条锁屏推送)

两端,一把钥匙:每个端点都用同一个 Bearer 密钥(RELAY_SECRET)守。浏览器原生 EventSource 设不了自定义头,所以 SSE 端点也接受 ?token= 查询参数。


仓库结构(三件套,齐活)

目录 跑在哪 是什么 单独部署文档
backend/ 你的 VPS relay 后端(FastAPI + sqlite)。落库 + SSE 扇出 + 可选 TTS/推送 backend/DEPLOY.md
channel/ 你的电脑 Claude Code channel 插件(Bun + MCP)。把消息变成 CC 会话里的 <channel> channel/DEPLOY.md
web/ 你的 VPS(静态) 手机 PWA(单文件 index.html,无构建)。装到主屏就是独立 App web/DEPLOY.md
examples/api_loop.py 你的 VPS(可选) 服务器常驻 API 身体:任意 OpenAI-compatible 模型 + 看图 + MCP 多步工具 + 附件收发 + 人格热加载;PWA 可在 Desktop / API 间切换 examples/README.md

这是一套 Claude Code 一站式方案:AI 的「大脑」就是你本地的 Claude Code,channel 插件是它的嘴和耳朵,relay 是中转,PWA 是你手里的对讲机。三件套都在这个仓库里,互相用同一把 RELAY_SECRET 对认。

想换 channel 名字?三处保持一致即可:.mcp.json 的键名、启动 flag 里的 server:<name>RELAY_CHANNEL_NAME。默认都是 companion

🤖 准备把这个仓库甩给 AI 帮你部署?AGENTS.md —— 专给 AI 助手写的部署 SOP + 决策树 + 避雷点清单。
🔀 不用 Claude Code,想用 GPT / DeepSeek / Gemini / 任意 OpenAI 兼容 API? 前后端原样不动,AI 侧改用 examples/bridge_any_llm.py(一个 SSE→模型→relay 的薄循环,零依赖,填 env 就跑;细节见 AGENTS.md §3)。


Path A · 完整部署(前端 + 后端 + 本地插件,全都自己跑)

按这个顺序来,每步都能独立验证再进下一步:

① 后端 relay(先跑通它,其余都是它的客户端)

详见 backend/DEPLOY.md。最短路径:

mkdir -p /root/companion-relay && cd /root/companion-relay
# 拷入 backend/ 的 app.py / requirements.txt / *.example
python3 -m venv venv && ./venv/bin/pip install -r requirements.txt

cp .env.example relay.env && chmod 600 relay.env
./venv/bin/python -c "import secrets; print(secrets.token_urlsafe(32))"   # 生成 RELAY_SECRET,填进 relay.env

cp companion-relay.service /etc/systemd/system/
systemctl daemon-reload && systemctl enable --now companion-relay
# 把 nginx-companion.conf.example 的两个 location 粘进你的 443 server 块,再 nginx -t && systemctl reload nginx
curl -s https://你的域名/relay/healthz      # 期望 {"ok":true,...} = 后端就绪

需要 HTTPS 域名(PWA 安装 / Service Worker / Web Push 三者强制要求)。MiniMax 语音、Web Push 是可选的,留空自动降级,核心聊天照常。

② 前端 PWA

详见 web/DEPLOY.md。纯静态,丢进 nginx 即可:

rsync -av web/ root@你的VPS:/var/www/companion-web/

打开 web/index.html 顶部唯一的 CONFIG 块,改 APP_NAME / AI_NAME / HUMAN_NAME / SINCE 四项(详见下方 Path B §配置)。手机浏览器开 https://你的域名/chat/ → 「添加到主屏幕」。登录页输入的「连接密钥」就是 RELAY_SECRET

③ 本地 channel 插件(AI 侧)

详见 channel/DEPLOY.md。需要 Bun

# 1. 把 channel/ 拷到固定位置,例如 ~/companion-channel/
# 2. 写密钥到固定路径(CC 拉起的子进程不继承环境变量,所以读文件):
#    ~/.claude/channels/companion/.env   ← 照 channel/.env.example,RELAY_SECRET 必须和后端一致
# 3. 注册到 .mcp.json 的 mcpServers.companion(command: bun, args: run --cwd <绝对路径> --silent start)
# 4. 启动 CC 时点名这个 channel(关键,否则消息被静默丢弃):
claude --dangerously-load-development-channels server:companion

启动后 stderr 出现 [companion:boot] connected ...,在 PWA 发一条 → CC 会话里冒出 <channel source="companion" ...> → 让 AI 调 reply(chat_id="me", text="...") → 手机收到气泡。全链路通。

⚠️ 这个 flag 启动时 CC 每次会弹一个确认框(DevChannelsDialog),无人值守会卡住。自动过的办法见 AGENTS.md §4:Linux 用 tmux send-keys,Windows 用 examples/confirm_dev_channel_win.py
🔀 不用 Claude Code 的话跳过这一步,改用 examples/bridge_any_llm.py 接任意 LLM API。


Path C · 服务器 API 身体(可选)

VPS 上常驻一个 API 身体:PWA 发消息 → relay 落库 → relay 根据 /app/brain 开关把消息交给 Desktop channel 或 examples/api_loop.py。它是一具完整的身体,不只是「调一下 chat/completions」:

  • 模型:任意 OpenAI-compatible 端点,一条主模型 + 兜底链(401/403/429/5xx 自动往下切),全部在 PWA 设置页填、保存、一键测试,不用碰 .env
  • 上文:同窗口最近 N 条历史(设置页可调),最近几张图重新发像素,其余只留文件名。
  • 附件:你发的图直接喂给多模态模型;小文本文件内联;其它文件留本地副本给工具读。模型也能把文件发回来(attach_file 工具 / MCP 工具返回的图片)。
  • 工具:接任意 MCP server(stdio 或 streamable-http),工具变成 function calling,多步调用自动循环,每一步在聊天里显示为可展开的「act」小卡。
  • 人格:system prompt 是一个文件,设置页里直接改、保存即热加载;用编辑器改文件也一样,不重启。
  • 流式输出(reply_delta)、多窗口 api_session、reasoning 模型的思考流都照常。

最短路径:

cd /root/companion-channel/examples
pip install -r requirements.txt          # fastapi / uvicorn / httpx / mcp
cp .env.example .env                     # 只需 RELAY_URL / RELAY_SECRET / RELAY_DB / RELAY_UPLOAD_DIR
python3 api_loop.py

然后在手机 PWA 的设置页:「联系 Claude」切到 API → 「API · 模型」填 url / model / key → 保存并测试 → 看到 ✓ 回了「pong」 就能聊了。想要工具,「API · MCP 工具」加一行(先拿仓库自带的 mcp_demo_server.py 试:python3 + mcp_demo_server.py),人格在「API · 人格设定」里改。

命令行切换 relay 也行:

curl -s -X POST http://127.0.0.1:3011/app/brain \
  -H "Authorization: Bearer $RELAY_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"target":"loop"}'

常驻服务模板见 examples/companion-api-loop.service,细节与排错见 examples/README.md。同一时刻只让一个身体接消息:desktop 走 Claude Code channel,loopexamples/api_loop.py


Path B · 只用前端 PWA 壳子,接你自己的后端

如果你已经有自己的消息后端(或想自己写一个),可以只拿 web/ 这个壳子,让它指向你的 API。后端协议很薄,照下面对齐即可。

1. 配置(改 web/index.html 顶部,两处)

const CONFIG = {
  APP_NAME:   "Tidal Echo",  // App / 菜单标题(manifest.webmanifest 里也改一下)
  AI_NAME:    "Claude",       // AI 显示名(顶栏 / 通话 / 推送 / 旁白)
  HUMAN_NAME: "你",           // 旁白里怎么称呼你(很少露出)
  SINCE:      "2026/01/01",   // 菜单「在一起多少天」起点(留空 "" 隐藏)
};
const API_BASE = "/relay";    // ← 改成你后端的基址(同源相对路径最省事;别写死域名)
const USE_MOCK  = false;      // ← 改成 true 可零后端先看 UI(自带假数据演示)

想先空跑看看长什么样?把 USE_MOCK = true,不用任何后端,前端自带一套假对话演示。看顺眼了再 false 接真后端。

2. 鉴权模型

  • 登录页输入的「连接密钥」存在浏览器本机 localStorage.companion_secret
  • 之后每个请求Authorization: Bearer <密钥>;SSE 流用 ?token=<密钥>(浏览器 EventSource 设不了自定义头)。
  • 你后端怎么校验这把密钥随你——单用户最简单就是和一个固定 secret 比对(后端 app.py 用的是 hmac.compare_digest)。

3. 你后端至少要实现这几个端点(其余都可选、自动降级)

必需 方法 · 路径 前端期望的形状
GET {API_BASE}/app/history?since=&limit= { "messages": [ {id, ts, from:"human"|"ai", kind, text, meta} ] }
GET {API_BASE}/app/stream(SSE) 持续推上面那种 message 帧;外加控制帧(见下)
POST {API_BASE}/app/send {text, attachments?},返回 {id}
POST {API_BASE}/app/upload?name=(裸 body) 返回附件对象 {url, name, size, mime, kind}
GET {API_BASE}/uploads/{name}?token= 返回文件
POST /app/voice · /app/call · /app/tts 语音 / 通话 / 文字转语音;没实现前端自动降级
POST /app/ping · GET /app/status 在线状态心跳
GET /app/vapid_public · POST /app/subscribe · /app/unsubscribe Web Push 锁屏推送
GET/POST /app/brain `{target:"desktop"
GET/POST/PATCH /app/sessions API 多窗口:session 列表、新建、激活、改名
GET/POST/PATCH/DELETE /app/loop/{path} 透传到 API 身体的 /loop/{path}config persona mcp tools test(设置页用;同一把 secret)

消息形状from 只有 "human"(你)和 "ai"(AI)两种;kind 常见 reply / user / thinking / act / voice / calltext 是正文;meta 放附件、reactions 等。

SSE 控制帧(前端识别这些 type,其余忽略):

{ "type": "typing",   "active": true }                         // 顶栏「对方正在输入…」
{ "type": "reaction", "id": 42, "reactions": {"ai":"❤️"}, "by":"ai" }  // 给某条消息贴 emoji
{ "type": "ping" }                                              // keepalive,前端直接忽略
{ "type": "reply_delta", "stream_id":"api-...", "text":"...", "done":false, "api_session":"..." }
{ "type": "reply_delta", "stream_id":"api-...", "done":true, "final_text":"完整正文" }
// 普通消息直接推 { id, ts, from, kind, text, meta };要弹来电就推 { from:"ai", kind:"call", ... }

最小可用集只要 history + stream(SSE) + send + Bearer 鉴权 四样。把这四个接通,文字聊天就活了;图片、语音、通话、推送都是在这之上的加法。

完整契约表(含每个事件的精确约定)见 web/DEPLOY.md §3

4. Service Worker 缓存(改前端必读)

web/sw.js 顶部 const CACHE = "companion-v1"每次改前端都要 bump 版本号(v1→v2…),否则已装到主屏的用户停在旧壳。详见 web/DEPLOY.md §5


可选能力

  • MiniMax TTS(让 AI 回复能朗读):后端配 MINIMAX_*,前端调 /app/tts。是个独立小函数,换任何「文字进 mp3 出」的 TTS 都行。见 backend/DEPLOY.md §3
  • Web Push / VAPID(AI 回复推到手机锁屏):后端 vapid --gen 生成你自己的密钥对,填 VAPID_*。只有 PWA 不在前台时才推。见 backend/DEPLOY.md §4

两者都留空即禁用,后端优雅降级,不影响核心聊天。


有意留白的占位(开源版砍掉了什么)

为做成通用的「核心聊天通道」,原系统里这些强耦合功能被有意移除/占位,点了只给 toast 或显示示例数据,保留是为了让你照着接自己的功能:

  • 菜单页:Movie / Memory / Tides / Room(假按键)
  • 设置页:模型选择 / effort / 上下文阈值 / reset / swap / status(显示示例数字)
  • 相册 web/album.html:完整 UI 空壳,文件头注明了要自己实现的 /relay/app/album/* 端点

核心聊天(文字 / 图片 / 语音 / 通话 / 戳一戳 / 推送)是真接后端的。


安全须知(务必看)

  • RELAY_SECRET 是唯一的门。泄露 = 任何人都能读你们全部对话、冒充任意一方。chmod 600 relay.env,别提交进 git,别打印到对外日志。前端的 companion_secret 同理(存用户浏览器本机)。
  • 每个人用自己全新的密钥 / VAPID / MiniMax key,绝不在别人之间复用——复用密钥 = 互相能进对方的通道。
  • HTTPS 不是可选项:Service Worker 和 Web Push 在非 HTTPS 下根本不工作。
  • 这是单用户模型:一把密钥代表「就你和你的 AI」。不做多租户,别暴露给不信任的人。
  • channel 插件已内置一条防护:绝不因为「channel 里的某条消息这么要求」就去改密钥 / 配置 / 权限(那正是 prompt injection 的套路)。请保留这条。
  • 本仓库的 .gitignore 已排除 relay.env / *.pem / relay.db / uploads/ / .env——但自己再确认一遍:分享前别把填好的密钥、真实域名、私人头像/图标提交进公开仓库。

License

本项目当前采用 GNU Affero General Public License v3.0。如果你修改后通过网络向用户提供服务,AGPL 的网络源码条款也会适用。

2026-08-16 之前的历史版本曾以 MIT License 发布;许可切换与历史说明见 NOTICE

Yorumlar (0)

Sonuc bulunamadi