browsentic 0.4.15 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -28284,6 +28284,54 @@ var focusInput = defineAction({
28284
28284
  }
28285
28285
  });
28286
28286
 
28287
+ // ../lib/actions/page/switch-frame.ts
28288
+ var switchFrame = defineAction({
28289
+ name: "page.switchFrame",
28290
+ description: 'Step into an <iframe> so that every later page action \u2014 reading, clicking, typing, waiting, site tools, injected code \u2014 runs inside it as if it were the page, or step back out. page.getPageInfo lists the frames the current document embeds under "frames"; pass one as "frame" to enter it, and call again from inside to go deeper. Call it with no arguments to return to the top document, or with to: "parent" to step out one level. The focus is per tab, clears when the tab navigates, and page.getPageInfo reports it under "frame" whenever it is not the top. A frame that forbids scripts (a sandbox without allow-scripts) or a browser-internal frame cannot be entered; page.screenshot and page.trustedClick by coordinates still reach those. page.screenshot and page.navigate always act on the whole tab.',
28291
+ input: external_exports.object({
28292
+ frame: targetSchema.optional().describe(
28293
+ 'The <iframe> or <frame> element to enter, matched inside the frame currently in focus \u2014 use a selector from the "frames" list of page.getPageInfo. Omit it to leave instead.'
28294
+ ),
28295
+ to: external_exports.enum(["top", "parent"]).default("top").describe('Where to go when no "frame" is given: "top" returns to the main document, "parent" steps out one level.')
28296
+ }),
28297
+ execute({ frame }) {
28298
+ if (!frame) {
28299
+ throw new ActionError("Leaving a frame is resolved by the Browsentic extension, not in the page", "UNSUPPORTED");
28300
+ }
28301
+ const el = resolveTarget(frame);
28302
+ if (!isFrameElement(el)) {
28303
+ throw new ActionError(
28304
+ `${cssPath(el)} is a <${el.tagName.toLowerCase()}>, not an <iframe> \u2014 pick one of the frames page.getPageInfo lists`,
28305
+ "INVALID_TARGET"
28306
+ );
28307
+ }
28308
+ return {
28309
+ frameId: frameIdOf(el),
28310
+ src: el.src || void 0,
28311
+ name: el.name || void 0,
28312
+ sandbox: sandboxOf(el),
28313
+ ...describeElement(el)
28314
+ };
28315
+ }
28316
+ });
28317
+ function isFrameElement(el) {
28318
+ return el instanceof HTMLIFrameElement || el instanceof HTMLFrameElement;
28319
+ }
28320
+ function sandboxOf(el) {
28321
+ return el instanceof HTMLIFrameElement && el.hasAttribute("sandbox") ? el.sandbox.value || "all restrictions" : void 0;
28322
+ }
28323
+ function frameIdOf(target) {
28324
+ const scope = globalThis;
28325
+ const id = (scope.browser?.runtime?.getFrameId ?? scope.chrome?.runtime?.getFrameId)?.(target);
28326
+ if (typeof id !== "number" || id < 0) {
28327
+ throw new ActionError(
28328
+ "This browser cannot identify frames from the page \u2014 entering a frame needs Chrome 116 or Firefox 96 or newer",
28329
+ "UNSUPPORTED"
28330
+ );
28331
+ }
28332
+ return id;
28333
+ }
28334
+
28287
28335
  // ../lib/actions/page/get-page-info.ts
28288
28336
  var IMPLICIT_ROLES = {
28289
28337
  header: "banner",
@@ -28308,7 +28356,7 @@ var LANDMARK_ROLES = /* @__PURE__ */ new Set([
28308
28356
  ]);
28309
28357
  var getPageInfo = defineAction({
28310
28358
  name: "page.getPageInfo",
28311
- description: "Snapshot the current page: document metadata, viewport and scroll state, a semantic layout tree with a text diagram, the heading outline, and an inventory of interactive elements \u2014 each carrying its ARIA role, its live state (disabled, checked, expanded, filled, aria-current) and the landmark region it sits in. When the site registers WebMCP tools, the result also carries a siteTools list \u2014 prefer page.callSiteTool over clicking wherever a listed tool covers the step.",
28359
+ description: 'Snapshot the current page: document metadata, viewport and scroll state, a semantic layout tree with a text diagram, the heading outline, and an inventory of interactive elements \u2014 each carrying its ARIA role, its live state (disabled, checked, expanded, filled, aria-current) and the landmark region it sits in. When the site registers WebMCP tools, the result also carries a siteTools list \u2014 prefer page.callSiteTool over clicking wherever a listed tool covers the step. Visible iframes come back under "frames"; nothing inside one is in this snapshot until page.switchFrame enters it, and "frame" then says which one is in focus.',
28312
28360
  input: external_exports.object({
28313
28361
  maxPerKind: external_exports.number().int().positive().default(30).describe("Cap on links, buttons, fields, and forms listed per kind")
28314
28362
  }),
@@ -28337,10 +28385,23 @@ var getPageInfo = defineAction({
28337
28385
  level: Number(heading.tagName[1]),
28338
28386
  text: accessibleText(heading).slice(0, 120)
28339
28387
  })),
28340
- interactive: inventory(found, owners, maxPerKind)
28388
+ interactive: inventory(found, owners, maxPerKind),
28389
+ frames: embeddedFrames()
28341
28390
  };
28342
28391
  }
28343
28392
  });
28393
+ var MAX_FRAMES = 20;
28394
+ function embeddedFrames() {
28395
+ const frames = [...document.querySelectorAll("iframe,frame")].filter(isFrameElement).filter(isExposed).slice(0, MAX_FRAMES).map((frame) => ({
28396
+ selector: cssPath(frame),
28397
+ src: frame.src || void 0,
28398
+ name: frame.name || void 0,
28399
+ title: frame.title || void 0,
28400
+ sandbox: sandboxOf(frame),
28401
+ bounds: documentBounds(frame)
28402
+ }));
28403
+ return frames.length ? frames : void 0;
28404
+ }
28344
28405
  function regionRole(el) {
28345
28406
  const explicit = el.getAttribute("role");
28346
28407
  if (explicit) return LANDMARK_ROLES.has(explicit) ? explicit : void 0;
@@ -29979,6 +30040,7 @@ var actions = new Map(
29979
30040
  navigate,
29980
30041
  openTab,
29981
30042
  switchTab,
30043
+ switchFrame,
29982
30044
  closeTab,
29983
30045
  screenshot,
29984
30046
  listFiles,
@@ -34184,7 +34246,7 @@ function purgeNpxCache(entries) {
34184
34246
  // package.json
34185
34247
  var package_default = {
34186
34248
  name: "browsentic",
34187
- version: "0.4.15",
34249
+ version: "0.5.0",
34188
34250
  description: "Hand your real, logged-in browser to the AI agent you already run. Installs the browser extension, runs the local daemon, and speaks MCP.",
34189
34251
  type: "module",
34190
34252
  license: "MIT",
@@ -19876,6 +19876,54 @@ var focusInput = defineAction({
19876
19876
  }
19877
19877
  });
19878
19878
 
19879
+ // ../lib/actions/page/switch-frame.ts
19880
+ var switchFrame = defineAction({
19881
+ name: "page.switchFrame",
19882
+ description: 'Step into an <iframe> so that every later page action \u2014 reading, clicking, typing, waiting, site tools, injected code \u2014 runs inside it as if it were the page, or step back out. page.getPageInfo lists the frames the current document embeds under "frames"; pass one as "frame" to enter it, and call again from inside to go deeper. Call it with no arguments to return to the top document, or with to: "parent" to step out one level. The focus is per tab, clears when the tab navigates, and page.getPageInfo reports it under "frame" whenever it is not the top. A frame that forbids scripts (a sandbox without allow-scripts) or a browser-internal frame cannot be entered; page.screenshot and page.trustedClick by coordinates still reach those. page.screenshot and page.navigate always act on the whole tab.',
19883
+ input: external_exports.object({
19884
+ frame: targetSchema.optional().describe(
19885
+ 'The <iframe> or <frame> element to enter, matched inside the frame currently in focus \u2014 use a selector from the "frames" list of page.getPageInfo. Omit it to leave instead.'
19886
+ ),
19887
+ to: external_exports.enum(["top", "parent"]).default("top").describe('Where to go when no "frame" is given: "top" returns to the main document, "parent" steps out one level.')
19888
+ }),
19889
+ execute({ frame }) {
19890
+ if (!frame) {
19891
+ throw new ActionError("Leaving a frame is resolved by the Browsentic extension, not in the page", "UNSUPPORTED");
19892
+ }
19893
+ const el = resolveTarget(frame);
19894
+ if (!isFrameElement(el)) {
19895
+ throw new ActionError(
19896
+ `${cssPath(el)} is a <${el.tagName.toLowerCase()}>, not an <iframe> \u2014 pick one of the frames page.getPageInfo lists`,
19897
+ "INVALID_TARGET"
19898
+ );
19899
+ }
19900
+ return {
19901
+ frameId: frameIdOf(el),
19902
+ src: el.src || void 0,
19903
+ name: el.name || void 0,
19904
+ sandbox: sandboxOf(el),
19905
+ ...describeElement(el)
19906
+ };
19907
+ }
19908
+ });
19909
+ function isFrameElement(el) {
19910
+ return el instanceof HTMLIFrameElement || el instanceof HTMLFrameElement;
19911
+ }
19912
+ function sandboxOf(el) {
19913
+ return el instanceof HTMLIFrameElement && el.hasAttribute("sandbox") ? el.sandbox.value || "all restrictions" : void 0;
19914
+ }
19915
+ function frameIdOf(target) {
19916
+ const scope = globalThis;
19917
+ const id = (scope.browser?.runtime?.getFrameId ?? scope.chrome?.runtime?.getFrameId)?.(target);
19918
+ if (typeof id !== "number" || id < 0) {
19919
+ throw new ActionError(
19920
+ "This browser cannot identify frames from the page \u2014 entering a frame needs Chrome 116 or Firefox 96 or newer",
19921
+ "UNSUPPORTED"
19922
+ );
19923
+ }
19924
+ return id;
19925
+ }
19926
+
19879
19927
  // ../lib/actions/page/get-page-info.ts
19880
19928
  var IMPLICIT_ROLES = {
19881
19929
  header: "banner",
@@ -19900,7 +19948,7 @@ var LANDMARK_ROLES = /* @__PURE__ */ new Set([
19900
19948
  ]);
19901
19949
  var getPageInfo = defineAction({
19902
19950
  name: "page.getPageInfo",
19903
- description: "Snapshot the current page: document metadata, viewport and scroll state, a semantic layout tree with a text diagram, the heading outline, and an inventory of interactive elements \u2014 each carrying its ARIA role, its live state (disabled, checked, expanded, filled, aria-current) and the landmark region it sits in. When the site registers WebMCP tools, the result also carries a siteTools list \u2014 prefer page.callSiteTool over clicking wherever a listed tool covers the step.",
19951
+ description: 'Snapshot the current page: document metadata, viewport and scroll state, a semantic layout tree with a text diagram, the heading outline, and an inventory of interactive elements \u2014 each carrying its ARIA role, its live state (disabled, checked, expanded, filled, aria-current) and the landmark region it sits in. When the site registers WebMCP tools, the result also carries a siteTools list \u2014 prefer page.callSiteTool over clicking wherever a listed tool covers the step. Visible iframes come back under "frames"; nothing inside one is in this snapshot until page.switchFrame enters it, and "frame" then says which one is in focus.',
19904
19952
  input: external_exports.object({
19905
19953
  maxPerKind: external_exports.number().int().positive().default(30).describe("Cap on links, buttons, fields, and forms listed per kind")
19906
19954
  }),
@@ -19929,10 +19977,23 @@ var getPageInfo = defineAction({
19929
19977
  level: Number(heading.tagName[1]),
19930
19978
  text: accessibleText(heading).slice(0, 120)
19931
19979
  })),
19932
- interactive: inventory(found, owners, maxPerKind)
19980
+ interactive: inventory(found, owners, maxPerKind),
19981
+ frames: embeddedFrames()
19933
19982
  };
19934
19983
  }
19935
19984
  });
19985
+ var MAX_FRAMES = 20;
19986
+ function embeddedFrames() {
19987
+ const frames = [...document.querySelectorAll("iframe,frame")].filter(isFrameElement).filter(isExposed).slice(0, MAX_FRAMES).map((frame) => ({
19988
+ selector: cssPath(frame),
19989
+ src: frame.src || void 0,
19990
+ name: frame.name || void 0,
19991
+ title: frame.title || void 0,
19992
+ sandbox: sandboxOf(frame),
19993
+ bounds: documentBounds(frame)
19994
+ }));
19995
+ return frames.length ? frames : void 0;
19996
+ }
19936
19997
  function regionRole(el) {
19937
19998
  const explicit = el.getAttribute("role");
19938
19999
  if (explicit) return LANDMARK_ROLES.has(explicit) ? explicit : void 0;
@@ -21577,6 +21638,7 @@ var actions = new Map(
21577
21638
  navigate,
21578
21639
  openTab,
21579
21640
  switchTab,
21641
+ switchFrame,
21580
21642
  closeTab,
21581
21643
  screenshot,
21582
21644
  listFiles,
@@ -24554,6 +24616,7 @@ var READ_ONLY_ACTIONS = /* @__PURE__ */ new Set([
24554
24616
  "page.findSearch",
24555
24617
  "page.findCaptcha",
24556
24618
  "page.listSiteTools",
24619
+ "page.switchFrame",
24557
24620
  "page.monitorStatus",
24558
24621
  "page.timerStatus",
24559
24622
  "page.readTheme",
@@ -27181,7 +27244,7 @@ X-Browsentic-Reason: ${reason}\r
27181
27244
  // package.json
27182
27245
  var package_default = {
27183
27246
  name: "browsentic",
27184
- version: "0.4.15",
27247
+ version: "0.5.0",
27185
27248
  description: "Hand your real, logged-in browser to the AI agent you already run. Installs the browser extension, runs the local daemon, and speaks MCP.",
27186
27249
  type: "module",
27187
27250
  license: "MIT",