gemini-computer-use 0.2.3 → 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 +935 -148
  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 +688 -27
  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 +1949 -110
  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/core/domain.py ADDED
@@ -0,0 +1,303 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """core/domain.py - the ONE place that knows the project's public domain.
4
+
5
+ Every component that has to name the public host asks this module instead of
6
+ building its own chain of fallbacks:
7
+
8
+ * :mod:`gateway` (URLs and texts handed to clients),
9
+ * :mod:`core.agent` (the host a node dials for its tunnel),
10
+ * :mod:`core.web_share` (links published for shared files),
11
+ * :mod:`core.mcp_tools` (the ``public_url`` it passes on),
12
+ * :mod:`agy_sync`, :mod:`agy_watcher` (links printed into generated facts/logs).
13
+
14
+ Resolution order, highest priority first:
15
+
16
+ 1. ``configure(public_base_url=...)`` - explicit injection, for tests;
17
+ 2. ``MESH_PUBLIC_URL`` in the environment;
18
+ 3. ``AGY_PUBLIC_BASE_URL`` in the environment (legacy alias, same meaning);
19
+ 4. ``MESH_PUBLIC_URL`` (or ``AGY_PUBLIC_BASE_URL``) in the *domain file* - the one
20
+ host-level place to configure this:
21
+
22
+ ========== ==========================================================
23
+ Linux ``/etc/antigravity-mesh/domain.env``
24
+ Windows ``%USERPROFILE%\\.config\\antigravity-mesh\\domain.env``
25
+ Termux ``~/.config/antigravity-mesh/domain.env``
26
+ ========== ==========================================================
27
+
28
+ ``domain.env`` is plain ``KEY=VALUE`` and may carry a UTF-8 BOM, because
29
+ Windows PowerShell writes one. Point ``MESH_DOMAIN_FILE`` at another path to
30
+ override the location (used by the tests);
31
+ 5. ``MESH_GATEWAY`` in the environment - the legacy node-side setting. It is
32
+ honoured for the *tunnel host* only, so a node installed before ``domain.env``
33
+ existed keeps working until its operator moves the value into the domain file;
34
+ 6. :data:`DEFAULT_PUBLIC_BASE_URL` - the single default, declared once, here.
35
+
36
+ Nothing in this module imports another project module, and only the standard
37
+ library is used, so any component may import it - including ``gateway.py`` when it
38
+ is deployed on its own.
39
+ """
40
+
41
+ import os
42
+ import threading
43
+ from typing import Any, Dict, Optional
44
+ from urllib.parse import urlparse
45
+
46
+ #: The project default. Declared exactly once; every other module asks for it.
47
+ DEFAULT_PUBLIC_BASE_URL = "https://smart-server.online"
48
+
49
+ #: Neutral placeholder for *display* in an unconfigured deployment. It is not a
50
+ #: usable address and is never used to build a URL that a caller is expected to
51
+ #: reach - only tools that generate human-readable facts fall back to it, exactly
52
+ #: as they did before the domain moved here.
53
+ PLACEHOLDER_PUBLIC_BASE_URL = "https://<your-domain>"
54
+
55
+ #: ``KEY=VALUE`` keys understood in the environment and in the domain file.
56
+ _ENV_KEYS = ("MESH_PUBLIC_URL", "AGY_PUBLIC_BASE_URL")
57
+
58
+ #: Environment variables: the domain file location, and the legacy node host.
59
+ _DOMAIN_FILE_ENV = "MESH_DOMAIN_FILE"
60
+ _LEGACY_HOST_ENV = "MESH_GATEWAY"
61
+
62
+ _CONFIG_DIR_NAME = "antigravity-mesh"
63
+
64
+ _LOCK = threading.RLock()
65
+ _INJECTED: Optional[str] = None
66
+ _FILE_OVERRIDE: Optional[str] = None
67
+ #: (path, mtime) -> parsed values, so a per-request caller does not re-read the
68
+ #: file while an edit is still picked up (the mtime changes).
69
+ _FILE_CACHE: Dict[str, Any] = {"path": None, "mtime": None, "values": {}}
70
+
71
+
72
+ # ---------------------------------------------------------------------------
73
+ # The KEY=VALUE parser
74
+ # ---------------------------------------------------------------------------
75
+
76
+ def read_env_file(path: str) -> Dict[str, str]:
77
+ """Parse a KEY=VALUE file; tolerates a UTF-8 BOM (Windows PowerShell 5 writes one).
78
+
79
+ This is the only parser in the project: :mod:`core.agent` imports it from here
80
+ (and re-exports it, so callers of ``core.agent.read_env_file`` keep working).
81
+ """
82
+ values: Dict[str, str] = {}
83
+ try:
84
+ with open(path, "r", encoding="utf-8-sig") as handle:
85
+ for line in handle:
86
+ line = line.strip()
87
+ if not line or line.startswith("#") or "=" not in line:
88
+ continue
89
+ key, _, value = line.partition("=")
90
+ values[key.strip()] = value.strip().strip('"').strip("'")
91
+ except OSError:
92
+ pass
93
+ return values
94
+
95
+
96
+ # ---------------------------------------------------------------------------
97
+ # The domain file
98
+ # ---------------------------------------------------------------------------
99
+
100
+ def is_termux() -> bool:
101
+ """True when this process runs inside Termux on Android.
102
+
103
+ Termux is not FHS-compliant: a phone app is never root, there is no ``/etc``
104
+ at the usual place and no host-level configuration directory to put a domain
105
+ file in. The question is answered *here* rather than in a new module because
106
+ this one must stay importable on its own - ``gateway.py`` is deployed without
107
+ the rest of the package - and :mod:`core.agent` asks it too instead of
108
+ repeating the check.
109
+
110
+ Neither signal is trusted alone: Termux exports ``TERMUX_VERSION``, its
111
+ ``$PREFIX`` points inside ``com.termux``, and its application data directory
112
+ has a fixed path. A Linux host with an unrelated ``$PREFIX`` is not a phone.
113
+ """
114
+ if os.environ.get("TERMUX_VERSION"):
115
+ return True
116
+ if "com.termux" in os.environ.get("PREFIX", ""):
117
+ return True
118
+ return os.path.isdir("/data/data/com.termux/files/usr/bin")
119
+
120
+
121
+ def default_domain_file() -> str:
122
+ """Where ``domain.env`` lives when the operator did not override the path.
123
+
124
+ Linux keeps host-level configuration in ``/etc``. Windows and Termux have no
125
+ such place - Windows because the concept does not exist there, Termux because
126
+ a phone has no root and no ``/etc`` - so both use the per-user directory that
127
+ also holds ``agent.env``. ``install.sh`` resolves the same path for a Termux
128
+ install, so the installer and the node never disagree about where the domain
129
+ lives.
130
+ """
131
+ if os.name == "nt" or is_termux():
132
+ return os.path.join(os.path.expanduser("~"), ".config", _CONFIG_DIR_NAME, "domain.env")
133
+ return "/etc/%s/domain.env" % _CONFIG_DIR_NAME
134
+
135
+
136
+ def domain_file() -> str:
137
+ """The domain file in effect: injected path, then ``MESH_DOMAIN_FILE``, then default."""
138
+ with _LOCK:
139
+ override = _FILE_OVERRIDE
140
+ if override:
141
+ return override
142
+ return os.environ.get(_DOMAIN_FILE_ENV, "").strip() or default_domain_file()
143
+
144
+
145
+ def _domain_file_values() -> Dict[str, str]:
146
+ """Parsed domain file, cached by modification time."""
147
+ path = domain_file()
148
+ try:
149
+ mtime = os.stat(path).st_mtime
150
+ except OSError:
151
+ with _LOCK:
152
+ _FILE_CACHE.update({"path": path, "mtime": None, "values": {}})
153
+ return {}
154
+ with _LOCK:
155
+ if _FILE_CACHE["path"] == path and _FILE_CACHE["mtime"] == mtime:
156
+ return dict(_FILE_CACHE["values"])
157
+ values = read_env_file(path)
158
+ with _LOCK:
159
+ _FILE_CACHE.update({"path": path, "mtime": mtime, "values": values})
160
+ return dict(values)
161
+
162
+
163
+ def _invalidate_file_cache() -> None:
164
+ with _LOCK:
165
+ _FILE_CACHE.update({"path": None, "mtime": None, "values": {}})
166
+
167
+
168
+ # ---------------------------------------------------------------------------
169
+ # Resolution
170
+ # ---------------------------------------------------------------------------
171
+
172
+ def normalise_public_base_url(value: Any) -> str:
173
+ """``mesh.example.com/`` and ``https://mesh.example.com/`` -> ``https://mesh.example.com``.
174
+
175
+ A bare host gains the ``https://`` scheme; a trailing slash is dropped so that
176
+ joining a path never produces a double slash. An explicit ``http://`` is left
177
+ alone - the operator may have a good reason. Public because callers that accept
178
+ a domain of their own (``core.mcp_tools.configure(public_url=...)``) normalise it
179
+ through the same rules instead of inventing their own.
180
+ """
181
+ text = str(value or "").strip().strip('"').strip("'").rstrip("/")
182
+ if not text:
183
+ return ""
184
+ if not text.startswith(("http://", "https://")):
185
+ text = "https://" + text
186
+ return text.rstrip("/")
187
+
188
+
189
+ def _resolve() -> Dict[str, str]:
190
+ """``{"base": ..., "source": "injected|env|file|default"}`` for the current state."""
191
+ with _LOCK:
192
+ injected = _INJECTED
193
+ if injected:
194
+ return {"base": injected, "source": "injected"}
195
+
196
+ for key in _ENV_KEYS:
197
+ value = normalise_public_base_url(os.environ.get(key))
198
+ if value:
199
+ return {"base": value, "source": "env"}
200
+
201
+ file_values = _domain_file_values()
202
+ for key in _ENV_KEYS:
203
+ value = normalise_public_base_url(file_values.get(key))
204
+ if value:
205
+ return {"base": value, "source": "file"}
206
+
207
+ return {"base": DEFAULT_PUBLIC_BASE_URL, "source": "default"}
208
+
209
+
210
+ def public_base_url() -> str:
211
+ """Public base URL, e.g. ``https://mesh.example.com`` - never with a trailing slash."""
212
+ return _resolve()["base"]
213
+
214
+
215
+ def public_host() -> str:
216
+ """Host part of :func:`public_base_url`, e.g. ``mesh.example.com``."""
217
+ base = public_base_url()
218
+ return urlparse(base).netloc or base
219
+
220
+
221
+ def public_url(path: str = "") -> str:
222
+ """``public_base_url()`` joined with *path* (this is not gateway.public_url()).
223
+
224
+ ``gateway.public_url(path, user, token)`` additionally appends the node
225
+ credentials; this one is the plain join that share links and log links need.
226
+ """
227
+ base = public_base_url()
228
+ if not path:
229
+ return base
230
+ return base + (path if path.startswith("/") else "/" + path)
231
+
232
+
233
+ def gateway_host() -> str:
234
+ """Authority a node dials for its tunnel (no scheme, unless legacy carries one).
235
+
236
+ The configured public base wins, so moving the domain into ``domain.env`` moves
237
+ the tunnel with it. Only when nothing is configured does the legacy
238
+ ``MESH_GATEWAY`` decide, and a legacy value that already carries a scheme (for
239
+ example ``ws://10.0.0.1``) is passed through untouched, because the node's
240
+ tunnel builder honours such a scheme.
241
+ """
242
+ resolved = _resolve()
243
+ if resolved["source"] != "default":
244
+ parsed = urlparse(resolved["base"])
245
+ authority = parsed.netloc or parsed.path
246
+ if parsed.netloc and parsed.path:
247
+ authority = parsed.netloc + parsed.path
248
+ return authority
249
+
250
+ legacy = str(os.environ.get(_LEGACY_HOST_ENV, "") or "").strip().rstrip("/")
251
+ if legacy:
252
+ return legacy
253
+ return urlparse(DEFAULT_PUBLIC_BASE_URL).netloc or DEFAULT_PUBLIC_BASE_URL
254
+
255
+
256
+ def source() -> str:
257
+ """Where the current value comes from: ``injected``, ``env``, ``file`` or ``default``."""
258
+ return _resolve()["source"]
259
+
260
+
261
+ def is_explicit() -> bool:
262
+ """True when the domain was configured (by injection, environment or file).
263
+
264
+ Tools that must not assume a deployment - the ones that print neutral facts -
265
+ use this to decide between the configured domain and
266
+ :data:`PLACEHOLDER_PUBLIC_BASE_URL`.
267
+ """
268
+ return source() != "default"
269
+
270
+
271
+ # ---------------------------------------------------------------------------
272
+ # Injection (tests, and embedders that resolve the domain themselves)
273
+ # ---------------------------------------------------------------------------
274
+
275
+ def configure(**kwargs: Any) -> None:
276
+ """Inject the domain instead of reading the environment and the file.
277
+
278
+ Recognised keys:
279
+
280
+ ``public_base_url``
281
+ The value to use. ``None`` or ``""`` drops a previous injection, so the
282
+ environment and the domain file decide again.
283
+ ``domain_file``
284
+ Alternative ``domain.env`` path. ``None`` or ``""`` restores the
285
+ environment/default location.
286
+ """
287
+ global _INJECTED, _FILE_OVERRIDE
288
+ with _LOCK:
289
+ if "public_base_url" in kwargs:
290
+ _INJECTED = normalise_public_base_url(kwargs.get("public_base_url")) or None
291
+ if "domain_file" in kwargs:
292
+ value = str(kwargs.get("domain_file") or "").strip()
293
+ _FILE_OVERRIDE = value or None
294
+ _invalidate_file_cache()
295
+
296
+
297
+ def reset() -> None:
298
+ """Drop injection and cached file contents (call between tests)."""
299
+ global _INJECTED, _FILE_OVERRIDE
300
+ with _LOCK:
301
+ _INJECTED = None
302
+ _FILE_OVERRIDE = None
303
+ _invalidate_file_cache()