gemini-computer-use 0.2.3 β†’ 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/README.md CHANGED
@@ -1,87 +1,139 @@
1
- # πŸš€ Gemini Computer Use (Web & Mobile App) β€” FREE & Open Source
1
+ # πŸ“± Gemini Computer Use β€” control any PC from Gemini Spark (App & Web)
2
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).**
3
+ > **An MCP server that turns Google Gemini from a chatbot into a full-fledged mobile agent.**
4
+ > Type a task in the Gemini app on your phone β€” your PC, laptop or server executes it, and Gemini reports back with real results.
4
5
 
5
6
  [English](README.md) | [πŸ‡·πŸ‡Ί Русская вСрсия](#-русская-вСрсия)
6
7
 
7
8
  ---
8
9
 
9
- ## πŸ’‘ What is Gemini Computer Use?
10
+ ## πŸ’‘ The idea
11
+
12
+ Gemini in a browser or on a phone can talk, but it cannot *do* anything on your computer.
13
+ **Gemini Computer Use** closes that gap. It is a free, open-source **Model Context Protocol (MCP)** server that you connect to **Gemini Spark** ([gemini.google.com/spark/apps](https://gemini.google.com/spark/apps) or the Gemini mobile app) with a single link. After that, Gemini gets hands:
14
+
15
+ - runs shell commands, builds and tests code, manages git;
16
+ - reads, writes and edits files;
17
+ - searches the file system;
18
+ - starts long-running jobs and checks on them later;
19
+ - watches CPU, RAM and disk.
20
+
21
+ ```text
22
+ πŸ“± Gemini app / 🌐 gemini.google.com (Spark)
23
+ β”‚ MCP (SSE / Streamable HTTP)
24
+ β–Ό
25
+ ☁️ Gateway smart-server.online ← relay only, TLS
26
+ β”‚ outbound WebSocket tunnel (no open ports)
27
+ β–Ό
28
+ πŸ’» Your PC Β· laptop Β· VPS Β· home server β†’ executes the task
29
+ ```
30
+
31
+ **Result:** your phone becomes a remote control for every machine you own β€” and Gemini is the agent that operates them.
32
+
33
+ ---
10
34
 
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.
35
+ ## πŸ”₯ What you can do with it
12
36
 
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.
37
+ Just ask Gemini in plain language, from anywhere:
38
+
39
+ - *"Check why my home server is slow and show the top processes"*
40
+ - *"Pull the latest changes in ~/projects/api, run the tests and tell me what failed"*
41
+ - *"Fix the typo in config.yaml and restart the service"*
42
+ - *"Start a full backup in the background and tell me when it's done"*
43
+ - *"How much free disk space is left on my laptop?"*
44
+
45
+ No SSH client, no terminal on your phone, no VPN β€” only a chat.
14
46
 
15
47
  ---
16
48
 
17
- ## ✨ Key Features
49
+ ## ✨ Why it works this way
18
50
 
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.
51
+ | | |
52
+ | :--- | :--- |
53
+ | πŸ“± **Mobile-first agent** | Works in the official Gemini mobile app and on the web through Gemini Spark. |
54
+ | πŸ–₯️ **Any PC** | Linux, macOS, Windows β€” desktops, laptops, VPS, containers. |
55
+ | 🌐 **Behind any NAT** | The node opens an *outbound* WebSocket tunnel. No public IP, no port forwarding, no router setup. |
56
+ | ⚑ **One-line install** | Detects OS, architecture and device type; installs autostart (`systemd` / `launchd` / Windows task); copies your connection link to the clipboard. |
57
+ | πŸ›‘οΈ **Token-gated** | Every call needs a personal 128-bit token (`?token=…` or `Authorization: Bearer`). Everything else gets HTTP 401. |
58
+ | πŸ” **Many machines, one gateway** | Each node is addressed by `?user=<node-name>` on one shared domain. |
59
+ | πŸ†“ **Free & open source** | MIT license, works with the free Gemini tier. |
29
60
 
30
61
  ---
31
62
 
32
- ## πŸ› οΈ Available MCP Tools
63
+ ## πŸ› οΈ What Gemini can do on your machine (MCP tools)
33
64
 
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.
65
+ | Category | Tools |
66
+ | :--- | :--- |
67
+ | Terminal | `bash_exec(command)` |
68
+ | Files | `list_dir(path)` Β· `read_file(path, start_line, end_line)` Β· `write_file(path, content)` Β· `edit_file(path, old_string, new_string)` |
69
+ | Search | `grep_search(pattern, path)` Β· `glob_find(pattern, path)` |
70
+ | Long-running jobs | `run_job(command)` β†’ `job_output(job_id)` Β· `job_kill(job_id)` Β· `job_list()` |
71
+ | Status | `system_vitals()` Β· `system_info()` Β· `mesh_status()` |
72
+ | Agent behaviour | `get_orchestration_skill()` β€” loads the agent's operating rules |
73
+
74
+ > [!TIP]
75
+ > Gemini gives a single tool call roughly 30 seconds. For anything longer (builds, backups, downloads) the agent uses `run_job` and polls `job_output`, so tasks never get cut off.
38
76
 
39
77
  ---
40
78
 
41
- ## πŸš€ Quick Start (One-Liner Install)
79
+ ## πŸš€ Quick start β€” 2 minutes
42
80
 
43
- ### 🐧 Linux & 🍎 macOS (via curl)
81
+ ### 1. Install the node on the PC you want to control
82
+
83
+ **🐧 Linux / 🍎 macOS**
44
84
  ```bash
45
85
  curl -fsSL https://smart-server.online/install.sh | bash
46
86
  ```
47
87
 
48
- ### πŸͺŸ Windows (via PowerShell)
88
+ **πŸͺŸ Windows (PowerShell)**
49
89
  ```powershell
50
90
  irm https://smart-server.online/install.ps1 | iex
51
91
  ```
52
92
 
53
- ### πŸ“¦ Node.js / NPM (Cross-platform)
93
+ **πŸ“¦ Node.js (any OS)**
54
94
  ```bash
55
95
  npx gemini-computer-use
56
96
  ```
57
97
 
58
- ### πŸ› οΈ Or clone via Git
98
+ **πŸ› οΈ From source**
59
99
  ```bash
60
100
  git clone https://github.com/LevRa7/Computer-use-for-Gemini-App-Web.git
61
101
  cd Computer-use-for-Gemini-App-Web
62
102
  ./install.sh --quick
63
103
  ```
64
104
 
105
+ At the end the installer prints your personal MCP link and copies it to the clipboard:
106
+ ```text
107
+ https://smart-server.online/sse?user=<your-node-name>&token=<your_secret_token>
108
+ ```
109
+
110
+ ### 2. Connect it to Gemini Spark
111
+
112
+ 1. Open **[gemini.google.com/spark/apps](https://gemini.google.com/spark/apps)** in a browser or in the Gemini mobile app.
113
+ 2. Tap **Add App** (or **Settings βš™οΈ β†’ Tools / Extensions (MCP)**).
114
+ 3. Paste the link (**Ctrl+V**) and save.
115
+
116
+ ### 3. Give it a task
117
+
118
+ > *"Show system_vitals and list the 5 biggest folders in my home directory."*
119
+
120
+ That's it β€” Gemini is now an agent running on your machine. Repeat step 1 on other computers to control all of them from the same phone.
121
+
65
122
  ---
66
123
 
67
- ## πŸ“± Connecting to Google Gemini
124
+ ## πŸ–₯️ Deployment modes
68
125
 
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://smart-server.online/sse?user=<your-node-name>&token=<your_secret_token>
74
- ```
75
- Every node uses the **same shared domain** and is selected by `?user=` β€” there are no per-device subdomains to configure, and nothing extra to add to the TLS certificate.
76
- 4. Done! You can now prompt Gemini:
77
- - *"Check system resources using system_vitals"*
78
- - *"Create a FastAPI application and run test coverage via bash_exec"*
79
- - *"Git commit and push changes"*
126
+ | Mode | Command | When to use |
127
+ | :--- | :--- | :--- |
128
+ | **Gateway + tunnel** (recommended) | `./install.sh --quick` | Home PCs and laptops behind NAT β€” the mobile-agent scenario. |
129
+ | **Remote over SSH** | `./install.sh --ssh=user@host` | Deploy a node onto a remote Linux server from your terminal. |
130
+ | **Local standalone** | `./install.sh --mode=standalone --port=8096` | Pure localhost FastMCP server at `http://localhost:8096/sse`, no cloud relay. |
80
131
 
81
- ### One shared domain (canonical URL contract)
132
+ ---
82
133
 
83
- A node is **never** addressed by its own hostname. The gateway publishes a single
84
- shared domain and the node name travels as a query parameter:
134
+ ## πŸ”— One shared domain (canonical URL contract)
135
+
136
+ A node is **never** addressed by its own hostname. The gateway publishes a single shared domain, and the node name travels as a query parameter:
85
137
 
86
138
  | Purpose | Canonical URL |
87
139
  | :--- | :--- |
@@ -89,48 +141,28 @@ shared domain and the node name travels as a query parameter:
89
141
  | MCP Streamable HTTP | `https://<shared-domain>/mcp?user=<node-name>&token=<token>` |
90
142
  | Reverse tunnel (agent) | `wss://<shared-domain>/ws/tunnel?user=<node-name>&token=<token>` |
91
143
 
92
- Why: every extra hostname would need its own DNS record *and* its own SAN in the
93
- TLS certificate. A node whose name is missing from the certificate fails the TLS
94
- handshake, and the Gemini client then reports an opaque "cannot connect to host".
95
- With one shared domain the certificate covers every node forever, and adding a
96
- node is only a registration call.
144
+ Why: every extra hostname would need its own DNS record *and* its own SAN in the TLS certificate. A node whose name is missing from the certificate fails the TLS handshake, and the Gemini client then reports an opaque "cannot connect to host". With one shared domain the certificate covers every node forever, and adding a node is only a registration call.
97
145
 
98
- - The shared domain defaults to `smart-server.online`; override it with
99
- `./install.sh --domain=<shared-domain>` (node side) or `MESH_PUBLIC_URL`
100
- (gateway side).
101
- - Legacy per-device subdomain URLs still resolve for backwards compatibility and
102
- log a deprecation warning; set `MESH_LEGACY_SUBDOMAIN=0` on the gateway to
103
- reject them outright.
146
+ - The shared domain defaults to `smart-server.online`; override it with `./install.sh --domain=<shared-domain>` (node side) or `MESH_PUBLIC_URL` (gateway side).
147
+ - Legacy per-device subdomain URLs still resolve for backwards compatibility and log a deprecation warning; set `MESH_LEGACY_SUBDOMAIN=0` on the gateway to reject them outright.
104
148
 
105
149
  ---
106
150
 
107
- ## πŸ–₯️ Deployment Modes
151
+ ## πŸ”’ Security
108
152
 
109
- ### Mode 1: Cloud Gateway + Tunnel (Recommended)
110
- ```bash
111
- ./install.sh --quick
112
- ```
113
- Perfect for personal laptops and home workstations behind NAT. The connection is maintained via an outbound secure WebSocket tunnel.
114
-
115
- ### Mode 2: Local Standalone
116
- ```bash
117
- ./install.sh --mode=standalone --port=8096
118
- ```
119
- Runs an isolated, local FastMCP server on `http://localhost:8096/sse` without cloud relaying.
153
+ - **Token-gated endpoints** β€” every request must carry the node's secret token.
154
+ - **Outbound-only tunnel** β€” nodes never listen on public ports; the agent dials out to the gateway.
155
+ - **Gateway is a relay** β€” it terminates TLS and forwards calls; commands run only on your node.
156
+ - **Unprivileged by default** β€” the agent runs in user space, without root.
120
157
 
121
- ### Mode 3: Remote SSH Install
122
- ```bash
123
- ./install.sh --ssh=user@my-remote-server.com
124
- ```
125
- Automatically transfers files and configures the node on your remote server via SSH.
158
+ > [!WARNING]
159
+ > Whoever has your MCP link can run commands on that machine. Treat it like a password: don't publish it, and reinstall the node to rotate the token if it leaks.
126
160
 
127
161
  ---
128
162
 
129
- ## 🚒 Gateway Operations
163
+ ## 🚒 Self-hosting the gateway
130
164
 
131
- The gateway is a single host serving every node. Publish it only through the
132
- deploy script, so the running gateway, the served installers and the node
133
- bootstrap payload cannot drift apart:
165
+ The gateway is a single host serving every node. Publish it only through the deploy script, so the running gateway, the served installers and the node bootstrap payload cannot drift apart:
134
166
 
135
167
  ```bash
136
168
  MESH_GATEWAY_SSH=root@<gateway-ip> ./deploy_gateway.sh # deploy
@@ -138,14 +170,9 @@ MESH_GATEWAY_SSH=root@<gateway-ip> ./deploy_gateway.sh --dry-run # preview
138
170
  MESH_GATEWAY_SSH=root@<gateway-ip> MESH_GATEWAY_SSH_PASS_FILE=~/.ssh/gw-pass \
139
171
  ./deploy_gateway.sh # password auth
140
172
  ```
141
- It verifies every upload with an sha256 manifest on the host, takes a
142
- timestamped backup, installs with the right owner, restarts the service and
143
- finishes with a public health check. Nothing is installed if verification fails.
173
+ It verifies every upload with an sha256 manifest on the host, takes a timestamped backup, installs with the right owner, restarts the service and finishes with a public health check. Nothing is installed if verification fails.
144
174
 
145
- ### TLS certificate policy (one name, no per-device SANs)
146
-
147
- The certificate must cover the **shared domain only**. No device name is ever
148
- added to it, because a node is selected by `?user=` and never by a hostname:
175
+ **TLS certificate policy β€” one name, no per-device SANs.** The certificate must cover the shared domain only; a node is selected by `?user=`, never by a hostname:
149
176
 
150
177
  ```bash
151
178
  # what the certificate currently covers
@@ -154,106 +181,151 @@ sudo certbot certificates | grep -A1 'Certificate Name: smart-server.online'
154
181
  # canonical renewal check (staging, does not touch the live certificate)
155
182
  sudo certbot renew --dry-run --cert-name smart-server.online
156
183
  ```
157
- Legacy per-device SANs may still be present from before this contract. They are
158
- harmless and keep old bookmarked URLs working; drop them from the lineage at the
159
- next renewal once no client uses subdomain URLs any more (see the `domains =`
160
- line in `/etc/letsencrypt/renewal/<name>.conf`, or re-issue with a single `-d`).
184
+ Legacy per-device SANs may still be present from before this contract. They are harmless and keep old bookmarked URLs working; drop them at the next renewal once no client uses subdomain URLs any more (see the `domains =` line in `/etc/letsencrypt/renewal/<name>.conf`, or re-issue with a single `-d`).
161
185
 
162
186
  ---
163
187
 
164
- ## πŸ”’ Security Architecture
188
+ ## πŸ“„ License
165
189
 
166
- - **Token-Gated Endpoints**: Every incoming request must contain the cryptographic token via `?token=...` or `Authorization: Bearer`.
167
- - **Outbound-Only Tunnel**: Local nodes do not open listening public ports. The agent initiates an outbound WebSocket connection to the gateway.
168
- - **Unprivileged Execution**: Agents execute in user-space by default without requiring root privileges.
190
+ **MIT** β€” see [LICENSE](LICENSE).
169
191
 
192
+ ---
170
193
  ---
171
194
 
172
- ## πŸ“„ License
195
+ # πŸ‡·πŸ‡Ί Русская вСрсия
173
196
 
174
- This project is licensed under the **MIT License**. See [LICENSE](LICENSE) for details.
197
+ > **MCP-сСрвСр, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ ΠΏΡ€Π΅Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚ Google Gemini ΠΈΠ· Ρ‡Π°Ρ‚-Π±ΠΎΡ‚Π° Π² ΠΏΠΎΠ»Π½ΠΎΡ†Π΅Π½Π½ΠΎΠ³ΠΎ мобильного Π°Π³Π΅Π½Ρ‚Π°.**
198
+ > ΠΠ°ΠΏΠΈΡˆΠΈΡ‚Π΅ Π·Π°Π΄Π°Ρ‡Ρƒ Π² ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠΈ Gemini Π½Π° Ρ‚Π΅Π»Π΅Ρ„ΠΎΠ½Π΅ β€” ваш ПК, Π½ΠΎΡƒΡ‚Π±ΡƒΠΊ ΠΈΠ»ΠΈ сСрвСр Π²Ρ‹ΠΏΠΎΠ»Π½ΠΈΡ‚ Π΅Ρ‘, Π° Gemini вСрнётся с Ρ€Π΅Π°Π»ΡŒΠ½Ρ‹ΠΌ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ΠΎΠΌ.
175
199
 
176
- ---
177
200
  ---
178
201
 
179
- # πŸ‡·πŸ‡Ί Русская вСрсия
202
+ ## πŸ’‘ ИдСя
203
+
204
+ Gemini Π² Π±Ρ€Π°ΡƒΠ·Π΅Ρ€Π΅ ΠΈΠ»ΠΈ Π½Π° Ρ‚Π΅Π»Π΅Ρ„ΠΎΠ½Π΅ ΡƒΠΌΠ΅Π΅Ρ‚ Ρ€Π°Π·Π³ΠΎΠ²Π°Ρ€ΠΈΠ²Π°Ρ‚ΡŒ, Π½ΠΎ Π½ΠΈΡ‡Π΅Π³ΠΎ Π½Π΅ ΠΌΠΎΠΆΠ΅Ρ‚ *ΡΠ΄Π΅Π»Π°Ρ‚ΡŒ* Π½Π° вашСм ΠΊΠΎΠΌΠΏΡŒΡŽΡ‚Π΅Ρ€Π΅.
205
+ **Gemini Computer Use** устраняСт этот Ρ€Π°Π·Ρ€Ρ‹Π². Π­Ρ‚ΠΎ бСсплатный open-source сСрвСр **Model Context Protocol (MCP)**, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ ΠΏΠΎΠ΄ΠΊΠ»ΡŽΡ‡Π°Π΅Ρ‚ΡΡ ΠΊ **Gemini Spark** ([gemini.google.com/spark/apps](https://gemini.google.com/spark/apps) ΠΈΠ»ΠΈ мобильноС ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ Gemini) ΠΎΠ΄Π½ΠΎΠΉ ссылкой. ПослС этого Ρƒ Gemini ΠΏΠΎΡΠ²Π»ΡΡŽΡ‚ΡΡ Β«Ρ€ΡƒΠΊΠΈΒ»:
206
+
207
+ - выполняСт ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ Ρ‚Π΅Ρ€ΠΌΠΈΠ½Π°Π»Π°, собираСт ΠΈ тСстируСт ΠΊΠΎΠ΄, Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚ с git;
208
+ - Ρ‡ΠΈΡ‚Π°Π΅Ρ‚, создаёт ΠΈ Ρ€Π΅Π΄Π°ΠΊΡ‚ΠΈΡ€ΡƒΠ΅Ρ‚ Ρ„Π°ΠΉΠ»Ρ‹;
209
+ - ΠΈΡ‰Π΅Ρ‚ ΠΏΠΎ Ρ„Π°ΠΉΠ»ΠΎΠ²ΠΎΠΉ систСмС;
210
+ - запускаСт Π΄ΠΎΠ»Π³ΠΈΠ΅ Π·Π°Π΄Π°Ρ‡ΠΈ Π² Ρ„ΠΎΠ½Π΅ ΠΈ провСряСт ΠΈΡ… ΠΏΠΎΠ·ΠΆΠ΅;
211
+ - слСдит Π·Π° CPU, RAM ΠΈ диском.
212
+
213
+ ```text
214
+ πŸ“± ΠŸΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ Gemini / 🌐 gemini.google.com (Spark)
215
+ β”‚ MCP (SSE / Streamable HTTP)
216
+ β–Ό
217
+ ☁️ Шлюз smart-server.online ← Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Ρ€Π΅Π»Π΅ΠΉ, TLS
218
+ β”‚ исходящий WebSocket-Ρ‚ΡƒΠ½Π½Π΅Π»ΡŒ (Π±Π΅Π· ΠΎΡ‚ΠΊΡ€Ρ‹Ρ‚Ρ‹Ρ… ΠΏΠΎΡ€Ρ‚ΠΎΠ²)
219
+ β–Ό
220
+ πŸ’» Π’Π°Ρˆ ПК Β· Π½ΠΎΡƒΡ‚Π±ΡƒΠΊ Β· VPS Β· домашний сСрвСр β†’ выполняСт Π·Π°Π΄Π°Ρ‡Ρƒ
221
+ ```
180
222
 
181
- > **ΠŸΡ€Π΅Π²Ρ€Π°Ρ‚ΠΈΡ‚Π΅ свой ПК, Π½ΠΎΡƒΡ‚Π±ΡƒΠΊ ΠΈΠ»ΠΈ сСрвСр Π² Π°Π²Ρ‚ΠΎΠ½ΠΎΠΌΠ½Ρ‹ΠΉ ΠΈΡΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹ΠΉ ΡƒΠ·Π΅Π» для Google Gemini (Web ΠΈ Mobile App) Ρ‡Π΅Ρ€Π΅Π· Π½Π°Ρ‚ΠΈΠ²Π½Ρ‹ΠΉ ΠΏΡ€ΠΎΡ‚ΠΎΠΊΠΎΠ» Model Context Protocol (MCP).**
223
+ **Π˜Ρ‚ΠΎΠ³:** Ρ‚Π΅Π»Π΅Ρ„ΠΎΠ½ становится ΠΏΡƒΠ»ΡŒΡ‚ΠΎΠΌ управлСния всСми вашими машинами, Π° Gemini β€” Π°Π³Π΅Π½Ρ‚ΠΎΠΌ, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ ΠΈΠΌΠΈ управляСт.
182
224
 
183
225
  ---
184
226
 
185
- ## πŸ’‘ Π§Ρ‚ΠΎ это Ρ‚Π°ΠΊΠΎΠ΅?
227
+ ## πŸ”₯ Π§Ρ‚ΠΎ ΠΌΠΎΠΆΠ½ΠΎ Π΄Π΅Π»Π°Ρ‚ΡŒ
228
+
229
+ ΠŸΡ€ΠΎΡΡ‚ΠΎ попроситС Gemini ΠΎΠ±Ρ‹Ρ‡Π½Ρ‹ΠΌ языком, ΠΎΡ‚ΠΊΡƒΠ΄Π° ΡƒΠ³ΠΎΠ΄Π½ΠΎ:
186
230
 
187
- **Gemini Computer Use** β€” это ΠΎΡ‚ΠΊΡ€Ρ‹Ρ‚ΠΎΠ΅ ΠΈ бСсплатноС Ρ€Π΅ΡˆΠ΅Π½ΠΈΠ΅ для прямого управлСния вашим ΠΊΠΎΠΌΠΏΡŒΡŽΡ‚Π΅Ρ€ΠΎΠΌ, Π½ΠΎΡƒΡ‚Π±ΡƒΠΊΠΎΠΌ ΠΈΠ»ΠΈ ΡƒΠ΄Π°Π»Ρ‘Π½Π½Ρ‹ΠΌ сСрвСром ΠΈΠ· Π΄ΠΈΠ°Π»ΠΎΠ³ΠΎΠ²ΠΎΠ³ΠΎ ΠΎΠΊΠ½Π° **Google Gemini** (Π²ΠΊΠ»ΡŽΡ‡Π°Ρ Π²Π΅Π±-Π²Π΅Ρ€ΡΠΈΡŽ [gemini.google.com](https://gemini.google.com), мобильноС ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ Gemini ΠΈ Google AI Studio).
231
+ - *Β«ΠŸΠΎΡΠΌΠΎΡ‚Ρ€ΠΈ, ΠΏΠΎΡ‡Π΅ΠΌΡƒ Ρ‚ΠΎΡ€ΠΌΠΎΠ·ΠΈΡ‚ домашний сСрвСр, ΠΈ ΠΏΠΎΠΊΠ°ΠΆΠΈ Ρ‚ΠΎΠΏ процСссов»*
232
+ - *Β«ΠŸΠΎΠ΄Ρ‚ΡΠ½ΠΈ послСдниС измСнСния Π² ~/projects/api, ΠΏΡ€ΠΎΠ³ΠΎΠ½ΠΈ тСсты ΠΈ скаТи, Ρ‡Ρ‚ΠΎ ΡƒΠΏΠ°Π»ΠΎΒ»*
233
+ - *Β«Π˜ΡΠΏΡ€Π°Π²ΡŒ ΠΎΠΏΠ΅Ρ‡Π°Ρ‚ΠΊΡƒ Π² config.yaml ΠΈ пСрСзапусти сСрвис»*
234
+ - *«Запусти ΠΏΠΎΠ»Π½Ρ‹ΠΉ бэкап Π² Ρ„ΠΎΠ½Π΅ ΠΈ сообщи, ΠΊΠΎΠ³Π΄Π° закончится»*
235
+ - *«Бколько свободного мСста ΠΎΡΡ‚Π°Π»ΠΎΡΡŒ Π½Π° Π½ΠΎΡƒΡ‚Π±ΡƒΠΊΠ΅?Β»*
188
236
 
189
- ΠŸΡ€ΠΎΠ΅ΠΊΡ‚ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ ΠΎΡ„ΠΈΡ†ΠΈΠ°Π»ΡŒΠ½Ρ‹ΠΉ ΠΏΡ€ΠΎΡ‚ΠΎΠΊΠΎΠ» **Model Context Protocol (MCP)** ΠΎΡ‚ Google ΠΈ Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚ ΠΏΠΎΠ²Π΅Ρ€Ρ… Π·Π°Ρ‰ΠΈΡ‰Ρ‘Π½Π½ΠΎΠ³ΠΎ WebSocket-туннСля ΠΈΠ»ΠΈ Π² локальном Π°Π²Ρ‚ΠΎΠ½ΠΎΠΌΠ½ΠΎΠΌ Ρ€Π΅ΠΆΠΈΠΌΠ΅.
237
+ Π‘Π΅Π· SSH-ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π°, Π±Π΅Π· Ρ‚Π΅Ρ€ΠΌΠΈΠ½Π°Π»Π° Π½Π° Ρ‚Π΅Π»Π΅Ρ„ΠΎΠ½Π΅, Π±Π΅Π· VPN β€” Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Ρ‡Π°Ρ‚.
190
238
 
191
239
  ---
192
240
 
193
- ## ✨ ΠšΠ»ΡŽΡ‡Π΅Π²Ρ‹Π΅ возмоТности
241
+ ## ✨ ΠŸΠΎΡ‡Π΅ΠΌΡƒ это ΡƒΠ΄ΠΎΠ±Π½ΠΎ
194
242
 
195
- - πŸ†“ **100% БСсплатно**: Π Π°Π±ΠΎΡ‚Π°Π΅Ρ‚ со стандартным бСсплатным Ρ‚Π°Ρ€ΠΈΡ„ΠΎΠΌ Google Gemini (Web ΠΈ Mobile App).
196
- - ⚑ **Установка Π² ΠΎΠ΄Π½Ρƒ ΠΊΠΎΠΌΠ°Π½Π΄Ρƒ (One-Line Setup)**: Π‘ΠΊΡ€ΠΈΠΏΡ‚ автоматичСски опрСдСляСт Ρ‚ΠΈΠΏ устройства (Π½ΠΎΡƒΡ‚Π±ΡƒΠΊ/дСсктоп/VPS/сСрвСр), ОБ ΠΈ Π°Ρ€Ρ…ΠΈΡ‚Π΅ΠΊΡ‚ΡƒΡ€Ρƒ.
197
- - 🌐 **Π”Π²Π° Ρ€Π΅ΠΆΠΈΠΌΠ° Ρ€Π°Π±ΠΎΡ‚Ρ‹**:
198
- 1. **Cloud Gateway + Reverse Tunnel**: Π Π°Π±ΠΎΡ‚Π°Π΅Ρ‚ ΠΈΠ·-Π·Π° любого «сСрого» IP, Ρ€ΠΎΡƒΡ‚Π΅Ρ€Π° ΠΈ NAT Π±Π΅Π· Π±Π΅Π»ΠΎΠ³ΠΎ IP ΠΈ проброса ΠΏΠΎΡ€Ρ‚ΠΎΠ².
199
- 2. **Local Standalone**: ΠŸΠΎΠ»Π½ΠΎΡΡ‚ΡŒΡŽ Π»ΠΎΠΊΠ°Π»ΡŒΠ½Ρ‹ΠΉ FastMCP-сСрвСр Π½Π° `localhost:8096` Π±Π΅Π· Π²Π½Π΅ΡˆΠ½ΠΈΡ… зависимостСй.
200
- - πŸ›‘οΈ **Бтрогая Π±Π΅Π·ΠΎΠΏΠ°ΡΠ½ΠΎΡΡ‚ΡŒ**: ΠšΠ°ΠΆΠ΄Ρ‹ΠΉ запрос ΠΊ инструмСнтам Π·Π°Ρ‰ΠΈΡ‰Ρ‘Π½ ΠΏΠ΅Ρ€ΡΠΎΠ½Π°Π»ΡŒΠ½Ρ‹ΠΌ криптографичСским Ρ‚ΠΎΠΊΠ΅Π½ΠΎΠΌ (`?token=...`). Доступ посторонних строго отсСкаСтся (HTTP 401).
201
- - πŸ“‘ **Удалённая установка Ρ‡Π΅Ρ€Π΅Π· SSH**: Π Π°Π·Π²Ρ‘Ρ€Ρ‚Ρ‹Π²Π°Π½ΠΈΠ΅ ΡƒΠ·Π»Π° Π½Π° ΡƒΠ΄Π°Π»Ρ‘Π½Π½ΠΎΠΌ сСрвСрС ΠΎΠ΄Π½ΠΎΠΉ ΠΊΠΎΠΌΠ°Π½Π΄ΠΎΠΉ (`--ssh=user@host`).
202
- - πŸ”„ **Автозапуск**: Π˜Π½Ρ‚Π΅Π³Ρ€Π°Ρ†ΠΈΡ со слуТбами автозапуска (`systemd` Π² Linux, `launchd` Π² macOS, автозапуск Π² Windows).
203
- - πŸ“± **Π£ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ со смартфона**: ΠŸΠΈΡˆΠΈΡ‚Π΅ ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ Π² мобильном ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠΈ Gemini β€” ваша домашняя машина Π²Ρ‹ΠΏΠΎΠ»Π½ΠΈΡ‚ ΠΈΡ… Π² Ρ€Π΅Π°Π»ΡŒΠ½ΠΎΠΌ Π²Ρ€Π΅ΠΌΠ΅Π½ΠΈ!
204
- - πŸ’» **ΠšΡ€ΠΎΡΡΠΏΠ»Π°Ρ‚Ρ„ΠΎΡ€ΠΌΠ΅Π½Π½ΠΎΡΡ‚ΡŒ**: Полная ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° Linux, macOS ΠΈ Windows.
243
+ | | |
244
+ | :--- | :--- |
245
+ | πŸ“± **ΠœΠΎΠ±ΠΈΠ»ΡŒΠ½Ρ‹ΠΉ Π°Π³Π΅Π½Ρ‚** | Π Π°Π±ΠΎΡ‚Π°Π΅Ρ‚ Π² ΠΎΡ„ΠΈΡ†ΠΈΠ°Π»ΡŒΠ½ΠΎΠΌ ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠΈ Gemini ΠΈ Π² Π²Π΅Π±Π΅ Ρ‡Π΅Ρ€Π΅Π· Gemini Spark. |
246
+ | πŸ–₯️ **Π›ΡŽΠ±ΠΎΠΉ ПК** | Linux, macOS, Windows β€” дСсктопы, Π½ΠΎΡƒΡ‚Π±ΡƒΠΊΠΈ, VPS, ΠΊΠΎΠ½Ρ‚Π΅ΠΉΠ½Π΅Ρ€Ρ‹. |
247
+ | 🌐 **Π—Π° Π»ΡŽΠ±Ρ‹ΠΌ NAT** | Π£Π·Π΅Π» сам ΠΎΡ‚ΠΊΡ€Ρ‹Π²Π°Π΅Ρ‚ *исходящий* WebSocket-Ρ‚ΡƒΠ½Π½Π΅Π»ΡŒ. НС Π½ΡƒΠΆΠ½Ρ‹ Π±Π΅Π»Ρ‹ΠΉ IP, проброс ΠΏΠΎΡ€Ρ‚ΠΎΠ² ΠΈ настройка Ρ€ΠΎΡƒΡ‚Π΅Ρ€Π°. |
248
+ | ⚑ **Установка ΠΎΠ΄Π½ΠΎΠΉ ΠΊΠΎΠΌΠ°Π½Π΄ΠΎΠΉ** | ΠžΠΏΡ€Π΅Π΄Π΅Π»ΡΠ΅Ρ‚ ОБ, Π°Ρ€Ρ…ΠΈΡ‚Π΅ΠΊΡ‚ΡƒΡ€Ρƒ ΠΈ Ρ‚ΠΈΠΏ устройства; ставит автозапуск (`systemd` / `launchd` / Π·Π°Π΄Π°Ρ‡Π° Windows); ΠΊΠΎΠΏΠΈΡ€ΡƒΠ΅Ρ‚ ссылку ΠΏΠΎΠ΄ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΡ Π² Π±ΡƒΡ„Π΅Ρ€ ΠΎΠ±ΠΌΠ΅Π½Π°. |
249
+ | πŸ›‘οΈ **Доступ ΠΏΠΎ Ρ‚ΠΎΠΊΠ΅Π½Ρƒ** | ΠšΠ°ΠΆΠ΄Ρ‹ΠΉ Π²Ρ‹Π·ΠΎΠ² Ρ‚Ρ€Π΅Π±ΡƒΠ΅Ρ‚ Π»ΠΈΡ‡Π½Ρ‹ΠΉ 128-Π±ΠΈΡ‚Π½Ρ‹ΠΉ Ρ‚ΠΎΠΊΠ΅Π½ (`?token=…` ΠΈΠ»ΠΈ `Authorization: Bearer`). ΠžΡΡ‚Π°Π»ΡŒΠ½Ρ‹ΠΌ β€” HTTP 401. |
250
+ | πŸ” **Много машин, ΠΎΠ΄ΠΈΠ½ шлюз** | ΠšΠ°ΠΆΠ΄Ρ‹ΠΉ ΡƒΠ·Π΅Π» выбираСтся ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ΠΎΠΌ `?user=<имя-ΡƒΠ·Π»Π°>` Π½Π° ΠΎΠ΄Π½ΠΎΠΌ ΠΎΠ±Ρ‰Π΅ΠΌ Π΄ΠΎΠΌΠ΅Π½Π΅. |
251
+ | πŸ†“ **БСсплатно ΠΈ ΠΎΡ‚ΠΊΡ€Ρ‹Ρ‚ΠΎ** | ЛицСнзия MIT, Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚ Π½Π° бСсплатном Ρ‚Π°Ρ€ΠΈΡ„Π΅ Gemini. |
205
252
 
206
253
  ---
207
254
 
208
- ## πŸ› οΈ ДоступныС MCP-инструмСнты
255
+ ## πŸ› οΈ Π§Ρ‚ΠΎ Gemini ΠΌΠΎΠΆΠ΅Ρ‚ Π΄Π΅Π»Π°Ρ‚ΡŒ Π½Π° вашСй машинС (MCP-инструмСнты)
209
256
 
210
- - `bash_exec(command)` β€” Π°Π²Ρ‚ΠΎΠ½ΠΎΠΌΠ½ΠΎΠ΅ Π²Ρ‹ΠΏΠΎΠ»Π½Π΅Π½ΠΈΠ΅ ΠΊΠΎΠΌΠ°Π½Π΄ Ρ‚Π΅Ρ€ΠΌΠΈΠ½Π°Π»Π°, компиляция ΠΊΠΎΠ΄Π°, тСсты, git-ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΠΈ.
211
- - `system_vitals()` β€” ΠΌΠΎΠ½ΠΈΡ‚ΠΎΡ€ΠΈΠ½Π³ Π½Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Π² Ρ€Π΅Π°Π»ΡŒΠ½ΠΎΠΌ Π²Ρ€Π΅ΠΌΠ΅Π½ΠΈ (CPU, RAM, диск).
212
- - `get_orchestration_skill()` β€” систСмныС ΠΏΡ€Π°Π²ΠΈΠ»Π° ΠΈ скилл Π°Π²Ρ‚ΠΎΠ½ΠΎΠΌΠ½ΠΎΠ³ΠΎ оркСстратора инфраструктуры.
257
+ | ΠšΠ°Ρ‚Π΅Π³ΠΎΡ€ΠΈΡ | Π˜Π½ΡΡ‚Ρ€ΡƒΠΌΠ΅Π½Ρ‚Ρ‹ |
258
+ | :--- | :--- |
259
+ | Π’Π΅Ρ€ΠΌΠΈΠ½Π°Π» | `bash_exec(command)` |
260
+ | Π€Π°ΠΉΠ»Ρ‹ | `list_dir(path)` Β· `read_file(path, start_line, end_line)` Β· `write_file(path, content)` Β· `edit_file(path, old_string, new_string)` |
261
+ | Поиск | `grep_search(pattern, path)` · `glob_find(pattern, path)` |
262
+ | Π”ΠΎΠ»Π³ΠΈΠ΅ Π·Π°Π΄Π°Ρ‡ΠΈ | `run_job(command)` β†’ `job_output(job_id)` Β· `job_kill(job_id)` Β· `job_list()` |
263
+ | БостояниС | `system_vitals()` Β· `system_info()` Β· `mesh_status()` |
264
+ | ПовСдСниС Π°Π³Π΅Π½Ρ‚Π° | `get_orchestration_skill()` β€” Π·Π°Π³Ρ€ΡƒΠΆΠ°Π΅Ρ‚ Ρ€Π°Π±ΠΎΡ‡ΠΈΠ΅ ΠΏΡ€Π°Π²ΠΈΠ»Π° Π°Π³Π΅Π½Ρ‚Π° |
265
+
266
+ > [!TIP]
267
+ > Gemini Π΄Π°Ρ‘Ρ‚ ΠΎΠ΄Π½ΠΎΠΌΡƒ Π²Ρ‹Π·ΠΎΠ²Ρƒ инструмСнта ΠΎΠΊΠΎΠ»ΠΎ 30 сСкунд. Всё, Ρ‡Ρ‚ΠΎ дольшС (сборки, бэкапы, Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ), Π°Π³Π΅Π½Ρ‚ запускаСт Ρ‡Π΅Ρ€Π΅Π· `run_job` ΠΈ Π·Π°Π±ΠΈΡ€Π°Π΅Ρ‚ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ Ρ‡Π΅Ρ€Π΅Π· `job_output` β€” Π·Π°Π΄Π°Ρ‡ΠΈ Π½Π΅ ΠΎΠ±Ρ€Ρ‹Π²Π°ΡŽΡ‚ΡΡ.
213
268
 
214
269
  ---
215
270
 
216
- ## πŸš€ Быстрый старт (Установка Π² 1 ΠΊΠΎΠΌΠ°Π½Π΄Ρƒ)
271
+ ## πŸš€ Быстрый старт β€” 2 ΠΌΠΈΠ½ΡƒΡ‚Ρ‹
272
+
273
+ ### 1. УстановитС ΡƒΠ·Π΅Π» Π½Π° ПК, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΌ Ρ…ΠΎΡ‚ΠΈΡ‚Π΅ ΡƒΠΏΡ€Π°Π²Π»ΡΡ‚ΡŒ
217
274
 
218
- #### 🐧 Linux & 🍎 macOS (Ρ‡Π΅Ρ€Π΅Π· curl):
275
+ **🐧 Linux / 🍎 macOS**
219
276
  ```bash
220
277
  curl -fsSL https://smart-server.online/install.sh | bash
221
278
  ```
222
279
 
223
- #### πŸͺŸ Windows (Ρ‡Π΅Ρ€Π΅Π· PowerShell):
280
+ **πŸͺŸ Windows (PowerShell)**
224
281
  ```powershell
225
282
  irm https://smart-server.online/install.ps1 | iex
226
283
  ```
227
284
 
228
- #### πŸ“¦ Node.js / NPM (ΠšΡ€ΠΎΡΡΠΏΠ»Π°Ρ‚Ρ„ΠΎΡ€ΠΌΠ΅Π½Π½ΠΎ):
285
+ **πŸ“¦ Node.js (любая ОБ)**
229
286
  ```bash
230
287
  npx gemini-computer-use
231
288
  ```
232
289
 
233
- #### πŸ› οΈ Π›ΠΈΠ±ΠΎ Π²Ρ€ΡƒΡ‡Π½ΡƒΡŽ Ρ‡Π΅Ρ€Π΅Π· Git:
290
+ **πŸ› οΈ Из исходников**
234
291
  ```bash
235
292
  git clone https://github.com/LevRa7/Computer-use-for-Gemini-App-Web.git
236
293
  cd Computer-use-for-Gemini-App-Web
237
294
  ./install.sh --quick
238
295
  ```
239
296
 
297
+ Π’ ΠΊΠΎΠ½Ρ†Π΅ установщик Π²Ρ‹Π²Π΅Π΄Π΅Ρ‚ Π²Π°ΡˆΡƒ Π»ΠΈΡ‡Π½ΡƒΡŽ MCP-ссылку ΠΈ скопируСт Π΅Ρ‘ Π² Π±ΡƒΡ„Π΅Ρ€ ΠΎΠ±ΠΌΠ΅Π½Π°:
298
+ ```text
299
+ https://smart-server.online/sse?user=<имя-вашСго-ΡƒΠ·Π»Π°>&token=<ваш_сСкрСтный_Ρ‚ΠΎΠΊΠ΅Π½>
300
+ ```
301
+
302
+ ### 2. ΠŸΠΎΠ΄ΠΊΠ»ΡŽΡ‡ΠΈΡ‚Π΅ Π΅Ρ‘ ΠΊ Gemini Spark
303
+
304
+ 1. ΠžΡ‚ΠΊΡ€ΠΎΠΉΡ‚Π΅ **[gemini.google.com/spark/apps](https://gemini.google.com/spark/apps)** Π² Π±Ρ€Π°ΡƒΠ·Π΅Ρ€Π΅ ΠΈΠ»ΠΈ Π² мобильном ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠΈ Gemini.
305
+ 2. НаТмитС **Π”ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅** (ΠΈΠ»ΠΈ **Настройки βš™οΈ β†’ Π˜Π½ΡΡ‚Ρ€ΡƒΠΌΠ΅Π½Ρ‚Ρ‹ / Π Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΡ (MCP)**).
306
+ 3. Π’ΡΡ‚Π°Π²ΡŒΡ‚Π΅ ссылку (**Ctrl+V**) ΠΈ сохранитС.
307
+
308
+ ### 3. Π”Π°ΠΉΡ‚Π΅ Π·Π°Π΄Π°Ρ‡Ρƒ
309
+
310
+ > *«ПокаТи system_vitals ΠΈ 5 самых Π±ΠΎΠ»ΡŒΡˆΠΈΡ… ΠΏΠ°ΠΏΠΎΠΊ Π² домашнСм ΠΊΠ°Ρ‚Π°Π»ΠΎΠ³Π΅.Β»*
311
+
312
+ Π“ΠΎΡ‚ΠΎΠ²ΠΎ β€” Gemini Ρ‚Π΅ΠΏΠ΅Ρ€ΡŒ Π°Π³Π΅Π½Ρ‚, Ρ€Π°Π±ΠΎΡ‚Π°ΡŽΡ‰ΠΈΠΉ Π½Π° вашСй машинС. ΠŸΠΎΠ²Ρ‚ΠΎΡ€ΠΈΡ‚Π΅ шаг 1 Π½Π° Π΄Ρ€ΡƒΠ³ΠΈΡ… ΠΊΠΎΠΌΠΏΡŒΡŽΡ‚Π΅Ρ€Π°Ρ…, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΡƒΠΏΡ€Π°Π²Π»ΡΡ‚ΡŒ ΠΈΠΌΠΈ всСми с ΠΎΠ΄Π½ΠΎΠ³ΠΎ Ρ‚Π΅Π»Π΅Ρ„ΠΎΠ½Π°.
313
+
240
314
  ---
241
315
 
242
- ## πŸ“± ΠŸΠΎΠ΄ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠ΅ ΠΊ Google Gemini
316
+ ## πŸ–₯️ Π Π΅ΠΆΠΈΠΌΡ‹ развёртывания
243
317
 
244
- 1. ΠžΡ‚ΠΊΡ€ΠΎΠΉΡ‚Π΅ страницу **[gemini.google.com/spark/apps](https://gemini.google.com/spark/apps)** (Π² Π±Ρ€Π°ΡƒΠ·Π΅Ρ€Π΅ ΠΈΠ»ΠΈ ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠΈ).
245
- 2. НаТмитС **Π”ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅** (Add App / Настройки βž” MCP).
246
- 3. Бсылка Π½Π° ваш MCP-сСрвСр **автоматичСски скопирована Π² Π±ΡƒΡ„Π΅Ρ€ ΠΎΠ±ΠΌΠ΅Π½Π°** ΠΏΡ€ΠΈ установкС β€” просто Π²ΡΡ‚Π°Π²ΡŒΡ‚Π΅ Π΅Ρ‘ (**Ctrl+V**):
247
- ```text
248
- https://smart-server.online/sse?user=<имя-вашСго-ΡƒΠ·Π»Π°>&token=<ваш_сСкрСтный_Ρ‚ΠΎΠΊΠ΅Π½>
249
- ```
250
- ВсС ΡƒΠ·Π»Ρ‹ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΡŽΡ‚ **ΠΎΠ΄ΠΈΠ½ ΠΎΠ±Ρ‰ΠΈΠΉ Π΄ΠΎΠΌΠ΅Π½**, Π° Π½ΡƒΠΆΠ½Ρ‹ΠΉ ΡƒΠ·Π΅Π» выбираСтся ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ΠΎΠΌ `?user=` β€” субдомСны для ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ устройства Π½Π΅ ΡΠΎΠ·Π΄Π°ΡŽΡ‚ΡΡ, ΠΈ Π΄ΠΎΠ±Π°Π²Π»ΡΡ‚ΡŒ Ρ‡Ρ‚ΠΎ-Π»ΠΈΠ±ΠΎ Π² TLS-сСртификат Π½Π΅ Π½ΡƒΠΆΠ½ΠΎ.
251
- 4. Π“ΠΎΡ‚ΠΎΠ²ΠΎ! Π’Π΅ΠΏΠ΅Ρ€ΡŒ Gemini ΠΌΠΎΠΆΠ΅Ρ‚ Π²Ρ‹ΠΏΠΎΠ»Π½ΡΡ‚ΡŒ ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ прямо Π½Π° вашСй машинС.
318
+ | Π Π΅ΠΆΠΈΠΌ | Команда | Когда ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ |
319
+ | :--- | :--- | :--- |
320
+ | **Шлюз + Ρ‚ΡƒΠ½Π½Π΅Π»ΡŒ** (рСкомСндуСтся) | `./install.sh --quick` | Π”ΠΎΠΌΠ°ΡˆΠ½ΠΈΠ΅ ПК ΠΈ Π½ΠΎΡƒΡ‚Π±ΡƒΠΊΠΈ Π·Π° NAT β€” сцСнарий мобильного Π°Π³Π΅Π½Ρ‚Π°. |
321
+ | **Π£Π΄Π°Π»Ρ‘Π½Π½ΠΎ Ρ‡Π΅Ρ€Π΅Π· SSH** | `./install.sh --ssh=user@host` | Π Π°Π·Π²Π΅Ρ€Π½ΡƒΡ‚ΡŒ ΡƒΠ·Π΅Π» Π½Π° ΡƒΠ΄Π°Π»Ρ‘Π½Π½ΠΎΠΌ Linux-сСрвСрС прямо ΠΈΠ· Ρ‚Π΅Ρ€ΠΌΠΈΠ½Π°Π»Π°. |
322
+ | **Π›ΠΎΠΊΠ°Π»ΡŒΠ½Ρ‹ΠΉ Π°Π²Ρ‚ΠΎΠ½ΠΎΠΌΠ½Ρ‹ΠΉ** | `./install.sh --mode=standalone --port=8096` | Чисто Π»ΠΎΠΊΠ°Π»ΡŒΠ½Ρ‹ΠΉ FastMCP-сСрвСр Π½Π° `http://localhost:8096/sse`, Π±Π΅Π· ΠΎΠ±Π»Π°Ρ‡Π½ΠΎΠ³ΠΎ рСлСя. |
252
323
 
253
- ### Один ΠΎΠ±Ρ‰ΠΈΠΉ Π΄ΠΎΠΌΠ΅Π½ (каноничСский Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ URL)
324
+ ---
254
325
 
255
- Π£Π·Π΅Π» **Π½ΠΈΠΊΠΎΠ³Π΄Π°** Π½Π΅ адрСсуСтся собствСнным ΠΈΠΌΠ΅Π½Π΅ΠΌ хоста. Шлюз ΠΏΡƒΠ±Π»ΠΈΠΊΡƒΠ΅Ρ‚ ΠΎΠ΄ΠΈΠ½
256
- ΠΎΠ±Ρ‰ΠΈΠΉ Π΄ΠΎΠΌΠ΅Π½, Π° имя ΡƒΠ·Π»Π° пСрСдаётся ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ΠΎΠΌ:
326
+ ## πŸ”— Один ΠΎΠ±Ρ‰ΠΈΠΉ Π΄ΠΎΠΌΠ΅Π½ (каноничСский Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ URL)
327
+
328
+ Π£Π·Π΅Π» **Π½ΠΈΠΊΠΎΠ³Π΄Π°** Π½Π΅ адрСсуСтся собствСнным ΠΈΠΌΠ΅Π½Π΅ΠΌ хоста. Шлюз ΠΏΡƒΠ±Π»ΠΈΠΊΡƒΠ΅Ρ‚ ΠΎΠ΄ΠΈΠ½ ΠΎΠ±Ρ‰ΠΈΠΉ Π΄ΠΎΠΌΠ΅Π½, Π° имя ΡƒΠ·Π»Π° пСрСдаётся ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ΠΎΠΌ:
257
329
 
258
330
  | НазначСниС | ΠšΠ°Π½ΠΎΠ½ΠΈΡ‡Π΅ΡΠΊΠΈΠΉ URL |
259
331
  | :--- | :--- |
@@ -261,47 +333,45 @@ cd Computer-use-for-Gemini-App-Web
261
333
  | MCP Streamable HTTP | `https://<ΠΎΠ±Ρ‰ΠΈΠΉ-Π΄ΠΎΠΌΠ΅Π½>/mcp?user=<имя-ΡƒΠ·Π»Π°>&token=<Ρ‚ΠΎΠΊΠ΅Π½>` |
262
334
  | ΠžΠ±Ρ€Π°Ρ‚Π½Ρ‹ΠΉ Ρ‚ΡƒΠ½Π½Π΅Π»ΡŒ (Π°Π³Π΅Π½Ρ‚) | `wss://<ΠΎΠ±Ρ‰ΠΈΠΉ-Π΄ΠΎΠΌΠ΅Π½>/ws/tunnel?user=<имя-ΡƒΠ·Π»Π°>&token=<Ρ‚ΠΎΠΊΠ΅Π½>` |
263
335
 
264
- ΠŸΠΎΡ‡Π΅ΠΌΡƒ Ρ‚Π°ΠΊ: ΠΊΠ°ΠΆΠ΄ΠΎΠΌΡƒ Π΄ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΠΌΡƒ ΠΈΠΌΠ΅Π½ΠΈ хоста Π½ΡƒΠΆΠ½Π° своя DNS-запись **ΠΈ** свой
265
- SAN Π² TLS-сСртификатС. Если ΠΈΠΌΠ΅Π½ΠΈ Π½Π΅Ρ‚ Π² сСртификатС, TLS-Ρ€ΡƒΠΊΠΎΠΏΠΎΠΆΠ°Ρ‚ΠΈΠ΅ ΠΏΠ°Π΄Π°Π΅Ρ‚, ΠΈ
266
- ΠΊΠ»ΠΈΠ΅Π½Ρ‚ Gemini ΠΏΠΎΠΊΠ°Π·Ρ‹Π²Π°Π΅Ρ‚ Π½Π΅Π²Π½ΡΡ‚Π½ΡƒΡŽ ΠΎΡˆΠΈΠ±ΠΊΡƒ Β«Π½Π΅ удаётся ΠΏΠΎΠ΄ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒΡΡ ΠΊ хосту».
267
- Π‘ ΠΎΠ΄Π½ΠΈΠΌ ΠΎΠ±Ρ‰ΠΈΠΌ Π΄ΠΎΠΌΠ΅Π½ΠΎΠΌ сСртификат ΠΏΠΎΠΊΡ€Ρ‹Π²Π°Π΅Ρ‚ всС ΡƒΠ·Π»Ρ‹ сразу, Π° Π΄ΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΠ΅ ΡƒΠ·Π»Π° β€”
268
- это Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π²Ρ‹Π·ΠΎΠ² рСгистрации.
336
+ ΠŸΠΎΡ‡Π΅ΠΌΡƒ Ρ‚Π°ΠΊ: ΠΊΠ°ΠΆΠ΄ΠΎΠΌΡƒ Π΄ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΠΌΡƒ ΠΈΠΌΠ΅Π½ΠΈ хоста Π½ΡƒΠΆΠ½Π° своя DNS-запись **ΠΈ** свой SAN Π² TLS-сСртификатС. Если ΠΈΠΌΠ΅Π½ΠΈ Π½Π΅Ρ‚ Π² сСртификатС, TLS-Ρ€ΡƒΠΊΠΎΠΏΠΎΠΆΠ°Ρ‚ΠΈΠ΅ ΠΏΠ°Π΄Π°Π΅Ρ‚, ΠΈ ΠΊΠ»ΠΈΠ΅Π½Ρ‚ Gemini ΠΏΠΎΠΊΠ°Π·Ρ‹Π²Π°Π΅Ρ‚ Π½Π΅Π²Π½ΡΡ‚Π½ΡƒΡŽ ΠΎΡˆΠΈΠ±ΠΊΡƒ Β«Π½Π΅ удаётся ΠΏΠΎΠ΄ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒΡΡ ΠΊ хосту». Π‘ ΠΎΠ΄Π½ΠΈΠΌ ΠΎΠ±Ρ‰ΠΈΠΌ Π΄ΠΎΠΌΠ΅Π½ΠΎΠΌ сСртификат ΠΏΠΎΠΊΡ€Ρ‹Π²Π°Π΅Ρ‚ всС ΡƒΠ·Π»Ρ‹ сразу, Π° Π΄ΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΠ΅ ΡƒΠ·Π»Π° β€” это Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π²Ρ‹Π·ΠΎΠ² рСгистрации.
337
+
338
+ - ΠžΠ±Ρ‰ΠΈΠΉ Π΄ΠΎΠΌΠ΅Π½ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ `smart-server.online`; пСрСопрСдСляСтся Ρ‡Π΅Ρ€Π΅Π· `./install.sh --domain=<ΠΎΠ±Ρ‰ΠΈΠΉ-Π΄ΠΎΠΌΠ΅Π½>` (Π½Π° сторонС ΡƒΠ·Π»Π°) ΠΈΠ»ΠΈ `MESH_PUBLIC_URL` (Π½Π° сторонС шлюза).
339
+ - Π‘Ρ‚Π°Ρ€Ρ‹Π΅ ссылки с субдомСнами устройства ΠΏΡ€ΠΎΠ΄ΠΎΠ»ΠΆΠ°ΡŽΡ‚ Ρ€Π°Π±ΠΎΡ‚Π°Ρ‚ΡŒ для совмСстимости ΠΈ ΠΏΠΈΡˆΡƒΡ‚ ΠΏΡ€Π΅Π΄ΡƒΠΏΡ€Π΅ΠΆΠ΄Π΅Π½ΠΈΠ΅ Π² Π»ΠΎΠ³; `MESH_LEGACY_SUBDOMAIN=0` Π½Π° шлюзС ΠΎΡ‚ΠΊΠ»ΡŽΡ‡Π°Π΅Ρ‚ ΠΈΡ….
340
+
341
+ ---
342
+
343
+ ## πŸ”’ Π‘Π΅Π·ΠΎΠΏΠ°ΡΠ½ΠΎΡΡ‚ΡŒ
344
+
345
+ - **Доступ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΏΠΎ Ρ‚ΠΎΠΊΠ΅Π½Ρƒ** β€” ΠΊΠ°ΠΆΠ΄Ρ‹ΠΉ запрос Π΄ΠΎΠ»ΠΆΠ΅Π½ ΡΠΎΠ΄Π΅Ρ€ΠΆΠ°Ρ‚ΡŒ сСкрСтный Ρ‚ΠΎΠΊΠ΅Π½ ΡƒΠ·Π»Π°.
346
+ - **Волько исходящий Ρ‚ΡƒΠ½Π½Π΅Π»ΡŒ** β€” ΡƒΠ·Π»Ρ‹ Π½Π΅ ΡΠ»ΡƒΡˆΠ°ΡŽΡ‚ ΠΏΡƒΠ±Π»ΠΈΡ‡Π½Ρ‹Π΅ ΠΏΠΎΡ€Ρ‚Ρ‹; Π°Π³Π΅Π½Ρ‚ сам ΠΏΠΎΠ΄ΠΊΠ»ΡŽΡ‡Π°Π΅Ρ‚ΡΡ ΠΊ ΡˆΠ»ΡŽΠ·Ρƒ.
347
+ - **Шлюз β€” Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Ρ€Π΅Π»Π΅ΠΉ** β€” ΠΎΠ½ Ρ‚Π΅Ρ€ΠΌΠΈΠ½ΠΈΡ€ΡƒΠ΅Ρ‚ TLS ΠΈ ΠΏΠ΅Ρ€Π΅Π΄Π°Ρ‘Ρ‚ Π²Ρ‹Π·ΠΎΠ²Ρ‹; ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ Π²Ρ‹ΠΏΠΎΠ»Π½ΡΡŽΡ‚ΡΡ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π½Π° вашСм ΡƒΠ·Π»Π΅.
348
+ - **Π‘Π΅Π· root ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ** β€” Π°Π³Π΅Π½Ρ‚ Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚ Π² пространствС ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ.
269
349
 
270
- - ΠžΠ±Ρ‰ΠΈΠΉ Π΄ΠΎΠΌΠ΅Π½ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ `smart-server.online`; пСрСопрСдСляСтся Ρ‡Π΅Ρ€Π΅Π·
271
- `./install.sh --domain=<ΠΎΠ±Ρ‰ΠΈΠΉ-Π΄ΠΎΠΌΠ΅Π½>` (Π½Π° сторонС ΡƒΠ·Π»Π°) ΠΈΠ»ΠΈ `MESH_PUBLIC_URL`
272
- (Π½Π° сторонС шлюза).
273
- - Π‘Ρ‚Π°Ρ€Ρ‹Π΅ ссылки с субдомСнами устройства ΠΏΡ€ΠΎΠ΄ΠΎΠ»ΠΆΠ°ΡŽΡ‚ Ρ€Π°Π±ΠΎΡ‚Π°Ρ‚ΡŒ для совмСстимости
274
- ΠΈ ΠΏΠΈΡˆΡƒΡ‚ ΠΏΡ€Π΅Π΄ΡƒΠΏΡ€Π΅ΠΆΠ΄Π΅Π½ΠΈΠ΅ Π² Π»ΠΎΠ³; `MESH_LEGACY_SUBDOMAIN=0` Π½Π° шлюзС ΠΎΡ‚ΠΊΠ»ΡŽΡ‡Π°Π΅Ρ‚ ΠΈΡ….
350
+ > [!WARNING]
351
+ > Π›ΡŽΠ±ΠΎΠΉ, Ρƒ ΠΊΠΎΠ³ΠΎ Π΅ΡΡ‚ΡŒ ваша MCP-ссылка, ΠΌΠΎΠΆΠ΅Ρ‚ Π²Ρ‹ΠΏΠΎΠ»Π½ΡΡ‚ΡŒ ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ Π½Π° этой машинС. Π₯Ρ€Π°Π½ΠΈΡ‚Π΅ Π΅Ρ‘ ΠΊΠ°ΠΊ ΠΏΠ°Ρ€ΠΎΠ»ΡŒ: Π½Π΅ ΠΏΡƒΠ±Π»ΠΈΠΊΡƒΠΉΡ‚Π΅, Π° ΠΏΡ€ΠΈ ΡƒΡ‚Π΅Ρ‡ΠΊΠ΅ пСрСустановитС ΡƒΠ·Π΅Π», Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΡΠΌΠ΅Π½ΠΈΡ‚ΡŒ Ρ‚ΠΎΠΊΠ΅Π½.
275
352
 
276
353
  ---
277
354
 
278
- ## 🚒 Эксплуатация шлюза
355
+ ## 🚒 Бвой шлюз (self-hosting)
279
356
 
280
- Шлюз β€” ΠΎΠ΄ΠΈΠ½ хост Π½Π° всС ΡƒΠ·Π»Ρ‹. ΠŸΡƒΠ±Π»ΠΈΠΊΡƒΠΉΡ‚Π΅ Π΅Π³ΠΎ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ скриптом, Ρ‡Ρ‚ΠΎΠ±Ρ‹ Ρ€Π°Π±ΠΎΡ‚Π°ΡŽΡ‰ΠΈΠΉ
281
- шлюз, ΠΎΡ‚Π΄Π°Π²Π°Π΅ΠΌΡ‹Π΅ установщики ΠΈ bootstrap-ΠΊΠΎΠ΄ ΡƒΠ·Π»ΠΎΠ² Π½Π΅ Ρ€Π°ΡΡ…ΠΎΠ΄ΠΈΠ»ΠΈΡΡŒ:
357
+ Шлюз β€” ΠΎΠ΄ΠΈΠ½ хост Π½Π° всС ΡƒΠ·Π»Ρ‹. ΠŸΡƒΠ±Π»ΠΈΠΊΡƒΠΉΡ‚Π΅ Π΅Π³ΠΎ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ скриптом, Ρ‡Ρ‚ΠΎΠ±Ρ‹ Ρ€Π°Π±ΠΎΡ‚Π°ΡŽΡ‰ΠΈΠΉ шлюз, ΠΎΡ‚Π΄Π°Π²Π°Π΅ΠΌΡ‹Π΅ установщики ΠΈ bootstrap-ΠΊΠΎΠ΄ ΡƒΠ·Π»ΠΎΠ² Π½Π΅ Ρ€Π°ΡΡ…ΠΎΠ΄ΠΈΠ»ΠΈΡΡŒ:
282
358
 
283
359
  ```bash
284
360
  MESH_GATEWAY_SSH=root@<ip-шлюза> ./deploy_gateway.sh # Π·Π°Π΄Π΅ΠΏΠ»ΠΎΠΈΡ‚ΡŒ
285
361
  MESH_GATEWAY_SSH=root@<ip-шлюза> ./deploy_gateway.sh --dry-run # прСдпросмотр
286
362
  ```
287
- Π‘ΠΊΡ€ΠΈΠΏΡ‚ провСряСт ΠΊΠ°ΠΆΠ΄ΡƒΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ манифСстом sha256 Π½Π° хостС, Π΄Π΅Π»Π°Π΅Ρ‚ бэкап с
288
- ΠΌΠ΅Ρ‚ΠΊΠΎΠΉ Π²Ρ€Π΅ΠΌΠ΅Π½ΠΈ, ставит Ρ„Π°ΠΉΠ»Ρ‹ с Π½ΡƒΠΆΠ½Ρ‹ΠΌ Π²Π»Π°Π΄Π΅Π»ΡŒΡ†Π΅ΠΌ, пСрСзапускаСт слуТбу ΠΈ
289
- Π·Π°Π²Π΅Ρ€ΡˆΠ°Π΅Ρ‚ ΠΏΡƒΠ±Π»ΠΈΡ‡Π½ΠΎΠΉ ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΎΠΉ Π·Π΄ΠΎΡ€ΠΎΠ²ΡŒΡ. ΠŸΡ€ΠΈ нСсовпадСнии Ρ…ΡΡˆΠ΅ΠΉ Π½ΠΈΡ‡Π΅Π³ΠΎ Π½Π΅
290
- устанавливаСтся.
363
+ Π‘ΠΊΡ€ΠΈΠΏΡ‚ провСряСт ΠΊΠ°ΠΆΠ΄ΡƒΡŽ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ манифСстом sha256 Π½Π° хостС, Π΄Π΅Π»Π°Π΅Ρ‚ бэкап с ΠΌΠ΅Ρ‚ΠΊΠΎΠΉ Π²Ρ€Π΅ΠΌΠ΅Π½ΠΈ, ставит Ρ„Π°ΠΉΠ»Ρ‹ с Π½ΡƒΠΆΠ½Ρ‹ΠΌ Π²Π»Π°Π΄Π΅Π»ΡŒΡ†Π΅ΠΌ, пСрСзапускаСт слуТбу ΠΈ Π·Π°Π²Π΅Ρ€ΡˆΠ°Π΅Ρ‚ ΠΏΡƒΠ±Π»ΠΈΡ‡Π½ΠΎΠΉ ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΎΠΉ Π·Π΄ΠΎΡ€ΠΎΠ²ΡŒΡ. ΠŸΡ€ΠΈ нСсовпадСнии Ρ…ΡΡˆΠ΅ΠΉ Π½ΠΈΡ‡Π΅Π³ΠΎ Π½Π΅ устанавливаСтся.
291
364
 
292
- **ΠŸΠΎΠ»ΠΈΡ‚ΠΈΠΊΠ° TLS-сСртификата: Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΎΠ±Ρ‰ΠΈΠΉ Π΄ΠΎΠΌΠ΅Π½, Π±Π΅Π· SAN Π½Π° устройства.**
293
- ИмСна ΡƒΠ·Π»ΠΎΠ² Π² сСртификат Π½Π΅ Π΄ΠΎΠ±Π°Π²Π»ΡΡŽΡ‚ΡΡ Π½ΠΈΠΊΠΎΠ³Π΄Π° β€” ΡƒΠ·Π΅Π» выбираСтся `?user=`:
365
+ **ΠŸΠΎΠ»ΠΈΡ‚ΠΈΠΊΠ° TLS-сСртификата: Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΎΠ±Ρ‰ΠΈΠΉ Π΄ΠΎΠΌΠ΅Π½, Π±Π΅Π· SAN Π½Π° устройства.** ИмСна ΡƒΠ·Π»ΠΎΠ² Π² сСртификат Π½Π΅ Π΄ΠΎΠ±Π°Π²Π»ΡΡŽΡ‚ΡΡ Π½ΠΈΠΊΠΎΠ³Π΄Π° β€” ΡƒΠ·Π΅Π» выбираСтся `?user=`:
294
366
 
295
367
  ```bash
296
368
  sudo certbot certificates | grep -A1 'Certificate Name: smart-server.online'
297
369
  sudo certbot renew --dry-run --cert-name smart-server.online # ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° продлСния
298
370
  ```
299
- Π‘Ρ‚Π°Ρ€Ρ‹Π΅ SAN'Ρ‹ устройств ΠΌΠΎΠ³ΡƒΡ‚ ΠΎΡΡ‚Π°Ρ‚ΡŒΡΡ с ΠΏΡ€Π΅ΠΆΠ½Π΅Π³ΠΎ ΠΊΠΎΠ½Ρ‚Ρ€Π°ΠΊΡ‚Π°: ΠΎΠ½ΠΈ Π±Π΅Π·Π²Ρ€Π΅Π΄Π½Ρ‹ ΠΈ
300
- ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°ΡŽΡ‚ ΡΠΎΠ²ΠΌΠ΅ΡΡ‚ΠΈΠΌΠΎΡΡ‚ΡŒ старых ссылок. Π£Π±Ρ€Π°Ρ‚ΡŒ ΠΈΡ… ΠΌΠΎΠΆΠ½ΠΎ ΠΏΡ€ΠΈ ΡΠ»Π΅Π΄ΡƒΡŽΡ‰Π΅ΠΌ
301
- ΠΏΡ€ΠΎΠ΄Π»Π΅Π½ΠΈΠΈ, ΠΊΠΎΠ³Π΄Π° субдомСнными ссылками Π½ΠΈΠΊΡ‚ΠΎ Π½Π΅ ΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ.
371
+ Π‘Ρ‚Π°Ρ€Ρ‹Π΅ SAN'Ρ‹ устройств ΠΌΠΎΠ³ΡƒΡ‚ ΠΎΡΡ‚Π°Ρ‚ΡŒΡΡ с ΠΏΡ€Π΅ΠΆΠ½Π΅Π³ΠΎ ΠΊΠΎΠ½Ρ‚Ρ€Π°ΠΊΡ‚Π°: ΠΎΠ½ΠΈ Π±Π΅Π·Π²Ρ€Π΅Π΄Π½Ρ‹ ΠΈ ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°ΡŽΡ‚ ΡΠΎΠ²ΠΌΠ΅ΡΡ‚ΠΈΠΌΠΎΡΡ‚ΡŒ старых ссылок. Π£Π±Ρ€Π°Ρ‚ΡŒ ΠΈΡ… ΠΌΠΎΠΆΠ½ΠΎ ΠΏΡ€ΠΈ ΡΠ»Π΅Π΄ΡƒΡŽΡ‰Π΅ΠΌ ΠΏΡ€ΠΎΠ΄Π»Π΅Π½ΠΈΠΈ, ΠΊΠΎΠ³Π΄Π° субдомСнными ссылками Π½ΠΈΠΊΡ‚ΠΎ Π½Π΅ ΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ.
302
372
 
303
373
  ---
304
374
 
305
375
  ## πŸ“„ ЛицСнзия
306
376
 
307
- РаспространяСтся ΠΏΠΎΠ΄ свободной Π»ΠΈΡ†Π΅Π½Π·ΠΈΠ΅ΠΉ **MIT**. ΠŸΠΎΠ΄Ρ€ΠΎΠ±Π½ΠΎΡΡ‚ΠΈ Π² Ρ„Π°ΠΉΠ»Π΅ [LICENSE](LICENSE).
377
+ **MIT** β€” подробности Π² Ρ„Π°ΠΉΠ»Π΅ [LICENSE](LICENSE).
package/core/agent.py CHANGED
@@ -7,9 +7,27 @@ so an unmodified gateway keeps working.
7
7
 
8
8
  Configuration (read once at import/startup)::
9
9
 
10
+ MESH_GATEWAY shared gateway domain
11
+ MESH_USER node name
12
+ MESH_TOKEN node token
13
+ MESH_CONFIG_FILE KEY=VALUE file used for any of the three above that is not
14
+ set in the environment (default:
15
+ ~/.config/antigravity-mesh/agent.env). This keeps the token
16
+ out of launchers such as the Windows Startup script.
10
17
  MESH_WORKSPACE base directory for relative paths (default: cwd)
11
18
  MESH_READ_ONLY "1"/"true" -> mutating tools refuse to run
12
19
  MESH_WRITE_ROOTS pathsep-separated list of roots allowed for writes
20
+
21
+ Resilience contract:
22
+
23
+ * every link failure (DNS, TLS, reset, half-open socket after sleep) is retried
24
+ forever with capped exponential backoff plus jitter;
25
+ * tool calls run concurrently, so a slow command never delays another call or
26
+ the keepalive pings;
27
+ * only one agent per node name runs on a machine. The gateway keeps exactly one
28
+ tunnel per node and a new tunnel supersedes the old one, so two agents would
29
+ knock each other off in an endless loop and fail every in-flight call. A
30
+ second instance therefore waits for the lock instead of connecting.
13
31
  """
14
32
 
15
33
  import asyncio
@@ -17,6 +35,8 @@ import json
17
35
  import logging
18
36
  import os
19
37
  import random
38
+ import sys
39
+ import time
20
40
 
21
41
  import websockets
22
42
 
@@ -25,9 +45,49 @@ from core import mcp_tools
25
45
  logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
26
46
  logger = logging.getLogger("agy-agent")
27
47
 
28
- GATEWAY_HOST = os.environ.get("MESH_GATEWAY", "smart-server.online")
29
- USER = os.environ.get("MESH_USER", "levra7")
30
- TOKEN = os.environ.get("MESH_TOKEN", "")
48
+ CONFIG_DIR = os.path.join(os.path.expanduser("~"), ".config", "antigravity-mesh")
49
+ DEFAULT_CONFIG_FILE = os.path.join(CONFIG_DIR, "agent.env")
50
+
51
+ #: Backoff cap for ordinary link failures.
52
+ MAX_BACKOFF = 15.0
53
+ #: Wait after the gateway rejects the token: retrying fast cannot help, but the
54
+ #: node must recover by itself once it is re-registered.
55
+ UNAUTHORIZED_BACKOFF = 60.0
56
+
57
+
58
+ def read_env_file(path: str) -> dict:
59
+ """Parse a KEY=VALUE file; tolerates a UTF-8 BOM (Windows PowerShell 5 writes one)."""
60
+ values = {}
61
+ try:
62
+ with open(path, "r", encoding="utf-8-sig") as handle:
63
+ for line in handle:
64
+ line = line.strip()
65
+ if not line or line.startswith("#") or "=" not in line:
66
+ continue
67
+ key, _, value = line.partition("=")
68
+ values[key.strip()] = value.strip().strip('"').strip("'")
69
+ except OSError:
70
+ pass
71
+ return values
72
+
73
+
74
+ def _load_settings() -> dict:
75
+ file_values = read_env_file(os.environ.get("MESH_CONFIG_FILE") or DEFAULT_CONFIG_FILE)
76
+
77
+ def pick(key: str, default: str) -> str:
78
+ return os.environ.get(key) or file_values.get(key) or default
79
+
80
+ return {
81
+ "gateway": pick("MESH_GATEWAY", "smart-server.online"),
82
+ "user": pick("MESH_USER", "levra7"),
83
+ "token": pick("MESH_TOKEN", ""),
84
+ }
85
+
86
+
87
+ _SETTINGS = _load_settings()
88
+ GATEWAY_HOST = _SETTINGS["gateway"]
89
+ USER = _SETTINGS["user"]
90
+ TOKEN = _SETTINGS["token"]
31
91
 
32
92
 
33
93
  def _env_flag(name: str) -> bool:
@@ -53,12 +113,113 @@ def configure_from_env() -> None:
53
113
  configure_from_env()
54
114
 
55
115
 
116
+ # ---------------------------------------------------------------------------
117
+ # Single instance per node name
118
+ # ---------------------------------------------------------------------------
119
+
120
+ class InstanceLock:
121
+ """Non-blocking, OS-released file lock (fcntl on POSIX, msvcrt on Windows).
122
+
123
+ The OS drops the lock when the process dies, so a crash never leaves a stale
124
+ lock behind.
125
+ """
126
+
127
+ def __init__(self, path: str):
128
+ self.path = path
129
+ self._fh = None
130
+
131
+ def try_acquire(self) -> bool:
132
+ os.makedirs(os.path.dirname(self.path), exist_ok=True)
133
+ fh = open(self.path, "a+")
134
+ try:
135
+ if os.name == "nt":
136
+ import msvcrt
137
+ fh.seek(0)
138
+ msvcrt.locking(fh.fileno(), msvcrt.LK_NBLCK, 1)
139
+ else:
140
+ import fcntl
141
+ fcntl.flock(fh.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
142
+ except OSError:
143
+ fh.close()
144
+ return False
145
+ fh.seek(0)
146
+ fh.truncate()
147
+ fh.write(str(os.getpid()))
148
+ fh.flush()
149
+ self._fh = fh
150
+ return True
151
+
152
+
153
+ def wait_for_instance_lock(user: str) -> InstanceLock:
154
+ """Block until this process is the only agent for ``user`` on the machine."""
155
+ safe = "".join(c for c in user if c.isalnum() or c in "-_") or "node"
156
+ lock = InstanceLock(os.path.join(CONFIG_DIR, "agent-%s.lock" % safe))
157
+ warned = False
158
+ while not lock.try_acquire():
159
+ if not warned:
160
+ logger.warning("Another agent for node '%s' is already running on this machine; "
161
+ "waiting for it to exit instead of fighting over the tunnel.", user)
162
+ warned = True
163
+ time.sleep(10)
164
+ return lock
165
+
166
+
167
+ # ---------------------------------------------------------------------------
168
+ # Tunnel
169
+ # ---------------------------------------------------------------------------
170
+
56
171
  async def handle_tool_call(name: str, args: dict) -> dict:
57
172
  """Forward one tool call to the shared implementation in a worker thread."""
58
173
  logger.info("Executing %s", name)
59
174
  return await asyncio.to_thread(mcp_tools.call_tool, name, args or {})
60
175
 
61
176
 
177
+ async def _serve_call(ws, req_id, params: dict) -> None:
178
+ tool_name = params.get("name")
179
+ tool_args = params.get("arguments", {})
180
+ try:
181
+ res = await handle_tool_call(tool_name, tool_args)
182
+ except Exception as tool_exc:
183
+ # A tool must never take the tunnel down with it.
184
+ logger.warning(f"Tool {tool_name} failed: {tool_exc}")
185
+ res = {"error": f"{type(tool_exc).__name__}: {tool_exc}"}
186
+ try:
187
+ await ws.send(json.dumps({"id": req_id, "result": res}, ensure_ascii=False))
188
+ except Exception as send_exc:
189
+ # The link dropped while the tool ran; the gateway already failed the
190
+ # call, and the reconnect loop restores the tunnel.
191
+ logger.warning(f"Could not deliver result of {tool_name}: {send_exc}")
192
+
193
+
194
+ def _is_unauthorized(exc: BaseException) -> bool:
195
+ """True when the gateway rejected the node's credentials.
196
+
197
+ The gateway closes with 4001 before accepting the websocket; Starlette turns
198
+ that into an HTTP 403 handshake response, so depending on timing the client
199
+ sees either the close code or the HTTP status. Handles websockets' legacy
200
+ and new exception shapes.
201
+ """
202
+ for attr in ("rcvd", "received"):
203
+ frame = getattr(exc, attr, None)
204
+ if frame is not None and getattr(frame, "code", None) == 4001:
205
+ return True
206
+ if getattr(exc, "code", None) == 4001:
207
+ return True
208
+ status = getattr(exc, "status_code", None) # legacy InvalidStatusCode
209
+ response = getattr(exc, "response", None) # websockets >= 14 InvalidStatus
210
+ if status is None and response is not None:
211
+ status = getattr(response, "status_code", None)
212
+ return status in (401, 403)
213
+
214
+
215
+ def tunnel_uri() -> str:
216
+ """Canonical tunnel URL: one shared domain, node selected by ?user=."""
217
+ base = GATEWAY_HOST.rstrip("/")
218
+ if not base.startswith(("ws://", "wss://")):
219
+ base = "wss://" + base
220
+ return f"{base}/ws/tunnel?user={USER}&token={TOKEN}"
221
+
222
+
62
223
  async def run_agent():
63
224
  """Stay connected to the gateway, healing every kind of link failure.
64
225
 
@@ -67,13 +228,18 @@ async def run_agent():
67
228
  hammering loop when the network is genuinely down. The backoff resets after a
68
229
  healthy connection, so a long-lived session is not penalised.
69
230
  """
70
- uri = f"wss://{GATEWAY_HOST}/ws/tunnel?user={USER}&token={TOKEN}"
71
- logger.info(f"Connecting to Gateway {uri}...")
231
+ if not TOKEN:
232
+ logger.error("MESH_TOKEN is empty (env and %s): the gateway will reject this node. "
233
+ "Re-run the installer.", os.environ.get("MESH_CONFIG_FILE") or DEFAULT_CONFIG_FILE)
234
+ uri = tunnel_uri()
235
+ logger.info(f"Connecting to Gateway {uri.split('&token=')[0]} ...")
72
236
  backoff = 1.0
237
+ in_flight = set()
73
238
  while True:
74
239
  try:
75
240
  async with websockets.connect(uri, ping_interval=10, ping_timeout=10,
76
- close_timeout=5) as ws:
241
+ close_timeout=5, open_timeout=15,
242
+ max_size=None) as ws:
77
243
  logger.info(f"Connected to Mesh Gateway as '{USER}'!")
78
244
  backoff = 1.0 # healthy again
79
245
  async for raw_msg in ws:
@@ -81,26 +247,36 @@ async def run_agent():
81
247
  data = json.loads(raw_msg)
82
248
  except Exception:
83
249
  continue
84
- req_id = data.get("id")
85
- method = data.get("method")
86
- params = data.get("params", {})
87
- if method == "tools/call":
88
- tool_name = params.get("name")
89
- tool_args = params.get("arguments", {})
90
- try:
91
- res = await handle_tool_call(tool_name, tool_args)
92
- except Exception as tool_exc:
93
- # A tool must never take the tunnel down with it.
94
- logger.warning(f"Tool {tool_name} failed: {tool_exc}")
95
- res = {"error": f"{type(tool_exc).__name__}: {tool_exc}"}
96
- resp = {"id": req_id, "result": res}
97
- await ws.send(json.dumps(resp, ensure_ascii=False))
250
+ if data.get("method") == "tools/call":
251
+ # Each call runs on its own: Gemini issues parallel calls,
252
+ # and serialising them pushed later ones past the
253
+ # gateway's 28 s limit.
254
+ task = asyncio.create_task(_serve_call(ws, data.get("id"), data.get("params", {})))
255
+ in_flight.add(task)
256
+ task.add_done_callback(in_flight.discard)
257
+ # A clean close (e.g. gateway restart) still needs a reconnect.
258
+ raise ConnectionError("tunnel closed by gateway")
259
+ except asyncio.CancelledError:
260
+ raise
98
261
  except Exception as e:
99
- delay = min(backoff, 15.0) + random.uniform(0, 0.5)
100
- logger.warning(f"Connection lost: {e}. Reconnecting in {delay:.1f}s...")
262
+ if _is_unauthorized(e):
263
+ delay = UNAUTHORIZED_BACKOFF
264
+ logger.error("Gateway rejected node '%s' (4001 Unauthorized): the token is wrong or the "
265
+ "node was removed. Re-run the installer. Retrying in %.0fs.", USER, delay)
266
+ else:
267
+ delay = min(backoff, MAX_BACKOFF) + random.uniform(0, 0.5)
268
+ logger.warning(f"Connection lost: {e}. Reconnecting in {delay:.1f}s...")
269
+ backoff = min(backoff * 2, MAX_BACKOFF)
101
270
  await asyncio.sleep(delay)
102
- backoff = min(backoff * 2, 15.0)
271
+
272
+
273
+ def main() -> None:
274
+ _lock = wait_for_instance_lock(USER) # held for the life of the process
275
+ try:
276
+ asyncio.run(run_agent())
277
+ except KeyboardInterrupt:
278
+ sys.exit(0)
103
279
 
104
280
 
105
281
  if __name__ == "__main__":
106
- asyncio.run(run_agent())
282
+ main()
package/core/mcp_tools.py CHANGED
@@ -52,6 +52,64 @@ except Exception: # pragma: no cover
52
52
  _core_get_host_vitals = None # type: ignore
53
53
 
54
54
 
55
+ # ---------------------------------------------------------------------------
56
+ # Platform / command shell
57
+ # ---------------------------------------------------------------------------
58
+
59
+ _IS_WINDOWS = os.name == "nt"
60
+
61
+ #: Hide the console window of every child process on Windows: the node runs
62
+ #: hidden, and a flashing window per command would be the only thing the user sees.
63
+ _POPEN_FLAGS = 0
64
+ if _IS_WINDOWS: # pragma: no cover - Windows only
65
+ _POPEN_FLAGS = getattr(subprocess, "CREATE_NO_WINDOW", 0x08000000) | getattr(
66
+ subprocess, "CREATE_NEW_PROCESS_GROUP", 0x00000200)
67
+
68
+ _SHELL_CACHE: Optional[Tuple[str, List[str]]] = None
69
+
70
+
71
+ def _git_bash_windows() -> Optional[str]:
72
+ """Git for Windows' bash.exe, never System32\\bash.exe.
73
+
74
+ On Windows "bash" on PATH is normally the WSL launcher: it runs the command
75
+ inside a Linux VM (wrong files, wrong processes) or fails without WSL.
76
+ """
77
+ roots = [os.environ.get(k) for k in ("ProgramFiles", "ProgramW6432", "ProgramFiles(x86)", "LOCALAPPDATA")]
78
+ candidates = []
79
+ for root in filter(None, roots):
80
+ candidates.append(os.path.join(root, "Git", "bin", "bash.exe"))
81
+ candidates.append(os.path.join(root, "Programs", "Git", "bin", "bash.exe"))
82
+ git = shutil.which("git")
83
+ if git: # ...\Git\cmd\git.exe -> ...\Git\bin\bash.exe
84
+ candidates.append(os.path.join(os.path.dirname(os.path.dirname(git)), "bin", "bash.exe"))
85
+ for path in candidates:
86
+ if os.path.isfile(path):
87
+ return path
88
+ return None
89
+
90
+
91
+ def command_shell() -> Tuple[str, List[str]]:
92
+ """(name, argv prefix) of the shell that runs bash_exec / run_job commands."""
93
+ global _SHELL_CACHE
94
+ if _SHELL_CACHE is None:
95
+ if _IS_WINDOWS:
96
+ bash = _git_bash_windows()
97
+ if bash:
98
+ _SHELL_CACHE = ("git-bash", [bash, "-c"])
99
+ else:
100
+ ps = shutil.which("pwsh") or shutil.which("powershell") or "powershell"
101
+ _SHELL_CACHE = ("powershell", [ps, "-NoLogo", "-NoProfile", "-NonInteractive",
102
+ "-ExecutionPolicy", "Bypass", "-Command"])
103
+ else:
104
+ bash = shutil.which("bash")
105
+ _SHELL_CACHE = ("bash", [bash, "-c"]) if bash else ("sh", ["/bin/sh", "-c"])
106
+ return _SHELL_CACHE
107
+
108
+
109
+ def _shell_argv(command: str) -> List[str]:
110
+ return command_shell()[1] + [command]
111
+
112
+
55
113
  # ---------------------------------------------------------------------------
56
114
  # Configuration / module state
57
115
  # ---------------------------------------------------------------------------
@@ -472,6 +530,9 @@ def _tool_system_info(args: Dict[str, Any]) -> Dict[str, Any]:
472
530
  "desktop": os.environ.get("XDG_CURRENT_DESKTOP") or os.environ.get("DESKTOP_SESSION") or "",
473
531
  "session_type": os.environ.get("XDG_SESSION_TYPE", ""),
474
532
  "shell": os.environ.get("SHELL", ""),
533
+ # The shell bash_exec/run_job really use: on a Windows node without Git
534
+ # Bash this is PowerShell, and commands must be written for it.
535
+ "command_shell": command_shell()[0],
475
536
  "disks": run("df -h --output=target,size,used,avail,pcent 2>/dev/null | head -8"),
476
537
  "memory": run("free -h 2>/dev/null | head -3"),
477
538
  "load": run("uptime"),
@@ -573,12 +634,14 @@ def _run_bash(command: str, timeout: int) -> Tuple[str, str, int, float]:
573
634
  started = time.time()
574
635
  try:
575
636
  proc = subprocess.run(
576
- ["bash", "-c", command],
637
+ _shell_argv(command),
577
638
  cwd=_WORKSPACE if os.path.isdir(_WORKSPACE) else None,
639
+ stdin=subprocess.DEVNULL,
578
640
  capture_output=True,
579
641
  text=True,
580
642
  timeout=timeout,
581
643
  errors="replace",
644
+ creationflags=_POPEN_FLAGS,
582
645
  )
583
646
  return proc.stdout or "", proc.stderr or "", proc.returncode, round(time.time() - started, 3)
584
647
  except subprocess.TimeoutExpired as exc:
@@ -1024,6 +1087,8 @@ def _pid_alive(pid: Any, start_time: Any = None) -> bool:
1024
1087
  return False
1025
1088
  except (TypeError, ValueError):
1026
1089
  pass
1090
+ if _IS_WINDOWS:
1091
+ return _pid_alive_windows(pid_int)
1027
1092
  try:
1028
1093
  os.kill(pid_int, 0)
1029
1094
  except ProcessLookupError:
@@ -1035,6 +1100,32 @@ def _pid_alive(pid: Any, start_time: Any = None) -> bool:
1035
1100
  return True
1036
1101
 
1037
1102
 
1103
+ def _pid_alive_windows(pid: int) -> bool:
1104
+ """Liveness probe for Windows.
1105
+
1106
+ os.kill(pid, 0) must never be used there: any signal other than
1107
+ CTRL_C/CTRL_BREAK goes to TerminateProcess, so the "probe" would kill the
1108
+ very job it checks.
1109
+ """
1110
+ try:
1111
+ import ctypes
1112
+ kernel32 = ctypes.windll.kernel32 # type: ignore[attr-defined]
1113
+ PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
1114
+ STILL_ACTIVE = 259
1115
+ handle = kernel32.OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, False, int(pid))
1116
+ if not handle:
1117
+ return False
1118
+ try:
1119
+ code = ctypes.c_ulong()
1120
+ if not kernel32.GetExitCodeProcess(handle, ctypes.byref(code)):
1121
+ return False
1122
+ return code.value == STILL_ACTIVE
1123
+ finally:
1124
+ kernel32.CloseHandle(handle)
1125
+ except Exception:
1126
+ return False
1127
+
1128
+
1038
1129
  def _duration_from_meta(meta: Dict[str, Any]) -> Optional[float]:
1039
1130
  started = meta.get("started_at")
1040
1131
  finished = meta.get("finished_at")
@@ -1209,13 +1300,14 @@ def _tool_run_job(args: Dict[str, Any]) -> Dict[str, Any]:
1209
1300
 
1210
1301
  try:
1211
1302
  proc = subprocess.Popen(
1212
- ["bash", "-c", command],
1303
+ _shell_argv(command),
1213
1304
  cwd=cwd,
1214
1305
  stdin=subprocess.DEVNULL,
1215
1306
  stdout=subprocess.PIPE,
1216
1307
  stderr=subprocess.PIPE,
1217
1308
  bufsize=0,
1218
- start_new_session=True,
1309
+ start_new_session=not _IS_WINDOWS,
1310
+ creationflags=_POPEN_FLAGS,
1219
1311
  )
1220
1312
  except Exception as exc:
1221
1313
  return {"error": "failed to start job: %s" % exc}
@@ -1338,13 +1430,25 @@ def _tool_job_output(args: Dict[str, Any]) -> Dict[str, Any]:
1338
1430
  return {"error": "job_output failed: %s" % exc}
1339
1431
 
1340
1432
 
1433
+ # Windows' signal module has no SIGKILL/SIGHUP/SIGQUIT: a literal dict of them
1434
+ # raised AttributeError at import, so the whole node failed to start there.
1341
1435
  _SIGNALS = {
1342
- "TERM": _signal.SIGTERM,
1343
- "KILL": _signal.SIGKILL,
1344
- "INT": _signal.SIGINT,
1345
- "HUP": _signal.SIGHUP,
1346
- "QUIT": _signal.SIGQUIT,
1436
+ name: getattr(_signal, "SIG" + name)
1437
+ for name in ("TERM", "KILL", "INT", "HUP", "QUIT")
1438
+ if hasattr(_signal, "SIG" + name)
1347
1439
  }
1440
+ if _IS_WINDOWS:
1441
+ # Every name is accepted on Windows; the job is ended with a tree kill.
1442
+ for _name in ("TERM", "KILL", "INT", "HUP", "QUIT"):
1443
+ _SIGNALS.setdefault(_name, _signal.SIGTERM)
1444
+
1445
+
1446
+ def _kill_tree_windows(pid: int) -> None:
1447
+ """End a job and its children on Windows (there are no process groups to signal)."""
1448
+ proc = subprocess.run(["taskkill", "/T", "/F", "/PID", str(int(pid))],
1449
+ capture_output=True, text=True, timeout=15)
1450
+ if proc.returncode != 0:
1451
+ raise OSError((proc.stderr or proc.stdout or "taskkill failed").strip())
1348
1452
 
1349
1453
 
1350
1454
  def _tool_job_kill(args: Dict[str, Any]) -> Dict[str, Any]:
@@ -1373,15 +1477,22 @@ def _tool_job_kill(args: Dict[str, Any]) -> Dict[str, Any]:
1373
1477
 
1374
1478
  pid = meta.get("pid")
1375
1479
  delivered = False
1376
- try:
1377
- os.killpg(os.getpgid(int(pid)), sig)
1378
- delivered = True
1379
- except Exception:
1480
+ if _IS_WINDOWS:
1380
1481
  try:
1381
- os.kill(int(pid), sig)
1482
+ _kill_tree_windows(int(pid))
1382
1483
  delivered = True
1383
1484
  except Exception as exc:
1384
- return {"error": "failed to signal job %s: %s" % (job_id, exc)}
1485
+ return {"error": "failed to stop job %s: %s" % (job_id, exc)}
1486
+ else:
1487
+ try:
1488
+ os.killpg(os.getpgid(int(pid)), sig)
1489
+ delivered = True
1490
+ except Exception:
1491
+ try:
1492
+ os.kill(int(pid), sig)
1493
+ delivered = True
1494
+ except Exception as exc:
1495
+ return {"error": "failed to signal job %s: %s" % (job_id, exc)}
1385
1496
 
1386
1497
  if delivered:
1387
1498
  _update_meta(job_id, status="killed", finished_at=_now_iso(), duration=_duration_from_meta(meta))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gemini-computer-use",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
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": {