gemini-computer-use 0.1.2 → 0.2.3
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/README.md +113 -2
- package/core/agent.py +69 -20
- package/core/mcp_tools.py +1658 -0
- package/core/policies.py +5 -2
- package/core/server.py +862 -5
- package/gateway.py +1491 -0
- package/install.ps1 +116 -36
- package/install.sh +116 -28
- package/package.json +4 -3
package/install.ps1
CHANGED
|
@@ -18,7 +18,10 @@ try {
|
|
|
18
18
|
$OutputEncoding = [System.Text.Encoding]::UTF8
|
|
19
19
|
} catch {}
|
|
20
20
|
|
|
21
|
-
$ErrorActionPreference = "
|
|
21
|
+
$ErrorActionPreference = "Continue"
|
|
22
|
+
if (Test-Path variable:global:PSNativeCommandUseErrorActionPreference) {
|
|
23
|
+
$global:PSNativeCommandUseErrorActionPreference = $false
|
|
24
|
+
}
|
|
22
25
|
|
|
23
26
|
$Hostname = $env:COMPUTERNAME.ToLower() -replace '[^a-z0-9_-]', ''
|
|
24
27
|
$IsLaptop = [bool](Get-CimInstance -ClassName Win32_Battery -ErrorAction SilentlyContinue)
|
|
@@ -50,20 +53,41 @@ if ($DryRun) {
|
|
|
50
53
|
exit 0
|
|
51
54
|
}
|
|
52
55
|
|
|
53
|
-
# Python check & auto-install
|
|
56
|
+
# Python check & auto-install.
|
|
57
|
+
# Windows ships a "python.exe" App Execution Alias that opens the Microsoft Store
|
|
58
|
+
# instead of running Python, so "python --version" can look successful while doing
|
|
59
|
+
# nothing. Resolve a real interpreter once and pin its full path in $PyExe.
|
|
54
60
|
$hasPython = $false
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
$PyExe = $null
|
|
62
|
+
$candidates = @()
|
|
63
|
+
$cmdPython = Get-Command python -ErrorAction SilentlyContinue
|
|
64
|
+
if ($cmdPython) { $candidates += $cmdPython.Source }
|
|
65
|
+
$candidates += @(
|
|
66
|
+
"$env:LOCALAPPDATA\Programs\Python\Python312\python.exe",
|
|
67
|
+
"$env:LOCALAPPDATA\Programs\Python\Python311\python.exe",
|
|
68
|
+
"$env:ProgramFiles\Python312\python.exe",
|
|
69
|
+
"$env:ProgramFiles\Python311\python.exe"
|
|
70
|
+
)
|
|
71
|
+
$cmdPy = Get-Command py -ErrorAction SilentlyContinue
|
|
72
|
+
if ($cmdPy) { $candidates += $cmdPy.Source }
|
|
73
|
+
|
|
74
|
+
foreach ($cand in $candidates) {
|
|
75
|
+
if (-not $cand -or -not (Test-Path $cand)) { continue }
|
|
76
|
+
try {
|
|
77
|
+
# Require a working sys import, not just a zero exit code.
|
|
78
|
+
$probe = & $cand -c "import sys; print(sys.version_info[0])" 2>&1
|
|
79
|
+
if ($LASTEXITCODE -eq 0 -and "$probe".Trim() -eq "3") {
|
|
80
|
+
if ((Split-Path $cand -Leaf) -eq "py.exe") { $PyExe = "$cand -3" } else { $PyExe = $cand }
|
|
81
|
+
$pyVer = & $cand --version 2>&1
|
|
82
|
+
if ($Lang -eq "ru") {
|
|
83
|
+
Write-Host "[1/3] Python обнаружен: $pyVer" -ForegroundColor Green
|
|
84
|
+
} else {
|
|
85
|
+
Write-Host "[1/3] Python detected: $pyVer" -ForegroundColor Green
|
|
86
|
+
}
|
|
87
|
+
$hasPython = $true
|
|
88
|
+
break
|
|
62
89
|
}
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
} catch {
|
|
66
|
-
$hasPython = $false
|
|
90
|
+
} catch { continue }
|
|
67
91
|
}
|
|
68
92
|
|
|
69
93
|
if (-not $hasPython) {
|
|
@@ -112,13 +136,47 @@ if (-not $hasPython) {
|
|
|
112
136
|
}
|
|
113
137
|
}
|
|
114
138
|
|
|
115
|
-
# Install dependencies
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
|
|
139
|
+
# Install dependencies.
|
|
140
|
+
# The tunnel agent needs "websockets"; core/mcp_tools.py and core/server.py
|
|
141
|
+
# are standard library only, so this is the single external requirement.
|
|
142
|
+
if ($Lang -eq "ru") {
|
|
143
|
+
Write-Host "[2/3] Проверка библиотек (websockets)..." -ForegroundColor Yellow
|
|
144
|
+
} else {
|
|
145
|
+
Write-Host "[2/3] Checking dependencies (websockets)..." -ForegroundColor Yellow
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
# Use the interpreter resolved above; fall back to PATH only if that failed.
|
|
149
|
+
$pyRun = if ($PyExe) { $PyExe } else { "python" }
|
|
150
|
+
$pyExeOnly = ($pyRun -split " ")[0]
|
|
151
|
+
|
|
152
|
+
# Upgrading pip first avoids the "old pip cannot build wheels" failure common
|
|
153
|
+
# on a fresh Python installation.
|
|
154
|
+
& $pyExeOnly -c "import websockets" 2>$null
|
|
155
|
+
if ($LASTEXITCODE -ne 0) {
|
|
156
|
+
& $pyExeOnly -m pip install --disable-pip-version-check --quiet --upgrade pip 2>&1 | Out-Null
|
|
157
|
+
& $pyExeOnly -m pip install --disable-pip-version-check --quiet websockets 2>&1 | Out-Null
|
|
158
|
+
if ($LASTEXITCODE -ne 0) {
|
|
159
|
+
& $pyExeOnly -m ensurepip --default-pip 2>&1 | Out-Null
|
|
160
|
+
& $pyExeOnly -m pip install --disable-pip-version-check --quiet websockets 2>&1 | Out-Null
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
& $pyExeOnly -c "import websockets" 2>$null
|
|
165
|
+
if ($LASTEXITCODE -ne 0) {
|
|
166
|
+
if ($Lang -eq "ru") {
|
|
167
|
+
Write-Host "[!] Не удалось установить websockets автоматически." -ForegroundColor Red
|
|
168
|
+
Write-Host " Выполните вручную: $pyExeOnly -m pip install websockets" -ForegroundColor Yellow
|
|
169
|
+
} else {
|
|
170
|
+
Write-Host "[!] Could not install websockets automatically." -ForegroundColor Red
|
|
171
|
+
Write-Host " Run manually: $pyExeOnly -m pip install websockets" -ForegroundColor Yellow
|
|
172
|
+
}
|
|
173
|
+
exit 1
|
|
174
|
+
}
|
|
175
|
+
if ($Lang -eq "ru") {
|
|
176
|
+
Write-Host "[OK] websockets установлен." -ForegroundColor Green
|
|
177
|
+
} else {
|
|
178
|
+
Write-Host "[OK] websockets is installed." -ForegroundColor Green
|
|
179
|
+
}
|
|
122
180
|
|
|
123
181
|
$ConfigDir = "$env:USERPROFILE\.config\antigravity-mesh"
|
|
124
182
|
if (!(Test-Path $ConfigDir)) { New-Item -ItemType Directory -Path $ConfigDir -Force | Out-Null }
|
|
@@ -135,6 +193,7 @@ if (-not $ScriptDir -or -not (Test-Path "$ScriptDir\core\agent.py")) {
|
|
|
135
193
|
$files = @(
|
|
136
194
|
"core/agent.py",
|
|
137
195
|
"core/server.py",
|
|
196
|
+
"core/mcp_tools.py",
|
|
138
197
|
"core/vitals.py",
|
|
139
198
|
"core/__init__.py",
|
|
140
199
|
"skills/orchestrator.md"
|
|
@@ -193,7 +252,7 @@ if ($Mode -eq "standalone") {
|
|
|
193
252
|
} else {
|
|
194
253
|
Write-Host "=== Starting in Local Standalone Mode on port $Port ===" -ForegroundColor Blue
|
|
195
254
|
}
|
|
196
|
-
Start-Process
|
|
255
|
+
Start-Process $pyExeOnly -ArgumentList "-m core.server --host 127.0.0.1 --port=$Port" -WorkingDirectory $ScriptDir -WindowStyle Hidden
|
|
197
256
|
$LocalUrl = "http://localhost:$Port/sse"
|
|
198
257
|
$Copied = $false
|
|
199
258
|
try {
|
|
@@ -235,9 +294,9 @@ if ([string]::IsNullOrWhiteSpace($User)) {
|
|
|
235
294
|
}
|
|
236
295
|
|
|
237
296
|
if ($Lang -eq "ru") {
|
|
238
|
-
Write-Host "[2/3] Регистрация
|
|
297
|
+
Write-Host "[2/3] Регистрация узла '$User' на общем домене ($Gateway)..." -ForegroundColor Yellow
|
|
239
298
|
} else {
|
|
240
|
-
Write-Host "[2/3] Registering
|
|
299
|
+
Write-Host "[2/3] Registering node '$User' on the shared domain ($Gateway)..." -ForegroundColor Yellow
|
|
241
300
|
}
|
|
242
301
|
$detectedMac = ""
|
|
243
302
|
try {
|
|
@@ -248,7 +307,10 @@ try {
|
|
|
248
307
|
} catch {}
|
|
249
308
|
if ([string]::IsNullOrWhiteSpace($detectedMac)) {
|
|
250
309
|
try {
|
|
251
|
-
$
|
|
310
|
+
$res = python -c "import uuid; print(':'.join(['{:02x}'.format((uuid.getnode() >> ele) & 0xff) for ele in range(0,8*6,8)][::-1]))" 2>&1
|
|
311
|
+
if ($LASTEXITCODE -eq 0 -and $res) {
|
|
312
|
+
$detectedMac = $res.ToString().Trim()
|
|
313
|
+
}
|
|
252
314
|
} catch {}
|
|
253
315
|
}
|
|
254
316
|
|
|
@@ -268,7 +330,20 @@ $response = Invoke-RestMethod -Uri "https://$Gateway/api/register" -Method Post
|
|
|
268
330
|
|
|
269
331
|
$AssignedUser = $response.username
|
|
270
332
|
$AssignedToken = $response.token
|
|
333
|
+
|
|
334
|
+
# ------------------------------------------------------------------------------
|
|
335
|
+
# SHARED-DOMAIN CONTRACT: one public domain serves every node and the node is
|
|
336
|
+
# selected by ?user=<node-name>. Never build a per-device subdomain URL: each
|
|
337
|
+
# extra hostname needs its own DNS record and TLS SAN, and a missing SAN makes
|
|
338
|
+
# the Gemini client fail with an opaque "cannot connect to host" error.
|
|
339
|
+
# The gateway returns this canonical URL; the fallback keeps older gateways
|
|
340
|
+
# working without ever advertising a subdomain.
|
|
341
|
+
# ------------------------------------------------------------------------------
|
|
342
|
+
$PublicBaseUrl = "https://$Gateway"
|
|
271
343
|
$SseUrl = $response.sse_url
|
|
344
|
+
if ([string]::IsNullOrWhiteSpace($SseUrl)) {
|
|
345
|
+
$SseUrl = "$PublicBaseUrl/sse?user=$AssignedUser&token=$AssignedToken"
|
|
346
|
+
}
|
|
272
347
|
|
|
273
348
|
# Quick Copy to Clipboard
|
|
274
349
|
$Copied = $false
|
|
@@ -299,20 +374,25 @@ $env:MESH_GATEWAY = $Gateway
|
|
|
299
374
|
$env:MESH_USER = $AssignedUser
|
|
300
375
|
$env:MESH_TOKEN = $AssignedToken
|
|
301
376
|
|
|
302
|
-
# Create Windows Startup launcher for persistent autostart
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
WshShell
|
|
311
|
-
WshShell.
|
|
312
|
-
"
|
|
377
|
+
# Create Windows Startup launcher for persistent autostart.
|
|
378
|
+
# The interpreter is pinned by full path: relying on PATH breaks when the
|
|
379
|
+
# Microsoft Store "python" alias is present or PATH changes between sessions.
|
|
380
|
+
$startupDir = [Environment]::GetFolderPath("Startup")
|
|
381
|
+
$startupVbs = "$startupDir\antigravity-agent.vbs"
|
|
382
|
+
$pyExeForVbs = if ($pyExeOnly) { $pyExeOnly } else { "python" }
|
|
383
|
+
$agentLog = "$ConfigDir\agent.log"
|
|
384
|
+
@"
|
|
385
|
+
Set WshShell = CreateObject("WScript.Shell")
|
|
386
|
+
WshShell.Environment("PROCESS")("MESH_GATEWAY") = "$Gateway"
|
|
387
|
+
WshShell.Environment("PROCESS")("MESH_USER") = "$AssignedUser"
|
|
388
|
+
WshShell.Environment("PROCESS")("MESH_TOKEN") = "$AssignedToken"
|
|
389
|
+
WshShell.CurrentDirectory = "$ScriptDir"
|
|
390
|
+
' Log the output so a silent autostart failure can be diagnosed later.
|
|
391
|
+
WshShell.Run "cmd /c """"$pyExeForVbs"" -m core.agent >> """"$agentLog"""" 2>&1""", 0, False
|
|
392
|
+
"@ | Out-File -FilePath $startupVbs -Encoding ascii
|
|
313
393
|
|
|
314
394
|
# Launch now
|
|
315
|
-
Start-Process
|
|
395
|
+
Start-Process $pyExeOnly -ArgumentList "-m core.agent" -WorkingDirectory $ScriptDir -WindowStyle Hidden
|
|
316
396
|
|
|
317
397
|
Clear-Host
|
|
318
398
|
|
package/install.sh
CHANGED
|
@@ -29,6 +29,7 @@ if [ ! -f "$SCRIPT_DIR/core/agent.py" ]; then
|
|
|
29
29
|
mkdir -p "$BOOTSTRAP_DIR/core" "$BOOTSTRAP_DIR/skills"
|
|
30
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
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/mcp_tools.py" -o "$BOOTSTRAP_DIR/core/mcp_tools.py" 2>/dev/null || curl -fsSL "https://raw.githubusercontent.com/LevRa7/Computer-use-for-Gemini-App-Web/main/core/mcp_tools.py" -o "$BOOTSTRAP_DIR/core/mcp_tools.py" 2>/dev/null || true
|
|
32
33
|
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
34
|
curl -fsSL "https://${GATEWAY}/core/__init__.py" -o "$BOOTSTRAP_DIR/core/__init__.py" 2>/dev/null || true
|
|
34
35
|
curl -fsSL "https://${GATEWAY}/skills/orchestrator.md" -o "$BOOTSTRAP_DIR/skills/orchestrator.md" 2>/dev/null || true
|
|
@@ -139,7 +140,10 @@ while [[ $# -gt 0 ]]; do
|
|
|
139
140
|
shift
|
|
140
141
|
;;
|
|
141
142
|
--domain=*)
|
|
143
|
+
# The shared public domain that serves every node. This is NOT a
|
|
144
|
+
# per-device subdomain: nodes are selected with ?user=<node>.
|
|
142
145
|
DOMAIN="${1#*=}"
|
|
146
|
+
GATEWAY="$DOMAIN"
|
|
143
147
|
shift
|
|
144
148
|
;;
|
|
145
149
|
--dry-run)
|
|
@@ -147,7 +151,10 @@ while [[ $# -gt 0 ]]; do
|
|
|
147
151
|
shift
|
|
148
152
|
;;
|
|
149
153
|
-h|--help)
|
|
150
|
-
echo "Usage: $0 [--lang=en|ru] [-q|--quick] [--mode=tunnel|standalone|gateway] [--user=<
|
|
154
|
+
echo "Usage: $0 [--lang=en|ru] [-q|--quick] [--mode=tunnel|standalone|gateway] [--user=<node-name>] [--token=<token>] [--domain=<shared-domain>] [--port=<port>] [--ssh=user@host] [--dry-run]"
|
|
155
|
+
echo ""
|
|
156
|
+
echo "All nodes use ONE shared domain; the node is selected by ?user=<node-name>:"
|
|
157
|
+
echo " MCP URL: https://<shared-domain>/sse?user=<node-name>&token=<token>"
|
|
151
158
|
exit 0
|
|
152
159
|
;;
|
|
153
160
|
*)
|
|
@@ -157,6 +164,20 @@ while [[ $# -gt 0 ]]; do
|
|
|
157
164
|
esac
|
|
158
165
|
done
|
|
159
166
|
|
|
167
|
+
# ------------------------------------------------------------------------------
|
|
168
|
+
# SHARED-DOMAIN CONTRACT (canonical step)
|
|
169
|
+
# Every node is reached through ONE public domain ($GATEWAY); the node is
|
|
170
|
+
# selected by the ?user= query parameter. The installer never mints a
|
|
171
|
+
# per-device subdomain: each extra hostname would need its own DNS record and
|
|
172
|
+
# TLS SAN, and a name missing from the certificate fails the handshake with an
|
|
173
|
+
# opaque "cannot connect to host" error in the Gemini client.
|
|
174
|
+
# sse https://<shared-domain>/sse?user=<node-name>&token=<token>
|
|
175
|
+
# http https://<shared-domain>/mcp?user=<node-name>&token=<token>
|
|
176
|
+
# tunnel wss://<shared-domain>/ws/tunnel?user=<node-name>&token=<token>
|
|
177
|
+
# $GATEWAY is read at call time so the interactive menu can still change it.
|
|
178
|
+
# ------------------------------------------------------------------------------
|
|
179
|
+
node_sse_url() { printf 'https://%s/sse?user=%s&token=%s' "$GATEWAY" "$1" "$2"; }
|
|
180
|
+
|
|
160
181
|
# Language prompt if interactive and not specified
|
|
161
182
|
if [ -z "$LANG_CHOICE" ]; then
|
|
162
183
|
if [ "$QUICK" = false ] && [ -z "$MODE" ] && [ -t 0 ]; then
|
|
@@ -295,7 +316,11 @@ if [ "$DRY_RUN" = true ]; then
|
|
|
295
316
|
echo "[DRY-RUN] Port: $PORT"
|
|
296
317
|
echo "[DRY-RUN] TLS: $TLS"
|
|
297
318
|
if [ -n "$DOMAIN" ]; then echo "[DRY-RUN] Domain: $DOMAIN"; fi
|
|
298
|
-
if [ -n "$USERNAME" ]; then
|
|
319
|
+
if [ -n "$USERNAME" ]; then
|
|
320
|
+
echo "[DRY-RUN] User: $USERNAME"
|
|
321
|
+
echo "[DRY-RUN] Canonical MCP URL: $(node_sse_url "$USERNAME" "${TOKEN:-<token>}")"
|
|
322
|
+
fi
|
|
323
|
+
if [ -n "$TOKEN" ]; then echo "[DRY-RUN] Token: set (standalone: stored in ${CONFIG_DIR}/standalone.env, chmod 600)"; fi
|
|
299
324
|
echo "[DRY-RUN] Systemd service and dependencies check: OK"
|
|
300
325
|
exit 0
|
|
301
326
|
fi
|
|
@@ -333,18 +358,18 @@ if [ "$QUICK" = false ] && [ -z "$MODE" ] && [ -t 0 ]; then
|
|
|
333
358
|
echo ""
|
|
334
359
|
if [ "$LANG_CHOICE" = "ru" ]; then
|
|
335
360
|
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} (Ввести имя
|
|
361
|
+
echo -e " ${GREEN}1)${RESET} ⚡ ${BOLD}Быстрая настройка${RESET} (Имя узла как имя ПК + Общий домен + Автозапуск) [Рекомендуется]"
|
|
362
|
+
echo -e " ${BLUE}2)${RESET} 🖥️ ${BOLD}Локальный Standalone${RESET} (Только localhost:${PORT}, без облачного шлюза)"
|
|
363
|
+
echo -e " ${YELLOW}3)${RESET} ⚙️ ${BOLD}Кастомная настройка${RESET} (Ввести имя узла вручную, выбор общего домена)"
|
|
339
364
|
echo -e " ${CYAN}4)${RESET} 📡 ${BOLD}Удаленная установка на SSH-сервер${RESET}"
|
|
340
365
|
echo -e " ${RESET}0) Выход"
|
|
341
366
|
echo ""
|
|
342
367
|
read -rp "Ваш выбор [1]: " MENU_CHOICE
|
|
343
368
|
else
|
|
344
369
|
echo -e "${BOLD}Select Installation Mode:${RESET}"
|
|
345
|
-
echo -e " ${GREEN}1)${RESET} ⚡ ${BOLD}Quick Setup${RESET} (
|
|
346
|
-
echo -e " ${BLUE}2)${RESET} 🖥️ ${BOLD}Local Standalone${RESET} (localhost:${PORT} only, no
|
|
347
|
-
echo -e " ${YELLOW}3)${RESET} ⚙️ ${BOLD}Custom Setup${RESET} (Custom
|
|
370
|
+
echo -e " ${GREEN}1)${RESET} ⚡ ${BOLD}Quick Setup${RESET} (Node name from PC name + shared domain + Autostart) [Recommended]"
|
|
371
|
+
echo -e " ${BLUE}2)${RESET} 🖥️ ${BOLD}Local Standalone${RESET} (localhost:${PORT} only, no cloud gateway)"
|
|
372
|
+
echo -e " ${YELLOW}3)${RESET} ⚙️ ${BOLD}Custom Setup${RESET} (Custom node name, custom shared domain)"
|
|
348
373
|
echo -e " ${CYAN}4)${RESET} 📡 ${BOLD}Remote SSH Installation${RESET}"
|
|
349
374
|
echo -e " ${RESET}0) Exit"
|
|
350
375
|
echo ""
|
|
@@ -364,11 +389,11 @@ if [ "$QUICK" = false ] && [ -z "$MODE" ] && [ -t 0 ]; then
|
|
|
364
389
|
MODE="tunnel"
|
|
365
390
|
echo ""
|
|
366
391
|
if [ "$LANG_CHOICE" = "ru" ]; then
|
|
367
|
-
read -rp "Введите имя
|
|
368
|
-
read -rp "
|
|
392
|
+
read -rp "Введите имя узла [по умолчанию: ${DETECTED_HOSTNAME}]: " CUSTOM_SUB
|
|
393
|
+
read -rp "Общий домен шлюза [по умолчанию: ${GATEWAY}]: " CUSTOM_GW
|
|
369
394
|
else
|
|
370
|
-
read -rp "Enter
|
|
371
|
-
read -rp "
|
|
395
|
+
read -rp "Enter node name [default: ${DETECTED_HOSTNAME}]: " CUSTOM_SUB
|
|
396
|
+
read -rp "Shared gateway domain [default: ${GATEWAY}]: " CUSTOM_GW
|
|
372
397
|
fi
|
|
373
398
|
if [ -n "$CUSTOM_SUB" ]; then USERNAME="$CUSTOM_SUB"; fi
|
|
374
399
|
if [ -n "$CUSTOM_GW" ]; then GATEWAY="$CUSTOM_GW"; fi
|
|
@@ -376,10 +401,10 @@ if [ "$QUICK" = false ] && [ -z "$MODE" ] && [ -t 0 ]; then
|
|
|
376
401
|
4)
|
|
377
402
|
echo ""
|
|
378
403
|
if [ "$LANG_CHOICE" = "ru" ]; then
|
|
379
|
-
read -rp "Введите SSH цель (например, user
|
|
404
|
+
read -rp "Введите SSH цель (например, user@<host-ip>): " REMOTE_TARGET
|
|
380
405
|
read -rp "Порт SSH [22]: " REMOTE_PORT
|
|
381
406
|
else
|
|
382
|
-
read -rp "Enter SSH target (e.g., user
|
|
407
|
+
read -rp "Enter SSH target (e.g., user@<host-ip>): " REMOTE_TARGET
|
|
383
408
|
read -rp "SSH Port [22]: " REMOTE_PORT
|
|
384
409
|
fi
|
|
385
410
|
REMOTE_PORT=${REMOTE_PORT:-22}
|
|
@@ -416,6 +441,37 @@ if [ "$MODE" = "standalone" ]; then
|
|
|
416
441
|
ensure_dependencies
|
|
417
442
|
mkdir -p "$CONFIG_DIR"
|
|
418
443
|
|
|
444
|
+
# Honour --token in standalone mode without embedding the secret in the
|
|
445
|
+
# (world-readable) unit/plist: keep it in a 0600 env file for systemd
|
|
446
|
+
# (EnvironmentFile) and use a 0600 plist environment block on macOS.
|
|
447
|
+
# core.server reads MESH_TOKEN as the default for --token.
|
|
448
|
+
STANDALONE_ENV_FILE="$CONFIG_DIR/standalone.env"
|
|
449
|
+
ENV_FILE_LINE=""
|
|
450
|
+
PLIST_ENV_BLOCK=""
|
|
451
|
+
if [ -n "$TOKEN" ]; then
|
|
452
|
+
printf 'MESH_TOKEN=%s\n' "$TOKEN" > "$STANDALONE_ENV_FILE"
|
|
453
|
+
chmod 600 "$STANDALONE_ENV_FILE"
|
|
454
|
+
ENV_FILE_LINE="EnvironmentFile=$STANDALONE_ENV_FILE"
|
|
455
|
+
PLIST_TOKEN=$(printf '%s' "$TOKEN" | sed -e 's/&/\&/g' -e 's/</\</g' -e 's/>/\>/g')
|
|
456
|
+
PLIST_ENV_BLOCK=" <key>EnvironmentVariables</key>
|
|
457
|
+
<dict>
|
|
458
|
+
<key>MESH_TOKEN</key>
|
|
459
|
+
<string>${PLIST_TOKEN}</string>
|
|
460
|
+
</dict>"
|
|
461
|
+
if [ "$LANG_CHOICE" = "ru" ]; then
|
|
462
|
+
echo -e "${GREEN}[✓] Токен авторизации включён (chmod 600).${RESET}"
|
|
463
|
+
else
|
|
464
|
+
echo -e "${GREEN}[OK] Authorization token enabled (chmod 600).${RESET}"
|
|
465
|
+
fi
|
|
466
|
+
else
|
|
467
|
+
rm -f "$STANDALONE_ENV_FILE" 2>/dev/null || true
|
|
468
|
+
if [ "$LANG_CHOICE" = "ru" ]; then
|
|
469
|
+
echo -e "${YELLOW}[!] Токен не задан (--token): сервер доступен без авторизации на 127.0.0.1.${RESET}"
|
|
470
|
+
else
|
|
471
|
+
echo -e "${YELLOW}[!] No --token given: server is unauthenticated on 127.0.0.1.${RESET}"
|
|
472
|
+
fi
|
|
473
|
+
fi
|
|
474
|
+
|
|
419
475
|
if [ "$LANG_CHOICE" = "ru" ]; then
|
|
420
476
|
echo "[2/3] Настройка systemd автозапуска на ПК..."
|
|
421
477
|
else
|
|
@@ -438,6 +494,7 @@ if [ "$MODE" = "standalone" ]; then
|
|
|
438
494
|
<string>$(which python3)</string>
|
|
439
495
|
<string>-m</string>
|
|
440
496
|
<string>core.server</string>
|
|
497
|
+
<string>--host=127.0.0.1</string>
|
|
441
498
|
<string>--port=$PORT</string>
|
|
442
499
|
</array>
|
|
443
500
|
<key>WorkingDirectory</key>
|
|
@@ -446,9 +503,13 @@ if [ "$MODE" = "standalone" ]; then
|
|
|
446
503
|
<true/>
|
|
447
504
|
<key>KeepAlive</key>
|
|
448
505
|
<true/>
|
|
506
|
+
$PLIST_ENV_BLOCK
|
|
449
507
|
</dict>
|
|
450
508
|
</plist>
|
|
451
509
|
EOF
|
|
510
|
+
if [ -n "$TOKEN" ]; then
|
|
511
|
+
chmod 600 "$PLIST_FILE"
|
|
512
|
+
fi
|
|
452
513
|
launchctl unload "$PLIST_FILE" 2>/dev/null || true
|
|
453
514
|
launchctl load -w "$PLIST_FILE" 2>/dev/null || true
|
|
454
515
|
else
|
|
@@ -462,8 +523,9 @@ After=network.target
|
|
|
462
523
|
|
|
463
524
|
[Service]
|
|
464
525
|
Type=simple
|
|
526
|
+
$ENV_FILE_LINE
|
|
465
527
|
WorkingDirectory=$SCRIPT_DIR
|
|
466
|
-
ExecStart=$PYTHON_BIN -m core.server --port=$PORT
|
|
528
|
+
ExecStart=$PYTHON_BIN -m core.server --host 127.0.0.1 --port=$PORT
|
|
467
529
|
Restart=always
|
|
468
530
|
RestartSec=5
|
|
469
531
|
|
|
@@ -473,7 +535,11 @@ EOF
|
|
|
473
535
|
systemctl --user daemon-reload 2>/dev/null || true
|
|
474
536
|
systemctl --user enable --now agy-standalone.service 2>/dev/null || {
|
|
475
537
|
pkill -f "core.server" 2>/dev/null || true
|
|
476
|
-
|
|
538
|
+
if [ -n "$TOKEN" ]; then
|
|
539
|
+
nohup env MESH_TOKEN="$TOKEN" $PYTHON_BIN -m core.server --host 127.0.0.1 --port="$PORT" > "$CONFIG_DIR/standalone.log" 2>&1 &
|
|
540
|
+
else
|
|
541
|
+
nohup $PYTHON_BIN -m core.server --host 127.0.0.1 --port="$PORT" > "$CONFIG_DIR/standalone.log" 2>&1 &
|
|
542
|
+
fi
|
|
477
543
|
}
|
|
478
544
|
loginctl enable-linger "$USER" 2>/dev/null || true
|
|
479
545
|
fi
|
|
@@ -483,7 +549,28 @@ EOF
|
|
|
483
549
|
else
|
|
484
550
|
echo "[3/3] Verifying local endpoint..."
|
|
485
551
|
fi
|
|
486
|
-
|
|
552
|
+
HEALTH_URL="http://127.0.0.1:${PORT}/health"
|
|
553
|
+
HEALTH_OK=false
|
|
554
|
+
for _attempt in 1 2 3 4 5 6 7 8 9 10; do
|
|
555
|
+
HTTP_CODE=$(curl -s -o /dev/null -m 2 -w "%{http_code}" "$HEALTH_URL" 2>/dev/null || true)
|
|
556
|
+
if [ "$HTTP_CODE" = "200" ]; then
|
|
557
|
+
HEALTH_OK=true
|
|
558
|
+
break
|
|
559
|
+
fi
|
|
560
|
+
sleep 1
|
|
561
|
+
done
|
|
562
|
+
if [ "$HEALTH_OK" != true ]; then
|
|
563
|
+
if [ "$LANG_CHOICE" = "ru" ]; then
|
|
564
|
+
echo -e "\033[1;31m[ОШИБКА] Standalone MCP-сервер не отвечает (HTTP ${HTTP_CODE:-нет ответа}): $HEALTH_URL\033[0m" >&2
|
|
565
|
+
echo -e " Проверьте статус: ${CYAN}systemctl --user status agy-standalone.service${RESET}" >&2
|
|
566
|
+
echo -e " Логи: ${CYAN}journalctl --user -u agy-standalone.service -n 50 --no-pager${RESET}" >&2
|
|
567
|
+
else
|
|
568
|
+
echo -e "\033[1;31m[ERROR] Standalone MCP server is not responding (HTTP ${HTTP_CODE:-no response}): $HEALTH_URL\033[0m" >&2
|
|
569
|
+
echo -e " Check status: ${CYAN}systemctl --user status agy-standalone.service${RESET}" >&2
|
|
570
|
+
echo -e " Logs: ${CYAN}journalctl --user -u agy-standalone.service -n 50 --no-pager${RESET}" >&2
|
|
571
|
+
fi
|
|
572
|
+
exit 1
|
|
573
|
+
fi
|
|
487
574
|
|
|
488
575
|
MCP_LOCAL_URL="http://localhost:${PORT}/sse"
|
|
489
576
|
|
|
@@ -507,7 +594,7 @@ EOF
|
|
|
507
594
|
|
|
508
595
|
if [ "$LANG_CHOICE" = "ru" ]; then
|
|
509
596
|
echo ""
|
|
510
|
-
|
|
597
|
+
copy_msg=""
|
|
511
598
|
if [ "$COPIED" = true ]; then
|
|
512
599
|
copy_msg="[OK] ССЫЛКА СКОПИРОВАНА В БУФЕР ОБМЕНА! (Вставьте через Ctrl+V)"
|
|
513
600
|
else
|
|
@@ -522,7 +609,7 @@ EOF
|
|
|
522
609
|
echo ""
|
|
523
610
|
else
|
|
524
611
|
echo ""
|
|
525
|
-
|
|
612
|
+
copy_msg=""
|
|
526
613
|
if [ "$COPIED" = true ]; then
|
|
527
614
|
copy_msg="[OK] URL COPIED TO CLIPBOARD! (Press Ctrl+V to paste)"
|
|
528
615
|
else
|
|
@@ -543,9 +630,9 @@ fi
|
|
|
543
630
|
# BRANCH: CLOUD GATEWAY + REVERSE TUNNEL
|
|
544
631
|
# ==============================================================================
|
|
545
632
|
if [ "$LANG_CHOICE" = "ru" ]; then
|
|
546
|
-
echo -e "\n${BOLD}${MAGENTA}=== Настройка облачного туннеля
|
|
633
|
+
echo -e "\n${BOLD}${MAGENTA}=== Настройка облачного туннеля (общий домен) ===${RESET}"
|
|
547
634
|
else
|
|
548
|
-
echo -e "\n${BOLD}${MAGENTA}=== Configuring Cloud Gateway Tunnel
|
|
635
|
+
echo -e "\n${BOLD}${MAGENTA}=== Configuring Cloud Gateway Tunnel (shared domain) ===${RESET}"
|
|
549
636
|
fi
|
|
550
637
|
|
|
551
638
|
if [ -z "$USERNAME" ]; then
|
|
@@ -566,9 +653,9 @@ fi
|
|
|
566
653
|
ensure_dependencies
|
|
567
654
|
|
|
568
655
|
if [ "$LANG_CHOICE" = "ru" ]; then
|
|
569
|
-
echo "[2/4] Регистрация
|
|
656
|
+
echo "[2/4] Регистрация узла '${USERNAME}' на общем домене (${GATEWAY})..."
|
|
570
657
|
else
|
|
571
|
-
echo "[2/4] Registering
|
|
658
|
+
echo "[2/4] Registering node '${USERNAME}' on the shared domain (${GATEWAY})..."
|
|
572
659
|
fi
|
|
573
660
|
|
|
574
661
|
EXISTING_TOKEN=""
|
|
@@ -633,9 +720,9 @@ fi
|
|
|
633
720
|
|
|
634
721
|
if [ "$ASSIGNED_USER" != "$USERNAME" ]; then
|
|
635
722
|
if [ "$LANG_CHOICE" = "ru" ]; then
|
|
636
|
-
echo -e "${YELLOW}ℹ️ Имя '${USERNAME}' уже было занято. Автоматически
|
|
723
|
+
echo -e "${YELLOW}ℹ️ Имя '${USERNAME}' уже было занято. Автоматически назначено имя узла:${RESET} ${BOLD}${GREEN}${ASSIGNED_USER}${RESET}"
|
|
637
724
|
else
|
|
638
|
-
echo -e "${YELLOW}ℹ️ Name '${USERNAME}' was already taken. Automatically assigned
|
|
725
|
+
echo -e "${YELLOW}ℹ️ Name '${USERNAME}' was already taken. Automatically assigned node name:${RESET} ${BOLD}${GREEN}${ASSIGNED_USER}${RESET}"
|
|
639
726
|
fi
|
|
640
727
|
fi
|
|
641
728
|
|
|
@@ -724,7 +811,8 @@ fi
|
|
|
724
811
|
|
|
725
812
|
sleep 1
|
|
726
813
|
|
|
727
|
-
|
|
814
|
+
# Canonical shared-domain MCP URL (one domain for every node; ?user= selects it).
|
|
815
|
+
MCP_URL="$(node_sse_url "$ASSIGNED_USER" "$ASSIGNED_TOKEN")"
|
|
728
816
|
|
|
729
817
|
# Quick Copy to Clipboard
|
|
730
818
|
COPIED=false
|
|
@@ -746,7 +834,7 @@ clear 2>/dev/null || printf "\033[2J\033[H" || true
|
|
|
746
834
|
|
|
747
835
|
if [ "$LANG_CHOICE" = "ru" ]; then
|
|
748
836
|
echo ""
|
|
749
|
-
|
|
837
|
+
copy_msg=""
|
|
750
838
|
if [ "$COPIED" = true ]; then
|
|
751
839
|
copy_msg="[OK] ССЫЛКА СКОПИРОВАНА В БУФЕР ОБМЕНА! (Вставьте через Ctrl+V)"
|
|
752
840
|
else
|
|
@@ -761,7 +849,7 @@ if [ "$LANG_CHOICE" = "ru" ]; then
|
|
|
761
849
|
echo ""
|
|
762
850
|
else
|
|
763
851
|
echo ""
|
|
764
|
-
|
|
852
|
+
copy_msg=""
|
|
765
853
|
if [ "$COPIED" = true ]; then
|
|
766
854
|
copy_msg="[OK] URL COPIED TO CLIPBOARD! (Paste with Ctrl+V)"
|
|
767
855
|
else
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gemini-computer-use",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Turn your PC, Laptop
|
|
3
|
+
"version": "0.2.3",
|
|
4
|
+
"description": "Turn your PC, Laptop or Server into an autonomous execution node for Google Gemini via native MCP: 14 tools, write/edit/search, background jobs, secure reverse tunnel.",
|
|
5
5
|
"main": "bin/cli.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"gemini-computer-use": "bin/cli.js"
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"install.sh",
|
|
17
17
|
"install.ps1",
|
|
18
18
|
"README.md",
|
|
19
|
-
"LICENSE"
|
|
19
|
+
"LICENSE",
|
|
20
|
+
"gateway.py"
|
|
20
21
|
],
|
|
21
22
|
"keywords": [
|
|
22
23
|
"gemini",
|