gemini-computer-use 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +196 -0
- package/bin/cli.js +27 -0
- package/core/__init__.py +0 -0
- package/core/agent.py +57 -0
- package/core/agent_harness.py +60 -0
- package/core/code_cache.py +232 -0
- package/core/hooks.py +61 -0
- package/core/policies.py +99 -0
- package/core/schemas.py +49 -0
- package/core/server.py +69 -0
- package/core/subagents.py +55 -0
- package/core/triggers.py +70 -0
- package/core/vitals.py +117 -0
- package/install.ps1 +339 -0
- package/install.sh +777 -0
- package/package.json +41 -0
- package/skills/orchestrator.md +14 -0
package/install.sh
ADDED
|
@@ -0,0 +1,777 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ==============================================================================
|
|
3
|
+
# Antigravity Mesh - Universal Turnkey Installer (v0.1.2)
|
|
4
|
+
# Bilingual: English (Default) & Russian, Device Detection, SSH & Autostart
|
|
5
|
+
# ==============================================================================
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
# Defaults
|
|
9
|
+
MODE=""
|
|
10
|
+
TLS="none"
|
|
11
|
+
PORT="8096"
|
|
12
|
+
DOMAIN=""
|
|
13
|
+
GATEWAY="smart-server.online"
|
|
14
|
+
USERNAME=""
|
|
15
|
+
TOKEN=""
|
|
16
|
+
DRY_RUN=false
|
|
17
|
+
QUICK=false
|
|
18
|
+
SSH_TARGET=""
|
|
19
|
+
SSH_PORT="22"
|
|
20
|
+
LANG_CHOICE=""
|
|
21
|
+
EXPLICIT_LANG=""
|
|
22
|
+
|
|
23
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
|
|
24
|
+
[ -z "$SCRIPT_DIR" ] && SCRIPT_DIR="."
|
|
25
|
+
|
|
26
|
+
# Bootstrap if piped from curl or run outside project
|
|
27
|
+
if [ ! -f "$SCRIPT_DIR/core/agent.py" ]; then
|
|
28
|
+
BOOTSTRAP_DIR="$HOME/.gemini-computer-use"
|
|
29
|
+
mkdir -p "$BOOTSTRAP_DIR/core" "$BOOTSTRAP_DIR/skills"
|
|
30
|
+
curl -fsSL "https://${GATEWAY}/core/agent.py" -o "$BOOTSTRAP_DIR/core/agent.py" 2>/dev/null || curl -fsSL "https://raw.githubusercontent.com/LevRa7/Computer-use-for-Gemini-App-Web/main/core/agent.py" -o "$BOOTSTRAP_DIR/core/agent.py" 2>/dev/null || true
|
|
31
|
+
curl -fsSL "https://${GATEWAY}/core/server.py" -o "$BOOTSTRAP_DIR/core/server.py" 2>/dev/null || curl -fsSL "https://raw.githubusercontent.com/LevRa7/Computer-use-for-Gemini-App-Web/main/core/server.py" -o "$BOOTSTRAP_DIR/core/server.py" 2>/dev/null || true
|
|
32
|
+
curl -fsSL "https://${GATEWAY}/core/vitals.py" -o "$BOOTSTRAP_DIR/core/vitals.py" 2>/dev/null || curl -fsSL "https://raw.githubusercontent.com/LevRa7/Computer-use-for-Gemini-App-Web/main/core/vitals.py" -o "$BOOTSTRAP_DIR/core/vitals.py" 2>/dev/null || true
|
|
33
|
+
curl -fsSL "https://${GATEWAY}/core/__init__.py" -o "$BOOTSTRAP_DIR/core/__init__.py" 2>/dev/null || true
|
|
34
|
+
curl -fsSL "https://${GATEWAY}/skills/orchestrator.md" -o "$BOOTSTRAP_DIR/skills/orchestrator.md" 2>/dev/null || true
|
|
35
|
+
SCRIPT_DIR="$BOOTSTRAP_DIR"
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
CONFIG_DIR="$HOME/.config/antigravity-mesh"
|
|
39
|
+
CONFIG_FILE="$CONFIG_DIR/agent.env"
|
|
40
|
+
|
|
41
|
+
# Colors
|
|
42
|
+
BOLD="\033[1m"
|
|
43
|
+
GREEN="\033[0;32m"
|
|
44
|
+
CYAN="\033[0;36m"
|
|
45
|
+
YELLOW="\033[1;33m"
|
|
46
|
+
BLUE="\033[0;34m"
|
|
47
|
+
MAGENTA="\033[0;35m"
|
|
48
|
+
RESET="\033[0m"
|
|
49
|
+
|
|
50
|
+
ensure_python() {
|
|
51
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
52
|
+
return 0
|
|
53
|
+
fi
|
|
54
|
+
[ "$LANG_CHOICE" = "ru" ] && echo -e "\033[1;33m[!] Python 3 не найден. Автоматическая установка системных пакетов...\033[0m" || echo -e "\033[1;33m[!] Python 3 not found. Installing system packages...\033[0m"
|
|
55
|
+
if command -v apt-get >/dev/null 2>&1; then
|
|
56
|
+
[ "$(id -u)" -eq 0 ] && apt-get update -qq && apt-get install -y -qq python3 python3-pip python3-venv curl || sudo apt-get update -qq && sudo apt-get install -y -qq python3 python3-pip python3-venv curl
|
|
57
|
+
elif command -v dnf >/dev/null 2>&1; then
|
|
58
|
+
[ "$(id -u)" -eq 0 ] && dnf install -y -q python3 python3-pip curl || sudo dnf install -y -q python3 python3-pip curl
|
|
59
|
+
elif command -v yum >/dev/null 2>&1; then
|
|
60
|
+
[ "$(id -u)" -eq 0 ] && yum install -y -q python3 python3-pip curl || sudo yum install -y -q python3 python3-pip curl
|
|
61
|
+
elif command -v pacman >/dev/null 2>&1; then
|
|
62
|
+
[ "$(id -u)" -eq 0 ] && pacman -Sy --noconfirm python python-pip curl || sudo pacman -Sy --noconfirm python python-pip curl
|
|
63
|
+
elif command -v apk >/dev/null 2>&1; then
|
|
64
|
+
apk add --no-cache python3 py3-pip curl
|
|
65
|
+
elif [ "$(uname -s)" = "Darwin" ] && command -v brew >/dev/null 2>&1; then
|
|
66
|
+
brew install python3
|
|
67
|
+
fi
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
ensure_dependencies() {
|
|
71
|
+
ensure_python
|
|
72
|
+
PYTHON_BIN="$(command -v python3 || echo "/usr/bin/python3")"
|
|
73
|
+
if ! "$PYTHON_BIN" -c "import websockets" 2>/dev/null; then
|
|
74
|
+
[ "$LANG_CHOICE" = "ru" ] && echo "[1/4] Автономная установка зависимостей (websockets)..." || echo "[1/4] Installing dependencies (websockets)..."
|
|
75
|
+
"$PYTHON_BIN" -m pip install websockets --break-system-packages 2>/dev/null || \
|
|
76
|
+
"$PYTHON_BIN" -m pip install websockets 2>/dev/null || {
|
|
77
|
+
mkdir -p "$CONFIG_DIR"
|
|
78
|
+
local VENV_DIR="$CONFIG_DIR/venv"
|
|
79
|
+
if ! "$PYTHON_BIN" -m venv "$VENV_DIR" 2>/dev/null; then
|
|
80
|
+
if command -v apt-get >/dev/null 2>&1; then
|
|
81
|
+
[ "$(id -u)" -eq 0 ] && apt-get install -y -qq python3-venv 2>/dev/null || sudo apt-get install -y -qq python3-venv 2>/dev/null || true
|
|
82
|
+
"$PYTHON_BIN" -m venv "$VENV_DIR" 2>/dev/null || true
|
|
83
|
+
fi
|
|
84
|
+
fi
|
|
85
|
+
if [ -f "$VENV_DIR/bin/pip" ]; then
|
|
86
|
+
"$VENV_DIR/bin/pip" install --quiet websockets
|
|
87
|
+
PYTHON_BIN="$VENV_DIR/bin/python3"
|
|
88
|
+
fi
|
|
89
|
+
}
|
|
90
|
+
fi
|
|
91
|
+
PYTHON_BIN="${PYTHON_BIN:-$(command -v python3 || echo "/usr/bin/python3")}"
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
# Parse CLI args
|
|
96
|
+
while [[ $# -gt 0 ]]; do
|
|
97
|
+
case "$1" in
|
|
98
|
+
--lang=*)
|
|
99
|
+
LANG_CHOICE="${1#*=}"
|
|
100
|
+
EXPLICIT_LANG=true
|
|
101
|
+
shift
|
|
102
|
+
;;
|
|
103
|
+
-q|--quick)
|
|
104
|
+
QUICK=true
|
|
105
|
+
shift
|
|
106
|
+
;;
|
|
107
|
+
--mode=*)
|
|
108
|
+
MODE="${1#*=}"
|
|
109
|
+
shift
|
|
110
|
+
;;
|
|
111
|
+
--user=*)
|
|
112
|
+
USERNAME="${1#*=}"
|
|
113
|
+
shift
|
|
114
|
+
;;
|
|
115
|
+
--token=*)
|
|
116
|
+
TOKEN="${1#*=}"
|
|
117
|
+
shift
|
|
118
|
+
;;
|
|
119
|
+
--gateway=*)
|
|
120
|
+
GATEWAY="${1#*=}"
|
|
121
|
+
shift
|
|
122
|
+
;;
|
|
123
|
+
--port=*)
|
|
124
|
+
PORT="${1#*=}"
|
|
125
|
+
shift
|
|
126
|
+
;;
|
|
127
|
+
--ssh=*)
|
|
128
|
+
SSH_ARG="${1#*=}"
|
|
129
|
+
if [[ "$SSH_ARG" =~ ^([^:]+):([0-9]+)$ ]]; then
|
|
130
|
+
SSH_TARGET="${BASH_REMATCH[1]}"
|
|
131
|
+
SSH_PORT="${BASH_REMATCH[2]}"
|
|
132
|
+
else
|
|
133
|
+
SSH_TARGET="$SSH_ARG"
|
|
134
|
+
fi
|
|
135
|
+
shift
|
|
136
|
+
;;
|
|
137
|
+
--tls=*)
|
|
138
|
+
TLS="${1#*=}"
|
|
139
|
+
shift
|
|
140
|
+
;;
|
|
141
|
+
--domain=*)
|
|
142
|
+
DOMAIN="${1#*=}"
|
|
143
|
+
shift
|
|
144
|
+
;;
|
|
145
|
+
--dry-run)
|
|
146
|
+
DRY_RUN=true
|
|
147
|
+
shift
|
|
148
|
+
;;
|
|
149
|
+
-h|--help)
|
|
150
|
+
echo "Usage: $0 [--lang=en|ru] [-q|--quick] [--mode=tunnel|standalone|gateway] [--user=<subdomain>] [--token=<token>] [--port=<port>] [--ssh=user@host] [--dry-run]"
|
|
151
|
+
exit 0
|
|
152
|
+
;;
|
|
153
|
+
*)
|
|
154
|
+
echo -e "${YELLOW}[ERROR] Unknown parameter: $1${RESET}"
|
|
155
|
+
exit 1
|
|
156
|
+
;;
|
|
157
|
+
esac
|
|
158
|
+
done
|
|
159
|
+
|
|
160
|
+
# Language prompt if interactive and not specified
|
|
161
|
+
if [ -z "$LANG_CHOICE" ]; then
|
|
162
|
+
if [ "$QUICK" = false ] && [ -z "$MODE" ] && [ -t 0 ]; then
|
|
163
|
+
echo -e "\n${BOLD}${CYAN}╔════════════════════════════════════════════════════════════════════════╗${RESET}"
|
|
164
|
+
echo -e "${BOLD}${CYAN}║ 🌐 LANGUAGE SELECTION / ВЫБОР ЯЗЫКА ║${RESET}"
|
|
165
|
+
echo -e "${BOLD}${CYAN}╚════════════════════════════════════════════════════════════════════════╝${RESET}"
|
|
166
|
+
echo -e " ${GREEN}1)${RESET} English (Default)"
|
|
167
|
+
echo -e " ${BLUE}2)${RESET} Русский"
|
|
168
|
+
read -rp "Select / Выберите [1]: " LANG_INPUT
|
|
169
|
+
if [ "$LANG_INPUT" = "2" ] || [ "$LANG_INPUT" = "ru" ] || [ "$LANG_INPUT" = "RU" ]; then
|
|
170
|
+
LANG_CHOICE="ru"
|
|
171
|
+
else
|
|
172
|
+
LANG_CHOICE="en"
|
|
173
|
+
fi
|
|
174
|
+
else
|
|
175
|
+
LANG_CHOICE="en"
|
|
176
|
+
fi
|
|
177
|
+
fi
|
|
178
|
+
|
|
179
|
+
# Device detection
|
|
180
|
+
detect_device() {
|
|
181
|
+
DETECTED_HOSTNAME=$(hostname -s 2>/dev/null || hostname)
|
|
182
|
+
DETECTED_ARCH=$(uname -m)
|
|
183
|
+
|
|
184
|
+
if [ "$(uname -s)" = "Darwin" ]; then
|
|
185
|
+
DETECTED_OS="macOS $(sw_vers -productVersion 2>/dev/null || '')"
|
|
186
|
+
if [ "$LANG_CHOICE" = "ru" ]; then
|
|
187
|
+
DETECTED_TYPE="Apple Mac"
|
|
188
|
+
sysctl -n hw.model 2>/dev/null | grep -qi "book" && DETECTED_TYPE="Apple MacBook (Ноутбук)"
|
|
189
|
+
else
|
|
190
|
+
DETECTED_TYPE="Apple Mac"
|
|
191
|
+
sysctl -n hw.model 2>/dev/null | grep -qi "book" && DETECTED_TYPE="Apple MacBook (Laptop)"
|
|
192
|
+
fi
|
|
193
|
+
elif [ -f /etc/os-release ]; then
|
|
194
|
+
# shellcheck disable=SC1091
|
|
195
|
+
. /etc/os-release
|
|
196
|
+
DETECTED_OS="${PRETTY_NAME:-$NAME}"
|
|
197
|
+
else
|
|
198
|
+
DETECTED_OS=$(uname -s)
|
|
199
|
+
fi
|
|
200
|
+
|
|
201
|
+
if [ "$(uname -s)" != "Darwin" ]; then
|
|
202
|
+
if [ "$LANG_CHOICE" = "ru" ]; then
|
|
203
|
+
DETECTED_TYPE="Десктоп / Сервер"
|
|
204
|
+
if [ -d /sys/class/power_supply ] && ls /sys/class/power_supply/BAT* 1>/dev/null 2>&1; then
|
|
205
|
+
DETECTED_TYPE="Ноутбук (Laptop)"
|
|
206
|
+
elif grep -q -i "microsoft" /proc/version 2>/dev/null; then
|
|
207
|
+
DETECTED_TYPE="WSL (Windows Subsystem for Linux)"
|
|
208
|
+
elif [ -f /.dockerenv ] || grep -q "docker\|containerd" /proc/1/cgroup 2>/dev/null; then
|
|
209
|
+
DETECTED_TYPE="Контейнер (Docker/LXC)"
|
|
210
|
+
elif command -v systemd-detect-virt >/dev/null 2>&1 && systemd-detect-virt -q; then
|
|
211
|
+
DETECTED_TYPE="Облачный сервер / VPS ($(systemd-detect-virt))"
|
|
212
|
+
fi
|
|
213
|
+
else
|
|
214
|
+
DETECTED_TYPE="Desktop / Server"
|
|
215
|
+
if [ -d /sys/class/power_supply ] && ls /sys/class/power_supply/BAT* 1>/dev/null 2>&1; then
|
|
216
|
+
DETECTED_TYPE="Laptop"
|
|
217
|
+
elif grep -q -i "microsoft" /proc/version 2>/dev/null; then
|
|
218
|
+
DETECTED_TYPE="WSL (Windows Subsystem for Linux)"
|
|
219
|
+
elif [ -f /.dockerenv ] || grep -q "docker\|containerd" /proc/1/cgroup 2>/dev/null; then
|
|
220
|
+
DETECTED_TYPE="Container (Docker/LXC)"
|
|
221
|
+
elif command -v systemd-detect-virt >/dev/null 2>&1 && systemd-detect-virt -q; then
|
|
222
|
+
DETECTED_TYPE="Cloud VPS ($(systemd-detect-virt))"
|
|
223
|
+
fi
|
|
224
|
+
fi
|
|
225
|
+
fi
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
detect_device
|
|
229
|
+
|
|
230
|
+
print_device_info() {
|
|
231
|
+
if [ "$LANG_CHOICE" = "ru" ]; then
|
|
232
|
+
echo -e "${CYAN}╔════════════════════════════════════════════════════════════════════════╗${RESET}"
|
|
233
|
+
echo -e "${CYAN}║${RESET} ${BOLD}🔍 Обнаружено устройство:${RESET}"
|
|
234
|
+
echo -e "${CYAN}║${RESET} • Имя хоста : ${GREEN}${DETECTED_HOSTNAME}${RESET}"
|
|
235
|
+
echo -e "${CYAN}║${RESET} • Тип : ${YELLOW}${DETECTED_TYPE}${RESET}"
|
|
236
|
+
echo -e "${CYAN}║${RESET} • ОС : ${DETECTED_OS}"
|
|
237
|
+
echo -e "${CYAN}║${RESET} • Архитектура : ${DETECTED_ARCH}"
|
|
238
|
+
echo -e "${CYAN}╚════════════════════════════════════════════════════════════════════════╝${RESET}"
|
|
239
|
+
else
|
|
240
|
+
echo -e "${CYAN}╔════════════════════════════════════════════════════════════════════════╗${RESET}"
|
|
241
|
+
echo -e "${CYAN}║${RESET} ${BOLD}🔍 Detected Device:${RESET}"
|
|
242
|
+
echo -e "${CYAN}║${RESET} • Hostname : ${GREEN}${DETECTED_HOSTNAME}${RESET}"
|
|
243
|
+
echo -e "${CYAN}║${RESET} • Type : ${YELLOW}${DETECTED_TYPE}${RESET}"
|
|
244
|
+
echo -e "${CYAN}║${RESET} • OS : ${DETECTED_OS}"
|
|
245
|
+
echo -e "${CYAN}║${RESET} • Architecture: ${DETECTED_ARCH}"
|
|
246
|
+
echo -e "${CYAN}╚════════════════════════════════════════════════════════════════════════╝${RESET}"
|
|
247
|
+
fi
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
print_mcp_box() {
|
|
251
|
+
local title="$1"
|
|
252
|
+
local url="$2"
|
|
253
|
+
local copy_msg="$3"
|
|
254
|
+
|
|
255
|
+
local max_len=${#title}
|
|
256
|
+
[ ${#url} -gt $max_len ] && max_len=${#url}
|
|
257
|
+
[ -n "$copy_msg" ] && [ ${#copy_msg} -gt $max_len ] && max_len=${#copy_msg}
|
|
258
|
+
|
|
259
|
+
local width=$(( max_len + 4 ))
|
|
260
|
+
[ $width -lt 84 ] && width=84
|
|
261
|
+
|
|
262
|
+
local dashes=$(printf '%*s' "$width" '' | tr ' ' '-')
|
|
263
|
+
local border=" +${dashes}+"
|
|
264
|
+
local empty=" |$(printf '%*s' "$width" '')|"
|
|
265
|
+
|
|
266
|
+
echo -e "${GREEN}${border}${RESET}"
|
|
267
|
+
|
|
268
|
+
# Title
|
|
269
|
+
local pad_title=$(( width - 2 - ${#title} ))
|
|
270
|
+
printf " ${GREEN}|${RESET} ${BOLD}%s${RESET}%*s ${GREEN}|${RESET}\n" "$title" "$pad_title" ""
|
|
271
|
+
|
|
272
|
+
echo -e "${GREEN}${empty}${RESET}"
|
|
273
|
+
|
|
274
|
+
# URL
|
|
275
|
+
local pad_url=$(( width - 2 - ${#url} ))
|
|
276
|
+
printf " ${GREEN}|${RESET} ${BOLD}${YELLOW}%s${RESET}%*s ${GREEN}|${RESET}\n" "$url" "$pad_url" ""
|
|
277
|
+
|
|
278
|
+
echo -e "${GREEN}${empty}${RESET}"
|
|
279
|
+
|
|
280
|
+
# Copy message
|
|
281
|
+
if [ -n "$copy_msg" ]; then
|
|
282
|
+
local pad_copy=$(( width - 2 - ${#copy_msg} ))
|
|
283
|
+
printf " ${GREEN}|${RESET} ${BOLD}${GREEN}%s${RESET}%*s ${GREEN}|${RESET}\n" "$copy_msg" "$pad_copy" ""
|
|
284
|
+
fi
|
|
285
|
+
|
|
286
|
+
echo -e "${GREEN}${border}${RESET}"
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
# Dry-run
|
|
290
|
+
if [ "$DRY_RUN" = true ]; then
|
|
291
|
+
echo "[DRY-RUN] Simulating Antigravity Mesh installation..."
|
|
292
|
+
echo "[DRY-RUN] Language: $LANG_CHOICE"
|
|
293
|
+
echo "[DRY-RUN] Detected Host: $DETECTED_HOSTNAME ($DETECTED_TYPE)"
|
|
294
|
+
echo "[DRY-RUN] Selected Mode: ${MODE:-standalone}"
|
|
295
|
+
echo "[DRY-RUN] Port: $PORT"
|
|
296
|
+
echo "[DRY-RUN] TLS: $TLS"
|
|
297
|
+
if [ -n "$DOMAIN" ]; then echo "[DRY-RUN] Domain: $DOMAIN"; fi
|
|
298
|
+
if [ -n "$USERNAME" ]; then echo "[DRY-RUN] User: $USERNAME"; fi
|
|
299
|
+
echo "[DRY-RUN] Systemd service and dependencies check: OK"
|
|
300
|
+
exit 0
|
|
301
|
+
fi
|
|
302
|
+
|
|
303
|
+
# SSH Remote install branch
|
|
304
|
+
if [ -n "$SSH_TARGET" ]; then
|
|
305
|
+
if [ "$LANG_CHOICE" = "ru" ]; then
|
|
306
|
+
echo -e "${BOLD}${BLUE}=== Удаленная установка Antigravity Mesh через SSH ===${RESET}"
|
|
307
|
+
echo -e "Целевой сервер: ${CYAN}${SSH_TARGET}${RESET} (порт: ${SSH_PORT})"
|
|
308
|
+
else
|
|
309
|
+
echo -e "${BOLD}${BLUE}=== Remote Antigravity Mesh SSH Installation ===${RESET}"
|
|
310
|
+
echo -e "Target Server: ${CYAN}${SSH_TARGET}${RESET} (port: ${SSH_PORT})"
|
|
311
|
+
fi
|
|
312
|
+
|
|
313
|
+
SSH_BIN="ssh -o StrictHostKeyChecking=no"
|
|
314
|
+
SCP_BIN="scp -o StrictHostKeyChecking=no"
|
|
315
|
+
if [ -n "$SSHPASS" ] && command -v sshpass >/dev/null 2>&1; then
|
|
316
|
+
SSH_BIN="sshpass -e $SSH_BIN"
|
|
317
|
+
SCP_BIN="sshpass -e $SCP_BIN"
|
|
318
|
+
fi
|
|
319
|
+
|
|
320
|
+
$SSH_BIN -p "$SSH_PORT" "$SSH_TARGET" "mkdir -p ~/antigravity-mesh/core ~/antigravity-mesh/skills"
|
|
321
|
+
$SCP_BIN -P "$SSH_PORT" -r "$SCRIPT_DIR/core"/* "$SSH_TARGET:~/antigravity-mesh/core/"
|
|
322
|
+
$SCP_BIN -P "$SSH_PORT" "$SCRIPT_DIR/install.sh" "$SSH_TARGET:~/antigravity-mesh/"
|
|
323
|
+
if [ -d "$SCRIPT_DIR/skills" ]; then
|
|
324
|
+
$SCP_BIN -P "$SSH_PORT" -r "$SCRIPT_DIR/skills"/* "$SSH_TARGET:~/antigravity-mesh/skills/" 2>/dev/null || true
|
|
325
|
+
fi
|
|
326
|
+
$SSH_BIN -p "$SSH_PORT" "$SSH_TARGET" "cd ~/antigravity-mesh && bash install.sh --quick --lang=$LANG_CHOICE"
|
|
327
|
+
exit 0
|
|
328
|
+
fi
|
|
329
|
+
|
|
330
|
+
# Interactive Menu if no mode is specified and not in quick mode
|
|
331
|
+
if [ "$QUICK" = false ] && [ -z "$MODE" ] && [ -t 0 ]; then
|
|
332
|
+
print_device_info
|
|
333
|
+
echo ""
|
|
334
|
+
if [ "$LANG_CHOICE" = "ru" ]; then
|
|
335
|
+
echo -e "${BOLD}Выберите режим установки:${RESET}"
|
|
336
|
+
echo -e " ${GREEN}1)${RESET} ⚡ ${BOLD}Быстрая настройка${RESET} (Субдомен как имя ПК + Облачный шлюз + Автозапуск) [Рекомендуется]"
|
|
337
|
+
echo -e " ${BLUE}2)${RESET} 🖥️ ${BOLD}Локальный Standalone${RESET} (Только localhost:${PORT}, без субдомена и шлюза)"
|
|
338
|
+
echo -e " ${YELLOW}3)${RESET} ⚙️ ${BOLD}Кастомная настройка${RESET} (Ввести имя субдомена вручную, выбор шлюза)"
|
|
339
|
+
echo -e " ${CYAN}4)${RESET} 📡 ${BOLD}Удаленная установка на SSH-сервер${RESET}"
|
|
340
|
+
echo -e " ${RESET}0) Выход"
|
|
341
|
+
echo ""
|
|
342
|
+
read -rp "Ваш выбор [1]: " MENU_CHOICE
|
|
343
|
+
else
|
|
344
|
+
echo -e "${BOLD}Select Installation Mode:${RESET}"
|
|
345
|
+
echo -e " ${GREEN}1)${RESET} ⚡ ${BOLD}Quick Setup${RESET} (Auto-subdomain from PC name + Cloud Gateway + Autostart) [Recommended]"
|
|
346
|
+
echo -e " ${BLUE}2)${RESET} 🖥️ ${BOLD}Local Standalone${RESET} (localhost:${PORT} only, no subdomain, no cloud gateway)"
|
|
347
|
+
echo -e " ${YELLOW}3)${RESET} ⚙️ ${BOLD}Custom Setup${RESET} (Custom subdomain name, custom gateway/port)"
|
|
348
|
+
echo -e " ${CYAN}4)${RESET} 📡 ${BOLD}Remote SSH Installation${RESET}"
|
|
349
|
+
echo -e " ${RESET}0) Exit"
|
|
350
|
+
echo ""
|
|
351
|
+
read -rp "Select [1]: " MENU_CHOICE
|
|
352
|
+
fi
|
|
353
|
+
MENU_CHOICE=${MENU_CHOICE:-1}
|
|
354
|
+
|
|
355
|
+
case "$MENU_CHOICE" in
|
|
356
|
+
1)
|
|
357
|
+
MODE="tunnel"
|
|
358
|
+
QUICK=true
|
|
359
|
+
;;
|
|
360
|
+
2)
|
|
361
|
+
MODE="standalone"
|
|
362
|
+
;;
|
|
363
|
+
3)
|
|
364
|
+
MODE="tunnel"
|
|
365
|
+
echo ""
|
|
366
|
+
if [ "$LANG_CHOICE" = "ru" ]; then
|
|
367
|
+
read -rp "Введите имя субдомена [по умолчанию: ${DETECTED_HOSTNAME}]: " CUSTOM_SUB
|
|
368
|
+
read -rp "Хост шлюза [по умолчанию: ${GATEWAY}]: " CUSTOM_GW
|
|
369
|
+
else
|
|
370
|
+
read -rp "Enter subdomain name [default: ${DETECTED_HOSTNAME}]: " CUSTOM_SUB
|
|
371
|
+
read -rp "Gateway host [default: ${GATEWAY}]: " CUSTOM_GW
|
|
372
|
+
fi
|
|
373
|
+
if [ -n "$CUSTOM_SUB" ]; then USERNAME="$CUSTOM_SUB"; fi
|
|
374
|
+
if [ -n "$CUSTOM_GW" ]; then GATEWAY="$CUSTOM_GW"; fi
|
|
375
|
+
;;
|
|
376
|
+
4)
|
|
377
|
+
echo ""
|
|
378
|
+
if [ "$LANG_CHOICE" = "ru" ]; then
|
|
379
|
+
read -rp "Введите SSH цель (например, user@192.168.1.50): " REMOTE_TARGET
|
|
380
|
+
read -rp "Порт SSH [22]: " REMOTE_PORT
|
|
381
|
+
else
|
|
382
|
+
read -rp "Enter SSH target (e.g., user@192.168.1.50): " REMOTE_TARGET
|
|
383
|
+
read -rp "SSH Port [22]: " REMOTE_PORT
|
|
384
|
+
fi
|
|
385
|
+
REMOTE_PORT=${REMOTE_PORT:-22}
|
|
386
|
+
exec "$0" "--ssh=${REMOTE_TARGET}:${REMOTE_PORT}" "--lang=${LANG_CHOICE}"
|
|
387
|
+
;;
|
|
388
|
+
0)
|
|
389
|
+
echo "Cancelled."
|
|
390
|
+
exit 0
|
|
391
|
+
;;
|
|
392
|
+
*)
|
|
393
|
+
MODE="tunnel"
|
|
394
|
+
QUICK=true
|
|
395
|
+
;;
|
|
396
|
+
esac
|
|
397
|
+
fi
|
|
398
|
+
|
|
399
|
+
if [ -z "$MODE" ]; then
|
|
400
|
+
MODE="tunnel"
|
|
401
|
+
fi
|
|
402
|
+
|
|
403
|
+
print_device_info
|
|
404
|
+
|
|
405
|
+
# ==============================================================================
|
|
406
|
+
# BRANCH: STANDALONE MODE
|
|
407
|
+
# ==============================================================================
|
|
408
|
+
if [ "$MODE" = "standalone" ]; then
|
|
409
|
+
if [ "$LANG_CHOICE" = "ru" ]; then
|
|
410
|
+
echo -e "\n${BOLD}${BLUE}=== Настройка локального Standalone FastMCP сервера ===${RESET}"
|
|
411
|
+
echo "[1/3] Проверка окружения Python..."
|
|
412
|
+
else
|
|
413
|
+
echo -e "\n${BOLD}${BLUE}=== Setting up Local Standalone FastMCP Server ===${RESET}"
|
|
414
|
+
echo "[1/3] Checking Python environment..."
|
|
415
|
+
fi
|
|
416
|
+
ensure_dependencies
|
|
417
|
+
mkdir -p "$CONFIG_DIR"
|
|
418
|
+
|
|
419
|
+
if [ "$LANG_CHOICE" = "ru" ]; then
|
|
420
|
+
echo "[2/3] Настройка systemd автозапуска на ПК..."
|
|
421
|
+
else
|
|
422
|
+
echo "[2/3] Configuring autostart background service..."
|
|
423
|
+
fi
|
|
424
|
+
|
|
425
|
+
if [ "$(uname -s)" = "Darwin" ]; then
|
|
426
|
+
LAUNCH_AGENTS="$HOME/Library/LaunchAgents"
|
|
427
|
+
mkdir -p "$LAUNCH_AGENTS"
|
|
428
|
+
PLIST_FILE="$LAUNCH_AGENTS/com.antigravity.mesh.standalone.plist"
|
|
429
|
+
cat << EOF > "$PLIST_FILE"
|
|
430
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
431
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
432
|
+
<plist version="1.0">
|
|
433
|
+
<dict>
|
|
434
|
+
<key>Label</key>
|
|
435
|
+
<string>com.antigravity.mesh.standalone</string>
|
|
436
|
+
<key>ProgramArguments</key>
|
|
437
|
+
<array>
|
|
438
|
+
<string>$(which python3)</string>
|
|
439
|
+
<string>-m</string>
|
|
440
|
+
<string>core.server</string>
|
|
441
|
+
<string>--port=$PORT</string>
|
|
442
|
+
</array>
|
|
443
|
+
<key>WorkingDirectory</key>
|
|
444
|
+
<string>$SCRIPT_DIR</string>
|
|
445
|
+
<key>RunAtLoad</key>
|
|
446
|
+
<true/>
|
|
447
|
+
<key>KeepAlive</key>
|
|
448
|
+
<true/>
|
|
449
|
+
</dict>
|
|
450
|
+
</plist>
|
|
451
|
+
EOF
|
|
452
|
+
launchctl unload "$PLIST_FILE" 2>/dev/null || true
|
|
453
|
+
launchctl load -w "$PLIST_FILE" 2>/dev/null || true
|
|
454
|
+
else
|
|
455
|
+
USER_SYSTEMD_DIR="$HOME/.config/systemd/user"
|
|
456
|
+
mkdir -p "$USER_SYSTEMD_DIR"
|
|
457
|
+
SERVICE_FILE="$USER_SYSTEMD_DIR/agy-standalone.service"
|
|
458
|
+
cat << EOF > "$SERVICE_FILE"
|
|
459
|
+
[Unit]
|
|
460
|
+
Description=Antigravity Mesh Local Standalone MCP Server
|
|
461
|
+
After=network.target
|
|
462
|
+
|
|
463
|
+
[Service]
|
|
464
|
+
Type=simple
|
|
465
|
+
WorkingDirectory=$SCRIPT_DIR
|
|
466
|
+
ExecStart=$PYTHON_BIN -m core.server --port=$PORT
|
|
467
|
+
Restart=always
|
|
468
|
+
RestartSec=5
|
|
469
|
+
|
|
470
|
+
[Install]
|
|
471
|
+
WantedBy=default.target
|
|
472
|
+
EOF
|
|
473
|
+
systemctl --user daemon-reload 2>/dev/null || true
|
|
474
|
+
systemctl --user enable --now agy-standalone.service 2>/dev/null || {
|
|
475
|
+
pkill -f "core.server" 2>/dev/null || true
|
|
476
|
+
nohup $PYTHON_BIN -m core.server --port="$PORT" > "$CONFIG_DIR/standalone.log" 2>&1 &
|
|
477
|
+
}
|
|
478
|
+
loginctl enable-linger "$USER" 2>/dev/null || true
|
|
479
|
+
fi
|
|
480
|
+
|
|
481
|
+
if [ "$LANG_CHOICE" = "ru" ]; then
|
|
482
|
+
echo "[3/3] Проверка локального эндпоинта..."
|
|
483
|
+
else
|
|
484
|
+
echo "[3/3] Verifying local endpoint..."
|
|
485
|
+
fi
|
|
486
|
+
sleep 1
|
|
487
|
+
|
|
488
|
+
MCP_LOCAL_URL="http://localhost:${PORT}/sse"
|
|
489
|
+
|
|
490
|
+
# Quick Copy to Clipboard
|
|
491
|
+
COPIED=false
|
|
492
|
+
B64_URL=$(printf "%s" "$MCP_LOCAL_URL" | base64 | tr -d '\r\n')
|
|
493
|
+
printf "\033]52;c;%s\a" "$B64_URL" 2>/dev/null || true
|
|
494
|
+
if command -v wl-copy >/dev/null 2>&1; then
|
|
495
|
+
printf "%s" "$MCP_LOCAL_URL" | wl-copy 2>/dev/null && COPIED=true
|
|
496
|
+
elif command -v xclip >/dev/null 2>&1; then
|
|
497
|
+
printf "%s" "$MCP_LOCAL_URL" | xclip -selection clipboard 2>/dev/null && COPIED=true
|
|
498
|
+
elif command -v xsel >/dev/null 2>&1; then
|
|
499
|
+
printf "%s" "$MCP_LOCAL_URL" | xsel --clipboard --input 2>/dev/null && COPIED=true
|
|
500
|
+
elif command -v pbcopy >/dev/null 2>&1; then
|
|
501
|
+
printf "%s" "$MCP_LOCAL_URL" | pbcopy 2>/dev/null && COPIED=true
|
|
502
|
+
elif command -v clip.exe >/dev/null 2>&1; then
|
|
503
|
+
printf "%s" "$MCP_LOCAL_URL" | clip.exe 2>/dev/null && COPIED=true
|
|
504
|
+
fi
|
|
505
|
+
|
|
506
|
+
clear 2>/dev/null || printf "\033[2J\033[H" || true
|
|
507
|
+
|
|
508
|
+
if [ "$LANG_CHOICE" = "ru" ]; then
|
|
509
|
+
echo ""
|
|
510
|
+
local copy_msg=""
|
|
511
|
+
if [ "$COPIED" = true ]; then
|
|
512
|
+
copy_msg="[OK] ССЫЛКА СКОПИРОВАНА В БУФЕР ОБМЕНА! (Вставьте через Ctrl+V)"
|
|
513
|
+
else
|
|
514
|
+
copy_msg="Скопируйте ссылку выше"
|
|
515
|
+
fi
|
|
516
|
+
print_mcp_box "ССЫЛКА ЛОКАЛЬНОГО MCP-СЕРВЕРА (СКОПИРУЙТЕ):" "$MCP_LOCAL_URL" "$copy_msg"
|
|
517
|
+
echo ""
|
|
518
|
+
echo -e " ${BOLD}Куда вставлять ссылку в Google Gemini:${RESET}"
|
|
519
|
+
echo -e " 1. Откройте в браузере: ${CYAN}https://gemini.google.com/spark/apps${RESET}"
|
|
520
|
+
echo -e " 2. Нажмите ${BOLD}'Добавить приложение'${RESET} (Add app / Настройки MCP)"
|
|
521
|
+
echo -e " 3. Вставьте скопированную ссылку в поле ${BOLD}'URL сервера'${RESET} (${BOLD}Ctrl+V${RESET}) и нажмите ${BOLD}Подключить${RESET}."
|
|
522
|
+
echo ""
|
|
523
|
+
else
|
|
524
|
+
echo ""
|
|
525
|
+
local copy_msg=""
|
|
526
|
+
if [ "$COPIED" = true ]; then
|
|
527
|
+
copy_msg="[OK] URL COPIED TO CLIPBOARD! (Press Ctrl+V to paste)"
|
|
528
|
+
else
|
|
529
|
+
copy_msg="Copy the URL above"
|
|
530
|
+
fi
|
|
531
|
+
print_mcp_box "LOCAL MCP SERVER URL (COPY THIS):" "$MCP_LOCAL_URL" "$copy_msg"
|
|
532
|
+
echo ""
|
|
533
|
+
echo -e " ${BOLD}Where to paste this URL in Google Gemini:${RESET}"
|
|
534
|
+
echo -e " 1. Open in your browser: ${CYAN}https://gemini.google.com/spark/apps${RESET}"
|
|
535
|
+
echo -e " 2. Click ${BOLD}'Add app'${RESET} (or navigate to MCP settings)"
|
|
536
|
+
echo -e " 3. Paste the URL into the ${BOLD}'Server URL'${RESET} field (${BOLD}Ctrl+V${RESET}) and click ${BOLD}Connect${RESET}."
|
|
537
|
+
echo ""
|
|
538
|
+
fi
|
|
539
|
+
exit 0
|
|
540
|
+
fi
|
|
541
|
+
|
|
542
|
+
# ==============================================================================
|
|
543
|
+
# BRANCH: CLOUD GATEWAY + REVERSE TUNNEL
|
|
544
|
+
# ==============================================================================
|
|
545
|
+
if [ "$LANG_CHOICE" = "ru" ]; then
|
|
546
|
+
echo -e "\n${BOLD}${MAGENTA}=== Настройка облачного туннеля и субдомена ===${RESET}"
|
|
547
|
+
else
|
|
548
|
+
echo -e "\n${BOLD}${MAGENTA}=== Configuring Cloud Gateway Tunnel & Subdomain ===${RESET}"
|
|
549
|
+
fi
|
|
550
|
+
|
|
551
|
+
if [ -z "$USERNAME" ]; then
|
|
552
|
+
CLEAN_HOST=$(echo "$DETECTED_HOSTNAME" | tr '[:upper:]' '[:lower:]' | tr -cd 'a-z0-9_-')
|
|
553
|
+
USERNAME="$CLEAN_HOST"
|
|
554
|
+
fi
|
|
555
|
+
USERNAME=$(echo "$USERNAME" | tr '[:upper:]' '[:lower:]' | tr -cd 'a-z0-9_-')
|
|
556
|
+
[ -z "$USERNAME" ] && USERNAME="mesh-node"
|
|
557
|
+
|
|
558
|
+
mkdir -p "$CONFIG_DIR"
|
|
559
|
+
|
|
560
|
+
if [ "$LANG_CHOICE" = "ru" ]; then
|
|
561
|
+
echo "[1/4] Проверка Python зависимостей (websockets)..."
|
|
562
|
+
else
|
|
563
|
+
echo "[1/4] Checking Python dependencies (websockets)..."
|
|
564
|
+
fi
|
|
565
|
+
|
|
566
|
+
ensure_dependencies
|
|
567
|
+
|
|
568
|
+
if [ "$LANG_CHOICE" = "ru" ]; then
|
|
569
|
+
echo "[2/4] Регистрация субдомена '${USERNAME}' на шлюзе (${GATEWAY})..."
|
|
570
|
+
else
|
|
571
|
+
echo "[2/4] Registering subdomain '${USERNAME}' on Gateway (${GATEWAY})..."
|
|
572
|
+
fi
|
|
573
|
+
|
|
574
|
+
EXISTING_TOKEN=""
|
|
575
|
+
if [ -f "$CONFIG_FILE" ]; then
|
|
576
|
+
CFG_USER=$(grep "MESH_USER" "$CONFIG_FILE" 2>/dev/null | cut -d '=' -f2 || true)
|
|
577
|
+
CFG_TOKEN=$(grep "MESH_TOKEN" "$CONFIG_FILE" 2>/dev/null | cut -d '=' -f2 || true)
|
|
578
|
+
if [ "$CFG_USER" = "$USERNAME" ] && [ -n "$CFG_TOKEN" ]; then
|
|
579
|
+
EXISTING_TOKEN="$CFG_TOKEN"
|
|
580
|
+
fi
|
|
581
|
+
fi
|
|
582
|
+
|
|
583
|
+
if [ -n "$TOKEN" ]; then
|
|
584
|
+
REQ_TOKEN="$TOKEN"
|
|
585
|
+
elif [ -n "$EXISTING_TOKEN" ]; then
|
|
586
|
+
REQ_TOKEN="$EXISTING_TOKEN"
|
|
587
|
+
else
|
|
588
|
+
REQ_TOKEN=""
|
|
589
|
+
fi
|
|
590
|
+
|
|
591
|
+
DETECTED_MAC=""
|
|
592
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
593
|
+
DETECTED_MAC=$(python3 -c "import uuid; print(':'.join(['{:02x}'.format((uuid.getnode() >> ele) & 0xff) for ele in range(0,8*6,8)][::-1]))" 2>/dev/null || true)
|
|
594
|
+
fi
|
|
595
|
+
if [ -z "$DETECTED_MAC" ] || [ "$DETECTED_MAC" = "00:00:00:00:00:00" ]; then
|
|
596
|
+
for iface in /sys/class/net/*; do
|
|
597
|
+
if [ -f "$iface/address" ] && [ "$(cat "$iface/type" 2>/dev/null)" = "1" ]; then
|
|
598
|
+
addr=$(cat "$iface/address" 2>/dev/null)
|
|
599
|
+
if [ -n "$addr" ] && [ "$addr" != "00:00:00:00:00:00" ]; then
|
|
600
|
+
DETECTED_MAC="$addr"
|
|
601
|
+
break
|
|
602
|
+
fi
|
|
603
|
+
fi
|
|
604
|
+
done
|
|
605
|
+
fi
|
|
606
|
+
|
|
607
|
+
REG_BODY="{\"username\": \"${USERNAME}\", \"auto_suffix\": true, \"mac_address\": \"${DETECTED_MAC}\", \"os\": \"${DETECTED_OS}\""
|
|
608
|
+
if [ -n "$REQ_TOKEN" ]; then
|
|
609
|
+
REG_BODY="${REG_BODY}, \"token\": \"${REQ_TOKEN}\""
|
|
610
|
+
fi
|
|
611
|
+
REG_BODY="${REG_BODY}}"
|
|
612
|
+
|
|
613
|
+
REG_RESP=$(curl -s -X POST "https://${GATEWAY}/api/register" \
|
|
614
|
+
-H "Content-Type: application/json" \
|
|
615
|
+
-d "$REG_BODY")
|
|
616
|
+
|
|
617
|
+
ASSIGNED_USER=$(python3 -c "import json, sys; print(json.loads(sys.argv[1]).get('username', ''))" "$REG_RESP" 2>/dev/null || true)
|
|
618
|
+
ASSIGNED_TOKEN=$(python3 -c "import json, sys; print(json.loads(sys.argv[1]).get('token', ''))" "$REG_RESP" 2>/dev/null || true)
|
|
619
|
+
|
|
620
|
+
if [ -z "$ASSIGNED_TOKEN" ]; then
|
|
621
|
+
FALLBACK_USER="${USERNAME}-$(date +%s | tail -c 4)"
|
|
622
|
+
REG_RESP=$(curl -s -X POST "https://${GATEWAY}/api/register" \
|
|
623
|
+
-H "Content-Type: application/json" \
|
|
624
|
+
-d "{\"username\": \"${FALLBACK_USER}\", \"auto_suffix\": true, \"mac_address\": \"${DETECTED_MAC}\", \"os\": \"${DETECTED_OS}\"}")
|
|
625
|
+
ASSIGNED_USER=$(python3 -c "import json, sys; print(json.loads(sys.argv[1]).get('username', ''))" "$REG_RESP" 2>/dev/null || true)
|
|
626
|
+
ASSIGNED_TOKEN=$(python3 -c "import json, sys; print(json.loads(sys.argv[1]).get('token', ''))" "$REG_RESP" 2>/dev/null || true)
|
|
627
|
+
fi
|
|
628
|
+
|
|
629
|
+
if [ -z "$ASSIGNED_TOKEN" ]; then
|
|
630
|
+
echo -e "${RED}[ERROR] Failed to obtain authentication token from gateway.${RESET}"
|
|
631
|
+
exit 1
|
|
632
|
+
fi
|
|
633
|
+
|
|
634
|
+
if [ "$ASSIGNED_USER" != "$USERNAME" ]; then
|
|
635
|
+
if [ "$LANG_CHOICE" = "ru" ]; then
|
|
636
|
+
echo -e "${YELLOW}ℹ️ Имя '${USERNAME}' уже было занято. Автоматически назначен субдомен:${RESET} ${BOLD}${GREEN}${ASSIGNED_USER}${RESET}"
|
|
637
|
+
else
|
|
638
|
+
echo -e "${YELLOW}ℹ️ Name '${USERNAME}' was already taken. Automatically assigned subdomain:${RESET} ${BOLD}${GREEN}${ASSIGNED_USER}${RESET}"
|
|
639
|
+
fi
|
|
640
|
+
fi
|
|
641
|
+
|
|
642
|
+
if [ "$LANG_CHOICE" = "ru" ]; then
|
|
643
|
+
echo "[3/4] Сохранение конфигурации в ${CONFIG_FILE}..."
|
|
644
|
+
else
|
|
645
|
+
echo "[3/4] Saving configuration to ${CONFIG_FILE}..."
|
|
646
|
+
fi
|
|
647
|
+
|
|
648
|
+
cat << EOF > "$CONFIG_FILE"
|
|
649
|
+
MESH_GATEWAY=${GATEWAY}
|
|
650
|
+
MESH_USER=${ASSIGNED_USER}
|
|
651
|
+
MESH_TOKEN=${ASSIGNED_TOKEN}
|
|
652
|
+
EOF
|
|
653
|
+
chmod 600 "$CONFIG_FILE"
|
|
654
|
+
|
|
655
|
+
if [ "$LANG_CHOICE" = "ru" ]; then
|
|
656
|
+
echo "[4/4] Настройка и запуск службы автозапуска на ПК..."
|
|
657
|
+
else
|
|
658
|
+
echo "[4/4] Configuring and starting background autostart service..."
|
|
659
|
+
fi
|
|
660
|
+
|
|
661
|
+
if [ "$(uname -s)" = "Darwin" ]; then
|
|
662
|
+
LAUNCH_AGENTS="$HOME/Library/LaunchAgents"
|
|
663
|
+
mkdir -p "$LAUNCH_AGENTS"
|
|
664
|
+
PLIST_FILE="$LAUNCH_AGENTS/com.antigravity.mesh.agent.plist"
|
|
665
|
+
cat << EOF > "$PLIST_FILE"
|
|
666
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
667
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
668
|
+
<plist version="1.0">
|
|
669
|
+
<dict>
|
|
670
|
+
<key>Label</key>
|
|
671
|
+
<string>com.antigravity.mesh.agent</string>
|
|
672
|
+
<key>ProgramArguments</key>
|
|
673
|
+
<array>
|
|
674
|
+
<string>$(which python3)</string>
|
|
675
|
+
<string>-m</string>
|
|
676
|
+
<string>core.agent</string>
|
|
677
|
+
</array>
|
|
678
|
+
<key>WorkingDirectory</key>
|
|
679
|
+
<string>$SCRIPT_DIR</string>
|
|
680
|
+
<key>EnvironmentVariables</key>
|
|
681
|
+
<dict>
|
|
682
|
+
<key>MESH_GATEWAY</key>
|
|
683
|
+
<string>$GATEWAY</string>
|
|
684
|
+
<key>MESH_USER</key>
|
|
685
|
+
<string>$ASSIGNED_USER</string>
|
|
686
|
+
<key>MESH_TOKEN</key>
|
|
687
|
+
<string>$ASSIGNED_TOKEN</string>
|
|
688
|
+
</dict>
|
|
689
|
+
<key>RunAtLoad</key>
|
|
690
|
+
<true/>
|
|
691
|
+
<key>KeepAlive</key>
|
|
692
|
+
<true/>
|
|
693
|
+
</dict>
|
|
694
|
+
</plist>
|
|
695
|
+
EOF
|
|
696
|
+
launchctl unload "$PLIST_FILE" 2>/dev/null || true
|
|
697
|
+
launchctl load -w "$PLIST_FILE" 2>/dev/null || true
|
|
698
|
+
else
|
|
699
|
+
USER_SYSTEMD_DIR="$HOME/.config/systemd/user"
|
|
700
|
+
mkdir -p "$USER_SYSTEMD_DIR"
|
|
701
|
+
cat << EOF > "$USER_SYSTEMD_DIR/agy-agent.service"
|
|
702
|
+
[Unit]
|
|
703
|
+
Description=Antigravity Mesh Reverse RPC Agent
|
|
704
|
+
After=network.target
|
|
705
|
+
|
|
706
|
+
[Service]
|
|
707
|
+
Type=simple
|
|
708
|
+
EnvironmentFile=$CONFIG_FILE
|
|
709
|
+
WorkingDirectory=$SCRIPT_DIR
|
|
710
|
+
ExecStart=$PYTHON_BIN -m core.agent
|
|
711
|
+
Restart=always
|
|
712
|
+
RestartSec=5
|
|
713
|
+
|
|
714
|
+
[Install]
|
|
715
|
+
WantedBy=default.target
|
|
716
|
+
EOF
|
|
717
|
+
systemctl --user daemon-reload 2>/dev/null || true
|
|
718
|
+
systemctl --user enable --now agy-agent.service 2>/dev/null || {
|
|
719
|
+
pkill -f "core.agent" 2>/dev/null || true
|
|
720
|
+
nohup $PYTHON_BIN -m core.agent > "$CONFIG_DIR/agent.log" 2>&1 &
|
|
721
|
+
}
|
|
722
|
+
loginctl enable-linger "$USER" 2>/dev/null || true
|
|
723
|
+
fi
|
|
724
|
+
|
|
725
|
+
sleep 1
|
|
726
|
+
|
|
727
|
+
MCP_URL="https://${ASSIGNED_USER}.${GATEWAY}/sse?token=${ASSIGNED_TOKEN}"
|
|
728
|
+
|
|
729
|
+
# Quick Copy to Clipboard
|
|
730
|
+
COPIED=false
|
|
731
|
+
B64_URL=$(printf "%s" "$MCP_URL" | base64 | tr -d '\r\n')
|
|
732
|
+
printf "\033]52;c;%s\a" "$B64_URL" 2>/dev/null || true
|
|
733
|
+
if command -v wl-copy >/dev/null 2>&1; then
|
|
734
|
+
printf "%s" "$MCP_URL" | wl-copy 2>/dev/null && COPIED=true
|
|
735
|
+
elif command -v xclip >/dev/null 2>&1; then
|
|
736
|
+
printf "%s" "$MCP_URL" | xclip -selection clipboard 2>/dev/null && COPIED=true
|
|
737
|
+
elif command -v xsel >/dev/null 2>&1; then
|
|
738
|
+
printf "%s" "$MCP_URL" | xsel --clipboard --input 2>/dev/null && COPIED=true
|
|
739
|
+
elif command -v pbcopy >/dev/null 2>&1; then
|
|
740
|
+
printf "%s" "$MCP_URL" | pbcopy 2>/dev/null && COPIED=true
|
|
741
|
+
elif command -v clip.exe >/dev/null 2>&1; then
|
|
742
|
+
printf "%s" "$MCP_URL" | clip.exe 2>/dev/null && COPIED=true
|
|
743
|
+
fi
|
|
744
|
+
|
|
745
|
+
clear 2>/dev/null || printf "\033[2J\033[H" || true
|
|
746
|
+
|
|
747
|
+
if [ "$LANG_CHOICE" = "ru" ]; then
|
|
748
|
+
echo ""
|
|
749
|
+
local copy_msg=""
|
|
750
|
+
if [ "$COPIED" = true ]; then
|
|
751
|
+
copy_msg="[OK] ССЫЛКА СКОПИРОВАНА В БУФЕР ОБМЕНА! (Вставьте через Ctrl+V)"
|
|
752
|
+
else
|
|
753
|
+
copy_msg="Скопируйте ссылку выше"
|
|
754
|
+
fi
|
|
755
|
+
print_mcp_box "ССЫЛКА MCP-СЕРВЕРА ДЛЯ ПОДКЛЮЧЕНИЯ (СКОПИРУЙТЕ):" "$MCP_URL" "$copy_msg"
|
|
756
|
+
echo ""
|
|
757
|
+
echo -e " ${BOLD}Куда вставлять ссылку в Google Gemini:${RESET}"
|
|
758
|
+
echo -e " 1. Откройте в браузере: ${CYAN}https://gemini.google.com/spark/apps${RESET}"
|
|
759
|
+
echo -e " 2. Нажмите ${BOLD}'Добавить приложение'${RESET} (Add app / Настройки MCP)"
|
|
760
|
+
echo -e " 3. Вставьте скопированную ссылку в поле ${BOLD}'URL сервера'${RESET} (${BOLD}Ctrl+V${RESET}) и нажмите ${BOLD}Подключить${RESET}."
|
|
761
|
+
echo ""
|
|
762
|
+
else
|
|
763
|
+
echo ""
|
|
764
|
+
local copy_msg=""
|
|
765
|
+
if [ "$COPIED" = true ]; then
|
|
766
|
+
copy_msg="[OK] URL COPIED TO CLIPBOARD! (Paste with Ctrl+V)"
|
|
767
|
+
else
|
|
768
|
+
copy_msg="Copy the URL above"
|
|
769
|
+
fi
|
|
770
|
+
print_mcp_box "MCP SERVER CONNECTION URL (COPY THIS):" "$MCP_URL" "$copy_msg"
|
|
771
|
+
echo ""
|
|
772
|
+
echo -e " ${BOLD}Where to paste this URL in Google Gemini:${RESET}"
|
|
773
|
+
echo -e " 1. Open in your browser: ${CYAN}https://gemini.google.com/spark/apps${RESET}"
|
|
774
|
+
echo -e " 2. Click ${BOLD}'Add app'${RESET} (or navigate to MCP settings)"
|
|
775
|
+
echo -e " 3. Paste the URL into the ${BOLD}'Server URL'${RESET} field (${BOLD}Ctrl+V${RESET}) and click ${BOLD}Connect${RESET}."
|
|
776
|
+
echo ""
|
|
777
|
+
fi
|