多台電腦(大腦、手腳、螢幕)、一行加電腦、文件與 make #2

Merged
daniel.w merged 9 commits from daniel.w/multi-device into main 2026-11-04 14:40:38 +00:00
10 changed files with 760 additions and 2 deletions
Showing only changes of commit 867732bd73 - Show all commits

View File

@ -2074,10 +2074,17 @@ computer_use_screenshot、click、mouse_move、mouse_button(按住/放開)
| 邊說邊插話的即時語音 | 原版用 xAI 即時語音服務;AnyBot 的通話模式是輪流說話 |
| 帳單、方案 | 不需要:AnyBot 用你自己的 AI 金鑰 |
| 代你購物與付款 | 刻意不做:會動到你的錢和卡片 |
| 即時同步(同一個帳號的兩台電腦同時改同一個 Bot) | 帳號只做每日加密備份和還原;要 24 小時運作,請把 AnyBot 放在一直開著的主機(第 36 節) |
| 原生 iPhone/Android App | 用手機網頁版,可加到主畫面、收推播通知 |
## 多台電腦:大腦、手腳、螢幕(2026-11-04)
- 帳號裡一台 AnyBot 當大腦(Bot、聊天、記憶、排程都在它上面),其他有視窗的電腦當螢幕(看到、改的都是大腦的 Bot),沒有螢幕的電腦當手腳;同一個帳號的電腦之間即時同步。
- 每台 AnyBot 和帳號伺服器之間保持一條長連線(WebSocket),斷了自動接回;連不到時畫面說明是哪一段。
- 「加一台電腦」給一行 `anybot setup 網址 配對碼`,新電腦不用密碼就接成那個大腦的手腳。
- 看不到圖片的模型:你帳號裡看得到圖的模型會先描述圖片(看圖小幫手)。
- 詳細說明:docs/multi-device.md;技術細節:docs/architecture.md、docs/protocol.md。
## 側邊欄:釘選與分區(照原版重做,2026-11-04)
- 釘選的 Bot 放在最上面的大頭像格子裡(主要 Bot 第一個,有星星)。右鍵「釘選/取消釘選」,或把 Bot 拖進格子(第一次拖時出現「拖曳到此處以釘選」);格子內可拖曳排序;拖到分區會取消釘選並移過去。

109
Makefile Normal file
View File

@ -0,0 +1,109 @@
# AnyBot — build and install from source. make help lists everything.
#
# git clone git@git.30cm.net:daniel.w/AnyBot.git && cd AnyBot
# make build (release)
# make install install for you: ~/Applications/AnyBot.app on a Mac, ~/.local/bin on Linux
# make run open it without installing
CARGO ?= cargo
UNAME := $(shell uname -s)
VERSION ?= $(shell git describe --tags --always 2>/dev/null | sed 's/^v//' || echo dev)
TARGET := $(or $(CARGO_TARGET_DIR),target)
APPDIR ?= $(HOME)/Applications
BIN := $(TARGET)/release/anybot
RELAY := $(if $(CARGO_TARGET_DIR),$(CARGO_TARGET_DIR),relay/target)/release/anybot-relay
.PHONY: all build app relay run serve setup relay-run test deps install uninstall hands server dist clean help
all: build
help:
@echo "AnyBot $(VERSION)"
@echo ""
@echo " make 建置 AnyBot 和帳號伺服器(正式版)"
@echo " make run 直接打開 AnyBot(不安裝)"
@echo " make install 安裝:Mac → $(APPDIR)/AnyBot.app;Linux → ~/.local/bin/anybot 和選單"
@echo " make uninstall 移除安裝的程式(你的 Bot 和聊天不會刪)"
@echo " make deps Linux:安裝系統套件(會問 sudo 密碼)"
@echo " make test 跑測試"
@echo ""
@echo " make serve 不開視窗執行(伺服器、一直開著的電腦)"
@echo " make setup 把這台接到你的 AnyBot(終端機裡的引導畫面)"
@echo " make hands Linux:裝好虛擬螢幕等,再接成大腦的手腳"
@echo " make relay-run 在這台跑帳號伺服器(埠 8080,資料在 ./relay-data)"
@echo " make server 在伺服器上架好帳號伺服器 + 大腦 + HTTPS(Docker)"
@echo ""
@echo " make dist 打包發行檔到 dist/(Mac:.app 的 zip;Linux:三個 tar.gz)"
@echo " make clean 清掉建置結果"
build: app relay
app:
$(CARGO) build --release --bin anybot
relay:
cd relay && $(CARGO) build --release
run: app
$(BIN)
serve: app
$(BIN) --serve
setup: app
$(BIN) setup
relay-run: relay
$(RELAY) --port 8080 --data relay-data
test:
$(CARGO) test --bin anybot
cd relay && $(CARGO) test
deps:
ifeq ($(UNAME),Linux)
sh deploy/linux/install.sh --deps-only
else ifeq ($(UNAME),Darwin)
@xcode-select -p >/dev/null 2>&1 || xcode-select --install
@command -v cargo >/dev/null || echo "需要 Rust:curl --proto '=https' -sSf https://sh.rustup.rs | sh"
else
@echo "Windows:請照 docs/install.md 安裝 Rust 和 Visual Studio Build Tools"
endif
install: app
ifeq ($(UNAME),Darwin)
KEEP_APP=1 sh deploy/mac/bundle.sh $(VERSION)
mkdir -p "$(APPDIR)"
rm -rf "$(APPDIR)/AnyBot.app"
mv dist/AnyBot.app "$(APPDIR)/AnyBot.app"
@echo "已安裝 $(APPDIR)/AnyBot.app(第一次打開:按右鍵 › 打開)"
else
sh deploy/linux/install.sh
endif
uninstall:
ifeq ($(UNAME),Darwin)
rm -rf "$(APPDIR)/AnyBot.app"
else
rm -f "$(HOME)/.local/bin/anybot" "$(HOME)/.local/share/applications/anybot.desktop"
rm -f "$(HOME)/.local/share/icons/hicolor/scalable/apps/anybot.svg" "$(HOME)/.local/share/icons/hicolor/256x256/apps/anybot.png" "$(HOME)/.local/share/icons/hicolor/128x128/apps/anybot.png"
endif
@echo "已移除程式。你的 Bot、聊天和設定還在(要全部刪掉請看 docs/install.md)。"
hands: app
sh deploy/linux/hands.sh --deps
server:
cd deploy/server && ./setup.sh
dist: build
ifeq ($(UNAME),Darwin)
sh deploy/mac/bundle.sh $(VERSION)
else
sh deploy/ci/package-linux.sh $(VERSION)
endif
clean:
$(CARGO) clean
cd relay && $(CARGO) clean
rm -rf dist

74
README.md Normal file
View File

@ -0,0 +1,74 @@
# AnyBot
你自己的 AI Bot:可以聊天,也能在它自己的電腦和你的電腦上幫你做事(檔案、指令、瀏覽器、螢幕操作、例行任務)。每個 Bot 可以用不同的 AI 服務:xAI Grok、OpenAI、Claude、Gemini、OpenRouter、OpenCode Go、Ollama(本機),或任何 OpenAI 相容的服務。
- 桌面 App:macOS、Linux(Windows 可以編譯,還沒有安裝包)
- 手機:網頁版,可加到主畫面、收推播通知
- 多台電腦:一台當**大腦**(保存 Bot、聊天、記憶和排程),其他電腦當**手腳**或**螢幕**
- 用 Rust 和 egui 寫成,一個執行檔就是全部(視窗、無視窗伺服器模式、設定工具)
## 從原始碼安裝
```sh
git clone git@git.30cm.net:daniel.w/AnyBot.git
cd AnyBot
make deps # 只有 Linux 需要:安裝系統套件(會問 sudo 密碼)
make # 建置
make install # Mac → ~/Applications/AnyBot.app;Linux → ~/.local/bin/anybot 和選單
```
需要 Rust(`curl --proto '=https' -sSf https://sh.rustup.rs | sh`)。Mac 另外需要 Xcode Command Line Tools(`make deps` 會提示)。詳細步驟、各平台需求和常見問題:[docs/install.md](docs/install.md)。
常用指令(`make help` 列出全部):
| 指令 | 做什麼 |
|---|---|
| `make run` | 不安裝,直接打開 |
| `make test` | 跑測試 |
| `make serve` | 不開視窗執行(伺服器、一直開著的電腦) |
| `make setup` | 把這台接到你的 AnyBot(終端機裡的引導畫面) |
| `make server` | 在伺服器上架好帳號伺服器、大腦和 HTTPS(Docker) |
| `make dist` | 打包發行檔到 `dist/` |
| `make uninstall` | 移除程式(Bot 和聊天不會刪) |
## 多台電腦一起用
```
手機(網頁) 筆電(螢幕) 沒螢幕的 Linux(手腳)
\ | /
└──── 帳號伺服器(一條一直開著的連線)────┘
|
大腦:Bot、聊天、記憶、排程
```
1. **架帳號伺服器和大腦**:在伺服器上執行 `make server`(只問網域、帳號、密碼);或先用一台 Mac 當大腦([docs/multi-device.md](docs/multi-device.md))。
2. **其他電腦當螢幕**:打開 AnyBot › 設定 › 手機與帳號,登入同一個帳號,在「大腦」選它。
3. **加一台手腳**:手機 AnyBot 右上「⋯ › 加一台電腦」,把出現的那一行貼到那台的終端機:
```sh
anybot setup https://anybot.example.com 469615
```
不用帳號密碼,會設成開機自動執行;沒有螢幕就開一個虛擬螢幕。
## 文件
| 文件 | 內容 |
|---|---|
| [docs/install.md](docs/install.md) | 從原始碼建置和安裝(macOS、Linux、Windows)、更新、移除、常見問題 |
| [docs/multi-device.md](docs/multi-device.md) | 大腦、手腳、螢幕:怎麼架、怎麼接、連不上時看什麼 |
| [docs/architecture.md](docs/architecture.md) | 技術架構:程式分層、資料存放、安全設計、同步怎麼運作 |
| [docs/protocol.md](docs/protocol.md) | 帳號伺服器和 AnyBot 之間的介面與長連線格式 |
| [docs/release.md](docs/release.md) | 發行:打包、Gitea Actions 自動建置、runner 設定 |
| [FEATURES.md](FEATURES.md) | 所有功能的完整說明 |
| [relay/README.md](relay/README.md) | 帳號伺服器的架設細節 |
## 專案結構
```
src/ AnyBot(桌面 App、無視窗模式、anybot setup)
app/ 視窗介面(聊天、設定、電腦、大腦同步的畫面)
relay/ 帳號伺服器(獨立的小程式)
mobile/ 手機網頁(由 AnyBot 和帳號伺服器提供)
box/ Bot 自己的電腦(Docker 容器)
deploy/ 安裝與部署:mac/、linux/、server/、ci/
docs/ 技術文件
```

View File

@ -3,6 +3,7 @@
#
# ./deploy/linux/install.sh build from source, then install
# ./deploy/linux/install.sh --deps also install the system packages (asks for sudo)
# ./deploy/linux/install.sh --deps-only only the system packages (make deps)
#
# Puts the app in ~/.local/bin/anybot, the menu entry in ~/.local/share/applications and the
# icon in ~/.local/share/icons. Run it from the app/ folder.
@ -21,7 +22,7 @@ DEPS_FEDORA="alsa-lib libxkbcommon-x11 mesa-libGL xdg-utils libnotify libsecret
DEPS_ARCH="alsa-lib libxkbcommon-x11 mesa xdg-utils libnotify libsecret espeak-ng zenity xclip wl-clipboard ffmpeg docker noto-fonts-cjk"
BUILD_DEBIAN="build-essential pkg-config libasound2-dev"
if [ "$1" = "--deps" ]; then
if [ "$1" = "--deps" ] || [ "$1" = "--deps-only" ]; then
if command -v apt-get >/dev/null; then
sudo apt-get update && sudo apt-get install -y $DEPS_DEBIAN $BUILD_DEBIAN
elif command -v dnf >/dev/null; then
@ -37,6 +38,8 @@ if [ "$1" = "--deps" ]; then
fi
fi
[ "$1" = "--deps-only" ] && exit 0
OUT="${CARGO_TARGET_DIR:-target}/release/anybot"
if [ -x "$OUT" ] && [ "$1" != "--rebuild" ]; then
echo "Using the existing build ($OUT)."

View File

@ -1,6 +1,7 @@
#!/bin/sh
# Make AnyBot.app from the release build and zip it (run from app/ on a Mac, after
# `cargo build --release`): deploy/mac/bundle.sh 1.2.0 → dist/AnyBot-1.2.0-macos-<arch>.zip
# With KEEP_APP=1 it leaves dist/AnyBot.app instead of zipping it (make install).
# Signed ad hoc (no Apple developer account): the first time, right-click › 打開.
set -e
VERSION=${1:?version}
@ -39,6 +40,9 @@ cat > "$APP/Contents/Info.plist" <<PLIST
</dict></plist>
PLIST
codesign --force --deep -s - "$APP"
if [ "${KEEP_APP:-}" = 1 ]; then
exit 0
fi
ditto -c -k --norsrc --keepParent "$APP" "$OUT/AnyBot-$VERSION-macos-$ARCH.zip"
rm -rf "$APP"
ls -la "$OUT"

126
docs/architecture.md Normal file
View File

@ -0,0 +1,126 @@
# 技術架構
AnyBot 是一個 Rust 程式(`anybot`),用 egui/eframe 畫介面。同一個執行檔有三種跑法:
| 跑法 | 指令 | 用途 |
|---|---|---|
| 視窗 | `anybot` | 桌面 App |
| 無視窗 | `anybot --serve` | 伺服器、一直開著的電腦、手腳:介面邏輯照跑,只是不畫畫面 |
| 設定 | `anybot setup …` | 終端機裡把這台接到帳號(`src/setup.rs`) |
另一個小程式 `anybot-relay`(`relay/`)是帳號伺服器。
## 程式分層
```
┌─────────────── 介面(src/app.rs、src/app/*)───────────────┐
│ 聊天、側邊欄、設定、電腦、檔案預覽、大腦同步(mirror_ui) │
│ 每個畫面一個 App 方法;狀態全部在 App.state(model::State) │
└───────┬───────────────────────────────┬────────────────────┘
│ 送出訊息 │ 手機/其他 AnyBot 的要求
┌───────▼──────────┐ ┌────────▼───────────────────┐
│ 回合(agent.rs) │ │ 遠端(remote.rs) │
│ 呼叫模型、跑工具、 │ │ 手機 API、裝置連結、大腦同步 │
│ 等你核准、整理摘要 │ │ 長連線(tunnel.rs、wire.rs) │
└───┬─────────┬────┘ └──────────────────────────────┘
│ │
┌───▼───┐ ┌───▼──────────────────────────────────────────────┐
│ 模型 │ │ 工具(tools.rs 為主) │
│ provi- │ │ 檔案與指令、Bot 的電腦(cloudbox.rs)、瀏覽器 │
│ ders.rs│ │ (botbrowser.rs、browser.rs、web.rs)、螢幕操作 │
└────────┘ │ (computer.rs、desktop.rs、axread.rs;Linux:x11.rs、 │
│ wl.rs、portal.rs、desk.rs)、Mac App(macapps.rs)、 │
│ 保險庫(vault*.rs)、MCP(mcp.rs)、記憶(memory.rs) │
└──────────────────────────────────────────────────────┘
```
| 檔案 | 做什麼 |
|---|---|
| `model.rs` | 資料:State、Bot、Message、設定;讀寫 `state.json` |
| `agent.rs` | 一個回合:組系統指示、送模型、收串流、跑工具、核准關卡、長對話摘要 |
| `providers.rs` | 各家模型的 API(OpenAI 相容、Anthropic、OpenCode Go…)、串流、重試、看不到圖時的看圖小幫手 |
| `tools.rs` | 檔案、指令、背景指令;指令在 macOS 沙盒裡跑(見「安全」) |
| `policy.rs` | 哪些動作直接做、先問、拒絕 |
| `cloudbox.rs`、`box/` | Bot 自己的電腦:一個 Docker 容器(有桌面、瀏覽器) |
| `computer.rs`、`desktop.rs`、`axread.rs` | 看畫面、滑鼠鍵盤;讀 App 的按鈕清單(agent-desktop) |
| `botbrowser.rs`、`browser.rs`、`web.rs` | AnyBot 自己的瀏覽器(Chrome DevTools 協定)、讀你的瀏覽器、抓網頁 |
| `routines.rs`、`triggers.rs` | 例行任務和觸發 |
| `channels.rs` | 訊息管道(Slack、Discord、Telegram) |
| `remote.rs` | 手機 API、帳號登入、裝置連結、大腦同步的請求端 |
| `tunnel.rs`、`wire.rs` | 和帳號伺服器之間的長連線(WebSocket) |
| `app/mirror_ui.rs` | 大腦和螢幕之間的同步 |
| `setup.rs` | `anybot setup` |
| `httpd.rs` | 很小的 HTTP 伺服器(AnyBot 和帳號伺服器共用) |
| `mobile/index.html` | 手機網頁(單一檔案,編進程式裡) |
## 資料存放
都在資料資料夾裡(macOS `~/Library/Application Support/AnyBot/`、Linux `~/.local/share/AnyBot/`、Windows `%APPDATA%\AnyBot\`;`ANYBOT_DATA_DIR` 可以改):
| 檔案 | 內容 |
|---|---|
| `state.json` | 全部的 Bot、聊天、設定和金鑰 |
| `attachments/bot-<id>/` | 聊天附件 |
| `browser/` | AnyBot 瀏覽器的設定檔(登入狀態) |
| `brain-copy.json` | 螢幕電腦保留的大腦副本(離線時還能看) |
| `brain-files/` | 螢幕電腦從大腦下載的附件 |
| `image-notes.json` | 看圖小幫手對每張圖的描述(不用重看) |
寫入時先寫暫存檔再改名,不會寫一半。
## 安全
- **核准關卡**:工具動作照規則直接做、先問或拒絕(`policy.rs`,FEATURES.md 第 16、19 節)。「直接操作模式」可以跳過詢問。
- **指令的沙盒(macOS)**:Bot 執行的指令在 `sandbox-exec` 裡跑,讀不到也寫不到 AnyBot 的資料資料夾和 `.ssh`、`.gnupg`、鑰匙圈、`.aws` 等;只有 `attachments/` 可以讀(不能寫)。環境變數裡的模型金鑰會先拿掉。
- **Bot 的電腦**:放在容器裡,和你的電腦分開。
- **保險庫**:密碼、卡號、CVC 不會交給模型,由 AnyBot 直接填進網頁。
- **多台電腦**:金鑰只留在大腦(下面說明);手腳只接受帳號裡的 AnyBot,用配對碼加入的只接受那一個大腦。
## 多台電腦怎麼運作
### 連線
每台 AnyBot(大腦、螢幕、手腳)都保持一條 WebSocket 連到帳號伺服器(`/relay/host/ws`),雙方每 15–20 秒互相確認,45–60 秒沒聲音就重連。帳號伺服器沒有 WebSocket 時(舊版),改用 HTTP 長輪詢(`/relay/host/next`)。
- 手機的要求:手機 → 帳號伺服器(HTTPS)→ 這條連線 → AnyBot → 回覆原路回去。
- AnyBot 找另一台 AnyBot(同步、手腳):從自己的連線送 `call` 上去,伺服器轉給對方的連線,答案以 `ret` 回來。
- 連線斷掉時,等待中的請求立刻收到「斷線了」,不用等逾時;伺服器也立刻知道這台離線。
- 超過 1 KB、而且壓得小的內容用 deflate 壓縮(圖片、PDF 這類已壓縮的照原樣送)。
格式見 [protocol.md](protocol.md)。
### 大腦和螢幕的同步(`app/mirror_ui.rs`)
大腦回答「從版本 N 之後改了什麼」:
- **設定**:State 去掉聊天、拿掉金鑰後算指紋,變了才送整份。
- **聊天**:每則訊息有指紋,只送第一個不同的那則之後的部分。
- **討論串**、**誰在工作**、**等你核准的事**。
螢幕的請求會等最多 20 秒,有變化就馬上回(長輪詢,跑在長連線上)。大腦上的改動通常 1 秒內出現在螢幕上。
螢幕上的改動變成小操作送回大腦:
- 單一值(`set`、`bot_set`);
- 清單和對照表逐項修改(`patch`、`bot_patch`:新增或修改某幾項、刪掉某幾項、改順序),所以兩台同時改同一份清單的不同項目,兩邊都會保留;
- 新增、刪除、排序 Bot(`bot_add`、`bot_del`、`bot_order`)。
每個改動有編號,大腦回報收到第幾號;還沒確認的改動,會疊在大腦傳回來的新設定上,打字不會被蓋回去。所有改動和聊天動作由一個工作排隊依序送出。送失敗時,螢幕重新要一份完整資料。
聊天動作(傳訊息、停止、核准、重新產生、開新對話…)送 `act`,由大腦執行。附件先上傳到大腦。
### 金鑰只在大腦
送給螢幕的設定裡,模型金鑰、訊息管道的 Token、連接器的環境變數和標頭都換成「••••(已設定,在大腦上)」。螢幕改設定送回來時,大腦看到這個替代字,就保留原本的值(清單裡依 id 或名字對上)。螢幕要取得模型清單時,由大腦用自己的金鑰去抓。
### 手腳
大腦的 Bot 呼叫工具時可以指定 `computer`。如果是帳號裡的另一台,就透過長連線送 `/api/device/run` 給它,由它在自己身上執行(螢幕、檔案、指令、它的 AnyBot 瀏覽器),再把文字和截圖傳回來。那台要開「裝置連結」才接受;若設了只給某個大腦用(`hands_for`),帳號伺服器會附上是哪一台在問,不是那台就拒絕。
### 配對碼
已登入的手機或 AnyBot 向帳號伺服器要一組 6 位數字(10 分鐘、一次)。新電腦用這組數字換到自己的身分(主機 id 和金鑰),不經過密碼。配對碼記得它是給哪個大腦的,新電腦就只讓那個大腦使用。
### 看圖小幫手
模型拒收圖片時,AnyBot 記下這個模型看不到圖。之後送給它的請求裡,你傳的每張圖會先交給你帳號裡看得到圖的模型(依序試 xAI、Anthropic、OpenAI、Gemini、OpenRouter…,先用你設的預設模型),把描述(包括圖裡的文字)放進訊息。描述依圖片記住,同一張圖不會再看一次;失敗的模型 10 分鐘內跳過。

103
docs/install.md Normal file
View File

@ -0,0 +1,103 @@
# 從原始碼建置和安裝
## 需要準備
| 平台 | 需要 |
|---|---|
| 全部 | Rust(stable,用 rustup 安裝)、git、make |
| macOS 12 以後 | Xcode Command Line Tools(`xcode-select --install`) |
| Linux | 編譯用:`build-essential pkg-config libasound2-dev`;執行用的系統套件見下面,`make deps` 會一次裝好 |
| Windows | Visual Studio Build Tools(C++)。可以 `cargo build --release`,安裝包還沒做 |
安裝 Rust:
```sh
curl --proto '=https' -sSf https://sh.rustup.rs | sh
```
## 步驟
```sh
git clone git@git.30cm.net:daniel.w/AnyBot.git
cd AnyBot
make deps # Linux:安裝系統套件(會問 sudo 密碼);Mac:檢查 Xcode 工具
make # 建置 AnyBot 和帳號伺服器(第一次約 5–15 分鐘)
make install
```
`make install` 做的事:
- **macOS**:做出 `AnyBot.app`(含圖示),放到 `~/Applications/`。沒有 Apple 開發者簽章,第一次打開請按右鍵 › 打開。想放到別的地方:`make install APPDIR=/Applications`。
- **Linux**:程式放在 `~/.local/bin/anybot`,選單項目和圖示放在 `~/.local/share/`。不需要 root。
不想安裝、只想試:`make run`。
## Linux 的系統套件
`make deps` 依發行版(apt、dnf、pacman)安裝這些,全部都是選用的,缺了 App 會說明少了什麼:
| 套件 | 用途 |
|---|---|
| libasound2、libxkbcommon-x11-0、libgl1、xdg-utils | 視窗和聲音(必要) |
| libnotify-bin | 通知 |
| libsecret-tools | 保險庫存在系統鑰匙圈 |
| espeak-ng | 唸出回覆 |
| zenity | 選檔案的視窗 |
| xclip / wl-clipboard | 貼上圖片;GNOME/KDE Wayland 上打中文 |
| ffmpeg | 影片、語音、示範錄影 |
| docker.io | Bot 自己的電腦 |
| fonts-noto-cjk | 更多中文字形(內建的粉圓體已經夠用) |
沒有螢幕的 Linux(只當手腳或大腦):只需要 `libasound2`。要讓 Bot 看得到畫面,再執行 `make hands`,會裝好虛擬螢幕(Xvfb)、視窗管理員和輔助使用服務。
## 更新
```sh
git pull
make install
```
你的 Bot、聊天和設定不在程式裡,更新不會動到它們。
## 移除
```sh
make uninstall
```
只移除程式。要連資料一起刪,刪掉資料資料夾:
| 平台 | 資料在哪 |
|---|---|
| macOS | `~/Library/Application Support/AnyBot/` |
| Linux | `~/.local/share/AnyBot/` |
| Windows | `%APPDATA%\AnyBot\` |
設成開機自動執行的(`anybot setup` 裝的)也要停掉:Linux 執行 `systemctl --user disable --now anybot`。
## 其他執行方式
| 指令 | 用途 |
|---|---|
| `make serve`(`anybot --serve`) | 不開視窗:Bot、例行任務、訊息管道一直運作,用手機操作 |
| `make setup`(`anybot setup`) | 把這台接到你的 AnyBot(大腦或手腳),見 [multi-device.md](multi-device.md) |
| `make relay-run` | 在這台跑帳號伺服器(測試用,埠 8080) |
| `make server` | 在伺服器上用 Docker 架帳號伺服器、大腦和 HTTPS |
| `ANYBOT_DATA_DIR=/某個資料夾 anybot` | 用另一份資料(測試、同一台跑兩份) |
## 常見問題
**Mac 說「無法打開,因為無法驗證開發者」**
按右鍵 › 打開,再按一次「打開」。只有第一次需要。
**Mac 上 Bot 看不到螢幕、不能點擊**
系統設定 › 隱私權與安全性:「螢幕錄製」和「輔助使用」要打開 AnyBot。App 的 設定 › 電腦 有按鈕直接開到那一頁。
**Linux 啟動時說找不到 libasound.so.2**
`sudo apt-get install libasound2`(Ubuntu 24.04 以後叫 `libasound2t64`)。
**編譯時找不到 alsa 或 pkg-config**
Linux 少了編譯用的套件:`sudo apt-get install build-essential pkg-config libasound2-dev`。
**第一次 `make` 很久**
要下載並編譯所有套件,第一次需要 5–15 分鐘,之後只會重編有改的部分。

109
docs/multi-device.md Normal file
View File

@ -0,0 +1,109 @@
# 多台電腦:大腦、手腳、螢幕
帳號裡選一台 AnyBot 當**大腦**,其他電腦和手機都連到它。
| 角色 | 是什麼 | 怎麼設 |
|---|---|---|
| 大腦 | 保存所有 Bot、聊天、記憶和例行任務,由它思考和排程。要一直開著 | 登入帳號就是(預設) |
| 螢幕 | 有視窗的電腦:看到的、改的都是大腦的 Bot;同時也是大腦的手腳 | 設定 › 手機與帳號 › 大腦 › 「改用它當大腦」 |
| 手腳 | 讓大腦的 Bot 操作這台:畫面、點擊打字、檔案和指令、這台的瀏覽器 | 「加一台電腦」的那一行,或 `anybot setup` |
| 手機 | 網頁版,打開帳號伺服器的網址 | 登入同一個帳號 |
所有電腦都透過**帳號伺服器**互相連絡。每台 AnyBot 主動連過去、保持一條一直開著的連線,所以電腦不用開放連接埠,也不用固定 IP。
## 一、架帳號伺服器和大腦
### 做法 A:放在伺服器上(推薦,Mac 關機時大腦也還在)
需要:一台有 Docker 的 Linux 主機,以及一個指到它的網域。
```sh
git clone git@git.30cm.net:daniel.w/AnyBot.git && cd AnyBot
make server
```
只問三件事:網域、帳號、密碼。會自動申請 HTTPS 憑證、建立帳號、啟動大腦。完成畫面會告訴你手機和電腦怎麼連。之後再執行一次 `make server`,可以選「用現有設定,更新並重新啟動」。
不想在伺服器上編譯:下載發行檔 `AnyBot-<版本>-server-x86_64.tar.gz`,解開後 `cd deploy/server && ./setup.sh`,用的是已經編好的程式。
### 做法 B:先用一台 Mac 當大腦(家裡用 Tailscale)
1. 跑帳號伺服器,並設成開機自動啟動:
```sh
make relay
mkdir -p "$HOME/Library/Application Support/AnyBot/relay"
cp relay/target/release/anybot-relay "$HOME/Library/Application Support/AnyBot/relay/"
cp deploy/mac/com.anybot.relay.plist ~/Library/LaunchAgents/ # 先把裡面的網址改成你的
launchctl load ~/Library/LaunchAgents/com.anybot.relay.plist
```
2. 用 Tailscale 給它 HTTPS 網址:`tailscale serve --bg 8780`(網址像 `https://mac-mini.xxxx.ts.net`)。
3. 打開 AnyBot › 設定 › 手機與帳號,填這個網址,建立帳號並登入。這台就是大腦。
只有同一個 Tailscale 網路裡的手機和電腦連得到。
## 二、接上其他電腦
### 有視窗的電腦(螢幕)
打開 AnyBot › 設定 › 手機與帳號,登入同一個帳號,在「大腦」那一欄按某一台旁邊的「改用它當大腦」。
- 這台原本的 Bot 會留著不刪,可以按「把這台的 Bot 搬過去」複製到大腦(連同聊天、金鑰、技能和連接器;已經在大腦上的不會重複搬)。
- 隨時可以按「改回用這台自己的 Bot」。
- 選大腦時也會打開這台的「裝置連結」:大腦的 Bot 可以用這台當手腳,每個動作仍照大腦的規則先問你。
### 沒有螢幕的 Linux(手腳)
1. 在手機 AnyBot 右上「⋯ › 加一台電腦」,或電腦 AnyBot 的 設定 › 手機與帳號 › 大腦 › 加一台電腦 › 產生配對碼。
2. 會出現一行(10 分鐘內有效,只能用一次):
```sh
anybot setup https://anybot.example.com 469615
```
3. 在那台電腦裝好 AnyBot(`make install`,或發行檔 `AnyBot-<版本>-headless-x86_64.tar.gz` 裡的 `./install.sh`),再貼上這一行執行。
它會:加入帳號(不用密碼)、只讓產生配對碼的那個大腦使用、設成開機自動執行(systemd 使用者服務 `anybot`)、沒有螢幕時開一個 1440×900 的虛擬螢幕,最後確認大腦看得到它。大腦約一分鐘內就會把它列進 Bot 可以用的電腦。
想讓 Bot 看得到這台的畫面,先執行 `make hands`(裝 Xvfb、視窗管理員、輔助使用服務)。
只打 `anybot setup` 會出現一個畫面:填網址和配對碼(或改用帳號密碼)。給程式用:
```sh
ANYBOT_ACCOUNT_PASSWORD=… anybot setup --server https://… --user 帳號 [--brain-name 雲端大腦] [--no-service]
```
登出後也要繼續跑:`sudo loginctl enable-linger $USER`(完成畫面會提醒)。
### 手機
打開帳號伺服器的網址,登入。可以「加入主畫面」當 App 用、開通知。帳號裡有螢幕電腦時,手機會直接打開它們用的大腦。
## 三、連不上時
畫面上方會說是哪一段連不到:
| 看到 | 意思 | 怎麼辦 |
|---|---|---|
| 這台電腦沒有連上網路 | 這台斷網 | 檢查 Wi-Fi 或網路線 |
| 連不到帳號伺服器:伺服器沒有開 | 帳號伺服器沒在跑 | 伺服器上 `docker compose ps`;Mac 上看 `launchctl list | grep anybot` |
| 帳號伺服器暫時無法使用 | 正在重開或 HTTPS 憑證還沒好 | 等一下,會自動重連 |
| 大腦目前離線 | 大腦關機、睡眠或 AnyBot 沒開 | 打開大腦;這段時間還能看之前的對話 |
| 大腦收到了,但太久沒有回應 | 大腦太忙或網路很慢 | 稍後再試 |
| 這台電腦的帳號登入失效了 | 帳號被登出或金鑰換了 | 重新登入 |
| 這台電腦只讓大腦「…」使用 | 別台想用這台當手腳 | 正常:配對時就限定了 |
大腦連不到時:之前的對話還能看,不能傳訊息;連回來會自動恢復,不用重開。
**忘記帳號密碼**:在帳號伺服器那台,先停掉它,再執行(輸入兩次新密碼,畫面不顯示):
```sh
anybot-relay --data <資料夾> --reset-password <帳號>
```
舊的加密備份是用舊密碼鎖的,改完之後在電腦上按一次「立刻備份」。
## 四、安全
- 模型金鑰、訊息管道的 Token、連接器的密碼只在大腦上。螢幕電腦看到的是「已設定,在大腦上」,在螢幕上改設定也不會洗掉真的金鑰。
- 手腳只接受帳號裡的 AnyBot,用配對碼加入的只接受那一個大腦。
- 每個動作照大腦的規則先問你(或照你設的「一律允許」)。
- 配對碼 10 分鐘有效、只能用一次,連續猜錯會被暫停 5 分鐘。
- 技術細節見 [architecture.md](architecture.md) 和 [protocol.md](protocol.md)。

117
docs/protocol.md Normal file
View File

@ -0,0 +1,117 @@
# 介面與長連線格式
給要改帳號伺服器或寫其他用戶端的人。所有回覆都是 JSON;錯誤是 `{"error": "白話的說明"}` 加上對應的 HTTP 狀態碼。
## 身分
| 誰 | 怎麼證明 | 從哪來 |
|---|---|---|
| 人(手機) | `Authorization: Bearer <session>` | `POST /relay/login` |
| 一台 AnyBot(主機) | `Authorization: Bearer <host token>` | `POST /relay/hosts`(登入後)或 `POST /relay/pair/claim`(配對碼) |
伺服器只存密碼和金鑰的雜湊值。
## 帳號伺服器(relay)
### 帳號
| 方法 | 路徑 | 說明 |
|---|---|---|
| POST | `/relay/signup` | `{user, password, invite}`。第一個帳號不用邀請碼,之後要 `ANYBOT_RELAY_INVITE` |
| POST | `/relay/login` | `{user, password, device}` → `{token}`。錯 8 次停 5 分鐘 |
| POST | `/relay/logout` | 登出這個 session |
| GET | `/relay/me` | 帳號、可用的 AnyBot(含 `online`、`uses`、`hands`)、登入過的裝置、有沒有備份 |
### 主機
| 方法 | 路徑 | 說明 |
|---|---|---|
| POST | `/relay/hosts` | (人)`{name}` → `{id, token}`。同名的主機會換新金鑰 |
| POST | `/relay/hosts/<id>/remove` | (人)移除主機 |
| GET | `/relay/host/ws` | (主機)長連線,見下面 |
| GET | `/relay/host/next` | (主機)舊式長輪詢:有要求就回 job,25 秒沒有回 204 |
| POST | `/relay/host/reply/<job id>` | (主機)回答舊式 job |
| GET | `/relay/host/peers` | (主機)帳號裡的其他主機 |
| GET/POST | `/relay/host/members` | (主機)團隊成員和角色(admin、member) |
| POST | `/relay/host/notify` | (主機)推播通知到帳號的手機 |
| POST | `/relay/host/sites/<name>` | (主機)發布 Bot 做的網頁,公開在 `/s/<name>/` |
| PUT/GET | `/relay/backup` | 加密備份(伺服器打不開) |
### 配對碼
| 方法 | 路徑 | 說明 |
|---|---|---|
| POST | `/relay/pair` | (人或主機)`{for: 大腦的主機 id}` → `{code, expires_in: 600}` |
| POST | `/relay/pair/claim` | (不用登入)`{code, name}` → `{id, token, user, for, for_name}`。只能用一次;同一個位址錯 8 次停 5 分鐘 |
### 轉送
| 路徑 | 誰 | 轉成 |
|---|---|---|
| `/h/<host>/api/...` | 手機 | 那台 AnyBot 的 `/api/...`,角色是 owner/admin/member |
| `/relay/hosts/<host>/api/sync[/...]` | 同帳號的主機 | 大腦的 `/api/sync...`,角色 `mirror` |
| `/relay/hosts/<host>/api/device/run` | 同帳號的主機 | 手腳的 `/api/device/run`,角色 `device` |
轉送時伺服器附上 `user`、`role` 和 `from`(問的那台主機 id)。對方不在線時立刻回 503「目前離線」;收到了但 35 秒內沒回答,回 504。
## AnyBot 提供的介面(`remote::handle`)
只有經過帳號伺服器(或同一個網路、用手機密碼登入)才到得了。
| 方法 | 路徑 | 角色 | 說明 |
|---|---|---|---|
| GET | `/api/state?since=` | 手機 | Bot 清單、誰在工作、等核准的事(有變化才回,最多等 20 秒) |
| GET | `/api/bots/<id>?since=` | 手機 | 一個 Bot 的訊息 |
| POST | `/api/bots/<id>/<動作>` | 手機 | `send`、`stop`、`answer`、`read`、`new`、`upload`… |
| GET/POST | `/api/screen/<box\|mac>` | 手機 | 看畫面、點擊打字 |
| POST | `/api/device/run` | device | `{tool, args, who}` → `{text, ok, image}`:在這台跑一個工具 |
| GET | `/api/sync?since=&client=` | mirror | 大腦的變化(見 architecture.md) |
| POST | `/api/sync/edit` | mirror | `{ops, client, seq}` → `{ok, applied}` |
| POST | `/api/sync/act` | mirror | `{bot, action, …}`:send、stop、answer、read、new、regenerate、reply、restore |
| POST | `/api/sync/upload/<bot>?name=` | mirror | 原始檔案內容 → `{path}` |
| GET | `/api/sync/file?path=` | mirror | 大腦上的附件(只限 `attachments/` 裡的檔案) |
| POST | `/api/sync/models` | mirror | `{provider}` → `{models}`:用大腦的金鑰取得模型清單 |
## 長連線的格式(`src/wire.rs`)
WebSocket 的二進位訊息,每則是:
```
[4 位元組,表頭長度,big-endian] [表頭 JSON] [內容的原始位元組]
```
內容不做 base64。`z: true` 表示內容用 deflate 壓過;`z_ok: true` 表示問的人看得懂壓過的回答。
| t | 方向 | 表頭欄位 | 意思 |
|---|---|---|---|
| `hello` | AnyBot → 伺服器 | `brain`、`hands` | 這台在看哪個大腦、是不是手腳(手機和設定畫面用) |
| `job` | 伺服器 → AnyBot | `id, method, path, query, user, role, from, z, z_ok` | 給這台的要求 |
| `reply` | AnyBot → 伺服器 | `id, status, ctype, headers, z` | 回答 job |
| `call` | AnyBot → 伺服器 | `id, to, method, path, query, z` | 要問帳號裡的另一台(只能是 `/api/sync…` 或 `/api/device/run`) |
| `ret` | 伺服器 → AnyBot | `id, status, ctype, z` | call 的回答 |
心跳用 WebSocket 的 ping/pong。連線斷掉時,伺服器把還在等的 job 都回 503「斷線了」,AnyBot 把還在等的 call 都回「和帳號伺服器的連線斷了」。
## 同步的改動(`/api/sync/edit` 的 ops)
| op | 欄位 | 說明 |
|---|---|---|
| `set` | `key, value` | 換掉一個設定(`next_*` 計數只會變大) |
| `patch` | `key, patch` | 清單或對照表逐項修改 |
| `merge` | `key, value` | 搬移時補上大腦沒有的(providers、skills、mcp_servers) |
| `bot_add` | `bot` | 新增 Bot(id 撞到會換) |
| `bot_del` | `id` | 刪除(主要 Bot 不能刪) |
| `bot_set` | `id, key, value` | 改 Bot 的一個欄位 |
| `bot_patch` | `id, key, patch` | 逐項修改 Bot 的清單欄位(例行任務保留大腦記錄的執行時間) |
| `bot_read` | `id` | 標為已讀 |
| `bot_order` | `ids` | Bot 的順序 |
`patch` 的格式:
```json
{"value": …} 換掉整個值
{"map": {"put": {"鍵": 值}, "del": ["鍵"]}} 對照表
{"list": {"put": [項目], "del": [鍵], "order": [鍵]}} 清單;鍵是項目的 id,沒有就用 name,純值就是值本身
```
清單裡的項目沒有 id 或名字、或鍵重複時,送整個值。

106
docs/release.md Normal file
View File

@ -0,0 +1,106 @@
# 發行
## 自己打包
```sh
make dist # 版本號取自 git tag(沒有就用 commit 編號)
make dist VERSION=1.2.0 # 指定版本號
```
| 在哪打包 | 產生 |
|---|---|
| Mac | `dist/AnyBot-<版本>-macos-arm64.zip`:AnyBot.app(含圖示,ad hoc 簽章) |
| Linux | `dist/AnyBot-<版本>-ubuntu-<架構>.tar.gz`:有視窗的版本,`./install.sh` 安裝 |
| | `dist/AnyBot-<版本>-headless-<架構>.tar.gz`:沒有螢幕的 Linux(手腳或大腦),附 `hands.sh` |
| | `dist/AnyBot-<版本>-server-<架構>.tar.gz`:帳號伺服器+大腦+HTTPS,用已編好的程式建 Docker 映像,`deploy/server/setup.sh` |
Linux 版要在 Ubuntu 22.04(glibc 2.35)上編譯,才能在較新的 Ubuntu、Debian 12 等直接跑;用 `deploy/ci/Dockerfile.linux` 這個環境就是。
打包腳本:`deploy/mac/bundle.sh`、`deploy/ci/package-linux.sh`。
## 推 tag 自動建置(Gitea Actions)
`.gitea/workflows/release.yml`:推一個 `v` 開頭的 tag,兩個 runner 各自建置,把檔案掛到那個 tag 的 Release 上(沒有 Release 會自動建立;重建時會換掉同名的檔案)。
```sh
git tag v1.2.0
git push origin v1.2.0
```
| 工作 | runner 標籤 | 做什麼 |
|---|---|---|
| linux | `anybot-linux` | 在 `anybot-builder:22.04` 容器裡編譯、跑測試、打三個 Linux 包、上傳 |
| macos | `macos-arm64` | 在 Mac 上直接編譯、做 AnyBot.app、上傳 |
上傳用 `secrets.RELEASE_TOKEN`;沒設時用 Gitea 自動給的 token。如果自動的 token 沒有建立 Release 的權限,到 repo › 設定 › Actions › Secrets 加一個 `RELEASE_TOKEN`(有這個 repo 寫入權限的 access token)。
### 設定 Linux runner
在一台有 Docker 的 Linux(x86_64)上:
1. 在 Gitea 打開 Actions(管理員:`app.ini` 的 `[actions] ENABLED = true`),到 repo › 設定 › Actions › Runners › 建立新的 runner,複製註冊權杖。
2. 安裝 runner([gitea/runner](https://gitea.com/gitea/runner/releases),下載後核對 `.sha256`):
```sh
V=5.3.0
curl -fsSLO https://gitea.com/gitea/runner/releases/download/v$V/gitea-runner-$V-linux-amd64.xz
curl -fsSLO https://gitea.com/gitea/runner/releases/download/v$V/gitea-runner-$V-linux-amd64.xz.sha256
sha256sum -c gitea-runner-$V-linux-amd64.xz.sha256 && xz -d gitea-runner-$V-linux-amd64.xz
install -m 755 gitea-runner-$V-linux-amd64 ~/.local/bin/gitea-runner
```
3. 建好建置環境(只要一次;更新 Rust 時再建一次):
```sh
docker build -t anybot-builder:22.04 -f deploy/ci/Dockerfile.linux deploy/ci
```
4. 設定和註冊:
```sh
mkdir -p ~/.config/gitea-runner
cp deploy/ci/runner-linux.yaml ~/.config/gitea-runner/config.yaml # 檔案路徑裡的使用者名稱照你的改
cd ~/.config/gitea-runner
gitea-runner register --no-interactive --instance https://你的-gitea --token 註冊權杖 \
--name anybot-linux --labels "anybot-linux:docker://anybot-builder:22.04" -c config.yaml
```
5. 一直跑(systemd 使用者服務):
```ini
# ~/.config/systemd/user/gitea-runner.service
[Unit]
Description=Gitea runner (AnyBot releases)
After=network-online.target docker.service
[Service]
WorkingDirectory=%h/.config/gitea-runner
ExecStart=%h/.local/bin/gitea-runner daemon -c %h/.config/gitea-runner/config.yaml
Restart=always
RestartSec=5
[Install]
WantedBy=default.target
```
```sh
systemctl --user daemon-reload && systemctl --user enable --now gitea-runner
sudo loginctl enable-linger $USER # 登出後也繼續跑
```
`runner-linux.yaml` 用兩個 Docker 磁碟區保留下載的套件(`anybot-cargo-registry`)和編譯結果(`anybot-ci-target`),之後每次發行只重編有改的部分。
### 設定 Mac runner
Apple 的 App 只能在 Mac 上編譯。在一台裝好 Rust 和 Xcode Command Line Tools 的 Mac 上:
```sh
V=5.3.0
curl -fsSLO https://gitea.com/gitea/runner/releases/download/v$V/gitea-runner-$V-darwin-arm64.xz
curl -fsSLO https://gitea.com/gitea/runner/releases/download/v$V/gitea-runner-$V-darwin-arm64.xz.sha256
shasum -a 256 -c gitea-runner-$V-darwin-arm64.xz.sha256 && xz -d gitea-runner-$V-darwin-arm64.xz
install -m 755 gitea-runner-$V-darwin-arm64 ~/.local/bin/gitea-runner
mkdir -p ~/.config/gitea-runner && cd ~/.config/gitea-runner
gitea-runner register --no-interactive --instance https://你的-gitea --token 註冊權杖 \
--name anybot-mac --labels "macos-arm64:host"
gitea-runner daemon # 或做成 launchd 代理,開機自動執行
```
`host` 表示工作直接在這台 Mac 上跑(不用容器)。編譯結果保留在工作資料夾旁的 `anybot-target/`。
### 注意
- runner 要連得到 Gitea 的網址(HTTPS),也要能從 GitHub 下載 `actions/checkout`(Gitea 預設的 action 來源)。
- Mac 版用 ad hoc 簽章,沒有公證;使用者第一次打開要按右鍵 › 打開。要正式簽章和公證需要 Apple 開發者帳號。