@hera-al/browser-server 1.0.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 (69) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +212 -0
  3. package/dist/config.d.ts +44 -0
  4. package/dist/config.js +74 -0
  5. package/dist/core/cdp.d.ts +124 -0
  6. package/dist/core/cdp.helpers.d.ts +14 -0
  7. package/dist/core/cdp.helpers.js +148 -0
  8. package/dist/core/cdp.js +309 -0
  9. package/dist/core/chrome.d.ts +21 -0
  10. package/dist/core/chrome.executables.d.ts +10 -0
  11. package/dist/core/chrome.executables.js +559 -0
  12. package/dist/core/chrome.js +257 -0
  13. package/dist/core/chrome.profile-decoration.d.ts +11 -0
  14. package/dist/core/chrome.profile-decoration.js +148 -0
  15. package/dist/core/constants.d.ts +9 -0
  16. package/dist/core/constants.js +9 -0
  17. package/dist/core/profiles.d.ts +31 -0
  18. package/dist/core/profiles.js +99 -0
  19. package/dist/core/target-id.d.ts +12 -0
  20. package/dist/core/target-id.js +21 -0
  21. package/dist/data-dir.d.ts +2 -0
  22. package/dist/data-dir.js +6 -0
  23. package/dist/logger.d.ts +16 -0
  24. package/dist/logger.js +125 -0
  25. package/dist/playwright/pw-role-snapshot.d.ts +32 -0
  26. package/dist/playwright/pw-role-snapshot.js +337 -0
  27. package/dist/playwright/pw-session.d.ts +119 -0
  28. package/dist/playwright/pw-session.js +530 -0
  29. package/dist/playwright/pw-tools-core.activity.d.ts +22 -0
  30. package/dist/playwright/pw-tools-core.activity.js +47 -0
  31. package/dist/playwright/pw-tools-core.d.ts +9 -0
  32. package/dist/playwright/pw-tools-core.downloads.d.ts +35 -0
  33. package/dist/playwright/pw-tools-core.downloads.js +186 -0
  34. package/dist/playwright/pw-tools-core.interactions.d.ts +104 -0
  35. package/dist/playwright/pw-tools-core.interactions.js +404 -0
  36. package/dist/playwright/pw-tools-core.js +9 -0
  37. package/dist/playwright/pw-tools-core.responses.d.ts +14 -0
  38. package/dist/playwright/pw-tools-core.responses.js +91 -0
  39. package/dist/playwright/pw-tools-core.shared.d.ts +7 -0
  40. package/dist/playwright/pw-tools-core.shared.js +50 -0
  41. package/dist/playwright/pw-tools-core.snapshot.d.ts +65 -0
  42. package/dist/playwright/pw-tools-core.snapshot.js +144 -0
  43. package/dist/playwright/pw-tools-core.state.d.ts +47 -0
  44. package/dist/playwright/pw-tools-core.state.js +154 -0
  45. package/dist/playwright/pw-tools-core.storage.d.ts +48 -0
  46. package/dist/playwright/pw-tools-core.storage.js +76 -0
  47. package/dist/playwright/pw-tools-core.trace.d.ts +13 -0
  48. package/dist/playwright/pw-tools-core.trace.js +26 -0
  49. package/dist/server/browser-context.d.ts +29 -0
  50. package/dist/server/browser-context.js +137 -0
  51. package/dist/server/browser-server.d.ts +7 -0
  52. package/dist/server/browser-server.js +49 -0
  53. package/dist/server/routes/act.d.ts +4 -0
  54. package/dist/server/routes/act.js +176 -0
  55. package/dist/server/routes/basic.d.ts +4 -0
  56. package/dist/server/routes/basic.js +36 -0
  57. package/dist/server/routes/index.d.ts +4 -0
  58. package/dist/server/routes/index.js +16 -0
  59. package/dist/server/routes/snapshot.d.ts +4 -0
  60. package/dist/server/routes/snapshot.js +143 -0
  61. package/dist/server/routes/storage.d.ts +4 -0
  62. package/dist/server/routes/storage.js +117 -0
  63. package/dist/server/routes/tabs.d.ts +4 -0
  64. package/dist/server/routes/tabs.js +51 -0
  65. package/dist/server/standalone.d.ts +9 -0
  66. package/dist/server/standalone.js +42 -0
  67. package/dist/utils.d.ts +18 -0
  68. package/dist/utils.js +58 -0
  69. package/package.json +66 -0
@@ -0,0 +1,143 @@
1
+ import { Hono } from "hono";
2
+ import fs from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import crypto from "node:crypto";
6
+ import { snapshotRoleViaPlaywright, snapshotAiViaPlaywright, pdfViaPlaywright, } from "../../playwright/pw-tools-core.snapshot.js";
7
+ import { takeScreenshotViaPlaywright } from "../../playwright/pw-tools-core.interactions.js";
8
+ import { snapshotAria as snapshotAriaCdp, snapshotDom as snapshotDomCdp, getDomText, querySelector, } from "../../core/cdp.js";
9
+ import { getChromeWebSocketUrl } from "../../core/chrome.js";
10
+ import { normalizeCdpWsUrl } from "../../core/cdp.js";
11
+ import { fetchJson, appendCdpPath } from "../../core/cdp.helpers.js";
12
+ export function snapshotRoutes(ctx) {
13
+ const app = new Hono();
14
+ // Role-based snapshot (Playwright)
15
+ app.get("/snapshot", async (c) => {
16
+ const profileName = c.req.query("profile");
17
+ const mode = c.req.query("mode") || "role";
18
+ const selector = c.req.query("selector");
19
+ const frameSelector = c.req.query("frameSelector");
20
+ const interactive = c.req.query("interactive") === "true";
21
+ const compact = c.req.query("compact") === "true";
22
+ const targetId = c.req.query("targetId");
23
+ try {
24
+ const profile = ctx.getProfile(profileName);
25
+ const cdpUrl = profile.cdpUrl;
26
+ const tid = targetId || ctx.getLastTargetId(profile.name);
27
+ if (mode === "aria") {
28
+ // CDP-based aria snapshot
29
+ const wsUrl = await resolveWsUrl(cdpUrl, tid);
30
+ const result = await snapshotAriaCdp({ wsUrl });
31
+ return c.json({ ok: true, result });
32
+ }
33
+ if (mode === "dom") {
34
+ const wsUrl = await resolveWsUrl(cdpUrl, tid);
35
+ const result = await snapshotDomCdp({ wsUrl });
36
+ return c.json({ ok: true, result });
37
+ }
38
+ if (mode === "text" || mode === "html") {
39
+ const wsUrl = await resolveWsUrl(cdpUrl, tid);
40
+ const result = await getDomText({
41
+ wsUrl,
42
+ format: mode,
43
+ selector: selector || undefined,
44
+ });
45
+ return c.json({ ok: true, result });
46
+ }
47
+ if (mode === "query") {
48
+ const sel = selector || c.req.query("q") || "";
49
+ if (!sel) {
50
+ return c.json({ ok: false, error: "selector (q) is required for query mode" }, 400);
51
+ }
52
+ const wsUrl = await resolveWsUrl(cdpUrl, tid);
53
+ const result = await querySelector({ wsUrl, selector: sel });
54
+ return c.json({ ok: true, result });
55
+ }
56
+ if (mode === "ai") {
57
+ const result = await snapshotAiViaPlaywright({
58
+ cdpUrl,
59
+ targetId: tid,
60
+ });
61
+ return c.json({ ok: true, result });
62
+ }
63
+ // Default: role snapshot via Playwright
64
+ const result = await snapshotRoleViaPlaywright({
65
+ cdpUrl,
66
+ targetId: tid,
67
+ selector: selector || undefined,
68
+ frameSelector: frameSelector || undefined,
69
+ options: {
70
+ interactive: interactive || undefined,
71
+ compact: compact || undefined,
72
+ },
73
+ });
74
+ return c.json({ ok: true, result });
75
+ }
76
+ catch (err) {
77
+ return c.json({ ok: false, error: err instanceof Error ? err.message : String(err) }, 500);
78
+ }
79
+ });
80
+ app.post("/screenshot", async (c) => {
81
+ const body = await c.req.json().catch(() => ({}));
82
+ try {
83
+ const profile = ctx.getProfile(body.profile);
84
+ const cdpUrl = profile.cdpUrl;
85
+ const tid = body.targetId || ctx.getLastTargetId(profile.name);
86
+ const shot = await takeScreenshotViaPlaywright({
87
+ cdpUrl,
88
+ targetId: tid,
89
+ ref: body.ref,
90
+ element: body.element,
91
+ fullPage: body.fullPage,
92
+ type: body.type,
93
+ });
94
+ const ext = body.type === "jpeg" ? "jpg" : "png";
95
+ const fileName = `screenshot-${crypto.randomUUID().slice(0, 8)}.${ext}`;
96
+ const dir = path.join(os.tmpdir(), "grabmeabeer", "screenshots");
97
+ await fs.mkdir(dir, { recursive: true });
98
+ const filePath = path.join(dir, fileName);
99
+ await fs.writeFile(filePath, shot.buffer);
100
+ return c.json({ ok: true, path: filePath, size: shot.buffer.length });
101
+ }
102
+ catch (err) {
103
+ return c.json({ ok: false, error: err instanceof Error ? err.message : String(err) }, 500);
104
+ }
105
+ });
106
+ app.post("/pdf", async (c) => {
107
+ const body = await c.req.json().catch(() => ({}));
108
+ try {
109
+ const profile = ctx.getProfile(body.profile);
110
+ const cdpUrl = profile.cdpUrl;
111
+ const tid = body.targetId || ctx.getLastTargetId(profile.name);
112
+ const pdf = await pdfViaPlaywright({ cdpUrl, targetId: tid });
113
+ const fileName = `page-${crypto.randomUUID().slice(0, 8)}.pdf`;
114
+ const dir = path.join(os.tmpdir(), "grabmeabeer", "pdfs");
115
+ await fs.mkdir(dir, { recursive: true });
116
+ const filePath = path.join(dir, fileName);
117
+ await fs.writeFile(filePath, pdf.buffer);
118
+ return c.json({ ok: true, path: filePath, size: pdf.buffer.length });
119
+ }
120
+ catch (err) {
121
+ return c.json({ ok: false, error: err instanceof Error ? err.message : String(err) }, 500);
122
+ }
123
+ });
124
+ return app;
125
+ }
126
+ async function resolveWsUrl(cdpUrl, targetId) {
127
+ if (targetId) {
128
+ const listUrl = appendCdpPath(cdpUrl, "/json/list");
129
+ const targets = await fetchJson(listUrl, 2000).catch(() => []);
130
+ const target = targets.find(t => t.id === targetId);
131
+ const wsRaw = target?.webSocketDebuggerUrl;
132
+ if (wsRaw) {
133
+ return normalizeCdpWsUrl(wsRaw, cdpUrl);
134
+ }
135
+ }
136
+ // Fall back to browser debugger URL
137
+ const wsUrl = await getChromeWebSocketUrl(cdpUrl, 2000);
138
+ if (!wsUrl) {
139
+ throw new Error(`Cannot resolve WebSocket URL for ${cdpUrl}`);
140
+ }
141
+ return wsUrl;
142
+ }
143
+ //# sourceMappingURL=snapshot.js.map
@@ -0,0 +1,4 @@
1
+ import { Hono } from "hono";
2
+ import type { BrowserContext } from "../browser-context.js";
3
+ export declare function storageRoutes(ctx: BrowserContext): Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
4
+ //# sourceMappingURL=storage.d.ts.map
@@ -0,0 +1,117 @@
1
+ import { Hono } from "hono";
2
+ import { cookiesGetViaPlaywright, cookiesSetViaPlaywright, cookiesClearViaPlaywright, storageGetViaPlaywright, storageSetViaPlaywright, storageClearViaPlaywright, } from "../../playwright/pw-tools-core.storage.js";
3
+ export function storageRoutes(ctx) {
4
+ const app = new Hono();
5
+ // Cookies
6
+ app.get("/cookies", async (c) => {
7
+ const profileName = c.req.query("profile");
8
+ const targetId = c.req.query("targetId");
9
+ try {
10
+ const profile = ctx.getProfile(profileName);
11
+ const result = await cookiesGetViaPlaywright({
12
+ cdpUrl: profile.cdpUrl,
13
+ targetId: targetId || ctx.getLastTargetId(profile.name),
14
+ });
15
+ return c.json({ ok: true, ...result });
16
+ }
17
+ catch (err) {
18
+ return c.json({ ok: false, error: err instanceof Error ? err.message : String(err) }, 500);
19
+ }
20
+ });
21
+ app.post("/cookies", async (c) => {
22
+ const body = await c.req.json();
23
+ try {
24
+ const profile = ctx.getProfile(body.profile);
25
+ await cookiesSetViaPlaywright({
26
+ cdpUrl: profile.cdpUrl,
27
+ targetId: body.targetId || ctx.getLastTargetId(profile.name),
28
+ cookie: body.cookie,
29
+ });
30
+ return c.json({ ok: true });
31
+ }
32
+ catch (err) {
33
+ return c.json({ ok: false, error: err instanceof Error ? err.message : String(err) }, 500);
34
+ }
35
+ });
36
+ app.delete("/cookies", async (c) => {
37
+ const profileName = c.req.query("profile");
38
+ const targetId = c.req.query("targetId");
39
+ try {
40
+ const profile = ctx.getProfile(profileName);
41
+ await cookiesClearViaPlaywright({
42
+ cdpUrl: profile.cdpUrl,
43
+ targetId: targetId || ctx.getLastTargetId(profile.name),
44
+ });
45
+ return c.json({ ok: true });
46
+ }
47
+ catch (err) {
48
+ return c.json({ ok: false, error: err instanceof Error ? err.message : String(err) }, 500);
49
+ }
50
+ });
51
+ // localStorage / sessionStorage
52
+ app.get("/storage/:kind", async (c) => {
53
+ const kind = c.req.param("kind");
54
+ if (kind !== "local" && kind !== "session") {
55
+ return c.json({ ok: false, error: "kind must be 'local' or 'session'" }, 400);
56
+ }
57
+ const profileName = c.req.query("profile");
58
+ const targetId = c.req.query("targetId");
59
+ const key = c.req.query("key");
60
+ try {
61
+ const profile = ctx.getProfile(profileName);
62
+ const result = await storageGetViaPlaywright({
63
+ cdpUrl: profile.cdpUrl,
64
+ targetId: targetId || ctx.getLastTargetId(profile.name),
65
+ kind,
66
+ key: key || undefined,
67
+ });
68
+ return c.json({ ok: true, ...result });
69
+ }
70
+ catch (err) {
71
+ return c.json({ ok: false, error: err instanceof Error ? err.message : String(err) }, 500);
72
+ }
73
+ });
74
+ app.post("/storage/:kind", async (c) => {
75
+ const kind = c.req.param("kind");
76
+ if (kind !== "local" && kind !== "session") {
77
+ return c.json({ ok: false, error: "kind must be 'local' or 'session'" }, 400);
78
+ }
79
+ const body = await c.req.json();
80
+ try {
81
+ const profile = ctx.getProfile(body.profile);
82
+ await storageSetViaPlaywright({
83
+ cdpUrl: profile.cdpUrl,
84
+ targetId: body.targetId || ctx.getLastTargetId(profile.name),
85
+ kind,
86
+ key: body.key,
87
+ value: body.value,
88
+ });
89
+ return c.json({ ok: true });
90
+ }
91
+ catch (err) {
92
+ return c.json({ ok: false, error: err instanceof Error ? err.message : String(err) }, 500);
93
+ }
94
+ });
95
+ app.delete("/storage/:kind", async (c) => {
96
+ const kind = c.req.param("kind");
97
+ if (kind !== "local" && kind !== "session") {
98
+ return c.json({ ok: false, error: "kind must be 'local' or 'session'" }, 400);
99
+ }
100
+ const profileName = c.req.query("profile");
101
+ const targetId = c.req.query("targetId");
102
+ try {
103
+ const profile = ctx.getProfile(profileName);
104
+ await storageClearViaPlaywright({
105
+ cdpUrl: profile.cdpUrl,
106
+ targetId: targetId || ctx.getLastTargetId(profile.name),
107
+ kind,
108
+ });
109
+ return c.json({ ok: true });
110
+ }
111
+ catch (err) {
112
+ return c.json({ ok: false, error: err instanceof Error ? err.message : String(err) }, 500);
113
+ }
114
+ });
115
+ return app;
116
+ }
117
+ //# sourceMappingURL=storage.js.map
@@ -0,0 +1,4 @@
1
+ import { Hono } from "hono";
2
+ import type { BrowserContext } from "../browser-context.js";
3
+ export declare function tabsRoutes(ctx: BrowserContext): Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
4
+ //# sourceMappingURL=tabs.d.ts.map
@@ -0,0 +1,51 @@
1
+ import { Hono } from "hono";
2
+ export function tabsRoutes(ctx) {
3
+ const app = new Hono();
4
+ app.get("/tabs", async (c) => {
5
+ const profile = c.req.query("profile");
6
+ try {
7
+ const tabs = await ctx.listTabs(profile);
8
+ return c.json({ ok: true, tabs });
9
+ }
10
+ catch (err) {
11
+ return c.json({ ok: false, error: err instanceof Error ? err.message : String(err) }, 500);
12
+ }
13
+ });
14
+ app.post("/tabs/open", async (c) => {
15
+ const body = await c.req.json();
16
+ const url = body.url || "about:blank";
17
+ try {
18
+ const tab = await ctx.openTab(url, body.profile);
19
+ return c.json({ ok: true, tab });
20
+ }
21
+ catch (err) {
22
+ return c.json({ ok: false, error: err instanceof Error ? err.message : String(err) }, 500);
23
+ }
24
+ });
25
+ app.post("/tabs/focus", async (c) => {
26
+ const body = await c.req.json();
27
+ if (!body.id) {
28
+ return c.json({ ok: false, error: "id is required" }, 400);
29
+ }
30
+ try {
31
+ await ctx.focusTab(body.id, body.profile);
32
+ return c.json({ ok: true });
33
+ }
34
+ catch (err) {
35
+ return c.json({ ok: false, error: err instanceof Error ? err.message : String(err) }, 500);
36
+ }
37
+ });
38
+ app.delete("/tabs/:id", async (c) => {
39
+ const id = c.req.param("id");
40
+ const profile = c.req.query("profile");
41
+ try {
42
+ await ctx.closeTab(id, profile);
43
+ return c.json({ ok: true });
44
+ }
45
+ catch (err) {
46
+ return c.json({ ok: false, error: err instanceof Error ? err.message : String(err) }, 500);
47
+ }
48
+ });
49
+ return app;
50
+ }
51
+ //# sourceMappingURL=tabs.js.map
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Standalone entry point for the browser HTTP server.
4
+ * Can be spawned as a subprocess by standardnode or run directly.
5
+ *
6
+ * Usage: tsx src/browser/server/standalone.ts [--port 3002] [--headless true] [--noSandbox true]
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=standalone.d.ts.map
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Standalone entry point for the browser HTTP server.
4
+ * Can be spawned as a subprocess by standardnode or run directly.
5
+ *
6
+ * Usage: tsx src/browser/server/standalone.ts [--port 3002] [--headless true] [--noSandbox true]
7
+ */
8
+ import { startBrowserServer, stopBrowserServer } from "./browser-server.js";
9
+ import { BrowserConfigSchema, resolveBrowserConfig } from "../config.js";
10
+ function parseArgs() {
11
+ const args = process.argv.slice(2);
12
+ const result = {};
13
+ for (let i = 0; i < args.length; i++) {
14
+ if (args[i].startsWith("--") && args[i + 1] && !args[i + 1].startsWith("--")) {
15
+ result[args[i].slice(2)] = args[++i];
16
+ }
17
+ }
18
+ return result;
19
+ }
20
+ const args = parseArgs();
21
+ const port = parseInt(args.port ?? "3002", 10);
22
+ const parsed = BrowserConfigSchema.parse({
23
+ enabled: true,
24
+ controlPort: port,
25
+ headless: args.headless === "true",
26
+ noSandbox: args.noSandbox === "true",
27
+ attachOnly: args.attachOnly === "true",
28
+ executablePath: args.executablePath || undefined,
29
+ });
30
+ const config = resolveBrowserConfig(parsed);
31
+ await startBrowserServer(config);
32
+ // Signal parent process (if spawned via fork / IPC)
33
+ if (process.send) {
34
+ process.send({ type: "ready", port });
35
+ }
36
+ const shutdown = async () => {
37
+ await stopBrowserServer();
38
+ process.exit(0);
39
+ };
40
+ process.on("SIGTERM", shutdown);
41
+ process.on("SIGINT", shutdown);
42
+ //# sourceMappingURL=standalone.js.map
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Check if a port is available (not in use).
3
+ * Throws if the port is already taken.
4
+ */
5
+ export declare function ensurePortAvailable(port: number): Promise<void>;
6
+ /**
7
+ * Format an error into a human-readable message.
8
+ */
9
+ export declare function formatErrorMessage(err: unknown): string;
10
+ /**
11
+ * Check if a hostname is a loopback address.
12
+ */
13
+ export declare function isLoopbackHost(host: string): boolean;
14
+ /**
15
+ * Convert raw WebSocket data to string.
16
+ */
17
+ export declare function rawDataToString(data: unknown): string;
18
+ //# sourceMappingURL=utils.d.ts.map
package/dist/utils.js ADDED
@@ -0,0 +1,58 @@
1
+ import net from "node:net";
2
+ /**
3
+ * Check if a port is available (not in use).
4
+ * Throws if the port is already taken.
5
+ */
6
+ export async function ensurePortAvailable(port) {
7
+ return new Promise((resolve, reject) => {
8
+ const server = net.createServer();
9
+ server.once("error", (err) => {
10
+ if (err.code === "EADDRINUSE") {
11
+ reject(new Error(`Port ${port} is already in use`));
12
+ }
13
+ else {
14
+ reject(err);
15
+ }
16
+ });
17
+ server.once("listening", () => {
18
+ server.close(() => resolve());
19
+ });
20
+ server.listen(port, "127.0.0.1");
21
+ });
22
+ }
23
+ /**
24
+ * Format an error into a human-readable message.
25
+ */
26
+ export function formatErrorMessage(err) {
27
+ if (err instanceof Error)
28
+ return err.message;
29
+ if (typeof err === "string")
30
+ return err;
31
+ return String(err);
32
+ }
33
+ /**
34
+ * Check if a hostname is a loopback address.
35
+ */
36
+ export function isLoopbackHost(host) {
37
+ const lower = host.toLowerCase();
38
+ return (lower === "localhost" ||
39
+ lower === "127.0.0.1" ||
40
+ lower === "::1" ||
41
+ lower === "[::1]" ||
42
+ lower === "0.0.0.0");
43
+ }
44
+ /**
45
+ * Convert raw WebSocket data to string.
46
+ */
47
+ export function rawDataToString(data) {
48
+ if (typeof data === "string")
49
+ return data;
50
+ if (Buffer.isBuffer(data))
51
+ return data.toString("utf-8");
52
+ if (data instanceof ArrayBuffer)
53
+ return Buffer.from(data).toString("utf-8");
54
+ if (Array.isArray(data))
55
+ return Buffer.concat(data).toString("utf-8");
56
+ return String(data);
57
+ }
58
+ //# sourceMappingURL=utils.js.map
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@hera-al/browser-server",
3
+ "version": "1.0.0",
4
+ "description": "Hera Browser Server — local browser automation via Playwright and CDP",
5
+ "license": "MIT",
6
+ "author": "TGP <ltoscano@gmail.com>",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/ltoscano/hera_al.git",
10
+ "directory": "packages/browser-server"
11
+ },
12
+ "homepage": "https://github.com/ltoscano/hera_al",
13
+ "keywords": [
14
+ "browser-automation",
15
+ "playwright",
16
+ "cdp",
17
+ "chrome-devtools-protocol",
18
+ "ai-agent",
19
+ "hera"
20
+ ],
21
+ "type": "module",
22
+ "main": "./dist/server/browser-server.js",
23
+ "types": "./dist/server/browser-server.d.ts",
24
+ "exports": {
25
+ ".": {
26
+ "import": "./dist/server/browser-server.js",
27
+ "types": "./dist/server/browser-server.d.ts"
28
+ },
29
+ "./config": {
30
+ "import": "./dist/config.js",
31
+ "types": "./dist/config.d.ts"
32
+ },
33
+ "./utils": {
34
+ "import": "./dist/utils.js",
35
+ "types": "./dist/utils.d.ts"
36
+ },
37
+ "./standalone": {
38
+ "import": "./dist/server/standalone.js",
39
+ "types": "./dist/server/standalone.d.ts"
40
+ }
41
+ },
42
+ "bin": {
43
+ "hera-browser": "./dist/server/standalone.js"
44
+ },
45
+ "files": ["dist", "!dist/**/*.map", "!src", "!tsconfig.json"],
46
+ "engines": { "node": ">=18" },
47
+ "scripts": {
48
+ "start": "node dist/server/standalone.js",
49
+ "dev": "tsx watch src/server/standalone.ts",
50
+ "build": "tsc",
51
+ "prepublishOnly": "npm run build"
52
+ },
53
+ "dependencies": {
54
+ "@hono/node-server": "^1.14.0",
55
+ "hono": "^4.7.0",
56
+ "playwright-core": "^1.50.0",
57
+ "ws": "^8.18.0",
58
+ "zod": "^4.0.0"
59
+ },
60
+ "devDependencies": {
61
+ "@types/node": "^22.13.4",
62
+ "@types/ws": "^8.5.13",
63
+ "tsx": "^4.19.3",
64
+ "typescript": "^5.7.3"
65
+ }
66
+ }