diff --git a/README.md b/README.md index 4dfe354..9a5dbb2 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,8 @@ Boss 是一个面向多设备开发协作的 agent control plane。 - Web 控制台 - `boss-worker` 模拟执行器 - `boss-worker` 外部命令执行模式,可接本地 Codex / Claude / 自定义脚本 +- AI Glasses 云服务器一键部署脚本 +- 命令行对话入口脚本 - `npm run smoke` 自动跑端到端验证 - `Dockerfile` + `compose.yaml` 支持容器启动 @@ -60,6 +62,40 @@ npm run dev BOSS_DATA_FILE=.boss-data/local-dev.json npm run dev ``` +## 云端主控部署 + +如果你要按最初的产品策略来跑: + +- 云服务器只跑 Boss control plane +- 你的 Windows / Mac 本机各自跑 worker +- 你通过浏览器或命令行和云端主控对话 + +仓库已经内置 AI Glasses 云服务器部署脚本: + +```bash +./scripts/deploy_ai_glasses_server.sh +``` + +部署完成后,先用这个脚本确认云端状态: + +```bash +./scripts/boss_cloud_status.sh +``` + +查看云端日志: + +```bash +./scripts/boss_cloud_logs.sh +``` + +默认云端入口: + +```bash +http://111.231.132.51:43210 +``` + +这就是你当前最短的“主账号对话入口”。 + 如果你要手工启动 worker: ```bash @@ -108,6 +144,30 @@ npm run worker -- \ --executor ./scripts/claude_executor.sh ``` +## 怎么和系统对话 + +当前推荐两种方式: + +1. 浏览器入口 +打开云端控制台: + +```bash +http://111.231.132.51:43210 +``` + +这是最符合产品策略的入口,也是主控面。 + +2. 命令行聊天入口 +仓库里自带一个简单 CLI,可以直接发消息给 Boss: + +```bash +BOSS_SERVER_URL=http://111.231.132.51:43210 ./scripts/boss_chat.sh create "Boss 主控对话" +BOSS_SERVER_URL=http://111.231.132.51:43210 ./scripts/boss_chat.sh send "先调研这个问题,不要急着改代码。" +BOSS_SERVER_URL=http://111.231.132.51:43210 ./scripts/boss_chat.sh status +``` + +这条 CLI 入口后面也很容易改造成 Telegram / Slack / 企业微信 webhook。 + 一键本地 demo: ```bash @@ -142,4 +202,6 @@ docker compose up --build - 审批、暂停、恢复、取消、重排 - SSE 实时事件流和 Web 控制台 - 会话归档与恢复 +- 云端主控部署脚本与状态脚本 +- 命令行对话入口 - 一键 demo 启动 diff --git a/compose.cloud.yaml b/compose.cloud.yaml new file mode 100644 index 0000000..292f00e --- /dev/null +++ b/compose.cloud.yaml @@ -0,0 +1,15 @@ +services: + boss: + build: . + container_name: boss-control-plane + restart: unless-stopped + environment: + PORT: 43210 + BOSS_DATA_FILE: .boss-data/cloud-store.json + ports: + - "43210:43210" + volumes: + - boss-data:/app/.boss-data + +volumes: + boss-data: diff --git a/scripts/boss_chat.sh b/scripts/boss_chat.sh new file mode 100755 index 0000000..4b429ff --- /dev/null +++ b/scripts/boss_chat.sh @@ -0,0 +1,133 @@ +#!/usr/bin/env bash +set -euo pipefail + +server_url="${BOSS_SERVER_URL:-http://127.0.0.1:43210}" +session_file="${BOSS_SESSION_FILE:-.boss-session}" + +usage() { + cat <<'EOF' +Usage: + boss_chat.sh sessions + boss_chat.sh create [title] + boss_chat.sh send "" [session_id] + boss_chat.sh status [session_id] + +Environment: + BOSS_SERVER_URL Default: http://127.0.0.1:43210 + BOSS_SESSION_FILE Default: .boss-session +EOF +} + +require_tools() { + command -v curl >/dev/null 2>&1 || { + echo "curl is required" >&2 + exit 1 + } + command -v node >/dev/null 2>&1 || { + echo "node is required" >&2 + exit 1 + } +} + +request() { + local method="$1" + local path="$2" + local body="${3:-}" + + if [[ -n "$body" ]]; then + curl -fsS -X "$method" "$server_url$path" -H 'Content-Type: application/json' -d "$body" + else + curl -fsS -X "$method" "$server_url$path" + fi +} + +json_payload() { + local key="$1" + local value="$2" + node -e 'const [key, value] = process.argv.slice(1); process.stdout.write(JSON.stringify({ [key]: value }));' "$key" "$value" +} + +json_message_payload() { + local value="$1" + node -e 'const value = process.argv[1]; process.stdout.write(JSON.stringify({ content: value, channel: "cli" }));' "$value" +} + +json_get() { + local expr="$1" + node -e "const fs = require('fs'); const data = JSON.parse(fs.readFileSync(0, 'utf8')); const result = (${expr}); if (result === undefined || result === null) process.exit(2); if (typeof result === 'string') process.stdout.write(result); else process.stdout.write(JSON.stringify(result, null, 2));" +} + +save_session_id() { + printf '%s' "$1" > "$session_file" +} + +load_session_id() { + if [[ -f "$session_file" ]]; then + cat "$session_file" + return 0 + fi + return 1 +} + +create_session() { + local title="${1:-Boss 主控对话}" + local payload + payload="$(request POST /api/sessions "$(json_payload title "$title")")" + local session_id + session_id="$(printf '%s' "$payload" | json_get "data.session.id")" + save_session_id "$session_id" + printf '%s\n' "$payload" +} + +command="${1:-}" +shift || true + +if [[ -z "$command" ]]; then + usage + exit 1 +fi + +require_tools + +case "$command" in + sessions) + request GET /api/sessions | node -e 'const fs=require("fs"); const data=JSON.parse(fs.readFileSync(0,"utf8")); for (const session of data) console.log(`${session.id}\t${session.status}\t${session.title}`);' + ;; + create) + create_session "${1:-Boss 主控对话}" + ;; + send) + message="${1:-}" + if [[ -z "$message" ]]; then + echo "Missing message." >&2 + exit 1 + fi + session_id="${2:-}" + if [[ -z "$session_id" ]]; then + session_id="$(load_session_id || true)" + fi + if [[ -z "$session_id" ]]; then + session_json="$(create_session "Boss 主控对话")" + session_id="$(printf '%s' "$session_json" | json_get "data.session.id")" + fi + request POST "/api/sessions/$session_id/messages" "$(json_message_payload "$message")" + printf '\n' + save_session_id "$session_id" + ;; + status) + session_id="${1:-}" + if [[ -z "$session_id" ]]; then + session_id="$(load_session_id || true)" + fi + if [[ -z "$session_id" ]]; then + echo "No session id found. Run create or send first." >&2 + exit 1 + fi + request GET "/api/sessions/$session_id" + printf '\n' + ;; + *) + usage + exit 1 + ;; +esac diff --git a/scripts/boss_cloud_logs.sh b/scripts/boss_cloud_logs.sh new file mode 100755 index 0000000..1e09b34 --- /dev/null +++ b/scripts/boss_cloud_logs.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -euo pipefail + +remote_dir="${BOSS_REMOTE_DIR:-/home/ubuntu/boss}" +tail_lines="${1:-150}" + +export CODEX_HOME="${CODEX_HOME:-$HOME/.codex}" +export AG_SERVER_SKILL="${AG_SERVER_SKILL:-$CODEX_HOME/skills/ai-glasses-server-debug}" +export AG_SERVER="${AG_SERVER:-$AG_SERVER_SKILL/scripts/server_ssh.sh}" + +"$AG_SERVER" exec "set -euo pipefail; cd $(printf '%q' "$remote_dir"); sudo docker compose -f compose.cloud.yaml -p boss logs --tail $(printf '%q' "$tail_lines")" diff --git a/scripts/boss_cloud_status.sh b/scripts/boss_cloud_status.sh new file mode 100755 index 0000000..d1a0351 --- /dev/null +++ b/scripts/boss_cloud_status.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -euo pipefail + +remote_dir="${BOSS_REMOTE_DIR:-/home/ubuntu/boss}" + +export CODEX_HOME="${CODEX_HOME:-$HOME/.codex}" +export AG_SERVER_SKILL="${AG_SERVER_SKILL:-$CODEX_HOME/skills/ai-glasses-server-debug}" +export AG_SERVER="${AG_SERVER:-$AG_SERVER_SKILL/scripts/server_ssh.sh}" + +"$AG_SERVER" exec "set -euo pipefail; cd $(printf '%q' "$remote_dir"); sudo docker compose -f compose.cloud.yaml -p boss ps; echo '---'; curl -fsS http://127.0.0.1:43210/api/health" diff --git a/scripts/deploy_ai_glasses_server.sh b/scripts/deploy_ai_glasses_server.sh new file mode 100755 index 0000000..4986741 --- /dev/null +++ b/scripts/deploy_ai_glasses_server.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +set -euo pipefail + +branch="${1:-main}" +remote_dir="${BOSS_REMOTE_DIR:-/home/ubuntu/boss}" +remote_repo="${BOSS_REMOTE_REPO:-https://git.hyzq.site/krisolo/boss.git}" + +export CODEX_HOME="${CODEX_HOME:-$HOME/.codex}" +export AG_SERVER_SKILL="${AG_SERVER_SKILL:-$CODEX_HOME/skills/ai-glasses-server-debug}" +export AG_SERVER="${AG_SERVER:-$AG_SERVER_SKILL/scripts/server_ssh.sh}" + +if [[ ! -x "$AG_SERVER" ]]; then + echo "AI Glasses server wrapper not found: $AG_SERVER" >&2 + exit 1 +fi + +remote_script=$(cat <