diff --git a/FEATURES.md b/FEATURES.md index 22bd751..d7c7e2f 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -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 拖進格子(第一次拖時出現「拖曳到此處以釘選」);格子內可拖曳排序;拖到分區會取消釘選並移過去。 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..94bee1e --- /dev/null +++ b/Makefile @@ -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 diff --git a/README.md b/README.md new file mode 100644 index 0000000..13b81e0 --- /dev/null +++ b/README.md @@ -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/ 技術文件 +``` diff --git a/deploy/linux/install.sh b/deploy/linux/install.sh index deffda3..7690147 100755 --- a/deploy/linux/install.sh +++ b/deploy/linux/install.sh @@ -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)." diff --git a/deploy/mac/bundle.sh b/deploy/mac/bundle.sh index 62d90ee..8639713 100755 --- a/deploy/mac/bundle.sh +++ b/deploy/mac/bundle.sh @@ -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-.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 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" diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..4a09cdc --- /dev/null +++ b/docs/architecture.md @@ -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-/` | 聊天附件 | +| `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 分鐘內跳過。 diff --git a/docs/install.md b/docs/install.md new file mode 100644 index 0000000..097fd85 --- /dev/null +++ b/docs/install.md @@ -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 分鐘,之後只會重編有改的部分。 diff --git a/docs/multi-device.md b/docs/multi-device.md new file mode 100644 index 0000000..bd13967 --- /dev/null +++ b/docs/multi-device.md @@ -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)。 diff --git a/docs/protocol.md b/docs/protocol.md new file mode 100644 index 0000000..a4c565b --- /dev/null +++ b/docs/protocol.md @@ -0,0 +1,117 @@ +# 介面與長連線格式 + +給要改帳號伺服器或寫其他用戶端的人。所有回覆都是 JSON;錯誤是 `{"error": "白話的說明"}` 加上對應的 HTTP 狀態碼。 + +## 身分 + +| 誰 | 怎麼證明 | 從哪來 | +|---|---|---| +| 人(手機) | `Authorization: Bearer ` | `POST /relay/login` | +| 一台 AnyBot(主機) | `Authorization: Bearer ` | `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//remove` | (人)移除主機 | +| GET | `/relay/host/ws` | (主機)長連線,見下面 | +| GET | `/relay/host/next` | (主機)舊式長輪詢:有要求就回 job,25 秒沒有回 204 | +| POST | `/relay/host/reply/` | (主機)回答舊式 job | +| GET | `/relay/host/peers` | (主機)帳號裡的其他主機 | +| GET/POST | `/relay/host/members` | (主機)團隊成員和角色(admin、member) | +| POST | `/relay/host/notify` | (主機)推播通知到帳號的手機 | +| POST | `/relay/host/sites/` | (主機)發布 Bot 做的網頁,公開在 `/s//` | +| 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//api/...` | 手機 | 那台 AnyBot 的 `/api/...`,角色是 owner/admin/member | +| `/relay/hosts//api/sync[/...]` | 同帳號的主機 | 大腦的 `/api/sync...`,角色 `mirror` | +| `/relay/hosts//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/?since=` | 手機 | 一個 Bot 的訊息 | +| POST | `/api/bots//<動作>` | 手機 | `send`、`stop`、`answer`、`read`、`new`、`upload`… | +| GET/POST | `/api/screen/` | 手機 | 看畫面、點擊打字 | +| 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/?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 或名字、或鍵重複時,送整個值。 diff --git a/docs/release.md b/docs/release.md new file mode 100644 index 0000000..17d807f --- /dev/null +++ b/docs/release.md @@ -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 開發者帳號。