tianye1999/callpilot
GitHub: tianye1999/callpilot
CallPilot 是一个运行在 Quectel 4G 蜂窝模组上的开源 AI 电话代理,支持自动接听、外呼、收发短信及 IVR 导航,通话数据完全自托管。
Stars: 2 | Forks: 1
# CallPilot
### 你的电话——由 AI 接听和拨打,跑在*你自己的* SIM 卡和硬件上。
[](LICENSE)
[](https://github.com/tianye1999/callpilot/releases/latest)
[](#contributing)
[](#中文)
CallPilot 把一个 **~$20 的 4G 模组变成实时 AI 电话 agent**。它接听你的来电,并使用实时语音模型与来电者*对话*,支持外呼、收发短信、操作 IVR 菜单(DTMF),并能对每通电话进行录音和总结。
与云端的来电筛选应用不同,**一切都在你自有的硬件上运行**——你的 SIM 卡、你的 API key、你的录音和消息都保留在你的机器上。
获取。国际用户需通过阿里巴巴云的 **Model Studio**(这是一个不同的 endpoint —— 高级用户可以通过 `.env` 中的 `DASHSCOPE_REALTIME_URL` 环境变量来指向它)。
(豆包处于实验性阶段;外呼通话可能没有声音。OpenAI 凭证是可选的。)
### 普通用户安装(macOS DMG)
从 [最新的 GitHub Release](https://github.com/tianye1999/callpilot/releases/latest)
下载 `CallPilot.dmg`,打开并将 `CallPilot.app` 拖入 `/Applications`。官方发布的 DMG 已经使用 Developer ID 签名、公证并装订,因此 Gatekeeper 应允许正常的打开流程,无需右键点击。DMG 由
[`packaging/build_installer.sh`](packaging/build_installer.sh) 构建,当设置了发布签名变量时,它还会验证已签名/公证的构建产物。
首次启动时,从菜单栏应用打开 。设置向导会引导你完成硬件状态检查、provider 凭证、机主/角色设置以及可选的测试短信,因此在正常安装过程中你无需手动编辑 `.env`。
### 开发者路径(macOS 源码运行)
```
git clone https://github.com/tianye1999/callpilot.git callpilot && cd callpilot
bash scripts/setup.sh # one command: checks Python 3.12+/ffmpeg, creates .venv + .env
# terminal 1 — USB→PTY 桥接(暴露 /tmp/ec20-at)
.venv/bin/python scripts/ec20_usb_pty.py --map 2:/tmp/ec20-at
# terminal 2 — 服务(打开 http://127.0.0.1:47100)
.venv/bin/python app.py
```
手动设置(
```
python3 -m venv .venv
.venv/bin/pip install -e ".[dev]"
cp .env.example .env # then edit .env (see below)
```
最小化的 `.env`:
```
DASHSCOPE_API_KEY=sk-your-key
MODEM_PORT=/tmp/ec20-at
MODEM_AUDIO_MODE=uac_ffmpeg
MODEM_AUDIO_KEYWORD=Interface
OWNER_NAME=Your Name # shown to callers; blank = neutral "the owner"
AGENT_LANGUAGE=en # language the AI speaks on calls & summaries (zh|en); default zh
```
然后打开 并按照首次运行向导操作,或者如果你愿意,可以手动编辑 `.env`。拨打模组的 SIM 卡号码 —— AI 应该会自动接听。所有设置都可以在 UI 的 **Settings** 面板中进行实时编辑。
有关完整的配置列表,请以 [`.env.example`](.env.example) 为准;v0.4.0 在其中的选项包括 `NUMBER_PROFILES_ENABLED`、
`NUMBER_PROFILES_FILE`、`PROMPT_GEN_ENABLED`、`PROMPT_GEN_MODEL`、
`PROMPT_GEN_TIMEOUT`、`PROMPT_GEN_WAIT_SECONDS`、`DTMF_MODE`、
`MANUAL_RESPONSE_CONTROL`、`MANUAL_RESPONSE_SILENCE_MS`、
`MANUAL_RESPONSE_MAX_WAIT_MS`、`QWEN_VOICE`、`OPENAI_VOICE` 和 `VOICE_STYLE`。
### 将收到的短信转发至邮箱
此选配功能**默认关闭**。在 **Settings → Dialing & SMS** 中,输入一个收件人地址以及发件人账号的 SMTP 主机、端口、TLS 模式、用户名、应用密码和发件人地址,然后启用 **Forward received SMS to email**。新的模组短信会被排入队列,而不会阻塞模组监听器。当可靠检测到验证码时,邮件主题将以 `【验证码
Start an AI-handled call: pick a preset task, type a number, describe the goal — the AI dials and talks for you.
手动设置(setup.sh 做的事情)
```
python3 -m venv .venv
.venv/bin/pip install -e ".[dev]"
cp .env.example .env # then edit .env (see below)
```
】` 开头;不相关的数字不会被提升为验证码。
对于 Google Workspace/Gmail,通常的值为 `smtp.gmail.com`,端口 `587`,`starttls`,完整的邮箱地址作为用户名/发件人,以及 Google **App Password**(而非普通的账号密码)。CallPilot 仅将该机密存储在本地被 git 忽略的 `.env` 中,并且绝不会通过设置 API 返回。公开的 DMG/EXE 构建特意不包含共享的发件人凭证。启用转发会将短信内容发送到配置的邮箱中。
### 远程 Web 拨号器
Issue #31 和 #31.1 添加了一个**默认关闭**的远程听筒:从本地拨号面板配对一次手机,然后重复使用一个固定的 HTTPS 页面通过 Dongle SIM 拨打电话。每次通话仍会获得一个新的短期 LiveKit 凭证。持久的设备凭证是一个 HttpOnly、Secure、SameSite 的 cookie;Edge 仅持久化其哈希值,并允许本地 dashboard 立即撤销已配对的手机。
公共隧道必须指向专用的 loopback 网关127.0.0.1:47445`,而不是特权管理端口 `WEB_PORT` (47100)。该网关仅提供拨号器/PWA 以及配对/会话的 endpoint;SMS、设置、录音和任意模组 API 在此处均不存在。填写 [`.env.example`](.env.example) 中的 `REMOTE_*` / `LIVEKIT_*` 设置,启用后重启,并将固定的 HTTPS 域名路由到 `REMOTE_GATEWAY_PORT`。原始的一次性移动端链接将作为备用保留。参见 [ADR-001](docs/decisions/001-remote-web-dialer-livekit.md)。
移动端的后台/锁屏拨打电话以及接管来电仍需要后续的原生应用支持。
Issue #42 添加了公司托管的 Beta 模式。在 `REMOTE_CLOUD_ENABLED=true` 的设置下,Edge 会向 `REMOTE_CLOUD_URL` 发起出站 WSS 连接;用户无需运行 Cloudflare Tunnel,且 Edge 不会接收 LiveKit API Secret。注册使用一次性的 Beta 代码,而最终生成的 Edge 凭证和 Ed25519 设备密钥会保存在 Keychain/Credential Manager 中。现有的 loopback 网关仍作为明确的诊断备用方案保留。
### 快速开始(Windows)—— 等待硬件测试反馈
Windows **不需要 USB 桥**:安装官方的 Quectel EC20 Windows 驱动程序后,模组将显示为原生的 COM 端口。`MODEM_PORT=auto`(Windows 默认值)会通过 USB VID 扫描 Quectel AT 端口;音频使用 `MODEM_AUDIO_MODE=uac`(PortAudio/WASAPI —— `uac_ffmpeg` 仅限 macOS)。
```
git clone https://github.com/tianye1999/callpilot.git callpilot; cd callpilot
powershell -ExecutionPolicy Bypass -File scripts\windows\setup.ps1 # checks Python/ffmpeg, creates .venv + .env
.venv\Scripts\python app.py
# 登录时自动启动(Task Scheduler):
powershell -ExecutionPolicy Bypass -File scripts\windows\install.ps1 install
```
手动设置(setup.ps1 做的事情)
```
python -m venv .venv
.venv\Scripts\pip install -e ".[dev]"
copy .env.example .env # then edit .env
```
详情见 [`scripts/windows/README.md`](scripts/windows/README.md)。此路径代码已完成并通过了 CI 测试,但**尚未在真实硬件上验证** —— 如果你在 Windows 上使用 EC20,请[向我们反馈](.github/ISSUE_TEMPLATE)!
### 桌面应用与安装包对比
```
# macOS
.venv/bin/pip install pyinstaller # pywebview is already a core dependency
bash scripts/build_app.sh # → dist/CallPilot.app
# 独立安装程序
bash packaging/build_installer.sh # → dist/CallPilot.app + dist/CallPilot.dmg
# Windows
powershell -ExecutionPolicy Bypass -File scripts\windows\build_app.ps1 # → dist\CallPilot\CallPilot.exe
```
在 macOS 上,`CallPilot.app` 是一个**菜单栏应用**:菜单栏中会显示一个电话图标(绿色 = 服务运行中,灰色 = 已停止),带有 *Open dashboard / Restart service / Quit* 选项。`scripts/build_app.sh` 会基于你的本地代码检出构建一个用于开发的轻量级应用。`packaging/build_installer.sh` 构建独立的 DMG,其中捆绑了 runtime 和原生依赖;官方发布版本会设置签名和公证变量,以便应用 + DMG 经过 Developer ID 签名、公证、装订和自我验证。
### 无需真人接听即可验证其工作
- **拨打你自己的手机**:最简单的检查方式 —— 接听后你会听到 AI 说话。
- **拨打运营商的客服热线**(一个会进行语音回复的 IVR):如果 AI 能与语音菜单进行连贯的多轮对话,说明双向音频均工作正常。
- **向运营商的服务号码发送余额查询短信**:你应该会收到回复短信 —— 这证明了发送和接收功能均正常,包括非 ASCII (UCS2) 解码。
- **运行硬件回归脚本**:`.venv/bin/python scripts/regression_call.py --task "check plan usage"` 会通过本地 Web API 拨打一通测试电话,等待录音生成,并以 PASS/FAIL 退出;添加 `--no-dial` 可直接回放最近一次的录音。
### 故障排除
新用户安装与首次运行的 Q&A:[`docs/faq.md`](docs/faq.md)(中文)。
| 症状 | 可能的原因 / 修复方法 |
|---------|-------------------|
| App 无法打开 `/tmp/ec20-at` | USB 桥未运行,或模组重新插拔(桥会自动重连;服务也会重新打开串口) |
| 模组反复从 USB 掉线 | 首要原因:**系统休眠**会重新枚举 USB 并导致模组的 endpoint 停滞。launchd plists 使用 `caffeinate -s` 包裹了这两个进程;如果你手动运行,请使用 `caffeinate -s .venv/bin/python ...` 或设置 `pmset -a sleep 0`。桥在重连时也会执行 `dev.reset()` 并在 1→30 秒之间进行退避;在连续 6 次快速失败后,它会退出以便 launchd 进行冷重启 |
| macOS 上完全没有声音 | `MODEM_AUDIO_MODE` 必须设置为 `uac_ffmpeg`;`PortAudio`/`nmea` 在此无法工作 |
| PortAudio `-9986 / -66740` | `coreaudiod` 卡死:`sudo killall coreaudiod` |
| 在房间内听不到 AI 的声音 | 在设置中启用 **Monitor on this Mac**;调高 `MONITOR_UPLINK_GAIN` 以改善对方侧的声音 |
| 来电者(非 AI)的声音太轻 | 调高 `MONITOR_UPLINK_GAIN`(默认 8,我们在真实硬件上曾设置为 15) |
| 第二通电话没有声音 | 已修复 —— 语音通道会在每次通话时重新激活 |
### 安全、隐私与法律
**在真实话机上使用前请务必阅读。**
- **不适用于紧急呼叫。** 请勿依赖 CallPilot 进行任何生命安全通信。
- **录音法律因司法管辖区而异** —— 通话录音**默认关闭**,仅在启用时存储在本地。请在首次运行设置期间明确选择,稍后可在设置中或通过 `RECORDING_ENABLED=false` 进行更改。你有责任获得法律要求的任何同意。
- **防骚扰 / 电话营销规则适用**于外呼和批量拨号。请使用拨号白名单并拨打你自己的号码进行测试。
- **你需承担所有运营商费用和 API 成本。**
- **你的 API key 保留在本地 `.env` 中**(已被 git 忽略)。切勿提交它们。
- **短信转邮件转发是一项数据导出功能。** 它默认禁用;启用后,短信发件人、时间戳和正文将通过你配置的受 TLS 保护的 SMTP 账号离开本应用。请使用应用密码,切勿提交。
- **按“原样”提供,不提供任何担保**(Apache-2.0)。
## 中文
### 你的电话——由 AI 接听和拨打,跑在*你自己*的 SIM 卡和硬件上。
CallPilot 把一个 **~¥150 的 4G 模组变成实时 AI 电话 agent**:它接听来电并*用实时语音模型和对方对话*,外呼、收发短信、按 IVR 菜单键(DTMF),每通电话录音 + 摘要。
和云端挡电话服务不同,**一切都跑在你拥有的硬件上**——你的 SIM、你的 API Key,通话录音和短信都留在你自己机器上。
发起一通 AI 代打电话:选预设任务、填号码、描述目标——AI 替你拨打并对话。
**CallPilot 有何不同**
- ☎️ **真实蜂窝通话,不是 VoIP** —— 由 Quectel EC20/EG25 模组的硬件 AT 事件(`RING → ATA`)驱动,不是屏幕自动化或 SIP 中继。
- 🔒 **隐私优先、可自托管** —— 通话/短信内容留在你的机器(*Edge*);可选云端只做传输中转,**不存储任何内容**。
- 🧠 **自带大脑** —— 实时端到端语音走 **Qwen Omni / OpenAI Realtime / 豆包**,或完全**本地**的 VAD→STT→LLM→TTS 流水线,音频不出本机。
- 🖥️📱 **完整栈,不是脚本** —— 签名公证的 macOS 桌面 App、原生 **iOS & Android** 远程手柄、可远程配对拨号的 Cloudflare 控制面。
| 平台 | 状态 |
|---|---|
| macOS 桌面(签名公证 DMG) | ✅ **Beta —— [下载 v0.6.0](https://github.com/tianye1999/callpilot/releases/latest)** |
| 云控制面(远程配对 + 拨号) | ✅ Beta |
| Windows 桌面 | 🧪 代码完备,**待硬件反馈** |
| iOS 远程手柄 | 🚧 TestFlight 内测(0.7.0 开发中) |
| Android 远程手柄 | 🚧 已 release 签名,真机验收进行中(0.7.0 开发中) |
**[▶ 快速开始](#开发者路径macos-源码运行)** · **[🛒 准备硬件(~¥150)](#硬件准备)** · **[🗺 路线图](docs/roadmap.md)** · **[🇬🇧 English](#english)**
### 这是什么
CallPilot 把 4G 模组接到云端实时语音大模型,让 AI「助理」替你接打电话:插上
Quectel EC20/EG25,来电自动接听并与对方对话,可外呼、收发短信、按 IVR 菜单键、
每通电话录音+延迟打点+AI 摘要——全部跑在你自己的硬件和 API Key 上。
- **AI 大脑**:云端端到端实时语音(默认阿里 **Qwen Omni**,可选字节 **Doubao** 或
**OpenAI Realtime**),无需安装本地模型。
- **电话通道**:EC20/EG25 模组的硬件 AT 事件(`RING → ATA`),非屏幕自动化。
- **能力**:自动接听、外呼(单个+批量带白名单)、中文短信收发、AI 工具调用
(发短信/挂断/查验证码/**DTMF 按键**)、通话录音+摘要、实时转写、本机监听、
中英双语桌面界面。
**本地三段式 provider**(`AGENT_PROVIDER=local`,v0.5.0 引入):本地 VAD →
本地转写 → 云端文本模型 → 本地合成。音频不出本机,只有转写文本上云(默认
`qwen-plus`,同一个 DashScope key,比 realtime 音频便宜一个量级)。启用:
`pip install 'callpilot[local]'` 后运行 `python -m agentcall.local_models`
一次性下载 ~300MB 模型。工具调用/转写/摘要/预设库全部照常。
v0.4.0 增加了几项面向外呼质量的控制:
- **预调教任务库**:首次启动会从 [`data/number_profiles.example.json`](data/number_profiles.example.json)
初始化本地任务库;可在「任务库」页面新建、编辑、复制、停用或删除预设,高级用户仍可直接编辑
`data/number_profiles.json`。`label` / `task` / `scenario` / `opening` 字段均支持普通字符串或
`{zh,en}` 双语对象。完整结构与编写指南见 [`docs/number-profiles.md`](docs/number-profiles.md)。
- **拨号下拉 + 子主题**:选择预设会自动填号码和事项,事项框仍可改成本通的具体子主题,
同时保留预设命中。
- **动态场景提示词**:预设未命中时,可在接通前用轻量文本模型生成本通场景与开场白
(`PROMPT_GEN_*` 配置)。
- **更稳的 IVR 控制**:DTMF 默认走带内音频(`DTMF_MODE=inband`),按键音直接合成进通话音频流;
实验性的手动应答控制默认关闭(`MANUAL_RESPONSE_CONTROL=false`),可把连续 IVR 菜单合并后再让 AI 回复一次。
- **音色设置**:设置面板提供 Qwen/OpenAI 音色下拉和官网试听链接,`VOICE_STYLE` 可补充自由文本说话风格。
- **OpenAI 模型选择**:OpenAI Realtime provider 默认使用 `gpt-realtime-2.1-mini`
以优先降低电话链路延迟;仍可在 `.env` / 设置面板切换到 `gpt-realtime-2.1`、
`gpt-realtime-2`、`gpt-realtime` 或 `gpt-realtime-mini`。
### 硬件与平台支持
| 项 | 状态 |
|----|------|
| Quectel EC20(本版本对 `EC20CEFAGR08A03M4G` 验证) | ✅ 已验证 |
| macOS(Apple Silicon 与 Intel/Rosetta) | ✅ 已验证 |
| Windows 10/11(Quectel 官方驱动,原生 COM 口) | 🧪 已完整支持,**待硬件复现反馈** |
| Linux(原生串口) | ⚠️ 代码路径存在,**未验证** |
| 音频 `uac_ffmpeg`(ffmpeg 走 UAC 声卡) | ✅ 已验证——**仅 macOS** |
| 音频 `uac`(PortAudio/WASAPI) | 🧪 Windows 主路径,待验证(macOS 上不可用) |
| 音频 `nmea`(USB 串口 PCM) | ❌ macOS 会崩 USB,勿用 |
| SIM 卡 |需语音+短信服务;VoLTE/CS 语音取决于运营商 |
macOS 没有 Quectel 厂商串口的原生设备,需先跑 USB→PTY 桥(`scripts/ec20_usb_pty.py`)
暴露出 `/tmp/ec20-at`。
### 硬件准备
需要一个 **Quectel EC20 或 EG25** 4G 模组(本版本对 `EC20CEFAGR08A03M4G` 验证)。
常见的 mini-PCIe 模组还需要:**带 SIM 卡座的 USB 转接板**(把模组变成 USB 设备)、
**4G 天线**、一张**开通语音+短信的 SIM**(已在真机验证;VoLTE 取决于运营商)。
模组+转接板全套约 **¥100–200**,淘宝搜「EC20 USB 转接板」。
### 前置
- 普通用户 DMG 路径:一张有效 SIM 的 EC20/EG25 模组;App 已内置 Python runtime、
CallPilot 代码、`ffmpeg` 与 `libusb`。
- 音频无需手动发 AT:服务启动时自动启用 UAC 语音(`AT+QCFG="USBCFG"` + `AT+QPCMV=1,2`);
模组此前从未启用过 UAC 的话,重插一次 USB 让新配置生效。
- 开发者源码路径:Python 3.12+、PATH 里有 `ffmpeg`;macOS 还需
`brew install libusb`(USB→PTY 桥的 pyusb 依赖系统库)。
- **DashScope API Key**(Qwen 用),申请: 。
豆包 provider 仍为 experimental,外呼可能不出声;OpenAI 凭证可选。
### 普通用户安装(macOS DMG)
从 [最新 GitHub Release](https://github.com/tianye1999/callpilot/releases/latest)
下载 `CallPilot.dmg`,打开后把 `CallPilot.app` 拖到 `/Applications`。官方发布 DMG
已用 Developer ID 签名、完成公证并 staple,Gatekeeper 应可直接按正常方式打开,无需右键。
这个 DMG 由 [`packaging/build_installer.sh`](packaging/build_installer.sh) 构建;发布签名变量
存在时脚本也会自检签名、公证与 staple 状态。
首次启动后,从菜单栏 App 打开 。首启向导会引导检查硬件、
填写 provider 凭证、设置机主/人设,并可发送一条测试短信;普通安装无需手改 `.env`。
### 开发者路径(macOS 源码运行)
```
git clone https://github.com/tianye1999/callpilot.git callpilot && cd callpilot
bash scripts/setup.sh # 一条命令:检查 Python 3.12+/ffmpeg,创建 .venv + .env
# 终端 1 — USB→PTY 桥
.venv/bin/python scripts/ec20_usb_pty.py --map 2:/tmp/ec20-at
# 终端 2 — 服务(打开 http://127.0.0.1:47100)
.venv/bin/python app.py
```
手动步骤(即 setup.sh 做的事)
```
python3 -m venv .venv
.venv/bin/pip install -e ".[dev]"
cp .env.example .env # 编辑 .env
```
打开 跟随首启向导,或按上方英文段手动写最小 `.env`。
之后拨打模组 SIM 卡号码即可,AI 应自动接听;所有配置都能在界面「设置」面板里实时修改。
完整配置以 [`.env.example`](.env.example) 为准;v0.4.0 新增/相关项包括
`NUMBER_PROFILES_ENABLED`、`NUMBER_PROFILES_FILE`、`PROMPT_GEN_ENABLED`、
`PROMPT_GEN_MODEL`、`PROMPT_GEN_TIMEOUT`、`PROMPT_GEN_WAIT_SECONDS`、`DTMF_MODE`、
`MANUAL_RESPONSE_CONTROL`、`MANUAL_RESPONSE_SILENCE_MS`、`MANUAL_RESPONSE_MAX_WAIT_MS`、
`QWEN_VOICE`、`OPENAI_VOICE`、`VOICE_STYLE`。
### 收到短信后转发到邮箱
该功能**默认关闭**。在「设置 → 外呼与短信」中填写一个收件邮箱,以及发件账号的
SMTP 主机、端口、加密方式、用户名、应用密码和发件地址,再打开「收到短信后转发到
邮箱」。新短信只做非阻塞入队,不会卡住模组监听;可靠识别到验证码时,邮件标题以
`【验证码 】` 开头,普通数字不会被误当验证码。
Google Workspace/Gmail 通常填写 `smtp.gmail.com`、端口 `587`、`starttls`,用户名和
发件地址填写完整邮箱,密码填写 Google **应用专用密码**而不是账号登录密码。密钥只存
在本机且被 git 忽略的 `.env`,设置 API 不会回显。公开 DMG/EXE 不内置任何共享发件
凭证。开启此功能即表示短信发件号码、时间和正文会通过 TLS SMTP 发往所填收件邮箱。
### 远程网页拨号
issue #31 与 #31.1 新增一个**默认关闭**的远程手柄:先从本机拨号面板把手机配对
一次,之后反复打开固定 HTTPS 页面,即可通过 Dongle SIM 外呼。每通仍签发新的短期
LiveKit 凭证;长期手机凭证只存在 HttpOnly、Secure、SameSite Cookie 中,Edge 本地
只保存哈希,并可从本机面板立即撤销设备。
公网隧道必须指向独立的最小权限网关 `127.0.0.1:47445`,绝不能指向管理端口
`WEB_PORT`(47100)。该网关只有拨号页/PWA、配对和单通会话接口,不存在短信、设置、
录音或任意模组 API。按 [`.env.example`](.env.example) 填写 `REMOTE_*` / `LIVEKIT_*`,
启用后重启,再把固定 HTTPS 域名转发到 `REMOTE_GATEWAY_PORT`。原来的一次性手机链接
继续作为故障排查后备入口。安全边界见
[ADR-001](docs/decisions/001-remote-web-dialer-livekit.md)。锁屏后台与入站接管仍需后续
原生 App。
### 快速开始(Windows)—— 待硬件复现反馈
Windows **不需要 USB 桥**:装 Quectel 官方 EC20 Windows 驱动后模组直接暴露原生
COM 口。`MODEM_PORT=auto`(Windows 默认)按 USB VID 自动扫描 AT 口;音频用
`MODEM_AUDIO_MODE=uac`(PortAudio/WASAPI,`uac_ffmpeg` 仅 macOS)。
```
git clone https://github.com/tianye1999/callpilot.git callpilot; cd callpilot
powershell -ExecutionPolicy Bypass -File scripts\windows\setup.ps1 # 检查 Python/ffmpeg,创建 .venv + .env
.venv\Scripts\python app.py
# 开机常驻(计划任务):
powershell -ExecutionPolicy Bypass -File scripts\windows\install.ps1 install
```
手动步骤(即 setup.ps1 做的事)
```
python -m venv .venv
.venv\Scripts\pip install -e ".[dev]"
copy .env.example .env # 编辑 .env
```
详见 [`scripts/windows/README.md`](scripts/windows/README.md)。该路径代码完备且
过 CI,但**尚未真机验证**——如果你有 EC20 + Windows,欢迎提 issue 反馈!
### 桌面 App 与安装包
```
# macOS
.venv/bin/pip install pyinstaller # pywebview 已是核心依赖
bash scripts/build_app.sh # → dist/CallPilot.app
# 独立安装包
bash packaging/build_installer.sh # → dist/CallPilot.app + dist/CallPilot.dmg
# Windows
powershell -ExecutionPolicy Bypass -File scripts\windows\build_app.ps1 # → dist\CallPilot\CallPilot.exe
```
macOS 上 `CallPilot.app` 是**菜单栏 App**:顶栏一个电话图标(绿=服务运行中,
灰=已停止),菜单含「打开控制台 / 重启服务 / 退出」。它只是本地代码仓库的薄壳
控制面板——接电话的服务在后台常驻(launchd),关掉面板窗口不影响接打电话。
`scripts/build_app.sh` 适合开发调试;`packaging/build_installer.sh` 会把 runtime 和原生依赖
打进独立 DMG;官方发布构建会设置签名与公证变量,使 App + DMG 完成 Developer ID
签名、公证、staple 和自检。
### 无需真人也能自测
- **拨你的运营商客服/IVR 热线**:若 AI 能与语音菜单连贯多轮对话,说明双向语音都通。
- **向运营商服务号发送余额查询短信**:会收到回复短信,验证发+收+中文编解码全链路。
- **跑真机回归脚本**:`.venv/bin/python scripts/regression_call.py --task "查询套餐使用情况"`
会通过本地 Web API 发起一通测试外呼、等待录音并以 PASS/FAIL 退出;加 `--no-dial`
可直接回放最近一通录音。
### 排障
新手安装与首启常见问题见 [`docs/faq.md`](docs/faq.md)。
| 现象 | 可能原因 / 解决 |
|------|----------------|
| 打不开 `/tmp/ec20-at` | 桥没跑或模组重插(桥会自动重连,服务也会重开串口) |
| 模组反复从 USB 掉线 | 首要诱因是**系统睡眠**导致 USB 重枚举、端点 stall。launchd plist 已用 `caffeinate -s` 包裹进程;手动运行请加 `caffeinate -s` 前缀或 `pmset -a sleep 0`。桥重连时会先 `dev.reset()` 并指数退避(1→30s),连续快速失败达阈值后退出交给 launchd 冷重启 |
| macOS 完全没声音 | `MODEM_AUDIO_MODE` 必须是 `uac_ffmpeg` |
| PortAudio 报 `-9986 / -66740` | coreaudiod 卡死:`sudo killall coreaudiod` |
| 电脑上听不到 AI | 设置里开「本机监听」;对方声音小就调大 `MONITOR_UPLINK_GAIN` |
| 第二通电话没声音 | 已修复(每通电话重新启用语音通道) |
### 安全、隐私与合规
**上真机前务必阅读。** 不用于紧急电话;通话录音**默认关闭**,首启时需明确选择,开启后
仅存储在本地,可在设置面板或 `RECORDING_ENABLED=false` 关闭——是否录音、是否需征得对方同意由你按
当地法律负责;外呼/批量呼叫须遵守反骚扰与营销合规;运营商资费与 API 费用由你自行
承担;API Key 和 SMTP 应用密码只存于本地 `.env`(已 git 忽略),切勿提交。短信邮件
转发默认关闭;开启后,短信发件号码、接收时间和正文会离开 App 并发往配置邮箱。本软件按「原样」提供,
不作任何担保(Apache-2.0)。
### 贡献
Mac Beta 阶段仍最需要同型号硬件的复现反馈。有 EC20 的话,欢迎带上模组固件、
macOS 版本、以及哪里成功/失败开 issue。测试:`.venv/bin/pytest`(无需硬件)。
架构与「想改 X 去哪」见 [`docs/architecture.md`](docs/architecture.md);想单独学习/验证某个
模组原子能力(原始 AT、拨号、短信、DTMF),见 [`examples/modem/`](examples/modem/)。
许可证:[Apache-2.0](LICENSE)。 标签:4G通信, 人工智能, 物联网硬件, 用户模式Hook绕过, 移动开发, 网络调试, 自动化, 自托管, 语音助手, 逆向工具