@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.
- package/dist/cli.js +144 -23
- 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: {
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
6932
|
+
return result;
|
|
6903
6933
|
} catch (error) {
|
|
6904
6934
|
if (error instanceof MemoryError) server.current = null;
|
|
6905
|
-
|
|
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: {
|
|
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
|
-
|
|
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)
|
|
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
|
|