browsentic 0.4.13 → 0.4.15

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
@@ -2276,7 +2276,7 @@ var require_websocket = __commonJS({
2276
2276
  var http = __require("http");
2277
2277
  var net = __require("net");
2278
2278
  var tls = __require("tls");
2279
- var { randomBytes: randomBytes3, createHash: createHash3 } = __require("crypto");
2279
+ var { randomBytes: randomBytes4, createHash: createHash3 } = __require("crypto");
2280
2280
  var { Duplex, Readable } = __require("stream");
2281
2281
  var { URL: URL2 } = __require("url");
2282
2282
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -2814,7 +2814,7 @@ var require_websocket = __commonJS({
2814
2814
  }
2815
2815
  }
2816
2816
  const defaultPort = isSecure ? 443 : 80;
2817
- const key = randomBytes3(16).toString("base64");
2817
+ const key = randomBytes4(16).toString("base64");
2818
2818
  const request = isSecure ? https.request : http.request;
2819
2819
  const protocolSet = /* @__PURE__ */ new Set();
2820
2820
  let perMessageDeflate;
@@ -6658,7 +6658,7 @@ var require_compile = __commonJS({
6658
6658
  const schOrFunc = root.refs[ref];
6659
6659
  if (schOrFunc)
6660
6660
  return schOrFunc;
6661
- let _sch = resolve.call(this, root, ref);
6661
+ let _sch = resolve3.call(this, root, ref);
6662
6662
  if (_sch === void 0) {
6663
6663
  const schema = (_a3 = root.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref];
6664
6664
  const { schemaId } = this.opts;
@@ -6685,7 +6685,7 @@ var require_compile = __commonJS({
6685
6685
  function sameSchemaEnv(s1, s2) {
6686
6686
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
6687
6687
  }
6688
- function resolve(root, ref) {
6688
+ function resolve3(root, ref) {
6689
6689
  let sch;
6690
6690
  while (typeof (sch = this.refs[ref]) == "string")
6691
6691
  ref = sch;
@@ -7316,55 +7316,55 @@ var require_fast_uri = __commonJS({
7316
7316
  }
7317
7317
  return uri;
7318
7318
  }
7319
- function resolve(baseURI, relativeURI, options) {
7319
+ function resolve3(baseURI, relativeURI, options) {
7320
7320
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
7321
7321
  const resolved = resolveComponent(parse3(baseURI, schemelessOptions), parse3(relativeURI, schemelessOptions), schemelessOptions, true);
7322
7322
  schemelessOptions.skipEscape = true;
7323
7323
  return serialize(resolved, schemelessOptions);
7324
7324
  }
7325
- function resolveComponent(base, relative2, options, skipNormalization) {
7325
+ function resolveComponent(base, relative3, options, skipNormalization) {
7326
7326
  const target = {};
7327
7327
  if (!skipNormalization) {
7328
7328
  base = parse3(serialize(base, options), options);
7329
- relative2 = parse3(serialize(relative2, options), options);
7329
+ relative3 = parse3(serialize(relative3, options), options);
7330
7330
  }
7331
7331
  options = options || {};
7332
- if (!options.tolerant && relative2.scheme) {
7333
- target.scheme = relative2.scheme;
7334
- target.userinfo = relative2.userinfo;
7335
- target.host = relative2.host;
7336
- target.port = relative2.port;
7337
- target.path = removeDotSegments(relative2.path || "");
7338
- target.query = relative2.query;
7332
+ if (!options.tolerant && relative3.scheme) {
7333
+ target.scheme = relative3.scheme;
7334
+ target.userinfo = relative3.userinfo;
7335
+ target.host = relative3.host;
7336
+ target.port = relative3.port;
7337
+ target.path = removeDotSegments(relative3.path || "");
7338
+ target.query = relative3.query;
7339
7339
  } else {
7340
- if (relative2.userinfo !== void 0 || relative2.host !== void 0 || relative2.port !== void 0) {
7341
- target.userinfo = relative2.userinfo;
7342
- target.host = relative2.host;
7343
- target.port = relative2.port;
7344
- target.path = removeDotSegments(relative2.path || "");
7345
- target.query = relative2.query;
7340
+ if (relative3.userinfo !== void 0 || relative3.host !== void 0 || relative3.port !== void 0) {
7341
+ target.userinfo = relative3.userinfo;
7342
+ target.host = relative3.host;
7343
+ target.port = relative3.port;
7344
+ target.path = removeDotSegments(relative3.path || "");
7345
+ target.query = relative3.query;
7346
7346
  } else {
7347
- if (!relative2.path) {
7347
+ if (!relative3.path) {
7348
7348
  target.path = base.path;
7349
- if (relative2.query !== void 0) {
7350
- target.query = relative2.query;
7349
+ if (relative3.query !== void 0) {
7350
+ target.query = relative3.query;
7351
7351
  } else {
7352
7352
  target.query = base.query;
7353
7353
  }
7354
7354
  } else {
7355
- if (relative2.path[0] === "/") {
7356
- target.path = removeDotSegments(relative2.path);
7355
+ if (relative3.path[0] === "/") {
7356
+ target.path = removeDotSegments(relative3.path);
7357
7357
  } else {
7358
7358
  if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) {
7359
- target.path = "/" + relative2.path;
7359
+ target.path = "/" + relative3.path;
7360
7360
  } else if (!base.path) {
7361
- target.path = relative2.path;
7361
+ target.path = relative3.path;
7362
7362
  } else {
7363
- target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative2.path;
7363
+ target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative3.path;
7364
7364
  }
7365
7365
  target.path = removeDotSegments(target.path);
7366
7366
  }
7367
- target.query = relative2.query;
7367
+ target.query = relative3.query;
7368
7368
  }
7369
7369
  target.userinfo = base.userinfo;
7370
7370
  target.host = base.host;
@@ -7372,7 +7372,7 @@ var require_fast_uri = __commonJS({
7372
7372
  }
7373
7373
  target.scheme = base.scheme;
7374
7374
  }
7375
- target.fragment = relative2.fragment;
7375
+ target.fragment = relative3.fragment;
7376
7376
  return target;
7377
7377
  }
7378
7378
  function equal(uriA, uriB, options) {
@@ -7580,7 +7580,7 @@ var require_fast_uri = __commonJS({
7580
7580
  var fastUri = {
7581
7581
  SCHEMES,
7582
7582
  normalize,
7583
- resolve,
7583
+ resolve: resolve3,
7584
7584
  resolveComponent,
7585
7585
  equal,
7586
7586
  serialize,
@@ -10570,7 +10570,8 @@ var require_dist = __commonJS({
10570
10570
  });
10571
10571
 
10572
10572
  // cli.ts
10573
- import { readFileSync as readFileSync9 } from "fs";
10573
+ import { readFileSync as readFileSync10 } from "fs";
10574
+ import { createInterface } from "readline/promises";
10574
10575
 
10575
10576
  // node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
10576
10577
  import process3 from "process";
@@ -26686,12 +26687,12 @@ var StdioServerTransport = class {
26686
26687
  this.onclose?.();
26687
26688
  }
26688
26689
  send(message) {
26689
- return new Promise((resolve) => {
26690
+ return new Promise((resolve3) => {
26690
26691
  const json2 = serializeMessage(message);
26691
26692
  if (this._stdout.write(json2)) {
26692
- resolve();
26693
+ resolve3();
26693
26694
  } else {
26694
- this._stdout.once("drain", resolve);
26695
+ this._stdout.once("drain", resolve3);
26695
26696
  }
26696
26697
  });
26697
26698
  }
@@ -27680,7 +27681,7 @@ function hoverSequence(el, at) {
27680
27681
 
27681
27682
  // ../lib/actions/page/drag-element.ts
27682
27683
  var FRAME_MS = 16;
27683
- var settle = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
27684
+ var settle = (ms) => new Promise((resolve3) => setTimeout(resolve3, ms));
27684
27685
  function mouseInit(at, buttons) {
27685
27686
  return {
27686
27687
  clientX: at.x,
@@ -28392,8 +28393,8 @@ function renderDiagram(regions) {
28392
28393
  const walk2 = (nodes, indent) => {
28393
28394
  nodes.forEach((region, i) => {
28394
28395
  const last = i === nodes.length - 1;
28395
- const { bounds, contains } = region;
28396
- const counts = ["links", "buttons", "fields"].filter((kind) => contains[kind] > 0).map((kind) => ` \xB7 ${contains[kind]} ${kind}`).join("");
28396
+ const { bounds, contains: contains2 } = region;
28397
+ const counts = ["links", "buttons", "fields"].filter((kind) => contains2[kind] > 0).map((kind) => ` \xB7 ${contains2[kind]} ${kind}`).join("");
28397
28398
  lines.push(
28398
28399
  `${indent}${last ? "\u2514" : "\u251C"} ${regionName(region)} \xB7 ${bounds.width}\xD7${bounds.height} @ (${bounds.x},${bounds.y})${counts}`
28399
28400
  );
@@ -28698,7 +28699,7 @@ function pickWithLens({ hint, timeoutMs }) {
28698
28699
  const box = root.querySelector(".box");
28699
28700
  const chip = root.querySelector(".chip");
28700
28701
  let hovered = null;
28701
- return new Promise((resolve) => {
28702
+ return new Promise((resolve3) => {
28702
28703
  const dismiss = () => settle2({ cancelled: true });
28703
28704
  dismissCurrent = dismiss;
28704
28705
  const timer = setTimeout(() => settle2({ timedOut: true }), timeoutMs);
@@ -28766,7 +28767,7 @@ function pickWithLens({ hint, timeoutMs }) {
28766
28767
  host.remove();
28767
28768
  cursor.remove();
28768
28769
  if (dismissCurrent === dismiss) dismissCurrent = null;
28769
- resolve(outcome);
28770
+ resolve3(outcome);
28770
28771
  }
28771
28772
  });
28772
28773
  }
@@ -29201,12 +29202,12 @@ function callToolkit(fn, args, timeoutMs) {
29201
29202
  throw new ActionError("No toolkit is installed in this page.", TOOLKIT_MISSING);
29202
29203
  }
29203
29204
  const callId = crypto.randomUUID();
29204
- return new Promise((resolve, reject) => {
29205
+ return new Promise((resolve3, reject) => {
29205
29206
  const onResult = (event) => {
29206
29207
  const reply = parseReply(event.detail);
29207
29208
  if (reply?.id !== callId) return;
29208
29209
  stop2();
29209
- if (reply.ok) resolve(reply.value ?? null);
29210
+ if (reply.ok) resolve3(reply.value ?? null);
29210
29211
  else reject(new ActionError(reply.error ?? "The function threw.", CODE_ERROR));
29211
29212
  };
29212
29213
  const timer = setTimeout(() => {
@@ -29330,11 +29331,11 @@ var scrollTo = defineAction({
29330
29331
  }
29331
29332
  });
29332
29333
  function scrollSettled() {
29333
- return new Promise((resolve) => {
29334
+ return new Promise((resolve3) => {
29334
29335
  const done = () => {
29335
29336
  window.removeEventListener("scrollend", done);
29336
29337
  clearTimeout(timer);
29337
- resolve();
29338
+ resolve3();
29338
29339
  };
29339
29340
  const timer = setTimeout(done, 1e3);
29340
29341
  window.addEventListener("scrollend", done);
@@ -29748,7 +29749,7 @@ var LINGER_AFTER = {
29748
29749
  var MAX_DURATION_MS = 12e4;
29749
29750
  var keyDelays = (text3, base) => [...text3].map((char) => base * (LINGER_AFTER[char] ?? 1));
29750
29751
  var budgetFor = (delays, jitter) => Math.round(delays.reduce((total, ms) => total + ms, 0) * (1 + jitter));
29751
- var pause = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
29752
+ var pause = (ms) => new Promise((resolve3) => setTimeout(resolve3, ms));
29752
29753
  var typeText = defineAction({
29753
29754
  name: "page.typeText",
29754
29755
  description: "Stream text into a field one keystroke at a time, at a human pace \u2014 a real key event per character, pauses that vary, and longer breaths after punctuation. Use page.fillInput when you just need the value in the field; use this when the page should watch someone type.",
@@ -29898,12 +29899,12 @@ var waitForElement = defineAction({
29898
29899
  if (satisfied(element)) {
29899
29900
  return { state, elapsedMs: 0, element: element ? describeElement(element) : void 0 };
29900
29901
  }
29901
- await new Promise((resolve, reject) => {
29902
+ await new Promise((resolve3, reject) => {
29902
29903
  const check2 = () => {
29903
29904
  element = find();
29904
29905
  if (satisfied(element)) {
29905
29906
  cleanup();
29906
- resolve();
29907
+ resolve3();
29907
29908
  }
29908
29909
  };
29909
29910
  const observer = new MutationObserver(check2);
@@ -30078,7 +30079,7 @@ function assertToolNamesRoundTrip(actionNames) {
30078
30079
  }
30079
30080
 
30080
30081
  // cli.ts
30081
- import { basename as basename2, join as join15 } from "path";
30082
+ import { basename as basename3, join as join18 } from "path";
30082
30083
 
30083
30084
  // agent/agent-skills.ts
30084
30085
  import { createHash } from "crypto";
@@ -31671,6 +31672,38 @@ async function probeExisting() {
31671
31672
  }
31672
31673
  return null;
31673
31674
  }
31675
+ async function runningDaemons() {
31676
+ const found = /* @__PURE__ */ new Map();
31677
+ for (const port of DAEMON_PORTS) {
31678
+ const pid = await healthyPid(port);
31679
+ if (pid !== null && !found.has(pid)) found.set(pid, port);
31680
+ }
31681
+ return [...found].map(([pid, port]) => ({ pid, port }));
31682
+ }
31683
+ async function stopDaemons(graceMs = 5e3) {
31684
+ const daemons = await runningDaemons();
31685
+ for (const { pid } of daemons) kill(pid, "SIGTERM");
31686
+ const remaining = await waitForExit(daemons, graceMs);
31687
+ for (const { pid } of remaining) kill(pid, "SIGKILL");
31688
+ const stubborn = await waitForExit(remaining, 2e3);
31689
+ clearLockfile();
31690
+ return { stopped: daemons.filter((daemon) => !stubborn.includes(daemon)), stubborn };
31691
+ }
31692
+ function kill(pid, signal) {
31693
+ try {
31694
+ process.kill(pid, signal);
31695
+ } catch {
31696
+ }
31697
+ }
31698
+ async function waitForExit(daemons, timeoutMs) {
31699
+ const deadline = Date.now() + timeoutMs;
31700
+ let alive = daemons.filter((daemon) => isRunning(daemon.pid));
31701
+ while (alive.length && Date.now() < deadline) {
31702
+ await delay(POLL_INTERVAL_MS);
31703
+ alive = alive.filter((daemon) => isRunning(daemon.pid));
31704
+ }
31705
+ return alive;
31706
+ }
31674
31707
  async function healthyPid(port) {
31675
31708
  try {
31676
31709
  const response = await fetch(`http://127.0.0.1:${port}/health`, {
@@ -31684,7 +31717,7 @@ async function healthyPid(port) {
31684
31717
  }
31685
31718
  }
31686
31719
  function delay(ms) {
31687
- return new Promise((resolve) => setTimeout(resolve, ms));
31720
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
31688
31721
  }
31689
31722
 
31690
31723
  // install.ts
@@ -31797,6 +31830,71 @@ function install(dir, force = false) {
31797
31830
  return { dir, version: version2, source: packaged.source, files: sources.length, changed, alreadyCurrent: false };
31798
31831
  }
31799
31832
 
31833
+ // npx.ts
31834
+ import { existsSync as existsSync5, readFileSync as readFileSync9, readdirSync as readdirSync5, realpathSync } from "fs";
31835
+ import { homedir as homedir7 } from "os";
31836
+ import { dirname as dirname6, join as join15, resolve, sep as sep2 } from "path";
31837
+ import { fileURLToPath as fileURLToPath5 } from "url";
31838
+ var packageRoot = resolve(dirname6(fileURLToPath5(import.meta.url)), "..");
31839
+ var inNpxCache = (path) => path.split(sep2).includes("_npx");
31840
+ function installKind() {
31841
+ if (inNpxCache(packageRoot)) return "npx";
31842
+ if (existsSync5(join15(packageRoot, "tsup.config.ts"))) return "repo";
31843
+ return "global";
31844
+ }
31845
+ function real(path) {
31846
+ try {
31847
+ return realpathSync(path);
31848
+ } catch {
31849
+ return path;
31850
+ }
31851
+ }
31852
+ function cacheRoots() {
31853
+ const roots = [
31854
+ process.env.npm_config_cache,
31855
+ process.platform === "win32" ? join15(process.env.LOCALAPPDATA ?? homedir7(), "npm-cache") : null,
31856
+ join15(homedir7(), ".npm")
31857
+ ].filter((root) => !!root);
31858
+ return [...new Set(roots.map((root) => join15(root, "_npx")))];
31859
+ }
31860
+ function readJson(path) {
31861
+ try {
31862
+ return JSON.parse(readFileSync9(path, "utf8"));
31863
+ } catch {
31864
+ return null;
31865
+ }
31866
+ }
31867
+ function npxEntries() {
31868
+ const own = inNpxCache(packageRoot) ? real(resolve(packageRoot, "..", "..")) : null;
31869
+ const scanned = cacheRoots().flatMap((root) => {
31870
+ try {
31871
+ return readdirSync5(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => real(join15(root, entry.name)));
31872
+ } catch {
31873
+ return [];
31874
+ }
31875
+ });
31876
+ const entries = /* @__PURE__ */ new Map();
31877
+ for (const dir of [...scanned, ...own ? [own] : []]) {
31878
+ if (entries.has(dir)) continue;
31879
+ const manifest = readJson(join15(dir, "node_modules", "browsentic", "package.json"));
31880
+ if (!manifest) continue;
31881
+ const requested = readJson(join15(dir, "package.json"))?._npx;
31882
+ entries.set(dir, {
31883
+ dir,
31884
+ version: typeof manifest.version === "string" ? manifest.version : null,
31885
+ packages: Array.isArray(requested?.packages) ? requested.packages : [],
31886
+ running: dir === own
31887
+ });
31888
+ }
31889
+ return [...entries.values()];
31890
+ }
31891
+ function pinnedVersion() {
31892
+ const own = npxEntries().find((entry) => entry.running);
31893
+ const spec = own?.packages.find((name) => name === "browsentic" || name.startsWith("browsentic@"));
31894
+ const requested = spec?.slice("browsentic@".length);
31895
+ return requested && /^\d+\.\d+\.\d+/.test(requested) ? requested : null;
31896
+ }
31897
+
31800
31898
  // remote-bridge.ts
31801
31899
  import { randomUUID as randomUUID5 } from "crypto";
31802
31900
 
@@ -31823,11 +31921,11 @@ var RemoteBridge = class _RemoteBridge {
31823
31921
  pending = /* @__PURE__ */ new Map();
31824
31922
  manifestListeners = /* @__PURE__ */ new Set();
31825
31923
  static connect(port, token, runId) {
31826
- return new Promise((resolve, reject) => {
31924
+ return new Promise((resolve3, reject) => {
31827
31925
  const socket = new import_websocket.default(`ws://127.0.0.1:${port}/control`, {
31828
31926
  headers: { authorization: `Bearer ${token}` }
31829
31927
  });
31830
- socket.once("open", () => resolve(new _RemoteBridge(socket, runId)));
31928
+ socket.once("open", () => resolve3(new _RemoteBridge(socket, runId)));
31831
31929
  socket.once("error", reject);
31832
31930
  });
31833
31931
  }
@@ -31874,14 +31972,14 @@ var RemoteBridge = class _RemoteBridge {
31874
31972
  }
31875
31973
  request(request, timeoutMs = REQUEST_TIMEOUT_MS) {
31876
31974
  if (this.socket.readyState !== import_websocket.default.OPEN) return Promise.resolve(null);
31877
- return new Promise((resolve) => {
31975
+ return new Promise((resolve3) => {
31878
31976
  const timer = setTimeout(() => {
31879
31977
  this.pending.delete(request.id);
31880
- resolve(null);
31978
+ resolve3(null);
31881
31979
  }, timeoutMs);
31882
31980
  this.pending.set(request.id, (message) => {
31883
31981
  clearTimeout(timer);
31884
- resolve(message);
31982
+ resolve3(message);
31885
31983
  });
31886
31984
  this.socket.send(JSON.stringify(request));
31887
31985
  });
@@ -31912,6 +32010,83 @@ function invokeTimeoutFor(action, input2) {
31912
32010
  return REQUEST_TIMEOUT_MS;
31913
32011
  }
31914
32012
 
32013
+ // self-update.ts
32014
+ import { spawnSync } from "child_process";
32015
+ import { rmSync as rmSync5 } from "fs";
32016
+ var REGISTRY = process.env.BROWSENTIC_REGISTRY ?? "https://registry.npmjs.org";
32017
+ async function publishedVersion(timeoutMs = 4e3) {
32018
+ try {
32019
+ const response = await fetch(`${REGISTRY}/browsentic/latest`, {
32020
+ headers: { accept: "application/vnd.npm.install-v1+json" },
32021
+ signal: AbortSignal.timeout(timeoutMs)
32022
+ });
32023
+ if (!response.ok) return null;
32024
+ const manifest = await response.json();
32025
+ return typeof manifest.version === "string" ? manifest.version : null;
32026
+ } catch {
32027
+ return null;
32028
+ }
32029
+ }
32030
+ function isNewer(candidate, current) {
32031
+ const release = (version2) => version2.split("-")[0].split(".").map((part) => Number.parseInt(part, 10) || 0);
32032
+ const [a, b] = [release(candidate), release(current)];
32033
+ for (let i = 0; i < 3; i++) if ((a[i] ?? 0) !== (b[i] ?? 0)) return (a[i] ?? 0) > (b[i] ?? 0);
32034
+ const [pre, currentPre] = [candidate.split("-")[1], current.split("-")[1]];
32035
+ if (pre === currentPre) return false;
32036
+ if (!pre) return true;
32037
+ if (!currentPre) return false;
32038
+ return pre > currentPre;
32039
+ }
32040
+ async function upgradeCli(current, forward) {
32041
+ const pinned = pinnedVersion();
32042
+ if (pinned) return null;
32043
+ const latest = await publishedVersion();
32044
+ if (!latest || !isNewer(latest, current)) return null;
32045
+ const kind = installKind();
32046
+ if (kind === "repo") {
32047
+ console.error(`
32048
+ ! browsentic ${latest} is published; this is ${current}, from a source checkout.`);
32049
+ console.error(` Update it with "git pull && yarn setup".
32050
+ `);
32051
+ return null;
32052
+ }
32053
+ console.error(`
32054
+ \u21BB Updating the command itself, ${current} \u2192 ${latest}
32055
+ `);
32056
+ const rerun = [...forward, "--no-self-update"];
32057
+ if (kind === "npx") {
32058
+ for (const entry of npxEntries()) rmSync5(entry.dir, { recursive: true, force: true });
32059
+ const code = run("npx", ["-y", `browsentic@${latest}`, ...rerun]);
32060
+ if (code !== 0) {
32061
+ console.error(`
32062
+ That did not run, but nothing is broken: the cache is cleared, so a plain`);
32063
+ console.error(` \`npx browsentic ${forward[0] ?? "setup"}\` fetches ${latest} fresh next time.
32064
+ `);
32065
+ }
32066
+ return code;
32067
+ }
32068
+ const installed = run("npm", ["install", "-g", `browsentic@${latest}`]);
32069
+ if (installed !== 0) {
32070
+ console.error(`
32071
+ Could not replace the global install. Run this yourself, then try again:
32072
+ `);
32073
+ console.error(` npm install -g browsentic@${latest}
32074
+ `);
32075
+ return installed;
32076
+ }
32077
+ return run("browsentic", rerun);
32078
+ }
32079
+ function run(command2, args) {
32080
+ const result = spawnSync(command2, args, { stdio: "inherit", shell: process.platform === "win32" });
32081
+ if (result.error) {
32082
+ console.error(`
32083
+ Could not run \`${command2}\`: ${result.error.message}
32084
+ `);
32085
+ return 1;
32086
+ }
32087
+ return result.status ?? 1;
32088
+ }
32089
+
31915
32090
  // node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-compat.js
31916
32091
  function isZ4Schema(s) {
31917
32092
  const schema = s;
@@ -32504,7 +32679,7 @@ var Protocol = class {
32504
32679
  return;
32505
32680
  }
32506
32681
  const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
32507
- await new Promise((resolve) => setTimeout(resolve, pollInterval));
32682
+ await new Promise((resolve3) => setTimeout(resolve3, pollInterval));
32508
32683
  options?.signal?.throwIfAborted();
32509
32684
  }
32510
32685
  } catch (error51) {
@@ -32521,7 +32696,7 @@ var Protocol = class {
32521
32696
  */
32522
32697
  request(request, resultSchema, options) {
32523
32698
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
32524
- return new Promise((resolve, reject) => {
32699
+ return new Promise((resolve3, reject) => {
32525
32700
  const earlyReject = (error51) => {
32526
32701
  reject(error51);
32527
32702
  };
@@ -32599,7 +32774,7 @@ var Protocol = class {
32599
32774
  if (!parseResult.success) {
32600
32775
  reject(parseResult.error);
32601
32776
  } else {
32602
- resolve(parseResult.data);
32777
+ resolve3(parseResult.data);
32603
32778
  }
32604
32779
  } catch (error51) {
32605
32780
  reject(error51);
@@ -32860,12 +33035,12 @@ var Protocol = class {
32860
33035
  }
32861
33036
  } catch {
32862
33037
  }
32863
- return new Promise((resolve, reject) => {
33038
+ return new Promise((resolve3, reject) => {
32864
33039
  if (signal.aborted) {
32865
33040
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
32866
33041
  return;
32867
33042
  }
32868
- const timeoutId = setTimeout(resolve, interval);
33043
+ const timeoutId = setTimeout(resolve3, interval);
32869
33044
  signal.addEventListener("abort", () => {
32870
33045
  clearTimeout(timeoutId);
32871
33046
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
@@ -33918,10 +34093,98 @@ function text2(uri, mimeType, body) {
33918
34093
  return { contents: [{ uri, mimeType, text: body }] };
33919
34094
  }
33920
34095
 
34096
+ // uninstall.ts
34097
+ import { existsSync as existsSync6, readdirSync as readdirSync6, rmSync as rmSync6 } from "fs";
34098
+ import { isAbsolute as isAbsolute4, join as join17, relative as relative2 } from "path";
34099
+
34100
+ // screenshots.ts
34101
+ import { randomBytes as randomBytes3 } from "crypto";
34102
+ import { chmodSync as chmodSync5, mkdirSync as mkdirSync8, writeFileSync as writeFileSync7 } from "fs";
34103
+ import { homedir as homedir8 } from "os";
34104
+ import { basename as basename2, isAbsolute as isAbsolute3, join as join16, resolve as resolve2, sep as sep3 } from "path";
34105
+ function screenshotDir() {
34106
+ const configured = readAgentConfig().screenshotDir;
34107
+ if (typeof configured === "string" && configured.trim()) return expandHome3(configured.trim());
34108
+ return join16(homedir8(), "browsentic", "screenshot");
34109
+ }
34110
+ function expandHome3(p) {
34111
+ if (p === "~") return homedir8();
34112
+ if (p.startsWith("~/")) return join16(homedir8(), p.slice(2));
34113
+ return isAbsolute3(p) ? p : join16(homedir8(), p);
34114
+ }
34115
+
34116
+ // uninstall.ts
34117
+ function contains(root, path) {
34118
+ const inside = relative2(root, path);
34119
+ return !inside.startsWith("..") && !isAbsolute4(inside);
34120
+ }
34121
+ function planUninstall(options = {}) {
34122
+ const config2 = readAgentConfig();
34123
+ const extension2 = extensionDir(config2.extensionDir);
34124
+ const roots = [stateDir, userDir];
34125
+ const removals = [];
34126
+ if (!roots.some((root) => contains(root, extension2)) && existsSync6(extension2)) {
34127
+ removals.push({ label: "extension", path: extension2, holds: "the unpacked build Chrome loads" });
34128
+ }
34129
+ removals.push({
34130
+ label: "state",
34131
+ path: stateDir,
34132
+ holds: "pairing keys, config, approvals, logs",
34133
+ keep: options.keepSkills ? ["skills"] : void 0
34134
+ });
34135
+ removals.push({
34136
+ label: "files",
34137
+ path: userDir,
34138
+ holds: "the extension, skills, site maps, screenshots, captured downloads",
34139
+ keep: options.keepSkills ? ["skills"] : void 0
34140
+ });
34141
+ const elsewhere = [];
34142
+ const outside = (label2, path, holds) => {
34143
+ if (!roots.some((root) => contains(root, path)) && existsSync6(path)) elsewhere.push({ label: label2, path, holds });
34144
+ };
34145
+ outside("skills", uploadedSkillsDir(), "site maps and uploaded skills");
34146
+ outside("screenshots", screenshotDir(), "captures taken with save: true");
34147
+ outside("downloads", downloadDir(), "files captured from pages");
34148
+ return {
34149
+ removals: removals.filter((removal) => existsSync6(removal.path)),
34150
+ elsewhere,
34151
+ npx: npxEntries()
34152
+ };
34153
+ }
34154
+ function removeAll(removals) {
34155
+ return removals.map((removal) => {
34156
+ try {
34157
+ const kept = removal.keep?.length ? keepingSome(removal.path, removal.keep) : [];
34158
+ if (!kept.length) rmSync6(removal.path, { recursive: true, force: true });
34159
+ return { removal, removed: true, kept };
34160
+ } catch (error51) {
34161
+ return { removal, removed: false, kept: [], error: error51.message };
34162
+ }
34163
+ });
34164
+ }
34165
+ function keepingSome(dir, keep) {
34166
+ const kept = [];
34167
+ for (const entry of readdirSync6(dir)) {
34168
+ if (keep.includes(entry)) kept.push(entry);
34169
+ else rmSync6(join17(dir, entry), { recursive: true, force: true });
34170
+ }
34171
+ return kept;
34172
+ }
34173
+ function purgeNpxCache(entries) {
34174
+ return entries.map((entry) => {
34175
+ try {
34176
+ rmSync6(entry.dir, { recursive: true, force: true });
34177
+ return { entry, removed: true };
34178
+ } catch (error51) {
34179
+ return { entry, removed: false, error: error51.message };
34180
+ }
34181
+ });
34182
+ }
34183
+
33921
34184
  // package.json
33922
34185
  var package_default = {
33923
34186
  name: "browsentic",
33924
- version: "0.4.13",
34187
+ version: "0.4.15",
33925
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.",
33926
34189
  type: "module",
33927
34190
  license: "MIT",
@@ -33978,7 +34241,8 @@ var package_default = {
33978
34241
  var USAGE = `browsentic ${package_default.version} \u2014 hand your real browser to the agent you already run
33979
34242
 
33980
34243
  browsentic setup install the extension, start the daemon, print a pairing code
33981
- browsentic update refresh the installed extension and restart the daemon
34244
+ browsentic update pull the newest build \u2014 the command itself, then the extension
34245
+ browsentic uninstall stop the daemon and remove everything Browsentic wrote
33982
34246
  browsentic pair issue a one-time code to type into the extension
33983
34247
  browsentic status daemon, extension and agent state
33984
34248
  browsentic sessions list paired browsers
@@ -34006,7 +34270,7 @@ For MCP clients
34006
34270
  browsentic mcp serve MCP over stdio \u2014 what a client runs, not what you type
34007
34271
  claude mcp add browsentic -- browsentic mcp
34008
34272
  `;
34009
- var invokedAs = basename2(process.argv[1] ?? "").replace(/\.(?:js|cjs|mjs|exe|cmd|ps1)$/i, "");
34273
+ var invokedAs = basename3(process.argv[1] ?? "").replace(/\.(?:js|cjs|mjs|exe|cmd|ps1)$/i, "");
34010
34274
  var servesBare = invokedAs === "browsentic-mcp" || !!process.env.BROWSENTIC_AGENT_RUN;
34011
34275
  var [command] = process.argv.slice(2);
34012
34276
  switch (command) {
@@ -34023,6 +34287,9 @@ switch (command) {
34023
34287
  case "update":
34024
34288
  await setup(["--no-pair", "--restart", ...process.argv.slice(3)]);
34025
34289
  break;
34290
+ case "uninstall":
34291
+ await uninstall(process.argv.slice(3));
34292
+ break;
34026
34293
  case "pair":
34027
34294
  await pair();
34028
34295
  break;
@@ -34042,7 +34309,7 @@ switch (command) {
34042
34309
  await showStatus();
34043
34310
  break;
34044
34311
  case "stop":
34045
- stop();
34312
+ await stop();
34046
34313
  break;
34047
34314
  case "restart":
34048
34315
  await restart();
@@ -34118,7 +34385,7 @@ function printSkills() {
34118
34385
  ].filter(Boolean);
34119
34386
  console.log(`${skill.name} (${tags.join(" \xB7 ")})`);
34120
34387
  if (skill.description) console.log(` ${skill.description}`);
34121
- if (skill.provenance === "generated") console.log(` ${join15(uploadedSkillsDir(), skill.name)}/`);
34388
+ if (skill.provenance === "generated") console.log(` ${join18(uploadedSkillsDir(), skill.name)}/`);
34122
34389
  }
34123
34390
  console.log(`
34124
34391
  Read in order: ${skillDirNames().join(" \u2192 ")} (a later one shadows an earlier one by name)`);
@@ -34164,34 +34431,27 @@ async function showStatus() {
34164
34431
  );
34165
34432
  if (!status2.pairedBrowsers) console.log('\nRun "browsentic setup" to install the extension, or "browsentic pair" if it is already loaded.');
34166
34433
  }
34167
- function stop() {
34168
- const lock = readLockfile();
34169
- if (!lock) return console.log("No daemon lockfile; nothing to stop.");
34170
- try {
34171
- process.kill(lock.pid, "SIGTERM");
34172
- console.log(`Stopped daemon (pid ${lock.pid}).`);
34173
- } catch {
34174
- console.log(`Daemon (pid ${lock.pid}) was not running; clearing the lockfile.`);
34175
- clearLockfile();
34434
+ async function stop() {
34435
+ const { stopped, stubborn } = await stopDaemons();
34436
+ for (const daemon of stopped) console.log(`Stopped daemon (pid ${daemon.pid}) on 127.0.0.1:${daemon.port}.`);
34437
+ for (const daemon of stubborn) {
34438
+ console.error(`Daemon (pid ${daemon.pid}) on 127.0.0.1:${daemon.port} would not exit \u2014 kill it by hand.`);
34176
34439
  }
34440
+ if (!stopped.length && !stubborn.length) console.log("No daemon is answering; nothing to stop.");
34441
+ if (stubborn.length) process.exitCode = 1;
34177
34442
  }
34178
34443
  async function restart() {
34179
- const lock = readLockfile();
34180
- stop();
34181
- const deadline = Date.now() + 5e3;
34182
- while (lock && isRunning(lock.pid)) {
34183
- if (Date.now() > deadline) {
34184
- console.error(`Daemon (pid ${lock.pid}) is still exiting \u2014 try again in a moment.`);
34185
- process.exit(1);
34186
- }
34187
- await new Promise((resolve) => setTimeout(resolve, 150));
34444
+ const { stubborn } = await stopDaemons();
34445
+ if (stubborn.length) {
34446
+ console.error(`Daemon (pid ${stubborn[0].pid}) is still exiting \u2014 try again in a moment.`);
34447
+ process.exit(1);
34188
34448
  }
34189
34449
  const fresh = await ensureDaemon();
34190
34450
  console.log(`Daemon running on 127.0.0.1:${fresh.port} (pid ${fresh.pid}, v${fresh.daemonVersion}).`);
34191
34451
  }
34192
34452
  function showLogs() {
34193
34453
  try {
34194
- process.stdout.write(readFileSync9(logPath, "utf8"));
34454
+ process.stdout.write(readFileSync10(logPath, "utf8"));
34195
34455
  } catch {
34196
34456
  console.log(`No log at ${logPath} yet.`);
34197
34457
  }
@@ -34222,6 +34482,10 @@ async function setup(argv) {
34222
34482
  const at = argv.indexOf(`--${name}`);
34223
34483
  return at === -1 ? void 0 : argv[at + 1];
34224
34484
  };
34485
+ if (!flag("no-self-update")) {
34486
+ const code2 = await upgradeCli(package_default.version, process.argv.slice(2));
34487
+ if (code2 !== null) process.exit(code2);
34488
+ }
34225
34489
  const browser = valueOf("browser") ?? "chrome";
34226
34490
  if (browser === "firefox") {
34227
34491
  console.log(`
@@ -34260,15 +34524,11 @@ async function setup(argv) {
34260
34524
  }
34261
34525
  if (flag("restart")) await restart();
34262
34526
  const lock = await ensureDaemon();
34263
- let code;
34264
- let alreadyPaired = false;
34265
- if (!flag("no-pair")) {
34266
- const bridge = await RemoteBridge.connect(lock.port, lock.token);
34267
- const sessions = await bridge.sessions();
34268
- alreadyPaired = sessions.some((session) => session.origin.startsWith("chrome-extension://"));
34269
- if (!alreadyPaired) code = (await bridge.pair()).code;
34270
- await bridge.close();
34271
- }
34527
+ const bridge = await RemoteBridge.connect(lock.port, lock.token);
34528
+ const sessions = await bridge.sessions();
34529
+ const alreadyPaired = sessions.some((session) => session.origin.startsWith("chrome-extension://"));
34530
+ const code = alreadyPaired || flag("no-pair") ? void 0 : (await bridge.pair()).code;
34531
+ await bridge.close();
34272
34532
  if (json2) {
34273
34533
  console.log(
34274
34534
  JSON.stringify(
@@ -34308,11 +34568,91 @@ async function setup(argv) {
34308
34568
  console.log(` ${groupCode(code)}
34309
34569
  `);
34310
34570
  console.log(` Single use, expires in 10 minutes. Need another? "browsentic pair"
34571
+ `);
34572
+ } else {
34573
+ console.log(` 2. Run "browsentic pair" and paste the code into the Browsentic popup.
34311
34574
  `);
34312
34575
  }
34313
34576
  console.log(` Then open the side panel and say what you want.
34314
34577
  `);
34315
34578
  }
34579
+ async function uninstall(argv) {
34580
+ const flag = (name) => argv.includes(`--${name}`);
34581
+ const plan = planUninstall({ keepSkills: flag("keep-skills") });
34582
+ const daemons = await runningDaemons();
34583
+ const kind = installKind();
34584
+ console.log(`
34585
+ Browsentic ${package_default.version} \u2014 uninstall
34586
+ `);
34587
+ if (!daemons.length && !plan.removals.length && !plan.npx.length) {
34588
+ console.log(" Nothing to remove; this machine is already clean.\n");
34589
+ return;
34590
+ }
34591
+ console.log(" This removes:\n");
34592
+ for (const daemon of daemons) console.log(` daemon 127.0.0.1:${daemon.port}, pid ${daemon.pid}`);
34593
+ for (const removal of plan.removals) {
34594
+ console.log(` ${removal.label.padEnd(11)} ${removal.path}`);
34595
+ console.log(` ${removal.holds}${removal.keep ? " \u2014 keeping skills/" : ""}`);
34596
+ }
34597
+ for (const entry of plan.npx) {
34598
+ const running = entry.running ? ", the copy running right now" : "";
34599
+ console.log(` npx cache ${entry.dir}`);
34600
+ console.log(` browsentic ${entry.version ?? "unknown"}${running}`);
34601
+ }
34602
+ if (plan.elsewhere.length) {
34603
+ console.log("\n Left alone \u2014 configuration put these outside the two roots:\n");
34604
+ for (const dir of plan.elsewhere) console.log(` ${dir.label.padEnd(11)} ${dir.path} (${dir.holds})`);
34605
+ }
34606
+ console.log("\n You will have to remove these yourself:\n");
34607
+ console.log(" the Browsentic card at chrome://extensions. Do that first \u2014 remove the");
34608
+ console.log(" directory while the card is loaded and the browser is left holding a");
34609
+ console.log(" broken one. It is also what clears recordings and held secrets, which");
34610
+ console.log(" live in extension storage rather than on disk.");
34611
+ if (kind === "global") console.log("\n the command itself: npm rm -g browsentic");
34612
+ if (kind === "repo") console.log("\n the global link: yarn daemon:unlink");
34613
+ console.log("\n the entry in your MCP client, e.g. claude mcp remove browsentic");
34614
+ if (flag("dry-run")) {
34615
+ console.log("\n --dry-run: nothing was removed.\n");
34616
+ return;
34617
+ }
34618
+ if (!flag("yes") && !argv.includes("-y")) {
34619
+ if (!process.stdin.isTTY) {
34620
+ console.error("\n Nothing was removed \u2014 this is not a terminal, so there is nobody to ask.");
34621
+ console.error(" Re-run it with --yes.\n");
34622
+ process.exit(1);
34623
+ }
34624
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
34625
+ const answer = await rl.question("\n Remove all of it? [y/N] ");
34626
+ rl.close();
34627
+ if (!/^y(es)?$/i.test(answer.trim())) return console.log("\n Nothing was removed.\n");
34628
+ }
34629
+ console.log();
34630
+ const lock = await probeExisting();
34631
+ if (lock) {
34632
+ try {
34633
+ const bridge = await RemoteBridge.connect(lock.port, lock.token);
34634
+ const revoked = await bridge.revoke();
34635
+ await bridge.close();
34636
+ if (revoked) console.log(` \u2713 Unpaired ${revoked} browser${revoked === 1 ? "" : "s"}`);
34637
+ } catch {
34638
+ console.log(" \xB7 Unpair skipped, the daemon did not answer");
34639
+ }
34640
+ }
34641
+ const { stopped, stubborn } = await stopDaemons();
34642
+ if (stopped.length) console.log(` \u2713 Daemon stopped (pid ${stopped.map((daemon) => daemon.pid).join(", ")})`);
34643
+ for (const daemon of stubborn) console.log(` \u2717 Daemon pid ${daemon.pid} would not exit \u2014 kill it by hand`);
34644
+ for (const outcome of removeAll(plan.removals)) {
34645
+ if (!outcome.removed) console.log(` \u2717 ${outcome.removal.path} \u2014 ${outcome.error}`);
34646
+ else if (outcome.kept.length) console.log(` \u2713 Emptied ${outcome.removal.path} \u2014 kept ${outcome.kept.join(", ")}/`);
34647
+ else console.log(` \u2713 Removed ${outcome.removal.path}`);
34648
+ }
34649
+ for (const purge of purgeNpxCache(plan.npx)) {
34650
+ if (purge.removed) console.log(` \u2713 Cleared ${purge.entry.dir}`);
34651
+ else console.log(` \u2717 ${purge.entry.dir} \u2014 ${purge.error}`);
34652
+ }
34653
+ console.log("\n Done. Remove the card at chrome://extensions if you have not.\n");
34654
+ if (stubborn.length) process.exitCode = 1;
34655
+ }
34316
34656
  async function showSessions() {
34317
34657
  const bridge = await connect();
34318
34658
  const sessions = await bridge.sessions();