gemini-computer-use 0.2.4 → 0.4.1

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.
Files changed (41) hide show
  1. package/README.md +733 -16
  2. package/bin/cli.js +112 -17
  3. package/core/__pycache__/__init__.cpython-313.pyc +0 -0
  4. package/core/__pycache__/agent.cpython-313.pyc +0 -0
  5. package/core/__pycache__/code_cache.cpython-313.pyc +0 -0
  6. package/core/__pycache__/mcp_tools.cpython-313.pyc +0 -0
  7. package/core/__pycache__/schemas.cpython-313.pyc +0 -0
  8. package/core/__pycache__/server.cpython-313.pyc +0 -0
  9. package/core/__pycache__/vitals.cpython-313.pyc +0 -0
  10. package/core/__pycache__/web_share.cpython-313.pyc +0 -0
  11. package/core/agent.py +503 -18
  12. package/core/agent_harness.py +9 -2
  13. package/core/code_cache.py +15 -3
  14. package/core/device.py +2400 -0
  15. package/core/domain.py +303 -0
  16. package/core/mcp_tools.py +1840 -112
  17. package/core/policies.py +35 -5
  18. package/core/server.py +78 -32
  19. package/core/termux.py +916 -0
  20. package/core/updater.py +1713 -0
  21. package/core/version.py +31 -0
  22. package/core/vitals.py +66 -2
  23. package/core/web_share.py +814 -0
  24. package/gateway.py +1469 -98
  25. package/install-gui.cmd +40 -0
  26. package/install-gui.ps1 +1882 -0
  27. package/install-gui.strings.json +211 -0
  28. package/install.ps1 +960 -92
  29. package/install.sh +1214 -81
  30. package/ops/doctor.ps1 +735 -0
  31. package/ops/mesh-domain.sh +86 -0
  32. package/ops/nginx/README.md +77 -0
  33. package/ops/nginx/antigravity-mesh-mcp.conf +172 -0
  34. package/ops/nginx/mesh-domain.vhost.template +58 -0
  35. package/ops/nginx/render-domain.sh +123 -0
  36. package/ops/update.ps1 +373 -0
  37. package/ops/windows/README.md +298 -0
  38. package/ops/windows/agent-watchdog.ps1 +575 -0
  39. package/ops/windows/run-hidden.vbs +64 -0
  40. package/package.json +6 -2
  41. package/skills/orchestrator.md +12 -0
package/README.md CHANGED
@@ -41,6 +41,7 @@ Just ask Gemini in plain language, from anywhere:
41
41
  - *"Fix the typo in config.yaml and restart the service"*
42
42
  - *"Start a full backup in the background and tell me when it's done"*
43
43
  - *"How much free disk space is left on my laptop?"*
44
+ - *"Turn on the phone's torch, and tell me its battery level and signal"*
44
45
 
45
46
  No SSH client, no terminal on your phone, no VPN — only a chat.
46
47
 
@@ -51,9 +52,9 @@ No SSH client, no terminal on your phone, no VPN — only a chat.
51
52
  | | |
52
53
  | :--- | :--- |
53
54
  | 📱 **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
+ | 🖥️ **Any PC** | Linux, macOS, Windows — desktops, laptops, VPS, containers. **Android phones and tablets** run the same node inside Termux. |
55
56
  | 🌐 **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
+ | ⚡ **One-line install** | Detects OS, architecture and device type; installs autostart (`systemd` / `launchd` / Windows task / runit + Termux:Boot on Android); copies your connection link to the clipboard. |
57
58
  | 🛡️ **Token-gated** | Every call needs a personal 128-bit token (`?token=…` or `Authorization: Bearer`). Everything else gets HTTP 401. |
58
59
  | 🔁 **Many machines, one gateway** | Each node is addressed by `?user=<node-name>` on one shared domain. |
59
60
  | 🆓 **Free & open source** | MIT license, works with the free Gemini tier. |
@@ -62,14 +63,142 @@ No SSH client, no terminal on your phone, no VPN — only a chat.
62
63
 
63
64
  ## 🛠️ What Gemini can do on your machine (MCP tools)
64
65
 
65
- | Category | Tools |
66
+ **26 tools.** Every one of them runs on your machine under your own user account — the gateway only carries the calls.
67
+
68
+ ### Which calls ask for your confirmation
69
+
70
+ Gemini Spark decides whether to stop and ask *"confirm this action?"* from the **MCP annotation hints** each tool advertises (`readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`). A tool advertised with no hints at all is treated as destructive by the spec's defaults — that is why a server that declares none gets a confirmation prompt on *every single call*. Here the hints are explicit:
71
+
72
+ | Class | Tools | Confirmation |
73
+ | :--- | :--- | :--- |
74
+ | Read-only | `mesh_status`, `system_info`, `system_vitals`, `get_orchestration_skill`, `list_dir`, `read_file`, `grep_search`, `glob_find`, `job_output`, `job_list`, `share_list`, `device_info` | never asked |
75
+ | Local work — commands, builds, file writes, jobs, shares, device actions | `bash_exec`, `run_job`, `write_file`, `edit_file`, `job_kill`, `share_file`, `serve_dir`, `device_control` | not asked |
76
+ | **Sensitive — observes the physical world or a person's private data** | `device_capture` (camera, microphone, location, fingerprint, USB, infrared), `device_messages` (SMS, call log, contacts, calls) | **asked first** |
77
+ | **Confirmed — install/delete, `sudo`, system paths** | `system_change` (installs/removes software, deletes data, runs `sudo`, edits system paths by shell), `system_write` (writes file content into a system path), `mesh_update` (installs a release), `unshare` (revokes and deletes the published copy) | **asked first** |
78
+
79
+ `device_capture` and `device_messages` delete nothing, yet they are advertised with `destructiveHint`. That hint is the only one clients such as Gemini Spark reliably turn into a *"confirm this action?"* prompt, and a camera, a microphone or an SMS list that fires silently is worse than an honest over-classification. They were put in that class deliberately. `device_control` stays ordinary local work for the same reason: marking a torch or a vibrate as destructive would only train you to click through the prompts that matter.
80
+
81
+ Four classes are gated, and the gateway — not the hint — enforces them, because a hint is static per tool and cannot tell `ls` from `apt install`:
82
+
83
+ 1. **Installing or removing software** — package managers, uninstallers, `msiexec`, `dpkg`/`rpm`, `curl … | bash`, `iwr … | iex`.
84
+ 2. **Deleting data** — `rm`, `Remove-Item`, `del`, `rd`, `shred`, `dd`, `mkfs`, `git clean -fd`, `docker rm`/`prune`, `truncate -s 0`.
85
+ 3. **Running as root** — `sudo`, `doas`, `pkexec`, `runas`, `Start-Process -Verb RunAs`.
86
+ 4. **System paths** — `/etc`, `/usr`, `/boot`, `/var/lib`, `/opt`, `/dev`, `C:\Windows`, `C:\Program Files`, `ProgramData`, the registry. Reads of those paths (`cat /etc/os-release`, `systemctl status`) are *not* gated, and neither is ordinary work in your own directories.
87
+
88
+ `bash_exec`/`run_job` refuse such commands and tell the model to re-issue them as `system_change`; `write_file`/`edit_file` refuse a system path and point at `system_write` (which takes the exact content, so there is no shell quoting to get wrong). The gate is moved, not removed — inspection, builds, tests, config edits in your projects and the like still run without a dialog.
89
+
90
+ The tool surface and the confirmation policy live in [gateway.py](gateway.py) (annotations, `INSTALL_COMMAND_PATTERNS` / `DELETE_COMMAND_PATTERNS`, `SYSTEM_PATH_RE`, `classify_command()`) and in [core/mcp_tools.py](core/mcp_tools.py) (`TOOL_ANNOTATIONS` for the node's own surface, which also carries the device branch's four tools); tests compare the two surfaces and pin the classifier, so a gated command cannot slip through.
91
+
92
+ > [!NOTE]
93
+ > The classifier matches a verb at the start of a command segment (after `sudo`/`env`/`timeout`/`powershell -Command` prefixes), so `echo "rm -rf /"` or `grep rm notes.txt` still run, while `curl … | bash` counts as an install. It is a UX gate, not a sandbox: for hard guarantees independent of any prompt use the node's own switches — `MESH_READ_ONLY=1` and `MESH_WRITE_ROOTS` ([core/agent.py](core/agent.py)).
94
+
95
+ ### Status and host information
96
+
97
+ | Tool | What it does |
98
+ | :--- | :--- |
99
+ | `mesh_status()` | Confirms the node is reachable, with live evidence from the host itself: what kind of machine answered (`device_class`, `scenario`) and its `battery_percent`. Call it first if the machine looks offline. |
100
+ | `system_info()` | One-call host summary: OS, desktop, user, home, disks, memory, load, top processes, the current wallpaper, `command_shell` — the shell `bash_exec` will actually use — and the device blocks: battery and charging, network and signal, language, time and timezone, CPU, RAM, storage, cameras, microphones and sensors. |
101
+ | `system_vitals()` | CPU, RAM and disk metrics, plus the battery block and the thermal sensors the platform exposes. |
102
+
103
+ ### Shell
104
+
105
+ | Tool | What it does |
66
106
  | :--- | :--- |
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 |
107
+ | `bash_exec(command, timeout_sec, max_chars, cursor)` | Runs a shell command. The shell matches the **host**, not the tool's name — `bash` on Linux/macOS, PowerShell or `cmd.exe` on Windows; check `command_shell` from `system_info()` first. Output is paginated: when it is cut, call again with `cursor=next_cursor`, nothing is dropped; output above 2 MB is spooled to a file returned in `saved_to`. `timeout_sec` is 1–120 (default 25). Installs/removals, `sudo` and system paths are **refused here** and have to go through `system_change`. |
108
+ | `system_change(command, timeout_sec, background, cwd, max_chars, cursor)` | Installs, removes or deletes on the host, runs privileged commands and edits system paths — the confirmed twin of `bash_exec`, and the only tool allowed to run `apt`/`dnf`/`pacman`/`pip`/`npm`/`winget`/`msiexec`/`rm`/`Remove-Item`/`sudo` or disk tools. It is advertised as destructive, so Gemini asks you first. `background=true` runs it as a job (for long installs) and returns a `job_id` for `job_output`; `cwd` applies to that mode. |
109
+
110
+ ### Files
111
+
112
+ | Tool | What it does |
113
+ | :--- | :--- |
114
+ | `list_dir(path)` | Lists files and directories (workspace by default). |
115
+ | `read_file(path, start_line, end_line, max_chars, cursor)` | Reads a text file with line numbers, optionally restricted to a line range. Paginated like `bash_exec`. |
116
+ | `write_file(path, content, create_dirs, mode)` | Atomically creates or overwrites a file (temp file + `os.replace`). `mode` is POSIX-only: on Windows it is not honoured, and the result says so instead of pretending. A path inside the system is **refused** and has to go through `system_write`. |
117
+ | `edit_file(path, old_string, new_string, expected_sha256, replace_all)` | Replaces an exact substring. `old_string` must match exactly once unless `replace_all` is set; `expected_sha256` guards against overwriting a file that changed since it was read. System paths are refused — read the file and send the whole new content to `system_write`. |
118
+ | `system_write(path, content, create_dirs, mode)` | Writes a file inside a system path (`/etc`, `/usr`, `/boot`, `/var/lib`, `C:\Windows`, `C:\Program Files`, `ProgramData`, the registry) — the confirmed twin of `write_file` and the only way to write there. Gemini asks you first. Content is exact, so there is no shell quoting to get wrong. |
119
+
120
+ ### Search
121
+
122
+ | Tool | What it does |
123
+ | :--- | :--- |
124
+ | `grep_search(pattern, path, glob, limit, ignore_case, fixed, context)` | Recursively searches file contents, skipping binary files and heavy directories. `fixed` treats the pattern as literal text, `context` adds surrounding lines, `limit` is 1–1000 (default 200). |
125
+ | `glob_find(pattern, path)` | Finds files by glob pattern (`*` and `**`). |
126
+
127
+ ### Long-running work
128
+
129
+ | Tool | What it does |
130
+ | :--- | :--- |
131
+ | `run_job(command, cwd)` | Starts a command in the background and returns a `job_id`. On an Android node it takes the wake lock for the lifetime of the job, so Android does not freeze it when the screen goes off. |
132
+ | `job_output(job_id, wait_ms, max_chars, cursor)` | Reads a job's output, optionally waiting up to 20 s for completion. Paginated. |
133
+ | `job_kill(job_id, signal)` | Terminates a job. `signal` is `TERM` (default), `KILL`, `INT`, `HUP` or `QUIT`. |
134
+ | `job_list(limit)` | Lists recent jobs, newest first (20 by default, max 50), including whether each one holds a `wake_lock`. |
135
+
136
+ ### Device (phone, laptop or server)
137
+
138
+ These four tools read and act on the device the node itself runs on. They are advertised on **every** node — a phone-only action on a laptop answers with `available: false`, the reason and, where one exists, the fix, instead of failing.
139
+
140
+ | Tool | What it does |
141
+ | :--- | :--- |
142
+ | `device_info(section, sensor, fresh, quick)` | One-call device report: battery and charging, network interfaces with Wi-Fi and cellular signal, language, time and timezone, CPU, RAM, storage, cameras, microphones, sensors and what the platform can actually reach. `section` is `summary`, `all` (default) or one of `device`, `battery`, `network`, `locale`, `time`, `hardware`, `storage`, `cameras`, `microphones`, `sensors`, `capabilities`. `sensor` takes one live sample from a named sensor (Android; the name comes from the `sensors` block), `fresh` bypasses the few-second cache and `quick` skips the slow sections. Every block carries `available` and `source`; a block the platform cannot answer says why instead of showing a zero. |
143
+ | `device_control(action, on, value, stream, text, title, id, url, path, state, timeout_sec)` | Twenty reversible actions: `torch`, `vibrate`, `volume`, `volume_get`, `brightness`, `tts_speak`, `toast`, `notify`, `notify_list`, `notify_remove`, `clipboard_get`, `clipboard_set`, `media`, `media_scan`, `wakelock`, `download`, `open`, `share`, `dialog`, `wallpaper`. `value` is milliseconds for `vibrate`, a 0–15 level for `volume` and 0–255 for `brightness`; `stream` picks the audio stream (`music` by default); `text` is the body for TTS, toast, notification, clipboard and dialog, and `play\|pause\|stop\|info` for `media`. Nothing here is destructive. |
144
+ | `device_capture(action, camera_id, path, seconds, provider, frequency, pattern)` | Eleven actions: `camera_list`, `camera_photo`, `mic_record_start`, `mic_record_stop`, `mic_record_status`, `location`, `fingerprint`, `usb_list`, `usb_access`, `infrared_frequencies`, `infrared_transmit`. **Asks first.** `path` is the output file for `camera_photo` and `mic_record_start` (default: the node's capture directory) and the device path from `usb_list` for `usb_access`; `camera_id` comes from `camera_list`, `seconds` limits a recording, `provider` is `gps`, `network` or `passive`, and `frequency`/`pattern` drive `infrared_transmit`. A photo or a recording stays in the capture directory and is never uploaded by itself — `share_file` publishes it only when you ask for that. `fingerprint` returns only the verdict; `usb_access` asks Android for permission and returns the descriptor but deliberately refuses to run a program for you. |
145
+ | `device_messages(action, number, text, limit, offset, type, query)` | Five actions: `sms_list`, `sms_send`, `call_log`, `contacts`, `call`. **Asks first, and is off until the operator enables it with `MESH_DEVICE_PIM=1`** — it is the private data of whoever holds the phone. `limit` is 1–50 (default 10), `offset` pages through the list, `type` filters it and `query` filters contacts on the node. |
146
+
147
+ Example:
148
+
149
+ ```text
150
+ "Check the phone's battery and signal."
151
+ → device_info(section="summary")
152
+
153
+ "Turn on the torch."
154
+ → device_control(action="torch", on=true)
155
+
156
+ "Take a photo and share it with me."
157
+ → device_capture(action="camera_photo") # asks for confirmation
158
+ → share_file(path=<the path it returned>)
159
+ ```
160
+
161
+ ### Device switches (operator)
162
+
163
+ Read from `agent.env` at startup ([core/agent.py](core/agent.py), applied in [core/mcp_tools.py](core/mcp_tools.py) and [core/device.py](core/device.py)):
164
+
165
+ | Variable | Default | What it does |
166
+ | :--- | :--- | :--- |
167
+ | `MESH_DEVICE` | `auto` | `auto` keeps the branch answering everywhere (only the answer differs per platform), `0` switches the branch off, `1` forces it on. |
168
+ | `MESH_DEVICE_ACTIONS` | empty | Comma-separated allowlist of action names for `device_control`, `device_capture` and `device_messages`; empty means every action the build implements. |
169
+ | `MESH_DEVICE_CAPTURE_DIR` | shared storage on a phone once `termux-setup-storage` has been granted (`~/storage/dcim/antigravity-mesh` or `~/storage/shared/AntigravityMesh`), otherwise `~/.cache/antigravity-mesh/captures` | Where photos and recordings are written. |
170
+ | `MESH_DEVICE_PIM` | `0` | `1` enables SMS, the call log, contacts and placing a call. |
171
+ | `MESH_DEVICE_QUICK` | `0` | `1` keeps `system_info` instant by skipping network, cameras, microphones and sensors. |
172
+ | `MESH_READ_ONLY` | `0` | `1` refuses every `device_control` and `device_capture` action, and blocks `sms_send` and `call`. This is the node-side guarantee that does not depend on any client prompt. |
173
+
174
+ ### Publishing files and web pages
175
+
176
+ These four make something on your machine readable from the internet. The link is served by the gateway on the shared domain, so **the node has to stay connected for it to work**.
177
+
178
+ | Tool | What it does |
179
+ | :--- | :--- |
180
+ | `share_file(path, name, overwrite)` | Publishes **one file**: it is copied into the node's share root and you get `https://<shared-domain>/<node>/<name>-<random>/<filename>`. `overwrite` replaces an existing share with the same name. |
181
+ | `serve_dir(path, name)` | Serves a **directory** in place — no copy is made. `index.html` is used when present, otherwise a directory listing is shown. Read-only (GET/HEAD only). Returns `https://<shared-domain>/<node>/<name>-<random>/`. |
182
+ | `share_list()` | Lists active shares with their URLs, their roots, and whether the local server is running. |
183
+ | `unshare(name)` | Stops a share and revokes its link. Accepts the share name, its slug or the full URL. A file share also deletes the copy in the share root; a served directory is left untouched on disk. |
184
+
185
+ > [!IMPORTANT]
186
+ > The random part of the path **is** the credential — anyone who has the link can read the file or browse the directory. Treat a share link like a password, and `unshare` it when you are done. Size limits: 32 MiB per file by default, 64 MiB ceiling via `MESH_WEB_MAX_BYTES`.
187
+
188
+ ### Agent behaviour
189
+
190
+ | Tool | What it does |
191
+ | :--- | :--- |
192
+ | `get_orchestration_skill()` | Loads the agent's current operating rules and orchestration skill. |
193
+
194
+ ### Keeping the node up to date
195
+
196
+ | Tool | What it does |
197
+ | :--- | :--- |
198
+ | `mesh_update(action, force, offline, restart)` | Reports, checks for or installs a newer release of the node's own code. `status` reads local state (no network), `check` asks GitHub, `apply` downloads the payload, verifies its published SHA-256, installs it with a rollback backup and restarts the agent. |
199
+
200
+ > [!TIP]
201
+ > Nodes update themselves: a running agent checks for a newer release in the background (every 6 hours by default) and installs what it finds, and on Windows the `AntigravityMeshUpdater` task checks once a day even when no agent is running. `MESH_UPDATE_AUTO=0` switches that to "report only". Details, environment variables and rollback: [docs/UPDATES.md](docs/UPDATES.md).
73
202
 
74
203
  > [!TIP]
75
204
  > 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.
@@ -85,11 +214,27 @@ No SSH client, no terminal on your phone, no VPN — only a chat.
85
214
  curl -fsSL https://smart-server.online/install.sh | bash
86
215
  ```
87
216
 
217
+ **📱 Android (Termux)** — no root, works from F-Droid's Termux
218
+ ```bash
219
+ curl -fsSL https://smart-server.online/install.sh | bash
220
+ ```
221
+ Autostart on a phone is a runit service plus the Termux:Boot app, and the node is
222
+ named after the device model — see [docs/TERMUX.md](docs/TERMUX.md).
223
+
88
224
  **🪟 Windows (PowerShell)**
89
225
  ```powershell
90
226
  irm https://smart-server.online/install.ps1 | iex
91
227
  ```
92
228
 
229
+ **🪟 Windows — visual installer** (from a clone or a release)
230
+
231
+ ```powershell
232
+ .\install-gui.cmd
233
+ ```
234
+
235
+ A WinForms wizard that mirrors the console installer's variants, switches language at
236
+ runtime and shows the MCP link at the end — see [docs/GUI_INSTALLER.md](docs/GUI_INSTALLER.md).
237
+
93
238
  **📦 Node.js (any OS)**
94
239
  ```bash
95
240
  npx gemini-computer-use
@@ -129,6 +274,206 @@ That's it — Gemini is now an agent running on your machine. Repeat step 1 on o
129
274
  | **Remote over SSH** | `./install.sh --ssh=user@host` | Deploy a node onto a remote Linux server from your terminal. |
130
275
  | **Local standalone** | `./install.sh --mode=standalone --port=8096` | Pure localhost FastMCP server at `http://localhost:8096/sse`, no cloud relay. |
131
276
 
277
+ On Windows the same variants are available in the visual installer (`.\install-gui.cmd`); local standalone stays console-only there (`.\install.ps1 -Mode standalone -Port 8096`).
278
+
279
+ On **Android/Termux** the same `./install.sh` variants work; autostart is a runit service plus the Termux:Boot app instead of systemd, and standalone stays reachable from that phone alone — see [docs/TERMUX.md](docs/TERMUX.md).
280
+
281
+ ---
282
+
283
+ ## 🪟 Windows installation
284
+
285
+ Windows already ships PowerShell 5.1, so there is nothing to prepare — no Python, no Node.js, no administrator rights. Both installers finish by putting the same MCP link on the clipboard.
286
+
287
+ | | Console installer | Visual installer |
288
+ | :--- | :--- | :--- |
289
+ | Start | `irm https://smart-server.online/install.ps1 \| iex` | `.\install-gui.cmd` |
290
+ | Needs | nothing but PowerShell | a clone or an unpacked release — it drives `install.ps1`, `core/` and `install.sh` |
291
+ | Variants | `-Mode tunnel` (default), `-Mode standalone`, `-User`, `-Gateway`, `-Token`, `-Port`, `-DryRun` | Quick setup, Custom setup, Remote over SSH |
292
+ | Language | `-Lang en` / `-Lang ru` | switch in the window header, or `-Lang` |
293
+
294
+ ### Automatic dependencies and architecture
295
+
296
+ Nothing has to be prepared by hand first. Both installers obtain what they need, and if
297
+ they cannot, they stop with an explicit message instead of writing an autostart entry that
298
+ can never work.
299
+
300
+ | | Windows (`install.ps1`) | Linux / macOS (`install.sh`) |
301
+ | :--- | :--- | :--- |
302
+ | Python | `winget` → the python.org installer for **this** architecture → `uv` | `apt` / `dnf` / `yum` / `zypper` / `pacman` / `apk` / `xbps` / `brew` → `uv` |
303
+ | `websockets` | `pip` → `pip --user` → `ensurepip` → `uv` → a venv | `pip` → `pip --break-system-packages` → `pip --user` → `ensurepip` → `uv` → a venv |
304
+ | If all of that fails | stops, and prints the exact command to run | stops, and prints the exact command to run |
305
+
306
+ `websockets` is the only external Python requirement: `core/mcp_tools.py` and
307
+ `core/server.py` are standard library only. `uv` is fetched only when the cheaper paths
308
+ have already failed.
309
+
310
+ **The architecture decides which build is downloaded.** The installer asks the *OS*, not
311
+ the process, because a 32-bit PowerShell on 64-bit Windows reports `x86` and hides the
312
+ real value in `PROCESSOR_ARCHITEW6432`, and an emulated x64 process on ARM64 reports
313
+ `AMD64`:
314
+
315
+ | Machine | Python build | `uv` archive |
316
+ | :--- | :--- | :--- |
317
+ | Windows x64 | `python-3.12.5-amd64.exe` | `x86_64-pc-windows-msvc` |
318
+ | Windows ARM64 | `python-3.12.5-arm64.exe` | `aarch64-pc-windows-msvc` |
319
+ | Windows 32-bit | `python-3.12.5.exe` | — (uv ships no 32-bit Windows build) |
320
+ | Linux `x86_64` | the package manager's own build | `x86_64-unknown-linux-gnu` |
321
+ | Linux `aarch64` | the package manager's own build | `aarch64-unknown-linux-gnu` |
322
+ | Linux `armv7l` / `i686` | the package manager's own build | `armv7-unknown-linux-gnueabihf` / `i686-unknown-linux-gnu` |
323
+ | macOS `arm64` / `x86_64` | `brew`, or `uv` | `aarch64-apple-darwin` / `x86_64-apple-darwin` |
324
+
325
+ The interpreter that ends up pinned is the one that actually has `websockets` — which can
326
+ be a venv or a `uv`-managed CPython, both deliberately outside `PATH`. `install.ps1 -DryRun`
327
+ and `install.sh --dry-run` report the architecture without changing anything, and the visual
328
+ installer shows it in its preflight.
329
+
330
+ ### Download the setup executable
331
+
332
+ The release also ships a single compiled installer, for machines where you would rather
333
+ not clone or download anything else — `AntigravityMesh-Setup-<version>.exe`:
334
+
335
+ ```powershell
336
+ .\AntigravityMesh-Setup-0.4.1.exe # open the visual installer
337
+ .\AntigravityMesh-Setup-0.4.1.exe -Lang ru # start in Russian
338
+ .\AntigravityMesh-Setup-0.4.1.exe -SelfTest # headless self-check, prints JSON
339
+ .\AntigravityMesh-Setup-0.4.1.exe --version # print the version
340
+ ```
341
+
342
+ It carries the wizard, `install.ps1`, `core/` and `install.sh` inside itself, unpacks them
343
+ to `%LOCALAPPDATA%\AntigravityMesh\setup\<version>` (override with `MESH_SETUP_DIR`) and
344
+ runs the wizard from there. It needs nothing but the .NET Framework and PowerShell that
345
+ Windows already has, and it is built from this repository by
346
+ `.\build-installer-exe.ps1` — no SDK, no NuGet, no network.
347
+
348
+ It is **not code-signed**, so SmartScreen may ask for confirmation the first time you run
349
+ it.
350
+
351
+ ### Visual installer
352
+
353
+ ![Windows visual installer](docs/images/gui-welcome-en.png)
354
+
355
+ - **Quick setup** (recommended) — node name = PC name, shared gateway domain, Windows autostart. The one to pick on a home PC or laptop.
356
+ - **Custom setup** — your own node name, your own shared domain, optional token.
357
+ - **Remote over SSH** — copy the node code to a Linux host and run `install.sh` there. Needs the Windows *OpenSSH Client* feature and key-based authentication; interactive password prompts are not supported.
358
+
359
+ The first screen runs `install.ps1 -DryRun` and reports what it found — Python, `websockets`, the resolved gateway domain, the config directory, the autostart path — without changing anything. Before starting, the window shows the exact command it will run; during the run it streams the installer output; at the end it shows the MCP link with a copy button, the three-step Gemini instructions and the paths it created.
360
+
361
+ Full details, screenshots and the SSH notes: [docs/GUI_INSTALLER.md](docs/GUI_INSTALLER.md).
362
+
363
+ ### What a Windows installation creates
364
+
365
+ | Path | What it is |
366
+ | :--- | :--- |
367
+ | `%USERPROFILE%\.config\antigravity-mesh\agent.env` | `MESH_GATEWAY`, `MESH_USER`, `MESH_TOKEN` |
368
+ | `%USERPROFILE%\.config\antigravity-mesh\domain.env` | `MESH_PUBLIC_URL` — the resolved shared domain, so the node's share links, its tunnel and the gateway name the same host (not written when the built-in default is all that is configured) |
369
+ | `…\Start Menu\Programs\Startup\antigravity-agent.vbs` | autostart entry, so the node comes back after a reboot |
370
+ | `%USERPROFILE%\.config\antigravity-mesh\agent.log` | agent output, for when a silent autostart fails |
371
+
372
+ ### Language
373
+
374
+ The visual installer starts in the language of your Windows language settings — the display language and the preferred-languages list are read from the registry — and falls back to English. `-Lang ru` / `-Lang en` overrides the detection. The switch in the window header changes every label immediately.
375
+
376
+ ### Troubleshooting
377
+
378
+ - **"No gateway domain configured"** — this copy was not published with a domain. Pass one: use *Custom setup* in the wizard, or `.\install.ps1 -Gateway <shared-domain>`, or set `MESH_PUBLIC_URL`.
379
+ - **Registration fails** — the installer stops *before* writing anything when the gateway cannot be reached. Check that the domain resolves, then retry with an explicit `-Gateway`.
380
+ - **`python` opens the Microsoft Store** — that is the App Execution Alias, not an interpreter. Both installers resolve a real `python.exe` by full path and ignore the alias; the wizard never pins an alias into the autostart entry.
381
+ - **The node does not come back after a reboot** — run `.\ops\doctor.ps1`: it prints the autostart interpreter, the heartbeat age, the last `agent.log` lines (including a torn final line) and the gateway's own view of this node. `.\ops\windows\agent-watchdog.ps1` performs that check once and restarts the agent; the scheduled task `AntigravityMeshWatchdog` runs it every five minutes, so a dead agent comes back on its own.
382
+
383
+ ### Standalone on Windows
384
+
385
+ The wizard does not offer it; use the console installer:
386
+
387
+ ```powershell
388
+ .\install.ps1 -Mode standalone -Port 8096
389
+ ```
390
+
391
+ That starts a local-only FastMCP server at `http://localhost:8096/sse` and puts that URL on the clipboard.
392
+
393
+ ---
394
+
395
+ ## 📱 Android installation (Termux)
396
+
397
+ The same `install.sh` installs a node on an unrooted phone or tablet, and the phone
398
+ then shows up in Gemini Spark like any other machine. Full guide:
399
+ **[docs/TERMUX.md](docs/TERMUX.md)**.
400
+
401
+ ```bash
402
+ # 1. Termux from F-Droid (not Google Play); optionally Termux:Boot and Termux:API
403
+ pkg update -y
404
+ # 2. the usual one-liner
405
+ curl -fsSL https://smart-server.online/install.sh | bash
406
+ ```
407
+
408
+ What a phone changes, and how the installer handles it:
409
+
410
+ | | |
411
+ | :--- | :--- |
412
+ | **Packages** | `pkg` instead of `apt`, `python`/`python-pip` instead of `python3-pip`/`python3-venv` — a phone is never root and has no `sudo`. |
413
+ | **Node name** | The device model (`Pixel 7 Pro` → `pixel7pro`): Android answers `localhost` to every app, and the gateway keeps one tunnel per name. |
414
+ | **Domain file** | `~/.config/antigravity-mesh/domain.env` — there is no `/etc` to write to. The installer records the resolved shared domain there, so the phone's share links, its tunnel and the gateway name the same host (the built-in default is never recorded). |
415
+ | **Autostart** | A **runit** service (`termux-services`, the `Restart=always` equivalent) plus a **Termux:Boot** script that takes the wake lock after a reboot. |
416
+ | **Clipboard** | `termux-clipboard-set` (Termux:API), so the MCP link goes straight into the Gemini app. |
417
+
418
+ > [!IMPORTANT]
419
+ > Two Android-side switches are yours to flip: install **Termux:Boot** and open it
420
+ > once, and set battery optimisation to *Unrestricted* for Termux. Without them
421
+ > Android unloads the node with the screen off.
422
+
423
+ The phone must also be able to **resolve the gateway name**. A gateway that lives on
424
+ a private network (Tailscale, a VPN, a DNS override on your laptop) resolves there and
425
+ nowhere else — mobile data resolves nothing private. Put the phone on that network, or
426
+ give the gateway a publicly resolvable domain; the installer detects a name it cannot
427
+ resolve and says so before writing anything: see
428
+ [docs/TERMUX.md](docs/TERMUX.md#private-gateway-tailscale--vpn).
429
+
430
+ ```bash
431
+ sv status agy-agent # is it up?
432
+ sv restart agy-agent # restart now
433
+ tail -f $PREFIX/var/log/sv/agy-agent/current
434
+ ```
435
+
436
+ `--mode=standalone` also works, but it serves `127.0.0.1` only — reachable from
437
+ inside that phone alone. Use the default tunnel mode to drive the phone from Gemini.
438
+
439
+ ---
440
+
441
+ ## 🔄 Automatic updates
442
+
443
+ A node installs itself from a GitHub release and then keeps itself current the same
444
+ way. You do not re-install anything by hand, and nothing unverified is installed.
445
+
446
+ - **It checks by itself.** A running agent asks the GitHub Releases API every 6
447
+ hours (`MESH_UPDATE_CHECK_INTERVAL`), compares the tag with the version in
448
+ `core/version.py` and installs what it finds. On Windows the daily
449
+ `AntigravityMeshUpdater` task does the same for a machine whose agent is not
450
+ running. `MESH_UPDATE_AUTO=0` turns installing off and keeps reporting.
451
+ - **It verifies before it installs.** The payload is downloaded, its SHA-256 is
452
+ compared with the published checksum, and only then are `core/`, `skills/` and
453
+ `ops/` replaced. No checksum, no update.
454
+ - **It can always go back.** The replaced directories are saved under
455
+ `%USERPROFILE%\.config\antigravity-mesh\backups\<version>-<timestamp>\`, and an
456
+ update interrupted by a kill or a power cut is rolled back automatically on the
457
+ next start.
458
+ - **The node comes back new.** The agent is restarted through whatever supervises
459
+ it (the watchdog task on Windows, `systemctl --user restart agy-agent.service`,
460
+ `launchctl kickstart -k`) — or started directly when nothing supervises it.
461
+
462
+ By hand, any time:
463
+
464
+ ```powershell
465
+ gemini-computer-use update -Check # is a newer release published?
466
+ gemini-computer-use update # install it now and restart
467
+ gemini-computer-use update -Check -Json
468
+ ```
469
+
470
+ From Gemini: ask the node to run `mesh_update` (`status`, then `check`, then
471
+ `apply`).
472
+
473
+ The state of the last check is in the heartbeat, so `ops/doctor.ps1` shows it, and
474
+ the full history is in `%USERPROFILE%\.config\antigravity-mesh\update.log`.
475
+ Configuration, exit codes, rollback and troubleshooting: [docs/UPDATES.md](docs/UPDATES.md).
476
+
132
477
  ---
133
478
 
134
479
  ## 🔗 One shared domain (canonical URL contract)
@@ -145,6 +490,9 @@ Why: every extra hostname would need its own DNS record *and* its own SAN in the
145
490
 
146
491
  - 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
492
  - 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.
493
+ - The installer now records the domain it resolved in the domain file, so the node's share links, its tunnel and the gateway all name the same host: `MESH_PUBLIC_URL=https://<shared-domain>` in `domain.env`, written in both install branches (standalone, and cloud-gateway/tunnel right after `agent.env`). The value is normalised first, the `__MESH_DOMAIN__` placeholder and an empty value are never written, the write is skipped when the file already names that host, and a file that cannot be written is not fatal — the installer prints the exact command to run by hand. The built-in default is never recorded at all: it is a fallback, not a configuration, and pinning it would stop `core/domain.py` from consulting the legacy `MESH_GATEWAY` at all, so a later change in `agent.env` would be silently ignored — nothing is lost, because with no file that same value is already the resolver's last fallback. `--dry-run` reports the file and the value it would write — including `not written (the built-in default is a fallback, not a configuration)` — and still changes nothing on disk.
494
+ - Why this matters: share links are built by `core/domain.py`, whose chain is `MESH_PUBLIC_URL` → `AGY_PUBLIC_BASE_URL` → the domain file → the built-in default, and which deliberately never reads the legacy `MESH_GATEWAY` that `agent.env` carries (only `gateway_host()`, the tunnel host, does). A node installed the normal way — `agent.env` with `MESH_GATEWAY`/`MESH_USER`/`MESH_TOKEN` and no domain file — therefore dialled the right gateway while minting links on the built-in default. On an already-installed node, re-run the installer or write the one line by hand: `mkdir -p ~/.config/antigravity-mesh && echo 'MESH_PUBLIC_URL=https://<domain>' > ~/.config/antigravity-mesh/domain.env`, or the same with `sudo tee /etc/antigravity-mesh/domain.env` on Linux. Existing share links keep working; new ones use the configured domain.
495
+ - `MESH_DOMAIN_FILE` only overrides the *path*, and it belongs to the node's own configuration rather than to the installer invocation: keep it in `agent.env` (every `MESH_*` key there is exported to the node), or the domain is written into a file the node never reads. The installer creates the parent directory of whichever file is in effect. Over `--ssh=<host>` the domain resolved here is handed to the remote `install.sh` as `--domain=`, so the target records that host instead of re-resolving it (a repository copy still carrying the `__MESH_DOMAIN__` placeholder would otherwise fall back to the built-in default).
148
496
 
149
497
  ---
150
498
 
@@ -172,6 +520,16 @@ MESH_GATEWAY_SSH=root@<gateway-ip> MESH_GATEWAY_SSH_PASS_FILE=~/.ssh/gw-pass \
172
520
  ```
173
521
  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.
174
522
 
523
+ > [!TIP]
524
+ > **One place for the domain.** `MESH_PUBLIC_URL` in `domain.env`
525
+ > (`/etc/antigravity-mesh/domain.env` on Linux, `%USERPROFILE%\.config\antigravity-mesh\domain.env`
526
+ > on Windows) is the only setting that names it: the node, the gateway, the installers, the public
527
+ > file links and the nginx vhosts all resolve it through `core/domain.py`. Switching domains is one
528
+ > value plus `ops/nginx/render-domain.sh --apply` — see [docs/DOMAIN.md](docs/DOMAIN.md).
529
+
530
+ > [!IMPORTANT]
531
+ > **`install.ps1` encoding — one file, two representations.** The repository copy is UTF-8 *with* a BOM: Windows PowerShell 5.1 decodes a BOM-less script with the ANSI code page, the Russian strings then turn into smart quotes and the parser rejects the whole file, so `.\install.ps1` from a clone would not run. The copy the gateway serves is written *without* the BOM by `deploy_gateway.sh`, because `irm … | iex` receives the BOM as part of the first token and `param(...)` then stops being the first statement. nginx declares `charset utf-8` for that location, so the BOM-less copy still decodes correctly. Please keep both properties when editing the file.
532
+
175
533
  **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:
176
534
 
177
535
  ```bash
@@ -233,6 +591,7 @@ Gemini в браузере или на телефоне умеет разгов
233
591
  - *«Исправь опечатку в config.yaml и перезапусти сервис»*
234
592
  - *«Запусти полный бэкап в фоне и сообщи, когда закончится»*
235
593
  - *«Сколько свободного места осталось на ноутбуке?»*
594
+ - *«Включи фонарик на телефоне и скажи уровень заряда и сигнал»*
236
595
 
237
596
  Без SSH-клиента, без терминала на телефоне, без VPN — только чат.
238
597
 
@@ -254,14 +613,142 @@ Gemini в браузере или на телефоне умеет разгов
254
613
 
255
614
  ## 🛠️ Что Gemini может делать на вашей машине (MCP-инструменты)
256
615
 
257
- | Категория | Инструменты |
616
+ **26 инструментов.** Все они выполняются на вашей машине под вашей учётной записью — шлюз только передаёт вызовы.
617
+
618
+ ### На какие вызовы Gemini спросит подтверждение
619
+
620
+ Gemini Spark решает, останавливаться ли с вопросом *«подтвердить действие?»*, по **подсказкам MCP-аннотаций** (`readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`), которые инструмент объявляет в `tools/list`. Инструмент без аннотаций считается деструктивным по умолчанию спецификации — поэтому сервер, который их не объявляет, получает запрос подтверждения на **каждом** вызове. Здесь подсказки заданы явно:
621
+
622
+ | Класс | Инструменты | Подтверждение |
623
+ | :--- | :--- | :--- |
624
+ | Только чтение | `mesh_status`, `system_info`, `system_vitals`, `get_orchestration_skill`, `list_dir`, `read_file`, `grep_search`, `glob_find`, `job_output`, `job_list`, `share_list`, `device_info` | не запрашивается |
625
+ | Локальная работа — команды, сборки, запись файлов, задачи, публикации, действия с устройством | `bash_exec`, `run_job`, `write_file`, `edit_file`, `job_kill`, `share_file`, `serve_dir`, `device_control` | не запрашивается |
626
+ | **Чувствительные — наблюдают за физическим миром или личными данными** | `device_capture` (камера, микрофон, местоположение, отпечаток, USB, ИК-порт), `device_messages` (SMS, журнал вызовов, контакты, звонки) | **запрашивается** |
627
+ | **Подтверждаемые — установка/удаление, `sudo`, системные пути** | `system_change` (установка/удаление ПО, удаление данных, `sudo`, правка системных путей через оболочку), `system_write` (запись файла в системный путь), `mesh_update` (устанавливает релиз), `unshare` (отзывает и удаляет опубликованную копию) | **запрашивается** |
628
+
629
+ `device_capture` и `device_messages` ничего не удаляют, но объявлены с `destructiveHint`. Эта подсказка — единственная, которую клиенты вроде Gemini Spark надёжно превращают в вопрос *«подтвердить действие?»*, а молча сработавшая камера, микрофон или список SMS хуже честной перестраховки. В этот класс они отнесены намеренно. `device_control` по той же причине остаётся обычной локальной работой: пометить так фонарик или вибрацию — значит приучить вас закрывать те диалоги, которые действительно важны.
630
+
631
+ Под барьером четыре класса, и следит за ними шлюз, а не подсказка: подсказка статична на инструмент и не отличает `ls` от `apt install`.
632
+
633
+ 1. **Установка и удаление ПО** — пакетные менеджеры, деинсталляторы, `msiexec`, `dpkg`/`rpm`, `curl … | bash`, `iwr … | iex`.
634
+ 2. **Удаление данных** — `rm`, `Remove-Item`, `del`, `rd`, `shred`, `dd`, `mkfs`, `git clean -fd`, `docker rm`/`prune`, `truncate -s 0`.
635
+ 3. **Работа от root** — `sudo`, `doas`, `pkexec`, `runas`, `Start-Process -Verb RunAs`.
636
+ 4. **Системные разделы** — `/etc`, `/usr`, `/boot`, `/var/lib`, `/opt`, `/dev`, `C:\Windows`, `C:\Program Files`, `ProgramData`, реестр. **Чтение** этих путей (`cat /etc/os-release`, `systemctl status`) не блокируется, как и обычная работа в ваших каталогах.
637
+
638
+ `bash_exec`/`run_job` отклоняют такие команды и предлагают переоформить их как `system_change`; `write_file`/`edit_file` отклоняют системный путь и направляют в `system_write` (он принимает точное содержимое, поэтому экранировать ничего не нужно). Барьер не убран, а перенесён: осмотр, сборки, тесты, правка конфигов в ваших проектах идут без диалога.
639
+
640
+ Поверхность инструментов и политика подтверждений живут в [gateway.py](gateway.py) (аннотации, `INSTALL_COMMAND_PATTERNS` / `DELETE_COMMAND_PATTERNS`, `SYSTEM_PATH_RE`, `classify_command()`) и в [core/mcp_tools.py](core/mcp_tools.py) (`TOOL_ANNOTATIONS` для собственной поверхности узла, включая четыре инструмента ветки устройства); тесты сравнивают обе поверхности и фиксируют классификатор, чтобы закрытая команда не просочилась.
641
+
642
+ > [!NOTE]
643
+ > Классификатор ищет глагол в начале сегмента команды (после префиксов `sudo`/`env`/`timeout`/`powershell -Command`), поэтому `echo "rm -rf /"` или `grep rm notes.txt` выполняются как раньше, а `curl … | bash` считается установкой. Это UX-барьер, а не песочница: жёсткая гарантия — переключатели узла `MESH_READ_ONLY` и `MESH_WRITE_ROOTS` ([core/agent.py](core/agent.py)).
644
+
645
+ ### Состояние и сведения о хосте
646
+
647
+ | Инструмент | Что делает |
648
+ | :--- | :--- |
649
+ | `mesh_status()` | Подтверждает, что узел доступен, живыми данными с самого хоста: что за машина ответила (`device_class`, `scenario`) и её `battery_percent`. Вызывать первым, если машина кажется offline. |
650
+ | `system_info()` | Сводка о хосте одним вызовом: ОС, рабочий стол, пользователь, домашний каталог, диски, память, загрузка, топ процессов, текущие обои, `command_shell` — та оболочка, которую реально использует `bash_exec`, — и блоки устройства: батарея и зарядка, сеть и сигнал, язык, время и часовой пояс, CPU, ОЗУ, накопитель, камеры, микрофоны и датчики. |
651
+ | `system_vitals()` | Метрики CPU, ОЗУ и дисков, плюс блок батареи и термические датчики, которые отдаёт платформа. |
652
+
653
+ ### Оболочка
654
+
655
+ | Инструмент | Что делает |
656
+ | :--- | :--- |
657
+ | `bash_exec(command, timeout_sec, max_chars, cursor)` | Выполняет команду оболочки. Оболочка соответствует **хосту**, а не названию инструмента — `bash` на Linux/macOS, PowerShell или `cmd.exe` на Windows; сначала посмотрите `command_shell` из `system_info()`. Вывод постраничный: если обрезан, вызовите снова с `cursor=next_cursor`, ничего не теряется; вывод больше 2 МБ сохраняется в файл, путь в `saved_to`. `timeout_sec` — 1–120 (по умолчанию 25). Установка/удаление, `sudo` и системные пути здесь **отклоняются** — их надо выполнять через `system_change`. |
658
+ | `system_change(command, timeout_sec, background, cwd, max_chars, cursor)` | Установка, удаление ПО и данных, привилегированные команды и правка системных путей — «подтверждаемый» двойник `bash_exec` и единственный инструмент, которому разрешены `apt`/`dnf`/`pacman`/`pip`/`npm`/`winget`/`msiexec`/`rm`/`Remove-Item`/`sudo` и работа с дисками. Объявлен деструктивным, поэтому Gemini спрашивает подтверждение. `background=true` запускает его как фоновую задачу (для долгих установок) и возвращает `job_id` для `job_output`; `cwd` действует только в этом режиме. |
659
+
660
+ ### Файлы
661
+
662
+ | Инструмент | Что делает |
663
+ | :--- | :--- |
664
+ | `list_dir(path)` | Список файлов и каталогов (по умолчанию — рабочий каталог). |
665
+ | `read_file(path, start_line, end_line, max_chars, cursor)` | Читает текстовый файл с номерами строк, при желании — диапазон строк. Постранично, как `bash_exec`. |
666
+ | `write_file(path, content, create_dirs, mode)` | Атомарно создаёт или перезаписывает файл (временный файл + `os.replace`). `mode` — только для POSIX: на Windows он не применяется, и результат об этом честно сообщает. Путь внутри системы **отклоняется** — для него есть `system_write`. |
667
+ | `edit_file(path, old_string, new_string, expected_sha256, replace_all)` | Заменяет точную подстроку. `old_string` должен встречаться ровно один раз, если не задан `replace_all`; `expected_sha256` защищает от перезаписи файла, изменившегося после чтения. Системные пути отклоняются — прочитайте файл и отправьте новое содержимое целиком в `system_write`. |
668
+ | `system_write(path, content, create_dirs, mode)` | Запись файла в системный путь (`/etc`, `/usr`, `/boot`, `/var/lib`, `C:\Windows`, `C:\Program Files`, `ProgramData`, реестр) — «подтверждаемый» двойник `write_file` и единственный способ туда писать. Gemini спрашивает подтверждение. Содержимое передаётся точно, экранировать для оболочки ничего не нужно. |
669
+
670
+ ### Поиск
671
+
672
+ | Инструмент | Что делает |
673
+ | :--- | :--- |
674
+ | `grep_search(pattern, path, glob, limit, ignore_case, fixed, context)` | Рекурсивно ищет по содержимому файлов, пропуская двоичные файлы и тяжёлые каталоги. `fixed` — поиск как по обычному тексту, `context` — строки вокруг совпадения, `limit` — 1–1000 (по умолчанию 200). |
675
+ | `glob_find(pattern, path)` | Ищет файлы по маске (`*` и `**`). |
676
+
677
+ ### Долгие задачи
678
+
679
+ | Инструмент | Что делает |
680
+ | :--- | :--- |
681
+ | `run_job(command, cwd)` | Запускает команду в фоне и возвращает `job_id`. На Android-узле берёт wake lock на всё время задачи, чтобы Android не заморозил её при выключенном экране. |
682
+ | `job_output(job_id, wait_ms, max_chars, cursor)` | Читает вывод задачи, при желании ожидая завершения до 20 с. Постранично. |
683
+ | `job_kill(job_id, signal)` | Завершает задачу. `signal` — `TERM` (по умолчанию), `KILL`, `INT`, `HUP` или `QUIT`. |
684
+ | `job_list(limit)` | Список последних задач, новые сверху (20 по умолчанию, максимум 50), с признаком `wake_lock` у каждой. |
685
+
686
+ ### Устройство (телефон, ноутбук или сервер)
687
+
688
+ Эти четыре инструмента читают само устройство, на котором работает узел, и управляют им. Они объявлены на **каждом** узле — действие для телефона на ноутбуке отвечает `available: false`, причиной и, где она есть, подсказкой `fix`, а не отказом.
689
+
690
+ | Инструмент | Что делает |
691
+ | :--- | :--- |
692
+ | `device_info(section, sensor, fresh, quick)` | Отчёт об устройстве одним вызовом: батарея и зарядка, сетевые интерфейсы с уровнем Wi-Fi и сотового сигнала, язык, время и часовой пояс, CPU, ОЗУ, накопитель, камеры, микрофоны, датчики и то, до чего платформа реально дотягивается. `section` — `summary`, `all` (по умолчанию) или один из `device`, `battery`, `network`, `locale`, `time`, `hardware`, `storage`, `cameras`, `microphones`, `sensors`, `capabilities`. `sensor` берёт один живой замер с датчика по имени (Android; имя — из блока `sensors`), `fresh` обходит короткий кэш, `quick` пропускает медленные разделы. У каждого блока есть `available` и `source`; блок, на который у платформы нет ответа, говорит причину, а не показывает ноль. |
693
+ | `device_control(action, on, value, stream, text, title, id, url, path, state, timeout_sec)` | Двадцать обратимых действий: `torch`, `vibrate`, `volume`, `volume_get`, `brightness`, `tts_speak`, `toast`, `notify`, `notify_list`, `notify_remove`, `clipboard_get`, `clipboard_set`, `media`, `media_scan`, `wakelock`, `download`, `open`, `share`, `dialog`, `wallpaper`. `value` — миллисекунды для `vibrate`, уровень 0–15 для `volume` и 0–255 для `brightness`; `stream` выбирает аудиопоток (`music` по умолчанию); `text` — тело для TTS, toast, уведомления, буфера обмена и диалога, а для `media` — `play\|pause\|stop\|info`. Ничего деструктивного здесь нет. |
694
+ | `device_capture(action, camera_id, path, seconds, provider, frequency, pattern)` | Одиннадцать действий: `camera_list`, `camera_photo`, `mic_record_start`, `mic_record_stop`, `mic_record_status`, `location`, `fingerprint`, `usb_list`, `usb_access`, `infrared_frequencies`, `infrared_transmit`. **Спрашивает подтверждение.** `path` задаёт выходной файл для `camera_photo` и `mic_record_start` (по умолчанию — каталог съёмки узла) и путь устройства из `usb_list` для `usb_access`; `camera_id` берётся из `camera_list`, `seconds` ограничивает запись, `provider` — `gps`, `network` или `passive`, а `frequency`/`pattern` — для `infrared_transmit`. Фото и запись остаются в каталоге съёмки и сами никуда не отправляются — `share_file` публикует файл только по отдельной просьбе. `fingerprint` возвращает только вердикт; `usb_access` запрашивает у Android разрешение и отдаёт дескриптор, но намеренно отказывается запускать программу за вас. |
695
+ | `device_messages(action, number, text, limit, offset, type, query)` | Пять действий: `sms_list`, `sms_send`, `call_log`, `contacts`, `call`. **Спрашивает подтверждение и выключен, пока оператор не включит его через `MESH_DEVICE_PIM=1`** — это личные данные того, у кого телефон в руках. `limit` — 1–50 (по умолчанию 10), `offset` листает список, `type` фильтрует его, а `query` фильтрует контакты на узле. |
696
+
697
+ Пример:
698
+
699
+ ```text
700
+ «Проверь батарею и сигнал на телефоне».
701
+ → device_info(section="summary")
702
+
703
+ «Включи фонарик».
704
+ → device_control(action="torch", on=true)
705
+
706
+ «Сделай фото и поделись им со мной».
707
+ → device_capture(action="camera_photo") # спрашивает подтверждение
708
+ → share_file(path=<путь, который он вернул>)
709
+ ```
710
+
711
+ ### Переключатели устройства (оператор)
712
+
713
+ Читаются из `agent.env` при запуске ([core/agent.py](core/agent.py), применяются в [core/mcp_tools.py](core/mcp_tools.py) и [core/device.py](core/device.py)):
714
+
715
+ | Переменная | По умолчанию | Что делает |
716
+ | :--- | :--- | :--- |
717
+ | `MESH_DEVICE` | `auto` | `auto` — ветка отвечает везде (различается только ответ), `0` — выключает ветку, `1` — включает принудительно. |
718
+ | `MESH_DEVICE_ACTIONS` | пусто | Список разрешённых имён действий через запятую для `device_control`, `device_capture` и `device_messages`; пусто — все действия, реализованные сборкой. |
719
+ | `MESH_DEVICE_CAPTURE_DIR` | общее хранилище телефона после `termux-setup-storage` (`~/storage/dcim/antigravity-mesh` или `~/storage/shared/AntigravityMesh`), иначе `~/.cache/antigravity-mesh/captures` | Куда пишутся фото и записи. |
720
+ | `MESH_DEVICE_PIM` | `0` | `1` включает SMS, журнал вызовов, контакты и звонок. |
721
+ | `MESH_DEVICE_QUICK` | `0` | `1` оставляет `system_info` мгновенным, пропуская сеть, камеры, микрофоны и датчики. |
722
+ | `MESH_READ_ONLY` | `0` | `1` отказывает в каждом действии `device_control` и `device_capture`, а также блокирует `sms_send` и `call`. Это гарантия на стороне узла, не зависящая от диалога клиента. |
723
+
724
+ ### Публикация файлов и веб-страниц
725
+
726
+ Эти четыре делают что-то на вашей машине доступным из интернета. Ссылку отдаёт шлюз на общем домене, поэтому **узел должен оставаться подключённым**.
727
+
728
+ | Инструмент | Что делает |
729
+ | :--- | :--- |
730
+ | `share_file(path, name, overwrite)` | Публикует **один файл**: он копируется в каталог публикаций узла, и вы получаете `https://<общий-домен>/<узел>/<имя>-<случайное>/<файл>`. `overwrite` заменяет публикацию с тем же именем. |
731
+ | `serve_dir(path, name)` | Отдаёт **каталог** на месте — копия не создаётся. Если есть `index.html`, отдаётся он, иначе показывается список файлов. Только чтение (GET/HEAD). Возвращает `https://<общий-домен>/<узел>/<имя>-<случайное>/`. |
732
+ | `share_list()` | Список активных публикаций: ссылки, корни и запущен ли локальный сервер. |
733
+ | `unshare(name)` | Останавливает публикацию и отзывает ссылку. Принимает имя, slug или полную ссылку. Для файла удаляется и копия в каталоге публикаций; отдаваемый каталог на диске не трогается. |
734
+
735
+ > [!IMPORTANT]
736
+ > Случайная часть пути **и есть** пароль — любой, у кого есть ссылка, прочитает файл или просмотрит каталог. Относитесь к ссылке как к паролю и снимайте публикацию через `unshare`, когда она больше не нужна. Ограничения: 32 МиБ на файл по умолчанию, потолок 64 МиБ через `MESH_WEB_MAX_BYTES`.
737
+
738
+ ### Поведение агента
739
+
740
+ | Инструмент | Что делает |
741
+ | :--- | :--- |
742
+ | `get_orchestration_skill()` | Загружает текущие рабочие правила агента и навык оркестрации. |
743
+
744
+ ### Поддержание узла в актуальном состоянии
745
+
746
+ | Инструмент | Что делает |
258
747
  | :--- | :--- |
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()` — загружает рабочие правила агента |
748
+ | `mesh_update(action, force, offline, restart)` | Сообщает, проверяет или устанавливает новую версию кода самого узла. `status` — локальное состояние (без сети), `check` — запрос к GitHub, `apply` — скачивает payload, проверяет опубликованный SHA-256, ставит с бэкапом для отката и перезапускает агент. |
749
+
750
+ > [!TIP]
751
+ > Узлы обновляются сами: работающий агент проверяет новые релизы в фоне (по умолчанию раз в 6 часов) и устанавливает найденное, а на Windows задача `AntigravityMeshUpdater` проверяет раз в сутки даже когда агент не запущен. `MESH_UPDATE_AUTO=0` переводит это в режим «только сообщать». Подробности, переменные окружения и откат: [docs/UPDATES.md](docs/UPDATES.md).
265
752
 
266
753
  > [!TIP]
267
754
  > Gemini даёт одному вызову инструмента около 30 секунд. Всё, что дольше (сборки, бэкапы, загрузки), агент запускает через `run_job` и забирает результат через `job_output` — задачи не обрываются.
@@ -277,11 +764,27 @@ Gemini в браузере или на телефоне умеет разгов
277
764
  curl -fsSL https://smart-server.online/install.sh | bash
278
765
  ```
279
766
 
767
+ **📱 Android (Termux)** — без root, Termux из F-Droid
768
+ ```bash
769
+ curl -fsSL https://smart-server.online/install.sh | bash
770
+ ```
771
+ Автозапуск на телефоне — служба runit плюс приложение Termux:Boot, а имя узла
772
+ берётся из модели устройства: см. [docs/TERMUX.md](docs/TERMUX.md).
773
+
280
774
  **🪟 Windows (PowerShell)**
281
775
  ```powershell
282
776
  irm https://smart-server.online/install.ps1 | iex
283
777
  ```
284
778
 
779
+ **🪟 Windows — визуальный установщик** (из клона или релиза)
780
+
781
+ ```powershell
782
+ .\install-gui.cmd
783
+ ```
784
+
785
+ Мастер на WinForms: те же варианты, что и в консольном установщике, переключение языка
786
+ на ходу и готовая ссылка MCP в конце — см. [docs/GUI_INSTALLER.md](docs/GUI_INSTALLER.md).
787
+
285
788
  **📦 Node.js (любая ОС)**
286
789
  ```bash
287
790
  npx gemini-computer-use
@@ -321,6 +824,207 @@ https://smart-server.online/sse?user=<имя-вашего-узла>&token=<ва
321
824
  | **Удалённо через SSH** | `./install.sh --ssh=user@host` | Развернуть узел на удалённом Linux-сервере прямо из терминала. |
322
825
  | **Локальный автономный** | `./install.sh --mode=standalone --port=8096` | Чисто локальный FastMCP-сервер на `http://localhost:8096/sse`, без облачного релея. |
323
826
 
827
+ На Windows те же варианты есть в визуальном установщике (`.\install-gui.cmd`); локальный автономный режим там остаётся консольным (`.\install.ps1 -Mode standalone -Port 8096`).
828
+
829
+ На **Android/Termux** работают те же варианты `./install.sh`; автозапуск — служба runit плюс приложение Termux:Boot вместо systemd, а автономный режим доступен только внутри самого телефона: см. [docs/TERMUX.md](docs/TERMUX.md).
830
+
831
+ ---
832
+
833
+ ## 🪟 Установка на Windows
834
+
835
+ В Windows уже есть PowerShell 5.1, поэтому готовить ничего не нужно — ни Python, ни Node.js, ни права администратора. Оба установщика заканчивают одинаково: кладут ссылку MCP в буфер обмена.
836
+
837
+ | | Консольный установщик | Визуальный установщик |
838
+ | :--- | :--- | :--- |
839
+ | Запуск | `irm https://smart-server.online/install.ps1 \| iex` | `.\install-gui.cmd` |
840
+ | Что нужно | только PowerShell | клон или распакованный релиз — он вызывает `install.ps1`, `core/` и `install.sh` |
841
+ | Варианты | `-Mode tunnel` (по умолчанию), `-Mode standalone`, `-User`, `-Gateway`, `-Token`, `-Port`, `-DryRun` | Быстрая настройка, Кастомная настройка, Удалённо по SSH |
842
+ | Язык | `-Lang en` / `-Lang ru` | переключатель в шапке окна или `-Lang` |
843
+
844
+ ### Автоматические зависимости и архитектура
845
+
846
+ Заранее готовить ничего не нужно. Оба установщика сами добывают всё необходимое, а если
847
+ не смогли — останавливаются с явным сообщением, вместо того чтобы прописать автозапуск,
848
+ который заведомо не заработает.
849
+
850
+ | | Windows (`install.ps1`) | Linux / macOS (`install.sh`) |
851
+ | :--- | :--- | :--- |
852
+ | Python | `winget` → установщик python.org для **этой** архитектуры → `uv` | `apt` / `dnf` / `yum` / `zypper` / `pacman` / `apk` / `xbps` / `brew` → `uv` |
853
+ | `websockets` | `pip` → `pip --user` → `ensurepip` → `uv` → venv | `pip` → `pip --break-system-packages` → `pip --user` → `ensurepip` → `uv` → venv |
854
+ | Если не вышло ничего | остановка и точная команда для ручного запуска | остановка и точная команда для ручного запуска |
855
+
856
+ `websockets` — единственная внешняя зависимость Python: `core/mcp_tools.py` и
857
+ `core/server.py` используют только стандартную библиотеку. `uv` скачивается лишь после
858
+ того, как более дешёвые пути уже не сработали.
859
+
860
+ **Архитектура определяет, какая сборка скачивается.** Установщик спрашивает саму *ОС*, а
861
+ не процесс: 32-битный PowerShell на 64-битной Windows сообщает `x86` и прячет настоящее
862
+ значение в `PROCESSOR_ARCHITEW6432`, а эмулируемый x64-процесс на ARM64 сообщает `AMD64`:
863
+
864
+ | Машина | Сборка Python | Архив `uv` |
865
+ | :--- | :--- | :--- |
866
+ | Windows x64 | `python-3.12.5-amd64.exe` | `x86_64-pc-windows-msvc` |
867
+ | Windows ARM64 | `python-3.12.5-arm64.exe` | `aarch64-pc-windows-msvc` |
868
+ | Windows 32-бит | `python-3.12.5.exe` | — (32-битных сборок uv для Windows нет) |
869
+ | Linux `x86_64` | сборка пакетного менеджера | `x86_64-unknown-linux-gnu` |
870
+ | Linux `aarch64` | сборка пакетного менеджера | `aarch64-unknown-linux-gnu` |
871
+ | Linux `armv7l` / `i686` | сборка пакетного менеджера | `armv7-unknown-linux-gnueabihf` / `i686-unknown-linux-gnu` |
872
+ | macOS `arm64` / `x86_64` | `brew` или `uv` | `aarch64-apple-darwin` / `x86_64-apple-darwin` |
873
+
874
+ В автозапуск прописывается тот интерпретатор, у которого реально есть `websockets`, — это
875
+ может быть venv или CPython, поставленный `uv`, и оба намеренно живут вне `PATH`.
876
+ `install.ps1 -DryRun` и `install.sh --dry-run` показывают архитектуру, ничего не меняя, а
877
+ визуальный установщик выводит её в предполётной проверке.
878
+
879
+ ### Готовый .exe установщика
880
+
881
+ В релизе есть один собранный установщик — `AntigravityMesh-Setup-<версия>.exe`, для машин,
882
+ где не хочется ничего клонировать:
883
+
884
+ ```powershell
885
+ .\AntigravityMesh-Setup-0.4.1.exe # открыть визуальный установщик
886
+ .\AntigravityMesh-Setup-0.4.1.exe -Lang ru # начать на русском
887
+ .\AntigravityMesh-Setup-0.4.1.exe -SelfTest # самопроверка без окна, печатает JSON
888
+ .\AntigravityMesh-Setup-0.4.1.exe --version # показать версию
889
+ ```
890
+
891
+ Внутри него лежат мастер, `install.ps1`, `core/` и `install.sh`; он распаковывает их в
892
+ `%LOCALAPPDATA%\AntigravityMesh\setup\<версия>` (можно переопределить переменной
893
+ `MESH_SETUP_DIR`) и запускает мастера оттуда. Ему нужны только .NET Framework и
894
+ PowerShell, которые в Windows уже есть, а собирается он из этого же репозитория скриптом
895
+ `.\build-installer-exe.ps1` — без SDK, без NuGet и без сети.
896
+
897
+ Файл **не подписан**, поэтому SmartScreen при первом запуске может попросить
898
+ подтверждение.
899
+
900
+ ### Визуальный установщик
901
+
902
+ ![Визуальный установщик для Windows](docs/images/gui-welcome-ru.png)
903
+
904
+ - **Быстрая настройка** (рекомендуется) — имя узла = имя ПК, общий домен шлюза, автозапуск Windows. Это вариант для домашнего ПК или ноутбука.
905
+ - **Кастомная настройка** — своё имя узла, свой общий домен, необязательный токен.
906
+ - **Удалённо по SSH** — код узла копируется на Linux-хост, там запускается `install.sh`. Нужен компонент Windows *«Клиент OpenSSH»* и вход по ключу; ввод пароля не поддерживается.
907
+
908
+ Первый экран запускает `install.ps1 -DryRun` и показывает, что найдено: Python, `websockets`, разрешённый домен шлюза, каталог конфигурации и путь автозапуска — ничего не меняя. Перед запуском окно показывает точную команду, во время установки — построчный вывод установщика, в конце — ссылку MCP с кнопкой копирования, инструкцию из трёх шагов и созданные пути.
909
+
910
+ Подробности, снимки экрана и особенности SSH: [docs/GUI_INSTALLER.md](docs/GUI_INSTALLER.md).
911
+
912
+ ### Что создаётся при установке на Windows
913
+
914
+ | Путь | Что это |
915
+ | :--- | :--- |
916
+ | `%USERPROFILE%\.config\antigravity-mesh\agent.env` | `MESH_GATEWAY`, `MESH_USER`, `MESH_TOKEN` |
917
+ | `%USERPROFILE%\.config\antigravity-mesh\domain.env` | `MESH_PUBLIC_URL` — разрешённый общий домен, чтобы ссылки узла, его туннель и шлюз называли один и тот же хост (не пишется, если настроено только встроенное значение по умолчанию) |
918
+ | `…\Start Menu\Programs\Startup\antigravity-agent.vbs` | запись автозапуска, чтобы узел поднимался после перезагрузки |
919
+ | `%USERPROFILE%\.config\antigravity-mesh\agent.log` | вывод агента — на случай, если автозапуск молча не сработал |
920
+
921
+ ### Язык
922
+
923
+ Визуальный установщик стартует на языке ваших языковых настроек Windows — язык интерфейса и список предпочитаемых языков читаются из реестра, — а если ничего не найдено, на английском. Флаг `-Lang ru` / `-Lang en` переопределяет автоопределение. Переключатель в шапке окна меняет все подписи сразу.
924
+
925
+ ### Если что-то не работает
926
+
927
+ - **«Домен шлюза не задан»** — эта копия не была опубликована с доменом. Укажите его: режим *«Кастомная настройка»* в мастере, либо `.\install.ps1 -Gateway <общий-домен>`, либо переменная `MESH_PUBLIC_URL`.
928
+ - **Регистрация не проходит** — установщик останавливается **до** записи файлов, если шлюз недоступен. Проверьте, что домен разрешается, и повторите с явным `-Gateway`.
929
+ - **`python` открывает Microsoft Store** — это заглушка App Execution Alias, а не интерпретатор. Оба установщика находят настоящий `python.exe` по полному пути; в автозапуск заглушка не попадает никогда.
930
+ - **Узел не поднимается после перезагрузки** — запустите `.\ops\doctor.ps1`: он покажет интерпретатор из автозапуска, свежесть heartbeat, последние строки `agent.log` (включая оборванную) и то, что о узле думает шлюз. `.\ops\windows\agent-watchdog.ps1` выполняет ту же проверку один раз и поднимает агент; задача `AntigravityMeshWatchdog` повторяет её каждые пять минут, поэтому умерший агент поднимается сам.
931
+
932
+ ### Автономный режим на Windows
933
+
934
+ В мастере его нет, используйте консольный установщик:
935
+
936
+ ```powershell
937
+ .\install.ps1 -Mode standalone -Port 8096
938
+ ```
939
+
940
+ Он поднимает локальный FastMCP-сервер на `http://localhost:8096/sse` и кладёт эту ссылку в буфер обмена.
941
+
942
+ ---
943
+
944
+ ## 📱 Установка на Android (Termux)
945
+
946
+ Тот же `install.sh` ставит узел на смартфон или планшет без root, и телефон
947
+ появляется в Gemini Spark как обычная машина. Полная инструкция:
948
+ **[docs/TERMUX.md](docs/TERMUX.md)**.
949
+
950
+ ```bash
951
+ # 1. Termux из F-Droid (не из Google Play); по желанию Termux:Boot и Termux:API
952
+ pkg update -y
953
+ # 2. обычная однострочная установка
954
+ curl -fsSL https://smart-server.online/install.sh | bash
955
+ ```
956
+
957
+ Что меняется на телефоне и как это решает установщик:
958
+
959
+ | | |
960
+ | :--- | :--- |
961
+ | **Пакеты** | `pkg` вместо `apt`, `python`/`python-pip` вместо `python3-pip`/`python3-venv`: на телефоне нет root и нет `sudo`. |
962
+ | **Имя узла** | Модель устройства (`Pixel 7 Pro` → `pixel7pro`): Android отвечает `localhost` всем приложениям, а шлюз держит один туннель на имя. |
963
+ | **Файл домена** | `~/.config/antigravity-mesh/domain.env` — писать в `/etc` некуда. Установщик записывает туда разрешённый общий домен, чтобы ссылки телефона, его туннель и шлюз называли один и тот же хост (встроенное значение по умолчанию не записывается). |
964
+ | **Автозапуск** | Служба **runit** (`termux-services`, аналог `Restart=always`) плюс скрипт **Termux:Boot**, который берёт wake lock после перезагрузки. |
965
+ | **Буфер обмена** | `termux-clipboard-set` (Termux:API): ссылка MCP сразу вставляется в приложение Gemini. |
966
+
967
+ > [!IMPORTANT]
968
+ > Два переключателя на стороне Android нужно включить вам: установить
969
+ > **Termux:Boot** и открыть его один раз, а также поставить оптимизацию батареи в
970
+ > «Без ограничений» для Termux. Без этого Android выгружает узел при выключенном
971
+ > экране.
972
+
973
+ Телефон также должен **резолвить имя шлюза**. Шлюз в приватной сети (Tailscale,
974
+ VPN, запись в DNS на ноутбуке) виден только там, а мобильная сеть приватные имена
975
+ не знает. Подключите телефон к этой сети либо дайте шлюзу публично резолвимое имя:
976
+ установщик распознаёт нерезолвимое имя и сообщает об этом до любых записей — см.
977
+ [docs/TERMUX.md](docs/TERMUX.md#10-приватный-шлюз-tailscale--vpn).
978
+
979
+ ```bash
980
+ sv status agy-agent # работает ли
981
+ sv restart agy-agent # перезапустить
982
+ tail -f $PREFIX/var/log/sv/agy-agent/current
983
+ ```
984
+
985
+ `--mode=standalone` тоже работает, но слушает только `127.0.0.1` — доступен лишь
986
+ внутри этого телефона. Чтобы управлять телефоном из Gemini, используйте режим
987
+ туннеля (по умолчанию).
988
+
989
+ ---
990
+
991
+ ## 🔄 Автоматические обновления
992
+
993
+ Узел устанавливается из релиза на GitHub и дальше поддерживает себя в актуальном
994
+ состоянии сам. Ничего не нужно переустанавливать руками, и ничего
995
+ непроверенного не устанавливается.
996
+
997
+ - **Узел проверяет сам.** Работающий агент раз в 6 часов
998
+ (`MESH_UPDATE_CHECK_INTERVAL`) спрашивает GitHub Releases API, сравнивает тег с
999
+ версией в `core/version.py` и ставит найденное. На Windows то же самое раз в
1000
+ сутки делает задача `AntigravityMeshUpdater` — даже если агент не запущен.
1001
+ `MESH_UPDATE_AUTO=0` отключает установку, оставляя только уведомления.
1002
+ - **Сначала проверка, потом установка.** Payload скачивается, его SHA-256
1003
+ сравнивается с опубликованной контрольной суммой, и только затем заменяются
1004
+ `core/`, `skills/` и `ops/`. Нет суммы — нет обновления.
1005
+ - **Откат возможен всегда.** Заменённые каталоги сохраняются в
1006
+ `%USERPROFILE%\.config\antigravity-mesh\backups\<версия>-<время>\`, а
1007
+ обновление, прерванное убийством процесса или отключением питания,
1008
+ откатывается автоматически при следующем запуске.
1009
+ - **Узел возвращается уже новым.** Агент перезапускается тем, что за ним
1010
+ присматривает (задача-сторож на Windows, `systemctl --user restart agy-agent.service`,
1011
+ `launchctl kickstart -k`), а если надзора нет — запускается напрямую.
1012
+
1013
+ Вручную, в любой момент:
1014
+
1015
+ ```powershell
1016
+ gemini-computer-use update -Check # вышел ли новый релиз?
1017
+ gemini-computer-use update # установить сейчас и перезапустить
1018
+ gemini-computer-use update -Check -Json
1019
+ ```
1020
+
1021
+ Из Gemini: попросите узел выполнить `mesh_update` (`status`, затем `check`, затем
1022
+ `apply`).
1023
+
1024
+ Результат последней проверки попадает в heartbeat, поэтому его показывает
1025
+ `ops\doctor.ps1`, а полная история — в `%USERPROFILE%\.config\antigravity-mesh\update.log`.
1026
+ Настройки, коды выхода, откат и диагностика: [docs/UPDATES.md](docs/UPDATES.md).
1027
+
324
1028
  ---
325
1029
 
326
1030
  ## 🔗 Один общий домен (канонический формат URL)
@@ -337,6 +1041,9 @@ https://smart-server.online/sse?user=<имя-вашего-узла>&token=<ва
337
1041
 
338
1042
  - Общий домен по умолчанию `smart-server.online`; переопределяется через `./install.sh --domain=<общий-домен>` (на стороне узла) или `MESH_PUBLIC_URL` (на стороне шлюза).
339
1043
  - Старые ссылки с субдоменами устройства продолжают работать для совместимости и пишут предупреждение в лог; `MESH_LEGACY_SUBDOMAIN=0` на шлюзе отключает их.
1044
+ - Установщик теперь записывает разрешённый домен в файл домена, чтобы публичные ссылки узла, его туннель и шлюз называли один и тот же хост: `MESH_PUBLIC_URL=https://<общий-домен>` в `domain.env` — в обеих ветках установки (standalone и «облачный шлюз/туннель» сразу после `agent.env`). Значение сначала нормализуется, плейсхолдер `__MESH_DOMAIN__` и пустое значение не пишутся никогда, уже названный хост не переписывается, а неудачная запись не фатальна — установщик печатает точную команду для ручного запуска. Встроенное значение по умолчанию не записывается вообще: это запасной вариант, а не конфигурация, и закрепление его в файле заставило бы `core/domain.py` вовсе перестать смотреть на устаревший `MESH_GATEWAY`, так что позднейшая правка `agent.env` молча игнорировалась бы — потерь нет, потому что без файла то же значение и есть последний запасной вариант резолвера. Сухой прогон сообщает файл и значение, которое было бы записано, — включая `not written (the built-in default is a fallback, not a configuration)`, — и по-прежнему ничего не меняет на диске.
1045
+ - Зачем это нужно: ссылки на файлы строит `core/domain.py` по цепочке `MESH_PUBLIC_URL` → `AGY_PUBLIC_BASE_URL` → файл `domain.env` → встроенное значение по умолчанию, а устаревший `MESH_GATEWAY` из `agent.env` он намеренно не читает (его читает только `gateway_host()` — хост туннеля). Поэтому узел, поставленный обычным способом (`agent.env` с `MESH_GATEWAY`/`MESH_USER`/`MESH_TOKEN` и без файла домена), звонил на правильный шлюз, а ссылки публиковал на встроенном домене. На уже установленном узле: повторите установку или допишите одну строку вручную — `mkdir -p ~/.config/antigravity-mesh && echo 'MESH_PUBLIC_URL=https://<домен>' > ~/.config/antigravity-mesh/domain.env`, в Linux то же через `sudo tee /etc/antigravity-mesh/domain.env`. Уже выданные ссылки продолжают работать, новые берут настроенный домен.
1046
+ - `MESH_DOMAIN_FILE` переопределяет только *путь*, и задавать его нужно в конфигурации самого узла, а не в запуске установщика: держите его в `agent.env` (все ключи `MESH_*` оттуда экспортируются узлу), иначе домен окажется в файле, который узел никогда не прочитает. Каталог того файла, который реально используется, установщик создаёт сам. При `--ssh=<хост>` разрешённый здесь домен передаётся удалённому `install.sh` как `--domain=`, поэтому цель записывает именно этот хост, а не разрешает домен заново (копия из репозитория с плейсхолдером `__MESH_DOMAIN__` иначе ушла бы на встроенное значение).
340
1047
 
341
1048
  ---
342
1049
 
@@ -362,6 +1069,16 @@ MESH_GATEWAY_SSH=root@<ip-шлюза> ./deploy_gateway.sh --dry-run # пред
362
1069
  ```
363
1070
  Скрипт проверяет каждую загрузку манифестом sha256 на хосте, делает бэкап с меткой времени, ставит файлы с нужным владельцем, перезапускает службу и завершает публичной проверкой здоровья. При несовпадении хэшей ничего не устанавливается.
364
1071
 
1072
+ > [!TIP]
1073
+ > **Домен задаётся в одном месте.** `MESH_PUBLIC_URL` в `domain.env`
1074
+ > (`/etc/antigravity-mesh/domain.env` в Linux, `%USERPROFILE%\.config\antigravity-mesh\domain.env`
1075
+ > в Windows) — единственная настройка, где он назван: узел, шлюз, установщики, публичные ссылки
1076
+ > на файлы и конфиги nginx берут его через `core/domain.py`. Смена домена — это одно значение и
1077
+ > `ops/nginx/render-domain.sh --apply`, подробности в [docs/DOMAIN.md](docs/DOMAIN.md).
1078
+
1079
+ > [!IMPORTANT]
1080
+ > **Кодировка `install.ps1` — один файл, два представления.** В репозитории файл лежит в UTF-8 *с* меткой BOM: Windows PowerShell 5.1 читает скрипт без метки в ANSI-кодировке, русский текст превращается в «умные кавычки», и парсер отвергает файл целиком — то есть `.\install.ps1` из клона просто не запустится. Копию, которую отдаёт шлюз, `deploy_gateway.sh` записывает *без* метки: `irm … | iex` получает метку как часть первого токена, и `param(...)` перестаёт быть первым оператором. Для этого адреса nginx объявляет `charset utf-8`, поэтому копия без метки читается корректно. Пожалуйста, сохраняйте оба свойства при правке файла.
1081
+
365
1082
  **Политика TLS-сертификата: только общий домен, без SAN на устройства.** Имена узлов в сертификат не добавляются никогда — узел выбирается `?user=`:
366
1083
 
367
1084
  ```bash