@bubblegum-ai/node 0.0.6-alpha.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/README.md ADDED
@@ -0,0 +1,144 @@
1
+ # @bubblegum-ai/node
2
+
3
+ Node/TypeScript client for [**Bubblegum**](https://github.com/bishnu133/bubblegum) —
4
+ drive AI-powered, natural-language Playwright/Appium test steps from JS/TS.
5
+
6
+ > **Status: alpha scaffold (`0.2.0` slice).** This is a thin, typed client that
7
+ > spawns the Python **bubblegum bridge** and speaks its JSON-RPC protocol. The
8
+ > Python engine stays the single source of truth for grounding/self-healing — we
9
+ > do **not** re-implement it in TypeScript. See
10
+ > [`docs/distribution-npm-and-pypi.md`](../../docs/distribution-npm-and-pypi.md)
11
+ > and [`docs/bridge-protocol.md`](../../docs/bridge-protocol.md).
12
+
13
+ ## Prerequisites
14
+
15
+ The client launches the engine as a child process, so a Python engine must be
16
+ importable on the machine running your tests:
17
+
18
+ ```bash
19
+ pip install "bubblegum-ai[web]" # the engine + Playwright
20
+ python -m playwright install chromium # one-time browser download
21
+ # (mobile: pip install "bubblegum-ai[mobile]" + a running Appium server/device)
22
+ ```
23
+
24
+ Confirm the bridge is runnable:
25
+
26
+ ```bash
27
+ python -m bubblegum.bridge # should wait for JSON-RPC on stdin (Ctrl-D to exit)
28
+ ```
29
+
30
+ ## Install
31
+
32
+ ```bash
33
+ npm install @bubblegum-ai/node
34
+ ```
35
+
36
+ ## Quick start
37
+
38
+ ```ts
39
+ import { Bubblegum } from "@bubblegum-ai/node";
40
+
41
+ const bg = await Bubblegum.launch({ url: "https://the-internet.herokuapp.com/login" });
42
+ try {
43
+ await bg.act('Enter "tomsmith" into Username');
44
+ await bg.act('Enter "SuperSecretPassword!" into Password');
45
+ await bg.act("Click Login");
46
+ const r = await bg.verify("You logged into a secure area");
47
+ console.log(r.status); // "passed" | "recovered" | "failed"
48
+ } finally {
49
+ await bg.close();
50
+ }
51
+ ```
52
+
53
+ `launch()` spawns the bridge, negotiates the protocol via `handshake`, and opens
54
+ an **engine-owned** session (the Playwright `Page` / Appium driver lives in the
55
+ Python process). Every method returns the same `StepResult` shape as the Python
56
+ SDK.
57
+
58
+ ### Heal a stale selector (adoption path)
59
+
60
+ ```ts
61
+ // old: await page.click("#login-btn"); // selector now stale
62
+ const r = await bg.recover({ failedSelector: "#login-btn", intent: "Click Login" });
63
+ // r.status === "recovered" when Bubblegum healed it
64
+ ```
65
+
66
+ ### Mobile
67
+
68
+ ```ts
69
+ const bg = await Bubblegum.launch({
70
+ channel: "mobile",
71
+ appiumUrl: "http://127.0.0.1:4723",
72
+ capabilities: { platformName: "Android", "appium:app": "/path/to/app.apk" },
73
+ });
74
+ await bg.act("Tap Login");
75
+ ```
76
+
77
+ ### Attach to your own browser (CDP, client-owned)
78
+
79
+ Instead of letting the engine launch its own Chromium, point it at the browser
80
+ **your** Playwright test already drives, over the Chrome DevTools Protocol. Launch
81
+ Chromium with a remote-debugging port, then `attach`:
82
+
83
+ ```ts
84
+ import { chromium } from "@playwright/test";
85
+ import { Bubblegum } from "@bubblegum-ai/node";
86
+
87
+ const browser = await chromium.launch({ args: ["--remote-debugging-port=9222"] });
88
+ const page = await browser.newPage();
89
+ await page.goto("https://example.com/login");
90
+
91
+ const bg = await Bubblegum.attach({ cdpEndpoint: "http://localhost:9222" });
92
+ await bg.act("Click Login"); // drives the page you just opened
93
+ await bg.close(); // disconnects; your browser keeps running
94
+ ```
95
+
96
+ The engine attaches to an existing page (`pageIndex`, default 0) and never
97
+ creates or closes your browser/page. Requires the engine to advertise the
98
+ `channel.web.cdp` capability (Bubblegum ≥ 0.0.6); `attach()` throws a clear error
99
+ otherwise. CDP attach is Chromium-only.
100
+
101
+ ## API
102
+
103
+ | Method | Returns | Notes |
104
+ | --- | --- | --- |
105
+ | `Bubblegum.launch(opts)` | `Promise<Bubblegum>` | spawn + handshake + `session.open` |
106
+ | `bg.act(instruction, options?)` | `Promise<StepResult>` | |
107
+ | `bg.verify(instruction, options?)` | `Promise<StepResult>` | |
108
+ | `bg.extract(instruction, options?)` | `Promise<StepResult>` | value in `target.metadata.extracted_value` |
109
+ | `bg.recover({ failedSelector, intent, options? })` | `Promise<StepResult>` | |
110
+ | `bg.isVisible / isChecked / selectedValue(target)` | `Promise<boolean \| string>` | |
111
+ | `bg.explain(instruction)` | `Promise<string>` | dry-run rationale |
112
+ | `bg.summary()` | `Promise<SessionSummary>` | |
113
+ | `bg.close()` | `Promise<void>` | closes the session + bridge |
114
+
115
+ `options` is forwarded verbatim to the engine (`timeout_ms`, `selector`,
116
+ `action_type`, `value`, `assertion_type`, `expected_value`, `max_cost_level`, …),
117
+ matching the Python how-to guides.
118
+
119
+ ### Advanced: `BridgeClient`
120
+
121
+ `Bubblegum` wraps a lower-level `BridgeClient` (`bg.bridge`) that you can use
122
+ directly, or with an injected `Transport` (e.g. for tests or a future daemon
123
+ socket). See `src/client.ts`.
124
+
125
+ ## Versioning
126
+
127
+ The client pins to a compatible engine. It refuses to start against a
128
+ `protocol_version` it doesn't support and tells you to upgrade — newer engines
129
+ keep serving older clients (additive-first). This package's major/minor track the
130
+ engine; install a matching `bubblegum-ai`.
131
+
132
+ ## Develop
133
+
134
+ ```bash
135
+ npm install
136
+ npm run build # tsc -> dist/
137
+ npm test # build + node:test (no Python needed; mock transport)
138
+ npm run typecheck
139
+ ```
140
+
141
+ ## Not yet (roadmap)
142
+
143
+ - Auto-bootstrap of a managed Python venv when the engine isn't found.
144
+ - Published to npm + dual-publish CI alongside PyPI.
@@ -0,0 +1,56 @@
1
+ import { Handshake } from "./protocol.js";
2
+ /**
3
+ * A line-oriented duplex link to the bridge. The default implementation spawns
4
+ * the Python `bubblegum bridge` process, but it is an interface so tests (and
5
+ * future transports — e.g. a long-lived daemon socket) can inject their own.
6
+ */
7
+ export interface Transport {
8
+ /** Write one newline-delimited JSON-RPC message. */
9
+ send(line: string): void;
10
+ /** Register the handler for each inbound line. */
11
+ onLine(cb: (line: string) => void): void;
12
+ /** Register the handler invoked when the link closes (optionally with an error). */
13
+ onClose(cb: (err?: Error) => void): void;
14
+ /** Tear the link down. */
15
+ close(): Promise<void>;
16
+ }
17
+ export interface SpawnOptions {
18
+ /** Executable to launch (default: `python`). */
19
+ command?: string;
20
+ /** Args (default: `["-m", "bubblegum.bridge"]`). */
21
+ args?: string[];
22
+ cwd?: string;
23
+ env?: NodeJS.ProcessEnv;
24
+ }
25
+ /** Spawn the Python bridge as a child process and frame JSON-RPC over its stdio. */
26
+ export declare function spawnBridgeTransport(opts?: SpawnOptions): Transport;
27
+ export interface BridgeClientOptions {
28
+ /** Inject a transport (tests/daemon). When omitted, a Python bridge is spawned. */
29
+ transport?: Transport;
30
+ /** Spawn options for the default transport. Ignored when `transport` is given. */
31
+ spawn?: SpawnOptions;
32
+ }
33
+ /**
34
+ * Low-level JSON-RPC client over a {@link Transport}: assigns request ids,
35
+ * correlates responses, surfaces engine errors as {@link BridgeError}, and
36
+ * negotiates the protocol version via {@link BridgeClient.handshake}.
37
+ */
38
+ export declare class BridgeClient {
39
+ private readonly transport;
40
+ private readonly pending;
41
+ private nextId;
42
+ private closed;
43
+ /** Populated after a successful {@link handshake}. */
44
+ handshakeInfo?: Handshake;
45
+ constructor(opts?: BridgeClientOptions);
46
+ private onLine;
47
+ private onClose;
48
+ /** Issue a request and resolve with its `result` (or reject with a {@link BridgeError}). */
49
+ request<T = unknown>(method: string, params?: Record<string, unknown>): Promise<T>;
50
+ /** Negotiate with the engine; throws if its protocol version is unsupported. */
51
+ handshake(): Promise<Handshake>;
52
+ /** True if the engine advertised the given capability in its handshake. */
53
+ hasCapability(name: string): boolean;
54
+ close(): Promise<void>;
55
+ }
56
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAIA,OAAO,EAEL,SAAS,EAIV,MAAM,eAAe,CAAC;AAEvB;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,oDAAoD;IACpD,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,kDAAkD;IAClD,MAAM,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IACzC,oFAAoF;IACpF,OAAO,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,KAAK,IAAI,GAAG,IAAI,CAAC;IACzC,0BAA0B;IAC1B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,MAAM,WAAW,YAAY;IAC3B,gDAAgD;IAChD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oDAAoD;IACpD,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACzB;AAED,oFAAoF;AACpF,wBAAgB,oBAAoB,CAAC,IAAI,GAAE,YAAiB,GAAG,SAAS,CA2BvE;AAOD,MAAM,WAAW,mBAAmB;IAClC,mFAAmF;IACnF,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,kFAAkF;IAClF,KAAK,CAAC,EAAE,YAAY,CAAC;CACtB;AAED;;;;GAIG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IACtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA8B;IACtD,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,MAAM,CAAS;IACvB,sDAAsD;IACtD,aAAa,CAAC,EAAE,SAAS,CAAC;gBAEd,IAAI,GAAE,mBAAwB;IAM1C,OAAO,CAAC,MAAM;IAoBd,OAAO,CAAC,OAAO;IAOf,4FAA4F;IAC5F,OAAO,CAAC,CAAC,GAAG,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,OAAO,CAAC,CAAC,CAAC;IAYtF,gFAAgF;IAC1E,SAAS,IAAI,OAAO,CAAC,SAAS,CAAC;IAarC,2EAA2E;IAC3E,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI9B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAI7B"}
package/dist/client.js ADDED
@@ -0,0 +1,113 @@
1
+ import { spawn } from "node:child_process";
2
+ import { createInterface } from "node:readline";
3
+ import { BridgeError } from "./errors.js";
4
+ import { ErrorCodes, SUPPORTED_PROTOCOL_VERSIONS, isErrorResponse, } from "./protocol.js";
5
+ /** Spawn the Python bridge as a child process and frame JSON-RPC over its stdio. */
6
+ export function spawnBridgeTransport(opts = {}) {
7
+ const command = opts.command ?? "python";
8
+ const args = opts.args ?? ["-m", "bubblegum.bridge"];
9
+ const child = spawn(command, args, {
10
+ cwd: opts.cwd,
11
+ env: opts.env,
12
+ stdio: ["pipe", "pipe", "inherit"], // engine logs/errors pass through to our stderr
13
+ });
14
+ const rl = createInterface({ input: child.stdout });
15
+ return {
16
+ send(line) {
17
+ child.stdin.write(line + "\n");
18
+ },
19
+ onLine(cb) {
20
+ rl.on("line", cb);
21
+ },
22
+ onClose(cb) {
23
+ child.on("error", (err) => cb(err));
24
+ child.on("exit", () => cb());
25
+ },
26
+ async close() {
27
+ rl.close();
28
+ child.stdin.end();
29
+ if (child.exitCode === null)
30
+ child.kill();
31
+ },
32
+ };
33
+ }
34
+ /**
35
+ * Low-level JSON-RPC client over a {@link Transport}: assigns request ids,
36
+ * correlates responses, surfaces engine errors as {@link BridgeError}, and
37
+ * negotiates the protocol version via {@link BridgeClient.handshake}.
38
+ */
39
+ export class BridgeClient {
40
+ transport;
41
+ pending = new Map();
42
+ nextId = 1;
43
+ closed = false;
44
+ /** Populated after a successful {@link handshake}. */
45
+ handshakeInfo;
46
+ constructor(opts = {}) {
47
+ this.transport = opts.transport ?? spawnBridgeTransport(opts.spawn);
48
+ this.transport.onLine((line) => this.onLine(line));
49
+ this.transport.onClose((err) => this.onClose(err));
50
+ }
51
+ onLine(line) {
52
+ const trimmed = line.trim();
53
+ if (!trimmed)
54
+ return;
55
+ let msg;
56
+ try {
57
+ msg = JSON.parse(trimmed);
58
+ }
59
+ catch {
60
+ return; // ignore non-JSON noise on the channel
61
+ }
62
+ if (typeof msg.id !== "number")
63
+ return; // we only issue numeric ids
64
+ const pending = this.pending.get(msg.id);
65
+ if (!pending)
66
+ return;
67
+ this.pending.delete(msg.id);
68
+ if (isErrorResponse(msg)) {
69
+ pending.reject(new BridgeError(msg.error.code, msg.error.message, msg.error.data));
70
+ }
71
+ else {
72
+ pending.resolve(msg.result);
73
+ }
74
+ }
75
+ onClose(err) {
76
+ this.closed = true;
77
+ const reason = err ?? new BridgeError(ErrorCodes.InternalError, "bridge connection closed");
78
+ for (const [, pending] of this.pending)
79
+ pending.reject(reason);
80
+ this.pending.clear();
81
+ }
82
+ /** Issue a request and resolve with its `result` (or reject with a {@link BridgeError}). */
83
+ request(method, params = {}) {
84
+ if (this.closed) {
85
+ return Promise.reject(new BridgeError(ErrorCodes.InternalError, "bridge is closed"));
86
+ }
87
+ const id = this.nextId++;
88
+ const payload = { jsonrpc: "2.0", id, method, params };
89
+ return new Promise((resolve, reject) => {
90
+ this.pending.set(id, { resolve: resolve, reject });
91
+ this.transport.send(JSON.stringify(payload));
92
+ });
93
+ }
94
+ /** Negotiate with the engine; throws if its protocol version is unsupported. */
95
+ async handshake() {
96
+ const info = await this.request("handshake");
97
+ if (!SUPPORTED_PROTOCOL_VERSIONS.includes(info.protocol_version)) {
98
+ throw new BridgeError(ErrorCodes.Unsupported, `engine protocol v${info.protocol_version} is not supported by this client ` +
99
+ `(supports: ${SUPPORTED_PROTOCOL_VERSIONS.join(", ")}). Upgrade @bubblegum-ai/node.`);
100
+ }
101
+ this.handshakeInfo = info;
102
+ return info;
103
+ }
104
+ /** True if the engine advertised the given capability in its handshake. */
105
+ hasCapability(name) {
106
+ return this.handshakeInfo?.capabilities.includes(name) ?? false;
107
+ }
108
+ async close() {
109
+ await this.transport.close();
110
+ this.onClose();
111
+ }
112
+ }
113
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEhD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EACL,UAAU,EAGV,2BAA2B,EAC3B,eAAe,GAChB,MAAM,eAAe,CAAC;AA2BvB,oFAAoF;AACpF,MAAM,UAAU,oBAAoB,CAAC,OAAqB,EAAE;IAC1D,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,QAAQ,CAAC;IACzC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAC;IACrD,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE;QACjC,GAAG,EAAE,IAAI,CAAC,GAAG;QACb,GAAG,EAAE,IAAI,CAAC,GAAG;QACb,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,gDAAgD;KACrF,CAAC,CAAC;IACH,MAAM,EAAE,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,MAAO,EAAE,CAAC,CAAC;IAErD,OAAO;QACL,IAAI,CAAC,IAAY;YACf,KAAK,CAAC,KAAM,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;QAClC,CAAC;QACD,MAAM,CAAC,EAA0B;YAC/B,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACpB,CAAC;QACD,OAAO,CAAC,EAAyB;YAC/B,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;YACpC,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;QAC/B,CAAC;QACD,KAAK,CAAC,KAAK;YACT,EAAE,CAAC,KAAK,EAAE,CAAC;YACX,KAAK,CAAC,KAAM,CAAC,GAAG,EAAE,CAAC;YACnB,IAAI,KAAK,CAAC,QAAQ,KAAK,IAAI;gBAAE,KAAK,CAAC,IAAI,EAAE,CAAC;QAC5C,CAAC;KACF,CAAC;AACJ,CAAC;AAcD;;;;GAIG;AACH,MAAM,OAAO,YAAY;IACN,SAAS,CAAY;IACrB,OAAO,GAAG,IAAI,GAAG,EAAmB,CAAC;IAC9C,MAAM,GAAG,CAAC,CAAC;IACX,MAAM,GAAG,KAAK,CAAC;IACvB,sDAAsD;IACtD,aAAa,CAAa;IAE1B,YAAY,OAA4B,EAAE;QACxC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,oBAAoB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACpE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;QACnD,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;IACrD,CAAC;IAEO,MAAM,CAAC,IAAY;QACzB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO;YAAE,OAAO;QACrB,IAAI,GAAoB,CAAC;QACzB,IAAI,CAAC;YACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAoB,CAAC;QAC/C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,CAAC,uCAAuC;QACjD,CAAC;QACD,IAAI,OAAO,GAAG,CAAC,EAAE,KAAK,QAAQ;YAAE,OAAO,CAAC,4BAA4B;QACpE,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACzC,IAAI,CAAC,OAAO;YAAE,OAAO;QACrB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC5B,IAAI,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC;YACzB,OAAO,CAAC,MAAM,CAAC,IAAI,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;QACrF,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC;IAEO,OAAO,CAAC,GAAW;QACzB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACnB,MAAM,MAAM,GAAG,GAAG,IAAI,IAAI,WAAW,CAAC,UAAU,CAAC,aAAa,EAAE,0BAA0B,CAAC,CAAC;QAC5F,KAAK,MAAM,CAAC,EAAE,OAAO,CAAC,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC/D,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;IAED,4FAA4F;IAC5F,OAAO,CAAc,MAAc,EAAE,SAAkC,EAAE;QACvE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChB,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,WAAW,CAAC,UAAU,CAAC,aAAa,EAAE,kBAAkB,CAAC,CAAC,CAAC;QACvF,CAAC;QACD,MAAM,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,EAAE,OAAO,EAAE,KAAc,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QAChE,OAAO,IAAI,OAAO,CAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACxC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,OAA+B,EAAE,MAAM,EAAE,CAAC,CAAC;YAC3E,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;QAC/C,CAAC,CAAC,CAAC;IACL,CAAC;IAED,gFAAgF;IAChF,KAAK,CAAC,SAAS;QACb,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,CAAY,WAAW,CAAC,CAAC;QACxD,IAAI,CAAE,2BAAiD,CAAC,QAAQ,CAAC,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC;YACxF,MAAM,IAAI,WAAW,CACnB,UAAU,CAAC,WAAW,EACtB,oBAAoB,IAAI,CAAC,gBAAgB,mCAAmC;gBAC1E,cAAc,2BAA2B,CAAC,IAAI,CAAC,IAAI,CAAC,gCAAgC,CACvF,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;QAC1B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,2EAA2E;IAC3E,aAAa,CAAC,IAAY;QACxB,OAAO,IAAI,CAAC,aAAa,EAAE,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC;IAClE,CAAC;IAED,KAAK,CAAC,KAAK;QACT,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;QAC7B,IAAI,CAAC,OAAO,EAAE,CAAC;IACjB,CAAC;CACF"}
@@ -0,0 +1,7 @@
1
+ /** An error returned by the bridge (a JSON-RPC error response) or a transport failure. */
2
+ export declare class BridgeError extends Error {
3
+ readonly code: number;
4
+ readonly data?: unknown;
5
+ constructor(code: number, message: string, data?: unknown);
6
+ }
7
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,qBAAa,WAAY,SAAQ,KAAK;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;gBAEZ,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO;CAM1D"}
package/dist/errors.js ADDED
@@ -0,0 +1,12 @@
1
+ /** An error returned by the bridge (a JSON-RPC error response) or a transport failure. */
2
+ export class BridgeError extends Error {
3
+ code;
4
+ data;
5
+ constructor(code, message, data) {
6
+ super(message);
7
+ this.name = "BridgeError";
8
+ this.code = code;
9
+ this.data = data;
10
+ }
11
+ }
12
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,MAAM,OAAO,WAAY,SAAQ,KAAK;IAC3B,IAAI,CAAS;IACb,IAAI,CAAW;IAExB,YAAY,IAAY,EAAE,OAAe,EAAE,IAAc;QACvD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;QAC1B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @bubblegum-ai/node — Node/TypeScript client for the Bubblegum engine.
3
+ *
4
+ * Drives AI-powered, natural-language Playwright/Appium test steps from JS/TS by
5
+ * spawning the Python `bubblegum bridge` and speaking its JSON-RPC protocol. The
6
+ * Python engine stays the single source of truth for grounding; this package is
7
+ * a thin, typed proxy. See ../../docs/distribution-npm-and-pypi.md.
8
+ */
9
+ export { Bubblegum } from "./session.js";
10
+ export type { Channel, LaunchOptions, AttachOptions, RecoverArgs } from "./session.js";
11
+ export { BridgeClient, spawnBridgeTransport } from "./client.js";
12
+ export type { Transport, SpawnOptions, BridgeClientOptions } from "./client.js";
13
+ export { BridgeError } from "./errors.js";
14
+ export { PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS, ErrorCodes, } from "./protocol.js";
15
+ export type { Handshake, JsonRpcResponse } from "./protocol.js";
16
+ export type { StepResult, StepStatus, ResolvedTarget, ErrorInfo, SessionSummary, StepOptions, } from "./types.js";
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,YAAY,EAAE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEvF,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACjE,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEhF,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,OAAO,EACL,gBAAgB,EAChB,2BAA2B,EAC3B,UAAU,GACX,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEhE,YAAY,EACV,UAAU,EACV,UAAU,EACV,cAAc,EACd,SAAS,EACT,cAAc,EACd,WAAW,GACZ,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @bubblegum-ai/node — Node/TypeScript client for the Bubblegum engine.
3
+ *
4
+ * Drives AI-powered, natural-language Playwright/Appium test steps from JS/TS by
5
+ * spawning the Python `bubblegum bridge` and speaking its JSON-RPC protocol. The
6
+ * Python engine stays the single source of truth for grounding; this package is
7
+ * a thin, typed proxy. See ../../docs/distribution-npm-and-pypi.md.
8
+ */
9
+ export { Bubblegum } from "./session.js";
10
+ export { BridgeClient, spawnBridgeTransport } from "./client.js";
11
+ export { BridgeError } from "./errors.js";
12
+ export { PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS, ErrorCodes, } from "./protocol.js";
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAGzC,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAGjE,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,OAAO,EACL,gBAAgB,EAChB,2BAA2B,EAC3B,UAAU,GACX,MAAM,eAAe,CAAC"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Wire protocol for the Bubblegum bridge — the TypeScript mirror of
3
+ * `bubblegum/bridge/protocol.py`. Keep these in lockstep with the Python side.
4
+ *
5
+ * The client negotiates against the engine via `handshake`: it feature-detects
6
+ * on `capabilities` and refuses to run against a `protocol_version` it does not
7
+ * support, so a newer engine can keep serving an older client (additive-first).
8
+ */
9
+ /** Protocol versions this client speaks. v1 = the 0.1.0 bridge slice. */
10
+ export declare const SUPPORTED_PROTOCOL_VERSIONS: readonly [1];
11
+ /** The latest protocol version this client was written against. */
12
+ export declare const PROTOCOL_VERSION = 1;
13
+ /** JSON-RPC 2.0 + bridge error codes (mirror of the Python side). */
14
+ export declare const ErrorCodes: {
15
+ readonly ParseError: -32700;
16
+ readonly InvalidRequest: -32600;
17
+ readonly MethodNotFound: -32601;
18
+ readonly InvalidParams: -32602;
19
+ readonly InternalError: -32603;
20
+ readonly SessionNotFound: -32001;
21
+ readonly EngineError: -32002;
22
+ readonly Unsupported: -32003;
23
+ };
24
+ export type JsonRpcId = number | string | null;
25
+ export interface JsonRpcRequest {
26
+ jsonrpc: "2.0";
27
+ id?: JsonRpcId;
28
+ method: string;
29
+ params?: Record<string, unknown>;
30
+ }
31
+ export interface JsonRpcSuccess {
32
+ jsonrpc: "2.0";
33
+ id: JsonRpcId;
34
+ result: unknown;
35
+ }
36
+ export interface JsonRpcErrorResponse {
37
+ jsonrpc: "2.0";
38
+ id: JsonRpcId;
39
+ error: {
40
+ code: number;
41
+ message: string;
42
+ data?: unknown;
43
+ };
44
+ }
45
+ export type JsonRpcResponse = JsonRpcSuccess | JsonRpcErrorResponse;
46
+ export declare function isErrorResponse(msg: JsonRpcResponse): msg is JsonRpcErrorResponse;
47
+ /** Result of the `handshake` method. */
48
+ export interface Handshake {
49
+ engine_version: string;
50
+ protocol_version: number;
51
+ capabilities: string[];
52
+ }
53
+ //# sourceMappingURL=protocol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,yEAAyE;AACzE,eAAO,MAAM,2BAA2B,cAAe,CAAC;AAExD,mEAAmE;AACnE,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAElC,qEAAqE;AACrE,eAAO,MAAM,UAAU;;;;;;;;;CASb,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;AAE/C,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,KAAK,CAAC;IACf,EAAE,CAAC,EAAE,SAAS,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,KAAK,CAAC;IACf,EAAE,EAAE,SAAS,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,KAAK,CAAC;IACf,EAAE,EAAE,SAAS,CAAC;IACd,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CAC1D;AAED,MAAM,MAAM,eAAe,GAAG,cAAc,GAAG,oBAAoB,CAAC;AAEpE,wBAAgB,eAAe,CAAC,GAAG,EAAE,eAAe,GAAG,GAAG,IAAI,oBAAoB,CAEjF;AAED,wCAAwC;AACxC,MAAM,WAAW,SAAS;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Wire protocol for the Bubblegum bridge — the TypeScript mirror of
3
+ * `bubblegum/bridge/protocol.py`. Keep these in lockstep with the Python side.
4
+ *
5
+ * The client negotiates against the engine via `handshake`: it feature-detects
6
+ * on `capabilities` and refuses to run against a `protocol_version` it does not
7
+ * support, so a newer engine can keep serving an older client (additive-first).
8
+ */
9
+ /** Protocol versions this client speaks. v1 = the 0.1.0 bridge slice. */
10
+ export const SUPPORTED_PROTOCOL_VERSIONS = [1];
11
+ /** The latest protocol version this client was written against. */
12
+ export const PROTOCOL_VERSION = 1;
13
+ /** JSON-RPC 2.0 + bridge error codes (mirror of the Python side). */
14
+ export const ErrorCodes = {
15
+ ParseError: -32700,
16
+ InvalidRequest: -32600,
17
+ MethodNotFound: -32601,
18
+ InvalidParams: -32602,
19
+ InternalError: -32603,
20
+ SessionNotFound: -32001,
21
+ EngineError: -32002,
22
+ Unsupported: -32003,
23
+ };
24
+ export function isErrorResponse(msg) {
25
+ return msg.error !== undefined;
26
+ }
27
+ //# sourceMappingURL=protocol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,yEAAyE;AACzE,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,CAAC,CAAU,CAAC;AAExD,mEAAmE;AACnE,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC;AAElC,qEAAqE;AACrE,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,UAAU,EAAE,CAAC,KAAK;IAClB,cAAc,EAAE,CAAC,KAAK;IACtB,cAAc,EAAE,CAAC,KAAK;IACtB,aAAa,EAAE,CAAC,KAAK;IACrB,aAAa,EAAE,CAAC,KAAK;IACrB,eAAe,EAAE,CAAC,KAAK;IACvB,WAAW,EAAE,CAAC,KAAK;IACnB,WAAW,EAAE,CAAC,KAAK;CACX,CAAC;AAyBX,MAAM,UAAU,eAAe,CAAC,GAAoB;IAClD,OAAQ,GAA4B,CAAC,KAAK,KAAK,SAAS,CAAC;AAC3D,CAAC"}
@@ -0,0 +1,85 @@
1
+ import { BridgeClient, BridgeClientOptions } from "./client.js";
2
+ import { SessionSummary, StepOptions, StepResult } from "./types.js";
3
+ export type Channel = "web" | "mobile";
4
+ export interface LaunchOptions extends BridgeClientOptions {
5
+ /** "web" (default) or "mobile". */
6
+ channel?: Channel;
7
+ /** Web: start URL to open. */
8
+ url?: string;
9
+ /** Web: run headless (default true). */
10
+ headless?: boolean;
11
+ /** Resolve-only — never execute. */
12
+ dryRun?: boolean;
13
+ /** Mobile: Appium server URL (required for the mobile channel). */
14
+ appiumUrl?: string;
15
+ /** Mobile: Appium capabilities. */
16
+ capabilities?: Record<string, unknown>;
17
+ /**
18
+ * Web client-owned mode: attach the engine to an existing Chromium over CDP
19
+ * (e.g. `http://localhost:9222`) instead of launching one, so the engine
20
+ * drives the same browser your test already controls. Requires the engine to
21
+ * advertise the `channel.web.cdp` capability.
22
+ */
23
+ cdpEndpoint?: string;
24
+ /** Which existing page to attach to when using `cdpEndpoint` (default 0). */
25
+ pageIndex?: number;
26
+ }
27
+ /** Options for {@link Bubblegum.attach} — `cdpEndpoint` is required. */
28
+ export interface AttachOptions extends Omit<LaunchOptions, "channel"> {
29
+ cdpEndpoint: string;
30
+ }
31
+ export interface RecoverArgs {
32
+ failedSelector: string;
33
+ intent: string;
34
+ options?: StepOptions;
35
+ }
36
+ /**
37
+ * The ergonomic, engine-owned session. `launch()` spawns the bridge, negotiates
38
+ * the protocol, and opens a session whose Playwright/Appium handle lives inside
39
+ * the Python engine; every call here is a thin proxy to the same four primitives
40
+ * the Python SDK exposes, returning the identical `StepResult` shape.
41
+ *
42
+ * ```ts
43
+ * const bg = await Bubblegum.launch({ url: "https://example.com/login" });
44
+ * try {
45
+ * await bg.act('Enter "tom" into Username');
46
+ * await bg.act("Click Login");
47
+ * await bg.verify("Dashboard is visible");
48
+ * } finally {
49
+ * await bg.close();
50
+ * }
51
+ * ```
52
+ */
53
+ export declare class Bubblegum {
54
+ private readonly client;
55
+ private readonly sessionId;
56
+ private constructor();
57
+ /** Spawn the bridge, handshake, and open a session (engine-owned by default). */
58
+ static launch(opts?: LaunchOptions): Promise<Bubblegum>;
59
+ /**
60
+ * Attach the engine to a browser your test already controls, over CDP
61
+ * (client-owned mode). Launch your Chromium with a remote-debugging port and
62
+ * pass its endpoint:
63
+ *
64
+ * ```ts
65
+ * const browser = await chromium.launch({ args: ["--remote-debugging-port=9222"] });
66
+ * const bg = await Bubblegum.attach({ cdpEndpoint: "http://localhost:9222" });
67
+ * await bg.act("Click Login"); // drives the page your test opened
68
+ * ```
69
+ */
70
+ static attach(opts: AttachOptions): Promise<Bubblegum>;
71
+ /** The bridge client (for advanced use / capability checks). */
72
+ get bridge(): BridgeClient;
73
+ act(instruction: string, options?: StepOptions): Promise<StepResult>;
74
+ verify(instruction: string, options?: StepOptions): Promise<StepResult>;
75
+ extract(instruction: string, options?: StepOptions): Promise<StepResult>;
76
+ recover(args: RecoverArgs): Promise<StepResult>;
77
+ isVisible(target: string): Promise<boolean>;
78
+ isChecked(target: string): Promise<boolean>;
79
+ selectedValue(target: string): Promise<string>;
80
+ explain(instruction: string): Promise<string>;
81
+ summary(): Promise<SessionSummary>;
82
+ /** Close the engine session and tear down the bridge process. */
83
+ close(): Promise<void>;
84
+ }
85
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAGhE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAErE,MAAM,MAAM,OAAO,GAAG,KAAK,GAAG,QAAQ,CAAC;AAEvC,MAAM,WAAW,aAAc,SAAQ,mBAAmB;IACxD,mCAAmC;IACnC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,8BAA8B;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,wCAAwC;IACxC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,oCAAoC;IACpC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,mEAAmE;IACnE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mCAAmC;IACnC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6EAA6E;IAC7E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wEAAwE;AACxE,MAAM,WAAW,aAAc,SAAQ,IAAI,CAAC,aAAa,EAAE,SAAS,CAAC;IACnE,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,WAAW,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,SAAS;IAElB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAF5B,OAAO;IAKP,iFAAiF;WACpE,MAAM,CAAC,IAAI,GAAE,aAAkB,GAAG,OAAO,CAAC,SAAS,CAAC;IA2BjE;;;;;;;;;;OAUG;IACH,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,SAAS,CAAC;IAItD,gEAAgE;IAChE,IAAI,MAAM,IAAI,YAAY,CAEzB;IAED,GAAG,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;IAQpE,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;IAQvE,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;IAQxE,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;IASzC,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAQ3C,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAQ3C,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAQ9C,OAAO,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAQnD,OAAO,IAAI,OAAO,CAAC,cAAc,CAAC;IAIlC,iEAAiE;IAC3D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAO7B"}
@@ -0,0 +1,141 @@
1
+ import { BridgeClient } from "./client.js";
2
+ import { BridgeError } from "./errors.js";
3
+ import { ErrorCodes } from "./protocol.js";
4
+ /**
5
+ * The ergonomic, engine-owned session. `launch()` spawns the bridge, negotiates
6
+ * the protocol, and opens a session whose Playwright/Appium handle lives inside
7
+ * the Python engine; every call here is a thin proxy to the same four primitives
8
+ * the Python SDK exposes, returning the identical `StepResult` shape.
9
+ *
10
+ * ```ts
11
+ * const bg = await Bubblegum.launch({ url: "https://example.com/login" });
12
+ * try {
13
+ * await bg.act('Enter "tom" into Username');
14
+ * await bg.act("Click Login");
15
+ * await bg.verify("Dashboard is visible");
16
+ * } finally {
17
+ * await bg.close();
18
+ * }
19
+ * ```
20
+ */
21
+ export class Bubblegum {
22
+ client;
23
+ sessionId;
24
+ constructor(client, sessionId) {
25
+ this.client = client;
26
+ this.sessionId = sessionId;
27
+ }
28
+ /** Spawn the bridge, handshake, and open a session (engine-owned by default). */
29
+ static async launch(opts = {}) {
30
+ const client = new BridgeClient(opts);
31
+ try {
32
+ await client.handshake();
33
+ if (opts.cdpEndpoint && !client.hasCapability("channel.web.cdp")) {
34
+ throw new BridgeError(ErrorCodes.Unsupported, "this engine does not support CDP attach (channel.web.cdp); upgrade bubblegum-ai");
35
+ }
36
+ const { session_id } = await client.request("session.open", {
37
+ channel: opts.channel ?? "web",
38
+ url: opts.url,
39
+ headless: opts.headless ?? true,
40
+ dry_run: opts.dryRun ?? false,
41
+ appium_url: opts.appiumUrl,
42
+ capabilities: opts.capabilities,
43
+ cdp_endpoint: opts.cdpEndpoint,
44
+ page_index: opts.pageIndex,
45
+ });
46
+ return new Bubblegum(client, session_id);
47
+ }
48
+ catch (err) {
49
+ await client.close();
50
+ throw err;
51
+ }
52
+ }
53
+ /**
54
+ * Attach the engine to a browser your test already controls, over CDP
55
+ * (client-owned mode). Launch your Chromium with a remote-debugging port and
56
+ * pass its endpoint:
57
+ *
58
+ * ```ts
59
+ * const browser = await chromium.launch({ args: ["--remote-debugging-port=9222"] });
60
+ * const bg = await Bubblegum.attach({ cdpEndpoint: "http://localhost:9222" });
61
+ * await bg.act("Click Login"); // drives the page your test opened
62
+ * ```
63
+ */
64
+ static attach(opts) {
65
+ return Bubblegum.launch({ ...opts, channel: "web" });
66
+ }
67
+ /** The bridge client (for advanced use / capability checks). */
68
+ get bridge() {
69
+ return this.client;
70
+ }
71
+ act(instruction, options) {
72
+ return this.client.request("act", {
73
+ session_id: this.sessionId,
74
+ instruction,
75
+ options,
76
+ });
77
+ }
78
+ verify(instruction, options) {
79
+ return this.client.request("verify", {
80
+ session_id: this.sessionId,
81
+ instruction,
82
+ options,
83
+ });
84
+ }
85
+ extract(instruction, options) {
86
+ return this.client.request("extract", {
87
+ session_id: this.sessionId,
88
+ instruction,
89
+ options,
90
+ });
91
+ }
92
+ recover(args) {
93
+ return this.client.request("recover", {
94
+ session_id: this.sessionId,
95
+ failed_selector: args.failedSelector,
96
+ intent: args.intent,
97
+ options: args.options,
98
+ });
99
+ }
100
+ async isVisible(target) {
101
+ const r = await this.client.request("is_visible", {
102
+ session_id: this.sessionId,
103
+ target,
104
+ });
105
+ return r.value;
106
+ }
107
+ async isChecked(target) {
108
+ const r = await this.client.request("is_checked", {
109
+ session_id: this.sessionId,
110
+ target,
111
+ });
112
+ return r.value;
113
+ }
114
+ async selectedValue(target) {
115
+ const r = await this.client.request("selected_value", {
116
+ session_id: this.sessionId,
117
+ target,
118
+ });
119
+ return r.value;
120
+ }
121
+ async explain(instruction) {
122
+ const r = await this.client.request("explain", {
123
+ session_id: this.sessionId,
124
+ instruction,
125
+ });
126
+ return r.explanation;
127
+ }
128
+ summary() {
129
+ return this.client.request("summary", { session_id: this.sessionId });
130
+ }
131
+ /** Close the engine session and tear down the bridge process. */
132
+ async close() {
133
+ try {
134
+ await this.client.request("session.close", { session_id: this.sessionId });
135
+ }
136
+ finally {
137
+ await this.client.close();
138
+ }
139
+ }
140
+ }
141
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * TypeScript mirror of the engine's result schemas (`bubblegum/core/schemas.py`).
3
+ *
4
+ * Kept intentionally close to the Pydantic models. `[key: string]: unknown`
5
+ * index signatures let newer engine fields flow through without breaking the
6
+ * client (additive-first), while the named fields stay strongly typed.
7
+ */
8
+ export type StepStatus = "passed" | "failed" | "recovered" | "dry_run" | "skipped";
9
+ export interface ResolvedTarget {
10
+ ref: string;
11
+ confidence: number;
12
+ resolver_name: string;
13
+ metadata?: Record<string, unknown>;
14
+ [key: string]: unknown;
15
+ }
16
+ export interface ErrorInfo {
17
+ error_type: string;
18
+ message: string;
19
+ resolver_name?: string | null;
20
+ [key: string]: unknown;
21
+ }
22
+ export interface StepResult {
23
+ status: StepStatus;
24
+ action: string;
25
+ target: ResolvedTarget | null;
26
+ confidence: number;
27
+ duration_ms: number;
28
+ error?: ErrorInfo | null;
29
+ traces?: unknown[];
30
+ [key: string]: unknown;
31
+ }
32
+ export interface SessionSummary {
33
+ total: number;
34
+ passed: number;
35
+ failed: number;
36
+ [key: string]: unknown;
37
+ }
38
+ /** Per-call options forwarded verbatim to the engine SDK (timeout_ms, selector, …). */
39
+ export type StepOptions = Record<string, unknown>;
40
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,WAAW,GAAG,SAAS,GAAG,SAAS,CAAC;AAEnF,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,SAAS;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,UAAU,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IACzB,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC;IACnB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,uFAAuF;AACvF,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * TypeScript mirror of the engine's result schemas (`bubblegum/core/schemas.py`).
3
+ *
4
+ * Kept intentionally close to the Pydantic models. `[key: string]: unknown`
5
+ * index signatures let newer engine fields flow through without breaking the
6
+ * client (additive-first), while the named fields stay strongly typed.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@bubblegum-ai/node",
3
+ "version": "0.0.6-alpha.0",
4
+ "description": "Node/TypeScript client for the Bubblegum engine — drive AI-powered, natural-language Playwright/Appium test steps from JS/TS via the Bubblegum JSON-RPC bridge.",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/index.d.ts",
9
+ "import": "./dist/index.js"
10
+ }
11
+ },
12
+ "types": "./dist/index.d.ts",
13
+ "files": [
14
+ "dist",
15
+ "README.md"
16
+ ],
17
+ "engines": {
18
+ "node": ">=18"
19
+ },
20
+ "scripts": {
21
+ "build": "tsc -p tsconfig.json",
22
+ "typecheck": "tsc -p tsconfig.json --noEmit",
23
+ "test": "npm run build && node --test test/*.test.mjs",
24
+ "prepublishOnly": "npm run build"
25
+ },
26
+ "keywords": [
27
+ "bubblegum",
28
+ "playwright",
29
+ "appium",
30
+ "testing",
31
+ "test-automation",
32
+ "self-healing",
33
+ "natural-language",
34
+ "ai"
35
+ ],
36
+ "license": "MIT",
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "git+https://github.com/bishnu133/bubblegum.git",
40
+ "directory": "clients/node"
41
+ },
42
+ "homepage": "https://github.com/bishnu133/bubblegum/tree/main/clients/node",
43
+ "bugs": {
44
+ "url": "https://github.com/bishnu133/bubblegum/issues"
45
+ },
46
+ "peerDependencies": {},
47
+ "devDependencies": {
48
+ "@types/node": "^20.11.0",
49
+ "typescript": "^5.4.0"
50
+ }
51
+ }