gemini-computer-use 0.1.2 → 0.2.4

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.ps1 CHANGED
@@ -18,7 +18,10 @@ try {
18
18
  $OutputEncoding = [System.Text.Encoding]::UTF8
19
19
  } catch {}
20
20
 
21
- $ErrorActionPreference = "Stop"
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
- try {
56
- $pyVer = python --version 2>&1
57
- if ($LASTEXITCODE -eq 0 -or $pyVer -match "Python 3") {
58
- if ($Lang -eq "ru") {
59
- Write-Host "[1/3] Python обнаружен: $pyVer" -ForegroundColor Green
60
- } else {
61
- Write-Host "[1/3] Python detected: $pyVer" -ForegroundColor Green
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
- $hasPython = $true
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
- if ($Lang -eq "ru") {
117
- Write-Host "[2/3] Проверка библиотек (websockets)..." -ForegroundColor Yellow
118
- } else {
119
- Write-Host "[2/3] Checking dependencies (websockets)..." -ForegroundColor Yellow
120
- }
121
- python -m pip install websockets --quiet 2>$null; if ($LASTEXITCODE -ne 0) { python -m ensurepip --default-pip 2>$null; python -m pip install websockets --quiet }
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 python -ArgumentList "-m core.server --port=$Port" -WorkingDirectory $ScriptDir -WindowStyle Hidden
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] Регистрация субдомена '$User' на шлюзе ($Gateway)..." -ForegroundColor Yellow
297
+ Write-Host "[2/3] Регистрация узла '$User' на общем домене ($Gateway)..." -ForegroundColor Yellow
239
298
  } else {
240
- Write-Host "[2/3] Registering subdomain '$User' on gateway ($Gateway)..." -ForegroundColor Yellow
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
- $detectedMac = python -c "import uuid; print(':'.join(['{:02x}'.format((uuid.getnode() >> ele) & 0xff) for ele in range(0,8*6,8)][::-1]))" 2>$null
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
- $startupDir = [Environment]::GetFolderPath("Startup")
304
- $startupVbs = "$startupDir\antigravity-agent.vbs"
305
- @"
306
- Set WshShell = CreateObject("WScript.Shell")
307
- WshShell.Environment("PROCESS")("MESH_GATEWAY") = "$Gateway"
308
- WshShell.Environment("PROCESS")("MESH_USER") = "$AssignedUser"
309
- WshShell.Environment("PROCESS")("MESH_TOKEN") = "$AssignedToken"
310
- WshShell.CurrentDirectory = "$ScriptDir"
311
- WshShell.Run "python -m core.agent", 0, False
312
- "@ | Out-File -FilePath $startupVbs -Encoding ascii
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 python -ArgumentList "-m core.agent" -WorkingDirectory $ScriptDir -WindowStyle Hidden
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=<subdomain>] [--token=<token>] [--port=<port>] [--ssh=user@host] [--dry-run]"
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 echo "[DRY-RUN] User: $USERNAME"; fi
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} (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)"
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 "Введите имя субдомена [по умолчанию: ${DETECTED_HOSTNAME}]: " CUSTOM_SUB
368
- read -rp "Хост шлюза [по умолчанию: ${GATEWAY}]: " CUSTOM_GW
392
+ read -rp "Введите имя узла [по умолчанию: ${DETECTED_HOSTNAME}]: " CUSTOM_SUB
393
+ read -rp "Общий домен шлюза [по умолчанию: ${GATEWAY}]: " CUSTOM_GW
369
394
  else
370
- read -rp "Enter subdomain name [default: ${DETECTED_HOSTNAME}]: " CUSTOM_SUB
371
- read -rp "Gateway host [default: ${GATEWAY}]: " CUSTOM_GW
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@192.168.1.50): " REMOTE_TARGET
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@192.168.1.50): " REMOTE_TARGET
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/&/\&amp;/g' -e 's/</\&lt;/g' -e 's/>/\&gt;/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
- nohup $PYTHON_BIN -m core.server --port="$PORT" > "$CONFIG_DIR/standalone.log" 2>&1 &
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
- sleep 1
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
- local copy_msg=""
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
- local copy_msg=""
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}=== Настройка облачного туннеля и субдомена ===${RESET}"
633
+ echo -e "\n${BOLD}${MAGENTA}=== Настройка облачного туннеля (общий домен) ===${RESET}"
547
634
  else
548
- echo -e "\n${BOLD}${MAGENTA}=== Configuring Cloud Gateway Tunnel & Subdomain ===${RESET}"
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] Регистрация субдомена '${USERNAME}' на шлюзе (${GATEWAY})..."
656
+ echo "[2/4] Регистрация узла '${USERNAME}' на общем домене (${GATEWAY})..."
570
657
  else
571
- echo "[2/4] Registering subdomain '${USERNAME}' on Gateway (${GATEWAY})..."
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}' уже было занято. Автоматически назначен субдомен:${RESET} ${BOLD}${GREEN}${ASSIGNED_USER}${RESET}"
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 subdomain:${RESET} ${BOLD}${GREEN}${ASSIGNED_USER}${RESET}"
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
- MCP_URL="https://${ASSIGNED_USER}.${GATEWAY}/sse?token=${ASSIGNED_TOKEN}"
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
- local copy_msg=""
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
- local copy_msg=""
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.1.2",
4
- "description": "Turn your PC, Laptop, or Server into an autonomous execution node for Google Gemini via Native MCP",
3
+ "version": "0.2.4",
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",