browsentic 0.7.4 → 0.7.5

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
@@ -3478,10 +3478,10 @@ var require_websocket_server = __commonJS({
3478
3478
  process.nextTick(emitClose, this);
3479
3479
  }
3480
3480
  } else {
3481
- const server = this._server;
3481
+ const server2 = this._server;
3482
3482
  this._removeListeners();
3483
3483
  this._removeListeners = this._server = null;
3484
- server.close(() => {
3484
+ server2.close(() => {
3485
3485
  emitClose(this);
3486
3486
  });
3487
3487
  }
@@ -3666,17 +3666,17 @@ var require_websocket_server = __commonJS({
3666
3666
  }
3667
3667
  };
3668
3668
  module.exports = WebSocketServer2;
3669
- function addListeners(server, map2) {
3670
- for (const event of Object.keys(map2)) server.on(event, map2[event]);
3669
+ function addListeners(server2, map2) {
3670
+ for (const event of Object.keys(map2)) server2.on(event, map2[event]);
3671
3671
  return function removeListeners() {
3672
3672
  for (const event of Object.keys(map2)) {
3673
- server.removeListener(event, map2[event]);
3673
+ server2.removeListener(event, map2[event]);
3674
3674
  }
3675
3675
  };
3676
3676
  }
3677
- function emitClose(server) {
3678
- server._state = CLOSED;
3679
- server.emit("close");
3677
+ function emitClose(server2) {
3678
+ server2._state = CLOSED;
3679
+ server2.emit("close");
3680
3680
  }
3681
3681
  function socketOnError() {
3682
3682
  this.destroy();
@@ -3695,11 +3695,11 @@ var require_websocket_server = __commonJS({
3695
3695
  ` + Object.keys(headers).map((h) => `${h}: ${headers[h]}`).join("\r\n") + "\r\n\r\n" + message
3696
3696
  );
3697
3697
  }
3698
- function abortHandshakeOrEmitwsClientError(server, req, socket, code, message, headers) {
3699
- if (server.listenerCount("wsClientError")) {
3698
+ function abortHandshakeOrEmitwsClientError(server2, req, socket, code, message, headers) {
3699
+ if (server2.listenerCount("wsClientError")) {
3700
3700
  const err = new Error(message);
3701
3701
  Error.captureStackTrace(err, abortHandshakeOrEmitwsClientError);
3702
- server.emit("wsClientError", err, socket, req);
3702
+ server2.emit("wsClientError", err, socket, req);
3703
3703
  } else {
3704
3704
  abortHandshake(socket, code, message, headers);
3705
3705
  }
@@ -7322,49 +7322,49 @@ var require_fast_uri = __commonJS({
7322
7322
  schemelessOptions.skipEscape = true;
7323
7323
  return serialize(resolved, schemelessOptions);
7324
7324
  }
7325
- function resolveComponent(base, relative3, options, skipNormalization) {
7325
+ function resolveComponent(base, relative4, options, skipNormalization) {
7326
7326
  const target = {};
7327
7327
  if (!skipNormalization) {
7328
7328
  base = parse3(serialize(base, options), options);
7329
- relative3 = parse3(serialize(relative3, options), options);
7329
+ relative4 = parse3(serialize(relative4, options), options);
7330
7330
  }
7331
7331
  options = options || {};
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;
7332
+ if (!options.tolerant && relative4.scheme) {
7333
+ target.scheme = relative4.scheme;
7334
+ target.userinfo = relative4.userinfo;
7335
+ target.host = relative4.host;
7336
+ target.port = relative4.port;
7337
+ target.path = removeDotSegments(relative4.path || "");
7338
+ target.query = relative4.query;
7339
7339
  } else {
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;
7340
+ if (relative4.userinfo !== void 0 || relative4.host !== void 0 || relative4.port !== void 0) {
7341
+ target.userinfo = relative4.userinfo;
7342
+ target.host = relative4.host;
7343
+ target.port = relative4.port;
7344
+ target.path = removeDotSegments(relative4.path || "");
7345
+ target.query = relative4.query;
7346
7346
  } else {
7347
- if (!relative3.path) {
7347
+ if (!relative4.path) {
7348
7348
  target.path = base.path;
7349
- if (relative3.query !== void 0) {
7350
- target.query = relative3.query;
7349
+ if (relative4.query !== void 0) {
7350
+ target.query = relative4.query;
7351
7351
  } else {
7352
7352
  target.query = base.query;
7353
7353
  }
7354
7354
  } else {
7355
- if (relative3.path[0] === "/") {
7356
- target.path = removeDotSegments(relative3.path);
7355
+ if (relative4.path[0] === "/") {
7356
+ target.path = removeDotSegments(relative4.path);
7357
7357
  } else {
7358
7358
  if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) {
7359
- target.path = "/" + relative3.path;
7359
+ target.path = "/" + relative4.path;
7360
7360
  } else if (!base.path) {
7361
- target.path = relative3.path;
7361
+ target.path = relative4.path;
7362
7362
  } else {
7363
- target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative3.path;
7363
+ target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative4.path;
7364
7364
  }
7365
7365
  target.path = removeDotSegments(target.path);
7366
7366
  }
7367
- target.query = relative3.query;
7367
+ target.query = relative4.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 = relative3.fragment;
7375
+ target.fragment = relative4.fragment;
7376
7376
  return target;
7377
7377
  }
7378
7378
  function equal(uriA, uriB, options) {
@@ -10570,7 +10570,7 @@ var require_dist = __commonJS({
10570
10570
  });
10571
10571
 
10572
10572
  // cli.ts
10573
- import { readFileSync as readFileSync12 } from "fs";
10573
+ import { readFileSync as readFileSync13 } from "fs";
10574
10574
  import { createInterface } from "readline/promises";
10575
10575
 
10576
10576
  // node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
@@ -11765,8 +11765,8 @@ function prefixIssues(path, issues) {
11765
11765
  function unwrapMessage(message) {
11766
11766
  return typeof message === "string" ? message : message?.message;
11767
11767
  }
11768
- function finalizeIssue(iss, ctx, config4) {
11769
- const message = iss.message ? iss.message : unwrapMessage(iss.inst?._zod.def?.error?.(iss)) ?? unwrapMessage(ctx?.error?.(iss)) ?? unwrapMessage(config4.customError?.(iss)) ?? unwrapMessage(config4.localeError?.(iss)) ?? "Invalid input";
11768
+ function finalizeIssue(iss, ctx, config5) {
11769
+ const message = iss.message ? iss.message : unwrapMessage(iss.inst?._zod.def?.error?.(iss)) ?? unwrapMessage(ctx?.error?.(iss)) ?? unwrapMessage(config5.customError?.(iss)) ?? unwrapMessage(config5.localeError?.(iss)) ?? "Invalid input";
11770
11770
  const { inst: _inst, continue: _continue, input: _input, ...rest } = iss;
11771
11771
  rest.path ?? (rest.path = []);
11772
11772
  rest.message = message;
@@ -30101,7 +30101,7 @@ function tidy(schema) {
30101
30101
  var isBound = (key) => key === "minimum" || key === "maximum";
30102
30102
 
30103
30103
  // ../lib/agents/catalog.ts
30104
- var AGENT_KINDS = ["claude", "codex", "antigravity", "vibe", "grok", "cursor", "qwen"];
30104
+ var AGENT_KINDS = ["claude", "codex", "antigravity", "vibe", "grok", "cursor", "qwen", "opencode"];
30105
30105
  var DEFAULT_AGENT = "claude";
30106
30106
  var AGENTS = {
30107
30107
  claude: {
@@ -30173,6 +30173,18 @@ var AGENTS = {
30173
30173
  // subcommand lists them, so these are curated.
30174
30174
  models: ["qwen3-coder-plus", "qwen3.7-plus", "qwen3.6-plus", "qwen3-max-2026-01-23"],
30175
30175
  beta: true
30176
+ },
30177
+ opencode: {
30178
+ kind: "opencode",
30179
+ label: "OpenCode",
30180
+ vendor: "Anomaly",
30181
+ bin: "opencode",
30182
+ install: "npm i -g opencode-ai",
30183
+ docs: "https://opencode.ai/docs/cli/",
30184
+ // Spelled provider/model, and reachable only through a provider OpenCode is signed in to: Zen's
30185
+ // free models refuse a run whose tools are narrowed to the browser.
30186
+ models: ["anthropic/claude-opus-5-5", "anthropic/claude-sonnet-5", "openai/gpt-5.6", "google/gemini-3.1-pro-preview"],
30187
+ beta: true
30176
30188
  }
30177
30189
  };
30178
30190
  var AGENT_LIST = AGENT_KINDS.map((kind) => AGENTS[kind]);
@@ -30228,12 +30240,12 @@ function assertToolNamesRoundTrip(actionNames) {
30228
30240
  }
30229
30241
 
30230
30242
  // cli.ts
30231
- import { basename as basename3, join as join22 } from "path";
30243
+ import { basename as basename3, join as join23 } from "path";
30232
30244
 
30233
30245
  // agent/agent-skills.ts
30234
30246
  import { createHash } from "crypto";
30235
- import { readFileSync as readFileSync7, readdirSync as readdirSync3, statSync as statSync3 } from "fs";
30236
- import { join as join14, sep } from "path";
30247
+ import { readFileSync as readFileSync8, readdirSync as readdirSync3, statSync as statSync3 } from "fs";
30248
+ import { join as join15, sep } from "path";
30237
30249
 
30238
30250
  // ../lib/skills/format.ts
30239
30251
  var MAX_BODY_BYTES = 32 * 1024;
@@ -30333,7 +30345,7 @@ function format(detail) {
30333
30345
 
30334
30346
  // agent/runners/index.ts
30335
30347
  import { spawn as spawn2 } from "child_process";
30336
- import { dirname as dirname3, join as join12 } from "path";
30348
+ import { dirname as dirname4, join as join13 } from "path";
30337
30349
  import { fileURLToPath as fileURLToPath2 } from "url";
30338
30350
 
30339
30351
  // agent/config.ts
@@ -30393,9 +30405,9 @@ function writeAgentModel(kind, model) {
30393
30405
  if (kind === "claude") delete next.model;
30394
30406
  write(next);
30395
30407
  }
30396
- function write(config4) {
30408
+ function write(config5) {
30397
30409
  mkdirSync3(stateDir, { recursive: true, mode: 448 });
30398
- writeFileSync2(configPath, `${JSON.stringify(config4, null, 2)}
30410
+ writeFileSync2(configPath, `${JSON.stringify(config5, null, 2)}
30399
30411
  `, { mode: 384 });
30400
30412
  }
30401
30413
  function rememberExtensionDir(dir) {
@@ -30786,8 +30798,8 @@ var antigravityRunner = {
30786
30798
  }
30787
30799
  }
30788
30800
  };
30789
- function mcpConfig(server) {
30790
- const servers = server ? { [MCP_SERVER_NAME]: server } : {};
30801
+ function mcpConfig(server2) {
30802
+ const servers = server2 ? { [MCP_SERVER_NAME]: server2 } : {};
30791
30803
  return `${JSON.stringify({ mcpServers: servers }, null, 2)}
30792
30804
  `;
30793
30805
  }
@@ -30844,7 +30856,7 @@ var codexRunner = {
30844
30856
  skillDirs: () => [join7(homedir4(), ".codex", "skills"), join7(homedir4(), ".codex", "prompts")],
30845
30857
  stream(context) {
30846
30858
  const { settings, mcp, research } = context;
30847
- const server = `mcp_servers.${MCP_SERVER_NAME}`;
30859
+ const server2 = `mcp_servers.${MCP_SERVER_NAME}`;
30848
30860
  const effort = effortOf(settings, this.efforts);
30849
30861
  return {
30850
30862
  cwd: this.workspace("run"),
@@ -30856,16 +30868,16 @@ var codexRunner = {
30856
30868
  ...SANDBOX,
30857
30869
  ...QUIETED,
30858
30870
  "-c",
30859
- `${server}.command=${tomlString(mcp.command)}`,
30871
+ `${server2}.command=${tomlString(mcp.command)}`,
30860
30872
  "-c",
30861
- `${server}.args=${tomlArray(mcp.args)}`,
30873
+ `${server2}.args=${tomlArray(mcp.args)}`,
30862
30874
  "-c",
30863
- `${server}.env=${tomlTable(mcp.env)}`,
30875
+ `${server2}.env=${tomlTable(mcp.env)}`,
30864
30876
  "-c",
30865
- `${server}.required=true`,
30877
+ `${server2}.required=true`,
30866
30878
  // Headless Codex refuses an MCP call it would have asked about; the daemon gates these tools itself.
30867
30879
  "-c",
30868
- `${server}.default_tools_approval_mode="approve"`,
30880
+ `${server2}.default_tools_approval_mode="approve"`,
30869
30881
  "-c",
30870
30882
  `developer_instructions=${tomlString(`${context.systemPrompt.trim()}
30871
30883
 
@@ -31223,11 +31235,11 @@ function toolOf(event) {
31223
31235
  const [key, value] = Object.entries(event.tool_call ?? {}).find(([name]) => name.endsWith("ToolCall")) ?? [];
31224
31236
  if (!key) return void 0;
31225
31237
  const args = value?.args;
31226
- const server = args?.["server"];
31227
- if (typeof server === "string") {
31228
- if (server === MCP_SERVER_NAME) return void 0;
31238
+ const server2 = args?.["server"];
31239
+ if (typeof server2 === "string") {
31240
+ if (server2 === MCP_SERVER_NAME) return void 0;
31229
31241
  const tool = args?.["name"];
31230
- return `${server}:${typeof tool === "string" ? tool : key}`;
31242
+ return `${server2}:${typeof tool === "string" ? tool : key}`;
31231
31243
  }
31232
31244
  return key.replace(/ToolCall$/, "");
31233
31245
  }
@@ -31432,13 +31444,13 @@ function explain2(message) {
31432
31444
  }
31433
31445
  var oneLine = (message) => message.replace(/\s+/g, " ").slice(0, 240);
31434
31446
  var sentence = (message) => /[.!?]$/.test(message.trim()) ? message.trim() : `${message.trim()}.`;
31435
- function config2(server) {
31447
+ function config2(server2) {
31436
31448
  const quote = (value) => JSON.stringify(value);
31437
- const env = Object.entries(server.env).map(([name, value]) => `${name} = ${quote(value)}`);
31449
+ const env = Object.entries(server2.env).map(([name, value]) => `${name} = ${quote(value)}`);
31438
31450
  return [
31439
31451
  `[mcp_servers.${MCP_SERVER_NAME}]`,
31440
- `command = ${quote(server.command)}`,
31441
- `args = [${server.args.map(quote).join(", ")}]`,
31452
+ `command = ${quote(server2.command)}`,
31453
+ `args = [${server2.args.map(quote).join(", ")}]`,
31442
31454
  `env = { ${env.join(", ")} }`,
31443
31455
  ""
31444
31456
  ].join("\n");
@@ -31451,11 +31463,215 @@ function lastLine(stdout) {
31451
31463
  return null;
31452
31464
  }
31453
31465
 
31454
- // agent/runners/qwen.ts
31466
+ // agent/runners/opencode.ts
31455
31467
  import { randomUUID as randomUUID5 } from "crypto";
31456
31468
  import { readFileSync as readFileSync5 } from "fs";
31457
31469
  import { homedir as homedir7 } from "os";
31458
- import { join as join10 } from "path";
31470
+ import { dirname as dirname3, join as join10, relative } from "path";
31471
+ var AGENT = "browsentic-contained";
31472
+ var INSTRUCTIONS2 = "instructions.md";
31473
+ var WEB_TOOLS3 = ["webfetch", "websearch"];
31474
+ var RESULT_BYTES = 1e5;
31475
+ var TOOL_TIMEOUT_MS = 30 * 6e4;
31476
+ var TRUNCATION = `A tool result over ${RESULT_BYTES / 1e3} KB comes back cut, and the saved copy OpenCode points to cannot be opened in this run, so ask for less at a time \u2014 \`page_getPageInfo\` with a small \`maxPerKind\`, \`page_extractText\` with the cursor it hands back.`;
31477
+ var SEALED2 = { OPENCODE_DISABLE_PROJECT_CONFIG: "1", OPENCODE_DISABLE_CLAUDE_CODE: "1", OPENCODE_DISABLE_SHARE: "1" };
31478
+ var sessionsDb = () => join10(stateDir, "agents", "opencode", "sessions.db");
31479
+ var configHome = () => join10(process.env.XDG_CONFIG_HOME || join10(homedir7(), ".config"), "opencode");
31480
+ var INSTALL = "npm i -g opencode-ai";
31481
+ var opencodeRunner = {
31482
+ kind: "opencode",
31483
+ versionArgs: ["--version"],
31484
+ // Passed as --variant, whose names each model defines; a model ignores one it lacks.
31485
+ efforts: ["none", "minimal", "low", "medium", "high", "xhigh", "max"],
31486
+ endsOnExit: true,
31487
+ workspace: (mode) => join10(stateDir, "agents", "opencode", mode),
31488
+ skillDirs: () => [
31489
+ join10(configHome(), "skills"),
31490
+ join10(configHome(), "skill"),
31491
+ join10(homedir7(), ".opencode", "skills"),
31492
+ join10(homedir7(), ".agents", "skills"),
31493
+ join10(homedir7(), ".claude", "skills")
31494
+ ],
31495
+ stream(context) {
31496
+ const { settings, research } = context;
31497
+ const base = this.workspace("run");
31498
+ sweepRunDirs(base);
31499
+ const cwd = conversationDir(base, context.conversation ?? context.runId);
31500
+ const allowed = [...context.mcpTools.map((tool) => `${MCP_SERVER_NAME}_${tool}`), ...research ? WEB_TOOLS3 : []];
31501
+ const effort = effortOf(settings, this.efforts);
31502
+ return {
31503
+ cwd,
31504
+ env: {
31505
+ ...SEALED2,
31506
+ OPENCODE_DB: sessionsDb(),
31507
+ OPENCODE_CONFIG_CONTENT: config3({
31508
+ mcp: { [MCP_SERVER_NAME]: server(context.mcp) },
31509
+ instructions: [join10(cwd, INSTRUCTIONS2)],
31510
+ permission: { "*": "deny", ...Object.fromEntries(allowed.map((tool) => [tool, "allow"])) }
31511
+ }),
31512
+ BROWSENTIC_AGENT_RUN: context.runId
31513
+ },
31514
+ files: [{ path: INSTRUCTIONS2, content: `${context.systemPrompt.trim()}
31515
+
31516
+ ${TRUNCATION}
31517
+ ` }],
31518
+ args: [
31519
+ ...invocation(),
31520
+ ...context.sessionId ? ["--session", context.sessionId] : [],
31521
+ ...settings.model ? ["--model", settings.model] : [],
31522
+ ...effort ? ["--variant", effort] : [],
31523
+ "--",
31524
+ context.instruction
31525
+ ]
31526
+ };
31527
+ },
31528
+ reader() {
31529
+ let spoke = false;
31530
+ let generated = 0;
31531
+ const report = (tokens, sink) => {
31532
+ const made = (tokens.output ?? 0) + (tokens.reasoning ?? 0);
31533
+ const prompt = (tokens.input ?? 0) + (tokens.cache?.read ?? 0) + (tokens.cache?.write ?? 0);
31534
+ generated += made;
31535
+ sink.usage({ contextTokens: prompt + made, outputTokens: generated });
31536
+ };
31537
+ return (line, sink) => {
31538
+ const event = parseJsonLine(line);
31539
+ if (!event) return;
31540
+ if (event.sessionID) sink.session(event.sessionID);
31541
+ const part = event.part;
31542
+ switch (event.type) {
31543
+ case "text":
31544
+ if (!part?.text) return;
31545
+ sink.text(spoke ? `
31546
+
31547
+ ${part.text}` : part.text);
31548
+ spoke = true;
31549
+ return;
31550
+ case "tool_use": {
31551
+ const name = part?.tool;
31552
+ if (!name || ownTool3(name)) return;
31553
+ if (WEB_TOOLS3.includes(name)) return sink.tool(part.callID ?? randomUUID5(), name);
31554
+ if (part.state?.status !== "completed") return;
31555
+ return sink.fail(
31556
+ "AGENT_UNSAFE",
31557
+ `OpenCode ran its own ${name} tool in this run, which Browsentic denied, so the run was stopped. Update OpenCode and Browsentic; if it persists, please report it.`
31558
+ );
31559
+ }
31560
+ case "step_finish":
31561
+ if (part?.tokens) report(part.tokens, sink);
31562
+ return;
31563
+ case "error":
31564
+ return sink.fail("AGENT_FAILED", explain3(event.error));
31565
+ }
31566
+ };
31567
+ },
31568
+ json(context) {
31569
+ const { settings, reads } = context;
31570
+ const cwd = this.workspace("task");
31571
+ const permission = { "*": "deny", ...reads ? { read: scratchReads(cwd) } : {} };
31572
+ return {
31573
+ cwd,
31574
+ env: {
31575
+ ...SEALED2,
31576
+ OPENCODE_DB: sessionsDb(),
31577
+ // Merged over the user's servers rather than replacing them: it adds none, and the ruleset hides theirs.
31578
+ OPENCODE_CONFIG_CONTENT: config3({ mcp: {}, permission })
31579
+ },
31580
+ args: [...invocation(), ...settings.model ? ["--model", settings.model] : [], "--", context.prompt]
31581
+ };
31582
+ },
31583
+ answer(stdout) {
31584
+ let said = [];
31585
+ for (const line of stdout.split("\n")) {
31586
+ const event = parseJsonLine(line);
31587
+ if (event?.type === "error") return { error: explain3(event.error) };
31588
+ if (event?.type === "step_start") said = [];
31589
+ if (event?.type === "text" && event.part?.text) said.push(event.part.text);
31590
+ }
31591
+ return { text: said.length ? said.join("\n\n") : void 0 };
31592
+ },
31593
+ hint(stderrTail) {
31594
+ if (/Unknown arguments?|Not enough arguments|Invalid values/i.test(stderrTail)) {
31595
+ return `Your OpenCode does not understand the flags Browsentic uses. Update it (${INSTALL}), then try again. (${stderrTail.trim()})`;
31596
+ }
31597
+ return null;
31598
+ },
31599
+ async check() {
31600
+ if (process.env.OPENCODE_API_KEY || process.env.OPENCODE_AUTH_CONTENT || signedIn2() || declaresProvider()) return null;
31601
+ return {
31602
+ code: "AGENT_NEEDS_PERMISSION",
31603
+ message: "OpenCode is signed in to no model provider, and its free OpenCode Zen models refuse a run whose tools Browsentic has narrowed to the browser.",
31604
+ fix: "opencode auth login"
31605
+ };
31606
+ }
31607
+ };
31608
+ var invocation = () => ["run", "--format", "json", "--pure", "--agent", AGENT];
31609
+ var ownTool3 = (name) => name.startsWith(`${MCP_SERVER_NAME}_`);
31610
+ var server = (mcp) => ({
31611
+ type: "local",
31612
+ command: [mcp.command, ...mcp.args],
31613
+ environment: mcp.env,
31614
+ enabled: true,
31615
+ timeout: TOOL_TIMEOUT_MS
31616
+ });
31617
+ function config3({ mcp, instructions, permission }) {
31618
+ return JSON.stringify({
31619
+ share: "disabled",
31620
+ autoupdate: false,
31621
+ snapshot: false,
31622
+ tool_output: { max_bytes: RESULT_BYTES, max_lines: RESULT_BYTES },
31623
+ mcp,
31624
+ ...instructions ? { instructions } : {},
31625
+ agent: { title: { disable: true }, [AGENT]: { mode: "primary", permission } }
31626
+ });
31627
+ }
31628
+ function scratchReads(workspace) {
31629
+ const scratch = join10(workspace, "tmp");
31630
+ const rules = { "*": "deny" };
31631
+ for (let root = dirname3(workspace); ; root = dirname3(root)) {
31632
+ rules[`${relative(root, scratch)}/*`] = "allow";
31633
+ if (root === dirname3(root)) return rules;
31634
+ }
31635
+ }
31636
+ function explain3(failure2) {
31637
+ const message = oneLine2(failure2?.data?.message ?? failure2?.name ?? "OpenCode reported an error");
31638
+ const status2 = failure2?.data?.statusCode;
31639
+ if (failure2?.data?.responseBody?.includes("FreeTierError")) {
31640
+ return `OpenCode Zen's free models refuse a run whose tools Browsentic has narrowed to the browser. Run "opencode auth login" to sign in to a provider, then pick one of its models in the Browsentic popup. (${message})`;
31641
+ }
31642
+ if (failure2?.name === "ProviderAuthError" || status2 === 401 || status2 === 403) {
31643
+ return `${message} If that is a login problem, run "opencode auth login" \u2014 a key kept only in an environment variable is not passed to a Browsentic run.`;
31644
+ }
31645
+ if (status2 === 429) return `The provider behind OpenCode is rate-limiting this account. Wait and try again. (${message})`;
31646
+ if (failure2?.name === "UnknownError") {
31647
+ return `OpenCode could not start this turn, most often because it does not know the model. Pick one in the Browsentic popup as "opencode models" lists it \u2014 provider/model \u2014 then try again. (${message})`;
31648
+ }
31649
+ return message;
31650
+ }
31651
+ var oneLine2 = (message) => message.replace(/\s+/g, " ").trim().slice(0, 240);
31652
+ var dataHome = () => join10(process.env.XDG_DATA_HOME || join10(homedir7(), ".local", "share"), "opencode");
31653
+ function signedIn2() {
31654
+ try {
31655
+ return Object.keys(JSON.parse(readFileSync5(join10(dataHome(), "auth.json"), "utf8"))).length > 0;
31656
+ } catch (error51) {
31657
+ return error51.code !== "ENOENT";
31658
+ }
31659
+ }
31660
+ function declaresProvider() {
31661
+ return ["opencode.json", "opencode.jsonc", "config.json"].some((name) => {
31662
+ try {
31663
+ return /"provider"\s*:/.test(readFileSync5(join10(configHome(), name), "utf8"));
31664
+ } catch {
31665
+ return false;
31666
+ }
31667
+ });
31668
+ }
31669
+
31670
+ // agent/runners/qwen.ts
31671
+ import { randomUUID as randomUUID6 } from "crypto";
31672
+ import { readFileSync as readFileSync6 } from "fs";
31673
+ import { homedir as homedir8 } from "os";
31674
+ import { join as join11 } from "path";
31459
31675
  var MACHINE = ["Bash", "exec", "Edit", "Read", "zoom_image", "monitor", "lsp", "save_memory"];
31460
31676
  var ESCAPES = [
31461
31677
  "skill",
@@ -31476,13 +31692,13 @@ var ESCAPES = [
31476
31692
  "image_gen",
31477
31693
  "read_mcp_resource"
31478
31694
  ];
31479
- var WEB_TOOLS3 = ["web_search", "web_fetch"];
31695
+ var WEB_TOOLS4 = ["web_search", "web_fetch"];
31480
31696
  var READ_TOOL2 = "read_file";
31481
31697
  var OTHER_READS = ["grep_search", "glob", "list_directory"];
31482
31698
  var APPROVAL = "default";
31483
31699
  var DANGEROUS = /^(run_shell_command|exec|edit|write_file|notebook_edit|read_file|grep_search|glob|list_directory|agent|skill|monitor|save_memory|lsp|zoom_image|image_gen|workflow|send_message|create_sub_session|propose_goal|cron_|team_|computer_use__|omni_)/;
31484
- var qwenHome = () => process.env.QWEN_HOME || join10(homedir7(), ".qwen");
31485
- var INSTALL = "npm i -g @qwen-code/qwen-code";
31700
+ var qwenHome = () => process.env.QWEN_HOME || join11(homedir8(), ".qwen");
31701
+ var INSTALL2 = "npm i -g @qwen-code/qwen-code";
31486
31702
  var AUTH_KEYS = ["QWEN_API_KEY", "OPENAI_API_KEY", "DASHSCOPE_API_KEY"];
31487
31703
  var AUTH_PREFIX = /^(QWEN_|DASHSCOPE_|BAILIAN_|OPENAI_).*(API_KEY|TOKEN)$/;
31488
31704
  var qwenRunner = {
@@ -31492,8 +31708,8 @@ var qwenRunner = {
31492
31708
  efforts: [],
31493
31709
  // Sessions are filed under ~/.qwen/projects/<sanitized-cwd>, so a resume only finds the
31494
31710
  // conversation it began in when this stays put.
31495
- workspace: (mode) => join10(stateDir, "agents", "qwen", mode),
31496
- skillDirs: () => [join10(qwenHome(), "skills"), join10(homedir7(), ".agents", "skills")],
31711
+ workspace: (mode) => join11(stateDir, "agents", "qwen", mode),
31712
+ skillDirs: () => [join11(qwenHome(), "skills"), join11(homedir8(), ".agents", "skills")],
31497
31713
  stream(context) {
31498
31714
  const { settings, research } = context;
31499
31715
  return {
@@ -31519,14 +31735,14 @@ var qwenRunner = {
31519
31735
  // to be auto-approved by name. One entry covers every tool the server offers.
31520
31736
  "--allowed-tools",
31521
31737
  `mcp__${MCP_SERVER_NAME}`,
31522
- ...research ? WEB_TOOLS3 : [],
31738
+ ...research ? WEB_TOOLS4 : [],
31523
31739
  "--exclude-tools",
31524
31740
  ...MACHINE,
31525
31741
  ...ESCAPES,
31526
- ...research ? [] : WEB_TOOLS3,
31742
+ ...research ? [] : WEB_TOOLS4,
31527
31743
  "--append-system-prompt",
31528
31744
  context.systemPrompt,
31529
- ...context.sessionId ? ["--resume", context.sessionId] : ["--session-id", randomUUID5()],
31745
+ ...context.sessionId ? ["--resume", context.sessionId] : ["--session-id", randomUUID6()],
31530
31746
  ...settings.model ? ["--model", settings.model] : []
31531
31747
  ]
31532
31748
  };
@@ -31562,7 +31778,7 @@ var qwenRunner = {
31562
31778
  }
31563
31779
  if (event?.type === "content_block_start" && event.content_block?.type === "tool_use") {
31564
31780
  const name = event.content_block.name ?? "tool";
31565
- if (WEB_TOOLS3.includes(name)) sink.tool(event.content_block.id ?? randomUUID5(), name);
31781
+ if (WEB_TOOLS4.includes(name)) sink.tool(event.content_block.id ?? randomUUID6(), name);
31566
31782
  }
31567
31783
  return;
31568
31784
  }
@@ -31576,7 +31792,7 @@ var qwenRunner = {
31576
31792
  if (message.is_error) {
31577
31793
  return sink.fail(
31578
31794
  "AGENT_FAILED",
31579
- explain3(message.error?.message) ?? (message.result?.trim() || "Qwen Code reported an error")
31795
+ explain4(message.error?.message) ?? (message.result?.trim() || "Qwen Code reported an error")
31580
31796
  );
31581
31797
  }
31582
31798
  return sink.done(message.subtype === "success" ? "end_turn" : message.subtype || "end_turn");
@@ -31604,7 +31820,7 @@ var qwenRunner = {
31604
31820
  "--exclude-tools",
31605
31821
  ...denied,
31606
31822
  ...ESCAPES,
31607
- ...WEB_TOOLS3,
31823
+ ...WEB_TOOLS4,
31608
31824
  ...settings.model ? ["--model", settings.model] : []
31609
31825
  ]
31610
31826
  };
@@ -31613,7 +31829,7 @@ var qwenRunner = {
31613
31829
  const answer = lastResult2(stdout);
31614
31830
  if (!answer) return {};
31615
31831
  if (answer.is_error) {
31616
- return { error: explain3(answer.error?.message) ?? (answer.result?.trim() || "Qwen Code reported an error") };
31832
+ return { error: explain4(answer.error?.message) ?? (answer.result?.trim() || "Qwen Code reported an error") };
31617
31833
  }
31618
31834
  return { text: typeof answer.result === "string" ? answer.result : void 0 };
31619
31835
  },
@@ -31626,7 +31842,7 @@ var qwenRunner = {
31626
31842
  return `Qwen Code refused the session id Browsentic minted. This is a bug in Browsentic \u2014 please report it. (${tail})`;
31627
31843
  }
31628
31844
  if (/unknown argument|unknown option|invalid values|not a valid choice/i.test(tail)) {
31629
- return `Your Qwen Code does not understand the flags Browsentic uses. Update it (${INSTALL}), then try again. (${tail})`;
31845
+ return `Your Qwen Code does not understand the flags Browsentic uses. Update it (${INSTALL2}), then try again. (${tail})`;
31630
31846
  }
31631
31847
  return null;
31632
31848
  },
@@ -31649,7 +31865,7 @@ function escaped(init) {
31649
31865
  if (live.length) {
31650
31866
  return `Qwen Code registered ${live.join(", ")} for this run, which Browsentic denied, so the run was stopped before the model saw them. Update Qwen Code and Browsentic; if it persists, please report it.`;
31651
31867
  }
31652
- const others = (init.mcp_servers ?? []).map((server) => server.name).filter((name) => name && name !== MCP_SERVER_NAME);
31868
+ const others = (init.mcp_servers ?? []).map((server2) => server2.name).filter((name) => name && name !== MCP_SERVER_NAME);
31653
31869
  if (others.length) {
31654
31870
  return `Qwen Code loaded the MCP server${others.length > 1 ? "s" : ""} ${others.join(", ")} beside Browsentic's own, which would reach the browser outside this run's gate, so the run was stopped. Update Qwen Code and Browsentic; if it persists, please report it.`;
31655
31871
  }
@@ -31658,27 +31874,27 @@ function escaped(init) {
31658
31874
  }
31659
31875
  return void 0;
31660
31876
  }
31661
- function explain3(message) {
31877
+ function explain4(message) {
31662
31878
  if (!message) return void 0;
31663
31879
  if (/No auth type is selected|API key not found/i.test(message)) {
31664
- return `Qwen Code has no model provider configured. Run "qwen" and use /auth, or export OPENAI_API_KEY with OPENAI_BASE_URL, then try again. (${oneLine2(message)})`;
31880
+ return `Qwen Code has no model provider configured. Run "qwen" and use /auth, or export OPENAI_API_KEY with OPENAI_BASE_URL, then try again. (${oneLine3(message)})`;
31665
31881
  }
31666
31882
  if (/qwen-oauth|Qwen OAuth/i.test(message)) {
31667
- return `Qwen OAuth's free tier was discontinued, so its requests are rejected. Configure another provider with /auth. (${oneLine2(message)})`;
31883
+ return `Qwen OAuth's free tier was discontinued, so its requests are rejected. Configure another provider with /auth. (${oneLine3(message)})`;
31668
31884
  }
31669
31885
  if (/rate limit|429|quota/i.test(message)) {
31670
- return `The provider behind Qwen Code is rate-limiting this account. Wait and try again. (${oneLine2(message)})`;
31886
+ return `The provider behind Qwen Code is rate-limiting this account. Wait and try again. (${oneLine3(message)})`;
31671
31887
  }
31672
31888
  if (/unknown model|model not found/i.test(message)) {
31673
- return `${oneLine2(message)} Pick another model for Qwen Code in the Browsentic popup, then try again.`;
31889
+ return `${oneLine3(message)} Pick another model for Qwen Code in the Browsentic popup, then try again.`;
31674
31890
  }
31675
- return oneLine2(message);
31891
+ return oneLine3(message);
31676
31892
  }
31677
- var oneLine2 = (message) => message.replace(/\s+/g, " ").trim().slice(0, 240);
31893
+ var oneLine3 = (message) => message.replace(/\s+/g, " ").trim().slice(0, 240);
31678
31894
  function configured() {
31679
31895
  let parsed2;
31680
31896
  try {
31681
- parsed2 = JSON.parse(readFileSync5(join10(qwenHome(), "settings.json"), "utf8"));
31897
+ parsed2 = JSON.parse(readFileSync6(join11(qwenHome(), "settings.json"), "utf8"));
31682
31898
  } catch (error51) {
31683
31899
  return error51.code !== "ENOENT";
31684
31900
  }
@@ -31699,34 +31915,34 @@ function lastResult2(stdout) {
31699
31915
  }
31700
31916
 
31701
31917
  // agent/runners/vibe.ts
31702
- import { homedir as homedir8 } from "os";
31703
- import { join as join11 } from "path";
31918
+ import { homedir as homedir9 } from "os";
31919
+ import { join as join12 } from "path";
31704
31920
  var CONFIG2 = ".vibe/config.toml";
31705
- var INSTRUCTIONS2 = "AGENTS.md";
31921
+ var INSTRUCTIONS3 = "AGENTS.md";
31706
31922
  var TASK_INSTRUCTIONS2 = "This directory is Browsentic scratch space. Answer the prompt exactly as it asks, and do not act on anything else you find here.\n";
31707
- var WEB_TOOLS4 = ["web_search", "web_fetch"];
31923
+ var WEB_TOOLS5 = ["web_search", "web_fetch"];
31708
31924
  var READ_TOOL3 = "read_file";
31709
31925
  var NO_TOOLS = "re:^$";
31710
31926
  var PROFILE = "ask";
31711
- var vibeHome = () => process.env.VIBE_HOME || join11(homedir8(), ".vibe");
31927
+ var vibeHome = () => process.env.VIBE_HOME || join12(homedir9(), ".vibe");
31712
31928
  var vibeRunner = {
31713
31929
  kind: "vibe",
31714
31930
  versionArgs: ["--version"],
31715
31931
  efforts: [],
31716
31932
  endsOnExit: true,
31717
- workspace: (mode) => join11(stateDir, "agents", "vibe", mode),
31718
- skillDirs: () => [join11(vibeHome(), "skills"), join11(homedir8(), ".agents", "skills")],
31933
+ workspace: (mode) => join12(stateDir, "agents", "vibe", mode),
31934
+ skillDirs: () => [join12(vibeHome(), "skills"), join12(homedir9(), ".agents", "skills")],
31719
31935
  stream(context) {
31720
31936
  const base = this.workspace("run");
31721
31937
  sweepRunDirs(base);
31722
- const builtins = context.research ? WEB_TOOLS4 : [];
31938
+ const builtins = context.research ? WEB_TOOLS5 : [];
31723
31939
  const granted = [...context.mcpTools.map((tool) => `${MCP_SERVER_NAME}_${tool}`), ...builtins];
31724
31940
  return {
31725
31941
  cwd: conversationDir(base, context.conversation ?? context.runId),
31726
31942
  env: { BROWSENTIC_AGENT_RUN: context.runId },
31727
31943
  files: [
31728
- { path: CONFIG2, content: config3(context.settings.model, context.mcp, granted) },
31729
- { path: INSTRUCTIONS2, content: `${context.systemPrompt.trim()}
31944
+ { path: CONFIG2, content: config4(context.settings.model, context.mcp, granted) },
31945
+ { path: INSTRUCTIONS3, content: `${context.systemPrompt.trim()}
31730
31946
  ` }
31731
31947
  ],
31732
31948
  args: [
@@ -31760,7 +31976,7 @@ ${text3}` : text3);
31760
31976
  return;
31761
31977
  }
31762
31978
  const tool = entry.type === "effect" ? entry.detail?.toolName : void 0;
31763
- if (tool && entry.id && !ownTool3(tool)) sink.tool(entry.id, tool);
31979
+ if (tool && entry.id && !ownTool4(tool)) sink.tool(entry.id, tool);
31764
31980
  };
31765
31981
  },
31766
31982
  json(context) {
@@ -31768,8 +31984,8 @@ ${text3}` : text3);
31768
31984
  return {
31769
31985
  cwd: this.workspace("task"),
31770
31986
  files: [
31771
- { path: CONFIG2, content: config3(context.settings.model, null, allowed) },
31772
- { path: INSTRUCTIONS2, content: TASK_INSTRUCTIONS2 }
31987
+ { path: CONFIG2, content: config4(context.settings.model, null, allowed) },
31988
+ { path: INSTRUCTIONS3, content: TASK_INSTRUCTIONS2 }
31773
31989
  ],
31774
31990
  args: [
31775
31991
  "--prompt",
@@ -31791,7 +32007,7 @@ ${text3}` : text3);
31791
32007
  },
31792
32008
  hint(stderrTail) {
31793
32009
  if (/Missing \w+ environment variable/i.test(stderrTail)) {
31794
- return `Mistral Vibe is installed but has no API key. Run "vibe --setup", or put MISTRAL_API_KEY in ${join11(vibeHome(), ".env")}, then try again. (${stderrTail.trim()})`;
32010
+ return `Mistral Vibe is installed but has no API key. Run "vibe --setup", or put MISTRAL_API_KEY in ${join12(vibeHome(), ".env")}, then try again. (${stderrTail.trim()})`;
31795
32011
  }
31796
32012
  if (/unrecognized arguments|invalid choice/i.test(stderrTail)) {
31797
32013
  return `Your Mistral Vibe does not understand the flags Browsentic uses. Update it, then try again. (${stderrTail.trim()})`;
@@ -31801,20 +32017,20 @@ ${text3}` : text3);
31801
32017
  };
31802
32018
  var enabling = (patterns) => patterns.flatMap((pattern) => ["--enabled-tools", pattern]);
31803
32019
  var textOf2 = (entry) => (entry.content ?? []).filter((block) => block.type === "text" && block.text).map((block) => block.text).join("\n\n");
31804
- var ownTool3 = (name) => name.startsWith(`${MCP_SERVER_NAME}_`);
31805
- function config3(model, server, granted) {
32020
+ var ownTool4 = (name) => name.startsWith(`${MCP_SERVER_NAME}_`);
32021
+ function config4(model, server2, granted) {
31806
32022
  const quote = (value) => JSON.stringify(value);
31807
32023
  const lines = model ? [`active_model = ${quote(model)}`, ""] : [];
31808
- if (server) {
32024
+ if (server2) {
31809
32025
  lines.push(
31810
32026
  "[[mcp_servers]]",
31811
32027
  `name = ${quote(MCP_SERVER_NAME)}`,
31812
32028
  'transport = "stdio"',
31813
- `command = [${quote(server.command)}]`,
31814
- `args = [${server.args.map(quote).join(", ")}]`,
32029
+ `command = [${quote(server2.command)}]`,
32030
+ `args = [${server2.args.map(quote).join(", ")}]`,
31815
32031
  "",
31816
32032
  "[mcp_servers.env]",
31817
- ...Object.entries(server.env).map(([name, value]) => `${name} = ${quote(value)}`),
32033
+ ...Object.entries(server2.env).map(([name, value]) => `${name} = ${quote(value)}`),
31818
32034
  ""
31819
32035
  );
31820
32036
  }
@@ -31830,26 +32046,27 @@ var RUNNERS = {
31830
32046
  vibe: vibeRunner,
31831
32047
  grok: grokRunner,
31832
32048
  cursor: cursorRunner,
31833
- qwen: qwenRunner
32049
+ qwen: qwenRunner,
32050
+ opencode: opencodeRunner
31834
32051
  };
31835
- var cliPath = join12(dirname3(fileURLToPath2(import.meta.url)), "cli.js");
32052
+ var cliPath = join13(dirname4(fileURLToPath2(import.meta.url)), "cli.js");
31836
32053
 
31837
32054
  // agent/skills.ts
31838
- import { existsSync as existsSync3, readFileSync as readFileSync6, readdirSync as readdirSync2 } from "fs";
31839
- import { homedir as homedir9 } from "os";
31840
- import { dirname as dirname4, isAbsolute, join as join13 } from "path";
32055
+ import { existsSync as existsSync3, readFileSync as readFileSync7, readdirSync as readdirSync2 } from "fs";
32056
+ import { homedir as homedir10 } from "os";
32057
+ import { dirname as dirname5, isAbsolute, join as join14 } from "path";
31841
32058
  import { fileURLToPath as fileURLToPath3 } from "url";
31842
- var bundledDir = join13(dirname4(fileURLToPath3(import.meta.url)), "..", "skills");
31843
- var userDir2 = join13(stateDir, "skills");
32059
+ var bundledDir = join14(dirname5(fileURLToPath3(import.meta.url)), "..", "skills");
32060
+ var userDir2 = join14(stateDir, "skills");
31844
32061
  function uploadedSkillsDir() {
31845
32062
  const configured2 = readAgentConfig().skillsDir;
31846
32063
  if (typeof configured2 === "string" && configured2.trim()) return expandHome(configured2.trim());
31847
- return join13(homedir9(), "browsentic", "skills");
32064
+ return join14(homedir10(), "browsentic", "skills");
31848
32065
  }
31849
32066
  function expandHome(p) {
31850
- if (p === "~") return homedir9();
31851
- if (p.startsWith("~/")) return join13(homedir9(), p.slice(2));
31852
- return isAbsolute(p) ? p : join13(homedir9(), p);
32067
+ if (p === "~") return homedir10();
32068
+ if (p.startsWith("~/")) return join14(homedir10(), p.slice(2));
32069
+ return isAbsolute(p) ? p : join14(homedir10(), p);
31853
32070
  }
31854
32071
  function skillDirs() {
31855
32072
  return [
@@ -31875,7 +32092,7 @@ function readDir(dir, source) {
31875
32092
  try {
31876
32093
  const entries = readdirSync2(dir, { withFileTypes: true }).filter((entry) => !entry.name.startsWith("."));
31877
32094
  files = entries.filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => entry.name);
31878
- directories = entries.filter((entry) => entry.isDirectory() && existsSync3(join13(dir, entry.name, SKILL_FILE))).map((entry) => entry.name);
32095
+ directories = entries.filter((entry) => entry.isDirectory() && existsSync3(join14(dir, entry.name, SKILL_FILE))).map((entry) => entry.name);
31879
32096
  } catch {
31880
32097
  return [];
31881
32098
  }
@@ -31886,13 +32103,13 @@ function readDir(dir, source) {
31886
32103
  log(`skill "${name}" exists as both ${name}.md and ${name}/${SKILL_FILE} in ${dir}; using the file`);
31887
32104
  continue;
31888
32105
  }
31889
- push(join13(dir, name, SKILL_FILE), name);
32106
+ push(join14(dir, name, SKILL_FILE), name);
31890
32107
  }
31891
- for (const file2 of files) push(join13(dir, file2), file2.replace(/\.md$/, ""));
32108
+ for (const file2 of files) push(join14(dir, file2), file2.replace(/\.md$/, ""));
31892
32109
  return skills;
31893
32110
  function push(path, fallbackName) {
31894
32111
  try {
31895
- const skill = parseSkill(readFileSync6(path, "utf8"), fallbackName, source);
32112
+ const skill = parseSkill(readFileSync7(path, "utf8"), fallbackName, source);
31896
32113
  if (!skill.body.trim()) log(`skill ${path} has no body; ignoring`);
31897
32114
  else {
31898
32115
  if (skill.category === "site-exploration" && !skill.domains.length) {
@@ -31929,8 +32146,8 @@ var MAX_SKILL_BYTES = 48 * 1024;
31929
32146
  var TTL_MS = 3e4;
31930
32147
  var cached2 = null;
31931
32148
  var known = /* @__PURE__ */ new Map();
31932
- function agentSkills(config4, { refresh = false } = {}) {
31933
- const agent = config4.agent;
32149
+ function agentSkills(config5, { refresh = false } = {}) {
32150
+ const agent = config5.agent;
31934
32151
  const dirs = RUNNERS[agent].skillDirs?.() ?? [];
31935
32152
  const signature = dirs.join("\n");
31936
32153
  if (!refresh && cached2 && cached2.agent === agent && cached2.dirs === signature && Date.now() - cached2.at < TTL_MS) {
@@ -31957,11 +32174,11 @@ function scan(dir, agent, out) {
31957
32174
  return;
31958
32175
  }
31959
32176
  for (const entry of entries) {
31960
- const path = entry.name.endsWith(".md") ? join14(dir, entry.name) : join14(dir, entry.name, SKILL_FILE);
32177
+ const path = entry.name.endsWith(".md") ? join15(dir, entry.name) : join15(dir, entry.name, SKILL_FILE);
31961
32178
  try {
31962
32179
  const stats = statSync3(path);
31963
32180
  if (!stats.isFile() || stats.size > MAX_SKILL_BYTES) continue;
31964
- const { fields, body } = splitFrontMatter(readFileSync7(path, "utf8"));
32181
+ const { fields, body } = splitFrontMatter(readFileSync8(path, "utf8"));
31965
32182
  if (!body.trim()) continue;
31966
32183
  const name = clean(unquote(fields.name) || entry.name.replace(/\.md$/, ""), MAX_NAME);
31967
32184
  if (!name) continue;
@@ -31982,12 +32199,12 @@ function idOf(path) {
31982
32199
  }
31983
32200
 
31984
32201
  // agent/approvals.ts
31985
- import { chmodSync as chmodSync2, mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
31986
- import { join as join15 } from "path";
31987
- var approvalsPath = join15(stateDir, "approvals.json");
32202
+ import { chmodSync as chmodSync2, mkdirSync as mkdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "fs";
32203
+ import { join as join16 } from "path";
32204
+ var approvalsPath = join16(stateDir, "approvals.json");
31988
32205
  function read() {
31989
32206
  try {
31990
- const parsed2 = JSON.parse(readFileSync8(approvalsPath, "utf8"));
32207
+ const parsed2 = JSON.parse(readFileSync9(approvalsPath, "utf8"));
31991
32208
  if (!Array.isArray(parsed2.grants)) return [];
31992
32209
  return parsed2.grants.filter(
31993
32210
  (grant) => !!grant && typeof grant.action === "string" && typeof grant.host === "string" && typeof grant.at === "string"
@@ -32013,21 +32230,21 @@ function forgetGrants(host) {
32013
32230
  }
32014
32231
 
32015
32232
  // downloads.ts
32016
- import { randomUUID as randomUUID6 } from "crypto";
32233
+ import { randomUUID as randomUUID7 } from "crypto";
32017
32234
  import {
32018
32235
  chmodSync as chmodSync3,
32019
32236
  copyFileSync,
32020
32237
  existsSync as existsSync4,
32021
32238
  mkdirSync as mkdirSync6,
32022
- readFileSync as readFileSync9,
32239
+ readFileSync as readFileSync10,
32023
32240
  renameSync as renameSync2,
32024
32241
  rmSync as rmSync3,
32025
32242
  statSync as statSync4,
32026
32243
  unlinkSync,
32027
32244
  writeFileSync as writeFileSync5
32028
32245
  } from "fs";
32029
- import { homedir as homedir10 } from "os";
32030
- import { basename, isAbsolute as isAbsolute2, join as join16 } from "path";
32246
+ import { homedir as homedir11 } from "os";
32247
+ import { basename, isAbsolute as isAbsolute2, join as join17 } from "path";
32031
32248
 
32032
32249
  // ../lib/downloads/limits.ts
32033
32250
  var MAX_DOWNLOAD_BYTES = 100 * 1024 * 1024;
@@ -32471,8 +32688,8 @@ var DEFAULT_FENCE = {
32471
32688
  // image-specific renderer instead.
32472
32689
  except: ["page.closeTab", "page.stopMonitor", "page.screenshot"]
32473
32690
  };
32474
- function policyFrom(config4 = {}, requireApproval = [SUBMIT_ACTION]) {
32475
- const overrides = config4.rules ?? {};
32691
+ function policyFrom(config5 = {}, requireApproval = [SUBMIT_ACTION]) {
32692
+ const overrides = config5.rules ?? {};
32476
32693
  const rules = DEFAULT_RULES.map((rule) => {
32477
32694
  const legacy = rule.id === "form-submission" && !requireApproval.includes(SUBMIT_ACTION) ? "allow" : rule.effect;
32478
32695
  return { ...rule, effect: overrides[rule.id] ?? legacy };
@@ -32480,9 +32697,9 @@ function policyFrom(config4 = {}, requireApproval = [SUBMIT_ACTION]) {
32480
32697
  return {
32481
32698
  rules,
32482
32699
  requireApproval,
32483
- unattended: config4.unattended === "allow" ? "allow" : "deny",
32484
- urlPayloadBytes: typeof config4.urlPayloadBytes === "number" && config4.urlPayloadBytes >= 0 ? config4.urlPayloadBytes : DEFAULT_URL_PAYLOAD_BYTES,
32485
- fence: config4.fence === false ? { ...DEFAULT_FENCE, enabled: false } : DEFAULT_FENCE
32700
+ unattended: config5.unattended === "allow" ? "allow" : "deny",
32701
+ urlPayloadBytes: typeof config5.urlPayloadBytes === "number" && config5.urlPayloadBytes >= 0 ? config5.urlPayloadBytes : DEFAULT_URL_PAYLOAD_BYTES,
32702
+ fence: config5.fence === false ? { ...DEFAULT_FENCE, enabled: false } : DEFAULT_FENCE
32486
32703
  };
32487
32704
  }
32488
32705
  var POLICY = policyFrom();
@@ -32529,6 +32746,8 @@ var RULE_IDS = new Set(DEFAULT_RULES.map((rule) => rule.id));
32529
32746
  var NEVER2 = ["Bash", "Edit", "Write", "NotebookEdit", "Glob", "Grep", "Task"];
32530
32747
  var NEVER_QWEN = ["Bash", "exec", "Edit", "agent", "skill", "monitor"];
32531
32748
  var GROK_SEALED = { GROK_MEMORY: "0", GROK_CLAUDE_MCPS_ENABLED: "false", GROK_CURSOR_MCPS_ENABLED: "false" };
32749
+ var OPENCODE_SEALED = { OPENCODE_DISABLE_PROJECT_CONFIG: "1", OPENCODE_DISABLE_CLAUDE_CODE: "1", OPENCODE_DISABLE_SHARE: "1" };
32750
+ var OPENCODE_CONFIG = ['"*":"deny"', '"share":"disabled"'];
32532
32751
  var CONTAINMENT = {
32533
32752
  claude: {
32534
32753
  localTools: "allowlist",
@@ -32698,24 +32917,55 @@ var CONTAINMENT = {
32698
32917
  denies: { flag: "--exclude-tools", tools: NEVER_QWEN },
32699
32918
  files: []
32700
32919
  }
32920
+ },
32921
+ opencode: {
32922
+ localTools: "allowlist",
32923
+ // A key in ANTHROPIC_* or OPENAI_* would be one of a dozen providers OpenCode reads; `opencode auth login` keeps them in a file.
32924
+ keepsEnv: ["OPENCODE_"],
32925
+ // `--auto` approves whatever is not denied, `--attach` runs the turn in a server started with someone
32926
+ // else's config, `--dir` moves it out of the workspace, and `--share` publishes it.
32927
+ forbidden: [/^--auto$/i, /^--attach/i, /^--dir/i, /^--share$/i],
32928
+ note: 'a per-run permission ruleset opening on "*": "deny", which OpenCode applies after the user\u2019s own rules, so the model is offered no tool that was not named \u2014 built-in, custom, or another MCP server\u2019s; external plugins and project config stay off, but MCP servers the user gave OpenCode itself still start, with their tools hidden',
32929
+ run: {
32930
+ // Plugins run inside OpenCode and can add tools or answer its permission prompts.
32931
+ required: ["--pure"],
32932
+ pairs: [
32933
+ ["--agent", "browsentic-contained"],
32934
+ ["--format", "json"]
32935
+ ],
32936
+ env: OPENCODE_SEALED,
32937
+ envContains: { OPENCODE_CONFIG_CONTENT: OPENCODE_CONFIG },
32938
+ files: ["instructions.md"]
32939
+ },
32940
+ task: {
32941
+ required: ["--pure"],
32942
+ pairs: [
32943
+ ["--agent", "browsentic-contained"],
32944
+ ["--format", "json"]
32945
+ ],
32946
+ env: OPENCODE_SEALED,
32947
+ // An empty map adds no server of ours, so a one-shot has no way to the browser.
32948
+ envContains: { OPENCODE_CONFIG_CONTENT: [...OPENCODE_CONFIG, '"mcp":{}'] },
32949
+ files: []
32950
+ }
32701
32951
  }
32702
32952
  };
32703
32953
 
32704
32954
  // downloads.ts
32705
- var indexPath = join16(stateDir, "downloads.json");
32955
+ var indexPath = join17(stateDir, "downloads.json");
32706
32956
  function downloadDir() {
32707
32957
  const configured2 = readAgentConfig().downloadDir;
32708
32958
  if (typeof configured2 === "string" && configured2.trim()) return expandHome2(configured2.trim());
32709
- return join16(homedir10(), "browsentic", "download");
32959
+ return join17(homedir11(), "browsentic", "download");
32710
32960
  }
32711
32961
  function expandHome2(p) {
32712
- if (p === "~") return homedir10();
32713
- if (p.startsWith("~/")) return join16(homedir10(), p.slice(2));
32714
- return isAbsolute2(p) ? p : join16(homedir10(), p);
32962
+ if (p === "~") return homedir11();
32963
+ if (p.startsWith("~/")) return join17(homedir11(), p.slice(2));
32964
+ return isAbsolute2(p) ? p : join17(homedir11(), p);
32715
32965
  }
32716
32966
  function readIndex() {
32717
32967
  try {
32718
- const parsed2 = JSON.parse(readFileSync9(indexPath, "utf8"));
32968
+ const parsed2 = JSON.parse(readFileSync10(indexPath, "utf8"));
32719
32969
  return Array.isArray(parsed2) ? parsed2 : [];
32720
32970
  } catch {
32721
32971
  return [];
@@ -32750,7 +33000,7 @@ var HEAD_BYTES = 64 * 1024;
32750
33000
  // ensure-daemon.ts
32751
33001
  import { spawn as spawn3 } from "child_process";
32752
33002
  import { fileURLToPath as fileURLToPath4 } from "url";
32753
- import { dirname as dirname5, join as join17 } from "path";
33003
+ import { dirname as dirname6, join as join18 } from "path";
32754
33004
 
32755
33005
  // ports.ts
32756
33006
  var daemonPorts = parsePorts(process.env.BROWSENTIC_PORTS) ?? DAEMON_PORTS;
@@ -32770,7 +33020,7 @@ async function ensureDaemon() {
32770
33020
  const existing = await probeExisting();
32771
33021
  if (existing) return existing;
32772
33022
  log("no daemon reachable; spawning one");
32773
- const daemonMain = join17(dirname5(fileURLToPath4(import.meta.url)), "daemon-main.js");
33023
+ const daemonMain = join18(dirname6(fileURLToPath4(import.meta.url)), "daemon-main.js");
32774
33024
  const env = { ...process.env };
32775
33025
  delete env.BROWSENTIC_AGENT_RUN;
32776
33026
  delete env.CLAUDECODE;
@@ -32855,21 +33105,21 @@ import {
32855
33105
  chmodSync as chmodSync4,
32856
33106
  existsSync as existsSync5,
32857
33107
  mkdirSync as mkdirSync7,
32858
- readFileSync as readFileSync10,
33108
+ readFileSync as readFileSync11,
32859
33109
  readdirSync as readdirSync4,
32860
33110
  renameSync as renameSync3,
32861
33111
  rmSync as rmSync4,
32862
33112
  statSync as statSync5,
32863
33113
  writeFileSync as writeFileSync6
32864
33114
  } from "fs";
32865
- import { join as join18, relative } from "path";
33115
+ import { join as join19, relative as relative2 } from "path";
32866
33116
  function walk(dir, base = dir) {
32867
33117
  return readdirSync4(dir, { withFileTypes: true }).flatMap((entry) => {
32868
- const full = join18(dir, entry.name);
32869
- return entry.isDirectory() ? walk(full, base) : [relative(base, full)];
33118
+ const full = join19(dir, entry.name);
33119
+ return entry.isDirectory() ? walk(full, base) : [relative2(base, full)];
32870
33120
  });
32871
33121
  }
32872
- var hash2 = (path) => createHash2("sha256").update(readFileSync10(path)).digest("hex");
33122
+ var hash2 = (path) => createHash2("sha256").update(readFileSync11(path)).digest("hex");
32873
33123
  function sameContent(a, b) {
32874
33124
  try {
32875
33125
  if (statSync5(a).size !== statSync5(b).size) return false;
@@ -32880,7 +33130,7 @@ function sameContent(a, b) {
32880
33130
  }
32881
33131
  function readStamp(dir) {
32882
33132
  try {
32883
- return JSON.parse(readFileSync10(installStampPath(dir), "utf8"));
33133
+ return JSON.parse(readFileSync11(installStampPath(dir), "utf8"));
32884
33134
  } catch {
32885
33135
  return null;
32886
33136
  }
@@ -32900,8 +33150,8 @@ function install(dir, force = false) {
32900
33150
  "Reinstall with `npm i -g browsentic`, or run `yarn build` if you are in a source checkout."
32901
33151
  );
32902
33152
  }
32903
- const manifestPath = join18(packaged.dir, "manifest.json");
32904
- const version2 = JSON.parse(readFileSync10(manifestPath, "utf8")).version;
33153
+ const manifestPath = join19(packaged.dir, "manifest.json");
33154
+ const version2 = JSON.parse(readFileSync11(manifestPath, "utf8")).version;
32905
33155
  const stamp = readStamp(dir);
32906
33156
  if (!force && stamp?.version === version2 && existsSync5(manifestPath)) {
32907
33157
  return {
@@ -32916,18 +33166,18 @@ function install(dir, force = false) {
32916
33166
  const sources = walk(packaged.dir);
32917
33167
  mkdirSync7(dir, { recursive: true, mode: 493 });
32918
33168
  for (const stale of walk(dir).filter((f) => /\.tmp-\d+$/.test(f))) {
32919
- rmSync4(join18(dir, stale), { force: true });
33169
+ rmSync4(join19(dir, stale), { force: true });
32920
33170
  }
32921
33171
  const ordered = [...sources.filter((f) => f !== "manifest.json"), "manifest.json"];
32922
33172
  let changed = 0;
32923
33173
  for (const rel of ordered) {
32924
- const from = join18(packaged.dir, rel);
32925
- const to = join18(dir, rel);
33174
+ const from = join19(packaged.dir, rel);
33175
+ const to = join19(dir, rel);
32926
33176
  if (!force && sameContent(from, to)) continue;
32927
- mkdirSync7(join18(to, ".."), { recursive: true, mode: 493 });
33177
+ mkdirSync7(join19(to, ".."), { recursive: true, mode: 493 });
32928
33178
  const tmp = `${to}.tmp-${process.pid}`;
32929
33179
  try {
32930
- writeFileSync6(tmp, readFileSync10(from), { mode: 420 });
33180
+ writeFileSync6(tmp, readFileSync11(from), { mode: 420 });
32931
33181
  chmodSync4(tmp, 420);
32932
33182
  renameSync3(tmp, to);
32933
33183
  changed++;
@@ -32946,7 +33196,7 @@ function install(dir, force = false) {
32946
33196
  const wanted = new Set(sources);
32947
33197
  for (const rel of walk(dir)) {
32948
33198
  if (wanted.has(rel) || rel === ".browsentic-install.json") continue;
32949
- rmSync4(join18(dir, rel), { force: true });
33199
+ rmSync4(join19(dir, rel), { force: true });
32950
33200
  }
32951
33201
  const record2 = {
32952
33202
  version: version2,
@@ -32960,17 +33210,17 @@ function install(dir, force = false) {
32960
33210
  }
32961
33211
 
32962
33212
  // npx.ts
32963
- import { existsSync as existsSync6, readFileSync as readFileSync11, readdirSync as readdirSync5, realpathSync } from "fs";
32964
- import { homedir as homedir11 } from "os";
32965
- import { dirname as dirname6, join as join19, resolve, sep as sep2 } from "path";
33213
+ import { existsSync as existsSync6, readFileSync as readFileSync12, readdirSync as readdirSync5, realpathSync } from "fs";
33214
+ import { homedir as homedir12 } from "os";
33215
+ import { dirname as dirname7, join as join20, resolve, sep as sep2 } from "path";
32966
33216
  import { fileURLToPath as fileURLToPath5 } from "url";
32967
- var packageRoot = resolve(dirname6(fileURLToPath5(import.meta.url)), "..");
33217
+ var packageRoot = resolve(dirname7(fileURLToPath5(import.meta.url)), "..");
32968
33218
  var APP_MARKER = ".browsentic-app.json";
32969
33219
  var inNpxCache = (path) => path.split(sep2).includes("_npx");
32970
33220
  function installKind() {
32971
33221
  if (inNpxCache(packageRoot)) return "npx";
32972
- if (existsSync6(join19(packageRoot, APP_MARKER))) return "app";
32973
- if (existsSync6(join19(packageRoot, "tsup.config.ts"))) return "repo";
33222
+ if (existsSync6(join20(packageRoot, APP_MARKER))) return "app";
33223
+ if (existsSync6(join20(packageRoot, "tsup.config.ts"))) return "repo";
32974
33224
  return "global";
32975
33225
  }
32976
33226
  function real(path) {
@@ -32983,14 +33233,14 @@ function real(path) {
32983
33233
  function cacheRoots() {
32984
33234
  const roots = [
32985
33235
  process.env.npm_config_cache,
32986
- process.platform === "win32" ? join19(process.env.LOCALAPPDATA ?? homedir11(), "npm-cache") : null,
32987
- join19(homedir11(), ".npm")
33236
+ process.platform === "win32" ? join20(process.env.LOCALAPPDATA ?? homedir12(), "npm-cache") : null,
33237
+ join20(homedir12(), ".npm")
32988
33238
  ].filter((root) => !!root);
32989
- return [...new Set(roots.map((root) => join19(root, "_npx")))];
33239
+ return [...new Set(roots.map((root) => join20(root, "_npx")))];
32990
33240
  }
32991
33241
  function readJson(path) {
32992
33242
  try {
32993
- return JSON.parse(readFileSync11(path, "utf8"));
33243
+ return JSON.parse(readFileSync12(path, "utf8"));
32994
33244
  } catch {
32995
33245
  return null;
32996
33246
  }
@@ -32999,7 +33249,7 @@ function npxEntries() {
32999
33249
  const own = inNpxCache(packageRoot) ? real(resolve(packageRoot, "..", "..")) : null;
33000
33250
  const scanned = cacheRoots().flatMap((root) => {
33001
33251
  try {
33002
- return readdirSync5(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => real(join19(root, entry.name)));
33252
+ return readdirSync5(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => real(join20(root, entry.name)));
33003
33253
  } catch {
33004
33254
  return [];
33005
33255
  }
@@ -33007,9 +33257,9 @@ function npxEntries() {
33007
33257
  const entries = /* @__PURE__ */ new Map();
33008
33258
  for (const dir of [...scanned, ...own ? [own] : []]) {
33009
33259
  if (entries.has(dir)) continue;
33010
- const manifest = readJson(join19(dir, "node_modules", "browsentic", "package.json"));
33260
+ const manifest = readJson(join20(dir, "node_modules", "browsentic", "package.json"));
33011
33261
  if (!manifest) continue;
33012
- const requested = readJson(join19(dir, "package.json"))?._npx;
33262
+ const requested = readJson(join20(dir, "package.json"))?._npx;
33013
33263
  entries.set(dir, {
33014
33264
  dir,
33015
33265
  version: typeof manifest.version === "string" ? manifest.version : null,
@@ -33042,7 +33292,7 @@ async function signedAddonAttached(version2, timeoutMs = 4e3) {
33042
33292
  }
33043
33293
 
33044
33294
  // remote-bridge.ts
33045
- import { randomUUID as randomUUID7 } from "crypto";
33295
+ import { randomUUID as randomUUID8 } from "crypto";
33046
33296
 
33047
33297
  // node_modules/ws/wrapper.mjs
33048
33298
  var import_stream2 = __toESM(require_stream(), 1);
@@ -33076,40 +33326,40 @@ var RemoteBridge = class _RemoteBridge {
33076
33326
  });
33077
33327
  }
33078
33328
  async describe() {
33079
- const reply = await this.request({ id: randomUUID7(), op: "describe", runId: this.runId });
33329
+ const reply = await this.request({ id: randomUUID8(), op: "describe", runId: this.runId });
33080
33330
  return reply && "tools" in reply ? { tools: reply.tools, reserved: reply.reserved } : { tools: [] };
33081
33331
  }
33082
33332
  async invoke(action, input2) {
33083
33333
  const reply = await this.request(
33084
- { id: randomUUID7(), op: "invoke", action, input: input2, runId: this.runId },
33334
+ { id: randomUUID8(), op: "invoke", action, input: input2, runId: this.runId },
33085
33335
  invokeTimeoutFor(action, input2)
33086
33336
  );
33087
33337
  if (reply && "result" in reply) return reply.result;
33088
33338
  return failure("DAEMON_UNREACHABLE", "The Browsentic daemon did not respond");
33089
33339
  }
33090
33340
  async status() {
33091
- const reply = await this.request({ id: randomUUID7(), op: "status" });
33341
+ const reply = await this.request({ id: randomUUID8(), op: "status" });
33092
33342
  if (reply && "status" in reply) return reply.status;
33093
33343
  throw new Error("The Browsentic daemon did not respond to a status request");
33094
33344
  }
33095
33345
  async pair() {
33096
- const reply = await this.request({ id: randomUUID7(), op: "pair" });
33346
+ const reply = await this.request({ id: randomUUID8(), op: "pair" });
33097
33347
  if (reply && "code" in reply) return reply;
33098
33348
  throw new Error("The Browsentic daemon did not issue a pairing code");
33099
33349
  }
33100
33350
  async sessions() {
33101
- const reply = await this.request({ id: randomUUID7(), op: "sessions" });
33351
+ const reply = await this.request({ id: randomUUID8(), op: "sessions" });
33102
33352
  return reply && "sessions" in reply ? reply.sessions : [];
33103
33353
  }
33104
33354
  async agent(change) {
33105
- const reply = await this.request({ id: randomUUID7(), op: "agent", ...change });
33355
+ const reply = await this.request({ id: randomUUID8(), op: "agent", ...change });
33106
33356
  if (reply && "state" in reply) return reply.state;
33107
33357
  throw new Error("The Browsentic daemon did not answer about its agent");
33108
33358
  }
33109
33359
  /** A browser is named by its session id; an origin still unpairs every browser presenting it. */
33110
33360
  async revoke(browser) {
33111
33361
  const named = browser?.includes("://") ? { origin: browser } : { session: browser };
33112
- const reply = await this.request({ id: randomUUID7(), op: "revoke", ...named });
33362
+ const reply = await this.request({ id: randomUUID8(), op: "revoke", ...named });
33113
33363
  return reply && "revoked" in reply ? reply.revoked : 0;
33114
33364
  }
33115
33365
  onManifestChanged(listener) {
@@ -35089,14 +35339,14 @@ var RESERVED_TOOLS = [
35089
35339
  function createMcpServer(bridge, version2, opts = {}) {
35090
35340
  const policy = policyFrom(readAgentConfig().guardrails);
35091
35341
  const tag2 = fenceTag();
35092
- const server = new Server(
35342
+ const server2 = new Server(
35093
35343
  { name: "browsentic", version: version2 },
35094
35344
  {
35095
35345
  capabilities: { tools: { listChanged: true }, resources: {} },
35096
35346
  instructions: "Controls the user\u2019s browser through the Browsentic extension. Tools act on the active tab. Start with page_getPageInfo (or the browsentic://page/diagram resource) to learn what is on the page and get stable selectors, then target elements by selector or visible text. page_screenshot hands the image back to you in the result and writes nothing to disk, so captures you take to see the page for yourself leave no files behind. Pass save: true only when the user asked for a picture they can keep; then read the returned savedTo path back so the image renders, and include that path in your reply. Passwords, keys, tokens and cookies are replaced in every result by a sealed placeholder such as \u27E6password:4f2a@example.com\u27E7; the real value stays in the browser. Pass a placeholder through unchanged as page_fillInput\u2019s value or page_typeText\u2019s text and it becomes the credential at the moment it reaches the field. Anywhere else it is refused, and it is never yours to read, rebuild or repeat."
35097
35347
  }
35098
35348
  );
35099
- server.setRequestHandler(ListToolsRequestSchema, async () => {
35349
+ server2.setRequestHandler(ListToolsRequestSchema, async () => {
35100
35350
  const { tools: actions2, reserved = opts.agentRun ? RESERVED_TOOLS.map((tool) => tool.action) : [] } = await bridge.describe();
35101
35351
  assertToolNamesRoundTrip([...actions2.map((action) => action.name), ...RESERVED_ACTIONS]);
35102
35352
  return {
@@ -35115,7 +35365,7 @@ function createMcpServer(bridge, version2, opts = {}) {
35115
35365
  ]
35116
35366
  };
35117
35367
  });
35118
- server.setRequestHandler(CallToolRequestSchema, async ({ params }) => {
35368
+ server2.setRequestHandler(CallToolRequestSchema, async ({ params }) => {
35119
35369
  if (params.name === STATUS_TOOL) return render(await status(bridge));
35120
35370
  const action = actionNameFor(params.name);
35121
35371
  const result = await bridge.invoke(action, params.arguments ?? {});
@@ -35125,8 +35375,8 @@ function createMcpServer(bridge, version2, opts = {}) {
35125
35375
  if (params.name === CAPTCHA_TOOL) return renderCaptcha(result, shouldFence(action, policy) ? tag2 : void 0);
35126
35376
  return render(result, shouldFence(action, policy) ? tag2 : void 0);
35127
35377
  });
35128
- server.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources: [...RESOURCES] }));
35129
- server.setRequestHandler(ReadResourceRequestSchema, async ({ params }) => {
35378
+ server2.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources: [...RESOURCES] }));
35379
+ server2.setRequestHandler(ReadResourceRequestSchema, async ({ params }) => {
35130
35380
  const { uri } = params;
35131
35381
  const resource = RESOURCES.find((candidate) => candidate.uri === uri);
35132
35382
  if (!resource) throw new Error(`Unknown resource: ${uri}`);
@@ -35146,9 +35396,9 @@ function createMcpServer(bridge, version2, opts = {}) {
35146
35396
  });
35147
35397
  bridge.onManifestChanged(() => {
35148
35398
  log("manifest changed; notifying MCP client");
35149
- void server.sendToolListChanged().catch((error51) => log("failed to notify tool list change", error51));
35399
+ void server2.sendToolListChanged().catch((error51) => log("failed to notify tool list change", error51));
35150
35400
  });
35151
- return server;
35401
+ return server2;
35152
35402
  }
35153
35403
  async function status(bridge) {
35154
35404
  const base = await bridge.status();
@@ -35275,32 +35525,32 @@ function text2(uri, mimeType, body) {
35275
35525
 
35276
35526
  // uninstall.ts
35277
35527
  import { existsSync as existsSync7, readdirSync as readdirSync6, rmSync as rmSync6 } from "fs";
35278
- import { isAbsolute as isAbsolute4, join as join21, relative as relative2 } from "path";
35528
+ import { isAbsolute as isAbsolute4, join as join22, relative as relative3 } from "path";
35279
35529
 
35280
35530
  // screenshots.ts
35281
35531
  import { randomBytes as randomBytes3 } from "crypto";
35282
35532
  import { chmodSync as chmodSync5, mkdirSync as mkdirSync8, writeFileSync as writeFileSync7 } from "fs";
35283
- import { homedir as homedir12 } from "os";
35284
- import { basename as basename2, isAbsolute as isAbsolute3, join as join20, resolve as resolve2, sep as sep3 } from "path";
35533
+ import { homedir as homedir13 } from "os";
35534
+ import { basename as basename2, isAbsolute as isAbsolute3, join as join21, resolve as resolve2, sep as sep3 } from "path";
35285
35535
  function screenshotDir() {
35286
35536
  const configured2 = readAgentConfig().screenshotDir;
35287
35537
  if (typeof configured2 === "string" && configured2.trim()) return expandHome3(configured2.trim());
35288
- return join20(homedir12(), "browsentic", "screenshot");
35538
+ return join21(homedir13(), "browsentic", "screenshot");
35289
35539
  }
35290
35540
  function expandHome3(p) {
35291
- if (p === "~") return homedir12();
35292
- if (p.startsWith("~/")) return join20(homedir12(), p.slice(2));
35293
- return isAbsolute3(p) ? p : join20(homedir12(), p);
35541
+ if (p === "~") return homedir13();
35542
+ if (p.startsWith("~/")) return join21(homedir13(), p.slice(2));
35543
+ return isAbsolute3(p) ? p : join21(homedir13(), p);
35294
35544
  }
35295
35545
 
35296
35546
  // uninstall.ts
35297
35547
  function contains(root, path) {
35298
- const inside = relative2(root, path);
35548
+ const inside = relative3(root, path);
35299
35549
  return !inside.startsWith("..") && !isAbsolute4(inside);
35300
35550
  }
35301
35551
  function planUninstall(options = {}) {
35302
- const config4 = readAgentConfig();
35303
- const extension2 = extensionDir(config4.extensionDir);
35552
+ const config5 = readAgentConfig();
35553
+ const extension2 = extensionDir(config5.extensionDir);
35304
35554
  const roots = [stateDir, userDir];
35305
35555
  const removals = [];
35306
35556
  if (!roots.some((root) => contains(root, extension2)) && existsSync7(extension2)) {
@@ -35346,7 +35596,7 @@ function keepingSome(dir, keep) {
35346
35596
  const kept = [];
35347
35597
  for (const entry of readdirSync6(dir)) {
35348
35598
  if (keep.includes(entry)) kept.push(entry);
35349
- else rmSync6(join21(dir, entry), { recursive: true, force: true });
35599
+ else rmSync6(join22(dir, entry), { recursive: true, force: true });
35350
35600
  }
35351
35601
  return kept;
35352
35602
  }
@@ -35364,7 +35614,8 @@ function purgeNpxCache(entries) {
35364
35614
  // package.json
35365
35615
  var package_default = {
35366
35616
  name: "browsentic",
35367
- version: "0.7.4",
35617
+ mcpName: "io.github.imshaikot/browsentic",
35618
+ version: "0.7.5",
35368
35619
  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.",
35369
35620
  type: "module",
35370
35621
  license: "MIT",
@@ -35434,7 +35685,7 @@ var USAGE = `browsentic ${package_default.version} \u2014 hand your real browser
35434
35685
  browsentic revoke [id] unpair one browser by the id "sessions" prints, or all of them
35435
35686
 
35436
35687
  browsentic agent show which agent runs the side panel, and which are installed
35437
- browsentic agent <name> switch to claude, codex, antigravity, vibe, grok, cursor or qwen
35688
+ browsentic agent <name> switch to claude, codex, antigravity, vibe, grok, cursor, qwen or opencode
35438
35689
  browsentic agent fix <name> let Browsentic fix what that agent still needs
35439
35690
  browsentic agent model <name> [model] pin that agent's model, or omit it for the CLI's default
35440
35691
 
@@ -35541,12 +35792,12 @@ ${USAGE}`);
35541
35792
  async function serve() {
35542
35793
  const lock = await ensureDaemon();
35543
35794
  const bridge = await RemoteBridge.connect(lock.port, lock.token, process.env.BROWSENTIC_AGENT_RUN);
35544
- const server = createMcpServer(bridge, package_default.version, { agentRun: !!process.env.BROWSENTIC_AGENT_RUN });
35545
- await server.connect(new StdioServerTransport());
35795
+ const server2 = createMcpServer(bridge, package_default.version, { agentRun: !!process.env.BROWSENTIC_AGENT_RUN });
35796
+ await server2.connect(new StdioServerTransport());
35546
35797
  log(`stdio MCP server attached to daemon on port ${lock.port}`);
35547
35798
  const shutdown = async () => {
35548
35799
  await bridge.close();
35549
- await server.close();
35800
+ await server2.close();
35550
35801
  process.exit(0);
35551
35802
  };
35552
35803
  for (const signal of ["SIGINT", "SIGTERM"]) process.on(signal, () => void shutdown());
@@ -35565,13 +35816,13 @@ function printTools() {
35565
35816
  function printSkills() {
35566
35817
  const skills = loadSkills();
35567
35818
  if (wantsJson) {
35568
- const config5 = readAgentConfig();
35819
+ const config6 = readAgentConfig();
35569
35820
  const listed = skills.map(({ body: _body, ...skill }) => ({
35570
35821
  ...skill,
35571
- path: skill.provenance === "generated" ? join22(uploadedSkillsDir(), skill.name) : void 0
35822
+ path: skill.provenance === "generated" ? join23(uploadedSkillsDir(), skill.name) : void 0
35572
35823
  }));
35573
- const own2 = agentSkills(config5).map(({ name, description }) => ({ name, description }));
35574
- console.log(JSON.stringify({ skills: listed, dirs: skillDirNames(), agent: config5.agent, agentSkills: own2 }, null, 2));
35824
+ const own2 = agentSkills(config6).map(({ name, description }) => ({ name, description }));
35825
+ console.log(JSON.stringify({ skills: listed, dirs: skillDirNames(), agent: config6.agent, agentSkills: own2 }, null, 2));
35575
35826
  return;
35576
35827
  }
35577
35828
  if (!skills.length) {
@@ -35589,15 +35840,15 @@ function printSkills() {
35589
35840
  ].filter(Boolean);
35590
35841
  console.log(`${skill.name} (${tags.join(" \xB7 ")})`);
35591
35842
  if (skill.description) console.log(` ${skill.description}`);
35592
- if (skill.provenance === "generated") console.log(` ${join22(uploadedSkillsDir(), skill.name)}/`);
35843
+ if (skill.provenance === "generated") console.log(` ${join23(uploadedSkillsDir(), skill.name)}/`);
35593
35844
  }
35594
35845
  console.log(`
35595
35846
  Read in order: ${skillDirNames().join(" \u2192 ")} (a later one shadows an earlier one by name)`);
35596
- const config4 = readAgentConfig();
35597
- const own = agentSkills(config4);
35847
+ const config5 = readAgentConfig();
35848
+ const own = agentSkills(config5);
35598
35849
  if (own.length) {
35599
35850
  console.log(`
35600
- ${AGENTS[config4.agent].label}'s own skills (attachable from the panel's / picker):`);
35851
+ ${AGENTS[config5.agent].label}'s own skills (attachable from the panel's / picker):`);
35601
35852
  for (const skill of own) {
35602
35853
  console.log(`${skill.name}`);
35603
35854
  if (skill.description) console.log(` ${skill.description}`);
@@ -35659,7 +35910,7 @@ async function restart() {
35659
35910
  }
35660
35911
  function showLogs() {
35661
35912
  try {
35662
- process.stdout.write(readFileSync12(logPath, "utf8"));
35913
+ process.stdout.write(readFileSync13(logPath, "utf8"));
35663
35914
  } catch {
35664
35915
  console.log(`No log at ${logPath} yet.`);
35665
35916
  }