@avasapp/agent-bridge 0.1.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.
Files changed (38) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +159 -0
  3. package/dist/adapters/expo-router.cjs +144 -0
  4. package/dist/adapters/expo-router.d.cts +38 -0
  5. package/dist/adapters/react-native-mmkv.cjs +102 -0
  6. package/dist/adapters/react-native-mmkv.d.cts +15 -0
  7. package/dist/adapters/tanstack-query.cjs +161 -0
  8. package/dist/adapters/tanstack-query.d.cts +10 -0
  9. package/dist/adapters/zustand.cjs +97 -0
  10. package/dist/adapters/zustand.d.cts +15 -0
  11. package/dist/chunk-UEWFQWCY.js +575 -0
  12. package/dist/cli.js +657 -0
  13. package/dist/client/index.cjs +568 -0
  14. package/dist/client/index.d.ts +112 -0
  15. package/dist/client/index.js +14 -0
  16. package/dist/expo/index.cjs +62 -0
  17. package/dist/expo/index.d.cts +9 -0
  18. package/dist/network/index.cjs +568 -0
  19. package/dist/network/index.d.cts +85 -0
  20. package/dist/noop/expo-router.cjs +26 -0
  21. package/dist/noop/expo.cjs +27 -0
  22. package/dist/noop/index.cjs +36 -0
  23. package/dist/noop/network.cjs +34 -0
  24. package/dist/noop/react-native-mmkv.cjs +26 -0
  25. package/dist/noop/tanstack-query.cjs +26 -0
  26. package/dist/noop/zustand.cjs +26 -0
  27. package/dist/runtime/index.cjs +933 -0
  28. package/dist/runtime/index.d.cts +69 -0
  29. package/dist/types-C6DUUHnB.d.cts +76 -0
  30. package/entries/expo-router.cjs +9 -0
  31. package/entries/expo.cjs +9 -0
  32. package/entries/index.cjs +9 -0
  33. package/entries/network.cjs +9 -0
  34. package/entries/react-native-mmkv.cjs +9 -0
  35. package/entries/tanstack-query.cjs +9 -0
  36. package/entries/zustand.cjs +9 -0
  37. package/package.json +120 -0
  38. package/skills/agent-bridge/SKILL.md +89 -0
@@ -0,0 +1,568 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/client/index.ts
31
+ var client_exports = {};
32
+ __export(client_exports, {
33
+ AgentBridgeCallError: () => AgentBridgeCallError,
34
+ connect: () => connect,
35
+ connectSession: () => connectSession,
36
+ listDevices: () => listDevices,
37
+ listSessions: () => listSessions
38
+ });
39
+ module.exports = __toCommonJS(client_exports);
40
+
41
+ // src/client/discover.ts
42
+ var DEFAULT_METRO = "localhost:8081";
43
+ function metroHost(metro) {
44
+ return (metro ?? process.env.AGENT_BRIDGE_METRO ?? DEFAULT_METRO).replace(/^https?:\/\//, "").replace(/\/+$/, "");
45
+ }
46
+ async function expoHostUri(metro) {
47
+ try {
48
+ const res = await fetch(`http://${metro}/`, {
49
+ headers: {
50
+ "expo-platform": "ios",
51
+ accept: "application/expo+json,application/json"
52
+ },
53
+ signal: AbortSignal.timeout(3e3)
54
+ });
55
+ if (!res.ok) return null;
56
+ const manifest = await res.json();
57
+ return manifest.extra?.expoClient?.hostUri ?? manifest.extra?.expoGo?.debuggerHost ?? manifest.hostUri ?? null;
58
+ } catch {
59
+ return null;
60
+ }
61
+ }
62
+ async function listCdpTargets(metro) {
63
+ const res = await fetch(`http://${metro}/json/list`, {
64
+ signal: AbortSignal.timeout(3e3)
65
+ });
66
+ if (!res.ok)
67
+ throw new Error(
68
+ `Metro at ${metro} answered /json/list with HTTP ${res.status}`
69
+ );
70
+ return await res.json();
71
+ }
72
+ function pickOne(items, filter, label) {
73
+ const matching = filter ? items.filter(
74
+ (item) => label(item).toLowerCase().includes(filter.toLowerCase())
75
+ ) : items;
76
+ const connected = items.map(label).join("; ") || "none";
77
+ if (matching.length === 0) {
78
+ throw new Error(
79
+ filter ? `No app matches "${filter}". Connected: ${connected}` : "No app is connected to Metro."
80
+ );
81
+ }
82
+ if (matching.length > 1 && !filter) {
83
+ throw new Error(
84
+ `${matching.length} apps are connected; pick one with --device. Connected: ${connected}`
85
+ );
86
+ }
87
+ return matching[0];
88
+ }
89
+
90
+ // src/shared/protocol.ts
91
+ var PLUGIN_NAME = "agent-bridge";
92
+ var CDP_GLOBAL = "__AGENT_BRIDGE__";
93
+ var CDP_REPLY_BINDING = "__agentBridgeReply";
94
+ function toAsciiJson(value) {
95
+ return JSON.stringify(value).replace(
96
+ /[\u007f-￿]/g,
97
+ (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, "0")}`
98
+ );
99
+ }
100
+
101
+ // src/client/connection.ts
102
+ var import_ws = __toESM(require("ws"), 1);
103
+ function openSocket(url, headers) {
104
+ return new Promise((resolve, reject) => {
105
+ const ws = new import_ws.default(url, headers ? { headers } : void 0);
106
+ let opened = false;
107
+ ws.on("error", (event) => {
108
+ if (opened) return;
109
+ const message = event?.message ?? String(event);
110
+ reject(new Error(`Could not open ${url}: ${message}`));
111
+ });
112
+ ws.once("close", () => {
113
+ if (!opened) reject(new Error(`${url} closed before opening`));
114
+ });
115
+ ws.once("open", () => {
116
+ opened = true;
117
+ resolve(ws);
118
+ });
119
+ });
120
+ }
121
+ var newCallId = () => `c${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`;
122
+ function createPending() {
123
+ const waiting = /* @__PURE__ */ new Map();
124
+ return {
125
+ wait(id, tool, timeoutMs) {
126
+ return new Promise((resolve, reject) => {
127
+ const timer = setTimeout(() => {
128
+ waiting.delete(id);
129
+ reject(new Error(`No reply to "${tool}" within ${timeoutMs} ms`));
130
+ }, timeoutMs);
131
+ waiting.set(id, (result) => {
132
+ clearTimeout(timer);
133
+ waiting.delete(id);
134
+ resolve(result);
135
+ });
136
+ });
137
+ },
138
+ settle(result) {
139
+ waiting.get(result.id)?.(result);
140
+ },
141
+ failAll(reason) {
142
+ for (const [id, done] of waiting) {
143
+ done({ id, from: "", ok: false, error: reason, ms: 0 });
144
+ }
145
+ }
146
+ };
147
+ }
148
+
149
+ // src/client/expo.ts
150
+ var deviceLabel = (d) => `${d.name} (${d.platform}, ${d.deviceId})`;
151
+ async function openBroadcast(metro, discoveryMs) {
152
+ const ws = await openSocket(`ws://${metro}/expo-dev-plugins/broadcast`);
153
+ const devices = /* @__PURE__ */ new Map();
154
+ const pending = createPending();
155
+ ws.on("message", (data, isBinary) => {
156
+ if (isBinary) return;
157
+ let frame;
158
+ try {
159
+ frame = JSON.parse(String(data));
160
+ } catch {
161
+ return;
162
+ }
163
+ if (frame.messageKey?.pluginName !== PLUGIN_NAME) return;
164
+ if (frame.messageKey.method === "hello:reply") {
165
+ const info = frame.payload;
166
+ devices.set(info.deviceId, info);
167
+ } else if (frame.messageKey.method === "result") {
168
+ pending.settle(frame.payload);
169
+ }
170
+ });
171
+ ws.on("close", () => pending.failAll("Expo's dev-tools socket closed"));
172
+ const send = (method, payload) => ws.send(
173
+ JSON.stringify({
174
+ messageKey: { pluginName: PLUGIN_NAME, method },
175
+ payload
176
+ })
177
+ );
178
+ send("hello", {});
179
+ await new Promise((r) => setTimeout(r, discoveryMs));
180
+ return { ws, devices, pending, send };
181
+ }
182
+ async function listExpoDevices(metro, discoveryMs = 600) {
183
+ const { ws, devices } = await openBroadcast(metro, discoveryMs);
184
+ ws.close();
185
+ return [...devices.values()];
186
+ }
187
+ async function connectExpo(metro, device, discoveryMs = 600) {
188
+ const { ws, devices, pending, send } = await openBroadcast(metro, discoveryMs);
189
+ let info;
190
+ try {
191
+ info = pickOne([...devices.values()], device, deviceLabel);
192
+ } catch (error) {
193
+ ws.close();
194
+ throw error;
195
+ }
196
+ return {
197
+ transport: "expo",
198
+ device: info,
199
+ call(tool, args, timeoutMs) {
200
+ const call = {
201
+ id: newCallId(),
202
+ tool,
203
+ args,
204
+ to: info.deviceId
205
+ };
206
+ const reply = pending.wait(call.id, tool, timeoutMs);
207
+ send("call", call);
208
+ return reply;
209
+ },
210
+ close: () => ws.close()
211
+ };
212
+ }
213
+
214
+ // src/client/cdp.ts
215
+ var import_ws2 = __toESM(require("ws"), 1);
216
+ var targetLabel = (t) => `${t.title}${t.deviceName ? ` [${t.deviceName}]` : ""}`;
217
+ async function connectCdp(metro, device) {
218
+ const target = pickOne(await listCdpTargets(metro), device, targetLabel);
219
+ const hostUri = await expoHostUri(metro);
220
+ const port = metro.split(":")[1] ?? "8081";
221
+ const origin = hostUri ? `http://${hostUri}` : `http://localhost:${port}`;
222
+ const closedEarly = `Metro closed the debugger socket. Origin sent: ${origin}. Is that the host Metro advertises?`;
223
+ const ws = await openSocket(target.webSocketDebuggerUrl, {
224
+ Origin: origin
225
+ }).catch(() => {
226
+ throw new Error(closedEarly);
227
+ });
228
+ let seq = 0;
229
+ let answered = false;
230
+ const commands = /* @__PURE__ */ new Map();
231
+ const pending = createPending();
232
+ ws.on("message", (data) => {
233
+ const message = JSON.parse(String(data));
234
+ if (message.id && commands.has(message.id)) {
235
+ answered = true;
236
+ commands.get(message.id)?.resolve(message);
237
+ commands.delete(message.id);
238
+ } else if (message.method === "Runtime.bindingCalled" && message.params?.name === CDP_REPLY_BINDING) {
239
+ pending.settle(JSON.parse(message.params.payload));
240
+ }
241
+ });
242
+ ws.on("close", () => {
243
+ const reason = answered ? "The debugger socket closed" : closedEarly;
244
+ for (const command of commands.values()) command.reject(new Error(reason));
245
+ commands.clear();
246
+ pending.failAll(reason);
247
+ });
248
+ const send = (method, params = {}) => new Promise((resolve, reject) => {
249
+ if (ws.readyState !== import_ws2.default.OPEN) {
250
+ reject(new Error(answered ? "The debugger socket closed" : closedEarly));
251
+ return;
252
+ }
253
+ const id = ++seq;
254
+ commands.set(id, { resolve, reject });
255
+ ws.send(JSON.stringify({ id, method, params }));
256
+ });
257
+ const evaluate = async (expression) => {
258
+ const message = await send("Runtime.evaluate", {
259
+ expression,
260
+ returnByValue: true
261
+ });
262
+ const details = message.result?.exceptionDetails;
263
+ if (details) {
264
+ const text = details.exception?.description ?? details.text ?? "evaluation failed";
265
+ throw new Error(text.split("\n")[0]);
266
+ }
267
+ return message.result?.result?.value;
268
+ };
269
+ await send("Runtime.enable");
270
+ await send("Runtime.addBinding", { name: CDP_REPLY_BINDING });
271
+ let info;
272
+ try {
273
+ info = JSON.parse(
274
+ String(await evaluate(`${CDP_GLOBAL}.info()`))
275
+ );
276
+ } catch {
277
+ ws.close();
278
+ throw new Error(
279
+ `agent-bridge isn't running in ${targetLabel(target)}. Is useAgentBridge mounted in a dev build?`
280
+ );
281
+ }
282
+ return {
283
+ transport: "cdp",
284
+ device: { ...info, name: `${info.name} (${targetLabel(target)})` },
285
+ async call(tool, args, timeoutMs) {
286
+ const call = { id: newCallId(), tool, args };
287
+ const reply = pending.wait(call.id, tool, timeoutMs);
288
+ try {
289
+ await evaluate(
290
+ `${CDP_GLOBAL}.dispatch(${JSON.stringify(toAsciiJson(call))})`
291
+ );
292
+ } catch (error) {
293
+ pending.settle({
294
+ id: call.id,
295
+ from: info.deviceId,
296
+ ok: false,
297
+ error: String(error),
298
+ ms: 0
299
+ });
300
+ }
301
+ return reply;
302
+ },
303
+ close: () => ws.close()
304
+ };
305
+ }
306
+
307
+ // src/client/open.ts
308
+ async function openConnection(options = {}) {
309
+ const metro = metroHost(options.metro);
310
+ const want = options.transport ?? "auto";
311
+ let expoError;
312
+ if (want !== "cdp") {
313
+ try {
314
+ return await connectExpo(metro, options.device);
315
+ } catch (error) {
316
+ if (want === "expo") throw error;
317
+ expoError = error;
318
+ }
319
+ }
320
+ try {
321
+ return await connectCdp(metro, options.device);
322
+ } catch (error) {
323
+ const expoNote = expoError ? ` (Expo socket: ${String(expoError)})` : "";
324
+ throw new Error(
325
+ `${error instanceof Error ? error.message : String(error)}${expoNote}`
326
+ );
327
+ }
328
+ }
329
+
330
+ // src/client/session/client.ts
331
+ var import_node_net = require("net");
332
+ var import_node_readline = require("readline");
333
+
334
+ // src/client/session/state.ts
335
+ var import_node_crypto = require("crypto");
336
+ var import_node_fs = require("fs");
337
+ var import_node_os = require("os");
338
+ var import_node_path = require("path");
339
+ var uid = () => process.getuid?.() ?? (0, import_node_os.userInfo)().username;
340
+ function privateDir(dir) {
341
+ (0, import_node_fs.mkdirSync)(dir, { recursive: true, mode: 448 });
342
+ const stat = (0, import_node_fs.lstatSync)(dir);
343
+ if (!stat.isDirectory())
344
+ throw new Error(`${dir} is not a directory; remove it and retry`);
345
+ if (process.getuid && stat.uid !== process.getuid())
346
+ throw new Error(`${dir} belongs to another user; refusing to use it`);
347
+ if (process.platform !== "win32" && (stat.mode & 63) !== 0)
348
+ (0, import_node_fs.chmodSync)(dir, 448);
349
+ return dir;
350
+ }
351
+ function stateDir() {
352
+ return privateDir(
353
+ process.env.AGENT_BRIDGE_STATE_DIR ?? (process.env.XDG_RUNTIME_DIR ? (0, import_node_path.join)(process.env.XDG_RUNTIME_DIR, "agent-bridge") : (0, import_node_path.join)((0, import_node_os.tmpdir)(), `agent-bridge-${uid()}`))
354
+ );
355
+ }
356
+ var shortHash = (text) => (0, import_node_crypto.createHash)("sha256").update(text).digest("hex").slice(0, 16);
357
+ function socketPath(name, dir) {
358
+ if (process.platform === "win32")
359
+ return `\\\\.\\pipe\\agent-bridge-${shortHash(dir)}-${name}`;
360
+ const path = (0, import_node_path.join)(dir, `${name}.sock`);
361
+ if (Buffer.byteLength(path) <= 100) return path;
362
+ const short = privateDir(`/tmp/agent-bridge-${uid()}`);
363
+ return (0, import_node_path.join)(short, `${shortHash(path)}.sock`);
364
+ }
365
+ var sessionFiles = (name, dir = stateDir()) => ({
366
+ state: (0, import_node_path.join)(dir, `${name}.json`),
367
+ error: (0, import_node_path.join)(dir, `${name}.error`),
368
+ log: (0, import_node_path.join)(dir, `${name}.log`),
369
+ socket: socketPath(name, dir)
370
+ });
371
+ function checkName(name) {
372
+ if (!/^[\w.-]{1,40}$/.test(name))
373
+ throw new Error(
374
+ `Session name "${name}" must be 1-40 letters, digits, ".", "_" or "-"`
375
+ );
376
+ return name;
377
+ }
378
+ function isAlive(pid) {
379
+ try {
380
+ process.kill(pid, 0);
381
+ return true;
382
+ } catch (error) {
383
+ return error.code === "EPERM";
384
+ }
385
+ }
386
+ function removeSessionFiles(name, dir = stateDir()) {
387
+ const files = sessionFiles(name, dir);
388
+ (0, import_node_fs.rmSync)(files.state, { force: true });
389
+ if (process.platform !== "win32") (0, import_node_fs.rmSync)(files.socket, { force: true });
390
+ }
391
+ function readState(file) {
392
+ try {
393
+ return JSON.parse((0, import_node_fs.readFileSync)(file, "utf8"));
394
+ } catch {
395
+ return null;
396
+ }
397
+ }
398
+ function listSessions(dir = stateDir()) {
399
+ const live = [];
400
+ for (const entry of (0, import_node_fs.readdirSync)(dir)) {
401
+ if (!entry.endsWith(".json")) continue;
402
+ const state = readState((0, import_node_path.join)(dir, entry));
403
+ if (!state) continue;
404
+ if (isAlive(state.pid)) live.push(state);
405
+ else removeSessionFiles(state.name, dir);
406
+ }
407
+ return live.sort((a, b) => a.startedAt - b.startedAt);
408
+ }
409
+ function readSession(name, dir = stateDir()) {
410
+ return listSessions(dir).find((s) => s.name === name) ?? null;
411
+ }
412
+ function pickSession(filter) {
413
+ if (filter.name) {
414
+ const named = readSession(checkName(filter.name));
415
+ if (!named) throw new Error(`No session named "${filter.name}" is running`);
416
+ return named;
417
+ }
418
+ const device = filter.device?.toLowerCase();
419
+ const matching = listSessions().filter(
420
+ (s) => s.metro === filter.metro && (!device || s.device.name.toLowerCase().includes(device) || s.device.deviceId.toLowerCase().includes(device)) && (!filter.transport || filter.transport === "auto" || filter.transport === s.transport)
421
+ );
422
+ return matching.length === 1 ? matching[0] : null;
423
+ }
424
+
425
+ // src/client/session/client.ts
426
+ function openSessionLink(state) {
427
+ return new Promise((resolve, reject) => {
428
+ const socket = (0, import_node_net.createConnection)(state.socket);
429
+ const waiting = /* @__PURE__ */ new Map();
430
+ let seq = 0;
431
+ let open = false;
432
+ const gone = `Session "${state.name}" closed the connection`;
433
+ socket.once("connect", () => {
434
+ open = true;
435
+ resolve({
436
+ request: (req) => new Promise((done, fail) => {
437
+ if (socket.destroyed || socket.writableEnded) {
438
+ fail(new Error(gone));
439
+ return;
440
+ }
441
+ const id = ++seq;
442
+ waiting.set(id, done);
443
+ socket.write(`${JSON.stringify({ ...req, id })}
444
+ `);
445
+ }),
446
+ close: () => socket.end()
447
+ });
448
+ });
449
+ socket.on("error", (error) => {
450
+ if (!open)
451
+ reject(
452
+ new Error(
453
+ `Session "${state.name}" isn't answering on ${state.socket}: ${error.message}`
454
+ )
455
+ );
456
+ });
457
+ socket.on("close", () => {
458
+ for (const [id, done] of waiting) done({ id, error: gone });
459
+ waiting.clear();
460
+ });
461
+ (0, import_node_readline.createInterface)({ input: socket }).on("line", (line) => {
462
+ const res = JSON.parse(line);
463
+ waiting.get(res.id)?.(res);
464
+ waiting.delete(res.id);
465
+ });
466
+ });
467
+ }
468
+ async function connectSession(options = {}) {
469
+ const state = pickSession({
470
+ name: options.name ?? process.env.AGENT_BRIDGE_SESSION,
471
+ metro: metroHost(options.metro),
472
+ device: options.device,
473
+ transport: options.transport
474
+ });
475
+ if (!state)
476
+ throw new Error(
477
+ "No single session matches. Start one with `agent-bridge session start`, or name it."
478
+ );
479
+ const link = await openSessionLink(state);
480
+ const info = await link.request({ op: "info" });
481
+ if (info.error || !info.device || !info.transport) {
482
+ link.close();
483
+ throw new Error(info.error ?? `Session "${state.name}" sent no device`);
484
+ }
485
+ const device = info.device;
486
+ const timed = async (tool, ...args) => {
487
+ const res = await link.request({
488
+ op: "call",
489
+ tool,
490
+ args,
491
+ timeoutMs: options.timeoutMs
492
+ });
493
+ if (res.error || !res.result) throw new Error(res.error ?? "No result");
494
+ const result = res.result;
495
+ const logs = result.logs ?? [];
496
+ if (!result.ok) throw new AgentBridgeCallError(tool, result.error, logs);
497
+ const { id: _id, from: _from, ok: _ok, ms: appMs, value, ...extra } = result;
498
+ return { ...extra, value, ms: res.ms ?? 0, appMs, logs };
499
+ };
500
+ return {
501
+ session: info.state ?? state,
502
+ transport: info.transport,
503
+ device,
504
+ timed,
505
+ call: async (tool, ...args) => (await timed(tool, ...args)).value,
506
+ tools: () => device.tools,
507
+ close: link.close
508
+ };
509
+ }
510
+
511
+ // src/client/index.ts
512
+ var AgentBridgeCallError = class extends Error {
513
+ constructor(tool, message, logs = []) {
514
+ super(`${tool}: ${message}`);
515
+ this.tool = tool;
516
+ this.logs = logs;
517
+ this.name = "AgentBridgeCallError";
518
+ }
519
+ tool;
520
+ logs;
521
+ };
522
+ async function connect(options = {}) {
523
+ const timeoutMs = options.timeoutMs ?? 1e4;
524
+ const conn = await openConnection(options);
525
+ const timed = async (tool, ...args) => {
526
+ const t0 = performance.now();
527
+ const result = await conn.call(tool, args, timeoutMs);
528
+ const ms = performance.now() - t0;
529
+ const logs = result.logs ?? [];
530
+ if (!result.ok) throw new AgentBridgeCallError(tool, result.error, logs);
531
+ return { value: result.value, ms, appMs: result.ms, logs };
532
+ };
533
+ return {
534
+ transport: conn.transport,
535
+ device: conn.device,
536
+ timed,
537
+ call: async (tool, ...args) => (await timed(tool, ...args)).value,
538
+ tools: () => conn.device.tools,
539
+ close: conn.close
540
+ };
541
+ }
542
+ async function listDevices(options = {}) {
543
+ const metro = metroHost(options.metro);
544
+ const [expo, cdp] = await Promise.all([
545
+ listExpoDevices(metro).catch(() => []),
546
+ listCdpTargets(metro).catch(() => [])
547
+ ]);
548
+ return [
549
+ ...expo.map((d) => ({
550
+ transport: "expo",
551
+ name: d.name,
552
+ deviceId: d.deviceId,
553
+ tools: d.tools.length
554
+ })),
555
+ ...cdp.map((t) => ({
556
+ transport: "cdp",
557
+ name: `${t.title}${t.deviceName ? ` [${t.deviceName}]` : ""}`
558
+ }))
559
+ ];
560
+ }
561
+ // Annotate the CommonJS export names for ESM import in node:
562
+ 0 && (module.exports = {
563
+ AgentBridgeCallError,
564
+ connect,
565
+ connectSession,
566
+ listDevices,
567
+ listSessions
568
+ });
@@ -0,0 +1,112 @@
1
+ /** An error or warning the app logged, threw or left unhandled. */
2
+ type LogEntry = {
3
+ level: 'error' | 'warn';
4
+ message: string;
5
+ /** First lines of the stack, when there was an Error. */
6
+ stack?: string;
7
+ /** Date.now() in the app. */
8
+ at: number;
9
+ /** The tool that was running when it was logged. */
10
+ during?: string;
11
+ /** Otherwise, the last tool that had finished. */
12
+ after?: string;
13
+ };
14
+ type ToolInfo = {
15
+ name: string;
16
+ description?: string;
17
+ };
18
+ type DeviceInfo = {
19
+ deviceId: string;
20
+ name: string;
21
+ platform: string;
22
+ protocol: number;
23
+ tools: ToolInfo[];
24
+ };
25
+
26
+ type TransportName = 'expo' | 'cdp';
27
+
28
+ /** What a running session writes next to its socket. Readable only by the user. */
29
+ type SessionState = {
30
+ name: string;
31
+ pid: number;
32
+ socket: string;
33
+ metro: string;
34
+ device: {
35
+ name: string;
36
+ deviceId: string;
37
+ platform: string;
38
+ };
39
+ /** The --device filter the session was started with, reused to reconnect. */
40
+ deviceFilter?: string;
41
+ transport: TransportName;
42
+ startedAt: number;
43
+ lastCallAt: number;
44
+ /** 0 means no idle timeout. */
45
+ idleMs: number;
46
+ };
47
+ /** Live sessions. State left by a daemon that died is removed on the way. */
48
+ declare function listSessions(dir?: string): SessionState[];
49
+
50
+ type SessionConnectOptions = {
51
+ /** Session name. Defaults to $AGENT_BRIDGE_SESSION, else the only session on this Metro. */
52
+ name?: string;
53
+ metro?: string;
54
+ device?: string;
55
+ transport?: 'auto' | TransportName;
56
+ /** Per-call timeout. Default: the session's (10 s). */
57
+ timeoutMs?: number;
58
+ };
59
+ /**
60
+ * Like `connect()`, through a running session (`agent-bridge session start`):
61
+ * no discovery, and the app stays connected when this closes.
62
+ */
63
+ declare function connectSession(options?: SessionConnectOptions): Promise<AgentBridge & {
64
+ session: SessionState;
65
+ }>;
66
+
67
+ type ConnectOptions = {
68
+ /** Metro's host:port. Defaults to $AGENT_BRIDGE_METRO, then localhost:8081. */
69
+ metro?: string;
70
+ /** Part of the app's name or device name, when more than one is connected. */
71
+ device?: string;
72
+ /** "auto" tries Expo's socket first and falls back to CDP. */
73
+ transport?: 'auto' | TransportName;
74
+ /** Per-call timeout. Default 10 s. */
75
+ timeoutMs?: number;
76
+ };
77
+ declare class AgentBridgeCallError extends Error {
78
+ readonly tool: string;
79
+ /** Errors the app attached to the failed reply. */
80
+ readonly logs: LogEntry[];
81
+ constructor(tool: string, message: string,
82
+ /** Errors the app attached to the failed reply. */
83
+ logs?: LogEntry[]);
84
+ }
85
+ /** `logs`: errors the app recorded since its previous reply. */
86
+ type Timed<T> = {
87
+ value: T;
88
+ ms: number;
89
+ appMs: number;
90
+ logs: LogEntry[];
91
+ };
92
+ type AgentBridge = {
93
+ transport: TransportName;
94
+ device: DeviceInfo;
95
+ /** Calls a tool and returns its value; throws AgentBridgeCallError if the tool threw. */
96
+ call: <T = unknown>(tool: string, ...args: unknown[]) => Promise<NoInfer<T>>;
97
+ /** Like `call`, plus the round trip and the time spent inside the app. */
98
+ timed: <T = unknown>(tool: string, ...args: unknown[]) => Promise<Timed<NoInfer<T>>>;
99
+ tools: () => ToolInfo[];
100
+ close: () => void;
101
+ };
102
+ declare function connect(options?: ConnectOptions): Promise<AgentBridge>;
103
+ type ListedDevice = {
104
+ transport: TransportName;
105
+ name: string;
106
+ deviceId?: string;
107
+ tools?: number;
108
+ };
109
+ /** Apps with the bridge on Expo's socket, plus every React Native page Metro knows. */
110
+ declare function listDevices(options?: Pick<ConnectOptions, 'metro'>): Promise<ListedDevice[]>;
111
+
112
+ export { type AgentBridge, AgentBridgeCallError, type ConnectOptions, type DeviceInfo, type ListedDevice, type LogEntry, type SessionConnectOptions, type SessionState, type Timed, type ToolInfo, type TransportName, connect, connectSession, listDevices, listSessions };
@@ -0,0 +1,14 @@
1
+ import {
2
+ AgentBridgeCallError,
3
+ connect,
4
+ connectSession,
5
+ listDevices,
6
+ listSessions
7
+ } from "../chunk-UEWFQWCY.js";
8
+ export {
9
+ AgentBridgeCallError,
10
+ connect,
11
+ connectSession,
12
+ listDevices,
13
+ listSessions
14
+ };