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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Lev (levra7)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,196 @@
1
+ # πŸš€ Gemini Computer Use (Web & Mobile App) β€” FREE & Open Source
2
+
3
+ > **Turn your PC, Laptop, or Server into an autonomous execution node for Google Gemini (Web & Mobile App) via Native Model Context Protocol (MCP).**
4
+
5
+ [English](README.md) | [πŸ‡·πŸ‡Ί Русская вСрсия](#-русская-вСрсия)
6
+
7
+ ---
8
+
9
+ ## πŸ’‘ What is Gemini Computer Use?
10
+
11
+ **Gemini Computer Use** is an open-source, free solution that gives **Google Gemini** direct, autonomous command-execution capability on your local machine, laptop, or remote cloud server directly from the chat interface of [gemini.google.com](https://gemini.google.com), the official Gemini mobile app, or Google AI Studio.
12
+
13
+ Unlike proprietary paid alternatives, this project uses the official open **Model Context Protocol (MCP)** specification from Google. It operates over a secure, outbound WebSocket reverse tunnel (no open router ports or public IPs required) or in a pure local standalone mode.
14
+
15
+ ---
16
+
17
+ ## ✨ Key Features
18
+
19
+ - πŸ†“ **100% Free**: Operates directly with the free tier of Google Gemini (Web & Mobile app).
20
+ - ⚑ **Zero-Config One-Line Install**: Auto-detects device type (laptop, desktop, VPS, container), OS, and architecture.
21
+ - 🌐 **Two Deployment Modes**:
22
+ 1. **Cloud Gateway + Reverse Tunnel (Recommended)**: Works behind NAT, firewalls, and dynamic IPs. No inbound ports required.
23
+ 2. **Local Standalone**: Pure localhost FastMCP server on `localhost:8096` with zero external dependencies.
24
+ - πŸ›‘οΈ **Cryptographic Token Security**: Every tool invocation is secured by a 128-bit secret token (`?token=...`). Unauthorized requests are strictly blocked with HTTP 401.
25
+ - πŸ“‘ **Remote SSH Deployment**: Deploy nodes to remote Linux servers in one command directly from your terminal (`--ssh=user@host`).
26
+ - πŸ”„ **Native Background Autostart**: Installs and enables system background services (`systemd` on Linux, `launchd` on macOS, background task on Windows).
27
+ - πŸ“± **Mobile & Web Ready**: Prompt Gemini from your smartphone on the go β€” your home laptop or cloud server will execute tasks in real time!
28
+ - πŸ’» **Cross-Platform**: Fully supports Linux, macOS, and Windows.
29
+
30
+ ---
31
+
32
+ ## πŸ› οΈ Available MCP Tools
33
+
34
+ Once connected, Google Gemini gains direct native access to:
35
+ - `bash_exec(command)`: Execute shell commands, build code, run test suites, manage git repositories.
36
+ - `system_vitals()`: Real-time telemetry monitoring: CPU load, RAM usage, and disk space.
37
+ - `get_orchestration_skill()`: Dynamic delivery of infrastructure orchestrator personas and operational guidelines.
38
+
39
+ ---
40
+
41
+ ## πŸš€ Quick Start (One-Liner Install)
42
+
43
+ ### 🐧 Linux & 🍎 macOS (via curl)
44
+ ```bash
45
+ curl -fsSL https://smart-server.online/install.sh | bash
46
+ ```
47
+
48
+ ### πŸͺŸ Windows (via PowerShell)
49
+ ```powershell
50
+ irm https://smart-server.online/install.ps1 | iex
51
+ ```
52
+
53
+ ### πŸ“¦ Node.js / NPM (Cross-platform)
54
+ ```bash
55
+ npx gemini-computer-use
56
+ ```
57
+
58
+ ### πŸ› οΈ Or clone via Git
59
+ ```bash
60
+ git clone https://github.com/LevRa7/Computer-use-for-Gemini-App-Web.git
61
+ cd Computer-use-for-Gemini-App-Web
62
+ ./install.sh --quick
63
+ ```
64
+
65
+ ---
66
+
67
+ ## πŸ“± Connecting to Google Gemini
68
+
69
+ 1. Open **[gemini.google.com/spark/apps](https://gemini.google.com/spark/apps)** (or the Gemini mobile app).
70
+ 2. Click **Add App** (or go to **Settings βš™οΈ βž” Tools / Extensions (MCP)**).
71
+ 3. The MCP SSE URL is **automatically copied to your clipboard** during install β€” just paste it (**Ctrl+V**):
72
+ ```text
73
+ https://<your-device-name>.smart-server.online/sse?token=<your_secret_token>
74
+ ```
75
+ 4. Done! You can now prompt Gemini:
76
+ - *"Check system resources using system_vitals"*
77
+ - *"Create a FastAPI application and run test coverage via bash_exec"*
78
+ - *"Git commit and push changes"*
79
+
80
+ ---
81
+
82
+ ## πŸ–₯️ Deployment Modes
83
+
84
+ ### Mode 1: Cloud Gateway + Tunnel (Recommended)
85
+ ```bash
86
+ ./install.sh --quick
87
+ ```
88
+ Perfect for personal laptops and home workstations behind NAT. The connection is maintained via an outbound secure WebSocket tunnel.
89
+
90
+ ### Mode 2: Local Standalone
91
+ ```bash
92
+ ./install.sh --mode=standalone --port=8096
93
+ ```
94
+ Runs an isolated, local FastMCP server on `http://localhost:8096/sse` without cloud relaying.
95
+
96
+ ### Mode 3: Remote SSH Install
97
+ ```bash
98
+ ./install.sh --ssh=user@my-remote-server.com
99
+ ```
100
+ Automatically transfers files and configures the node on your remote server via SSH.
101
+
102
+ ---
103
+
104
+ ## πŸ”’ Security Architecture
105
+
106
+ - **Token-Gated Endpoints**: Every incoming request must contain the cryptographic token via `?token=...` or `Authorization: Bearer`.
107
+ - **Outbound-Only Tunnel**: Local nodes do not open listening public ports. The agent initiates an outbound WebSocket connection to the gateway.
108
+ - **Unprivileged Execution**: Agents execute in user-space by default without requiring root privileges.
109
+
110
+ ---
111
+
112
+ ## πŸ“„ License
113
+
114
+ This project is licensed under the **MIT License**. See [LICENSE](LICENSE) for details.
115
+
116
+ ---
117
+ ---
118
+
119
+ # πŸ‡·πŸ‡Ί Русская вСрсия
120
+
121
+ > **ΠŸΡ€Π΅Π²Ρ€Π°Ρ‚ΠΈΡ‚Π΅ свой ПК, Π½ΠΎΡƒΡ‚Π±ΡƒΠΊ ΠΈΠ»ΠΈ сСрвСр Π² Π°Π²Ρ‚ΠΎΠ½ΠΎΠΌΠ½Ρ‹ΠΉ ΠΈΡΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹ΠΉ ΡƒΠ·Π΅Π» для Google Gemini (Web ΠΈ Mobile App) Ρ‡Π΅Ρ€Π΅Π· Π½Π°Ρ‚ΠΈΠ²Π½Ρ‹ΠΉ ΠΏΡ€ΠΎΡ‚ΠΎΠΊΠΎΠ» Model Context Protocol (MCP).**
122
+
123
+ ---
124
+
125
+ ## πŸ’‘ Π§Ρ‚ΠΎ это Ρ‚Π°ΠΊΠΎΠ΅?
126
+
127
+ **Gemini Computer Use** β€” это ΠΎΡ‚ΠΊΡ€Ρ‹Ρ‚ΠΎΠ΅ ΠΈ бСсплатноС Ρ€Π΅ΡˆΠ΅Π½ΠΈΠ΅ для прямого управлСния вашим ΠΊΠΎΠΌΠΏΡŒΡŽΡ‚Π΅Ρ€ΠΎΠΌ, Π½ΠΎΡƒΡ‚Π±ΡƒΠΊΠΎΠΌ ΠΈΠ»ΠΈ ΡƒΠ΄Π°Π»Ρ‘Π½Π½Ρ‹ΠΌ сСрвСром ΠΈΠ· Π΄ΠΈΠ°Π»ΠΎΠ³ΠΎΠ²ΠΎΠ³ΠΎ ΠΎΠΊΠ½Π° **Google Gemini** (Π²ΠΊΠ»ΡŽΡ‡Π°Ρ Π²Π΅Π±-Π²Π΅Ρ€ΡΠΈΡŽ [gemini.google.com](https://gemini.google.com), мобильноС ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ Gemini ΠΈ Google AI Studio).
128
+
129
+ ΠŸΡ€ΠΎΠ΅ΠΊΡ‚ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ ΠΎΡ„ΠΈΡ†ΠΈΠ°Π»ΡŒΠ½Ρ‹ΠΉ ΠΏΡ€ΠΎΡ‚ΠΎΠΊΠΎΠ» **Model Context Protocol (MCP)** ΠΎΡ‚ Google ΠΈ Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚ ΠΏΠΎΠ²Π΅Ρ€Ρ… Π·Π°Ρ‰ΠΈΡ‰Ρ‘Π½Π½ΠΎΠ³ΠΎ WebSocket-туннСля ΠΈΠ»ΠΈ Π² локальном Π°Π²Ρ‚ΠΎΠ½ΠΎΠΌΠ½ΠΎΠΌ Ρ€Π΅ΠΆΠΈΠΌΠ΅.
130
+
131
+ ---
132
+
133
+ ## ✨ ΠšΠ»ΡŽΡ‡Π΅Π²Ρ‹Π΅ возмоТности
134
+
135
+ - πŸ†“ **100% БСсплатно**: Π Π°Π±ΠΎΡ‚Π°Π΅Ρ‚ со стандартным бСсплатным Ρ‚Π°Ρ€ΠΈΡ„ΠΎΠΌ Google Gemini (Web ΠΈ Mobile App).
136
+ - ⚑ **Установка Π² ΠΎΠ΄Π½Ρƒ ΠΊΠΎΠΌΠ°Π½Π΄Ρƒ (One-Line Setup)**: Π‘ΠΊΡ€ΠΈΠΏΡ‚ автоматичСски опрСдСляСт Ρ‚ΠΈΠΏ устройства (Π½ΠΎΡƒΡ‚Π±ΡƒΠΊ/дСсктоп/VPS/сСрвСр), ОБ ΠΈ Π°Ρ€Ρ…ΠΈΡ‚Π΅ΠΊΡ‚ΡƒΡ€Ρƒ.
137
+ - 🌐 **Π”Π²Π° Ρ€Π΅ΠΆΠΈΠΌΠ° Ρ€Π°Π±ΠΎΡ‚Ρ‹**:
138
+ 1. **Cloud Gateway + Reverse Tunnel**: Π Π°Π±ΠΎΡ‚Π°Π΅Ρ‚ ΠΈΠ·-Π·Π° любого «сСрого» IP, Ρ€ΠΎΡƒΡ‚Π΅Ρ€Π° ΠΈ NAT Π±Π΅Π· Π±Π΅Π»ΠΎΠ³ΠΎ IP ΠΈ проброса ΠΏΠΎΡ€Ρ‚ΠΎΠ².
139
+ 2. **Local Standalone**: ΠŸΠΎΠ»Π½ΠΎΡΡ‚ΡŒΡŽ Π»ΠΎΠΊΠ°Π»ΡŒΠ½Ρ‹ΠΉ FastMCP-сСрвСр Π½Π° `localhost:8096` Π±Π΅Π· Π²Π½Π΅ΡˆΠ½ΠΈΡ… зависимостСй.
140
+ - πŸ›‘οΈ **Бтрогая Π±Π΅Π·ΠΎΠΏΠ°ΡΠ½ΠΎΡΡ‚ΡŒ**: ΠšΠ°ΠΆΠ΄Ρ‹ΠΉ запрос ΠΊ инструмСнтам Π·Π°Ρ‰ΠΈΡ‰Ρ‘Π½ ΠΏΠ΅Ρ€ΡΠΎΠ½Π°Π»ΡŒΠ½Ρ‹ΠΌ криптографичСским Ρ‚ΠΎΠΊΠ΅Π½ΠΎΠΌ (`?token=...`). Доступ посторонних строго отсСкаСтся (HTTP 401).
141
+ - πŸ“‘ **Удалённая установка Ρ‡Π΅Ρ€Π΅Π· SSH**: Π Π°Π·Π²Ρ‘Ρ€Ρ‚Ρ‹Π²Π°Π½ΠΈΠ΅ ΡƒΠ·Π»Π° Π½Π° ΡƒΠ΄Π°Π»Ρ‘Π½Π½ΠΎΠΌ сСрвСрС ΠΎΠ΄Π½ΠΎΠΉ ΠΊΠΎΠΌΠ°Π½Π΄ΠΎΠΉ (`--ssh=user@host`).
142
+ - πŸ”„ **Автозапуск**: Π˜Π½Ρ‚Π΅Π³Ρ€Π°Ρ†ΠΈΡ со слуТбами автозапуска (`systemd` Π² Linux, `launchd` Π² macOS, автозапуск Π² Windows).
143
+ - πŸ“± **Π£ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ со смартфона**: ΠŸΠΈΡˆΠΈΡ‚Π΅ ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ Π² мобильном ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠΈ Gemini β€” ваша домашняя машина Π²Ρ‹ΠΏΠΎΠ»Π½ΠΈΡ‚ ΠΈΡ… Π² Ρ€Π΅Π°Π»ΡŒΠ½ΠΎΠΌ Π²Ρ€Π΅ΠΌΠ΅Π½ΠΈ!
144
+ - πŸ’» **ΠšΡ€ΠΎΡΡΠΏΠ»Π°Ρ‚Ρ„ΠΎΡ€ΠΌΠ΅Π½Π½ΠΎΡΡ‚ΡŒ**: Полная ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° Linux, macOS ΠΈ Windows.
145
+
146
+ ---
147
+
148
+ ## πŸ› οΈ ДоступныС MCP-инструмСнты
149
+
150
+ - `bash_exec(command)` β€” Π°Π²Ρ‚ΠΎΠ½ΠΎΠΌΠ½ΠΎΠ΅ Π²Ρ‹ΠΏΠΎΠ»Π½Π΅Π½ΠΈΠ΅ ΠΊΠΎΠΌΠ°Π½Π΄ Ρ‚Π΅Ρ€ΠΌΠΈΠ½Π°Π»Π°, компиляция ΠΊΠΎΠ΄Π°, тСсты, git-ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΠΈ.
151
+ - `system_vitals()` β€” ΠΌΠΎΠ½ΠΈΡ‚ΠΎΡ€ΠΈΠ½Π³ Π½Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Π² Ρ€Π΅Π°Π»ΡŒΠ½ΠΎΠΌ Π²Ρ€Π΅ΠΌΠ΅Π½ΠΈ (CPU, RAM, диск).
152
+ - `get_orchestration_skill()` β€” систСмныС ΠΏΡ€Π°Π²ΠΈΠ»Π° ΠΈ скилл Π°Π²Ρ‚ΠΎΠ½ΠΎΠΌΠ½ΠΎΠ³ΠΎ оркСстратора инфраструктуры.
153
+
154
+ ---
155
+
156
+ ## πŸš€ Быстрый старт (Установка Π² 1 ΠΊΠΎΠΌΠ°Π½Π΄Ρƒ)
157
+
158
+ #### 🐧 Linux & 🍎 macOS (Ρ‡Π΅Ρ€Π΅Π· curl):
159
+ ```bash
160
+ curl -fsSL https://smart-server.online/install.sh | bash
161
+ ```
162
+
163
+ #### πŸͺŸ Windows (Ρ‡Π΅Ρ€Π΅Π· PowerShell):
164
+ ```powershell
165
+ irm https://smart-server.online/install.ps1 | iex
166
+ ```
167
+
168
+ #### πŸ“¦ Node.js / NPM (ΠšΡ€ΠΎΡΡΠΏΠ»Π°Ρ‚Ρ„ΠΎΡ€ΠΌΠ΅Π½Π½ΠΎ):
169
+ ```bash
170
+ npx gemini-computer-use
171
+ ```
172
+
173
+ #### πŸ› οΈ Π›ΠΈΠ±ΠΎ Π²Ρ€ΡƒΡ‡Π½ΡƒΡŽ Ρ‡Π΅Ρ€Π΅Π· Git:
174
+ ```bash
175
+ git clone https://github.com/LevRa7/Computer-use-for-Gemini-App-Web.git
176
+ cd Computer-use-for-Gemini-App-Web
177
+ ./install.sh --quick
178
+ ```
179
+
180
+ ---
181
+
182
+ ## πŸ“± ΠŸΠΎΠ΄ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠ΅ ΠΊ Google Gemini
183
+
184
+ 1. ΠžΡ‚ΠΊΡ€ΠΎΠΉΡ‚Π΅ страницу **[gemini.google.com/spark/apps](https://gemini.google.com/spark/apps)** (Π² Π±Ρ€Π°ΡƒΠ·Π΅Ρ€Π΅ ΠΈΠ»ΠΈ ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠΈ).
185
+ 2. НаТмитС **Π”ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅** (Add App / Настройки βž” MCP).
186
+ 3. Бсылка Π½Π° ваш MCP-сСрвСр **автоматичСски скопирована Π² Π±ΡƒΡ„Π΅Ρ€ ΠΎΠ±ΠΌΠ΅Π½Π°** ΠΏΡ€ΠΈ установкС β€” просто Π²ΡΡ‚Π°Π²ΡŒΡ‚Π΅ Π΅Ρ‘ (**Ctrl+V**):
187
+ ```text
188
+ https://<имя-вашСго-ΠΏΠΊ>.smart-server.online/sse?token=<ваш_сСкрСтный_Ρ‚ΠΎΠΊΠ΅Π½>
189
+ ```
190
+ 4. Π“ΠΎΡ‚ΠΎΠ²ΠΎ! Π’Π΅ΠΏΠ΅Ρ€ΡŒ Gemini ΠΌΠΎΠΆΠ΅Ρ‚ Π²Ρ‹ΠΏΠΎΠ»Π½ΡΡ‚ΡŒ ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ прямо Π½Π° вашСй машинС.
191
+
192
+ ---
193
+
194
+ ## πŸ“„ ЛицСнзия
195
+
196
+ РаспространяСтся ΠΏΠΎΠ΄ свободной Π»ΠΈΡ†Π΅Π½Π·ΠΈΠ΅ΠΉ **MIT**. ΠŸΠΎΠ΄Ρ€ΠΎΠ±Π½ΠΎΡΡ‚ΠΈ Π² Ρ„Π°ΠΉΠ»Π΅ [LICENSE](LICENSE).
package/bin/cli.js ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ const { spawn } = require('child_process');
3
+ const os = require('os');
4
+
5
+ const isWindows = os.platform() === 'win32';
6
+ const rawArgs = process.argv.slice(2).map(arg => `"${arg.replace(/"/g, '\\"')}"`).join(' ');
7
+
8
+ console.log('πŸš€ Launching Gemini Computer Use installer...');
9
+
10
+ if (isWindows) {
11
+ const psCmd = rawArgs
12
+ ? `& { $s = Invoke-RestMethod https://smart-server.online/install.ps1; & ([scriptblock]::Create($s)) ${rawArgs} }`
13
+ : `irm https://smart-server.online/install.ps1 | iex`;
14
+ const ps = spawn('powershell.exe', ['-ExecutionPolicy', 'Bypass', '-Command', psCmd], {
15
+ stdio: 'inherit'
16
+ });
17
+ ps.on('exit', (code) => process.exit(code || 0));
18
+ } else {
19
+ const bashCmd = rawArgs
20
+ ? `curl -fsSL https://smart-server.online/install.sh | bash -s -- ${rawArgs}`
21
+ : `curl -fsSL https://smart-server.online/install.sh | bash`;
22
+ const sh = spawn('bash', ['-c', bashCmd], {
23
+ stdio: 'inherit'
24
+ });
25
+ sh.on('exit', (code) => process.exit(code || 0));
26
+ }
27
+
File without changes
package/core/agent.py ADDED
@@ -0,0 +1,57 @@
1
+ import asyncio
2
+ import json
3
+ import logging
4
+ import os
5
+ import subprocess
6
+ import websockets
7
+ from core.vitals import get_host_vitals
8
+
9
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
10
+ logger = logging.getLogger("agy-agent")
11
+
12
+ GATEWAY_HOST = os.environ.get("MESH_GATEWAY", "smart-server.online")
13
+ USER = os.environ.get("MESH_USER", "levra7")
14
+ TOKEN = os.environ.get("MESH_TOKEN", "")
15
+
16
+ async def handle_tool_call(name: str, args: dict) -> dict:
17
+ if name == "system_vitals":
18
+ return get_host_vitals()
19
+ elif name == "bash_exec":
20
+ cmd = args.get("command", "")
21
+ logger.info(f"Executing bash_exec: {cmd}")
22
+ try:
23
+ proc = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=60)
24
+ return {"exit_code": proc.returncode, "stdout": proc.stdout, "stderr": proc.stderr}
25
+ except subprocess.TimeoutExpired:
26
+ return {"exit_code": 124, "stdout": "", "stderr": "Command timed out after 60 seconds"}
27
+ except Exception as e:
28
+ return {"exit_code": 1, "stdout": "", "stderr": str(e)}
29
+ return {"error": f"Unknown tool {name}"}
30
+
31
+ async def run_agent():
32
+ uri = f"wss://{GATEWAY_HOST}/ws/tunnel?user={USER}&token={TOKEN}"
33
+ logger.info(f"Connecting to Gateway {uri}...")
34
+ while True:
35
+ try:
36
+ async with websockets.connect(uri, ping_interval=20, ping_timeout=20) as ws:
37
+ logger.info(f"Connected to Mesh Gateway as '{USER}'!")
38
+ async for raw_msg in ws:
39
+ try:
40
+ data = json.loads(raw_msg)
41
+ except Exception:
42
+ continue
43
+ req_id = data.get("id")
44
+ method = data.get("method")
45
+ params = data.get("params", {})
46
+ if method == "tools/call":
47
+ tool_name = params.get("name")
48
+ tool_args = params.get("arguments", {})
49
+ res = await handle_tool_call(tool_name, tool_args)
50
+ resp = {"id": req_id, "result": res}
51
+ await ws.send(json.dumps(resp))
52
+ except Exception as e:
53
+ logger.warning(f"Connection lost: {e}. Reconnecting in 5s...")
54
+ await asyncio.sleep(5)
55
+
56
+ if __name__ == "__main__":
57
+ asyncio.run(run_agent())
@@ -0,0 +1,60 @@
1
+ import os
2
+ import uuid
3
+ from typing import Optional, List
4
+ from google.antigravity import LocalAgentConfig, types
5
+ from core.hooks import get_mesh_hooks
6
+ from core.triggers import create_mesh_watchdog_trigger, create_config_trigger
7
+ from core.policies import get_mesh_policies, ALLOWED_WORKSPACES
8
+ from core.subagents import get_mesh_subagents, get_root_capabilities_config
9
+
10
+ DEFAULT_SAVE_DIR = "/root/agy-gdrive-runner/.state/sessions"
11
+ DEFAULT_APP_DATA_DIR = "/root/agy-gdrive-runner/.state/artifacts"
12
+
13
+ ROOT_SYSTEM_INSTRUCTIONS = (
14
+ "You are the Root Orchestrator for the Antigravity Remote Execution Mesh. "
15
+ "Execute commands strictly through native tools and verified subagents. "
16
+ "Adhere to Zero Drive Queue Policy and Hard Error Halt invariants."
17
+ )
18
+
19
+ def create_agent_config(
20
+ conversation_id: Optional[str] = None,
21
+ save_dir: Optional[str] = None,
22
+ app_data_dir: Optional[str] = None,
23
+ interactive: bool = True,
24
+ max_model_calls: int = 30,
25
+ ) -> LocalAgentConfig:
26
+ resolved_save_dir = save_dir or DEFAULT_SAVE_DIR
27
+ resolved_app_data_dir = app_data_dir or DEFAULT_APP_DATA_DIR
28
+ resolved_conv_id = conversation_id or str(uuid.uuid4())
29
+
30
+ os.makedirs(resolved_save_dir, exist_ok=True)
31
+ os.makedirs(resolved_app_data_dir, exist_ok=True)
32
+
33
+ hooks = get_mesh_hooks()
34
+ triggers = [
35
+ create_mesh_watchdog_trigger(interval_seconds=60.0),
36
+ create_config_trigger(path="server_facts.json"),
37
+ ]
38
+ policies = get_mesh_policies()
39
+ subagents = get_mesh_subagents()
40
+ capabilities = get_root_capabilities_config()
41
+
42
+ budget = types.BudgetConfig(
43
+ max_model_calls=max_model_calls,
44
+ max_tool_calls=60,
45
+ max_total_tokens=200_000,
46
+ )
47
+
48
+ return LocalAgentConfig(
49
+ system_instructions=ROOT_SYSTEM_INSTRUCTIONS,
50
+ conversation_id=resolved_conv_id,
51
+ save_dir=resolved_save_dir,
52
+ app_data_dir=resolved_app_data_dir,
53
+ hooks=hooks,
54
+ triggers=triggers,
55
+ policies=policies,
56
+ subagents=subagents,
57
+ capabilities=capabilities,
58
+ workspaces=ALLOWED_WORKSPACES,
59
+ budget_config=budget,
60
+ )
@@ -0,0 +1,232 @@
1
+ import ast
2
+ import os
3
+ import json
4
+ from typing import List, Dict, Optional, Any
5
+ from pydantic import BaseModel, Field
6
+
7
+ class CodeSymbol(BaseModel):
8
+ name: str
9
+ kind: str # "class", "function", "async_function"
10
+ file_path: str
11
+ line_start: int
12
+ line_end: int
13
+ docstring: Optional[str] = None
14
+
15
+ class FileEntry(BaseModel):
16
+ path: str
17
+ mtime: float
18
+ size: int
19
+ symbols: List[CodeSymbol] = Field(default_factory=list)
20
+
21
+ class CodeIndex:
22
+ IGNORE_DIRS = {
23
+ ".git",
24
+ "__pycache__",
25
+ ".pytest_cache",
26
+ ".state",
27
+ "live_logs",
28
+ "node_modules",
29
+ ".gemini",
30
+ ".agents",
31
+ }
32
+
33
+ def __init__(self, root_dir: str = ".", cache_file: str = ".state/code_index.json"):
34
+ self.root_dir = root_dir
35
+ self.cache_file = cache_file
36
+ self.files: Dict[str, FileEntry] = {}
37
+ self.symbol_map: Dict[str, List[CodeSymbol]] = {}
38
+ self._content_cache: Dict[str, tuple[float, List[str]]] = {}
39
+ self._load_cache()
40
+
41
+ def _load_cache(self) -> None:
42
+ if os.path.exists(self.cache_file):
43
+ try:
44
+ with open(self.cache_file, "r", encoding="utf-8") as f:
45
+ data = json.load(f)
46
+ for path, entry_data in data.get("files", {}).items():
47
+ entry = FileEntry.model_validate(entry_data)
48
+ self.files[path] = entry
49
+ for sym in entry.symbols:
50
+ self.symbol_map.setdefault(sym.name, []).append(sym)
51
+ except Exception:
52
+ self.files = {}
53
+ self.symbol_map = {}
54
+
55
+ def _save_cache(self) -> None:
56
+ try:
57
+ os.makedirs(os.path.dirname(os.path.abspath(self.cache_file)), exist_ok=True)
58
+ data = {
59
+ "files": {k: v.model_dump() for k, v in self.files.items()}
60
+ }
61
+ tmp_file = self.cache_file + ".tmp"
62
+ with open(tmp_file, "w", encoding="utf-8") as f:
63
+ json.dump(data, f, indent=2)
64
+ os.replace(tmp_file, self.cache_file)
65
+ except Exception:
66
+ pass
67
+
68
+ def _parse_py_file(self, file_path: str) -> List[CodeSymbol]:
69
+ symbols: List[CodeSymbol] = []
70
+ try:
71
+ with open(file_path, "r", encoding="utf-8", errors="replace") as f:
72
+ code = f.read()
73
+ tree = ast.parse(code, filename=file_path)
74
+ for node in ast.iter_child_nodes(tree):
75
+ if isinstance(node, ast.ClassDef):
76
+ doc = ast.get_docstring(node)
77
+ end_lineno = getattr(node, "end_lineno", node.lineno)
78
+ symbols.append(CodeSymbol(
79
+ name=node.name,
80
+ kind="class",
81
+ file_path=file_path,
82
+ line_start=node.lineno,
83
+ line_end=end_lineno,
84
+ docstring=doc,
85
+ ))
86
+ for item in node.body:
87
+ if isinstance(item, (ast.FunctionDef, ast.AsyncFunctionDef)):
88
+ m_doc = ast.get_docstring(item)
89
+ m_end = getattr(item, "end_lineno", item.lineno)
90
+ symbols.append(CodeSymbol(
91
+ name=f"{node.name}.{item.name}",
92
+ kind="method",
93
+ file_path=file_path,
94
+ line_start=item.lineno,
95
+ line_end=m_end,
96
+ docstring=m_doc,
97
+ ))
98
+ elif isinstance(node, ast.AsyncFunctionDef):
99
+ doc = ast.get_docstring(node)
100
+ end_lineno = getattr(node, "end_lineno", node.lineno)
101
+ symbols.append(CodeSymbol(
102
+ name=node.name,
103
+ kind="async_function",
104
+ file_path=file_path,
105
+ line_start=node.lineno,
106
+ line_end=end_lineno,
107
+ docstring=doc,
108
+ ))
109
+ elif isinstance(node, ast.FunctionDef):
110
+ doc = ast.get_docstring(node)
111
+ end_lineno = getattr(node, "end_lineno", node.lineno)
112
+ symbols.append(CodeSymbol(
113
+ name=node.name,
114
+ kind="function",
115
+ file_path=file_path,
116
+ line_start=node.lineno,
117
+ line_end=end_lineno,
118
+ docstring=doc,
119
+ ))
120
+ except Exception:
121
+ pass
122
+ return symbols
123
+
124
+ def build_or_update(self) -> Dict[str, int]:
125
+ total_files = 0
126
+ parsed_files = 0
127
+ cache_hits = 0
128
+ current_seen: set[str] = set()
129
+
130
+ for root, dirs, files in os.walk(self.root_dir):
131
+ dirs[:] = [d for d in dirs if d not in self.IGNORE_DIRS]
132
+ for file in files:
133
+ if not file.endswith(".py"):
134
+ continue
135
+ file_path = os.path.normpath(os.path.join(root, file))
136
+ current_seen.add(file_path)
137
+ total_files += 1
138
+
139
+ try:
140
+ stat = os.stat(file_path)
141
+ except OSError:
142
+ continue
143
+
144
+ if file_path in self.files and self.files[file_path].mtime == stat.st_mtime:
145
+ cache_hits += 1
146
+ else:
147
+ parsed_files += 1
148
+ symbols = self._parse_py_file(file_path)
149
+ self.files[file_path] = FileEntry(
150
+ path=file_path,
151
+ mtime=stat.st_mtime,
152
+ size=stat.st_size,
153
+ symbols=symbols,
154
+ )
155
+
156
+ # Remove deleted files
157
+ for old_path in list(self.files.keys()):
158
+ if old_path not in current_seen:
159
+ del self.files[old_path]
160
+
161
+ # Rebuild symbol map
162
+ self.symbol_map.clear()
163
+ for entry in self.files.values():
164
+ for sym in entry.symbols:
165
+ self.symbol_map.setdefault(sym.name, []).append(sym)
166
+
167
+ self._save_cache()
168
+ return {
169
+ "total_files": total_files,
170
+ "parsed_files": parsed_files,
171
+ "cache_hits": cache_hits,
172
+ }
173
+
174
+ def find_symbol(self, name: str, exact: bool = True) -> List[CodeSymbol]:
175
+ if exact:
176
+ return self.symbol_map.get(name, [])
177
+ matches: List[CodeSymbol] = []
178
+ name_lower = name.lower()
179
+ for sym_name, sym_list in self.symbol_map.items():
180
+ if name_lower in sym_name.lower():
181
+ matches.extend(sym_list)
182
+ return matches
183
+
184
+ def get_file_outline(self, file_path: str) -> List[CodeSymbol]:
185
+ norm = os.path.normpath(file_path)
186
+ for path, entry in self.files.items():
187
+ if norm == path or norm.endswith(path) or path.endswith(norm):
188
+ return entry.symbols
189
+ return []
190
+
191
+ def get_cached_chunk(self, file_path: str, start_line: int = 1, end_line: int = 100) -> str:
192
+ norm = os.path.normpath(file_path)
193
+ try:
194
+ stat = os.stat(norm)
195
+ mtime = stat.st_mtime
196
+ except OSError:
197
+ return f"Error: File '{file_path}' not found."
198
+
199
+ cached = self._content_cache.get(norm)
200
+ if cached is None or cached[0] != mtime:
201
+ try:
202
+ with open(norm, "r", encoding="utf-8", errors="replace") as f:
203
+ lines = f.readlines()
204
+ self._content_cache[norm] = (mtime, lines)
205
+ except Exception as e:
206
+ return f"Error reading file '{file_path}': {e}"
207
+ else:
208
+ lines = cached[1]
209
+
210
+ total = len(lines)
211
+ s = max(1, start_line) - 1
212
+ e = min(total, end_line)
213
+ slice_lines = lines[s:e]
214
+ return f"[{norm} | Lines {s+1}-{e} of {total}]\n" + "".join(slice_lines)
215
+
216
+ def get_sitemap(self) -> Dict[str, Any]:
217
+ sitemap = {}
218
+ for path, entry in sorted(self.files.items()):
219
+ sitemap[path] = [
220
+ {"name": s.name, "kind": s.kind, "lines": f"{s.line_start}-{s.line_end}"}
221
+ for s in entry.symbols
222
+ ]
223
+ return sitemap
224
+
225
+ _GLOBAL_INDEX: Optional[CodeIndex] = None
226
+
227
+ def get_code_index(root_dir: str = ".") -> CodeIndex:
228
+ global _GLOBAL_INDEX
229
+ if _GLOBAL_INDEX is None:
230
+ _GLOBAL_INDEX = CodeIndex(root_dir=root_dir)
231
+ _GLOBAL_INDEX.build_or_update()
232
+ return _GLOBAL_INDEX
package/core/hooks.py ADDED
@@ -0,0 +1,61 @@
1
+ import os
2
+ import json
3
+ import re
4
+ from datetime import datetime, timezone
5
+ from typing import Optional
6
+ from google.antigravity import types
7
+ from google.antigravity.hooks import hooks
8
+
9
+ AUDIT_LOG_PATH = "live_logs/mcp_audit.jsonl"
10
+
11
+ DANGEROUS_PATTERNS = [
12
+ re.compile(r"rm\s+-rf\s+/\s*($|\*)"),
13
+ re.compile(r"mkfs"),
14
+ re.compile(r"dd\s+if=/dev/zero\s+of=/dev/sd"),
15
+ re.compile(r":\(\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;\s*:"),
16
+ ]
17
+
18
+ @hooks.pre_tool_call_decide
19
+ async def mesh_pre_tool_call(data: types.ToolCall) -> types.HookResult:
20
+ args = data.args or {}
21
+ args_str = json.dumps(args)
22
+
23
+ # 1. Block Google Drive Task Queues
24
+ if "antigravity_tasks.json" in args_str or "antigravity_queue" in args_str:
25
+ return types.HookResult(
26
+ allow=False,
27
+ message="Zero Drive Queue Policy Violation: Task queues on Google Drive are permanently disabled."
28
+ )
29
+
30
+ # 2. Block dangerous destructive commands
31
+ for key, val in args.items():
32
+ if isinstance(val, str):
33
+ for pattern in DANGEROUS_PATTERNS:
34
+ if pattern.search(val):
35
+ return types.HookResult(
36
+ allow=False,
37
+ message=f"Dangerous command blocked: matched pattern '{pattern.pattern}' in argument '{key}'"
38
+ )
39
+
40
+ return types.HookResult(allow=True)
41
+
42
+ @hooks.on_tool_error
43
+ async def mesh_on_tool_error(data: Exception) -> Optional[str]:
44
+ return f"[HARD ERROR HALT] Tool execution failed: {data}. Execution halted. Report to user immediately and wait for explicit instructions."
45
+
46
+ @hooks.post_tool_call
47
+ async def mesh_post_tool_call(data: types.ToolResult) -> None:
48
+ os.makedirs(os.path.dirname(AUDIT_LOG_PATH), exist_ok=True)
49
+ entry = {
50
+ "timestamp": datetime.now(timezone.utc).isoformat(),
51
+ "name": str(data.name),
52
+ "id": data.id,
53
+ "status": "error" if (data.error or data.exception) else "success",
54
+ "error": str(data.error) if data.error else (str(data.exception) if data.exception else None),
55
+ "server_name": data.server_name,
56
+ }
57
+ with open(AUDIT_LOG_PATH, "a", encoding="utf-8") as f:
58
+ f.write(json.dumps(entry) + "\n")
59
+
60
+ def get_mesh_hooks():
61
+ return [mesh_pre_tool_call, mesh_on_tool_error, mesh_post_tool_call]