@anusornneal/chat-relay 0.7.0 → 0.7.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.
@@ -0,0 +1,380 @@
1
+ param()
2
+
3
+ $ErrorActionPreference = "Stop"
4
+ $ProgressPreference = "SilentlyContinue"
5
+
6
+ Add-Type -AssemblyName System.Windows.Forms
7
+ Add-Type -AssemblyName System.Drawing
8
+
9
+ Add-Type @"
10
+ using System;
11
+ using System.Runtime.InteropServices;
12
+
13
+ public static class ChatRelayNative {
14
+ public const uint INPUT_MOUSE = 0;
15
+ public const uint INPUT_KEYBOARD = 1;
16
+ public const uint KEYEVENTF_KEYUP = 0x0002;
17
+ public const uint KEYEVENTF_UNICODE = 0x0004;
18
+
19
+ [StructLayout(LayoutKind.Sequential)]
20
+ public struct MOUSEINPUT {
21
+ public int dx;
22
+ public int dy;
23
+ public uint mouseData;
24
+ public uint dwFlags;
25
+ public uint time;
26
+ public UIntPtr dwExtraInfo;
27
+ }
28
+
29
+ [StructLayout(LayoutKind.Sequential)]
30
+ public struct KEYBDINPUT {
31
+ public ushort wVk;
32
+ public ushort wScan;
33
+ public uint dwFlags;
34
+ public uint time;
35
+ public UIntPtr dwExtraInfo;
36
+ }
37
+
38
+ [StructLayout(LayoutKind.Explicit)]
39
+ public struct INPUTUNION {
40
+ [FieldOffset(0)] public MOUSEINPUT mi;
41
+ [FieldOffset(0)] public KEYBDINPUT ki;
42
+ }
43
+
44
+ [StructLayout(LayoutKind.Sequential)]
45
+ public struct INPUT {
46
+ public uint type;
47
+ public INPUTUNION U;
48
+ }
49
+
50
+ [DllImport("user32.dll")] public static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize);
51
+ [DllImport("user32.dll")] public static extern bool SetCursorPos(int X, int Y);
52
+ [DllImport("user32.dll")] public static extern bool SetProcessDPIAware();
53
+ [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
54
+
55
+ public static bool MouseButton(uint downFlag, uint upFlag) {
56
+ var inputs = new INPUT[2];
57
+ inputs[0].type = INPUT_MOUSE;
58
+ inputs[0].U.mi.dwFlags = downFlag;
59
+ inputs[1].type = INPUT_MOUSE;
60
+ inputs[1].U.mi.dwFlags = upFlag;
61
+ return SendInput(2, inputs, Marshal.SizeOf(typeof(INPUT))) == 2;
62
+ }
63
+
64
+ public static bool Key(ushort virtualKey, bool down) {
65
+ var input = new INPUT[1];
66
+ input[0].type = INPUT_KEYBOARD;
67
+ input[0].U.ki.wVk = virtualKey;
68
+ input[0].U.ki.dwFlags = down ? 0u : KEYEVENTF_KEYUP;
69
+ return SendInput(1, input, Marshal.SizeOf(typeof(INPUT))) == 1;
70
+ }
71
+
72
+ public static bool UnicodeText(string text) {
73
+ foreach (char c in text) {
74
+ var inputs = new INPUT[2];
75
+ inputs[0].type = INPUT_KEYBOARD;
76
+ inputs[0].U.ki.wScan = c;
77
+ inputs[0].U.ki.dwFlags = KEYEVENTF_UNICODE;
78
+ inputs[1].type = INPUT_KEYBOARD;
79
+ inputs[1].U.ki.wScan = c;
80
+ inputs[1].U.ki.dwFlags = KEYEVENTF_UNICODE | KEYEVENTF_KEYUP;
81
+ if (SendInput(2, inputs, Marshal.SizeOf(typeof(INPUT))) != 2) return false;
82
+ }
83
+ return true;
84
+ }
85
+ }
86
+ "@
87
+
88
+ [ChatRelayNative]::SetProcessDPIAware() | Out-Null
89
+
90
+ function Result-Error([string]$Code) {
91
+ return [pscustomobject]@{ ok = $false; error = $Code }
92
+ }
93
+
94
+ function Test-InteractiveSession {
95
+ return [System.Windows.Forms.SystemInformation]::UserInteractive -and
96
+ ([ChatRelayNative]::GetForegroundWindow() -ne [IntPtr]::Zero)
97
+ }
98
+
99
+ function Resolve-Screen($Monitor) {
100
+ $screens = @([System.Windows.Forms.Screen]::AllScreens)
101
+ if ($screens.Count -eq 0) { return $null }
102
+
103
+ if ($null -eq $Monitor -or [string]$Monitor -eq "primary") {
104
+ for ($i = 0; $i -lt $screens.Count; $i++) {
105
+ if ($screens[$i].Primary) {
106
+ return [pscustomobject]@{ screen = $screens[$i]; index = $i }
107
+ }
108
+ }
109
+ return $null
110
+ }
111
+
112
+ if ([string]$Monitor -eq "secondary") {
113
+ for ($i = 0; $i -lt $screens.Count; $i++) {
114
+ if (-not $screens[$i].Primary) {
115
+ return [pscustomobject]@{ screen = $screens[$i]; index = $i }
116
+ }
117
+ }
118
+ return $null
119
+ }
120
+
121
+ $parsedIndex = 0
122
+ if (-not [int]::TryParse([string]$Monitor, [ref]$parsedIndex)) { return $null }
123
+ if ($parsedIndex -lt 0 -or $parsedIndex -ge $screens.Count) { return $null }
124
+ return [pscustomobject]@{ screen = $screens[$parsedIndex]; index = $parsedIndex }
125
+ }
126
+
127
+ function Invoke-Screenshot($Payload) {
128
+ if (-not [System.Windows.Forms.SystemInformation]::UserInteractive) {
129
+ return Result-Error "session_unavailable"
130
+ }
131
+
132
+ $resolved = Resolve-Screen $Payload.monitor
133
+ if ($null -eq $resolved) { return Result-Error "monitor_not_found" }
134
+
135
+ $screen = $resolved.screen
136
+ $bounds = $screen.Bounds
137
+ if ($bounds.Width -le 0 -or $bounds.Height -le 0) {
138
+ return Result-Error "session_unavailable"
139
+ }
140
+
141
+ $source = $null
142
+ try {
143
+ $source = New-Object System.Drawing.Bitmap $bounds.Width, $bounds.Height
144
+ $graphics = [System.Drawing.Graphics]::FromImage($source)
145
+ try {
146
+ $graphics.CopyFromScreen($bounds.Left, $bounds.Top, 0, 0, $bounds.Size)
147
+ } finally {
148
+ $graphics.Dispose()
149
+ }
150
+
151
+ $codec = [System.Drawing.Imaging.ImageCodecInfo]::GetImageEncoders() |
152
+ Where-Object { $_.MimeType -eq "image/jpeg" } |
153
+ Select-Object -First 1
154
+ if ($null -eq $codec) { return Result-Error "capture_failed" }
155
+
156
+ $maxBytes = 32768
157
+ $targetWidth = [Math]::Min($bounds.Width, 960)
158
+ $targetHeight = [Math]::Max(1, [int][Math]::Round($bounds.Height * ($targetWidth / [double]$bounds.Width)))
159
+ $qualities = @(58L, 42L, 30L, 22L)
160
+ $bytes = $null
161
+ $finalWidth = $targetWidth
162
+ $finalHeight = $targetHeight
163
+
164
+ for ($sizeAttempt = 0; $sizeAttempt -lt 3 -and $null -eq $bytes; $sizeAttempt++) {
165
+ $resized = New-Object System.Drawing.Bitmap $targetWidth, $targetHeight
166
+ $draw = [System.Drawing.Graphics]::FromImage($resized)
167
+ try {
168
+ $draw.CompositingQuality = [System.Drawing.Drawing2D.CompositingQuality]::HighSpeed
169
+ $draw.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::Bilinear
170
+ $draw.PixelOffsetMode = [System.Drawing.Drawing2D.PixelOffsetMode]::HighSpeed
171
+ $draw.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::HighSpeed
172
+ $draw.DrawImage($source, 0, 0, $targetWidth, $targetHeight)
173
+ } finally {
174
+ $draw.Dispose()
175
+ }
176
+
177
+ try {
178
+ foreach ($quality in $qualities) {
179
+ $stream = New-Object System.IO.MemoryStream
180
+ $parameters = New-Object System.Drawing.Imaging.EncoderParameters 1
181
+ $parameters.Param[0] = New-Object System.Drawing.Imaging.EncoderParameter(
182
+ [System.Drawing.Imaging.Encoder]::Quality,
183
+ $quality
184
+ )
185
+ try {
186
+ $resized.Save($stream, $codec, $parameters)
187
+ if ($stream.Length -le $maxBytes) {
188
+ $bytes = $stream.ToArray()
189
+ $finalWidth = $targetWidth
190
+ $finalHeight = $targetHeight
191
+ break
192
+ }
193
+ } finally {
194
+ $parameters.Dispose()
195
+ $stream.Dispose()
196
+ }
197
+ }
198
+ } finally {
199
+ $resized.Dispose()
200
+ }
201
+
202
+ if ($null -eq $bytes) {
203
+ $targetWidth = [Math]::Max(320, [int][Math]::Floor($targetWidth * 0.78))
204
+ $targetHeight = [Math]::Max(180, [int][Math]::Round($bounds.Height * ($targetWidth / [double]$bounds.Width)))
205
+ }
206
+ }
207
+
208
+ if ($null -eq $bytes) { return Result-Error "screenshot_too_large" }
209
+
210
+ return [pscustomobject]@{
211
+ ok = $true
212
+ mimeType = "image/jpeg"
213
+ data = [Convert]::ToBase64String($bytes)
214
+ width = $finalWidth
215
+ height = $finalHeight
216
+ desktopOriginX = $bounds.Left
217
+ desktopOriginY = $bounds.Top
218
+ desktopWidth = $bounds.Width
219
+ desktopHeight = $bounds.Height
220
+ scaleX = $bounds.Width / [double]$finalWidth
221
+ scaleY = $bounds.Height / [double]$finalHeight
222
+ monitorIndex = $resolved.index
223
+ isPrimary = [bool]$screen.Primary
224
+ deviceName = [string]$screen.DeviceName
225
+ byteLength = $bytes.Length
226
+ }
227
+ } catch {
228
+ return Result-Error "capture_failed"
229
+ } finally {
230
+ if ($null -ne $source) { $source.Dispose() }
231
+ }
232
+ }
233
+
234
+ function Invoke-MouseClick($Payload) {
235
+ if (-not (Test-InteractiveSession)) { return Result-Error "session_unavailable" }
236
+
237
+ $x = [int]$Payload.x
238
+ $y = [int]$Payload.y
239
+ $containsPoint = $false
240
+ foreach ($screen in @([System.Windows.Forms.Screen]::AllScreens)) {
241
+ if ($screen.Bounds.Contains($x, $y)) {
242
+ $containsPoint = $true
243
+ break
244
+ }
245
+ }
246
+ if (-not $containsPoint) { return Result-Error "invalid_coordinates" }
247
+
248
+ switch ([string]$Payload.button) {
249
+ "left" { $down = 0x0002; $up = 0x0004 }
250
+ "right" { $down = 0x0008; $up = 0x0010 }
251
+ "middle" { $down = 0x0020; $up = 0x0040 }
252
+ default { return Result-Error "invalid_button" }
253
+ }
254
+
255
+ if (-not [ChatRelayNative]::SetCursorPos($x, $y)) { return Result-Error "input_failed" }
256
+
257
+ $clicks = [int]$Payload.clicks
258
+ for ($i = 0; $i -lt $clicks; $i++) {
259
+ if (-not [ChatRelayNative]::MouseButton($down, $up)) { return Result-Error "input_failed" }
260
+ if ($i + 1 -lt $clicks) { Start-Sleep -Milliseconds 100 }
261
+ }
262
+
263
+ return [pscustomobject]@{ ok = $true; action = "mouse_click" }
264
+ }
265
+
266
+ function Invoke-KeyboardInput($Payload) {
267
+ if (-not (Test-InteractiveSession)) { return Result-Error "session_unavailable" }
268
+
269
+ if ($null -ne $Payload.text) {
270
+ if (-not [ChatRelayNative]::UnicodeText([string]$Payload.text)) {
271
+ return Result-Error "input_failed"
272
+ }
273
+ } else {
274
+ $mods = New-Object System.Collections.Generic.List[uint16]
275
+ if ($Payload.ctrl) { $mods.Add(0x11) }
276
+ if ($Payload.alt) { $mods.Add(0x12) }
277
+ if ($Payload.shift) { $mods.Add(0x10) }
278
+ if ($Payload.win) { $mods.Add(0x5B) }
279
+
280
+ foreach ($vk in $mods) {
281
+ if (-not [ChatRelayNative]::Key($vk, $true)) { return Result-Error "input_failed" }
282
+ }
283
+
284
+ try {
285
+ $keyCode = [uint16]$Payload.keyCode
286
+ if (-not [ChatRelayNative]::Key($keyCode, $true)) { return Result-Error "input_failed" }
287
+ if (-not [ChatRelayNative]::Key($keyCode, $false)) { return Result-Error "input_failed" }
288
+ } finally {
289
+ for ($i = $mods.Count - 1; $i -ge 0; $i--) {
290
+ [ChatRelayNative]::Key($mods[$i], $false) | Out-Null
291
+ }
292
+ }
293
+ }
294
+
295
+ return [pscustomobject]@{ ok = $true; action = "keyboard_input" }
296
+ }
297
+
298
+ function Invoke-Step($Payload) {
299
+ $completed = 0
300
+ foreach ($action in @($Payload.actions)) {
301
+ $result = $null
302
+ switch ([string]$action.type) {
303
+ "click" { $result = Invoke-MouseClick $action }
304
+ "text" { $result = Invoke-KeyboardInput $action }
305
+ "key" { $result = Invoke-KeyboardInput $action }
306
+ "wait" {
307
+ Start-Sleep -Milliseconds ([int]$action.ms)
308
+ $result = [pscustomobject]@{ ok = $true; action = "wait" }
309
+ }
310
+ default { $result = Result-Error "invalid_step_action" }
311
+ }
312
+
313
+ if (-not $result.ok) {
314
+ return [pscustomobject]@{
315
+ ok = $false
316
+ error = $result.error
317
+ actionsCompleted = $completed
318
+ }
319
+ }
320
+ $completed++
321
+ }
322
+
323
+ if ([int]$Payload.settleMs -gt 0) {
324
+ Start-Sleep -Milliseconds ([int]$Payload.settleMs)
325
+ }
326
+
327
+ if ($Payload.captureAfter -eq $false) {
328
+ return [pscustomobject]@{
329
+ ok = $true
330
+ action = "step"
331
+ actionsCompleted = $completed
332
+ }
333
+ }
334
+
335
+ $shotPayload = [pscustomobject]@{ monitor = $Payload.monitor }
336
+ $shot = Invoke-Screenshot $shotPayload
337
+ if (-not $shot.ok) {
338
+ return [pscustomobject]@{
339
+ ok = $false
340
+ error = $shot.error
341
+ actionsCompleted = $completed
342
+ }
343
+ }
344
+
345
+ $shot | Add-Member -NotePropertyName action -NotePropertyValue "step" -Force
346
+ $shot | Add-Member -NotePropertyName actionsCompleted -NotePropertyValue $completed -Force
347
+ return $shot
348
+ }
349
+
350
+ function Invoke-Operation([string]$Operation, $Payload) {
351
+ switch ($Operation) {
352
+ "screenshot" { return Invoke-Screenshot $Payload }
353
+ "mouse_click" { return Invoke-MouseClick $Payload }
354
+ "keyboard_input" { return Invoke-KeyboardInput $Payload }
355
+ "step" { return Invoke-Step $Payload }
356
+ default { return Result-Error "unknown_desktop_operation" }
357
+ }
358
+ }
359
+
360
+ while (($line = [Console]::In.ReadLine()) -ne $null) {
361
+ if ([string]::IsNullOrWhiteSpace($line)) { continue }
362
+
363
+ $id = $null
364
+ $result = $null
365
+ try {
366
+ $request = $line | ConvertFrom-Json
367
+ $id = [string]$request.id
368
+ $result = Invoke-Operation ([string]$request.operation) $request.args
369
+ } catch {
370
+ $result = Result-Error "desktop_worker_error"
371
+ }
372
+
373
+ $response = [pscustomobject]@{
374
+ id = $id
375
+ result = $result
376
+ }
377
+
378
+ [Console]::Out.WriteLine(($response | ConvertTo-Json -Compress -Depth 10))
379
+ [Console]::Out.Flush()
380
+ }
@@ -106,7 +106,29 @@ public static class ChatRelayNative {
106
106
  }
107
107
 
108
108
  if ($Operation -eq "screenshot") {
109
- $screen = [System.Windows.Forms.Screen]::PrimaryScreen
109
+ $screens = @([System.Windows.Forms.Screen]::AllScreens)
110
+ if ($screens.Count -eq 0) { Fail "session_unavailable" }
111
+
112
+ $monitor = if ($null -ne $payload.monitor) { $payload.monitor } else { "primary" }
113
+ $screen = $null
114
+ $monitorIndex = -1
115
+
116
+ if ([string]$monitor -eq "primary") {
117
+ $screen = [System.Windows.Forms.Screen]::PrimaryScreen
118
+ for ($i = 0; $i -lt $screens.Count; $i++) { if ($screens[$i].Primary) { $monitorIndex = $i; break } }
119
+ } elseif ([string]$monitor -eq "secondary") {
120
+ for ($i = 0; $i -lt $screens.Count; $i++) {
121
+ if (-not $screens[$i].Primary) { $screen = $screens[$i]; $monitorIndex = $i; break }
122
+ }
123
+ if ($null -eq $screen) { Fail "monitor_not_found" }
124
+ } else {
125
+ $parsedIndex = 0
126
+ if (-not [int]::TryParse([string]$monitor, [ref]$parsedIndex)) { Fail "invalid_monitor" }
127
+ if ($parsedIndex -lt 0 -or $parsedIndex -ge $screens.Count) { Fail "monitor_not_found" }
128
+ $screen = $screens[$parsedIndex]
129
+ $monitorIndex = $parsedIndex
130
+ }
131
+
110
132
  if ($null -eq $screen) { Fail "session_unavailable" }
111
133
  $bounds = $screen.Bounds
112
134
  if ($bounds.Width -le 0 -or $bounds.Height -le 0) { Fail "session_unavailable" }
@@ -189,6 +211,9 @@ public static class ChatRelayNative {
189
211
  desktopHeight = $bounds.Height
190
212
  scaleX = $bounds.Width / [double]$finalWidth
191
213
  scaleY = $bounds.Height / [double]$finalHeight
214
+ monitorIndex = $monitorIndex
215
+ isPrimary = [bool]$screen.Primary
216
+ deviceName = [string]$screen.DeviceName
192
217
  byteLength = $bytes.Length
193
218
  })
194
219
  exit 0
package/package.json CHANGED
@@ -1,75 +1,75 @@
1
1
  {
2
- "name": "@anusornneal/chat-relay",
3
- "version": "0.7.0",
4
- "description": "Zero-checkout remote MCP agent for ChatGPT with terminal, filesystem, process, multi-user, and multi-agent support.",
5
- "type": "module",
6
- "bin": {
7
- "chat-relay": "bin/chat-relay.js"
8
- },
9
- "files": [
10
- "bin/",
11
- "cli/",
12
- "agent/",
13
- "README.md"
14
- ],
15
- "engines": {
16
- "node": "\u003e=20"
17
- },
18
- "publishConfig": {
19
- "access": "public"
20
- },
21
- "scripts": {
22
- "start": "node bin/chat-relay.js remote",
23
- "remote": "node bin/chat-relay.js remote",
24
- "login": "node bin/chat-relay.js login",
25
- "logout": "node bin/chat-relay.js logout",
26
- "status": "node bin/chat-relay.js status",
27
- "dev": "wrangler dev",
28
- "dev:test": "wrangler dev --local --port 8790 --var AGENT_TOKEN:test-agent --var CALLER_TOKEN=test-caller --var ADMIN_TOKEN=test-admin",
29
- "deploy": "wrangler deploy",
30
- "agent": "node --env-file-if-exists=.dev.vars agent/local-agent.mjs",
31
- "admin": "node scripts/admin.mjs",
32
- "test:terminal": "node test/mcp-terminal-smoke.mjs",
33
- "test:full": "node test/multiuser-smoke.mjs",
34
- "test:device-auth": "node test/device-auth-smoke.mjs",
35
- "test:zero-checkout": "node test/zero-checkout-smoke.mjs",
36
- "verify:publish": "npm run verify:review \u0026\u0026 node scripts/verify-publish.mjs \u0026\u0026 npx wrangler deploy --dry-run \u0026\u0026 npm audit --omit=dev",
37
- "prepublishOnly": "npm run verify:publish",
38
- "test:oauth": "node test/oauth-smoke.mjs",
39
- "test:usage": "node test/usage-smoke.mjs",
40
- "test:admin-api": "node test/admin-api-smoke.mjs",
41
- "test:admin-auth": "node test/admin-auth-smoke.mjs",
42
- "test:dashboard": "node test/dashboard-smoke.mjs",
43
- "test:quota": "node test/quota-smoke.mjs",
44
- "test:ops": "node test/ops-smoke.mjs",
45
- "test:desktop": "node test/mcp-desktop-smoke.mjs",
46
- "test:device-management": "node test/device-management-smoke.mjs",
47
- "test:submission": "node test/submission-smoke.mjs",
48
- "verify:review": "node scripts/verify-review-artifacts.mjs"
49
- },
50
- "dependencies": {
51
- "ws": "^8.22.0"
52
- },
53
- "devDependencies": {
54
- "@modelcontextprotocol/server": "^2.0.0",
55
- "agents": "^0.24.0",
56
- "wrangler": "^4.143.0",
57
- "zod": "^4.6.5"
58
- },
59
- "repository": {
60
- "type": "git",
61
- "url": "git+https://github.com/anusornNeal/chat-relay.git"
62
- },
63
- "bugs": {
64
- "url": "https://github.com/anusornNeal/chat-relay/issues"
65
- },
66
- "homepage": "https://github.com/anusornNeal/chat-relay#readme",
67
- "keywords": [
68
- "chatgpt",
69
- "mcp",
70
- "remote-agent",
71
- "terminal",
72
- "cloudflare",
73
- "cli"
74
- ]
2
+ "name": "@anusornneal/chat-relay",
3
+ "version": "0.7.2",
4
+ "description": "Zero-checkout remote MCP agent for ChatGPT with terminal, filesystem, process, multi-user, and multi-agent support.",
5
+ "type": "module",
6
+ "bin": {
7
+ "chat-relay": "bin/chat-relay.js"
8
+ },
9
+ "files": [
10
+ "bin/",
11
+ "cli/",
12
+ "agent/",
13
+ "README.md"
14
+ ],
15
+ "engines": {
16
+ "node": ">=20"
17
+ },
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "scripts": {
22
+ "start": "node bin/chat-relay.js remote",
23
+ "remote": "node bin/chat-relay.js remote",
24
+ "login": "node bin/chat-relay.js login",
25
+ "logout": "node bin/chat-relay.js logout",
26
+ "status": "node bin/chat-relay.js status",
27
+ "dev": "wrangler dev",
28
+ "dev:test": "wrangler dev --local --port 8790 --var AGENT_TOKEN:test-agent --var CALLER_TOKEN=test-caller --var ADMIN_TOKEN=test-admin",
29
+ "deploy": "wrangler deploy",
30
+ "agent": "node --env-file-if-exists=.dev.vars agent/local-agent.mjs",
31
+ "admin": "node scripts/admin.mjs",
32
+ "test:terminal": "node test/mcp-terminal-smoke.mjs",
33
+ "test:full": "node test/multiuser-smoke.mjs",
34
+ "test:device-auth": "node test/device-auth-smoke.mjs",
35
+ "test:zero-checkout": "node test/zero-checkout-smoke.mjs",
36
+ "verify:publish": "npm run verify:review && node scripts/verify-publish.mjs && npx wrangler deploy --dry-run && npm audit --omit=dev",
37
+ "prepublishOnly": "npm run verify:publish",
38
+ "test:oauth": "node test/oauth-smoke.mjs",
39
+ "test:usage": "node test/usage-smoke.mjs",
40
+ "test:admin-api": "node test/admin-api-smoke.mjs",
41
+ "test:admin-auth": "node test/admin-auth-smoke.mjs",
42
+ "test:dashboard": "node test/dashboard-smoke.mjs",
43
+ "test:quota": "node test/quota-smoke.mjs",
44
+ "test:ops": "node test/ops-smoke.mjs",
45
+ "test:desktop": "node test/mcp-desktop-smoke.mjs",
46
+ "test:device-management": "node test/device-management-smoke.mjs",
47
+ "test:submission": "node test/submission-smoke.mjs",
48
+ "verify:review": "node scripts/verify-review-artifacts.mjs"
49
+ },
50
+ "dependencies": {
51
+ "ws": "^8.22.0"
52
+ },
53
+ "devDependencies": {
54
+ "@modelcontextprotocol/server": "^2.0.0",
55
+ "agents": "^0.24.0",
56
+ "wrangler": "^4.143.0",
57
+ "zod": "^4.6.5"
58
+ },
59
+ "repository": {
60
+ "type": "git",
61
+ "url": "git+https://github.com/anusornNeal/chat-relay.git"
62
+ },
63
+ "bugs": {
64
+ "url": "https://github.com/anusornNeal/chat-relay/issues"
65
+ },
66
+ "homepage": "https://github.com/anusornNeal/chat-relay#readme",
67
+ "keywords": [
68
+ "chatgpt",
69
+ "mcp",
70
+ "remote-agent",
71
+ "terminal",
72
+ "cloudflare",
73
+ "cli"
74
+ ]
75
75
  }