gemini-computer-use 0.2.4 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +741 -24
- package/bin/cli.js +112 -17
- package/core/agent.py +503 -18
- package/core/agent_harness.py +9 -2
- package/core/code_cache.py +15 -3
- package/core/device.py +2400 -0
- package/core/domain.py +303 -0
- package/core/mcp_tools.py +1840 -112
- package/core/policies.py +35 -5
- package/core/server.py +78 -32
- package/core/termux.py +916 -0
- package/core/updater.py +1713 -0
- package/core/version.py +31 -0
- package/core/vitals.py +66 -2
- package/core/web_share.py +814 -0
- package/gateway.py +1469 -98
- package/install-gui.cmd +40 -0
- package/install-gui.ps1 +1882 -0
- package/install-gui.strings.json +211 -0
- package/install.ps1 +960 -92
- package/install.sh +1214 -81
- package/ops/doctor.ps1 +735 -0
- package/ops/mesh-domain.sh +86 -0
- package/ops/nginx/README.md +77 -0
- package/ops/nginx/antigravity-mesh-mcp.conf +172 -0
- package/ops/nginx/mesh-domain.vhost.template +58 -0
- package/ops/nginx/render-domain.sh +123 -0
- package/ops/update.ps1 +373 -0
- package/ops/windows/README.md +298 -0
- package/ops/windows/agent-watchdog.ps1 +575 -0
- package/ops/windows/run-hidden.vbs +64 -0
- package/package.json +8 -3
- package/skills/orchestrator.md +12 -0
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
# ==============================================================================
|
|
2
|
+
# Antigravity Mesh - Windows agent watchdog (single shot supervisor)
|
|
3
|
+
#
|
|
4
|
+
# Meant to be run every 5 minutes by the Scheduled Task "AntigravityMeshWatchdog".
|
|
5
|
+
# It answers one question: is this node's agent alive, and if it is not, start it
|
|
6
|
+
# exactly the way the logon launcher would?
|
|
7
|
+
#
|
|
8
|
+
# WHY THIS EXISTS
|
|
9
|
+
# After a Windows Update reboot the logon launcher silently did nothing. The
|
|
10
|
+
# interpreter pinned in the Startup .vbs was the Microsoft Store App Execution
|
|
11
|
+
# Alias <...>\WindowsApps\python.exe: a file that exists and can be launched, but
|
|
12
|
+
# runs no Python at all. The only trace was a truncated "Python " line appended to
|
|
13
|
+
# agent.log without a trailing newline, and the node stayed offline for ~29 hours
|
|
14
|
+
# while the Gemini client showed a generic frontend error. This watchdog refuses
|
|
15
|
+
# that interpreter outright, and recovers the node from a pinned, verified one.
|
|
16
|
+
#
|
|
17
|
+
# WHAT IT DOES, IN ORDER
|
|
18
|
+
# 1. reads <ConfigDir>\agent.env (MESH_GATEWAY / MESH_USER / MESH_TOKEN);
|
|
19
|
+
# 2. reads the Startup launcher and takes the interpreter from its
|
|
20
|
+
# WshShell.Run line and the payload directory from WshShell.CurrentDirectory;
|
|
21
|
+
# 3. refuses (exit 2) when the interpreter is missing, is not a file, or lives
|
|
22
|
+
# under \WindowsApps\;
|
|
23
|
+
# 4. probes liveness: (a) a fresh <ConfigDir>\agent.heartbeat whose ts is newer
|
|
24
|
+
# than -GraceSeconds (default 60), else (b) the gateway's
|
|
25
|
+
# /health?user=<node> reporting node_online=true, else (c) a verified agent
|
|
26
|
+
# process - a python.exe/pythonw.exe/py.exe running "-m core.agent", or the
|
|
27
|
+
# cmd.exe launcher wrapper that also carries the agent.log redirect. A
|
|
28
|
+
# process that merely MENTIONS core.agent (a PowerShell session, a grep)
|
|
29
|
+
# never counts, or a dead node would look alive forever;
|
|
30
|
+
# 5. when the node is dead, appends exactly one diagnostic line to agent.log,
|
|
31
|
+
# starts the agent hidden with the same command line / environment / working
|
|
32
|
+
# directory contract as the .vbs, waits up to 20 s for liveness and logs the
|
|
33
|
+
# outcome.
|
|
34
|
+
#
|
|
35
|
+
# EXIT CODES
|
|
36
|
+
# 0 the node is online (already was, or was recovered by this run), or another
|
|
37
|
+
# watchdog instance is currently handling it
|
|
38
|
+
# 2 configuration / launcher problem: agent.env, the Startup .vbs or the pinned
|
|
39
|
+
# interpreter is missing, unusable, or is the Microsoft Store alias
|
|
40
|
+
# 3 the node is still offline after the recovery attempt
|
|
41
|
+
#
|
|
42
|
+
# PARAMETERS
|
|
43
|
+
# -ConfigDir where agent.env / agent.log / agent.heartbeat live.
|
|
44
|
+
# Default: %USERPROFILE%\.config\antigravity-mesh
|
|
45
|
+
# -LogFile this script's journal. Default: <ConfigDir>\watchdog.log
|
|
46
|
+
# -GraceSeconds heartbeat freshness threshold, in seconds, default 60: a
|
|
47
|
+
# heartbeat whose ts is older than this is not fresh. The agent
|
|
48
|
+
# beats every 15 s, so the default tolerates three missed beats.
|
|
49
|
+
# -Quiet write to -LogFile only, print nothing to the console.
|
|
50
|
+
#
|
|
51
|
+
# IDEMPOTENT BY DESIGN
|
|
52
|
+
# * A watchdog that sees a live node (or a verified agent process) never starts
|
|
53
|
+
# a second one.
|
|
54
|
+
# * A named mutex serialises overlapping runs, so a slow run cannot double-start
|
|
55
|
+
# the agent and the script never depends on the task scheduler being up.
|
|
56
|
+
# * The token is never printed and never written to any log; it is masked as
|
|
57
|
+
# "****" wherever a human would see it.
|
|
58
|
+
#
|
|
59
|
+
# This file is deliberately ASCII-only: Windows PowerShell 5.1 reads a BOM-less
|
|
60
|
+
# script with the ANSI code page, so non-ASCII text here is a portability bug.
|
|
61
|
+
#
|
|
62
|
+
# Usage:
|
|
63
|
+
# .\agent-watchdog.ps1
|
|
64
|
+
# .\agent-watchdog.ps1 -ConfigDir "$env:USERPROFILE\.config\antigravity-mesh"
|
|
65
|
+
# .\agent-watchdog.ps1 -GraceSeconds 120
|
|
66
|
+
# .\agent-watchdog.ps1 -Quiet -LogFile C:\logs\watchdog.log
|
|
67
|
+
# ==============================================================================
|
|
68
|
+
[CmdletBinding()]
|
|
69
|
+
param(
|
|
70
|
+
[string]$ConfigDir = '',
|
|
71
|
+
[string]$LogFile = '',
|
|
72
|
+
[int]$GraceSeconds = 60,
|
|
73
|
+
[switch]$Quiet
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
$ErrorActionPreference = 'Continue'
|
|
77
|
+
|
|
78
|
+
# ------------------------------------------------------------------ logging ---
|
|
79
|
+
# Two logs are in play and they must never be confused:
|
|
80
|
+
# * agent.log - the running agent's stdout/stderr; the watchdog appends ONE
|
|
81
|
+
# diagnostic line to it so the reason for a restart is visible
|
|
82
|
+
# next to the failure that caused it;
|
|
83
|
+
# * watchdog.log - this script's own journal (-LogFile), append-only.
|
|
84
|
+
$script:QuietMode = [bool]$Quiet
|
|
85
|
+
$script:LogTarget = ''
|
|
86
|
+
$script:Utf8NoBom = New-Object System.Text.UTF8Encoding -ArgumentList @($false)
|
|
87
|
+
|
|
88
|
+
function Write-LogLine {
|
|
89
|
+
param([string]$Text)
|
|
90
|
+
if ([string]::IsNullOrEmpty($script:LogTarget)) { return }
|
|
91
|
+
try {
|
|
92
|
+
$stamp = (Get-Date).ToString('yyyy-MM-ddTHH:mm:ssK')
|
|
93
|
+
[System.IO.File]::AppendAllText($script:LogTarget, "[$stamp] $Text" + [Environment]::NewLine, $script:Utf8NoBom)
|
|
94
|
+
} catch { }
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function Write-Note {
|
|
98
|
+
param([string]$Text)
|
|
99
|
+
Write-LogLine $Text
|
|
100
|
+
if (-not $script:QuietMode) { Write-Host $Text }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function Stop-WithConfigError {
|
|
104
|
+
param([string]$Message)
|
|
105
|
+
Write-Note ("CONFIG ERROR: " + $Message)
|
|
106
|
+
exit 2
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function Add-AgentLogLine {
|
|
110
|
+
param([string]$Line)
|
|
111
|
+
$path = Join-Path $ConfigDir 'agent.log'
|
|
112
|
+
$single = ($Line -replace '[\r\n]+', ' ').Trim()
|
|
113
|
+
try {
|
|
114
|
+
[System.IO.File]::AppendAllText($path, $single + [Environment]::NewLine, $script:Utf8NoBom)
|
|
115
|
+
return $true
|
|
116
|
+
} catch {
|
|
117
|
+
Write-Note ("could not append to " + $path + ": " + $_.Exception.Message)
|
|
118
|
+
return $false
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
# ------------------------------------------------------------------- secrets --
|
|
123
|
+
# Anything a human can read goes through here. The literal secret is replaced when
|
|
124
|
+
# it is long enough to be unambiguous, and the two shipped shapes of the token
|
|
125
|
+
# (agent.env KEY=value and the .vbs Environment(...) call) are always masked, so a
|
|
126
|
+
# short token cannot leak through pattern matching alone.
|
|
127
|
+
function Hide-Secret {
|
|
128
|
+
param([string]$Text, [string]$Secret)
|
|
129
|
+
if ([string]::IsNullOrEmpty($Text)) { return $Text }
|
|
130
|
+
$result = $Text
|
|
131
|
+
if ($Secret -and $Secret.Length -ge 4) { $result = $result.Replace($Secret, '****') }
|
|
132
|
+
$result = [regex]::Replace($result, '(?im)^(\s*MESH_TOKEN\s*=\s*).*$', '${1}****')
|
|
133
|
+
$result = [regex]::Replace($result, '(?im)(MESH_TOKEN"\)\s*=\s*").*?(")', '${1}****${2}')
|
|
134
|
+
return $result
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
# ---------------------------------------------------------------- file input --
|
|
138
|
+
function Read-TextFile {
|
|
139
|
+
param([string]$Path)
|
|
140
|
+
$bytes = [System.IO.File]::ReadAllBytes($Path)
|
|
141
|
+
if ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) {
|
|
142
|
+
return [System.Text.Encoding]::UTF8.GetString($bytes, 3, $bytes.Length - 3)
|
|
143
|
+
}
|
|
144
|
+
if ($bytes.Length -ge 2 -and $bytes[0] -eq 0xFF -and $bytes[1] -eq 0xFE) {
|
|
145
|
+
return [System.Text.Encoding]::Unicode.GetString($bytes, 2, $bytes.Length - 2)
|
|
146
|
+
}
|
|
147
|
+
if ($bytes.Length -ge 2 -and $bytes[0] -eq 0xFE -and $bytes[1] -eq 0xFF) {
|
|
148
|
+
return [System.Text.Encoding]::BigEndianUnicode.GetString($bytes, 2, $bytes.Length - 2)
|
|
149
|
+
}
|
|
150
|
+
# No BOM: UTF-16 without a BOM is full of zero bytes, UTF-8/ANSI is not. The
|
|
151
|
+
# launcher is written as UTF-16 ("Unicode") by install.ps1, but a hand-edited
|
|
152
|
+
# file may be ANSI or UTF-8, and both must be readable.
|
|
153
|
+
$hasNul = $false
|
|
154
|
+
$limit = [Math]::Min($bytes.Length, 512)
|
|
155
|
+
for ($i = 0; $i -lt $limit; $i++) {
|
|
156
|
+
if ($bytes[$i] -eq 0) { $hasNul = $true; break }
|
|
157
|
+
}
|
|
158
|
+
if ($hasNul) { return [System.Text.Encoding]::Unicode.GetString($bytes) }
|
|
159
|
+
return [System.Text.Encoding]::UTF8.GetString($bytes)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function Read-EnvFile {
|
|
163
|
+
param([string]$Path)
|
|
164
|
+
$values = @{}
|
|
165
|
+
try { $lines = @(Get-Content -LiteralPath $Path -Encoding UTF8 -ErrorAction Stop) } catch { return $values }
|
|
166
|
+
foreach ($line in $lines) {
|
|
167
|
+
if ($null -eq $line) { continue }
|
|
168
|
+
$text = ([string]$line).Trim()
|
|
169
|
+
if ($text.Length -eq 0) { continue }
|
|
170
|
+
if ($text.StartsWith('#')) { continue }
|
|
171
|
+
$index = $text.IndexOf('=')
|
|
172
|
+
if ($index -lt 1) { continue }
|
|
173
|
+
$key = $text.Substring(0, $index).Trim()
|
|
174
|
+
$value = $text.Substring($index + 1).Trim().Trim('"').Trim("'")
|
|
175
|
+
$values[$key] = $value
|
|
176
|
+
}
|
|
177
|
+
return $values
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
# ------------------------------------------------------------ launcher (.vbs) --
|
|
181
|
+
function Get-LauncherInfo {
|
|
182
|
+
param([string]$VbsPath)
|
|
183
|
+
$info = @{ Present = $false; Text = ''; Interpreter = ''; PayloadDir = ''; Error = '' }
|
|
184
|
+
if (-not (Test-Path -LiteralPath $VbsPath -PathType Leaf)) { $info.Error = 'not found'; return $info }
|
|
185
|
+
$info.Present = $true
|
|
186
|
+
try {
|
|
187
|
+
$info.Text = Read-TextFile $VbsPath
|
|
188
|
+
} catch {
|
|
189
|
+
$info.Error = $_.Exception.Message
|
|
190
|
+
return $info
|
|
191
|
+
}
|
|
192
|
+
# The Run line, as written by install.ps1:
|
|
193
|
+
# WshShell.Run "cmd /c """"<python.exe>"" -u -m core.agent >> """"<agent.log>"""" 2>&1""", 0, False
|
|
194
|
+
# The number of doubled quotes is a function of which layer wrote the file, so
|
|
195
|
+
# the pattern tolerates one or more quote characters on either side of the path.
|
|
196
|
+
# The pattern stops at the line boundary ([^\r\n]*, no $ anchor: a .vbs written
|
|
197
|
+
# by Out-File has CRLF endings and .NET's multiline $ refuses to match before
|
|
198
|
+
# the \r of a CRLF pair).
|
|
199
|
+
$run = [regex]::Match($info.Text, '(?im)^[^\r\n]*WshShell\.Run[^\r\n]*?cmd[ \t]+/c[ \t]+"+(?<exe>[^"]+?)"+[ \t]+(?:-u[ \t]+)?-m[ \t]+core\.agent[^\r\n]*')
|
|
200
|
+
if (-not $run.Success) {
|
|
201
|
+
# A launcher that no longer spells out "-m core.agent" is still worth
|
|
202
|
+
# reading: take the first absolute executable on the Run line.
|
|
203
|
+
$run = [regex]::Match($info.Text, '(?im)^[^\r\n]*WshShell\.Run[^\r\n]*?(?<exe>[A-Za-z]:\\[^"]*?\.(?:exe|cmd|bat))')
|
|
204
|
+
}
|
|
205
|
+
if (-not $run.Success) {
|
|
206
|
+
$run = [regex]::Match($info.Text, '(?im)^[^\r\n]*WshShell\.Run[^\r\n]*?cmd[ \t]+/c[ \t]+"+(?<exe>[^"]+?)"+')
|
|
207
|
+
}
|
|
208
|
+
if ($run.Success) { $info.Interpreter = $run.Groups['exe'].Value.Trim() }
|
|
209
|
+
$dir = [regex]::Match($info.Text, '(?im)^[^\r\n]*WshShell\.CurrentDirectory[ \t]*=[ \t]*"(?<dir>[^"]+)"')
|
|
210
|
+
if ($dir.Success) { $info.PayloadDir = $dir.Groups['dir'].Value.Trim() }
|
|
211
|
+
return $info
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
# ------------------------------------------------------------- liveness (a/b/c) --
|
|
215
|
+
function ConvertTo-UtcDateTime {
|
|
216
|
+
param($Value)
|
|
217
|
+
if ($null -eq $Value) { return $null }
|
|
218
|
+
if ($Value -is [datetime]) {
|
|
219
|
+
$stamp = [datetime]$Value
|
|
220
|
+
if ($stamp.Kind -eq [System.DateTimeKind]::Utc) { return $stamp }
|
|
221
|
+
if ($stamp.Kind -eq [System.DateTimeKind]::Local) { return $stamp.ToUniversalTime() }
|
|
222
|
+
return [System.DateTime]::SpecifyKind($stamp, [System.DateTimeKind]::Utc)
|
|
223
|
+
}
|
|
224
|
+
$text = ([string]$Value).Trim().Trim('"')
|
|
225
|
+
if ($text.Length -eq 0) { return $null }
|
|
226
|
+
$epoch = New-Object System.DateTime -ArgumentList @(1970, 1, 1, 0, 0, 0, [System.DateTimeKind]::Utc)
|
|
227
|
+
$number = 0.0
|
|
228
|
+
$isNumber = $false
|
|
229
|
+
if ($Value -is [double] -or $Value -is [single] -or $Value -is [decimal] -or $Value -is [int] -or $Value -is [long]) {
|
|
230
|
+
$number = [double]$Value
|
|
231
|
+
$isNumber = $true
|
|
232
|
+
} elseif ([double]::TryParse($text, [System.Globalization.NumberStyles]::Float, [System.Globalization.CultureInfo]::InvariantCulture, [ref]$number)) {
|
|
233
|
+
$isNumber = $true
|
|
234
|
+
}
|
|
235
|
+
# core/agent.py writes "ts" as a Unix timestamp in seconds, often fractional.
|
|
236
|
+
# Numbers are read with the invariant culture on purpose: on a comma-decimal
|
|
237
|
+
# locale "1767225600.25" would otherwise fail to parse and a live node would be
|
|
238
|
+
# declared dead.
|
|
239
|
+
if ($isNumber) {
|
|
240
|
+
# Unix seconds (10 digits) or Unix milliseconds (13 digits).
|
|
241
|
+
if ($number -gt 100000000000.0) { return $epoch.AddMilliseconds($number) }
|
|
242
|
+
if ($number -gt 100000000.0) { return $epoch.AddSeconds($number) }
|
|
243
|
+
return $null
|
|
244
|
+
}
|
|
245
|
+
$styles = [System.Globalization.DateTimeStyles]::AdjustToUniversal -bor [System.Globalization.DateTimeStyles]::AssumeUniversal
|
|
246
|
+
$parsed = [datetime]::MinValue
|
|
247
|
+
if ([datetime]::TryParse($text, [System.Globalization.CultureInfo]::InvariantCulture, $styles, [ref]$parsed)) {
|
|
248
|
+
return [System.DateTime]::SpecifyKind($parsed, [System.DateTimeKind]::Utc)
|
|
249
|
+
}
|
|
250
|
+
return $null
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function Get-HeartbeatState {
|
|
254
|
+
param([string]$Path, [int]$Grace)
|
|
255
|
+
$state = @{ Present = $false; Parsed = $false; AgeSeconds = $null; Fresh = $false; Detail = 'missing' }
|
|
256
|
+
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { return $state }
|
|
257
|
+
$state.Present = $true
|
|
258
|
+
try {
|
|
259
|
+
$raw = [System.IO.File]::ReadAllText($Path)
|
|
260
|
+
} catch {
|
|
261
|
+
$state.Detail = 'unreadable: ' + $_.Exception.Message
|
|
262
|
+
return $state
|
|
263
|
+
}
|
|
264
|
+
try {
|
|
265
|
+
$json = $raw | ConvertFrom-Json
|
|
266
|
+
} catch {
|
|
267
|
+
$state.Detail = 'not valid JSON'
|
|
268
|
+
return $state
|
|
269
|
+
}
|
|
270
|
+
$state.Parsed = $true
|
|
271
|
+
$stampRaw = $null
|
|
272
|
+
foreach ($name in @('ts', 'timestamp', 'time', 'last_seen', 'checked_at')) {
|
|
273
|
+
$prop = $json.PSObject.Properties[$name]
|
|
274
|
+
if ($prop -and $null -ne $prop.Value -and ([string]$prop.Value).Trim().Length -gt 0) {
|
|
275
|
+
$stampRaw = $prop.Value
|
|
276
|
+
break
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
if ($null -eq $stampRaw) { $state.Detail = 'JSON has no ts field'; return $state }
|
|
280
|
+
$stamp = ConvertTo-UtcDateTime $stampRaw
|
|
281
|
+
if ($null -eq $stamp) { $state.Detail = 'ts is not a timestamp: ' + ([string]$stampRaw); return $state }
|
|
282
|
+
$age = ((Get-Date).ToUniversalTime() - $stamp.ToUniversalTime()).TotalSeconds
|
|
283
|
+
$state.AgeSeconds = [Math]::Round($age, 1)
|
|
284
|
+
if ($age -le $Grace) {
|
|
285
|
+
$state.Fresh = $true
|
|
286
|
+
$state.Detail = 'fresh (' + $state.AgeSeconds + 's old)'
|
|
287
|
+
} else {
|
|
288
|
+
$state.Detail = 'stale (' + $state.AgeSeconds + 's old, grace ' + $Grace + 's)'
|
|
289
|
+
}
|
|
290
|
+
return $state
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function Get-GatewayHealth {
|
|
294
|
+
param([string]$Gateway, [string]$NodeUser, [int]$TimeoutSec = 10)
|
|
295
|
+
$state = @{ Url = ''; Ok = $false; Text = ''; Detail = 'not checked' }
|
|
296
|
+
if ([string]::IsNullOrWhiteSpace($Gateway) -or [string]::IsNullOrWhiteSpace($NodeUser)) {
|
|
297
|
+
$state.Detail = 'gateway or node name missing'
|
|
298
|
+
return $state
|
|
299
|
+
}
|
|
300
|
+
$hostName = ($Gateway.Trim() -replace '^[A-Za-z][A-Za-z0-9+.-]*://', '').Trim().TrimEnd('/')
|
|
301
|
+
if ($hostName.Length -eq 0) {
|
|
302
|
+
$state.Detail = 'gateway is empty after normalisation'
|
|
303
|
+
return $state
|
|
304
|
+
}
|
|
305
|
+
$state.Url = 'https://' + $hostName + '/health?user=' + [uri]::EscapeDataString($NodeUser)
|
|
306
|
+
try {
|
|
307
|
+
$response = Invoke-RestMethod -Uri $state.Url -Method Get -TimeoutSec $TimeoutSec -ErrorAction Stop
|
|
308
|
+
} catch {
|
|
309
|
+
$state.Detail = 'unreachable: ' + $_.Exception.Message
|
|
310
|
+
return $state
|
|
311
|
+
}
|
|
312
|
+
try { $state.Text = ($response | ConvertTo-Json -Compress -Depth 5) } catch { $state.Text = '' }
|
|
313
|
+
$prop = $response.PSObject.Properties['node_online']
|
|
314
|
+
$online = $false
|
|
315
|
+
if ($prop) {
|
|
316
|
+
if ($prop.Value -is [bool]) { $online = [bool]$prop.Value } else { $online = (([string]$prop.Value).Trim().ToLower() -eq 'true') }
|
|
317
|
+
}
|
|
318
|
+
$state.Ok = $online
|
|
319
|
+
if ($online) { $state.Detail = 'node_online=true' } else { $state.Detail = 'node_online=false' }
|
|
320
|
+
return $state
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function Test-AgentProcess {
|
|
324
|
+
# The command line ALONE is not evidence. A PowerShell session that merely
|
|
325
|
+
# mentions "core.agent" (a grep, a script that prints the module name) matches
|
|
326
|
+
# the bare substring, so a dead node would look alive and would never be
|
|
327
|
+
# restarted. The process must be a Python interpreter running the module, or
|
|
328
|
+
# the cmd.exe wrapper the logon launcher uses - which also carries the
|
|
329
|
+
# agent.log append redirect. PowerShell, wscript and everything else never
|
|
330
|
+
# qualifies, whatever its command line says.
|
|
331
|
+
param([string]$Name, [string]$CommandLine)
|
|
332
|
+
if ([string]::IsNullOrWhiteSpace($CommandLine)) { return $false }
|
|
333
|
+
$processName = ''
|
|
334
|
+
if ($Name) { $processName = ([string]$Name).Trim().ToLowerInvariant() }
|
|
335
|
+
foreach ($shell in @('powershell', 'pwsh', 'wscript', 'cscript')) {
|
|
336
|
+
if ($processName.StartsWith($shell)) { return $false }
|
|
337
|
+
}
|
|
338
|
+
# "-m core.agent" has to be a real argument, so it must open the command line
|
|
339
|
+
# or follow a space or a quote.
|
|
340
|
+
if ($CommandLine -notmatch '(?i)(^|[\s"])-m[\s"]+core\.agent\b') { return $false }
|
|
341
|
+
if ($processName -eq 'py.exe') { return $true }
|
|
342
|
+
if ($processName.StartsWith('python')) { return $true }
|
|
343
|
+
if ($processName -eq 'cmd.exe' -or $processName -eq 'cmd') {
|
|
344
|
+
# Launcher wrapper: cmd /c ""<python>" -u -m core.agent >> "<agent.log>" 2>&1"
|
|
345
|
+
return [bool]($CommandLine -match '(?i)agent\.log')
|
|
346
|
+
}
|
|
347
|
+
return $false
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function Get-AgentProcesses {
|
|
351
|
+
# A verified agent process is the third and last liveness signal: it is the
|
|
352
|
+
# only one that still works when the gateway is unreachable and the heartbeat
|
|
353
|
+
# was never written.
|
|
354
|
+
$found = @()
|
|
355
|
+
$procs = @()
|
|
356
|
+
try {
|
|
357
|
+
$procs = @(Get-CimInstance -ClassName Win32_Process -ErrorAction Stop)
|
|
358
|
+
} catch {
|
|
359
|
+
try { $procs = @(Get-WmiObject -Class Win32_Process -ErrorAction Stop) } catch { $procs = @() }
|
|
360
|
+
}
|
|
361
|
+
foreach ($proc in $procs) {
|
|
362
|
+
$commandLine = ''
|
|
363
|
+
$prop = $proc.PSObject.Properties['CommandLine']
|
|
364
|
+
if ($prop -and $prop.Value) { $commandLine = [string]$prop.Value }
|
|
365
|
+
if ($proc.ProcessId -eq $PID) { continue }
|
|
366
|
+
if (-not (Test-AgentProcess -Name ([string]$proc.Name) -CommandLine $commandLine)) { continue }
|
|
367
|
+
$found += @{ Pid = [int]$proc.ProcessId; Name = [string]$proc.Name; CommandLine = $commandLine }
|
|
368
|
+
}
|
|
369
|
+
return $found
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function Get-LivenessState {
|
|
373
|
+
param([string]$HeartbeatPath, [string]$Gateway, [string]$NodeUser, [int]$Grace, [int]$TimeoutSec)
|
|
374
|
+
$state = @{ Alive = $false; Via = ''; Heartbeat = $null; Gateway = $null; Processes = @(); Reason = '' }
|
|
375
|
+
# (a) heartbeat, (b) gateway health, (c) a verified agent process - in order.
|
|
376
|
+
$state.Heartbeat = Get-HeartbeatState -Path $HeartbeatPath -Grace $Grace
|
|
377
|
+
if ($state.Heartbeat.Fresh) { $state.Alive = $true; $state.Via = 'fresh heartbeat'; return $state }
|
|
378
|
+
$state.Gateway = Get-GatewayHealth -Gateway $Gateway -NodeUser $NodeUser -TimeoutSec $TimeoutSec
|
|
379
|
+
if ($state.Gateway.Ok) { $state.Alive = $true; $state.Via = 'gateway health'; return $state }
|
|
380
|
+
$state.Processes = @(Get-AgentProcesses)
|
|
381
|
+
if ($state.Processes.Count -gt 0) {
|
|
382
|
+
$state.Alive = $true
|
|
383
|
+
$state.Via = 'running process (pid ' + $state.Processes[0].Pid + ')'
|
|
384
|
+
return $state
|
|
385
|
+
}
|
|
386
|
+
$state.Reason = 'no fresh heartbeat (' + $state.Heartbeat.Detail + '), gateway ' + $state.Gateway.Detail + ', no verified agent process (a python.exe running -m core.agent, or the cmd.exe launcher wrapper)'
|
|
387
|
+
return $state
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function Resolve-FullPath {
|
|
391
|
+
param([string]$Path)
|
|
392
|
+
if ([string]::IsNullOrWhiteSpace($Path)) { return $Path }
|
|
393
|
+
try { return [System.IO.Path]::GetFullPath($Path) } catch { return $Path }
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
# ==============================================================================
|
|
397
|
+
# Main
|
|
398
|
+
# ==============================================================================
|
|
399
|
+
|
|
400
|
+
# --- 1. paths ----------------------------------------------------------------
|
|
401
|
+
if ([string]::IsNullOrWhiteSpace($ConfigDir)) {
|
|
402
|
+
$profileDir = $env:USERPROFILE
|
|
403
|
+
if ([string]::IsNullOrWhiteSpace($profileDir)) { $profileDir = [Environment]::GetFolderPath('UserProfile') }
|
|
404
|
+
if ([string]::IsNullOrWhiteSpace($profileDir)) {
|
|
405
|
+
if (-not $Quiet) { Write-Host 'CONFIG ERROR: cannot resolve the user profile directory; pass -ConfigDir explicitly.' }
|
|
406
|
+
exit 2
|
|
407
|
+
}
|
|
408
|
+
$ConfigDir = Join-Path $profileDir '.config\antigravity-mesh'
|
|
409
|
+
}
|
|
410
|
+
$ConfigDir = Resolve-FullPath $ConfigDir
|
|
411
|
+
if ([string]::IsNullOrWhiteSpace($LogFile)) { $LogFile = Join-Path $ConfigDir 'watchdog.log' }
|
|
412
|
+
$LogFile = Resolve-FullPath $LogFile
|
|
413
|
+
try {
|
|
414
|
+
$logDir = Split-Path -Parent $LogFile
|
|
415
|
+
if ($logDir -and -not (Test-Path -LiteralPath $logDir -PathType Container)) {
|
|
416
|
+
$null = New-Item -ItemType Directory -Path $logDir -Force -ErrorAction Stop
|
|
417
|
+
}
|
|
418
|
+
$script:LogTarget = $LogFile
|
|
419
|
+
} catch {
|
|
420
|
+
$script:LogTarget = ''
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
# --- 2. single instance ------------------------------------------------------
|
|
424
|
+
# Overlapping runs (a slow gateway probe plus the next 5-minute tick) must not
|
|
425
|
+
# start two agents. The OS releases the mutex when this process exits, so a
|
|
426
|
+
# crashed run can never wedge the watchdog.
|
|
427
|
+
$mutexName = 'AntigravityMeshWatchdog-' + ($env:USERNAME)
|
|
428
|
+
$mutex = $null
|
|
429
|
+
$acquired = $true
|
|
430
|
+
foreach ($candidate in @(('Global\' + $mutexName), $mutexName)) {
|
|
431
|
+
try {
|
|
432
|
+
$mutex = New-Object System.Threading.Mutex -ArgumentList @($false, $candidate)
|
|
433
|
+
$acquired = [bool]$mutex.WaitOne(0)
|
|
434
|
+
break
|
|
435
|
+
} catch [System.Threading.AbandonedMutexException] {
|
|
436
|
+
$acquired = $true
|
|
437
|
+
break
|
|
438
|
+
} catch {
|
|
439
|
+
$mutex = $null
|
|
440
|
+
$acquired = $true
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
if (-not $acquired) {
|
|
444
|
+
Write-LogLine 'another watchdog instance is already running; nothing to do'
|
|
445
|
+
exit 0
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if ($GraceSeconds -lt 0) { $GraceSeconds = 0 }
|
|
449
|
+
$heartbeatPath = Join-Path $ConfigDir 'agent.heartbeat'
|
|
450
|
+
|
|
451
|
+
# --- 3. configuration --------------------------------------------------------
|
|
452
|
+
$envFile = Join-Path $ConfigDir 'agent.env'
|
|
453
|
+
if (-not (Test-Path -LiteralPath $envFile -PathType Leaf)) {
|
|
454
|
+
Stop-WithConfigError ('no agent.env at ' + $envFile + ' - this node was never installed; run install.ps1')
|
|
455
|
+
}
|
|
456
|
+
$settings = Read-EnvFile $envFile
|
|
457
|
+
$missing = @()
|
|
458
|
+
foreach ($key in @('MESH_GATEWAY', 'MESH_USER', 'MESH_TOKEN')) {
|
|
459
|
+
if (-not $settings.ContainsKey($key) -or [string]::IsNullOrWhiteSpace($settings[$key])) { $missing += $key }
|
|
460
|
+
}
|
|
461
|
+
if ($missing.Count -gt 0) {
|
|
462
|
+
Stop-WithConfigError ('agent.env is missing or has an empty ' + ($missing -join ', ') + ' (' + $envFile + ') - refusing to start an agent from an incomplete configuration')
|
|
463
|
+
}
|
|
464
|
+
$gateway = $settings['MESH_GATEWAY'].Trim()
|
|
465
|
+
$nodeUser = $settings['MESH_USER'].Trim()
|
|
466
|
+
$token = $settings['MESH_TOKEN'].Trim()
|
|
467
|
+
|
|
468
|
+
Write-Note ('watchdog start: config=' + $ConfigDir + ' gateway=' + $gateway + ' user=' + $nodeUser + ' token=****')
|
|
469
|
+
|
|
470
|
+
# --- 4. the logon launcher ---------------------------------------------------
|
|
471
|
+
$startupDir = [Environment]::GetFolderPath('Startup')
|
|
472
|
+
if ([string]::IsNullOrWhiteSpace($startupDir)) {
|
|
473
|
+
Stop-WithConfigError 'cannot resolve the Startup folder ([Environment]::GetFolderPath("Startup") returned nothing)'
|
|
474
|
+
}
|
|
475
|
+
$vbsPath = Join-Path $startupDir 'antigravity-agent.vbs'
|
|
476
|
+
if (-not (Test-Path -LiteralPath $vbsPath -PathType Leaf)) {
|
|
477
|
+
Stop-WithConfigError ('the autostart launcher is missing: ' + $vbsPath + ' - this node would not start after a reboot; re-run install.ps1')
|
|
478
|
+
}
|
|
479
|
+
$launcher = Get-LauncherInfo $vbsPath
|
|
480
|
+
if (-not $launcher.Present) {
|
|
481
|
+
Stop-WithConfigError ('cannot read the autostart launcher ' + $vbsPath + ': ' + $launcher.Error)
|
|
482
|
+
}
|
|
483
|
+
$interpreter = $launcher.Interpreter
|
|
484
|
+
if ([string]::IsNullOrWhiteSpace($interpreter)) {
|
|
485
|
+
Stop-WithConfigError ('no interpreter found in the WshShell.Run line of ' + $vbsPath + ' - the launcher is unusable; re-run install.ps1')
|
|
486
|
+
}
|
|
487
|
+
if ($interpreter -match '(?i)\\WindowsApps\\') {
|
|
488
|
+
Stop-WithConfigError ('the pinned interpreter is the Microsoft Store App Execution Alias: ' + $interpreter + ' - that alias never runs Python and was the silent post-reboot failure; re-run install.ps1 so a real python.exe is pinned')
|
|
489
|
+
}
|
|
490
|
+
if (-not (Test-Path -LiteralPath $interpreter -PathType Leaf)) {
|
|
491
|
+
Stop-WithConfigError ('the pinned interpreter does not exist: ' + $interpreter + ' - re-run install.ps1, or fix the WshShell.Run line in ' + $vbsPath)
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
# The payload directory decides where "import core.agent" resolves from. Prefer
|
|
495
|
+
# exactly what the .vbs sets; fall back to the payload this watchdog was shipped
|
|
496
|
+
# inside, then to the config directory, and always say which one was used.
|
|
497
|
+
$payloadReason = 'WshShell.CurrentDirectory'
|
|
498
|
+
if ([string]::IsNullOrWhiteSpace($launcher.PayloadDir)) {
|
|
499
|
+
$payloadProblem = 'missing'
|
|
500
|
+
} elseif (-not (Test-Path -LiteralPath $launcher.PayloadDir -PathType Container)) {
|
|
501
|
+
$payloadProblem = 'not a directory (' + $launcher.PayloadDir + ')'
|
|
502
|
+
} else {
|
|
503
|
+
$payloadProblem = ''
|
|
504
|
+
}
|
|
505
|
+
if ($payloadProblem) {
|
|
506
|
+
$fallbackDir = ''
|
|
507
|
+
if ($PSScriptRoot) { $fallbackDir = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) }
|
|
508
|
+
if ($fallbackDir -and (Test-Path -LiteralPath $fallbackDir -PathType Container)) {
|
|
509
|
+
$payloadDir = $fallbackDir
|
|
510
|
+
$payloadReason = 'the watchdog location (WshShell.CurrentDirectory is ' + $payloadProblem + ')'
|
|
511
|
+
} else {
|
|
512
|
+
$payloadDir = $ConfigDir
|
|
513
|
+
$payloadReason = 'the config directory (WshShell.CurrentDirectory is ' + $payloadProblem + ' and no payload was found next to the watchdog)'
|
|
514
|
+
}
|
|
515
|
+
} else {
|
|
516
|
+
$payloadDir = $launcher.PayloadDir
|
|
517
|
+
}
|
|
518
|
+
Write-Note ('launcher=' + $vbsPath + ' interpreter=' + $interpreter + ' payload=' + $payloadDir + ' (' + $payloadReason + ')')
|
|
519
|
+
|
|
520
|
+
# --- 5. is the node alive? ---------------------------------------------------
|
|
521
|
+
$state = Get-LivenessState -HeartbeatPath $heartbeatPath -Gateway $gateway -NodeUser $nodeUser -Grace $GraceSeconds -TimeoutSec 10
|
|
522
|
+
if ($state.Alive) {
|
|
523
|
+
Write-Note ('node is online via ' + $state.Via + '; nothing to do')
|
|
524
|
+
exit 0
|
|
525
|
+
}
|
|
526
|
+
Write-Note ('node is offline: ' + $state.Reason)
|
|
527
|
+
|
|
528
|
+
# --- 6. recover --------------------------------------------------------------
|
|
529
|
+
# Exactly one line goes into agent.log: it says why the node was considered dead
|
|
530
|
+
# and which interpreter is being started. Everything written to a log passes
|
|
531
|
+
# through Hide-Secret, so the token cannot leak into agent.log even by accident.
|
|
532
|
+
$stamp = (Get-Date).ToString('yyyy-MM-ddTHH:mm:ssK')
|
|
533
|
+
$diagnostic = '[' + $stamp + '] watchdog: node is offline (' + $state.Reason + '); starting the agent with interpreter ' + $interpreter + ' from ' + $payloadDir
|
|
534
|
+
$null = Add-AgentLogLine (Hide-Secret -Text $diagnostic -Secret $token)
|
|
535
|
+
|
|
536
|
+
# Same contract as the logon launcher: cmd /c ""<interpreter>" -u -m core.agent
|
|
537
|
+
# >> "<agent.log>" 2>&1 - cmd strips the outer quote pair, leaving one quoted
|
|
538
|
+
# interpreter, an unbuffered core.agent run and an append redirection.
|
|
539
|
+
$env:MESH_GATEWAY = $gateway
|
|
540
|
+
$env:MESH_USER = $nodeUser
|
|
541
|
+
$env:MESH_TOKEN = $token
|
|
542
|
+
$agentLog = Join-Path $ConfigDir 'agent.log'
|
|
543
|
+
$argumentLine = '/c ""' + $interpreter + '" -u -m core.agent >> "' + $agentLog + '" 2>&1"'
|
|
544
|
+
$launched = $false
|
|
545
|
+
try {
|
|
546
|
+
$agentProcess = Start-Process -FilePath 'cmd.exe' -ArgumentList $argumentLine -WorkingDirectory $payloadDir -WindowStyle Hidden -PassThru -ErrorAction Stop
|
|
547
|
+
$launched = $true
|
|
548
|
+
Write-Note ('started the agent: pid=' + $agentProcess.Id + ' interpreter=' + $interpreter + ' cwd=' + $payloadDir)
|
|
549
|
+
} catch {
|
|
550
|
+
Write-Note ('could not start the agent: ' + $_.Exception.Message)
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
# --- 7. wait for liveness ----------------------------------------------------
|
|
554
|
+
$finalState = $null
|
|
555
|
+
if ($launched) {
|
|
556
|
+
$deadline = (Get-Date).AddSeconds(20)
|
|
557
|
+
while ((Get-Date) -lt $deadline) {
|
|
558
|
+
Start-Sleep -Seconds 2
|
|
559
|
+
$finalState = Get-LivenessState -HeartbeatPath $heartbeatPath -Gateway $gateway -NodeUser $nodeUser -Grace $GraceSeconds -TimeoutSec 5
|
|
560
|
+
if ($finalState.Alive) { break }
|
|
561
|
+
}
|
|
562
|
+
} else {
|
|
563
|
+
Write-Note 'not waiting for liveness: the launch failed'
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
if ($finalState -and $finalState.Alive) {
|
|
567
|
+
Write-Note ('node is online again via ' + $finalState.Via + '; recovery succeeded')
|
|
568
|
+
exit 0
|
|
569
|
+
}
|
|
570
|
+
if ($finalState) {
|
|
571
|
+
Write-Note ('node is STILL offline after the recovery attempt: ' + $finalState.Reason)
|
|
572
|
+
} else {
|
|
573
|
+
Write-Note 'node is STILL offline: no agent process was started'
|
|
574
|
+
}
|
|
575
|
+
exit 3
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
' ==============================================================================
|
|
2
|
+
' Antigravity Mesh - run a node script with no console window at all
|
|
3
|
+
'
|
|
4
|
+
' WHY THIS FILE EXISTS
|
|
5
|
+
' Task Scheduler starts a task inside the interactive session, and the task has
|
|
6
|
+
' no console of its own. When the action is powershell.exe, Windows therefore
|
|
7
|
+
' allocates a NEW console for it and only then applies "-WindowStyle Hidden":
|
|
8
|
+
' the window is created, drawn and hidden again, which the user sees as a black
|
|
9
|
+
' window that appears and disappears. The watchdog task fires every five minutes,
|
|
10
|
+
' so that flash is the most noticeable thing this project does on a desktop.
|
|
11
|
+
'
|
|
12
|
+
' wscript.exe is a GUI host: it never allocates a console. WshShell.Run with a
|
|
13
|
+
' window style of 0 passes SW_HIDE in the child's STARTUPINFO, so cmd.exe (and
|
|
14
|
+
' therefore PowerShell) is created hidden from the very first moment - nothing is
|
|
15
|
+
' ever drawn. This is the same mechanism the logon launcher
|
|
16
|
+
' (antigravity-agent.vbs) uses, which is why a node starts without a window.
|
|
17
|
+
'
|
|
18
|
+
' USAGE - the two scheduled tasks use exactly this. The script path is relative
|
|
19
|
+
' to the payload (the directory that holds core\, ops\ and install.ps1), which
|
|
20
|
+
' this file finds from its own location:
|
|
21
|
+
'
|
|
22
|
+
' wscript.exe run-hidden.vbs "ops\windows\agent-watchdog.ps1" -Quiet
|
|
23
|
+
' wscript.exe run-hidden.vbs "ops\update.ps1" -Quiet
|
|
24
|
+
'
|
|
25
|
+
' Output goes to <ConfigDir>\<script name>.out.log: a window that never appears
|
|
26
|
+
' also never shows an error.
|
|
27
|
+
'
|
|
28
|
+
' ASCII only, and no BOM: wscript.exe reads a .vbs with the ANSI code page.
|
|
29
|
+
' ==============================================================================
|
|
30
|
+
Option Explicit
|
|
31
|
+
|
|
32
|
+
Dim shell, fso, payload, scriptPath, logDir, logPath, command, index, extra, quote
|
|
33
|
+
|
|
34
|
+
Set shell = CreateObject("WScript.Shell")
|
|
35
|
+
Set fso = CreateObject("Scripting.FileSystemObject")
|
|
36
|
+
|
|
37
|
+
If WScript.Arguments.Count < 1 Then
|
|
38
|
+
WScript.Quit 2
|
|
39
|
+
End If
|
|
40
|
+
|
|
41
|
+
' <payload>\ops\windows\run-hidden.vbs -> three levels up is <payload>
|
|
42
|
+
payload = fso.GetParentFolderName(fso.GetParentFolderName(fso.GetParentFolderName(WScript.ScriptFullName)))
|
|
43
|
+
scriptPath = payload & "\" & WScript.Arguments(0)
|
|
44
|
+
|
|
45
|
+
extra = ""
|
|
46
|
+
For index = 1 To WScript.Arguments.Count - 1
|
|
47
|
+
extra = extra & " " & WScript.Arguments(index)
|
|
48
|
+
Next
|
|
49
|
+
|
|
50
|
+
logDir = shell.ExpandEnvironmentStrings("%USERPROFILE%") & "\.config\antigravity-mesh"
|
|
51
|
+
If Not fso.FolderExists(logDir) Then
|
|
52
|
+
logDir = payload
|
|
53
|
+
End If
|
|
54
|
+
logPath = logDir & "\" & fso.GetFileName(scriptPath) & ".out.log"
|
|
55
|
+
|
|
56
|
+
quote = Chr(34)
|
|
57
|
+
command = "cmd.exe /c " & quote & quote & "powershell.exe" & quote _
|
|
58
|
+
& " -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -NonInteractive -File " _
|
|
59
|
+
& quote & scriptPath & quote & extra _
|
|
60
|
+
& " >> " & quote & logPath & quote & " 2>&1" & quote
|
|
61
|
+
|
|
62
|
+
' 0 = the child window is never shown, False = do not wait for it
|
|
63
|
+
shell.Run command, 0, False
|
|
64
|
+
WScript.Quit 0
|
package/package.json
CHANGED
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gemini-computer-use",
|
|
3
|
-
"version": "0.2
|
|
4
|
-
"description": "Turn your PC, Laptop or
|
|
3
|
+
"version": "0.4.2",
|
|
4
|
+
"description": "Turn your PC, Laptop, Server or Android phone into an autonomous execution node for Google Gemini via native MCP: 26 tools, write/edit/search, background jobs, public file and page sharing, secure reverse tunnel, self-update, and a device branch (battery, signal, sensors, camera, microphone, notifications) for phones and laptops.",
|
|
5
5
|
"main": "bin/cli.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"gemini-computer-use": "bin/cli.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
-
"start": "node ./bin/cli.js"
|
|
10
|
+
"start": "node ./bin/cli.js",
|
|
11
|
+
"prepack": "node -e \"const fs=require('fs'),path=require('path');const walk=(d)=>{for(const e of fs.readdirSync(d,{withFileTypes:true})){if(e.name==='.git'||e.name==='node_modules'||e.name==='venv')continue;const p=path.join(d,e.name);if(e.isDirectory()){if(e.name==='__pycache__')fs.rmSync(p,{recursive:true,force:true});else walk(p);}else if(/\\.(pyc|pyo)$/.test(e.name))fs.rmSync(p,{force:true});}};walk('.');\""
|
|
11
12
|
},
|
|
12
13
|
"files": [
|
|
13
14
|
"bin",
|
|
14
15
|
"core",
|
|
15
16
|
"skills",
|
|
17
|
+
"ops",
|
|
16
18
|
"install.sh",
|
|
17
19
|
"install.ps1",
|
|
20
|
+
"install-gui.ps1",
|
|
21
|
+
"install-gui.strings.json",
|
|
22
|
+
"install-gui.cmd",
|
|
18
23
|
"README.md",
|
|
19
24
|
"LICENSE",
|
|
20
25
|
"gateway.py"
|