@seybel/breakhound 1.0.7 → 1.0.9

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.
Files changed (2) hide show
  1. package/dist/cli.js +144 -23
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -369,6 +369,7 @@ var NetworkTracker = class {
369
369
  }
370
370
  };
371
371
  var RESIZE_PROBE = `(() => {
372
+ if (window.__breakhound_settle) return;
372
373
  const MAX_DEPTH = 3;
373
374
  const MAX_DELAY = 2000;
374
375
  let depth = 0;
@@ -411,10 +412,11 @@ var RESIZE_PROBE = `(() => {
411
412
  };
412
413
 
413
414
  const wrappers = new WeakMap();
415
+ const wrapped = new WeakSet();
414
416
  const wrap = (listener) => {
415
417
  if (!listener) return listener;
416
418
  let w = wrappers.get(listener);
417
- if (!w) { w = tracked(listener, 1); wrappers.set(listener, w); }
419
+ if (!w) { w = tracked(listener, 1); wrappers.set(listener, w); wrapped.add(w); }
418
420
  return w;
419
421
  };
420
422
 
@@ -449,8 +451,25 @@ var RESIZE_PROBE = `(() => {
449
451
  };
450
452
  }
451
453
 
454
+ // A window resize listener added before this script: taken off and added again, wrapped. One set
455
+ // as window.onresize is wrapped where it is.
456
+ const adopt = (listener, capture, passive, once) => {
457
+ // The browser lists a listener added after this script by its wrapper.
458
+ if (!listener || wrappers.has(listener) || wrapped.has(listener)) return;
459
+ if (listener === window.onresize) {
460
+ window.onresize = wrap(listener);
461
+ return;
462
+ }
463
+ remove.call(window, 'resize', listener, capture);
464
+ window.addEventListener('resize', listener, { capture, passive, once });
465
+ };
466
+
452
467
  Object.defineProperty(window, '__breakhound_settle', {
453
- value: { pending: () => pending, listeners: () => listeners + (window.onresize ? 1 : 0) },
468
+ value: {
469
+ pending: () => pending,
470
+ listeners: () => listeners + (window.onresize ? 1 : 0),
471
+ adopt,
472
+ },
454
473
  });
455
474
  })();`;
456
475
  var FREEZE_CSS = `
@@ -6401,7 +6420,7 @@ async function startBridge(opts) {
6401
6420
  case "/breakhound/checks/progress": {
6402
6421
  const check = checks.get(String(body.id));
6403
6422
  if (check && typeof body.done === "number" && typeof body.message === "string") {
6404
- check.onProgress?.(body.done, body.message.slice(0, 200));
6423
+ check.onProgress?.(body.done, body.message.slice(0, 400));
6405
6424
  }
6406
6425
  return reply({});
6407
6426
  }
@@ -6507,6 +6526,9 @@ async function startBridge(opts) {
6507
6526
  };
6508
6527
  }
6509
6528
 
6529
+ // src/icon.ts
6530
+ var ICON_PNG = "data:image/png;base64,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";
6531
+
6510
6532
  // src/mcp.ts
6511
6533
  var PROTOCOL_VERSIONS = ["2025-11-25", "2025-06-18", "2025-03-26", "2024-11-05"];
6512
6534
  var INSTRUCTIONS = `breakhound finds responsive layout bugs by resizing pages in a real browser, and tells you the exact widths where they happen.
@@ -6519,12 +6541,15 @@ How to fix issues with it:
6519
6541
  5. When verify is clean for a route, checkpoint it. That commits the fix on its own, so it can be undone later with undo.
6520
6542
  Before finishing, run verify with no routes to check every page.
6521
6543
 
6544
+ scan and verify answer within about 45 seconds. When one says it's still running, it carries on: call it again with the same arguments to get its result, rather than something else. It says what it's waiting for; if that's the developer's browser tab, ask them to bring it into view.
6545
+
6522
6546
  Issues sent from the extension are checked again in the developer's own browser: verify asks the open breakhound panel to scan the pages, so their login and browser carry over. If no panel is open, verify says so; ask the developer to open it rather than calling scan, which replaces their issues with a headless scan.`;
6523
6547
  var FIX_PROMPT = `Fix the responsive layout issues breakhound found in this project.
6524
6548
 
6525
6549
  Start with list_issues: if I sent them from the breakhound extension they are already there, so don't scan again. Only scan when there are none.
6526
6550
  Work through the errors first, then the warnings. For each one: get_issue for the details, fix the cause in the code (not overflow: hidden), verify the routes you touched, and checkpoint each route once verify is clean for it.
6527
6551
  Before you finish, verify with no routes to check every page, and tell me what you fixed and anything you couldn't.`;
6552
+ var REPLY_WITHIN_MS = 45e3;
6528
6553
  var text = (t, isError = false) => ({
6529
6554
  content: [{ type: "text", text: t }],
6530
6555
  ...isError ? { isError } : {}
@@ -6583,6 +6608,7 @@ function createTools() {
6583
6608
  return [
6584
6609
  {
6585
6610
  name: "scan",
6611
+ kind: "long",
6586
6612
  description: "Find the app (starting its dev server if needed), discover its routes and scan each one at every width from 320 to 1920px in a headless browser. Returns every issue with its id. The result is the baseline that verify compares with, replacing any issues the developer sent from the browser extension, so check list_issues first. Takes from seconds to a few minutes on large apps.",
6587
6613
  inputSchema: {
6588
6614
  type: "object",
@@ -6610,6 +6636,7 @@ function createTools() {
6610
6636
  },
6611
6637
  {
6612
6638
  name: "list_routes",
6639
+ kind: "read",
6613
6640
  description: "Routes from the last scan, with their status and number of issues.",
6614
6641
  inputSchema: { type: "object", properties: {} },
6615
6642
  async run(_args, ctx) {
@@ -6619,6 +6646,7 @@ function createTools() {
6619
6646
  },
6620
6647
  {
6621
6648
  name: "list_issues",
6649
+ kind: "read",
6622
6650
  description: "Issues from the last scan or verify, each listed once even when it shows up on several routes. Filter by route or severity.",
6623
6651
  inputSchema: {
6624
6652
  type: "object",
@@ -6638,6 +6666,7 @@ function createTools() {
6638
6666
  },
6639
6667
  {
6640
6668
  name: "get_issue",
6669
+ kind: "read",
6641
6670
  description: "Everything about one issue: the widths, the element and where it is in the code (when known), measurements, computed styles, a likely fix, every page it is on, and a screenshot.",
6642
6671
  inputSchema: {
6643
6672
  type: "object",
@@ -6668,6 +6697,7 @@ function createTools() {
6668
6697
  },
6669
6698
  {
6670
6699
  name: "verify",
6700
+ kind: "long",
6671
6701
  description: "Scan routes again and compare with the baseline from scan: which issues are fixed, which are still there, and which are new (a change broke something, maybe at another width or on another route). Pass the routes you changed for a quick check, and nothing to check every route before you finish.",
6672
6702
  inputSchema: {
6673
6703
  type: "object",
@@ -6842,6 +6872,8 @@ async function runMcpServer(version, io = { input: process.stdin, output: proces
6842
6872
  let clientCanListRoots = false;
6843
6873
  let root = process.env.BREAKHOUND_ROOT ?? null;
6844
6874
  let queue = Promise.resolve();
6875
+ let job = null;
6876
+ const replyWithinMs = Number(process.env.BREAKHOUND_REPLY_WITHIN_MS) || REPLY_WITHIN_MS;
6845
6877
  const send = (message) => io.output.write(`${JSON.stringify({ jsonrpc: "2.0", ...message })}
6846
6878
  `);
6847
6879
  const request = (method, params = {}) => new Promise((resolve8, reject) => {
@@ -6871,13 +6903,17 @@ async function runMcpServer(version, io = { input: process.stdin, output: proces
6871
6903
  root ??= cwd;
6872
6904
  return root;
6873
6905
  }
6874
- async function callTool(id, params) {
6875
- const tool = tools.find((t) => t.name === params.name);
6876
- if (!tool) {
6877
- send({ id, error: { code: -32602, message: `Unknown tool: ${String(params.name)}` } });
6878
- return;
6879
- }
6906
+ const progressFor = (params) => {
6880
6907
  const token = params._meta?.progressToken;
6908
+ return (done, message) => {
6909
+ if (token !== void 0)
6910
+ send({
6911
+ method: "notifications/progress",
6912
+ params: { progressToken: token, progress: done, message }
6913
+ });
6914
+ };
6915
+ };
6916
+ async function runTool(tool, params, progress) {
6881
6917
  try {
6882
6918
  const dir = await projectRoot();
6883
6919
  const { config, warnings } = await loadConfig(dir);
@@ -6888,22 +6924,93 @@ async function runMcpServer(version, io = { input: process.stdin, output: proces
6888
6924
  root: dir,
6889
6925
  config,
6890
6926
  bridge,
6891
- progress(done, message) {
6892
- if (token !== void 0)
6893
- send({
6894
- method: "notifications/progress",
6895
- params: { progressToken: token, progress: done, message }
6896
- });
6897
- }
6927
+ progress
6898
6928
  })
6899
6929
  );
6900
6930
  if (warnings.length)
6901
6931
  result.content.push({ type: "text", text: `Config warnings: ${warnings.join(" ")}` });
6902
- send({ id, result });
6932
+ return result;
6903
6933
  } catch (error) {
6904
6934
  if (error instanceof MemoryError) server.current = null;
6905
- send({ id, result: text(error instanceof Error ? error.message : String(error), true) });
6935
+ return text(error instanceof Error ? error.message : String(error), true);
6936
+ }
6937
+ }
6938
+ const running = (j) => `${j.label} has been running for ${Math.round((Date.now() - j.startedAt) / 1e3)}s${j.latest ? ` (${j.latest})` : ""}.`;
6939
+ async function collect(j, params) {
6940
+ const late = new Promise((r) => setTimeout(() => r(null), replyWithinMs).unref());
6941
+ j.waiting = progressFor(params);
6942
+ const result = await Promise.race([j.done, late]);
6943
+ j.waiting = null;
6944
+ if (result) {
6945
+ if (job === j) job = null;
6946
+ return result;
6947
+ }
6948
+ return text(
6949
+ `Still running: ${running(j)} It carries on by itself. Call ${j.name} again with the same arguments to get its result; list_issues and get_issue answer meanwhile.`
6950
+ );
6951
+ }
6952
+ async function callTool(id, params) {
6953
+ const tool = tools.find((t) => t.name === params.name);
6954
+ if (!tool) {
6955
+ send({ id, error: { code: -32602, message: `Unknown tool: ${String(params.name)}` } });
6956
+ return;
6957
+ }
6958
+ const key = `${tool.name} ${JSON.stringify(params.arguments ?? {})}`;
6959
+ const busy = job && !job.finished ? job : null;
6960
+ if (tool.kind === "read") {
6961
+ const result = await runTool(tool, params, progressFor(params));
6962
+ if (busy)
6963
+ result.content.push({
6964
+ type: "text",
6965
+ text: `Note: ${running(busy)} Call ${busy.name} again with the same arguments for its result.`
6966
+ });
6967
+ send({ id, result });
6968
+ return;
6969
+ }
6970
+ if (job?.key === key) {
6971
+ send({ id, result: await collect(job, params) });
6972
+ return;
6973
+ }
6974
+ if (busy) {
6975
+ send({
6976
+ id,
6977
+ result: text(
6978
+ `Not yet: ${running(busy)} Call ${busy.name} again with the same arguments to get its result first.`,
6979
+ true
6980
+ )
6981
+ });
6982
+ return;
6906
6983
  }
6984
+ if (tool.kind !== "long") {
6985
+ job = null;
6986
+ queue = queue.then(() => runTool(tool, params, progressFor(params)));
6987
+ send({ id, result: await queue });
6988
+ return;
6989
+ }
6990
+ const args = params.arguments ?? {};
6991
+ const routes = strings(args.routes);
6992
+ const j = {
6993
+ key,
6994
+ name: tool.name,
6995
+ label: `${tool.name}${routes.length ? ` of ${routes.join(", ")}` : ""}`,
6996
+ startedAt: Date.now(),
6997
+ latest: "",
6998
+ finished: false,
6999
+ waiting: null,
7000
+ done: Promise.resolve(text(""))
7001
+ };
7002
+ j.done = queue.then(
7003
+ () => runTool(tool, params, (done, message) => {
7004
+ j.latest = message;
7005
+ j.waiting?.(done, message);
7006
+ })
7007
+ );
7008
+ j.done.then(() => {
7009
+ j.finished = true;
7010
+ });
7011
+ queue = j.done;
7012
+ job = j;
7013
+ send({ id, result: await collect(j, params) });
6907
7014
  }
6908
7015
  function handle(message) {
6909
7016
  const { id, method } = message;
@@ -6929,7 +7036,13 @@ async function runMcpServer(version, io = { input: process.stdin, output: proces
6929
7036
  result: {
6930
7037
  protocolVersion: PROTOCOL_VERSIONS.includes(asked) ? asked : PROTOCOL_VERSIONS[0],
6931
7038
  capabilities: { tools: { listChanged: false }, prompts: { listChanged: false } },
6932
- serverInfo: { name: "breakhound", version: VERSION },
7039
+ serverInfo: {
7040
+ name: "breakhound",
7041
+ version: VERSION,
7042
+ // Shown beside the server by clients that show icons. No sizes: some clients' older
7043
+ // schemas expect another shape for it, and an icon isn't worth a failed connection.
7044
+ icons: [{ src: ICON_PNG, mimeType: "image/png" }]
7045
+ },
6933
7046
  instructions: INSTRUCTIONS
6934
7047
  }
6935
7048
  });
@@ -6977,7 +7090,7 @@ async function runMcpServer(version, io = { input: process.stdin, output: proces
6977
7090
  });
6978
7091
  return;
6979
7092
  case "tools/call":
6980
- queue = queue.then(() => callTool(id, params));
7093
+ void callTool(id, params);
6981
7094
  return;
6982
7095
  default:
6983
7096
  send({ id, error: { code: -32601, message: `Method not found: ${String(method)}` } });
@@ -7121,6 +7234,7 @@ function show(path, cwd) {
7121
7234
  const rel = relative7(cwd, path);
7122
7235
  return rel.startsWith("..") ? path.replace(homedir(), "~") : rel;
7123
7236
  }
7237
+ var NOT_SHOWING_UP = "Not showing up? Your agent runs breakhound only while this folder is open in it, and with breakhound switched on in its MCP settings (Cursor and VS Code may add it switched off), so check both. How, for each agent: https://seybel.github.io/breakhound/guide/agents#not-connected";
7124
7238
  async function installMcp(cwd, options = {}) {
7125
7239
  const home = options.home ?? homedir();
7126
7240
  const clients = options.clients?.length ? allClients(cwd, home).filter((c) => options.clients?.includes(c.id)) : detectClients(cwd, home);
@@ -7132,20 +7246,26 @@ async function installMcp(cwd, options = {}) {
7132
7246
  return 1;
7133
7247
  }
7134
7248
  const changes = [];
7249
+ let alreadySetUp = false;
7135
7250
  for (const client of clients) {
7136
7251
  const current = existsSync15(client.file) ? readFileSync18(client.file, "utf8") : null;
7137
7252
  try {
7138
7253
  const next = client.update(current);
7139
- if (next === null)
7254
+ if (next === null) {
7255
+ alreadySetUp = true;
7140
7256
  console.log(
7141
7257
  `${styleText8("green", "\u2714")} ${client.name}: already set up in ${show(client.file, cwd)}`
7142
7258
  );
7143
- else changes.push({ client, next });
7259
+ } else changes.push({ client, next });
7144
7260
  } catch (error) {
7145
7261
  console.log(`${styleText8("yellow", "!")} ${client.name}: ${error.message}`);
7146
7262
  }
7147
7263
  }
7148
- if (!changes.length) return 0;
7264
+ if (!changes.length) {
7265
+ if (alreadySetUp) console.log(`
7266
+ ${NOT_SHOWING_UP}`);
7267
+ return 0;
7268
+ }
7149
7269
  console.log("\nbreakhound will add its MCP server to:\n");
7150
7270
  const { command, args } = serverCommand();
7151
7271
  for (const { client } of changes) {
@@ -7176,6 +7296,7 @@ async function installMcp(cwd, options = {}) {
7176
7296
  `
7177
7297
  Restart your agent (or reload its MCP servers), then ask it to "fix the responsive issues breakhound finds".`
7178
7298
  );
7299
+ console.log(NOT_SHOWING_UP);
7179
7300
  return 0;
7180
7301
  }
7181
7302
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seybel/breakhound",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "description": "Find where your layout breaks across every viewport width, then hand the fix to your coding agent.",
5
5
  "keywords": [
6
6
  "responsive",