browsentic 0.4.15 → 0.6.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
@@ -2131,7 +2131,7 @@ var require_extension = __commonJS({
2131
2131
  let inQuotes = false;
2132
2132
  let extensionName;
2133
2133
  let paramName;
2134
- let start = -1;
2134
+ let start2 = -1;
2135
2135
  let code = -1;
2136
2136
  let end = -1;
2137
2137
  let i = 0;
@@ -2139,45 +2139,45 @@ var require_extension = __commonJS({
2139
2139
  code = header.charCodeAt(i);
2140
2140
  if (extensionName === void 0) {
2141
2141
  if (end === -1 && tokenChars[code] === 1) {
2142
- if (start === -1) start = i;
2142
+ if (start2 === -1) start2 = i;
2143
2143
  } else if (i !== 0 && (code === 32 || code === 9)) {
2144
- if (end === -1 && start !== -1) end = i;
2144
+ if (end === -1 && start2 !== -1) end = i;
2145
2145
  } else if (code === 59 || code === 44) {
2146
- if (start === -1) {
2146
+ if (start2 === -1) {
2147
2147
  throw new SyntaxError(`Unexpected character at index ${i}`);
2148
2148
  }
2149
2149
  if (end === -1) end = i;
2150
- const name = header.slice(start, end);
2150
+ const name = header.slice(start2, end);
2151
2151
  if (code === 44) {
2152
2152
  push(offers, name, params);
2153
2153
  params = /* @__PURE__ */ Object.create(null);
2154
2154
  } else {
2155
2155
  extensionName = name;
2156
2156
  }
2157
- start = end = -1;
2157
+ start2 = end = -1;
2158
2158
  } else {
2159
2159
  throw new SyntaxError(`Unexpected character at index ${i}`);
2160
2160
  }
2161
2161
  } else if (paramName === void 0) {
2162
2162
  if (end === -1 && tokenChars[code] === 1) {
2163
- if (start === -1) start = i;
2163
+ if (start2 === -1) start2 = i;
2164
2164
  } else if (code === 32 || code === 9) {
2165
- if (end === -1 && start !== -1) end = i;
2165
+ if (end === -1 && start2 !== -1) end = i;
2166
2166
  } else if (code === 59 || code === 44) {
2167
- if (start === -1) {
2167
+ if (start2 === -1) {
2168
2168
  throw new SyntaxError(`Unexpected character at index ${i}`);
2169
2169
  }
2170
2170
  if (end === -1) end = i;
2171
- push(params, header.slice(start, end), true);
2171
+ push(params, header.slice(start2, end), true);
2172
2172
  if (code === 44) {
2173
2173
  push(offers, extensionName, params);
2174
2174
  params = /* @__PURE__ */ Object.create(null);
2175
2175
  extensionName = void 0;
2176
2176
  }
2177
- start = end = -1;
2178
- } else if (code === 61 && start !== -1 && end === -1) {
2179
- paramName = header.slice(start, i);
2180
- start = end = -1;
2177
+ start2 = end = -1;
2178
+ } else if (code === 61 && start2 !== -1 && end === -1) {
2179
+ paramName = header.slice(start2, i);
2180
+ start2 = end = -1;
2181
2181
  } else {
2182
2182
  throw new SyntaxError(`Unexpected character at index ${i}`);
2183
2183
  }
@@ -2186,13 +2186,13 @@ var require_extension = __commonJS({
2186
2186
  if (tokenChars[code] !== 1) {
2187
2187
  throw new SyntaxError(`Unexpected character at index ${i}`);
2188
2188
  }
2189
- if (start === -1) start = i;
2189
+ if (start2 === -1) start2 = i;
2190
2190
  else if (!mustUnescape) mustUnescape = true;
2191
2191
  isEscaping = false;
2192
2192
  } else if (inQuotes) {
2193
2193
  if (tokenChars[code] === 1) {
2194
- if (start === -1) start = i;
2195
- } else if (code === 34 && start !== -1) {
2194
+ if (start2 === -1) start2 = i;
2195
+ } else if (code === 34 && start2 !== -1) {
2196
2196
  inQuotes = false;
2197
2197
  end = i;
2198
2198
  } else if (code === 92) {
@@ -2203,15 +2203,15 @@ var require_extension = __commonJS({
2203
2203
  } else if (code === 34 && header.charCodeAt(i - 1) === 61) {
2204
2204
  inQuotes = true;
2205
2205
  } else if (end === -1 && tokenChars[code] === 1) {
2206
- if (start === -1) start = i;
2207
- } else if (start !== -1 && (code === 32 || code === 9)) {
2206
+ if (start2 === -1) start2 = i;
2207
+ } else if (start2 !== -1 && (code === 32 || code === 9)) {
2208
2208
  if (end === -1) end = i;
2209
2209
  } else if (code === 59 || code === 44) {
2210
- if (start === -1) {
2210
+ if (start2 === -1) {
2211
2211
  throw new SyntaxError(`Unexpected character at index ${i}`);
2212
2212
  }
2213
2213
  if (end === -1) end = i;
2214
- let value = header.slice(start, end);
2214
+ let value = header.slice(start2, end);
2215
2215
  if (mustUnescape) {
2216
2216
  value = value.replace(/\\/g, "");
2217
2217
  mustUnescape = false;
@@ -2223,17 +2223,17 @@ var require_extension = __commonJS({
2223
2223
  extensionName = void 0;
2224
2224
  }
2225
2225
  paramName = void 0;
2226
- start = end = -1;
2226
+ start2 = end = -1;
2227
2227
  } else {
2228
2228
  throw new SyntaxError(`Unexpected character at index ${i}`);
2229
2229
  }
2230
2230
  }
2231
2231
  }
2232
- if (start === -1 || inQuotes || code === 32 || code === 9) {
2232
+ if (start2 === -1 || inQuotes || code === 32 || code === 9) {
2233
2233
  throw new SyntaxError("Unexpected end of input");
2234
2234
  }
2235
2235
  if (end === -1) end = i;
2236
- const token = header.slice(start, end);
2236
+ const token = header.slice(start2, end);
2237
2237
  if (extensionName === void 0) {
2238
2238
  push(offers, token, params);
2239
2239
  } else {
@@ -3268,34 +3268,34 @@ var require_subprotocol = __commonJS({
3268
3268
  var { tokenChars } = require_validation();
3269
3269
  function parse3(header) {
3270
3270
  const protocols = /* @__PURE__ */ new Set();
3271
- let start = -1;
3271
+ let start2 = -1;
3272
3272
  let end = -1;
3273
3273
  let i = 0;
3274
3274
  for (i; i < header.length; i++) {
3275
3275
  const code = header.charCodeAt(i);
3276
3276
  if (end === -1 && tokenChars[code] === 1) {
3277
- if (start === -1) start = i;
3277
+ if (start2 === -1) start2 = i;
3278
3278
  } else if (i !== 0 && (code === 32 || code === 9)) {
3279
- if (end === -1 && start !== -1) end = i;
3279
+ if (end === -1 && start2 !== -1) end = i;
3280
3280
  } else if (code === 44) {
3281
- if (start === -1) {
3281
+ if (start2 === -1) {
3282
3282
  throw new SyntaxError(`Unexpected character at index ${i}`);
3283
3283
  }
3284
3284
  if (end === -1) end = i;
3285
- const protocol2 = header.slice(start, end);
3285
+ const protocol2 = header.slice(start2, end);
3286
3286
  if (protocols.has(protocol2)) {
3287
3287
  throw new SyntaxError(`The "${protocol2}" subprotocol is duplicated`);
3288
3288
  }
3289
3289
  protocols.add(protocol2);
3290
- start = end = -1;
3290
+ start2 = end = -1;
3291
3291
  } else {
3292
3292
  throw new SyntaxError(`Unexpected character at index ${i}`);
3293
3293
  }
3294
3294
  }
3295
- if (start === -1 || end !== -1) {
3295
+ if (start2 === -1 || end !== -1) {
3296
3296
  throw new SyntaxError("Unexpected end of input");
3297
3297
  }
3298
- const protocol = header.slice(start, i);
3298
+ const protocol = header.slice(start2, i);
3299
3299
  if (protocols.has(protocol)) {
3300
3300
  throw new SyntaxError(`The "${protocol}" subprotocol is duplicated`);
3301
3301
  }
@@ -11285,9 +11285,9 @@ function nullish(input2) {
11285
11285
  return input2 === null || input2 === void 0;
11286
11286
  }
11287
11287
  function cleanRegex(source) {
11288
- const start = source.startsWith("^") ? 1 : 0;
11288
+ const start2 = source.startsWith("^") ? 1 : 0;
11289
11289
  const end = source.endsWith("$") ? source.length - 1 : source.length;
11290
- return source.slice(start, end);
11290
+ return source.slice(start2, end);
11291
11291
  }
11292
11292
  function floatSafeRemainder(val, step) {
11293
11293
  const ratio = val / step;
@@ -14773,9 +14773,9 @@ var $ZodTemplateLiteral = /* @__PURE__ */ $constructor("$ZodTemplateLiteral", (i
14773
14773
  const source = part._zod.pattern instanceof RegExp ? part._zod.pattern.source : part._zod.pattern;
14774
14774
  if (!source)
14775
14775
  throw new Error(`Invalid template literal part: ${part._zod.traits}`);
14776
- const start = source.startsWith("^") ? 1 : 0;
14776
+ const start2 = source.startsWith("^") ? 1 : 0;
14777
14777
  const end = source.endsWith("$") ? source.length - 1 : source.length;
14778
- regexParts.push(source.slice(start, end));
14778
+ regexParts.push(source.slice(start2, end));
14779
14779
  } else if (part === null || primitiveTypes.has(typeof part)) {
14780
14780
  regexParts.push(escapeRegex(`${part}`));
14781
14781
  } else {
@@ -27722,14 +27722,14 @@ async function travel(from, to, steps, buttons, onStep) {
27722
27722
  else firePointer(elementAt(at), "pointermove", "mousemove", at, buttons);
27723
27723
  }
27724
27724
  }
27725
- async function approachFrom(start, grip, over) {
27726
- await travel(start, grip, APPROACH_STEPS, 0);
27725
+ async function approachFrom(start2, grip, over) {
27726
+ await travel(start2, grip, APPROACH_STEPS, 0);
27727
27727
  hoverSequence(over ?? document.body, grip);
27728
27728
  }
27729
27729
  async function pointerDrag(source, plan) {
27730
- const { approach: start, grip, drop, steps, holdMs, settleMs } = plan;
27730
+ const { approach: start2, grip, drop, steps, holdMs, settleMs } = plan;
27731
27731
  const held = source ?? elementAt(grip);
27732
- await approachFrom(start, grip, held);
27732
+ await approachFrom(start2, grip, held);
27733
27733
  firePointer(held, "pointerdown", "mousedown", grip, 1);
27734
27734
  await settle(holdMs);
27735
27735
  await travel(grip, drop, steps, 1);
@@ -27739,9 +27739,9 @@ async function pointerDrag(source, plan) {
27739
27739
  return { landedOn: landing ? cssPath(landing) : void 0 };
27740
27740
  }
27741
27741
  async function nativeDrag(source, plan) {
27742
- const { approach: start, grip, drop, steps, holdMs, settleMs } = plan;
27742
+ const { approach: start2, grip, drop, steps, holdMs, settleMs } = plan;
27743
27743
  const dataTransfer = new DataTransfer();
27744
- await approachFrom(start, grip, source);
27744
+ await approachFrom(start2, grip, source);
27745
27745
  firePointer(source, "pointerdown", "mousedown", grip, 1);
27746
27746
  await settle(holdMs);
27747
27747
  const started = fireDrag(source, "dragstart", grip, dataTransfer);
@@ -27836,14 +27836,14 @@ function digestOf(text3) {
27836
27836
  }
27837
27837
  return (hash3 >>> 0).toString(16).padStart(8, "0");
27838
27838
  }
27839
- function groupEnd(text3, start, budget) {
27840
- if (text3.length - start <= budget) return text3.length;
27841
- const window2 = text3.slice(start, start + budget);
27839
+ function groupEnd(text3, start2, budget) {
27840
+ if (text3.length - start2 <= budget) return text3.length;
27841
+ const window2 = text3.slice(start2, start2 + budget);
27842
27842
  for (const { pattern, floor } of BOUNDARIES) {
27843
27843
  const cut = lastBoundary(window2, pattern, Math.floor(budget * floor));
27844
- if (cut > 0) return start + cut;
27844
+ if (cut > 0) return start2 + cut;
27845
27845
  }
27846
- return start + budget;
27846
+ return start2 + budget;
27847
27847
  }
27848
27848
  function writeCursor(offset, delivered) {
27849
27849
  return `${offset}.${digestOf(delivered)}`;
@@ -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;
@@ -28906,12 +28967,12 @@ var pressKey = defineAction({
28906
28967
  el.dispatchEvent(new KeyboardEvent("keypress", init));
28907
28968
  }
28908
28969
  if (printable && (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) {
28909
- const start = el.selectionStart ?? el.value.length;
28970
+ const start2 = el.selectionStart ?? el.value.length;
28910
28971
  const end = el.selectionEnd ?? el.value.length;
28911
- const next = el.value.slice(0, start) + key + el.value.slice(end);
28972
+ const next = el.value.slice(0, start2) + key + el.value.slice(end);
28912
28973
  const proto = el instanceof HTMLInputElement ? HTMLInputElement.prototype : HTMLTextAreaElement.prototype;
28913
28974
  Object.getOwnPropertyDescriptor(proto, "value")?.set?.call(el, next);
28914
- const caret = start + key.length;
28975
+ const caret = start2 + key.length;
28915
28976
  try {
28916
28977
  el.setSelectionRange(caret, caret);
28917
28978
  } catch {
@@ -29789,7 +29850,7 @@ var typeText = defineAction({
29789
29850
  }
29790
29851
  if (clear) clearContent(el);
29791
29852
  focusForTyping(el);
29792
- const start = performance.now();
29853
+ const start2 = performance.now();
29793
29854
  const characters = [...text3];
29794
29855
  for (const [index, char] of characters.entries()) {
29795
29856
  const wait = index ? Math.round(delays[index - 1] * (1 + (Math.random() * 2 - 1) * jitter)) : 0;
@@ -29811,7 +29872,7 @@ var typeText = defineAction({
29811
29872
  element: describeElement(el),
29812
29873
  typed: characters.length,
29813
29874
  charDelayMs: base,
29814
- elapsedMs: Math.round(performance.now() - start),
29875
+ elapsedMs: Math.round(performance.now() - start2),
29815
29876
  appended: !clear,
29816
29877
  submitted
29817
29878
  };
@@ -29845,10 +29906,10 @@ function focusForTyping(el) {
29845
29906
  }
29846
29907
  function insertCharacter(el, char) {
29847
29908
  if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
29848
- const start = el.selectionStart ?? el.value.length;
29909
+ const start2 = el.selectionStart ?? el.value.length;
29849
29910
  const end = el.selectionEnd ?? el.value.length;
29850
- setNativeValue(el, el.value.slice(0, start) + char + el.value.slice(end));
29851
- const caret = start + char.length;
29911
+ setNativeValue(el, el.value.slice(0, start2) + char + el.value.slice(end));
29912
+ const caret = start2 + char.length;
29852
29913
  try {
29853
29914
  el.setSelectionRange(caret, caret);
29854
29915
  } catch {
@@ -29894,7 +29955,7 @@ var waitForElement = defineAction({
29894
29955
  hidden: !el || !isVisible(el),
29895
29956
  detached: !el
29896
29957
  })[state];
29897
- const start = performance.now();
29958
+ const start2 = performance.now();
29898
29959
  let element = find();
29899
29960
  if (satisfied(element)) {
29900
29961
  return { state, elapsedMs: 0, element: element ? describeElement(element) : void 0 };
@@ -29927,7 +29988,7 @@ var waitForElement = defineAction({
29927
29988
  });
29928
29989
  return {
29929
29990
  state,
29930
- elapsedMs: Math.round(performance.now() - start),
29991
+ elapsedMs: Math.round(performance.now() - start2),
29931
29992
  element: element ? describeElement(element) : void 0
29932
29993
  };
29933
29994
  }
@@ -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,
@@ -30229,6 +30291,21 @@ function settingsFor(stored, kind) {
30229
30291
  };
30230
30292
  }
30231
30293
  var text = (value) => typeof value === "string" && value.trim() ? value.trim() : void 0;
30294
+ function writeAgentModel(kind, model) {
30295
+ const stored = readStored();
30296
+ const agents = { ...stored.agents ?? {} };
30297
+ const scoped = { ...agents[kind] ?? {} };
30298
+ const value = text(model);
30299
+ if (value) scoped.model = value;
30300
+ else delete scoped.model;
30301
+ if (Object.keys(scoped).length) agents[kind] = scoped;
30302
+ else delete agents[kind];
30303
+ const next = { ...stored };
30304
+ if (Object.keys(agents).length) next.agents = agents;
30305
+ else delete next.agents;
30306
+ if (kind === "claude") delete next.model;
30307
+ write(next);
30308
+ }
30232
30309
  function write(config2) {
30233
30310
  mkdirSync3(stateDir, { recursive: true, mode: 448 });
30234
30311
  writeFileSync2(configPath, `${JSON.stringify(config2, null, 2)}
@@ -31836,9 +31913,11 @@ import { homedir as homedir7 } from "os";
31836
31913
  import { dirname as dirname6, join as join15, resolve, sep as sep2 } from "path";
31837
31914
  import { fileURLToPath as fileURLToPath5 } from "url";
31838
31915
  var packageRoot = resolve(dirname6(fileURLToPath5(import.meta.url)), "..");
31916
+ var APP_MARKER = ".browsentic-app.json";
31839
31917
  var inNpxCache = (path) => path.split(sep2).includes("_npx");
31840
31918
  function installKind() {
31841
31919
  if (inNpxCache(packageRoot)) return "npx";
31920
+ if (existsSync5(join15(packageRoot, APP_MARKER))) return "app";
31842
31921
  if (existsSync5(join15(packageRoot, "tsup.config.ts"))) return "repo";
31843
31922
  return "global";
31844
31923
  }
@@ -32047,6 +32126,13 @@ async function upgradeCli(current, forward) {
32047
32126
  console.error(`
32048
32127
  ! browsentic ${latest} is published; this is ${current}, from a source checkout.`);
32049
32128
  console.error(` Update it with "git pull && yarn setup".
32129
+ `);
32130
+ return null;
32131
+ }
32132
+ if (kind === "app") {
32133
+ console.error(`
32134
+ ! browsentic ${latest} is published; this is ${current}, installed by Browsentic.app.`);
32135
+ console.error(` Update it from the app, which replaces the command and the extension together.
32050
32136
  `);
32051
32137
  return null;
32052
32138
  }
@@ -34184,8 +34270,8 @@ function purgeNpxCache(entries) {
34184
34270
  // package.json
34185
34271
  var package_default = {
34186
34272
  name: "browsentic",
34187
- version: "0.4.15",
34188
- 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.",
34273
+ version: "0.6.0",
34274
+ description: "A browser extension with an AI side panel that hands your real, logged-in browser to the AI agent you already run. Installs the extension, runs the local daemon, and optionally speaks MCP.",
34189
34275
  type: "module",
34190
34276
  license: "MIT",
34191
34277
  packageManager: "yarn@4.17.1",
@@ -34197,8 +34283,12 @@ var package_default = {
34197
34283
  },
34198
34284
  bugs: "https://github.com/imshaikot/browsentic/issues",
34199
34285
  keywords: [
34200
- "browser-automation",
34286
+ "browser-extension",
34201
34287
  "chrome-extension",
34288
+ "ai-browser-extension",
34289
+ "side-panel",
34290
+ "browser-agent",
34291
+ "browser-automation",
34202
34292
  "ai-agent",
34203
34293
  "agentic-browsing",
34204
34294
  "mcp",
@@ -34251,6 +34341,7 @@ var USAGE = `browsentic ${package_default.version} \u2014 hand your real browser
34251
34341
  browsentic agent show which agent runs the side panel, and which are installed
34252
34342
  browsentic agent <name> switch to claude, codex or antigravity
34253
34343
  browsentic agent fix <name> let Browsentic fix what that agent still needs
34344
+ browsentic agent model <name> [model] pin that agent's model, or omit it for the CLI's default
34254
34345
 
34255
34346
  browsentic skills list the skills the agent can route to, and where they came from
34256
34347
  browsentic approvals list the \u201Calways on this site\u201D approvals you have granted
@@ -34259,9 +34350,12 @@ var USAGE = `browsentic ${package_default.version} \u2014 hand your real browser
34259
34350
  browsentic downloads clear delete all of them
34260
34351
  browsentic tools print the bundled tool manifest (no browser needed)
34261
34352
  browsentic logs print the daemon log
34353
+ browsentic start bring the background daemon up, if it is not already
34262
34354
  browsentic stop stop the background daemon
34263
34355
  browsentic restart stop the daemon and bring up a fresh one
34264
34356
  browsentic token print the control token (for MCP clients, not the browser)
34357
+
34358
+ agent, skills, approvals and downloads take --json, which is what the macOS app reads.
34265
34359
  browsentic --version print the version
34266
34360
 
34267
34361
  Getting started: browsentic setup
@@ -34273,6 +34367,8 @@ For MCP clients
34273
34367
  var invokedAs = basename3(process.argv[1] ?? "").replace(/\.(?:js|cjs|mjs|exe|cmd|ps1)$/i, "");
34274
34368
  var servesBare = invokedAs === "browsentic-mcp" || !!process.env.BROWSENTIC_AGENT_RUN;
34275
34369
  var [command] = process.argv.slice(2);
34370
+ var wantsJson = process.argv.includes("--json");
34371
+ var positional = process.argv.slice(3).filter((arg) => !arg.startsWith("--"));
34276
34372
  switch (command) {
34277
34373
  case void 0:
34278
34374
  if (servesBare) await serve();
@@ -34300,7 +34396,7 @@ switch (command) {
34300
34396
  await revoke(process.argv[3]);
34301
34397
  break;
34302
34398
  case "agent":
34303
- await chooseAgent(process.argv[3], process.argv[4]);
34399
+ await chooseAgent(positional[0], positional[1], positional[2]);
34304
34400
  break;
34305
34401
  case "tools":
34306
34402
  printTools();
@@ -34308,6 +34404,9 @@ switch (command) {
34308
34404
  case "status":
34309
34405
  await showStatus();
34310
34406
  break;
34407
+ case "start":
34408
+ await start();
34409
+ break;
34311
34410
  case "stop":
34312
34411
  await stop();
34313
34412
  break;
@@ -34318,10 +34417,10 @@ switch (command) {
34318
34417
  printSkills();
34319
34418
  break;
34320
34419
  case "approvals":
34321
- manageApprovals(process.argv[3], process.argv[4]);
34420
+ manageApprovals(positional[0], positional[1]);
34322
34421
  break;
34323
34422
  case "downloads":
34324
- manageDownloads(process.argv[3]);
34423
+ manageDownloads(positional[0]);
34325
34424
  break;
34326
34425
  case "logs":
34327
34426
  showLogs();
@@ -34370,6 +34469,16 @@ function printTools() {
34370
34469
  }
34371
34470
  function printSkills() {
34372
34471
  const skills = loadSkills();
34472
+ if (wantsJson) {
34473
+ const config3 = readAgentConfig();
34474
+ const listed = skills.map(({ body: _body, ...skill }) => ({
34475
+ ...skill,
34476
+ path: skill.provenance === "generated" ? join18(uploadedSkillsDir(), skill.name) : void 0
34477
+ }));
34478
+ const own2 = agentSkills(config3).map(({ name, description }) => ({ name, description }));
34479
+ console.log(JSON.stringify({ skills: listed, dirs: skillDirNames(), agent: config3.agent, agentSkills: own2 }, null, 2));
34480
+ return;
34481
+ }
34373
34482
  if (!skills.length) {
34374
34483
  console.log(`No skills found. Looked in:
34375
34484
  ${skillDirNames().join("\n ")}`);
@@ -34440,6 +34549,10 @@ async function stop() {
34440
34549
  if (!stopped.length && !stubborn.length) console.log("No daemon is answering; nothing to stop.");
34441
34550
  if (stubborn.length) process.exitCode = 1;
34442
34551
  }
34552
+ async function start() {
34553
+ const lock = await ensureDaemon();
34554
+ console.log(`Daemon running on 127.0.0.1:${lock.port} (pid ${lock.pid}, v${lock.daemonVersion}).`);
34555
+ }
34443
34556
  async function restart() {
34444
34557
  const { stubborn } = await stopDaemons();
34445
34558
  if (stubborn.length) {
@@ -34610,6 +34723,7 @@ async function uninstall(argv) {
34610
34723
  console.log(" live in extension storage rather than on disk.");
34611
34724
  if (kind === "global") console.log("\n the command itself: npm rm -g browsentic");
34612
34725
  if (kind === "repo") console.log("\n the global link: yarn daemon:unlink");
34726
+ if (kind === "app") console.log("\n the app itself: drag Browsentic.app from Applications to the Trash");
34613
34727
  console.log("\n the entry in your MCP client, e.g. claude mcp remove browsentic");
34614
34728
  if (flag("dry-run")) {
34615
34729
  console.log("\n --dry-run: nothing was removed.\n");
@@ -34665,7 +34779,15 @@ async function showSessions() {
34665
34779
  console.log(` extension v${session.extensionVersion}, paired ${session.pairedAt}, last seen ${session.lastSeenAt}`);
34666
34780
  }
34667
34781
  }
34668
- async function chooseAgent(first, second) {
34782
+ async function chooseAgent(first, second, third) {
34783
+ if (first === "model") {
34784
+ if (!isAgentKind(second)) {
34785
+ console.error(`Name the agent whose model to set. Pick one of: ${AGENT_KINDS.join(", ")}`);
34786
+ process.exit(1);
34787
+ }
34788
+ writeAgentModel(second, third ?? null);
34789
+ first = second = void 0;
34790
+ }
34669
34791
  const grant = first === "fix" || first === "setup";
34670
34792
  const named = grant ? second : first;
34671
34793
  if (named !== void 0 && !isAgentKind(named)) {
@@ -34676,6 +34798,10 @@ async function chooseAgent(first, second) {
34676
34798
  const bridge = await connect();
34677
34799
  const state = await bridge.agent(kind && (grant ? { grant: kind } : { set: kind }));
34678
34800
  await bridge.close();
34801
+ if (wantsJson) {
34802
+ console.log(JSON.stringify({ ...state, catalog: AGENT_LIST }, null, 2));
34803
+ return;
34804
+ }
34679
34805
  for (const runner of state.runners) {
34680
34806
  const agent = AGENTS[runner.kind];
34681
34807
  const mark = runner.kind === state.active ? "\u25CF" : "\u25CB";
@@ -34713,6 +34839,10 @@ function manageDownloads(sub) {
34713
34839
  return;
34714
34840
  }
34715
34841
  const downloads = storedDownloads();
34842
+ if (wantsJson) {
34843
+ console.log(JSON.stringify({ dir: downloadDir(), downloads }, null, 2));
34844
+ return;
34845
+ }
34716
34846
  if (!downloads.length) {
34717
34847
  console.log(`Nothing captured. Files land in ${downloadDir()} when an agent uses page.captureDownload.`);
34718
34848
  return;
@@ -34738,6 +34868,10 @@ function manageApprovals(sub, host) {
34738
34868
  return;
34739
34869
  }
34740
34870
  const grants = listGrants();
34871
+ if (wantsJson) {
34872
+ console.log(JSON.stringify({ grants }, null, 2));
34873
+ return;
34874
+ }
34741
34875
  if (!grants.length) {
34742
34876
  console.log("No standing approvals. Every gated action still asks.");
34743
34877
  return;