broods 0.35.0 → 0.36.0
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/dist/account.d.ts +1 -1
- package/dist/account.js +1 -1
- package/dist/chunk-09w5n6hc.js +1 -0
- package/dist/chunk-54pn8m91.js +15 -0
- package/dist/chunk-5tnnqapc.js +468 -0
- package/dist/chunk-6s753azk.js +1 -0
- package/dist/chunk-gvrqg36n.js +1 -0
- package/dist/{chunk-yd3vh7k7.js → chunk-mqsvqhkj.js} +2 -2
- package/dist/chunk-ny9c9z9g.js +0 -0
- package/dist/chunk-q2n2mxeg.js +5 -0
- package/dist/chunk-r4wpy43p.js +4 -0
- package/dist/chunk-s3cv33em.js +1 -0
- package/dist/cli/index.js +124 -100
- package/dist/index.d.ts +28 -9
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-gg06g89t.js +0 -1
- package/dist/chunk-grj4bad5.js +0 -15
- package/dist/chunk-vf5trjn9.js +0 -1
- package/dist/chunk-wgwmcq6q.js +0 -5
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
import{spawn as c,spawnSync as p}from"node:child_process";import{createHash as u}from"node:crypto";import{existsSync as h,mkdirSync as m,writeFileSync as g}from"node:fs";import{homedir as y}from"node:os";import{join as s}from"node:path";import{createInterface as f}from"node:readline";var n=`// broods-desktop: screenshots, mouse and keyboard for \`broods machine --computer\`.
|
|
2
|
+
//
|
|
3
|
+
// One JSON request per line on stdin, one JSON reply per line on stdout.
|
|
4
|
+
// Coordinates are screenshot pixels: the main display scaled to fit
|
|
5
|
+
// MAX_SHOT_EDGE, mapped to display points both ways in this file. Needs Screen
|
|
6
|
+
// Recording and Accessibility for the terminal that runs the daemon.
|
|
7
|
+
|
|
8
|
+
import AppKit
|
|
9
|
+
import CoreGraphics
|
|
10
|
+
import Foundation
|
|
11
|
+
import ImageIO
|
|
12
|
+
import UniformTypeIdentifiers
|
|
13
|
+
|
|
14
|
+
let MAX_SHOT_EDGE: CGFloat = 1280
|
|
15
|
+
// Past this a PNG of the screen is re-encoded as JPEG; text stays legible and
|
|
16
|
+
// the model reads a fraction of the bytes.
|
|
17
|
+
let MAX_PNG_BYTES = 500 * 1024
|
|
18
|
+
let JPEG_QUALITY = 0.85
|
|
19
|
+
let TYPING_DELAY_US: UInt32 = 12_000
|
|
20
|
+
let CLICK_DELAY_US: UInt32 = 60_000
|
|
21
|
+
|
|
22
|
+
struct Request: Decodable {
|
|
23
|
+
let id: String
|
|
24
|
+
let action: String
|
|
25
|
+
let coordinate: [Double]?
|
|
26
|
+
let startCoordinate: [Double]?
|
|
27
|
+
let text: String?
|
|
28
|
+
let scrollDirection: String?
|
|
29
|
+
let scrollAmount: Double?
|
|
30
|
+
let duration: Double?
|
|
31
|
+
let region: [Double]?
|
|
32
|
+
let repeatCount: Int?
|
|
33
|
+
let request: Bool?
|
|
34
|
+
|
|
35
|
+
enum CodingKeys: String, CodingKey {
|
|
36
|
+
case id, action, coordinate, text, duration, region, request
|
|
37
|
+
case startCoordinate = "start_coordinate"
|
|
38
|
+
case scrollDirection = "scroll_direction"
|
|
39
|
+
case scrollAmount = "scroll_amount"
|
|
40
|
+
case repeatCount = "repeat"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
struct Reply: Encodable {
|
|
45
|
+
let id: String
|
|
46
|
+
var text: String? = nil
|
|
47
|
+
var image: EncodedImage? = nil
|
|
48
|
+
var app: String? = nil
|
|
49
|
+
var error: String? = nil
|
|
50
|
+
var display: Display? = nil
|
|
51
|
+
var permissions: Permissions? = nil
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
struct EncodedImage: Encodable {
|
|
55
|
+
let data: String
|
|
56
|
+
let mediaType: String
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
struct Display: Encodable {
|
|
60
|
+
let width: Int
|
|
61
|
+
let height: Int
|
|
62
|
+
let scale: Double
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
struct Permissions: Encodable {
|
|
66
|
+
let screenRecording: Bool
|
|
67
|
+
let accessibility: Bool
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
struct ActionError: Error {
|
|
71
|
+
let message: String
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// xdotool key names to macOS virtual key codes (ANSI US layout).
|
|
75
|
+
let KEY_CODES: [String: CGKeyCode] = [
|
|
76
|
+
"a": 0, "s": 1, "d": 2, "f": 3, "h": 4, "g": 5, "z": 6, "x": 7, "c": 8, "v": 9,
|
|
77
|
+
"b": 11, "q": 12, "w": 13, "e": 14, "r": 15, "y": 16, "t": 17, "1": 18, "2": 19,
|
|
78
|
+
"3": 20, "4": 21, "6": 22, "5": 23, "equal": 24, "=": 24, "9": 25, "7": 26,
|
|
79
|
+
"minus": 27, "-": 27, "8": 28, "0": 29, "bracketright": 30, "]": 30, "o": 31,
|
|
80
|
+
"u": 32, "bracketleft": 33, "[": 33, "i": 34, "p": 35, "return": 36, "l": 37,
|
|
81
|
+
"j": 38, "apostrophe": 39, "'": 39, "k": 40, "semicolon": 41, ";": 41,
|
|
82
|
+
"backslash": 42, "\\\\": 42, "comma": 43, ",": 43, "slash": 44, "/": 44, "n": 45,
|
|
83
|
+
"m": 46, "period": 47, ".": 47, "tab": 48, "space": 49, "grave": 50, "\`": 50,
|
|
84
|
+
"backspace": 51, "delete": 51, "escape": 53, "esc": 53, "super": 55, "cmd": 55,
|
|
85
|
+
"command": 55, "meta": 55, "shift": 56, "caps_lock": 57, "alt": 58, "option": 58,
|
|
86
|
+
"ctrl": 59, "control": 59, "f17": 64, "kp_decimal": 65, "kp_multiply": 67,
|
|
87
|
+
"kp_add": 69, "kp_divide": 75, "kp_enter": 76, "kp_subtract": 78, "kp_equal": 81,
|
|
88
|
+
"kp_0": 82, "kp_1": 83, "kp_2": 84, "kp_3": 85, "kp_4": 86, "kp_5": 87, "kp_6": 88,
|
|
89
|
+
"kp_7": 89, "kp_8": 91, "kp_9": 92, "f5": 96, "f6": 97, "f7": 98, "f3": 99,
|
|
90
|
+
"f8": 100, "f9": 101, "f11": 103, "f13": 105, "f14": 107, "f10": 109, "f12": 111,
|
|
91
|
+
"f15": 113, "help": 114, "insert": 114, "home": 115, "prior": 116, "page_up": 116,
|
|
92
|
+
"kp_delete": 117, "f4": 118, "end": 119, "f2": 120, "next": 121, "page_down": 121,
|
|
93
|
+
"f1": 122, "left": 123, "right": 124, "down": 125, "up": 126,
|
|
94
|
+
]
|
|
95
|
+
let MODIFIER_FLAGS: [String: CGEventFlags] = [
|
|
96
|
+
"shift": .maskShift, "ctrl": .maskControl, "control": .maskControl,
|
|
97
|
+
"alt": .maskAlternate, "option": .maskAlternate, "super": .maskCommand,
|
|
98
|
+
"cmd": .maskCommand, "command": .maskCommand, "meta": .maskCommand,
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
// --- main loop --------------------------------------------------------------
|
|
102
|
+
|
|
103
|
+
setvbuf(stdout, nil, _IOLBF, 0)
|
|
104
|
+
let decoder = JSONDecoder()
|
|
105
|
+
let encoder = JSONEncoder()
|
|
106
|
+
while let line = readLine(strippingNewline: true) {
|
|
107
|
+
guard let data = line.data(using: .utf8), !line.isEmpty else { continue }
|
|
108
|
+
var reply: Reply
|
|
109
|
+
do {
|
|
110
|
+
let request = try decoder.decode(Request.self, from: data)
|
|
111
|
+
reply = Reply(id: request.id)
|
|
112
|
+
do {
|
|
113
|
+
try perform(request, into: &reply)
|
|
114
|
+
} catch let failure as ActionError {
|
|
115
|
+
reply.error = failure.message
|
|
116
|
+
}
|
|
117
|
+
if reply.error == nil, reply.display == nil, reply.permissions == nil {
|
|
118
|
+
reply.app = NSWorkspace.shared.frontmostApplication?.bundleIdentifier
|
|
119
|
+
}
|
|
120
|
+
} catch {
|
|
121
|
+
reply = Reply(id: "", error: "malformed request: \\(error)")
|
|
122
|
+
}
|
|
123
|
+
if let encoded = try? encoder.encode(reply), let text = String(data: encoded, encoding: .utf8) {
|
|
124
|
+
print(text)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// --- actions ----------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
func perform(_ request: Request, into reply: inout Reply) throws {
|
|
131
|
+
switch request.action {
|
|
132
|
+
case "display":
|
|
133
|
+
reply.display = displayInfo()
|
|
134
|
+
case "permissions":
|
|
135
|
+
reply.permissions = permissions(request: request.request ?? false)
|
|
136
|
+
case "screenshot":
|
|
137
|
+
reply.image = try screenshot(region: nil)
|
|
138
|
+
case "zoom":
|
|
139
|
+
guard let region = request.region, region.count == 4 else {
|
|
140
|
+
throw ActionError(message: "zoom needs region [x0, y0, x1, y1]")
|
|
141
|
+
}
|
|
142
|
+
reply.image = try screenshot(region: region)
|
|
143
|
+
case "cursor_position":
|
|
144
|
+
let pixel = toShot(cursor())
|
|
145
|
+
reply.text = "X=\\(Int(pixel.x.rounded())),Y=\\(Int(pixel.y.rounded()))"
|
|
146
|
+
case "mouse_move":
|
|
147
|
+
post(mouse: .mouseMoved, at: try point(request.coordinate, "coordinate"), button: .left)
|
|
148
|
+
// The window server applies the move asynchronously; a cursor_position
|
|
149
|
+
// that follows at once would still read the old spot.
|
|
150
|
+
usleep(CLICK_DELAY_US)
|
|
151
|
+
reply.text = "OK"
|
|
152
|
+
case "left_click", "right_click", "middle_click", "double_click", "triple_click":
|
|
153
|
+
try click(request)
|
|
154
|
+
reply.text = "OK"
|
|
155
|
+
case "left_mouse_down":
|
|
156
|
+
let at = request.coordinate != nil ? try point(request.coordinate, "coordinate") : cursor()
|
|
157
|
+
post(mouse: .leftMouseDown, at: at, button: .left)
|
|
158
|
+
reply.text = "OK"
|
|
159
|
+
case "left_mouse_up":
|
|
160
|
+
let at = request.coordinate != nil ? try point(request.coordinate, "coordinate") : cursor()
|
|
161
|
+
post(mouse: .leftMouseUp, at: at, button: .left)
|
|
162
|
+
reply.text = "OK"
|
|
163
|
+
case "left_click_drag":
|
|
164
|
+
let from = try point(request.startCoordinate, "start_coordinate")
|
|
165
|
+
let to = try point(request.coordinate, "coordinate")
|
|
166
|
+
try withModifiers(request.text) {
|
|
167
|
+
post(mouse: .mouseMoved, at: from, button: .left)
|
|
168
|
+
post(mouse: .leftMouseDown, at: from, button: .left)
|
|
169
|
+
usleep(CLICK_DELAY_US)
|
|
170
|
+
post(mouse: .leftMouseDragged, at: to, button: .left)
|
|
171
|
+
usleep(CLICK_DELAY_US)
|
|
172
|
+
post(mouse: .leftMouseUp, at: to, button: .left)
|
|
173
|
+
}
|
|
174
|
+
reply.text = "OK"
|
|
175
|
+
case "scroll":
|
|
176
|
+
try scroll(request)
|
|
177
|
+
reply.text = "OK"
|
|
178
|
+
case "key":
|
|
179
|
+
guard let chord = request.text, !chord.isEmpty else {
|
|
180
|
+
throw ActionError(message: "key needs text, e.g. \\"Return\\" or \\"cmd+s\\"")
|
|
181
|
+
}
|
|
182
|
+
for _ in 0..<max(1, min(request.repeatCount ?? 1, 100)) {
|
|
183
|
+
try pressChord(chord)
|
|
184
|
+
}
|
|
185
|
+
reply.text = "OK"
|
|
186
|
+
case "hold_key":
|
|
187
|
+
guard let chord = request.text, !chord.isEmpty else {
|
|
188
|
+
throw ActionError(message: "hold_key needs text")
|
|
189
|
+
}
|
|
190
|
+
let seconds = min(max(request.duration ?? 1, 0), 300)
|
|
191
|
+
let (code, flags) = try parseChord(chord)
|
|
192
|
+
keyEvent(code, flags: flags, down: true)
|
|
193
|
+
usleep(UInt32(seconds * 1_000_000))
|
|
194
|
+
keyEvent(code, flags: flags, down: false)
|
|
195
|
+
reply.text = "OK"
|
|
196
|
+
case "type":
|
|
197
|
+
guard let text = request.text else { throw ActionError(message: "type needs text") }
|
|
198
|
+
typeText(text)
|
|
199
|
+
reply.text = "OK"
|
|
200
|
+
case "wait":
|
|
201
|
+
let seconds = min(max(request.duration ?? 1, 0), 300)
|
|
202
|
+
usleep(UInt32(seconds * 1_000_000))
|
|
203
|
+
reply.text = "OK"
|
|
204
|
+
default:
|
|
205
|
+
throw ActionError(message: "unknown action \\(request.action)")
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
func click(_ request: Request) throws {
|
|
210
|
+
let at = request.coordinate != nil ? try point(request.coordinate, "coordinate") : cursor()
|
|
211
|
+
let (down, up, button): (CGEventType, CGEventType, CGMouseButton)
|
|
212
|
+
switch request.action {
|
|
213
|
+
case "right_click": (down, up, button) = (.rightMouseDown, .rightMouseUp, .right)
|
|
214
|
+
case "middle_click": (down, up, button) = (.otherMouseDown, .otherMouseUp, .center)
|
|
215
|
+
default: (down, up, button) = (.leftMouseDown, .leftMouseUp, .left)
|
|
216
|
+
}
|
|
217
|
+
let count = request.action == "double_click" ? 2 : request.action == "triple_click" ? 3 : 1
|
|
218
|
+
try withModifiers(request.text) {
|
|
219
|
+
post(mouse: .mouseMoved, at: at, button: button)
|
|
220
|
+
usleep(CLICK_DELAY_US)
|
|
221
|
+
for state in 1...count {
|
|
222
|
+
post(mouse: down, at: at, button: button, clickState: state)
|
|
223
|
+
post(mouse: up, at: at, button: button, clickState: state)
|
|
224
|
+
if state < count { usleep(CLICK_DELAY_US) }
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
func scroll(_ request: Request) throws {
|
|
230
|
+
let amount = Int32(max(1, min(request.scrollAmount ?? 3, 100)))
|
|
231
|
+
var dy: Int32 = 0
|
|
232
|
+
var dx: Int32 = 0
|
|
233
|
+
switch request.scrollDirection ?? "down" {
|
|
234
|
+
case "up": dy = amount
|
|
235
|
+
case "down": dy = -amount
|
|
236
|
+
case "left": dx = amount
|
|
237
|
+
case "right": dx = -amount
|
|
238
|
+
default: throw ActionError(message: "scroll_direction must be up, down, left or right")
|
|
239
|
+
}
|
|
240
|
+
if request.coordinate != nil {
|
|
241
|
+
post(mouse: .mouseMoved, at: try point(request.coordinate, "coordinate"), button: .left)
|
|
242
|
+
usleep(CLICK_DELAY_US)
|
|
243
|
+
}
|
|
244
|
+
try withModifiers(request.text) {
|
|
245
|
+
if let event = CGEvent(scrollWheelEvent2Source: nil, units: .line, wheelCount: 2, wheel1: dy, wheel2: dx, wheel3: 0) {
|
|
246
|
+
event.post(tap: .cghidEventTap)
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// --- keyboard ---------------------------------------------------------------
|
|
252
|
+
|
|
253
|
+
func parseChord(_ chord: String) throws -> (CGKeyCode, CGEventFlags) {
|
|
254
|
+
let parts = chord.split(separator: "+").map { String($0) }
|
|
255
|
+
guard let last = parts.last else { throw ActionError(message: "empty key chord") }
|
|
256
|
+
var flags: CGEventFlags = []
|
|
257
|
+
for modifier in parts.dropLast() {
|
|
258
|
+
guard let flag = MODIFIER_FLAGS[modifier.lowercased()] else {
|
|
259
|
+
throw ActionError(message: "unknown modifier \\(modifier)")
|
|
260
|
+
}
|
|
261
|
+
flags.insert(flag)
|
|
262
|
+
}
|
|
263
|
+
guard let code = KEY_CODES[last.lowercased()] else {
|
|
264
|
+
throw ActionError(message: "unknown key \\(last); use xdotool names like Return, Tab, ctrl+s")
|
|
265
|
+
}
|
|
266
|
+
if last.count == 1, last.uppercased() == last, last.lowercased() != last {
|
|
267
|
+
flags.insert(.maskShift)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return (code, flags)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
func pressChord(_ chord: String) throws {
|
|
274
|
+
let (code, flags) = try parseChord(chord)
|
|
275
|
+
keyEvent(code, flags: flags, down: true)
|
|
276
|
+
keyEvent(code, flags: flags, down: false)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
func keyEvent(_ code: CGKeyCode, flags: CGEventFlags, down: Bool) {
|
|
280
|
+
guard let event = CGEvent(keyboardEventSource: nil, virtualKey: code, keyDown: down) else { return }
|
|
281
|
+
event.flags = flags
|
|
282
|
+
event.post(tap: .cghidEventTap)
|
|
283
|
+
usleep(TYPING_DELAY_US)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Unicode typing: no layout lookup, any script goes in as it is.
|
|
287
|
+
func typeText(_ text: String) {
|
|
288
|
+
for unit in text.utf16 {
|
|
289
|
+
var units = [unit]
|
|
290
|
+
if let down = CGEvent(keyboardEventSource: nil, virtualKey: 0, keyDown: true),
|
|
291
|
+
let up = CGEvent(keyboardEventSource: nil, virtualKey: 0, keyDown: false) {
|
|
292
|
+
down.keyboardSetUnicodeString(stringLength: 1, unicodeString: &units)
|
|
293
|
+
up.keyboardSetUnicodeString(stringLength: 1, unicodeString: &units)
|
|
294
|
+
down.post(tap: .cghidEventTap)
|
|
295
|
+
up.post(tap: .cghidEventTap)
|
|
296
|
+
}
|
|
297
|
+
usleep(TYPING_DELAY_US)
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
func withModifiers(_ chord: String?, _ body: () throws -> Void) throws {
|
|
302
|
+
guard let chord, !chord.isEmpty else {
|
|
303
|
+
try body()
|
|
304
|
+
return
|
|
305
|
+
}
|
|
306
|
+
var codes: [CGKeyCode] = []
|
|
307
|
+
for name in chord.split(separator: "+") {
|
|
308
|
+
guard let code = KEY_CODES[String(name).lowercased()], MODIFIER_FLAGS[String(name).lowercased()] != nil else {
|
|
309
|
+
throw ActionError(message: "unknown modifier \\(name)")
|
|
310
|
+
}
|
|
311
|
+
codes.append(code)
|
|
312
|
+
}
|
|
313
|
+
for code in codes { keyEvent(code, flags: [], down: true) }
|
|
314
|
+
defer { for code in codes.reversed() { keyEvent(code, flags: [], down: false) } }
|
|
315
|
+
try body()
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// --- mouse ------------------------------------------------------------------
|
|
319
|
+
|
|
320
|
+
func cursor() -> CGPoint {
|
|
321
|
+
return CGEvent(source: nil)?.location ?? .zero
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
func post(mouse type: CGEventType, at point: CGPoint, button: CGMouseButton, clickState: Int = 1) {
|
|
325
|
+
guard let event = CGEvent(mouseEventSource: nil, mouseType: type, mouseCursorPosition: point, mouseButton: button) else { return }
|
|
326
|
+
event.setIntegerValueField(.mouseEventClickState, value: Int64(clickState))
|
|
327
|
+
event.post(tap: .cghidEventTap)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
func point(_ pair: [Double]?, _ field: String) throws -> CGPoint {
|
|
331
|
+
guard let pair, pair.count == 2 else {
|
|
332
|
+
throw ActionError(message: "\\(field) must be [x, y] in screenshot pixels")
|
|
333
|
+
}
|
|
334
|
+
let shot = shotSize()
|
|
335
|
+
guard pair[0] >= 0, pair[1] >= 0, pair[0] <= shot.width, pair[1] <= shot.height else {
|
|
336
|
+
throw ActionError(message: "\\(field) [\\(Int(pair[0])), \\(Int(pair[1]))] is outside the \\(Int(shot.width))x\\(Int(shot.height)) screenshot")
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return fromShot(CGPoint(x: pair[0], y: pair[1]))
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// --- display and scaling ----------------------------------------------------
|
|
343
|
+
|
|
344
|
+
func displayBounds() -> CGRect {
|
|
345
|
+
return CGDisplayBounds(CGMainDisplayID())
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Deterministic from the display bounds, so input and capture agree with no state.
|
|
349
|
+
func shotSize() -> CGSize {
|
|
350
|
+
let bounds = displayBounds()
|
|
351
|
+
let factor = min(1, MAX_SHOT_EDGE / max(bounds.width, bounds.height))
|
|
352
|
+
|
|
353
|
+
return CGSize(width: (bounds.width * factor).rounded(), height: (bounds.height * factor).rounded())
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
func fromShot(_ pixel: CGPoint) -> CGPoint {
|
|
357
|
+
let bounds = displayBounds()
|
|
358
|
+
let shot = shotSize()
|
|
359
|
+
|
|
360
|
+
return CGPoint(x: bounds.minX + pixel.x * bounds.width / shot.width, y: bounds.minY + pixel.y * bounds.height / shot.height)
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
func toShot(_ point: CGPoint) -> CGPoint {
|
|
364
|
+
let bounds = displayBounds()
|
|
365
|
+
let shot = shotSize()
|
|
366
|
+
|
|
367
|
+
return CGPoint(x: (point.x - bounds.minX) * shot.width / bounds.width, y: (point.y - bounds.minY) * shot.height / bounds.height)
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
func displayInfo() -> Display {
|
|
371
|
+
let shot = shotSize()
|
|
372
|
+
let bounds = displayBounds()
|
|
373
|
+
|
|
374
|
+
return Display(width: Int(shot.width), height: Int(shot.height), scale: Double(bounds.width / shot.width))
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
func permissions(request: Bool) -> Permissions {
|
|
378
|
+
if request {
|
|
379
|
+
_ = CGRequestScreenCaptureAccess()
|
|
380
|
+
_ = AXIsProcessTrustedWithOptions([kAXTrustedCheckOptionPrompt.takeUnretainedValue(): true] as CFDictionary)
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return Permissions(screenRecording: CGPreflightScreenCaptureAccess(), accessibility: AXIsProcessTrusted())
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// --- capture ----------------------------------------------------------------
|
|
387
|
+
|
|
388
|
+
// \`screencapture\` rather than CGDisplayCreateImage: it survives the API's
|
|
389
|
+
// deprecation on new macOS and needs the same Screen Recording grant.
|
|
390
|
+
func captureDisplay() throws -> CGImage {
|
|
391
|
+
let path = NSTemporaryDirectory() + "broods-desktop-\\(UUID().uuidString).png"
|
|
392
|
+
defer { try? FileManager.default.removeItem(atPath: path) }
|
|
393
|
+
let process = Process()
|
|
394
|
+
process.executableURL = URL(fileURLWithPath: "/usr/sbin/screencapture")
|
|
395
|
+
process.arguments = ["-x", "-m", "-t", "png", path]
|
|
396
|
+
try process.run()
|
|
397
|
+
process.waitUntilExit()
|
|
398
|
+
guard process.terminationStatus == 0,
|
|
399
|
+
let source = CGImageSourceCreateWithURL(URL(fileURLWithPath: path) as CFURL, nil),
|
|
400
|
+
let image = CGImageSourceCreateImageAtIndex(source, 0, nil) else {
|
|
401
|
+
throw ActionError(message: "screen capture failed; grant Screen Recording to this terminal (broods machine --doctor)")
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return image
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
func screenshot(region: [Double]?) throws -> EncodedImage {
|
|
408
|
+
let full = try captureDisplay()
|
|
409
|
+
let shot = shotSize()
|
|
410
|
+
var source = full
|
|
411
|
+
var frame = shot
|
|
412
|
+
if let region {
|
|
413
|
+
// Region is in screenshot pixels; the crop happens on the physical image.
|
|
414
|
+
let sx = CGFloat(full.width) / shot.width
|
|
415
|
+
let sy = CGFloat(full.height) / shot.height
|
|
416
|
+
let rect = CGRect(x: region[0] * sx, y: region[1] * sy, width: (region[2] - region[0]) * sx, height: (region[3] - region[1]) * sy).integral
|
|
417
|
+
guard rect.width > 0, rect.height > 0, let cropped = full.cropping(to: rect) else {
|
|
418
|
+
throw ActionError(message: "zoom region must be [x0, y0, x1, y1] inside the screenshot with x1 > x0 and y1 > y0")
|
|
419
|
+
}
|
|
420
|
+
source = cropped
|
|
421
|
+
frame = fit(rect.size, into: shot)
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return try encode(try resize(source, to: frame))
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
func fit(_ size: CGSize, into box: CGSize) -> CGSize {
|
|
428
|
+
let factor = min(box.width / size.width, box.height / size.height)
|
|
429
|
+
|
|
430
|
+
return CGSize(width: max(1, (size.width * factor).rounded()), height: max(1, (size.height * factor).rounded()))
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
func resize(_ image: CGImage, to size: CGSize) throws -> CGImage {
|
|
434
|
+
guard let context = CGContext(data: nil, width: Int(size.width), height: Int(size.height), bitsPerComponent: 8, bytesPerRow: 0, space: CGColorSpaceCreateDeviceRGB(), bitmapInfo: CGImageAlphaInfo.noneSkipLast.rawValue) else {
|
|
435
|
+
throw ActionError(message: "could not allocate the screenshot bitmap")
|
|
436
|
+
}
|
|
437
|
+
context.interpolationQuality = .high
|
|
438
|
+
context.draw(image, in: CGRect(origin: .zero, size: size))
|
|
439
|
+
guard let result = context.makeImage() else { throw ActionError(message: "could not scale the screenshot") }
|
|
440
|
+
|
|
441
|
+
return result
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
func encode(_ image: CGImage) throws -> EncodedImage {
|
|
445
|
+
let png = try encode(image, as: UTType.png, quality: nil)
|
|
446
|
+
if png.count <= MAX_PNG_BYTES {
|
|
447
|
+
return EncodedImage(data: png.base64EncodedString(), mediaType: "image/png")
|
|
448
|
+
}
|
|
449
|
+
let jpeg = try encode(image, as: UTType.jpeg, quality: JPEG_QUALITY)
|
|
450
|
+
|
|
451
|
+
return EncodedImage(data: jpeg.base64EncodedString(), mediaType: "image/jpeg")
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
func encode(_ image: CGImage, as type: UTType, quality: Double?) throws -> Data {
|
|
455
|
+
let data = NSMutableData()
|
|
456
|
+
guard let destination = CGImageDestinationCreateWithData(data, type.identifier as CFString, 1, nil) else {
|
|
457
|
+
throw ActionError(message: "could not create the image encoder")
|
|
458
|
+
}
|
|
459
|
+
var options: [CFString: Any] = [:]
|
|
460
|
+
if let quality { options[kCGImageDestinationLossyCompressionQuality] = quality }
|
|
461
|
+
CGImageDestinationAddImage(destination, image, options as CFDictionary)
|
|
462
|
+
guard CGImageDestinationFinalize(destination) else { throw ActionError(message: "could not encode the screenshot") }
|
|
463
|
+
|
|
464
|
+
return data as Data
|
|
465
|
+
}
|
|
466
|
+
`;var a="broods-desktop",w=s(y(),".broods","desktop"),C=320000;class l{#t;#r=!1;#e=new Map;constructor(t){this.#t=c(t,[],{stdio:["pipe","pipe","inherit"]}),f({input:this.#t.stdout}).on("line",(e)=>{let o;try{o=JSON.parse(e)}catch{return}let r=this.#e.get(o.id);if(!r)return;clearTimeout(r.timer),this.#e.delete(o.id),r.resolve(o)}),this.#t.on("exit",(e)=>{this.#r=!0;for(let[o,r]of this.#e)clearTimeout(r.timer),r.resolve({id:o,error:`the desktop helper exited (${e??"signal"})`});this.#e.clear()})}async display(){let t=await this.#o({action:"display"});if(!t.display)throw Error(t.error??"no display reported");return t.display}async permissions(t){let e=await this.#o({action:"permissions",request:t});if(!e.permissions)throw Error(e.error??"no permissions reported");return e.permissions}async run(t){return{...await this.#o(t),type:"computer-result",id:t.id}}stop(){this.#t.kill("SIGTERM")}#o(t){let e="id"in t?t.id:crypto.randomUUID();if(this.#r)return Promise.resolve({id:e,error:"the desktop helper is not running"});return new Promise((o)=>{let r=setTimeout(()=>{this.#e.delete(e),o({id:e,error:"the desktop helper did not answer"})},C);this.#e.set(e,{resolve:o,timer:r}),this.#t.stdin.write(`${JSON.stringify({...t,id:e})}
|
|
467
|
+
`)})}}async function G(t){let e=new l(b());try{let o=await e.display(),r=await e.permissions(t);return{display:o,driver:e,permissions:r}}catch(o){throw e.stop(),o}}function b(){if(process.platform!=="darwin")throw Error("computer use is macOS only for now");let t=u("sha256").update(n).digest("hex").slice(0,16),e=s(w,t),o=s(e,a);if(h(o))return o;m(e,{recursive:!0});let r=s(e,`${a}.swift`);g(r,n);let i=p("swiftc",["-O","-o",o,r],{encoding:"utf8"});if(i.error)throw Error("computer use needs the Swift compiler. Install Xcode Command Line Tools: xcode-select --install");if(i.status!==0)throw Error(`the desktop helper failed to build:
|
|
468
|
+
${i.stderr}`);return o}export{G as startDesktop};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{O}from"./chunk-54pn8m91.js";import{L,U}from"./chunk-mqsvqhkj.js";import"./chunk-s3cv33em.js";import{e,o,i,l,s}from"./cli/index.js";var R={project:e().optional(),stage:e().optional()},C={sandboxId:e().min(1),reservationKey:e().min(1)},v=i().describe("Must be true, after the owner has agreed to this delete."),E="'********' keeps a stored secret and null deletes a field: never send a placeholder you did not read from a get.",M=[{singular:"agent",plural:"agents",key:"agentId",createHint:"Needs name and config; a 409 returns the existing agentId.",deleteHint:"Deleting an agent also drops its runtime rows.",updateHint:E,list:(t)=>t.listAgents(),get:(t,r)=>t.getAgent(r),create:(t,r)=>t.createAgent(r),update:(t,r,n)=>t.updateAgent(r,n),remove:(t,r)=>t.deleteAgent(r)},{singular:"cron",plural:"crons",key:"cronId",createHint:"Needs name, agentId, scheduleExpression (rate/cron/at) and input or events.",list:(t)=>t.listCrons(),get:(t,r)=>t.getCron(r),create:(t,r)=>t.createCron(r),update:(t,r,n)=>t.updateCron(r,n),remove:(t,r)=>t.deleteCron(r)},{singular:"sandbox",plural:"sandboxes",key:"sandboxId",updateHint:E,list:(t)=>t.listSandboxes(),get:(t,r)=>t.getSandbox(r),create:(t,r)=>t.createSandbox(r),update:(t,r,n)=>t.updateSandbox(r,n),remove:(t,r)=>t.deleteSandbox(r)},{singular:"workspace",plural:"workspaces",key:"workspaceId",list:(t)=>t.listWorkspaces(),get:(t,r)=>t.getWorkspace(r),create:(t,r)=>t.createWorkspace(r),update:(t,r,n)=>t.updateWorkspace(r,n),remove:(t,r)=>t.deleteWorkspace(r)},{singular:"policy",plural:"policies",key:"policyId",createHint:"Agent-runtime policy document (tool.call, skill.load), not API credential scoping.",list:(t)=>t.listPolicies(),get:(t,r)=>t.getPolicy(r),create:(t,r)=>t.createPolicy(r),update:(t,r,n)=>t.updatePolicy(r,n),remove:(t,r)=>t.deletePolicy(r)},{singular:"role",plural:"roles",key:"roleId",createHint:"Needs name and a version-1 policy over the API namespace, e.g. agents:read. Account secret only.",list:(t)=>t.listRoles(),get:(t,r)=>t.getRole(r),create:(t,r)=>t.createRole(r),update:(t,r,n)=>t.updateRole(r,n),remove:(t,r)=>t.deleteRole(r)},{singular:"channel",plural:"channels",key:"channelId",list:(t)=>t.listChannels(),get:(t,r)=>t.getChannel(r),create:(t,r)=>t.createChannel(r),update:(t,r,n)=>t.updateChannel(r,n),remove:(t,r)=>t.deleteChannel(r)},{singular:"skill",plural:"skills",key:"skillName",createHint:'source is "files" (base64), "json" or "github". The stored name comes from the SKILL.md frontmatter.',list:(t)=>t.listSkills(),get:(t,r)=>t.getSkill(r),create:(t,r)=>t.createSkill(r),remove:(t,r)=>t.deleteSkill(r)},{singular:"mcp",plural:"mcp",key:"serverId",scoped:!0,list:(t,r)=>t.listMcp(r),get:(t,r)=>t.getMcp(r),create:(t,r,n)=>t.createMcp(n,r),update:(t,r,n)=>t.updateMcp(r,n),remove:(t,r)=>t.deleteMcp(r)}];function Pt(t={}){let r=L()?new U({}):null;if(!r&&!t.cli)throw Error("broods mcp needs a credential: set BROODS_SESSION_TOKEN or BROODS_ACCOUNT_SECRET, or run `broods login`.");let n=new O({name:"broods",version:"1"},{capabilities:{tools:{}}});if(r){for(let a of M)N(n,r,a,t.scope??{});j(n,r)}if(t.cli)P(n,t.cli);if(t.allowDestructive)q(n,r,t.cli??null);return n}function w(t,r){if(!t)throw Error(`Refusing without confirm:true. ${r}`)}async function d(t){try{return k(await t())}catch(r){return A(r)}}function A(t){return{content:[{type:"text",text:t instanceof Error?t.message:String(t)}],isError:!0}}function k(t){return{content:[{type:"text",text:JSON.stringify(t??null,null,2)}]}}function b(t,r,n,a){if(!t.scoped)return;let c=n??r.project,p=a??r.stage;if(!c||!p)throw Error(`${t.plural} live in one stage, so this call needs both 'project' and 'stage'`);return{project:c,stage:p}}function P(t,r){t.registerTool("list-orgs",{description:"Every organization this login can reach, with the current one marked.",inputSchema:{}},async()=>await d(async()=>{let n=await r.getOnboarding();return{currentOrgId:n.currentOrgId,orgs:n.orgs}})),t.registerTool("create-org",{description:"Create an organization and switch this login to it. Later calls act in the new org.",inputSchema:{name:e().min(1)}},async({name:n})=>await d(async()=>await r.createOnboardingOrg(n))),t.registerTool("select-org",{description:"Switch this login to another organization, by an orgId from list-orgs.",inputSchema:{orgId:e().min(1)}},async({orgId:n})=>await d(async()=>await r.selectOnboardingOrg(n))),t.registerTool("list-projects",{description:"Every project in the current org, empty ones last. Project names are not unique, so take the id from here.",inputSchema:{}},async()=>await d(async()=>await r.listProjects())),t.registerTool("list-stages",{description:"Every stage of one project, by project name.",inputSchema:{project:e().min(1)}},async({project:n})=>await d(async()=>await r.listStages(n))),t.registerTool("create-stage",{description:"Create a stage in a project, optionally cloning another stage's architecture and env vars. A project name that does not exist yet is created with it, which is the only way to make a project without deploying a manifest.",inputSchema:{project:e().min(1),name:e().min(1),from:e().optional().describe("Stage to clone architecture and env vars from.")}},async({project:n,name:a,from:c})=>await d(async()=>await r.createStage(n,a,c)))}function q(t,r,n){if(r)t.registerTool("rotate-secret",{description:"Rotate the account secret. The current secret stops working immediately and the new one is shown once, so this breaks every deployment and CI job still holding the old one. Requires confirm:true.",inputSchema:{confirm:v}},async({confirm:a})=>await d(async()=>(w(a,"Everything holding the current secret breaks the moment this runs."),await r.rotateSecret())));if(n)t.registerTool("delete-project",{description:"Delete a project and everything under it: stages, agents, canvas, env vars, crons and workspace files. Takes a projectId from list-projects and requires confirm:true.",inputSchema:{projectId:e().min(1),confirm:v}},async({projectId:a,confirm:c})=>await d(async()=>(w(c,`Project '${a}' and everything under it goes away: name it, get the owner's agreement, then retry.`),await n.deleteProject(a))))}function j(t,r){t.registerTool("list-cron-runs",{description:"Run history for one cron, newest first. Check this before telling anyone a cron fired.",inputSchema:{cronId:e().min(1),limit:o().int().positive().max(100).optional()}},async({cronId:a,limit:c})=>await d(async()=>await r.listCronRuns(a,c===void 0?{}:{limit:c})));let n=[["suspend-sandbox","Suspend a persistent sandbox reservation.",(a,c)=>r.suspendSandbox(a,c)],["resume-sandbox","Resume a suspended sandbox reservation.",(a,c)=>r.resumeSandbox(a,c)],["terminate-sandbox","Terminate a sandbox reservation and drop its live-instance row. Terminate only a sandbox you created, and never one holding state you did not put there.",(a,c)=>r.terminateSandbox(a,c)],["open-sandbox-terminal","Mint a short-lived sealed ticket for an interactive PTY session on a persistent sandbox.",(a,c)=>r.openSandboxTerminal(a,c)]];for(let[a,c,p]of n)t.registerTool(a,{description:c,inputSchema:C},async({sandboxId:f,reservationKey:g})=>await d(async()=>await p(f,g)));t.registerTool("snapshot-sandbox",{description:"Snapshot a sandbox reservation into a reusable image (self-hosted provider).",inputSchema:{sandboxId:e().min(1),reservationKey:e().min(1),name:e().min(1)}},async({sandboxId:a,reservationKey:c,name:p})=>await d(async()=>await r.snapshotSandbox(a,c,p))),t.registerTool("upload-skill",{description:"Replace one skill's bundle wholesale (PUT, not a merge) by skillName. 'body' is the same source shape create-skill takes.",inputSchema:{id:e().min(1).describe("The skillName."),body:s(e(),l())}},async({id:a,body:c})=>await d(async()=>await r.uploadSkill(a,c))),t.registerTool("list-env-vars",{description:"List environment variable names. Values are write-only: the plane never returns them, so there is no read.",inputSchema:{}},async()=>await d(async()=>await r.listEnvVars())),t.registerTool("set-env-var",{description:"Create or replace one write-only environment variable. Reference it from a config as ${NAME} rather than inlining the value.",inputSchema:{name:e().min(1),value:e()}},async({name:a,value:c})=>await d(async()=>(await r.setEnvVar(a,c),{name:a,stored:!0}))),t.registerTool("delete-env-var",{description:"Delete one environment variable.",inputSchema:{name:e().min(1),confirm:v}},async({name:a,confirm:c})=>await d(async()=>(w(c,`Anything referencing \${${a}} breaks the moment this env var goes.`),{deleted:await r.deleteEnvVar(a),name:a}))),t.registerTool("assume-role",{description:"Exchange a role for a short-lived session token (default 1h, max 12h). Hand the returned token to whatever tool needs narrow access, as BROODS_SESSION_TOKEN. It is shown once and is not stored here.",inputSchema:{roleId:e().min(1),ttlSeconds:o().int().positive().max(43200).optional()}},async({roleId:a,ttlSeconds:c})=>await d(async()=>await r.assumeRole(a,c===void 0?{}:{ttlSeconds:c}))),t.registerTool("update-account",{description:"Rename this account or change its description. Everything else about an account is derived, not settable.",inputSchema:{username:e().optional(),description:e().nullable().optional()}},async({username:a,description:c})=>await d(async()=>await r.updateAccount({...a===void 0?{}:{username:a},...c===void 0?{}:{description:c}}))),t.registerTool("get-account",{description:"The account this server's credential resolves to. Run it first to confirm which tenant you are about to change.",inputSchema:{}},async()=>await d(async()=>await r.getAccount()))}function N(t,r,n,a){let c=e().min(1).describe(`The ${n.key}.`),p=n.scoped?` ${n.plural} live in one stage, so this also takes 'project' and 'stage'.`:"",{list:f,get:g,create:y,update:S,remove:_}=n;if(f){let u=`List every ${n.singular} on the account.${p}`;if(n.scoped)t.registerTool(`list-${n.plural}`,{description:u,inputSchema:R},async({project:h,stage:m})=>await d(async()=>await f(r,b(n,a,h,m))));else t.registerTool(`list-${n.plural}`,{description:u,inputSchema:{}},async()=>await d(async()=>await f(r)))}if(g)t.registerTool(`get-${n.singular}`,{description:`Read one ${n.singular} by ${n.key}.`,inputSchema:{id:c}},async({id:u})=>await d(async()=>await g(r,u)));if(y){let u=`Create one ${n.singular}. 'body' is the request body the config plane documents.`+(n.createHint?` ${n.createHint}`:"")+p+" Prefer changing the broods/ manifest and deploying for anything the project already declares.",h=s(e(),l());if(n.scoped)t.registerTool(`create-${n.singular}`,{description:u,inputSchema:{body:h,...R}},async({body:m,project:T,stage:I})=>await d(async()=>await y(r,m,b(n,a,T,I))));else t.registerTool(`create-${n.singular}`,{description:u,inputSchema:{body:h}},async({body:m})=>await d(async()=>await y(r,m)))}if(S)t.registerTool(`update-${n.singular}`,{description:`Deep-merge a patch into one ${n.singular} and return the updated record, so there is no need to read it back.`+(n.updateHint?` ${n.updateHint}`:""),inputSchema:{id:c,body:s(e(),l())}},async({id:u,body:h})=>await d(async()=>await S(r,u,h)));if(_)t.registerTool(`delete-${n.singular}`,{description:`Delete one ${n.singular}. Requires confirm:true, and takes one ${n.key} per call: never loop this over a list.`+(n.deleteHint?` ${n.deleteHint}`:""),inputSchema:{id:c,confirm:v}},async({id:u,confirm:h})=>await d(async()=>(w(h,`Deleting ${n.singular} '${u}' goes away for good: name it, get the owner's agreement, then retry.`),{deleted:await _(r,u),id:u})))}export{Pt as createBroodsMcpServer};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{C,a,D,z,B,V,Z,b,x,H,re,ne,me,fe,he,ge,oe,se,ye,ve,be,O}from"./chunk-54pn8m91.js";import"./chunk-s3cv33em.js";var J=class{_readBuffer;_started=!1;_closed=!1;constructor(r=oe.stdin,o=oe.stdout,c){this._stdin=r,this._stdout=o,this._readBuffer=new he({maxBufferSize:c?.maxBufferSize})}onclose;onerror;onmessage;_ondata=(r)=>{try{this._readBuffer.append(r),this.processReadBuffer()}catch(o){this.onerror?.(o),this.close().catch(()=>{})}};_onerror=(r)=>{this.onerror?.(r)};_onstdouterror=(r)=>{this.onerror?.(r),this.close().catch(()=>{})};async start(){if(this._started)throw Error("StdioServerTransport already started! If using Server class, note that connect() calls start() automatically.");this._started=!0,this._stdin.on("data",this._ondata),this._stdin.on("error",this._onerror),this._stdout.on("error",this._onstdouterror)}processReadBuffer(){while(!0)try{let r=this._readBuffer.readMessage();if(r===null)break;this.onmessage?.(r)}catch(r){this.onerror?.(r)}}async close(){if(this._closed)return;if(this._closed=!0,this._stdin.off("data",this._ondata),this._stdin.off("error",this._onerror),this._stdout.off("error",this._onstdouterror),this._stdin.listenerCount("data")===0)this._stdin.pause();this._readBuffer.clear(),this.onclose?.()}send(r){if(this._closed)return Promise.reject(Error("StdioServerTransport is closed"));return new Promise((o,c)=>{let l=ge(r),t=!1,f=(_)=>{if(t)return;t=!0,this._stdout.off("error",f),this._stdout.off("drain",p),c(_)},p=()=>{if(t)return;t=!0,this._stdout.off("error",f),this._stdout.off("drain",p),o()};if(this._stdout.once("error",f),this._stdout.write(l)){if(t)return;t=!0,this._stdout.off("error",f),o()}else if(!t)this._stdout.once("drain",p)})}},k=3000,L=class{onclose;onerror;onmessage;_closed=!1;_pendingRequests=new Set;_drainWaiters=[];constructor(r,o,c){this._wire=r,this._onInstanceClose=o,this._outboundIntercept=c}async start(){}async send(r,o){if(H(r)||re(r)){let{id:c}=r;if(c!==void 0)this._settle(c)}if(this._closed)return;if(this._outboundIntercept?.(r)==="handled")return;return this._wire.send(r,o)}setProtocolVersion=(r)=>{this._wire.setProtocolVersion?.(r)};deliver(r,o){if(this._closed)return;if(b(r))this._pendingRequests.add(r.id);else if(x(r)&&r.method==="notifications/cancelled"){let c=r.params?.requestId;if(c!==void 0)this._settle(c)}this.onmessage?.(r,o)}async whenRequestsAnswered(r){if(this._closed||this._pendingRequests.size===0)return!0;return await new Promise((o)=>{let c=()=>{clearTimeout(l),o(!0)},l=setTimeout(()=>{this._drainWaiters=this._drainWaiters.filter((t)=>t!==c),o(!1)},r);this._drainWaiters.push(c)})}async close(){if(this._closed)return;this._closed=!0,this._pendingRequests.clear(),this._releaseDrainWaiters();try{this._onInstanceClose()}finally{this.onclose?.()}}_settle(r){if(this._pendingRequests.delete(r),this._pendingRequests.size===0)this._releaseDrainWaiters()}_releaseDrainWaiters(){let r=this._drainWaiters;this._drainWaiters=[];for(let o of r)o()}};function U(r){let o=r.params;if(r.method==="initialize"&&!ne(o)){let f=o!==null&&typeof o==="object"&&typeof o.protocolVersion==="string"?o.protocolVersion:void 0;return{kind:"legacy",reason:"initialize",...f!==void 0&&{requestedVersion:f}}}if(!B(o))return{kind:"legacy",reason:"no-claim"};let c=z(o),l=(c===void 0?[]:Z(c))[0];if(l!==void 0)return{kind:"invalid-envelope",issue:l};let t=V(o);if(t===void 0||!C.includes(t))return{kind:"unsupported-revision",requested:t??"unknown"};return{kind:"modern",revision:t,classification:{era:"modern",revision:t}}}function Q(r,o={}){let c=o.legacy??"serve",l=o.transport??new J,t={phase:"opening"},f,p=!1,_=()=>p||t.phase==="closed",d=(e)=>{try{o.onerror?.(e)}catch{}},y=(e,n,i,s)=>l.send({jsonrpc:"2.0",id:e,error:{code:n,message:i,...s!==void 0&&{data:s}}}).catch((u)=>d(h(u))),w=new ye(o.maxSubscriptions??se),M=(e)=>{if(!x(e))return;let n=w.routeOutbound(e);if(n==="passthrough")return;for(let i of n)l.send({jsonrpc:"2.0",...i}).catch((s)=>d(h(s)));return"handled"},I=async(e)=>{if(b(e)&&e.method==="subscriptions/listen"){let n=z(e.params),i=B(e.params)?(n===void 0?[]:Z(n))[0]:{key:"_meta",problem:"the per-request envelope is required on protocol revision 2026-07-28"},s=V(e.params),u;if(i!==void 0)u={jsonrpc:"2.0",id:e.id,error:{code:-32602,message:`Invalid _meta envelope: ${i.key}: ${i.problem}`}};else if(s===void 0||!C.includes(s)){let v=new D({supported:[...C],requested:s??"unknown"});u={jsonrpc:"2.0",id:e.id,error:{code:v.code,message:v.message,data:v.data}}}else u=w.serve(e);return await l.send("error"in u?u:{jsonrpc:"2.0",method:u.method,params:u.params}).catch((v)=>d(h(v))),!0}if(x(e)&&e.method==="notifications/cancelled"){let n=e.params?.requestId;if(n!==void 0&&w.cancel(n))return!0}return!1},q=(e,n,i)=>{let s=me({kind:"legacy",reason:n,...i!==void 0&&{requestedVersion:i}},C);if(s===void 0)return Promise.resolve();return d(Error(`Rejected 2025-era request on a modern-only stdio connection (${s.cell}): ${s.message}`)),y(e.id,s.code,s.message,s.data)},T=(e)=>{if(p||e===f)return;P()},m=async(e,n)=>{let i=await r({era:e}),s=i instanceof O?i.server:i;if(e==="modern")fe(s,n),ve(s,C),w.setServerCapabilities(s.getCapabilities(),be(s));let u=new L(l,()=>T(u),e==="modern"?M:void 0);return await i.connect(u),{product:i,channel:u}},R=(e)=>e.product.close().catch((n)=>d(h(n))),N=async(e)=>{f=e.channel;try{if(!await e.channel.whenRequestsAnswered(k))d(Error(`Discarded the probe instance with requests still unanswered after ${k}ms; continuing with the fallback`));await e.product.close()}catch(n){d(h(n))}finally{f=void 0}},W=async(e)=>{if(t.phase==="closed")return;if(t.phase==="pinned"){if(t.era==="modern"&&b(e)&&e.method==="initialize"&&!ne(e.params)){await q(e,"initialize",e.params!==null&&typeof e.params==="object"&&typeof e.params.protocolVersion==="string"?e.params.protocolVersion:void 0);return}if(t.era==="modern"&&await I(e))return;t.instance.channel.deliver(e);return}if(!b(e)&&!x(e)){d(Error("Discarded a JSON-RPC response received before the connection negotiated an era"));return}let n=U(e);switch(n.kind){case"invalid-envelope":{let i=`Invalid _meta envelope for protocol revision 2026-07-28: ${n.issue.key}: ${n.issue.problem}`;if(b(e))await y(e.id,a.InvalidParams,i,{envelope:n.issue});else d(Error(`Discarded a notification with a malformed envelope: ${i}`));return}case"unsupported-revision":if(b(e)){let i=new D({supported:[...C],requested:n.requested});d(i),await y(e.id,i.code,i.message,i.data)}else d(Error(`Discarded a notification claiming unsupported protocol revision ${n.requested}`));return;case"modern":if(b(e)&&e.method==="server/discover"){if(t.phase==="probe"){t.instance.channel.deliver(e,{classification:n.classification});return}let i=await m("modern",n.revision);if(_()){await R(i);return}t={phase:"probe",instance:i},i.channel.deliver(e,{classification:n.classification});return}if(t.phase==="probe"){if(x(e)){t.instance.channel.deliver(e,{classification:n.classification});return}t={phase:"pinned",era:"modern",instance:t.instance}}else{let i=await m("modern",n.revision);if(_()){await R(i);return}t={phase:"pinned",era:"modern",instance:i}}if(await I(e))return;t.instance.channel.deliver(e,{classification:n.classification});return;case"legacy":{if(c==="reject"){if(b(e))await q(e,n.reason,n.requestedVersion);return}if(t.phase==="probe"){if(await N(t.instance),_())return;t={phase:"opening"}}let i=await m("legacy");if(_()){await R(i);return}t={phase:"pinned",era:"legacy",instance:i},t.instance.channel.deliver(e);return}}},S=[],E=!1,A=async()=>{if(E)return;E=!0;try{while(S.length>0){let e=S.shift();try{await W(e)}catch(n){if(b(e))await y(e.id,a.InternalError,"Internal server error");d(h(n))}}}finally{E=!1}},P=async()=>{if(p||t.phase==="closed")return;p=!0;let e=t;t={phase:"closed"};for(let n of w.teardownAll())await l.send(n).catch((i)=>d(h(i)));if(e.phase==="probe"||e.phase==="pinned")await e.instance.product.close().catch((n)=>d(h(n)));await l.close().catch((n)=>d(h(n)))};l.onmessage=(e)=>{S.push(e),A()},l.onerror=(e)=>{if(d(e),t.phase==="probe"||t.phase==="pinned")t.instance.channel.onerror?.(e)},l.onclose=()=>{if(p||t.phase==="closed")return;p=!0;let e=t;if(t={phase:"closed"},e.phase==="probe"||e.phase==="pinned")e.instance.product.close().catch((n)=>d(h(n)))};let j=l.start().catch((e)=>{throw d(h(e)),e});return j.catch(()=>{}),{close:async()=>{await j.catch(()=>{}),await P()}}}function h(r){return r instanceof Error?r:Error(String(r))}export{Q as serveStdio};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var u=/^[A-Z][A-Z0-9_]*$/;class m extends Error{status;body;constructor(e,t,n,s){super(`${e} ${t} failed with ${n}: ${s}`);this.name="BroodsAccountApiError",this.status=n,this.body=s}}function
|
|
2
|
-
export{m
|
|
1
|
+
var u=/^[A-Z][A-Z0-9_]*$/;class m extends Error{status;body;constructor(e,t,n,s){super(`${e} ${t} failed with ${n}: ${s}`);this.name="BroodsAccountApiError",this.status=n,this.body=s}}function Ce(e){if(!u.test(e)||e.length>64)throw Error("envPlaceholder name must match /^[A-Z][A-Z0-9_]*$/ and be at most 64 characters.");return`\${${e}}`}function L(){return o("BROODS_SESSION_TOKEN")??o("BROODS_ACCOUNT_SECRET")}class U{baseUrl;bearerToken;fetchImpl;constructor(e={}){let t=e.baseUrl??o("BROODS_BASE_URL")??"https://gateway.broods.app",n=e.sessionToken??e.accountSecret??L();if(!n)throw Error("BroodsAccountClient requires an accountSecret or sessionToken (or BROODS_ACCOUNT_SECRET / BROODS_SESSION_TOKEN).");let s=t.length;while(s>0&&t.charCodeAt(s-1)===47)s-=1;this.baseUrl=t.slice(0,s),this.bearerToken=n,this.fetchImpl=e.fetch??fetch}async getAccount(){let e=await this.request("GET","/v1/account");if(!e)throw new m("GET","/v1/account",404,"Account not found");return e.account}async updateAccount(e){return(await this.request("PATCH","/v1/account",e))?.account??null}async assumeRole(e,t={}){let n=await this.request("POST","/v1/account/assume-role",{roleId:e,...t.ttlSeconds!==void 0?{ttlSeconds:t.ttlSeconds}:{}});if(!n)throw new m("POST","/v1/account/assume-role",404,"Role not found");return n}async rotateSecret(){let e=await this.request("POST","/v1/account/rotate-secret");if(!e)throw new m("POST","/v1/account/rotate-secret",404,"Account not found");return e}async deleteAccount(){return await this.request("DELETE","/v1/account")??{deleted:!1}}webhookUrl(e,t){let n=[e,t].map(encodeURIComponent);return`${this.baseUrl}/v1/webhooks/${n.join("/")}`}async listAgents(){return(await this.request("GET","/v1/agents"))?.agents??[]}async createAgent(e){let t=await this.request("POST","/v1/agents",e);if(!t)throw new m("POST","/v1/agents",404,"Not found");return t}async getAgent(e){return await this.request("GET",`/v1/agents/${encodeURIComponent(e)}`)}async updateAgent(e,t){return await this.request("PATCH",`/v1/agents/${encodeURIComponent(e)}`,t)}async deleteAgent(e){return(await this.request("DELETE",`/v1/agents/${encodeURIComponent(e)}`))?.deleted??!1}async listEnvVars(){return(await this.request("GET","/v1/env"))?.env??[]}async setEnvVar(e,t){await this.request("PUT",`/v1/env/${encodeURIComponent(e)}`,{value:t})}async deleteEnvVar(e){return(await this.request("DELETE",`/v1/env/${encodeURIComponent(e)}`))?.deleted??!1}async listCrons(){return(await this.request("GET","/v1/crons"))?.crons??[]}async createCron(e){let t=await this.request("POST","/v1/crons",e);if(!t)throw new m("POST","/v1/crons",404,"Not found");return t}async getCron(e){return await this.request("GET",`/v1/crons/${encodeURIComponent(e)}`)}async updateCron(e,t){return await this.request("PATCH",`/v1/crons/${encodeURIComponent(e)}`,t)}async deleteCron(e){return(await this.request("DELETE",`/v1/crons/${encodeURIComponent(e)}`))?.deleted??!1}async listCronRuns(e,t={}){let n=t.limit!==void 0?`?limit=${t.limit}`:"";return(await this.request("GET",`/v1/crons/${encodeURIComponent(e)}/runs${n}`))?.runs??[]}async listWorkspaces(){return(await this.request("GET","/v1/workspaces"))?.workspaces??[]}async createWorkspace(e){let t=await this.request("POST","/v1/workspaces",e);if(!t)throw new m("POST","/v1/workspaces",404,"Not found");return t}async getWorkspace(e){return await this.request("GET",`/v1/workspaces/${encodeURIComponent(e)}`)}async updateWorkspace(e,t){return await this.request("PATCH",`/v1/workspaces/${encodeURIComponent(e)}`,t)}async deleteWorkspace(e){return(await this.request("DELETE",`/v1/workspaces/${encodeURIComponent(e)}`))?.deleted??!1}async listWorkspaceFiles(e){return(await this.request("GET",`/v1/workspaces/${encodeURIComponent(e)}/files`))?.files??[]}async getWorkspaceFileUrl(e,t){let n=`?path=${encodeURIComponent(t)}`;return(await this.request("GET",`/v1/workspaces/${encodeURIComponent(e)}/files${n}`))?.url??null}async uploadWorkspaceFile(e,t){let n=`/v1/workspaces/${encodeURIComponent(e)}/files`,s=await this.request("POST",n,t);if(!s)throw new m("POST",n,404,"Workspace not found");return s.file}async renameWorkspaceFile(e,t,n){return(await this.request("PATCH",`/v1/workspaces/${encodeURIComponent(e)}/files`,{path:t,newPath:n}))?.renamed??!1}async deleteWorkspaceFile(e,t){return(await this.request("DELETE",`/v1/workspaces/${encodeURIComponent(e)}/files`,{path:t}))?.deleted??!1}async listSandboxes(){return(await this.request("GET","/v1/sandboxes"))?.sandboxes??[]}async createSandbox(e){let t=await this.request("POST","/v1/sandboxes",e);if(!t)throw new m("POST","/v1/sandboxes",404,"Not found");return t}async getSandbox(e){return await this.request("GET",`/v1/sandboxes/${encodeURIComponent(e)}`)}async updateSandbox(e,t){return await this.request("PATCH",`/v1/sandboxes/${encodeURIComponent(e)}`,t)}async deleteSandbox(e){return(await this.request("DELETE",`/v1/sandboxes/${encodeURIComponent(e)}`))?.deleted??!1}async suspendSandbox(e,t){return await this.sandboxAction(e,"suspend",{reservationKey:t})}async resumeSandbox(e,t){return await this.sandboxAction(e,"resume",{reservationKey:t})}async terminateSandbox(e,t){return await this.sandboxAction(e,"terminate",{reservationKey:t})}async snapshotSandbox(e,t,n){return await this.sandboxAction(e,"snapshot",{reservationKey:t,name:n})}async openSandboxTerminal(e,t){return await this.sandboxAction(e,"terminal",{reservationKey:t})}async listMcp(e){let t=`/v1/mcp${i(e)}`,n=await this.request("GET",t);if(!n)throw new m("GET",t,404,"Not found");return n.servers??[]}async createMcp(e,t){let n=`/v1/mcp${i(e)}`,s=await this.request("POST",n,t);if(!s)throw new m("POST",n,404,"Not found");return s}async uploadMcpBundle(e){let n=await this.request("POST","/v1/mcp/uploads");if(!n?.uploadUrl)throw new m("POST","/v1/mcp/uploads",502,"response omitted uploadUrl");let s=await this.fetchImpl(n.uploadUrl,{method:"POST",headers:{"Content-Type":"application/javascript"},body:e});if(!s.ok)throw new m("POST","/v1/mcp/uploads",s.status,await s.text());let{storageId:r}=await s.json();if(!r)throw new m("POST","/v1/mcp/uploads",502,"upload response omitted storageId");let a=await crypto.subtle.digest("SHA-256",new TextEncoder().encode(e)),c=Array.from(new Uint8Array(a)).map((l)=>l.toString(16).padStart(2,"0")).join("");return{bundleStorageId:r,sha256:c}}async getMcp(e){return await this.request("GET",`/v1/mcp/${encodeURIComponent(e)}`)}async updateMcp(e,t){return await this.request("PATCH",`/v1/mcp/${encodeURIComponent(e)}`,t)}async deleteMcp(e){return(await this.request("DELETE",`/v1/mcp/${encodeURIComponent(e)}`))?.deleted??!1}async listPolicies(){return(await this.request("GET","/v1/policies"))?.policies??[]}async createPolicy(e){let t=await this.request("POST","/v1/policies",e);if(!t)throw new m("POST","/v1/policies",404,"Not found");return t}async getPolicy(e){return await this.request("GET",`/v1/policies/${encodeURIComponent(e)}`)}async updatePolicy(e,t){return await this.request("PATCH",`/v1/policies/${encodeURIComponent(e)}`,t)}async deletePolicy(e){return(await this.request("DELETE",`/v1/policies/${encodeURIComponent(e)}`))?.deleted??!1}async listRoles(){return(await this.request("GET","/v1/roles"))?.roles??[]}async createRole(e){let t=await this.request("POST","/v1/roles",e);if(!t)throw new m("POST","/v1/roles",404,"Not found");return t}async getRole(e){return await this.request("GET",`/v1/roles/${encodeURIComponent(e)}`)}async updateRole(e,t){return await this.request("PATCH",`/v1/roles/${encodeURIComponent(e)}`,t)}async deleteRole(e){return(await this.request("DELETE",`/v1/roles/${encodeURIComponent(e)}`))?.deleted??!1}async listChannels(){return(await this.request("GET","/v1/channels"))?.channels??[]}async createChannel(e){let t=await this.request("POST","/v1/channels",e);if(!t)throw new m("POST","/v1/channels",404,"Not found");return t}async getChannel(e){return await this.request("GET",`/v1/channels/${encodeURIComponent(e)}`)}async updateChannel(e,t){return await this.request("PATCH",`/v1/channels/${encodeURIComponent(e)}`,t)}async deleteChannel(e){return(await this.request("DELETE",`/v1/channels/${encodeURIComponent(e)}`))?.deleted??!1}async listSkills(){return(await this.request("GET","/v1/skills"))?.skills??[]}async createSkill(e){let t=await this.request("POST","/v1/skills",e);if(!t)throw new m("POST","/v1/skills",404,"Not found");return t}async getSkill(e){return await this.request("GET",`/v1/skills/${encodeURIComponent(e)}`)}async uploadSkill(e,t){let n=`/v1/skills/${encodeURIComponent(e)}`,s=await this.request("PUT",n,t);if(!s)throw new m("PUT",n,404,"Skill not found");return s}async deleteSkill(e){return(await this.request("DELETE",`/v1/skills/${encodeURIComponent(e)}`))?.deleted??!1}async sandboxAction(e,t,n){let s=`/v1/sandboxes/${encodeURIComponent(e)}/${t}`,r=await this.request("POST",s,n);if(!r)throw new m("POST",s,404,"Sandbox not found");return r}async request(e,t,n){let s=await this.fetchImpl(`${this.baseUrl}${t}`,{method:e,headers:{Authorization:`Bearer ${this.bearerToken}`,...n!==void 0?{"Content-Type":"application/json"}:{}},...n!==void 0?{body:JSON.stringify(n)}:{}});if(s.status===404)return null;if(!s.ok)throw new m(e,t,s.status,await s.text());return await s.json()}}function o(e){return typeof process<"u"?process?.env?.[e]:void 0}function i(e){return`?${new URLSearchParams({project:e.project,stage:e.stage}).toString()}`}
|
|
2
|
+
export{m,Ce,L,U};
|
|
File without changes
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
function W(e){if(e.events&&e.input!==void 0)throw Error("Run input accepts either `input` or `events`, not both");if(e.events&&e.events.length>0)return e.events;if(typeof e.input==="string")return[{role:"user",content:[{type:"text",text:e.input}]}];throw Error("Run input requires `input` (string) or a non-empty `events` array")}async function*Se(e){let n=e.getReader(),t=new TextDecoder,o="";try{while(!0){let{done:r,value:s}=await n.read();if(r)break;o+=t.decode(s,{stream:!0});let i=o.split(`
|
|
2
|
+
`);o=i.pop()??"";for(let a of i)if(a.startsWith("data: "))yield a.slice(6)}}finally{n.releaseLock()}}var Me=500,ge=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,le=/^(?!0{32}$)[0-9a-f]{32}$/;function xe(e){return e==="DEBUG"||e==="INFO"||e==="WARN"||e==="ERROR"}function Ae(e){if(typeof e!=="object"||e===null)return!1;let n=e;if(n.type==="subscribe")return de(n);if(n.type==="unsubscribe")return D(n.stream);if(n.type==="fetchTrace")return $e(n.traceId);return!1}function ze(e){return e==="task"||e==="cron"||e==="subtask"}function G(e){return typeof e==="string"&&ge.test(e)}function $e(e){return typeof e==="string"&&le.test(e)}function D(e){return e==="logs"||e==="traces"}function de(e){let n=e.stream;if(!D(n))return!1;let t=e.backfill;if(t!==void 0&&(typeof t!=="number"||!Number.isSafeInteger(t)||t<0||t>500))return!1;if(e.liveOnly!==void 0&&typeof e.liveOnly!=="boolean")return!1;let o=e.minLevel;if(o!==void 0&&!xe(o))return!1;let r=e.sandboxId;return r===void 0||n==="logs"&&G(r)}import{mkdir as pe,readFile as fe,writeFile as ye}from"node:fs/promises";import{homedir as me}from"node:os";import{dirname as be,join as Re}from"node:path";var v="broods",w="_generated",k=Re(me(),".broods","config.json"),T="broods.app",M="dashboard",he="gateway";async function K(){let e=process.env.BROODS_TOKEN,n=process.env.BROODS_BASE_URL;if(e&&n)return{baseUrl:g(n),token:e,createdAt:new Date().toISOString()};try{let t=JSON.parse(await fe(k,"utf8"));if(typeof t.baseUrl!=="string"||!t.baseUrl)return null;return t}catch{return null}}async function J(e){await pe(be(k),{recursive:!0}),await ye(k,`${JSON.stringify(e,null,2)}
|
|
3
|
+
`,"utf8")}function _(){return process.env.BROODS_STAGE}function g(e){let n=e.length;while(n>0&&e.charCodeAt(n-1)===47)n-=1;return e.slice(0,n)}function ae(e){try{let n=new URL(e);if(!n.hostname.startsWith(`${M}.`))return;let t=n.hostname.slice(M.length+1);if(t!==T&&!t.endsWith(`.${T}`))return;return n.hostname=`${he}.${t}`,g(n.origin)}catch{return}}import{existsSync as B,readFileSync as O,statSync as Ce}from"node:fs";import{join as H,resolve as L}from"node:path";var E=new Map,Ie=new Set(Object.keys(process.env)),U=new Set;function f(e=process.cwd()){ve(e);let n=Te();return{dashboardUrl:process.env.BROODS_DASHBOARD_URL??n?.dashboardUrl,baseUrl:process.env.BROODS_BASE_URL??n?.baseUrl,token:process.env.BROODS_TOKEN??n?.token,project:process.env.BROODS_PROJECT,stage:_()}}function N(e,n=process.cwd()){let t=process.env[e];if(t===void 0)return!1;let o=L(n);for(let r of[".env.local",".env"]){let s=H(o,r);if(!B(s))continue;let i=j(O(s,"utf8"));if(i[e]!==void 0)return i[e]!==t}return!0}function ve(e){let n=L(e),t=process.env.BROODS_RELOAD_ENV==="1";for(let o of[".env",".env.local"]){let r=H(n,o);if(!B(r)){E.delete(r);continue}let s=Ce(r).mtimeMs,i=E.get(r);if(i&&i.mtimeMs===s)continue;E.set(r,{mtimeMs:s});for(let[a,u]of Object.entries(j(O(r,"utf8"))))if(t||!Ie.has(a)||U.has(a))process.env[a]=u,U.add(a)}}function j(e){let n={};for(let t of e.split(/\r?\n/)){let o=t.trim();if(!o||o.startsWith("#"))continue;let r=o.startsWith("export ")?o.slice(7).trim():o,s=r.indexOf("=");if(s<=0)continue;let i=r.slice(0,s).trim();if(!/^[A-Za-z_][A-Za-z0-9_]*$/.test(i))continue;n[i]=Ee(r.slice(s+1).trim())}return n}function Ee(e){if(e.startsWith('"')&&e.endsWith('"'))return e.slice(1,-1).replace(/\\n/g,`
|
|
4
|
+
`).replace(/\\"/g,'"');if(e.startsWith("'")&&e.endsWith("'"))return e.slice(1,-1);let n=e.indexOf(" #");return n>=0?e.slice(0,n).trimEnd():e}function Te(){try{let e=JSON.parse(O(k,"utf8"));if(typeof e.baseUrl!=="string"||typeof e.token!=="string")return null;return{baseUrl:g(e.baseUrl),...typeof e.dashboardUrl==="string"?{dashboardUrl:g(e.dashboardUrl)}:{},token:e.token}}catch{return null}}var P="https://gateway.broods.app";class we extends Error{accepted;constructor(e){super(`Ingress ${e.eventId} was accepted asynchronously with status ${e.status??"queued"}`);this.accepted=e;this.name="IngressAcceptedError"}}class F{baseUrl;apiKey;fetchImpl;constructor(e={}){f(),this.baseUrl=Y(e.baseUrl||e.host||process.env.BROODS_BASE_URL||process.env.BROODS_HOST||P),this.apiKey=e.apiKey||process.env.BROODS_API_KEY||void 0,this.fetchImpl=e.fetch??fetch}channelWebhookUrl(e){return`${this.baseUrl}${e.webhookPath.startsWith("/")?"":"/"}${e.webhookPath}`}accountWebhookUrl(e,n){let t=[e,n].map(encodeURIComponent);return`${this.baseUrl}/v1/webhooks/${t.join("/")}`}stageWebhookUrl(e,n,t){let o=[e,"dev",n,t].map(encodeURIComponent);return`${this.baseUrl}/v1/webhooks/${o.join("/")}`}agent(e,n){if(typeof e==="string"){let o=e,r=n??"";if(!r)throw Error(`Agent ${o} is missing a generated id. Run broods dev --once or broods deploy to sync it first.`);return{id:r,run:(s)=>this.run({...s,agentName:o,agentId:r}),runAsync:(s)=>this.runAsync({...s,agentName:o,agentId:r}),continue:(s)=>this.continue({...s,agentId:r}),stream:(s)=>this.stream({...s,agentName:o,agentId:r})}}let t=e;if(!t.id)throw Error(`Agent ${t.name} is missing a generated id. Run broods dev --once or broods deploy to sync it first.`);return{id:t.id,run:(o)=>this.run(t,o),runAsync:(o)=>this.runAsync(t,o),continue:(o)=>this.continue(t,o),stream:(o)=>this.stream(t,o)}}async run(e,n){let t=[],o="",r=n?this.stream(e,n):this.stream(e);for await(let s of r)if(t.push(s),s.type==="text-delta")o+=s.text;return{text:o,events:t}}async*stream(e,n){let t=n?{...n,agentId:e.id,agentName:e.name}:e,o=q(t,"cli"),r=n?this.scopedUrl(e):`${this.baseUrl}/v1/runs`,s=await this.openStream(o,r);if(!s.ok)throw Error(`Run failed: ${s.status} ${await s.text()}`);if(s.status===202)throw new we(Z(await s.json(),o));if(!s.body)throw Error("Run response has no body");for await(let i of Se(s.body)){let a;try{a=JSON.parse(i)}catch{continue}if(a.type==="error")throw Error(`Agent run failed: ${Pe(a.error)}`);yield a}}async runAsync(e,n){let t=n?{...n,agentId:e.id,agentName:e.name}:e,o={...q(t,"async"),background:!0},r=n?this.scopedUrl(e):`${this.baseUrl}/v1/runs`;return this.postAcceptedRun(r,o,"Async run")}async continue(e,n){let t=n?e:null,o=n??e,r={agentId:t?t.id:e.agentId,eventId:o.eventId??`continue-${crypto.randomUUID()}`,conversationKey:o.conversationKey,continue:!0},s=t?this.scopedUrl(t):`${this.baseUrl}/v1/runs`;return this.postAcceptedRun(s,r,"Continue")}async getAsyncStatus(e){let n=this.resolveStatusUrl(e),t=await this.fetchJson(n,{method:"GET",headers:this.apiKeyHeaders()});if(t.status===404)return{status:"not_found"};if(!t.ok)throw Error(`Status check failed: ${t.status} ${await t.text()}`);return await t.json()}async waitForAsyncStatus(e,n={}){let t=Date.now()+(n.timeoutMs??180000),o=n.intervalMs??2000;while(Date.now()<t){if(n.signal?.aborted)throw Error("Async status polling aborted.");let r=await this.getAsyncStatus(e);if(r.status==="awaiting_approval"||r.status==="awaiting_input"||r.status==="completed"||r.status==="failed"||r.status==="expired"||r.status==="not_found")return r;await tn(o,n.signal)}throw Error("Polling timeout")}async createCron(e){let n=await this.fetchJson(`${this.baseUrl}/v1/crons`,{method:"POST",headers:this.apiKeyHeaders(),body:JSON.stringify(en(e))});if(n.status!==201)throw Error(`Create cron job failed: ${n.status} ${await n.text()}`);return await n.json()}async listCrons(){let e=await this.fetchJson(`${this.baseUrl}/v1/crons`,{method:"GET",headers:this.apiKeyHeaders()});if(!e.ok)throw Error(`List cron jobs failed: ${e.status} ${await e.text()}`);return(await e.json()).crons}async getCron(e){let n=await this.fetchJson(`${this.baseUrl}/v1/crons/${encodeURIComponent(e)}`,{method:"GET",headers:this.apiKeyHeaders()});if(n.status===404)return null;if(!n.ok)throw Error(`Get cron job failed: ${n.status} ${await n.text()}`);return await n.json()}async listCronRuns(e,n={}){let t=new URLSearchParams;if(n.limit!==void 0)t.set("limit",String(n.limit));let o=t.size>0?`?${t}`:"",r=await this.fetchJson(`${this.baseUrl}/v1/crons/${encodeURIComponent(e)}/runs${o}`,{method:"GET",headers:this.apiKeyHeaders()});if(!r.ok)throw Error(`List cron job runs failed: ${r.status} ${await r.text()}`);return(await r.json()).runs}async updateCron(e,n){let t=await this.fetchJson(`${this.baseUrl}/v1/crons/${encodeURIComponent(e)}`,{method:"PATCH",headers:this.apiKeyHeaders(),body:JSON.stringify(n)});if(!t.ok)throw Error(`Update cron job failed: ${t.status} ${await t.text()}`);return await t.json()}async deleteCron(e){let n=await this.fetchJson(`${this.baseUrl}/v1/crons/${encodeURIComponent(e)}`,{method:"DELETE",headers:this.apiKeyHeaders()});if(n.status===404)return!1;if(!n.ok)throw Error(`Delete cron job failed: ${n.status} ${await n.text()}`);return(await n.json()).deleted}scopedUrl(e){if(e.projectSlug&&e.stageSlug&&e.endpointId)return`${this.baseUrl}/v1/projects/${encodeURIComponent(e.projectSlug)}/stages/${encodeURIComponent(e.stageSlug)}/agents/${encodeURIComponent(e.endpointId)}`;return`${this.baseUrl}/v1/runs`}asyncAgentRun(e,n){return{...e,conversationKey:n,poll:()=>this.getAsyncStatus(e),wait:(t)=>this.waitForAsyncStatus(e,t)}}async postAcceptedRun(e,n,t){let o=await this.fetchJson(e,{method:"POST",headers:this.apiKeyHeaders(),body:JSON.stringify(n)});if(o.status!==202)throw Error(`${t} failed: ${o.status} ${await nn(o,"202 JSON")}`);return this.asyncAgentRun(Z(await o.json(),n),n.conversationKey)}async openStream(e,n){if(this.apiKey)return await this.fetchCore(e,n,{Authorization:`Bearer ${this.apiKey}`});throw Error(`BroodsClient streams directly from the core service at ${this.baseUrl}. Provide apiKey. For a self-hosted core service, set host/baseUrl or BROODS_HOST/BROODS_BASE_URL.`)}async fetchCore(e,n,t){try{return await this.fetchImpl(n,{method:"POST",headers:{"Content-Type":"application/json",Accept:"text/event-stream",...t},body:JSON.stringify(e)})}catch(o){throw Error(`Cannot access the broods core service at ${n}. The SDK uses ${P} by default; set host/baseUrl or BROODS_HOST/BROODS_BASE_URL to your own core service URL if your account uses a custom deployment. Cause: ${o instanceof Error?o.message:String(o)}`)}}apiKeyHeaders(){if(!this.apiKey)throw Error("BroodsClient requires apiKey or BROODS_API_KEY.");return{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`}}async fetchJson(e,n){try{return await this.fetchImpl(e,{...n,headers:{"Content-Type":"application/json",...n.headers}})}catch(t){throw Error(`Cannot access the broods core service at ${e}. Cause: ${t instanceof Error?t.message:String(t)}`)}}resolveStatusUrl(e){if(typeof e!=="string")return e.statusUrl;if(/^https?:\/\//.test(e))return e;return on(this.baseUrl,e)}}function Y(e){let n=e.trim(),t=/^[A-Za-z][A-Za-z0-9+.-]*:\/\//.test(n)?n:`https://${n}`;return g(t)}function q(e,n){let t=e.eventId??`${n}-${Date.now()}`;return{agentId:e.agentId,eventId:t,conversationKey:e.conversationKey??t,events:W(e),...e.mode!==void 0?{mode:e.mode}:{},...e.idempotencyKey!==void 0?{idempotencyKey:e.idempotencyKey}:{},...e.system!==void 0?{system:e.system}:{},...e.model!==void 0?{model:e.model}:{}}}function Pe(e){if(typeof e==="string")return e;if(!e||typeof e!=="object")return String(e);let n=e,t=n.data?.error?.message??n.message??n.responseBody??JSON.stringify(e),o=n.name?`${n.name}: `:"",r=n.statusCode?` (HTTP ${n.statusCode})`:"";return`${o}${t}${r}`}function Z(e,n){if(!e||typeof e!=="object")throw Error("Async response must be an object");let t=e.statusUrl;if(typeof t!=="string"||t.length===0)throw Error("Async response missing statusUrl");let o=typeof e.runId==="string"?e.runId:_e(t);if(!o)throw Error("Async response missing runId");let r=typeof e.eventId==="string"?e.eventId:o,{status:s,requestedMode:i}=e;return{statusUrl:t,runId:o,eventId:r,agentId:n.agentId,...s==="accepted"||s==="queued"||s==="applied"||s==="processing"?{status:s}:{},...i==="reject"||i==="followup"||i==="collect"||i==="steer"?{requestedMode:i}:{}}}function _e(e){let n=new URL(e).pathname.match(/\/v1\/runs\/([^/]+)$/);return n?.[1]?decodeURIComponent(n[1]):void 0}function en(e){if("agentId"in e)return e;let n=e.agent,t=typeof n==="string"?n:n.id,{agent:o,...r}=e;return{...r,agentId:t}}async function nn(e,n){if((e.headers.get("content-type")??"").toLowerCase().includes("text/event-stream"))return await e.body?.cancel().catch(()=>{}),`expected ${n}, but the server returned an SSE stream. This usually means the core deployment ignored the background flag and ran the request on the direct streaming runner.`;let o=await e.text();if(o.length<=2000)return o;return`${o.slice(0,2000)}... [truncated ${o.length-2000} chars]`}function tn(e,n){return new Promise((t,o)=>{if(n?.aborted){o(Error("Async status polling aborted."));return}let r=setTimeout(t,e);n?.addEventListener("abort",()=>{clearTimeout(r),o(Error("Async status polling aborted."))},{once:!0})})}function on(e,n){return`${Y(e)}/v1/runs/${encodeURIComponent(n)}`}var rn=2000,z=0,b=1;class ke{baseUrl;apiKey;WebSocketImpl;connectTimeoutMs;constructor(e={}){if(f(),this.baseUrl=te(e.baseUrl||e.host||process.env.BROODS_BASE_URL||process.env.BROODS_HOST||P),this.apiKey=e.apiKey||process.env.BROODS_API_KEY||"",!this.apiKey)throw Error("BroodsWebSocketClient requires apiKey or BROODS_API_KEY.");this.WebSocketImpl=e.WebSocket,this.connectTimeoutMs=e.connectTimeoutMs??rn}subscribe(e,n={}){let t=this.resolveWebSocket(),o=this.buildUrl(e),r=sn(e),s=ne(this.baseUrl),i=new t(o,I(this.apiKey)),a=!1,u=!1,l=setTimeout(()=>{if(!a)m(s)},this.connectTimeoutMs),x=(c=1000,S="client closed")=>{if(u)return;if(u=!0,clearTimeout(l),e.signal?.removeEventListener("abort",C),i.readyState===b||i.readyState===z)i.close(c,S)},m=(c)=>{n.onError?.(c),x(1011,"websocket error")},ue=()=>{n.onDone?.(),x(1000,"done")},C=()=>{if(i.readyState===b)i.send(JSON.stringify({type:"cancel"}));m(Error("WebSocket run aborted."))};return e.signal?.addEventListener("abort",C,{once:!0}),i.onopen=()=>{if(a=!0,clearTimeout(l),e.signal?.aborted){C();return}i.send(JSON.stringify({type:"execute",agentId:r,...e.answers?{answers:e.answers}:{events:W(e)},sessionId:e.sessionId,...e.eventId!==void 0?{eventId:e.eventId}:{},...e.mode!==void 0?{mode:e.mode}:{},...e.idempotencyKey!==void 0?{idempotencyKey:e.idempotencyKey}:{},...e.system!==void 0?{system:e.system}:{},...e.model!==void 0?{model:e.model}:{}}))},i.onmessage=(c)=>{if(typeof c.data!=="string")return;let S=Q(c.data);if(!S)return;let A=ee(S,n);switch(n.onMessage?.(A),A.type){case"meta":n.onMeta?.(A);break;case"done":ue();break;case"error":m(Error(V(A.error)));break}},i.onerror=()=>{m(s)},i.onclose=(c)=>{if(clearTimeout(l),e.signal?.removeEventListener("abort",C),u||c.code===1000)return;m(Error(c.reason||"WebSocket connection closed."))},{url:o,sendControl:function(c){if(i.readyState!==b)throw Error("WebSocket is not open");i.send(JSON.stringify({type:"control",...c}))},close:x}}attach(e,n={}){let t=this.resolveWebSocket(),o=this.buildUrl(e),r=new t(o,I(this.apiKey)),s=!1,i=(a=1000,u="client closed")=>{if(s)return;if(s=!0,r.readyState===b||r.readyState===z)r.close(a,u)};return r.onopen=()=>r.send(JSON.stringify({type:"attach",...e,signal:void 0,agent:void 0})),r.onmessage=(a)=>{if(typeof a.data!=="string")return;let u=Q(a.data);if(!u)return;let l=ee(u,n);if(n.onMessage?.(l),l.type==="done")n.onDone?.();if(l.type==="error")n.onError?.(Error(V(l.error)))},r.onerror=()=>n.onError?.(ne(this.baseUrl)),r.onclose=(a)=>{if(!s&&a.code!==1000)n.onError?.(Error(a.reason||"WebSocket connection closed."))},{url:o,sendControl:function(a){if(r.readyState!==b)throw Error("WebSocket is not open");r.send(JSON.stringify({type:"control",...a}))},close:i}}async*stream(e){let n=[],t=!1,o=null,r=null,s=()=>{r?.(),r=null},i=this.subscribe(e,{onMessage:function(a){n.push(a),s()},onDone:function(){t=!0,s()},onError:function(a){o=a,s()}});try{while(!0){if(n.length>0){yield n.shift();continue}if(o)throw o;if(t)return;await new Promise((a)=>{r=a})}}finally{i.close()}}buildUrl(e){let n=an(e),t=e.projectSlug??e.agent?.projectSlug,o=e.stageSlug??e.agent?.stageSlug,r=t&&o?`/projects/${encodeURIComponent(t)}/stages/${encodeURIComponent(o)}`:"";return`${X(this.baseUrl)}/v1${r}/agents/${encodeURIComponent(n)}/ws`}resolveWebSocket(){let e=this.WebSocketImpl??globalThis.WebSocket;if(!e)throw Error("WebSocket is not available. Pass a WebSocket implementation in BroodsWebSocketClient options.");return e}}function X(e){let n=new URL(te(e));if(n.protocol==="https:")n.protocol="wss:";if(n.protocol==="http:")n.protocol="ws:";return g(n.toString())}function I(e){return["broods.v1",`broods.token.${e}`]}function V(e){if(typeof e==="string")return e;if(e&&typeof e==="object"&&typeof e.message==="string")return e.message;return e===void 0?"WebSocket stream error.":JSON.stringify(e)}function te(e){let n=e.trim();if(/^wss?:\/\//.test(n))return g(n);return Y(n)}function Q(e){try{let n=JSON.parse(e);return typeof n==="object"&&n!==null&&typeof n.type==="string"?n:null}catch{return null}}function sn(e){let n=e.agentId??e.agent?.id??e.endpointId;if(!n)throw Error("WebSocket run requires agent, agentId, or endpointId.");return n}function an(e){let n=e.endpointId??e.agent?.endpointId??e.agent?.id;if(!n)throw Error("WebSocket run requires agent or endpointId.");return n}function ee(e,n){if(e.type!=="output")return e;let t=e;return n.onOutput?.(t),t.data}function ne(e){return Error(`Cannot access the WebSocket service at ${e}.`)}var re=Symbol.for("broods.resource"),se=Symbol.for("broods.config"),ce=Symbol.for("broods.connection"),oe=/^[A-Z][A-Z0-9_]{0,63}$/,Oe=new Proxy((e)=>{if(!oe.test(e))throw Error("env name must match /^[A-Z][A-Z0-9_]*$/ and be at most 64 characters.");return{__beeblastEnv:!0,name:e}},{get:function(e,n,t){if(typeof n==="string"&&oe.test(n))throw Error(`env.${n} is not supported; use env("${n}")`);return Reflect.get(e,n,t)}});function Ne(e){let{name:n,description:t,...o}=e;return d("agent",n,t,o)}function je(e){return{[se]:!0,config:e}}function qe(e){let{name:n,description:t,...o}=e;return d("cron",n,t,o)}function De(e){return e}function Le(e){let{name:n,description:t,...o}=e;return d("mcp",n,t,o)}function Ue(e){let{name:n,description:t,...o}=e;return d("policy",n,t,o)}function Fe(e){let{name:n,description:t,...o}=e;return d("sandbox",n,t,o)}function Be(e){let{name:n,description:t,...o}=e;return d("skill",n,t,o)}function Ve(e){let{name:n,description:t,...o}=e;return d("workspace",n,t,o)}function Ze(e){return y("discord",e)}function He(e){return y("github",e)}function We(e){return y("pancake",e)}function Ge(e){return y("slack",e)}function Ke(e){return y("telegram",e)}function Je(e){return y("zalo",e)}function Ye(e){let{name:n,description:t,channelId:o,guildId:r,...s}=e;return p(n,t,o,r,s)}function Xe(e){let{name:n,description:t,repo:o,...r}=e,[s,...i]=o.split("/");if(!s||i.length!==1||!i[0])throw Error(`Channel "${n}" repo must be "owner/name", not "${o}"`);return p(n,t,o,s,r)}function Qe(e){let{name:n,description:t,conversationId:o,...r}=e;return p(n,t,o,void 0,r)}function et(e){let{name:n,description:t,channelId:o,teamId:r,...s}=e;return p(n,t,o,r,s)}function tt(e){let{name:n,description:t,chatId:o,...r}=e;return p(n,t,o,void 0,r)}function rt(e){let{name:n,description:t,chatId:o,...r}=e;return p(n,t,o,void 0,r)}function ie(e){return Boolean(e&&typeof e==="object"&&e[se])}function R(e){return Boolean(e&&typeof e==="object"&&e[ce])}function h(e){return Boolean(e&&typeof e==="object"&&e[re])}function p(e,n,t,o,r){return d("channelRecord",e,n,{...r,externalId:t,...o?{workspaceRef:o}:{}})}function y(e,n){let{partition:t,...o}=n;return{[ce]:!0,kind:"connection",type:e,...t?{partition:t}:{},config:o}}function d(e,n,t,o){return{[re]:!0,kind:e,name:n,...t?{description:t}:{},config:o}}
|
|
5
|
+
export{W,Se,Me,xe,Ae,ze,G,$e,v,w,k,K,J,_,g,ae,f,N,P,we,F,Y,ke,X,I,Oe,Ne,je,qe,De,Le,Ue,Fe,Be,Ve,Ze,He,We,Ge,Ke,Je,Ye,Xe,Qe,et,tt,rt,ie,R,h};
|