@browserbridge/bbx 1.0.0 → 1.0.1

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 (52) hide show
  1. package/README.md +3 -1
  2. package/docs/api-reference.md +33 -33
  3. package/docs/mcp-vs-cli.md +104 -104
  4. package/docs/publishing.md +1 -3
  5. package/docs/quickstart.md +6 -6
  6. package/docs/unpacked-extension.md +72 -0
  7. package/manifest.json +3 -17
  8. package/package.json +44 -42
  9. package/packages/agent-client/src/cli-helpers.js +10 -5
  10. package/packages/agent-client/src/cli.js +65 -135
  11. package/packages/agent-client/src/client.js +37 -17
  12. package/packages/agent-client/src/command-registry.js +101 -69
  13. package/packages/agent-client/src/detect.js +3 -6
  14. package/packages/agent-client/src/install.js +10 -27
  15. package/packages/agent-client/src/mcp-config.js +11 -30
  16. package/packages/agent-client/src/runtime.js +41 -20
  17. package/packages/agent-client/src/setup-status.js +13 -28
  18. package/packages/extension/src/background-helpers.js +51 -36
  19. package/packages/extension/src/background-routing.js +11 -13
  20. package/packages/extension/src/background.js +562 -299
  21. package/packages/extension/src/content-script-helpers.js +17 -16
  22. package/packages/extension/src/content-script.js +175 -109
  23. package/packages/extension/src/sidepanel-helpers.js +3 -1
  24. package/packages/extension/ui/popup.js +39 -20
  25. package/packages/extension/ui/sidepanel.js +108 -191
  26. package/packages/extension/ui/ui.css +2 -1
  27. package/packages/mcp-server/src/handlers.js +546 -250
  28. package/packages/mcp-server/src/server.js +558 -257
  29. package/packages/native-host/bin/bridge-daemon.js +6 -2
  30. package/packages/native-host/bin/install-manifest.js +2 -2
  31. package/packages/native-host/bin/postinstall.js +4 -2
  32. package/packages/native-host/src/config.js +11 -7
  33. package/packages/native-host/src/daemon.js +143 -92
  34. package/packages/native-host/src/install-manifest.js +73 -22
  35. package/packages/native-host/src/native-host.js +55 -40
  36. package/packages/protocol/src/budget.js +3 -7
  37. package/packages/protocol/src/capabilities.js +3 -3
  38. package/packages/protocol/src/errors.js +11 -11
  39. package/packages/protocol/src/protocol.js +104 -71
  40. package/packages/protocol/src/registry.js +300 -45
  41. package/packages/protocol/src/summary.js +249 -106
  42. package/packages/protocol/src/types.js +1 -1
  43. package/skills/browser-bridge/SKILL.md +1 -1
  44. package/skills/browser-bridge/agents/openai.yaml +3 -3
  45. package/skills/browser-bridge/references/interaction.md +33 -11
  46. package/skills/browser-bridge/references/patch-workflow.md +3 -0
  47. package/skills/browser-bridge/references/protocol.md +125 -70
  48. package/skills/browser-bridge/references/tailwind.md +12 -11
  49. package/skills/browser-bridge/references/token-efficiency.md +23 -22
  50. package/skills/browser-bridge/references/ui-workflows.md +8 -0
  51. package/packages/extension/ui/offscreen.html +0 -6
  52. package/packages/extension/ui/offscreen.js +0 -61
@@ -148,57 +148,32 @@ const SETUP_MATRIX_ORDER = /** @type {const} */ ([
148
148
  'agents',
149
149
  ]);
150
150
  /** @type {Map<string, number>} */
151
- const SETUP_MATRIX_RANK = new Map(
152
- SETUP_MATRIX_ORDER.map((key, index) => [key, index]),
153
- );
154
- const SETUP_MATRIX_BETA_KEYS = new Set([
155
- 'antigravity',
156
- 'windsurf',
157
- 'agents',
158
- ]);
159
-
160
- const nativeIndicator = /** @type {HTMLSpanElement} */ (
161
- document.getElementById('native-indicator')
162
- );
163
- const toggleButton = /** @type {HTMLButtonElement} */ (
164
- document.getElementById('bridge-toggle')
165
- );
166
- const actionLog = /** @type {HTMLDivElement} */ (
167
- document.getElementById('action-log')
168
- );
169
- const setupSection = /** @type {HTMLElement} */ (
170
- document.getElementById('native-setup')
171
- );
172
- const setupInstallCmd = /** @type {HTMLElement} */ (
173
- document.getElementById('setup-install-cmd')
174
- );
175
- const setupSkillCmd = /** @type {HTMLElement} */ (
176
- document.getElementById('setup-skill-cmd')
177
- );
178
- const setupMcpCmd = /** @type {HTMLElement} */ (
179
- document.getElementById('setup-mcp-cmd')
180
- );
181
- const controlSection = /** @type {HTMLElement} */ (
182
- document.getElementById('control-section')
183
- );
151
+ const SETUP_MATRIX_RANK = new Map(SETUP_MATRIX_ORDER.map((key, index) => [key, index]));
152
+ const SETUP_MATRIX_BETA_KEYS = new Set(['antigravity', 'windsurf', 'agents']);
153
+
154
+ const nativeIndicator =
155
+ /** @type {HTMLSpanElement} */ (document.getElementById('native-indicator'));
156
+ const toggleButton = /** @type {HTMLButtonElement} */ (document.getElementById('bridge-toggle'));
157
+ const actionLog = /** @type {HTMLDivElement} */ (document.getElementById('action-log'));
158
+ const setupSection = /** @type {HTMLElement} */ (document.getElementById('native-setup'));
159
+ const setupInstallCmd = /** @type {HTMLElement} */ (document.getElementById('setup-install-cmd'));
160
+ const setupSkillCmd = /** @type {HTMLElement} */ (document.getElementById('setup-skill-cmd'));
161
+ const setupMcpCmd = /** @type {HTMLElement} */ (document.getElementById('setup-mcp-cmd'));
162
+ const controlSection = /** @type {HTMLElement} */ (document.getElementById('control-section'));
184
163
  const installationSection = /** @type {HTMLDetailsElement} */ (
185
164
  document.getElementById('installation-section')
186
165
  );
187
- const setupStatusNote = /** @type {HTMLParagraphElement} */ (
188
- document.getElementById('setup-status-note')
189
- );
166
+ const setupStatusNote =
167
+ /** @type {HTMLParagraphElement} */ (document.getElementById('setup-status-note'));
190
168
  const setupStatusSummaryNote = /** @type {HTMLSpanElement} */ (
191
169
  document.getElementById('setup-status-summary-note')
192
170
  );
193
171
  const setupStatusMatrix = /** @type {HTMLDivElement} */ (
194
172
  document.getElementById('setup-status-matrix')
195
173
  );
196
- const activitySection = /** @type {HTMLElement} */ (
197
- document.getElementById('activity-section')
198
- );
199
- const activityHistogram = /** @type {HTMLDivElement} */ (
200
- document.getElementById('activity-histogram')
201
- );
174
+ const activitySection = /** @type {HTMLElement} */ (document.getElementById('activity-section'));
175
+ const activityHistogram =
176
+ /** @type {HTMLDivElement} */ (document.getElementById('activity-histogram'));
202
177
  const activityHistogramBars = /** @type {HTMLDivElement} */ (
203
178
  document.getElementById('activity-histogram-bars')
204
179
  );
@@ -208,21 +183,15 @@ const activityHistogramRange = /** @type {HTMLSpanElement} */ (
208
183
  const activitySummaryTokens = /** @type {HTMLSpanElement} */ (
209
184
  document.getElementById('activity-summary-tokens')
210
185
  );
211
- const agentStatus = /** @type {HTMLDivElement} */ (
212
- document.getElementById('agent-status')
213
- );
186
+ const agentStatus = /** @type {HTMLDivElement} */ (document.getElementById('agent-status'));
214
187
  const agentStatusDetail = /** @type {HTMLParagraphElement} */ (
215
188
  document.getElementById('agent-status-detail')
216
189
  );
217
- const agentDisclosure = /** @type {HTMLParagraphElement} */ (
218
- document.getElementById('agent-disclosure')
219
- );
220
- const examplesSection = /** @type {HTMLDetailsElement} */ (
221
- document.getElementById('examples-section')
222
- );
223
- const examplesContent = /** @type {HTMLDivElement} */ (
224
- document.getElementById('examples-content')
225
- );
190
+ const agentDisclosure =
191
+ /** @type {HTMLParagraphElement} */ (document.getElementById('agent-disclosure'));
192
+ const examplesSection =
193
+ /** @type {HTMLDetailsElement} */ (document.getElementById('examples-section'));
194
+ const examplesContent = /** @type {HTMLDivElement} */ (document.getElementById('examples-content'));
226
195
  /** @type {SidePanelCurrentTab | null} */
227
196
  let currentTabState = null;
228
197
  /** @type {ActionLogEntry[]} */
@@ -265,7 +234,7 @@ const MCP_PROMPT_EXAMPLES = Object.freeze([
265
234
  const ACTIVITY_HISTOGRAM_WINDOW_MS = 10 * 60 * 1000;
266
235
  const ACTIVITY_HISTOGRAM_BUCKET_MS = 30 * 1000;
267
236
  const ACTIVITY_HISTOGRAM_BARS = Math.floor(
268
- ACTIVITY_HISTOGRAM_WINDOW_MS / ACTIVITY_HISTOGRAM_BUCKET_MS,
237
+ ACTIVITY_HISTOGRAM_WINDOW_MS / ACTIVITY_HISTOGRAM_BUCKET_MS
269
238
  );
270
239
  const ACTIVITY_HISTOGRAM_TICK_MS = 5 * 1000;
271
240
  const HISTOGRAM_METHOD_FAMILIES = /** @type {const} */ ([
@@ -303,8 +272,7 @@ function copySetupText(target, text) {
303
272
  .then(() => {
304
273
  target.classList.add('copied');
305
274
  const copyButton = target.querySelector('.example-copy-button');
306
- const resetLabel =
307
- copyButton instanceof HTMLButtonElement ? copyButton.textContent : null;
275
+ const resetLabel = copyButton instanceof HTMLButtonElement ? copyButton.textContent : null;
308
276
  if (copyButton instanceof HTMLButtonElement) {
309
277
  copyButton.textContent = '✓';
310
278
  }
@@ -350,38 +318,16 @@ function readRequestedTabId() {
350
318
  /** @type {number | null} */
351
319
  const requestedTabId = readRequestedTabId();
352
320
 
353
- /**
354
- * @returns {Promise<number | null>}
355
- */
356
- async function resolveInitialScopeTabId() {
357
- if (requestedTabId != null) {
358
- return requestedTabId;
359
- }
360
-
361
- try {
362
- const [activeTab] = await chrome.tabs.query({ active: true, currentWindow: true });
363
- return typeof activeTab?.id === 'number' ? activeTab.id : null;
364
- } catch {
365
- return null;
366
- }
367
- }
368
-
369
321
  /**
370
322
  * @returns {Promise<void>}
371
323
  */
372
324
  async function connectSidepanelPort() {
373
- const initialScopeTabId = await resolveInitialScopeTabId();
374
325
  const nextPort = chrome.runtime.connect({ name: 'ui-sidepanel' });
375
326
  nextPort.onMessage.addListener(handlePortMessage);
376
327
  nextPort.onDisconnect.addListener(() => {
377
328
  setTimeout(connectSidepanelPort, 500);
378
329
  });
379
- nextPort.postMessage({
380
- type: 'state.request',
381
- scopeTabId: initialScopeTabId != null && initialScopeTabId > 0
382
- ? initialScopeTabId
383
- : undefined,
384
- });
330
+ nextPort.postMessage({ type: 'state.request' });
385
331
  port = nextPort;
386
332
  }
387
333
 
@@ -414,9 +360,6 @@ toggleButton.addEventListener('click', () => {
414
360
 
415
361
  port.postMessage({
416
362
  type: 'scope.set_enabled',
417
- tabId: requestedTabId != null && requestedTabId > 0
418
- ? requestedTabId
419
- : undefined,
420
363
  enabled: pendingEnabled,
421
364
  });
422
365
  });
@@ -438,9 +381,7 @@ setupStatusMatrix.addEventListener('contextmenu', (event) => {
438
381
  if (!(target instanceof HTMLElement)) {
439
382
  return;
440
383
  }
441
- const actionTarget = target.closest(
442
- '[data-context-kind][data-context-target]',
443
- );
384
+ const actionTarget = target.closest('[data-context-kind][data-context-target]');
444
385
  if (!(actionTarget instanceof HTMLElement)) {
445
386
  hideSetupContextMenu();
446
387
  return;
@@ -452,12 +393,7 @@ setupStatusMatrix.addEventListener('contextmenu', (event) => {
452
393
  const copyText = actionTarget.dataset.contextCopyText;
453
394
  const reinstallLabel = actionTarget.dataset.contextReinstallLabel;
454
395
  const uninstallLabel = actionTarget.dataset.contextUninstallLabel;
455
- if (
456
- (kind !== 'mcp' && kind !== 'skill') ||
457
- !targetKey ||
458
- !copyLabel ||
459
- !copyText
460
- ) {
396
+ if ((kind !== 'mcp' && kind !== 'skill') || !targetKey || !copyLabel || !copyText) {
461
397
  hideSetupContextMenu();
462
398
  return;
463
399
  }
@@ -502,13 +438,13 @@ function renderState(state) {
502
438
  state.setupStatusPending,
503
439
  state.setupStatusError,
504
440
  state.setupInstallPendingKey,
505
- state.setupInstallError,
441
+ state.setupInstallError
506
442
  );
507
443
 
508
444
  actionLog.replaceChildren(
509
445
  ...state.actionLog.map((entry, index, entries) =>
510
- renderActionLogEntry(entry, state.setupStatus, entries, index),
511
- ),
446
+ renderActionLogEntry(entry, state.setupStatus, entries, index)
447
+ )
512
448
  );
513
449
  currentActionLog = state.actionLog;
514
450
  updateActivityVisualizations();
@@ -565,15 +501,14 @@ function renderCurrentTab(currentTab) {
565
501
  toggleButton.textContent = 'Disable Window Access';
566
502
  toggleButton.disabled = false;
567
503
  toggleButton.dataset.enabled = String(currentTab.enabled);
568
- toggleErrorEl.textContent = 'This page cannot be interacted with. Switch to a normal web page to inspect and interact.';
504
+ toggleErrorEl.textContent =
505
+ 'This page cannot be interacted with. Switch to a normal web page to inspect and interact.';
569
506
  toggleErrorEl.hidden = false;
570
507
  controlSection.classList.remove('attention');
571
508
  return;
572
509
  }
573
510
 
574
- toggleButton.textContent = currentTab.enabled
575
- ? 'Disable Window Access'
576
- : 'Enable Window Access';
511
+ toggleButton.textContent = currentTab.enabled ? 'Disable Window Access' : 'Enable Window Access';
577
512
  toggleButton.disabled = !currentTab.url;
578
513
  toggleButton.dataset.enabled = String(currentTab.enabled);
579
514
  controlSection.classList.toggle('attention', currentTab.accessRequested && !currentTab.enabled);
@@ -619,7 +554,8 @@ function renderAgentStatus(state) {
619
554
 
620
555
  if (!currentTab) {
621
556
  agentStatus.textContent = 'Window access unavailable';
622
- agentStatusDetail.textContent = 'Open a normal web page in this Chrome window to enable Browser Bridge.';
557
+ agentStatusDetail.textContent =
558
+ 'Open a normal web page in this Chrome window to enable Browser Bridge.';
623
559
  agentDisclosure.hidden = false;
624
560
  return;
625
561
  }
@@ -628,25 +564,29 @@ function renderAgentStatus(state) {
628
564
 
629
565
  if (currentTab.enabled && currentTab.restricted) {
630
566
  agentStatus.textContent = 'Window access enabled';
631
- agentStatusDetail.textContent = 'This page cannot be interacted with. Switch to a normal web page to use Browser Bridge.';
567
+ agentStatusDetail.textContent =
568
+ 'This page cannot be interacted with. Switch to a normal web page to use Browser Bridge.';
632
569
  agentDisclosure.hidden = false;
633
570
  return;
634
571
  }
635
572
 
636
573
  if (currentTab.enabled) {
637
574
  agentStatus.textContent = 'Window access enabled';
638
- agentStatusDetail.textContent = 'Browser Bridge is enabled for this Chrome window. Requests default to the active tab, or can target another tab in this window explicitly.';
575
+ agentStatusDetail.textContent =
576
+ 'Browser Bridge is enabled for this Chrome window. Requests default to the active tab, or can target another tab in this window explicitly.';
639
577
  return;
640
578
  }
641
579
 
642
580
  if (currentTab.accessRequested) {
643
581
  agentStatus.textContent = 'Window access requested';
644
- agentStatusDetail.textContent = 'An agent requested access for this Chrome window. Enable it to allow page inspection and interaction.';
582
+ agentStatusDetail.textContent =
583
+ 'An agent requested access for this Chrome window. Enable it to allow page inspection and interaction.';
645
584
  return;
646
585
  }
647
586
 
648
587
  agentStatus.textContent = 'Window access';
649
- agentStatusDetail.textContent = 'Enable Browser Bridge to let your connected agent inspect and interact with pages in this Chrome window.';
588
+ agentStatusDetail.textContent =
589
+ 'Enable Browser Bridge to let your connected agent inspect and interact with pages in this Chrome window.';
650
590
  }
651
591
 
652
592
  /**
@@ -655,9 +595,7 @@ function renderAgentStatus(state) {
655
595
  * @returns {void}
656
596
  */
657
597
  function renderNativeStatus(connected, error) {
658
- const label = connected
659
- ? 'Native host connected'
660
- : error || 'Native host disconnected';
598
+ const label = connected ? 'Native host connected' : error || 'Native host disconnected';
661
599
  nativeIndicator.dataset.connected = String(connected);
662
600
  nativeIndicator.title = label;
663
601
  nativeIndicator.setAttribute('aria-label', label);
@@ -684,7 +622,9 @@ function renderNativeStatus(connected, error) {
684
622
  } else if (!nativeDiagnosticTimer) {
685
623
  nativeDiagnosticTimer = setTimeout(() => {
686
624
  nativeDiagnosticTimer = null;
687
- showSidepanelDiagnostic(`Native host unreachable for 10s. Run: npm install -g @browserbridge/bbx && ${setupInstallCmd.textContent || 'bbx install'}`);
625
+ showSidepanelDiagnostic(
626
+ `Native host unreachable for 10s. Run: npm install -g @browserbridge/bbx && ${setupInstallCmd.textContent || 'bbx install'}`
627
+ );
688
628
  }, NATIVE_DIAGNOSTIC_DELAY_MS);
689
629
  }
690
630
  }
@@ -698,7 +638,8 @@ function showSidepanelDiagnostic(message) {
698
638
  if (!el) {
699
639
  el = document.createElement('div');
700
640
  el.id = 'native-diagnostic';
701
- el.style.cssText = 'padding:8px 12px;margin:8px 0;background:var(--status-badge-bg,#fef3cd);color:var(--text-primary,#856404);border-radius:6px;font-size:12px;line-height:1.4';
641
+ el.style.cssText =
642
+ 'padding:8px 12px;margin:8px 0;background:var(--status-badge-bg,#fef3cd);color:var(--text-primary,#856404);border-radius:6px;font-size:12px;line-height:1.4';
702
643
  setupSection.after(el);
703
644
  }
704
645
  el.textContent = message;
@@ -788,7 +729,7 @@ function renderPromptExamples(setupStatus) {
788
729
 
789
730
  examplesContent.replaceChildren(
790
731
  createExamplesGroup('CLI skill', CLI_PROMPT_EXAMPLES),
791
- createExamplesGroup('MCP', MCP_PROMPT_EXAMPLES),
732
+ createExamplesGroup('MCP', MCP_PROMPT_EXAMPLES)
792
733
  );
793
734
  }
794
735
 
@@ -868,40 +809,27 @@ function syncExclusiveDetailsSections(source, other) {
868
809
  * @param {string | null} installError
869
810
  * @returns {void}
870
811
  */
871
- function renderSetupStatus(
872
- setupStatus,
873
- pending,
874
- error,
875
- installPendingKey,
876
- installError,
877
- ) {
812
+ function renderSetupStatus(setupStatus, pending, error, installPendingKey, installError) {
878
813
  if (!setupStatus && pending) {
879
814
  setupStatusSummaryNote.hidden = true;
880
815
  setupStatusSummaryNote.textContent = '';
881
816
  setupStatusNote.textContent = 'Checking global host setup…';
882
817
  setupStatusNote.hidden = false;
883
- setupStatusMatrix.replaceChildren(
884
- createStatusPlaceholder('Checking detected clients…'),
885
- );
818
+ setupStatusMatrix.replaceChildren(createStatusPlaceholder('Checking detected clients…'));
886
819
  return;
887
820
  }
888
821
 
889
822
  if (!setupStatus) {
890
823
  setupStatusSummaryNote.hidden = true;
891
824
  setupStatusSummaryNote.textContent = '';
892
- setupStatusNote.textContent =
893
- installError || error || 'Global host setup is unavailable.';
825
+ setupStatusNote.textContent = installError || error || 'Global host setup is unavailable.';
894
826
  setupStatusNote.hidden = false;
895
- setupStatusMatrix.replaceChildren(
896
- createStatusPlaceholder('No host setup status yet.'),
897
- );
827
+ setupStatusMatrix.replaceChildren(createStatusPlaceholder('No host setup status yet.'));
898
828
  return;
899
829
  }
900
830
 
901
831
  const scopeNote =
902
- setupStatus.scope === 'global'
903
- ? 'Global installs only'
904
- : 'Project installs only';
832
+ setupStatus.scope === 'global' ? 'Global installs only' : 'Project installs only';
905
833
  setupStatusSummaryNote.textContent = `* ${scopeNote}`;
906
834
  setupStatusSummaryNote.hidden = false;
907
835
 
@@ -926,7 +854,7 @@ function renderSetupStatus(
926
854
  const rows = buildSetupMatrixRows(setupStatus);
927
855
  if (!rows.length) {
928
856
  setupStatusMatrix.replaceChildren(
929
- createStatusPlaceholder('No supported clients or agents were detected.'),
857
+ createStatusPlaceholder('No supported clients or agents were detected.')
930
858
  );
931
859
  return;
932
860
  }
@@ -1018,11 +946,7 @@ function shouldRenderMcpClientRow(entry) {
1018
946
  * @returns {boolean}
1019
947
  */
1020
948
  function shouldRenderSkillTargetRow(entry) {
1021
- return (
1022
- entry.detected ||
1023
- entry.installed ||
1024
- entry.skills.some((skill) => skill.exists)
1025
- );
949
+ return entry.detected || entry.installed || entry.skills.some((skill) => skill.exists);
1026
950
  }
1027
951
 
1028
952
  /**
@@ -1098,7 +1022,7 @@ function renderMcpMatrixCell(row, installPendingKey) {
1098
1022
  installPendingKey === getInstallKey('mcp', row.key),
1099
1023
  'install',
1100
1024
  'Install',
1101
- '...',
1025
+ '...'
1102
1026
  );
1103
1027
  button.title = 'Install Browser Bridge MCP for generic agents';
1104
1028
  return button;
@@ -1121,7 +1045,7 @@ function renderMcpMatrixCell(row, installPendingKey) {
1121
1045
  installPendingKey === getInstallKey('mcp', row.key),
1122
1046
  'install',
1123
1047
  'Install',
1124
- '...',
1048
+ '...'
1125
1049
  );
1126
1050
  button.title = entry.configPath;
1127
1051
  return button;
@@ -1140,9 +1064,7 @@ function renderSkillMatrixCell(row, installPendingKey) {
1140
1064
  const reinstallLabel = getSkillReinstallLabel(entry);
1141
1065
  const uninstallLabel = getSkillUninstallLabel(entry);
1142
1066
 
1143
- const installable = entry.skills.every(
1144
- (skill) => !skill.exists || skill.managed,
1145
- );
1067
+ const installable = entry.skills.every((skill) => !skill.exists || skill.managed);
1146
1068
  if (!installable) {
1147
1069
  return createMatrixBadge('Custom', false, entry.basePath, {
1148
1070
  kind: 'skill',
@@ -1168,7 +1090,7 @@ function renderSkillMatrixCell(row, installPendingKey) {
1168
1090
  installPendingKey === getInstallKey('skill', row.key),
1169
1091
  'update',
1170
1092
  'Update',
1171
- 'Updating…',
1093
+ 'Updating…'
1172
1094
  );
1173
1095
  button.title = createSkillCellTitle(entry);
1174
1096
  assignSetupContext(button, {
@@ -1188,7 +1110,7 @@ function renderSkillMatrixCell(row, installPendingKey) {
1188
1110
  installPendingKey === getInstallKey('skill', row.key),
1189
1111
  'install',
1190
1112
  'Install',
1191
- '...',
1113
+ '...'
1192
1114
  );
1193
1115
  button.title = entry.basePath;
1194
1116
  return button;
@@ -1203,14 +1125,7 @@ function renderSkillMatrixCell(row, installPendingKey) {
1203
1125
  * @param {string} pendingLabel
1204
1126
  * @returns {HTMLButtonElement}
1205
1127
  */
1206
- function createSetupActionButton(
1207
- kind,
1208
- target,
1209
- pending,
1210
- variant,
1211
- label,
1212
- pendingLabel,
1213
- ) {
1128
+ function createSetupActionButton(kind, target, pending, variant, label, pendingLabel) {
1214
1129
  const button = document.createElement('button');
1215
1130
  button.type = 'button';
1216
1131
  button.className = 'setup-install-button';
@@ -1349,7 +1264,7 @@ function showSetupContextMenu(clientX, clientY, contextAction) {
1349
1264
  });
1350
1265
  hideSetupContextMenu();
1351
1266
  },
1352
- { once: true },
1267
+ { once: true }
1353
1268
  );
1354
1269
  setupContextMenu.append(copyButton);
1355
1270
 
@@ -1369,7 +1284,7 @@ function showSetupContextMenu(clientX, clientY, contextAction) {
1369
1284
  });
1370
1285
  hideSetupContextMenu();
1371
1286
  },
1372
- { once: true },
1287
+ { once: true }
1373
1288
  );
1374
1289
  setupContextMenu.append(reinstallButton);
1375
1290
  }
@@ -1390,7 +1305,7 @@ function showSetupContextMenu(clientX, clientY, contextAction) {
1390
1305
  });
1391
1306
  hideSetupContextMenu();
1392
1307
  },
1393
- { once: true },
1308
+ { once: true }
1394
1309
  );
1395
1310
  setupContextMenu.append(uninstallButton);
1396
1311
  }
@@ -1477,8 +1392,7 @@ function renderActionLogEntry(entry, setupStatus, entries, index) {
1477
1392
  const badges = document.createElement('span');
1478
1393
  badges.className = 'activity-badges';
1479
1394
 
1480
- const showScope =
1481
- !(requestedTabId != null && requestedTabId > 0) && entry.url;
1395
+ const showScope = !(requestedTabId != null && requestedTabId > 0) && entry.url;
1482
1396
  if (showScope) {
1483
1397
  const scopeLink = document.createElement('a');
1484
1398
  scopeLink.className = 'activity-scope-link';
@@ -1632,7 +1546,7 @@ function alignHistogramEndAt(value) {
1632
1546
  function buildActivityHistogram(entries) {
1633
1547
  const latestAt = entries.reduce(
1634
1548
  (maxAt, entry) => Math.max(maxAt, Number.isFinite(entry.at) ? entry.at : 0),
1635
- 0,
1549
+ 0
1636
1550
  );
1637
1551
  const endAt = alignHistogramEndAt(Math.max(Date.now(), latestAt));
1638
1552
  const startAt = endAt - ACTIVITY_HISTOGRAM_WINDOW_MS;
@@ -1643,9 +1557,7 @@ function buildActivityHistogram(entries) {
1643
1557
  }));
1644
1558
 
1645
1559
  /** @type {Map<number, Map<string, number>>} */
1646
- const bucketFamilies = new Map(
1647
- buckets.map((_, index) => [index, new Map()]),
1648
- );
1560
+ const bucketFamilies = new Map(buckets.map((_, index) => [index, new Map()]));
1649
1561
  let totalTokens = 0;
1650
1562
 
1651
1563
  for (const entry of entries) {
@@ -1659,7 +1571,7 @@ function buildActivityHistogram(entries) {
1659
1571
 
1660
1572
  const offset = Math.min(
1661
1573
  ACTIVITY_HISTOGRAM_BARS - 1,
1662
- Math.max(0, Math.floor((entry.at - startAt) / ACTIVITY_HISTOGRAM_BUCKET_MS)),
1574
+ Math.max(0, Math.floor((entry.at - startAt) / ACTIVITY_HISTOGRAM_BUCKET_MS))
1663
1575
  );
1664
1576
  const family = getHistogramMethodFamily(entry.method);
1665
1577
  const familyTotals = /** @type {Map<string, number>} */ (bucketFamilies.get(offset));
@@ -1670,12 +1582,13 @@ function buildActivityHistogram(entries) {
1670
1582
 
1671
1583
  for (let index = 0; index < buckets.length; index += 1) {
1672
1584
  const familyTotals = /** @type {Map<string, number>} */ (bucketFamilies.get(index));
1673
- buckets[index].segments = /** @type {typeof buckets[number]['segments']} */ (HISTOGRAM_METHOD_FAMILIES
1674
- .map((family) => ({
1675
- family,
1676
- tokens: familyTotals.get(family) ?? 0,
1677
- }))
1678
- .filter((segment) => segment.tokens > 0));
1585
+ buckets[index].segments =
1586
+ /** @type {typeof buckets[number]['segments']} */ (
1587
+ HISTOGRAM_METHOD_FAMILIES.map((family) => ({
1588
+ family,
1589
+ tokens: familyTotals.get(family) ?? 0,
1590
+ })).filter((segment) => segment.tokens > 0)
1591
+ );
1679
1592
  }
1680
1593
 
1681
1594
  return {
@@ -1703,12 +1616,7 @@ function renderActivityHistogram(histogram) {
1703
1616
  activityHistogram.hidden = false;
1704
1617
  activityHistogramRange.textContent = '10m window · 30s bins';
1705
1618
  activityHistogramBars.replaceChildren(
1706
- ...histogram.buckets.map((bucket) =>
1707
- createHistogramBar(
1708
- bucket,
1709
- maxTokens,
1710
- )
1711
- ),
1619
+ ...histogram.buckets.map((bucket) => createHistogramBar(bucket, maxTokens))
1712
1620
  );
1713
1621
  }
1714
1622
 
@@ -1718,9 +1626,7 @@ function renderActivityHistogram(histogram) {
1718
1626
  */
1719
1627
  function formatCompactCount(value) {
1720
1628
  if (value >= 1000) {
1721
- const compact = value >= 10000
1722
- ? Math.round(value / 1000)
1723
- : Math.round(value / 100) / 10;
1629
+ const compact = value >= 10000 ? Math.round(value / 1000) : Math.round(value / 100) / 10;
1724
1630
  return `${compact}k`;
1725
1631
  }
1726
1632
  return String(value);
@@ -1751,9 +1657,7 @@ function getAggregateCostClass(approxTokens) {
1751
1657
  function createHistogramBar(bucket, maxTokens) {
1752
1658
  const bar = document.createElement('span');
1753
1659
  const ratio = Math.log1p(bucket.totalTokens) / Math.log1p(maxTokens);
1754
- const height = bucket.totalTokens > 0
1755
- ? Math.max(14, Math.round(ratio * 100))
1756
- : 6;
1660
+ const height = bucket.totalTokens > 0 ? Math.max(14, Math.round(ratio * 100)) : 6;
1757
1661
  bar.className = 'activity-histogram-bar';
1758
1662
  bar.style.height = `${height}%`;
1759
1663
 
@@ -1762,21 +1666,21 @@ function createHistogramBar(bucket, maxTokens) {
1762
1666
  bar.title = `${new Date(bucket.bucketStart).toLocaleTimeString()} · no activity`;
1763
1667
  bar.setAttribute(
1764
1668
  'aria-label',
1765
- `${new Date(bucket.bucketStart).toLocaleTimeString()}, no token activity`,
1669
+ `${new Date(bucket.bucketStart).toLocaleTimeString()}, no token activity`
1766
1670
  );
1767
1671
  return bar;
1768
1672
  }
1769
1673
 
1770
1674
  const tooltipParts = bucket.segments.map(
1771
- (segment) => `${segment.family}: ${segment.tokens.toLocaleString()} tok`,
1675
+ (segment) => `${segment.family}: ${segment.tokens.toLocaleString()} tok`
1772
1676
  );
1773
1677
  bar.title = `${new Date(bucket.bucketStart).toLocaleTimeString()} · ${bucket.totalTokens.toLocaleString()} tok\n${tooltipParts.join('\n')}`;
1774
1678
  bar.setAttribute(
1775
1679
  'aria-label',
1776
- `${new Date(bucket.bucketStart).toLocaleTimeString()}, approximately ${bucket.totalTokens.toLocaleString()} tokens`,
1680
+ `${new Date(bucket.bucketStart).toLocaleTimeString()}, approximately ${bucket.totalTokens.toLocaleString()} tokens`
1777
1681
  );
1778
1682
  bar.append(
1779
- ...bucket.segments.map((segment) => createHistogramSegment(segment, bucket.totalTokens)),
1683
+ ...bucket.segments.map((segment) => createHistogramSegment(segment, bucket.totalTokens))
1780
1684
  );
1781
1685
  return bar;
1782
1686
  }
@@ -1811,13 +1715,21 @@ function getHistogramMethodFamily(method) {
1811
1715
  if (method.startsWith('styles.')) {
1812
1716
  return 'style';
1813
1717
  }
1814
- if (method.startsWith('input.') || method.startsWith('navigation.') || method.startsWith('viewport.')) {
1718
+ if (
1719
+ method.startsWith('input.') ||
1720
+ method.startsWith('navigation.') ||
1721
+ method.startsWith('viewport.')
1722
+ ) {
1815
1723
  return 'input';
1816
1724
  }
1817
1725
  if (method.startsWith('patch.')) {
1818
1726
  return 'patch';
1819
1727
  }
1820
- if (method.startsWith('screenshot.') || method.startsWith('cdp.') || method.startsWith('performance.')) {
1728
+ if (
1729
+ method.startsWith('screenshot.') ||
1730
+ method.startsWith('cdp.') ||
1731
+ method.startsWith('performance.')
1732
+ ) {
1821
1733
  return 'capture';
1822
1734
  }
1823
1735
  return 'other';
@@ -1830,10 +1742,12 @@ function getHistogramMethodFamily(method) {
1830
1742
  */
1831
1743
  function countRecentExpensiveRepeats(entries, index) {
1832
1744
  const current = entries[index];
1833
- const currentCostClass = current?.summaryTokens > 0
1834
- ? current.summaryCostClass
1835
- : current?.costClass;
1836
- if (!current || (!current.debuggerBacked && currentCostClass !== 'heavy' && currentCostClass !== 'extreme')) {
1745
+ const currentCostClass =
1746
+ current?.summaryTokens > 0 ? current.summaryCostClass : current?.costClass;
1747
+ if (
1748
+ !current ||
1749
+ (!current.debuggerBacked && currentCostClass !== 'heavy' && currentCostClass !== 'extreme')
1750
+ ) {
1837
1751
  return 0;
1838
1752
  }
1839
1753
 
@@ -1843,10 +1757,13 @@ function countRecentExpensiveRepeats(entries, index) {
1843
1757
  if (!candidate || candidate.method !== current.method) {
1844
1758
  break;
1845
1759
  }
1846
- const candidateCostClass = candidate.summaryTokens > 0
1847
- ? candidate.summaryCostClass
1848
- : candidate.costClass;
1849
- if (candidate.debuggerBacked || candidateCostClass === 'heavy' || candidateCostClass === 'extreme') {
1760
+ const candidateCostClass =
1761
+ candidate.summaryTokens > 0 ? candidate.summaryCostClass : candidate.costClass;
1762
+ if (
1763
+ candidate.debuggerBacked ||
1764
+ candidateCostClass === 'heavy' ||
1765
+ candidateCostClass === 'extreme'
1766
+ ) {
1850
1767
  count += 1;
1851
1768
  }
1852
1769
  }
@@ -125,7 +125,8 @@ body[data-windowed="true"] .panel-popup {
125
125
 
126
126
  .panel-wide {
127
127
  width: 100%;
128
- max-width: 440px;
128
+ max-width: none;
129
+ flex: 1 1 auto;
129
130
  }
130
131
 
131
132
  .panel section + section {