gologin-agent-browser-cli 0.2.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 (104) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +318 -0
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.js +336 -0
  5. package/dist/commands/back.d.ts +2 -0
  6. package/dist/commands/back.js +12 -0
  7. package/dist/commands/check.d.ts +2 -0
  8. package/dist/commands/check.js +17 -0
  9. package/dist/commands/click.d.ts +2 -0
  10. package/dist/commands/click.js +17 -0
  11. package/dist/commands/close.d.ts +2 -0
  12. package/dist/commands/close.js +12 -0
  13. package/dist/commands/cookies.d.ts +2 -0
  14. package/dist/commands/cookies.js +23 -0
  15. package/dist/commands/cookiesClear.d.ts +2 -0
  16. package/dist/commands/cookiesClear.js +12 -0
  17. package/dist/commands/cookiesImport.d.ts +2 -0
  18. package/dist/commands/cookiesImport.js +18 -0
  19. package/dist/commands/current.d.ts +2 -0
  20. package/dist/commands/current.js +9 -0
  21. package/dist/commands/dblclick.d.ts +2 -0
  22. package/dist/commands/dblclick.js +17 -0
  23. package/dist/commands/doctor.d.ts +2 -0
  24. package/dist/commands/doctor.js +31 -0
  25. package/dist/commands/eval.d.ts +2 -0
  26. package/dist/commands/eval.js +30 -0
  27. package/dist/commands/fill.d.ts +2 -0
  28. package/dist/commands/fill.js +18 -0
  29. package/dist/commands/find.d.ts +2 -0
  30. package/dist/commands/find.js +86 -0
  31. package/dist/commands/focus.d.ts +2 -0
  32. package/dist/commands/focus.js +17 -0
  33. package/dist/commands/forward.d.ts +2 -0
  34. package/dist/commands/forward.js +12 -0
  35. package/dist/commands/get.d.ts +2 -0
  36. package/dist/commands/get.js +19 -0
  37. package/dist/commands/hover.d.ts +2 -0
  38. package/dist/commands/hover.js +17 -0
  39. package/dist/commands/open.d.ts +2 -0
  40. package/dist/commands/open.js +67 -0
  41. package/dist/commands/pdf.d.ts +2 -0
  42. package/dist/commands/pdf.js +18 -0
  43. package/dist/commands/press.d.ts +2 -0
  44. package/dist/commands/press.js +19 -0
  45. package/dist/commands/reload.d.ts +2 -0
  46. package/dist/commands/reload.js +12 -0
  47. package/dist/commands/screenshot.d.ts +2 -0
  48. package/dist/commands/screenshot.js +22 -0
  49. package/dist/commands/scroll.d.ts +2 -0
  50. package/dist/commands/scroll.js +25 -0
  51. package/dist/commands/scrollIntoView.d.ts +2 -0
  52. package/dist/commands/scrollIntoView.js +17 -0
  53. package/dist/commands/select.d.ts +2 -0
  54. package/dist/commands/select.js +18 -0
  55. package/dist/commands/sessions.d.ts +2 -0
  56. package/dist/commands/sessions.js +15 -0
  57. package/dist/commands/shared.d.ts +7 -0
  58. package/dist/commands/shared.js +51 -0
  59. package/dist/commands/snapshot.d.ts +2 -0
  60. package/dist/commands/snapshot.js +16 -0
  61. package/dist/commands/storageClear.d.ts +2 -0
  62. package/dist/commands/storageClear.js +13 -0
  63. package/dist/commands/storageExport.d.ts +2 -0
  64. package/dist/commands/storageExport.js +24 -0
  65. package/dist/commands/storageImport.d.ts +2 -0
  66. package/dist/commands/storageImport.js +20 -0
  67. package/dist/commands/tabClose.d.ts +2 -0
  68. package/dist/commands/tabClose.js +21 -0
  69. package/dist/commands/tabFocus.d.ts +2 -0
  70. package/dist/commands/tabFocus.js +21 -0
  71. package/dist/commands/tabOpen.d.ts +2 -0
  72. package/dist/commands/tabOpen.js +13 -0
  73. package/dist/commands/tabs.d.ts +2 -0
  74. package/dist/commands/tabs.js +17 -0
  75. package/dist/commands/type.d.ts +2 -0
  76. package/dist/commands/type.js +18 -0
  77. package/dist/commands/uncheck.d.ts +2 -0
  78. package/dist/commands/uncheck.js +17 -0
  79. package/dist/commands/upload.d.ts +2 -0
  80. package/dist/commands/upload.js +18 -0
  81. package/dist/commands/wait.d.ts +2 -0
  82. package/dist/commands/wait.js +41 -0
  83. package/dist/daemon/browser.d.ts +67 -0
  84. package/dist/daemon/browser.js +818 -0
  85. package/dist/daemon/refStore.d.ts +9 -0
  86. package/dist/daemon/refStore.js +26 -0
  87. package/dist/daemon/server.d.ts +1 -0
  88. package/dist/daemon/server.js +313 -0
  89. package/dist/daemon/sessionManager.d.ts +66 -0
  90. package/dist/daemon/sessionManager.js +684 -0
  91. package/dist/daemon/snapshot.d.ts +8 -0
  92. package/dist/daemon/snapshot.js +285 -0
  93. package/dist/lib/config.d.ts +3 -0
  94. package/dist/lib/config.js +58 -0
  95. package/dist/lib/daemon.d.ts +9 -0
  96. package/dist/lib/daemon.js +197 -0
  97. package/dist/lib/errors.d.ts +12 -0
  98. package/dist/lib/errors.js +63 -0
  99. package/dist/lib/types.d.ts +407 -0
  100. package/dist/lib/types.js +2 -0
  101. package/dist/lib/utils.d.ts +27 -0
  102. package/dist/lib/utils.js +165 -0
  103. package/examples/agent-flow.sh +19 -0
  104. package/package.json +61 -0
@@ -0,0 +1,684 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SessionManager = void 0;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const errors_1 = require("../lib/errors");
10
+ const utils_1 = require("../lib/utils");
11
+ const browser_1 = require("./browser");
12
+ const refStore_1 = require("./refStore");
13
+ const snapshot_1 = require("./snapshot");
14
+ class SessionManager {
15
+ config;
16
+ sessions = new Map();
17
+ activeSessionId;
18
+ refStore = new refStore_1.RefStore();
19
+ constructor(config) {
20
+ this.config = config;
21
+ }
22
+ nowIso() {
23
+ return new Date().toISOString();
24
+ }
25
+ requireToken() {
26
+ if (!this.config.token) {
27
+ throw new errors_1.AppError("TOKEN_MISSING", "GOLOGIN_TOKEN is required for open", 400);
28
+ }
29
+ return this.config.token;
30
+ }
31
+ sessionExpired(session) {
32
+ if (session.idleTimeoutMs === undefined) {
33
+ return false;
34
+ }
35
+ const lastActivityAt = Date.parse(session.lastActivityAt);
36
+ if (Number.isNaN(lastActivityAt)) {
37
+ return false;
38
+ }
39
+ return Date.now() - lastActivityAt > session.idleTimeoutMs;
40
+ }
41
+ async destroySession(session) {
42
+ await (0, browser_1.closeSessionHandles)(session).catch(() => undefined);
43
+ this.sessions.delete(session.sessionId);
44
+ this.refStore.clear(session.sessionId);
45
+ if (session.autoCreatedProfile && session.profileId && this.config.token) {
46
+ await (0, browser_1.deleteProfile)(this.config.token, session.profileId).catch(() => undefined);
47
+ }
48
+ if (this.activeSessionId === session.sessionId) {
49
+ this.activeSessionId = Array.from(this.sessions.keys()).at(-1);
50
+ }
51
+ }
52
+ async getSessionOrThrow(sessionId) {
53
+ const resolvedId = sessionId ?? this.activeSessionId;
54
+ if (!resolvedId) {
55
+ throw new errors_1.AppError("SESSION_NOT_FOUND", "no active session", 404);
56
+ }
57
+ const session = this.sessions.get(resolvedId);
58
+ if (!session) {
59
+ throw new errors_1.AppError("SESSION_NOT_FOUND", `session ${resolvedId} does not exist`, 404, { sessionId: resolvedId });
60
+ }
61
+ if (this.sessionExpired(session)) {
62
+ const idleTimeoutMs = session.idleTimeoutMs;
63
+ await this.destroySession(session);
64
+ throw new errors_1.AppError("SESSION_EXPIRED", `session ${resolvedId} expired after ${idleTimeoutMs}ms of inactivity`, 404, { sessionId: resolvedId, idleTimeoutMs });
65
+ }
66
+ return session;
67
+ }
68
+ async evictExpiredSessions() {
69
+ for (const session of Array.from(this.sessions.values())) {
70
+ if (this.sessionExpired(session)) {
71
+ await this.destroySession(session);
72
+ }
73
+ }
74
+ }
75
+ toSummary(session) {
76
+ return {
77
+ sessionId: session.sessionId,
78
+ profileId: session.profileId,
79
+ url: session.currentUrl,
80
+ active: session.sessionId === this.activeSessionId,
81
+ hasSnapshot: session.hasSnapshot,
82
+ staleSnapshot: session.staleSnapshot,
83
+ liveViewUrl: session.liveViewUrl,
84
+ proxy: session.proxy,
85
+ createdAt: session.createdAt,
86
+ lastActivityAt: session.lastActivityAt,
87
+ idleTimeoutMs: session.idleTimeoutMs,
88
+ lastScreenshotPath: session.lastScreenshotPath,
89
+ lastPdfPath: session.lastPdfPath
90
+ };
91
+ }
92
+ touchSession(session) {
93
+ session.currentUrl = session.page.url();
94
+ session.lastActivityAt = this.nowIso();
95
+ this.activeSessionId = session.sessionId;
96
+ }
97
+ markSessionState(session, staleSnapshot = session.staleSnapshot) {
98
+ this.touchSession(session);
99
+ session.staleSnapshot = staleSnapshot;
100
+ }
101
+ resetSnapshotState(session, staleSnapshot = false) {
102
+ session.hasSnapshot = false;
103
+ session.staleSnapshot = staleSnapshot;
104
+ this.refStore.clear(session.sessionId);
105
+ }
106
+ activatePage(session, page, staleSnapshot = false) {
107
+ session.page = page;
108
+ this.resetSnapshotState(session, staleSnapshot);
109
+ this.touchSession(session);
110
+ }
111
+ validateIdleTimeout(idleTimeoutMs) {
112
+ if (idleTimeoutMs === undefined) {
113
+ return;
114
+ }
115
+ if (!Number.isInteger(idleTimeoutMs) || idleTimeoutMs <= 0) {
116
+ throw new errors_1.AppError("BAD_REQUEST", "--idle-timeout-ms must be a positive integer", 400);
117
+ }
118
+ }
119
+ async resolveTargetLocator(session, target) {
120
+ if ((0, utils_1.isRefTarget)(target)) {
121
+ const descriptor = this.refStore.get(session.sessionId, target);
122
+ if (!descriptor) {
123
+ throw new errors_1.AppError("REF_NOT_FOUND", `ref ${target} is stale or unavailable in session ${session.sessionId}; run snapshot again`, 404, {
124
+ ref: target,
125
+ sessionId: session.sessionId
126
+ });
127
+ }
128
+ return (0, browser_1.resolveDescriptorLocator)(session.page, descriptor);
129
+ }
130
+ return (0, browser_1.resolveSelectorLocator)(session.page, target, this.config.actionTimeoutMs);
131
+ }
132
+ async runTargetAction(session, target, action, value) {
133
+ const locator = await this.resolveTargetLocator(session, target);
134
+ await (0, browser_1.performLocatorAction)(session.page, locator, action, this.config.actionTimeoutMs, value);
135
+ this.markSessionState(session, true);
136
+ return {
137
+ sessionId: session.sessionId,
138
+ url: session.currentUrl,
139
+ staleSnapshot: true
140
+ };
141
+ }
142
+ async open(request) {
143
+ const token = this.requireToken();
144
+ this.validateIdleTimeout(request.idleTimeoutMs);
145
+ if (request.profileId && request.proxy) {
146
+ throw new errors_1.AppError("BAD_REQUEST", "proxy flags cannot be combined with --profile", 400);
147
+ }
148
+ if (request.sessionId && this.sessions.has(request.sessionId)) {
149
+ const existing = await this.getSessionOrThrow(request.sessionId);
150
+ if (request.profileId && existing.profileId && request.profileId !== existing.profileId) {
151
+ throw new errors_1.AppError("BAD_REQUEST", `session ${existing.sessionId} already uses profile ${existing.profileId}`, 400);
152
+ }
153
+ if (request.proxy) {
154
+ throw new errors_1.AppError("BAD_REQUEST", `session ${existing.sessionId} already exists; proxy cannot be changed`, 400);
155
+ }
156
+ if (request.idleTimeoutMs !== undefined) {
157
+ existing.idleTimeoutMs = request.idleTimeoutMs;
158
+ }
159
+ existing.currentUrl = await (0, browser_1.navigatePage)(existing.page, request.url, this.config.navigationTimeoutMs);
160
+ this.resetSnapshotState(existing, true);
161
+ this.touchSession(existing);
162
+ return {
163
+ sessionId: existing.sessionId,
164
+ profileId: existing.profileId,
165
+ url: existing.currentUrl,
166
+ liveViewUrl: existing.liveViewUrl,
167
+ proxy: existing.proxy,
168
+ idleTimeoutMs: existing.idleTimeoutMs
169
+ };
170
+ }
171
+ let profileId = request.profileId ?? this.config.defaultProfileId;
172
+ let autoCreatedProfile = false;
173
+ let resolvedProxy;
174
+ const sessionId = request.sessionId ?? (0, utils_1.generateSessionId)(this.sessions.keys());
175
+ const createdAt = this.nowIso();
176
+ if (!profileId) {
177
+ const created = await (0, browser_1.createManagedProfile)(token, sessionId, request.proxy);
178
+ profileId = created.profileId;
179
+ resolvedProxy = created.proxy;
180
+ autoCreatedProfile = true;
181
+ }
182
+ try {
183
+ const connection = await (0, browser_1.connectToBrowser)(this.config, token, profileId);
184
+ const currentUrl = await (0, browser_1.navigatePage)(connection.page, request.url, this.config.navigationTimeoutMs);
185
+ const lastActivityAt = this.nowIso();
186
+ if (!resolvedProxy && profileId) {
187
+ resolvedProxy = await (0, browser_1.getCloudProfileProxy)(token, profileId).catch(() => undefined);
188
+ }
189
+ const session = {
190
+ sessionId,
191
+ profileId,
192
+ autoCreatedProfile,
193
+ connectUrl: connection.connectUrl,
194
+ browser: connection.browser,
195
+ context: connection.context,
196
+ page: connection.page,
197
+ currentUrl,
198
+ hasSnapshot: false,
199
+ staleSnapshot: false,
200
+ proxy: resolvedProxy,
201
+ createdAt,
202
+ lastActivityAt,
203
+ idleTimeoutMs: request.idleTimeoutMs
204
+ };
205
+ this.sessions.set(sessionId, session);
206
+ this.activeSessionId = sessionId;
207
+ this.refStore.clear(sessionId);
208
+ return {
209
+ sessionId,
210
+ profileId,
211
+ url: currentUrl,
212
+ proxy: session.proxy,
213
+ idleTimeoutMs: session.idleTimeoutMs
214
+ };
215
+ }
216
+ catch (error) {
217
+ if (autoCreatedProfile && profileId) {
218
+ await (0, browser_1.deleteProfile)(token, profileId).catch(() => undefined);
219
+ }
220
+ throw error;
221
+ }
222
+ }
223
+ async snapshot(sessionId, interactive = false) {
224
+ const session = await this.getSessionOrThrow(sessionId);
225
+ const snapshot = await (0, snapshot_1.buildSnapshot)(session.page, { interactive });
226
+ this.refStore.set(session.sessionId, snapshot.refs);
227
+ session.currentUrl = session.page.url();
228
+ session.hasSnapshot = true;
229
+ session.staleSnapshot = false;
230
+ session.lastActivityAt = this.nowIso();
231
+ this.activeSessionId = session.sessionId;
232
+ return {
233
+ sessionId: session.sessionId,
234
+ url: session.currentUrl,
235
+ items: snapshot.items
236
+ };
237
+ }
238
+ async tabs(sessionId) {
239
+ const session = await this.getSessionOrThrow(sessionId);
240
+ this.touchSession(session);
241
+ return {
242
+ sessionId: session.sessionId,
243
+ tabs: await (0, browser_1.listTabs)(session)
244
+ };
245
+ }
246
+ async tabOpen(sessionId, url) {
247
+ const session = await this.getSessionOrThrow(sessionId);
248
+ const opened = await (0, browser_1.openTab)(session, url, this.config.navigationTimeoutMs);
249
+ this.activatePage(session, opened.page);
250
+ return {
251
+ sessionId: session.sessionId,
252
+ tabIndex: opened.tabIndex,
253
+ url: session.currentUrl,
254
+ staleSnapshot: session.staleSnapshot
255
+ };
256
+ }
257
+ async tabFocus(sessionId, index) {
258
+ const session = await this.getSessionOrThrow(sessionId);
259
+ const focused = await (0, browser_1.focusTab)(session, index);
260
+ this.activatePage(session, focused.page);
261
+ return {
262
+ sessionId: session.sessionId,
263
+ tabIndex: focused.tabIndex,
264
+ url: session.currentUrl,
265
+ staleSnapshot: session.staleSnapshot
266
+ };
267
+ }
268
+ async tabClose(sessionId, index) {
269
+ const session = await this.getSessionOrThrow(sessionId);
270
+ const closed = await (0, browser_1.closeTab)(session, index);
271
+ this.activatePage(session, closed.page);
272
+ return {
273
+ sessionId: session.sessionId,
274
+ closedTabIndex: closed.closedTabIndex,
275
+ activeTabIndex: closed.activeTabIndex,
276
+ url: session.currentUrl,
277
+ staleSnapshot: session.staleSnapshot
278
+ };
279
+ }
280
+ async click(sessionId, target) {
281
+ return this.runTargetAction(await this.getSessionOrThrow(sessionId), target, "click");
282
+ }
283
+ async type(sessionId, target, text) {
284
+ return this.runTargetAction(await this.getSessionOrThrow(sessionId), target, "type", text);
285
+ }
286
+ async fill(sessionId, target, text) {
287
+ return this.runTargetAction(await this.getSessionOrThrow(sessionId), target, "fill", text);
288
+ }
289
+ async hover(sessionId, target) {
290
+ return this.runTargetAction(await this.getSessionOrThrow(sessionId), target, "hover");
291
+ }
292
+ async focus(sessionId, target) {
293
+ return this.runTargetAction(await this.getSessionOrThrow(sessionId), target, "focus");
294
+ }
295
+ async doubleClick(sessionId, target) {
296
+ return this.runTargetAction(await this.getSessionOrThrow(sessionId), target, "dblclick");
297
+ }
298
+ async select(sessionId, target, value) {
299
+ return this.runTargetAction(await this.getSessionOrThrow(sessionId), target, "select", value);
300
+ }
301
+ async check(sessionId, target) {
302
+ return this.runTargetAction(await this.getSessionOrThrow(sessionId), target, "check");
303
+ }
304
+ async uncheck(sessionId, target) {
305
+ return this.runTargetAction(await this.getSessionOrThrow(sessionId), target, "uncheck");
306
+ }
307
+ async press(sessionId, key, target) {
308
+ const session = await this.getSessionOrThrow(sessionId);
309
+ if (target) {
310
+ const locator = await this.resolveTargetLocator(session, target);
311
+ await locator.focus({ timeout: this.config.actionTimeoutMs });
312
+ }
313
+ await (0, browser_1.pressKey)(session.page, key, this.config.actionTimeoutMs);
314
+ this.markSessionState(session, true);
315
+ return {
316
+ sessionId: session.sessionId,
317
+ url: session.currentUrl,
318
+ staleSnapshot: true
319
+ };
320
+ }
321
+ async scroll(sessionId, direction, pixels = 500, target) {
322
+ const session = await this.getSessionOrThrow(sessionId);
323
+ if (target) {
324
+ const locator = await this.resolveTargetLocator(session, target);
325
+ await (0, browser_1.scrollElement)(locator, direction, pixels);
326
+ }
327
+ else {
328
+ await (0, browser_1.scrollPage)(session.page, direction, pixels);
329
+ }
330
+ this.markSessionState(session);
331
+ return {
332
+ sessionId: session.sessionId,
333
+ url: session.currentUrl,
334
+ target,
335
+ direction,
336
+ pixels
337
+ };
338
+ }
339
+ async scrollIntoView(sessionId, target) {
340
+ const session = await this.getSessionOrThrow(sessionId);
341
+ const locator = await this.resolveTargetLocator(session, target);
342
+ await (0, browser_1.scrollLocatorIntoView)(locator, this.config.actionTimeoutMs);
343
+ this.markSessionState(session);
344
+ return {
345
+ sessionId: session.sessionId,
346
+ url: session.currentUrl,
347
+ staleSnapshot: session.staleSnapshot
348
+ };
349
+ }
350
+ async upload(sessionId, target, files) {
351
+ const session = await this.getSessionOrThrow(sessionId);
352
+ const locator = await this.resolveTargetLocator(session, target);
353
+ try {
354
+ await (0, browser_1.uploadFiles)(locator, files, this.config.actionTimeoutMs);
355
+ }
356
+ catch (error) {
357
+ throw new errors_1.AppError("UPLOAD_FAILED", error instanceof Error ? error.message : String(error), 500, {
358
+ files
359
+ });
360
+ }
361
+ this.markSessionState(session, true);
362
+ return {
363
+ sessionId: session.sessionId,
364
+ url: session.currentUrl,
365
+ staleSnapshot: true,
366
+ files
367
+ };
368
+ }
369
+ async wait(sessionId, request) {
370
+ const session = await this.getSessionOrThrow(sessionId);
371
+ const timeoutMs = request.timeoutMs ?? this.config.navigationTimeoutMs;
372
+ if (request.timeoutMs && !request.target && !request.text && !request.urlPattern && !request.loadState) {
373
+ await new Promise((resolve) => setTimeout(resolve, request.timeoutMs));
374
+ this.markSessionState(session);
375
+ return {
376
+ sessionId: session.sessionId,
377
+ url: session.currentUrl,
378
+ waitedFor: `${request.timeoutMs}ms`
379
+ };
380
+ }
381
+ if (request.target) {
382
+ const locator = await this.resolveTargetLocator(session, request.target);
383
+ await (0, browser_1.waitForTarget)(locator, timeoutMs);
384
+ this.markSessionState(session);
385
+ return {
386
+ sessionId: session.sessionId,
387
+ url: session.currentUrl,
388
+ waitedFor: request.target
389
+ };
390
+ }
391
+ if (request.text) {
392
+ await (0, browser_1.waitForPageText)(session.page, request.text, timeoutMs);
393
+ this.markSessionState(session);
394
+ return {
395
+ sessionId: session.sessionId,
396
+ url: session.currentUrl,
397
+ waitedFor: `text:${request.text}`
398
+ };
399
+ }
400
+ if (request.urlPattern) {
401
+ await (0, browser_1.waitForPageUrl)(session.page, request.urlPattern, timeoutMs);
402
+ this.markSessionState(session);
403
+ return {
404
+ sessionId: session.sessionId,
405
+ url: session.currentUrl,
406
+ waitedFor: `url:${request.urlPattern}`
407
+ };
408
+ }
409
+ if (request.loadState) {
410
+ await (0, browser_1.waitForPageLoad)(session.page, request.loadState, timeoutMs);
411
+ this.markSessionState(session);
412
+ return {
413
+ sessionId: session.sessionId,
414
+ url: session.currentUrl,
415
+ waitedFor: `load:${request.loadState}`
416
+ };
417
+ }
418
+ throw new errors_1.AppError("BAD_REQUEST", "wait requires a target, timeout, --text, --url, or --load", 400);
419
+ }
420
+ async get(sessionId, kind, target) {
421
+ const session = await this.getSessionOrThrow(sessionId);
422
+ if (kind === "url") {
423
+ this.markSessionState(session);
424
+ return {
425
+ sessionId: session.sessionId,
426
+ url: session.currentUrl,
427
+ value: session.currentUrl
428
+ };
429
+ }
430
+ if (kind === "title") {
431
+ const value = await session.page.title();
432
+ this.markSessionState(session);
433
+ return {
434
+ sessionId: session.sessionId,
435
+ url: session.currentUrl,
436
+ value
437
+ };
438
+ }
439
+ if (!target) {
440
+ throw new errors_1.AppError("BAD_REQUEST", `get ${kind} requires a target`, 400);
441
+ }
442
+ const locator = await this.resolveTargetLocator(session, target);
443
+ const value = await (0, browser_1.readLocatorValue)(locator, kind, this.config.actionTimeoutMs);
444
+ this.markSessionState(session);
445
+ return {
446
+ sessionId: session.sessionId,
447
+ url: session.currentUrl,
448
+ value
449
+ };
450
+ }
451
+ async back(sessionId) {
452
+ const session = await this.getSessionOrThrow(sessionId);
453
+ session.currentUrl = await (0, browser_1.navigateHistory)(session.page, "back", this.config.navigationTimeoutMs);
454
+ this.resetSnapshotState(session, true);
455
+ this.touchSession(session);
456
+ return {
457
+ sessionId: session.sessionId,
458
+ url: session.currentUrl,
459
+ staleSnapshot: true
460
+ };
461
+ }
462
+ async forward(sessionId) {
463
+ const session = await this.getSessionOrThrow(sessionId);
464
+ session.currentUrl = await (0, browser_1.navigateHistory)(session.page, "forward", this.config.navigationTimeoutMs);
465
+ this.resetSnapshotState(session, true);
466
+ this.touchSession(session);
467
+ return {
468
+ sessionId: session.sessionId,
469
+ url: session.currentUrl,
470
+ staleSnapshot: true
471
+ };
472
+ }
473
+ async reload(sessionId) {
474
+ const session = await this.getSessionOrThrow(sessionId);
475
+ session.currentUrl = await (0, browser_1.navigateHistory)(session.page, "reload", this.config.navigationTimeoutMs);
476
+ this.resetSnapshotState(session, true);
477
+ this.touchSession(session);
478
+ return {
479
+ sessionId: session.sessionId,
480
+ url: session.currentUrl,
481
+ staleSnapshot: true
482
+ };
483
+ }
484
+ async cookies(sessionId) {
485
+ const session = await this.getSessionOrThrow(sessionId);
486
+ const cookies = await (0, browser_1.readCookies)(session);
487
+ this.markSessionState(session);
488
+ return {
489
+ sessionId: session.sessionId,
490
+ url: session.currentUrl,
491
+ cookies
492
+ };
493
+ }
494
+ async cookiesImport(sessionId, cookies) {
495
+ const session = await this.getSessionOrThrow(sessionId);
496
+ const imported = await (0, browser_1.importCookies)(session, cookies);
497
+ this.markSessionState(session);
498
+ return {
499
+ sessionId: session.sessionId,
500
+ url: session.currentUrl,
501
+ staleSnapshot: session.staleSnapshot,
502
+ imported
503
+ };
504
+ }
505
+ async cookiesClear(sessionId) {
506
+ const session = await this.getSessionOrThrow(sessionId);
507
+ const cleared = await (0, browser_1.clearCookies)(session);
508
+ this.markSessionState(session);
509
+ return {
510
+ sessionId: session.sessionId,
511
+ url: session.currentUrl,
512
+ staleSnapshot: session.staleSnapshot,
513
+ cleared
514
+ };
515
+ }
516
+ async storageExport(sessionId, scope) {
517
+ const session = await this.getSessionOrThrow(sessionId);
518
+ const state = await (0, browser_1.exportStorageState)(session.page, scope);
519
+ this.markSessionState(session);
520
+ return {
521
+ sessionId: session.sessionId,
522
+ url: session.currentUrl,
523
+ state
524
+ };
525
+ }
526
+ async storageImport(sessionId, state, scope, clear = false) {
527
+ const session = await this.getSessionOrThrow(sessionId);
528
+ const imported = await (0, browser_1.importStorageState)(session.page, state, scope, clear);
529
+ this.markSessionState(session);
530
+ return {
531
+ sessionId: session.sessionId,
532
+ url: session.currentUrl,
533
+ staleSnapshot: session.staleSnapshot,
534
+ origin: imported.origin,
535
+ localKeys: imported.localKeys,
536
+ sessionKeys: imported.sessionKeys
537
+ };
538
+ }
539
+ async storageClear(sessionId, scope) {
540
+ const session = await this.getSessionOrThrow(sessionId);
541
+ const cleared = await (0, browser_1.clearStorageState)(session.page, scope);
542
+ this.markSessionState(session);
543
+ return {
544
+ sessionId: session.sessionId,
545
+ url: session.currentUrl,
546
+ staleSnapshot: session.staleSnapshot,
547
+ origin: cleared.origin,
548
+ scope: cleared.scope
549
+ };
550
+ }
551
+ async eval(sessionId, expression) {
552
+ const session = await this.getSessionOrThrow(sessionId);
553
+ const value = await (0, browser_1.evaluateExpression)(session.page, expression);
554
+ this.markSessionState(session);
555
+ return {
556
+ sessionId: session.sessionId,
557
+ url: session.currentUrl,
558
+ staleSnapshot: session.staleSnapshot,
559
+ value
560
+ };
561
+ }
562
+ async find(sessionId, request) {
563
+ const session = await this.getSessionOrThrow(sessionId);
564
+ const locator = await (0, browser_1.resolveSemanticLocator)(session.page, request.locator, this.config.actionTimeoutMs);
565
+ const value = await (0, browser_1.performLocatorAction)(session.page, locator, request.action, this.config.actionTimeoutMs, request.value);
566
+ const staleSnapshot = (0, browser_1.actionMutatesSnapshot)(request.action);
567
+ this.markSessionState(session, staleSnapshot ? true : session.staleSnapshot);
568
+ return {
569
+ sessionId: session.sessionId,
570
+ url: session.currentUrl,
571
+ staleSnapshot: session.staleSnapshot,
572
+ value
573
+ };
574
+ }
575
+ async screenshot(sessionId, targetPath, annotate = false, pressEscape = false) {
576
+ const session = await this.getSessionOrThrow(sessionId);
577
+ let pressedEscape = false;
578
+ try {
579
+ node_fs_1.default.mkdirSync(node_path_1.default.dirname(targetPath), { recursive: true });
580
+ if (pressEscape) {
581
+ await (0, browser_1.pressKey)(session.page, "Escape", this.config.actionTimeoutMs);
582
+ await new Promise((resolve) => setTimeout(resolve, 200));
583
+ pressedEscape = true;
584
+ }
585
+ if (annotate) {
586
+ const snapshot = await (0, snapshot_1.buildSnapshot)(session.page, { interactive: true });
587
+ this.refStore.set(session.sessionId, snapshot.refs);
588
+ session.hasSnapshot = true;
589
+ session.staleSnapshot = false;
590
+ const labels = [];
591
+ for (const descriptor of snapshot.refs) {
592
+ const locator = await (0, browser_1.resolveDescriptorLocator)(session.page, descriptor).catch(() => undefined);
593
+ if (!locator) {
594
+ continue;
595
+ }
596
+ const box = await locator.boundingBox().catch(() => null);
597
+ if (!box) {
598
+ continue;
599
+ }
600
+ labels.push({
601
+ ref: descriptor.ref,
602
+ x: box.x,
603
+ y: box.y
604
+ });
605
+ }
606
+ await (0, browser_1.annotatePageWithRefs)(session.page, labels);
607
+ }
608
+ await (0, browser_1.captureScreenshot)(session.page, targetPath, this.config.navigationTimeoutMs);
609
+ if (annotate) {
610
+ await (0, browser_1.clearPageAnnotations)(session.page).catch(() => undefined);
611
+ }
612
+ }
613
+ catch (error) {
614
+ if (annotate) {
615
+ await (0, browser_1.clearPageAnnotations)(session.page).catch(() => undefined);
616
+ }
617
+ const baseMessage = error instanceof Error ? error.message : String(error);
618
+ const hint = pressEscape
619
+ ? "Screenshot timed out or failed even after pressing Escape"
620
+ : "Screenshot timed out or failed; try pressing Escape first or re-run with --press-escape";
621
+ throw new errors_1.AppError("SCREENSHOT_FAILED", `${hint}: ${baseMessage}`, 500, {
622
+ path: targetPath,
623
+ pressEscape
624
+ });
625
+ }
626
+ if (pressedEscape && !annotate && session.hasSnapshot) {
627
+ session.staleSnapshot = true;
628
+ }
629
+ session.lastScreenshotPath = targetPath;
630
+ this.touchSession(session);
631
+ return {
632
+ sessionId: session.sessionId,
633
+ path: targetPath,
634
+ url: session.currentUrl,
635
+ annotated: annotate,
636
+ pressedEscape
637
+ };
638
+ }
639
+ async pdf(sessionId, targetPath) {
640
+ const session = await this.getSessionOrThrow(sessionId);
641
+ try {
642
+ node_fs_1.default.mkdirSync(node_path_1.default.dirname(targetPath), { recursive: true });
643
+ await (0, browser_1.savePdf)(session.page, targetPath);
644
+ }
645
+ catch (error) {
646
+ throw new errors_1.AppError("PDF_FAILED", error instanceof Error ? error.message : String(error), 500, {
647
+ path: targetPath
648
+ });
649
+ }
650
+ session.lastPdfPath = targetPath;
651
+ this.markSessionState(session);
652
+ return {
653
+ sessionId: session.sessionId,
654
+ path: targetPath,
655
+ url: session.currentUrl
656
+ };
657
+ }
658
+ async close(sessionId) {
659
+ const session = await this.getSessionOrThrow(sessionId);
660
+ await this.destroySession(session);
661
+ return {
662
+ sessionId: session.sessionId,
663
+ closed: true
664
+ };
665
+ }
666
+ async listSessions() {
667
+ await this.evictExpiredSessions();
668
+ return {
669
+ activeSessionId: this.activeSessionId,
670
+ sessions: Array.from(this.sessions.values()).map((session) => this.toSummary(session))
671
+ };
672
+ }
673
+ async currentSession() {
674
+ return this.toSummary(await this.getSessionOrThrow());
675
+ }
676
+ async closeAll() {
677
+ for (const session of Array.from(this.sessions.values())) {
678
+ await this.destroySession(session);
679
+ }
680
+ this.sessions.clear();
681
+ this.activeSessionId = undefined;
682
+ }
683
+ }
684
+ exports.SessionManager = SessionManager;
@@ -0,0 +1,8 @@
1
+ import type { Page } from "playwright";
2
+ import type { RawSnapshotCandidate, SnapshotBuildResult } from "../lib/types";
3
+ type SnapshotOptions = {
4
+ interactive?: boolean;
5
+ };
6
+ export declare function buildSnapshotModel(rawCandidates: RawSnapshotCandidate[], options?: SnapshotOptions): SnapshotBuildResult;
7
+ export declare function buildSnapshot(page: Page, options?: SnapshotOptions): Promise<SnapshotBuildResult>;
8
+ export {};