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/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